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 COLLAPSES. `has_homogeneous_scheduling`
73 * reproduces the reference's findstring defect and degenerates to
74 * `nstations == 1` for every discipline (see NetworkStruct). Only the
75 * response-time-CDF branch and one avgOrder arm consult it now, and both are
76 * written as the reference writes them.
77 *
78 */
79
80#include <algorithm>
81#include <cmath>
82#include <cstddef>
83#include <string>
84#include <vector>
85
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 * The LN layer engines are the four `--layer-solver` takes (mva, nc, fluid,
169 * ssa), so there is no MAM-layer engine to select.
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 s != AutoLayered::LN_MAM;
180}
181
182/**
183 * SolverENV solves a stage with the fluid analyzer under every coupling but the
184 * state-vector one, which uniformizes a CTMC; neither an MVA nor an NC stage
185 * solver exists here, so those two slots have no engine.
186 */
187inline bool auto_env_is_available(AutoEnv s) { return s == AutoEnv::ENV_FLUID; }
188
189// ---------------------------------------------------------------------------
190// Method method names: a selection intent, or a method family
191// ---------------------------------------------------------------------------
192
193/**
194 * `SolverAUTO.selectionIntents`, less 'bound'.
195 *
196 * 'bound' IS accepted, but it is not a ranking mode: the reference sets
197 * selectionMode='bound' and options.method='auto', i.e. it names SolverBA
198 * outright. `auto_resolve_token` therefore rewrites it to the 'ba' family
199 * before this test runs, which is why it is absent here.
200 */
201inline bool auto_is_selection_intent(const std::string& token) {
202 return token.empty() || token == "default" || token == "auto" || token == "heur" ||
203 token == "sim" || token == "exact" || token == "fast" ||
204 token == "accurate";
205}
206
207inline AutoMode auto_mode_of_token(const std::string& token) {
208 if (token.empty() || token == "default" || token == "auto" || token == "heur")
209 return AutoMode::HEUR;
210 if (token == "exact") return AutoMode::EXACT;
211 if (token == "sim") return AutoMode::SIM;
212 if (token == "fast") return AutoMode::FAST;
213 if (token == "accurate") return AutoMode::ACCURATE;
214 throw InputError("SolverAUTO: '" + token + "' is not a selection intent");
215}
216
217/**
218 * `SolverAUTO.familyAlias`: the canonical family of a method name, or "" when the
219 * method name names none. A family method name is not a selection intent -- it asks for a
220 * named engine and bypasses the ranking, as `-s auto --method nc.comom` does.
221 */
222inline std::string auto_family_alias(const std::string& name) {
223 if (name == "mam") return "mam";
224 if (name == "ag") return "ag";
225 if (name == "mva") return "mva";
226 if (name == "nc") return "nc";
227 if (name == "fluid" || name == "fld") return "fld";
228 if (name == "jmt") return "jmt";
229 if (name == "ssa") return "ssa";
230 if (name == "ctmc") return "ctmc";
231 if (name == "ldes" || name == "des") return "ldes";
232 if (name == "ba") return "ba";
233 if (name == "env") return "env";
234 if (name == "ln") return "ln";
235 if (name == "lqns" || name == "lqsim") return "lqns";
236 if (name == "qns") return "lqns";
237 if (name == "uq") return "uq";
238 return "";
239}
240
241/**
242 * `resolveMethodToken`, minus the unqualified-algorithm-name arm.
243 *
244 * A method name is an intent, or `family[.submethod]`. The reference has a third form
245 * -- a bare algorithm name such as `comom`, resolved by asking every family for
246 * its `listValidMethods` -- which needs a per-solver method-name registry this
247 * port does not have; it is refused by name here, with the qualified spelling
248 * in the message, rather than guessed at.
249 */
250struct AutoToken {
251 bool is_intent = true;
253 std::string family; ///< empty when is_intent
254 std::string submethod; ///< the method handed to the family, "default" when bare
255};
256
257inline AutoToken auto_resolve_token(const std::string& raw) {
258 AutoToken t;
259 const std::string token = raw.empty() ? std::string("default") : raw;
260 // 'bound' is a selection intent in the reference's own list, and it selects
261 // SolverBA with method 'auto' rather than picking a ranking
262 // (SolverAUTO.m:116-121). Rewriting it to the family method name here reuses the
263 // family path below and keeps the intent spelling usable, which is what the
264 // other three codebases accept.
265 if (token == "bound") {
266 t.is_intent = false;
267 t.family = "ba";
268 t.submethod = "auto";
269 return t;
270 }
271 if (auto_is_selection_intent(token)) {
272 t.is_intent = true;
273 t.mode = auto_mode_of_token(token);
274 return t;
275 }
276 const std::size_t dot = token.find('.');
277 const std::string head = dot == std::string::npos ? token : token.substr(0, dot);
278 const std::string rest = dot == std::string::npos ? std::string() : token.substr(dot + 1);
279 // bare "fluid" is an input alias of the "fld" family; the qualified spelling is "fld.<m>" only
280 const std::string fam =
281 (!rest.empty() && head == "fluid") ? std::string() : auto_family_alias(head);
282 if (fam.empty())
283 throw InputError(
284 "SolverAUTO: '" + token +
285 "' is neither a selection intent (default, heur, exact, sim, fast, accurate, "
286 "bound) "
287 "nor a method family (mva, nc, ctmc, fld, mam, ag, ba, ssa, ldes, jmt, ln, "
288 "env, lqns, uq). A bare algorithm name must be qualified by its family here, as in "
289 "'nc.comom': resolving it needs the per-family method registry "
290 "(listValidMethods) that this port does not carry");
291 t.is_intent = false;
292 t.family = fam;
293 t.submethod = rest.empty() ? std::string("default") : rest;
294 // `qns` and `qns.NAME` are SolverLQNS method names on a flat Network, so the
295 // whole token is the submethod and `lqns` the family.
296 if (head == "qns") t.submethod = token;
297 return t;
298}
299
300// ---------------------------------------------------------------------------
301// solverTraits.m
302// ---------------------------------------------------------------------------
303
304/** The structural traits the rankings are keyed on. Port of `solverTraits.m`. */
306 bool has_cache = false;
307 bool has_fcr = false;
308 bool has_fork = false;
309 bool has_map = false;
310 /** "none", "preempt", "ps" or "hol", in the reference's own precedence. */
311 std::string prio = "none";
312 bool is_closed = false;
313 bool is_open = false;
314 bool is_mixed = false;
315 bool has_multi_server = false;
316 bool is_product_form = false;
317 double pop_per_chain = 0.0;
318 double total_jobs = 0.0;
319 bool single_chain = false;
320};
321
322template <class T>
324 using qn::NodeType;
325 using lang::ProcessType;
327
328 AutoTraits t;
329 for (const qn::NodeDef& nd : sn.nodes)
330 if (nd.nodetype == NodeType::Cache) t.has_cache = true;
331 t.has_fcr = !sn.regions.empty();
332 t.has_fork = sn.has_fork();
333 t.has_multi_server = sn.has_multi_server();
334 t.is_product_form = sn.has_product_form();
335 t.single_chain = sn.nchains == 1;
336
337 bool has_open = false, has_closed = false;
338 for (const qn::JobClass& c : sn.classes) {
339 if (std::isinf(c.population)) has_open = true;
340 else has_closed = true;
341 }
342 t.is_open = has_open && !has_closed;
343 t.is_closed = has_closed && !has_open;
344 t.is_mixed = has_open && has_closed;
345
346 t.total_jobs = sn.total_jobs();
347 if (sn.nchains > 0) t.pop_per_chain = t.total_jobs / static_cast<double>(sn.nchains);
348
349 // Autocorrelated arrival or service: only MAM keeps the correlation, every
350 // other analytical solver sees the marginal only.
351 for (std::size_t i = 1; i <= sn.nstations && !t.has_map; ++i)
352 for (std::size_t r = 1; r <= sn.nclasses; ++r) {
353 const ProcessType p = sn.procid(i, r);
354 if (p == ProcessType::MAP || p == ProcessType::MMPP2) {
355 t.has_map = true;
356 break;
357 }
358 }
359
360 // Preemptive priority is a strictly narrower capability than HOL, so the
361 // two rank differently and are distinguished here rather than downstream.
362 bool preempt = false, psprio = false, hol = false;
363 for (const qn::Station<T>& s : sn.stations) {
364 if (s.sched == SchedStrategy::FCFSPRPRIO || s.sched == SchedStrategy::FCFSPIPRIO ||
365 s.sched == SchedStrategy::LCFSPRPRIO || s.sched == SchedStrategy::LCFSPIPRIO)
366 preempt = true;
367 if (s.sched == SchedStrategy::PSPRIO || s.sched == SchedStrategy::DPSPRIO ||
368 s.sched == SchedStrategy::GPSPRIO)
369 psprio = true;
370 if (s.sched == SchedStrategy::HOL || s.sched == SchedStrategy::LCFSPRIO ||
371 s.sched == SchedStrategy::SRPTPRIO)
372 hol = true;
373 }
374 if (preempt) t.prio = "preempt";
375 else if (psprio) t.prio = "ps";
376 else if (hol) t.prio = "hol";
377 return t;
378}
379
380// ---------------------------------------------------------------------------
381// The CTMC screen: ctmc_state_space_logsize.m
382// ---------------------------------------------------------------------------
383
384/**
385 * Worst-case log-size of the CTMC state space induced by `sn`, summed in log
386 * space over the reference's four factors: job placements (stars and bars, per
387 * class, open classes truncated at the cutoff, over the stations that keep no
388 * ordered buffer), the class-sequence multiplicity of every order-preserving
389 * buffer, service phases, and one routing pointer per round-robin (node,
390 * class).
391 *
392 * `cutoff` negative selects the analyzer's own default for open and mixed
393 * models, `ceil(6000^(1/(M*K)))`.
394 *
395 * MATLAB reads `sn.phasessz`; this port reads `phases_of`, which is the same
396 * quantity floored at one -- a single-phase representation contributes no
397 * factor either way.
398 */
399template <class T>
400double auto_ctmc_state_space_logsize(const qn::NetworkStruct<T>& sn, double cutoff = -1.0) {
403
404 const std::size_t M = sn.nstations, K = sn.nclasses;
405 if (M == 0 || K == 0) return 0.0;
406 if (!(cutoff > 0.0))
407 cutoff = std::ceil(std::pow(6000.0, 1.0 / static_cast<double>(M * K)));
408
409 const auto is_share = [](SchedStrategy s) {
410 return s == SchedStrategy::INF || s == SchedStrategy::PS || s == SchedStrategy::DPS ||
411 s == SchedStrategy::GPS || s == SchedStrategy::PSPRIO ||
412 s == SchedStrategy::DPSPRIO || s == SchedStrategy::GPSPRIO ||
413 s == SchedStrategy::LPS;
414 };
415 std::vector<bool> is_buffered(K, true);
416 std::size_t Kb = 0;
417 for (std::size_t r = 0; r < K; ++r) {
418 if (r < sn.issignal.size() && sn.issignal[r]) is_buffered[r] = false;
419 if (is_buffered[r]) ++Kb;
420 }
421 std::size_t n_ord = 0;
422 if (Kb > 1) {
423 for (std::size_t i = 0; i < M; ++i) {
424 const SchedStrategy s = sn.stations[i].sched;
425 if (s == SchedStrategy::EXT || is_share(s)) continue;
426 ++n_ord;
427 }
428 }
429 const std::size_t m_place = (M > n_ord) ? (M - n_ord) : 0;
430
431 double log_n = 0.0;
432 std::vector<double> nk_eff(K, 0.0);
433 for (std::size_t r = 0; r < K; ++r) {
434 const double nk = std::isinf(sn.classes[r].population) ? cutoff : sn.classes[r].population;
435 nk_eff[r] = nk;
436 if (m_place >= 1) {
437 const double m = static_cast<double>(m_place);
438 log_n += std::lgamma(1.0 + nk + m - 1.0) - std::lgamma(m) - std::lgamma(1.0 + nk);
439 }
440 }
441
442 if (n_ord > 0) {
443 double tot_jobs = 0.0;
444 for (std::size_t r = 0; r < K; ++r)
445 if (is_buffered[r]) tot_jobs += nk_eff[r];
446 const double log_k = std::log(static_cast<double>(Kb));
447 const double log_seq = (tot_jobs + 1.0) * log_k - std::log(static_cast<double>(Kb) - 1.0) +
448 std::log1p(-std::exp(-(tot_jobs + 1.0) * log_k));
449 log_n += static_cast<double>(n_ord) * log_seq;
450 }
451
452 for (std::size_t i = 1; i <= M; ++i) {
453 const SchedStrategy sched = sn.stations[i - 1].sched;
454 const bool shares = sched == SchedStrategy::INF || sched == SchedStrategy::PS ||
455 sched == SchedStrategy::DPS || sched == SchedStrategy::GPS ||
456 sched == SchedStrategy::PSPRIO || sched == SchedStrategy::DPSPRIO ||
457 sched == SchedStrategy::GPSPRIO || sched == SchedStrategy::LPS;
458 for (std::size_t r = 1; r <= K; ++r) {
459 const double p = static_cast<double>(std::max<std::size_t>(sn.phases_of(i, r), 1));
460 if (p <= 1.0) continue;
461 double m;
462 if (sched == SchedStrategy::EXT) m = 1.0;
463 else if (shares) m = nk_eff[r - 1];
464 else m = std::min(nk_eff[r - 1], sn.stations[i - 1].nservers);
465 if (!std::isfinite(m)) m = nk_eff[r - 1];
466 log_n += std::lgamma(1.0 + m + p - 1.0) - std::lgamma(p) - std::lgamma(1.0 + m);
467 }
468 }
469
470 // The routing pointers. MATLAB counts the out-degree from `sn.connmatrix`,
471 // which this port does not carry; `rtnodes` is the same graph after the
472 // refresh has resolved the strategies, and is what every other consumer
473 // here reads for the same purpose (see NetworkStruct::downstream_stations).
474 const std::size_t N = sn.nodes.size(), R = sn.nclasses;
475 if (sn.rtnodes.rows() >= N * R) {
476 for (std::size_t i = 1; i <= N; ++i) {
477 std::size_t nout = 0;
478 for (std::size_t j = 1; j <= N; ++j) {
479 bool linked = false;
480 for (std::size_t r = 0; r < R && !linked; ++r)
481 for (std::size_t s = 0; s < R && !linked; ++s)
483 sn.rtnodes((i - 1) * R + r, (j - 1) * R + s)) > 0.0)
484 linked = true;
485 if (linked) ++nout;
486 }
487 if (nout <= 1) continue;
488 std::size_t nrr = 0;
489 const std::vector<RoutingStrategy>& rt = sn.nodes[i - 1].routing;
490 for (std::size_t r = 0; r < rt.size(); ++r)
491 if (rt[r] == RoutingStrategy::RROBIN || rt[r] == RoutingStrategy::WRROBIN) ++nrr;
492 if (nrr > 0) log_n += static_cast<double>(nrr) * std::log(static_cast<double>(nout));
493 }
494 }
495 return log_n;
496}
497
498/**
499 * The cap `reachable_space_generator` enforces (solver_ctmc.h, `maxst`). It is
500 * this port's budget, standing in for the reference's memory model.
501 */
502const double kAutoCtmcStateCap = 3000000.0;
503
504template <class T>
505bool auto_ctmc_is_tractable(const qn::NetworkStruct<T>& sn, double cutoff = -1.0) {
506 return auto_ctmc_state_space_logsize(sn, cutoff) <= std::log(kAutoCtmcStateCap);
507}
508
509// ---------------------------------------------------------------------------
510// chooseSolverRanked.m
511// ---------------------------------------------------------------------------
512
513/**
514 * `Solver.supports(model)` for a candidate slot, tightened by the two
515 * method-level rules a flat feature set cannot express.
516 *
517 * `method name` is the method the slot would run: "" or "default" for the solver's
518 * own default, "exact" for the exactness-gated request.
519 */
520template <class T>
521bool auto_supports(AutoSolver s, const qn::NetworkStruct<T>& sn, const std::string& token) {
523 if (!auto_solver_is_available(s)) return false;
524 const std::string method = token.empty() ? std::string("default") : token;
525
526 qn::FeatureSet declared;
527 switch (s) {
528 case AutoSolver::MVA: declared = qn::mva_feature_set(method); break;
529 case AutoSolver::NC: declared = qn::nc_feature_set(method); break;
530 case AutoSolver::MAM: declared = qn::mam_feature_set(method); break;
531 case AutoSolver::FLUID: declared = qn::fluid_feature_set(method); break;
532 case AutoSolver::SSA: declared = qn::ssa_feature_set(method); break;
533 case AutoSolver::CTMC: declared = qn::ctmc_feature_set(method); break;
534 // The LDES slot is gated like any other now that an engine can stand
535 // behind it: `auto_solver_is_available` has already answered whether
536 // this machine has one, and what remains is the reference's own
537 // declaration, which the client honours by forwarding the model to the
538 // engine that implements it.
539 case AutoSolver::LDES: declared = qn::ldes_feature_set(method); break;
540 }
542 return false;
543
544 if (method == "exact" && (s == AutoSolver::MVA || s == AutoSolver::NC)) {
545 if (!sn.has_product_form()) {
546 // solver_mva_oi is exact for the order-independent and
547 // pass-and-swap stations whatever the product-form test says; NC
548 // has no such exemption.
549 bool oi = false;
550 if (s == AutoSolver::MVA)
551 for (const qn::Station<T>& st : sn.stations)
552 if (st.sched == SchedStrategy::OI || st.sched == SchedStrategy::PAS) oi = true;
553 if (!oi) return false;
554 }
555 }
556 if (s == AutoSolver::CTMC && !auto_ctmc_is_tractable(sn)) return false;
557 return true;
558}
559
560/** What a ranking resolved to, and what it had to skip to get there. */
563 /** Slots that outranked `solver` and have no engine in this port. */
564 std::vector<AutoSolver> skipped;
565 /** The method the choice was gated on: "" for the default, "exact". */
566 std::string method;
567};
568
569/**
570 * `chooseSolverRanked`: the first slot in ORDER that exists and accepts the
571 * model. Returns false when none qualifies, so the caller can fall back.
572 */
573template <class T>
574bool auto_ranked(const std::vector<AutoSolver>& order, const qn::NetworkStruct<T>& sn,
575 const std::string& token, AutoChoice& out) {
576 std::vector<AutoSolver> skipped;
577 for (std::size_t k = 0; k < order.size(); ++k) {
578 if (!auto_solver_is_available(order[k])) {
579 skipped.push_back(order[k]);
580 continue;
581 }
582 if (!auto_supports(order[k], sn, token)) continue;
583 out.solver = order[k];
584 out.skipped = skipped;
585 out.method = (token == "default") ? std::string() : token;
586 return true;
587 }
588 return false;
589}
590
591/**
592 * The candidate pool in slot order, filtered by `supports`: what the delegate
593 * retries through after the chosen solver fails (SolverAUTO.m:139-147).
594 */
595template <class T>
596std::vector<AutoSolver> auto_candidates(const qn::NetworkStruct<T>& sn) {
597 static const AutoSolver kSlots[] = {AutoSolver::MVA, AutoSolver::NC, AutoSolver::MAM,
600 std::vector<AutoSolver> out;
601 for (std::size_t i = 0; i < sizeof(kSlots) / sizeof(*kSlots); ++i)
602 if (auto_solver_is_available(kSlots[i]) && auto_supports(kSlots[i], sn, "default"))
603 out.push_back(kSlots[i]);
604 return out;
605}
606
607// ---------------------------------------------------------------------------
608// chooseAvgSolverHeur.m, the Network arm
609// ---------------------------------------------------------------------------
610
611namespace detail {
612
613/**
614 * `avgOrder` in chooseAvgSolverHeur.m, arm for arm and in its order. The arms
615 * OVERLAP, so the first match wins and reordering silently rehomes models.
616 *
617 * `homogeneous_inf` is the penultimate arm's predicate, passed in because it
618 * needs the struct and this table does not otherwise.
619 */
620inline std::vector<AutoSolver> avg_order(const AutoTraits& t, bool homogeneous_inf) {
621 if (t.has_cache)
624 if (t.has_fcr) {
625 if (t.total_jobs <= 10.0) return {AutoSolver::NC, AutoSolver::CTMC, AutoSolver::LDES};
627 }
628 if (t.prio == "preempt")
630 if (t.prio == "ps") return {AutoSolver::CTMC, AutoSolver::LDES, AutoSolver::SSA};
631 if (t.has_map)
633 if (t.prio == "hol")
636 if (t.pop_per_chain > 30.0) return {AutoSolver::FLUID, AutoSolver::MVA, AutoSolver::NC};
637 // No exact solver was available at this population (the exact-first pass
638 // above tried), so keep the exact-leaning approximate order.
639 if (t.total_jobs > 0.0 && t.total_jobs <= kAutoExactPopulationMax)
641 if (homogeneous_inf) return {AutoSolver::MVA, AutoSolver::NC, AutoSolver::FLUID};
643}
644
645inline UnsupportedError no_solver(const std::string& what,
646 const std::vector<AutoSolver>& skipped) {
647 std::string msg = "SolverAUTO: no solver supports this model" + what;
648 if (!skipped.empty()) {
649 msg += " (the ranking preferred ";
650 for (std::size_t i = 0; i < skipped.size(); ++i) {
651 if (i) msg += ", ";
652 msg += auto_solver_name(skipped[i]);
653 }
654 msg += ", which this port does not build)";
655 }
656 return UnsupportedError(msg);
657}
658
659} // namespace detail
660
661/**
662 * `chooseAvgSolverHeur`, the Network arm: exact first at small populations,
663 * then the trait-keyed ranking, then the whole pool in the global order.
664 *
665 * The INF branch of avgOrder consults `has_homogeneous_scheduling`, which is
666 * `nstations == 1` here and in MATLAB alike; see the header note.
667 */
668template <class T>
671 const AutoTraits t = auto_traits(sn);
672 AutoChoice out;
673
674 // Small populations: an approximation buys nothing there, so take an exact
675 // solver whenever one is available. The 'exact' method name is what makes this a
676 // claim rather than a preference -- MVA and NC reject it without a
677 // product-form solution, and CTMC is screened for a chain that fits.
678 if (t.total_jobs > 0.0 && t.total_jobs <= kAutoExactPopulationMax) {
679 const std::vector<AutoSolver> exact_order =
680 t.has_cache
681 ? std::vector<AutoSolver>{AutoSolver::NC, AutoSolver::MVA, AutoSolver::CTMC}
682 : std::vector<AutoSolver>{AutoSolver::MVA, AutoSolver::NC, AutoSolver::CTMC};
683 if (auto_ranked(exact_order, sn, "exact", out)) return out;
684 }
685
686 const std::vector<AutoSolver> order =
687 detail::avg_order(t, sn.has_homogeneous_scheduling(SchedStrategy::INF));
688 if (auto_ranked(order, sn, "default", out)) return out;
689
690 // Nothing in the ranked list is feasible: fall back to the whole pool in
691 // the global order rather than returning nothing.
692 const std::vector<AutoSolver> pool = {AutoSolver::MVA, AutoSolver::NC, AutoSolver::MAM,
695 if (auto_ranked(pool, sn, "default", out)) return out;
696 throw detail::no_solver("", pool);
697}
698
699template <class T>
703
704// ---------------------------------------------------------------------------
705// chooseSolverHeur.m, the Network arm
706// ---------------------------------------------------------------------------
707
708namespace detail {
709
710inline bool in_list(const std::string& m, const char* const* tab, std::size_t n) {
711 for (std::size_t i = 0; i < n; ++i)
712 if (m == tab[i]) return true;
713 return false;
714}
715
716/** The average-metric getters, which defer to the feature tree. */
717inline bool is_avg_method(const std::string& m) {
718 static const char* kAvg[] = {
719 "getAvgChainTable", "getAvgTputTable", "getAvgRespTTable", "getAvgUtilTable",
720 "getAvgSysTable", "getAvgNodeTable", "getAvgTable", "getAvgTableLayered", "getAvg",
721 "getAvgChain", "getAvgSys", "getAvgNode", "getAvgNodeChain", "getAvgArvRChain",
722 "getAvgQLenChain", "getAvgUtilChain", "getAvgRespTChain", "getAvgTputChain",
723 "getAvgSysRespT", "getAvgSysTput", "getAvgQLen", "getAvgUtil", "getAvgRespT",
724 "getAvgResidT", "getAvgWaitT", "getAvgTput", "getAvgArvR", "getAvgQLenTable",
725 "getAvgResidTChain", "getAvgNodeQLenChain", "getAvgNodeUtilChain",
726 "getAvgNodeRespTChain", "getAvgNodeResidTChain", "getAvgNodeTputChain",
727 "getAvgNodeArvRChain", "getAvgNodeChainTable", "getResults", "hasResults",
728 "getAvgHandles", "getTranHandles", "getAvgQLenHandles", "getAvgUtilHandles",
729 "getAvgRespTHandles", "getAvgTputHandles", "getAvgArvRHandles", "getAvgResidTHandles",
730 "getMethodFeatureSet", "supportsModelMethod", "isStochasticMethod", "libraries",
731 "showLibraryAttribution", "citations"};
732 return in_list(m, kAvg, sizeof(kAvg) / sizeof(*kAvg));
733}
734
735/** The ensemble getters, which exist for LayeredNetwork models only. */
736inline bool is_ensemble_method(const std::string& m) {
737 static const char* kEns[] = {"getEnsembleAvg", "getEnsembleAvgTables", "getSolver",
738 "setSolver", "getNumberOfModels", "getIteration",
739 "get_state", "set_state", "update_solver"};
740 return in_list(m, kEns, sizeof(kEns) / sizeof(*kEns));
741}
742
743inline bool is_cdf_method(const std::string& m) {
744 return m == "getCdfRespT" || m == "getCdfPassT" || m == "getPerctRespT";
745}
746
747inline bool is_tran_prob_method(const std::string& m) {
748 return m == "getTranProb" || m == "getTranProbSys" || m == "getTranProbAggr" ||
749 m == "getTranProbSysAggr";
750}
751
752inline bool is_prob_method(const std::string& m) {
753 return m == "getProb" || m == "getProbAggr" || m == "getProbSys" || m == "getProbSysAggr" ||
754 m == "getProbMarg" || m == "getProbNormConstAggr";
755}
756
757inline bool is_sample_method(const std::string& m) { return m == "sample" || m == "sampleSys"; }
758
759inline bool is_sample_aggr_method(const std::string& m) {
760 return m == "sampleAggr" || m == "sampleSysAggr";
761}
762
763inline bool is_cache_metric_method(const std::string& m) {
764 static const char* kTab[] = {"getAvgCacheTable", "getAvgCacheT", "getAvgItemTable",
765 "getAvgItemT", "cacheAvgT", "itemAvgT", "aCaT", "aIT"};
766 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
767}
768
769inline bool is_loss_metric_method(const std::string& m) {
770 static const char* kTab[] = {"getAvgLossTable", "getAvgLossT", "getAvgRegionLossTable",
771 "getAvgRegionLossT", "lossAvgT", "regionLossAvgT",
772 "aLT", "aRLT"};
773 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
774}
775
776inline bool is_orbit_metric_method(const std::string& m) {
777 static const char* kTab[] = {"getAvgOrbitTable", "getAvgOrbitT", "getAvgOrbit", "orbitAvgT",
778 "aOT"};
779 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
780}
781
782inline bool is_moment_method(const std::string& m) {
783 static const char* kTab[] = {"getMomentTable", "getMomentChainTable", "getMomentStationTable",
784 "getMomentT", "getMomentChainT", "getMomentStationT",
785 "momentT", "momentChainT", "momentStationT",
786 "mT", "mCT", "mST"};
787 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
788}
789
790inline bool is_sens_method(const std::string& m) {
791 static const char* kTab[] = {"getSensitivityTable", "getSensitivityT", "sensitivityT", "sT",
792 "supportsExactSensitivity"};
793 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
794}
795
796} // namespace detail
797
798/**
799 * `chooseNetworkSolver` in chooseSolverHeur.m: the getter names the metric
800 * family, the family names a ranking, and the average family alone consults the
801 * feature tree. A ranking that yields nothing falls back to the average
802 * heuristic, as the reference does, rather than refusing.
803 */
804template <class T>
805AutoChoice auto_choose_solver_heur(const qn::NetworkStruct<T>& sn, const std::string& method) {
807 if (detail::is_avg_method(method)) return auto_choose_avg_solver_ex(sn);
808 if (detail::is_ensemble_method(method))
809 throw InputError("SolverAUTO: method '" + method +
810 "' is only available for LayeredNetwork models");
811
812 std::vector<AutoSolver> order;
813 if (method == "getTranAvg") {
815 } else if (detail::is_cdf_method(method)) {
816 // NC gives the exact passage-time distribution on FCFS product form;
817 // otherwise Fluid is the smooth approximation, then the simulators.
818 if (sn.has_homogeneous_scheduling(SchedStrategy::FCFS) && sn.has_product_form())
820 else
822 } else if (method == "getTranCdfPassT" || method == "getTranCdfRespT") {
824 } else if (detail::is_tran_prob_method(method)) {
825 order = {AutoSolver::CTMC};
826 } else if (detail::is_sample_method(method)) {
828 } else if (detail::is_sample_aggr_method(method)) {
830 } else if (detail::is_prob_method(method)) {
831 if (sn.has_product_form())
833 else
835 } else if (detail::is_cache_metric_method(method)) {
838 } else if (detail::is_loss_metric_method(method)) {
840 } else if (detail::is_orbit_metric_method(method)) {
842 } else if (detail::is_moment_method(method)) {
844 } else if (detail::is_sens_method(method)) {
846 } else {
848 }
849
850 AutoChoice out;
851 if (auto_ranked(order, sn, "default", out)) return out;
852 // No solver in the metric's ranking supports the model: the average
853 // heuristic is the floor.
855}
856
857/** `chooseSolverExact`: the ranking restricted to solvers that answer exactly. */
858template <class T>
859AutoChoice auto_choose_solver_exact(const qn::NetworkStruct<T>& sn, const std::string& method) {
860 const AutoTraits t = auto_traits(sn);
861 std::vector<AutoSolver> order;
862 if (detail::is_tran_prob_method(method) || detail::is_cdf_method(method) ||
863 method == "getTranCdfPassT" || method == "getTranCdfRespT" || method == "getTranAvg") {
864 order = {AutoSolver::CTMC};
865 } else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
866 // A sample path is exact in distribution, not in the mean.
868 } else if (detail::is_prob_method(method)) {
870 else order = {AutoSolver::CTMC};
871 } else if (t.is_product_form && !t.has_multi_server) {
873 } else {
875 }
876 AutoChoice out;
877 if (auto_ranked(order, sn, "exact", out)) return out;
878 throw detail::no_solver(" exactly for method '" + method +
879 "'; use the default heuristic for the approximation",
880 order);
881}
882
883/** `chooseSolverSim`: the ranking restricted to simulators. */
884template <class T>
885AutoChoice auto_choose_solver_sim(const qn::NetworkStruct<T>& sn, const std::string& method) {
886 const std::vector<AutoSolver> order =
887 detail::is_sample_method(method)
888 ? std::vector<AutoSolver>{AutoSolver::SSA, AutoSolver::LDES}
889 : std::vector<AutoSolver>{AutoSolver::LDES, AutoSolver::SSA};
890 AutoChoice out;
891 if (auto_ranked(order, sn, "default", out)) return out;
892 throw detail::no_solver(" by simulation", order);
893}
894
895/**
896 * `chooseSolver`: the selection mode picks the ranking, and every mode but the
897 * two learned ones keeps the heuristic as its floor.
898 */
899template <class T>
901 AutoMode mode) {
902 AutoChoice out;
903 switch (mode) {
904 case AutoMode::EXACT: return auto_choose_solver_exact(sn, method);
905 case AutoMode::SIM: return auto_choose_solver_sim(sn, method);
906 case AutoMode::FAST:
907 // Cheapest analytical answer; the heuristic is the floor for
908 // metrics no mean-value solver can serve.
910 sn, "default", out))
911 return out;
912 return auto_choose_solver_heur(sn, method);
914 // A smooth or matrix-analytic answer preferred over the fastest.
917 sn, "default", out))
918 return out;
919 return auto_choose_solver_heur(sn, method);
920 case AutoMode::HEUR: break;
921 }
922 return auto_choose_solver_heur(sn, method);
923}
924
925/** The heuristic Network arm, by getter name. */
926template <class T>
927AutoSolver auto_choose_solver(const qn::NetworkStruct<T>& sn, const std::string& method) {
928 return auto_choose_solver_heur(sn, method).solver;
929}
930
931/**
932 * `delegate`'s proposed order: the chosen solver, then every feasible candidate
933 * in slot order. Duplicates are dropped, since the reference retries the chosen
934 * solver only once in practice.
935 */
936template <class T>
937std::vector<AutoSolver> auto_proposed_solvers(const qn::NetworkStruct<T>& sn,
938 const std::string& method, AutoMode mode) {
939 const AutoChoice chosen = auto_choose_solver_mode(sn, method, mode);
940 std::vector<AutoSolver> out(1, chosen.solver);
941 const std::vector<AutoSolver> cand = auto_candidates(sn);
942 for (std::size_t i = 0; i < cand.size(); ++i)
943 if (std::find(out.begin(), out.end(), cand[i]) == out.end()) out.push_back(cand[i]);
944 return out;
945}
946
947// ---------------------------------------------------------------------------
948// The LayeredNetwork and Environment arms
949// ---------------------------------------------------------------------------
950
953 std::vector<AutoLayered> skipped;
954};
955
956namespace detail {
957
958inline bool layered_ranked(const std::vector<AutoLayered>& order, AutoLayeredChoice& out) {
959 std::vector<AutoLayered> skipped;
960 for (std::size_t k = 0; k < order.size(); ++k) {
961 if (!auto_layered_is_available(order[k])) {
962 skipped.push_back(order[k]);
963 continue;
964 }
965 out.solver = order[k];
966 out.skipped = skipped;
967 return true;
968 }
969 return false;
970}
971
972} // namespace detail
973
974/**
975 * `chooseLayeredSolver` plus the LayeredNetwork arm of `chooseAvgSolverHeur`.
976 *
977 * There is no feature-set gate on this path: the reference gates a layered
978 * candidate with `SolverLN.supports(model)`, a LayeredNetwork-level check this
979 * port does not carry, so availability is the only screen. A cache task is what
980 * inverts the analytical order -- the cache layer is where NC beats MVA.
981 */
982inline AutoLayeredChoice auto_choose_layered_solver(const std::string& method,
983 bool has_cache_task,
984 AutoMode mode = AutoMode::HEUR) {
986 std::vector<AutoLayered> order;
987 if (mode == AutoMode::EXACT) {
988 // No layered solver is exact; NC layers are the closest available.
990 } else if (mode == AutoMode::SIM) {
991 // lqsim is the layered simulator; the LN solvers are the fallback.
993 } else if (method == "getTranAvg" || detail::is_cdf_method(method) ||
994 method == "getTranCdfPassT" || method == "getTranCdfRespT") {
996 } else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
998 } else if (detail::is_prob_method(method) || detail::is_tran_prob_method(method)) {
1000 } else if (detail::is_ensemble_method(method)) {
1003 } else if (has_cache_task) {
1004 // The LayeredNetwork arm of chooseAvgSolverHeur, which returns the NC
1005 // layer solver outright on a cache task.
1007 return out;
1008 } else {
1011 }
1012 if (has_cache_task && mode != AutoMode::SIM) {
1013 // A layered cache model needs the NC layer solver whichever metric was
1014 // asked for: it is the only one that solves the cache layer.
1015 std::vector<AutoLayered> promoted(1, AutoLayered::LN_NC);
1016 for (std::size_t i = 0; i < order.size(); ++i)
1017 if (order[i] != AutoLayered::LN_NC) promoted.push_back(order[i]);
1018 order = promoted;
1019 }
1020 if (detail::layered_ranked(order, out)) return out;
1021 throw UnsupportedError(
1022 "SolverAUTO: no LayeredNetwork solver in this port serves method '" + method +
1023 "'; the reference's ranking is served by SolverLQNS, which shells out to an external "
1024 "binary this port does not wrap");
1025}
1026
1029 std::vector<AutoEnv> skipped;
1030};
1031
1032/**
1033 * The Environment arm of `chooseSolverHeur` / `chooseSolverExact` /
1034 * `chooseSolverSim`.
1035 *
1036 * Fluid leads the heuristic ranking because the blending method is transient:
1037 * it restarts each stage from the mean state the previous one left, and an
1038 * inner solver without a transient analysis returns zeros for every blended
1039 * metric. That is also the only stage engine this port builds.
1040 */
1041inline AutoEnvChoice auto_choose_env_solver(const std::string& method,
1042 AutoMode mode = AutoMode::HEUR) {
1043 std::vector<AutoEnv> order;
1044 if (mode == AutoMode::EXACT) order = {AutoEnv::ENV_NC, AutoEnv::ENV_MVA};
1047
1048 AutoEnvChoice out;
1049 std::vector<AutoEnv> skipped;
1050 for (std::size_t k = 0; k < order.size(); ++k) {
1051 if (!auto_env_is_available(order[k])) {
1052 skipped.push_back(order[k]);
1053 continue;
1054 }
1055 out.solver = order[k];
1056 out.skipped = skipped;
1057 return out;
1058 }
1059 throw UnsupportedError(
1060 "SolverAUTO: the Environment ranking for method '" + method +
1061 "' selects an MVA or NC stage solver, and SolverENV in this port solves a stage with the "
1062 "fluid analyzer (or, under the state-vector coupling, an explicit chain); rerun with "
1063 "-s env, whose default coupling is the mean-field one");
1064}
1065
1066} // namespace autosolver
1067} // namespace line
1068
1069#endif // LINE_SOLVERS_AUTO_SOLVER_AUTO_H
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
A subset of the registry: MATLAB's SolverFeatureSet, whose list is a flag per field.
A network plus its refreshed 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)
The LN layer engines are the four --layer-solver takes (mva, nc, fluid, ssa), so there is no MAM-laye...
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, summed in log space over the reference's f...
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)
bool auto_env_is_available(AutoEnv s)
SolverENV solves a stage with the fluid analyzer under every coupling but the state-vector one,...
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.
const char * auto_solver_name(AutoSolver s)
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).
AutoToken auto_resolve_token(const std::string &raw)
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:181
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
Definition lang_types.h:391
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
Definition lang_types.h:485
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:326
bool ldes_is_available()
True when this machine can run the engine at all, by either image.
Definition ldes_probe.h:245
bool lqns_is_available()
True when lqns is installed AND is a release this port speaks.
Definition lqns_probe.h:69
FeatureSet ssa_feature_set(const std::string &)
SolverSSA.getFeatureSet, 98 MATLAB names.
FeatureSet ldes_feature_set(const std::string &)
SolverLDES.getFeatureSet, transcribed WHOLE.
FeatureSet fluid_feature_set(const std::string &method)
SolverFLD.getFeatureSet, transcribed, MINUS what the requested method cannot evaluate – the port of @...
FeatureSet used_lang_features(const NetworkStruct< T > &sn)
FeatureSet nc_feature_set(const std::string &method)
SolverNC.getFeatureSet, 48 names, transcribed unchanged.
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)
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
SolverSSA SSA
Definition solver.h:234
SolverMAM MAM
Definition solver.h:237
SolverLDES LDES
Definition solver.h:239
SolverNC NC
Definition solver.h:232
SolverCTMC CTMC
Definition solver.h:233
SolverMVA MVA
Definition solver.h:231
A queueing network and its refreshed NetworkStruct.
The DECLARED side of the gate: one feature set per solver.
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 > 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.
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