LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
m3pp22_fitc_cov.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_M3PP22_FITC_COV_H
6#define LINE_API_MAM_M3PP22_FITC_COV_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * M3PP(2, 2) fitted to the count COVARIANCE between its two classes
12 * (matlab/lib/m3a/m3a/m3pp/m3pp22_fitc_approx_cov_multiclass.m and
13 * matlab/lib/m3a/m3a/m3pp/m3pp22_fitc_approx_cov.m).
14 *
15 * TWO CLASSES ONLY, by construction, and the reference refuses more by name.
16 * Given the underlying MMPP(2) the per-phase marking probabilities (q1, q2) of
17 * the first class satisfy two relations: the class rate a1 is affine in them,
18 * and the count covariance sigma at t3 is a QUADRATIC in q2 once q1 has been
19 * eliminated,
20 *
21 * sigma(q2) = w0 + w1 q2 + w2 q2^2 ,
22 *
23 * so the inverse has two roots. Rather than picking one and repairing the
24 * result, the reference derives, for EACH root separately, the interval of
25 * covariances over which that root keeps both marking probabilities inside
26 * [0, 1] and the discriminant non-negative; it then clamps the requested
27 * covariance into whichever interval is closer to it and takes the matching
28 * root. A root whose interval is provably empty is flagged rather than clamped.
29 * That bound derivation is the bulk of the reference and of this port, and it
30 * is transcribed relation by relation: square-root argument >= 0, q2 >= 0,
31 * q1 >= 0, q2 <= 1, q1 <= 1.
32 *
33 * The clamp is the only approximation: the rates are matched exactly, and the
34 * covariance is matched exactly whenever the request lies inside the feasible
35 * interval of either root.
36 *
37 * Degenerate inputs short-circuit as in the reference: a Poisson underlying
38 * process splits D1 in proportion to the class rates, and a single class takes
39 * all of D1.
40 *
41 * Gated on transcendental arithmetic: the bounds carry exp(-(r1 + r2) t3).
42 */
43
44#include <cstddef>
45#include <vector>
46
51#include "line/num/number.h"
52#include "line/util/error.h"
53#include "line/util/matrix.h"
54
55namespace line {
56namespace mam {
57
58/** Result of the covariance-matching M3PP(2, 2) fits. */
59template <class T>
61 Mmap<T> mmap; ///< the fitted M3PP(2, 2)
62 T sigma; ///< the covariance actually realised, after clamping
63 int root; ///< 1 or 2, which root of the quadratic was taken; 0 when degenerate
64 bool clamped; ///< the requested covariance lay outside the feasible interval
65 bool degenerate; ///< the underlying process was Poisson, or there is a single class
66};
67
68/**
69 * Split a GIVEN MMPP(2) into two classes, matching the per-class rates exactly
70 * and the count covariance between them at t3 as closely as feasible.
71 *
72 * @param mmpp the underlying MAP, of order 1 (Poisson) or 2
73 * @param ai the per-class rates; at most two
74 * @param st3 the requested count covariance between the two classes at t3
75 * @param t3 the third time scale
76 */
77template <class T>
79 const std::vector<T>& ai, const T& st3,
80 const T& t3) {
82 "m3pp22_fitc_approx_cov_multiclass requires transcendental arithmetic");
83 using fitdetail::num_exp;
84 using fitdetail::num_sqrt;
85 using fitdetail::pw;
86
87 const T zero = num_traits<T>::from_int(0);
88 const T one = num_traits<T>::from_int(1);
89 const T two = num_traits<T>::from_int(2);
90 const T four = num_traits<T>::from_int(4);
91
92 const std::size_t m = ai.size();
93 if (m == 0) throw InputError("m3pp22_fitc_approx_cov_multiclass: no classes");
94 if (m > 2) throw InputError("m3pp22_fitc_approx_cov_multiclass: no more than two classes "
95 "supported");
96
98 res.mmap.D0 = mmpp.D0;
99 res.mmap.D1 = mmpp.D1;
100 res.sigma = zero;
101 res.root = 0;
102 res.clamped = false;
103 res.degenerate = false;
104
105 if (mmpp.D0.rows() == 1) {
106 // marked Poisson process: split D1 in proportion to the class rates
107 T asum = zero;
108 for (std::size_t i = 0; i < m; ++i) asum += ai[i];
109 for (std::size_t i = 0; i < m; ++i)
110 res.mmap.Dc.push_back(Matrix<T>(1, 1, T(ai[i] / asum * mmpp.D1(0, 0))));
111 res.degenerate = true;
112 return res;
113 }
114 if (m == 1) {
115 res.mmap.Dc.push_back(mmpp.D1);
116 res.degenerate = true;
117 return res;
118 }
119 if (mmpp.D0.rows() != 2)
120 throw InputError("m3pp22_fitc_approx_cov_multiclass: the underlying MAP must have order 2");
121
122 const T l1 = mmpp.D1(0, 0);
123 const T l2 = mmpp.D1(1, 1);
124 const T r1 = mmpp.D0(0, 1);
125 const T r2 = mmpp.D0(1, 0);
126 const T t = t3;
127 const T a1 = ai[0];
128
129 const T E = num_exp(T(-(r1 + r2) * t));
130 const T G = one - E - (r1 + r2) * t;
131 const T w0 = (two * r1 * G * (a1 * a1 * (r1 + r2) - a1 * r2 * (l1 - l2))) /
132 (r2 * pw(T(r1 + r2), 3));
133 const T w1 = -(two * r1 * G * (two * a1 * l2 * (r1 + r2) - l2 * r2 * (l1 - l2))) /
134 (r2 * pw(T(r1 + r2), 3));
135 const T w2 = ((two * l2 * l2 * r2 * t) * (r1 + r2) + two * l2 * l2 * r1 * (one - E)) /
136 (r2 * pw(T(r1 + r2), 2)) -
137 (two * l2 * l2 * t) / r2;
138 const T w3 = (r1 + r2) / (l2 * r1);
139 const T w4 = (l1 * r2) / (l2 * r1);
140 if (w2 == zero)
141 throw NumericError("m3pp22_fitc_approx_cov_multiclass: the covariance is linear in the "
142 "marking probability, so the reference's two-root inversion degenerates");
143
144 const T inf = num_traits<T>::from_double(1.0 / 0.0);
145 T L1 = -inf, L2 = -inf, U1 = inf, U2 = inf;
146 bool infeasible1 = false, infeasible2 = false;
147
148 const T z = w0 - w1 * w1 / (four * w2);
149
150 // square-root argument >= 0
151 if (w2 > zero) {
152 if (z > L1) L1 = z;
153 if (z > L2) L2 = z;
154 } else {
155 if (z < U1) U1 = z;
156 if (z < U2) U2 = z;
157 }
158 // q2 >= 0
159 if (w1 >= zero) {
160 if (w0 > L1) L1 = w0;
161 } else if (w2 < zero) {
162 infeasible1 = true;
163 }
164 if (w1 <= zero) {
165 if (w0 < U2) U2 = w0;
166 } else if (w2 > zero) {
167 infeasible2 = true;
168 }
169 // q1 >= 0
170 {
171 const T tmp = two * a1 * w3 * w2 + w1;
172 const T bnd = z + tmp * tmp / (four * w2);
173 if (tmp >= zero) {
174 if (bnd < U1) U1 = bnd;
175 } else if (w2 > zero) {
176 infeasible1 = true;
177 }
178 if (tmp <= zero) {
179 if (bnd > L2) L2 = bnd;
180 } else if (w2 < zero) {
181 infeasible2 = true;
182 }
183 }
184 // q2 <= 1
185 {
186 const T tmp = two * w2 + w1;
187 const T bnd = z + tmp * tmp / (four * w2);
188 if (tmp >= zero) {
189 if (bnd < U1) U1 = bnd;
190 } else if (w2 > zero) {
191 infeasible1 = true;
192 }
193 if (tmp <= zero) {
194 if (bnd > L2) L2 = bnd;
195 } else if (w2 < zero) {
196 infeasible2 = true;
197 }
198 }
199 // q1 <= 1
200 {
201 const T tmp = two * a1 * w2 * w3 - two * w2 * w4 + w1;
202 const T bnd = z + tmp * tmp / (four * w2);
203 if (tmp >= zero) {
204 if (bnd > L1) L1 = bnd;
205 } else if (w2 < zero) {
206 infeasible1 = true;
207 }
208 if (tmp <= zero) {
209 if (bnd < U2) U2 = bnd;
210 } else if (w2 > zero) {
211 infeasible2 = true;
212 }
213 }
214
215 if (infeasible1 && infeasible2)
216 throw NumericError("m3pp22_fitc_approx_cov_multiclass: empty feasibility region");
217
218 T sigma = zero;
219 int root = 0;
220 if (infeasible2) {
221 sigma = st3 < U1 ? st3 : U1;
222 if (sigma < L1) sigma = L1;
223 root = 1;
224 } else if (infeasible1) {
225 sigma = st3 < U2 ? st3 : U2;
226 if (sigma < L2) sigma = L2;
227 root = 2;
228 } else {
229 T s1 = st3 < U1 ? st3 : U1;
230 if (s1 < L1) s1 = L1;
231 T s2 = st3 < U2 ? st3 : U2;
232 if (s2 < L2) s2 = L2;
233 if (num_abs(T(s1 - st3)) < num_abs(T(s2 - st3))) {
234 sigma = s1;
235 root = 1;
236 } else {
237 sigma = s2;
238 root = 2;
239 }
240 }
241
242 const T disc = w1 * w1 - four * w2 * (w0 - sigma);
243 if (disc < zero)
244 throw NumericError("m3pp22_fitc_approx_cov_multiclass: negative discriminant after "
245 "clamping the covariance");
246 const T rt = num_sqrt(disc);
247 T q2 = root == 1 ? T((-w1 + rt) / (two * w2)) : T((-w1 - rt) / (two * w2));
248 T q1 = (a1 * (r1 + r2) - l2 * q2 * r1) / (l1 * r2);
249
250 const T tol = num_traits<T>::from_double(1e-8);
251 if (!(q1 >= tol && q1 <= one + tol && q2 >= tol && q2 <= one + tol))
252 throw NumericError("m3pp22_fitc_approx_cov_multiclass: the marking probabilities left "
253 "the unit box");
254 if (q1 < zero) q1 = zero;
255 if (q1 > one) q1 = one;
256 if (q2 < zero) q2 = zero;
257 if (q2 > one) q2 = one;
258
259 Matrix<T> Dc1(2, 2, zero), Dc2(2, 2, zero);
260 Dc1(0, 0) = q1 * l1;
261 Dc1(1, 1) = q2 * l2;
262 Dc2(0, 0) = (one - q1) * l1;
263 Dc2(1, 1) = (one - q2) * l2;
264 res.mmap.Dc.push_back(Dc1);
265 res.mmap.Dc.push_back(Dc2);
266 res.sigma = sigma;
267 res.root = root;
268 res.clamped = num_abs(T(sigma - st3)) > num_traits<T>::from_double(1e-12);
269 return res;
270}
271
272/**
273 * Fit the underlying MMPP(2) by optimization, then apply the covariance split.
274 *
275 * @param a,bt1,bt2,binf,m3t2,t1,t2 the aggregate counting characteristics
276 * @param ai the rates of the two classes, which must sum to a
277 * @param st3 the requested count covariance between them at t3
278 * @param t3 the third time scale
279 * @param opt tuning of the underlying MMPP(2) solve
280 */
281template <class T>
282M3pp22FitcCovResult<T> m3pp22_fitc_approx_cov(const T& a, const T& bt1, const T& bt2, const T& binf,
283 const T& m3t2, const T& t1, const T& t2,
284 const std::vector<T>& ai, const T& st3, const T& t3,
285 const AugLagOptions<T>& opt) {
287 "m3pp22_fitc_approx_cov requires transcendental arithmetic");
288 T asum = num_traits<T>::from_int(0);
289 for (std::size_t i = 0; i < ai.size(); ++i) asum += ai[i];
290 if (num_abs(T(a - asum)) > num_traits<T>::from_double(1e-8))
291 throw InputError("m3pp22_fitc_approx_cov: inconsistent per-class arrival rates");
292
293 const Mmpp2FitcApproxResult<T> base = mmpp2_fitc_approx(a, bt1, bt2, binf, m3t2, t1, t2, opt);
294 return m3pp22_fitc_approx_cov_multiclass(base.map, ai, st3, t3);
295}
296
297/** m3pp22_fitc_approx_cov with the default tuning of the MMPP(2) solve. */
298template <class T>
299M3pp22FitcCovResult<T> m3pp22_fitc_approx_cov(const T& a, const T& bt1, const T& bt2, const T& binf,
300 const T& m3t2, const T& t1, const T& t2,
301 const std::vector<T>& ai, const T& st3,
302 const T& t3) {
304 opt.ctol = num_traits<T>::from_double(1e-12);
305 return m3pp22_fitc_approx_cov(a, bt1, bt2, binf, m3t2, t1, t2, ai, st3, t3, opt);
306}
307
308} // namespace mam
309} // namespace line
310
311#endif // LINE_API_MAM_M3PP22_FITC_COV_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Scalar helpers shared by the MAP/PH moment-matching headers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
MMPP(2) fitted to counting-process characteristics by optimization (matlab/lib/kpctoolbox/mmpp/mmpp2_...
Mmpp2FitcApproxResult< T > mmpp2_fitc_approx(const T &a, const T &bt1, const T &bt2, const T &binf, const T &m3t2, const T &t1, const T &t2, const AugLagOptions< T > &opt)
Fit an MMPP(2) to counting characteristics.
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.
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...
T num_abs(const T &v)
Definition number.h:172
AugLagOptions< T > auglag_defaults()
Defaults: rho0 = 10, growth 10, feasibility 1e-10, 50 outer iterations.
Definition auglag.h:81
Number-type abstraction for the templated API port.
Tuning of the outer multiplier iteration.
Definition auglag.h:68
Result of the covariance-matching M3PP(2, 2) fits.
bool clamped
the requested covariance lay outside the feasible interval
Mmap< T > mmap
the fitted M3PP(2, 2)
T sigma
the covariance actually realised, after clamping
int root
1 or 2, which root of the quadratic was taken; 0 when degenerate
bool degenerate
the underlying process was Poisson, or there is a single class
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
Result of mmpp2_fitc_approx.
Map< T > map
the fitted MMPP(2), rescaled to rate a