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 * THERE IS NO `list_all_methods` TWIN. In the reference the model-independent
68 * list exists so that a method-NAME check can reject an unknown method name with the
69 * family's own explanation rather than a flat "unsupported"; `auto_resolve_token`
70 * here performs no such check -- it splits the method name and lets the family refuse
71 * the submethod by name -- so a second list would have no caller.
72 */
73
74#include <algorithm>
75#include <array>
76#include <cctype>
77#include <cmath>
78#include <cstddef>
79#include <initializer_list>
80#include <stdexcept>
81#include <string>
82#include <vector>
83
85#include "line/util/error.h"
92// The shape predicate the report asks for, which the analyzer header above does
93// not pull in: mdd builds no generator and is restricted to a shape the
94// state-space guard says nothing about. NC's cftp predicate comes in with
95// solver_nc_runner.h below.
105
106namespace line {
107namespace autosolver {
108
109/**
110 * The method families that solve a flat Network, in `SolverAUTO.familyNames`'
111 * own order -- which is also the order an unqualified algorithm name is looked
112 * up in, so it is not arbitrary.
113 *
114 * "ag" SITS AFTER "mam", whose RCAT names it took over, and is a family for the
115 * METHOD NAME and REPORT tables only: `--method ag.inap` and the model help
116 * reach SolverAG through it. It is deliberately NOT one of the ranked
117 * `AutoSolver` slots and NOT in the feature-set union the automatic ranking
118 * takes, so a G-network is still refused by "default" and has to be asked for
119 * by name; see _kb/06-solver-catalog.md, "SolverAG owns the RCAT methods".
120 */
121inline std::vector<std::string> auto_network_family_names() {
122 return {"mva", "nc", "ctmc", "fld", "mam", "ag", "ba", "ssa", "ldes", "jmt", "lqns"};
123}
124
125/**
126 * The prefixes under which a family advertises a SECOND SPELLING of a method it
127 * already declares plainly.
128 *
129 * THIS IS A DECLARATION, not a derivation, and belongs beside
130 * `auto_family_metrics` and `auto_method_class` for the same reason: the
131 * knowledge lives in the solver's own dispatch (the mva registry strips a
132 * leading "amva." before selecting an algorithm) and nothing exposes it, so a
133 * family that gains or loses an alias spelling must be edited into all four
134 * copies in the SAME change. An omission does not fail; it puts the same
135 * algorithm in the report twice.
136 */
137inline std::vector<std::string> auto_method_alias_prefixes(const std::string& family) {
138 if (family == "mva") return {"amva."};
139 return {};
140}
141
142/**
143 * Is `name` a second spelling of another method this family declares?
144 *
145 * The remainder has to be declared too, which is what keeps the rule from eating
146 * a genuine method that merely starts with the prefix: it is an alias only when
147 * the thing it aliases is there beside it.
148 */
149inline bool auto_is_method_alias(const std::string& family, const std::string& name,
150 const std::vector<std::string>& declared) {
151 for (const std::string& prefix : auto_method_alias_prefixes(family)) {
152 if (name.size() > prefix.size() && name.compare(0, prefix.size(), prefix) == 0 &&
153 std::find(declared.begin(), declared.end(), name.substr(prefix.size())) !=
154 declared.end())
155 return true;
156 }
157 return false;
158}
159
160namespace detail {
161
162/** The ranked slot a family occupies, or false when it has none. */
163inline bool auto_slot_of_family(const std::string& family, AutoSolver& out) {
164 if (family == "mva") { out = AutoSolver::MVA; return true; }
165 if (family == "nc") { out = AutoSolver::NC; return true; }
166 if (family == "mam") { out = AutoSolver::MAM; return true; }
167 if (family == "fld") { out = AutoSolver::FLUID; return true; }
168 if (family == "ssa") { out = AutoSolver::SSA; return true; }
169 if (family == "ctmc") { out = AutoSolver::CTMC; return true; }
170 if (family == "ldes") { out = AutoSolver::LDES; return true; }
171 return false;
172}
173
174/**
175 * The finite-buffer gate as a predicate.
176 *
177 * `has_binding_capacity` is the gate's own question asked without raising, which
178 * is what this needs: on the solve path a capped station the solver cannot
179 * honour has to stop the run rather than be reported unconstrained, but here the
180 * same verdict is a yes or a no. The families are the ones whose own runners ask
181 * it -- SolverMVA, SolverNC, SolverFLD and SolverLQNS -- and no feature name
182 * describes a capacity, which is why it cannot ride in the feature set.
183 *
184 * NC EXEMPTS ITS MEM ALGORITHM, which solves the censored GE/GE/c/0;N queue and
185 * therefore DOES honour the buffer. The exemption is on the LITERAL method name
186 * that `nc_dispatch` branches on, not on a resolved one: nothing resolves
187 * 'default' into 'mem', so exempting a default run would advertise a token that
188 * dispatches somewhere the buffer is ignored.
189 */
190template <class T>
191bool capacity_admits(const std::string& family, const qn::NetworkStruct<T>& sn,
192 const std::string& method) {
193 if (family != "mva" && family != "nc" && family != "fld" && family != "lqns") return true;
194 if (!qn::has_binding_capacity(sn)) return true;
195 if (family == "nc" && method == "mem") {
196 const nc::MemSupport ms = nc::solver_nc_mem_supports(sn);
197 return ms.supported && ms.blocking;
198 }
199 return false;
200}
201
202/**
203 * The feature set a family declares for a method, and the label its refusals
204 * are reported under; false when the family solves no flat Network.
205 *
206 * It is the switch `auto_supports` opens on, lifted out so that the bool gate
207 * and the reason-returning one read the same table. A second copy of it is how
208 * a family gains a feature in one answer and not in the other.
209 */
210inline bool declared_feature_set(const std::string& family, const std::string& method,
211 qn::FeatureSet& declared, std::string& label) {
212 if (family == "mva") { declared = qn::mva_feature_set(method); label = "SolverMVA"; return true; }
213 if (family == "nc") { declared = qn::nc_feature_set(method); label = "SolverNC"; return true; }
214 if (family == "mam") { declared = qn::mam_feature_set(method); label = "SolverMAM"; return true; }
215 if (family == "fld") { declared = qn::fluid_feature_set(method); label = "SolverFLD"; return true; }
216 if (family == "ssa") { declared = qn::ssa_feature_set(method); label = "SolverSSA"; return true; }
217 if (family == "ctmc") { declared = qn::ctmc_feature_set(method); label = "SolverCTMC"; return true; }
218 if (family == "ldes") { declared = qn::ldes_feature_set(method); label = "SolverLDES"; return true; }
219 if (family == "ag") { declared = qn::ag_feature_set(method); label = "SolverAG"; return true; }
220 if (family == "ba") { declared = qn::ba_feature_set(method); label = "SolverBA"; return true; }
221 if (family == "jmt") { declared = qn::jmt_feature_set(method); label = "SolverJMT"; return true; }
222 if (family == "lqns") {
223 declared = qn::lqns_network_feature_set(method);
224 label = "SolverLQNS";
225 return true;
226 }
227 return false;
228}
229
230} // namespace detail
231
232/**
233 * The methods a family declares ON THIS MODEL, empty when it declares none.
234 *
235 * The registries are asked rather than copied: `mva::list_valid_methods` and
236 * `ba::list_valid_methods` take the struct and narrow themselves, and a second
237 * copy of either shape rule here is how the two drift apart.
238 */
239template <class T>
240std::vector<std::string> auto_family_methods(const std::string& family,
241 const qn::NetworkStruct<T>& sn) {
242 if (family == "mva") return mva::list_valid_methods(sn);
243 if (family == "nc") return nc::list_valid_methods();
244 if (family == "ctmc") return ctmc::list_valid_methods();
245 if (family == "fld") return fluid::fluid_list_valid_methods();
246 if (family == "mam") return mam::list_valid_methods();
247 if (family == "ag") return ag::list_valid_methods();
248 if (family == "ba") return ba::list_valid_methods(sn);
249 if (family == "ssa") return ssa::list_valid_methods();
250 if (family == "ldes") return ldes::list_valid_methods();
251 if (family == "jmt") return jmt::jmt_list_valid_methods();
252 if (family == "lqns") return lqns::qns_methods();
253 return std::vector<std::string>();
254}
255
256/**
257 * `Solver.supportsModelMethod` for a family method name: may THIS model run THIS
258 * method of THIS family?
259 *
260 * The seven ranked slots defer to `auto_supports`, which is the same call
261 * `chooseSolverRanked` makes and already carries the product-form rule, the
262 * CTMC state-space screen and the LDES engine probe. The three families with no
263 * slot are gated on their own declared set, since they are reachable by explicit
264 * token and by no ranking.
265 */
266template <class T>
267std::string auto_family_refusal(const std::string& family, const qn::NetworkStruct<T>& sn,
268 const std::string& method) {
269 // THE PERFECT SAMPLER'S OWN PREDICATE FIRST, ahead of the capacity and
270 // feature gates, so a caller reads "the cftp method supports closed models
271 // only" rather than "(feature: OpenClass)". It is the same call
272 // `solver_nc_solve` refuses through; NC's normal gates follow when it passes.
273 if (family == "nc" && (method == "cftp" || method == "cftp.approx")) {
274 const std::string why = nc::solver_nc_cftp_supports(sn);
275 if (!why.empty()) return why;
276 }
277 if (!detail::capacity_admits(family, sn, method))
278 return "SolverAUTO: this method ignores the finite station capacity this model sets "
279 "(setCapacity / classCap), so it would report a capped station as unbounded.";
280
281 // rmf/refined resolve together off the presence of Cache nodes (fluid_resolve_method), so on a cache-free model
282 // 'rmf' is gated as 'refined' and inherits its closed-model restriction. MATLAB resolves at the same point.
283 std::string gated = method;
284 if (family == "fld") {
285 const std::string m = fluid::detail::fluid_unqualify(method);
286 if (m == "rmf" || m == "refined") gated = fluid::detail::fluid_has_cache(sn) ? "rmf" : "refined";
287 }
288 qn::FeatureSet declared;
289 std::string label;
290 if (!detail::declared_feature_set(family, gated, declared, label))
291 return "SolverAUTO: the '" + family + "' method family does not solve a flat Network.";
292
294 if (!r.ok) return r.reason;
295
296 // NC's structural per-method rules, the ones no feature name can carry:
297 // whether the method has a route on THIS model at all. `nc_method_refusal`
298 // is the single copy of them, asked here and by `solver_nc_solve`, so a pair
299 // this report offers is a pair the run accepts. Asked BEFORE `auto_supports`
300 // because it names the offending thing, where the fallback below can only
301 // say that some structural check refused the model.
302 if (family == "nc") {
303 const std::string ncr = nc::nc_method_refusal(sn, method);
304 if (!ncr.empty()) return ncr;
305 }
306
307 // THE STRUCTURAL RULES OF THE TWO WRAPPER-ADJACENT FAMILIES, asked of the
308 // families themselves rather than restated here. `jmt` has no ranked slot,
309 // so the branch below never reaches it, and `auto_supports` asks the MAM
310 // slot for its feature set only -- which is how findSolver came to offer
311 // jmt.jmva.<alg> on a multi-server model, the since-removed jmt.replication with
312 // no horizon and mam.retrial on a model that declares no orbit, each of which then
313 // raised when it was run. The two calls are the solvers' OWN predicates,
314 // the same ones their runners raise, taken with the default options a probe
315 // solver carries.
316 if (family == "jmt") {
317 const std::string why = jmt::jmt_method_refusal(sn, method, jmt::JmtOptions());
318 if (!why.empty()) return why;
319 }
320 if (family == "mam") {
321 const std::string why = mam::mam_model_method_refusal(sn, method);
322 if (!why.empty()) return why;
323 }
324 // AG's own structural gate, on the same terms: RCAT needs a Markovian
325 // (D0,D1) service law, state-independent routing, one server per station and
326 // no binding buffer, and none of the four is a feature name. It has no
327 // ranked slot, so nothing below reaches it.
328 if (family == "ag") {
329 const std::string why = ag::runner_detail::method_refusal(sn, method);
330 if (!why.empty()) return why;
331 }
332 // LQNS on a Network on the same terms: immediate feedback is outside BOTH
333 // conversions, and the multiserver approximations `qnsolver -m` does not
334 // offer are a rule about the method rather than about the model.
335 if (family == "lqns") {
336 const std::string why = lqns::qns_method_refusal(sn, method);
337 if (!why.empty()) return why;
338 }
339
340 // THE REMAINING THREE FAMILIES, on the same terms. `ba` has no ranked slot
341 // either, so nothing below reaches it; `ctmc` and `fluid` do have one, but
342 // `auto_supports` asks each for a rule about the SOLVER (state-space size,
343 // product form) and never about the METHOD, which is where a class count
344 // and a horizon live. Each call is the family's own predicate, the same one
345 // its analyzer raises, so a pair this report offers is a pair the run
346 // accepts and the two cannot drift.
347 if (family == "ba") {
348 const std::string why = ba::method_refusal(sn, method);
349 if (!why.empty()) return "SolverBA: " + why;
350 }
351 if (family == "ctmc") {
352 // Only the shape-restricted method has one; the rest are gated by the
353 // state-space size below, which is a question about the solver.
354 const std::string why =
355 method == "mdd" ? ctmc::solver_ctmc_mdd_supports(sn) : std::string();
356 if (!why.empty()) return why;
357 }
358 // THE FORK-JOIN MODEL CLASS, which is a question about the MODEL and not
359 // about the method, so both exact families have to clear it whichever name
360 // was asked for: each tag-augments through `fj_tag`, whose first line is
361 // `sn_fj_validate`. The feature set cannot state it -- Fork and Join are
362 // declared, and the rules are about how they are WIRED -- and the pairing in
363 // particular is a DECLARATION on the Join rather than something the routing
364 // implies, so a Join that names no fork leaves an unmatched Fork behind.
365 if (family == "ctmc" || family == "ssa") {
366 const std::string why = qn::sn_fj_supports(sn);
367 if (!why.empty()) return why;
368 }
369 // `nrm` IS THE ONE SSA METHOD WITH A MODEL CLASS OF ITS OWN, and the test
370 // already existed: `ssa_nrm_eligible` decides whether the dispatch may
371 // PREFER the NRM, while nothing decided whether the name could be OFFERED.
372 // So `ssa.nrm` was reported runnable on every model and an explicit request
373 // then raised from the analyzer. `ssa_nrm_supports` is the same six checks
374 // read as a sentence.
375 if (family == "ssa" && method == "nrm") {
376 const std::string why = ssa::detail::ssa_nrm_supports(sn);
377 if (!why.empty()) return why;
378 }
379 if (family == "fld") {
380 // The horizon is an OPTION, not a model feature, so it cannot be a
381 // feature-set delta. A probe carries the defaults, under which the
382 // time-varying methods have no finite end and the refusal says so.
383 const std::string why = fluid::fluid_qsys_horizon_supports(method, fluid::FluidOptions());
384 if (!why.empty()) return why;
385 // Fork AND open is a CONJUNCTION of two declared names, which no feature
386 // set can state; `dae` is the one method the MMT fixed point has no
387 // route for on an open model.
388 const std::string fj = fluid::detail::fluid_forkjoin_supports(sn, method);
389 if (!fj.empty()) return fj;
390 }
391
392 // WHAT auto_supports STILL REFUSES once the feature set has passed is one of
393 // its two residual rules, and each is named rather than reported as a bare
394 // no. Asking auto_supports rather than repeating the rules keeps it the one
395 // authority; the branches below only turn its verdict into a sentence.
397 if (detail::auto_slot_of_family(family, slot)) {
398 if (!auto_solver_is_available(slot))
399 return std::string(auto_solver_name(slot)) +
400 ": no engine for this solver is available in this build.";
401 if (!auto_supports(slot, sn, method)) {
402 if (method == "exact" && (slot == AutoSolver::MVA || slot == AutoSolver::NC))
403 return std::string(auto_solver_name(slot)) +
404 ": 'exact' needs a product-form model, and this one has no product-form "
405 "solution.";
406 if (slot == AutoSolver::CTMC)
407 return "SolverCTMC: the state space of this model is too large to enumerate.";
408 return label + ": this solver refuses the model through its own structural check.";
409 }
410 }
411 return "";
412}
413
414template <class T>
415bool auto_family_supports(const std::string& family, const qn::NetworkStruct<T>& sn,
416 const std::string& method) {
417 return auto_family_refusal(family, sn, method).empty();
418}
419
420// ---------------------------------------------------------------------------
421// findSolver: which solvers and methods can analyze this model
422// Port of @SolverAUTO/findSolver.m and its native python and JAR twins.
423// ---------------------------------------------------------------------------
424
425/**
426 * One row of `auto_find_solver`: a (family, method) pair this model can be
427 * asked for, whether it runs, what kind of answer it returns and which
428 * measures it can report.
429 *
430 * The fields are the columns of the MATLAB table, of the native python frame
431 * and of the JAR's SolverCandidate, under the same names, so a report can be
432 * compared across the four codebases row for row.
433 */
435 std::string solver; ///< the method family: "mva", "ctmc", "ldes", ...
436 std::string method; ///< the method name to pass, "mva.exact"
437 bool runnable = false; ///< the model passes this method's own support gate
438 std::string method_class; ///< "exact", "approx", "bound" or "simulation"
439 std::string metrics; ///< the measure groups the family answers, comma-joined
440 std::string reason; ///< why a refused pair was refused, "" when runnable
441};
442
443/**
444 * The measure groups `auto_find_solver` reports on, in report order.
445 *
446 * A group is a family of accessors that stand or fall together: a solver that
447 * returns getCdfRespT returns getCdfPassT and getPerctRespT as well, because
448 * all three read the same passage time, so listing the three separately would
449 * say nothing extra.
450 */
451inline std::vector<std::string> auto_metric_groups() {
452 return {"avg", "tran", "cdf", "prob", "tranprob", "sample",
453 "cache", "loss", "orbit", "moment", "sens"};
454}
455
456namespace detail {
457
458inline bool name_in(const std::string& n, std::initializer_list<const char*> names) {
459 for (const char* c : names)
460 if (n == c) return true;
461 return false;
462}
463
464inline bool starts_with(const std::string& s, const std::string& p) {
465 return s.size() >= p.size() && s.compare(0, p.size(), p) == 0;
466}
467
468} // namespace detail
469
470/**
471 * The measure group an accessor belongs to, "" when the name belongs to none.
472 *
473 * A group name maps to itself, so `auto_find_solver(sn, "cdf")` and
474 * `auto_find_solver(sn, "getCdfRespT")` ask the same question.
475 *
476 * THIS IS NOT `auto_choose_solver`'S TABLE, although both are keyed by accessor
477 * name. That one maps an accessor to a RANKING, i.e. which candidate should be
478 * preferred; this one maps it to a CAPABILITY question, i.e. which candidates
479 * can answer it at all. The two differ wherever a family can serve a measure
480 * but is never the one AUTO would pick for it.
481 */
482inline std::string auto_metric_group_of(const std::string& name) {
483 if (name.empty()) return "";
484 for (const std::string& g : auto_metric_groups())
485 if (g == name) return g;
486 if (name == "any" || name == "all") return "";
487 if (detail::name_in(name, {"getTranAvg", "getTranAvgVar", "tranAvg"})) return "tran";
488 if (detail::name_in(name, {"getCdfRespT", "getCdfPassT", "getPerctRespT", "getTranCdfPassT",
489 "getTranCdfRespT", "getCdfSysRespT"}))
490 return "cdf";
491 if (detail::name_in(name, {"getTranProb", "getTranProbSys", "getTranProbAggr",
492 "getTranProbSysAggr"}))
493 return "tranprob";
494 if (detail::name_in(name, {"getProb", "getProbAggr", "getProbSys", "getProbSysAggr",
495 "getProbMarg", "getProbNormConstAggr"}))
496 return "prob";
497 if (detail::name_in(name, {"sample", "sampleSys", "sampleAggr", "sampleSysAggr"}))
498 return "sample";
499 if (detail::name_in(name, {"getAvgCacheTable", "getAvgCacheT", "getAvgItemTable",
500 "getAvgItemT", "cacheAvgT", "itemAvgT", "aCaT", "aIT"}))
501 return "cache";
502 if (detail::name_in(name, {"getAvgLossTable", "getAvgLossT", "getAvgRegionLossTable",
503 "getAvgRegionLossT", "lossAvgT", "regionLossAvgT", "aLT", "aRLT"}))
504 return "loss";
505 if (detail::name_in(name, {"getAvgOrbitTable", "getAvgOrbitT", "getAvgOrbit", "orbitAvgT",
506 "aOT"}))
507 return "orbit";
508 if (detail::name_in(name, {"getMomentTable", "getMomentChainTable", "getMomentStationTable",
509 "getMomentT", "getMomentChainT", "getMomentStationT", "momentT",
510 "momentChainT", "momentStationT", "mT", "mCT", "mST"}))
511 return "moment";
512 if (detail::name_in(name, {"getSensitivityTable", "getSensitivityT", "sensitivityT", "sT",
513 "getSensitivity", "getSensitivityRanking"}))
514 return "sens";
515 // Everything else in the accessor surface is a mean measure: getAvg, its
516 // chain, node and system forms, their handles and their short aliases.
517 if (detail::starts_with(name, "getAvg") || detail::starts_with(name, "avg") ||
518 detail::name_in(name, {"getAvgSysRespT", "getAvgSysTput", "aT", "aNT", "aCT", "aST",
519 "aNCT"}))
520 return "avg";
521 return "";
522}
523
524/**
525 * The measure groups a method family can answer.
526 *
527 * Every family answers "avg", which is what a solver is for; the rest is the
528 * capability declaration this header owns.
529 *
530 * SOURCES, so that a claim here can be checked rather than trusted: "tran" is
531 * the reference's supportsTransientAnalysis, which FLD, CTMC, LDES and JMT
532 * override to true and no one else does. "cdf", "prob", "tranprob" and "sample"
533 * are the families that carry an implementation of the corresponding accessor
534 * rather than inheriting the base refusal. The remaining five groups are
535 * computed from a solver's own results, so no per-solver entry point marks
536 * them: their lists are the reference chooseSolverHeur's rankings for the same
537 * accessors, which is where AUTO already records who can serve them.
538 *
539 * A family that gains or loses a measure must be edited here in the same
540 * change, the way a solver that gains a feature is edited into its feature set:
541 * an omission here does not fail, it silently hides the family from a caller
542 * asking for that measure.
543 */
544inline std::vector<std::string> auto_family_metrics(const std::string& family) {
545 if (family == "mva") return {"avg", "prob", "cache", "orbit", "moment", "sens"};
546 if (family == "nc") return {"avg", "cdf", "prob", "cache", "moment", "sens"};
547 if (family == "ctmc")
548 return {"avg", "tran", "cdf", "prob", "tranprob", "sample", "cache", "loss", "orbit",
549 "moment"};
550 if (family == "fld") return {"avg", "tran", "cdf", "prob", "cache", "sens"};
551 if (family == "mam") return {"avg", "cdf"};
552 // The RCAT fixed point reports means only; the passage-time law it answers
553 // is the base exponential fit, not its own.
554 if (family == "ag") return {"avg"};
555 // A bound brackets the mean measures and nothing else.
556 if (family == "ba") return {"avg"};
557 if (family == "ssa") return {"avg", "cdf", "prob", "sample", "loss"};
558 if (family == "ldes")
559 return {"avg", "tran", "cdf", "prob", "sample", "cache", "loss", "orbit"};
560 if (family == "jmt") return {"avg", "tran", "cdf", "prob", "tranprob", "sample"};
561 return {"avg"};
562}
563
564/**
565 * `Solver.isStochasticMethod` for a family method name: does this method return
566 * seed-dependent estimates?
567 *
568 * SSA and LDES are simulators outright; JMT is one except through its
569 * analytical JMVA engine, whose own sampling variants are stochastic again; NC
570 * has the Monte Carlo, importance-sampling and MCMC routes, and answers for
571 * itself through `nc::is_stochastic_method` rather than through a second copy
572 * of that token list here.
573 */
574inline bool auto_is_stochastic_method(const std::string& family, const std::string& method) {
575 if (family == "ssa" || family == "ldes") return true;
576 if (family == "nc") return nc::is_stochastic_method(method);
577 if (family == "jmt") {
578 std::string tok;
579 std::vector<std::string> toks;
580 for (char ch : method) {
581 if (ch == '.' || ch == '/') {
582 toks.push_back(tok);
583 tok.clear();
584 } else {
585 tok += static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
586 }
587 }
588 toks.push_back(tok);
589 bool jmva = false;
590 for (const std::string& t : toks)
591 if (t == "jmva") jmva = true;
592 if (!jmva) return true;
593 for (const std::string& t : toks)
594 if (t == "ls" || t == "mci" || t == "imci" || t == "sampling") return true;
595 return false;
596 }
597 return false;
598}
599
600namespace detail {
601inline std::string exact_if(bool cond) { return cond ? "exact" : "approx"; }
602} // namespace detail
603
604/**
605 * What KIND of answer a method returns: "exact", "approx", "bound" or
606 * "simulation".
607 *
608 * "simulation" is not decided here: `is_stochastic` is the solver's own verdict,
609 * which already tokenizes qualified and runtime-resolved names.
610 *
611 * "exact" IS CLAIMED ONLY WHERE IT IS TRUE OF THIS MODEL, never of the
612 * algorithm in the abstract. Exactness of a normalizing constant or of mean
613 * value analysis is a property of the product-form model it is computed on, and
614 * of the QBD shape for the matrix analytic methods, so both conditions are
615 * passed in and a method that needs one reports "approx" without it. The bias
616 * is deliberate: an under-claimed "approx" costs a user a better method they
617 * could have had, an over-claimed "exact" costs them a wrong number they
618 * trusted.
619 *
620 * A CACHE IS THE THIRD CONDITION, and it was the over-claim the bias above
621 * exists to prevent. `has_product_form` answers about the QUEUEING network and
622 * knows nothing of a cache: the hit/miss split is a class switch whose
623 * probabilities are not routing data but the output of a cache model, so a
624 * network holding one reads as product form and "mva.exact" was labelled exact
625 * on it. Measured on the tut06 shape with an LRU cache: exact MVA returns QLen
626 * 0.2516 at the hit station where the CTMC returns 0.3022 and simulation 0.3023,
627 * a 17% error under a label that says there is none. The analytic families are
628 * conditioned on it; SolverCTMC is NOT, because its state space carries the
629 * cache contents and it is exact there, which is what the two numbers show.
630 */
631inline std::string auto_method_class(const std::string& family, const std::string& method,
632 bool is_stochastic, bool is_product_form,
633 bool is_qbd_shape, bool has_cache = false) {
634 // Bounds are what SolverBA is for; every one of its methods returns a
635 // bracket rather than an estimate.
636 if (family == "ba") return "bound";
637 if (is_stochastic) return "simulation";
638 if (family == "ctmc") {
639 // The generator is solved as written, so every state-space route is
640 // exact. "mdd" is exact on a product-form model and an approximation
641 // otherwise.
642 if (method == "mdd") return detail::exact_if(is_product_form);
643 return "exact";
644 }
645 if (family == "nc") {
646 // The normalizing-constant routes that evaluate G exactly rather than
647 // expanding or estimating it. The asymptotic expansions (pana, le,
648 // kt, bk, gm, ...) and the non-product-form "morrison" are
649 // approximations by construction and are left out. "cftp" samples the
650 // product-form stationary law itself, so it is exact on the same terms;
651 // "cftp.approx" says in its own name that it is not.
652 if (detail::name_in(method, {"exact", "divdiff", "ca", "comom", "comomld", "rec", "ms",
653 "cub", "rgf", "cftp"}))
654 return detail::exact_if(is_product_form && !has_cache);
655 return "approx";
656 }
657 if (family == "mva") {
658 // Exact MVA; every "amva.*" arm is an approximation, and so are the
659 // open-network QNA transforms.
660 if (detail::name_in(method, {"exact", "mva"}))
661 return detail::exact_if(is_product_form && !has_cache);
662 return "approx";
663 }
664 if (family == "jmt") {
665 // JMVA's exact algorithms. "default" and "jsim" are simulation and
666 // never reach here.
667 if (detail::name_in(method, {"jmva.mva", "jmva.recal", "jmva.comom", "jmva.treeconv"}))
668 return detail::exact_if(is_product_form && !has_cache);
669 return "approx";
670 }
671 if (family == "mam") {
672 // The QBD is solved exactly on the shape it is stated for, one queueing
673 // station fed by a Source. Everything named "dec.*" is a decomposition
674 // of a larger network into such queues, hence an approximation of it.
675 if (detail::name_in(method, {"default", "mna", "ldqbd", "bgchain", "retrial"}))
676 return detail::exact_if(is_qbd_shape);
677 return "approx";
678 }
679 if (family == "ag") {
680 // Every RCAT arm estimates the reversed rate of each synchronising
681 // action and iterates to a fixed point, an approximation by
682 // construction; SolverAG's "exact" is a vestigial alias that warns and
683 // runs "inap", so nothing here is claimed exact.
684 return "approx";
685 }
686 return "approx";
687}
688
689/**
690 * `SolverAUTO.findSolver`: which solvers and solver methods can analyze this
691 * model, and for the ones that cannot, why not.
692 *
693 * THE GATE IS NOT A SECOND ONE. It is `auto_family_supports`, the gate
694 * `auto_choose_solver` applies before delegating, asked of every candidate
695 * instead of of the first feasible one -- which is exactly what
696 * `auto_list_valid_methods` already did; that function is now the method column
697 * of the runnable rows, so the two cannot disagree. What is new is that the
698 * REASON is kept rather than discarded, and that the answer carries the two
699 * facts a caller needs in order to choose among the survivors: whether the
700 * method is exact on this model, and which measures it can report.
701 *
702 * `metric` narrows the report to the pairs that answer one measure, named
703 * either by its group ("cdf") or by the accessor that returns it
704 * ("getCdfRespT"); "" or "any" keeps every pair. `show_all` keeps the refused
705 * pairs too; by default only the runnable ones are listed, since a caller
706 * asking what it can run has no use for the rows that say it cannot.
707 */
708template <class T>
709std::vector<SolverCandidate> auto_find_solver(const qn::NetworkStruct<T>& sn,
710 const std::string& metric = std::string(),
711 bool show_all = false) {
712 const std::string group = auto_metric_group_of(metric);
713 if (group.empty() && !metric.empty() && metric != "any" && metric != "all") {
714 std::string groups;
715 for (const std::string& g : auto_metric_groups()) {
716 if (!groups.empty()) groups += ", ";
717 groups += g;
718 }
719 // InputError and not a bare runtime_error: this is a CALLER mistake, and
720 // the CLI reports the two differently -- an input error prints
721 // "line-cli: <message>" and exits 2, anything else prints "unexpected
722 // failure" and exits 3, which is what a typo'd measure was getting.
723 throw InputError("'" + metric + "' names no measure. Pass a group (" + groups +
724 ") or the accessor that returns it, e.g. 'getCdfRespT'.");
725 }
726
727 // The two model properties an exactness claim can rest on, evaluated once:
728 // a method whose exactness needs one of them reports "approx" without it.
729 const bool is_product_form = sn.has_product_form();
730 std::size_t nsources = 0;
731 for (const qn::Station<T>& st : sn.stations)
732 if (st.nodetype == lang::NodeType::Source) ++nsources;
733 const std::vector<double> njobs = sn.njobs();
734 bool all_open = !njobs.empty();
735 for (double n : njobs)
736 if (!std::isinf(n)) all_open = false;
737 const bool is_qbd_shape = all_open && (sn.nstations - nsources) == 1;
738 bool has_cache = false;
739 for (const qn::NodeDef& nd : sn.nodes)
740 if (nd.nodetype == qn::NodeType::Cache) has_cache = true;
741
742 std::vector<SolverCandidate> rows;
743 for (const std::string& family : auto_network_family_names()) {
744 const std::vector<std::string> groups = auto_family_metrics(family);
745 if (!group.empty() &&
746 std::find(groups.begin(), groups.end(), group) == groups.end())
747 continue;
748 std::string metric_list;
749 for (const std::string& g : groups) {
750 if (!metric_list.empty()) metric_list += ",";
751 metric_list += g;
752 }
753 const std::vector<std::string> declared = auto_family_methods(family, sn);
754 for (const std::string& name : declared) {
755 if (detail::starts_with(name, family + ".")) {
756 // A spelling already qualified with its own family. The fluid
757 // registry declares both "dae" and "fld.dae" so that its own
758 // gate takes either, and prefixing the family again yields
759 // "fld.fld.dae": a token that does resolve, but that names
760 // the same method twice and would double every fluid row.
761 continue;
762 }
763 if (auto_is_method_alias(family, name, declared)) {
764 // The same duplication under a different prefix. The mva
765 // registry advertises every AMVA name twice, plain and
766 // "amva."-prefixed, and the dispatch strips the prefix, so the
767 // two spellings are one algorithm; that alone was 20 of the 49
768 // mva rows of a report. The plain spelling is the one kept.
769 continue;
770 }
771 const std::string reason = auto_family_refusal(family, sn, name);
772 const bool ok = reason.empty();
773 if (!ok && !show_all) continue;
774 SolverCandidate row;
775 row.solver = family;
776 row.method = family + "." + name;
777 row.runnable = ok;
778 row.method_class = auto_method_class(
779 family, name, auto_is_stochastic_method(family, name), is_product_form,
780 is_qbd_shape, has_cache);
781 row.metrics = metric_list;
782 row.reason = ok ? std::string() : reason;
783 rows.push_back(row);
784 }
785 }
786 return rows;
787}
788
789/** Alias of `auto_find_solver`: the same table, asked for by method. */
790template <class T>
791std::vector<SolverCandidate> auto_find_method(const qn::NetworkStruct<T>& sn,
792 const std::string& metric = std::string(),
793 bool show_all = false) {
794 return auto_find_solver(sn, metric, show_all);
795}
796
797/** Alias of `auto_find_solver`: what can this model be solved with? */
798template <class T>
799std::vector<SolverCandidate> auto_help(const qn::NetworkStruct<T>& sn,
800 const std::string& metric = std::string(),
801 bool show_all = false) {
802 return auto_find_solver(sn, metric, show_all);
803}
804
805/**
806 * The rows as an aligned text table, the form the CLI and a console caller
807 * want. The reason column is last and unpadded, since it is the only one whose
808 * width is unbounded.
809 */
810inline std::string auto_find_solver_table(const std::vector<SolverCandidate>& rows) {
811 if (rows.empty()) return "No solver method can analyze this model.\n";
812 std::vector<std::array<std::string, 6>> cells;
813 cells.push_back({"Solver", "Method", "Runnable", "Class", "Metrics", "Reason"});
814 for (const SolverCandidate& r : rows)
815 cells.push_back({r.solver, r.method, r.runnable ? "true" : "false", r.method_class,
816 r.metrics, r.reason});
817 std::array<std::size_t, 6> width{};
818 for (const auto& row : cells)
819 for (std::size_t c = 0; c < 5; ++c) width[c] = std::max(width[c], row[c].size());
820 std::string out;
821 for (const auto& row : cells) {
822 std::string line;
823 for (std::size_t c = 0; c < 5; ++c) line += row[c] + std::string(width[c] - row[c].size() + 2, ' ');
824 line += row[5];
825 while (!line.empty() && line.back() == ' ') line.pop_back();
826 out += line + "\n";
827 }
828 return out;
829}
830
831/**
832 * `SolverAUTO.listValidMethods`: every method name this model can be asked for.
833 *
834 * The selection intents come first and unconditionally: they name a RANKING and
835 * not an algorithm, and finding a family that supports the model is the
836 * ranking's own job. `bound` is the one that is not quite a ranking -- it names
837 * SolverBA with method 'auto' -- and it is listed unconditionally all the same,
838 * because MATLAB and the JAR list it that way and a caller reading 'bound' as
839 * "give me bounds" should get SolverBA's own refusal rather than a missing
840 * method name. Then each family in `auto_network_family_names` order, bare method name
841 * first and its qualified methods after it.
842 */
843template <class T>
844std::vector<std::string> auto_list_valid_methods(const qn::NetworkStruct<T>& sn) {
845 // IT IS THE RUNNABLE ROWS OF `auto_find_solver`, projected onto their
846 // method name. The narrowing used to be written out a second time here,
847 // and a second copy of one gate is how two answers to one question start to
848 // differ; `auto_find_solver` owns it now, and this adds only the method names
849 // that name no single method: the selection intents and each family's bare
850 // token.
851 //
852 // A family that declares methods and keeps none loses its bare method name too:
853 // it delegates to the solver the per-method gate just refused. That falls
854 // out of the projection, since such a family contributes no row to name.
855 std::vector<std::string> out{"accurate", "auto", "bound", "default",
856 "exact", "fast", "heur", "sim"};
857 for (const SolverCandidate& row : auto_find_solver(sn)) {
858 out.push_back(row.method);
859 out.push_back(row.solver);
860 }
861 std::sort(out.begin(), out.end());
862 out.erase(std::unique(out.begin(), out.end()), out.end());
863 return out;
864}
865
866} // namespace autosolver
867} // namespace line
868
869#endif // LINE_SOLVERS_AUTO_AUTO_METHODS_H
InputError(const std::string &what)
Definition error.h:39
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.
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::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)
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.
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 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)
The structural half of SolverJMT's method gate; empty when admissible.
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,...
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::vector< std::string > list_valid_methods(const qn::NetworkStruct< T > &L)
Port of SolverMVA.listValidMethods.
MemSupport solver_nc_mem_supports(const qn::NetworkStruct< T > &sn)
Port of solver_nc_mem_supports.m.
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 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 jmt_feature_set(const std::string &method)
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)
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)
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,...
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, "" when runnable
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:119
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