LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_momlin.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_MOMLIN_H
6#define LINE_API_PFQN_MOMLIN_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Moment linearizer: approximate first and second queue-length moments of a
12 * large closed product-form network.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_momlin.m.
15 *
16 * Means come from the Schweitzer-Bard AMVA fixed point. Second moments use the
17 * exact product-form identity
18 *
19 * Cov[n_{i,r}, n_{j,s}] = D_{j,s} dQ_{i,r} / dD_{j,s},
20 *
21 * with the demand derivatives obtained by differentiating the AMVA fixed point
22 * itself, which gives a LINEAR fixed point in dQ that is iterated to
23 * convergence per parameter (j, s0). Both moments therefore carry the
24 * Schweitzer-Bard error and are exact only where Schweitzer-Bard is; for exact
25 * moments on tractable models use the pfqn_sens family, which differentiates
26 * the exact MVA or CoMoM recursion instead.
27 *
28 * Arithmetic: no transcendental is used anywhere, so the routine is left
29 * UNGATED and will instantiate at Rational. That is of limited practical use:
30 * the fixed point converges to its limit only asymptotically, so at Rational
31 * the iterates are exact but their numerators and denominators grow with every
32 * sweep and the tolerance test is met only after the same number of sweeps as
33 * in floating point, at far greater cost. Use double or Real for this one; the
34 * exact instantiation exists so that no call site is refused, not because it
35 * buys accuracy the algorithm does not have.
36 *
37 * REFERENCE DEFECTS: none found. The reference reassigns R (the class count)
38 * to the residence-time output on its last line, which is legal MATLAB and is
39 * correct because every use of the class count precedes it; the port keeps the
40 * two separate.
41 */
42
43#include <cstddef>
44#include <vector>
45
46#include "line/num/number.h"
47#include "line/util/error.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace pfqn {
52
53/** Return value of pfqn_momlin, mirroring [Q, X, U, R, QVar, QCov, dQ]. */
54template <class T>
56 Matrix<T> Q; ///< (M x R) mean queue length
57 std::vector<T> X; ///< (R) per-class throughput
58 Matrix<T> U; ///< (M x R) utilization
59 Matrix<T> R; ///< (M x R) residence time
60 Matrix<T> QVar; ///< (M x R) queue-length variance
61 /// (M x R x M x R) covariance and demand-derivative tensors, flattened as
62 /// ((i*R + r)*M + j)*R + s so that a caller can index them without a
63 /// four-dimensional container.
64 std::vector<T> QCov;
65 std::vector<T> dQ;
66 std::size_t M, Rc;
67
68 const T& cov(std::size_t i, std::size_t r, std::size_t j, std::size_t s) const {
69 return QCov[((i * Rc + r) * M + j) * Rc + s];
70 }
71 const T& dq(std::size_t i, std::size_t r, std::size_t j, std::size_t s) const {
72 return dQ[((i * Rc + r) * M + j) * Rc + s];
73 }
74};
75
76/**
77 * @brief Moment linearizer: approximate first and second queue-length moments
78 * of a large closed product-form network.
79 *
80 * @param L (M x R) demand matrix
81 * @param N (R) closed populations
82 * @param Z (R) think times
83 * @param tol convergence tolerance on the fixed points
84 * @param maxiter iteration cap
85 */
86template <class T>
87MomlinResult<T> pfqn_momlin(const Matrix<T>& L, const std::vector<int>& N,
88 const std::vector<T>& Z, const T& tol, int maxiter) {
89 const std::size_t M = L.rows(), R = L.cols();
90 if (M == 0 || R == 0) throw InputError("pfqn_momlin: empty demand matrix");
91 if (N.size() != R) throw InputError("pfqn_momlin: L and N disagree on the class count");
92 if (Z.size() != R) throw InputError("pfqn_momlin: L and Z disagree on the class count");
93 if (maxiter < 1) throw InputError("pfqn_momlin: maxiter must be at least one");
94 for (std::size_t r = 0; r < R; ++r)
95 if (N[r] < 0) throw InputError("pfqn_momlin: pfqn_momlin supports closed classes only");
96
97 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
98
99 // Schweitzer population-scaling coefficients c(r,s) = (N_s - delta_rs)/N_s.
100 Matrix<T> c(R, R, one);
101 for (std::size_t r = 0; r < R; ++r)
102 for (std::size_t s = 0; s < R; ++s) {
103 if (N[s] > 0)
104 c(r, s) = num_traits<T>::from_int(N[s] - (r == s ? 1 : 0)) /
106 else
107 c(r, s) = zero;
108 }
109
110 // ---- Schweitzer-Bard fixed point for the means --------------------------------
111 Matrix<T> Q(M, R, zero), Rm(M, R, zero);
112 std::vector<T> X(R, zero);
113 for (std::size_t r = 0; r < R; ++r)
114 if (N[r] > 0)
115 for (std::size_t i = 0; i < M; ++i)
116 Q(i, r) = num_traits<T>::from_int(N[r]) / num_traits<T>::from_int(static_cast<long>(M));
117 for (int it = 0; it < maxiter; ++it) {
118 const Matrix<T> Qold = Q;
119 for (std::size_t r = 0; r < R; ++r) {
120 if (N[r] == 0) {
121 X[r] = zero;
122 for (std::size_t i = 0; i < M; ++i) Rm(i, r) = zero;
123 continue;
124 }
125 T Rtot = zero;
126 for (std::size_t i = 0; i < M; ++i) {
127 T acc = zero;
128 for (std::size_t s = 0; s < R; ++s) acc += c(r, s) * Q(i, s);
129 Rm(i, r) = L(i, r) * (one + acc);
130 Rtot += Rm(i, r);
131 }
132 const T den = Z[r] + Rtot;
133 if (den <= zero) throw NumericError("pfqn_momlin: degenerate residence time");
134 X[r] = num_traits<T>::from_int(N[r]) / den;
135 for (std::size_t i = 0; i < M; ++i) Q(i, r) = X[r] * Rm(i, r);
136 }
137 T mx = zero;
138 for (std::size_t i = 0; i < M; ++i)
139 for (std::size_t r = 0; r < R; ++r) {
140 const T d = num_abs(T(Q(i, r) - Qold(i, r)));
141 if (d > mx) mx = d;
142 }
143 if (mx < tol) break;
144 }
145
146 MomlinResult<T> res;
147 res.M = M;
148 res.Rc = R;
149 res.Q = Q;
150 res.X = X;
151 res.R = Rm;
152 res.U = Matrix<T>(M, R, zero);
153 for (std::size_t r = 0; r < R; ++r)
154 for (std::size_t i = 0; i < M; ++i) res.U(i, r) = X[r] * L(i, r);
155
156 // ---- analytic linearization of the fixed point ---------------------------------
157 res.dQ.assign(M * R * M * R, zero);
158 for (std::size_t j = 0; j < M; ++j) {
159 for (std::size_t s0 = 0; s0 < R; ++s0) {
160 if (N[s0] == 0) continue; // empty class: derivative zero
161 Matrix<T> dq(M, R, zero);
162 for (int it = 0; it < maxiter; ++it) {
163 const Matrix<T> dqold = dq;
164 for (std::size_t r = 0; r < R; ++r) {
165 if (N[r] == 0) continue;
166 std::vector<T> dR(M, zero);
167 T dRsum = zero;
168 for (std::size_t i = 0; i < M; ++i) {
169 T acc = zero, dacc = zero;
170 for (std::size_t s = 0; s < R; ++s) {
171 acc += c(r, s) * Q(i, s);
172 dacc += c(r, s) * dq(i, s);
173 }
174 dR[i] = L(i, r) * dacc;
175 if (i == j && r == s0) dR[i] += one + acc;
176 dRsum += dR[i];
177 }
178 const T dXr = -(X[r] * X[r] / num_traits<T>::from_int(N[r])) * dRsum;
179 for (std::size_t i = 0; i < M; ++i) dq(i, r) = dXr * Rm(i, r) + X[r] * dR[i];
180 }
181 T mx = zero;
182 for (std::size_t i = 0; i < M; ++i)
183 for (std::size_t r = 0; r < R; ++r) {
184 const T d = num_abs(T(dq(i, r) - dqold(i, r)));
185 if (d > mx) mx = d;
186 }
187 if (mx < tol) break;
188 }
189 for (std::size_t i = 0; i < M; ++i)
190 for (std::size_t r = 0; r < R; ++r)
191 res.dQ[((i * R + r) * M + j) * R + s0] = dq(i, r);
192 }
193 }
194
195 // ---- second moments via the product-form covariance identity -------------------
196 res.QCov.assign(M * R * M * R, zero);
197 for (std::size_t i = 0; i < M; ++i)
198 for (std::size_t r = 0; r < R; ++r)
199 for (std::size_t j = 0; j < M; ++j)
200 for (std::size_t s = 0; s < R; ++s) {
201 const std::size_t k = ((i * R + r) * M + j) * R + s;
202 res.QCov[k] = L(j, s) * res.dQ[k];
203 }
204 res.QVar = Matrix<T>(M, R, zero);
205 for (std::size_t i = 0; i < M; ++i)
206 for (std::size_t r = 0; r < R; ++r) res.QVar(i, r) = res.cov(i, r, i, r);
207 return res;
208}
209
210/** Overload with the reference's defaults (tol 1e-8, maxiter 1000). */
211template <class T>
212MomlinResult<T> pfqn_momlin(const Matrix<T>& L, const std::vector<int>& N,
213 const std::vector<T>& Z) {
214 return pfqn_momlin(L, N, Z, num_traits<T>::from_double(1e-8), 1000);
215}
216
217} // namespace pfqn
218} // namespace line
219
220#endif // LINE_API_PFQN_MOMLIN_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.
@ Rm
repairman: a single station, rates from the balanced bound
Definition pfqn_mci.h:79
MomlinResult< T > pfqn_momlin(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const T &tol, int maxiter)
Moment linearizer: approximate first and second queue-length moments of a large closed product-form n...
Definition pfqn_momlin.h:87
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Return value of pfqn_momlin, mirroring [Q, X, U, R, QVar, QCov, dQ].
Definition pfqn_momlin.h:55
std::vector< T > dQ
Definition pfqn_momlin.h:65
Matrix< T > Q
(M x R) mean queue length
Definition pfqn_momlin.h:56
Matrix< T > QVar
(M x R) queue-length variance
Definition pfqn_momlin.h:60
const T & cov(std::size_t i, std::size_t r, std::size_t j, std::size_t s) const
Definition pfqn_momlin.h:68
Matrix< T > U
(M x R) utilization
Definition pfqn_momlin.h:58
Matrix< T > R
(M x R) residence time
Definition pfqn_momlin.h:59
std::vector< T > QCov
(M x R x M x R) covariance and demand-derivative tensors, flattened as ((i*R + r)*M + j)*R + s so tha...
Definition pfqn_momlin.h:64
std::vector< T > X
(R) per-class throughput
Definition pfqn_momlin.h:57
const T & dq(std::size_t i, std::size_t r, std::size_t j, std::size_t s) const
Definition pfqn_momlin.h:71