LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_mam_runner.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_MAM_SOLVER_MAM_RUNNER_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_RUNNER_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The SolverMAM class surface: `@@SolverMAM/runAnalyzer.m` and the gates around
12 * it.
13 *
14 * Everything that happens BEFORE and AFTER one inner solve lives here: the
15 * method whitelist (`SolverMAM.listValidMethods`), the per-method structural
16 * gates (`SolverMAM.supportsModelMethod`), the process gate, and the metric
17 * filter. `runAnalyzer`'s Finite Capacity Region rejection needs no counterpart:
18 * the C++ `NetworkStruct` has no region field, so such a model cannot be built.
19 *
20 * THE METRIC FILTER IS SHARED WITH SolverMVA AND SolverNC VERBATIM
21 * (`filter_metric`, `sn_get_residt_from_respt`, `sn_get_arvr_from_tput` in
22 * `solver_mva_runner.h`), because `@@NetworkSolver/getAvg` is
23 * solver-independent: it is literally the same code path for all three in the
24 * reference.
25 *
26 * NON-MARKOVIAN SERVICE IS CONVERTED, NOT REFUSED (since 2026-08-01).
27 * `solver_mam_analyzer.m:40` runs `sn_nonmarkov_toph` on its own copy of the
28 * struct, and so does `solver_mam_solve` here: a Gamma, Weibull, Lognormal,
29 * Pareto or Uniform is replaced by the two-moment concentrated-ME fit, or by the
30 * 20-phase Bernstein density fit under `phfit_cme = false`, and a Det by a
31 * 20-phase Erlang when `preserve_det` is off. The conversion runs AFTER the
32 * slotted test, which would otherwise see a continuous fit where a Geometric was
33 * declared. What survives the conversion and still reaches `check_processes` is
34 * NHPP / MAPt / PHt, whose content is a schedule that no homogeneous surrogate
35 * can carry, and those are refused by name.
36 *
37 * TWO GATES REMAIN STRICTER HERE THAN IN THE REFERENCE, and both are the honest
38 * outcome rather than a limitation:
39 *
40 * - A CLASS-SWITCHING CHAIN AT AN FCFS STATION. The reference's own chain-1
41 * mark collapse makes the marking handed to the queue solver disagree with
42 * the class count, and MATLAB then fails on a dimension mismatch. The
43 * analyzer refuses by name with both counts; see solver_mam_basic.h.
44 * - LOAD DEPENDENCE UNDER 'default' OUTSIDE THE LD-QBD SHAPE. MATLAB has no
45 * counterpart because it has no LoadDependence declaration for MAM at all
46 * (mam_feature_set's declaration is a C++-only widening, solver_feature_sets.h).
47 * `check_model_method` refuses a load-dependent model here when its shape is
48 * not the one `solver_mam_ldqbd` requires, because the featset gate cannot
49 * see topology and 'default' would otherwise fall through to
50 * `solver_mam_basic`, which never reads `st.lldscaling`.
51 */
52
53#include <algorithm>
54#include <cmath>
55#include <string>
56#include <vector>
57
70#include "line/util/error.h"
71
72namespace line {
73namespace mam {
74
75using lang::GlobalConstants;
76
77/**
78 * Port of `SolverMAM.listValidMethods`.
79 *
80 * The reference's list verbatim, in its order (the comment there records that
81 * test files index into it, so nothing may be inserted in the middle). A listed
82 * name that has no ported analyzer passes this gate and is then refused BY NAME
83 * by the dispatch, which is the honest outcome and different from silently
84 * solving a different model.
85 *
86 * `exact` at the end is the RCAT ALIAS -- `mam_dispatch.h` groups it with
87 * `inap`/`inapplus`/`inapinf`, exactly as `solver_mam_analyzer.m` does -- and
88 * NOT the retired autocat of `solver_mam_autocat.h`, whose own refusal message
89 * predates the reference re-advertising the name. `retrial` names the
90 * BMAP/PH/N/N analyzer that `default` also resolves to on a retrial topology.
91 */
92inline std::vector<std::string> list_valid_methods() {
93 // SolverMAM.m verbatim, in its order, MINUS the RCAT names
94 // ('inap', 'inapplus', 'inapinf', 'exact') which moved to SolverAG;
95 // mam_dispatch refuses those by name and says where they went.
96 return {"default", "dec.source", "dec.mmap", "dec.poisson", "mna", "ldqbd", "dec.source.mmap", "bgchain", "retrial"};
97}
98
99/**
100 * True for the four RCAT names that moved to SolverAG, with the redirect message.
101 *
102 * It lives here, and is called from `check_method` BEFORE the unlisted-method
103 * throw, because that ordering is the whole point: the names are no longer in
104 * `list_valid_methods`, so a caller carrying an old `options.method` would
105 * otherwise be told only that the method is "unsupported by this solver" and
106 * left to guess. `check_model_method` calls the same helper so the two paths
107 * cannot drift into two different messages.
108 */
109inline bool rcat_moved_to_ag(const std::string& method, std::string& why) {
110 if (method != "inap" && method != "inapplus" && method != "inapinf"
111 && method != "exact") {
112 return false;
113 }
114 why = "SolverMAM: the '" + method + "' method moved to SolverAG: RCAT decomposes the "
115 "model into cooperating agents rather than decomposing traffic, and no MAM "
116 "algorithm shares its machinery. Solve it with -s ag";
117 return true;
118}
119
120/** An unlisted method is refused; one that MOVED is redirected by name. */
121inline void check_method(const std::string& method) {
122 const std::vector<std::string> valid = list_valid_methods();
123 if (std::find(valid.begin(), valid.end(), method) != valid.end()) return;
124 std::string why;
125 if (rcat_moved_to_ag(method, why)) throw UnsupportedError(why);
126 throw UnsupportedError("SolverMAM: the '" + method + "' method is unsupported by this solver");
127}
128
129namespace runner_detail {
130
131/**
132 * The distributions `getFeatureSet` lists, plus the Markovian ones.
133 *
134 * Runs AFTER `sn_nonmarkov_toph`, so the families that have a Markovian
135 * surrogate are already gone; what can still arrive is a schedule family, whose
136 * whole content is the time dependence a single generator cannot carry.
137 *
138 * `is_discrete` is set when sn_is_discrete_time claimed the model: the lattice
139 * laws are then solved on their own time scale by solver_mam_dt and must not be
140 * refused for lacking a continuous phase-type fit, which is the only reason the
141 * refusal below exists.
142 */
143template <class T>
144void check_processes(const qn::NetworkStruct<T>& L, bool is_discrete) {
145 using lang::ProcessType;
146 if (is_discrete) return;
147 for (std::size_t i = 0; i < L.nstations; ++i)
148 for (std::size_t r = 0; r < L.nclasses; ++r) {
149 if (L.disabled[i][r]) continue;
150 const ProcessType p = L.service[i][r].type;
151 if (basic_detail::is_markovian_type(p) || p == ProcessType::DET) continue;
152 throw UnsupportedError(
153 std::string("SolverMAM: station '") + L.stations[i].name + "' class '" +
154 L.classes[r].name + "' uses a " + lang::process_to_text(p) +
155 " service process, which sn_nonmarkov_toph has no Markovian surrogate for: its "
156 "content is a piecewise-constant SCHEDULE, and collapsing it to one homogeneous "
157 "generator would answer for a stationary model the caller did not describe. Solve "
158 "it with SolverFLD or SolverLDES, which integrate the schedule");
159 }
160}
161
162/**
163 * True when any station declares a non-trivial load-dependent scaling, the
164 * same test `used_lang_features` (feature_set.h) applies to derive
165 * Feature::LoadDependence.
166 */
167template <class T>
168bool has_load_dependence(const qn::NetworkStruct<T>& L) {
169 for (const qn::Station<T>& st : L.stations)
170 if (!st.lldscaling.empty()) return true;
171 return false;
172}
173
174/** True when the model declares at least one G-network signal class. */
175template <class T>
176bool has_signal_class(const qn::NetworkStruct<T>& L) {
177 for (std::size_t r = 0; r < L.issignal.size(); ++r)
178 if (L.issignal[r]) return true;
179 return false;
180}
181
182/** `SolverMAM.supportsModelMethod`, the per-method structural gates. */
183template <class T>
184void check_model_method(const qn::NetworkStruct<T>& L, const std::string& method) {
185 using lang::ProcessType;
186 // The RCAT methods moved to SolverAG. `check_method` normally reports this
187 // first; the same helper is used here so a direct caller of
188 // check_model_method (the twin of SolverMAM.supportsModelMethod, which the
189 // reference also answers false for) gets the identical message.
190 {
191 std::string why;
192 if (rcat_moved_to_ag(method, why)) throw UnsupportedError(why);
193 }
194 // G-network signals belong to the RCAT analyzer alone: no MAM algorithm
195 // reads issignal, so every one of them would solve the model with the
196 // signals turned into ordinary customers and report that as the answer.
197 if (has_signal_class(L)) {
198 throw UnsupportedError(
199 "SolverMAM: the " + method + " method does not support G-network signals: no MAM "
200 "algorithm reads issignal, so this method would solve the model with every signal "
201 "turned into an ordinary customer. Use SolverAG (-s ag), whose RCAT methods are "
202 "the only ones that model signals");
203 }
204 if (method == "mna") {
205 bool open = false, closed = false;
206 for (const qn::JobClass& c : L.classes) {
207 if (std::isinf(c.population)) open = true;
208 else closed = true;
209 }
210 if (open && closed)
211 throw UnsupportedError(
212 "SolverMAM: the mna method does not support mixed open/closed models");
213 // mam_feature_set declares RoutingStrategy_RROBIN for 'mna' because
214 // solver_mna_open resolves the deterministic split through
215 // npfqn_traffic_split_rr. solver_mna_closed has no counterpart -- the
216 // reference corrects the open traffic equations only -- so a closed
217 // round-robin model would be solved as if the dispatcher were random.
218 if (!open) {
219 for (const qn::NodeDef& nd : L.nodes)
220 for (qn::RoutingStrategy rs : nd.routing)
221 if (rs == qn::RoutingStrategy::RROBIN)
222 throw UnsupportedError(
223 "SolverMAM: the mna method supports round-robin routing in open "
224 "models only");
225 }
226 } else if (method == "default") {
227 // mam_feature_set declares LoadDependence for 'default' because the
228 // single-class closed Delay+Queue shape routes here to solver_mam_ldqbd
229 // (mam_dispatch.h branch 2e), which reads st.lldscaling. The featset
230 // cannot see topology, so a load-dependent model outside that shape --
231 // or one carrying a setup/delay-off pair, which steals the same shape
232 // ahead of ldqbd -- must be refused here rather than falling through
233 // to solver_mam_basic, which never reads the field and would solve
234 // every level at the nominal rate.
235 if (has_load_dependence(L) &&
236 !(L.setupparam.empty() && dispatch_detail::is_closed_delay_queue(L)))
237 throw UnsupportedError(
238 "SolverMAM: this model uses load-dependent service rates outside the "
239 "single-class closed Delay+Queue shape that solver_mam_analyzer.m routes to the "
240 "level-dependent QBD (solver_mam_ldqbd); the 'default' method would otherwise "
241 "fall through to solver_mam_basic's dec.source decomposition, which does not "
242 "read the load-dependent scaling and would solve every level at the nominal "
243 "rate. Call method 'ldqbd' directly, which refuses by name if the shape still "
244 "does not fit, or restructure the model to the required shape");
245 } else if (method == "ldqbd") {
246 if (L.nclasses != 1)
247 throw UnsupportedError("SolverMAM: the ldqbd method requires a single-class model");
248 } else if (method == "retrial") {
249 // A "MUST BE PRESENT" RULE, which is why it cannot live in a feature
250 // set: a FeatureSet says "I accept this construct", so it can refuse a
251 // model for HAVING something and never for LACKING it.
252 // solver_mam_retrial needs the BMAP/PH/N/N bufferless retrial topology
253 // of Dudin et al. to analyze, and a model without one is not a smaller
254 // retrial model, it is a different one. mam_retrial_refusal is the same
255 // predicate mam_dispatch's 'retrial' arm and auto_family_refusal ask,
256 // and it reports WHICH requirement the model missed.
257 const std::string retrialWhy = mam_retrial_refusal(L);
258 if (!retrialWhy.empty()) throw UnsupportedError(retrialWhy);
259 } else if (method == "bgchain") {
260 // The closed classes ARE the background chain, so a purely open model has
261 // nothing to build it from. A purely CLOSED one is accepted: it is the
262 // degenerate case where the chain answers alone, with no open work to
263 // modulate it.
264 bool hasClosed = false;
265 for (std::size_t k = 0; k < L.nclasses; ++k) {
266 if (!std::isinf(L.classes[k].population)) hasClosed = true;
267 }
268 if (!hasClosed)
269 throw UnsupportedError(
270 "SolverMAM: the bgchain method requires at least one closed class: the background "
271 "chain IS the closed population vector, which a purely open model does not have. "
272 "Use the dec.source method");
273 // Priority disciplines need the per-class QBD of MMAPPH1PRPR, which has
274 // no counterpart in the modulated level-dependent QBD this method builds.
275 bool prioSched = false;
276 for (std::size_t i = 0; i < L.nstations; ++i) {
277 const lang::SchedStrategy sc = L.stations[i].sched;
279 prioSched = true;
280 }
281 if (prioSched && L.has_distinct_priorities())
282 throw UnsupportedError(
283 "SolverMAM: the bgchain method does not support class priorities: it aggregates "
284 "the open classes into one phase-type mixture per station, which cannot express a "
285 "priority order. Use the dec.source method");
286 if (L.has_fork())
287 throw UnsupportedError(
288 "SolverMAM: the bgchain method does not support fork-join: the background chain "
289 "conserves the closed population per station, which a fork violates. Use the "
290 "dec.source method");
291 }
292}
293
294} // namespace runner_detail
295
296/**
297 * `check_model_method` asked WITHOUT raising: the same verdict as a sentence.
298 *
299 * `autosolver::auto_family_refusal` needs a reason rather than an exception --
300 * the report answers yes or no per (family, method) pair and prints why -- and
301 * asking the gate itself is what keeps the offered pairs and the runnable ones
302 * the same set. Duplicating the rules there is how a method comes to be offered
303 * on a model its own runner refuses, which is exactly what happened to
304 * 'dec.mmap' and 'retrial'.
305 */
306template <class T>
307std::string mam_model_method_refusal(const qn::NetworkStruct<T>& L, const std::string& method) {
308 try {
309 runner_detail::check_model_method(L, method);
310 } catch (const std::exception& e) {
311 return std::string(e.what());
312 }
313 return std::string();
314}
315
316/**
317 * The gates and the dispatch of `@@SolverMAM/runAnalyzer.m`, without the metric
318 * filter.
319 */
320template <class T>
322 check_method(opt.method);
323 runner_detail::check_model_method(L, opt.method);
324 // Whether the slotted path will claim the model, which decides both the
325 // process gate below and branch -1 of the dispatch. Asking here keeps the
326 // two answers from diverging.
327 bool is_discrete = false;
328 if constexpr (num_traits<T>::has_transcendental) {
330 dtopt.timescale = opt.timescale;
331 dtopt.slotlength = opt.slotlength;
332 double slot = 1.0;
334 is_discrete = api::sn_is_discrete_time(L, dtopt, &slot, &dtinfo);
335 }
336
337 // `solver_mam_analyzer.m:40` runs sn_nonmarkov_toph on its own copy, AFTER
338 // the slotted test (which would otherwise see a continuous CME where a
339 // Geometric was declared) and with preserveDet on, so the exact MAP/D/c
340 // branch still sees its Det. The struct is copied only when something in it
341 // would actually be replaced.
342 // The RCAT methods build a CTMC per component out of (D0,D1), so a preserved
343 // Det would reach them with no matrix at all and be read back as its mean
344 // rate, and a concentrated matrix exponential is not a generator at all.
345 // They need a genuine phase-type, as SSA, Fluid and JMT do.
346 const bool preserve_det = opt.preserve_det;
347 qn::NetworkStruct<T> converted;
348 const qn::NetworkStruct<T>* Lp = &L;
349 if constexpr (num_traits<T>::has_transcendental) {
350 if (!is_discrete && api::sn_has_nonmarkov(L, preserve_det)) {
351 converted = L;
353 no.order = opt.nonmkv_order;
354 no.phfit = opt.phfit_cme ? api::PhFit::Cme : api::PhFit::Ph;
355 no.preserve_det = preserve_det;
356 api::sn_nonmarkov_toph(converted, no);
357 Lp = &converted;
358 }
359 }
360 const qn::NetworkStruct<T>& Lc = *Lp;
361
362 runner_detail::check_processes(Lc, is_discrete);
363 // runAnalyzerChecks' universal feature gate, AFTER the three structural
364 // checks so their specific messages (map_bernstein, single-class ldqbd,
365 // single-server inap, and the load-dependence shape check just above)
366 // still win. The declared set carries LoadDependence over MATLAB's for
367 // 'default' and 'ldqbd' only, without which every ldqbd model would
368 // refuse here; every other method leaves it undeclared, so a
369 // load-dependent model under 'dec.source', 'dec.poisson' or 'mna' is
370 // refused cleanly by the gate instead of silently solved at nominal rates.
371 qn::feature_gate("SolverMAM", qn::mam_feature_set(opt.method), Lc);
372 return mam_dispatch(Lc, opt);
373}
374
375/**
376 * Port of `@@SolverMAM/runAnalyzer.m` for the `lang='matlab'` path: solve, then
377 * apply the metric filter `@@NetworkSolver/getAvg` puts between the analyzer and
378 * the caller.
379 */
380template <class T>
382 const std::string origmethod = opt.method;
383 const MamSolution<T> d = solver_mam_solve(L, opt);
384 const mva::MvaSolution<T>& s = d.sol;
385
386 const std::size_t M = L.nstations, K = L.nclasses;
387 std::vector<std::vector<bool>> mask(M, std::vector<bool>(K, false));
388 for (std::size_t i = 0; i < M; ++i)
389 for (std::size_t k = 0; k < K; ++k)
390 mask[i][k] = num_traits<T>::to_double(s.R(i, k)) < 10.0 * GlobalConstants::FineTol;
391 std::vector<std::vector<bool>> srcmask(M, std::vector<bool>(K, false));
392 for (std::size_t i = 0; i < M; ++i)
393 if (L.stations[i].nodetype == qn::NodeType::Source)
394 for (std::size_t k = 0; k < K; ++k) srcmask[i][k] = true;
395
397 out.QN = mva::filter_metric(L, s.Q, mva::MetricKind::QLen, &mask);
398 out.UN = mva::filter_metric(L, s.U, mva::MetricKind::Util, &mask);
399 out.RN = mva::filter_metric(L, s.R, mva::MetricKind::RespT, nullptr);
400 out.TN = mva::filter_metric(L, s.Tp, mva::MetricKind::Tput, nullptr);
402 mva::MetricKind::ResidT, nullptr);
404 &srcmask);
405 out.CN = s.C;
406 out.XN = s.X;
407 out.method = origmethod;
408 // `runAnalyzer` reports 'default/<algorithm>' when the caller asked for the
409 // default, so the algorithm that produced the numbers is never lost.
410 out.actualmethod = (origmethod == "default" && !d.actualmethod.empty())
411 ? "default/" + d.actualmethod
412 : d.actualmethod;
413 out.iter = s.iter;
414 return out;
415}
416
417// ---------------------------------------------------------------------------
418// The rest of the @@SolverMAM class surface.
419//
420// Each of these mirrors one accessor and does its own gating; they take the
421// converged `AvgResult` where the reference calls `self.getAvg()` first, so a
422// caller cannot reach them on an unsolved model by accident.
423// ---------------------------------------------------------------------------
424
425/** `@@SolverMAM/getProb.m`: the joint (level, phase) table at a node. */
426template <class T>
428 std::size_t node, const mva::AvgResult<T>& avg) {
429 return solver_mam_getprob(L, opt, node, avg);
430}
431
432/** `@@SolverMAM/getProbMarg.m`: P(n jobs of one class) at a station. */
433template <class T>
435 std::size_t ist, std::size_t jobclass,
436 const mva::AvgResult<T>& avg) {
437 return solver_mam_getprobmarg(L, opt, ist, jobclass, avg);
438}
439
440/** `@@SolverMAM/getMAMResult.m`: the M/G/1-type internals of a single queue. */
441template <class T>
445
446/**
447 * `@@SolverMAM/getCdfRespT.m`: the response-time CDF per class.
448 *
449 * `getSjrnT` and `sjrnT` are aliases of this in the reference and are not given
450 * separate entry points here; there is nothing for them to do that this does
451 * not already do, and a second name for one function is a maintenance cost
452 * rather than a feature.
453 */
454template <class T>
455std::vector<RespTCdf<T>> solver_mam_get_cdf_respt(const qn::NetworkStruct<T>& L,
456 const MamOptions& opt) {
457 check_method(opt.method);
458 runner_detail::check_model_method(L, opt.method);
459 // The same non-Markovian conversion the steady-state entry runs. NOT
460 // exempted for a slotted model: solver_mam_passage_time reads the CONTINUOUS
461 // phase-type fit, so a lattice law has to be refused here even though the
462 // steady-state entry solves it on its own time scale.
463 qn::NetworkStruct<T> converted;
464 const qn::NetworkStruct<T>* Lp = &L;
465 if constexpr (num_traits<T>::has_transcendental) {
466 if (api::sn_has_nonmarkov(L, opt.preserve_det)) {
467 converted = L;
469 no.order = opt.nonmkv_order;
470 no.phfit = opt.phfit_cme ? api::PhFit::Cme : api::PhFit::Ph;
471 no.preserve_det = opt.preserve_det;
472 api::sn_nonmarkov_toph(converted, no);
473 Lp = &converted;
474 }
475 }
476 runner_detail::check_processes(*Lp, false);
477 return solver_mam_passage_time(*Lp, opt);
478}
479
480/** `@@SolverMAM/getSjrnT.m` and `sjrnT.m`, both aliases of getCdfRespT. */
481template <class T>
482std::vector<RespTCdf<T>> solver_mam_get_sjrn_t(const qn::NetworkStruct<T>& L,
483 const MamOptions& opt) {
484 return solver_mam_get_cdf_respt(L, opt);
485}
486
487/**
488 * Whether `getPerctRespT` reads the FJ_codes table rather than inverting a CDF.
489 *
490 * The reference decides by whether the last solve left a `result.Percentile`
491 * behind, which happens exactly when the analyzer took branch 2a. That is a
492 * state test on a stateless interface here, so the condition is recomputed from
493 * the model. Exposed rather than inlined because the CLI needs the SAME answer:
494 * a fork-join model has no response-time CDF at all, so a caller that asked for
495 * one must be told which of the two it is getting.
496 *
497 * Compiled out below double: `mam_fj_is_homogeneous` fits a phase-type per
498 * branch and is not instantiable in an exact field.
499 */
500template <class T>
502 if constexpr (!num_traits<T>::has_transcendental) {
503 (void)L;
504 (void)opt;
505 return false;
506 } else {
507 // Branch 2a is reachable from 'default' and 'dec.source' only; any other
508 // method names a different analyzer, which stores no percentile table.
509 if (opt.method != "default" && opt.method != "dec.source") return false;
510 if (L.fj.empty()) return false;
511 return mam_fj_is_homogeneous(L).ok;
512 }
513}
514
515/**
516 * `@@SolverMAM/getPerctRespT.m`: response-time percentiles per class.
517 *
518 * TWO PATHS, split by `mam_has_fj_percentiles`: a homogeneous fork-join model
519 * reads the table FJ_codes stored, and everything else inverts the
520 * response-time CDF.
521 */
522template <class T>
523std::vector<std::vector<T>> solver_mam_get_perct_respt(const qn::NetworkStruct<T>& L,
524 const MamOptions& opt,
525 const std::vector<double>& percentiles) {
527 return solver_mam_fj_percentiles(L, opt, percentiles);
528 const std::vector<RespTCdf<T>> rd = solver_mam_get_cdf_respt(L, opt);
529 std::vector<std::vector<T>> out;
530 out.reserve(rd.size());
531 for (const RespTCdf<T>& c : rd) out.push_back(mam_percentiles_from_cdf(c, percentiles));
532 return out;
533}
534
535/**
536 * Port of `@@SolverMAM/getTranAvg.m`: transient queue length, utilization and
537 * throughput.
538 *
539 * The reference forces `options.method = 'ldqbd'` and `options.lang = 'matlab'`
540 * before delegating, so the transient path is NOT selected by the caller's
541 * method; it is fixed. Reproduced: this ignores `opt.method` entirely.
542 *
543 * Which engine runs is decided by `mam_transient_qbd_applicable`: a correlated
544 * or non-Poisson arrival, or a non-renewal service, goes to the Laplace-domain
545 * transient QBD, and everything else to the QBD fast path.
546 */
547template <class T>
549 MamOptions opt = opt_in;
550 opt.method = "ldqbd";
551 runner_detail::check_model_method(L, opt.method);
552 // As in the passage-time entry: the transient engines are continuous-time,
553 // so a lattice law is refused rather than read through its continuous fit.
554 runner_detail::check_processes(L, false);
555 // The transient entry never reaches solver_mam_solve, so it needs its own
556 // gate, and that gate is NOT the steady-state ldqbd envelope even though the
557 // method name says ldqbd. `ldqbd` withdraws FiniteCapacity because the
558 // steady-state LD-QBD solves the buffer away, but BOTH transient engines
559 // honour it: solver_mam_transient_qbd builds the level-dependent generator up
560 // to the cap, and solver_mam_ldqbd_transient integrates that same generator.
561 // The M/M/1/5 case in test_mam.cpp checks the exact stationary law the
562 // construction never uses, so it is a genuine oracle for the limit. Gating
563 // this entry on the steady-state set refused a model these engines solve.
564 qn::FeatureSet tranSet = qn::mam_feature_set(opt.method);
565 tranSet.set(qn::Feature::FiniteCapacity);
566 qn::feature_gate("SolverMAM", tranSet, L);
569}
570
571} // namespace mam
572} // namespace line
573
574#endif // LINE_SOLVERS_MAM_SOLVER_MAM_RUNNER_H
UnsupportedError(const std::string &what)
Definition error.h:51
A subset of the registry: MATLAB's SolverFeatureSet, whose list is a flag per field.
FeatureSet & set(Feature f)
A network plus its refreshed NetworkStruct.
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< std::vector< bool > > disabled
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::vector< std::pair< std::size_t, std::size_t > > fj
fj(f,j): the Join node j that closes the Fork node f, 1-based.
The exception types the port throws.
Port of solver_mam_analyzer.m: one inner solve, choosing the analyzer that fits the model and the req...
The option and result types SolverMAM shares with its analyzers.
bool sn_has_nonmarkov(const qn::NetworkStruct< T > &sn, bool preserve_det=false)
Whether any law in the struct would be replaced, so a caller can skip copying the struct when there i...
@ Ph
Bernstein density fit: a genuine phase-type, shape-carrying.
@ Cme
concentrated ME plus exponential tail: two moments, exactly
void sn_nonmarkov_toph(qn::NetworkStruct< T > &sn, const NonmarkovOptions &opts=NonmarkovOptions())
Replace every non-Markovian service and firing law by a Markovian surrogate.
bool sn_is_discrete_time(const qn::NetworkStruct< T > &sn, const DiscreteTimeOptions &options, double *slot_length, DiscreteTimeInfo *info)
True when every law of sn is lattice-valued on slot_length.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
Definition lang_types.h:389
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
Definition lang_types.h:483
const char * process_to_text(ProcessType p)
The MATLAB ProcessType name, as sn.procid prints it.
Definition lang_types.h:560
std::vector< RespTCdf< T > > solver_mam_get_sjrn_t(const qn::NetworkStruct< T > &L, const MamOptions &opt)
@@SolverMAM/getSjrnT.m and sjrnT.m, both aliases of getCdfRespT.
qsys::BmapM1Result< T > solver_mam_getmamresult(const qn::NetworkStruct< T > &L)
Port of @@SolverMAM/getMAMResult.m: the matrix-analytic internals of a single-queue model,...
bool rcat_moved_to_ag(const std::string &method, std::string &why)
True for the four RCAT names that moved to SolverAG, with the redirect message.
void check_method(const std::string &method)
An unlisted method is refused; one that MOVED is redirected by name.
MamSolution< T > mam_dispatch(const qn::NetworkStruct< T > &L, const MamOptions &opt_in)
The ladder.
std::vector< T > mam_percentiles_from_cdf(const RespTCdf< T > &cdf, const std::vector< double > &pcts)
Port of the CDF path of @@SolverMAM/getPerctRespT.m: linear interpolation of the response-time CDF at...
TranResult< T > solver_mam_get_tran_avg(const qn::NetworkStruct< T > &L, const MamOptions &opt_in)
Port of @@SolverMAM/getTranAvg.m: transient queue length, utilization and throughput.
std::vector< std::string > list_valid_methods()
Port of SolverMAM.listValidMethods.
TranResult< T > solver_mam_transient_qbd(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_transient_qbd.m.
MamSolution< T > solver_mam_solve(const qn::NetworkStruct< T > &L, const MamOptions &opt)
The gates and the dispatch of @@SolverMAM/runAnalyzer.m, without the metric filter.
std::vector< RespTCdf< T > > solver_mam_get_cdf_respt(const qn::NetworkStruct< T > &L, const MamOptions &opt)
@@SolverMAM/getCdfRespT.m: the response-time CDF per class.
std::vector< RespTCdf< T > > solver_mam_passage_time(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_passage_time.m.
mva::AvgResult< T > solver_mam_run_analyzer(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of @@SolverMAM/runAnalyzer.m for the lang='matlab' path: solve, then apply the metric filter @@N...
ProbTable< T > solver_mam_get_prob(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t node, const mva::AvgResult< T > &avg)
@@SolverMAM/getProb.m: the joint (level, phase) table at a node.
bool mam_transient_qbd_applicable(const qn::NetworkStruct< T > &L)
Port of mam_transient_qbd_applicable.m: true when the Laplace-domain transient QBD should run instead...
MamFjInfo mam_fj_is_homogeneous(const qn::NetworkStruct< T > &L)
Port of fj_is_homogeneous.m.
std::vector< std::vector< T > > solver_mam_get_perct_respt(const qn::NetworkStruct< T > &L, const MamOptions &opt, const std::vector< double > &percentiles)
@@SolverMAM/getPerctRespT.m: response-time percentiles per class.
bool mam_has_fj_percentiles(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Whether getPerctRespT reads the FJ_codes table rather than inverting a CDF.
std::vector< T > solver_mam_get_prob_marg(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t ist, std::size_t jobclass, const mva::AvgResult< T > &avg)
@@SolverMAM/getProbMarg.m: P(n jobs of one class) at a station.
TranResult< T > solver_mam_ldqbd_transient(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_ldqbd_transient.m.
ProbTable< T > solver_mam_getprob(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t node, const mva::AvgResult< T > &avg)
Port of @@SolverMAM/getProb.m.
std::string mam_model_method_refusal(const qn::NetworkStruct< T > &L, const std::string &method)
check_model_method asked WITHOUT raising: the same verdict as a sentence.
std::vector< std::vector< T > > solver_mam_fj_percentiles(const qn::NetworkStruct< T > &L, const MamOptions &opt, const std::vector< double > &percentiles)
@@SolverMAM/getPerctRespT.m's fork-join path: the percentiles solver_mam_fj.m stores in percResults....
std::string mam_retrial_refusal(const qn::NetworkStruct< T > &L)
The 'retrial' method's applicability as one sentence; empty when applicable.
std::vector< T > solver_mam_getprobmarg(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t ist, std::size_t jobclass, const mva::AvgResult< T > &avg)
Port of @@SolverMAM/getProbMarg.m: P(n jobs of class jobclass) at station ist, for n = 0....
qsys::BmapM1Result< T > solver_mam_get_mam_result(const qn::NetworkStruct< T > &L)
@@SolverMAM/getMAMResult.m: the M/G/1-type internals of a single queue.
Matrix< T > sn_get_residt_from_respt(const qn::NetworkStruct< T > &L, const Matrix< T > &RN)
Port of sn_get_residt_from_respt: the per-JOB residence time.
Matrix< T > filter_metric(const qn::NetworkStruct< T > &L, const Matrix< T > &metric, MetricKind kind, const std::vector< std::vector< bool > > *zero_mask)
Port of filterMetric: what @@NetworkSolver/getAvg does between the analyzer and the caller.
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
FeatureSet mam_feature_set(const std::string &method)
SolverMAM.getFeatureSet, the union of its four setTrue calls: 55 MATLAB names, WIDENED for 'default'/...
void feature_gate(const std::string &solver, const FeatureSet &declared, const NetworkStruct< T > &sn, const std::string &requested_method="", const std::string &resolved_method="")
runAnalyzerChecks: refuse a model the solver does not declare, by name.
A queueing network and its refreshed NetworkStruct.
Port of matlab/src/api/sn/sn_is_discrete_time.m.
Replace every non-Markovian service and firing law by a Markovian surrogate.
The DECLARED side of the gate: one feature set per solver.
Port of solver_mam_fj.m, the fork-join route of SolverMAM.
Port of solver_mam_ldqbd_transient.m: transient queue length, utilization and throughput of a single-...
Port of solver_mam_passage_time.m: the response-time (sojourn-time) distribution of a single open que...
Port of @@SolverMAM/getProb.m and @@SolverMAM/getProbMarg.m: the joint (level, phase) and marginal qu...
Transient analysis of a single-class open queue by the Laplace-domain transient QBD plus numerical in...
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
Which laws the model carries, and why it was refused when it was.
How the caller may override the automatic decision.
std::string timescale
"auto", "discrete" or "continuous".
double slotlength
Slot length in model time units.
options.config.nonmkv and friends.
std::size_t order
nonmkvorder, the phase budget
PhFit phfit
which surrogate family
bool preserve_det
Leave Det alone for the exact MAP/D/c branch, options.config.preserveDet.
static constexpr double FineTol
Definition lang_types.h:668
The options SolverMAM reads.
Definition mam_types.h:29
What the MAM dispatch returns: the metrics plus the algorithm that ran.
Definition mam_types.h:124
std::string actualmethod
The concrete algorithm, as the reference's actualmethod.
Definition mam_types.h:127
mva::MvaSolution< T > sol
Definition mam_types.h:125
The joint (level, phase) table getProb returns: rows levels, cols phases.
One class's response-time CDF, the reference's RD{station, class} = [F, X].
What getTranAvg returns: queue length, utilization and throughput curves.
The metrics getAvg returns, after filtering.
Matrix< T > TN
throughput
Matrix< T > RN
response time, per visit
Matrix< T > UN
utilization
Matrix< T > WN
residence time, per job
std::string method
the method asked for
std::string actualmethod
the algorithm that ran
Matrix< T > QN
queue length
std::vector< T > CN
system response time per class
std::vector< T > XN
system throughput per class
Matrix< T > AN
arrival rate
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
Everything qsys_bmapm1 returns, mirroring the MATLAB result struct.
Definition qsys_bmapm1.h:62