LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
CME.m
1classdef CME < ME
2 % Concentrated Matrix Exponential (CME) distribution
3 %
4 % A CME is the matrix-exponential distribution of odd order 2*n+1 whose
5 % squared coefficient of variation is (numerically) minimal for that order,
6 % from the tables of Horvath, Horvath and Telek. Its SCV decays as O(1/n^2)
7 % and therefore goes far below the Erlang bound 1/order attainable by a
8 % phase-type distribution of the same order: order 101 gives SCV 3.9e-4,
9 % where Erlang-101 gives 9.9e-3.
10 %
11 % The density of the unit-mean CME with n harmonic terms is
12 %
13 % f(x) = mu1*exp(-mu1*x)*(c + sum_k [a_k*cos(k*w*mu1*x) + b_k*sin(k*w*mu1*x)])
14 %
15 % with w = omega, which is exactly alpha*expm(A*x)*(-A*e) for the
16 % block-diagonal A = blkdiag(-mu1, mu1*[-1 -k*w; k*w -1], k=1..n). The
17 % parameters a, b, c, omega and mu1 are read from the same iltcme.json
18 % table used by the CME inverse Laplace transform (matlab_ilt).
19 %
20 % Copyright (c) 2012-2026, Imperial College London
21 % All rights reserved.
22
23 properties
24 cmeMean; % Mean of the distribution
25 cmeOrder; % Number of phases, an odd integer 2*n+1
26 end
27
28 methods
29 function self = CME(mean, order)
30 % SELF = CME(MEAN, ORDER)
31 % Create a concentrated matrix-exponential distribution
32 %
33 % @param mean Mean of the distribution (positive)
34 % @param order Number of phases, an odd integer 2*n+1 with n in the table
35 % @return self CME distribution instance
36
37 [alpha, A] = CME.representation(order);
38
39 if ~isscalar(mean) || ~isfinite(mean) || mean <= 0
40 line_error(mfilename, 'CME mean must be a positive finite number.');
41 end
42
43 % The CME density is nonnegative by construction, so the density
44 % scan of ME is skipped: it can never fire and it costs O(1e5)
45 % propagations of a (2n+1)-square matrix, prohibitive at high order.
46 self@ME(alpha, A/mean, false);
47
48 self.cmeMean = mean;
49 self.cmeOrder = double(order);
50
51 % The process type stays ME: a CME is a matrix-exponential
52 % representation, so every solver gate, sn.procid entry and JSON key
53 % that accepts ME accepts it unchanged.
54 self.obj = jline.lang.processes.CME(mean, double(order));
55
56 % mean and order stay object properties rather than distribution
57 % parameters: the parameter list of an ME is (alpha, A), and every
58 % marshaller that serializes an ME reads exactly those two, so
59 % appending to it would change getNumParams for the ME family.
60 % The tabulated minimum SCV for this order is recomputed from the
61 % representation by getSCV, so it is not stored either.
62 end
63
64 function o = getOrder(self)
65 % O = GETORDER()
66 % Get the CME order, i.e. the number of phases
67 o = self.cmeOrder;
68 end
69 end
70
71 methods(Static)
72 function params = table()
73 % PARAMS = TABLE()
74 % Load and cache the CME parameter table from iltcme.json
75 %
76 % The same table backs matlab_ilt, which caches it in the global
77 % cmeParams, so the two share one decode per session.
78
79 global cmeParams;
80 if isempty(cmeParams)
81 cmeParams = jsondecode(fileread('iltcme.json'));
82 end
83 params = cmeParams;
84 end
85
86 function entry = tableEntry(order)
87 % ENTRY = TABLEENTRY(ORDER)
88 % Select the CME table entry realizing the given number of phases
89 %
90 % The table is keyed by the number of harmonic terms n, so an order
91 % of 2*n+1 phases maps to the entries with that n. Several entries
92 % can share an n (the 'full' and 'approx' optimizations), and the
93 % most concentrated one is taken, matching the selection rule of the
94 % CME inverse Laplace transform.
95
96 order = double(order);
97 if ~isscalar(order) || order < 3 || mod(order,2) == 0 || order ~= fix(order)
98 line_error(mfilename, sprintf('CME order must be an odd integer of the form 2*n+1 with n >= 1, got %g.', order));
99 end
100 n = (order-1)/2;
101
102 params = CME.table();
103 entry = [];
104 bestcv2 = Inf;
105 for i = 1:numel(params)
106 if iscell(params)
107 cand = params{i};
108 else
109 cand = params(i);
110 end
111 if cand.n == n && cand.cv2 < bestcv2
112 entry = cand;
113 bestcv2 = cand.cv2;
114 end
115 end
116
117 if isempty(entry)
118 orders = CME.getSupportedOrders();
119 [~,j] = min(abs(orders-order));
120 line_error(mfilename, sprintf('No tabulated CME of order %d; the nearest available order is %d. Use CME.getSupportedOrders() for the full list.', order, orders(j)));
121 end
122 end
123
124 function orders = getSupportedOrders()
125 % ORDERS = GETSUPPORTEDORDERS()
126 % Get the sorted list of CME orders (phase counts) in the table
127
128 params = CME.table();
129 ns = zeros(numel(params),1);
130 for i = 1:numel(params)
131 if iscell(params)
132 ns(i) = params{i}.n;
133 else
134 ns(i) = params(i).n;
135 end
136 end
137 orders = unique(2*ns+1)';
138 end
139
140 function scv = getMinSCV(order)
141 % SCV = GETMINSCV(ORDER)
142 % Get the tabulated minimal SCV attained by a CME of the given order
143
144 entry = CME.tableEntry(order);
145 scv = entry.cv2;
146 end
147
148 function cme = fitMeanAndSCV(mean, scv)
149 % CME = FITMEANANDSCV(MEAN, SCV)
150 % Create the lowest-order CME with the given mean and SCV at most scv
151 %
152 % @param mean Target mean
153 % @param scv Target squared coefficient of variation, an upper bound.
154 % The lowest tabulated order whose minimal SCV does not exceed
155 % it is selected, so the result is at least as concentrated as
156 % requested.
157 % @return cme CME of that order rescaled to the requested mean
158
159 params = CME.table();
160 bestorder = Inf;
161 mincv2 = Inf;
162 maxn = 0;
163 for i = 1:numel(params)
164 if iscell(params)
165 cand = params{i};
166 else
167 cand = params(i);
168 end
169 mincv2 = min(mincv2, cand.cv2);
170 maxn = max(maxn, cand.n);
171 if cand.cv2 <= scv
172 bestorder = min(bestorder, 2*cand.n+1);
173 end
174 end
175
176 if ~isfinite(bestorder)
177 line_error(mfilename, sprintf('No tabulated CME reaches SCV %g; the most concentrated entry has SCV %g at order %d.', scv, mincv2, 2*maxn+1));
178 end
179
180 cme = CME(mean, bestorder);
181 end
182
183 function [alpha, A, scv] = representation(order)
184 % [ALPHA, A, SCV] = REPRESENTATION(ORDER)
185 % Build the unit-mean (alpha, A) matrix-exponential form of a CME
186 %
187 % A = blkdiag(-mu1, mu1*[-1 -k*w; k*w -1], k=1..n) reproduces the
188 % exponential envelope in its first phase and the k-th harmonic in
189 % its k-th 2x2 rotation block, since expm(mu1*x*[-1 -kw; kw -1]) is
190 % exp(-mu1*x) times the rotation by k*w*mu1*x. The entries of alpha
191 % follow by matching -alpha*expm(A*x)*A*e term by term: with
192 % wk = k*w and d = 2*(1+wk^2),
193 %
194 % alpha(1) = c
195 % alpha(2k) = ((1+wk)*a_k - (1-wk)*b_k)/d
196 % alpha(2k+1) = ((1-wk)*a_k + (1+wk)*b_k)/d
197 %
198 % The result has unit mean and sums to one, as any (alpha, A) whose
199 % density integrates to one must.
200
201 entry = CME.tableEntry(order);
202 a = entry.a(:);
203 b = entry.b(:);
204 c = entry.c;
205 mu1 = entry.mu1;
206 w = entry.omega;
207 n = entry.n;
208
209 sz = 2*n+1;
210 A = zeros(sz, sz);
211 alpha = zeros(1, sz);
212 A(1,1) = -mu1;
213 alpha(1) = c;
214 for k = 1:n
215 i = 2*k;
216 wk = k*w;
217 A(i,i) = -mu1;
218 A(i,i+1) = -wk*mu1;
219 A(i+1,i) = wk*mu1;
220 A(i+1,i+1) = -mu1;
221 d = 2*(1+wk^2);
222 alpha(i) = ((1+wk)*a(k) - (1-wk)*b(k))/d;
223 alpha(i+1) = ((1-wk)*a(k) + (1+wk)*b(k))/d;
224 end
225
226 % The tabulated coefficients satisfy the normalization only to about
227 %% 1e-12, and the residual grows with the order, so alpha is rescaled by its own
228 %% sum: alpha*e = 1 is an exact requirement of a matrix-exponential distribution
229 %% (the density integrates to one) and CheckMERepresentation enforces it to
230 %% prec*size. The rescaling changes the density by the same 1e-12 relative amount
231 %% it removes.
232 alpha = alpha / sum(alpha);
233
234 scv = entry.cv2;
235 end
236 end
237end
Definition Station.m:245