LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
map2_fit.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_MAP2_FIT_H
6#define LINE_API_MAM_MAP2_FIT_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Explicit inverse characterization of a second-order acyclic MAP
12 * (matlab/lib/kpctoolbox/map/map2_fit.m).
13 *
14 * Implements A. Heindl, G. Horvath, K. Gross, "Explicit inverse
15 * characterization of acyclic MAPs of second order": given the first three
16 * moments e1, e2, e3 and the lag-1 autocorrelation parameter g2, the two
17 * canonical forms (hyperexponential for h2 > 0, hypoexponential for
18 * -1/4 <= h2 < 0) are written down directly from the normalized moments
19 * h2 = (r2 - r1^2)/r1^2, h3 = (r3 r1 - r2^2)/r1^4, r_k = e_k/k!.
20 *
21 * e3 may be one of the reference's sentinel values, which pick a third moment
22 * from e1, e2 and g2 instead of matching one:
23 * e3 = -1 maximize the range of feasible correlations
24 * e3 = -2 minimum feasible e3
25 * e3 = -3 maximum feasible e3
26 * e3 in (-1, 0) interpolate between the two extremes with weight |e3|
27 * The reference's e3 = -4 draws that interpolation weight from MATLAB's global
28 * random stream; it is NOT ported, because it cannot be reproduced without
29 * that stream. Pass e3 = -r for the deterministic equivalent with weight r.
30 *
31 * Gated on transcendental arithmetic: c = sqrt(b^2 + 4 h2^3) is taken in every
32 * branch, and the sentinel e3 selections take sqrt(-h2).
33 *
34 * Divergence from the reference, deliberate: MATLAB's h2 == 0 branch builds
35 * the Poisson process for the case h3 == 0 and g2 == 0 but omits the return
36 * statement, so control falls through to the final else and the function
37 * returns an empty MAP with ERR = 30 -- the fitted process is discarded. This
38 * port returns the Poisson process, which is what the branch clearly intends,
39 * and reports err = 0.
40 */
41
45#include "line/num/number.h"
46#include "line/util/error.h"
47#include "line/util/matrix.h"
48
49namespace line {
50namespace mam {
51
52/** Result of map2_fit: the MAP plus the reference's ERR code. */
53template <class T>
55 Map<T> map; ///< valid only when has_map is true
56 bool has_map; ///< false when the characteristics are infeasible
57 int err; ///< 0 ok, -1 fitted but structurally infeasible, else MATLAB ERR
58 T e3_used; ///< the third moment actually matched (differs when a sentinel was passed)
59};
60
61namespace fitdetail {
62
63/** Shared assembly of the two-parameter (a, d1, d2) canonical AMAP(2) form. */
64template <class T>
65Map<T> map2_fit_form(const T& r1, const T& h2, const T& h3, const T& b, const T& c, const T& g2,
66 const T& a) {
67 const T two = num_traits<T>::from_int(2);
68 const T one = num_traits<T>::from_int(1);
69 const T den = (one - a) * (two * h2 + b - c) + two * c;
70 if (den == num_traits<T>::from_int(0)) throw NumericError("map2_fit: degenerate canonical form");
71 const T d1 = ((one - a) * (two * h2 * g2 + b - c) + g2 * (b + c) - (b - c)) / den;
72 const T d2 = ((g2 - one) * (b - c)) / den;
73 const T s = one / (two * r1 * h3);
74 const T u = two * h2 + b - c;
75 const T v = two * h2 + b + c;
76 Map<T> m;
79 m.D0(0, 0) = s * (-u);
80 m.D0(0, 1) = s * (u * (one - a));
81 m.D0(1, 1) = s * (-v);
82 m.D1(0, 0) = s * (u * d1);
83 m.D1(0, 1) = s * (u * (a - d1));
84 m.D1(1, 0) = s * (v * d2);
85 m.D1(1, 1) = s * (v * (one - d2));
86 return m;
87}
88
89/** Diagonal (reversible) hyperexponential form of map2_fit. */
90template <class T>
91Map<T> map2_fit_hyper_diag(const T& r1, const T& h2, const T& h3, const T& b, const T& c,
92 const T& g2) {
93 const T one = num_traits<T>::from_int(1);
94 const T two = num_traits<T>::from_int(2);
95 const T four = num_traits<T>::from_int(4);
96 if (c == num_traits<T>::from_int(0)) throw NumericError("map2_fit: zero discriminant root");
97 const T u = two * h2 + b - c;
98 const T v = two * h2 + b + c;
99 const T s0 = one / (two * r1 * h3);
100 const T s1 = one / (four * r1 * h3);
101 Map<T> m;
102 m.D0 = Matrix<T>(2, 2, num_traits<T>::from_int(0));
103 m.D1 = Matrix<T>(2, 2, num_traits<T>::from_int(0));
104 m.D0(0, 0) = s0 * (-u);
105 m.D0(1, 1) = s0 * (-v);
106 m.D1(0, 0) = s1 * (u * (one - b / c + g2 * (one + b / c)));
107 m.D1(0, 1) = s1 * (u * (one + b / c) * (one - g2));
108 m.D1(1, 0) = s1 * (v * (one - b / c) * (one - g2));
109 m.D1(1, 1) = s1 * (v * (one + b / c + g2 * (one - b / c)));
110 return m;
111}
112
113} // namespace fitdetail
114
115/** Fit an AMAP(2) to (e1, e2, e3, g2); see the header comment for e3 sentinels. */
116template <class T>
117Map2FitResult<T> map2_fit(const T& e1, const T& e2, const T& e3_in, const T& g2) {
118 static_assert(num_traits<T>::has_transcendental, "map2_fit requires transcendental arithmetic");
119 using fitdetail::num_sqrt;
120 using fitdetail::pw;
121
122 const T zero = num_traits<T>::from_int(0);
123 const T one = num_traits<T>::from_int(1);
124 const T two = num_traits<T>::from_int(2);
125 const T three = num_traits<T>::from_int(3);
126 const T six = num_traits<T>::from_int(6);
127 const T twelve = num_traits<T>::from_int(12);
128
130 res.has_map = false;
131 res.err = 0;
132
133 const T r1 = e1;
134 const T r2 = e2 / two;
135 if (r1 == zero) throw InputError("map2_fit: zero first moment");
136 const T h2 = (r2 - r1 * r1) / (r1 * r1);
137
138 T e3 = e3_in;
139 const T scv = (e2 - e1 * e1) / (e1 * e1);
140 const T c32 = three / two;
141 if (e3 == -one) {
142 if (one <= scv && scv < three) {
143 if (g2 < zero) {
144 const T h3 = h2 - h2 * h2;
145 e3 = twelve * pw(e1, 3) * h2 + six * pw(e1, 3) * h3 +
146 six * pw(e1, 3) * (one + h2 * h2);
147 } else {
148 e3 = (c32 + num_traits<T>::from_double(1e-3)) * e2 * e2 / e1;
149 }
150 } else if (three <= scv) {
151 e3 = (c32 + num_traits<T>::from_double(1e-3)) * e2 * e2 / e1;
152 } else if (zero < scv && scv < one) {
153 e3 = (one + num_traits<T>::from_double(1e-10)) *
154 (twelve * pw(e1, 3) * h2 +
155 six * pw(e1, 3) * (h2 * (one - h2 - two * num_sqrt(T(-h2)))) +
156 six * pw(e1, 3) * (one + h2 * h2));
157 }
158 } else if (e3 == -two) {
159 if (one <= scv) {
160 e3 = (c32 + num_traits<T>::from_double(1e-6)) * e2 * e2 / e1;
161 } else if (zero < scv && scv < one) {
162 const T h3 = h2 * (one - h2 - two * num_sqrt(T(-h2)));
163 e3 = six * pw(e1, 3) * (h2 * h2 + h3);
164 }
165 } else if (e3 == -three) {
166 if (one <= scv) {
168 } else if (zero < scv && scv < one) {
169 const T h3 = h2 * h2;
170 e3 = six * pw(e1, 3) * (h2 * h2 + h3);
171 }
172 } else if (e3 > -one && e3 < zero) {
173 const T r = num_abs(e3);
174 if (one <= scv) {
175 e3 = r * (c32 + num_traits<T>::from_double(1e-6)) * e2 * e2 / e1 +
176 (one - r) * num_traits<T>::from_double(1e6);
177 } else if (zero < scv && scv < one) {
178 const T h3 = r * h2 * (one - h2 - two * num_sqrt(T(-h2))) + (one - r) * (h2 * h2);
179 e3 = six * pw(e1, 3) * (h2 * h2 + h3);
180 }
181 }
182 res.e3_used = e3;
183
184 const T r3 = e3 / six;
185 const T h3 = (r3 * r1 - r2 * r2) / pw(r1, 4);
186 const T b = h3 + h2 * h2 - h2;
187 const T crad = b * b + num_traits<T>::from_int(4) * pw(h2, 3);
188 if (crad < zero) throw NumericError("map2_fit: negative discriminant b^2 + 4 h2^3");
189 const T c = num_sqrt(crad);
190
191 if (r1 <= zero) {
192 res.err = 10; // mean out of bounds
193 return res;
194 }
195
196 if (h2 == zero) {
197 if (h3 == zero && g2 == zero) {
198 // See the header note: MATLAB drops this result by falling through.
199 res.map = map_exponential_mean(e1);
200 res.has_map = true;
201 res.err = 0;
202 } else {
203 res.err = 20; // correlated exponential
204 }
205 return res;
206 }
207
208 const T qhypo_lo = h2 * (one - h2 - two * num_sqrt(T(-h2)));
209 const bool hypo_region = (num_traits<T>::from_rational(-1, 4) <= h2 && h2 < zero &&
210 qhypo_lo <= h3 && h3 <= -(h2 * h2));
211
212 if (h2 > zero && h3 > zero) {
213 if (b >= zero) {
214 if ((b - c) / (b + c) <= g2 && g2 < one) {
215 res.map = fitdetail::map2_fit_hyper_diag(r1, h2, h3, b, c, g2);
216 res.has_map = true;
217 } else {
218 res.err = 51;
219 return res;
220 }
221 } else {
222 if (zero <= g2 && g2 < one) {
223 res.map = fitdetail::map2_fit_hyper_diag(r1, h2, h3, b, c, g2);
224 res.has_map = true;
225 } else if (-(h3 + h2 * h2) / h2 <= g2 && g2 < zero) {
226 const T a = (h3 + h2 * h2) / h2;
227 res.map = fitdetail::map2_fit_form(r1, h2, h3, b, c, g2, a);
228 res.has_map = true;
229 } else {
230 res.err = 52;
231 return res;
232 }
233 }
234 } else if (hypo_region) {
235 if (g2 >= zero) {
236 const T sq = num_sqrt(T(-h3));
237 if (g2 <= -((h2 + sq) * (h2 + sq)) / h2) {
238 const T a = (two * h2 + b - c) * (h2 + sq) / (two * h2 * sq);
239 res.map = fitdetail::map2_fit_form(r1, h2, h3, b, T(-c), g2, a);
240 res.has_map = true;
241 } else {
242 res.err = 53;
243 return res;
244 }
245 } else {
246 if (g2 >= -(h3 + h2 * h2) / h2) {
247 const T a = (h3 + h2 * h2) / h2;
248 res.map = fitdetail::map2_fit_form(r1, h2, h3, b, T(-c), g2, a);
249 res.has_map = true;
250 } else {
251 res.err = 54;
252 return res;
253 }
254 }
255 } else {
256 res.err = (h2 > zero && h3 < zero) ? 40 : 30; // h3 / h2 out of bounds
257 return res;
258 }
259
260 if (res.has_map && !map_isfeasible(res.map, T(num_traits<T>::from_double(1e-10)))) res.err = -1;
261 return res;
262}
263
264/** Three-argument form: map2_fit(e1, e2, g2), i.e. e3 selected automatically. */
265template <class T>
266Map2FitResult<T> map2_fit(const T& e1, const T& e2, const T& g2) {
267 return map2_fit(e1, e2, T(-num_traits<T>::from_int(1)), g2);
268}
269
270} // namespace mam
271} // namespace line
272
273#endif // LINE_API_MAM_MAP2_FIT_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...
MAP constructors and structural transformations.
Dense matrix and non-owning view.
Map2FitResult< T > map2_fit(const T &e1, const T &e2, const T &e3_in, const T &g2)
Fit an AMAP(2) to (e1, e2, e3, g2); see the header comment for e3 sentinels.
Definition map2_fit.h:117
bool map_isfeasible(const Map< T > &m, const T &tol)
Structural feasibility of a MAP within a tolerance (map_isfeasible.m): off-diagonal D0 and all of D1 ...
Map< T > map_exponential_mean(const T &mean)
Poisson process with the given mean inter-arrival time (map_exponential.m).
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Result of map2_fit: the MAP plus the reference's ERR code.
Definition map2_fit.h:54
bool has_map
false when the characteristics are infeasible
Definition map2_fit.h:56
Map< T > map
valid only when has_map is true
Definition map2_fit.h:55
int err
0 ok, -1 fitted but structurally infeasible, else MATLAB ERR
Definition map2_fit.h:57
T e3_used
the third moment actually matched (differs when a sentinel was passed)
Definition map2_fit.h:58
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