LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_looping.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_LOOPING_H
6#define LINE_API_PFQN_LOOPING_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Eager Looping bounds for closed multiclass product-form networks.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_looping.m, cross-checked against
14 * jar/src/main/java/jline/api/pfqn/mva/Pfqn_looping.java. D. L. Eager,
15 * "Bounding Algorithms for Queueing Network Models of Computer Systems",
16 * Ph.D. thesis, Tech. Rept. CSRG-156, University of Toronto, 1984. Looping
17 * supplies the initial pessimistic and optimistic estimates that the
18 * multiple-class performance bound hierarchy starts from, so it carries a pair
19 * of bounds rather than a single fixed point.
20 *
21 * It is built on the convolution identity of Zahorjan (1980)
22 *
23 * Q_jk(N - 1_c) = [X_j^{+k}(N - 1_c) / X_j(N)] Q_jk(N),
24 *
25 * with X_j^{+k}(N - 1_c) estimated from the level-0 multiple-class PBH upper
26 * bound B_j and X_j(N) from the optimistic response time R_j^(opt). A HEAP H_j
27 * is the class-j congestion that the current queue-length lower bounds have
28 * not yet accounted for; it is charged back at the pessimistic inflation
29 * factor V_c = max_k D_ck or the optimistic one L_c = min_k D_ck, which are
30 * the largest and smallest delays one customer can inflict. The level-0
31 * multiple-class PBH bounds on the mean response time are
32 *
33 * J_j(n) = sum_k D_jk, B_j(n) = sum_k D_jk + (sum(n) - 1) max_k D_jk,
34 *
35 * i.e. an arriving customer queues behind nobody, respectively behind every
36 * other customer in the network at its own worst centre.
37 *
38 * Arithmetic: sums, products, divisions, maxima and minima only, so each
39 * iterate is EXACT in rational arithmetic; the stopping rule selects which
40 * iterate is returned.
41 */
42
43#include <cmath>
44#include <cstddef>
45#include <vector>
46
47#include "line/num/number.h"
48#include "line/util/error.h"
49#include "line/util/matrix.h"
50
51namespace line {
52namespace pfqn {
53
54/** Return value of pfqn_looping: the throughput bracket plus its queue lengths. */
55template <class T>
57 std::vector<T> Xlo; ///< (R) pessimistic (lower) throughput bound
58 std::vector<T> Xup; ///< (R) optimistic (upper) throughput bound
59 Matrix<T> Q; ///< (M x R) queue lengths on the pessimistic side
60 Matrix<T> R; ///< (M x R) residence times
61 std::size_t iterations = 0;
62 bool converged = false;
63};
64
65/**
66 * @brief Eager Looping bounds for closed multiclass product-form networks.
67 *
68 * @param L (M x R) demands, @param N (R) populations,
69 * @param Z (R) think times (empty for none)
70 * @param tol convergence tolerance on the queue lengths
71 * @param maxiter iteration cap
72 */
73template <class T>
74LoopingBounds<T> pfqn_looping(const Matrix<T>& L, const std::vector<T>& N,
75 const std::vector<T>& Z, double tol = 1e-6,
76 std::size_t maxiter = 1000) {
77 const std::size_t M = L.rows(), R = L.cols();
78 if (N.size() != R) throw InputError("pfqn_looping: L and N disagree on the class count");
79 if (!Z.empty() && Z.size() != R) throw InputError("pfqn_looping: Z has the wrong length");
80
81 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
82 const T two = num_traits<T>::from_int(2);
84 r.Xlo.assign(R, zero);
85 r.Xup.assign(R, zero);
86 r.Q = Matrix<T>(M, R, zero);
87 r.R = Matrix<T>(M, R, zero);
88 if (M == 0) return r;
89
90 std::vector<T> Dtot(R, zero), Vpess(R, zero), Lopt(R, zero);
91 T Ntot = zero;
92 for (std::size_t c = 0; c < R; ++c) {
93 T mx = L(0, c), mn = L(0, c);
94 for (std::size_t k = 0; k < M; ++k) {
95 Dtot[c] += L(k, c);
96 if (L(k, c) > mx) mx = L(k, c);
97 if (L(k, c) < mn) mn = L(k, c);
98 }
99 Vpess[c] = mx;
100 Lopt[c] = mn;
101 Ntot += N[c];
102 }
103 // level-0 multiple-class PBH bounds on R_j, at population N - 1_c
104 std::vector<T> Jbnd(R, zero), Bm(R, zero);
105 for (std::size_t c = 0; c < R; ++c) {
106 Jbnd[c] = Dtot[c];
107 const T span = (Ntot > two) ? T(Ntot - two) : zero;
108 Bm[c] = T(Dtot[c] + span * Vpess[c]);
109 }
110
111 // Qm[c][j][k] = Q_jk(N - 1_c)
112 std::vector<std::vector<std::vector<T> > > Qm(
113 R, std::vector<std::vector<T> >(R, std::vector<T>(M, zero)));
114 const T Mt = num_traits<T>::from_int(static_cast<long>(M));
115 for (std::size_t c = 0; c < R; ++c)
116 for (std::size_t j = 0; j < R; ++j) {
117 const T nj = (c == j) ? T(N[j] - one) : N[j];
118 const T seed = (nj > zero) ? T(nj / Mt) : zero;
119 for (std::size_t k = 0; k < M; ++k) Qm[c][j][k] = seed;
120 }
121 Matrix<T> Hopt(R, R, zero), Hpess(R, R, zero); // (j, c)
122
123 std::vector<T> Rc(R, zero), Rpess(R, zero), Ropt(R, zero);
124 for (std::size_t it = 1; it <= maxiter; ++it) {
125 r.iterations = it;
126 const Matrix<T> Qprev = r.Q;
127
128 for (std::size_t c = 0; c < R; ++c) {
129 if (N[c] == zero) {
130 for (std::size_t k = 0; k < M; ++k) r.R(k, c) = zero;
131 r.Xlo[c] = zero;
132 Rc[c] = zero;
133 Rpess[c] = zero;
134 continue;
135 }
136 T rsum = zero;
137 for (std::size_t k = 0; k < M; ++k) {
138 T qk = zero;
139 for (std::size_t j = 0; j < R; ++j) qk += Qm[c][j][k];
140 r.R(k, c) = L(k, c) * T(one + qk);
141 rsum += r.R(k, c);
142 }
143 Rc[c] = rsum;
144 T hp = zero;
145 for (std::size_t j = 0; j < R; ++j) hp += Hpess(j, c);
146 const T zc = Z.empty() ? zero : Z[c];
147 Rpess[c] = T(Rc[c] + Vpess[c] * hp);
148 const T den = T(zc + Rpess[c]);
149 if (den == zero) throw NumericError("pfqn_looping: zero pessimistic cycle time");
150 r.Xlo[c] = N[c] / den;
151 }
152 for (std::size_t c = 0; c < R; ++c) {
153 if (N[c] == zero) {
154 Ropt[c] = zero;
155 continue;
156 }
157 const T zc = Z.empty() ? zero : Z[c];
158 bool anySat = false;
159 T sat = zero;
160 for (std::size_t k = 0; k < M; ++k) {
161 T used = zero;
162 for (std::size_t j = 0; j < R; ++j)
163 if (j != c) used += r.Xlo[j] * L(k, j);
164 const T den = T(one - used);
165 if (den > zero) {
166 const T v = T(L(k, c) * N[c] / den - zc);
167 if (!anySat || v > sat) {
168 sat = v;
169 anySat = true;
170 }
171 }
172 }
173 T ho = zero;
174 for (std::size_t j = 0; j < R; ++j) ho += Hopt(j, c);
175 T best = T(Rc[c] + Lopt[c] * ho);
176 if (anySat && sat > best) best = sat;
177 if (Dtot[c] > best) best = Dtot[c];
178 // an optimistic bound can never exceed the pessimistic one
179 Ropt[c] = (best < Rpess[c]) ? best : Rpess[c];
180 }
181 for (std::size_t c = 0; c < R; ++c)
182 for (std::size_t k = 0; k < M; ++k) r.Q(k, c) = r.Xlo[c] * r.R(k, c);
183
184 for (std::size_t c = 0; c < R; ++c)
185 for (std::size_t j = 0; j < R; ++j) {
186 const T nj = (c == j) ? T(N[j] - one) : N[j];
187 const T zj = Z.empty() ? zero : Z[j];
188 T qsum = zero;
189 if (N[j] <= zero || nj <= zero) {
190 for (std::size_t k = 0; k < M; ++k) Qm[c][j][k] = zero;
191 } else {
192 const T f = T(T(nj / N[j]) * T(T(zj + Ropt[j]) / T(zj + Bm[j])));
193 for (std::size_t k = 0; k < M; ++k) {
194 Qm[c][j][k] = f * r.Q(k, j);
195 qsum += Qm[c][j][k];
196 }
197 }
198 if (nj > zero) {
199 const T ho = T(T(Jbnd[j] / T(zj + Jbnd[j])) * nj - qsum);
200 const T hp = T(T(Bm[j] / T(zj + Bm[j])) * nj - qsum);
201 Hopt(j, c) = (ho > zero) ? ho : zero;
202 Hpess(j, c) = (hp > zero) ? hp : zero;
203 } else {
204 Hopt(j, c) = zero;
205 Hpess(j, c) = zero;
206 }
207 }
208
209 bool anyNonEmpty = false;
210 double maxdiff = 0.0;
211 for (std::size_t c = 0; c < R; ++c) {
212 if (N[c] <= zero) continue;
213 anyNonEmpty = true;
214 for (std::size_t k = 0; k < M; ++k) {
215 const double d =
216 std::fabs(num_traits<T>::to_double(T(r.Q(k, c) - Qprev(k, c))));
217 if (d > maxdiff) maxdiff = d;
218 }
219 }
220 if (!anyNonEmpty || (it > 1 && maxdiff < tol)) {
221 r.converged = true;
222 break;
223 }
224 }
225
226 for (std::size_t c = 0; c < R; ++c) {
227 if (N[c] <= zero) continue;
228 const T zc = Z.empty() ? zero : Z[c];
229 const T den = T(zc + Ropt[c]);
230 if (den == zero) throw NumericError("pfqn_looping: zero optimistic cycle time");
231 r.Xup[c] = N[c] / den;
232 }
233 return r;
234}
235
236template <class T>
237LoopingBounds<T> pfqn_looping(const Matrix<T>& L, const std::vector<T>& N) {
238 return pfqn_looping(L, N, std::vector<T>());
239}
240
241} // namespace pfqn
242} // namespace line
243
244#endif // LINE_API_PFQN_LOOPING_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.
LoopingBounds< T > pfqn_looping(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, double tol=1e-6, std::size_t maxiter=1000)
Eager Looping bounds for closed multiclass product-form networks.
Number-type abstraction for the templated API port.
Return value of pfqn_looping: the throughput bracket plus its queue lengths.
std::vector< T > Xup
(R) optimistic (upper) throughput bound
Matrix< T > Q
(M x R) queue lengths on the pessimistic side
std::vector< T > Xlo
(R) pessimistic (lower) throughput bound
Matrix< T > R
(M x R) residence times