LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
polling_qsys_table.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_POLLING_POLLING_QSYS_TABLE_H
6#define LINE_API_POLLING_POLLING_QSYS_TABLE_H
7
8/**
9 * @file
10 * @ingroup api_polling
11 * Exact mean waiting times of a polling system with a polling table.
12 *
13 * Templated port of matlab/src/api/polling/polling_qsys_table.m. The server
14 * visits the queues in the order table[0], table[1], ..., table[M-1] and then
15 * starts over, every queue appearing at least once and possibly several
16 * times; all queues are served exhaustively or all gated.
17 *
18 * Method (buffer occupancy, Eisenberg 1972): the first and second moments of
19 * the queue lengths at the polling instants obey an affine recursion over the
20 * table positions. Composing the M steps gives a linear system of dimension
21 * n + n^2 whose solution is the cycle-stationary moment vector, then
22 * propagated to every position. The waiting time follows by length biasing:
23 *
24 * exhaustive W_i = lambda_i b2_i/(2(1-rho_i)) + sum_m E[X_i(X_i-1)]_m / (2 lambda_i sum_m E[X_i]_m)
25 * gated W_i = (1+rho_i) sum_m E[X_i(X_i-1)]_m / (2 lambda_i sum_m E[X_i]_m)
26 *
27 * the sums running over the positions m of queue i. For table = 1..n this is
28 * the classical cyclic system (Takagi 1988, eqs. 15 and 21), which the tests
29 * use as the oracle together with the CTMC of the same station.
30 *
31 * Everything is rational in the input moments plus one linear solve, so the
32 * port is instantiable at an exact field.
33 */
34
35#include <cstddef>
36#include <string>
37#include <vector>
38
40#include "line/num/number.h"
41#include "line/util/error.h"
42#include "line/util/lu.h"
43#include "line/util/matrix.h"
44
45namespace line {
46namespace polling {
47
48/** Result of polling_qsys_table: waiting times and polling-instant moments. */
49template <class T>
51 std::vector<T> W; ///< mean waiting time per queue
52 std::vector<std::vector<T> > EX; ///< EX[m][i] = E[X_i] at the polling instant of position m
53 std::vector<Matrix<T> > EX2; ///< EX2[m](i,j) = E[X_i X_j] at the polling instant of position m
54};
55
56/**
57 * Polling table, exhaustive or gated.
58 *
59 * @param lambda arrival rate per queue (Poisson)
60 * @param b mean service time per queue
61 * @param b2 second raw moment of the service time per queue
62 * @param table queue indices, 1-based as in MATLAB, e.g. {1, 2, 1, 3}
63 * @param s_mean mean switchover of the leg out of each table position
64 * @param s_var variance of the switchover of the leg out of each position
65 * @param gated false for exhaustive, true for gated
66 */
67template <class T>
68PollingTableResult<T> polling_qsys_table_full(const std::vector<T>& lambda, const std::vector<T>& b,
69 const std::vector<T>& b2,
70 const std::vector<std::size_t>& table,
71 const std::vector<T>& s_mean,
72 const std::vector<T>& s_var, bool gated) {
73 static const char* who = "polling_qsys_table";
74 const std::size_t n = lambda.size();
75 const std::size_t M = table.size();
76 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
77 const T two = num_traits<T>::from_int(2);
78 if (n == 0 || b.size() != n || b2.size() != n)
79 throw InputError(ErrorKind{"Argument"}, std::string(who) + ": lambda, b and b2 must have one entry per queue");
80 if (s_mean.size() != M || s_var.size() != M)
81 throw InputError(ErrorKind{"Argument"}, std::string(who) + ": polling_qsys_table needs one switchover per table position (" +
82 std::to_string(M) + ").");
83 {
84 std::vector<bool> seen(n, false);
85 for (std::size_t p : table) {
86 if (p < 1 || p > n)
87 throw InputError(ErrorKind{"Argument"}, std::string(who) + ": The polling table must list every queue 1.." +
88 std::to_string(n) + " at least once.");
89 seen[p - 1] = true;
90 }
91 for (std::size_t i = 0; i < n; ++i)
92 if (!seen[i])
93 throw InputError(ErrorKind{"Argument"}, std::string(who) + ": The polling table must list every queue 1.." +
94 std::to_string(n) + " at least once.");
95 }
96 std::vector<T> rho1(n), s2(M);
97 T rho = zero;
98 for (std::size_t i = 0; i < n; ++i) {
99 if (lambda[i] <= zero)
100 throw InputError(ErrorKind{"Argument"}, std::string(who) + ": polling_qsys_table needs a positive arrival rate at every queue.");
101 rho1[i] = lambda[i] * b[i];
102 rho += rho1[i];
103 }
104 if (rho >= one)
105 throw NumericError(ErrorKind{"Instability"}, std::string(who) + ": The polling system is unstable: rho >= 1.");
106 bool anysw = false;
107 for (std::size_t m = 0; m < M; ++m) {
108 s2[m] = s_var[m] + s_mean[m] * s_mean[m];
109 if (s_mean[m] != zero) anysw = true;
110 }
111 if (!anysw)
112 throw InputError(ErrorKind{"Argument"}, std::string(who) + ": polling_qsys_table needs a positive total switchover time per cycle.");
113
114 // visit period moments per queue: a service time (gated) or a busy period (exhaustive)
115 std::vector<T> y1(n), y2(n);
116 for (std::size_t q = 0; q < n; ++q) {
117 if (gated) {
118 y1[q] = b[q];
119 y2[q] = b2[q];
120 } else {
121 const T omr = one - rho1[q];
122 y1[q] = b[q] / omr;
123 y2[q] = b2[q] / (omr * omr * omr);
124 }
125 }
126
127 const std::size_t N = n + n * n;
128 // S is stored column-major after the n means, as MATLAB's reshape does
129 const auto Sidx = [n](std::size_t i, std::size_t j) { return n + j * n + i; };
130 // visit of queue q at position m followed by the switchover leg m
131 const auto step = [&](const std::vector<T>& zi, std::size_t q, std::size_t m) {
132 std::vector<T> lp(lambda);
133 if (!gated) lp[q] = zero;
134 const T muq = zi[q];
135 const T Sqq = zi[Sidx(q, q)];
136 const T EV = y1[q] * muq;
137 const T EV2 = muq * (y2[q] - y1[q] * y1[q]) + Sqq * y1[q] * y1[q];
138 std::vector<T> mu1(n);
139 for (std::size_t i = 0; i < n; ++i) mu1[i] = (i == q ? zero : zi[i]) + lp[i] * y1[q] * muq;
140 std::vector<T> zo(N, zero);
141 for (std::size_t j = 0; j < n; ++j) {
142 for (std::size_t i = 0; i < n; ++i) {
143 const T DSD = (i != q && j != q) ? zi[Sidx(i, j)] : zero;
144 const T DSq = (i != q) ? zi[Sidx(i, q)] : zero;
145 const T SqD = (j != q) ? zi[Sidx(q, j)] : zero;
146 T s1v = DSD + DSq * y1[q] * lp[j] + lp[i] * y1[q] * SqD + lp[i] * lp[j] * EV2;
147 if (i == j) s1v += lp[i] * EV;
148 T s2v = s1v + mu1[i] * lambda[j] * s_mean[m] + lambda[i] * mu1[j] * s_mean[m] +
149 lambda[i] * lambda[j] * s2[m];
150 if (i == j) s2v += lambda[i] * s_mean[m];
151 zo[Sidx(i, j)] = s2v;
152 }
153 }
154 for (std::size_t i = 0; i < n; ++i) zo[i] = mu1[i] + lambda[i] * s_mean[m];
155 return zo;
156 };
157
158 std::vector<Matrix<T> > A(M, Matrix<T>(N, N, zero));
159 std::vector<std::vector<T> > c(M);
160 for (std::size_t m = 0; m < M; ++m) {
161 const std::size_t q = table[m] - 1;
162 c[m] = step(std::vector<T>(N, zero), q, m);
163 for (std::size_t k = 0; k < N; ++k) {
164 std::vector<T> e(N, zero);
165 e[k] = one;
166 const std::vector<T> col = step(e, q, m);
167 for (std::size_t r = 0; r < N; ++r) A[m](r, k) = col[r] - c[m][r];
168 }
169 }
170 const auto matvec = [N, zero](const Matrix<T>& X, const std::vector<T>& v) {
171 std::vector<T> out(N, zero);
172 for (std::size_t r = 0; r < N; ++r)
173 for (std::size_t k = 0; k < N; ++k) out[r] += X(r, k) * v[k];
174 return out;
175 };
176 // cycle map from position 1 back to position 1
177 Matrix<T> Acyc(N, N, zero);
178 for (std::size_t r = 0; r < N; ++r) Acyc(r, r) = one;
179 std::vector<T> ccyc(N, zero);
180 for (std::size_t m = 0; m < M; ++m) {
181 std::vector<T> nc = matvec(A[m], ccyc);
182 for (std::size_t r = 0; r < N; ++r) nc[r] += c[m][r];
183 ccyc = nc;
184 Matrix<T> P(N, N, zero);
185 for (std::size_t r = 0; r < N; ++r)
186 for (std::size_t k = 0; k < N; ++k) {
187 T s = zero;
188 for (std::size_t l = 0; l < N; ++l) s += A[m](r, l) * Acyc(l, k);
189 P(r, k) = s;
190 }
191 Acyc = P;
192 }
193 Matrix<T> IA(N, N, zero);
194 for (std::size_t r = 0; r < N; ++r)
195 for (std::size_t k = 0; k < N; ++k) IA(r, k) = (r == k ? one : zero) - Acyc(r, k);
196 std::vector<std::vector<T> > z(M);
197 z[0] = solve(IA, ccyc);
198 for (std::size_t m = 0; m + 1 < M; ++m) {
199 z[m + 1] = matvec(A[m], z[m]);
200 for (std::size_t r = 0; r < N; ++r) z[m + 1][r] += c[m][r];
201 }
202
204 out.EX.assign(M, std::vector<T>(n, zero));
205 out.EX2.assign(M, Matrix<T>(n, n, zero));
206 for (std::size_t m = 0; m < M; ++m)
207 for (std::size_t i = 0; i < n; ++i) {
208 out.EX[m][i] = z[m][i];
209 for (std::size_t j = 0; j < n; ++j)
210 out.EX2[m](i, j) = (z[m][Sidx(i, j)] + z[m][Sidx(j, i)]) / two;
211 }
212 out.W.assign(n, zero);
213 for (std::size_t i = 0; i < n; ++i) {
214 T num = zero, den = zero;
215 for (std::size_t m = 0; m < M; ++m) {
216 if (table[m] != i + 1) continue;
217 num += out.EX2[m](i, i) - out.EX[m][i];
218 den += out.EX[m][i];
219 }
220 const T resid = num / (two * lambda[i] * den);
221 if (gated)
222 out.W[i] = (one + rho1[i]) * resid;
223 else
224 out.W[i] = lambda[i] * b2[i] / (two * (one - rho1[i])) + resid;
225 }
226 return out;
227}
228
229/** Mean waiting times only; see polling_qsys_table_full. */
230template <class T>
231std::vector<T> polling_qsys_table(const std::vector<T>& lambda, const std::vector<T>& b,
232 const std::vector<T>& b2, const std::vector<std::size_t>& table,
233 const std::vector<T>& s_mean, const std::vector<T>& s_var,
234 bool gated) {
235 return polling_qsys_table_full(lambda, b, b2, table, s_mean, s_var, gated).W;
236}
237
238} // namespace polling
239} // namespace line
240
241#endif // LINE_API_POLLING_POLLING_QSYS_TABLE_H
InputError(const std::string &what)
Definition error.h:105
NumericError(const std::string &what)
Definition error.h:112
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
std::vector< T > polling_qsys_table(const std::vector< T > &lambda, const std::vector< T > &b, const std::vector< T > &b2, const std::vector< std::size_t > &table, const std::vector< T > &s_mean, const std::vector< T > &s_var, bool gated)
Mean waiting times only; see polling_qsys_table_full.
PollingTableResult< T > polling_qsys_table_full(const std::vector< T > &lambda, const std::vector< T > &b, const std::vector< T > &b2, const std::vector< std::size_t > &table, const std::vector< T > &s_mean, const std::vector< T > &s_var, bool gated)
Polling table, exhaustive or gated.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Number-type abstraction for the templated API port.
Mean waiting times in polling systems: 1-limited and decrementing service, and the pseudo-conservatio...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Result of polling_qsys_table: waiting times and polling-instant moments.
std::vector< Matrix< T > > EX2
EX2[m](i,j) = E[X_i X_j] at the polling instant of position m.
std::vector< T > W
mean waiting time per queue
std::vector< std::vector< T > > EX
EX[m][i] = E[X_i] at the polling instant of position m.