LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
m3pp2m_fitc_approx.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_FITC_APPROX_H
6#define LINE_API_MAM_M3PP2M_FITC_APPROX_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * M3PP(2, m) fitted to counting-process characteristics with an optimized
12 * per-class split
13 * (matlab/lib/m3a/m3a/m3pp/m3pp2m_fitc_approx.m,
14 * matlab/lib/m3a/m3a/m3pp/m3pp2m_fitc_approx_ag.m,
15 * matlab/lib/m3a/m3a/m3pp/m3pp2m_fitc_approx_ag_multiclass.m).
16 *
17 * The underlying MMPP(2) comes from mmpp2_fitc_approx. The per-class split is
18 * then a pair of per-phase marking probabilities (q1i, q2i) per class, each an
19 * AFFINE function of the class rate ai (matched exactly) and of one free
20 * per-class quantity:
21 * - the non-'ag' variant frees dvi, the difference between the variance of
22 * class i and that of all other classes combined at resolution t3;
23 * - the 'ag' variant frees gi, the sum of the variance of class i and its
24 * covariance with all other classes combined at t3.
25 * Since m3pp2m_fitc.h can match only m - 1 of those exactly (the last class
26 * absorbs the remainder), the reference instead asks for the least-squares
27 * compromise over ALL m classes, subject to the marking probabilities being
28 * non-negative and summing to one in each phase. That is a quadratic program:
29 *
30 * min sum_i (x_i / target_i - 1)^2
31 * s.t. q1i(x_i) >= 0, q2i(x_i) >= 0 for every class i
32 * sum_i q1i(x_i) = 1, sum_i q2i(x_i) = 1
33 *
34 * (the reference writes the objective as 1/2 x'Hx + f'x with H = diag(2/t_i^2)
35 * and f_i = -2/t_i, which is the expansion of the sum above minus the constant
36 * m; its reported fit_error = fx + m is therefore exactly the sum above, and
37 * that is what M3pp2mFitcApproxResult::class_objective holds).
38 *
39 * Because the two coefficients q1i_dvi and q2i_dvi do not depend on i, both
40 * equality rows constrain only sum_i x_i; they have rank one and, as the
41 * algebra of the reference guarantees, a consistent right-hand side, so the
42 * program is a single equality plus 2m inequalities.
43 *
44 * ACCEPTANCE CONTRACT (see line/util/auglag.h). MATLAB solves the QP with
45 * quadprog (interior-point-convex) or the bundled Goldfarb-Idnani QP; the JAR
46 * uses OSQP. This port uses the augmented Lagrangian with a
47 * Levenberg-Marquardt inner solver on the residual (x_i/target_i - 1). The
48 * iterates do not match any of them. What is guaranteed and tested is the
49 * specification:
50 * 1. the returned MMAP is valid -- sum_i Dc_i = D1 and every Dc_i
51 * non-negative to the toolbox feasibility tolerance 1e-8, and the
52 * underlying (D0, D1) passes map_isfeasible (see mmap_isfeasible_tol);
53 * 2. the per-class rates are matched exactly, by construction;
54 * 3. the achieved objective (the reference's fit_error) is returned so it
55 * can be compared against any other QP solver's on the same input;
56 * 4. when the requested per-class targets are those of an actual M3PP(2, m),
57 * the objective reaches zero and the achieved targets, recomputed
58 * independently with mmap_count_var, reproduce the requested ones.
59 *
60 * REFERENCE DEFECTS (matlab/lib/m3a/m3a/m3pp/m3pp2m_fitc_approx.m):
61 * 1. the QP call reads `[x,fx] = QP(H, h, A, b, Aeq, beq, lb, ub, options)`,
62 * but `h` is never defined in that function -- the linear term is built
63 * as `f`. Every call therefore raises "Unrecognized function or variable
64 * 'h'" before the QP runs, so this MATLAB entry point cannot execute.
65 * The '_ag' path is unaffected: m3pp2m_fitc_approx_ag_multiclass.m calls
66 * quadprog(H, f, ...) correctly.
67 * 2. the same dead call passes lb = 1e-6 * ones(m,1). The free variables are
68 * variance DIFFERENCES, which are routinely negative (a minority class
69 * has less variance than all others combined), and the equality row fixes
70 * their sum, which is negative on ordinary inputs. Reproduction: for the
71 * MMPP(2) l1 = 2, l2 = 0.5, r1 = 0.3, r2 = 0.7 at t3 = 1 the equality
72 * forces sum_i x_i = -1.8976, so with m = 3 and lb = 1e-6 the feasible
73 * set is empty. This port therefore leaves the box unconstrained by
74 * default -- which is what the sibling _ag_multiclass does, its
75 * quadprog call passing [] for lb and ub -- and exposes optional bounds
76 * for a caller that wants them.
77 * MATLAB was NOT edited.
78 *
79 * DIVERGENCE FROM THE JAR: M3pp2m_fitc_approx.java and
80 * M3pp2m_fitc_approx_ag_multiclass.java add 2m further inequality rows
81 * (q1i <= 1, q2i <= 1) that neither MATLAB file has, and then project the
82 * solution post hoc with max(0, q) followed by a row renormalization. The
83 * extra rows can make a program infeasible that MATLAB solves, and the
84 * projection silently changes the fitted per-class rates. This port follows
85 * MATLAB: 2m rows, no post-hoc projection, and the infeasibility of the result
86 * reported through M3pp2mFitcApproxResult::feasible rather than repaired.
87 *
88 * Gated on transcendental arithmetic.
89 */
90
91#include <cstddef>
92#include <vector>
93
99#include "line/num/number.h"
100#include "line/util/auglag.h"
101#include "line/util/error.h"
102#include "line/util/matrix.h"
103
104namespace line {
105namespace mam {
106
107/** Result of the optimization-based M3PP(2, m) fits. */
108template <class T>
110 Mmap<T> mmap; ///< the fitted M3PP(2, m)
111 T mmpp_objective; ///< objective of the underlying MMPP(2) fit (zero when it was given)
112 T class_objective; ///< sum_i (x_i/target_i - 1)^2, the reference's fit_error
113 T class_violation; ///< worst constraint violation of the per-class QP
114 bool degenerate; ///< the underlying process was a Poisson process
115 bool feasible; ///< mmap_isfeasible_tol of the result at 1e-8
116};
117
118namespace fitdetail {
119
120/** The six affine coefficients of the per-class marking probabilities. */
121template <class T>
122struct M3ppSplitCoeffs {
123 T q1_a; ///< coefficient of ai in q1i
124 T q1_x; ///< coefficient of the free variable in q1i
125 T q1_c; ///< constant term of q1i
126 T q2_a;
127 T q2_x;
128 T q2_c;
129};
130
131/**
132 * Coefficients of the non-'ag' split (m3pp2m_fitc_approx.m), the free variable
133 * being the per-class variance difference at t3. The reference's repeated
134 * product sinh(u) exp(-u) with u = (r1 + r2) t / 2 is (1 - exp(-(r1+r2) t))/2
135 * exactly; it is evaluated in that form here, as in m3pp2m_fitc.h.
136 */
137template <class T>
138M3ppSplitCoeffs<T> m3pp_split_coeffs_dv(const T& l1, const T& l2, const T& r1, const T& r2,
139 const T& t) {
140 const T one = num_traits<T>::from_int(1);
141 const T two = num_traits<T>::from_int(2);
142 const T three = num_traits<T>::from_int(3);
143 const T four = num_traits<T>::from_int(4);
144 const T SH = (one - num_exp(T(-(r1 + r2) * t))) / two;
145
146 const T BR = two * l1 * SH - two * l2 * SH - l1 * r1 * t - l1 * r2 * t + l2 * r1 * t +
147 l2 * r2 * t;
148 const T DEN1 = l1 * r2 * (r1 + r2) * BR;
149 const T BR2 = l2 * l2 * r1 * r1 * t - two * l2 * l2 * r1 * SH - l1 * l2 * r1 * r1 * t +
150 l2 * l2 * r1 * r2 * t + two * l1 * l2 * r1 * SH - l1 * l2 * r1 * r2 * t;
151 const T DEN2 = (r1 + r2) * BR2;
152
153 const T P4 = pw(r1, 4) + four * pw(r1, 3) * r2 + num_traits<T>::from_int(6) * r1 * r1 * r2 * r2 +
154 four * r1 * pw(r2, 3) + pw(r2, 4);
155 const T CN = l1 * pw(r2, 4) * t + l2 * pw(r1, 4) * t + three * l1 * r1 * pw(r2, 3) * t +
156 l1 * pw(r1, 3) * r2 * t + l2 * r1 * pw(r2, 3) * t + three * l2 * pw(r1, 3) * r2 * t +
157 three * l1 * r1 * r1 * r2 * r2 * t + two * l1 * l1 * r1 * r2 * r2 * t +
158 two * l1 * l1 * r1 * r1 * r2 * t + three * l2 * r1 * r1 * r2 * r2 * t +
159 two * l2 * l2 * r1 * r2 * r2 * t + two * l2 * l2 * r1 * r1 * r2 * t -
160 four * l1 * l1 * r1 * r2 * SH - four * l2 * l2 * r1 * r2 * SH -
161 four * l1 * l2 * r1 * r2 * r2 * t - four * l1 * l2 * r1 * r1 * r2 * t +
162 num_traits<T>::from_int(8) * l1 * l2 * r1 * r2 * SH;
163
164 M3ppSplitCoeffs<T> q;
165 q.q1_a = (pw(r1, 4) * t / two + pw(r2, 4) * t / two - l1 * pw(r2, 3) * t +
166 l2 * pw(r2, 3) * t + two * r1 * pw(r2, 3) * t + two * pw(r1, 3) * r2 * t +
167 three * r1 * r1 * r2 * r2 * t + two * l1 * r2 * r2 * SH - two * l2 * r2 * r2 * SH -
168 two * l1 * r1 * r2 * r2 * t - l1 * r1 * r1 * r2 * t + two * l2 * r1 * r2 * r2 * t +
169 l2 * r1 * r1 * r2 * t + two * l1 * r1 * r2 * SH - two * l2 * r1 * r2 * SH) /
170 DEN1;
171 q.q1_x = -P4 / (four * DEN1);
172 q.q1_c = -CN / (four * DEN1);
173 q.q2_a = -(pw(r1, 4) * t / two + pw(r2, 4) * t / two + l1 * pw(r1, 3) * t -
174 l2 * pw(r1, 3) * t + two * r1 * pw(r2, 3) * t + two * pw(r1, 3) * r2 * t +
175 three * r1 * r1 * r2 * r2 * t - two * l1 * r1 * r1 * SH + two * l2 * r1 * r1 * SH +
176 l1 * r1 * r2 * r2 * t + two * l1 * r1 * r1 * r2 * t - l2 * r1 * r2 * r2 * t -
177 two * l2 * r1 * r1 * r2 * t - two * l1 * r1 * r2 * SH + two * l2 * r1 * r2 * SH) /
178 DEN2;
179 q.q2_x = P4 / (four * DEN2);
180 q.q2_c = CN / (four * DEN2);
181 return q;
182}
183
184/**
185 * Coefficients of the 'ag' split (m3pp2m_fitc_approx_ag_multiclass.m), the
186 * free variable being the per-class variance-plus-covariance at t3. Both
187 * constant terms are zero there.
188 */
189template <class T>
190M3ppSplitCoeffs<T> m3pp_split_coeffs_ag(const T& l1, const T& l2, const T& r1, const T& r2,
191 const T& t) {
192 const T zero = num_traits<T>::from_int(0);
193 const T two = num_traits<T>::from_int(2);
194 const T three = num_traits<T>::from_int(3);
195 const T E = num_exp(T(-(r1 + r2) * t));
196
197 const T f1num = l1 * r2 *
198 (two * l2 * r1 - two * l1 * r1 + pw(r1, 3) * t + pw(r2, 3) * t +
199 two * l1 * r1 * r1 * t - two * l2 * r1 * r1 * t + three * r1 * r2 * r2 * t +
200 three * r1 * r1 * r2 * t + two * l1 * r1 * E - two * l2 * r1 * E +
201 two * l1 * r1 * r2 * t - two * l2 * r1 * r2 * t);
202 const T f1 = f1num / pw(T(r1 + r2), 4);
203 const T f2num = l2 * r1 *
204 (two * l1 * r2 - two * l2 * r2 + pw(r1, 3) * t + pw(r2, 3) * t -
205 two * l1 * r2 * r2 * t + two * l2 * r2 * r2 * t + three * r1 * r2 * r2 * t +
206 three * r1 * r1 * r2 * t - two * l1 * r2 * E + two * l2 * r2 * E -
207 two * l1 * r1 * r2 * t + two * l2 * r1 * r2 * t);
208 const T f2 = f2num / pw(T(r1 + r2), 4);
209 const T tmp = f1 * l2 * r1 - f2 * l1 * r2;
210 if (tmp == zero)
211 throw NumericError("m3pp2m_fitc_approx_ag: degenerate per-class split (f1 l2 r1 = f2 l1 r2)");
212
213 M3ppSplitCoeffs<T> q;
214 q.q1_a = -(f2 * (r1 + r2)) / tmp;
215 q.q1_x = (l2 * r1) / tmp;
216 q.q1_c = zero;
217 q.q2_a = (f1 * (r1 + r2)) / tmp;
218 q.q2_x = -(l1 * r2) / tmp;
219 q.q2_c = zero;
220 return q;
221}
222
223/**
224 * Solve the per-class quadratic program described in the header comment.
225 *
226 * @param q the six affine coefficients
227 * @param ai per-class rates
228 * @param target per-class targets (dvt3 or gt3); none may be zero, since the
229 * objective is relative to them
230 * @param a total rate
231 * @param bounds optional box on the free variables; pass an empty vector for
232 * the unconstrained default (see the reference-defect note)
233 * @param opt tuning of the constrained solve
234 * @param objective out: sum_i (x_i/target_i - 1)^2 at the answer
235 * @param violation out: worst constraint violation at the answer
236 */
237template <class T>
238std::vector<T> m3pp_split_solve(const M3ppSplitCoeffs<T>& q, const std::vector<T>& ai,
239 const std::vector<T>& target, const T& a,
240 const std::vector<Bound<T>>& bounds, const AugLagOptions<T>& opt,
241 T& objective, T& violation) {
242 const T zero = num_traits<T>::from_int(0);
243 const T one = num_traits<T>::from_int(1);
244 const std::size_t m = ai.size();
245 if (target.size() != m) throw InputError("m3pp_split_solve: target length disagrees with ai");
246 for (std::size_t i = 0; i < m; ++i)
247 if (target[i] == zero)
248 throw InputError("m3pp_split_solve: a per-class target is zero, so the relative "
249 "objective of the reference is undefined");
250 if (!bounds.empty() && bounds.size() != m)
251 throw InputError("m3pp_split_solve: one bound per class is required");
252
253 const std::vector<T> tgt = target;
254 auto resid = [tgt, m, one](const std::vector<T>& x) {
255 std::vector<T> r(m);
256 for (std::size_t i = 0; i < m; ++i) r[i] = x[i] / tgt[i] - one;
257 return r;
258 };
259
260 const std::vector<T> rates = ai;
261 const M3ppSplitCoeffs<T> qc = q;
262 const T mT = num_traits<T>::from_int(long(m));
263 auto heq = [qc, rates, a, m, mT, one](const std::vector<T>& x) {
264 T s = num_traits<T>::from_int(0);
265 for (std::size_t i = 0; i < m; ++i) s += x[i];
266 std::vector<T> hv(2);
267 hv[0] = qc.q1_a * a + qc.q1_x * s + mT * qc.q1_c - one;
268 hv[1] = qc.q2_a * a + qc.q2_x * s + mT * qc.q2_c - one;
269 return hv;
270 };
271 const std::vector<Bound<T>> bnd = bounds;
272 auto gineq = [qc, rates, m, bnd](const std::vector<T>& x) {
273 std::vector<T> gv;
274 for (std::size_t i = 0; i < m; ++i) {
275 // q1i >= 0 and q2i >= 0, the reference's A x <= b
276 gv.push_back(T(-(qc.q1_a * rates[i] + qc.q1_x * x[i] + qc.q1_c)));
277 gv.push_back(T(-(qc.q2_a * rates[i] + qc.q2_x * x[i] + qc.q2_c)));
278 }
279 for (std::size_t i = 0; i < bnd.size(); ++i) {
280 if (bnd[i].has_lo) gv.push_back(T(bnd[i].lo - x[i]));
281 if (bnd[i].has_hi) gv.push_back(T(x[i] - bnd[i].hi));
282 }
283 return gv;
284 };
285
286 // start from the requested targets themselves, the point of zero objective
287 std::vector<T> x0 = target;
288 const AugLagResult<T> sol = auglag_ls(resid, m, heq, gineq, x0, opt);
289 objective = sol.fval;
290 violation = sol.violation;
291 return sol.x;
292}
293
294} // namespace fitdetail
295
296/**
297 * Assemble the M3PP from an underlying MAP and the per-phase marking
298 * probabilities. The last class is NOT special-cased: every class gets the
299 * probability the QP assigned it, and the equality rows are what make the
300 * columns sum to D1.
301 */
302template <class T>
303Mmap<T> m3pp2m_assemble(const Map<T>& base, const std::vector<T>& q1, const std::vector<T>& q2) {
304 const T zero = num_traits<T>::from_int(0);
305 if (q1.size() != q2.size()) throw InputError("m3pp2m_assemble: q1 and q2 differ in length");
306 Mmap<T> mm;
307 mm.D0 = base.D0;
308 mm.D1 = base.D1;
309 for (std::size_t i = 0; i < q1.size(); ++i) {
310 Matrix<T> Dc(2, 2, zero);
311 Dc(0, 0) = q1[i] * base.D1(0, 0);
312 Dc(1, 1) = q2[i] * base.D1(1, 1);
313 mm.Dc.push_back(Dc);
314 }
315 return mm;
316}
317
318namespace fitdetail {
319
320/** Shared body of the two multiclass splits, once the coefficients are known. */
321template <class T>
322M3pp2mFitcApproxResult<T> m3pp2m_split_and_assemble(const Map<T>& base,
323 const M3ppSplitCoeffs<T>& q,
324 const std::vector<T>& ai,
325 const std::vector<T>& target, const T& a,
326 const std::vector<Bound<T>>& bounds,
327 const AugLagOptions<T>& opt) {
328 const std::size_t m = ai.size();
329 M3pp2mFitcApproxResult<T> res;
330 res.degenerate = false;
331 res.mmpp_objective = num_traits<T>::from_int(0);
332
333 T obj = num_traits<T>::from_int(0);
334 T viol = num_traits<T>::from_int(0);
335 const std::vector<T> x = m3pp_split_solve(q, ai, target, a, bounds, opt, obj, viol);
336
337 std::vector<T> q1(m), q2(m);
338 for (std::size_t i = 0; i < m; ++i) {
339 q1[i] = q.q1_a * ai[i] + q.q1_x * x[i] + q.q1_c;
340 q2[i] = q.q2_a * ai[i] + q.q2_x * x[i] + q.q2_c;
341 }
342 res.mmap = m3pp2m_assemble(base, q1, q2);
343 res.class_objective = obj;
344 res.class_violation = viol;
345 res.feasible = mmap_isfeasible_tol(res.mmap, T(num_traits<T>::from_double(1e-8)));
346 return res;
347}
348
349/** The Poisson and single-class short-circuits shared by all three entry points. */
350template <class T>
351bool m3pp2m_trivial_split(const Map<T>& base, const std::vector<T>& ai, const T& a,
352 M3pp2mFitcApproxResult<T>& res) {
353 const std::size_t m = ai.size();
354 if (base.D0.rows() == 1) {
355 // marked Poisson process: split D1 in proportion to the class rates
356 res.degenerate = true;
357 res.mmap.D0 = base.D0;
358 res.mmap.D1 = base.D1;
359 for (std::size_t i = 0; i < m; ++i) {
360 Matrix<T> Dc(1, 1, T(ai[i] / a * base.D1(0, 0)));
361 res.mmap.Dc.push_back(Dc);
362 }
363 res.class_objective = num_traits<T>::from_int(0);
364 res.class_violation = num_traits<T>::from_int(0);
365 res.feasible = mmap_isfeasible_tol(res.mmap, T(num_traits<T>::from_double(1e-8)));
366 return true;
367 }
368 if (m == 1) {
369 res.mmap.D0 = base.D0;
370 res.mmap.D1 = base.D1;
371 res.mmap.Dc.push_back(base.D1);
372 res.class_objective = num_traits<T>::from_int(0);
373 res.class_violation = num_traits<T>::from_int(0);
374 res.feasible = mmap_isfeasible_tol(res.mmap, T(num_traits<T>::from_double(1e-8)));
375 return true;
376 }
377 return false;
378}
379
380} // namespace fitdetail
381
382/**
383 * m3pp2m_fitc_approx: fit the underlying MMPP(2) by optimization, then split
384 * the classes on their variance DIFFERENCES at t3.
385 *
386 * @param a,bt1,bt2,binf,m3t2,t1,t2 the aggregate counting characteristics
387 * @param ai per-class rates, which must sum to a
388 * @param dvt3 per-class variance differences at t3
389 * @param t3 the third time scale
390 * @param bounds optional box on the free variables; empty for none, which is
391 * the default (see the reference-defect note in the header)
392 * @param opt tuning of the constrained solve
393 */
394template <class T>
395M3pp2mFitcApproxResult<T> m3pp2m_fitc_approx(const T& a, const T& bt1, const T& bt2, const T& binf,
396 const T& m3t2, const T& t1, const T& t2,
397 const std::vector<T>& ai, const std::vector<T>& dvt3,
398 const T& t3, const std::vector<Bound<T>>& bounds,
399 const AugLagOptions<T>& opt) {
401 "m3pp2m_fitc_approx requires transcendental arithmetic");
402 const T zero = num_traits<T>::from_int(0);
403 const std::size_t m = ai.size();
404 if (m == 0) throw InputError("m3pp2m_fitc_approx: no classes");
405 T asum = zero;
406 for (std::size_t i = 0; i < m; ++i) asum += ai[i];
407 if (num_abs(T(a - asum)) > num_traits<T>::from_double(1e-8))
408 throw InputError("m3pp2m_fitc_approx: inconsistent per-class arrival rates");
409
410 const Mmpp2FitcApproxResult<T> base = mmpp2_fitc_approx(a, bt1, bt2, binf, m3t2, t1, t2, opt);
411
413 res.degenerate = false;
414 res.mmpp_objective = base.objective;
415 if (fitdetail::m3pp2m_trivial_split(base.map, ai, a, res)) return res;
416
417 const fitdetail::M3ppSplitCoeffs<T> q = fitdetail::m3pp_split_coeffs_dv(
418 base.map.D1(0, 0), base.map.D1(1, 1), base.map.D0(0, 1), base.map.D0(1, 0), t3);
420 fitdetail::m3pp2m_split_and_assemble(base.map, q, ai, dvt3, a, bounds, opt);
421 out.mmpp_objective = base.objective;
422 return out;
423}
424
425/** m3pp2m_fitc_approx with no box on the free variables and the default tuning. */
426template <class T>
427M3pp2mFitcApproxResult<T> m3pp2m_fitc_approx(const T& a, const T& bt1, const T& bt2, const T& binf,
428 const T& m3t2, const T& t1, const T& t2,
429 const std::vector<T>& ai, const std::vector<T>& dvt3,
430 const T& t3) {
432 opt.ctol = num_traits<T>::from_double(1e-12);
433 return m3pp2m_fitc_approx(a, bt1, bt2, binf, m3t2, t1, t2, ai, dvt3, t3,
434 std::vector<Bound<T>>(), opt);
435}
436
437/**
438 * m3pp2m_fitc_approx_ag_multiclass: split a GIVEN MMPP(2) into m classes on
439 * their variance-plus-covariance at t3.
440 *
441 * @param mmpp the underlying MAP, of order 1 (Poisson) or 2
442 * @param ai per-class rates, which must sum to the rate of mmpp
443 * @param gt3 per-class variance-plus-covariance targets at t3
444 * @param t3 the third time scale
445 * @param bounds optional box on the free variables; empty for none
446 * @param opt tuning of the constrained solve
447 */
448template <class T>
450 const std::vector<T>& ai,
451 const std::vector<T>& gt3, const T& t3,
452 const std::vector<Bound<T>>& bounds,
453 const AugLagOptions<T>& opt) {
455 "m3pp2m_fitc_approx_ag_multiclass requires transcendental arithmetic");
456 const T zero = num_traits<T>::from_int(0);
457 const std::size_t m = ai.size();
458 if (m == 0) throw InputError("m3pp2m_fitc_approx_ag_multiclass: no classes");
459 const T a = map_lambda(mmpp);
460 T asum = zero;
461 for (std::size_t i = 0; i < m; ++i) asum += ai[i];
462 if (num_abs(T(a - asum)) > num_traits<T>::from_double(1e-8))
463 throw InputError("m3pp2m_fitc_approx_ag_multiclass: inconsistent per-class arrival rates");
464
466 res.degenerate = false;
467 res.mmpp_objective = zero;
468 if (fitdetail::m3pp2m_trivial_split(mmpp, ai, a, res)) return res;
469 if (mmpp.D0.rows() != 2)
470 throw InputError("m3pp2m_fitc_approx_ag_multiclass: the underlying MAP must have order 2");
471
472 const fitdetail::M3ppSplitCoeffs<T> q = fitdetail::m3pp_split_coeffs_ag(
473 mmpp.D1(0, 0), mmpp.D1(1, 1), mmpp.D0(0, 1), mmpp.D0(1, 0), t3);
474 return fitdetail::m3pp2m_split_and_assemble(mmpp, q, ai, gt3, a, bounds, opt);
475}
476
477/** m3pp2m_fitc_approx_ag_multiclass with no box and the default tuning. */
478template <class T>
480 const std::vector<T>& ai,
481 const std::vector<T>& gt3,
482 const T& t3) {
484 opt.ctol = num_traits<T>::from_double(1e-12);
485 return m3pp2m_fitc_approx_ag_multiclass(mmpp, ai, gt3, t3, std::vector<Bound<T>>(), opt);
486}
487
488/**
489 * m3pp2m_fitc_approx_ag: fit the underlying MMPP(2) by optimization, then
490 * apply the 'ag' per-class split.
491 *
492 * @param a,bt1,bt2,binf,m3t2,t1,t2 the aggregate counting characteristics
493 * @param ai per-class rates, which must sum to a
494 * @param gt3 per-class variance-plus-covariance targets at t3
495 * @param t3 the third time scale
496 * @param bounds optional box on the free variables; empty for none
497 * @param opt tuning of the constrained solve
498 */
499template <class T>
500M3pp2mFitcApproxResult<T> m3pp2m_fitc_approx_ag(const T& a, const T& bt1, const T& bt2,
501 const T& binf, const T& m3t2, const T& t1,
502 const T& t2, const std::vector<T>& ai,
503 const std::vector<T>& gt3, const T& t3,
504 const std::vector<Bound<T>>& bounds,
505 const AugLagOptions<T>& opt) {
507 "m3pp2m_fitc_approx_ag requires transcendental arithmetic");
508 const T zero = num_traits<T>::from_int(0);
509 T asum = zero;
510 for (std::size_t i = 0; i < ai.size(); ++i) asum += ai[i];
511 if (num_abs(T(a - asum)) > num_traits<T>::from_double(1e-8))
512 throw InputError("m3pp2m_fitc_approx_ag: inconsistent per-class arrival rates");
513
514 const Mmpp2FitcApproxResult<T> base = mmpp2_fitc_approx(a, bt1, bt2, binf, m3t2, t1, t2, opt);
516 m3pp2m_fitc_approx_ag_multiclass(base.map, ai, gt3, t3, bounds, opt);
517 res.mmpp_objective = base.objective;
518 return res;
519}
520
521/** m3pp2m_fitc_approx_ag with no box and the default tuning. */
522template <class T>
523M3pp2mFitcApproxResult<T> m3pp2m_fitc_approx_ag(const T& a, const T& bt1, const T& bt2,
524 const T& binf, const T& m3t2, const T& t1,
525 const T& t2, const std::vector<T>& ai,
526 const std::vector<T>& gt3, const T& t3) {
528 opt.ctol = num_traits<T>::from_double(1e-12);
529 return m3pp2m_fitc_approx_ag(a, bt1, bt2, binf, m3t2, t1, t2, ai, gt3, t3,
530 std::vector<Bound<T>>(), opt);
531}
532
533} // namespace mam
534} // namespace line
535
536#endif // LINE_API_MAM_M3PP2M_FITC_APPROX_H
Augmented Lagrangian method for equality- and inequality-constrained minimization,...
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.
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_...
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...
bool mmap_isfeasible_tol(const Mmap< T > &m, const T &tol)
Feasibility of a marked MAP WITHIN A TOLERANCE, the semantics of matlab/lib/m3a/m3a/mmap/mmap_isfeasi...
M3pp2mFitcApproxResult< T > m3pp2m_fitc_approx_ag_multiclass(const Map< T > &mmpp, 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_multiclass: split a GIVEN MMPP(2) into m classes on their variance-plus-covaria...
Mmap< T > m3pp2m_assemble(const Map< T > &base, const std::vector< T > &q1, const std::vector< T > &q2)
Assemble the M3PP from an underlying MAP and the per-phase marking probabilities.
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.
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...
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
Definition map_moment.h:79
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
AugLagResult< T > auglag_ls(R r, std::size_t m, H h, G g, const std::vector< T > &x0, const AugLagOptions< T > &opt)
Augmented Lagrangian with a least-squares objective and levmar as the inner solver.
Definition auglag.h:246
Number-type abstraction for the templated API port.
Tuning of the outer multiplier iteration.
Definition auglag.h:68
Box constraint on one variable.
Definition neldermead.h:110
Result of the optimization-based M3PP(2, m) fits.
Mmap< T > mmap
the fitted M3PP(2, m)
T class_objective
sum_i (x_i/target_i - 1)^2, the reference's fit_error
bool feasible
mmap_isfeasible_tol of the result at 1e-8
T mmpp_objective
objective of the underlying MMPP(2) fit (zero when it was given)
bool degenerate
the underlying process was a Poisson process
T class_violation
worst constraint violation of the per-class QP
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
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
Result of mmpp2_fitc_approx.
Map< T > map
the fitted MMPP(2), rescaled to rate a
T objective
sum of squared relative errors at the optimum