LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_comomrm.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_COMOMRM_H
6#define LINE_API_PFQN_COMOMRM_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * CoMoM (class-oriented method of moments) for the finite repairman model:
12 * one queueing station of multiplicity m plus a delay.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_comomrm.m. This is the routine
15 * pfqn_nc dispatches to for the repairman case under both the 'comom' and the
16 * 'default' method.
17 *
18 * The basis h at stage r holds 2r normalizing constants: the r "plus" moments
19 * G(n + e_s) and the r constants G(n) and G(n - e_s), s < r. Adding one
20 * class-r job applies
21 *
22 * h <- ( F1r + F2r / n_r ) h,
23 *
24 * with F1r a single 1 in the leading position and F2r built from the
25 * convolution equation (CE) and the population constraints (PC),
26 *
27 * F2r = [ -C^{-1} A12 B2r ; B2r ], B2r = [ m L_r I , Z_r I ],
28 *
29 * where C^{-1} is available in closed form for the repairman model (the
30 * reference writes it out rather than solving a system, and so does this port,
31 * so no linear solver is involved anywhere). Moving from class r-1 to class r
32 * expands the basis by interleaving two fresh entries carried over from the
33 * PREVIOUS step's basis.
34 *
35 * SCALING. The reference renormalizes h after every step and folds the
36 * discarded factors into a log accumulator; the expansion step then divides
37 * the carried-over entries by the last scale factor to put them on the current
38 * scale. This port carries the UNSCALED basis and the previous unscaled basis
39 * instead, which makes the expansion a plain copy and makes
40 *
41 * G(N) = Gremaind * h[R]
42 *
43 * exact, with h[R] the reference's h(end-(R-1)). The two formulations are
44 * algebraically identical: MATLAB's lG = lG0 + log(h(end-(R-1))) + sum(log
45 * scale) is the log of exactly that product, since h_scaled * prod(scale) is
46 * the unscaled basis by construction.
47 *
48 * Arithmetic: EXACT-CAPABLE. Every operation is an addition, a multiplication
49 * or a division in the field of the inputs. The only transcendentals in the
50 * reference are the log-space scale bookkeeping described above and the
51 * factln/exp seeding of the zero-think-time head, which is a ratio of
52 * factorials and is formed here directly.
53 */
54
55#include <cstddef>
56#include <vector>
57
60#include "line/num/number.h"
61#include "line/util/error.h"
62#include "line/util/matrix.h"
64
65namespace line {
66namespace pfqn {
67
68template <class T>
70 T G; ///< normalizing constant
71 double lG; ///< its logarithm
72 std::vector<T> basis; ///< the final unscaled basis h
73};
74
75/**
76 * @brief CoMoM (class-oriented method of moments) for the finite repairman
77 * model: one queueing station of multiplicity m plus a delay.
78 *
79 * @param L (1 x R) demands at the single queueing station
80 * @param N (R) populations
81 * @param Z (K x R) think times
82 * @param m multiplicity of the queueing station
83 */
84template <class T>
85ComomResult<T> pfqn_comomrm(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
86 int m) {
87 if (!L.empty() && L.rows() != 1)
88 throw InputError("pfqn_comomrm: the solver accepts at most a single queueing station");
89 if (m < 1) throw InputError("pfqn_comomrm: the multiplicity must be at least one");
90
91 const T zero = num_traits<T>::from_int(0);
92 const T one = num_traits<T>::from_int(1);
93 const T mT = num_traits<T>::from_int(m);
94
95 const NcSanitizeResult<T> san = pfqn_nc_sanitize(L, N, Z);
96 const std::size_t R = san.N.size();
97
99 if (R == 0) {
100 res.G = san.Gremaind;
101 res.lG = san.lGremaind;
102 res.basis.assign(1, one);
103 return res;
104 }
105
106 std::vector<T> Lv(R, zero), Zv(R, zero);
107 for (std::size_t r = 0; r < R; ++r) {
108 if (!san.L.empty()) Lv[r] = san.L(0, r);
109 for (std::size_t k = 0; k < san.Z.rows(); ++k) Zv[r] += san.Z(k, r);
110 }
111
112 // pfqn_nc_sanitize already orders the zero-think-time classes first, so the
113 // head is a prefix and the iteration below starts right after it.
114 std::size_t nzt = 0;
115 while (nzt < R && Zv[nzt] == zero) ++nzt;
116
117 // basis-seeding rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
118 std::vector<int> nvec(R, 0);
119 for (std::size_t z = 0; z < nzt; ++z) nvec[z] = san.N[z];
120 const auto headTerm = [&](const std::vector<int>& v, int extra) {
121 int tot = 0;
122 for (int x : v) tot += x;
123 T num = num_factorial<T>(static_cast<unsigned>(tot + extra));
124 for (int x : v) num /= num_factorial<T>(static_cast<unsigned>(x));
125 return num;
126 };
127
128 std::vector<T> h;
129 if (nzt > 0) {
130 h.assign(2 + 2 * nzt, zero);
131 std::size_t k = 0;
132 h[k++] = headTerm(nvec, m); // factln(sum+m+1-1)
133 for (std::size_t z = 0; z < nzt; ++z) {
134 std::vector<int> v = nvec;
135 v[z] -= 1;
136 h[k++] = headTerm(v, m);
137 }
138 h[k++] = headTerm(nvec, m - 1);
139 for (std::size_t z = 0; z < nzt; ++z) {
140 std::vector<int> v = nvec;
141 v[z] -= 1;
142 h[k++] = headTerm(v, m - 1);
143 }
144 } else {
145 h.assign(2, one);
146 }
147
148 if (nzt == R) {
149 // Every class has zero think time: the trivial model is the answer.
150 res.basis = h;
151 res.G = san.Gremaind * h[h.size() - R - 1];
153 return res;
154 }
155
156 // ---- iterate over the remaining classes ---------------------------------
157 std::vector<T> hprev = h;
158 Matrix<T> F1, F2; // rebuilt once per class, reused across its jobs
159 for (std::size_t r = nzt; r < R; ++r) {
160 const std::size_t rr = r + 1; // the reference's 1-based class index
161 for (int Nr = 1; Nr <= san.N[r]; ++Nr) {
162 nvec[r] += 1;
163 if (Nr == 1) {
164 if (rr > nzt + 1) {
165 // basis expansion rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
166 const std::size_t p = rr - 1;
167 std::vector<T> hr(2 * rr, zero);
168 for (std::size_t i = 0; i < p; ++i) hr[i] = h[i];
169 for (std::size_t i = 0; i < p; ++i) hr[rr + i] = h[p + i];
170 hr[p] = hprev[0];
171 hr[2 * rr - 1] = hprev[p];
172 h.swap(hr);
173 }
174 // A12 (rr x rr)
175 Matrix<T> A12(rr, rr, zero);
176 A12(0, 0) = -one;
177 for (std::size_t s = 0; s + 1 < rr; ++s) {
178 A12(1 + s, 0) = num_traits<T>::from_int(san.N[s]);
179 A12(1 + s, 1 + s) = -Zv[s];
180 }
181 // B2r (rr x 2rr) = [ m L_r I , Z_r I ]
182 Matrix<T> B2r(rr, 2 * rr, zero);
183 for (std::size_t i = 0; i < rr; ++i) {
184 B2r(i, i) = mT * Lv[r];
185 B2r(i, rr + i) = Zv[r];
186 }
187 // iC (rr x rr): closed form of C^{-1} for the repairman model.
188 Matrix<T> iC(rr, rr, zero);
189 const T minv = -one / mT;
190 for (std::size_t j = 0; j < rr; ++j) iC(0, j) = minv;
191 for (std::size_t i = 1; i < rr; ++i) iC(i, i) = minv;
192 iC(0, 0) = one;
193
194 // F2r = [ -iC * A12 * B2r ; B2r ]
195 Matrix<T> W(rr, rr, zero); // -iC * A12
196 for (std::size_t i = 0; i < rr; ++i)
197 for (std::size_t j = 0; j < rr; ++j) {
198 T s = zero;
199 for (std::size_t k = 0; k < rr; ++k) s += iC(i, k) * A12(k, j);
200 W(i, j) = -s;
201 }
202 F2 = Matrix<T>(2 * rr, 2 * rr, zero);
203 for (std::size_t i = 0; i < rr; ++i)
204 for (std::size_t j = 0; j < 2 * rr; ++j) {
205 T s = zero;
206 for (std::size_t k = 0; k < rr; ++k) s += W(i, k) * B2r(k, j);
207 F2(i, j) = s;
208 }
209 for (std::size_t i = 0; i < rr; ++i)
210 for (std::size_t j = 0; j < 2 * rr; ++j) F2(rr + i, j) = B2r(i, j);
211 F1 = Matrix<T>(2 * rr, 2 * rr, zero);
212 F1(0, 0) = one;
213 }
214 hprev = h;
215 const T inv = one / num_traits<T>::from_int(nvec[r]);
216 std::vector<T> hn(2 * rr, zero);
217 for (std::size_t i = 0; i < 2 * rr; ++i) {
218 T s = zero;
219 for (std::size_t j = 0; j < 2 * rr; ++j) s += (F1(i, j) + F2(i, j) * inv) * hprev[j];
220 hn[i] = s;
221 }
222 h.swap(hn);
223 }
224 }
225
226 res.basis = h;
227 res.G = san.Gremaind * h[h.size() - R];
229 return res;
230}
231
232/** Overload with the unit multiplicity default. */
233template <class T>
234ComomResult<T> pfqn_comomrm(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z) {
235 return pfqn_comomrm(L, N, Z, 1);
236}
237
238} // namespace pfqn
239} // namespace line
240
241#endif // LINE_API_PFQN_COMOMRM_H
InputError(const std::string &what)
Definition error.h:39
std::size_t rows() const
Definition matrix.h:89
bool empty() const
Definition matrix.h:92
The exception types the port throws.
Dense matrix and non-owning view.
NcSanitizeResult< T > pfqn_nc_sanitize(const std::vector< T > &lambda, const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const T &atol)
Preprocessing shared by the normalizing-constant solvers: drop the classes that cannot contribute,...
ComomResult< T > pfqn_comomrm(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, int m)
CoMoM (class-oriented method of moments) for the finite repairman model: one queueing station of mult...
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:184
Number-type abstraction for the templated API port.
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Preprocessing shared by the normalizing-constant solvers: drop the classes that cannot contribute,...
Population-vector enumeration and combinatorics.
std::vector< T > basis
the final unscaled basis h
T G
normalizing constant
double lG
its logarithm
T Gremaind
multiplicative factor removed from G
Matrix< T > Z
retained think times, rescaled and reordered
Matrix< T > L
retained demands, rescaled and reordered
double lGremaind
its logarithm, for the log-space callers
std::vector< int > N
retained populations, reordered