LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_sens_momldmx.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_MOMLDMX_H
6#define LINE_API_PFQN_SENS_MOMLDMX_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Exact higher queue-length moments (up to order three) of mixed open/closed
12 * product-form networks with limited load dependence.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_sens_momldmx.m. Theorem 1 of
15 * Akyildiz and Strelen (IEEE TComm 39(6):828-832, 1991) states that one
16 * further factor Q_(j,T) in a moment costs one derivative with respect to a
17 * parameter y_j scaling the demands of the classes in T at station j. With
18 * m = E[Q_(j,T)]:
19 *
20 * Var[Q_jT] = dm/dy_j
21 * E[Q_jT^3] = d2m/dy_j^2 + (1 + 3m) dm/dy_j + m^3
22 * Cov[Q_iT,Q_jT'] = dm_iT/dy_(j,T')
23 *
24 * The derivatives come from second-order forward-mode differentiation of the
25 * Bruell-Balbo-Afshari mixed load-dependent MVA (the recursion of
26 * pfqn_mvaldmx and pfqn_sens_mvaldmx), with the effective-capacity second
27 * derivatives supplied by pfqn_sens_ldmx_ec. The result struct is the one of
28 * pfqn_sens_mom, so the two are interchangeable on closed load-independent
29 * input.
30 *
31 * Arithmetic. Field-only apart from the skewness, as in pfqn_sens_mom; the
32 * primal Pc(0) keeps the reference's eps floor.
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"
45
46namespace line {
47namespace pfqn {
48
49/**
50 * @brief Exact moments E[Q], Var[Q], E[Q^2], E[Q^3] and covariances of the
51 * grouped queue lengths of a mixed load-dependent product-form network.
52 *
53 * @param lambda (R) arrival rates, zero on closed classes
54 * @param D (M x R) service demands
55 * @param N (R) closed populations; a negative entry marks an open class
56 * @param Z (R) think times
57 * @param mu (M x max(1,sum N closed)) limited load-dependent rates
58 * @param groups (R) 0-based group label of each class; empty means one group
59 * holding every class (per-station totals)
60 */
61template <class T>
62SensMomResult<T> pfqn_sens_momldmx(const std::vector<T>& lambda, const Matrix<T>& D,
63 const std::vector<int>& N, const std::vector<T>& Z,
64 const Matrix<T>& mu, const std::vector<int>& groups) {
65 const std::size_t M = D.rows();
66 const std::size_t R = D.cols();
67 if (lambda.size() != R || N.size() != R || Z.size() != R)
68 throw InputError("pfqn_sens_momldmx: lambda, N, Z and D disagree on the class count");
69 if (mu.rows() != M)
70 throw InputError("pfqn_sens_momldmx: mu and D disagree on the station count");
71
72 std::vector<int> grp = groups.empty() ? std::vector<int>(R, 0) : groups;
73 if (grp.size() != R)
74 throw InputError("pfqn_sens_momldmx: groups must have one label per class");
75 std::size_t G = 0;
76 for (int g : grp) {
77 if (g < 0) throw InputError("pfqn_sens_momldmx: group labels start at zero");
78 if (static_cast<std::size_t>(g) + 1 > G) G = static_cast<std::size_t>(g) + 1;
79 }
80 {
81 std::vector<bool> seen(G, false);
82 for (int g : grp) seen[static_cast<std::size_t>(g)] = true;
83 for (std::size_t g = 0; g < G; ++g)
84 if (!seen[g])
85 throw InputError(
86 "pfqn_sens_momldmx: groups must label the classes consecutively with no empty group");
87 }
88
89 const T zero = num_traits<T>::from_int(0);
90 const T one = num_traits<T>::from_int(1);
91 const T two = num_traits<T>::from_int(2);
92
93 std::vector<std::size_t> openClasses, closedClasses;
94 for (std::size_t r = 0; r < R; ++r) {
95 if (N[r] < 0) {
96 openClasses.push_back(r);
97 } else {
98 if (lambda[r] != zero && N[r] > 0)
99 throw InputError("pfqn_sens_momldmx: an arrival rate cannot be set on a closed class");
100 closedClasses.push_back(r);
101 }
102 }
103 const std::size_t Cn = closedClasses.size();
104
105 std::vector<int> Nc(Cn, 0);
106 std::vector<T> Zc(Cn, zero);
107 Matrix<T> Dc(M, Cn, zero);
108 int NCtot = 0;
109 for (std::size_t c = 0; c < Cn; ++c) {
110 Nc[c] = N[closedClasses[c]];
111 Zc[c] = Z[closedClasses[c]];
112 NCtot += Nc[c];
113 for (std::size_t i = 0; i < M; ++i) Dc(i, c) = D(i, closedClasses[c]);
114 }
115 if (static_cast<int>(mu.cols()) < (NCtot > 1 ? NCtot : 1))
116 throw InputError(
117 "pfqn_sens_momldmx: the load-dependent rates must be given up to the maximum closed "
118 "population");
119
120 Matrix<T> mux(M, mu.cols() + 1, zero);
121 for (std::size_t i = 0; i < M; ++i) {
122 for (std::size_t k = 0; k < mu.cols(); ++k) mux(i, k) = mu(i, k);
123 mux(i, mu.cols()) = mu(i, mu.cols() - 1);
124 }
125 const SensLdmxEcResult<T> ec = pfqn_sens_ldmx_ec(lambda, D, mux);
126
127 // ---- parameter list: y(h,g) multiplies D(h,r) for every r in group g ----
128 const std::size_t P = M * G;
129 const auto pidx = [&](std::size_t h, std::size_t g) { return h * G + g; };
130 // eq. (21): Lo is linear in the open demands, so d2Lo = 0.
131 Matrix<T> dLo(M, P, zero);
132 for (std::size_t j = 0; j < M; ++j)
133 for (std::size_t oi = 0; oi < openClasses.size(); ++oi) {
134 const std::size_t o = openClasses[oi];
135 dLo(j, pidx(j, static_cast<std::size_t>(grp[o]))) += lambda[o] * D(j, o);
136 }
137
138 // ---- population lattice over the closed classes, first class fastest ----
139 const std::vector<std::size_t> prods = plane_sizes(Nc);
140 const std::size_t NT = population_count(Nc);
141 const std::size_t NL = static_cast<std::size_t>(NCtot) + 1;
142
143 std::vector<Matrix<T>> Pc(NT, Matrix<T>(M, NL, zero));
144 std::vector<std::vector<Matrix<T>>> dPc(NT, std::vector<Matrix<T>>(M, Matrix<T>(NL, P, zero)));
145 std::vector<std::vector<Matrix<T>>> d2Pc(NT, std::vector<Matrix<T>>(M, Matrix<T>(NL, P, zero)));
146 Matrix<T> x(NT, Cn, zero);
147 std::vector<Matrix<T>> dx(NT, Matrix<T>(Cn, P, zero));
148 std::vector<Matrix<T>> d2x(NT, Matrix<T>(Cn, P, zero));
149 std::vector<Matrix<T>> w(NT, Matrix<T>(M, Cn, zero));
150 std::vector<std::vector<Matrix<T>>> dw(NT, std::vector<Matrix<T>>(M, Matrix<T>(Cn, P, zero)));
151 std::vector<std::vector<Matrix<T>>> d2w(NT, std::vector<Matrix<T>>(M, Matrix<T>(Cn, P, zero)));
152
153 for (std::size_t i = 0; i < M; ++i) Pc[0](i, 0) = one; // eq. (16)
154
155 std::vector<T> dacc(P, zero), d2acc(P, zero);
156 for (std::size_t k = 0; k < NT; ++k) {
157 std::vector<int> nvec(Cn, 0);
158 int nc = 0;
159 for (std::size_t c = 0; c < Cn; ++c) {
160 nvec[c] = static_cast<int>((k / prods[c]) % static_cast<std::size_t>(Nc[c] + 1));
161 nc += nvec[c];
162 }
163 if (nc == 0) continue; // the empty population keeps Pc(0) = 1
164
165 // ---- residence times, eq. (12)-(13) ------------------------------------
166 for (std::size_t i = 0; i < M; ++i) {
167 for (std::size_t c = 0; c < Cn; ++c) {
168 if (nvec[c] <= 0) continue;
169 const std::size_t kc = k - prods[c];
170 const std::size_t pc = pidx(i, static_cast<std::size_t>(grp[closedClasses[c]]));
171 T acc = zero;
172 for (std::size_t p = 0; p < P; ++p) {
173 dacc[p] = zero;
174 d2acc[p] = zero;
175 }
176 for (int n = 1; n <= nc; ++n) {
177 const std::size_t nu = static_cast<std::size_t>(n);
178 const T Pp = Pc[kc](i, nu - 1);
179 const T nT = num_traits<T>::from_int(n);
180 const T ECn = ec.EC(i, nu - 1);
181 const T dECn = ec.dEC(i, nu - 1);
182 const T d2ECn = ec.d2EC(i, nu - 1);
183 acc += nT * ECn * Pp;
184 for (std::size_t p = 0; p < P; ++p) {
185 const T dL = dLo(i, p);
186 const T dPp = dPc[kc][i](nu - 1, p);
187 dacc[p] += nT * (dECn * dL * Pp + ECn * dPp);
188 d2acc[p] += nT * (d2ECn * dL * dL * Pp + two * dECn * dL * dPp +
189 ECn * d2Pc[kc][i](nu - 1, p));
190 }
191 }
192 w[k](i, c) = Dc(i, c) * acc;
193 for (std::size_t p = 0; p < P; ++p) {
194 if (p == pc) { // d(D y)/dy = D
195 dw[k][i](c, p) = Dc(i, c) * (dacc[p] + acc);
196 d2w[k][i](c, p) = Dc(i, c) * (d2acc[p] + two * dacc[p]);
197 } else {
198 dw[k][i](c, p) = Dc(i, c) * dacc[p];
199 d2w[k][i](c, p) = Dc(i, c) * d2acc[p];
200 }
201 }
202 }
203 }
204
205 // ---- throughputs, eq. (18) ---------------------------------------------
206 for (std::size_t c = 0; c < Cn; ++c) {
207 T sw = zero;
208 for (std::size_t i = 0; i < M; ++i) sw += w[k](i, c);
209 const T den = Zc[c] + sw;
210 if (den == zero) {
211 x(k, c) = zero;
212 continue;
213 }
214 const T nT = num_traits<T>::from_int(nvec[c]);
215 x(k, c) = nT / den;
216 if (nvec[c] > 0) {
217 const T den2 = den * den;
218 const T den3 = den2 * den;
219 for (std::size_t p = 0; p < P; ++p) {
220 T sdw = zero, sd2w = zero;
221 for (std::size_t i = 0; i < M; ++i) {
222 sdw += dw[k][i](c, p);
223 sd2w += d2w[k][i](c, p);
224 }
225 dx[k](c, p) = -nT * sdw / den2;
226 d2x[k](c, p) = -nT * sd2w / den2 + two * nT * sdw * sdw / den3;
227 }
228 }
229 }
230
231 // ---- conditional marginals, eq. (14)-(17) --------------------------------
232 for (std::size_t i = 0; i < M; ++i) {
233 for (int n = 1; n <= nc; ++n) {
234 const std::size_t nu = static_cast<std::size_t>(n);
235 for (std::size_t c = 0; c < Cn; ++c) {
236 if (nvec[c] <= 0) continue;
237 const std::size_t kc = k - prods[c];
238 const std::size_t pc = pidx(i, static_cast<std::size_t>(grp[closedClasses[c]]));
239 const T Pp = Pc[kc](i, nu - 1);
240 const T ECn = ec.EC(i, nu - 1);
241 const T dECn = ec.dEC(i, nu - 1);
242 const T d2ECn = ec.d2EC(i, nu - 1);
243 const T xc = x(k, c);
244 const T t = ECn * xc * Pp;
245 Pc[k](i, nu) += Dc(i, c) * t;
246 for (std::size_t p = 0; p < P; ++p) {
247 const T dL = dLo(i, p);
248 const T dE1 = dECn * dL;
249 const T d2E1 = d2ECn * dL * dL;
250 const T dxc = dx[k](c, p);
251 const T dPp = dPc[kc][i](nu - 1, p);
252 const T dt = dE1 * xc * Pp + ECn * dxc * Pp + ECn * xc * dPp;
253 const T d2t = d2E1 * xc * Pp + ECn * d2x[k](c, p) * Pp +
254 ECn * xc * d2Pc[kc][i](nu - 1, p) +
255 two * (dE1 * dxc * Pp + dE1 * xc * dPp + ECn * dxc * dPp);
256 if (p == pc) {
257 dPc[k][i](nu, p) += Dc(i, c) * (dt + t);
258 d2Pc[k][i](nu, p) += Dc(i, c) * (d2t + two * dt);
259 } else {
260 dPc[k][i](nu, p) += Dc(i, c) * dt;
261 d2Pc[k][i](nu, p) += Dc(i, c) * d2t;
262 }
263 }
264 }
265 }
266 // eq. (17), with the reference's floor on the primal only
267 T s1 = zero;
268 for (int n = 1; n <= nc; ++n) s1 += Pc[k](i, static_cast<std::size_t>(n));
269 const T epsT = num_traits<T>::from_double(2.220446049250313e-16);
270 const T p0 = one - s1;
271 Pc[k](i, 0) = p0 > epsT ? p0 : epsT;
272 for (std::size_t p = 0; p < P; ++p) {
273 T ds = zero, d2s = zero;
274 for (int n = 1; n <= nc; ++n) {
275 ds += dPc[k][i](static_cast<std::size_t>(n), p);
276 d2s += d2Pc[k][i](static_cast<std::size_t>(n), p);
277 }
278 dPc[k][i](0, p) = -ds;
279 d2Pc[k][i](0, p) = -d2s;
280 }
281 }
282 }
283
284 // ---- measures at the full population -------------------------------------
285 const std::size_t kN = NT - 1;
287 res.XN.assign(R, zero);
288 res.QN = Matrix<T>(M, R, zero);
289 res.UN = Matrix<T>(M, R, zero);
290 res.CN = Matrix<T>(M, R, zero);
291 std::vector<Matrix<T>> dQN(P, Matrix<T>(M, R, zero));
292 std::vector<Matrix<T>> d2QN(P, Matrix<T>(M, R, zero));
293
294 // closed classes, eq. (32)
295 for (std::size_t c = 0; c < Cn; ++c) {
296 const std::size_t cls = closedClasses[c];
297 res.XN[cls] = x(kN, c);
298 const std::size_t kc = Nc[c] > 0 ? kN - prods[c] : kN;
299 for (std::size_t i = 0; i < M; ++i) {
300 res.CN(i, cls) = w[kN](i, c);
301 res.QN(i, cls) = res.XN[cls] * res.CN(i, cls);
302 for (std::size_t p = 0; p < P; ++p) {
303 dQN[p](i, cls) = dx[kN](c, p) * w[kN](i, c) + x(kN, c) * dw[kN][i](c, p);
304 d2QN[p](i, cls) = d2x[kN](c, p) * w[kN](i, c) + two * dx[kN](c, p) * dw[kN][i](c, p) +
305 x(kN, c) * d2w[kN][i](c, p);
306 }
307 if (Nc[c] > 0) {
308 T uacc = zero;
309 for (int n = 1; n <= NCtot; ++n) {
310 const std::size_t nu = static_cast<std::size_t>(n);
311 uacc += Dc(i, c) * x(kN, c) * ec.Eprime(i, nu - 1) / ec.E(i, nu - 1) *
312 Pc[kc](i, nu - 1);
313 }
314 res.UN(i, cls) = uacc;
315 }
316 }
317 }
318
319 // open classes, eq. (33)
320 for (std::size_t oi = 0; oi < openClasses.size(); ++oi) {
321 const std::size_t r = openClasses[oi];
322 res.XN[r] = lambda[r];
323 for (std::size_t i = 0; i < M; ++i) {
324 const std::size_t pr = pidx(i, static_cast<std::size_t>(grp[r]));
325 T acc = zero;
326 for (std::size_t p = 0; p < P; ++p) {
327 dacc[p] = zero;
328 d2acc[p] = zero;
329 }
330 for (int n = 0; n <= NCtot; ++n) {
331 const std::size_t nu = static_cast<std::size_t>(n);
332 const T Pn = Pc[kN](i, nu);
333 const T nT = num_traits<T>::from_int(n + 1);
334 const T ECn = ec.EC(i, nu);
335 const T dECn = ec.dEC(i, nu);
336 const T d2ECn = ec.d2EC(i, nu);
337 acc += nT * ECn * Pn;
338 for (std::size_t p = 0; p < P; ++p) {
339 const T dL = dLo(i, p);
340 const T dPn = dPc[kN][i](nu, p);
341 dacc[p] += nT * (dECn * dL * Pn + ECn * dPn);
342 d2acc[p] += nT * (d2ECn * dL * dL * Pn + two * dECn * dL * dPn +
343 ECn * d2Pc[kN][i](nu, p));
344 }
345 }
346 const T scale = lambda[r] * D(i, r);
347 res.QN(i, r) = scale * acc;
348 if (lambda[r] != zero) res.CN(i, r) = res.QN(i, r) / lambda[r];
349 for (std::size_t p = 0; p < P; ++p) {
350 if (p == pr) {
351 dQN[p](i, r) = scale * (dacc[p] + acc);
352 d2QN[p](i, r) = scale * (d2acc[p] + two * dacc[p]);
353 } else {
354 dQN[p](i, r) = scale * dacc[p];
355 d2QN[p](i, r) = scale * d2acc[p];
356 }
357 }
358 T uacc = zero;
359 for (int n = 0; n <= NCtot; ++n) {
360 const std::size_t nu = static_cast<std::size_t>(n);
361 uacc += lambda[r] * ec.Eprime(i, nu + 1) / ec.E(i, nu + 1) * Pc[kN](i, nu);
362 }
363 res.UN(i, r) = uacc;
364 }
365 }
366
367 // ---- group moments, packed exactly as pfqn_sens_mom ------------------------
368 res.m = Matrix<T>(M, G, zero);
369 res.d2m = Matrix<T>(M, G, zero);
370 res.Cov = Matrix<T>(P, P, zero);
371 res.dm = Matrix<T>(P, P, zero);
372 res.Var = Matrix<T>(M, G, zero);
373 res.M2 = Matrix<T>(M, G, zero);
374 res.M3 = Matrix<T>(M, G, zero);
375 res.Skew = Matrix<double>(M, G, 0.0);
376 res.CovAsym = zero;
377 for (std::size_t i = 0; i < M; ++i)
378 for (std::size_t r = 0; r < R; ++r) {
379 const std::size_t g = static_cast<std::size_t>(grp[r]);
380 res.m(i, g) += res.QN(i, r);
381 for (std::size_t j = 0; j < M; ++j)
382 for (std::size_t g2 = 0; g2 < G; ++g2)
383 res.dm(pidx(i, g), pidx(j, g2)) += dQN[pidx(j, g2)](i, r);
384 res.d2m(i, g) += d2QN[pidx(i, g)](i, r);
385 }
386 for (std::size_t a = 0; a < P; ++a)
387 for (std::size_t b = 0; b < P; ++b) {
388 const T d = num_abs(T(res.dm(a, b) - res.dm(b, a)));
389 if (d > res.CovAsym) res.CovAsym = d;
390 }
391 for (std::size_t a = 0; a < P; ++a)
392 for (std::size_t b = 0; b < P; ++b) res.Cov(a, b) = (res.dm(a, b) + res.dm(b, a)) / two;
393 for (std::size_t i = 0; i < M; ++i)
394 for (std::size_t g = 0; g < G; ++g) {
395 const T d1 = res.dm(pidx(i, g), pidx(i, g));
396 const T mg = res.m(i, g);
397 res.Var(i, g) = d1;
398 res.M2(i, g) = d1 + mg * mg;
399 res.M3(i, g) = res.d2m(i, g) + (one + num_traits<T>::from_int(3) * mg) * d1 + mg * mg * mg;
400 const T mu3 = res.M3(i, g) - num_traits<T>::from_int(3) * mg * res.M2(i, g) + two * mg * mg * mg;
401 const double var = num_traits<T>::to_double(res.Var(i, g));
402 res.Skew(i, g) = var > 0.0 ? num_traits<T>::to_double(mu3) / std::pow(var, 1.5)
403 : std::nan("");
404 }
405 return res;
406}
407
408} // namespace pfqn
409} // namespace line
410
411#endif // LINE_API_PFQN_SENS_MOMLDMX_H
InputError(const std::string &what)
Definition error.h:105
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...
SensMomResult< T > pfqn_sens_momldmx(const std::vector< T > &lambda, const Matrix< T > &D, const std::vector< int > &N, const std::vector< T > &Z, const Matrix< T > &mu, const std::vector< int > &groups)
Exact moments E[Q], Var[Q], E[Q^2], E[Q^3] and covariances of the grouped queue lengths of a mixed lo...
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
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:184
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...
Exact moments E[Q], Var[Q], E[Q^2] and E[Q^3] of the grouped queue lengths of a closed product-form n...
Population-vector enumeration and combinatorics.
Matrix< T > d2EC
(M x Nt) d2EC(i,n)/dLo(i)^2, for pfqn_sens_momldmx
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< double > Skew
(M x G) skewness, NaN where the variance vanishes
Matrix< T > UN
(M x R) utilization
std::vector< T > XN
(R) throughput
Matrix< T > CN
(M x R) residence time (MATLAB field .R)
Matrix< T > M3
(M x G) E[Q^3]
Matrix< T > Cov
(M*G x M*G) Cov[Q_(i,g),Q_(j,g')] at row i*G+g, column j*G+g'.
T CovAsym
raw asymmetry of Cov before symmetrization
Matrix< T > d2m
(M x G) the scaled pure second derivative
Matrix< T > M2
(M x G) E[Q^2]
Matrix< T > QN
(M x R) mean queue length
Matrix< T > m
(M x G) E[Q_(i,g)]
Matrix< T > Var
(M x G)
Matrix< T > dm
(M*G x M*G) the raw scaled first derivative, same layout