LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_rqna.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_RQNA_H
6#define LINE_SOLVERS_MVA_SOLVER_RQNA_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Robust Queueing Network Analyzer (RQNA), a port of
12 * matlab/src/solvers/MVA/solver_rqna.m.
13 *
14 * Single-class open network of single-server FCFS queues with Markovian routing
15 * and general (non-renewal) external arrival and (non-exponential) service.
16 * Implements Whitt & You (2018) Algorithm 1: traffic-rate + limiting-variability
17 * equations, time-dependent IDC equations (via npfqn_traffic_idc /
18 * npfqn_traffic_idc_at), and the robust-queueing workload approximation
19 * (qsys_gig1_rq), plus the near-immediate feedback elimination of Algorithm 2 /
20 * Section 4.1-4.2, applied by default.
21 *
22 * REFERENCE INDEXING, REPRODUCED. Like solver_qna, the reference indexes the
23 * stateful-indexed sn.rt with STATION indices, which is exact only when every
24 * stateful node is a station; that precondition is asserted. Single class, so
25 * sn.rt(i,j) is the station-to-station routing directly.
26 *
27 * ARITHMETIC: transcendental (counting-process IDC needs expm, the RQ workload
28 * a square root), so under Rational the whole body is discarded and RQNA is
29 * refused by name, matching solver_qna.
30 */
31
32#include <cstddef>
33#include <functional>
34#include <vector>
35
44#include "line/util/error.h"
45#include "line/util/linalg.h"
46#include "line/util/matrix.h"
47
48namespace line {
49namespace mva {
50
51namespace rqna_detail {
52
53/** Submatrix A(rows, cols) by index lists (0-based). */
54template <class T>
55Matrix<T> submat(const Matrix<T>& A, const std::vector<std::size_t>& r,
56 const std::vector<std::size_t>& c) {
57 Matrix<T> B(r.size(), c.size(), num_traits<T>::from_int(0));
58 for (std::size_t i = 0; i < r.size(); ++i)
59 for (std::size_t j = 0; j < c.size(); ++j) B(i, j) = A(r[i], c[j]);
60 return B;
61}
62
63/**
64 * Near-immediate feedback probability at station a (Whitt-You eq. 3.8/3.9,
65 * H={a}): the probability of returning to a before visiting any strictly
66 * higher-rho station.
67 *
68 * Delegated to `npfqn_feedback_elim` so that the solver and the API function
69 * cannot drift apart: they answer the same question, and a private copy of the
70 * rule here is how the two came to differ on ties in the first place.
71 */
72template <class T>
73T rqna_phat(const Matrix<T>& P, const std::vector<T>& rho, std::size_t a) {
74 return npfqn::npfqn_feedback_elim(P, rho).feedbackProb[a];
75}
76
77/**
78 * Geometric random sum of i.i.d. PH service with success prob (1-p):
79 * PH(alpha, T) -> D0 + p t0 alpha, D1 = (1-p) t0 alpha, t0 = -D0 e.
80 */
81template <class T>
82mam::Map<T> rqna_geom_map(const mam::Map<T>& map, const T& p) {
83 const std::size_t s = map.D0.rows();
84 std::vector<T> e(s, num_traits<T>::from_int(1));
85 std::vector<T> t0(s, num_traits<T>::from_int(0));
86 for (std::size_t i = 0; i < s; ++i) {
87 T acc = num_traits<T>::from_int(0);
88 for (std::size_t j = 0; j < s; ++j) acc = T(acc - map.D0(i, j) * e[j]);
89 t0[i] = acc;
90 }
91 const std::vector<T> al = mam::map_pie(map);
92 mam::Map<T> out;
93 out.D0 = Matrix<T>(s, s, num_traits<T>::from_int(0));
94 out.D1 = Matrix<T>(s, s, num_traits<T>::from_int(0));
95 for (std::size_t i = 0; i < s; ++i)
96 for (std::size_t j = 0; j < s; ++j) {
97 out.D0(i, j) = T(map.D0(i, j) + p * (t0[i] * al[j]));
98 out.D1(i, j) = T((num_traits<T>::from_int(1) - p) * (t0[i] * al[j]));
99 }
100 return out;
101}
102
103} // namespace rqna_detail
104
105/**
106 * @param L the single-class open network struct
107 * @param opt options (opt.tol is the feedback-elimination threshold). The
108 * reference's config knobs rqna_feedback_elim / rqna_alpha /
109 * rqna_beta are not exposed by MvaOptions, so this port runs their
110 * MATLAB defaults: feedback elimination ON, no IDC corrections.
111 * @return the AvgResult-style MvaSolution
112 */
113template <class T>
115 if constexpr (!num_traits<T>::has_transcendental) {
116 throw UnsupportedError(
117 "solver_rqna: the robust queueing-network analyzer needs transcendental arithmetic "
118 "(counting-process indices of dispersion); rerun with --arith double or --arith real");
119 } else {
120 using rqna_detail::rqna_phat;
121 using rqna_detail::rqna_geom_map;
122 using rqna_detail::submat;
123 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
124
125 // One predicate for the gate and the run: list_valid_methods asks the same
126 // question before it advertises 'rqna', so the sentence a caller reads here is
127 // the sentence that kept the row off the report.
128 {
129 const std::string rqna_reason = mva_single_class_open_reason(L, "rqna");
130 if (!rqna_reason.empty()) throw UnsupportedError(rqna_reason);
131 }
132 const std::size_t M = L.nstations;
133 for (std::size_t r = 0; r < L.nclasses; ++r)
134 if (std::isfinite(L.classes[r].population))
135 throw UnsupportedError("solver_rqna: RQNA supports open networks only (no closed classes)");
136 if (L.nof_stateful() != M)
137 throw UnsupportedError(
138 "solver_rqna: the reference indexes the stateful-indexed sn.rt with station indices, "
139 "which is only correct when every stateful node is a station");
140
142 s.Q = Matrix<T>(M, 1, zero);
143 s.U = Matrix<T>(M, 1, zero);
144 s.R = Matrix<T>(M, 1, zero);
145 s.Tp = Matrix<T>(M, 1, zero);
146 s.X.assign(1, zero);
147 s.C.assign(1, zero);
148 s.method = opt.method;
149 s.iter = 1;
150
151 // Source and queueing (incl. delay) stations.
152 std::size_t src = M; // sentinel
153 std::vector<bool> isSource(M, false), schedInf(M, false);
154 for (std::size_t i = 0; i < M; ++i) {
155 isSource[i] = (L.stations[i].nodetype == qn::NodeType::Source);
156 schedInf[i] = (L.stations[i].sched == qn::SchedStrategy::INF);
157 if (isSource[i] && src == M) src = i;
158 }
159 if (src == M)
160 throw UnsupportedError("solver_rqna: RQNA requires an open network with a Source station");
161 std::vector<std::size_t> qstat;
162 for (std::size_t i = 0; i < M; ++i)
163 if (!isSource[i]) qstat.push_back(i);
164 const std::size_t nq = qstat.size();
165
166 // Station-to-station single-class routing sn.rt (K == 1).
167 auto rtS = [&](std::size_t i, std::size_t j) -> T { return L.rt(i, j); };
168
169 // External arrival process from the Source.
170 const mam::Map<T> arvMAP = lang::dist_to_map(L.service[src][0]);
171 const T lambda_src = mam::map_lambda(arvMAP);
172 const T c2_src = mam::map_idc(arvMAP);
173
174 // Per-queue data.
175 std::vector<T> mu(nq, zero), cs2(nq, zero), lambda0(nq, zero), qsplit(nq, zero);
176 std::vector<mam::Map<T> > svcMAP;
177 svcMAP.reserve(nq);
178 Matrix<T> P(nq, nq, zero);
179 for (std::size_t a = 0; a < nq; ++a) {
180 const std::size_t ia = qstat[a];
181 mu[a] = L.rates(ia, 0);
182 cs2[a] = L.scv(ia, 0);
183 svcMAP.push_back(lang::dist_to_map(L.service[ia][0]));
184 qsplit[a] = rtS(src, ia);
185 lambda0[a] = T(lambda_src * qsplit[a]);
186 for (std::size_t b = 0; b < nq; ++b) P(a, b) = rtS(ia, qstat[b]);
187 }
188
189 // External-arrival IDC seen by each queue (eq. 31): split of the source.
190 std::vector<T> c2a0(nq, zero);
191 for (std::size_t a = 0; a < nq; ++a) c2a0[a] = T(qsplit[a] * c2_src + (one - qsplit[a]));
192
193 // IDC handles for the time-dependent solve.
194 std::function<std::vector<T>(const T&)> a0IdcFun = [&](const T& t) {
195 std::vector<T> out(nq, zero);
196 const T Iarv = mam::map_count_idc(arvMAP, t);
197 for (std::size_t a = 0; a < nq; ++a) out[a] = T(qsplit[a] * Iarv + (one - qsplit[a]));
198 return out;
199 };
200 std::function<std::vector<T>(const std::vector<T>&)> sIdcFun =
201 [&](const std::vector<T>& tt) {
202 std::vector<T> out(nq, zero);
203 for (std::size_t a = 0; a < nq; ++a) out[a] = mam::map_count_idc(svcMAP[a], tt[a]);
204 return out;
205 };
206
207 // Corrections (eqs. 34, 38-39), configurable.
209 // Defaults follow npfqn_traffic_idc's own defaults; config overrides not
210 // wired through MvaOptions here (parity with the analytic default path).
211
213 npfqn::npfqn_traffic_idc(lambda0, P, c2a0, mu, cs2, corr);
214 const std::vector<T>& lambda = ctx.lambda;
215 const std::vector<T>& rho = ctx.rho;
216
217 // component IDC I_a(x) reached through ctx (npfqn_traffic_idc_at).
218 auto component = [&](const T& x, std::size_t a) -> T {
219 return npfqn::npfqn_traffic_idc_at(ctx, x, a0IdcFun, sIdcFun)[a];
220 };
221
222 for (std::size_t a = 0; a < nq; ++a) {
223 const std::size_t ia = qstat[a];
224 s.Tp(ia, 0) = lambda[a];
225 if (!(lambda[a] > zero)) continue;
226 if (schedInf[ia]) {
227 s.U(ia, 0) = T(lambda[a] / mu[a]);
228 s.Q(ia, 0) = T(lambda[a] / mu[a]);
229 s.R(ia, 0) = T(one / mu[a]);
230 continue;
231 }
232 T phat = zero;
233 phat = rqna_phat(P, rho, a);
234 if (phat > num_traits<T>::from_double(opt.tol)) {
235 // Near-immediate feedback elimination at station a.
236 const T tol = num_traits<T>::from_double(1e-9);
237 std::vector<std::size_t> Hc, Hi;
238 for (std::size_t i = 0; i < nq; ++i) {
239 if (i == a) continue;
240 if (rho[i] <= T(rho[a] + tol)) Hc.push_back(i);
241 else Hi.push_back(i);
242 }
243 std::vector<std::size_t> R; // retained, a first
244 R.push_back(a);
245 for (std::size_t i : Hi) R.push_back(i);
246 const std::size_t m = R.size();
247
248 Matrix<T> Fhc, G, Pred;
249 if (Hc.empty()) {
250 G = Matrix<T>(0, m, zero);
251 Pred = submat(P, R, R);
252 } else {
253 const std::size_t nc = Hc.size();
254 Matrix<T> ImP(nc, nc, zero);
255 for (std::size_t i = 0; i < nc; ++i) {
256 ImP(i, i) = one;
257 for (std::size_t j = 0; j < nc; ++j) ImP(i, j) = T(ImP(i, j) - P(Hc[i], Hc[j]));
258 }
259 Fhc = inverse(ImP);
260 G = matmul(Fhc, submat(P, Hc, R)); // nc x m
261 Pred = submat(P, R, R);
262 const Matrix<T> corr2 = matmul(matmul(submat(P, R, Hc), Fhc), submat(P, Hc, R));
263 for (std::size_t i = 0; i < m; ++i)
264 for (std::size_t j = 0; j < m; ++j) Pred(i, j) = T(Pred(i, j) + corr2(i, j));
265 }
266 T ph = Pred(0, 0);
267 if (ph < zero) ph = zero;
268 if (ph > num_traits<T>::from_double(1.0 - 1e-9)) ph = num_traits<T>::from_double(1.0 - 1e-9);
269 if (ph > zero)
270 for (std::size_t j = 0; j < m; ++j) Pred(0, j) = T(Pred(0, j) / (one - ph));
271 Pred(0, 0) = zero;
272
273 // First-passage external rate and IDC into each retained station.
274 const T idc_arv_inf = mam::map_idc(arvMAP);
275 std::vector<T> lam0R(m, zero);
276 for (std::size_t rr = 0; rr < m; ++rr) {
277 lam0R[rr] = lambda0[R[rr]];
278 for (std::size_t ii = 0; ii < Hc.size(); ++ii)
279 lam0R[rr] = T(lam0R[rr] + lambda0[Hc[ii]] * G(ii, rr));
280 }
281 std::vector<T> c2a0R(m, zero);
282 for (std::size_t rr = 0; rr < m; ++rr) {
283 if (!(lam0R[rr] > zero)) continue;
284 T acc = T(lambda0[R[rr]] * (qsplit[R[rr]] * idc_arv_inf + (one - qsplit[R[rr]])));
285 for (std::size_t ii = 0; ii < Hc.size(); ++ii) {
286 const T g = G(ii, rr);
287 const T ci = T(qsplit[Hc[ii]] * idc_arv_inf + (one - qsplit[Hc[ii]]));
288 acc = T(acc + lambda0[Hc[ii]] * g * (g * ci + (one - g)));
289 }
290 c2a0R[rr] = T(acc / lam0R[rr]);
291 }
292 std::function<std::vector<T>(const T&)> a0IdcR = [&, R, Hc, G, lam0R, m](const T& t) {
293 std::vector<T> Ir(m, one);
294 const T Iarv = mam::map_count_idc(arvMAP, t);
295 auto c2fun_i = [&](std::size_t i) -> T {
296 return T(qsplit[i] * Iarv + (one - qsplit[i]));
297 };
298 for (std::size_t rr = 0; rr < m; ++rr) {
299 if (!(lam0R[rr] > zero)) continue;
300 T num = T(lambda0[R[rr]] * c2fun_i(R[rr]));
301 for (std::size_t ii = 0; ii < Hc.size(); ++ii) {
302 const T g = G(ii, rr);
303 num = T(num + lambda0[Hc[ii]] * g * (g * c2fun_i(Hc[ii]) + (one - g)));
304 }
305 Ir[rr] = T(num / lam0R[rr]);
306 }
307 return Ir;
308 };
309
310 // Service data on R; a gets the geometric-sum (folded) service.
311 std::vector<T> muR(m, zero), cs2R(m, zero);
312 std::vector<mam::Map<T> > svcR;
313 svcR.reserve(m);
314 for (std::size_t rr = 0; rr < m; ++rr) {
315 muR[rr] = mu[R[rr]];
316 cs2R[rr] = cs2[R[rr]];
317 svcR.push_back(svcMAP[R[rr]]);
318 }
319 svcR[0] = rqna_geom_map(svcMAP[a], ph);
320 muR[0] = T((one - ph) * mu[a]);
321 cs2R[0] = T(ph + (one - ph) * cs2[a]);
322 std::function<std::vector<T>(const std::vector<T>&)> sIdcR =
323 [&, m](const std::vector<T>& tt) {
324 std::vector<T> out(m, zero);
325 for (std::size_t rr = 0; rr < m; ++rr)
326 out[rr] = mam::map_count_idc(svcR[rr], tt[rr]);
327 return out;
328 };
329
330 const npfqn::TrafficIdcContext<T> ctxR =
331 npfqn::npfqn_traffic_idc(lam0R, Pred, c2a0R, muR, cs2R, corr);
332 auto IaFunA = [&](const T& x) -> T {
333 return npfqn::npfqn_traffic_idc_at(ctxR, x, a0IdcR, sIdcR)[0];
334 };
335 const T Wt = qsys::qsys_gig1_rq(rho[a], muR[0], cs2R[0], IaFunA).W;
336 s.R(ia, 0) = T((one - ph) * Wt + one / mu[a]);
337 } else {
338 auto IaFun_a = [&](const T& x) -> T { return component(x, a); };
339 const T Wa = qsys::qsys_gig1_rq(rho[a], mu[a], cs2[a], IaFun_a).W;
340 s.R(ia, 0) = T(Wa + one / mu[a]);
341 }
342 s.U(ia, 0) = rho[a];
343 s.Q(ia, 0) = T(lambda[a] * s.R(ia, 0));
344 }
345
346 s.Tp(src, 0) = lambda_src;
347 T Csum = zero;
348 for (std::size_t i = 0; i < M; ++i) Csum = T(Csum + s.R(i, 0));
349 s.C[0] = Csum;
350 s.X[0] = lambda_src;
351 return s;
352 } // if constexpr has_transcendental
353}
354
355} // namespace mva
356} // namespace line
357
358#endif // LINE_SOLVERS_MVA_SOLVER_RQNA_H
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
std::size_t nof_stateful() const
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
Matrix< T > rt
sn.rt and sn.rtnodes: the class-expanded routing.
std::vector< JobClass > classes
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 ...
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Index of dispersion for counts (IDC) of a MAP at resolution t.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
The option and result types every MVA analyzer shares.
mam::Map< T > dist_to_map(const Distrib< T > &d)
T map_idc(const Map< T > &m)
Index of dispersion for counts, I = 1 + 2(lambda - pie (Q + e pi)^-1 D1 e).
Definition map_moment.h:196
std::vector< T > map_count_idc(const Map< T > &m, const std::vector< T > &t)
Index of dispersion for counts (IDC) of a MAP at resolution t.
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Definition map_moment.h:89
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
Definition map_moment.h:79
std::string mva_single_class_open_reason(const qn::NetworkStruct< T > &L, const std::string &method)
RQNA and RQT decompose an open network into GI/G/1 queues and build one uncertainty set per flow out ...
Definition mva_types.h:178
MvaSolution< T > solver_rqna(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
TrafficIdcContext< T > npfqn_traffic_idc(const std::vector< T > &lambda0, const Matrix< T > &P, const std::vector< T > &c2a0, const std::vector< T > &mu, const std::vector< T > &cs2, const TrafficIdcCorrections &corrections)
Traffic variability equations of the Robust Queueing Network Analyzer (W.
FeedbackElimResult< T > npfqn_feedback_elim(const Matrix< T > &P, const std::vector< T > &rho, const std::vector< T > &cs2=std::vector< T >(), const std::vector< T > &lambda=std::vector< T >(), bool immediateOnly=false)
Near-immediate feedback elimination for the robust queueing network analyzer.
std::vector< T > npfqn_traffic_idc_at(const TrafficIdcContext< T > &ctx, const T &t, const std::function< std::vector< T >(const T &)> &a0IdcFun, const std::function< std::vector< T >(const std::vector< T > &)> &sIdcFun)
Time-dependent IDC equations (eq.
Gig1RqResult< T > qsys_gig1_rq(const T &rho, const T &mu, const T &cs2, IaFun &&IaFun_)
Robust Queueing (RQ) approximation of a G/GI/1 queue characterized by its arrival index of dispersion...
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
A queueing network and its refreshed NetworkStruct.
Near-immediate feedback elimination for the robust queueing network analyzer.
Traffic variability equations of the Robust Queueing Network Analyzer (W.
Robust Queueing (RQ) approximation of a G/GI/1 queue characterized by its arrival index of dispersion...
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
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
std::vector< T > C
Definition mva_types.h:98
Context returned by npfqn_traffic_idc, mirroring the MATLAB ctx struct.
std::vector< T > rho
(K) utilization lambda_i / mu_i
std::vector< T > lambda
(K) total arrival rate at each queue
Toggle for the two correction terms, mirroring MATLAB's corrections.