LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
auto_methods.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_AUTO_METHODS_H
6#define LINE_SOLVERS_AUTO_AUTO_METHODS_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * `SolverAUTO.listValidMethods`: the method names THIS MODEL can actually run.
12 *
13 * WHY IT IS A SEPARATE HEADER. `solver_auto.h` is deliberately light -- a
14 * struct, a ranking table and the feature sets -- and it is included by the CLI,
15 * the facade, the examples and `test_all_headers`. Answering this question needs
16 * every family's method registry, i.e. the MVA, NC, CTMC, fluid, MAM, BA, SSA,
17 * LDES, JMT and LQNS runners, so putting it there would make a heavy include of a
18 * cheap one for every caller that only wants the ranking.
19 *
20 * THE GATE IS THE FEATURE SET, asked of every candidate rather than of the first
21 * feasible one. `auto_supports` already asks it for the seven ranked slots, and
22 * it is the same question `chooseSolverRanked` asks before delegating: is every
23 * feature the model uses declared by the solver that would run this method? A
24 * family whose feature set refuses the model contributes nothing, and a method
25 * whose own set refuses it is not offered. Both matter, because a per-method set
26 * is where the deltas live -- MVA's `rqna` consumes MAP arrivals that the rest of
27 * the envelope does not, MAM's LoadDependence holds only for the methods that
28 * read `lldscaling`.
29 *
30 * WHAT A FLAT FEATURE SET CANNOT SAY, and is therefore asked separately, exactly
31 * as the reference's `supportsModelMethod` overrides do:
32 * product form 'exact' is not exact without one (SolverMVA's OI/PAS
33 * stations excepted); folded into `auto_supports`
34 * a chain that fits the CTMC slot is screened for state-space size
35 * a binding buffer setCapacity/classCap, which MVA, NC, FLD and LQNS refuse
36 * through `check_binding_capacity` and no feature name
37 * describes
38 * the model SHAPE `mva::list_valid_methods(L)` and `ba::list_valid_methods(L)`
39 * are themselves model-aware: the queueing-system closed
40 * forms are offered on a two-station open model and nowhere
41 * else, the QRF reduction bounds on a single-class closed
42 * network of single servers, the three open-network bounds
43 * on a fully open one. Asking the registry for the model is
44 * what keeps that rule in one place.
45 *
46 * A FAMILY WHOSE EVERY METHOD IS REFUSED LOSES ITS BARE TOKEN TOO: `nc` alone
47 * delegates to SolverNC, which is exactly the rejection the per-method gate just
48 * returned.
49 *
50 * WHAT IS NOT LISTED, and why each is a token this port would refuse anyway:
51 * ln, env, uq take a LayeredNetwork, an Environment or an inner-solver
52 * factory rather than a Network. The reference's own
53 * `buildFamilySolver` cannot construct them from here
54 * either, and its `familyAcceptsModelClass` drops them.
55 * `lqns` IS listed: on a Network its methods are the qns
56 * ones, which reach `qnsolver`.
57 * ldes without an engine `auto_supports` folds `auto_solver_is_available` in,
58 * and the CLI refuses `--method ldes` by name when no
59 * engine is found beside the binary. Listing it there
60 * would be a claim the very next call denies.
61 * JMT IS LISTED although it is not a ranked candidate. It has no slot in
62 * `AutoSolver` -- the reference removed it as one, LDES subsuming its feature
63 * set -- but `-s auto --method jmt` reaches it as an explicit method name, and
64 * `listValidMethods` answers about the method names the caller may ask for, not about
65 * the ones the ranking would pick.
66 *
67 * The model-independent twin, `listAllMethods`, is `SolverAUTO::list_all_methods`
68 * (solver.h): the intents, 'auto', and every instantiable family with its
69 * registry row (method_registry.h) in qualified form.
70 */
71
72#include <algorithm>
73#include <array>
74#include <cctype>
75#include <cmath>
76#include <cstddef>
77#include <initializer_list>
78#include <stdexcept>
79#include <string>
80#include <vector>
81
83#include "line/util/error.h"
90// The shape predicate the report asks for, which the analyzer header above does
91// not pull in: mdd builds no generator and is restricted to a shape the
92// state-space guard says nothing about. NC's cftp predicate comes in with
93// solver_nc_runner.h below.
103
104namespace line {
105namespace autosolver {
106
107/**
108 * The method families that solve a flat Network, in `SolverAUTO.familyNames`'
109 * own order -- which is also the order an unqualified algorithm name is looked
110 * up in, so it is not arbitrary.
111 *
112 * "ag" SITS AFTER "mam", whose RCAT names it took over, and is a family for the
113 * METHOD NAME and REPORT tables only: `--method ag.inap` and the model help
114 * reach SolverAG through it. It is deliberately NOT one of the ranked
115 * `AutoSolver` slots and NOT in the feature-set union the automatic ranking
116 * takes, so a G-network is still refused by "default" and has to be asked for
117 * by name; see _kb/06-solver-catalog.md, "SolverAG owns the RCAT methods".
118 */
119inline std::vector<std::string> auto_network_family_names() {
120 return {"mva", "nc", "ctmc", "fld", "mam", "ag", "ba", "ssa", "ldes", "jmt", "lqns"};
121}
122
123/**
124 * The prefixes under which a family advertises a SECOND SPELLING of a method it
125 * already declares plainly.
126 *
127 * THIS IS A DECLARATION, not a derivation, and belongs beside
128 * `auto_family_metrics` and `auto_method_class` for the same reason: the
129 * knowledge lives in the solver's own dispatch (the mva registry strips a
130 * leading "amva." before selecting an algorithm) and nothing exposes it, so a
131 * family that gains or loses an alias spelling must be edited into all four
132 * copies in the SAME change. An omission does not fail; it puts the same
133 * algorithm in the report twice.
134 */
135inline std::vector<std::string> auto_method_alias_prefixes(const std::string& family) {
136 if (family == "mva") return {"amva."};
137 return {};
138}
139
140/**
141 * Is `name` a second spelling of another method this family declares?
142 *
143 * The remainder has to be declared too, which is what keeps the rule from eating
144 * a genuine method that merely starts with the prefix: it is an alias only when
145 * the thing it aliases is there beside it.
146 */
147inline bool auto_is_method_alias(const std::string& family, const std::string& name,
148 const std::vector<std::string>& declared) {
149 for (const std::string& prefix : auto_method_alias_prefixes(family)) {
150 if (name.size() > prefix.size() && name.compare(0, prefix.size(), prefix) == 0 &&
151 std::find(declared.begin(), declared.end(), name.substr(prefix.size())) !=
152 declared.end())
153 return true;
154 }
155 return false;
156}
157
158namespace detail {
159
160/** The ranked slot a family occupies, or false when it has none. */
161inline bool auto_slot_of_family(const std::string& family, AutoSolver& out) {
162 if (family == "mva") { out = AutoSolver::MVA; return true; }
163 if (family == "nc") { out = AutoSolver::NC; return true; }
164 if (family == "mam") { out = AutoSolver::MAM; return true; }
165 if (family == "fld") { out = AutoSolver::FLUID; return true; }
166 if (family == "ssa") { out = AutoSolver::SSA; return true; }
167 if (family == "ctmc") { out = AutoSolver::CTMC; return true; }
168 if (family == "ldes") { out = AutoSolver::LDES; return true; }
169 return false;
170}
171
172/**
173 * The finite-buffer gate as a predicate.
174 *
175 * `has_binding_capacity` is the gate's own question asked without raising, which
176 * is what this needs: on the solve path a capped station the solver cannot
177 * honour has to stop the run rather than be reported unconstrained, but here the
178 * same verdict is a yes or a no. The families are the ones whose own runners ask
179 * it -- SolverMVA, SolverNC, SolverFLD and SolverLQNS -- and no feature name
180 * describes a capacity, which is why it cannot ride in the feature set.
181 *
182 * NC EXEMPTS ITS MEM ALGORITHM, which solves the censored GE/GE/c/0;N queue and
183 * therefore DOES honour the buffer. The exemption is on the LITERAL method name
184 * that `nc_dispatch` branches on, not on a resolved one: nothing resolves
185 * 'default' into 'mem', so exempting a default run would advertise a token that
186 * dispatches somewhere the buffer is ignored.
187 */
188template <class T>
189bool capacity_admits(const std::string& family, const qn::NetworkStruct<T>& sn,
190 const std::string& method) {
191 if (family != "mva" && family != "nc" && family != "fld" && family != "lqns") return true;
192 if (!qn::has_binding_capacity(sn)) return true;
193 // FLD's 'dae' carries the buffer as an algebraic constraint on the drift, which
194 // is why 'default' resolves to it on a capped model (fluid_resolve_method); its
195 // own applicability is the runner's `fluid_dae_applicable`, not this gate.
196 if (family == "fld" && fluid::detail::fluid_unqualify(method) == "dae") return true;
197 if (family == "nc" && method == "mem") {
198 const nc::MemSupport ms = nc::solver_nc_mem_supports(sn);
199 return ms.supported && ms.blocking;
200 }
201 return false;
202}
203
204/**
205 * The feature set a family declares for a method, and the label its refusals
206 * are reported under; false when the family solves no flat Network.
207 *
208 * It is the switch `auto_supports` opens on, lifted out so that the bool gate
209 * and the reason-returning one read the same table. A second copy of it is how
210 * a family gains a feature in one answer and not in the other.
211 */
212inline bool declared_feature_set(const std::string& family, const std::string& method,
213 qn::FeatureSet& declared, std::string& label) {
214 if (family == "mva") { declared = qn::mva_feature_set(method); label = "SolverMVA"; return true; }
215 if (family == "nc") { declared = qn::nc_feature_set(method); label = "SolverNC"; return true; }
216 if (family == "mam") { declared = qn::mam_feature_set(method); label = "SolverMAM"; return true; }
217 if (family == "fld") { declared = qn::fluid_feature_set(method); label = "SolverFLD"; return true; }
218 if (family == "ssa") { declared = qn::ssa_feature_set(method); label = "SolverSSA"; return true; }
219 if (family == "ctmc") { declared = qn::ctmc_feature_set(method); label = "SolverCTMC"; return true; }
220 if (family == "ldes") { declared = qn::ldes_feature_set(method); label = "SolverLDES"; return true; }
221 if (family == "ag") { declared = qn::ag_feature_set(method); label = "SolverAG"; return true; }
222 if (family == "ba") { declared = qn::ba_feature_set(method); label = "SolverBA"; return true; }
223 if (family == "jmt") { declared = qn::jmt_feature_set(method); label = "SolverJMT"; return true; }
224 if (family == "lqns") {
225 declared = qn::lqns_network_feature_set(method);
226 label = "SolverLQNS";
227 return true;
228 }
229 return false;
230}
231
232} // namespace detail
233
234/**
235 * The methods a family declares ON THIS MODEL, empty when it declares none.
236 *
237 * The registries are asked rather than copied: `mva::list_valid_methods` and
238 * `ba::list_valid_methods` take the struct and narrow themselves, and a second
239 * copy of either shape rule here is how the two drift apart.
240 */
241template <class T>
242std::vector<std::string> auto_family_methods(const std::string& family,
243 const qn::NetworkStruct<T>& sn) {
244 if (family == "mva") return mva::list_valid_methods(sn);
245 if (family == "nc") return nc::list_valid_methods();
246 if (family == "ctmc") return ctmc::list_valid_methods();
247 if (family == "fld") return fluid::fluid_list_valid_methods();
248 if (family == "mam") return mam::list_valid_methods();
249 if (family == "ag") return ag::list_valid_methods();
250 if (family == "ba") return ba::list_valid_methods(sn);
251 if (family == "ssa") return ssa::list_valid_methods();
252 if (family == "ldes") return ldes::list_valid_methods();
253 if (family == "jmt") return jmt::jmt_list_valid_methods();
254 if (family == "lqns") return lqns::qns_methods();
255 return std::vector<std::string>();
256}
257
258/**
259 * `familyDeclaringMethod`: the first family, in `familyNames` order, whose method list on this model
260 * names `name` (case-insensitively), or "" when none does. A family with no engine on this machine
261 * (ldes, lqns) is skipped, as the reference skips a family whose probe solver cannot be built; ln,
262 * env and uq take another model class and are never asked.
263 */
264template <class T>
265std::string auto_family_declaring_method(const qn::NetworkStruct<T>& sn, const std::string& name) {
266 const auto lower = [](std::string s) {
267 std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
268 return s;
269 };
270 const std::string want = lower(name);
271 for (const std::string& fam : auto_network_family_names()) {
272 if (fam == "ldes" && !ldes::ldes_is_available()) continue;
273 if (fam == "lqns" && !lqns::qnsolver_is_available()) continue;
274 for (const std::string& m : auto_family_methods(fam, sn))
275 if (lower(m) == want) return fam;
276 }
277 return std::string();
278}
279
280/**
281 * `resolveMethodToken` with its third arm: an intent, a `family[.submethod]`, or else a BARE method
282 * name, which resolves to the family that declares it (`auto_family_declaring_method`) with the whole
283 * name as the submethod, so `comom` reaches SolverNC as `nc.comom` does. A name no family declares is
284 * refused as `LINE:unsupported:Method`, the id and wording of `resolveMethodToken.m`.
285 */
286template <class T>
287AutoToken auto_resolve_token(const std::string& raw, const qn::NetworkStruct<T>& sn) {
288 try {
289 return auto_resolve_token(raw);
290 } catch (const InputError&) {
291 // neither an intent nor a family: fall through to the bare-name arm
292 }
293 const std::string fam = auto_family_declaring_method(sn, raw);
294 if (!fam.empty()) {
295 AutoToken t;
296 t.is_intent = false;
297 t.family = fam;
298 t.submethod = raw;
299 return t;
300 }
301 throw UnsupportedError(ErrorKind{"Method"},
302 "SolverAUTO: Unrecognized method '" + raw + "'. Valid tokens are a selection intent (default, "
303 "heur, sim, exact, fast, accurate, bound), a method family (mva, nc, ctmc, fld, mam, ag, ba, ssa, "
304 "ldes, jmt, ln, env, lqns, uq), or a qualified method name such as 'nc.comom'.");
305}
306
307/**
308 * `Solver.supportsModelMethod` for a family method name: may THIS model run THIS
309 * method of THIS family?
310 *
311 * The seven ranked slots defer to `auto_supports`, which is the same call
312 * `chooseSolverRanked` makes and already carries the product-form rule, the
313 * CTMC state-space screen and the LDES engine probe. The three families with no
314 * slot are gated on their own declared set, since they are reachable by explicit
315 * token and by no ranking.
316 */
317template <class T>
318std::string auto_family_refusal(const std::string& family, const qn::NetworkStruct<T>& sn,
319 const std::string& method) {
320 // THE PERFECT SAMPLER'S OWN PREDICATE FIRST, ahead of the capacity and
321 // feature gates, so a caller reads "the cftp method supports closed models
322 // only" rather than "(feature: OpenClass)". It is the same call
323 // `solver_nc_solve` refuses through; NC's normal gates follow when it passes.
324 if (family == "nc" && (method == "cftp" || method == "cftp.approx")) {
325 const std::string why = nc::solver_nc_cftp_supports(sn);
326 if (!why.empty()) return why;
327 }
328 // A non-Markovian reneging patience is refused by name for every MVA and FLD method, ahead of the
329 // feature set (which would only say "Reneging"); it is never replaced by an exponential of the same mean.
330 if (family == "mva" || family == "fld") {
331 const std::string why = api::sn_patience_refusal(sn);
332 if (!why.empty()) return (family == "mva" ? "SolverMVA: " : "SolverFLD: ") + why;
333 }
334 if (family == "mva") {
335 // 'default' on a reneging station is not the base envelope: the run resolves it to 'erlanga' or
336 // 'mgisrgi' (resolve_method), so the gate answers for THAT method, offering 'default' where it
337 // applies and refusing with its reason otherwise (MATLAB SolverMVA.supportsModelMethod).
338 if (method == "default") {
339 const std::string resolved = mva::resolve_method(sn, method);
340 if (resolved == "erlanga" || resolved == "mgisrgi") return auto_family_refusal(family, sn, resolved);
341 }
342 if (method == "erlanga") {
343 const std::string why = mva::mva_erlanga_patience_reason(sn);
344 if (!why.empty()) return "SolverMVA: " + why;
345 }
346 }
347 if (!detail::capacity_admits(family, sn, method))
348 return "SolverAUTO: this method ignores the finite station capacity this model sets "
349 "(setCapacity / classCap), so it would report a capped station as unbounded.";
350
351 // rmf/refined resolve together off the presence of Cache nodes (fluid_resolve_method), so on a cache-free model
352 // 'rmf' is gated as 'refined' and inherits its closed-model restriction. MATLAB resolves at the same point.
353 std::string gated = method;
354 if (family == "fld") {
355 const std::string m = fluid::detail::fluid_unqualify(method);
356 if (m == "rmf" || m == "refined") gated = fluid::detail::fluid_has_cache(sn) ? "rmf" : "refined";
357 }
358 qn::FeatureSet declared;
359 std::string label;
360 if (!detail::declared_feature_set(family, gated, declared, label))
361 return "SolverAUTO: the '" + family + "' method family does not solve a flat Network.";
362
364 if (!r.ok) return r.reason;
365
366 // NC's structural per-method rules, the ones no feature name can carry:
367 // whether the method has a route on THIS model at all. `nc_method_refusal`
368 // is the single copy of them, asked here and by `solver_nc_solve`, so a pair
369 // this report offers is a pair the run accepts. Asked BEFORE `auto_supports`
370 // because it names the offending thing, where the fallback below can only
371 // say that some structural check refused the model.
372 if (family == "nc") {
373 const std::string ncr = nc::nc_method_refusal(sn, method);
374 if (!ncr.empty()) return ncr;
375 }
376
377 // THE STRUCTURAL RULES OF THE TWO WRAPPER-ADJACENT FAMILIES, asked of the
378 // families themselves rather than restated here. `jmt` has no ranked slot,
379 // so the branch below never reaches it, and `auto_supports` asks the MAM
380 // slot for its feature set only -- which is how findSolver came to offer
381 // jmt.jmva.<alg> on a multi-server model, the since-removed jmt.replication with
382 // no horizon and mam.retrial on a model that declares no orbit, each of which then
383 // raised when it was run. The two calls are the solvers' OWN predicates,
384 // the same ones their runners raise, taken with the default options a probe
385 // solver carries.
386 if (family == "jmt") {
387 const std::string why = jmt::jmt_method_refusal(sn, method, jmt::JmtOptions());
388 if (!why.empty()) return why;
389 }
390 if (family == "mam") {
391 const std::string why = mam::mam_model_method_refusal(sn, method);
392 if (!why.empty()) return why;
393 }
394 // AG's own structural gate, on the same terms: RCAT needs a Markovian
395 // (D0,D1) service law, state-independent routing, one server per station and
396 // no binding buffer, and none of the four is a feature name. It has no
397 // ranked slot, so nothing below reaches it.
398 if (family == "ag") {
399 const std::string why = ag::runner_detail::method_refusal(sn, method);
400 if (!why.empty()) return why;
401 }
402 // LQNS on a Network on the same terms: immediate feedback is outside BOTH
403 // conversions, and the multiserver approximations `qnsolver -m` does not
404 // offer are a rule about the method rather than about the model.
405 if (family == "lqns") {
406 const std::string why = lqns::qns_method_refusal(sn, method);
407 if (!why.empty()) return why;
408 }
409
410 // THE REMAINING THREE FAMILIES, on the same terms. `ba` has no ranked slot
411 // either, so nothing below reaches it; `ctmc` and `fluid` do have one, but
412 // `auto_supports` asks each for a rule about the SOLVER (state-space size,
413 // product form) and never about the METHOD, which is where a class count
414 // and a horizon live. Each call is the family's own predicate, the same one
415 // its analyzer raises, so a pair this report offers is a pair the run
416 // accepts and the two cannot drift.
417 if (family == "ba") {
418 const std::string why = ba::method_refusal(sn, method);
419 if (!why.empty()) return "SolverBA: " + why;
420 }
421 if (family == "ctmc") {
422 // Only the shape-restricted method has one; the rest are gated by the
423 // state-space size below, which is a question about the solver.
424 const std::string why =
425 method == "mdd" ? ctmc::solver_ctmc_mdd_supports(sn) : std::string();
426 if (!why.empty()) return why;
427 }
428 // THE FORK-JOIN MODEL CLASS, which is a question about the MODEL and not
429 // about the method, so both exact families have to clear it whichever name
430 // was asked for: each tag-augments through `fj_tag`, whose first line is
431 // `sn_fj_validate`. The feature set cannot state it -- Fork and Join are
432 // declared, and the rules are about how they are WIRED -- and the pairing in
433 // particular is a DECLARATION on the Join rather than something the routing
434 // implies, so a Join that names no fork leaves an unmatched Fork behind.
435 if (family == "ctmc" || family == "ssa") {
436 const std::string why = qn::sn_fj_supports(sn);
437 if (!why.empty()) return why;
438 }
439 // `nrm` IS THE ONE SSA METHOD WITH A MODEL CLASS OF ITS OWN, and the test
440 // already existed: `ssa_nrm_eligible` decides whether the dispatch may
441 // PREFER the NRM, while nothing decided whether the name could be OFFERED.
442 // So `ssa.nrm` was reported runnable on every model and an explicit request
443 // then raised from the analyzer. `ssa_nrm_supports` is the same six checks
444 // read as a sentence.
445 if (family == "ssa" && method == "nrm") {
446 const std::string why = ssa::detail::ssa_nrm_supports(sn);
447 if (!why.empty()) return why;
448 }
449 // A fluid Petri net has one engine, the NRM Petri runner, so `serial` is withdrawn and every other SSA
450 // method is bound by that runner's reach (`ssa_fluid_refusal`, as `SolverSSA.supportsModelMethod`).
451 if (family == "ssa") {
452 const api::SnFluidVerdict fv = ssa::detail::ssa_fluid_refusal(sn, method);
453 if (!fv.ok) return fv.reason;
454 }
455 if (family == "fld") {
456 // The horizon is an OPTION, not a model feature, so it cannot be a
457 // feature-set delta. A probe carries the defaults, under which the
458 // time-varying methods have no finite end and the refusal says so.
459 const std::string why = fluid::fluid_qsys_horizon_supports(method, fluid::FluidOptions());
460 if (!why.empty()) return why;
461 // Fork AND open is a CONJUNCTION of two declared names, which no feature
462 // set can state; `dae` is the one method the MMT fixed point has no
463 // route for on an open model.
464 const std::string fj = fluid::detail::fluid_forkjoin_supports(sn, method);
465 if (!fj.empty()) return fj;
466 }
467
468 // WHAT auto_supports STILL REFUSES once the feature set has passed is one of
469 // its two residual rules, and each is named rather than reported as a bare
470 // no. Asking auto_supports rather than repeating the rules keeps it the one
471 // authority; the branches below only turn its verdict into a sentence.
473 if (detail::auto_slot_of_family(family, slot)) {
474 if (!auto_solver_is_available(slot))
475 return std::string(auto_solver_name(slot)) +
476 ": no engine for this solver is available in this build.";
477 if (!auto_supports(slot, sn, method)) {
478 if (method == "exact" && (slot == AutoSolver::MVA || slot == AutoSolver::NC))
479 return std::string(auto_solver_name(slot)) +
480 ": 'exact' needs a product-form model, and this one has no product-form "
481 "solution.";
482 if (slot == AutoSolver::CTMC)
483 return "SolverCTMC: the state space of this model is too large to enumerate.";
484 return label + ": this solver refuses the model through its own structural check.";
485 }
486 }
487 return "";
488}
489
490template <class T>
491bool auto_family_supports(const std::string& family, const qn::NetworkStruct<T>& sn,
492 const std::string& method) {
493 return auto_family_refusal(family, sn, method).empty();
494}
495
496/**
497 * `resolveMethod` for a family method name: the concrete method METHOD runs as on
498 * THIS model, i.e. what the run dispatches when asked for it.
499 *
500 * Each family's rule is its own function, asked rather than restated; the ones
501 * MATLAB overrides `resolveMethod` for are the ones that resolve:
502 * - mva `mva::resolve_method`: `default` to `rqna` on a bursty single-class
503 * open model, to `erlanga`/`mgisrgi` on a reneging single queue;
504 * - fld `default` to `fluid_resolve_default_method`'s choice (`dae` on a
505 * Petri net, then `fluid_resolve_method`), `mfq`/`aoi` to `matrix` off
506 * the single-queue shape, `rmf`/`refined` by the presence of a Cache;
507 * - ba `ba::resolve_model_method`: the aliases, then `qrf.bas` on a blocked
508 * model of the right shape;
509 * - jmt `default` to `jsim`;
510 * - nc the identity (`nc::resolve_method`), as every other family.
511 * A name that does not resolve is returned as GIVEN, never canonicalized, so the
512 * report says "runs as" only where the method that answers is a different one.
513 */
514template <class T>
515std::string auto_family_resolve_method(const std::string& family, const qn::NetworkStruct<T>& sn,
516 const std::string& method) {
517 if (family == "mva") return mva::resolve_method(sn, method);
518 if (family == "nc") return nc::resolve_method(sn, method);
519 if (family == "ba") return ba::resolve_model_method(sn, method);
520 if (family == "jmt") {
521 std::string low;
522 for (char ch : method) low += static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
523 return low == "default" ? std::string("jsim") : method;
524 }
525 if (family == "fld") {
526 const std::string m = fluid::detail::fluid_unqualify(method);
527 // fluid_resolve_default_method.m asks the Petri question first; the C++
528 // runner branches on it before resolving, so the resolution states it here.
529 if (m == "default" && fluid::detail::fluid_is_petri_net(sn)) return "dae";
530 if (m != "default" && m != "mfq" && m != "rmf" && m != "refined") return method;
531 const std::string r = fluid::detail::fluid_resolve_method(sn, method);
532 return r == m ? method : r;
533 }
534 return method;
535}
536
537/** `NetworkSolver.supportsResolvedMethod`'s answer: the gate on the resolved name. */
539 bool ok = true; ///< the model passes the gate of the method that runs
540 std::string reason; ///< why it does not, "" when it does
541 std::string method; ///< the method METHOD runs as on this model
542};
543
544/**
545 * `NetworkSolver.supportsResolvedMethod` for a family method name: resolve first,
546 * then gate the RESOLVED name, which is what the run does. findSolver asks this of
547 * every declared name, so a row it calls runnable is a run the gate admits.
548 */
549template <class T>
552 const std::string& method) {
554 v.method = auto_family_resolve_method(family, sn, method);
555 v.reason = auto_family_refusal(family, sn, v.method);
556 v.ok = v.reason.empty();
557 return v;
558}
559
560// ---------------------------------------------------------------------------
561// findSolver: which solvers and methods can analyze this model
562// Port of @SolverAUTO/findSolver.m and its native python and JAR twins.
563// ---------------------------------------------------------------------------
564
565/**
566 * One row of `auto_find_solver`: a (family, method) pair this model can be
567 * asked for, whether it runs, what kind of answer it returns and which
568 * measures it can report.
569 *
570 * The fields are the columns of the MATLAB table, of the native python frame
571 * and of the JAR's SolverCandidate, under the same names, so a report can be
572 * compared across the four codebases row for row.
573 */
575 std::string solver; ///< the method family: "mva", "ctmc", "ldes", ...
576 std::string method; ///< the method name to pass, "mva.exact"
577 bool runnable = false; ///< the model passes this method's own support gate
578 std::string method_class; ///< "exact", "approx", "bound" or "simulation"
579 std::string metrics; ///< the measure groups the family answers, comma-joined
580 std::string reason; ///< why a refused pair was refused, prefixed by "'x' runs as 'y' on
581 ///< this model." when the name resolves; "" for a runnable unresolved one
582};
583
584/**
585 * The measure groups `auto_find_solver` reports on, in report order.
586 *
587 * A group is a family of accessors that stand or fall together: a solver that
588 * returns getCdfRespT returns getCdfPassT and getPerctRespT as well, because
589 * all three read the same passage time, so listing the three separately would
590 * say nothing extra.
591 */
592inline std::vector<std::string> auto_metric_groups() {
593 return {"avg", "tran", "cdf", "prob", "tranprob", "sample",
594 "cache", "loss", "orbit", "moment", "sens"};
595}
596
597namespace detail {
598
599inline bool name_in(const std::string& n, std::initializer_list<const char*> names) {
600 for (const char* c : names)
601 if (n == c) return true;
602 return false;
603}
604
605inline bool starts_with(const std::string& s, const std::string& p) {
606 return s.size() >= p.size() && s.compare(0, p.size(), p) == 0;
607}
608
609} // namespace detail
610
611/**
612 * The measure group an accessor belongs to, "" when the name belongs to none.
613 *
614 * A group name maps to itself, so `auto_find_solver(sn, "cdf")` and
615 * `auto_find_solver(sn, "getCdfRespT")` ask the same question.
616 *
617 * THIS IS NOT `auto_choose_solver`'S TABLE, although both are keyed by accessor
618 * name. That one maps an accessor to a RANKING, i.e. which candidate should be
619 * preferred; this one maps it to a CAPABILITY question, i.e. which candidates
620 * can answer it at all. The two differ wherever a family can serve a measure
621 * but is never the one AUTO would pick for it.
622 */
623inline std::string auto_metric_group_of(const std::string& name) {
624 if (name.empty()) return "";
625 for (const std::string& g : auto_metric_groups())
626 if (g == name) return g;
627 if (name == "any" || name == "all") return "";
628 if (detail::name_in(name, {"getTranAvg", "getTranAvgVar", "tranAvg"})) return "tran";
629 if (detail::name_in(name, {"getCdfRespT", "getCdfPassT", "getPerctRespT", "getTranCdfPassT",
630 "getTranCdfRespT", "getCdfSysRespT"}))
631 return "cdf";
632 if (detail::name_in(name, {"getTranProb", "getTranProbSys", "getTranProbAggr",
633 "getTranProbSysAggr"}))
634 return "tranprob";
635 if (detail::name_in(name, {"getProb", "getProbAggr", "getProbSys", "getProbSysAggr",
636 "getProbMarg", "getProbNormConstAggr"}))
637 return "prob";
638 if (detail::name_in(name, {"sample", "sampleSys", "sampleAggr", "sampleSysAggr"}))
639 return "sample";
640 if (detail::name_in(name, {"getAvgCacheTable", "getAvgCacheT", "getAvgItemTable",
641 "getAvgItemT", "cacheAvgT", "itemAvgT", "aCaT", "aIT"}))
642 return "cache";
643 if (detail::name_in(name, {"getAvgLossTable", "getAvgLossT", "getAvgRegionLossTable",
644 "getAvgRegionLossT", "lossAvgT", "regionLossAvgT", "aLT", "aRLT"}))
645 return "loss";
646 if (detail::name_in(name, {"getAvgOrbitTable", "getAvgOrbitT", "getAvgOrbit", "orbitAvgT",
647 "aOT"}))
648 return "orbit";
649 if (detail::name_in(name, {"getMomentTable", "getMomentChainTable", "getMomentStationTable",
650 "getMomentT", "getMomentChainT", "getMomentStationT", "momentT",
651 "momentChainT", "momentStationT", "mT", "mCT", "mST"}))
652 return "moment";
653 if (detail::name_in(name, {"getSensitivityTable", "getSensitivityT", "sensitivityT", "sT",
654 "getSensitivity", "getSensitivityRanking"}))
655 return "sens";
656 // Everything else in the accessor surface is a mean measure: getAvg, its
657 // chain, node and system forms, their handles and their short aliases.
658 if (detail::starts_with(name, "getAvg") || detail::starts_with(name, "avg") ||
659 detail::name_in(name, {"getAvgSysRespT", "getAvgSysTput", "aT", "aNT", "aCT", "aST",
660 "aNCT"}))
661 return "avg";
662 return "";
663}
664
665/**
666 * The measure groups a method family can answer.
667 *
668 * Every family answers "avg", which is what a solver is for; the rest is the
669 * capability declaration this header owns.
670 *
671 * SOURCES, so that a claim here can be checked rather than trusted: "tran" is
672 * the reference's supportsTransientAnalysis, which FLD, CTMC, LDES and JMT
673 * override to true and no one else does. "cdf", "prob", "tranprob" and "sample"
674 * are the families that carry an implementation of the corresponding accessor
675 * rather than inheriting the base refusal. The remaining five groups are
676 * computed from a solver's own results, so no per-solver entry point marks
677 * them: their lists are the reference chooseSolverHeur's rankings for the same
678 * accessors, which is where AUTO already records who can serve them.
679 *
680 * A family that gains or loses a measure must be edited here in the same
681 * change, the way a solver that gains a feature is edited into its feature set:
682 * an omission here does not fail, it silently hides the family from a caller
683 * asking for that measure.
684 */
685inline std::vector<std::string> auto_family_metrics(const std::string& family) {
686 if (family == "mva") return {"avg", "prob", "cache", "orbit", "moment", "sens"};
687 if (family == "nc") return {"avg", "cdf", "prob", "cache", "moment", "sens"};
688 if (family == "ctmc")
689 return {"avg", "tran", "cdf", "prob", "tranprob", "sample", "cache", "loss", "orbit",
690 "moment"};
691 if (family == "fld") return {"avg", "tran", "cdf", "prob", "cache", "sens"};
692 if (family == "mam") return {"avg", "cdf"};
693 // The RCAT fixed point reports means only; the passage-time law it answers
694 // is the base exponential fit, not its own.
695 if (family == "ag") return {"avg"};
696 // A bound brackets the mean measures and nothing else.
697 if (family == "ba") return {"avg"};
698 if (family == "ssa") return {"avg", "cdf", "prob", "sample", "loss"};
699 if (family == "ldes")
700 return {"avg", "tran", "cdf", "prob", "sample", "cache", "loss", "orbit"};
701 if (family == "jmt") return {"avg", "tran", "cdf", "prob", "tranprob", "sample"};
702 return {"avg"};
703}
704
705/**
706 * `Solver.isStochasticMethod` for a family method name: does this method return
707 * seed-dependent estimates?
708 *
709 * SSA and LDES are simulators outright; JMT is one except through its
710 * analytical JMVA engine, whose own sampling variants are stochastic again; NC
711 * has the Monte Carlo, importance-sampling and MCMC routes, and answers for
712 * itself through `nc::is_stochastic_method` rather than through a second copy
713 * of that token list here.
714 */
715inline bool auto_is_stochastic_method(const std::string& family, const std::string& method) {
716 if (family == "ssa" || family == "ldes") return true;
717 if (family == "nc") return nc::is_stochastic_method(method);
718 if (family == "jmt") {
719 std::string tok;
720 std::vector<std::string> toks;
721 for (char ch : method) {
722 if (ch == '.' || ch == '/') {
723 toks.push_back(tok);
724 tok.clear();
725 } else {
726 tok += static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
727 }
728 }
729 toks.push_back(tok);
730 bool jmva = false;
731 for (const std::string& t : toks)
732 if (t == "jmva") jmva = true;
733 if (!jmva) return true;
734 for (const std::string& t : toks)
735 if (t == "ls" || t == "mci" || t == "imci" || t == "sampling") return true;
736 return false;
737 }
738 return false;
739}
740
741namespace detail {
742inline std::string exact_if(bool cond) { return cond ? "exact" : "approx"; }
743} // namespace detail
744
745/**
746 * What KIND of answer a method returns: "exact", "approx", "bound" or
747 * "simulation".
748 *
749 * "simulation" is not decided here: `is_stochastic` is the solver's own verdict,
750 * which already tokenizes qualified and runtime-resolved names.
751 *
752 * "exact" IS CLAIMED ONLY WHERE IT IS TRUE OF THIS MODEL, never of the
753 * algorithm in the abstract. Exactness of a normalizing constant or of mean
754 * value analysis is a property of the product-form model it is computed on, and
755 * of the QBD shape for the matrix analytic methods, so both conditions are
756 * passed in and a method that needs one reports "approx" without it. The bias
757 * is deliberate: an under-claimed "approx" costs a user a better method they
758 * could have had, an over-claimed "exact" costs them a wrong number they
759 * trusted.
760 *
761 * A CACHE IS THE THIRD CONDITION, and it was the over-claim the bias above
762 * exists to prevent. `has_product_form` answers about the QUEUEING network and
763 * knows nothing of a cache: the hit/miss split is a class switch whose
764 * probabilities are not routing data but the output of a cache model, so a
765 * network holding one reads as product form and "mva.exact" was labelled exact
766 * on it. Measured on the tut06 shape with an LRU cache: exact MVA returns QLen
767 * 0.2516 at the hit station where the CTMC returns 0.3022 and simulation 0.3023,
768 * a 17% error under a label that says there is none. The analytic families are
769 * conditioned on it; SolverCTMC is NOT, because its state space carries the
770 * cache contents and it is exact there, which is what the two numbers show.
771 */
772inline std::string auto_method_class(const std::string& family, const std::string& method,
773 bool is_stochastic, bool is_product_form,
774 bool is_qbd_shape, bool has_cache = false) {
775 // Bounds are what SolverBA is for; every one of its methods returns a
776 // bracket rather than an estimate.
777 if (family == "ba") return "bound";
778 if (is_stochastic) return "simulation";
779 if (family == "ctmc") {
780 // The generator is solved as written, so every state-space route is
781 // exact. "mdd" is exact on a product-form model and an approximation
782 // otherwise.
783 if (method == "mdd") return detail::exact_if(is_product_form);
784 return "exact";
785 }
786 if (family == "nc") {
787 // The normalizing-constant routes that evaluate G exactly rather than
788 // expanding or estimating it. The asymptotic expansions (pana, le,
789 // kt, bk, gm, ...) and the non-product-form "morrison" are
790 // approximations by construction and are left out. "cftp" samples the
791 // product-form stationary law itself, so it is exact on the same terms;
792 // "cftp.approx" says in its own name that it is not.
793 if (detail::name_in(method, {"exact", "divdiff", "ca", "comom", "comomld", "rec", "ms",
794 "cub", "rgf", "cftp"}))
795 return detail::exact_if(is_product_form && !has_cache);
796 return "approx";
797 }
798 if (family == "mva") {
799 // Exact MVA; every "amva.*" arm is an approximation, and so are the
800 // open-network QNA transforms.
801 if (detail::name_in(method, {"exact", "mva"}))
802 return detail::exact_if(is_product_form && !has_cache);
803 return "approx";
804 }
805 if (family == "jmt") {
806 // JMVA's exact algorithms. "default" and "jsim" are simulation and
807 // never reach here.
808 if (detail::name_in(method, {"jmva.mva", "jmva.recal", "jmva.comom", "jmva.treeconv"}))
809 return detail::exact_if(is_product_form && !has_cache);
810 return "approx";
811 }
812 if (family == "mam") {
813 // The QBD is solved exactly on the shape it is stated for, one queueing
814 // station fed by a Source. Everything named "dec.*" is a decomposition
815 // of a larger network into such queues, hence an approximation of it.
816 if (detail::name_in(method, {"default", "mna", "ldqbd", "bgchain", "retrial"}))
817 return detail::exact_if(is_qbd_shape);
818 return "approx";
819 }
820 if (family == "ag") {
821 // Every RCAT arm estimates the reversed rate of each synchronising
822 // action and iterates to a fixed point, an approximation by
823 // construction; SolverAG's "exact" is a vestigial alias that warns and
824 // runs "inap", so nothing here is claimed exact.
825 return "approx";
826 }
827 return "approx";
828}
829
830/**
831 * `SolverAUTO.findSolver`: which solvers and solver methods can analyze this
832 * model, and for the ones that cannot, why not.
833 *
834 * THE GATE IS NOT A SECOND ONE. It is `auto_family_supports`, the gate
835 * `auto_choose_solver` applies before delegating, asked of every candidate
836 * instead of of the first feasible one -- which is exactly what
837 * `auto_list_valid_methods` already did; that function is now the method column
838 * of the runnable rows, so the two cannot disagree. What is new is that the
839 * REASON is kept rather than discarded, and that the answer carries the two
840 * facts a caller needs in order to choose among the survivors: whether the
841 * method is exact on this model, and which measures it can report.
842 *
843 * `metric` narrows the report to the pairs that answer one measure, named
844 * either by its group ("cdf") or by the accessor that returns it
845 * ("getCdfRespT"); "" or "any" keeps every pair. `show_all` keeps the refused
846 * pairs too; by default only the runnable ones are listed, since a caller
847 * asking what it can run has no use for the rows that say it cannot.
848 */
849template <class T>
850std::vector<SolverCandidate> auto_find_solver(const qn::NetworkStruct<T>& sn,
851 const std::string& metric = std::string(),
852 bool show_all = false) {
853 const std::string group = auto_metric_group_of(metric);
854 if (group.empty() && !metric.empty() && metric != "any" && metric != "all") {
855 std::string groups;
856 for (const std::string& g : auto_metric_groups()) {
857 if (!groups.empty()) groups += ", ";
858 groups += g;
859 }
860 // InputError and not a bare runtime_error: this is a CALLER mistake, and
861 // the CLI reports the two differently -- an input error prints
862 // "line-cli: <message>" and exits 2, anything else prints "unexpected
863 // failure" and exits 3, which is what a typo'd measure was getting.
864 throw InputError("'" + metric + "' names no measure. Pass a group (" + groups +
865 ") or the accessor that returns it, e.g. 'getCdfRespT'.");
866 }
867
868 // The two model properties an exactness claim can rest on, evaluated once:
869 // a method whose exactness needs one of them reports "approx" without it.
870 const bool is_product_form = sn.has_product_form();
871 std::size_t nsources = 0;
872 for (const qn::Station<T>& st : sn.stations)
873 if (st.nodetype == lang::NodeType::Source) ++nsources;
874 const std::vector<double> njobs = sn.njobs();
875 bool all_open = !njobs.empty();
876 for (double n : njobs)
877 if (!std::isinf(n)) all_open = false;
878 const bool is_qbd_shape = all_open && (sn.nstations - nsources) == 1;
879 bool has_cache = false;
880 for (const qn::NodeDef& nd : sn.nodes)
881 if (nd.nodetype == qn::NodeType::Cache) has_cache = true;
882
883 std::vector<SolverCandidate> rows;
884 for (const std::string& family : auto_network_family_names()) {
885 const std::vector<std::string> groups = auto_family_metrics(family);
886 if (!group.empty() &&
887 std::find(groups.begin(), groups.end(), group) == groups.end())
888 continue;
889 std::string metric_list;
890 for (const std::string& g : groups) {
891 if (!metric_list.empty()) metric_list += ",";
892 metric_list += g;
893 }
894 const std::vector<std::string> declared = auto_family_methods(family, sn);
895 for (const std::string& name : declared) {
896 // Dedicated reward estimates are outside AUTO's metric catalog.
897 if (family == "ssa" && name == "mlmc") continue;
898 if (detail::starts_with(name, family + ".")) {
899 // A spelling already qualified with its own family. The fluid
900 // registry declares both "dae" and "fld.dae" so that its own
901 // gate takes either, and prefixing the family again yields
902 // "fld.fld.dae": a token that does resolve, but that names
903 // the same method twice and would double every fluid row.
904 continue;
905 }
906 if (auto_is_method_alias(family, name, declared)) {
907 // The same duplication under a different prefix. The mva
908 // registry advertises every AMVA name twice, plain and
909 // "amva."-prefixed, and the dispatch strips the prefix, so the
910 // two spellings are one algorithm; that alone was 20 of the 49
911 // mva rows of a report. The plain spelling is the one kept.
912 continue;
913 }
914 // Gated as the method the name RUNS AS, which is what the run gates:
915 // `default` refused for "(feature: Join)" reads as nonsense until the
916 // row says it ran as rqna, and a runnable `mfq` on a tandem is honest
917 // only if the row says it runs as matrix.
918 const ResolvedVerdict v = auto_family_supports_resolved(family, sn, name);
919 const bool ok = v.ok;
920 std::string reason = ok ? std::string() : v.reason;
921 if (v.method != name)
922 reason = "'" + name + "' runs as '" + v.method + "' on this model." +
923 (reason.empty() ? std::string() : " " + reason);
924 if (!ok && !show_all) continue;
925 SolverCandidate row;
926 row.solver = family;
927 row.method = family + "." + name;
928 row.runnable = ok;
929 row.method_class = auto_method_class(
930 family, name, auto_is_stochastic_method(family, name), is_product_form,
931 is_qbd_shape, has_cache);
932 row.metrics = metric_list;
933 row.reason = reason;
934 rows.push_back(row);
935 }
936 }
937 return rows;
938}
939
940/** Alias of `auto_find_solver`: the same table, asked for by method. */
941template <class T>
942std::vector<SolverCandidate> auto_find_method(const qn::NetworkStruct<T>& sn,
943 const std::string& metric = std::string(),
944 bool show_all = false) {
945 return auto_find_solver(sn, metric, show_all);
946}
947
948/** Alias of `auto_find_solver`: what can this model be solved with? */
949template <class T>
950std::vector<SolverCandidate> auto_help(const qn::NetworkStruct<T>& sn,
951 const std::string& metric = std::string(),
952 bool show_all = false) {
953 return auto_find_solver(sn, metric, show_all);
954}
955
956/**
957 * The rows as an aligned text table, the form the CLI and a console caller
958 * want. The reason column is last and unpadded, since it is the only one whose
959 * width is unbounded.
960 */
961inline std::string auto_find_solver_table(const std::vector<SolverCandidate>& rows) {
962 if (rows.empty()) return "No solver method can analyze this model.\n";
963 std::vector<std::array<std::string, 6>> cells;
964 cells.push_back({"Solver", "Method", "Runnable", "Class", "Metrics", "Reason"});
965 for (const SolverCandidate& r : rows)
966 cells.push_back({r.solver, r.method, r.runnable ? "true" : "false", r.method_class,
967 r.metrics, r.reason});
968 std::array<std::size_t, 6> width{};
969 for (const auto& row : cells)
970 for (std::size_t c = 0; c < 5; ++c) width[c] = std::max(width[c], row[c].size());
971 std::string out;
972 for (const auto& row : cells) {
973 std::string line;
974 for (std::size_t c = 0; c < 5; ++c) line += row[c] + std::string(width[c] - row[c].size() + 2, ' ');
975 line += row[5];
976 while (!line.empty() && line.back() == ' ') line.pop_back();
977 out += line + "\n";
978 }
979 return out;
980}
981
982/**
983 * `SolverAUTO.listValidMethods`: every method name this model can be asked for.
984 *
985 * The selection intents come first and unconditionally: they name a RANKING and
986 * not an algorithm, and finding a family that supports the model is the
987 * ranking's own job. `bound` is the one that is not quite a ranking -- it names
988 * SolverBA with method 'auto' -- and it is listed unconditionally all the same,
989 * because MATLAB and the JAR list it that way and a caller reading 'bound' as
990 * "give me bounds" should get SolverBA's own refusal rather than a missing
991 * method name. Then each family in `auto_network_family_names` order, bare method name
992 * first and its qualified methods after it.
993 */
994template <class T>
995std::vector<std::string> auto_list_valid_methods(const qn::NetworkStruct<T>& sn) {
996 // IT IS THE RUNNABLE ROWS OF `auto_find_solver`, projected onto their
997 // method name. The narrowing used to be written out a second time here,
998 // and a second copy of one gate is how two answers to one question start to
999 // differ; `auto_find_solver` owns it now, and this adds only the method names
1000 // that name no single method: the selection intents and each family's bare
1001 // token.
1002 //
1003 // A family that declares methods and keeps none loses its bare method name too:
1004 // it delegates to the solver the per-method gate just refused. That falls
1005 // out of the projection, since such a family contributes no row to name.
1006 std::vector<std::string> out{"accurate", "auto", "bound", "default",
1007 "exact", "fast", "heur", "sim"};
1008 for (const SolverCandidate& row : auto_find_solver(sn)) {
1009 out.push_back(row.method);
1010 out.push_back(row.solver);
1011 }
1012 std::sort(out.begin(), out.end());
1013 out.erase(std::unique(out.begin(), out.end()), out.end());
1014 return out;
1015}
1016
1017} // namespace autosolver
1018} // namespace line
1019
1020#endif // LINE_SOLVERS_AUTO_AUTO_METHODS_H
Malformed or inconsistent input (dimensions, negative populations, ...): LINE:input:<kind>.
Definition error.h:103
InputError(const std::string &what)
Definition error.h:105
UnsupportedError(const std::string &what)
Definition error.h:119
A subset of the registry: MATLAB's SolverFeatureSet, whose list is a flag per field.
A network plus its refreshed NetworkStruct.
The exception types the port throws.
The language-feature gate: what a MODEL uses against what a SOLVER declares.
The fluid solver's outermost entry point: @@SolverFLD/runAnalyzer.m's method resolution over solver_f...
The option and result records of SolverLDES, the discrete-event simulator.
The flat-Network path of @@SolverLQNS: its qns methods, served by qnsolver of the RADS/LQNS distribut...
std::vector< std::string > list_valid_methods()
Every method SolverAG serves, as the other families expose theirs.
std::string sn_patience_refusal(const qn::NetworkStruct< T > &sn, std::size_t ist, std::size_t r)
Port of sn_patience_refusal.m: the by-name refusal of a reneging patience that is not a Markovian law...
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...
AutoSolver
The Network candidate slots, in the reference's slot order (SolverAUTO.m:41-50), which is also the or...
std::string auto_method_class(const std::string &family, const std::string &method, bool is_stochastic, bool is_product_form, bool is_qbd_shape, bool has_cache=false)
What KIND of answer a method returns: "exact", "approx", "bound" or "simulation".
std::vector< std::string > auto_network_family_names()
The method families that solve a flat Network, in SolverAUTO.familyNames' own order – which is also t...
std::string auto_metric_group_of(const std::string &name)
The measure group an accessor belongs to, "" when the name belongs to none.
bool auto_solver_is_available(AutoSolver s)
True when this port has an engine behind the slot at all.
std::string auto_family_refusal(const std::string &family, const qn::NetworkStruct< T > &sn, const std::string &method)
Solver.supportsModelMethod for a family method name: may THIS model run THIS method of THIS family?
std::string auto_find_solver_table(const std::vector< SolverCandidate > &rows)
The rows as an aligned text table, the form the CLI and a console caller want.
bool auto_is_stochastic_method(const std::string &family, const std::string &method)
Solver.isStochasticMethod for a family method name: does this method return seed-dependent estimates?
std::vector< std::string > auto_family_metrics(const std::string &family)
The measure groups a method family can answer.
std::vector< SolverCandidate > auto_find_method(const qn::NetworkStruct< T > &sn, const std::string &metric=std::string(), bool show_all=false)
Alias of auto_find_solver: the same table, asked for by method.
std::string auto_family_resolve_method(const std::string &family, const qn::NetworkStruct< T > &sn, const std::string &method)
resolveMethod for a family method name: the concrete method METHOD runs as on THIS model,...
std::vector< std::string > auto_metric_groups()
The measure groups auto_find_solver reports on, in report order.
bool auto_family_supports(const std::string &family, const qn::NetworkStruct< T > &sn, const std::string &method)
const char * auto_solver_name(AutoSolver s)
AutoToken auto_resolve_token(const std::string &raw, const qn::NetworkStruct< T > &sn)
resolveMethodToken with its third arm: an intent, a family[.submethod], or else a BARE method name,...
std::vector< SolverCandidate > auto_find_solver(const qn::NetworkStruct< T > &sn, const std::string &metric=std::string(), bool show_all=false)
SolverAUTO.findSolver: which solvers and solver methods can analyze this model, and for the ones that...
std::vector< SolverCandidate > auto_help(const qn::NetworkStruct< T > &sn, const std::string &metric=std::string(), bool show_all=false)
Alias of auto_find_solver: what can this model be solved with?
std::vector< std::string > auto_list_valid_methods(const qn::NetworkStruct< T > &sn)
SolverAUTO.listValidMethods: every method name this model can be asked for.
std::vector< std::string > auto_family_methods(const std::string &family, const qn::NetworkStruct< T > &sn)
The methods a family declares ON THIS MODEL, empty when it declares none.
ResolvedVerdict auto_family_supports_resolved(const std::string &family, const qn::NetworkStruct< T > &sn, const std::string &method)
NetworkSolver.supportsResolvedMethod for a family method name: resolve first, then gate the RESOLVED ...
std::string auto_family_declaring_method(const qn::NetworkStruct< T > &sn, const std::string &name)
familyDeclaringMethod: the first family, in familyNames order, whose method list on this model names ...
std::vector< std::string > auto_method_alias_prefixes(const std::string &family)
The prefixes under which a family advertises a SECOND SPELLING of a method it already declares plainl...
bool auto_is_method_alias(const std::string &family, const std::string &name, const std::vector< std::string > &declared)
Is name a second spelling of another method this family declares?
std::string resolve_model_method(const qn::NetworkStruct< T > &L, const std::string &method)
Port of ba_resolve_model_method.m (SolverBA.resolveMethod): the method METHOD runs as on THIS model.
std::string method_refusal(const qn::NetworkStruct< T > &L, const std::string &method)
The STRUCTURAL premises of the SolverBA bound families, in one place: the reason METHOD cannot bound ...
std::vector< std::string > list_valid_methods()
Port of SolverBA.listValidMethods.
std::vector< std::string > list_valid_methods()
Port of SolverCTMC.listValidMethods.
std::string solver_ctmc_mdd_supports(const NetworkStruct< T > &sn)
Can the mdd decision-diagram method be asked for this model?
std::string fluid_qsys_horizon_supports(const std::string &method, const FluidOptions &opt)
The horizon rule the time-varying limits impose, as a public predicate a REPORT can ask: empty when m...
Definition fluid_qsys.h:143
std::vector< std::string > fluid_list_valid_methods()
Port of SolverFLD.listValidMethods.
std::vector< std::string > jmt_list_valid_methods()
Port of SolverJMT.listValidMethods.
std::string jmt_method_refusal(const qn::NetworkStruct< T > &sn, const std::string &method, const JmtOptions &opt)
bool ldes_is_available()
True when this machine can run the engine at all, by either image.
Definition ldes_probe.h:253
std::vector< std::string > list_valid_methods()
Port of SolverLDES.listValidMethods.
std::vector< std::string > qns_methods()
Port of SolverLQNS.listValidMethods on a flat Network.
std::string qns_method_refusal(const qn::NetworkStruct< T > &L, const std::string &method)
SolverLQNS.supportsModelMethod's structural rules on a Network, as the REASON they refuse,...
bool qnsolver_is_available()
Port of SolverLQNS.hasQnsolver: a native qnsolver binary on the PATH.
std::vector< std::string > list_valid_methods()
Port of SolverMAM.listValidMethods.
std::string mam_model_method_refusal(const qn::NetworkStruct< T > &L, const std::string &method)
check_model_method asked WITHOUT raising: the same verdict as a sentence.
std::string resolve_method(const qn::NetworkStruct< T > &L, const std::string &method)
Port of SolverMVA.resolveMethod: the feature-driven default -> rqna upgrade for a bursty single-class...
std::vector< std::string > list_valid_methods(const qn::NetworkStruct< T > &L)
Port of SolverMVA.listValidMethods.
std::string mva_erlanga_patience_reason(const qn::NetworkStruct< T > &L)
Why method 'erlanga' cannot answer this model's patience, or "".
MemSupport solver_nc_mem_supports(const qn::NetworkStruct< T > &sn)
Port of solver_nc_mem_supports.m.
std::string resolve_method(const qn::NetworkStruct< T > &, const std::string &method)
Port of SolverNC.resolveMethod: default is NOT resolved ahead of the run.
std::vector< std::string > list_valid_methods()
Port of SolverNC.listValidMethods.
bool is_stochastic_method(const std::string &method)
Port of SolverNC.isStochasticMethod.
std::string nc_method_refusal(const qn::NetworkStruct< T > &sn, const std::string &method, bool slotted=false, bool for_report=true)
May method run on this model?
std::string solver_nc_cftp_supports(const qn::NetworkStruct< T > &sn)
The cftp model-class gate as a public predicate.
FeatureSet ldes_feature_set(const std::string &)
SolverLDES.getFeatureSet, transcribed WHOLE.
FeatureSet fluid_feature_set(const std::string &method)
FeatureSet ssa_feature_set(const std::string &method)
FeatureSet jmt_feature_set(const std::string &method)
Port of SolverJMT.getFeatureSet (@@SolverJMT/SolverJMT.m:180-290).
FeatureSet used_lang_features(const NetworkStruct< T > &sn)
FeatureSet lqns_network_feature_set(const std::string &)
SolverLQNS.getFeatureSet on a flat Network (its qns methods), transcribed WHOLE.
FeatureSet nc_feature_set(const std::string &method)
SolverNC.getFeatureSet, 48 names, transcribed unchanged.
FeatureSet ba_feature_set(const std::string &method)
SolverBA.getFeatureSet, transcribed name for name.
std::string sn_fj_supports(const NetworkStruct< T > &sn)
Can the exact fork-join construction be asked for this model?
Definition fj_tag.h:282
bool has_binding_capacity(const NetworkStruct< T > &sn)
getUsedLangFeatures: the features the MODEL uses.
FeatureSet ctmc_feature_set(const std::string &method)
SolverCTMC.getFeatureSet, the reference's 104 MATLAB names in full.
SupportResult feature_set_supports(const std::string &solver, const FeatureSet &declared, const FeatureSet &used)
SolverFeatureSet.supports: is every feature the model uses declared?
FeatureSet mam_feature_set(const std::string &method)
SolverMAM.getFeatureSet, the union of its four setTrue calls: 55 MATLAB names, WIDENED for 'default'/...
FeatureSet mva_feature_set(const std::string &raw_method)
SolverMVA.getFeatureSet, plus getMethodFeatureSet's per-method deltas.
FeatureSet ag_feature_set(const std::string &)
SolverAG.getFeatureSet: what the RCAT decomposition can represent.
std::vector< std::string > list_valid_methods()
Port of SolverSSA.listValidMethods.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
The gates and the dispatch of the agent-based (RCAT) solver.
The SolverAUTO chooser: which solver a model is handed to.
The SolverBA class surface: @@SolverBA/runAnalyzer.m, listValidMethods, getBounds and getBoundsTable.
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
The mdd method of SolverCTMC: stationary analysis of a closed single-class network whose state space ...
The DECLARED side of the gate: one feature set per solver.
Port of SolverJMT, the Java Modelling Tools client.
The SolverMAM class surface: @@SolverMAM/runAnalyzer.m and the gates around it.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
The SolverNC class surface: @@SolverNC/runAnalyzer.m and the gates around it.
The SolverSSA entry surface: a port of @@SolverSSA/runAnalyzer.m's method whitelist,...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
The verdict of sn_fluid_refusal: ok when no rule is broken, else the reason.
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
NetworkSolver.supportsResolvedMethod's answer: the gate on the resolved name.
bool ok
the model passes the gate of the method that runs
std::string method
the method METHOD runs as on this model
std::string reason
why it does not, "" when it does
One row of auto_find_solver: a (family, method) pair this model can be asked for, whether it runs,...
std::string method
the method name to pass, "mva.exact"
bool runnable
the model passes this method's own support gate
std::string metrics
the measure groups the family answers, comma-joined
std::string reason
why a refused pair was refused, prefixed by "'x' runs as 'y' on this model.
std::string solver
the method family: "mva", "ctmc", "ldes", ...
std::string method_class
"exact", "approx", "bound" or "simulation"
Controls, defaulting to SolverOptions('Fluid') in the reference.
The options of one JMT solve, SolverOptions('JMT') restricted to what is read.
Definition solver_jmt.h:121
A node of the network.
One station of the network.
The outcome of the gate: the verdict, the offending features, the message.
std::string reason
empty when ok