LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_panacea.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_PFQN_PFQN_PANACEA_H
6#define LINE_API_PFQN_PFQN_PANACEA_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * PANACEA normal-usage asymptotic expansion of the normalizing constant
12 * (Ramakrishnan and Mitra, BSTJ 61(10):2849-2872, 1982).
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_panacea.m. In the normal usage
15 * regime, where alpha_r = 1 - sum_i N_i r_ir > 0 for every station, the
16 * constant admits the expansion
17 *
18 * log G = -sum_r factln(N_r) + sum_r N_r log Z_r + log(sum_k I_k) - sum_i log alpha_i
19 *
20 * whose coefficients I_2, I_3 are assembled from convolution-algorithm values
21 * of the scaled demand matrix gammatilde at small auxiliary populations. The
22 * expansion is available at 1, 2 or 3 terms, as in the original package.
23 *
24 * NOT-NORMAL-USAGE. MATLAB returns NaN when min(alpha) < 0. The port reports
25 * it through a flag on the result rather than a NaN, so a caller that ignores
26 * the flag gets a value it can recognize as unusable instead of a quiet NaN
27 * travelling into a solver.
28 *
29 * ARITHMETIC. The result is the logarithm of a truncated asymptotic series, so
30 * the routine is gated on num_traits<T>::has_transcendental. The pfqn_ca calls
31 * inside it are exact and would remain so at exact arithmetic; it is the
32 * expansion and the logarithms that are not.
33 */
34
35#include <cmath>
36#include <cstddef>
37#include <vector>
38
41#include "line/num/number.h"
42#include "line/util/error.h"
43#include "line/util/matrix.h"
44
45namespace line {
46namespace pfqn {
47
48/** Return value of pfqn_panacea, mirroring [Gn, lGn]. */
49template <class T>
51 T G;
52 T lG;
53 bool normalUsage; ///< false where MATLAB returns NaN (min alpha < 0)
54};
55
56/**
57 * @brief PANACEA normal-usage asymptotic expansion of the normalizing
58 * constant (Ramakrishnan and Mitra, BSTJ 61(10):2849-2872, 1982).
59 *
60 * @param L (M x R) demands
61 * @param N (R) population
62 * @param Z (R) think times; empty means MATLAB's 1e-8 placeholder
63 * @param terms 1, 2 or 3
64 */
65template <class T>
66PanaceaResult<T> pfqn_panacea(const Matrix<T>& L, const std::vector<int>& N,
67 const std::vector<T>& Z, int terms) {
69 "pfqn_panacea requires transcendental arithmetic (asymptotic expansion of log G)");
70 using std::exp;
71 using std::log;
72 const std::size_t q = L.rows(), p = L.cols();
73 if (N.size() != p) throw InputError("pfqn_panacea: L and N disagree on the class count");
74 if (terms < 1 || terms > 3)
75 throw InputError("pfqn_panacea: the terms parameter must be 1, 2 or 3");
76 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
77 std::vector<T> Zv = Z;
78 if (Zv.empty()) Zv.assign(p, num_traits<T>::from_double(1e-8));
79 if (Zv.size() != p) throw InputError("pfqn_panacea: Z has the wrong length");
80
82 res.normalUsage = true;
83
84 // Degenerate: no station carries any demand, the delay carries everything.
85 bool anyDemand = false;
86 for (std::size_t i = 0; i < q; ++i)
87 for (std::size_t r = 0; r < p; ++r)
88 if (L(i, r) != zero) anyDemand = true;
89 if (q == 0 || !anyDemand) {
90 T lG = zero;
91 for (std::size_t r = 0; r < p; ++r) {
92 lG -= detail::num_factln<T>(num_traits<T>::from_int(N[r]));
93 lG += num_traits<T>::from_int(N[r]) * log(Zv[r]);
94 }
95 res.lG = lG;
96 res.G = exp(lG);
97 return res;
98 }
99
100 // scaled-load rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
101 Matrix<T> rr(q, p, zero);
102 T invmax = zero;
103 bool first = true;
104 for (std::size_t i = 0; i < q; ++i)
105 for (std::size_t s = 0; s < p; ++s) {
106 if (Zv[s] == zero) throw InputError("pfqn_panacea: zero think time");
107 rr(i, s) = T(L(i, s) / Zv[s]);
108 if (rr(i, s) > zero) {
109 const T inv = T(one / rr(i, s));
110 if (first || inv > invmax) {
111 invmax = inv;
112 first = false;
113 }
114 }
115 }
116 if (first) throw InputError("pfqn_panacea: no positive demand ratio");
117 const T Nt = invmax;
118
119 std::vector<T> beta(p);
120 for (std::size_t s = 0; s < p; ++s) beta[s] = T(num_traits<T>::from_int(N[s]) / Nt);
121 Matrix<T> gamma(q, p);
122 for (std::size_t i = 0; i < q; ++i)
123 for (std::size_t s = 0; s < p; ++s) gamma(i, s) = T(rr(i, s) * Nt);
124 std::vector<T> alpha(q);
125 for (std::size_t i = 0; i < q; ++i) {
126 T a = one;
127 for (std::size_t s = 0; s < p; ++s) a -= num_traits<T>::from_int(N[s]) * rr(i, s);
128 alpha[i] = a;
129 if (a < zero) res.normalUsage = false;
130 }
131 if (!res.normalUsage) {
132 res.G = zero;
133 res.lG = zero;
134 return res;
135 }
136 Matrix<T> gt(q, p);
137 for (std::size_t i = 0; i < q; ++i)
138 for (std::size_t s = 0; s < p; ++s) gt(i, s) = T(gamma(i, s) / alpha[i]);
139
140 const Matrix<T> noZ;
141 std::vector<T> I;
142 I.push_back(one); // A0
143 if (terms >= 2) {
144 T A1 = zero;
145 for (std::size_t j = 0; j < p; ++j) {
146 std::vector<int> m(p, 0);
147 m[j] = 2;
148 A1 -= beta[j] * pfqn_ca(gt, m, noZ).G;
149 }
150 I.push_back(T(A1 / Nt));
151 }
152 if (terms >= 3) {
153 T A2 = zero;
154 for (std::size_t j = 0; j < p; ++j) {
155 std::vector<int> m(p, 0);
156 m[j] = 3;
157 A2 += num_traits<T>::from_int(2) * beta[j] * pfqn_ca(gt, m, noZ).G;
158 m.assign(p, 0);
159 m[j] = 4;
160 A2 += num_traits<T>::from_int(3) * T(beta[j] * beta[j]) * pfqn_ca(gt, m, noZ).G;
161 for (std::size_t k = 0; k < p; ++k) {
162 if (k == j) continue;
163 m.assign(p, 0);
164 m[j] = 2;
165 m[k] = 2;
166 A2 += num_traits<T>::from_rational(1, 2) * beta[j] * beta[k] * pfqn_ca(gt, m, noZ).G;
167 }
168 }
169 I.push_back(T(A2 / T(Nt * Nt)));
170 }
171
172 T Isum = zero;
173 for (const T& v : I) Isum += v;
174 if (Isum <= zero) throw NumericError("pfqn_panacea: non-positive asymptotic series");
175 T lG = zero;
176 for (std::size_t s = 0; s < p; ++s) {
177 lG -= detail::num_factln<T>(num_traits<T>::from_int(N[s]));
178 lG += num_traits<T>::from_int(N[s]) * log(Zv[s]);
179 }
180 lG += log(Isum);
181 for (std::size_t i = 0; i < q; ++i) lG -= log(alpha[i]);
182 res.lG = lG;
183 res.G = exp(lG);
184 return res;
185}
186
187template <class T>
188PanaceaResult<T> pfqn_panacea(const Matrix<T>& L, const std::vector<int>& N,
189 const std::vector<T>& Z) {
190 return pfqn_panacea(L, N, Z, 3);
191}
192
193} // namespace pfqn
194} // namespace line
195
196#endif // LINE_API_PFQN_PFQN_PANACEA_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Dense matrix and non-owning view.
NcResult< T > pfqn_ca(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z)
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Definition pfqn_ca.h:120
PanaceaResult< T > pfqn_panacea(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, int terms)
PANACEA normal-usage asymptotic expansion of the normalizing constant (Ramakrishnan and Mitra,...
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Return value of pfqn_panacea, mirroring [Gn, lGn].
bool normalUsage
false where MATLAB returns NaN (min alpha < 0)