LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
m3pp2m_interleave.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_MAM_M3PP2M_INTERLEAVE_H
6#define LINE_API_MAM_M3PP2M_INTERLEAVE_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * LUMPED interleaving of several M3PP(2, m), and the two fitters built on it
12 * (matlab/lib/m3a/m3a/m3pp/m3pp2m_interleave.m,
13 * matlab/lib/m3a/m3a/m3pp/m3pp2m_fitc_theoretical.m,
14 * matlab/lib/m3a/m3a/m3pp/m3pp22_interleave_fitc.m).
15 *
16 * Interleaving is the cheap alternative to superposition. Superposing L
17 * two-phase processes gives the PRODUCT chain, of order 2^L; interleaving
18 * instead lays the L phase processes on a single BIRTH-DEATH chain of order
19 * L + 1, where phase h means "the first h components are in their fast phase".
20 * The off-diagonal rates are recovered by DIFFERENCING: the upper rate out of
21 * level j is component j's r1 minus the rates already spent on levels above it,
22 * and symmetrically downwards. That differencing is only meaningful when the
23 * components' rates are ordered, which is what m3pp22_interleave_fitc's linear
24 * program arranges before it ever fits a component -- it does not fit L
25 * processes and then hope they interleave, it SOLVES for off-diagonal rates
26 * that admit the interleaving and fits the components to those.
27 *
28 * The class matrices carry no cross terms: class j of component i fires at its
29 * phase-1 rate on levels h <= i and at its phase-2 rate above, so sum_c Dc = D1
30 * holds level by level.
31 *
32 * THE LINEAR PROGRAM IS A FEASIBILITY PROBLEM. The reference passes a ZERO
33 * objective to linprog, so every feasible point is optimal and which vertex
34 * comes back is the solver's choice; different LP backends hand different
35 * MMPP(2)s to the per-pair covariance split, and a covariance one accepts
36 * another can report infeasible. That is a property of the reference, not of
37 * this port, and it is why m3pp22_interleave_fitc reports the realised
38 * covariance per pair rather than asserting the requested one.
39 *
40 * Gated on transcendental arithmetic, and on double alone for the LP.
41 */
42
43#include <cstddef>
44#include <string>
45#include <vector>
46
58#include "line/num/number.h"
59#include "line/util/error.h"
60#include "line/util/matrix.h"
61#include "line/util/simplex.h"
62
63namespace line {
64namespace mam {
65
66/**
67 * Interleave L M3PP(2, m_i) into one M3PP of order L + 1 whose class list is
68 * the concatenation of theirs.
69 */
70template <class T>
71Mmap<T> m3pp2m_interleave(const std::vector<Mmap<T>>& parts) {
72 const T zero = num_traits<T>::from_int(0);
73 const std::size_t L = parts.size();
74 if (L == 0) throw InputError("m3pp2m_interleave: no components");
75 // a one-phase component is a marked Poisson flow (what m3pp2m_fitc returns when
76 // the flow is not overdispersed): it switches no level and fires at every level
77 std::vector<bool> deg(L, false);
78 bool all_deg = true;
79 for (std::size_t i = 0; i < L; ++i) {
80 if (parts[i].order() != 1 && parts[i].order() != 2)
81 throw InputError("m3pp2m_interleave: every component must have order 1 or 2");
82 deg[i] = parts[i].order() == 1;
83 all_deg = all_deg && deg[i];
84 }
85 if (all_deg) {
86 Mmap<T> p;
87 p.D1 = Matrix<T>(1, 1, zero);
88 for (std::size_t i = 0; i < L; ++i)
89 for (std::size_t c = 0; c < parts[i].classes(); ++c) {
90 p.Dc.push_back(parts[i].Dc[c]);
91 p.D1(0, 0) += parts[i].Dc[c](0, 0);
92 }
93 p.D0 = Matrix<T>(1, 1, zero);
94 p.D0(0, 0) = zero - p.D1(0, 0);
95 return p;
96 }
97
98 // r(0, i): upper off-diagonal rate contributed by component i, differenced
99 // from the top down; r(1, i): lower rate, differenced from the bottom up.
100 std::vector<T> r0(L, zero), r1(L, zero);
101 for (std::size_t k = L; k-- > 0;) {
102 if (deg[k]) continue;
103 T acc = zero;
104 for (std::size_t j = k + 1; j < L; ++j) acc += r0[j];
105 r0[k] = parts[k].D0(0, 1) - acc;
106 }
107 for (std::size_t i = 0; i < L; ++i) {
108 if (deg[i]) continue;
109 T acc = zero;
110 for (std::size_t j = 0; j < i; ++j) acc += r1[j];
111 r1[i] = parts[i].D0(1, 0) - acc;
112 }
113
114 std::size_t M = 0;
115 for (std::size_t i = 0; i < L; ++i) M += parts[i].classes();
116 const std::size_t n = 2 + (L - 1);
117
118 Mmap<T> s;
119 s.D0 = Matrix<T>(n, n, zero);
120 for (std::size_t i = 0; i < n; ++i)
121 for (std::size_t j = 0; j < n; ++j) {
122 if (j > i)
123 s.D0(i, j) = r0[j - 1];
124 else if (j < i)
125 s.D0(i, j) = r1[j];
126 }
127
128 for (std::size_t i = 0; i < L; ++i) {
129 for (std::size_t c = 0; c < parts[i].classes(); ++c) {
130 Matrix<T> Dc(n, n, zero);
131 for (std::size_t h = 0; h < n; ++h)
132 Dc(h, h) = (h <= i || deg[i]) ? parts[i].Dc[c](0, 0) : parts[i].Dc[c](1, 1);
133 s.Dc.push_back(Dc);
134 }
135 }
136
137 s.D1 = Matrix<T>(n, n, zero);
138 for (std::size_t c = 0; c < M; ++c)
139 for (std::size_t i = 0; i < n; ++i)
140 for (std::size_t j = 0; j < n; ++j) s.D1(i, j) += s.Dc[c](i, j);
141
142 for (std::size_t h = 0; h < n; ++h) {
143 T acc = zero;
144 for (std::size_t j = 0; j < n; ++j) acc += s.D0(h, j) + s.D1(h, j);
145 s.D0(h, h) = -acc;
146 }
147 return s;
148}
149
150/**
151 * Fit the counting characteristics of a GIVEN MMAP with an M3PP(2, m).
152 *
153 * @param mm the MMAP(n, m) to fit
154 * @param method 'exact_delta', 'approx_delta', 'approx_cov' or 'approx_ag'
155 * @param t the single finite time scale (the reference sets t1 = t2 = t3 = t)
156 * @param tinf the near-infinite time scale
157 */
158template <class T>
159Mmap<T> m3pp2m_fitc_theoretical(const Mmap<T>& mm, const std::string& method, const T& t,
160 const T& tinf) {
162 "m3pp2m_fitc_theoretical requires transcendental arithmetic");
163 const std::size_t m = mm.classes();
164 if (m == 0) throw InputError("m3pp2m_fitc_theoretical: the MMAP has no classes");
165 if (method == "approx_cov" && m > 2)
166 throw InputError(ErrorKind{"Argument"}, "m3pp2m_fitc_theoretical: approximate covariance fitting only supports "
167 "two classes");
168
169 const T t1 = t, t2 = t, t3 = t;
170 const Map<T> joint = mm.map();
171
172 std::vector<T> ts;
173 ts.push_back(t1);
174 ts.push_back(t2);
175 ts.push_back(tinf);
176 ts.push_back(t3);
177 const std::vector<T> mu = map_count_mean(joint, ts);
178 const std::vector<T> vr = map_count_var(joint, ts);
179
180 const T a = mu[0] / t1;
181 const T bt1 = vr[0] / (a * t1);
182 const T bt2 = vr[1] / (a * t2);
183 const T binf = vr[2] / (a * tinf);
184
185 std::vector<unsigned> orders;
186 orders.push_back(1);
187 orders.push_back(2);
188 orders.push_back(3);
189 const std::vector<T> mt2 = map_count_moment(joint, t2, orders);
190 const T m3t2 = fitdetail::m3_from_raw(mt2[0], mt2[1], mt2[2]);
191
192 const std::vector<T> ai = mmap_count_mean(mm, num_traits<T>::from_int(1));
193
194 if (method == "exact_delta" || method == "approx_delta") {
195 std::vector<T> dvt3(m);
196 for (std::size_t i = 0; i < m; ++i) {
197 Mmap<T> two;
198 two.D0 = mm.D0;
199 two.D1 = mm.D1;
200 two.Dc.push_back(mm.Dc[i]);
201 Matrix<T> rest = mm.D1;
202 for (std::size_t r = 0; r < rest.rows(); ++r)
203 for (std::size_t c = 0; c < rest.cols(); ++c) rest(r, c) -= mm.Dc[i](r, c);
204 two.Dc.push_back(rest);
205 const std::vector<T> V = mmap_count_var(two, t3);
206 dvt3[i] = V[0] - V[1];
207 }
208 if (method == "exact_delta")
209 return m3pp2m_fitc(a, bt1, bt2, binf, m3t2, t1, t2, ai, dvt3, t3).mmap;
210 return m3pp2m_fitc_approx(a, bt1, bt2, binf, m3t2, t1, t2, ai, dvt3, t3).mmap;
211 }
212 if (method == "approx_cov") {
213 const std::vector<T> vi = mmap_count_var(mm, t3);
215 for (std::size_t i = 0; i < vi.size(); ++i) sum += vi[i];
216 const T s = (vr[3] - sum) / num_traits<T>::from_int(2);
217 return m3pp22_fitc_approx_cov(a, bt1, bt2, binf, m3t2, t1, t2, ai, s, t3).mmap;
218 }
219 if (method == "approx_ag") {
220 std::vector<T> gt3(m);
221 for (std::size_t i = 0; i < m; ++i) {
222 Mmap<T> two;
223 two.D0 = mm.D0;
224 two.D1 = mm.D1;
225 two.Dc.push_back(mm.Dc[i]);
226 Matrix<T> rest = mm.D1;
227 for (std::size_t r = 0; r < rest.rows(); ++r)
228 for (std::size_t c = 0; c < rest.cols(); ++c) rest(r, c) -= mm.Dc[i](r, c);
229 two.Dc.push_back(rest);
230 const std::vector<T> V = mmap_count_var(two, t3);
231 const Matrix<T> S = mmap_count_mcov(two, t3);
232 gt3[i] = V[0] + S(0, 1);
233 }
234 return m3pp2m_fitc_approx_ag(a, bt1, bt2, binf, m3t2, t1, t2, ai, gt3, t3).mmap;
235 }
236 throw InputError("m3pp2m_fitc_theoretical: unknown method '" + method + "'");
237}
238
239/** m3pp2m_fitc_theoretical with the reference's default scales t = 10, tinf = 1e4. */
240template <class T>
241Mmap<T> m3pp2m_fitc_theoretical(const Mmap<T>& mm, const std::string& method) {
244}
245
246/** Result of m3pp22_interleave_fitc. */
247template <class T>
249 Mmap<T> mmap; ///< the lumped process, of order L + 1
250 std::vector<Mmap<T>> parts; ///< the L M3PP(2, 2) components
251 std::vector<T> sigma; ///< the covariance realised for each pair
252};
253
254/**
255 * Fit L PAIRS of classes into one MMAP by lumped interleaving of L M3PP(2, 2).
256 *
257 * @param av (L x 2) per-class rates
258 * @param btv per-pair IDC at t
259 * @param binfv per-pair asymptotic IDC
260 * @param stv per-pair count covariance at t
261 * @param t the time scale
262 */
263template <class T>
264M3pp22InterleaveResult<T> m3pp22_interleave_fitc(const Matrix<T>& av, const std::vector<T>& btv,
265 const std::vector<T>& binfv,
266 const std::vector<T>& stv, const T& t) {
268 "m3pp22_interleave_fitc requires transcendental arithmetic");
269 using fitdetail::lambertw0;
270 using fitdetail::num_exp;
271 using fitdetail::num_sqrt;
272 using fitdetail::pw;
273
274 const T zero = num_traits<T>::from_int(0);
275 const T one = num_traits<T>::from_int(1);
276 const T two = num_traits<T>::from_int(2);
277 const std::size_t L = av.rows();
278 if (L == 0) throw InputError("m3pp22_interleave_fitc: no pairs");
279 if (av.cols() != 2) throw InputError("m3pp22_interleave_fitc: av must have two columns");
280 if (btv.size() != L || binfv.size() != L || stv.size() != L)
281 throw InputError("m3pp22_interleave_fitc: btv, binfv and stv must have one entry per pair");
282
283 // bounds on the upper off-diagonal element of each MMPP(2)
284 std::vector<T> uv(L, zero), dv(L, zero);
285 for (std::size_t i = 0; i < L; ++i) {
286 const T a = av(i, 0) + av(i, 1);
287 if (!(binfv[i] > btv[i] && btv[i] > one))
288 throw InputError("m3pp22_interleave_fitc: infeasible IDC pair, IDC(inf) must exceed "
289 "IDC(t) and IDC(t) must exceed one");
290 const T c = (binfv[i] - one) / (binfv[i] - btv[i]);
291 const T w = lambertw0(T(-c * num_exp(T(-c))), 200u);
292 const T d = (w + c) / t;
293 const T z = (binfv[i] - one) * pw(d, 3) * a;
294 uv[i] = d * z / (two * a * a * d * d + z);
295 dv[i] = d;
296 }
297
298 // feasibility LP over the 2L per-level off-diagonal rates
299 lp::LpModel<T> model(2 * L);
300 model.set_maximize(false);
301 for (std::size_t j = 0; j < 2 * L; ++j) {
302 model.set_lower(j, zero);
303 model.set_free_upper(j);
304 model.set_cost(j, zero);
305 }
306 const T eps = num_traits<T>::from_double(1e-6);
307 for (std::size_t i = 0; i < L; ++i) {
308 model.row_clear();
309 for (std::size_t j = i; j < L; ++j) model.row_add(j, one);
310 model.emit_le(T(dv[i] - eps));
311 model.row_clear();
312 for (std::size_t j = i; j < L; ++j) model.row_add(j, T(-one));
313 model.emit_le(T(-uv[i] - eps));
314 }
315 for (std::size_t i = 0; i < L; ++i) {
316 model.row_clear();
317 for (std::size_t j = i; j < L; ++j) model.row_add(j, one);
318 for (std::size_t j = 0; j <= i; ++j) model.row_add(L + j, one);
319 model.emit_eq(dv[i]);
320 }
321 const lp::LpSolution<T> sol = lp::simplex_solve(model);
322 if (!sol.ok())
323 throw NumericError(ErrorKind{"Infeasible"}, "m3pp22_interleave_fitc: no feasible set of off-diagonal rates (" +
324 std::string(lp::lp_status_name(sol.status)) + ")");
325
327 for (std::size_t i = 0; i < L; ++i) {
328 T r1 = zero, r2 = zero;
329 for (std::size_t j = i; j < L; ++j) r1 += sol.x[j];
330 for (std::size_t j = 0; j <= i; ++j) r2 += sol.x[L + j];
331 const T a = av(i, 0) + av(i, 1);
332 const T d = r1 + r2;
333 const T z = (binfv[i] - one) * pw(d, 3) * a;
334 const T delta = num_sqrt(T(z / (two * r1 * r2)));
335 const T l2 = a - r2 / d * delta;
336 const T l1 = l2 + delta;
337
338 Map<T> base;
339 base.D0 = Matrix<T>(2, 2, zero);
340 base.D1 = Matrix<T>(2, 2, zero);
341 base.D0(0, 1) = r1;
342 base.D0(1, 0) = r2;
343 base.D1(0, 0) = l1;
344 base.D1(1, 1) = l2;
345 for (std::size_t h = 0; h < 2; ++h)
346 base.D0(h, h) = -(base.D0(h, 0) + base.D0(h, 1) + base.D1(h, 0) + base.D1(h, 1));
347
348 std::vector<T> ai;
349 ai.push_back(av(i, 0));
350 ai.push_back(av(i, 1));
351 const M3pp22FitcCovResult<T> f = m3pp22_fitc_approx_cov_multiclass(base, ai, stv[i], t);
352 out.parts.push_back(f.mmap);
353 out.sigma.push_back(f.sigma);
354 }
355
356 out.mmap = m3pp2m_interleave(out.parts);
357 return out;
358}
359
360} // namespace mam
361} // namespace line
362
363#endif // LINE_API_MAM_M3PP2M_INTERLEAVE_H
InputError(const std::string &what)
Definition error.h:105
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:112
Sparse LP in the natural form, with per-variable bounds.
Definition simplex.h:112
void emit_eq(const T &rhs)
Definition simplex.h:217
void set_maximize(bool m)
true to maximize c'x (the default), false to minimize.
Definition simplex.h:174
void emit_le(const T &rhs)
Definition simplex.h:216
void set_free_upper(std::size_t j)
Definition simplex.h:147
void set_cost(std::size_t j, const T &v)
Definition simplex.h:164
void set_lower(std::size_t j, const T &v)
Definition simplex.h:129
void row_clear()
Discard whatever the row accumulator holds.
Definition simplex.h:179
void row_add(std::size_t j, const T &v)
row(j) += v, the accumulation the MATLAB reference performs.
Definition simplex.h:188
The exception types the port throws.
M3PP(2, 2) fitted to the count COVARIANCE between its two classes (matlab/lib/m3a/m3a/m3pp/m3pp22_fit...
Marked MMPP(2) with m classes, fitted to counting-process characteristics (matlab/lib/m3a/m3a/m3pp/m3...
M3PP(2, m) fitted to counting-process characteristics with an optimized per-class split (matlab/lib/m...
M3PP obtained by SUPERPOSING one second-order process per class (matlab/lib/m3a/m3a/m3pp/m3pp_superpo...
Mean of the counting process of a MAP at resolution t.
Power moments of the counts of a MAP in a window of length t.
Variance of the counting process of a MAP at resolution t.
Dense matrix and non-owning view.
Per-class variance of the counting process of a marked MAP.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
Marked MAP statistics: embedded chains, class-transition probabilities, forward and cross moments,...
MMPP(2) matching counting-process characteristics (matlab/lib/kpctoolbox/mmpp/mmpp2_fitc....
const char * lp_status_name(LpStatus s)
Definition simplex.h:84
LpSolution< T > simplex_solve(const LpModel< T > &model, std::size_t max_iterations=0)
Solve the model.
Definition simplex.h:286
M3pp2mFitcApproxResult< T > m3pp2m_fitc_approx_ag(const T &a, const T &bt1, const T &bt2, const T &binf, const T &m3t2, const T &t1, const T &t2, const std::vector< T > &ai, const std::vector< T > &gt3, const T &t3, const std::vector< Bound< T > > &bounds, const AugLagOptions< T > &opt)
m3pp2m_fitc_approx_ag: fit the underlying MMPP(2) by optimization, then apply the 'ag' per-class spli...
M3pp22InterleaveResult< T > m3pp22_interleave_fitc(const Matrix< T > &av, const std::vector< T > &btv, const std::vector< T > &binfv, const std::vector< T > &stv, const T &t)
Fit L PAIRS of classes into one MMAP by lumped interleaving of L M3PP(2, 2).
Mmap< T > m3pp2m_interleave(const std::vector< Mmap< T > > &parts)
Interleave L M3PP(2, m_i) into one M3PP of order L + 1 whose class list is the concatenation of their...
M3pp2mFitcResult< T > m3pp2m_fitc(const T &a, const T &bt1, const T &bt2, const T &binf, const T &m3t2, const T &t1, const T &t2, const std::vector< T > &ai, const std::vector< T > &dvt3, const T &t3)
Fit an M3PP(2, m).
Definition m3pp2m_fitc.h:60
Matrix< T > mmap_count_mcov(const Mmap< T > &mm, const T &t)
Covariance matrix of the per-class counts over a window of length t.
Definition mmap_stats.h:222
std::vector< T > map_count_var(const Map< T > &m, const std::vector< T > &t)
Variance of the counting process of a MAP at resolution t.
std::vector< T > map_count_mean(const Map< T > &m, const std::vector< T > &t)
Mean of the counting process of a MAP at resolution t.
std::vector< T > mmap_count_mean(const Mmap< T > &mm, const T &t)
Per-class mean of the counting process over a window of length t.
Definition mmap_stats.h:182
M3pp22FitcCovResult< T > m3pp22_fitc_approx_cov(const T &a, const T &bt1, const T &bt2, const T &binf, const T &m3t2, const T &t1, const T &t2, const std::vector< T > &ai, const T &st3, const T &t3, const AugLagOptions< T > &opt)
Fit the underlying MMPP(2) by optimization, then apply the covariance split.
M3pp2mFitcApproxResult< T > m3pp2m_fitc_approx(const T &a, const T &bt1, const T &bt2, const T &binf, const T &m3t2, const T &t1, const T &t2, const std::vector< T > &ai, const std::vector< T > &dvt3, const T &t3, const std::vector< Bound< T > > &bounds, const AugLagOptions< T > &opt)
m3pp2m_fitc_approx: fit the underlying MMPP(2) by optimization, then split the classes on their varia...
std::vector< T > map_count_moment(const Map< T > &m, const T &t, const std::vector< unsigned > &orders)
Power moments of the counts of a MAP in a window of length t.
std::vector< T > mmap_count_var(const Mmap< T > &mm, const T &t)
Per-class variance of the counting process of a marked MAP.
M3pp22FitcCovResult< T > m3pp22_fitc_approx_cov_multiclass(const Map< T > &mmpp, const std::vector< T > &ai, const T &st3, const T &t3)
Split a GIVEN MMPP(2) into two classes, matching the per-class rates exactly and the count covariance...
Mmap< T > m3pp2m_fitc_theoretical(const Mmap< T > &mm, const std::string &method, const T &t, const T &tinf)
Fit the counting characteristics of a GIVEN MMAP with an M3PP(2, m).
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
Templated primal simplex with Bland's rule.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
bool ok() const
Definition simplex.h:99
std::vector< T > x
primal solution in the ORIGINAL variable space
Definition simplex.h:96
Result of the covariance-matching M3PP(2, 2) fits.
Mmap< T > mmap
the fitted M3PP(2, 2)
T sigma
the covariance actually realised, after clamping
Result of m3pp22_interleave_fitc.
Mmap< T > mmap
the lumped process, of order L + 1
std::vector< Mmap< T > > parts
the L M3PP(2, 2) components
std::vector< T > sigma
the covariance realised for each pair
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54
An MMAP: the underlying MAP plus the per-class arrival matrices.
Definition mmap_lambda.h:45
Map< T > map() const
Definition mmap_lambda.h:52
std::size_t classes() const
Definition mmap_lambda.h:51
Matrix< T > D0
Definition mmap_lambda.h:46
Matrix< T > D1
Definition mmap_lambda.h:47
std::vector< Matrix< T > > Dc
per-class matrices, sum_c Dc = D1
Definition mmap_lambda.h:48