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