LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_sens_ldmx_ec.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_LDMX_EC_H
6#define LINE_API_PFQN_SENS_LDMX_EC_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Effective capacity terms of the mixed load-dependent MVA of
12 * Bruell-Balbo-Afshari, together with their exact derivatives with respect to
13 * the open-class load Lo(i) of each station.
14 *
15 * Templated port of matlab/src/api/pfqn/pfqn_sens_ldmx_ec.m. The primal terms
16 * duplicate pfqn_ldmx_ec.h; they are recomputed here rather than reused
17 * because every intermediate (E1, F2, F3, F2prime) has to be carried alongside
18 * its derivative, and the reference does the same.
19 *
20 * Lo(i) = sum_r lambda(r) D(i,r) is the only channel through which a demand
21 * enters E, Eprime and EC, since the load-dependent rates mu do not depend on
22 * the demands. Station i's terms depend on Lo(i) alone, so one derivative per
23 * station suffices and the chain rule then gives the derivative with respect
24 * to any demand-scaling parameter. This is the factorization behind equations
25 * (19), (21) and (24)-(31) of the reference, reproduced here term by term.
26 *
27 * Arithmetic. Every term is a rational function of Lo and of the reciprocal
28 * rates, differentiated symbolically, so the routine stays in the field and
29 * instantiates at line::Rational with no gate.
30 *
31 * Reference: I. F. Akyildiz and J. C. Strelen, "Moment Analysis for
32 * Load-Dependent Mixed Product Form Queueing Networks", IEEE Trans.
33 * Communications 39(6):828-832, 1991.
34 */
35
36#include <cstddef>
37#include <vector>
38
39#include "line/num/number.h"
40#include "line/util/error.h"
41#include "line/util/matrix.h"
42
43namespace line {
44namespace pfqn {
45
46template <class T>
48 Matrix<T> EC; ///< (M x Nt) effective capacity, EC(i,n-1) for n = 1..Nt
49 Matrix<T> E; ///< (M x Nt+1) E-function, column n
50 Matrix<T> Eprime; ///< (M x Nt+1) Eprime-function, column n
51 std::vector<T> Lo; ///< (M) open load per station
52 Matrix<T> dEC; ///< (M x Nt) dEC(i,n)/dLo(i)
53 Matrix<T> dE; ///< (M x Nt+1) dE(i,n)/dLo(i)
54 Matrix<T> dEprime; ///< (M x Nt+1) dEprime(i,n)/dLo(i)
55};
56
57/**
58 * @brief Effective capacity terms of the mixed load-dependent MVA of
59 * Bruell-Balbo-Afshari, together with their exact derivatives with
60 * respect to the open-class load Lo(i) of each station.
61 *
62 * @param lambda (R) arrival rates, zero on the closed classes
63 * @param D (M x R) service demands
64 * @param mu (M x Nt) load-dependent rate lattice, limited load dependence
65 */
66template <class T>
67SensLdmxEcResult<T> pfqn_sens_ldmx_ec(const std::vector<T>& lambda, const Matrix<T>& D,
68 const Matrix<T>& mu) {
69 const std::size_t M = mu.rows();
70 const std::size_t Nt = mu.cols();
71 if (M == 0 || Nt == 0) throw InputError("pfqn_sens_ldmx_ec: the rate lattice is empty");
72 if (D.rows() != M)
73 throw InputError("pfqn_sens_ldmx_ec: D and mu disagree on the station count");
74 if (lambda.size() != D.cols())
75 throw InputError("pfqn_sens_ldmx_ec: lambda and D disagree on the class count");
76
77 const T zero = num_traits<T>::from_int(0);
78 const T one = num_traits<T>::from_int(1);
79
81 res.Lo.assign(M, zero);
82 for (std::size_t i = 0; i < M; ++i)
83 for (std::size_t r = 0; r < lambda.size(); ++r) res.Lo[i] += lambda[r] * D(i, r);
84
85 std::vector<std::size_t> b(M, 1);
86 for (std::size_t i = 0; i < M; ++i) {
87 const T& last = mu(i, Nt - 1);
88 std::size_t k = 1;
89 while (k < Nt && mu(i, k - 1) != last) ++k;
90 b[i] = k;
91 }
92
93 // padding-width rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
94 const std::size_t Cw = 2 * Nt + 2;
95 Matrix<T> C(M, Cw, zero);
96 for (std::size_t i = 0; i < M; ++i)
97 for (std::size_t k = 0; k < Cw; ++k) {
98 const T& rate = k < Nt ? mu(i, k) : mu(i, Nt - 1);
99 if (rate == zero) throw NumericError("pfqn_sens_ldmx_ec: a load-dependent rate is zero");
100 C(i, k) = one / rate;
101 }
102 // 1-based rate index of the reference.
103 const auto Cq = [&](std::size_t i, std::size_t k) -> const T& { return C(i, k - 1); };
104
105 res.EC = Matrix<T>(M, Nt, zero);
106 res.E = Matrix<T>(M, Nt + 1, zero);
107 res.Eprime = Matrix<T>(M, Nt + 1, zero);
108 res.dEC = Matrix<T>(M, Nt, zero);
109 res.dE = Matrix<T>(M, Nt + 1, zero);
110 res.dEprime = Matrix<T>(M, Nt + 1, zero);
111
112 for (std::size_t i = 0; i < M; ++i) {
113 const std::size_t bi = b[i];
114 const T Cb = Cq(i, bi);
115 const T den = one - res.Lo[i] * Cb;
116 if (den == zero)
117 throw NumericError(
118 "pfqn_sens_ldmx_ec: the station is saturated by the open classes (Lo * C(b) = 1)");
119 const std::size_t nhead = bi >= 2 ? bi - 1 : 0; // n0 = 0 .. bi-2
120
121 std::vector<T> E1(Nt + 1, zero), dE1(Nt + 1, zero);
122 std::vector<T> E2(Nt + 1, zero), dE2(Nt + 1, zero);
123 std::vector<T> E3(Nt + 1, zero), dE3(Nt + 1, zero);
124 std::vector<T> E2p(Nt + 1, zero), dE2p(Nt + 1, zero);
125 Matrix<T> F2(Nt + 1, nhead + 1, zero), dF2(Nt + 1, nhead + 1, zero);
126 Matrix<T> F3(Nt + 1, nhead + 1, zero), dF3(Nt + 1, nhead + 1, zero);
127 Matrix<T> F2p(Nt + 1, nhead + 1, zero), dF2p(Nt + 1, nhead + 1, zero);
128
129 for (std::size_t n = 0; n <= Nt; ++n) {
130 if (n >= bi) {
131 // E(n) = 1/den^(n+1) => dE/dLo = (n+1) Cb / den^(n+2)
132 res.E(i, n) = one / num_pow_int(den, static_cast<unsigned>(n + 1));
133 res.dE(i, n) = num_traits<T>::from_int(static_cast<long>(n + 1)) * Cb /
134 num_pow_int(den, static_cast<unsigned>(n + 2));
135 res.Eprime(i, n) = Cb * res.E(i, n);
136 res.dEprime(i, n) = Cb * res.dE(i, n);
137 continue;
138 }
139 // ---- E1, eq. (25)-(26) -----------------------------------------
140 if (n == 0) {
141 E1[0] = one / den;
142 dE1[0] = Cb / (den * den);
143 for (std::size_t j = 1; j + 1 <= bi; ++j) {
144 E1[0] *= Cq(i, j) / Cb;
145 dE1[0] *= Cq(i, j) / Cb;
146 }
147 } else {
148 const T fac = Cb / Cq(i, n);
149 E1[n] = one / den * fac * E1[n - 1];
150 dE1[n] = Cb / (den * den) * fac * E1[n - 1] + one / den * fac * dE1[n - 1];
151 }
152
153 // ---- F2, eq. (27)-(28) ------------------------------------------
154 for (std::size_t n0 = 0; n0 <= nhead; ++n0) {
155 if (n0 == 0) {
156 F2(n, 0) = one;
157 dF2(n, 0) = zero;
158 } else {
159 const T coef = num_traits<T>::from_int(static_cast<long>(n + n0)) /
160 num_traits<T>::from_int(static_cast<long>(n0)) * Cq(i, n + n0);
161 F2(n, n0) = coef * res.Lo[i] * F2(n, n0 - 1);
162 dF2(n, n0) = coef * (F2(n, n0 - 1) + res.Lo[i] * dF2(n, n0 - 1));
163 }
164 }
165 E2[n] = zero;
166 dE2[n] = zero;
167 for (std::size_t n0 = 0; n0 < nhead; ++n0) { // n0 = 0 .. bi-2
168 E2[n] += F2(n, n0);
169 dE2[n] += dF2(n, n0);
170 }
171
172 // ---- F3, eq. (29)-(30) ------------------------------------------
173 for (std::size_t n0 = 0; n0 <= nhead; ++n0) {
174 if (n == 0 && n0 == 0) {
175 F3(0, 0) = one;
176 for (std::size_t j = 1; j + 1 <= bi; ++j) F3(0, 0) *= Cq(i, j) / Cb;
177 dF3(0, 0) = zero;
178 } else if (n > 0 && n0 == 0) {
179 const T fac = Cb / Cq(i, n);
180 F3(n, 0) = fac * F3(n - 1, 0);
181 dF3(n, 0) = fac * dF3(n - 1, 0);
182 } else {
183 const T coef = num_traits<T>::from_int(static_cast<long>(n + n0)) /
184 num_traits<T>::from_int(static_cast<long>(n0)) * Cb;
185 F3(n, n0) = coef * res.Lo[i] * F3(n, n0 - 1);
186 dF3(n, n0) = coef * (F3(n, n0 - 1) + res.Lo[i] * dF3(n, n0 - 1));
187 }
188 }
189 E3[n] = zero;
190 dE3[n] = zero;
191 for (std::size_t n0 = 0; n0 < nhead; ++n0) {
192 E3[n] += F3(n, n0);
193 dE3[n] += dF3(n, n0);
194 }
195
196 // ---- F2prime -----------------------------------------------------
197 for (std::size_t n0 = 0; n0 <= nhead; ++n0) {
198 if (n0 == 0) {
199 F2p(n, 0) = Cq(i, n + 1);
200 dF2p(n, 0) = zero;
201 } else {
202 const T coef = num_traits<T>::from_int(static_cast<long>(n + n0)) /
203 num_traits<T>::from_int(static_cast<long>(n0)) *
204 Cq(i, n + n0 + 1);
205 F2p(n, n0) = coef * res.Lo[i] * F2p(n, n0 - 1);
206 dF2p(n, n0) = coef * (F2p(n, n0 - 1) + res.Lo[i] * dF2p(n, n0 - 1));
207 }
208 }
209 E2p[n] = zero;
210 dE2p[n] = zero;
211 for (std::size_t n0 = 0; n0 < nhead; ++n0) {
212 E2p[n] += F2p(n, n0);
213 dE2p[n] += dF2p(n, n0);
214 }
215
216 // ---- E = E1 + E2 - E3, eq. (23)-(24) ------------------------------
217 res.E(i, n) = E1[n] + E2[n] - E3[n];
218 res.dE(i, n) = dE1[n] + dE2[n] - dE3[n];
219 if (n + 1 < bi) {
220 res.Eprime(i, n) = Cb * E1[n] + E2p[n] - Cb * E3[n];
221 res.dEprime(i, n) = Cb * dE1[n] + dE2p[n] - Cb * dE3[n];
222 } else {
223 res.Eprime(i, n) = Cb * res.E(i, n);
224 res.dEprime(i, n) = Cb * res.dE(i, n);
225 }
226 }
227
228 // EC(n) = C(n) E(n)/E(n-1), eq. (19), and the quotient rule for dEC.
229 for (std::size_t n = 1; n <= Nt; ++n) {
230 if (res.E(i, n - 1) == zero) throw NumericError("pfqn_sens_ldmx_ec: E vanishes");
231 res.EC(i, n - 1) = Cq(i, n) * res.E(i, n) / res.E(i, n - 1);
232 res.dEC(i, n - 1) = Cq(i, n) *
233 (res.dE(i, n) * res.E(i, n - 1) - res.E(i, n) * res.dE(i, n - 1)) /
234 (res.E(i, n - 1) * res.E(i, n - 1));
235 }
236 }
237
238 return res;
239}
240
241} // namespace pfqn
242} // namespace line
243
244#endif // LINE_API_PFQN_SENS_LDMX_EC_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.
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...
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:192
Number-type abstraction for the templated API port.
std::vector< T > Lo
(M) open load per station
Matrix< T > dEprime
(M x Nt+1) dEprime(i,n)/dLo(i)
Matrix< T > dE
(M x Nt+1) dE(i,n)/dLo(i)
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