LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
map_mmpp2.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_MAP_MMPP2_H
6#define LINE_API_MAM_MAP_MMPP2_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Fit an MMPP(2) to a mean, an SCV, a skewness and a lag-1 autocorrelation.
12 *
13 * Templated port of matlab/lib/kpctoolbox/map/map_mmpp2.m. An MMPP(2) is the
14 * two-state Markov-modulated Poisson process
15 *
16 * D0 = [-mu00-q01, q01; q10, -mu11-q10], D1 = diag(mu00, mu11),
17 *
18 * and the four rates are the closed-form inverse of its first three moments and
19 * its autocorrelation decay rate G2. That inverse is Maple output: two branches,
20 * the second some 18 KB of algebra in a single expression.
21 *
22 * THE ALGEBRA IS MACHINE-TRANSCRIBED, NOT RETYPED. It was produced by
23 * `cpp/tools/matlab_expr_to_cpp.py`, which parses the MATLAB expression and
24 * re-emits it, then binds the repeated radicals and denominators to `cseN`
25 * temporaries (Maple repeats the same 2 KB radical dozens of times, so the
26 * literal form is both unreadable and O(repeats) to evaluate). The generated
27 * form was checked against the MATLAB source evaluated term by term on 40
28 * random feasible inputs: the relative deviation is EXACTLY zero. Do not
29 * hand-edit the generated blocks; regenerate them.
30 *
31 * THE FEASIBILITY GATES ARE THE REFERENCE'S, and each names what it refuses. An
32 * MMPP(2) is over-dispersed (SCV >= 1) and non-negatively autocorrelated, and at
33 * SCV = 1 exactly the fit is degenerate: G2 divides by (1 - 1/SCV) and every
34 * rate returns NaN, so that boundary is refused by name with a pointer to the
35 * Poisson process it is really asking for. ACF1 = -1 and SKEW = -1 are the
36 * reference's sentinels for "give me the extreme feasible value".
37 *
38 * ARITHMETIC: transcendental, for the radicals.
39 */
40
41#include <cmath>
42
46#include "line/num/number.h"
47#include "line/util/error.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace mam {
52
53/**
54 * @brief Fit an MMPP(2) to a mean, an SCV, a skewness and a lag-1
55 * autocorrelation.
56 *
57 * @param MEAN mean inter-arrival time
58 * @param SCV_in squared coefficient of variation, which must exceed one
59 * @param SKEW skewness, or -1 for the minimum-skewness fit
60 * @param ACF1 lag-1 autocorrelation, or -1 for the maximum feasible value
61 */
62template <class T>
63Map<T> map_mmpp2(const T& MEAN, const T& SCV_in, const T& SKEW, const T& ACF1) {
65 "map_mmpp2 inverts the moment equations through radicals");
66 using fitdetail::num_sqrt;
67 using fitdetail::pw;
68 const T one = num_traits<T>::from_int(1), two = num_traits<T>::from_int(2);
69 const T zero = num_traits<T>::from_int(0);
70 const double FEASTOLD = std::pow(10.0, -static_cast<double>(map_feastol()));
71 const T FEASTOL = num_traits<T>::from_double(FEASTOLD);
72
73 const T E1 = MEAN;
74 const T E2 = T((one + SCV_in) * E1 * E1);
75 T E3 = T(-(two * pw(E1, 3) - num_traits<T>::from_int(3) * E1 * E2 -
76 SKEW * num_sqrt(T(pw(T(E2 - E1 * E1), 3)))));
77
78 if (SCV_in < one - FEASTOL)
79 throw InputError(
80 "map_mmpp2: the SCV is infeasible, the inter-arrival times of an MMPP(2) are "
81 "over-dispersed (SCV >= 1)");
82 if (num_abs(T(SCV_in - one)) <= FEASTOL)
83 throw InputError(
84 "map_mmpp2: SCV = 1 is the Poisson boundary, where the MMPP(2) fit is degenerate: "
85 "the decay rate G2 = ACF1/(1 - 1/SCV)/0.5 divides by zero and every rate comes back "
86 "NaN. Use map_exponential_mean for a Poisson process");
87
88 const T RHO0MAX = T(num_traits<T>::from_rational(1, 2) * (one - one / SCV_in));
89 const bool acfAuto = (ACF1 == num_traits<T>::from_int(-1));
90 if (!acfAuto) {
91 if (ACF1 < -FEASTOL)
92 throw InputError(
93 "map_mmpp2: a negative ACF1 is infeasible, an MMPP(2) cannot be negatively "
94 "autocorrelated. Pass ACF1 = -1 to request the maximum feasible autocorrelation");
95 if (ACF1 > RHO0MAX + FEASTOL)
96 throw InputError(
97 "map_mmpp2: ACF1 exceeds the maximum lag-1 autocorrelation feasible at this SCV. "
98 "Pass ACF1 = -1 to request it");
99 }
100 const T G2 = acfAuto
101 ? T(one - num_traits<T>::from_double(10.0 * FEASTOLD))
102 : T(ACF1 / (one - one / SCV_in) / num_traits<T>::from_rational(1, 2));
103
104 if (SKEW == num_traits<T>::from_int(-1) && SCV_in > one)
105 E3 = T((num_traits<T>::from_rational(3, 2) + num_traits<T>::from_double(0.001)) * E2 * E2 /
106 E1);
107 const T E3MIN = T(num_traits<T>::from_rational(3, 2) * E2 * E2 / E1);
108 if (E3 < E3MIN - FEASTOL)
109 throw InputError(
110 "map_mmpp2: the requested skewness gives a third moment below the minimum feasible at "
111 "this SCV. Pass SKEW = -1 for the minimum-skewness fit");
112
113 // The reference recomputes SCV from the moments before the algebra.
114 const T SCV = T((E2 - E1 * E1) / (E1 * E1));
115
116 T mu00v, mu11v, q01v, q10v;
117 if (num_traits<T>::to_double(G2) < 1e-6) {
118 // ---- BEGIN GENERATED (matlab_expr_to_cpp.py, map_mmpp2.m G2 < 1e-6) ----
119 const T cse2 = ((num_traits<T>::from_int(6) * pw(E1, 3)) * SCV);
120 const T cse3 = (num_traits<T>::from_int(3) * pw(E1, 3));
121 const T cse0 = (((cse2 + (cse3 * pw(SCV, 2))) + cse3) - (num_traits<T>::from_int(2) * E3));
122 const T cse1 = (cse2 - E3);
123
124 const T mu00 = (((num_traits<T>::from_int(2) * cse1) / E1) / cse0);
125
126 const T mu11 = num_traits<T>::from_int(0);
127
128 const T q01 = (((((num_traits<T>::from_int(9) * pw(E1, 5)) * (SCV - num_traits<T>::from_int(1))) * ((pw(SCV, 2) - (num_traits<T>::from_int(2) * SCV)) + num_traits<T>::from_int(1))) / cse1) / cse0);
129
130 const T q10 = ((((-num_traits<T>::from_int(3)) * (SCV - num_traits<T>::from_int(1))) * pw(E1, 2)) / cse1);
131 // ---- END GENERATED ----
132 mu00v = mu00;
133 mu11v = mu11;
134 q01v = q01;
135 q10v = q10;
136 } else {
137 // ---- BEGIN GENERATED (matlab_expr_to_cpp.py, map_mmpp2.m else branch) ----
138 const T cse45 = (num_traits<T>::from_int(18) * pw(E1, 6));
139 const T cse35 = (cse45 * G2);
140 const T cse49 = (num_traits<T>::from_int(6) * pw(E1, 3));
141 const T cse39 = (cse49 * G2);
142 const T cse43 = (num_traits<T>::from_int(12) * pw(E1, 3));
143 const T cse44 = ((num_traits<T>::from_int(6) * G2) * SCV);
144 const T cse50 = (num_traits<T>::from_int(9) * pw(E1, 6));
145 const T cse24 = (((((((((((pw(E3, 2) - ((cse43 * SCV) * E3)) + (cse39 * E3)) - ((cse44 * pw(E1, 3)) * E3)) + (((num_traits<T>::from_int(18) * G2) * pw(SCV, 3)) * pw(E1, 6))) - (cse35 * pw(SCV, 2))) + (cse50 * pw(G2, 2))) + ((num_traits<T>::from_int(36) * pw(E1, 6)) * pw(SCV, 2))) + (cse35 * SCV)) - ((cse45 * SCV) * pw(G2, 2))) + ((cse50 * pw(SCV, 2)) * pw(G2, 2))) - cse35);
146 const T cse34 = (cse49 * SCV);
147 const T cse48 = (num_traits<T>::from_int(3) * pw(E1, 3));
148 const T cse38 = (cse48 * G2);
149 const T cse42 = ((-num_traits<T>::from_int(3)) * pw(E1, 3));
150 const T cse23 = (((((cse42 * G2) + (cse38 * SCV)) - cse34) + E3) + num_sqrt(cse24));
151 const T cse25 = ((((cse42 * pw(SCV, 2)) - cse34) - cse48) + (num_traits<T>::from_int(2) * E3));
152 const T cse11 = ((cse43 * cse23) / cse25);
153 const T cse0 = (cse11 * pw(G2, 2));
154 const T cse1 = (cse11 * pw(G2, 3));
155 const T cse2 = ((((num_traits<T>::from_int(24) * pw(E1, 3)) * cse23) / cse25) * pw(G2, 2));
156 const T cse13 = (((num_traits<T>::from_int(36) * cse23) * pw(E1, 5)) / cse25);
157 const T cse3 = (cse13 * pw(G2, 2));
158 const T cse4 = ((((num_traits<T>::from_int(18) * cse23) * pw(E1, 5)) / cse25) * pw(G2, 3));
159 const T cse18 = (((num_traits<T>::from_int(6) * cse23) * pw(E1, 2)) / cse25);
160 const T cse5 = (cse18 * pw(G2, 2));
161 const T cse46 = (num_traits<T>::from_int(18) * pw(E1, 3));
162 const T cse6 = (((cse46 * cse23) / cse25) * G2);
163 const T cse7 = ((((num_traits<T>::from_int(27) * pw(E1, 3)) * cse23) / cse25) * G2);
164 const T cse17 = (((num_traits<T>::from_int(3) * cse23) * pw(E1, 2)) / cse25);
165 const T cse8 = (cse17 * G2);
166 const T cse22 = (num_traits<T>::from_int(9) * cse23);
167 const T cse9 = (((cse22 * pw(E1, 5)) / cse25) * G2);
168 const T cse15 = ((cse48 * cse23) / cse25);
169 const T cse10 = (cse15 * G2);
170 const T cse12 = (((num_traits<T>::from_int(12) * cse23) * pw(E1, 2)) / cse25);
171 const T cse51 = (num_traits<T>::from_int(9) * pw(E1, 3));
172 const T cse14 = ((cse51 * cse23) / cse25);
173 const T cse16 = ((cse49 * cse23) / cse25);
174 const T cse19 = (((num_traits<T>::from_int(4) * cse23) / cse25) * E3);
175 const T cse20 = ((cse23 / cse25) * E3);
176 const T cse21 = ((cse23 / E1) / cse25);
177 const T cse27 = (cse43 * pw(G2, 2));
178 const T cse26 = (cse27 * SCV);
179 const T cse28 = (cse43 * pw(G2, 3));
180 const T cse29 = (cse46 * pw(G2, 2));
181 const T cse47 = (num_traits<T>::from_int(18) * pw(E1, 5));
182 const T cse30 = (cse47 * pw(G2, 3));
183 const T cse31 = ((num_traits<T>::from_int(27) * pw(E1, 5)) * pw(G2, 2));
184 const T cse32 = ((num_traits<T>::from_int(6) * pw(E1, 2)) * pw(G2, 2));
185 const T cse52 = (num_traits<T>::from_int(3) * pw(E1, 2));
186 const T cse41 = (cse52 * G2);
187 const T cse33 = (cse41 * E3);
188 const T cse36 = (cse43 * G2);
189 const T cse37 = (cse47 * G2);
190 const T cse40 = (cse51 * G2);
191
192 const T mu00 = (((G2 * (((((((((((((((((-num_traits<T>::from_int(4)) * E3) * G2) + (cse19 * G2)) - cse6) - (cse6 * pw(SCV, 2))) - cse27) - (cse0 * SCV)) + ((cse11 * G2) * SCV)) + (cse36 * pw(SCV, 2))) - (cse14 * SCV)) + cse15) + cse26) + (cse14 * pw(SCV, 2))) + cse36) + cse0) - (cse15 * pw(SCV, 3)))) / ((((((((((((((((((((((((((cse28 * SCV) + ((cse48 * pw(SCV, 3)) * G2)) - cse28) + (cse29 * pw(SCV, 2))) - cse38) + (cse7 * pw(SCV, 2))) - (cse40 * pw(SCV, 2))) + cse29) - cse26) + (cse40 * SCV)) - (cse1 * SCV)) - ((cse14 * pw(SCV, 3)) * G2)) - (cse2 * pw(SCV, 2))) - (cse20 * pw(SCV, 2))) + (cse19 * pw(G2, 2))) + cse1) - cse20) + ((((num_traits<T>::from_int(2) * cse23) / cse25) * E3) * SCV)) + (cse14 * G2)) + (cse2 * SCV)) - (cse7 * SCV)) + (cse16 * SCV)) - (cse11 * pw(SCV, 2))) - cse2) + (cse16 * pw(SCV, 3))) - ((num_traits<T>::from_int(4) * E3) * pw(G2, 2)))) / E1);
193
194 const T mu11 = cse21;
195
196 const T q01 = ((((-num_traits<T>::from_int(3)) * pw(E1, 2)) * ((((((((((((((((((((((-num_traits<T>::from_int(6)) * cse23) * pw(E1, 2)) / cse25) * SCV) + ((cse12 * G2) * SCV)) - (cse44 * pw(E1, 2))) - cse8) + (cse21 * E3)) + cse41) + (cse18 * pw(SCV, 2))) - ((((cse22 * pw(E1, 2)) / cse25) * pw(SCV, 2)) * G2)) + (cse41 * pw(SCV, 2))) - ((((E3 * cse23) / E1) / cse25) * SCV)) - (cse5 * SCV)) + (cse32 * SCV)) + (cse17 * pw(G2, 2))) - ((((G2 * cse23) / E1) / cse25) * E3)) - (cse52 * pw(G2, 2))) + ((cse17 * pw(SCV, 2)) * pw(G2, 2))) - ((cse52 * pw(SCV, 2)) * pw(G2, 2))) + (((((G2 * SCV) * cse23) / E1) / cse25) * E3))) / ((((((((((((((((((((((((((((((-num_traits<T>::from_int(45)) * cse23) * pw(E1, 5)) / cse25) * G2) * pw(SCV, 2)) + (((num_traits<T>::from_int(18) * pw(G2, 2)) * pw(E1, 5)) * SCV)) + cse30) - (cse31 * pw(SCV, 2))) + (cse32 * E3)) - cse31) - (cse30 * SCV)) - (cse37 * SCV)) + (cse37 * pw(SCV, 2))) + cse33) - (cse33 * SCV)) + (cse21 * pw(E3, 2))) + ((cse8 * SCV) * E3)) - (cse3 * SCV)) + cse3) + (cse13 * pw(SCV, 2))) + (((((num_traits<T>::from_int(45) * cse23) * pw(E1, 5)) / cse25) * G2) * SCV)) - ((cse12 * SCV) * E3)) - (cse8 * E3)) + (cse9 * pw(SCV, 3))) + (cse3 * pw(SCV, 2))) - (cse5 * E3)) + (cse4 * SCV)) - cse4) - cse9));
197
198 const T q10 = ((((num_traits<T>::from_int(3) * ((((((((((((((((((cse42 * cse23) / cse25) * pw(SCV, 3)) - (cse10 * pw(SCV, 2))) + (cse49 * pw(SCV, 2))) + (cse38 * pw(SCV, 2))) + (cse15 * pw(SCV, 2))) + ((cse16 * G2) * SCV)) - (E3 * SCV)) - cse34) + (cse20 * SCV)) - (cse39 * SCV)) - (cse15 * SCV)) - cse20) + cse38) - cse10) + cse15) + E3)) * pw(E1, 2)) * ((-num_traits<T>::from_int(1)) + G2)) / cse24);
199 // ---- END GENERATED ----
200 mu00v = mu00;
201 mu11v = mu11;
202 q01v = q01;
203 q10v = q10;
204 }
205
206 const T rates[4] = {mu00v, mu11v, q01v, q10v};
207 for (int i = 0; i < 4; ++i) {
208 const double v = num_traits<T>::to_double(rates[i]);
209 if (std::isnan(v) || v < -FEASTOLD)
210 throw InputError(
211 "map_mmpp2: the requested (MEAN, SCV, SKEW, ACF1) is not MMPP(2)-feasible; the "
212 "fit gives a rate that is not a MAP");
213 }
214 // The reference clamps a rate that is negative only within the tolerance.
215 const T a = mu00v > zero ? mu00v : zero;
216 const T b = mu11v > zero ? mu11v : zero;
217 const T c = q01v > zero ? q01v : zero;
218 const T d = q10v > zero ? q10v : zero;
219
220 Map<T> m;
221 m.D0 = Matrix<T>(2, 2, zero);
222 m.D1 = Matrix<T>(2, 2, zero);
223 m.D0(0, 0) = T(-a - c);
224 m.D0(0, 1) = c;
225 m.D0(1, 0) = d;
226 m.D0(1, 1) = T(-b - d);
227 m.D1(0, 0) = a;
228 m.D1(1, 1) = b;
229 return m;
230}
231
232} // namespace mam
233} // namespace line
234
235#endif // LINE_API_MAM_MAP_MMPP2_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Analytic distances between continuous-time MAPs.
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.
Map< T > map_mmpp2(const T &MEAN, const T &SCV_in, const T &SKEW, const T &ACF1)
Fit an MMPP(2) to a mean, an SCV, a skewness and a lag-1 autocorrelation.
Definition map_mmpp2.h:63
int map_feastol()
Tolerance exponent shared by the KPC feasibility checks (map_feastol.m).
Definition map_moment.h:49
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
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