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 % see _kb/04-networkstruct.md (CME.m representation invariants)
57 end
58
59 function o = getOrder(self)
60 % O = GETORDER()
61 % Get the CME order, i.e. the number of phases
62 o = self.cmeOrder;
63 end
64 end
65
66 methods(Static)
67 function params = table()
68 % PARAMS = TABLE()
69 % Load and cache the CME parameter table from iltcme.json
70 %
71 % The same table backs matlab_ilt, which caches it in the global
72 % cmeParams, so the two share one decode per session.
73
74 global cmeParams;
75 if isempty(cmeParams)
76 cmeParams = jsondecode(fileread('iltcme.json'));
77 end
78 params = cmeParams;
79 end
80
81 function entry = tableEntry(order)
82 % ENTRY = TABLEENTRY(ORDER)
83 % Select the CME table entry realizing the given number of phases
84 %
85 % The table is keyed by the number of harmonic terms n, so an order
86 % of 2*n+1 phases maps to the entries with that n. Several entries
87 % can share an n (the 'full' and 'approx' optimizations), and the
88 % most concentrated one is taken, matching the selection rule of the
89 % CME inverse Laplace transform.
90
91 order = double(order);
92 if ~isscalar(order) || order < 3 || mod(order,2) == 0 || order ~= fix(order)
93 line_error(mfilename, sprintf('CME order must be an odd integer of the form 2*n+1 with n >= 1, got %g.', order));
94 end
95 n = (order-1)/2;
96
97 params = CME.table();
98 entry = [];
99 bestcv2 = Inf;
100 for i = 1:numel(params)
101 if iscell(params)
102 cand = params{i};
103 else
104 cand = params(i);
105 end
106 if cand.n == n && cand.cv2 < bestcv2
107 entry = cand;
108 bestcv2 = cand.cv2;
109 end
110 end
111
112 if isempty(entry)
113 orders = CME.getSupportedOrders();
114 [~,j] = min(abs(orders-order));
115 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)));
116 end
117 end
118
119 function orders = getSupportedOrders()
120 % ORDERS = GETSUPPORTEDORDERS()
121 % Get the sorted list of CME orders (phase counts) in the table
122
123 params = CME.table();
124 ns = zeros(numel(params),1);
125 for i = 1:numel(params)
126 if iscell(params)
127 ns(i) = params{i}.n;
128 else
129 ns(i) = params(i).n;
130 end
131 end
132 orders = unique(2*ns+1)';
133 end
134
135 function scv = getMinSCV(order)
136 % SCV = GETMINSCV(ORDER)
137 % Get the tabulated minimal SCV attained by a CME of the given order
138
139 entry = CME.tableEntry(order);
140 scv = entry.cv2;
141 end
142
143 function cme = fitMeanAndSCV(mean, scv)
144 % CME = FITMEANANDSCV(MEAN, SCV)
145 % Create the lowest-order CME with the given mean and SCV at most scv
146 %
147 % @param mean Target mean
148 % @param scv Target squared coefficient of variation, an upper bound.
149 % The lowest tabulated order whose minimal SCV does not exceed
150 % it is selected, so the result is at least as concentrated as
151 % requested.
152 % @return cme CME of that order rescaled to the requested mean
153
154 params = CME.table();
155 bestorder = Inf;
156 mincv2 = Inf;
157 maxn = 0;
158 for i = 1:numel(params)
159 if iscell(params)
160 cand = params{i};
161 else
162 cand = params(i);
163 end
164 mincv2 = min(mincv2, cand.cv2);
165 maxn = max(maxn, cand.n);
166 if cand.cv2 <= scv
167 bestorder = min(bestorder, 2*cand.n+1);
168 end
169 end
170
171 if ~isfinite(bestorder)
172 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));
173 end
174
175 cme = CME(mean, bestorder);
176 end
177
178 function [alpha, A, scv] = representation(order)
179 % [ALPHA, A, SCV] = REPRESENTATION(ORDER)
180 % Build the unit-mean (alpha, A) matrix-exponential form of a CME
181 %
182 % A = blkdiag(-mu1, mu1*[-1 -k*w; k*w -1], k=1..n) reproduces the
183 % exponential envelope in its first phase and the k-th harmonic in
184 % its k-th 2x2 rotation block, since expm(mu1*x*[-1 -kw; kw -1]) is
185 % exp(-mu1*x) times the rotation by k*w*mu1*x. The entries of alpha
186 % follow by matching -alpha*expm(A*x)*A*e term by term: with
187 % wk = k*w and d = 2*(1+wk^2),
188 %
189 % alpha(1) = c
190 % alpha(2k) = ((1+wk)*a_k - (1-wk)*b_k)/d
191 % alpha(2k+1) = ((1-wk)*a_k + (1+wk)*b_k)/d
192 %
193 % The result has unit mean and sums to one, as any (alpha, A) whose
194 % density integrates to one must.
195
196 entry = CME.tableEntry(order);
197 a = entry.a(:);
198 b = entry.b(:);
199 c = entry.c;
200 mu1 = entry.mu1;
201 w = entry.omega;
202 n = entry.n;
203
204 sz = 2*n+1;
205 A = zeros(sz, sz);
206 alpha = zeros(1, sz);
207 A(1,1) = -mu1;
208 alpha(1) = c;
209 for k = 1:n
210 i = 2*k;
211 wk = k*w;
212 A(i,i) = -mu1;
213 A(i,i+1) = -wk*mu1;
214 A(i+1,i) = wk*mu1;
215 A(i+1,i+1) = -mu1;
216 d = 2*(1+wk^2);
217 alpha(i) = ((1+wk)*a(k) - (1-wk)*b(k))/d;
218 alpha(i+1) = ((1-wk)*a(k) + (1+wk)*b(k))/d;
219 end
220
221 % see _kb/04-networkstruct.md (CME.m representation invariants)
222 alpha = alpha / sum(alpha);
223
224 scv = entry.cv2;
225 end
226 end
227end