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 // The state-space screen prices the explicit generator, which only default/exact build. `mdd` enumerates no
557 // generator and is exempt, as SolverCTMC.supportsModelMethod exempts it in the reference (cftp lives in NC).
558 if (s == AutoSolver::CTMC && method != "mdd" && !auto_ctmc_is_tractable(sn)) return false;
559 return true;
560}
561
562/** What a ranking resolved to, and what it had to skip to get there. */
565 /** Slots that outranked `solver` and have no engine in this port. */
566 std::vector<AutoSolver> skipped;
567 /** The method the choice was gated on: "" for the default, "exact". */
568 std::string method;
569};
570
571/**
572 * `chooseSolverRanked`: the first slot in ORDER that exists and accepts the
573 * model. Returns false when none qualifies, so the caller can fall back.
574 */
575template <class T>
576bool auto_ranked(const std::vector<AutoSolver>& order, const qn::NetworkStruct<T>& sn,
577 const std::string& token, AutoChoice& out) {
578 std::vector<AutoSolver> skipped;
579 for (std::size_t k = 0; k < order.size(); ++k) {
580 if (!auto_solver_is_available(order[k])) {
581 skipped.push_back(order[k]);
582 continue;
583 }
584 if (!auto_supports(order[k], sn, token)) continue;
585 out.solver = order[k];
586 out.skipped = skipped;
587 out.method = (token == "default") ? std::string() : token;
588 return true;
589 }
590 return false;
591}
592
593/**
594 * The candidate pool in slot order, filtered by `supports`: what the delegate
595 * retries through after the chosen solver fails (SolverAUTO.m:139-147).
596 */
597template <class T>
598std::vector<AutoSolver> auto_candidates(const qn::NetworkStruct<T>& sn) {
599 static const AutoSolver kSlots[] = {AutoSolver::MVA, AutoSolver::NC, AutoSolver::MAM,
602 std::vector<AutoSolver> out;
603 for (std::size_t i = 0; i < sizeof(kSlots) / sizeof(*kSlots); ++i)
604 if (auto_solver_is_available(kSlots[i]) && auto_supports(kSlots[i], sn, "default"))
605 out.push_back(kSlots[i]);
606 return out;
607}
608
609// ---------------------------------------------------------------------------
610// chooseAvgSolverHeur.m, the Network arm
611// ---------------------------------------------------------------------------
612
613namespace detail {
614
615/**
616 * `avgOrder` in chooseAvgSolverHeur.m, arm for arm and in its order. The arms
617 * OVERLAP, so the first match wins and reordering silently rehomes models.
618 *
619 * `homogeneous_inf` is the penultimate arm's predicate, passed in because it
620 * needs the struct and this table does not otherwise.
621 */
622inline std::vector<AutoSolver> avg_order(const AutoTraits& t, bool homogeneous_inf) {
623 if (t.has_cache)
626 if (t.has_fcr) {
627 if (t.total_jobs <= 10.0) return {AutoSolver::NC, AutoSolver::CTMC, AutoSolver::LDES};
629 }
630 if (t.prio == "preempt")
632 if (t.prio == "ps") return {AutoSolver::CTMC, AutoSolver::LDES, AutoSolver::SSA};
633 if (t.has_map)
635 if (t.prio == "hol")
638 if (t.pop_per_chain > 30.0) return {AutoSolver::FLUID, AutoSolver::MVA, AutoSolver::NC};
639 // No exact solver was available at this population (the exact-first pass
640 // above tried), so keep the exact-leaning approximate order.
641 if (t.total_jobs > 0.0 && t.total_jobs <= kAutoExactPopulationMax)
643 if (homogeneous_inf) return {AutoSolver::MVA, AutoSolver::NC, AutoSolver::FLUID};
645}
646
647inline UnsupportedError no_solver(const std::string& what,
648 const std::vector<AutoSolver>& skipped) {
649 std::string msg = "SolverAUTO: no solver supports this model" + what;
650 if (!skipped.empty()) {
651 msg += " (the ranking preferred ";
652 for (std::size_t i = 0; i < skipped.size(); ++i) {
653 if (i) msg += ", ";
654 msg += auto_solver_name(skipped[i]);
655 }
656 msg += ", which this port does not build)";
657 }
658 return UnsupportedError(msg);
659}
660
661} // namespace detail
662
663/**
664 * `chooseAvgSolverHeur`, the Network arm: exact first at small populations,
665 * then the trait-keyed ranking, then the whole pool in the global order.
666 *
667 * The INF branch of avgOrder consults `has_homogeneous_scheduling`, which is
668 * `nstations == 1` here and in MATLAB alike; see the header note.
669 */
670template <class T>
673 const AutoTraits t = auto_traits(sn);
674 AutoChoice out;
675
676 // Small populations: an approximation buys nothing there, so take an exact
677 // solver whenever one is available. The 'exact' method name is what makes this a
678 // claim rather than a preference -- MVA and NC reject it without a
679 // product-form solution, and CTMC is screened for a chain that fits.
680 if (t.total_jobs > 0.0 && t.total_jobs <= kAutoExactPopulationMax) {
681 const std::vector<AutoSolver> exact_order =
682 t.has_cache
683 ? std::vector<AutoSolver>{AutoSolver::NC, AutoSolver::MVA, AutoSolver::CTMC}
684 : std::vector<AutoSolver>{AutoSolver::MVA, AutoSolver::NC, AutoSolver::CTMC};
685 if (auto_ranked(exact_order, sn, "exact", out)) return out;
686 }
687
688 const std::vector<AutoSolver> order =
689 detail::avg_order(t, sn.has_homogeneous_scheduling(SchedStrategy::INF));
690 if (auto_ranked(order, sn, "default", out)) return out;
691
692 // Nothing in the ranked list is feasible: fall back to the whole pool in
693 // the global order rather than returning nothing.
694 const std::vector<AutoSolver> pool = {AutoSolver::MVA, AutoSolver::NC, AutoSolver::MAM,
697 if (auto_ranked(pool, sn, "default", out)) return out;
698 throw detail::no_solver("", pool);
699}
700
701template <class T>
705
706// ---------------------------------------------------------------------------
707// chooseSolverHeur.m, the Network arm
708// ---------------------------------------------------------------------------
709
710namespace detail {
711
712inline bool in_list(const std::string& m, const char* const* tab, std::size_t n) {
713 for (std::size_t i = 0; i < n; ++i)
714 if (m == tab[i]) return true;
715 return false;
716}
717
718/** The average-metric getters, which defer to the feature tree. */
719inline bool is_avg_method(const std::string& m) {
720 static const char* kAvg[] = {
721 "getAvgChainTable", "getAvgTputTable", "getAvgRespTTable", "getAvgUtilTable",
722 "getAvgSysTable", "getAvgNodeTable", "getAvgTable", "getAvgTableLayered", "getAvg",
723 "getAvgChain", "getAvgSys", "getAvgNode", "getAvgNodeChain", "getAvgArvRChain",
724 "getAvgQLenChain", "getAvgUtilChain", "getAvgRespTChain", "getAvgTputChain",
725 "getAvgSysRespT", "getAvgSysTput", "getAvgQLen", "getAvgUtil", "getAvgRespT",
726 "getAvgResidT", "getAvgWaitT", "getAvgTput", "getAvgArvR", "getAvgQLenTable",
727 "getAvgResidTChain", "getAvgNodeQLenChain", "getAvgNodeUtilChain",
728 "getAvgNodeRespTChain", "getAvgNodeResidTChain", "getAvgNodeTputChain",
729 "getAvgNodeArvRChain", "getAvgNodeChainTable", "getResults", "hasResults",
730 "getAvgHandles", "getTranHandles", "getAvgQLenHandles", "getAvgUtilHandles",
731 "getAvgRespTHandles", "getAvgTputHandles", "getAvgArvRHandles", "getAvgResidTHandles",
732 "getMethodFeatureSet", "supportsModelMethod", "isStochasticMethod", "libraries",
733 "showLibraryAttribution", "citations"};
734 return in_list(m, kAvg, sizeof(kAvg) / sizeof(*kAvg));
735}
736
737/** The ensemble getters, which exist for LayeredNetwork models only. */
738inline bool is_ensemble_method(const std::string& m) {
739 static const char* kEns[] = {"getEnsembleAvg", "getEnsembleAvgTables", "getSolver",
740 "setSolver", "getNumberOfModels", "getIteration",
741 "get_state", "set_state", "update_solver"};
742 return in_list(m, kEns, sizeof(kEns) / sizeof(*kEns));
743}
744
745inline bool is_cdf_method(const std::string& m) {
746 return m == "getCdfRespT" || m == "getCdfPassT" || m == "getPerctRespT";
747}
748
749inline bool is_tran_prob_method(const std::string& m) {
750 return m == "getTranProb" || m == "getTranProbSys" || m == "getTranProbAggr" ||
751 m == "getTranProbSysAggr";
752}
753
754inline bool is_prob_method(const std::string& m) {
755 return m == "getProb" || m == "getProbAggr" || m == "getProbSys" || m == "getProbSysAggr" ||
756 m == "getProbMarg" || m == "getProbNormConstAggr";
757}
758
759inline bool is_sample_method(const std::string& m) { return m == "sample" || m == "sampleSys"; }
760
761inline bool is_sample_aggr_method(const std::string& m) {
762 return m == "sampleAggr" || m == "sampleSysAggr";
763}
764
765inline bool is_cache_metric_method(const std::string& m) {
766 static const char* kTab[] = {"getAvgCacheTable", "getAvgCacheT", "getAvgItemTable",
767 "getAvgItemT", "cacheAvgT", "itemAvgT", "aCaT", "aIT"};
768 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
769}
770
771inline bool is_loss_metric_method(const std::string& m) {
772 static const char* kTab[] = {"getAvgLossTable", "getAvgLossT", "getAvgRegionLossTable",
773 "getAvgRegionLossT", "lossAvgT", "regionLossAvgT",
774 "aLT", "aRLT"};
775 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
776}
777
778inline bool is_orbit_metric_method(const std::string& m) {
779 static const char* kTab[] = {"getAvgOrbitTable", "getAvgOrbitT", "getAvgOrbit", "orbitAvgT",
780 "aOT"};
781 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
782}
783
784inline bool is_moment_method(const std::string& m) {
785 static const char* kTab[] = {"getMomentTable", "getMomentChainTable", "getMomentStationTable",
786 "getMomentT", "getMomentChainT", "getMomentStationT",
787 "momentT", "momentChainT", "momentStationT",
788 "mT", "mCT", "mST"};
789 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
790}
791
792inline bool is_sens_method(const std::string& m) {
793 static const char* kTab[] = {"getSensitivityTable", "getSensitivityT", "sensitivityT", "sT",
794 "supportsExactSensitivity"};
795 return in_list(m, kTab, sizeof(kTab) / sizeof(*kTab));
796}
797
798} // namespace detail
799
800/**
801 * `chooseNetworkSolver` in chooseSolverHeur.m: the getter names the metric
802 * family, the family names a ranking, and the average family alone consults the
803 * feature tree. A ranking that yields nothing falls back to the average
804 * heuristic, as the reference does, rather than refusing.
805 */
806template <class T>
807AutoChoice auto_choose_solver_heur(const qn::NetworkStruct<T>& sn, const std::string& method) {
809 if (detail::is_avg_method(method)) return auto_choose_avg_solver_ex(sn);
810 if (detail::is_ensemble_method(method))
811 throw InputError("SolverAUTO: method '" + method +
812 "' is only available for LayeredNetwork models");
813
814 std::vector<AutoSolver> order;
815 if (method == "getTranAvg") {
817 } else if (detail::is_cdf_method(method)) {
818 // NC gives the exact passage-time distribution on FCFS product form;
819 // otherwise Fluid is the smooth approximation, then the simulators.
820 if (sn.has_homogeneous_scheduling(SchedStrategy::FCFS) && sn.has_product_form())
822 else
824 } else if (method == "getTranCdfPassT" || method == "getTranCdfRespT") {
826 } else if (detail::is_tran_prob_method(method)) {
827 order = {AutoSolver::CTMC};
828 } else if (detail::is_sample_method(method)) {
830 } else if (detail::is_sample_aggr_method(method)) {
832 } else if (detail::is_prob_method(method)) {
833 if (sn.has_product_form())
835 else
837 } else if (detail::is_cache_metric_method(method)) {
840 } else if (detail::is_loss_metric_method(method)) {
842 } else if (detail::is_orbit_metric_method(method)) {
844 } else if (detail::is_moment_method(method)) {
846 } else if (detail::is_sens_method(method)) {
848 } else {
850 }
851
852 AutoChoice out;
853 if (auto_ranked(order, sn, "default", out)) return out;
854 // No solver in the metric's ranking supports the model: the average
855 // heuristic is the floor.
857}
858
859/** `chooseSolverExact`: the ranking restricted to solvers that answer exactly. */
860template <class T>
861AutoChoice auto_choose_solver_exact(const qn::NetworkStruct<T>& sn, const std::string& method) {
862 const AutoTraits t = auto_traits(sn);
863 std::vector<AutoSolver> order;
864 if (detail::is_tran_prob_method(method) || detail::is_cdf_method(method) ||
865 method == "getTranCdfPassT" || method == "getTranCdfRespT" || method == "getTranAvg") {
866 order = {AutoSolver::CTMC};
867 } else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
868 // A sample path is exact in distribution, not in the mean.
870 } else if (detail::is_prob_method(method)) {
872 else order = {AutoSolver::CTMC};
873 } else if (t.is_product_form && !t.has_multi_server) {
875 } else {
877 }
878 AutoChoice out;
879 if (auto_ranked(order, sn, "exact", out)) return out;
880 throw detail::no_solver(" exactly for method '" + method +
881 "'; use the default heuristic for the approximation",
882 order);
883}
884
885/** `chooseSolverSim`: the ranking restricted to simulators. */
886template <class T>
887AutoChoice auto_choose_solver_sim(const qn::NetworkStruct<T>& sn, const std::string& method) {
888 const std::vector<AutoSolver> order =
889 detail::is_sample_method(method)
890 ? std::vector<AutoSolver>{AutoSolver::SSA, AutoSolver::LDES}
891 : std::vector<AutoSolver>{AutoSolver::LDES, AutoSolver::SSA};
892 AutoChoice out;
893 if (auto_ranked(order, sn, "default", out)) return out;
894 throw detail::no_solver(" by simulation", order);
895}
896
897/**
898 * `chooseSolver`: the selection mode picks the ranking, and every mode but the
899 * two learned ones keeps the heuristic as its floor.
900 */
901template <class T>
903 AutoMode mode) {
904 AutoChoice out;
905 switch (mode) {
906 case AutoMode::EXACT: return auto_choose_solver_exact(sn, method);
907 case AutoMode::SIM: return auto_choose_solver_sim(sn, method);
908 case AutoMode::FAST:
909 // Cheapest analytical answer; the heuristic is the floor for
910 // metrics no mean-value solver can serve.
912 sn, "default", out))
913 return out;
914 return auto_choose_solver_heur(sn, method);
916 // A smooth or matrix-analytic answer preferred over the fastest.
919 sn, "default", out))
920 return out;
921 return auto_choose_solver_heur(sn, method);
922 case AutoMode::HEUR: break;
923 }
924 return auto_choose_solver_heur(sn, method);
925}
926
927/** The heuristic Network arm, by getter name. */
928template <class T>
929AutoSolver auto_choose_solver(const qn::NetworkStruct<T>& sn, const std::string& method) {
930 return auto_choose_solver_heur(sn, method).solver;
931}
932
933/**
934 * `delegate`'s proposed order: the chosen solver, then every feasible candidate
935 * in slot order. Duplicates are dropped, since the reference retries the chosen
936 * solver only once in practice.
937 */
938template <class T>
939std::vector<AutoSolver> auto_proposed_solvers(const qn::NetworkStruct<T>& sn,
940 const std::string& method, AutoMode mode) {
941 const AutoChoice chosen = auto_choose_solver_mode(sn, method, mode);
942 std::vector<AutoSolver> out(1, chosen.solver);
943 const std::vector<AutoSolver> cand = auto_candidates(sn);
944 for (std::size_t i = 0; i < cand.size(); ++i)
945 if (std::find(out.begin(), out.end(), cand[i]) == out.end()) out.push_back(cand[i]);
946 return out;
947}
948
949// ---------------------------------------------------------------------------
950// The LayeredNetwork and Environment arms
951// ---------------------------------------------------------------------------
952
955 std::vector<AutoLayered> skipped;
956};
957
958namespace detail {
959
960inline bool layered_ranked(const std::vector<AutoLayered>& order, AutoLayeredChoice& out) {
961 std::vector<AutoLayered> skipped;
962 for (std::size_t k = 0; k < order.size(); ++k) {
963 if (!auto_layered_is_available(order[k])) {
964 skipped.push_back(order[k]);
965 continue;
966 }
967 out.solver = order[k];
968 out.skipped = skipped;
969 return true;
970 }
971 return false;
972}
973
974} // namespace detail
975
976/**
977 * `chooseLayeredSolver` plus the LayeredNetwork arm of `chooseAvgSolverHeur`.
978 *
979 * There is no feature-set gate on this path: the reference gates a layered
980 * candidate with `SolverLN.supports(model)`, a LayeredNetwork-level check this
981 * port does not carry, so availability is the only screen. A cache task is what
982 * inverts the analytical order -- the cache layer is where NC beats MVA.
983 */
984inline AutoLayeredChoice auto_choose_layered_solver(const std::string& method,
985 bool has_cache_task,
986 AutoMode mode = AutoMode::HEUR) {
988 std::vector<AutoLayered> order;
989 if (mode == AutoMode::EXACT) {
990 // No layered solver is exact; NC layers are the closest available.
992 } else if (mode == AutoMode::SIM) {
993 // lqsim is the layered simulator; the LN solvers are the fallback.
995 } else if (method == "getTranAvg" || detail::is_cdf_method(method) ||
996 method == "getTranCdfPassT" || method == "getTranCdfRespT") {
998 } else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
1000 } else if (detail::is_prob_method(method) || detail::is_tran_prob_method(method)) {
1002 } else if (detail::is_ensemble_method(method)) {
1005 } else if (has_cache_task) {
1006 // The LayeredNetwork arm of chooseAvgSolverHeur, which returns the NC
1007 // layer solver outright on a cache task.
1009 return out;
1010 } else {
1013 }
1014 if (has_cache_task && mode != AutoMode::SIM) {
1015 // A layered cache model needs the NC layer solver whichever metric was
1016 // asked for: it is the only one that solves the cache layer.
1017 std::vector<AutoLayered> promoted(1, AutoLayered::LN_NC);
1018 for (std::size_t i = 0; i < order.size(); ++i)
1019 if (order[i] != AutoLayered::LN_NC) promoted.push_back(order[i]);
1020 order = promoted;
1021 }
1022 if (detail::layered_ranked(order, out)) return out;
1023 throw UnsupportedError(
1024 "SolverAUTO: no LayeredNetwork solver in this port serves method '" + method +
1025 "'; the reference's ranking is served by SolverLQNS, which shells out to an external "
1026 "binary this port does not wrap");
1027}
1028
1031 std::vector<AutoEnv> skipped;
1032};
1033
1034/**
1035 * The Environment arm of `chooseSolverHeur` / `chooseSolverExact` /
1036 * `chooseSolverSim`.
1037 *
1038 * Fluid leads the heuristic ranking because the blending method is transient:
1039 * it restarts each stage from the mean state the previous one left, and an
1040 * inner solver without a transient analysis returns zeros for every blended
1041 * metric. That is also the only stage engine this port builds.
1042 */
1043inline AutoEnvChoice auto_choose_env_solver(const std::string& method,
1044 AutoMode mode = AutoMode::HEUR) {
1045 std::vector<AutoEnv> order;
1046 if (mode == AutoMode::EXACT) order = {AutoEnv::ENV_NC, AutoEnv::ENV_MVA};
1049
1050 AutoEnvChoice out;
1051 std::vector<AutoEnv> skipped;
1052 for (std::size_t k = 0; k < order.size(); ++k) {
1053 if (!auto_env_is_available(order[k])) {
1054 skipped.push_back(order[k]);
1055 continue;
1056 }
1057 out.solver = order[k];
1058 out.skipped = skipped;
1059 return out;
1060 }
1061 throw UnsupportedError(
1062 "SolverAUTO: the Environment ranking for method '" + method +
1063 "' selects an MVA or NC stage solver, and SolverENV in this port solves a stage with the "
1064 "fluid analyzer (or, under the state-vector coupling, an explicit chain); rerun with "
1065 "-s env, whose default coupling is the mean-field one");
1066}
1067
1068} // namespace autosolver
1069} // namespace line
1070
1071#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