LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
polling_qsys_exhaustive.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_EXHAUSTIVE_H
6#define LINE_API_POLLING_POLLING_QSYS_EXHAUSTIVE_H
7
8/**
9 * @file
10 * @ingroup api_polling
11 * Mean waiting times of a polling system under exhaustive service.
12 *
13 * Templated port of matlab/src/api/polling/polling_qsys_exhaustive.m, which is
14 * the station-time method of Ferguson and Aminetzah (1985) as reported by
15 * Takagi, ACM Computing Surveys 20(1), 1988, eq. (15). The MATLAB version takes
16 * MAP descriptors and immediately reduces them to the first two moments of the
17 * arrival, service and switchover processes; this port takes those moments
18 * directly through line::polling::PollingMoments, exactly as the 1-limited and
19 * decrementing ports do, so the MAP reduction stays in line::mam.
20 *
21 * The method solves an n^2 x n^2 linear system for the station times r_ij and
22 * then reads the waiting times off them. Everything is a rational function of
23 * the input moments plus one exact linear solve, so the whole computation stays
24 * in the field: a polling system with rational parameters has an exactly
25 * representable mean waiting time and the port is instantiable at Rational.
26 * That is worth having here because the denominators 1 - rho and 1 - rho_i both
27 * vanish at a stability boundary and appear cubed, so a rounded evaluation near
28 * one can return a finite but meaningless number.
29 *
30 * ORACLES USED IN THE TESTS.
31 * - n = 1 collapse. The formula reduces to the M/G/1 queue with multiple
32 * vacations, W = lambda b2/(2(1-rho)) + E[R^2]/(2 E[R]), which the port
33 * reproduces exactly (in Rational, digit for digit).
34 * - Pseudo-conservation law of Boxma and Groenendijk (1987),
35 * sum_i rho_i W_i = rho/(2(1-rho)) sum_i lambda_i b2_i
36 * + rho (delta2tot + R^2)/(2R)
37 * + R (rho^2 - sum_i rho_i^2)/(2(1-rho))
38 * + sum_i E[M_i],
39 * with E[M_i] = 0 under exhaustive service and E[M_i] = rho_i^2 R/(1-rho)
40 * under gated service. The port satisfies it as an identity, so exhaustive
41 * and gated are cross-checked against one common invariant.
42 *
43 * A simulator is deliberately NOT used as an oracle: LINE's LDES polling server
44 * parks at the last visited queue rather than roving, which puts a systematic
45 * negative offset of a few percent on its waiting times relative to Takagi.
46 */
47
48#include <cstddef>
49#include <vector>
50
52#include "line/num/number.h"
53#include "line/util/error.h"
54#include "line/util/lu.h"
55#include "line/util/matrix.h"
56
57namespace line {
58namespace polling {
59
60namespace detail {
61
62/** Derived aggregates shared by the exhaustive and gated formulas. */
63template <class T>
64struct PollingAggregates {
65 std::vector<T> rho1; ///< per-queue load lambda_i b_i
66 T rho; ///< total load
67 T R; ///< total switchover time
68};
69
70template <class T>
71PollingAggregates<T> polling_aggregates(const PollingMoments<T>& m, const char* who) {
72 const std::size_t n = m.size();
73 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
74 PollingAggregates<T> a;
75 a.rho1.resize(n);
76 a.rho = zero;
77 a.R = zero;
78 for (std::size_t i = 0; i < n; ++i) {
79 a.rho1[i] = m.lambda[i] * m.b[i];
80 if (a.rho1[i] <= zero)
81 throw InputError(std::string(who) + ": every queue must carry a positive load");
82 a.rho += a.rho1[i];
83 a.R += m.r[i];
84 }
85 if (a.rho >= one) throw NumericError(std::string(who) + ": unstable system, rho >= 1");
86 if (a.R <= zero) throw InputError(std::string(who) + ": zero total switchover time");
87 return a;
88}
89
90} // namespace detail
91
92/**
93 * Exhaustive service: the server empties a queue completely before switching.
94 *
95 * Takagi (1988) eq. (15) in the station-time form. The unknowns are the n^2
96 * station times r_ij collected row-major as index (i-1) n + j, 1-based in the
97 * reference and shifted by one here.
98 *
99 * @param m per-queue first two moments of arrivals, service and switchover
100 * @return the n mean waiting times
101 */
102template <class T>
104 m.validate("polling_qsys_exhaustive");
105 const std::size_t n = m.size();
106 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
107 const T two = num_traits<T>::from_int(2);
108 const detail::PollingAggregates<T> a =
109 detail::polling_aggregates(m, "polling_qsys_exhaustive");
110 const std::vector<T>& rho1 = a.rho1;
111 const T rho = a.rho, r = a.R;
112
113 const std::size_t nn = n * n;
114 Matrix<T> A(nn, nn, zero);
115 std::vector<T> rhs(nn, zero);
116 std::size_t row = 0;
117 for (std::size_t i = 1; i <= n; ++i) {
118 for (std::size_t j = 1; j <= n; ++j, ++row) {
119 if (i > j) {
120 for (std::size_t k = i + 1; k <= n; ++k) A(row, (j - 1) * n + k - 1) -= one;
121 for (std::size_t k = 1; k + 1 <= j; ++k) A(row, (j - 1) * n + k - 1) -= one;
122 for (std::size_t k = j; k + 1 <= i; ++k) A(row, (k - 1) * n + j - 1) -= one;
123 A(row, (i - 1) * n + j - 1) += (one - rho1[i - 1]) / rho1[i - 1];
124 } else if (j > i) {
125 for (std::size_t k = i + 1; k + 1 <= j; ++k) A(row, (j - 1) * n + k - 1) -= one;
126 for (std::size_t k = j; k <= n; ++k) A(row, (k - 1) * n + j - 1) -= one;
127 for (std::size_t k = 1; k + 1 <= i; ++k) A(row, (k - 1) * n + j - 1) -= one;
128 A(row, (i - 1) * n + j - 1) += (one - rho1[i - 1]) / rho1[i - 1];
129 } else {
130 A(row, (i - 1) * n + i - 1) += one;
131 for (std::size_t k = 1; k <= n; ++k)
132 if (k != i) A(row, (i - 1) * n + k - 1) -= rho1[i - 1] / (one - rho1[i - 1]);
133 // The switchover variance charged to queue i is the one of the
134 // switchover that PRECEDES it, index i-1 with wraparound to n.
135 const T dprev = (i > 1) ? m.delta2[i - 2] : m.delta2[n - 1];
136 const T omr = one - rho1[i - 1];
137 rhs[row] = dprev / (omr * omr) +
138 m.lambda[i - 1] * m.b2[i - 1] * r * omr / ((one - rho) * omr * omr * omr);
139 }
140 }
141 }
142
143 const std::vector<T> f = solve(A, rhs);
144
145 std::vector<T> W(n);
146 for (std::size_t i = 1; i <= n; ++i) {
147 const T omr = one - rho1[i - 1];
148 T w = m.lambda[i - 1] * m.b2[i - 1] / (two * omr);
149 w += r * omr / (two * (one - rho));
150 T s = zero;
151 for (std::size_t j = 1; j <= n; ++j)
152 if (j != i) s += f[(i - 1) * n + j - 1];
153 s *= omr / rho1[i - 1];
154 s += (i > 1) ? m.delta2[i - 2] : m.delta2[n - 1];
155 s /= r * omr * two / (one - rho);
156 W[i - 1] = w + s;
157 }
158 return W;
159}
160
161/**
162 * Gated service: only the jobs found at the polling instant are served.
163 *
164 * Takagi (1988) eq. (20), same station-time unknowns and the same layout. The
165 * switchover variance enters at index i here, not i-1 as in the exhaustive
166 * form; that asymmetry is in the reference and is reproduced, and both forms
167 * satisfy the pseudo-conservation law, which is what pins them.
168 */
169template <class T>
170std::vector<T> polling_qsys_gated(const PollingMoments<T>& m) {
171 m.validate("polling_qsys_gated");
172 const std::size_t n = m.size();
173 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
174 const T two = num_traits<T>::from_int(2);
175 const detail::PollingAggregates<T> a = detail::polling_aggregates(m, "polling_qsys_gated");
176 const std::vector<T>& rho1 = a.rho1;
177 const T rho = a.rho, r = a.R;
178
179 const std::size_t nn = n * n;
180 Matrix<T> A(nn, nn, zero);
181 std::vector<T> rhs(nn, zero);
182 std::size_t row = 0;
183 for (std::size_t i = 1; i <= n; ++i) {
184 for (std::size_t j = 1; j <= n; ++j, ++row) {
185 if (i > j) {
186 for (std::size_t k = i; k <= n; ++k) A(row, (j - 1) * n + k - 1) = -one;
187 for (std::size_t k = 1; k + 1 <= j; ++k) A(row, (j - 1) * n + k - 1) = -one;
188 for (std::size_t k = j; k + 1 <= i; ++k) A(row, (k - 1) * n + j - 1) = -one;
189 A(row, (i - 1) * n + j - 1) = one / rho1[i - 1];
190 } else if (j > i) {
191 for (std::size_t k = i; k + 1 <= j; ++k) A(row, (j - 1) * n + k - 1) = -one;
192 for (std::size_t k = j; k <= n; ++k) A(row, (k - 1) * n + j - 1) = -one;
193 for (std::size_t k = 1; k + 1 <= i; ++k) A(row, (k - 1) * n + j - 1) = -one;
194 A(row, (i - 1) * n + j - 1) = one / rho1[i - 1];
195 } else {
196 A(row, (i - 1) * n + i - 1) += one;
197 for (std::size_t k = 1; k <= n; ++k)
198 if (k != i) A(row, (i - 1) * n + k - 1) = -rho1[i - 1];
199 for (std::size_t k = 1; k <= n; ++k)
200 A(row, (k - 1) * n + i - 1) -= rho1[i - 1] * rho1[i - 1];
201 rhs[row] = m.delta2[i - 1] + m.lambda[i - 1] * m.b2[i - 1] * r / (one - rho);
202 }
203 }
204 }
205
206 const std::vector<T> f = solve(A, rhs);
207
208 std::vector<T> W(n);
209 for (std::size_t i = 1; i <= n; ++i) {
210 T w = (one + rho1[i - 1]) * r / (two * (one - rho));
211 T s = zero;
212 for (std::size_t j = 1; j <= n; ++j)
213 if (j != i) s += f[(i - 1) * n + j - 1];
214 s /= rho1[i - 1];
215 for (std::size_t j = 1; j <= n; ++j) s += f[(j - 1) * n + i - 1];
216 w += (one - rho) * (one + rho1[i - 1]) * s / (two * r);
217 W[i - 1] = w;
218 }
219 return W;
220}
221
222} // namespace polling
223} // namespace line
224
225#endif // LINE_API_POLLING_POLLING_QSYS_EXHAUSTIVE_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
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_exhaustive(const PollingMoments< T > &m)
Exhaustive service: the server empties a queue completely before switching.
std::vector< T > polling_qsys_gated(const PollingMoments< T > &m)
Gated service: only the jobs found at the polling instant are served.
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.
Per-queue first two moments of the arrival, service and switchover processes, the reduced form both f...
void validate(const char *who) const
std::vector< T > b2
second raw moment of the service time
std::vector< T > lambda
arrival rate per queue
std::vector< T > delta2
variance of the switchover time per queue