LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_mva_polling.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_SOLVERS_MVA_SOLVER_MVA_POLLING_H
6#define LINE_SOLVERS_MVA_SOLVER_MVA_POLLING_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The multiclass open polling analyzer (ladder branch 5).
12 *
13 * Templated port of `matlab/src/solvers/MVA/solver_mva_polling_analyzer.m`.
14 *
15 * A polling system is a single server that visits each class's buffer in turn,
16 * switching over between them. Each CLASS is a queue of the polling model, and
17 * the mean waiting time of every class follows from the pseudo-conservation
18 * laws of its discipline: EXHAUSTIVE (serve until the buffer empties), GATED
19 * (serve exactly what was present at the visit), 1-limited (one job per visit)
20 * and DECREMENTING. The `polling_qsys_*` routines carry those laws; this
21 * analyzer builds their two-moment inputs from the arrival, service and
22 * switchover distributions and maps the resulting waiting times back onto the
23 * per-class [Q,U,R,T] block.
24 *
25 * SUPPORTED. EXHAUSTIVE and GATED at any number of servers-of-one; K-limited
26 * only at K = 1 (the `polling_qsys_1limited` law); DECREMENTING. The reference
27 * refuses K > 1 and any other discipline, and so does this port. `exact` is
28 * available only for EXHAUSTIVE / GATED with Poisson arrivals at a single
29 * server, where the two-moment law is exact.
30 *
31 * Arithmetic: TRANSCENDENTAL. The pseudo-conservation laws are field
32 * arithmetic, but the input SCVs come from distribution moments that are only
33 * defined at a transcendental T.
34 */
35
36#include <cmath>
37#include <vector>
38
45
46namespace line {
47namespace mva {
48
49/** Port of `solver_mva_polling_analyzer.m`. */
50template <class T>
52 using qn::NodeType;
53 if constexpr (!num_traits<T>::has_transcendental) {
54 (void)L;
55 (void)opt;
56 throw UnsupportedError(
57 "solver_mva_polling_analyzer: the two-moment polling inputs come from distribution "
58 "SCVs, which need a transcendental T");
59 } else {
60 const T zero = num_traits<T>::from_int(0);
61 const T one = num_traits<T>::from_int(1);
62 const std::size_t M = L.nstations, K = L.nclasses;
63
64 std::size_t src = 0, q = 0;
65 for (std::size_t i = 0; i < M; ++i) {
66 if (L.stations[i].nodetype == NodeType::Source) src = i + 1;
67 if (L.stations[i].nodetype == NodeType::Queue) q = i + 1;
68 }
69 if (src == 0 || q == 0)
70 throw InputError("solver_mva_polling_analyzer: a polling system needs a Source and a Queue");
71 const qn::Station<T>& qst = L.stations[q - 1];
72 if (qst.polling_type.empty())
73 throw InputError("solver_mva_polling_analyzer: the Queue has no polling parameters");
74 const std::size_t qstateful = L.stateful_of_station(q);
75 // the queue's visit ratio per class, summed over chains: each open class
76 // sits in its own chain, so reading only chain 0 would zero every class but
77 // the first
78 std::vector<T> Vq(K, zero);
79 for (std::size_t c = 0; c < L.nchains; ++c)
80 for (std::size_t r = 0; r < K; ++r) Vq[r] += L.visits[c](qstateful - 1, r);
81
82 // per-class arrival rate at the queue, service moments and switchover
83 // moments -- the PollingMoments the qsys routines consume
85 pm.lambda.assign(K, zero);
86 pm.b.assign(K, zero);
87 pm.b2.assign(K, zero);
88 pm.r.assign(K, zero);
89 pm.delta2.assign(K, zero);
90 std::vector<T> mu(K, zero), ca2(K, one);
91 for (std::size_t r = 0; r < K; ++r) {
92 pm.lambda[r] = T(L.rates(src - 1, r) * Vq[r]);
93 mu[r] = L.rates(q - 1, r);
94 const T b = mu[r] > zero ? T(one / mu[r]) : zero;
95 pm.b[r] = b;
96 // E[S^2] OFF THE PROCESS REPRESENTATION, not off the nominal SCV. The
97 // reference hands the qsys routines `sn.proc{queue_ist}`, and
98 // `refreshProcessRepresentations` has already replaced a non-Markovian
99 // service by `convertToMAP` -- an Erlang matched to the SCV, 20 phases
100 // when the law is deterministic. Reading `E[S]^2 (1 + scv)` instead gave
101 // a Det service scv = 0 exactly, i.e. b2 = E[S]^2, where the reference
102 // carries the Erlang-20 value E[S]^2 (1 + 1/20); on
103 // polling_exhaustive_det that is a 5th-significant-digit gap in W.
104 if (!L.service[q - 1][r].disabled && mu[r] > zero) {
105 pm.b2[r] = mam::map_moment(lang::dist_to_map(L.service[q - 1][r]), 2);
106 } else {
107 pm.b2[r] = T(b * b * T(one + L.scv(q - 1, r)));
108 }
109 ca2[r] = L.scv(src - 1, r);
110 // AN ABSENT SWITCHOVER IS `Immediate()`, NOT ZERO. Both references
111 // default the cell that way -- `solver_mva_polling_analyzer.m` fills it
112 // with `Immediate()` and the Python twin appends one -- and the
113 // distinction is the whole answer: the station-time formula divides by
114 // the total walk time R, so leaving an unset switchover at zero made R
115 // zero and `polling_qsys_exhaustive` refuse a model the reference
116 // solves. `polling_exhaustive_exp` sets no switchover at all and is
117 // goldened for finite waiting times.
118 const bool has_so = r < qst.switchover.size() && !qst.switchover[r].disabled;
119 // The reference feeds the qsys routines sn.proc, i.e. the PROCESS
120 // representation, and Immediate.getProcess() is the exponential of rate
121 // GlobalConstants.Immediate rather than a point mass at zero. Reading
122 // `so.mean` instead made an immediate switchover a zero total walk time,
123 // which the qsys routines refuse for the same reason.
124 const bool immediate = !has_so || qst.switchover[r].is_immediate();
125 const T so_mean = immediate ? T(num_traits<T>::from_int(1) /
127 : qst.switchover[r].mean;
128 const T so_scv = immediate ? one : qst.switchover[r].scv;
129 pm.r[r] = so_mean;
130 pm.delta2[r] = T(so_scv * so_mean * so_mean);
131 }
132
133 // the discipline, assumed identical across buffers as the reference does
134 const lang::PollingType pt = qst.polling_type[0];
135 std::string method = opt.method;
136 if (method == "exact") {
137 bool all_poisson = true;
138 for (std::size_t r = 0; r < K; ++r)
139 if (std::fabs(num_traits<T>::to_double(ca2[r]) - 1.0) >= 1e-9) all_poisson = false;
140 const bool single_server =
141 !std::isfinite(qst.nservers) ? false : std::llround(qst.nservers) == 1;
142 if (!(all_poisson && single_server &&
144 throw UnsupportedError(
145 "solver_mva_polling_analyzer: the exact method covers only EXHAUSTIVE / GATED "
146 "polling with Poisson arrivals at a single server");
147 method = "stationtime";
148 }
149
150 std::vector<T> W;
151 switch (pt) {
154 break;
157 break;
159 if (qst.polling_par != 1)
160 throw UnsupportedError(
161 "solver_mva_polling_analyzer: K-limited polling is only available at K = 1");
163 break;
166 break;
167 default:
168 throw UnsupportedError("solver_mva_polling_analyzer: unsupported polling type");
169 }
170
171 // R = W + 1/mu, then scale by the queue's visit ratio, as the reference does
172 const int k = std::isfinite(qst.nservers) ? static_cast<int>(std::llround(qst.nservers)) : 1;
173 MvaSolution<T> out;
174 out.Q = Matrix<T>(M, K, zero);
175 out.U = Matrix<T>(M, K, zero);
176 out.R = Matrix<T>(M, K, zero);
177 out.Tp = Matrix<T>(M, K, zero);
178 out.C.assign(K, zero);
179 out.X.assign(K, zero);
180 for (std::size_t r = 0; r < K; ++r) {
181 const T Rr = T(T(W[r] + (mu[r] > zero ? T(one / mu[r]) : zero)) * Vq[r]);
182 out.R(q - 1, r) = Rr;
183 out.C[r] = Rr;
184 out.Tp(src - 1, r) = pm.lambda[r];
185 out.Tp(q - 1, r) = pm.lambda[r];
186 out.X[r] = pm.lambda[r];
187 out.U(q - 1, r) = mu[r] > zero
188 ? T(pm.lambda[r] / mu[r] / num_traits<T>::from_int(k))
189 : zero;
190 out.Q(q - 1, r) = T(pm.lambda[r] * Rr);
191 }
192 out.method = "stationtime";
193 out.lG = 0.0;
194 (void)method;
195 return out;
196 }
197}
198
199} // namespace mva
200} // namespace line
201
202#endif // LINE_SOLVERS_MVA_SOLVER_MVA_POLLING_H
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
std::size_t stateful_of_station(std::size_t st) const
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< Matrix< T > > visits
(nchains) each (nstateful x nclasses)
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
The option and result types every MVA analyzer shares.
mam::Map< T > dist_to_map(const Distrib< T > &d)
PollingType
Polling service disciplines, with the values of MATLAB PollingType.
Definition lang_types.h:370
@ KLIMITED
serve at most K per visit (K in pollingPar)
Definition lang_types.h:373
@ EXHAUSTIVE
serve until the queue empties
Definition lang_types.h:372
@ GATED
serve exactly the jobs present at the polling instant
Definition lang_types.h:371
@ DECREMENTING
serve until the queue is one shorter than at arrival
Definition lang_types.h:374
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:324
T map_moment(const Map< T > &m, unsigned k)
Raw moment of order k of the inter-arrival time: k!
Definition map_moment.h:118
MvaSolution< T > solver_mva_polling_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_polling_analyzer.m.
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_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 > polling_qsys_1limited(const PollingMoments< T > &m)
1-limited polling: one job served per visit.
A queueing network and its refreshed NetworkStruct.
Mean waiting times in polling systems: 1-limited and decrementing service.
Mean waiting times of a polling system under exhaustive service.
static constexpr double Immediate
Rate of an Immediate distribution; its mean is 1/Immediate = 1e-8.
Definition lang_types.h:674
The options SolverMVA reads.
Definition mva_types.h:31
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:96
std::vector< T > X
Definition mva_types.h:98
double lG
log of the normalizing constant, the reference's lG.
Definition mva_types.h:118
std::vector< T > C
Definition mva_types.h:98
Per-queue first two moments of the arrival, service and switchover processes, the reduced form both f...
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
One station of the network.
double nservers
may be infinite (a Delay, or an inf-scheduled task)
std::vector< lang::PollingType > polling_type
Polling parameters for a POLLING station, MATLAB's pollingType, switchoverTime and pollingPar on the ...
std::vector< Distrib< T > > switchover