2 % Concentrated Matrix Exponential (CME) distribution
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.
11 % The density of the unit-mean CME with n harmonic terms
is
13 % f(x) = mu1*exp(-mu1*x)*(c + sum_k [a_k*cos(k*w*mu1*x) + b_k*sin(k*w*mu1*x)])
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).
20 % Copyright (c) 2012-2026, Imperial College London
21 % All rights reserved.
24 cmeMean; % Mean of the distribution
25 cmeOrder; % Number of phases, an odd integer 2*n+1
29 function self = CME(mean, order)
30 % SELF = CME(MEAN, ORDER)
31 % Create a concentrated matrix-exponential distribution
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
37 [alpha, A] = CME.representation(order);
39 if ~isscalar(mean) || ~isfinite(mean) || mean <= 0
40 line_error(mfilename,
'CME mean must be a positive finite number.');
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);
49 self.cmeOrder = double(order);
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));
56 % see _kb/04-networkstruct.md (CME.m representation invariants)
59 function o = getOrder(self)
61 % Get the CME order, i.e. the number of phases
67 function params = table()
69 % Load and cache the CME parameter table from iltcme.json
71 % The same table backs matlab_ilt, which caches it in the global
72 % cmeParams, so the two share one decode per session.
76 cmeParams = jsondecode(fileread(
'iltcme.json'));
81 function entry = tableEntry(order)
82 % ENTRY = TABLEENTRY(ORDER)
83 % Select the CME table entry realizing the given number of phases
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.
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));
100 for i = 1:numel(params)
106 if cand.n == n && cand.cv2 < bestcv2
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)));
119 function orders = getSupportedOrders()
120 % ORDERS = GETSUPPORTEDORDERS()
121 % Get the sorted list of CME orders (phase counts) in the table
123 params = CME.table();
124 ns = zeros(numel(params),1);
125 for i = 1:numel(params)
132 orders = unique(2*ns+1)
';
135 function scv = getMinSCV(order)
136 % SCV = GETMINSCV(ORDER)
137 % Get the tabulated minimal SCV attained by a CME of the given order
139 entry = CME.tableEntry(order);
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
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
152 % @return cme CME of that order rescaled to the requested mean
154 params = CME.table();
158 for i = 1:numel(params)
164 mincv2 = min(mincv2, cand.cv2);
165 maxn = max(maxn, cand.n);
167 bestorder = min(bestorder, 2*cand.n+1);
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));
175 cme = CME(mean, bestorder);
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
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),
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
193 % The result has unit mean and sums to one, as any (alpha, A) whose
194 % density integrates to one must.
196 entry = CME.tableEntry(order);
206 alpha = zeros(1, sz);
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;
221 % see _kb/04-networkstruct.md (CME.m representation invariants)
222 alpha = alpha / sum(alpha);