LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_auto.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_AUTO_SOLVER_AUTO_H
6#define LINE_SOLVERS_AUTO_SOLVER_AUTO_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The SolverAUTO chooser: which solver a model is handed to.
12 *
13 * WHAT THIS PORTS. `matlab/src/solvers/AUTO/@@SolverAUTO/` selects in two
14 * layers, and both are here:
15 *
16 * solverTraits.m computes the structural traits ONCE per call, so that the
17 * choosers stay tables of rankings rather than a second place where model
18 * inspection is written. `auto_traits` is that function.
19 *
20 * chooseSolverRanked.m walks a list of candidate slots and returns the FIRST
21 * that exists and whose feature set accepts the model, or nothing, so the
22 * caller can fall back rather than hand an infeasible solver to the
23 * delegate. `auto_ranked` is that function, and it is what makes every
24 * ranking below a preference rather than a claim.
25 *
26 * chooseSolverHeur.m / chooseAvgSolverHeur.m / chooseSolverExact.m /
27 * chooseSolverSim.m and the `fast` and `accurate` arms of chooseSolver.m
28 * are the rankings themselves, ported list for list, in the reference's own
29 * order. The Network, LayeredNetwork and Environment arms all have a
30 * counterpart here.
31 *
32 * THE FEATURE-SET GATE IS THE WHOLE POINT OF THE REWRITE. The previous port
33 * transcribed the SUPERSEDED feature cascade (population thresholds 30/10/5
34 * over a first-match tree) and consulted no feature set at all, so it named a
35 * solver that could then refuse the model. Here `auto_supports` asks the same
36 * question `Solver.supports` asks -- is every feature the model uses declared
37 * by that solver? -- from `used_lang_features` and the per-solver sets in
38 * `solver_feature_sets.h`, and the answer decides.
39 *
40 * TWO GATES ARE FINER THAN A FLAT FEATURE SET, and the reference states both
41 * outside `getFeatureSet` for that reason:
42 *
43 * 'exact' needs a product-form solution (SolverMVA.supportsExactness,
44 * SolverNC.supportsModelMethod), with the order-independent and
45 * pass-and-swap stations exempt for MVA because solver_mva_oi is exact for
46 * them regardless. Reproduced in `auto_supports`.
47 *
48 * CTMC needs its chain to FIT. The reference screens the slot with
49 * SolverCTMC.isStateSpaceTractable, which prices the worst-case state space
50 * against host memory through a profiled power law. This port has no such
51 * calibration and its generator has a hard cap instead, so the screen here
52 * compares the SAME estimator -- `ctmc_state_space_logsize`, ported
53 * factor for factor -- against that cap. The gate is therefore
54 * host-independent where the reference's is host-dependent: the estimate is
55 * identical, the budget it is compared to is this port's own.
56 *
57 * AN ABSENT ENGINE IS SKIPPED, AND SAID SO. In the reference an unavailable
58 * candidate is an empty slot and chooseSolverRanked skips it silently; that is
59 * how SolverLQNS behaves when the binary is not installed. The MAM layer engine
60 * is absent from this port in exactly that sense, and LDES and LQNS are absent
61 * only where their engines are not installed, so all three are skipped the same
62 * way -- but skipping changes which engine answers, so every choice carries
63 * `skipped`, the slots that outranked the winner and had no engine behind them.
64 * The CLI prints it. Silence there would report the second choice as if it had
65 * been the first.
66 *
67 * JMT IS NOT A CANDIDATE AT ALL, in the reference either (SolverAUTO.m:45-47):
68 * LDES subsumes its feature set, so automatic selection never dispatches to the
69 * external simulator and SolverJMT stays reachable only through an explicit
70 * token. The enum has no JMT slot for that reason.
71 *
72 * THE HOMOGENEOUS-SCHEDULING PREDICATE. `has_homogeneous_scheduling` is true
73 * when every station uses the given discipline (see NetworkStruct; it used to
74 * collapse to `nstations == 1`). Only the response-time-CDF branch and one
75 * avgOrder arm consult it, and both are written as the reference writes them.
76 *
77 */
78
79#include <algorithm>
80#include <cmath>
81#include <cstddef>
82#include <string>
83#include <vector>
84
90#include "line/util/error.h"
91
92namespace line {
93namespace autosolver {
94
95/**
96 * The Network candidate slots, in the reference's slot order (SolverAUTO.m:41-50),
97 * which is also the order the delegate retries in. There is no JMT slot: the
98 * reference removed it as a candidate.
99 */
100enum class AutoSolver { MVA = 0, NC, MAM, FLUID, SSA, CTMC, LDES };
101
102/** The LayeredNetwork candidate slots (SolverAUTO.m:52-56). */
104
105/** The Environment candidate slots (SolverAUTO.m:58-60). */
106enum class AutoEnv { ENV_MVA = 0, ENV_NC, ENV_FLUID };
107
108/** The selection intents of `SolverAUTO.selectionIntents`, less 'bound'. */
109enum class AutoMode { HEUR, EXACT, SIM, FAST, ACCURATE };
110
111/** Population at or below which an exact solver is preferred (EXACT_POPULATION_MAX). */
112const double kAutoExactPopulationMax = 5.0;
113
114inline const char* auto_solver_name(AutoSolver s) {
115 switch (s) {
116 case AutoSolver::MVA: return "mva";
117 case AutoSolver::NC: return "nc";
118 case AutoSolver::MAM: return "mam";
119 case AutoSolver::FLUID: return "fld";
120 case AutoSolver::SSA: return "ssa";
121 case AutoSolver::CTMC: return "ctmc";
122 case AutoSolver::LDES: return "ldes";
123 }
124 return "";
125}
126
127/**
128 * The layered names are the CLI's own tokens, because that is what the choice
129 * is spent on: `ln.comom` runs the layers under NC and `ln.mva` under MVA.
130 */
131inline const char* auto_layered_name(AutoLayered s) {
132 switch (s) {
133 case AutoLayered::LQNS: return "lqns";
134 case AutoLayered::LN_NC: return "ln.comom";
135 case AutoLayered::LN_MVA: return "ln.mva";
136 case AutoLayered::LN_MAM: return "ln.mam";
137 case AutoLayered::LN_FLUID: return "ln.fld";
138 }
139 return "";
140}
141
142inline const char* auto_env_name(AutoEnv s) {
143 switch (s) {
144 case AutoEnv::ENV_MVA: return "env.mva";
145 case AutoEnv::ENV_NC: return "env.nc";
146 case AutoEnv::ENV_FLUID: return "env.fld";
147 }
148 return "";
149}
150
151/**
152 * True when this port has an engine behind the slot at all.
153 *
154 * THE LDES SLOT IS MACHINE-DEPENDENT, like the LQNS one below and for the same
155 * reason: the simulator is a client of an engine LINE ships beside the binary
156 * (`common/ldes`, `common/ldes.jar`), and `ldes_is_available` answers whether
157 * this machine has one. A port that answered "never" would silently disagree
158 * with the reference wherever the engine IS present -- LDES leads the
159 * loss-metric, aggregate-sampling and `sim` rankings, so the disagreement would
160 * be a different engine answering, not a missing option.
161 */
163 if (s == AutoSolver::LDES) return ldes::ldes_is_available();
164 return true;
165}
166
167/**
168 * Every LN layer engine the ranking names is one `--layer-solver` takes (mva,
169 * nc, fluid, ssa, mam, ctmc), so each `ln.*` candidate is available.
170 *
171 * LQNS IS AVAILABLE ONLY WHERE ITS BINARY IS. That makes this choice
172 * machine-dependent, and deliberately so: `chooseAvgSolverHeur.m` gates its
173 * LQNS candidate on `SolverLQNS.isAvailable()` for the same reason, because
174 * LINE ships no LQNS binary. A port that answered "never" here would silently
175 * disagree with the reference on every machine that has one installed.
176 */
179 return true;
180}
181
182/**
183 * All three candidate ensembles are built here (`EnvOptions::stage_solver` =
184 * `fluid`, `mva`, `nc`). As in the reference, an MVA or NC ensemble is
185 * then refused by the mean-field coupling for want of a transient, and the
186 * retry list moves on to the next candidate.
187 */
188inline bool auto_env_is_available(AutoEnv) { return true; }
189
190/** The `EnvOptions::stage_solver` (and `--stage-solver`) name of a candidate ensemble. */
191inline const char* auto_env_stage_solver(AutoEnv s) {
192 switch (s) {
193 case AutoEnv::ENV_MVA: return "mva";
194 case AutoEnv::ENV_NC: return "nc";
195 case AutoEnv::ENV_FLUID: return "fluid";
196 }
197 return "fluid";
198}
199
200// ---------------------------------------------------------------------------
201// Method method names: a selection intent, or a method family
202// ---------------------------------------------------------------------------
203
204/**
205 * `SolverAUTO.selectionIntents`, less 'bound'.
206 *
207 * 'bound' IS accepted, but it is not a ranking mode: the reference sets
208 * selectionMode='bound' and options.method='auto', i.e. it names SolverBA
209 * outright. `auto_resolve_token` therefore rewrites it to the 'ba' family
210 * before this test runs, which is why it is absent here.
211 */
212inline bool auto_is_selection_intent(const std::string& token) {
213 return token.empty() || token == "default" || token == "auto" || token == "heur" ||
214 token == "sim" || token == "exact" || token == "fast" ||
215 token == "accurate";
216}
217
218inline AutoMode auto_mode_of_token(const std::string& token) {
219 if (token.empty() || token == "default" || token == "auto" || token == "heur")
220 return AutoMode::HEUR;
221 if (token == "exact") return AutoMode::EXACT;
222 if (token == "sim") return AutoMode::SIM;
223 if (token == "fast") return AutoMode::FAST;
224 if (token == "accurate") return AutoMode::ACCURATE;
225 throw InputError("SolverAUTO: '" + token + "' is not a selection intent");
226}
227
228/**
229 * `SolverAUTO.familyAlias`: the canonical family of a method name, or "" when the
230 * method name names none. A family method name is not a selection intent -- it asks for a
231 * named engine and bypasses the ranking, as `-s auto --method nc.comom` does.
232 */
233inline std::string auto_family_alias(const std::string& name) {
234 if (name == "mam") return "mam";
235 if (name == "ag") return "ag";
236 if (name == "mva") return "mva";
237 if (name == "nc") return "nc";
238 if (name == "fluid" || name == "fld") return "fld";
239 if (name == "jmt") return "jmt";
240 if (name == "ssa") return "ssa";
241 if (name == "ctmc") return "ctmc";
242 if (name == "ldes" || name == "des") return "ldes";
243 if (name == "ba") return "ba";
244 if (name == "env") return "env";
245 if (name == "ln") return "ln";
246 if (name == "lqns" || name == "lqsim") return "lqns";
247 if (name == "qns") return "lqns";
248 if (name == "uq") return "uq";
249 return "";
250}
251
252/**
253 * `resolveMethodToken`, minus the unqualified-algorithm-name arm.
254 *
255 * A method name is an intent, or `family[.submethod]`. The reference has a third form
256 * -- a bare algorithm name such as `comom`, resolved by asking every family for
257 * its `listValidMethods` -- which needs the model; it is refused here as an
258 * InputError, and `auto_resolve_token(raw, sn)` (auto_methods.h) carries that arm.
259 */
260struct AutoToken {
261 bool is_intent = true;
263 std::string family; ///< empty when is_intent
264 std::string submethod; ///< the method handed to the family, "default" when bare
265};
266
267inline AutoToken auto_resolve_token(const std::string& raw) {
268 AutoToken t;
269 const std::string token = raw.empty() ? std::string("default") : raw;
270 // 'bound' is a selection intent in the reference's own list, and it selects
271 // SolverBA with method 'auto' rather than picking a ranking
272 // (SolverAUTO.m:116-121). Rewriting it to the family method name here reuses the
273 // family path below and keeps the intent spelling usable, which is what the
274 // other three codebases accept.
275 if (token == "bound") {
276 t.is_intent = false;
277 t.family = "ba";
278 t.submethod = "auto";
279 return t;
280 }
281 if (auto_is_selection_intent(token)) {
282 t.is_intent = true;
283 t.mode = auto_mode_of_token(token);
284 return t;
285 }
286 const std::size_t dot = token.find('.');
287 const std::string head = dot == std::string::npos ? token : token.substr(0, dot);
288 const std::string rest = dot == std::string::npos ? std::string() : token.substr(dot + 1);
289 // bare "fluid" is an input alias of the "fld" family; the qualified spelling is "fld.<m>" only
290 const std::string fam =
291 (!rest.empty() && head == "fluid") ? std::string() : auto_family_alias(head);
292 if (fam.empty())
293 throw InputError(
294 "SolverAUTO: '" + token +
295 "' is neither a selection intent (default, heur, exact, sim, fast, accurate, "
296 "bound) "
297 "nor a method family (mva, nc, ctmc, fld, mam, ag, ba, ssa, ldes, jmt, ln, "
298 "env, lqns, uq). A bare algorithm name must be qualified by its family here, as in "
299 "'nc.comom': resolving it needs the per-family method registry "
300 "(listValidMethods) that this port does not carry");
301 t.is_intent = false;
302 t.family = fam;
303 t.submethod = rest.empty() ? std::string("default") : rest;
304 // `qns` and `qns.NAME` are SolverLQNS method names on a flat Network, so the
305 // whole token is the submethod and `lqns` the family.
306 if (head == "qns") t.submethod = token;
307 return t;
308}
309
310// ---------------------------------------------------------------------------
311// solverTraits.m
312// ---------------------------------------------------------------------------
313
314/** The structural traits the rankings are keyed on. Port of `solverTraits.m`. */
316 bool has_cache = false;
317 bool has_fcr = false;
318 bool has_fork = false;
319 bool has_map = false;
320 /** "none", "preempt", "ps" or "hol", in the reference's own precedence. */
321 std::string prio = "none";
322 bool is_closed = false;
323 bool is_open = false;
324 bool is_mixed = false;
325 bool has_multi_server = false;
326 bool is_product_form = false;
327 double pop_per_chain = 0.0;
328 double total_jobs = 0.0;
329 bool single_chain = false;
330};
331
332template <class T>
334 using qn::NodeType;
335 using lang::ProcessType;
337
338 AutoTraits t;
339 for (const qn::NodeDef& nd : sn.nodes)
340 if (nd.nodetype == NodeType::Cache) t.has_cache = true;
341 t.has_fcr = !sn.regions.empty();
342 t.has_fork = sn.has_fork();
343 t.has_multi_server = sn.has_multi_server();
344 t.is_product_form = sn.has_product_form();
345 t.single_chain = sn.nchains == 1;
346
347 bool has_open = false, has_closed = false;
348 for (const qn::JobClass& c : sn.classes) {
349 if (std::isinf(c.population)) has_open = true;
350 else has_closed = true;
351 }
352 t.is_open = has_open && !has_closed;
353 t.is_closed = has_closed && !has_open;
354 t.is_mixed = has_open && has_closed;
355
356 t.total_jobs = sn.total_jobs();
357 if (sn.nchains > 0) t.pop_per_chain = t.total_jobs / static_cast<double>(sn.nchains);
358
359 // Autocorrelated arrival or service: only MAM keeps the correlation, every
360 // other analytical solver sees the marginal only.
361 for (std::size_t i = 1; i <= sn.nstations && !t.has_map; ++i)
362 for (std::size_t r = 1; r <= sn.nclasses; ++r) {
363 const ProcessType p = sn.procid(i, r);
364 if (p == ProcessType::MAP || p == ProcessType::MMPP2) {
365 t.has_map = true;
366 break;
367 }
368 }
369
370 // Preemptive priority is a strictly narrower capability than HOL, so the
371 // two rank differently and are distinguished here rather than downstream.
372 bool preempt = false, psprio = false, hol = false;
373 for (const qn::Station<T>& s : sn.stations) {
374 if (s.sched == SchedStrategy::FCFSPRPRIO || s.sched == SchedStrategy::FCFSPIPRIO ||
375 s.sched == SchedStrategy::LCFSPRPRIO || s.sched == SchedStrategy::LCFSPIPRIO)
376 preempt = true;
377 if (s.sched == SchedStrategy::PSPRIO || s.sched == SchedStrategy::DPSPRIO ||
378 s.sched == SchedStrategy::GPSPRIO)
379 psprio = true;
380 if (s.sched == SchedStrategy::HOL || s.sched == SchedStrategy::LCFSPRIO ||
381 s.sched == SchedStrategy::SRPTPRIO)
382 hol = true;
383 }
384 if (preempt) t.prio = "preempt";
385 else if (psprio) t.prio = "ps";
386 else if (hol) t.prio = "hol";
387 return t;
388}
389
390// ---------------------------------------------------------------------------
391// The CTMC screen: ctmc_state_space_logsize.m
392// ---------------------------------------------------------------------------
393
394/**
395 * Worst-case log-size of the CTMC state space induced by `sn`: the SAME
396 * estimator SolverCTMC's memory gate uses, `mc::ctmc_state_space_logsize`
397 * (port of `ctmc_state_space_logsize.m`), not a copy of it.
398 *
399 * This used to be a second, older implementation, and it drifted: it priced a
400 * closed chain whose jobs switch class per class instead of as one population
401 * unit, had no factor for a cache's content or in-transit slot, and kept the
402 * pre-DP class-sequence bound for ordered buffers. So the AUTO screen and the
403 * CTMC gate could disagree on the same model (fix J1, 2026-10-16).
404 *
405 * `cutoff` negative selects the analyzer's own default for open and mixed
406 * models, `ceil(6000^(1/(M*K)))`.
407 */
408template <class T>
409double auto_ctmc_state_space_logsize(const qn::NetworkStruct<T>& sn, double cutoff = -1.0) {
411 o.cutoff = cutoff;
413}
414
415/**
416 * The cap `reachable_space_generator` enforces (solver_ctmc.h, `maxst`). It is
417 * this port's budget, standing in for the reference's memory model.
418 */
419const double kAutoCtmcStateCap = 3000000.0;
420
421template <class T>
422bool auto_ctmc_is_tractable(const qn::NetworkStruct<T>& sn, double cutoff = -1.0) {
423 return auto_ctmc_state_space_logsize(sn, cutoff) <= std::log(kAutoCtmcStateCap);
424}
425
426// ---------------------------------------------------------------------------
427// chooseSolverRanked.m
428// ---------------------------------------------------------------------------
429
430/**
431 * `Solver.supports(model)` for a candidate slot, tightened by the two
432 * method-level rules a flat feature set cannot express.
433 *
434 * `method name` is the method the slot would run: "" or "default" for the solver's
435 * own default, "exact" for the exactness-gated request.
436 */
437template <class T>
438bool auto_supports(AutoSolver s, const qn::NetworkStruct<T>& sn, const std::string& token) {
440 if (!auto_solver_is_available(s)) return false;
441 const std::string method = token.empty() ? std::string("default") : token;
442
443 qn::FeatureSet declared;
444 switch (s) {
445 case AutoSolver::MVA: declared = qn::mva_feature_set(method); break;
446 case AutoSolver::NC: declared = qn::nc_feature_set(method); break;
447 case AutoSolver::MAM: declared = qn::mam_feature_set(method); break;
448 // An UNQUALIFIED slot asks the static `SolverFLD.supports(model)`, which reads the class envelope;
449 // 'default' narrows HOL, GPS and LoadDependence away (chooseSolverRanked.m, SolverAUTO.m:166).
451 declared = token.empty() ? qn::fluid_class_feature_set() : qn::fluid_feature_set(method);
452 break;
453 case AutoSolver::SSA: declared = qn::ssa_feature_set(method); break;
454 case AutoSolver::CTMC: declared = qn::ctmc_feature_set(method); break;
455 // The LDES slot is gated like any other now that an engine can stand
456 // behind it: `auto_solver_is_available` has already answered whether
457 // this machine has one, and what remains is the reference's own
458 // declaration, which the client honours by forwarding the model to the
459 // engine that implements it.
460 case AutoSolver::LDES: declared = qn::ldes_feature_set(method); break;
461 }
463 return false;
464
465 if (method == "exact" && (s == AutoSolver::MVA || s == AutoSolver::NC)) {
466 if (!sn.has_product_form()) {
467 // solver_mva_oi is exact for the order-independent and
468 // pass-and-swap stations whatever the product-form test says; NC
469 // has no such exemption.
470 bool oi = false;
471 if (s == AutoSolver::MVA)
472 for (const qn::Station<T>& st : sn.stations)
473 if (st.sched == SchedStrategy::OI || st.sched == SchedStrategy::PAS) oi = true;
474 if (!oi) return false;
475 }
476 }
477 // The state-space screen prices the explicit generator, which only default/exact build. `mdd` enumerates no
478 // generator and is exempt, as SolverCTMC.supportsModelMethod exempts it in the reference (cftp lives in NC).
479 if (s == AutoSolver::CTMC && method != "mdd" && !auto_ctmc_is_tractable(sn)) return false;
480 return true;
481}
482
483/** What a ranking resolved to, and what it had to skip to get there. */
486 /** Slots that outranked `solver` and have no engine in this port. */
487 std::vector<AutoSolver> skipped;
488 /** The method the choice was gated on: "" for the default, "exact". */
489 std::string method;
490};
491
492/**
493 * `chooseSolverRanked`: the first slot in ORDER that exists and accepts the
494 * model. Returns false when none qualifies, so the caller can fall back.
495 */
496template <class T>
497bool auto_ranked(const std::vector<AutoSolver>& order, const qn::NetworkStruct<T>& sn,
498 const std::string& token, AutoChoice& out) {
499 std::vector<AutoSolver> skipped;
500 for (std::size_t k = 0; k < order.size(); ++k) {
501 if (!auto_solver_is_available(order[k])) {
502 skipped.push_back(order[k]);
503 continue;
504 }
505 if (!auto_supports(order[k], sn, token)) continue;
506 out.solver = order[k];
507 out.skipped = skipped;
508 out.method = (token == "default") ? std::string() : token;
509 return true;
510 }
511 return false;
512}
513
514/**
515 * The candidate pool in slot order, filtered by `supports`: what the delegate
516 * retries through after the chosen solver fails (SolverAUTO.m:139-147).
517 */
518template <class T>
519std::vector<AutoSolver> auto_candidates(const qn::NetworkStruct<T>& sn) {
520 static const AutoSolver kSlots[] = {AutoSolver::MVA, AutoSolver::NC, AutoSolver::MAM,
523 std::vector<AutoSolver> out;
524 for (std::size_t i = 0; i < sizeof(kSlots) / sizeof(*kSlots); ++i)
525 if (auto_solver_is_available(kSlots[i]) && auto_supports(kSlots[i], sn, std::string()))
526 out.push_back(kSlots[i]);
527 return out;
528}
529
530// ---------------------------------------------------------------------------
531// chooseAvgSolverHeur.m, the Network arm
532// ---------------------------------------------------------------------------
533
534namespace detail {
535
536/**
537 * `avgOrder` in chooseAvgSolverHeur.m, arm for arm and in its order. The arms
538 * OVERLAP, so the first match wins and reordering silently rehomes models.
539 *
540 * `homogeneous_inf` is the penultimate arm's predicate, passed in because it
541 * needs the struct and this table does not otherwise.
542 */
543inline std::vector<AutoSolver> avg_order(const AutoTraits& t, bool homogeneous_inf) {
544 if (t.has_cache)
547 if (t.has_fcr) {
548 if (t.total_jobs <= 10.0) return {AutoSolver::NC, AutoSolver::CTMC, AutoSolver::LDES};
550 }
551 if (t.prio == "preempt")
553 if (t.prio == "ps") return {AutoSolver::CTMC, AutoSolver::LDES, AutoSolver::SSA};
554 if (t.has_map)
556 if (t.prio == "hol")
559 if (t.pop_per_chain > 30.0) return {AutoSolver::FLUID, AutoSolver::MVA, AutoSolver::NC};
560 // No exact solver was available at this population (the exact-first pass
561 // above tried), so keep the exact-leaning approximate order.
562 if (t.total_jobs > 0.0 && t.total_jobs <= kAutoExactPopulationMax)
564 if (homogeneous_inf) return {AutoSolver::MVA, AutoSolver::NC, AutoSolver::FLUID};
566}
567
568inline UnsupportedError no_solver(const std::string& what,
569 const std::vector<AutoSolver>& skipped) {
570 std::string msg = "SolverAUTO: no solver supports this model" + what;
571 if (!skipped.empty()) {
572 msg += " (the ranking preferred ";
573 for (std::size_t i = 0; i < skipped.size(); ++i) {
574 if (i) msg += ", ";
575 msg += auto_solver_name(skipped[i]);
576 }
577 msg += ", which this port does not build)";
578 }
579 return UnsupportedError(ErrorKind{"Model"}, msg);
580}
581
582} // namespace detail
583
584/**
585 * `chooseAvgSolverHeur`, the Network arm: exact first at small populations,
586 * then the trait-keyed ranking, then the whole pool in the global order.
587 *
588 * The INF branch of avgOrder consults `has_homogeneous_scheduling`, true when
589 * every station is a delay; see the header note.
590 */
591template <class T>
594 const AutoTraits t = auto_traits(sn);
595 AutoChoice out;
596
597 // Small populations: an approximation buys nothing there, so take an exact
598 // solver whenever one is available. The 'exact' method name is what makes this a
599 // claim rather than a preference -- MVA and NC reject it without a
600 // product-form solution, and CTMC is screened for a chain that fits.
601 if (t.total_jobs > 0.0 && t.total_jobs <= kAutoExactPopulationMax) {
602 const std::vector<AutoSolver> exact_order =
603 t.has_cache
604 ? std::vector<AutoSolver>{AutoSolver::NC, AutoSolver::MVA, AutoSolver::CTMC}
605 : std::vector<AutoSolver>{AutoSolver::MVA, AutoSolver::NC, AutoSolver::CTMC};
606 if (auto_ranked(exact_order, sn, "exact", out)) return out;
607 }
608
609 const std::vector<AutoSolver> order =
610 detail::avg_order(t, sn.has_homogeneous_scheduling(SchedStrategy::INF));
611 if (auto_ranked(order, sn, std::string(), out)) return out;
612
613 // Nothing in the ranked list is feasible: fall back to the whole pool in
614 // the global order rather than returning nothing.
615 const std::vector<AutoSolver> pool = {AutoSolver::MVA, AutoSolver::NC, AutoSolver::MAM,
618 if (auto_ranked(pool, sn, std::string(), out)) return out;
619 throw detail::no_solver("", pool);
620}
621
622template <class T>
626
627// ---------------------------------------------------------------------------
628// chooseSolverHeur.m, the Network arm
629// ---------------------------------------------------------------------------
630
631namespace detail {
632
633inline bool in_list(const std::string& m, const char* const* tab, std::size_t n) {
634 for (std::size_t i = 0; i < n; ++i)
635 if (m == tab[i]) return true;
636 return false;
637}
638
639/** The average-metric getters, which defer to the feature tree. */
640inline bool is_avg_method(const std::string& m) {
641 static const char* kAvg[] = {
642 "getAvgChainTable", "getAvgTputTable", "getAvgRespTTable", "getAvgUtilTable",
643 "getAvgSysTable", "getAvgNodeTable", "getAvgTable", "getAvgTableLayered", "getAvg",
644 "getAvgChain", "getAvgSys", "getAvgNode", "getAvgNodeChain", "getAvgArvRChain",
645 "getAvgQLenChain", "getAvgUtilChain", "getAvgRespTChain", "getAvgTputChain",
646 "getAvgSysRespT", "getAvgSysTput", "getAvgQLen", "getAvgUtil", "getAvgRespT",
647 "getAvgResidT", "getAvgWaitT", "getAvgTput", "getAvgArvR", "getAvgQLenTable",
648 "getAvgResidTChain", "getAvgNodeQLenChain", "getAvgNodeUtilChain",
649 "getAvgNodeRespTChain", "getAvgNodeResidTChain", "getAvgNodeTputChain",
650 "getAvgNodeArvRChain", "getAvgNodeChainTable", "getResults", "hasResults",
651 "getAvgHandles", "getTranHandles", "getAvgQLenHandles", "getAvgUtilHandles",
652 "getAvgRespTHandles", "getAvgTputHandles", "getAvgArvRHandles", "getAvgResidTHandles",
653 "getMethodFeatureSet", "supportsModelMethod", "isStochasticMethod", "libraries",
654 "showLibraryAttribution", "citations"};
655 return in_list(m, kAvg, sizeof(kAvg) / sizeof(*kAvg));
656}
657
658/** The ensemble getters, which exist for LayeredNetwork models only. */
659inline bool is_ensemble_method(const std::string& m) {
660 static const char* kEns[] = {"getEnsembleAvg", "getEnsembleAvgTables", "getSolver",
661 "setSolver", "getNumberOfModels", "getIteration",
662 "get_state", "set_state", "update_solver"};
663 return in_list(m, kEns, sizeof(kEns) / sizeof(*kEns));
664}
665
666inline bool is_cdf_method(const std::string& m) {
667 return m == "getCdfRespT" || m == "getCdfPassT" || m == "getPerctRespT";
668}
669
670inline bool is_tran_prob_method(const std::string& m) {
671 return m == "getTranProb" || m == "getTranProbSys" || m == "getTranProbAggr" ||
672 m == "getTranProbSysAggr";
673}
674
675inline bool is_prob_method(const std::string& m) {
676 return m == "getProb" || m == "getProbAggr" || m == "getProbSys" || m == "getProbSysAggr" ||
677 m == "getProbMarg" || m == "getProbNormConstAggr";
678}
679
680inline bool is_sample_method(const std::string& m) { return m == "sample" || m == "sampleSys"; }
681
682inline bool is_sample_aggr_method(const std::string& m) {
683 return m == "sampleAggr" || m == "sampleSysAggr";
684}
685
686inline bool is_cache_metric_method(const std::string& m) {
687 static const char* kTab[] = {"getAvgCacheTable", "getAvgCacheT", "getAvgItemTable",
688 "getAvgItemT", "cacheAvgT", "itemAvgT", "aCaT", "aIT"};
689 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
690}
691
692inline bool is_loss_metric_method(const std::string& m) {
693 static const char* kTab[] = {"getAvgLossTable", "getAvgLossT", "getAvgRegionLossTable",
694 "getAvgRegionLossT", "lossAvgT", "regionLossAvgT",
695 "aLT", "aRLT"};
696 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
697}
698
699inline bool is_orbit_metric_method(const std::string& m) {
700 static const char* kTab[] = {"getAvgOrbitTable", "getAvgOrbitT", "getAvgOrbit", "orbitAvgT",
701 "aOT"};
702 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
703}
704
705inline bool is_moment_method(const std::string& m) {
706 static const char* kTab[] = {"getMomentTable", "getMomentChainTable", "getMomentStationTable",
707 "getMomentT", "getMomentChainT", "getMomentStationT",
708 "momentT", "momentChainT", "momentStationT",
709 "mT", "mCT", "mST"};
710 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
711}
712
713inline bool is_sens_method(const std::string& m) {
714 static const char* kTab[] = {"getSensitivityTable", "getSensitivityT", "sensitivityT", "sT",
715 "supportsExactSensitivity"};
716 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
717}
718
719} // namespace detail
720
721/**
722 * `chooseNetworkSolver` in chooseSolverHeur.m: the getter names the metric
723 * family, the family names a ranking, and the average family alone consults the
724 * feature tree. A ranking that yields nothing falls back to the average
725 * heuristic, as the reference does, rather than refusing.
726 */
727template <class T>
728AutoChoice auto_choose_solver_heur(const qn::NetworkStruct<T>& sn, const std::string& method) {
730 if (detail::is_avg_method(method)) return auto_choose_avg_solver_ex(sn);
731 if (detail::is_ensemble_method(method))
732 throw InputError(ErrorKind{"Argument"}, "SolverAUTO: method '" + method +
733 "' is only available for LayeredNetwork models");
734
735 std::vector<AutoSolver> order;
736 if (method == "getTranAvg") {
738 } else if (detail::is_cdf_method(method)) {
739 // NC gives the exact passage-time distribution on FCFS product form;
740 // otherwise Fluid is the smooth approximation, then the simulators.
741 if (sn.has_homogeneous_scheduling(SchedStrategy::FCFS) && sn.has_product_form())
743 else
745 } else if (method == "getTranCdfPassT" || method == "getTranCdfRespT") {
747 } else if (detail::is_tran_prob_method(method)) {
748 order = {AutoSolver::CTMC};
749 } else if (detail::is_sample_method(method)) {
751 } else if (detail::is_sample_aggr_method(method)) {
753 } else if (detail::is_prob_method(method)) {
754 if (sn.has_product_form())
756 else
758 } else if (detail::is_cache_metric_method(method)) {
761 } else if (detail::is_loss_metric_method(method)) {
763 } else if (detail::is_orbit_metric_method(method)) {
765 } else if (detail::is_moment_method(method)) {
767 } else if (detail::is_sens_method(method)) {
769 } else {
771 }
772
773 AutoChoice out;
774 if (auto_ranked(order, sn, std::string(), out)) return out;
775 // No solver in the metric's ranking supports the model: the average
776 // heuristic is the floor.
778}
779
780/** `chooseSolverExact`: the ranking restricted to solvers that answer exactly. */
781template <class T>
782AutoChoice auto_choose_solver_exact(const qn::NetworkStruct<T>& sn, const std::string& method) {
783 const AutoTraits t = auto_traits(sn);
784 std::vector<AutoSolver> order;
785 if (detail::is_tran_prob_method(method) || detail::is_cdf_method(method) ||
786 method == "getTranCdfPassT" || method == "getTranCdfRespT" || method == "getTranAvg") {
787 order = {AutoSolver::CTMC};
788 } else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
789 // A sample path is exact in distribution, not in the mean.
791 } else if (detail::is_prob_method(method)) {
793 else order = {AutoSolver::CTMC};
794 } else {
797 else
799 // chooseSolverExact.m: an open class leaves CTMC no finite chain to solve exactly, and the exact
800 // open product-form answer is MVA's, so it leads the ranking there.
801 bool any_open = false;
802 for (const qn::JobClass& c : sn.classes)
803 if (std::isinf(c.population)) any_open = true;
804 if (any_open) {
805 order.erase(std::remove(order.begin(), order.end(), AutoSolver::CTMC), order.end());
806 if (t.is_product_form) order.insert(order.begin(), AutoSolver::MVA);
807 }
808 }
809 AutoChoice out;
810 if (auto_ranked(order, sn, "exact", out)) return out;
811 throw detail::no_solver(" exactly for method '" + method +
812 "'; use the default heuristic for the approximation",
813 order);
814}
815
816/** `chooseSolverSim`: the ranking restricted to simulators. */
817template <class T>
818AutoChoice auto_choose_solver_sim(const qn::NetworkStruct<T>& sn, const std::string& method) {
819 const std::vector<AutoSolver> order =
820 detail::is_sample_method(method)
821 ? std::vector<AutoSolver>{AutoSolver::SSA, AutoSolver::LDES}
822 : std::vector<AutoSolver>{AutoSolver::LDES, AutoSolver::SSA};
823 AutoChoice out;
824 if (auto_ranked(order, sn, std::string(), out)) return out;
825 throw detail::no_solver(" by simulation", order);
826}
827
828/**
829 * `chooseSolver`: the selection mode picks the ranking, and every mode but the
830 * two learned ones keeps the heuristic as its floor.
831 */
832template <class T>
834 AutoMode mode) {
835 AutoChoice out;
836 switch (mode) {
837 case AutoMode::EXACT: return auto_choose_solver_exact(sn, method);
838 case AutoMode::SIM: return auto_choose_solver_sim(sn, method);
839 case AutoMode::FAST:
840 // Cheapest analytical answer; the heuristic is the floor for
841 // metrics no mean-value solver can serve.
843 sn, std::string(), out))
844 return out;
845 return auto_choose_solver_heur(sn, method);
847 // A smooth or matrix-analytic answer preferred over the fastest.
850 sn, std::string(), out))
851 return out;
852 return auto_choose_solver_heur(sn, method);
853 case AutoMode::HEUR: break;
854 }
855 return auto_choose_solver_heur(sn, method);
856}
857
858/** The heuristic Network arm, by getter name. */
859template <class T>
860AutoSolver auto_choose_solver(const qn::NetworkStruct<T>& sn, const std::string& method) {
861 return auto_choose_solver_heur(sn, method).solver;
862}
863
864/**
865 * `delegate`'s proposed order: the chosen solver, then every feasible candidate
866 * in slot order. Duplicates are dropped, since the reference retries the chosen
867 * solver only once in practice.
868 */
869template <class T>
870std::vector<AutoSolver> auto_proposed_solvers(const qn::NetworkStruct<T>& sn,
871 const std::string& method, AutoMode mode) {
872 const AutoChoice chosen = auto_choose_solver_mode(sn, method, mode);
873 std::vector<AutoSolver> out(1, chosen.solver);
874 const std::vector<AutoSolver> cand = auto_candidates(sn);
875 for (std::size_t i = 0; i < cand.size(); ++i)
876 if (std::find(out.begin(), out.end(), cand[i]) == out.end()) out.push_back(cand[i]);
877 return out;
878}
879
880// ---------------------------------------------------------------------------
881// The LayeredNetwork and Environment arms
882// ---------------------------------------------------------------------------
883
886 std::vector<AutoLayered> skipped;
887};
888
889namespace detail {
890
891inline bool layered_ranked(const std::vector<AutoLayered>& order, AutoLayeredChoice& out) {
892 std::vector<AutoLayered> skipped;
893 for (std::size_t k = 0; k < order.size(); ++k) {
894 if (!auto_layered_is_available(order[k])) {
895 skipped.push_back(order[k]);
896 continue;
897 }
898 out.solver = order[k];
899 out.skipped = skipped;
900 return true;
901 }
902 return false;
903}
904
905} // namespace detail
906
907/**
908 * `chooseLayeredSolver` plus the LayeredNetwork arm of `chooseAvgSolverHeur`.
909 *
910 * There is no feature-set gate on this path: the reference gates a layered
911 * candidate with `SolverLN.supports(model)`, a LayeredNetwork-level check this
912 * port does not carry, so availability is the only screen. A cache task is what
913 * inverts the analytical order -- the cache layer is where NC beats MVA.
914 */
915inline AutoLayeredChoice auto_choose_layered_solver(const std::string& method,
916 bool has_cache_task,
917 AutoMode mode = AutoMode::HEUR) {
919 std::vector<AutoLayered> order;
920 if (mode == AutoMode::EXACT) {
921 // No layered solver is exact; NC layers are the closest available.
923 } else if (mode == AutoMode::SIM) {
924 // lqsim is the layered simulator; the LN solvers are the fallback.
926 } else if (method == "getTranAvg" || detail::is_cdf_method(method) ||
927 method == "getTranCdfPassT" || method == "getTranCdfRespT") {
929 } else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
931 } else if (detail::is_prob_method(method) || detail::is_tran_prob_method(method)) {
933 } else if (detail::is_ensemble_method(method)) {
936 } else if (has_cache_task) {
937 // The LayeredNetwork arm of chooseAvgSolverHeur, which returns the NC
938 // layer solver outright on a cache task.
940 return out;
941 } else {
944 }
945 if (has_cache_task && mode != AutoMode::SIM) {
946 // A layered cache model needs the NC layer solver whichever metric was
947 // asked for: it is the only one that solves the cache layer.
948 std::vector<AutoLayered> promoted(1, AutoLayered::LN_NC);
949 for (std::size_t i = 0; i < order.size(); ++i)
950 if (order[i] != AutoLayered::LN_NC) promoted.push_back(order[i]);
951 order = promoted;
952 }
953 if (detail::layered_ranked(order, out)) return out;
954 throw UnsupportedError(ErrorKind{"Method"},
955 "SolverAUTO: no LayeredNetwork solver in this port serves method '" + method +
956 "'; the reference's ranking is served by SolverLQNS, which shells out to an external "
957 "binary this port does not wrap");
958}
959
962 std::vector<AutoEnv> skipped;
963 /**
964 * `delegate.m`'s proposed list: the chosen ensemble, then every candidate in
965 * slot order (MVA, NC, FLUID) not already on it. The mean-field coupling
966 * refuses an MVA or NC ensemble for want of a transient, so an EXACT run,
967 * ranked [NC, MVA], still ends on the fluid ensemble as the reference's does.
968 */
969 std::vector<AutoEnv> retry;
970};
971
972/**
973 * The Environment arm of `chooseSolverHeur` / `chooseSolverExact` /
974 * `chooseSolverSim`.
975 *
976 * Fluid leads the heuristic ranking because the blending method is transient:
977 * it restarts each stage from the mean state the previous one left, and an
978 * inner solver without a transient analysis is refused by that coupling.
979 */
980inline AutoEnvChoice auto_choose_env_solver(const std::string& method,
981 AutoMode mode = AutoMode::HEUR) {
982 std::vector<AutoEnv> order;
983 if (mode == AutoMode::EXACT) order = {AutoEnv::ENV_NC, AutoEnv::ENV_MVA};
986
987 AutoEnvChoice out;
988 std::vector<AutoEnv> skipped;
989 for (std::size_t k = 0; k < order.size(); ++k) {
990 if (!auto_env_is_available(order[k])) {
991 skipped.push_back(order[k]);
992 continue;
993 }
994 out.solver = order[k];
995 out.skipped = skipped;
996 out.retry.push_back(order[k]);
998 for (std::size_t j = 0; j < 3; ++j)
999 if (slots[j] != order[k] && auto_env_is_available(slots[j]))
1000 out.retry.push_back(slots[j]);
1001 return out;
1002 }
1003 throw UnsupportedError(ErrorKind{"Method"},
1004 "SolverAUTO: no Environment stage solver in this port serves method '" + method + "'");
1005}
1006
1007} // namespace autosolver
1008} // namespace line
1009
1010#endif // LINE_SOLVERS_AUTO_SOLVER_AUTO_H
InputError(const std::string &what)
Definition error.h:105
UnsupportedError(const std::string &what)
Definition error.h:119
A subset of the registry: MATLAB's SolverFeatureSet, whose list is a flag per field.
A network plus its refreshed NetworkStruct.
Worst-case log-size of the CTMC state space induced by a NetworkStruct.
The exception types the port throws.
Where the LDES engine is, and whether this machine can run it.
Is a usable lqns installed on this machine?
bool auto_supports(AutoSolver s, const qn::NetworkStruct< T > &sn, const std::string &token)
Solver.supports(model) for a candidate slot, tightened by the two method-level rules a flat feature s...
bool auto_layered_is_available(AutoLayered s)
Every LN layer engine the ranking names is one --layer-solver takes (mva, nc, fluid,...
AutoEnvChoice auto_choose_env_solver(const std::string &method, AutoMode mode=AutoMode::HEUR)
The Environment arm of chooseSolverHeur / chooseSolverExact / chooseSolverSim.
AutoSolver
The Network candidate slots, in the reference's slot order (SolverAUTO.m:41-50), which is also the or...
bool auto_ctmc_is_tractable(const qn::NetworkStruct< T > &sn, double cutoff=-1.0)
double auto_ctmc_state_space_logsize(const qn::NetworkStruct< T > &sn, double cutoff=-1.0)
Worst-case log-size of the CTMC state space induced by sn: the SAME estimator SolverCTMC's memory gat...
AutoChoice auto_choose_solver_exact(const qn::NetworkStruct< T > &sn, const std::string &method)
chooseSolverExact: the ranking restricted to solvers that answer exactly.
AutoSolver auto_choose_solver(const qn::NetworkStruct< T > &sn, const std::string &method)
The heuristic Network arm, by getter name.
std::string auto_family_alias(const std::string &name)
SolverAUTO.familyAlias: the canonical family of a method name, or "" when the method name names none.
AutoTraits auto_traits(const qn::NetworkStruct< T > &sn)
bool auto_solver_is_available(AutoSolver s)
True when this port has an engine behind the slot at all.
bool auto_is_selection_intent(const std::string &token)
SolverAUTO.selectionIntents, less 'bound'.
const double kAutoExactPopulationMax
Population at or below which an exact solver is preferred (EXACT_POPULATION_MAX).
AutoSolver auto_choose_avg_solver(const qn::NetworkStruct< T > &sn)
AutoChoice auto_choose_avg_solver_ex(const qn::NetworkStruct< T > &sn)
chooseAvgSolverHeur, the Network arm: exact first at small populations, then the trait-keyed ranking,...
AutoLayeredChoice auto_choose_layered_solver(const std::string &method, bool has_cache_task, AutoMode mode=AutoMode::HEUR)
chooseLayeredSolver plus the LayeredNetwork arm of chooseAvgSolverHeur.
AutoMode auto_mode_of_token(const std::string &token)
const char * auto_env_stage_solver(AutoEnv s)
The EnvOptions::stage_solver (and --stage-solver) name of a candidate ensemble.
const double kAutoCtmcStateCap
The cap reachable_space_generator enforces (solver_ctmc.h, maxst).
AutoChoice auto_choose_solver_sim(const qn::NetworkStruct< T > &sn, const std::string &method)
chooseSolverSim: the ranking restricted to simulators.
bool auto_env_is_available(AutoEnv)
All three candidate ensembles are built here (EnvOptions::stage_solver = fluid, mva,...
const char * auto_solver_name(AutoSolver s)
AutoToken auto_resolve_token(const std::string &raw, const qn::NetworkStruct< T > &sn)
resolveMethodToken with its third arm: an intent, a family[.submethod], or else a BARE method name,...
std::vector< AutoSolver > auto_candidates(const qn::NetworkStruct< T > &sn)
The candidate pool in slot order, filtered by supports: what the delegate retries through after the c...
std::vector< AutoSolver > auto_proposed_solvers(const qn::NetworkStruct< T > &sn, const std::string &method, AutoMode mode)
delegate's proposed order: the chosen solver, then every feasible candidate in slot order.
AutoChoice auto_choose_solver_mode(const qn::NetworkStruct< T > &sn, const std::string &method, AutoMode mode)
chooseSolver: the selection mode picks the ranking, and every mode but the two learned ones keeps the...
const char * auto_env_name(AutoEnv s)
AutoChoice auto_choose_solver_heur(const qn::NetworkStruct< T > &sn, const std::string &method)
chooseNetworkSolver in chooseSolverHeur.m: the getter names the metric family, the family names a ran...
bool auto_ranked(const std::vector< AutoSolver > &order, const qn::NetworkStruct< T > &sn, const std::string &token, AutoChoice &out)
chooseSolverRanked: the first slot in ORDER that exists and accepts the model.
AutoEnv
The Environment candidate slots (SolverAUTO.m:58-60).
AutoLayered
The LayeredNetwork candidate slots (SolverAUTO.m:52-56).
AutoMode
The selection intents of SolverAUTO.selectionIntents, less 'bound'.
const char * auto_layered_name(AutoLayered s)
The layered names are the CLI's own tokens, because that is what the choice is spent on: ln....
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:185
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
Definition lang_types.h:490
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:331
bool ldes_is_available()
True when this machine can run the engine at all, by either image.
Definition ldes_probe.h:253
bool lqns_is_available()
True when lqns is installed AND is a release this port speaks.
Definition lqns_probe.h:69
double ctmc_state_space_logsize(const qn::NetworkStruct< T > &sn, const CtmcSizeOptions &opt=CtmcSizeOptions())
Worst-case log state-space size of sn.
FeatureSet ldes_feature_set(const std::string &)
SolverLDES.getFeatureSet, transcribed WHOLE.
FeatureSet fluid_feature_set(const std::string &method)
FeatureSet ssa_feature_set(const std::string &method)
FeatureSet used_lang_features(const NetworkStruct< T > &sn)
FeatureSet nc_feature_set(const std::string &method)
SolverNC.getFeatureSet, 48 names, transcribed unchanged.
FeatureSet fluid_class_feature_set()
SolverFLD.getFeatureSet, transcribed, MINUS what the requested method cannot evaluate – the port of @...
FeatureSet ctmc_feature_set(const std::string &method)
SolverCTMC.getFeatureSet, the reference's 104 MATLAB names in full.
SupportResult feature_set_supports(const std::string &solver, const FeatureSet &declared, const FeatureSet &used)
SolverFeatureSet.supports: is every feature the model uses declared?
FeatureSet mam_feature_set(const std::string &method)
SolverMAM.getFeatureSet, the union of its four setTrue calls: 55 MATLAB names, WIDENED for 'default'/...
FeatureSet mva_feature_set(const std::string &raw_method)
SolverMVA.getFeatureSet, plus getMethodFeatureSet's per-method deltas.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
SolverSSA SSA
Definition solver.h:549
SolverMAM MAM
Definition solver.h:552
SolverLDES LDES
Definition solver.h:554
SolverNC NC
Definition solver.h:547
SolverCTMC CTMC
Definition solver.h:548
SolverMVA MVA
Definition solver.h:546
A queueing network and its refreshed NetworkStruct.
The DECLARED side of the gate: one feature set per solver.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
What a ranking resolved to, and what it had to skip to get there.
std::string method
The method the choice was gated on: "" for the default, "exact".
std::vector< AutoSolver > skipped
Slots that outranked solver and have no engine in this port.
std::vector< AutoEnv > retry
delegate.m's proposed list: the chosen ensemble, then every candidate in slot order (MVA,...
std::vector< AutoEnv > skipped
std::vector< AutoLayered > skipped
resolveMethodToken, minus the unqualified-algorithm-name arm.
std::string submethod
the method handed to the family, "default" when bare
std::string family
empty when is_intent
The structural traits the rankings are keyed on.
std::string prio
"none", "preempt", "ps" or "hol", in the reference's own precedence.
Options the estimator reads; only the cutoff matters.
double cutoff
< 0 or non-finite = not given, take the solver default
One job class of the network.
double population
infinite for an open class
A node of the network.
One station of the network.
SchedStrategy sched