LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_qna.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_QNA_H
6#define LINE_SOLVERS_MVA_SOLVER_QNA_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * QNA, the two-moment open-network decomposition analyzer.
12 *
13 * Templated port of `matlab/src/solvers/MVA/solver_qna.m`, which implements
14 * Section 7.2.3 of N. Gautam, "Analysis of Queues", CRC Press 2012, with
15 * corrections the reference records as agreed with the author.
16 *
17 * The structure is decomposition-aggregation. Each sweep
18 *
19 * 1. SUPERPOSES the flows into every station: a1(i,r) is the arrival rate of
20 * class r at station i and a2(i,r) the squared coefficient of variation of
21 * that superposed stream, both accumulated over the routing;
22 * 2. SOLVES each station in isolation -- an infinite server passes its input
23 * SCV straight through, a PS station takes a geometric-bound queue length,
24 * and an FCFS station takes the multiserver GI/G/c waiting-time
25 * approximation with the Whitt alpha(m) correction;
26 * 3. SPLITS the departure stream along each outgoing arc, `f2 = 1 + p (d2-k)`
27 * with k the round-robin split degree (`npfqn_traffic_split_rr`): at k = 1
28 * this is the exact SCV of a Bernoulli-thinned renewal stream, and at k > 1
29 * that of a one-in-k deterministic dispatch, which is less variable;
30 *
31 * and `da_fpi` drives the sweeps to a fixed point on the queue lengths.
32 *
33 * CLOSED CHAINS. The reference seeds a closed chain from `State.toMarginal` of
34 * its reference station (`solver_qna.m:94`): the TOTAL number of jobs the
35 * initial state holds there, written into every class column of that row. This
36 * port reads the same quantity from `sn_declared_marginal`, which decodes a
37 * declared state and falls back to `initDefault`'s placement. Each sweep then
38 * rescales a closed chain's column to its population, which, unlike the open
39 * case below, is a finite factor, so the sweeps genuinely iterate to a fixed
40 * point; the PS station takes the finite-population geometric bound
41 * (U - U^(N+1)) / (1 - U), and the result is rescaled to the population once
42 * more on the way out. `SolverMVA` still advertises `qna` for fully open models
43 * only (its feature set withdraws ClosedClass), exactly as the reference does,
44 * so the closed path is reached by calling the analyzer directly.
45 *
46 * REFERENCE INDEXING, REPRODUCED AND CHECKED. `solver_qna.m` indexes `sn.rt`,
47 * which is STATEFUL-indexed, with STATION indices, and reads `sn.njobs`, which
48 * is CLASS-indexed, with a CHAIN index. Both are silently correct exactly when
49 * the stations coincide with the stateful nodes and the chains with the
50 * classes, which is the shape of the models the method is advertised for. This
51 * port checks that instead of assuming it, and refuses by name otherwise -- the
52 * alternative is reading an unrelated row and returning a number.
53 *
54 * SELF-LOOPING CLASSES. The reference special-cases `sn.isslc` in three places.
55 * This port has no SelfLoopingClass (`JobClassType` is OPEN or CLOSED only), so
56 * those branches are unreachable and are not transcribed; a class type this
57 * port cannot build cannot reach here.
58 *
59 * Arithmetic: TRANSCENDENTAL. `da_fpi` stops on a tolerance, and the alpha(m)
60 * correction takes a real power of the utilization.
61 */
62
63#include <cmath>
64#include <string>
65#include <vector>
66
67#include "line/api/da/da_fpi.h"
73
74namespace line {
75namespace mva {
76
77/**
78 * Port of `solver_qna.m`.
79 *
80 * @param L the model; open, closed or mixed chains
81 * @param opt tol drives the saturation test, iter_max / iter_tol the sweeps
82 */
83template <class T>
85 using qn::NodeType;
87 if constexpr (!num_traits<T>::has_transcendental) {
88 (void)L;
89 (void)opt;
90 throw UnsupportedError(ErrorKind{"Arithmetic"},
91 "solver_qna: the decomposition sweeps stop on a tolerance and the multiserver "
92 "correction takes a real power, so exact rational arithmetic cannot run it");
93 } else {
94 const T zero = num_traits<T>::from_int(0);
95 const T one = num_traits<T>::from_int(1);
96 const std::size_t M = L.nstations, K = L.nclasses, C = L.nchains;
97
98 if (L.nof_stateful() != M)
99 throw UnsupportedError(ErrorKind{"Topology"},
100 "solver_qna: the reference indexes the stateful-indexed sn.rt with station indices, "
101 "which is only correct when every stateful node is a station; this model has " +
102 std::to_string(L.nof_stateful()) + " stateful nodes and " + std::to_string(M) +
103 " stations");
104 if (C != K)
105 throw UnsupportedError(ErrorKind{"MultiClass"},
106 "solver_qna: the reference reads the class-indexed sn.njobs with a chain index, which "
107 "is only correct when each chain holds exactly one class");
108 // One predicate for the gate and the run: qn::mva_feature_set withholds 'qna'
109 // for a discipline the station update has no arm for, and the update refuses
110 // it by name rather than leaving that station's row of Q, U, R and T at zero
111 // and returning the table as a solution.
112 {
113 const std::string qna_reason = mva_qna_scheduling_reason(L);
114 if (!qna_reason.empty()) throw UnsupportedError(ErrorKind{"Model"}, qna_reason);
115 }
116
117 // S = 1/rates, with a NaN scv read as zero, as the reference does
118 Matrix<T> S(M, K, zero), scv(M, K, zero);
119 for (std::size_t i = 0; i < M; ++i)
120 for (std::size_t r = 0; r < K; ++r) {
121 const T mu = L.rates(i, r);
122 S(i, r) = (mu > zero) ? T(one / mu) : zero;
123 const double v = num_traits<T>::to_double(L.scv(i, r));
124 scv(i, r) = std::isnan(v) ? zero : L.scv(i, r);
125 }
126
127 // V(i,k): the visits summed over chains, at station level
128 Matrix<T> V(M, K, zero);
129 for (std::size_t c = 0; c < C; ++c)
130 for (std::size_t i = 0; i < M; ++i)
131 for (std::size_t k = 0; k < K; ++k)
132 V(i, k) = T(V(i, k) + L.visits[c](L.stateful_of_station(i + 1) - 1, k));
133
134 const Matrix<T>& rt = L.rt;
135 auto RT = [&](std::size_t i, std::size_t r, std::size_t j, std::size_t s) -> T {
136 return rt(i * K + r, j * K + s);
137 };
138
139 // The deterministic (round-robin) split degrees, 1 where the split is
140 // Markovian; a flow thinned out of a one-in-k dispatch is the k-fold
141 // convolution, whose SCV at d2 = 1 is 1 + p (1 - k), below the renewal 1.
143
144 // f2(i,r -> j,s): the SCV of each flow, initialised on every arc that does
145 // not end at a Source
146 Matrix<T> f2(M * K, M * K, zero);
147 std::vector<bool> is_source(M, false);
148 for (std::size_t i = 0; i < M; ++i)
149 is_source[i] = (L.stations[i].nodetype == NodeType::Source);
150 for (std::size_t i = 0; i < M; ++i)
151 for (std::size_t j = 0; j < M; ++j) {
152 if (is_source[j]) continue;
153 for (std::size_t r = 0; r < K; ++r)
154 for (std::size_t s = 0; s < K; ++s)
155 if (RT(i, r, j, s) > zero)
156 f2(i * K + r, j * K + s) =
157 T(one + RT(i, r, j, s) * T(one - kRR(i, r)));
158 }
159
160 // per-chain arrival rate and the SCV of the superposed source stream
161 std::vector<T> lambda(C, zero), d2c(C, zero);
162 Matrix<T> Tp(M, K, zero), Q(M, K, zero), U(M, K, zero), Rt(M, K, zero);
163 // the closed chains' seed: the total job count of the initial state at the
164 // reference station, in every class column of that row (`State.toMarginal`)
166 for (std::size_t c = 0; c < C; ++c) {
167 const std::size_t ref = L.classes[L.inchain[c][0] - 1].refstat;
168 if (std::isfinite(L.classes[c].population)) {
169 T ni = zero;
170 for (std::size_t k = 0; k < K; ++k) ni += n0(ref - 1, k);
171 for (std::size_t k = 0; k < K; ++k) Q(ref - 1, k) = ni;
172 }
173 std::vector<T> lam_in, scv_in;
174 for (std::size_t k : L.inchain[c]) {
175 lam_in.push_back(L.rates(ref - 1, k - 1));
176 scv_in.push_back(scv(ref - 1, k - 1));
177 }
178 T s = zero;
179 for (const T& x : lam_in)
180 if (std::isfinite(num_traits<T>::to_double(x))) s += x;
181 lambda[c] = s;
182 d2c[c] = da::da_traffic_superpos(lam_in, scv_in);
183 // only an OPEN chain's throughput is known at its reference station
184 if (!std::isfinite(L.classes[c].population))
185 for (std::size_t a = 0; a < L.inchain[c].size(); ++a)
186 Tp(ref - 1, L.inchain[c][a] - 1) = lam_in[a];
187 }
188 // d2(i): the SCV of the departure stream of station i. Every reference
189 // station is seeded with the SAME rate-weighted mean over the chains, which
190 // is what the reference's `d2(refStatIdx) = d2c*lambda'/sum(lambda)` writes
191 // once per chain.
192 std::vector<T> d2(M, zero);
193 {
194 T num = zero, den = zero;
195 for (std::size_t c = 0; c < C; ++c) {
196 num += T(d2c[c] * lambda[c]);
197 den += lambda[c];
198 }
199 const T seed = (den > zero) ? T(num / den) : zero;
200 for (std::size_t c = 0; c < C; ++c)
201 d2[L.classes[L.inchain[c][0] - 1].refstat - 1] = seed;
202 }
203
204 Matrix<T> a1(M, K, zero), a2(M, K, zero);
205 const T tol = num_traits<T>::from_double(opt.tol);
206
207 // One decomposition sweep. `da_fpi` iterates it on the flattened queue
208 // lengths; every other quantity is carried in the enclosing scope, as the
209 // reference carries it in the enclosing function's workspace.
210 auto sweep = [&](const std::vector<T>& qin,
211 std::size_t itnum) -> std::pair<std::vector<T>, std::vector<T>> {
212 for (std::size_t i = 0; i < M; ++i)
213 for (std::size_t k = 0; k < K; ++k) Q(i, k) = qin[i * K + k];
214
215 // THE RENORMALISATION IS LOAD BEARING, AND IT IS WHAT ENDS THE LOOP.
216 // The reference rescales each chain's queue lengths to its population,
217 // `Q(:,c) = njobs(c) * Q(:,c) / sum(Q(:,c))`. On an OPEN chain njobs is
218 // infinite and the initial iterate is zero, so this is Inf * 0 / 0 =
219 // NaN; the reference point handed back to da_fpi is therefore NaN, the
220 // convergence measure is NaN, and `da_nanstop` -- which the reference
221 // sets precisely to reproduce its legacy while-loop -- stops the
222 // iteration after ONE sweep. QNA as the reference ships it does not
223 // iterate its fixed point on an open model, and the numbers it reports
224 // are the first sweep's, computed with every flow SCV still at its
225 // initial 1. Reproduced rather than corrected: skipping the NaN lets
226 // the sweeps run to their actual fixed point and changes every result
227 // (Q1 = 0.75 instead of the reference's 0.875 on the Erlang tandem).
228 std::vector<T> qref(M * K, zero);
229 for (std::size_t c = 0; c < C; ++c) {
230 const double nc = L.classes[c].population;
231 T colsum = zero;
232 for (std::size_t i = 0; i < M; ++i) colsum += Q(i, c);
233 for (std::size_t i = 0; i < M; ++i)
236 }
237 for (std::size_t i = 0; i < M; ++i)
238 for (std::size_t k = 0; k < K; ++k) qref[i * K + k] = Q(i, k);
239
240 if (itnum == 1)
241 for (std::size_t c = 0; c < C; ++c)
242 for (std::size_t m = 0; m < M; ++m)
243 for (std::size_t k : L.inchain[c])
244 Tp(m, k - 1) = T(V(m, k - 1) * lambda[c]);
245
246 // ---- superposition ------------------------------------------------
247 for (std::size_t i = 0; i < M; ++i) {
248 T lambda_i = zero;
249 for (std::size_t k = 0; k < K; ++k) lambda_i += Tp(i, k);
250 for (std::size_t r = 0; r < K; ++r) {
251 a1(i, r) = zero;
252 a2(i, r) = zero;
253 }
254 for (std::size_t j = 0; j < M; ++j)
255 for (std::size_t r = 0; r < K; ++r)
256 for (std::size_t s = 0; s < K; ++s) {
257 const T p = RT(j, s, i, r);
258 if (!(p > zero)) continue;
259 a1(i, r) = T(a1(i, r) + Tp(j, s) * p);
260 if (lambda_i > zero)
261 a2(i, r) = T(a2(i, r) + T(one / lambda_i) * f2(j * K + s, i * K + r) *
262 Tp(j, s) * p);
263 }
264 }
265
266 // ---- solve each station in isolation ------------------------------
267 for (std::size_t i = 0; i < M; ++i) {
268 if (L.stations[i].nodetype == NodeType::Join) continue; // no-op, as in the reference
269 switch (L.stations[i].sched) {
270 case SchedStrategy::INF: {
271 // departure SCV of a delay: MATLAB writes d2(ist,s)=a2(ist,s)
272 // but every downstream read is the scalar d2(ist), i.e. the
273 // FIRST class column a2(ist,1); JAR reads the same. Use a2(i,0).
274 d2[i] = a2(i, 0);
275 for (std::size_t k = 0; k < K; ++k) {
276 Tp(i, k) = a1(i, k);
277 Q(i, k) = T(Tp(i, k) * S(i, k) * V(i, k));
278 U(i, k) = Q(i, k);
279 Rt(i, k) = Tp(i, k) > zero ? T(Q(i, k) / Tp(i, k)) : zero;
280 }
281 break;
282 }
283 case SchedStrategy::PS: {
284 for (std::size_t c = 0; c < C; ++c) {
285 for (std::size_t k : L.inchain[c]) {
286 Tp(i, k - 1) = T(lambda[c] * V(i, k - 1));
287 U(i, k - 1) = T(S(i, k - 1) * Tp(i, k - 1));
288 }
289 T usum = zero;
290 for (std::size_t k = 0; k < K; ++k) usum += U(i, k);
291 const T uden = usum < T(one - tol) ? usum : T(one - tol);
292 // the reference's finite-population geometric bound
293 // (U - U^(Nc+1)) / (1 - Uden); Nc is infinite on an open
294 // chain, where U^(Nc+1) vanishes for a stable station
295 double Nc = 0.0;
296 for (std::size_t k : L.inchain[c]) Nc += L.classes[k - 1].population;
297 for (std::size_t k : L.inchain[c]) {
298 using std::pow;
299 const T Uk = U(i, k - 1);
300 const T num = std::isfinite(Nc)
301 ? T(Uk - T(pow(Uk, num_traits<T>::from_double(Nc + 1.0))))
302 : Uk;
303 Q(i, k - 1) = T(one - uden) > zero ? T(num / T(one - uden)) : zero;
304 Rt(i, k - 1) =
305 Tp(i, k - 1) > zero ? T(Q(i, k - 1) / Tp(i, k - 1)) : zero;
306 }
307 }
308 break;
309 }
310 case SchedStrategy::FCFS: {
311 using std::pow;
312 using std::sqrt;
314 std::vector<T> mu(K, zero), rho_cls(K, zero);
315 T lambda_i = zero;
316 for (std::size_t r = 0; r < K; ++r) {
317 const double m = num_traits<T>::to_double(L.rates(i, r));
318 mu[r] = std::isnan(m) ? zero : L.rates(i, r);
319 const T den = T(ftol + mu[r]);
320 rho_cls[r] = den > zero ? T(a1(i, r) / den) : zero;
321 if (std::isnan(num_traits<T>::to_double(rho_cls[r]))) rho_cls[r] = zero;
322 lambda_i += a1(i, r);
323 }
324 const T mi = num_traits<T>::from_double(L.stations[i].nservers);
325 T rho = zero;
326 for (std::size_t r = 0; r < K; ++r) rho += rho_cls[r];
327 rho = T(rho / mi);
328 if (rho < T(one - tol)) {
329 // Whitt's alpha(m): the heavier form above rho = 0.7
330 const T alpha = num_traits<T>::to_double(rho) > 0.7
331 ? T(T(pow(rho, mi) + rho) / num_traits<T>::from_int(2))
332 : T(pow(rho, T(T(mi + one) / num_traits<T>::from_int(2))));
333 const T mubar = rho > zero ? T(lambda_i / rho) : zero;
334 T c2 = T(-one);
335 for (std::size_t r = 0; r < K; ++r) {
336 if (!(mu[r] > zero) || !(lambda_i > zero)) continue;
337 const T q = T(mubar / mi / mu[r]);
338 c2 += T(a1(i, r) / lambda_i * q * q * T(scv(i, r) + one));
339 }
340 T a2sum = zero;
341 for (std::size_t r = 0; r < K; ++r) a2sum += a2(i, r);
342 const T Wiq = mubar > zero
343 ? T(T(alpha / mubar) * T(one / T(one - rho)) *
344 T(T(a2sum + c2) / num_traits<T>::from_int(2)))
345 : zero;
346 for (std::size_t k = 0; k < K; ++k)
347 Q(i, k) = mu[k] > zero ? T(a1(i, k) / mu[k] + a1(i, k) * Wiq) : zero;
348 d2[i] = T(one + T(rho * rho * T(c2 - one) / sqrt(mi)) +
349 T(T(one - rho * rho) * T(a2sum - one)));
350 } else {
351 // saturated: the reference parks the queue length at the
352 // class population, which is infinite on an open class
353 for (std::size_t k = 0; k < K; ++k)
354 Q(i, k) = num_traits<T>::from_double(L.classes[k].population);
355 d2[i] = one;
356 }
357 for (std::size_t k = 0; k < K; ++k) {
358 Tp(i, k) = a1(i, k);
359 U(i, k) = T(Tp(i, k) * S(i, k) / mi);
360 Rt(i, k) = Tp(i, k) > zero ? T(Q(i, k) / Tp(i, k)) : zero;
361 }
362 break;
363 }
364 default:
365 // EXT (a Source or a Sink) carries no queue; every other
366 // discipline is one the reference's switch does not handle,
367 // and falling through would leave the station unsolved.
368 if (L.stations[i].sched != SchedStrategy::EXT)
369 throw UnsupportedError(ErrorKind{"SchedStrategy"},
370 std::string("solver_qna: no isolated-station solution for ") +
371 lang::sched_to_text(L.stations[i].sched) + " scheduling");
372 break;
373 }
374 }
375
376 // ---- splitting ----------------------------------------------------
377 for (std::size_t i = 0; i < M; ++i)
378 for (std::size_t j = 0; j < M; ++j) {
379 if (is_source[j]) continue;
380 for (std::size_t r = 0; r < K; ++r)
381 for (std::size_t s = 0; s < K; ++s)
382 if (RT(i, r, j, s) > zero)
383 // k-fold convolution then Bernoulli thinning at
384 // q = k p: C^2 = 1 + p (d2 - k), the Markovian
385 // 1 + p (d2 - 1) at k = 1
386 f2(i * K + r, j * K + s) =
387 T(one + RT(i, r, j, s) * T(d2[i] - kRR(i, r)));
388 }
389
390 std::vector<T> qnew(M * K, zero);
391 for (std::size_t i = 0; i < M; ++i)
392 for (std::size_t k = 0; k < K; ++k) qnew[i * K + k] = Q(i, k);
393 return std::make_pair(qnew, qref);
394 };
395
397 // the legacy while-loop ran one extra sweep at the cap, and exited on a
398 // non-finite convergence measure
399 fo.iter_max = static_cast<std::size_t>(opt.iter_max) + 1;
400 fo.iter_tol = opt.iter_tol;
401 fo.nanstop = true;
402 // the first iterate is Q as seeded above: zero, except a closed chain's
403 // reference-station row
404 std::vector<T> q0(M * K, zero);
405 for (std::size_t i = 0; i < M; ++i)
406 for (std::size_t k = 0; k < K; ++k) q0[i * K + k] = Q(i, k);
407 const da::FpiResult<T> fr = da::da_fpi<T>(sweep, q0, fo);
408 for (std::size_t i = 0; i < M; ++i)
409 for (std::size_t k = 0; k < K; ++k) Q(i, k) = fr.x[i * K + k];
410
411 // a closed chain's column is rescaled to its population on the way out
412 for (std::size_t c = 0; c < C; ++c) {
413 const double nc = L.classes[c].population;
414 if (!std::isfinite(nc)) continue;
415 T colsum = zero;
416 for (std::size_t i = 0; i < M; ++i) colsum += Q(i, c);
417 for (std::size_t i = 0; i < M; ++i)
418 Q(i, c) = T(num_traits<T>::from_double(nc) * Q(i, c) / colsum);
419 }
420
421 // an infinite server's utilization IS its queue length
422 for (std::size_t i = 0; i < M; ++i)
423 if (L.stations[i].sched == SchedStrategy::INF)
424 for (std::size_t k = 0; k < K; ++k) U(i, k) = Q(i, k);
425
426 MvaSolution<T> out;
427 out.Q = Q;
428 out.U = U;
429 out.R = Rt;
430 out.Tp = Tp;
431 out.C.assign(K, zero);
432 out.X.assign(K, zero);
433 for (std::size_t k = 0; k < K; ++k)
434 for (std::size_t i = 0; i < M; ++i) out.C[k] += Rt(i, k);
435 // the reference takes |Q| and maps every NaN to zero on the way out
436 auto clean = [](Matrix<T>& A) {
437 for (std::size_t i = 0; i < A.rows(); ++i)
438 for (std::size_t j = 0; j < A.cols(); ++j)
439 if (std::isnan(num_traits<T>::to_double(A(i, j))))
440 A(i, j) = num_traits<T>::from_int(0);
441 };
442 for (std::size_t i = 0; i < out.Q.rows(); ++i)
443 for (std::size_t j = 0; j < out.Q.cols(); ++j)
444 if (out.Q(i, j) < zero) out.Q(i, j) = T(-out.Q(i, j));
445 clean(out.Q);
446 clean(out.U);
447 clean(out.R);
448 for (std::size_t k = 0; k < K; ++k)
449 if (std::isnan(num_traits<T>::to_double(out.C[k]))) out.C[k] = zero;
450 out.method = "qna";
451 out.iter = static_cast<int>(fr.iterations);
452 out.lG = 0.0;
453 return out;
454 }
455}
456
457} // namespace mva
458} // namespace line
459
460#endif // LINE_SOLVERS_MVA_SOLVER_QNA_H
UnsupportedError(const std::string &what)
Definition error.h:119
A network plus its refreshed NetworkStruct.
std::size_t stateful_of_station(std::size_t st) const
std::size_t nof_stateful() const
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 ...
std::vector< std::vector< std::size_t > > inchain
1-based class indices per chain
std::vector< Matrix< T > > visits
(nchains) each (nstateful x nclasses)
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
Superposition of independent renewal flows (Whitt's QNA stationary-interval method).
The option and result types every MVA analyzer shares.
Matrix< T > sn_declared_marginal(const qn::NetworkStruct< T > &sn)
The (nstations x nclasses) per-class job counts of the model's OWN state.
Definition sn_state.h:85
T da_traffic_superpos(const std::vector< T > &lambda, const std::vector< T > &a2)
Superposition of independent renewal flows (Whitt's QNA stationary-interval method).
FpiResult< T > da_fpi(const std::function< std::pair< std::vector< T >, std::vector< T > >(const std::vector< T > &, std::size_t)> &iterfun, const std::vector< T > &x0, const FpiOptions &options=FpiOptions())
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
Definition da_fpi.h:102
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:185
const char * sched_to_text(SchedStrategy s)
Definition lang_types.h:235
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:331
MvaSolution< T > solver_qna(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_qna.m.
Definition solver_qna.h:84
std::string mva_qna_scheduling_reason(const qn::NetworkStruct< T > &L)
QNA's station update has an arm for INF, PS and FCFS and none for any other discipline,...
Definition mva_types.h:279
Matrix< T > npfqn_traffic_split_rr(const qn::NetworkStruct< T > &sn)
Port of npfqn_traffic_split_rr.m.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Deterministic (round-robin) split degrees of every station-class departure stream.
Ports of matlab/src/api/sn/sn_get_state_aggr.m and sn_is_state_valid.m.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Options mirroring the fields MATLAB reads off the options struct.
Definition da_fpi.h:50
std::size_t iter_max
Definition da_fpi.h:51
bool nanstop
stop when the increment norm is NaN (the reference's isnan test)
Definition da_fpi.h:55
std::size_t iterations
Definition da_fpi.h:88
std::vector< T > x
Definition da_fpi.h:87
static constexpr double FineTol
Definition lang_types.h:765
The options SolverMVA reads.
Definition mva_types.h:32
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:97
std::vector< T > X
Definition mva_types.h:99
double lG
log of the normalizing constant, the reference's lG.
Definition mva_types.h:119
std::vector< T > C
Definition mva_types.h:99