LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
aph2_fitall.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_APH2_FITALL_H
6#define LINE_API_MAM_APH2_FITALL_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * All APH(2) representations matching three moments
12 * (matlab/lib/m3a/m3a/aph2/aph2_fitall.m).
13 *
14 * The two phase means solve a quadratic whose discriminant is
15 * tmp0 = M3^2/9 + (8 M1^3/3 - 2 M1 M2) M3 - 3 M1^2 M2^2 + 2 M2^3,
16 * giving one solution when tmp0 vanishes (identical diagonal entries of D0)
17 * and two otherwise. Each is retained only when both phase means are positive
18 * and the branching probability lies in [0, 1] up to a degeneracy tolerance.
19 *
20 * As in the in-tree MATLAB (the "added by GC" branches), an infeasible
21 * discriminant or an empty feasible set falls back to a single aph_fit(M1, M2,
22 * M3, 2), so the result is never empty.
23 *
24 * Gated on transcendental arithmetic: the discriminant square root and the
25 * SCV <= 1 lower bound of the third moment.
26 */
27
28#include <vector>
29
34#include "line/num/number.h"
35#include "line/util/error.h"
36
37namespace line {
38namespace mam {
39
40/**
41 * All feasible APH(2) fits of (M1, M2, M3). degentol is the tolerance used
42 * both for the "M3 sits on its lower bound" degeneracy and for accepting a
43 * branching probability marginally outside [0, 1] (MATLAB uses 1e-8).
44 */
45template <class T>
46std::vector<Map<T>> aph2_fitall(const T& M1, const T& M2, const T& M3, const T& degentol) {
48 "aph2_fitall requires transcendental arithmetic");
49 using fitdetail::num_sqrt;
50 using fitdetail::pw;
51
52 const T zero = num_traits<T>::from_int(0);
53 const T one = num_traits<T>::from_int(1);
54 const T two = num_traits<T>::from_int(2);
55 const T three = num_traits<T>::from_int(3);
56
57 if (M1 <= zero) throw InputError("aph2_fitall: non-positive first moment");
58
59 const T SCV = (M2 - M1 * M1) / (M1 * M1);
60 bool degenerate = false;
61 if (SCV <= one) {
62 const T d = one - SCV;
63 const T M3lb = three * pw(M1, 3) * (three * SCV - one + num_sqrt(two) * d * num_sqrt(d));
64 if (num_abs(T(M3 - M3lb)) < degentol) degenerate = true;
65 }
66
67 T tmp0 = zero;
68 if (!degenerate) {
69 tmp0 = M3 * M3 / num_traits<T>::from_int(9) +
70 ((num_traits<T>::from_int(8) * pw(M1, 3)) / three - two * M2 * M1) * M3 -
71 three * M1 * M1 * M2 * M2 + two * pw(M2, 3);
72 if (tmp0 < zero) {
73 std::vector<Map<T>> out;
74 out.push_back(aph_fit(M1, M2, M3, 2u).aph);
75 return out;
76 }
77 }
78
79 const T tmp1 = three * num_sqrt(tmp0);
80 const T tmp2 = M3 - three * M1 * M2;
81 const T tmp3 = num_traits<T>::from_int(6) * M2 - num_traits<T>::from_int(12) * M1 * M1;
82 // M2 == 2 M1^2 is SCV == 1: the EXPONENTIAL, and the commonest input there
83 // is, not a malformed moment set. The reference does not guard it -- at
84 // SCV <= 1 with M3 on its lower bound it takes the tmp0 == 0 path and
85 // evaluates tmp2/tmp3, which for an exponential is 0/0 and yields NaN that
86 // the caller later discards as unfeasible. Throwing was worse than the NaN,
87 // since it took down a Poisson split/merge that has an exact answer; but
88 // returning the NaN would be worse still. Both roots of this quadratic are
89 // unusable when tmp3 vanishes (0/0 when tmp2 does too, otherwise infinite),
90 // so hand the moments to the general n-phase fitter, exactly as the
91 // infeasible branch above already does.
92 if (tmp3 == zero) {
93 std::vector<Map<T>> out;
94 out.push_back(aph_fit(M1, M2, M3, 2u).aph);
95 return out;
96 }
97
98 const std::size_t n = (tmp0 == zero) ? 1u : 2u;
99 std::vector<T> h1v(n, zero), h2v(n, zero);
100 if (n == 1) {
101 h2v[0] = tmp2 / tmp3;
102 h1v[0] = h2v[0];
103 } else {
104 h2v[0] = (tmp2 + tmp1) / tmp3;
105 h2v[1] = (tmp2 - tmp1) / tmp3;
106 h1v[1] = h2v[0];
107 h1v[0] = h2v[1];
108 }
109
110 std::vector<Map<T>> out;
111 for (std::size_t j = 0; j < n; ++j) {
112 const T h1 = h1v[j];
113 const T h2 = h2v[j];
114 if (h2 == zero) continue;
115 T r1 = (M1 - h1) / h2;
116 if (h1 > zero && h2 > zero && r1 >= -degentol && r1 <= one + degentol) {
117 if (r1 > one) r1 = one;
118 if (r1 < zero) r1 = zero;
119 out.push_back(aph2_assemble(h1, h2, r1));
120 }
121 }
122 if (out.empty()) out.push_back(aph_fit(M1, M2, M3, 2u).aph);
123 return out;
124}
125
126/** aph2_fitall with the MATLAB default degentol = 1e-8. */
127template <class T>
128std::vector<Map<T>> aph2_fitall(const T& M1, const T& M2, const T& M3) {
129 return aph2_fitall(M1, M2, M3, T(num_traits<T>::from_double(1e-8)));
130}
131
132} // namespace mam
133} // namespace line
134
135#endif // LINE_API_MAM_APH2_FITALL_H
Assemble an APH(2) from its canonical parameters (matlab/lib/m3a/m3a/aph2/aph2_assemble....
Minimal-order acyclic phase-type fit of the first three moments (matlab/lib/kpctoolbox/aph/aph_fit....
InputError(const std::string &what)
Definition error.h:39
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...
std::vector< Map< T > > aph2_fitall(const T &M1, const T &M2, const T &M3, const T &degentol)
All feasible APH(2) fits of (M1, M2, M3).
Definition aph2_fitall.h:46
Map< T > aph2_assemble(const T &l1, const T &l2, const T &p1)
APH(2) with phase means l1, l2 and continuation probability p1.
AphFitResult< T > aph_fit(const T &e1, const T &e2, const T &e3, unsigned nmax, const T &tol)
Fit an APH(n) with n <= nmax to the raw moments e1, e2, e3.
Definition aph_fit.h:176
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.