LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
polling_qsys_1limited.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_1LIMITED_H
6#define LINE_API_POLLING_POLLING_QSYS_1LIMITED_H
7
8/**
9 * @file
10 * @ingroup api_polling
11 * Mean waiting times in polling systems: 1-limited and decrementing service.
12 *
13 * Templated port of matlab/src/api/polling/polling_qsys_1limited.m and
14 * polling_qsys_decrementing.m. The MATLAB versions take MAP descriptors and
15 * reduce them to the first two moments of the arrival, service and switchover
16 * processes; this port takes those moments directly, so the MAP reduction stays
17 * in line::mam (map_lambda, map_mean, map_moment, map_var) and the waiting-time
18 * formula is a pure rational function of the moments. Callers holding MAPs
19 * compose the two.
20 *
21 * Both formulas stay in the field, so a polling system with rational parameters
22 * has an exactly representable mean waiting time. Worth having: the denominator
23 * 1 - rho - lambda R vanishes at the stability boundary, and near it a rounded
24 * evaluation can return a finite but meaningless number where the exact one
25 * shows the pole.
26 */
27
28#include <cstddef>
29#include <limits>
30#include <vector>
31
32#include "line/num/number.h"
33#include "line/util/error.h"
34
35namespace line {
36namespace polling {
37
38/**
39 * Per-queue first two moments of the arrival, service and switchover
40 * processes, the reduced form both formulas consume.
41 */
42template <class T>
44 std::vector<T> lambda; ///< arrival rate per queue
45 std::vector<T> b; ///< mean service time per queue
46 std::vector<T> b2; ///< second raw moment of the service time
47 std::vector<T> r; ///< mean switchover time per queue
48 std::vector<T> delta2; ///< variance of the switchover time per queue
49
50 std::size_t size() const { return lambda.size(); }
51 void validate(const char* who) const {
52 if (lambda.empty()) throw InputError(std::string(who) + ": empty system");
53 if (b.size() != lambda.size() || b2.size() != lambda.size() ||
54 r.size() != lambda.size() || delta2.size() != lambda.size())
55 throw InputError(std::string(who) + ": moment vectors have different lengths");
56 }
57};
58
59/**
60 * 1-limited polling: one job served per visit.
61 *
62 * W_i = (1-rho+rho_i)/(1-rho-lambda_i R) * (1-rho)/((1-rho)rho + sum rho_j^2)
63 * * (rho/(2(1-rho)) sum_j lambda_j b2_j + rho sum_j delta2_j/(2R)
64 * + R/(2(1-rho)) rho_i (1+rho_i))
65 */
66template <class T>
67std::vector<T> polling_qsys_1limited(const PollingMoments<T>& m) {
68 m.validate("polling_qsys_1limited");
69 const std::size_t n = m.size();
70 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
71 const T two = num_traits<T>::from_int(2);
72
73 std::vector<T> rho1(n);
74 T rho = zero, R = zero, sumLb2 = zero, sumDelta2 = zero, sumRho2 = zero;
75 for (std::size_t i = 0; i < n; ++i) {
76 rho1[i] = m.lambda[i] * m.b[i];
77 rho += rho1[i];
78 R += m.r[i];
79 sumLb2 += m.lambda[i] * m.b2[i];
80 sumDelta2 += m.delta2[i];
81 sumRho2 += rho1[i] * rho1[i];
82 }
83 if (rho >= one) throw NumericError("polling_qsys_1limited: unstable system, rho >= 1");
84 if (R == zero) throw InputError("polling_qsys_1limited: zero total switchover time");
85
86 const T den2 = (one - rho) * rho + sumRho2;
87 if (den2 == zero) throw NumericError("polling_qsys_1limited: degenerate load configuration");
88
89 std::vector<T> W(n);
90 for (std::size_t i = 0; i < n; ++i) {
91 const T den1 = one - rho - m.lambda[i] * R;
92 // AT THE POLE THE ANSWER IS INFINITE, NOT UNAVAILABLE. Queue i is stable
93 // under 1-limited service while rho + lambda_i R < 1; on the boundary its
94 // mean waiting time diverges, and polling_qsys_1limited.m divides through
95 // and returns Inf rather than erroring. Refusing here turned a model the
96 // reference answers into a failed solve (polling_klimited, whose second
97 // class sits exactly on the boundary). An exact field has no infinity, so
98 // there the pole is still reported rather than misrepresented as a number.
99 if (den1 == zero) {
100 if constexpr (num_traits<T>::is_exact) {
101 throw NumericError(
102 "polling_qsys_1limited: queue at the stability boundary, "
103 "rho + lambda_i R = 1, and the exact field has no infinity");
104 } else {
105 W[i] = num_traits<T>::from_double(std::numeric_limits<double>::infinity());
106 continue;
107 }
108 }
109 T w = (one - rho + rho1[i]) / den1;
110 w *= (one - rho) / den2;
111 w *= rho / (two * (one - rho)) * sumLb2 + rho * sumDelta2 / (two * R) +
112 R / (two * (one - rho)) * (rho1[i] * (one + rho1[i]));
113 W[i] = w;
114 }
115 return W;
116}
117
118/**
119 * Decrementing service, symmetric systems only (the MATLAB version rejects
120 * asymmetric parameters with a 1e-6 relative tolerance; here the check is
121 * exact, which is the right test in an exact field and a stricter one in
122 * double).
123 *
124 * W = delta2/(2r) + (N lambda b2 (1 - lambda r) + (r + lambda delta2)(N - rho))
125 * / (2 (1 - rho - lambda r (N - rho)))
126 */
127template <class T>
129 m.validate("polling_qsys_decrementing");
130 const std::size_t n = m.size();
131 for (std::size_t i = 1; i < n; ++i)
132 if (m.lambda[i] != m.lambda[0] || m.b[i] != m.b[0] || m.b2[i] != m.b2[0] ||
133 m.r[i] != m.r[0] || m.delta2[i] != m.delta2[0])
134 throw InputError(
135 "polling_qsys_decrementing: only symmetric systems are supported (identical "
136 "arrival, service and switchover parameters across all queues)");
137
138 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
139 const T two = num_traits<T>::from_int(2);
140 const T N = num_traits<T>::from_int(static_cast<long>(n));
141 const T lam = m.lambda[0], b2s = m.b2[0], r = m.r[0], d2 = m.delta2[0];
142 const T rho = N * lam * m.b[0];
143
144 const T denom = two * (one - rho - lam * r * (N - rho));
145 if (denom <= zero)
146 throw NumericError(
147 "polling_qsys_decrementing: unstable system, rho + lambda r (N - rho) >= 1");
148
149 const T residualSwitchover = (r > zero) ? d2 / (two * r) : zero;
150 const T w = residualSwitchover +
151 (N * lam * b2s * (one - lam * r) + (r + lam * d2) * (N - rho)) / denom;
152 return std::vector<T>(n, w);
153}
154
155} // namespace polling
156} // namespace line
157
158#endif // LINE_API_POLLING_POLLING_QSYS_1LIMITED_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.
std::vector< T > polling_qsys_decrementing(const PollingMoments< T > &m)
Decrementing service, symmetric systems only (the MATLAB version rejects asymmetric parameters with a...
std::vector< T > polling_qsys_1limited(const PollingMoments< T > &m)
1-limited polling: one job served per visit.
Number-type abstraction for the templated API port.
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
std::vector< T > r
mean switchover time per queue
std::vector< T > b
mean service time per queue