LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mva_dispatch.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_MVA_DISPATCH_H
6#define LINE_SOLVERS_MVA_MVA_DISPATCH_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `@@SolverMVA/mvaDispatch.m`: one inner solve, choosing the analyzer
12 * that fits the model.
13 *
14 * THE ORDER IS THE CONTRACT. The branches are NOT disjoint -- a single-class
15 * open Source-Queue-Sink model with a size-based discipline satisfies two of
16 * them, a cache model with load dependence satisfies two more -- so the first
17 * match wins and reordering silently changes which algorithm a model gets.
18 * The sequence below is the reference's, top to bottom:
19 *
20 * 0 closed models with a shortest-job-next station <- ported
21 * 1 order-independent / pass-and-swap stations
22 * 2 delayed-hit retrieval caches, open then closed
23 * 3 size-based scheduling in an open Source-Queue-Sink model
24 * 4 single-class open Source-Queue-Sink <- ported
25 * 5 multiclass open polling
26 * 6 multiclass open HOL priority <- ported
27 * 7 multiclass open DPS, at most three classes <- ported
28 * 8 non-reentrant cache (Source-Cache-Sink)
29 * 9 integrated caching-queueing
30 * 10 bound methods (moved to SolverBA in the reference)
31 * 11 Marie's aggregation-decomposition
32 * 12 load- or class-dependent scaling <- ported
33 * 13 everything else <- solver_mva_analyzer,
34 * of whose method switch 'mvac' is dispatched here (solver_mvac.h)
35 *
36 * WHAT IS NOT PORTED IS REFUSED BY NAME, never allowed to fall through to the
37 * generic analyzer. A cache model solved as an ordinary queueing network
38 * returns numbers -- they are simply not the model's -- and the same holds for
39 * a polling system and for an OI station, whose rate function the AMVA cannot
40 * represent at all.
41 */
42
43#include <algorithm>
44#include <cmath>
45#include <limits>
46#include <memory>
47#include <string>
48#include <vector>
49
85#include "line/util/rootfind.h"
103
104namespace line {
105namespace mva {
106
107/** What the dispatch returns: the metrics plus the algorithm that produced them. */
108template <class T>
111 /** The concrete algorithm, as the reference's `actualmethod`. */
112 std::string actualmethod;
113 /**
114 * Set only by the integrated cacheqn branch: the converged struct whose
115 * routing carries the actual hit/miss probabilities, from which the runner
116 * derives ArvR and ResidT. Empty for every other model.
117 */
118 std::shared_ptr<qn::NetworkStruct<T>> refreshed_struct;
119 /**
120 * What a cache branch measured, EMPTY on every model without a Cache.
121 *
122 * A cache's hit / miss / delayed-hit split is a SOLVER RESULT, not model
123 * state, and it is the only part of the answer the (station x class) tables
124 * cannot carry. Dropping it here is what left `-a cache` refusing under
125 * `-s mva` and, through CPPLINE, left MATLAB's Cache node holding whatever
126 * the PREVIOUS solver wrote -- retrieval_simple printed the LDES table under
127 * the MVA label. Assembled by `solvers::cache_metrics_of`, one rule for
128 * every branch of every solver.
129 */
131 /**
132 * Non-empty when the analyzer that ran would have raised a reference
133 * `line_warning` and still returned a usable answer, carrying its text.
134 * Set only by the SJN branch today (solver_mva_sjn.h).
135 *
136 * KNOWN DIVERGENCE, DELIBERATE. This port has no general warning channel --
137 * no line_warning equivalent exists under cpp/include/line/ -- so the text
138 * stops here, at the dispatch boundary. A caller of `mva_dispatch` sees it;
139 * a user going through SolverMVA does NOT, because solver_mva_runner.h does
140 * not read the field. Closing that last hop needs a runner change and a
141 * decision about where a solver-level warning should surface at all, which
142 * is wider than this analyzer.
143 */
144 std::string warning;
145};
146
147namespace detail {
148
149/** True when the model is exactly a Source, a Queue and a Sink. */
150template <class T>
151bool is_open_sqs(const qn::NetworkStruct<T>& L) {
152 if (L.nof_nodes() != 3) return false;
153 int src = 0, q = 0, snk = 0;
154 for (const qn::NodeDef& nd : L.nodes) {
155 if (nd.nodetype == qn::NodeType::Source) ++src;
156 else if (nd.nodetype == qn::NodeType::Queue) ++q;
157 else if (nd.nodetype == qn::NodeType::Sink) ++snk;
158 }
159 return src == 1 && q == 1 && snk == 1 && L.nclosedjobs() == 0.0;
160}
161
162/**
163 * The method names `solver_mva_qsys_analyzer` has an arm for.
164 *
165 * ONE PREDICATE FOR THE INTERCEPTION AND THE RUN. The Source-Queue-Sink shape is
166 * claimed by that analyzer, which answers this FIXED list of closed forms and
167 * refuses every other name -- so each general network method the report offers
168 * on an open model was advertised on the one open shape it could not run on and
169 * threw "not available for a model with one station and one class" the moment it
170 * was asked for: `mva`, `amva`, `sum`, `esum`, `lin`, `gflin`, `egflin`, `qli`,
171 * `fli`, `qd` and `qdlin`, eleven of them. They are NETWORK methods, and the
172 * general branch solves a one-queue network exactly as it solves a larger one,
173 * so the interception stands aside for them rather than claiming a model it
174 * cannot answer. `qna` was the first name found this way and used to be excluded
175 * by hand at the call site; it needs no special case now, having no arm here
176 * either.
177 *
178 * Judged on the ALIASED name, the same `amva_method_alias` the analyzer applies
179 * on the way in. Mirrors `matlab/src/solvers/MVA/@SolverMVA/mvaDispatch.m` and
180 * its JAR and native python twins.
181 */
182inline bool qsys_serves_method(const std::string& method) {
183 static const char* const kServed[] = {
184 "default", "exact", "erlanga", "mgisrgi", "gigk.diffusion",
185 "mm1", "mmk", "mg1", "mgi1", "gigk",
186 "gigk.kingman_approx", "gigk.whitt", "gig1", "gig1.allen",
187 "gig1.kingman", "gig1.heyman", "gig1.kobayashi", "gig1.klb", "gig1.marchal",
188 "gig1.gelenbe", "gig1.kimura", "gig1.extremal", "qed", "rqna",
189 "rqt", "gm1", "gim1"};
190 for (std::size_t i = 0; i < sizeof(kServed) / sizeof(*kServed); ++i)
191 if (method == kServed[i]) return true;
192 return false;
193}
194
195/** The same, with a Cache in place of the Queue. */
196template <class T>
197bool is_open_scs(const qn::NetworkStruct<T>& L) {
198 if (L.nof_nodes() != 3) return false;
199 int src = 0, ca = 0, snk = 0;
200 for (const qn::NodeDef& nd : L.nodes) {
201 if (nd.nodetype == qn::NodeType::Source) ++src;
202 else if (nd.nodetype == qn::NodeType::Cache) ++ca;
203 else if (nd.nodetype == qn::NodeType::Sink) ++snk;
204 }
205 return src == 1 && ca == 1 && snk == 1 && L.nclosedjobs() == 0.0;
206}
207
208/** The size-based disciplines the reference routes to its own analyzer. */
209inline bool is_size_based(qn::SchedStrategy s) {
210 return s == qn::SchedStrategy::SRPT || s == qn::SchedStrategy::PSJF ||
211 s == qn::SchedStrategy::FB || s == qn::SchedStrategy::LRPT ||
212 s == qn::SchedStrategy::SETF;
213}
214
215/** 1-based station index of the single node of this type, 0 when absent. */
216template <class T>
217std::size_t station_of_type(const qn::NetworkStruct<T>& L, qn::NodeType ty) {
218 for (std::size_t i = 0; i < L.nof_nodes(); ++i)
219 if (L.nodes[i].nodetype == ty) return L.nodes[i].station;
220 return 0;
221}
222
223/** True when every finite SCV of a station's row is 1, as the reference tests. */
224template <class T>
225bool row_is_exponential(const qn::NetworkStruct<T>& L, std::size_t ist) {
226 for (std::size_t r = 0; r < L.nclasses; ++r) {
227 if (L.disabled[ist - 1][r]) continue;
228 const double v = num_traits<T>::to_double(L.scv(ist - 1, r));
229 if (!std::isfinite(v)) continue;
230 if (std::fabs(v - 1.0) >= 1e-6) return false;
231 }
232 return true;
233}
234
235} // namespace detail
236
237/**
238 * Port of `solver_mva_qsys_analyzer.m`: the closed forms for a single-class
239 * open Source-Queue-Sink model.
240 *
241 * `exact` resolves to M/M/1, M/M/k, M/G/1 or G/M/1 and REFUSES anything else,
242 * since no closed form covers it; `default` additionally falls back to the
243 * G/G/k approximation and to the KLB G/G/1 approximation.
244 */
245template <class T>
247 // The open-queue closed forms evaluate transcendentals (square roots, LSTs,
248 // Brent roots), so under exact/Rational arithmetic the whole body is
249 // discarded and the model is refused by name -- the field-arithmetic ladder
250 // branches stay exact. Guarding the body (not just a static_assert) is what
251 // lets mva_dispatch<Rational> compile at all.
252 if constexpr (!num_traits<T>::has_transcendental) {
253 throw UnsupportedError(
254 "solver_mva_qsys_analyzer: the open queueing-system closed forms need transcendental "
255 "arithmetic; rerun this model with --arith double or --arith real");
256 } else {
257 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
258 const std::size_t M = L.nstations;
259 const std::size_t src = detail::station_of_type(L, qn::NodeType::Source);
260 const std::size_t q = detail::station_of_type(L, qn::NodeType::Queue);
261 if (src == 0 || q == 0) throw InputError("solver_mva_qsys_analyzer: no Source-Queue pair");
262
263 // The visit ratio of the queue, which for a feedback model exceeds one and
264 // separates the per-visit quantities from the per-job ones.
265 const std::size_t qstateful = L.stateful_of_station(q);
266 const T Vq = L.visits[0](qstateful - 1, 0);
267 const T srcRate = L.rates(src - 1, 0);
268 const T lambda = T(srcRate * Vq);
269 const T mu = L.rates(q - 1, 0);
270 const double k = L.stations[q - 1].nservers;
271 const unsigned ku = std::isfinite(k) ? static_cast<unsigned>(std::llround(k)) : 1u;
272 const T ca = num_traits<T>::from_double(std::sqrt(num_traits<T>::to_double(L.scv(src - 1, 0))));
273 const T cs = num_traits<T>::from_double(std::sqrt(num_traits<T>::to_double(L.scv(q - 1, 0))));
274 const bool ca1 = std::fabs(num_traits<T>::to_double(ca) - 1.0) < 1e-12;
275 const bool cs1 = std::fabs(num_traits<T>::to_double(cs) - 1.0) < 1e-12;
276
277 // Finite-capacity loss branch (M/M/1/K with tail drop), the ONE finite
278 // buffer this solver honours: the moment-based MacGregor Smith loss
279 // probability, exact only at scv=1, with the queue length taken from the
280 // truncated M/M/1/K distribution. Being an approximation in general it is
281 // not offered under method='exact'; the capacity gate in the runner exempts
282 // exactly this shape for every other method, so the two must agree.
283 if (sn_is_mm1k_loss(L)) {
284 if (opt.method == "exact")
285 throw UnsupportedError(
286 "solver_mva_qsys_analyzer: M/M/1/K tail-drop is solved by the approximate "
287 "'mg1k.mgs' method (MacGregor Smith); it is not available under method='exact'. "
288 "Use the default method, or SolverCTMC/SolverNC for an exact result");
289 const double Kcap = L.cap[q - 1];
290 const T rho = T(lambda / mu);
291 const T Ploss =
292 qsys::qsys_mg1k_loss_mgs(lambda, mu, T(cs * cs), static_cast<unsigned>(std::llround(Kcap)))
293 .lossProbability;
294 const T Tq = T(lambda * (one - Ploss)); // carried throughput
295 const double rhod = num_traits<T>::to_double(rho);
296 T Lsys = zero;
297 if (std::fabs(rhod - 1.0) < 1e-10) {
298 Lsys = num_traits<T>::from_double(Kcap / 2.0); // L'Hopital limit at rho = 1
299 } else {
300 const T Kp1 = num_traits<T>::from_double(Kcap + 1.0);
301 const T rKp1 = qsys::detail::num_pow(rho, Kp1);
302 Lsys = T(rho / (one - rho) - Kp1 * rKp1 / (one - rKp1));
303 }
305 MvaSolution<T>& ls = lout.sol;
306 ls.Q = Matrix<T>(M, 1, zero);
307 ls.U = Matrix<T>(M, 1, zero);
308 ls.R = Matrix<T>(M, 1, zero);
309 ls.Tp = Matrix<T>(M, 1, zero);
310 ls.X.assign(1, zero);
311 ls.C.assign(1, zero);
312 ls.method = opt.method;
313 ls.iter = 1;
314 ls.R(q - 1, 0) = T(Lsys / Tq); // per-visit response time, by Little
315 ls.Q(q - 1, 0) = Lsys;
316 ls.U(q - 1, 0) = T(Tq / mu); // single-server utilization
317 ls.Tp(q - 1, 0) = Tq; // carried (effective) rate
318 ls.Tp(src - 1, 0) = lambda; // offered arrival rate
319 ls.X[0] = Tq; // system throughput = carried rate
320 ls.C[0] = T(ls.R(q - 1, 0) * Vq);
321 lout.actualmethod = "mg1k.mgs";
322 return lout;
323 }
324
325 // Empty unless the queue reneges.
326 const api::PatienceHandles<T> hpat = api::sn_patience_handles(L, q - 1, 0);
327
328 std::string method = amva_method_alias(opt.method);
329 if (method == "exact") {
330 if (ca1 && cs1 && ku == 1) method = "mm1";
331 else if (ca1 && cs1 && ku > 1) method = "mmk";
332 else if (ca1 && ku == 1) method = "mg1";
333 else if (cs1 && ku == 1) method = "gm1";
334 else
335 throw UnsupportedError(
336 "solver_mva_qsys_analyzer: no exact closed form for this queueing system "
337 "(neither arrivals nor service are exponential, or it has several servers)");
338 } else if (method == "default") {
339 // A station customers walk away from is a different model, not a
340 // correction to one: nothing in the G/G/k family below carries an
341 // abandonment rate, so the choice is made here and not by ca/cs.
342 if (hpat.present) method = hpat.isExponential ? "erlanga" : "mgisrgi";
343 else if (ca1 && cs1 && ku == 1) method = "mm1";
344 else if (ca1 && cs1 && ku > 1) method = "mmk";
345 else if (ca1 && ku == 1) method = "mg1";
346 else if (cs1 && ku == 1) method = "gm1";
347 else if (ku > 1) method = "gigk";
348 else method = "gig1.klb";
349 }
350
351 // Whitt family, full metric set. These methods answer a station whose
352 // CARRIED throughput is below the offered rate -- customers abandon, or are
353 // blocked -- so Little's law on lambda would silently overstate the queue
354 // and the common tail below cannot be used.
355 if (method == "erlanga" || method == "mgisrgi" || method == "gigk.diffusion") {
356 const double cap = L.cap[q - 1];
357 // waiting spaces, servers excluded
358 const double room = std::isfinite(cap)
359 ? std::max(0.0, cap - static_cast<double>(ku))
360 : std::numeric_limits<double>::infinity();
361 T Lsys = zero, Tq = zero, Uq = zero;
362 if (method == "gigk.diffusion") {
363 const qsys::QsysGgnmResult<T> d = qsys::qsys_ggnm_diffusion(lambda, mu, ku, room, ca, cs);
364 Lsys = d.meanNumber;
365 Tq = d.throughput;
366 Uq = d.utilization;
367 } else {
368 if (!hpat.present)
369 throw UnsupportedError("solver_mva_qsys_analyzer: method '" + method +
370 "' needs a reneging patience law on the queue");
372 (method == "erlanga" || hpat.isExponential)
373 // Exponential patience makes the state-dependent
374 // approximation exact, so take the exact chain either way.
375 ? qsys::qsys_erlanga(lambda, mu, hpat.rate, ku, room)
376 : qsys::qsys_mgisrgi_whitt(lambda, mu, ku, room, hpat.as_patience());
377 Lsys = ab.meanNumber;
378 Tq = ab.throughput;
379 Uq = ab.utilization;
380 }
382 MvaSolution<T>& as = aout.sol;
383 as.Q = Matrix<T>(M, 1, zero);
384 as.U = Matrix<T>(M, 1, zero);
385 as.R = Matrix<T>(M, 1, zero);
386 as.Tp = Matrix<T>(M, 1, zero);
387 as.X.assign(1, zero);
388 as.C.assign(1, zero);
389 as.method = opt.method;
390 as.iter = 1;
391 // Little's law on the CARRIED rate, as the loss branch above and
392 // SolverCTMC report it.
393 as.R(q - 1, 0) = Tq > zero ? T(Lsys / Tq) : zero;
394 as.Q(q - 1, 0) = Lsys;
395 as.U(q - 1, 0) = Uq;
396 as.Tp(q - 1, 0) = Tq; // carried rate
397 as.Tp(src - 1, 0) = srcRate; // offered arrival rate
398 as.X[0] = Tq;
399 as.C[0] = T(as.R(q - 1, 0) * Vq);
400 aout.actualmethod = method;
401 return aout;
402 }
403
404 T R = zero;
405 if (method == "mm1") R = qsys::qsys_mm1(lambda, mu).W;
406 else if (method == "mmk") R = qsys::qsys_mmk(lambda, mu, ku).W;
407 else if (method == "mg1" || method == "mgi1") R = qsys::qsys_mg1(lambda, mu, cs).W;
408 else if (method == "gigk") R = qsys::qsys_gigk_approx(lambda, mu, ca, cs, ku).W;
409 else if (method == "gigk.kingman_approx")
410 R = qsys::qsys_gigk_approx_kingman(lambda, mu, ca, cs, ku).W;
411 else if (method == "gig1.kingman") R = qsys::qsys_gig1_ubnd_kingman(lambda, mu, ca, cs).W;
412 else if (method == "gig1.heyman") R = qsys::qsys_gig1_approx_heyman(lambda, mu, ca, cs).W;
413 else if (method == "gig1" || method == "gig1.allen")
414 R = qsys::qsys_gig1_approx_allencunneen(lambda, mu, ca, cs).W;
415 else if (method == "gig1.kobayashi") R = qsys::qsys_gig1_approx_kobayashi(lambda, mu, ca, cs).W;
416 else if (method == "gig1.klb") R = qsys::qsys_gig1_approx_klb(lambda, mu, ca, cs).W;
417 else if (method == "gig1.marchal") R = qsys::qsys_gig1_approx_marchal(lambda, mu, ca, cs).W;
418 else if (method == "gig1.gelenbe") R = qsys::qsys_gig1_approx_gelenbe(lambda, mu, ca, cs).W;
419 else if (method == "gig1.kimura") R = qsys::qsys_gig1_approx_kimura(lambda, mu, ca, cs).W;
420 else if (method == "gigk.whitt") R = qsys::qsys_gigk_approx_whitt(lambda, mu, ca, cs, ku).W;
421 // RQNA and RQT reach the SINGLE-QUEUE robust formulas, not the network
422 // analyzers of solver_rqna.h / solver_rqt.h: this shape is one node, and
423 // the reference answers it here (solver_mva_qsys_analyzer.m cases rqna
424 // and rqt). Without these two arms the qsys interception below claimed the
425 // model and then refused the method, so `rqna` and `rqt` were advertised
426 // on every Source-Queue-Sink model and ran on none of them.
427 else if (method == "rqna") {
428 // The arrival flow enters through its index of dispersion for counts.
429 const mam::Map<T> arvMAP = lang::dist_to_map(L.service[src - 1][0]);
430 const T rho1 = lambda / mu;
431 auto IaFun = [&](const T& x) { return mam::map_count_idc(arvMAP, x); };
432 R = T(qsys::qsys_gig1_rq(rho1, mu, T(cs * cs), IaFun).W + one / mu);
433 } else if (method == "rqt") {
434 // Polyhedral uncertainty sets for the arrival and service flows.
435 const T rho1 = lambda / (num_traits<T>::from_int(static_cast<int>(ku)) * mu);
436 const T Gamma_a = ca / lambda;
437 const T two = num_traits<T>::from_int(2);
438 const T Gamma_s =
439 qsys::qsys_gigk_rqt_gamma(rho1, mu, Gamma_a, T(cs / mu), ku, two);
440 R = qsys::qsys_gigk_rqt(lambda, mu, Gamma_a, Gamma_s, ku, two, two).W;
441 }
442 else if (method == "qed") R = T(qsys::qsys_mmk_qed(lambda, mu, ku).meanWait + one / mu);
443 // The upper end, gig1.kingman already reporting a bound.
444 else if (method == "gig1.extremal")
445 R = T(qsys::qsys_gig1_bnds_extremal(lambda, mu, ca, cs).upperBound + one / mu);
446 else if (method == "gm1" || method == "gim1") {
447 // The PH path is exact only when sn.proc holds the arrival law ITSELF.
448 // For a non-Markovian arrival the refresh substitutes an Erlang-n SCV
449 // fit, so the PH sigma-root would answer a different model; those cases
450 // take the transform's sigma-root instead, as the reference does.
451 const lang::Distrib<T>& arv = L.service[src - 1][0];
452 const bool markovian = arv.has_map();
453 bool done = false;
454 if (markovian) {
455 const mam::Map<T> m = lang::dist_to_map(arv);
456 const std::vector<T> pie = mam::map_pie(m);
457 R = qsys::qsys_phm1(pie, m.D0, mu, num_traits<T>::from_double(1e-16)).meanSojournTime;
458 done = true;
459 }
460 if (!done) {
461 // sigma solves A*(mu - mu sigma) = sigma on (0,1)
462 //
463 // THE UPPER BRACKET CANNOT SIT AT 1. sigma = 1 is ALWAYS a root of
464 // this equation, and near it the transform is evaluated at s = mu(1-x)
465 // -> 0, where A*(s) is a difference of two exponentials that both
466 // tend to 1: at x = 1-1e-12 the cancellation leaves A* correct only to
467 // about 1e-4, so f came out POSITIVE at both ends and Brent refused
468 // the bracket outright ("the bracket endpoints do not straddle a
469 // root"), which killed SolverMVA on every G/M/1 with a non-Markovian
470 // arrival -- gallery_um1 (Uniform(1,2) -> Exp(2)) among them. f(0) is
471 // A*(mu) > 0 always, so only the upper end has to be found: walk it
472 // toward 1 until the sign turns, exactly as far as the sought root
473 // requires and no further. The reference sidesteps the same trap by
474 // seeding an UNBRACKETED fzero at 0.5 (solver_mva_qsys_analyzer.m).
475 auto f = [&](const T& x) { return T(lang::dist_lst(arv, T(mu - mu * x)) - x); };
476 double hi = 0.5;
477 bool straddles = false;
478 for (int i = 0; i < 8; ++i) {
480 straddles = true;
481 break;
482 }
483 hi = 1.0 - (1.0 - hi) / 10.0;
484 }
485 const RootResult<T> rr =
486 straddles ? root_brent(f, num_traits<T>::from_double(1e-12),
489 : RootResult<T>();
490 if (straddles && rr.converged) {
491 R = qsys::qsys_gm1(rr.root, mu);
492 } else {
493 R = qsys::qsys_gg1(lambda, mu, T(ca * ca), one).W;
494 }
495 }
496 } else {
497 throw UnsupportedError("solver_mva_qsys_analyzer: method '" + method +
498 "' is not available for a model with one station and one class");
499 }
500
502 MvaSolution<T>& s = out.sol;
503 s.Q = Matrix<T>(M, 1, zero);
504 s.U = Matrix<T>(M, 1, zero);
505 s.R = Matrix<T>(M, 1, zero);
506 s.Tp = Matrix<T>(M, 1, zero);
507 s.X.assign(1, zero);
508 s.C.assign(1, zero);
509 s.method = opt.method;
510 s.iter = 1;
511 // Per-visit response time and queue length at the queue; per-job cycle time
512 // and system throughput. For a feedback model (Vq > 1) the two differ.
513 s.R(q - 1, 0) = R;
514 s.C[0] = T(R * Vq);
515 s.X[0] = srcRate;
516 s.U(q - 1, 0) = T(lambda / mu / num_traits<T>::from_double(std::isfinite(k) ? k : 1.0));
517 s.Tp(src - 1, 0) = srcRate;
518 s.Tp(q - 1, 0) = lambda;
519 s.Q(q - 1, 0) = T(lambda * R);
520 out.actualmethod = method;
521 return out;
522 } // if constexpr has_transcendental
523}
524
525/**
526 * Port of `solver_mva_qsys_prio_analyzer.m`: the exact Cobham formula for a
527 * single open M/G/1 queue with non-preemptive priorities.
528 *
529 * The classes are handed to `qsys_mg1_prio` in PRIORITY order (lowest value
530 * first), which is the order the formula's nested sums assume.
531 */
532template <class T>
534 const MvaOptions& opt) {
535 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
536 const std::size_t M = L.nstations, K = L.nclasses;
537 const std::size_t src = detail::station_of_type(L, qn::NodeType::Source);
538 const std::size_t q = detail::station_of_type(L, qn::NodeType::Queue);
539
540 std::vector<std::size_t> order(K);
541 for (std::size_t r = 0; r < K; ++r) order[r] = r;
542 std::stable_sort(order.begin(), order.end(), [&](std::size_t a, std::size_t b) {
543 return L.classes[a].prio < L.classes[b].prio;
544 });
545
546 std::vector<T> lam(K, zero), mu(K, zero), cs(K, one);
547 for (std::size_t j = 0; j < K; ++j) {
548 const std::size_t r = order[j];
549 lam[j] = L.rates(src - 1, r);
550 mu[j] = L.rates(q - 1, r);
551 const double v = num_traits<T>::to_double(L.scv(q - 1, r));
552 cs[j] = (std::isfinite(v) && v > 0.0) ? num_traits<T>::from_double(std::sqrt(v)) : one;
553 }
554 const std::vector<T> W = qsys::qsys_mg1_prio(lam, mu, cs).W;
555
557 MvaSolution<T>& s = out.sol;
558 s.Q = Matrix<T>(M, K, zero);
559 s.U = Matrix<T>(M, K, zero);
560 s.R = Matrix<T>(M, K, zero);
561 s.Tp = Matrix<T>(M, K, zero);
562 s.C.assign(K, zero);
563 s.X.assign(K, zero);
564 s.method = opt.method;
565 s.iter = 0;
566 for (std::size_t j = 0; j < K; ++j) {
567 const std::size_t r = order[j];
568 if (!(lam[j] > zero) || !std::isfinite(num_traits<T>::to_double(mu[j]))) continue;
569 s.R(q - 1, r) = W[j];
570 s.C[r] = W[j];
571 s.X[r] = lam[j];
572 s.U(q - 1, r) = T(lam[j] / mu[j]);
573 s.Tp(q - 1, r) = lam[j];
574 s.Tp(src - 1, r) = lam[j];
575 s.Q(q - 1, r) = T(lam[j] * W[j]);
576 }
577 out.actualmethod = "mg1.prio";
578 return out;
579}
580
581/**
582 * The exact multiclass M/M/1-DPS solve the reference inlines in mvaDispatch:
583 * the truncated multiclass chain of `qsys_mm1_dps`, which the AMVA cross-term
584 * correction cannot reproduce (it violates the equal-rate conservation law).
585 */
586template <class T>
588 // The truncated-chain M/M/1-DPS closed form (qsys_mm1_dps) needs
589 // transcendental arithmetic; refuse by name under exact/Rational.
590 if constexpr (!num_traits<T>::has_transcendental) {
591 throw UnsupportedError(
592 "solver_mva_dps_exact: the exact DPS closed form needs transcendental arithmetic; "
593 "rerun this model with --arith double or --arith real");
594 } else {
595 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
596 const std::size_t M = L.nstations, K = L.nclasses;
597 const std::size_t src = detail::station_of_type(L, qn::NodeType::Source);
598 const std::size_t q = detail::station_of_type(L, qn::NodeType::Queue);
599
600 std::vector<T> lam(K, zero), mu(K, zero), w(K, one);
601 for (std::size_t r = 0; r < K; ++r) {
602 lam[r] = L.rates(src - 1, r);
603 mu[r] = L.rates(q - 1, r);
604 const std::vector<T>& sp = L.stations[q - 1].schedparam;
605 if (sp.size() > r && sp[r] > zero) w[r] = sp[r];
606 }
607 const std::vector<T> Wd = qsys::qsys_mm1_dps(lam, mu, w).T_;
608
610 MvaSolution<T>& s = out.sol;
611 s.Q = Matrix<T>(M, K, zero);
612 s.U = Matrix<T>(M, K, zero);
613 s.R = Matrix<T>(M, K, zero);
614 s.Tp = Matrix<T>(M, K, zero);
615 s.C.assign(K, zero);
616 s.X.assign(K, zero);
617 s.method = opt.method;
618 s.iter = 0;
619 for (std::size_t r = 0; r < K; ++r) {
620 s.R(q - 1, r) = Wd[r];
621 s.C[r] = Wd[r];
622 s.X[r] = lam[r];
623 if (mu[r] > zero) s.U(q - 1, r) = T(lam[r] / mu[r]);
624 s.Tp(q - 1, r) = lam[r];
625 s.Tp(src - 1, r) = lam[r];
626 s.Q(q - 1, r) = T(lam[r] * Wd[r]);
627 }
628 out.actualmethod = "mm1.dps";
629 return out;
630 } // if constexpr has_transcendental
631}
632
633/**
634 * Port of `solver_mvald_analyzer.m`: the load-dependent branch.
635 *
636 * `exact` and `mva` take the exact load-dependent recursion; `default` takes it
637 * too on a small closed product-form model, mirroring the default-to-exact
638 * upgrade the non-LD analyzer applies; everything else goes to the AMVA, whose
639 * lld and cd terms are now in place.
640 */
641template <class T>
643 const Matrix<T>& init_sol) {
644 const std::string method = amva_method_alias(opt.method);
645 const bool has_cd = [&] {
646 for (const auto& st : L.stations)
647 if (st.cdscaling) return true;
648 return false;
649 }();
650
651 if (method == "exact" || method == "mva") {
652 if (has_cd)
653 throw UnsupportedError(
654 "solver_mvald_analyzer: there is no exact class-dependent solver in MVA");
656 out.sol = solver_mvald(L, opt);
657 out.actualmethod = "exact";
658 return out;
659 }
660 if (!(method == "default" || method == "amva" || method == "qd" || method == "lin" ||
661 method == "qdlin"))
662 throw UnsupportedError("solver_mvald_analyzer: the '" + method +
663 "' method is not supported by the load-dependent MVA solver");
664
665 if (method == "default" && !has_cd) {
666 double Nsum = 0.0;
667 bool integral = true, finite = true;
668 for (const qn::JobClass& c : L.classes) {
669 if (!std::isfinite(c.population)) finite = false;
670 else {
671 Nsum += c.population;
672 if (c.population != std::floor(c.population)) integral = false;
673 }
674 }
675 if (finite && integral && L.nchains <= 4 && Nsum <= 20.0 && L.has_product_form()) {
677 out.sol = solver_mvald(L, opt);
678 out.actualmethod = "exact";
679 return out;
680 }
681 }
682 bool converged = true;
684 MvaOptions o = opt;
686 out.sol = solver_amva(L, d, o, init_sol, converged);
687 // solver_amva computes the outer residual and reports it here; dropping the
688 // flag on the floor left every caller with only `iter`, which on this route
689 // aggregates the nested sweeps and cannot decide convergence.
690 out.sol.converged = converged;
691 out.actualmethod = out.sol.method;
692 return out;
693}
694
695/**
696 * Port of `solver_mva_qsys_sizebased_analyzer.m`: the M/G/1 formulas for the
697 * size-based disciplines (Wierman and Harchol-Balter, SIGMETRICS 2003).
698 *
699 * The arrival rate of each class is the source rate times the queue's VISIT
700 * ratio, and the response time comes back per visit, so both are multiplied
701 * out the same way the reference does.
702 *
703 * PER-VISIT VERSUS PER-JOB. `W` comes back per visit, so the response time is
704 * `W * visits` while the queue length stays `lambda * W` with lambda already
705 * carrying the visit ratio. That makes `Q != T * R` at the station whenever the
706 * visit ratio differs from one: the reference inflates R by the visits but not
707 * Q, so Little's law fails on a feedback routing. It is a defect of the
708 * reference, reproduced here rather than silently corrected -- this branch is
709 * only ever dispatched on a plain Source-Queue-Sink, where the visit ratio is
710 * one and the discrepancy cannot arise.
711 */
712template <class T>
714 const MvaOptions& opt) {
715 // The M/G/1 size-based closed forms (SRPT/PSJF/FB/LRPT/SETF) evaluate
716 // transcendentals; refuse by name under exact/Rational.
717 if constexpr (!num_traits<T>::has_transcendental) {
718 throw UnsupportedError(
719 "solver_mva_qsys_sizebased_analyzer: the M/G/1 size-based closed forms need "
720 "transcendental arithmetic; rerun this model with --arith double or --arith real");
721 } else {
722 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
723 const std::size_t M = L.nstations, K = L.nclasses;
724 const std::size_t src = detail::station_of_type(L, qn::NodeType::Source);
725 const std::size_t q = detail::station_of_type(L, qn::NodeType::Queue);
726 const std::size_t qstateful = L.stateful_of_station(q);
727
728 // sn.visits is indexed by CHAIN. The reference wrote `sn.visits{source_ist}`
729 // -- the chain whose number happens to equal the Source's STATION index --
730 // so every class outside chain 1 read a visit of 0 and the analyzer rejected
731 // its own multiclass model. Fixed in MATLAB in the same change; take the
732 // chain each class belongs to.
733 std::vector<std::size_t> chain_of(K, 0);
734 for (std::size_t c = 0; c < L.nchains; ++c)
735 for (std::size_t r : L.inchain[c]) chain_of[r - 1] = c;
736
737 std::vector<T> lambda(K, zero), mu(K, zero), cs(K, one), vis(K, one);
738 for (std::size_t r = 0; r < K; ++r) {
739 vis[r] = L.visits[chain_of[r]](qstateful - 1, r);
740 lambda[r] = T(L.rates(src - 1, r) * vis[r]);
741 mu[r] = L.rates(q - 1, r);
742 const double v = num_traits<T>::to_double(L.scv(q - 1, r));
743 cs[r] = (std::isfinite(v) && v > 0.0) ? num_traits<T>::from_double(std::sqrt(v)) : one;
744 if (!(lambda[r] > zero) || !(mu[r] > zero))
745 throw InputError(
746 "solver_mva_qsys_sizebased_analyzer: the arrival and service rates must be "
747 "positive");
748 }
749
750 const qn::SchedStrategy sched = L.stations[q - 1].sched;
751 std::vector<T> W;
752 std::string actual;
753 switch (sched) {
754 case qn::SchedStrategy::SRPT:
755 W = qsys::qsys_mg1_srpt(lambda, mu, cs).W;
756 actual = "mg1.srpt";
757 break;
758 case qn::SchedStrategy::PSJF:
759 W = qsys::qsys_mg1_psjf(lambda, mu, cs).W;
760 actual = "mg1.psjf";
761 break;
762 case qn::SchedStrategy::FB:
763 W = qsys::qsys_mg1_fb(lambda, mu, cs).W;
764 actual = "mg1.fb";
765 break;
766 case qn::SchedStrategy::LRPT:
767 W = qsys::qsys_mg1_lrpt(lambda, mu, cs).W;
768 actual = "mg1.lrpt";
769 break;
770 case qn::SchedStrategy::SETF:
771 W = qsys::qsys_mg1_setf(lambda, mu, cs).W;
772 actual = "mg1.setf";
773 break;
774 default:
775 throw UnsupportedError(std::string("solver_mva_qsys_sizebased_analyzer: ") +
776 lang::sched_to_text(sched) + " is not a size-based discipline");
777 }
778
780 MvaSolution<T>& s = out.sol;
781 s.Q = Matrix<T>(M, K, zero);
782 s.U = Matrix<T>(M, K, zero);
783 s.R = Matrix<T>(M, K, zero);
784 s.Tp = Matrix<T>(M, K, zero);
785 s.C.assign(K, zero);
786 s.X.assign(K, zero);
787 s.method = opt.method;
788 s.iter = 1;
789 for (std::size_t r = 0; r < K; ++r) {
790 s.R(q - 1, r) = T(W[r] * vis[r]);
791 s.C[r] = s.R(q - 1, r);
792 s.X[r] = lambda[r];
793 s.U(q - 1, r) = T(lambda[r] / mu[r]);
794 s.Tp(q - 1, r) = lambda[r];
795 s.Tp(src - 1, r) = lambda[r];
796 s.Q(q - 1, r) = T(lambda[r] * W[r]);
797 }
798 out.actualmethod = actual;
799 return out;
800 } // if constexpr has_transcendental
801}
802
803/**
804 * Port of `solver_mva_marie_analyzer.m`: Marie's iterative
805 * aggregation-decomposition for a CLOSED network with non-exponential FCFS
806 * service.
807 *
808 * Only FCFS is service-time sensitive; PS and LCFSPR are insensitive
809 * (product form) and are handed an SCV of one, which is what makes the
810 * decomposition exact for them. Delay stations fold into the per-chain think
811 * time rather than entering the isolation, and multiserver isolation is
812 * single-chain only -- all three restrictions are the reference's, and each is
813 * refused by name rather than approximated.
814 */
815template <class T>
817 // Marie's decomposition fits a Coxian per isolated station (square roots) and
818 // stops on a tolerance; refuse by name under exact/Rational.
819 if constexpr (!num_traits<T>::has_transcendental) {
820 throw UnsupportedError(
821 "solver_mva_marie_analyzer: Marie's aggregation-decomposition needs transcendental "
822 "arithmetic; rerun this model with --arith double or --arith real");
823 } else {
824 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
826 const std::size_t M = L.nstations, C = L.nchains;
827
828 for (std::size_t c = 0; c < C; ++c)
829 if (!std::isfinite(d.Nchain[c]))
830 throw UnsupportedError(
831 "solver_mva_marie_analyzer: the 'marie' method supports closed models only; this "
832 "model has open classes");
833 for (const auto& nd : L.nodes)
834 if (nd.nodetype == qn::NodeType::Source)
835 throw UnsupportedError(
836 "solver_mva_marie_analyzer: the 'marie' method supports closed models only; this "
837 "model has a Source");
838
839 std::vector<std::size_t> qrows, drows;
840 for (std::size_t i = 0; i < M; ++i) {
841 const qn::SchedStrategy sc = L.stations[i].sched;
842 const bool delay = std::isinf(L.stations[i].nservers) || sc == qn::SchedStrategy::INF;
843 if (delay) {
844 drows.push_back(i);
845 continue;
846 }
847 if (!(sc == qn::SchedStrategy::FCFS || sc == qn::SchedStrategy::PS ||
848 sc == qn::SchedStrategy::LCFSPR))
849 throw UnsupportedError(std::string("solver_mva_marie_analyzer: the 'marie' method "
850 "supports FCFS, PS, LCFSPR and Delay stations "
851 "only; station '") +
852 L.stations[i].name + "' is " + lang::sched_to_text(sc));
853 qrows.push_back(i);
854 }
855
856 std::vector<T> Z(C, zero);
857 for (std::size_t c = 0; c < C; ++c)
858 for (std::size_t i : drows) Z[c] += d.Lchain(i, c);
859
860 const std::size_t Mq = qrows.size();
861 Matrix<T> Lq(Mq, C, zero), SCV(Mq, C, one);
862 std::vector<int> ns(Mq, 1);
863 bool multiserver = false;
864 for (std::size_t a = 0; a < Mq; ++a) {
865 const std::size_t i = qrows[a];
866 const double srv = L.stations[i].nservers;
867 ns[a] = std::isfinite(srv) ? static_cast<int>(std::llround(srv)) : 1;
868 if (ns[a] > 1) multiserver = true;
869 for (std::size_t c = 0; c < C; ++c) {
870 Lq(a, c) = d.Lchain(i, c);
871 if (L.stations[i].sched != qn::SchedStrategy::FCFS) continue;
872 const double v = num_traits<T>::to_double(d.SCVchain(i, c));
873 if (std::isfinite(v) && v > 0.0) SCV(a, c) = num_traits<T>::from_double(v);
874 }
875 }
876 if (C > 1 && multiserver)
877 throw UnsupportedError(
878 "solver_mva_marie_analyzer: the 'marie' method supports multiserver stations for "
879 "single-chain models only");
880
881 std::vector<int> N(C, 0);
882 for (std::size_t c = 0; c < C; ++c) N[c] = static_cast<int>(std::llround(d.Nchain[c]));
884 if (Mq == 0) {
885 // Nothing to isolate: with every station an infinite server the
886 // aggregation-decomposition degenerates to the exact delay solution
887 // X_c = N_c / Z_c, and pfqn_marie would be handed a zero-row demand matrix.
888 mr.X.assign(C, zero);
889 mr.Q = Matrix<T>(0, C, zero);
890 mr.U = Matrix<T>(0, C, zero);
891 mr.it = 1;
892 for (std::size_t c = 0; c < C; ++c)
893 if (Z[c] > zero) mr.X[c] = T(num_traits<T>::from_double(d.Nchain[c]) / Z[c]);
894 } else {
895 mr = C == 1 ? pfqn::pfqn_marie(Lq, N, Z, SCV, 1e-8, 1000, ns)
896 : pfqn::pfqn_marie(Lq, N, Z, SCV, 1e-8, 1000, std::vector<int>());
897 }
898
899 Matrix<T> Qchain(M, C, zero), Uchain(M, C, zero), Rchain(M, C, zero), Tchain(M, C, zero);
900 for (std::size_t a = 0; a < Mq; ++a)
901 for (std::size_t c = 0; c < C; ++c) {
902 Qchain(qrows[a], c) = mr.Q(a, c);
903 Uchain(qrows[a], c) = mr.U(a, c);
904 }
905 for (std::size_t i = 0; i < M; ++i)
906 for (std::size_t c = 0; c < C; ++c) Tchain(i, c) = T(mr.X[c] * d.Vchain(i, c));
907 for (std::size_t i = 0; i < M; ++i)
908 for (std::size_t c = 0; c < C; ++c)
909 if (Tchain(i, c) > zero) Rchain(i, c) = T(Qchain(i, c) / Tchain(i, c));
910 // A delay station holds T S jobs, all of them in service.
911 for (std::size_t i : drows)
912 for (std::size_t c = 0; c < C; ++c) {
913 Qchain(i, c) = T(Tchain(i, c) * d.STchain(i, c));
914 Uchain(i, c) = Qchain(i, c);
915 Rchain(i, c) = d.STchain(i, c);
916 }
917
918 const ClassResults<T> cr =
919 sn_deaggregate_chain_results(L, d, Qchain, Uchain, Rchain, Tchain, mr.X);
921 out.sol.Q = cr.Q;
922 out.sol.U = cr.U;
923 out.sol.R = cr.R;
924 out.sol.Tp = cr.Tp;
925 out.sol.C = cr.C;
926 out.sol.X = cr.X;
927 out.sol.method = opt.method;
928 out.sol.iter = mr.it;
929 out.actualmethod = "marie";
930 return out;
931 } // if constexpr has_transcendental
932}
933
934/**
935 * The ladder itself. `init_sol` is the warm start the outer solver carries
936 * across iterations; it reaches the AMVA paths only, as it does in the
937 * reference.
938 */
939template <class T>
941 const Matrix<T>& init_sol) {
942 using qn::NodeType;
943 using qn::SchedStrategy;
944 const std::string method = amva_method_alias(opt.method);
945
946 // 0. closed models with a shortest-job-next station. This sits ABOVE every
947 // other branch because the reference tests hasSJN first and the branches
948 // are not disjoint: an SJF station in a closed model with a delay would
949 // otherwise reach the generic AMVA path, which reads only the mean service
950 // time and would report the station as if it scheduled size-blind. The
951 // OPEN case has no population to recur over and is refused by name rather
952 // than solved as though the discipline did nothing.
953 if (sn_has_sjn(L)) {
954 bool open = false;
955 for (const auto& c : L.classes)
956 if (std::isinf(c.population)) open = true;
957 if (open)
958 throw UnsupportedError(
959 "SolverMVA supports shortest-job-next (SJF) scheduling only in closed models, the "
960 "conditional waiting time equation being a population recursion. Use SolverLDES, "
961 "or SolverMVA with SRPT or PSJF for the preemptive size-based open queue");
964 sout.sol = sr.sol;
965 sout.actualmethod = sr.actualmethod;
966 sout.warning = sr.warning;
967 return sout;
968 }
969
970 // 1. order-independent / pass-and-swap stations. Detection is
971 // `nc_is_oi_model`'s: PAS or OI scheduling, a rate function, and an
972 // ALL-ZERO swap graph. A station with those disciplines that does not
973 // qualify is a genuine pass-and-swap station, which is not product-form
974 // and has no exact mean-value analyzer, so it is refused by name rather
975 // than handed to an AMVA that cannot represent its rate function at all.
976 // The gate is the reference's, and all three conjuncts matter: an OI
977 // station must be present, the WHOLE model must be order-independent
978 // (nc_is_oi_model: closed, and every other station product-form), and the
979 // method must be `default` or `exact`. MVA reaches OI/PAS ONLY through the
980 // exact analyzer; any other method would fall through to an AMVA that only
981 // ever sees the single-job rates and would silently report a zero queue
982 // length at the OI station.
983 {
984 const bool has_oi_station = std::any_of(
985 L.stations.begin(), L.stations.end(), [](const qn::Station<T>& st) {
986 return st.sched == SchedStrategy::OI || st.sched == SchedStrategy::PAS;
987 });
988 const bool exact_method = (opt.method == "default" || opt.method == "exact");
989 if (!find_oi_stations(L).empty() && nc_is_oi_model(L) && exact_method) {
991 oout.sol = solver_mva_oi_analyzer(L, opt);
992 oout.actualmethod = "oi";
993 return oout;
994 }
995 if (has_oi_station)
996 throw UnsupportedError(
997 "SolverMVA supports order-independent (OI) and pass-and-swap (PAS) stations only "
998 "through its exact order-independent analyzer, which requires method 'default' or "
999 "'exact' (got '" + opt.method +
1000 "'), an empty/zero swap graph at every OI/PAS station, a closed model, and every "
1001 "other station to be product-form (INF, PS, LCFS-PR, SIRO, or "
1002 "class-independent-rate FCFS). Use SolverCTMC or SolverLDES for this model");
1003 }
1004
1005 // 2. delayed-hit retrieval caches (Cache.setRetrievalSystem). The two
1006 // variants are DIFFERENT METHODS, not one method on two topologies, which
1007 // is why the Source test decides between them rather than parameterizing
1008 // one call. The OPEN model has no population to recur over, so the hit /
1009 // miss / delayed-hit split comes from the open fixed point
1010 // (`retrieval_fpi`) and all three are reported. The CLOSED model has no
1011 // exogenous arrival rate to run that fixed point on, so the split emerges
1012 // from a decomposition-aggregation sweep instead, and the delayed-hit
1013 // fraction folds into the miss (see solver_mva_cacheqn_retrieval.h).
1014 for (const auto& kv : L.nodeparam) {
1015 if (kv.second.retrieval_capacity <= 0) continue;
1016 DispatchResult<T> rout;
1017 if (detail::station_of_type(L, NodeType::Source) == 0) {
1019 rout.sol = cr.sol;
1021 cr.latency, cr.hitproblist, Matrix<T>(),
1022 std::vector<T>());
1023 } else {
1025 rout.sol = solver_mva_retrieval_analyzer(L, opt, &co);
1027 co.latency, co.hitproblist, co.itemprob,
1028 std::vector<T>());
1029 }
1030 rout.actualmethod = "fpi";
1031 return rout;
1032 }
1033
1034 const bool sqs = detail::is_open_sqs(L);
1035 const std::size_t qst = detail::station_of_type(L, NodeType::Queue);
1036 const SchedStrategy qsched = qst ? L.stations[qst - 1].sched : SchedStrategy::NONE;
1037 const std::size_t srcst = detail::station_of_type(L, NodeType::Source);
1038
1039 // 8. non-reentrant cache: a Source-Cache-Sink model
1040 if (detail::is_open_scs(L)) {
1041 DispatchResult<T> cout;
1043 cout.sol = cr.sol;
1044 cout.cache = solvers::cache_metrics_of(L, cr.hitprob, cr.missprob, std::vector<T>(),
1045 std::vector<T>(), cr.hitproblist, cr.itemprob,
1046 std::vector<T>());
1047 cout.actualmethod = cr.actualmethod;
1048 return cout;
1049 }
1050
1051 // 9. any OTHER cache -- one embedded in a queueing network -- is the
1052 // integrated caching-queueing analyzer (decomposition-aggregation).
1053 if (!L.nodeparam.empty()) {
1054 DispatchResult<T> qout;
1055 std::shared_ptr<qn::NetworkStruct<T>> refreshed(new qn::NetworkStruct<T>());
1057 qout.sol = solver_mva_cacheqn_analyzer(L, opt, refreshed.get(), &co);
1058 qout.refreshed_struct = refreshed;
1059 // The integrated branch reports the split per (cache, class) and no
1060 // delayed-hit fraction; the per-item law rides in separately because
1061 // only this branch has one per cache rather than one per model.
1063 for (std::size_t ci = 0; ci < qout.cache.caches.size() && ci < co.itemprob.size(); ++ci)
1064 qout.cache.caches[ci].itemprob = co.itemprob[ci];
1065 qout.actualmethod = (opt.method == "exact") ? "exact" : "default";
1066 return qout;
1067 }
1068
1069 // 3. size-based scheduling
1070 if (sqs && detail::is_size_based(qsched)) return solver_mva_qsys_sizebased_analyzer(L, opt);
1071
1072 // 4. single-class open Source-Queue-Sink.
1073 //
1074 // CLAIMED ONLY FOR THE NAMES THE ANALYZER ANSWERS, `detail::qsys_serves_method`.
1075 // Anything else -- 'qna', 'mva', 'amva', the linearizers, the qd family,
1076 // sum/esum -- falls through to the network branches below and is solved
1077 // there, as it is on every larger open network; this branch used to claim
1078 // the model for them and then refuse the method by name. 'rqna' and 'rqt'
1079 // are served here: the analyzer answers them with single-queue robust
1080 // formulas of their own.
1081 //
1082 // THE M/M/1/K LOSS SHAPE IS THE EXCEPTION and is claimed whatever the name.
1083 // Its branch is chosen by the SHAPE and not by the method, and the capacity
1084 // gate in the runner exempts exactly this shape for every method but
1085 // 'exact', so the two have to agree on which models come here.
1086 if (sqs && L.nclasses == 1 && (detail::qsys_serves_method(method) || sn_is_mm1k_loss(L)))
1087 return solver_mva_qsys_analyzer(L, opt);
1088
1089 // 5. multiclass open polling
1090 if (sqs && L.nclasses > 1 && qsched == SchedStrategy::POLLING) {
1091 DispatchResult<T> pout;
1093 pout.actualmethod = "stationtime";
1094 return pout;
1095 }
1096
1097 // 6. multiclass open HOL priority, single server, Poisson arrivals
1098 if (sqs && L.nclasses > 1 && qsched == SchedStrategy::HOL &&
1099 L.stations[qst - 1].nservers == 1.0 && detail::row_is_exponential(L, srcst))
1101
1102 // 7. multiclass open DPS, at most three classes, exponential everywhere
1103 if (sqs && L.nclasses > 1 && L.nclasses <= 3 && qsched == SchedStrategy::DPS &&
1104 L.stations[qst - 1].nservers == 1.0 && detail::row_is_exponential(L, srcst) &&
1105 detail::row_is_exponential(L, qst))
1106 return solver_mva_dps_exact(L, opt);
1107
1108 // 10. the bound family, which the reference moved to SolverBA
1109 static const char* kBounds[] = {"aba.upper", "aba.lower", "bjb.upper", "bjb.lower",
1110 "pb.upper", "pb.lower", "gb.upper", "gb.lower",
1111 "sb.upper", "sb.lower", "mwba.upper", "mwba.lower"};
1112 for (const char* b : kBounds)
1113 if (method == b)
1114 throw UnsupportedError("SolverMVA: bound methods have moved to SolverBA; use "
1115 "SolverBA with method '" +
1116 method + "'");
1117
1118 // 11. Marie's aggregation-decomposition
1119 if (method == "marie") return solver_mva_marie_analyzer(L, opt);
1120
1121 // 12. load- or class-dependent scaling
1122 bool has_scaling = false;
1123 for (const auto& st : L.stations)
1124 if (!st.lldscaling.empty() || st.cdscaling) has_scaling = true;
1125 if (has_scaling) return solver_mvald_analyzer(L, opt, init_sol);
1126
1127 // 13. an ordinary queueing network. MVAC is a case of the analyzer's method
1128 // switch (solver_mva_analyzer.m:21-23) and so belongs BELOW branch 12: a
1129 // load-dependent model reaches solver_mvald_analyzer in the reference and
1130 // never sees the 'mvac' case, which is why this is not tested higher up.
1131 if (method == "mvac") {
1132 DispatchResult<T> mout;
1133 mout.sol = solver_mvac_analyzer(L, opt);
1134 mout.actualmethod = "mvac";
1135 return mout;
1136 }
1137 // QNA is dispatched here rather than in solver_mva_analyzer only because
1138 // solver_qna.h cannot include solver_mva.h without a cycle; the reference
1139 // reaches it from the analyzer's method table, and no branch above claims
1140 // 'qna', so the model it sees is the same.
1141 if (method == "qna") {
1142 DispatchResult<T> qout;
1143 qout.sol = solver_qna(L, opt);
1144 qout.actualmethod = "qna";
1145 return qout;
1146 }
1147 // RQNA (robust queueing-network analyzer) is likewise reached by method name,
1148 // and by the resolveMethod default->rqna upgrade for a bursty single-class
1149 // open network; dispatched here for the same include-cycle reason as qna.
1150 if (method == "rqna") {
1151 DispatchResult<T> rout;
1152 rout.sol = solver_rqna(L, opt);
1153 rout.actualmethod = "rqna";
1154 return rout;
1155 }
1156 // RQT (robust queueing theory) is reached by method name only; its
1157 // uncertainty-set decomposition has no default-dispatch upgrade.
1158 if (method == "rqt") {
1159 DispatchResult<T> tout;
1160 tout.sol = solver_rqt(L, opt);
1161 tout.actualmethod = "rqt";
1162 return tout;
1163 }
1164 // The horizontal-cut MVA for one exponential delay and one FCFS MAP queue
1165 // (mapqn_amva), reached by name; its shape gate is mva_mapqn_reason.
1166 if (method == "amva.mapqn" || method == "mapqn") {
1167 DispatchResult<T> mout;
1168 mout.sol = solver_mapqn(L, opt);
1169 mout.actualmethod = "amva.mapqn";
1170 return mout;
1171 }
1172 // bursty single-class open network: 'default' upgrades to RQNA, exactly as
1173 // the terminal branch of solver_mva_analyzer.m does. resolveMethod computes
1174 // this only for the feature gate and never writes it back to options, so the
1175 // substitution belongs HERE, after the structural special cases -- a 3-node
1176 // MAP/M/1 is a G/M/1 the qsys analyzer (branch 4) already solved and must
1177 // keep method='default'. A fork-transformed model is exempt: it is forced to
1178 // amva just below.
1179 if (method == "default" && !opt.base_has_fork && !L.has_fork() && L.nclasses == 1) {
1180 bool all_open = true;
1181 for (std::size_t r = 0; r < L.nclasses; ++r)
1182 if (std::isfinite(L.classes[r].population)) all_open = false;
1183 if (all_open && api::sn_has_bursty_arrival(L)) {
1184 DispatchResult<T> rout;
1185 rout.sol = solver_rqna(L, opt);
1186 rout.actualmethod = "rqna";
1187 return rout;
1188 }
1189 }
1190
1191 MvaOptions o = opt;
1192 if ((opt.base_has_fork || L.has_fork()) && method == "default") {
1193 // The fork transform yields a mixed model with auxiliary near-zero-rate
1194 // open classes. The default ladder would send it to exact mixed MVA,
1195 // which degenerates to zero there; the approximation needs the AMVA.
1196 //
1197 // The test is on the BASE model, as the reference writes it: by the
1198 // time this runs the transform has already removed the fork, so
1199 // `L.has_fork()` alone is false on exactly the models the rule is for.
1200 o.method = "amva";
1201 }
1203 out.sol = solver_mva_analyzer(L, o, init_sol);
1204 out.actualmethod = out.sol.method;
1205 return out;
1206}
1207
1208} // namespace mva
1209} // namespace line
1210
1211#endif // LINE_SOLVERS_MVA_MVA_DISPATCH_H
What a solver observed about the Cache nodes of a model.
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::size_t nof_nodes() 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::map< std::size_t, CacheParam< T > > nodeparam
Cache parameters by 1-based NODE index; only Cache nodes have an entry.
std::vector< double > cap
sn.cap and sn.classcap: the total and per-class buffers.
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< NodeDef > nodes
every node, in creation order
std::vector< Matrix< T > > visits
(nchains) each (nstateful x nclasses)
double nclosedjobs() const
sn.nclosedjobs: the total population of the closed classes.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
Index of dispersion for counts (IDC) of a MAP at resolution t.
PatienceHandles< T > sn_patience_handles(const qn::NetworkStruct< T > &sn, std::size_t ist, std::size_t r)
Build the patience handles of station ist (0-based), class r.
bool sn_has_bursty_arrival(const qn::NetworkStruct< T > &sn)
mam::Map< T > dist_to_map(const Distrib< T > &d)
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
T dist_lst(const Distrib< T > &d, const T &s)
sn.lst: the Laplace-Stieltjes transform E[exp(-sX)].
const char * sched_to_text(SchedStrategy s)
Definition lang_types.h:230
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:324
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
DispatchResult< T > solver_mva_qsys_prio_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_qsys_prio_analyzer.m: the exact Cobham formula for a single open M/G/1 queue with ...
DispatchResult< T > solver_mva_qsys_sizebased_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_qsys_sizebased_analyzer.m: the M/G/1 formulas for the size-based disciplines (Wier...
MvaSolution< T > solver_mva_polling_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_polling_analyzer.m.
MvaSolution< T > solver_mva_cacheqn_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt, qn::NetworkStruct< T > *refreshed_out=nullptr, MvaCacheqnCacheOutputs< T > *cache_out=nullptr)
bool sn_has_sjn(const qn::NetworkStruct< T > &L)
True when the layer has an SJF station, the reference's any(sn.sched == SchedStrategy....
std::vector< std::size_t > find_oi_stations(const qn::NetworkStruct< T > &L)
The OI stations of a model, 1-based station indices.
bool sn_is_mm1k_loss(const qn::NetworkStruct< T > &L)
Port of matlab/src/api/sn/sn_is_mm1k_loss.m: a single-class open Source-Queue-Sink system whose queue...
MvaSolution< T > solver_rqna(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
SjnAnalyzerResult< T > solver_mva_sjn_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_sjn_analyzer.m.
std::string amva_method_alias(const std::string &m)
The amva.
DispatchResult< T > solver_mva_marie_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_marie_analyzer.m: Marie's iterative aggregation-decomposition for a CLOSED network...
CacheResult< T > solver_mva_cache_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_cache_analyzer.m for a Source-Cache-Sink model.
MvaSolution< T > solver_mvac_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mvac.m.
Definition solver_mvac.h:71
MvaSolution< T > solver_mapqn(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
MvaSolution< T > solver_qna(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_qna.m.
Definition solver_qna.h:76
DispatchResult< T > solver_mvald_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt, const Matrix< T > &init_sol)
Port of solver_mvald_analyzer.m: the load-dependent branch.
ClassResults< T > sn_deaggregate_chain_results(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, const Matrix< T > &Qchain, const Matrix< T > &Uchain, const Matrix< T > &Rchain, const Matrix< T > &Tchain, const std::vector< T > &Xchain)
Port of sn_deaggregate_chain_results.
Definition sn_chain.h:214
MvaSolution< T > solver_mvald(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mvald.m: exact MVA on a load-dependent model, through pfqn_mvaldmx.
MvaCacheqnRetrievalSolution< T > solver_mva_cacheqn_retrieval_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_cacheqn_retrieval_analyzer.m.
MvaSolution< T > solver_mva_oi_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_oi_analyzer.m.
MvaSolution< T > solver_rqt(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Definition solver_rqt.h:59
DispatchResult< T > solver_mva_qsys_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_qsys_analyzer.m: the closed forms for a single-class open Source-Queue-Sink model.
DispatchResult< T > solver_mva_dps_exact(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
The exact multiclass M/M/1-DPS solve the reference inlines in mvaDispatch: the truncated multiclass c...
MvaSolution< T > solver_amva(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, MvaOptions opt, const Matrix< T > &init_sol, bool &converged)
bool nc_is_oi_model(const qn::NetworkStruct< T > &L)
Port of nc_is_oi_model: whether the model as a WHOLE is order-independent, which is a strictly strong...
MvaSolution< T > solver_mva_retrieval_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt, MvaRetrievalCacheOutputs< T > *cache_out=nullptr)
MvaSolution< T > solver_mva_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt, const Matrix< T > &init_sol)
Port of solver_mva_analyzer.m, default and the explicit method names.
DispatchResult< T > mva_dispatch(const qn::NetworkStruct< T > &L, const MvaOptions &opt, const Matrix< T > &init_sol)
The ladder itself.
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
Definition sn_chain.h:63
MarieResult< T > pfqn_marie(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const Matrix< T > &scv, double tol, int maxiter, const std::vector< int > &nservers)
Marie's method for a closed network with FCFS Coxian service.
Definition pfqn_marie.h:647
GigkRqtResult< T > qsys_gigk_rqt(const T &lambda, const T &mu, const T &Gamma_a, const T &Gamma_s, std::size_t k, const T &alpha_a, const T &alpha_s)
Robust Queueing Theory (RQT) worst-case system time of a G/G/k FCFS queue.
QsysQedResult< T > qsys_mmk_qed(const T &lambda, const T &mu, unsigned s)
Halfin-Whitt QED approximation for the M/M/s queue, and the square-root staffing rule that inverts it...
QsysResult< T > qsys_gigk_approx_kingman(const T &lambda, const T &mu, const T &ca, const T &cs, unsigned k)
Kingman (Lee-Longton) scaling of the exact M/M/k waiting time.
QsysResult< T > qsys_mm1(const T &lambda, const T &mu)
Exact mean response time of the M/M/1 queue.
Definition qsys_mm1.h:35
Gig1ExtremalResult< T > qsys_gig1_bnds_extremal(const T &lambda, const T &mu, const T &ca, const T &cs, std::size_t K=4000, std::size_t N=2000, bool skipTight=false)
Extremal two-moment bounds for the GI/GI/1 queue.
QsysResult< T > qsys_gigk_approx(const T &lambda, const T &mu, const T &ca, const T &cs, unsigned k)
Default G/I/G/k approximation of the mean response time.
QsysGgnmResult< T > qsys_ggnm_diffusion(const T &lambda, const T &mu, unsigned n, double m, const T &ca, const T &cs, const std::function< T(const T &)> &serviceCcdf=std::function< T(const T &)>(), double tol=1e-12, std::size_t panels=4000)
Diffusion approximation for the G/GI/n/m queue.
Mg1DisciplineResult< T > qsys_mg1_srpt(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 under SRPT (shortest remaining processing time), by the Schrage-Miller formula.
QsysAbandonResult< T > qsys_erlanga(const T &lambda, const T &mu, const T &theta, unsigned s, double r=std::numeric_limits< double >::infinity(), const MgisrgiOptions &opts=MgisrgiOptions())
Exact analysis of the Erlang A model M/M/s/r+M.
T qsys_gm1(const T &sigma, const T &mu)
Exact mean response time of the G/M/1 queue.
Definition qsys_gm1.h:40
QsysResult< T > qsys_gig1_approx_gelenbe(const T &lambda, const T &mu, const T &ca, const T &cs)
Gelenbe diffusion approximation with instantaneous-return boundary.
QsysResult< T > qsys_gig1_approx_marchal(const T &lambda, const T &mu, const T &ca, const T &cs)
Marchal approximation of the mean response time of a G/I/G/1 queue.
Mg1DisciplineResult< T > qsys_mg1_fb(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 under FB (feedback), also called LAS (least attained service).
Definition qsys_mg1_fb.h:82
QsysAbandonResult< T > qsys_mgisrgi_whitt(const T &lambda, const T &mu, unsigned s, double r, const Patience< T > &patience, const MgisrgiOptions &opts=MgisrgiOptions())
Engineering solution of the call-center model M/GI/s/r+GI.
QsysResult< T > qsys_gigk_approx_whitt(const T &lambda, const T &mu, const T &ca, const T &cs, unsigned k)
Whitt (1993) approximation for the GI/G/k queue, eqs.
Mg1DisciplineResult< T > qsys_mg1_lrpt(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 under LRPT (longest remaining processing time).
PhM1Result< T > qsys_phm1(const std::vector< T > &alpha, const Matrix< T > &Tm, const T &mu, const T &tol)
Exact PH/M/1, the GI/M/1 queue with phase-type interarrival times.
Definition qsys_phm1.h:92
Mm1DpsResult< T > qsys_mm1_dps(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &w, const T &tol, unsigned maxCutoff)
Multiclass M/M/1 under DPS (discriminatory processor sharing), solved numerically on the truncated po...
QsysResult< T > qsys_mg1(const T &lambda, const T &mu, const T &cs)
Exact mean response time of the M/G/1 queue (Pollaczek-Khinchine).
Definition qsys_mg1.h:39
QsysResult< T > qsys_gig1_approx_kobayashi(const T &lambda, const T &mu, const T &ca, const T &cs)
Kobayashi diffusion approximation for the G/I/G/1 queue.
Mg1kLossMgsResult< T > qsys_mg1k_loss_mgs(const T &lambda, const T &mu, const T &mu_scv, unsigned K)
MacGregor Smith's closed-form approximation of the M/G/1/K loss probability.
T qsys_gigk_rqt_gamma(const T &rho, const T &mu, const T &Gamma_a, const T &sigma_s, std::size_t k, const T &alpha_a, const std::string &regime="independent")
Service variability parameter of the Robust Queueing Theory (RQT) framework.
QsysResult< T > qsys_gig1_approx_allencunneen(const T &lambda, const T &mu, const T &ca, const T &cs)
Allen-Cunneen approximation of the mean response time of a G/I/G/1 queue.
QsysResult< T > qsys_mmk(const T &lambda, const T &mu, unsigned k)
Exact mean response time of the M/M/k queue (Erlang-C).
Definition qsys_mmk.h:60
QsysResult< T > qsys_gig1_approx_heyman(const T &lambda, const T &mu, const T &ca, const T &cs)
Heyman approximation of the mean response time of a G/I/G/1 queue.
QsysResult< T > qsys_gg1(const T &lambda, const T &mu, const T &ca2, const T &cs2)
G/G/1 dispatcher: exact where a two-moment description determines the answer, Allen-Cunneen otherwise...
Definition qsys_gg1.h:118
QsysResult< T > qsys_gig1_approx_klb(const T &lambda, const T &mu, const T &ca, const T &cs)
Kraemer and Langenbach-Belz approximation for the G/I/G/1 queue.
Mg1PrioResult< T > qsys_mg1_prio(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 with non-preemptive head-of-line priorities: per-class mean response times from the Cobham/Klei...
Mg1DisciplineResult< T > qsys_mg1_psjf(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 under PSJF (preemptive shortest job first).
QsysResult< T > qsys_gig1_ubnd_kingman(const T &lambda, const T &mu, const T &ca, const T &cs)
Kingman upper bound on the mean waiting time of a G/G/1 queue.
Mg1DisciplineResult< T > qsys_mg1_setf(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 under SETF (shortest elapsed time first), the non-preemptive counterpart of FB/LAS.
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...
QsysResult< T > qsys_gig1_approx_kimura(const T &lambda, const T &mu, const T &ca, const T &cs)
Kimura diffusion-interpolation approximation for the G/I/G/1 queue.
CacheMetrics< T > cache_metrics_of(const qn::NetworkStruct< T > &sn, const std::vector< T > &hitprob, const std::vector< T > &missprob, const std::vector< T > &delayedprob, const std::vector< T > &latency, const Matrix< T > &hitproblist, const Matrix< T > &itemprob, const std::vector< T > &listcost)
Assemble CacheMetrics from what a cache analyzer returned.
CacheMetrics< T > cache_metrics_of_matrix(const qn::NetworkStruct< T > &sn, const Matrix< T > &hitprob, const Matrix< T > &missprob)
The same, for the integrated caching-queueing branch, whose hit and miss probabilities are (ncaches x...
RootResult< T > root_brent(F f, const T &a0, const T &b0, const T &tol, unsigned maxiter=200)
Brent's method on a bracket with a sign change.
Definition rootfind.h:130
A queueing network and its refreshed NetworkStruct.
Marie's iterative aggregation-decomposition for closed networks with FCFS general (Coxian) service.
G/G/1 dispatcher: exact where a two-moment description determines the answer, Allen-Cunneen otherwise...
Diffusion approximation for the G/GI/n/m queue.
Allen-Cunneen approximation of the mean response time of a G/I/G/1 queue.
Gelenbe diffusion approximation with instantaneous-return boundary.
Heyman approximation of the mean response time of a G/I/G/1 queue.
Kimura diffusion-interpolation approximation for the G/I/G/1 queue.
Kraemer and Langenbach-Belz approximation for the G/I/G/1 queue.
Kobayashi diffusion approximation for the G/I/G/1 queue.
Marchal approximation of the mean response time of a G/I/G/1 queue.
Extremal two-moment bounds for the GI/GI/1 queue.
Robust Queueing (RQ) approximation of a G/GI/1 queue characterized by its arrival index of dispersion...
Kingman upper bound on the mean waiting time of a G/G/1 queue.
Default G/I/G/k approximation of the mean response time.
Kingman (Lee-Longton) scaling of the exact M/M/k waiting time.
Whitt (1993) approximation for the GI/G/k queue, eqs.
Robust Queueing Theory (RQT) worst-case system time of a G/G/k FCFS queue.
Service variability parameter of the Robust Queueing Theory (RQT) framework.
Exact mean response time of the G/M/1 queue.
Exact mean response time of the M/G/1 queue (Pollaczek-Khinchine).
M/G/1 under FB (feedback), also called LAS (least attained service).
M/G/1 under LRPT (longest remaining processing time).
M/G/1 with non-preemptive head-of-line priorities: per-class mean response times from the Cobham/Klei...
M/G/1 under PSJF (preemptive shortest job first).
M/G/1 under SETF (shortest elapsed time first), the non-preemptive counterpart of FB/LAS.
M/G/1 under SRPT (shortest remaining processing time), by the Schrage-Miller formula.
MacGregor Smith's closed-form approximation of the M/G/1/K loss probability.
Engineering solution of the call-center model M/GI/s/r+GI.
Exact mean response time of the M/M/1 queue.
Multiclass M/M/1 under DPS (discriminatory processor sharing), solved numerically on the truncated po...
Exact mean response time of the M/M/k queue (Erlang-C).
Halfin-Whitt QED approximation for the M/M/s queue, and the square-root staffing rule that inverts it...
Exact PH/M/1, the GI/M/1 queue with phase-type interarrival times.
Deterministic scalar root finding.
Port of matlab/src/api/sn/sn_has_bursty_arrival.m.
Port of matlab/src/api/sn/sn_patience_handles.m.
SolverMVA method 'amva.mapqn': the horizontal-cut mean value analysis (mapqn_amva) of a closed multic...
SolverMVA over a SolverLN layer.
The non-reentrant cache analyzer: a Source-Cache-Sink model.
Integrated caching-queueing analyzer, a port of matlab/src/solvers/MVA/solver_mva_cacheqn_analyzer....
Port of solver_mva_cacheqn_retrieval_analyzer.m: a CLOSED integrated cache-queueing model whose Cache...
Exact mean-value analysis for networks with order-independent stations.
The multiclass open polling analyzer (ladder branch 5).
Delayed-hit (retrieval-system) cache analyzer, a port of matlab/src/solvers/MVA/solver_mva_retrieval_...
Closed networks with shortest-job-next (SJF) stations, ladder branch 0.
MVAC, exact mean value analysis BY CHAIN (Conway, de Souza e Silva and Lavenberg, IEEE Trans.
QNA, the two-moment open-network decomposition analyzer.
Robust Queueing Network Analyzer (RQNA), a port of matlab/src/solvers/MVA/solver_rqna....
Robust Queueing Network Analyzer (RQNA) of Robust Queueing Theory, a port of matlab/src/solvers/MVA/s...
Outcome of a scalar solve.
Definition rootfind.h:52
bool converged
tolerance was met before the iteration cap
Definition rootfind.h:57
T root
best estimate of the root
Definition rootfind.h:53
The patience law of one station-class pair, in the forms the solvers consume.
qsys::Patience< T > as_patience() const
This law as the Patience argument of the abandonment solvers.
bool present
Whether the pair declares reneging at all; everything below is unset when false.
bool isExponential
Whether the law is exponential, in which case the analysis is exact.
T rate
The abandonment rate 1/mean.
bool has_map() const
True when the type carries a (D0,D1) pair of its own.
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D0
Definition map_moment.h:54
What the cache analyzer reports beyond the [Q,U,R,T] block.
std::vector< T > hitprob
per class, NaN where the class does not read
std::vector< T > missprob
Matrix< T > itemprob
(n x h+1) per-item occupancy, column 0 = miss.
Matrix< T > hitproblist
(R x h) per-list hit fractions, access-weighted over items.
The chain-level view of a layer, as sn_get_demands_chain returns it.
Definition sn_chain.h:46
std::vector< double > Nchain
(C) population, infinite for an open chain
Definition sn_chain.h:51
Matrix< T > STchain
(M x C) mean service time
Definition sn_chain.h:48
Matrix< T > Lchain
(M x C) demand
Definition sn_chain.h:47
Matrix< T > Vchain
(M x C) visits
Definition sn_chain.h:49
Matrix< T > SCVchain
(M x C)
Definition sn_chain.h:52
Class-level results, as sn_deaggregate_chain_results returns them.
Definition sn_chain.h:191
std::vector< T > C
Definition sn_chain.h:193
std::vector< T > X
Definition sn_chain.h:193
What the dispatch returns: the metrics plus the algorithm that produced them.
std::string actualmethod
The concrete algorithm, as the reference's actualmethod.
std::string warning
Non-empty when the analyzer that ran would have raised a reference line_warning and still returned a ...
solvers::CacheMetrics< T > cache
What a cache branch measured, EMPTY on every model without a Cache.
std::shared_ptr< qn::NetworkStruct< T > > refreshed_struct
Set only by the integrated cacheqn branch: the converged struct whose routing carries the actual hit/...
The cache half of the reference's return list: the (ncaches x nclasses) hit and miss split,...
Matrix< T > missprob
(ncaches x nclasses)
Matrix< T > hitprob
(ncaches x nclasses)
std::vector< Matrix< T > > itemprob
per cache, (n x h+1); EMPTY = not computed
What the closed delayed-hit analyzer returns.
std::vector< T > delayedprob
(K) zero on this path, by the reference's convention
Matrix< T > hitproblist
(K x h) NaN: not computed on this path
std::vector< T > missprob
(K) the delayed fraction is folded in here
std::vector< T > latency
(K) NaN: not computed on this path
std::vector< T > hitprob
(K) P(item cached), read class only
The options SolverMVA reads.
Definition mva_types.h:31
std::string method
Definition mva_types.h:32
The cache half of the reference's return list: hitprob, missprob, delayedprob, latency,...
Matrix< T > itemprob
(n x h+1), column 0 = miss
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
What the analyzer returns, the reference's metrics plus its actualmethod.
std::string warning
Non-empty when the reference would have called line_warning AND RETURNED, carrying its text.
Result of pfqn_marie, mirroring the six MATLAB outputs.
Definition pfqn_marie.h:342
int it
iterations performed
Definition pfqn_marie.h:353
Matrix< T > Q
(M x R) mean queue length
Definition pfqn_marie.h:344
std::vector< T > X
(R) per-class throughput
Definition pfqn_marie.h:343
Matrix< T > U
(M x R) utilization
Definition pfqn_marie.h:345
One job class of the network.
double population
infinite for an open class
A node of the network.
One station of the network.
Steady-state measures of a multiserver queue with customer abandonment.
Steady-state measures of the G/GI/n/m diffusion approximation.
T meanNumber
mean number in system
Every Cache node of the model, in node order; empty on a model with none.