LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_sens_mvaldmx.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_SENS_MVALDMX_H
6#define LINE_API_PFQN_SENS_MVALDMX_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Exact queue-length variances and covariances of a mixed open/closed
12 * product-form network with limited load dependence, the load-dependent and
13 * mixed counterpart of pfqn_sens_mva.
14 *
15 * Templated port of matlab/src/api/pfqn/pfqn_sens_mvaldmx.m. Theorem 1,
16 * equation (11), of Akyildiz and Strelen states that one further factor Q_jT
17 * in a moment costs one derivative with respect to a parameter y_j scaling the
18 * demands of the classes of T at station j; taking k = 2 and T = {s} gives
19 *
20 * Cov[n(i,r),n(j,s)] = d nbar(i,r) / dy_(j,s) |_{y=1}
21 *
22 * which is evaluated here by forward-mode differentiation of the mixed
23 * load-dependent MVA of Bruell-Balbo-Afshari, i.e. of the recursion
24 * pfqn_mvaldmx implements. The differentiated equations are (13) for the
25 * residence times, (15)-(17) for the conditional marginals, (18) for the
26 * throughputs, (19) and (24)-(31) for the effective capacities (delegated to
27 * pfqn_sens_ldmx_ec), (32) for the closed-class queue lengths and (33) for the
28 * open-class ones.
29 *
30 * A demand-scaling parameter perturbs the whole network through the
31 * closed-class throughputs, so the derivatives must be propagated for every
32 * parameter and the cross-station covariances come out at no extra cost; they
33 * are returned in QCovFull. That is unlike pfqn_sens_mva, whose cheaper
34 * same-station recursion cannot reach them.
35 *
36 * Arithmetic. Field operations only, so the routine instantiates at
37 * line::Rational. The one non-field constant is the max(eps, .) floor the
38 * reference keeps on P(0) so that the base measures agree with pfqn_mvaldmx
39 * entry by entry; it is a numerical guard, not part of the model, and the
40 * derivative is the exact -sum of the derivatives, as in the reference.
41 *
42 * Reference: I. F. Akyildiz and J. C. Strelen, "Moment Analysis for
43 * Load-Dependent Mixed Product Form Queueing Networks", IEEE Trans.
44 * Communications 39(6):828-832, 1991.
45 */
46
47#include <cstddef>
48#include <vector>
49
51#include "line/num/number.h"
52#include "line/util/error.h"
53#include "line/util/matrix.h"
55
56namespace line {
57namespace pfqn {
58
59template <class T>
61 std::vector<T> XN; ///< (R) throughput
62 Matrix<T> QN; ///< (M x R) mean queue length
63 Matrix<T> UN; ///< (M x R) utilization
64 Matrix<T> CN; ///< (M x R) residence time (MATLAB field .R)
65
66 std::vector<Matrix<T>> QCov; ///< (M) matrices R x R, same-station covariance
67 /// (M*R x M*R) Cov[n(i,r),n(j,s)] at row i*R+r, column j*R+s.
69 Matrix<T> QVar; ///< (M x R)
70 std::vector<T> QTotVar; ///< (M)
71 T QCovAsym; ///< residual before symmetrization
72};
73
74/**
75 * @brief Exact queue-length variances and covariances of a mixed open/closed
76 * product-form network with limited load dependence, the load-dependent
77 * and mixed counterpart of pfqn_sens_mva.
78 *
79 * @param lambda (R) arrival rates, zero on the closed classes
80 * @param D (M x R) service demands
81 * @param N (R) population, negative marks an open class (MATLAB uses Inf)
82 * @param Z (R) think times
83 * @param mu (M x >= sum of the closed populations) load-dependent rates
84 */
85template <class T>
86SensMvaldmxResult<T> pfqn_sens_mvaldmx(const std::vector<T>& lambda, const Matrix<T>& D,
87 const std::vector<int>& N, const std::vector<T>& Z,
88 const Matrix<T>& mu) {
89 const std::size_t M = D.rows();
90 const std::size_t R = D.cols();
91 if (lambda.size() != R || N.size() != R || Z.size() != R)
92 throw InputError("pfqn_sens_mvaldmx: lambda, N, Z and D disagree on the class count");
93 if (mu.rows() != M)
94 throw InputError("pfqn_sens_mvaldmx: mu and D disagree on the station count");
95
96 const T zero = num_traits<T>::from_int(0);
97 const T one = num_traits<T>::from_int(1);
98
99 std::vector<std::size_t> openClasses, closedClasses;
100 for (std::size_t r = 0; r < R; ++r) {
101 if (N[r] < 0) {
102 openClasses.push_back(r);
103 } else {
104 if (lambda[r] != zero && N[r] > 0)
105 throw InputError("pfqn_sens_mvaldmx: an arrival rate cannot be set on a closed class");
106 closedClasses.push_back(r);
107 }
108 }
109 const std::size_t Cn = closedClasses.size();
110 if (Cn == 0)
111 throw InputError(
112 "pfqn_sens_mvaldmx: at least one closed class is required; use the open-class formulas "
113 "directly otherwise");
114
115 std::vector<int> Nc(Cn, 0);
116 std::vector<T> Zc(Cn, zero);
117 Matrix<T> Dc(M, Cn, zero);
118 int NCtot = 0;
119 for (std::size_t c = 0; c < Cn; ++c) {
120 Nc[c] = N[closedClasses[c]];
121 Zc[c] = Z[closedClasses[c]];
122 NCtot += Nc[c];
123 for (std::size_t i = 0; i < M; ++i) Dc(i, c) = D(i, closedClasses[c]);
124 }
125 if (static_cast<int>(mu.cols()) < NCtot)
126 throw InputError(
127 "pfqn_sens_mvaldmx: the load-dependent rates must be given up to the maximum closed "
128 "population");
129
130 // The reference appends one more saturation column so that the effective
131 // capacities reach sum(N)+1, which the open-class equation (33) reads.
132 Matrix<T> mux(M, mu.cols() + 1, zero);
133 for (std::size_t i = 0; i < M; ++i) {
134 for (std::size_t k = 0; k < mu.cols(); ++k) mux(i, k) = mu(i, k);
135 mux(i, mu.cols()) = mu(i, mu.cols() - 1);
136 }
137 const SensLdmxEcResult<T> ec = pfqn_sens_ldmx_ec(lambda, D, mux);
138
139 // ---- parameter list: y(j,r) multiplies D(j,r) --------------------------
140 const std::size_t P = M * R;
141 const auto pidx = [&](std::size_t j, std::size_t r) { return j * R + r; };
142 // eq. (21): only an open-class parameter at station i perturbs Lo(i).
143 Matrix<T> dLo(M, P, zero);
144 for (std::size_t j = 0; j < M; ++j)
145 for (std::size_t r = 0; r < R; ++r)
146 if (N[r] < 0) dLo(j, pidx(j, r)) = lambda[r] * D(j, r);
147
148 // ---- population lattice over the closed classes, first class fastest ----
149 const std::vector<std::size_t> prods = plane_sizes(Nc);
150 const std::size_t NT = population_count(Nc);
151 const std::size_t NL = static_cast<std::size_t>(NCtot) + 1;
152
153 std::vector<Matrix<T>> Pc(NT, Matrix<T>(M, NL, zero));
154 std::vector<std::vector<Matrix<T>>> dPc(NT, std::vector<Matrix<T>>(M, Matrix<T>(NL, P, zero)));
155 Matrix<T> x(NT, Cn, zero);
156 std::vector<Matrix<T>> dx(NT, Matrix<T>(Cn, P, zero));
157 std::vector<Matrix<T>> w(NT, Matrix<T>(M, Cn, zero));
158 std::vector<std::vector<Matrix<T>>> dw(NT, std::vector<Matrix<T>>(M, Matrix<T>(Cn, P, zero)));
159
160 for (std::size_t i = 0; i < M; ++i) Pc[0](i, 0) = one; // eq. (16)
161
162 std::vector<T> dacc(P, zero);
163 for (std::size_t k = 0; k < NT; ++k) {
164 std::vector<int> nvec(Cn, 0);
165 int nc = 0;
166 for (std::size_t c = 0; c < Cn; ++c) {
167 nvec[c] = static_cast<int>((k / prods[c]) % static_cast<std::size_t>(Nc[c] + 1));
168 nc += nvec[c];
169 }
170
171 // ---- residence times, eq. (12) and its derivative eq. (13) ---------
172 for (std::size_t i = 0; i < M; ++i) {
173 for (std::size_t c = 0; c < Cn; ++c) {
174 if (nvec[c] <= 0) continue;
175 const std::size_t kc = k - prods[c];
176 const std::size_t cls = closedClasses[c];
177 T acc = zero;
178 for (std::size_t p = 0; p < P; ++p) dacc[p] = zero;
179 for (int n = 1; n <= nc; ++n) {
180 const T Pprev = Pc[kc](i, static_cast<std::size_t>(n - 1));
181 const T nT = num_traits<T>::from_int(n);
182 acc += nT * ec.EC(i, static_cast<std::size_t>(n - 1)) * Pprev;
183 for (std::size_t p = 0; p < P; ++p)
184 dacc[p] += nT * (ec.dEC(i, static_cast<std::size_t>(n - 1)) * dLo(i, p) * Pprev +
185 ec.EC(i, static_cast<std::size_t>(n - 1)) *
186 dPc[kc][i](static_cast<std::size_t>(n - 1), p));
187 }
188 w[k](i, c) = Dc(i, c) * acc;
189 for (std::size_t p = 0; p < P; ++p) {
190 T dwq = Dc(i, c) * dacc[p];
191 if (p == pidx(i, cls)) dwq += Dc(i, c) * acc; // d(D y)/dy = D
192 dw[k][i](c, p) = dwq;
193 }
194 }
195 }
196
197 // ---- throughputs, eq. (18) -------------------------------------------
198 for (std::size_t c = 0; c < Cn; ++c) {
199 T sw = zero;
200 for (std::size_t i = 0; i < M; ++i) sw += w[k](i, c);
201 const T den = Zc[c] + sw;
202 if (den == zero) {
203 x(k, c) = zero;
204 continue;
205 }
206 const T nT = num_traits<T>::from_int(nvec[c]);
207 x(k, c) = nT / den;
208 if (nvec[c] > 0) {
209 const T den2 = den * den;
210 for (std::size_t p = 0; p < P; ++p) {
211 T sdw = zero;
212 for (std::size_t i = 0; i < M; ++i) sdw += dw[k][i](c, p);
213 dx[k](c, p) = -nT / den2 * sdw;
214 }
215 }
216 }
217
218 // ---- conditional marginals, eq. (14)-(15) -----------------------------
219 for (std::size_t i = 0; i < M; ++i) {
220 for (int n = 1; n <= nc; ++n) {
221 const std::size_t nu = static_cast<std::size_t>(n);
222 for (std::size_t c = 0; c < Cn; ++c) {
223 if (nvec[c] <= 0) continue;
224 const std::size_t kc = k - prods[c];
225 const std::size_t cls = closedClasses[c];
226 const T Pprev = Pc[kc](i, nu - 1);
227 const T ECn = ec.EC(i, nu - 1);
228 Pc[k](i, nu) += Dc(i, c) * ECn * x(k, c) * Pprev;
229 for (std::size_t p = 0; p < P; ++p) {
230 T dt = Dc(i, c) * (ec.dEC(i, nu - 1) * dLo(i, p) * x(k, c) * Pprev +
231 ECn * dx[k](c, p) * Pprev +
232 ECn * x(k, c) * dPc[kc][i](nu - 1, p));
233 if (p == pidx(i, cls)) dt += Dc(i, c) * ECn * x(k, c) * Pprev;
234 dPc[k][i](nu, p) += dt;
235 }
236 }
237 }
238 // eq. (17), with the reference's floor on the primal only
239 T s1 = zero;
240 for (int n = 1; n <= nc; ++n) s1 += Pc[k](i, static_cast<std::size_t>(n));
241 const T epsT = num_traits<T>::from_double(2.220446049250313e-16);
242 const T p0 = one - s1;
243 Pc[k](i, 0) = p0 > epsT ? p0 : epsT;
244 for (std::size_t p = 0; p < P; ++p) {
245 T ds = zero;
246 for (int n = 1; n <= nc; ++n) ds += dPc[k][i](static_cast<std::size_t>(n), p);
247 dPc[k][i](0, p) = -ds;
248 }
249 }
250 }
251
252 // ---- measures at the full population -----------------------------------
253 const std::size_t kN = NT - 1;
255 res.XN.assign(R, zero);
256 res.QN = Matrix<T>(M, R, zero);
257 res.UN = Matrix<T>(M, R, zero);
258 res.CN = Matrix<T>(M, R, zero);
259 std::vector<Matrix<T>> dQN(P, Matrix<T>(M, R, zero));
260
261 // closed classes, eq. (32)
262 for (std::size_t c = 0; c < Cn; ++c) {
263 const std::size_t cls = closedClasses[c];
264 res.XN[cls] = x(kN, c);
265 const std::size_t kc = Nc[c] > 0 ? kN - prods[c] : kN;
266 for (std::size_t i = 0; i < M; ++i) {
267 res.CN(i, cls) = w[kN](i, c);
268 res.QN(i, cls) = res.XN[cls] * res.CN(i, cls);
269 for (std::size_t p = 0; p < P; ++p)
270 dQN[p](i, cls) = dx[kN](c, p) * w[kN](i, c) + x(kN, c) * dw[kN][i](c, p);
271 T uacc = zero;
272 for (int n = 1; n <= NCtot; ++n) {
273 const std::size_t nu = static_cast<std::size_t>(n);
274 uacc += Dc(i, c) * x(kN, c) * ec.Eprime(i, nu - 1) / ec.E(i, nu - 1) *
275 Pc[kc](i, nu - 1);
276 }
277 res.UN(i, cls) = uacc;
278 }
279 }
280
281 // open classes, eq. (33)
282 for (std::size_t oi = 0; oi < openClasses.size(); ++oi) {
283 const std::size_t r = openClasses[oi];
284 res.XN[r] = lambda[r];
285 for (std::size_t i = 0; i < M; ++i) {
286 T acc = zero;
287 for (std::size_t p = 0; p < P; ++p) dacc[p] = zero;
288 for (int n = 0; n <= NCtot; ++n) {
289 const std::size_t nu = static_cast<std::size_t>(n);
290 const T Pn = Pc[kN](i, nu);
291 const T nT = num_traits<T>::from_int(n + 1);
292 acc += nT * ec.EC(i, nu) * Pn;
293 for (std::size_t p = 0; p < P; ++p)
294 dacc[p] += nT * (ec.dEC(i, nu) * dLo(i, p) * Pn +
295 ec.EC(i, nu) * dPc[kN][i](nu, p));
296 }
297 res.QN(i, r) = lambda[r] * D(i, r) * acc;
298 if (lambda[r] != zero) res.CN(i, r) = res.QN(i, r) / lambda[r];
299 for (std::size_t p = 0; p < P; ++p) {
300 T dq = lambda[r] * D(i, r) * dacc[p];
301 if (p == pidx(i, r)) dq += lambda[r] * D(i, r) * acc;
302 dQN[p](i, r) = dq;
303 }
304 T uacc = zero;
305 for (int n = 0; n <= NCtot; ++n) {
306 const std::size_t nu = static_cast<std::size_t>(n);
307 uacc += lambda[r] * ec.Eprime(i, nu + 1) / ec.E(i, nu + 1) * Pc[kN](i, nu);
308 }
309 res.UN(i, r) = uacc;
310 }
311 }
312
313 // ---- moments -------------------------------------------------------------
314 res.QCovFull = Matrix<T>(M * R, M * R, zero);
315 for (std::size_t i = 0; i < M; ++i)
316 for (std::size_t r = 0; r < R; ++r)
317 for (std::size_t j = 0; j < M; ++j)
318 for (std::size_t s = 0; s < R; ++s)
319 res.QCovFull(i * R + r, j * R + s) = dQN[pidx(j, s)](i, r);
320 res.QCovAsym = zero;
321 for (std::size_t a = 0; a < M * R; ++a)
322 for (std::size_t b = 0; b < M * R; ++b) {
323 const T d = num_abs(T(res.QCovFull(a, b) - res.QCovFull(b, a)));
324 if (d > res.QCovAsym) res.QCovAsym = d;
325 }
326 for (std::size_t a = 0; a < M * R; ++a)
327 for (std::size_t b = a + 1; b < M * R; ++b) {
328 const T avg = (res.QCovFull(a, b) + res.QCovFull(b, a)) / num_traits<T>::from_int(2);
329 res.QCovFull(a, b) = avg;
330 res.QCovFull(b, a) = avg;
331 }
332
333 res.QCov.assign(M, Matrix<T>(R, R, zero));
334 res.QVar = Matrix<T>(M, R, zero);
335 res.QTotVar.assign(M, zero);
336 for (std::size_t i = 0; i < M; ++i) {
337 T tot = zero;
338 for (std::size_t r = 0; r < R; ++r) {
339 for (std::size_t s = 0; s < R; ++s) {
340 res.QCov[i](r, s) = res.QCovFull(i * R + r, i * R + s);
341 tot += res.QCov[i](r, s);
342 }
343 res.QVar(i, r) = res.QCov[i](r, r);
344 }
345 res.QTotVar[i] = tot;
346 }
347 return res;
348}
349
350} // namespace pfqn
351} // namespace line
352
353#endif // LINE_API_PFQN_SENS_MVALDMX_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
The exception types the port throws.
Dense matrix and non-owning view.
SensLdmxEcResult< T > pfqn_sens_ldmx_ec(const std::vector< T > &lambda, const Matrix< T > &D, const Matrix< T > &mu)
Effective capacity terms of the mixed load-dependent MVA of Bruell-Balbo-Afshari, together with their...
SensMvaldmxResult< T > pfqn_sens_mvaldmx(const std::vector< T > &lambda, const Matrix< T > &D, const std::vector< int > &N, const std::vector< T > &Z, const Matrix< T > &mu)
Exact queue-length variances and covariances of a mixed open/closed product-form network with limited...
std::size_t population_count(const std::vector< int > &N)
Number of population vectors n with 0 <= n <= N.
Definition population.h:38
T num_abs(const T &v)
Definition number.h:172
std::vector< std::size_t > plane_sizes(const std::vector< int > &N)
Mixed-radix plane sizes: prods[r] = prod_{s<r} (N[s]+1).
Definition population.h:27
Number-type abstraction for the templated API port.
Effective capacity terms of the mixed load-dependent MVA of Bruell-Balbo-Afshari, together with their...
Population-vector enumeration and combinatorics.
Matrix< T > dEC
(M x Nt) dEC(i,n)/dLo(i)
Matrix< T > E
(M x Nt+1) E-function, column n
Matrix< T > EC
(M x Nt) effective capacity, EC(i,n-1) for n = 1..Nt
Matrix< T > Eprime
(M x Nt+1) Eprime-function, column n
Matrix< T > CN
(M x R) residence time (MATLAB field .R)
Matrix< T > UN
(M x R) utilization
T QCovAsym
residual before symmetrization
Matrix< T > QCovFull
(M*R x M*R) Cov[n(i,r),n(j,s)] at row i*R+r, column j*R+s.
std::vector< Matrix< T > > QCov
(M) matrices R x R, same-station covariance
std::vector< T > XN
(R) throughput
Matrix< T > QN
(M x R) mean queue length