LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
maph2m_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_MAPH2M_FIT_H
6#define LINE_API_MAM_MAPH2M_FIT_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Fit a MAPH(2,m): a second-order acyclic phase-type marked with m classes.
12 *
13 * Templated port of matlab/lib/m3a/m3a/maph2/maph2m_fit.m,
14 * maph2m_fit_multiclass.m, maph2m_fit_mmap.m and maph2m_fit_trace.m.
15 *
16 * The construction separates the TIMING from the MARKING. An APH(2) in
17 * canonical acyclic form fixes the inter-arrival law from (M1, M2, M3); the
18 * class marking then splits each of its two exit flows among the m classes with
19 * probabilities q(j,c), and those are the only free parameters left. Writing
20 * h1, h2 for the two phase means and r1 for the branch probability out of phase
21 * one, the per-class BACKWARD moment is affine in the split, so
22 *
23 * q(j,c) = fB(c) q_b(j,c) + q_0(j,c),
24 *
25 * with q_b and q_0 the coefficients the reference derives. The fit is then a
26 * QUADRATIC PROGRAM in the achieved backward moments fB: minimize
27 * sum_c w(c) (fB(c)/B(c) - 1)^2 subject to each q(j,.) being a probability
28 * vector. Its Hessian is diagonal and positive, so the program is convex and
29 * its unconstrained minimizer is fB = B; the constraints are what make the
30 * answer differ from the target.
31 *
32 * THE DEGENERATE FORM HAS NO FREEDOM AT ALL. When r1 = 1 the second phase is
33 * unreachable except through the first, both exit flows see the same class law,
34 * and the only thing that can be matched is the class probability vector p.
35 * The reference detects that at |1 - r1| < 1e-6 and sets q(1,c) = q(2,c) = p(c)
36 * without solving anything; reproduced here, because solving the program on a
37 * singular coefficient set returns whatever the solver's regularization
38 * happens to give.
39 *
40 * THE SOLVER IS NOT quadprog. MATLAB runs an interior-point QP; this port uses
41 * `line/util/auglag.h`, whose header states the acceptance contract for exactly
42 * this substitution. The acceptance here is the specification: the fitted MAPH
43 * reproduces p exactly (it is an equality constraint), its inter-arrival moments
44 * are the APH's, and its backward moments approach B as closely as the
45 * feasibility of the split allows. Iterates and multipliers are NOT comparable
46 * with MATLAB's.
47 *
48 * `maph2m_fit` runs the whole thing once per APH(2) form that `aph2_fit`
49 * returns and keeps the one whose backward moments land closest, which is why
50 * the fitter needs `aph2_fit`'s full list and not just its selected form.
51 *
52 * ARITHMETIC: transcendental, through aph2_fit and the solver.
53 */
54
55#include <cmath>
56#include <cstddef>
57#include <vector>
58
65#include "line/num/number.h"
66#include "line/util/auglag.h"
67#include "line/util/error.h"
68#include "line/util/matrix.h"
69
70namespace line {
71namespace mam {
72
73/** The fitted MAPH and the backward moments it actually achieved. */
74template <class T>
77 std::vector<T> fB; ///< achieved per-class backward moments
78};
79
80/**
81 * Mark a canonical acyclic APH(2) with m classes.
82 *
83 * @param aph the APH(2), in canonical acyclic form
84 * @param p per-class probabilities, summing to one
85 * @param B per-class target backward moments
86 * @param classWeights per-class weights in the objective; empty means uniform
87 */
88template <class T>
89Maph2mFitResult<T> maph2m_fit_multiclass(const Map<T>& aph, const std::vector<T>& p,
90 const std::vector<T>& B,
91 const std::vector<T>& classWeights = std::vector<T>()) {
93 "maph2m_fit_multiclass solves a quadratic program");
94 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
95 if (aph.D0.rows() != 2) throw InputError("maph2m_fit_multiclass: the APH must be second order");
96 if (!(num_abs(T(aph.D0(1, 0))) <= zero))
97 throw InputError("maph2m_fit_multiclass: the APH must be acyclic");
98 if (!(num_abs(T(aph.D1(0, 1))) <= zero) || !(num_abs(T(aph.D1(1, 1))) <= zero))
99 throw InputError("maph2m_fit_multiclass: the APH must be in canonical acyclic form");
100
101 const std::size_t k = p.size();
102 if (k == 0) throw InputError("maph2m_fit_multiclass: no classes given");
103 if (B.size() != k)
104 throw InputError("maph2m_fit_multiclass: one backward moment per class is required");
105 std::vector<T> w = classWeights;
106 if (w.empty()) w.assign(k, one);
107 if (w.size() != k) throw InputError("maph2m_fit_multiclass: one weight per class is required");
108
109 const T h1 = T(-one / aph.D0(0, 0));
110 const T h2 = T(-one / aph.D0(1, 1));
111 const T r1 = T(aph.D0(0, 1) * h1);
112
114 out.maph.D0 = aph.D0;
115 out.maph.D1 = aph.D1;
116 out.maph.Dc.assign(k, Matrix<T>(2, 2, zero));
117
118 std::vector<std::vector<T>> q(2, std::vector<T>(k, zero));
119 bool solved = false;
120
121 const double degentol = 1e-6, feastol = 1e-8;
122 if (std::fabs(1.0 - num_traits<T>::to_double(r1)) < degentol) {
123 // Degenerate: one degree of freedom, so only the class probabilities.
124 for (std::size_t c = 0; c < k; ++c) {
125 q[0][c] = p[c];
126 q[1][c] = p[c];
127 }
128 } else {
129 std::vector<std::vector<T>> qb(2, std::vector<T>(k, zero));
130 std::vector<std::vector<T>> q0(2, std::vector<T>(k, zero));
131 for (std::size_t c = 0; c < k; ++c) {
132 qb[0][c] = T(p[c] * (one / (h2 * (r1 - one))));
133 q0[0][c] = T(p[c] * (-(h1 + h2) / (h2 * (r1 - one))));
134 qb[1][c] = T(p[c] * (one / (h2 * r1)));
135 q0[1][c] = T(p[c] * (-h1 / (h2 * r1)));
136 }
137
138 // min sum_c w(c) (x(c)/B(c) - 1)^2, i.e. the reference's (1/2)x'Hx+h'x
139 // shifted by a constant, over the two equality and 4k inequality rows.
140 auto fobj = [&](const std::vector<T>& x) {
141 T s = zero;
142 for (std::size_t c = 0; c < k; ++c) {
143 const T r = T(x[c] / B[c] - one);
144 s += w[c] * r * r;
145 }
146 return s;
147 };
148 auto heq = [&](const std::vector<T>& x) {
149 std::vector<T> v(2, zero);
150 for (std::size_t j = 0; j < 2; ++j) {
151 T s = zero;
152 for (std::size_t c = 0; c < k; ++c) s += qb[j][c] * x[c] + q0[j][c];
153 v[j] = T(s - one); // each row of q must sum to one
154 }
155 return v;
156 };
157 auto gineq = [&](const std::vector<T>& x) {
158 std::vector<T> v;
159 v.reserve(4 * k);
160 for (std::size_t c = 0; c < k; ++c)
161 for (std::size_t j = 0; j < 2; ++j) {
162 const T qq = T(qb[j][c] * x[c] + q0[j][c]);
163 v.push_back(T(qq - one)); // q <= 1
164 v.push_back(-qq); // q >= 0
165 }
166 return v;
167 };
168
169 std::vector<T> x0 = B;
170 std::vector<Bound<T>> bounds(k);
171 for (std::size_t c = 0; c < k; ++c) {
172 bounds[c].lo = num_traits<T>::from_double(1e-6);
173 bounds[c].hi = num_traits<T>::from_double(1e6);
174 if (x0[c] < bounds[c].lo) x0[c] = bounds[c].lo;
175 if (x0[c] > bounds[c].hi) x0[c] = bounds[c].hi;
176 }
177 const AugLagResult<T> r = auglag(fobj, heq, gineq, x0, bounds);
178 out.fB = r.x;
179 solved = true;
180 for (std::size_t c = 0; c < k; ++c)
181 for (std::size_t j = 0; j < 2; ++j) q[j][c] = T(r.x[c] * qb[j][c] + q0[j][c]);
182 }
183
184 // The reference's feasibility gate, then its clamp-and-renormalize.
185 for (std::size_t j = 0; j < 2; ++j) {
186 T lo = q[j][0], s = zero;
187 for (std::size_t c = 0; c < k; ++c) {
188 if (q[j][c] < lo) lo = q[j][c];
189 s += q[j][c];
190 }
191 if (num_traits<T>::to_double(lo) < -feastol ||
192 num_traits<T>::to_double(s) > 1.0 + feastol)
193 throw NumericError(
194 "maph2m_fit_multiclass: feasibility could not be restored; the requested class "
195 "probabilities and backward moments admit no valid split of the APH(2) exit flows");
196 }
197 for (std::size_t j = 0; j < 2; ++j) {
198 T s = zero;
199 for (std::size_t c = 0; c < k; ++c) {
200 if (q[j][c] < zero) q[j][c] = zero;
201 s += q[j][c];
202 }
203 if (!(num_traits<T>::to_double(s) > 0.0))
204 throw NumericError("maph2m_fit_multiclass: a split lost all its mass");
205 for (std::size_t c = 0; c < k; ++c) q[j][c] = T(q[j][c] / s);
206 }
207
208 // Dc = D1 .* [q(1,c) 0; q(2,c) 0]: only the first column of D1 is non-zero
209 // in canonical acyclic form, so the split acts on the restart flow alone.
210 for (std::size_t c = 0; c < k; ++c) {
211 out.maph.Dc[c](0, 0) = T(out.maph.D1(0, 0) * q[0][c]);
212 out.maph.Dc[c](1, 0) = T(out.maph.D1(1, 0) * q[1][c]);
213 }
214 if (!solved) {
215 const std::vector<std::vector<T>> bm =
216 mmap_backward_moment(out.maph, std::vector<unsigned>(1, 1u), true);
217 out.fB.assign(k, zero);
218 for (std::size_t c = 0; c < k; ++c) out.fB[c] = bm[c][0];
219 }
220 return out;
221}
222
223/**
224 * Fit a MAPH(2,m) to three moments, the class probabilities and the per-class
225 * backward moments, trying every APH(2) form and keeping the closest.
226 */
227template <class T>
228Mmap<T> maph2m_fit(const T& M1, const T& M2, const T& M3, const std::vector<T>& p,
229 const std::vector<T>& B) {
230 const Aph2FitResult<T> a = aph2_fit(M1, M2, M3);
231 if (a.aphs.empty()) throw NumericError("maph2m_fit: no APH(2) fits the given moments");
232 Mmap<T> best;
233 double bestErr = 0.0;
234 bool have = false;
235 for (std::size_t j = 0; j < a.aphs.size(); ++j) {
237 try {
238 r = maph2m_fit_multiclass(a.aphs[j], p, B);
239 } catch (const Error&) {
240 continue; // this form admits no valid split; the next one may
241 }
242 double err = 0.0;
243 for (std::size_t c = 0; c < p.size(); ++c) {
244 const double d = num_traits<T>::to_double(T(r.fB[c] / B[c])) - 1.0;
245 err += d * d;
246 }
247 if (!have || err < bestErr) {
248 bestErr = err;
249 best = r.maph;
250 have = true;
251 }
252 }
253 if (!have)
254 throw NumericError(
255 "maph2m_fit: no APH(2) form admits a valid class split for the requested class "
256 "probabilities and backward moments");
257 return best;
258}
259
260/** Fit a MAPH(2,m) to the descriptors measured on a marked MAP. */
261template <class T>
263 const std::vector<T> p = mmap_pc(m);
264 const std::vector<std::vector<T>> bm =
265 mmap_backward_moment(m, std::vector<unsigned>(1, 1u), true);
266 std::vector<T> B(p.size(), num_traits<T>::from_int(0));
267 for (std::size_t c = 0; c < p.size(); ++c) B[c] = bm[c][0];
268 return maph2m_fit(map_moment(m.map(), 1), map_moment(m.map(), 2), map_moment(m.map(), 3), p, B);
269}
270
271/**
272 * Fit a MAPH(2,m) to the descriptors measured on a marked trace.
273 *
274 * @param Tv the inter-arrival times
275 * @param A the class of each arrival, 1-based as the reference indexes them
276 */
277template <class T>
278Mmap<T> maph2m_fit_trace(const std::vector<T>& Tv, const std::vector<int>& A) {
279 if (Tv.empty() || Tv.size() != A.size())
280 throw InputError("maph2m_fit_trace: the trace and its labels must agree in length");
281 const T zero = num_traits<T>::from_int(0);
282 T m1 = zero, m2 = zero, m3 = zero;
283 for (std::size_t i = 0; i < Tv.size(); ++i) {
284 const T x = Tv[i];
285 m1 += x;
286 m2 += x * x;
287 m3 += x * x * x;
288 }
289 const T n = num_traits<T>::from_int(static_cast<long>(Tv.size()));
290 const std::vector<T> p = trace::mtrace_pc<T>(A);
291 const Matrix<T> bm = trace::mtrace_backward_moment(Tv, A, std::vector<unsigned>(1, 1u));
292 std::vector<T> B(p.size(), zero);
293 for (std::size_t c = 0; c < p.size(); ++c) B[c] = bm(c, 0);
294 return maph2m_fit(T(m1 / n), T(m2 / n), T(m3 / n), p, B);
295}
296
297} // namespace mam
298} // namespace line
299
300#endif // LINE_API_MAM_MAPH2M_FIT_H
APH(2) fit of three moments, with a fallback to adjusted moments (matlab/lib/m3a/m3a/aph2/aph2_fit....
Augmented Lagrangian method for equality- and inequality-constrained minimization,...
Base error for the multiprecision C++ port.
Definition error.h:31
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
Compression of a marked MAP into a smaller representation, and the two M3A primitives it is built fro...
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
Backward moments of a marked trace: the moments of the inter-arrival time that PRECEDES an event of e...
Class probabilities of a marked trace, p_c = count_c / N.
Mmap< T > maph2m_fit(const T &M1, const T &M2, const T &M3, const std::vector< T > &p, const std::vector< T > &B)
Fit a MAPH(2,m) to three moments, the class probabilities and the per-class backward moments,...
Definition maph2m_fit.h:228
Aph2FitResult< T > aph2_fit(const T &M1, const T &M2, const T &M3)
Fit an APH(2) to (M1, M2, M3), relaxing the moments if necessary.
Definition aph2_fit.h:42
Mmap< T > maph2m_fit_mmap(const Mmap< T > &m)
Fit a MAPH(2,m) to the descriptors measured on a marked MAP.
Definition maph2m_fit.h:262
Mmap< T > maph2m_fit_trace(const std::vector< T > &Tv, const std::vector< int > &A)
Fit a MAPH(2,m) to the descriptors measured on a marked trace.
Definition maph2m_fit.h:278
std::vector< std::vector< T > > mmap_backward_moment(const Mmap< T > &m, const std::vector< unsigned > &orders, bool normalized)
Class-conditional backward moments of an MMAP (mmap_backward_moment.m).
std::vector< T > mmap_pc(const Mmap< T > &m)
Class probabilities seen by an arriving job, pc = pie (-D0)^-1 D1^(c) e.
T map_moment(const Map< T > &m, unsigned k)
Raw moment of order k of the inter-arrival time: k!
Definition map_moment.h:118
Maph2mFitResult< T > maph2m_fit_multiclass(const Map< T > &aph, const std::vector< T > &p, const std::vector< T > &B, const std::vector< T > &classWeights=std::vector< T >())
Mark a canonical acyclic APH(2) with m classes.
Definition maph2m_fit.h:89
Matrix< T > mtrace_backward_moment(const std::vector< T > &Tv, const std::vector< int > &A, const std::vector< unsigned > &orders, bool norm=true)
Backward moments of a marked trace: the moments of the inter-arrival time that PRECEDES an event of e...
std::vector< T > mtrace_pc(const std::vector< int > &A)
Class probabilities of a marked trace, p_c = count_c / N.
Definition mtrace_pc.h:42
T num_abs(const T &v)
Definition number.h:172
AugLagResult< T > auglag(F f, H h, G g, const std::vector< T > &x0, const std::vector< Bound< T > > &bounds, const AugLagOptions< T > &opt)
Augmented Lagrangian with a scalar objective and a simplex inner solver.
Definition auglag.h:141
Number-type abstraction for the templated API port.
Outcome of a constrained solve.
Definition auglag.h:96
std::vector< T > x
best point found
Definition auglag.h:97
Result of aph2_fit.
Definition aph2_fit.h:34
std::vector< Map< T > > aphs
every feasible form found
Definition aph2_fit.h:36
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
The fitted MAPH and the backward moments it actually achieved.
Definition maph2m_fit.h:75
std::vector< T > fB
achieved per-class backward moments
Definition maph2m_fit.h:77
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