5#ifndef LINE_SOLVERS_AUTO_SOLVER_AUTO_H
6#define LINE_SOLVERS_AUTO_SOLVER_AUTO_H
202 return token.empty() || token ==
"default" || token ==
"auto" || token ==
"heur" ||
203 token ==
"sim" || token ==
"exact" || token ==
"fast" ||
208 if (token.empty() || token ==
"default" || token ==
"auto" || token ==
"heur")
214 throw InputError(
"SolverAUTO: '" + token +
"' is not a selection intent");
223 if (name ==
"mam")
return "mam";
224 if (name ==
"ag")
return "ag";
225 if (name ==
"mva")
return "mva";
226 if (name ==
"nc")
return "nc";
227 if (name ==
"fluid" || name ==
"fld")
return "fld";
228 if (name ==
"jmt")
return "jmt";
229 if (name ==
"ssa")
return "ssa";
230 if (name ==
"ctmc")
return "ctmc";
231 if (name ==
"ldes" || name ==
"des")
return "ldes";
232 if (name ==
"ba")
return "ba";
233 if (name ==
"env")
return "env";
234 if (name ==
"ln")
return "ln";
235 if (name ==
"lqns" || name ==
"lqsim")
return "lqns";
236 if (name ==
"qns")
return "lqns";
237 if (name ==
"uq")
return "uq";
259 const std::string token = raw.empty() ? std::string(
"default") : raw;
265 if (token ==
"bound") {
276 const std::size_t dot = token.find(
'.');
277 const std::string head = dot == std::string::npos ? token : token.substr(0, dot);
278 const std::string rest = dot == std::string::npos ? std::string() : token.substr(dot + 1);
280 const std::string fam =
284 "SolverAUTO: '" + token +
285 "' is neither a selection intent (default, heur, exact, sim, fast, accurate, "
287 "nor a method family (mva, nc, ctmc, fld, mam, ag, ba, ssa, ldes, jmt, ln, "
288 "env, lqns, uq). A bare algorithm name must be qualified by its family here, as in "
289 "'nc.comom': resolving it needs the per-family method registry "
290 "(listValidMethods) that this port does not carry");
293 t.
submethod = rest.empty() ? std::string(
"default") : rest;
337 bool has_open =
false, has_closed =
false;
339 if (std::isinf(c.
population)) has_open =
true;
340 else has_closed =
true;
342 t.
is_open = has_open && !has_closed;
344 t.
is_mixed = has_open && has_closed;
351 for (std::size_t i = 1; i <=
sn.nstations && !t.
has_map; ++i)
352 for (std::size_t r = 1; r <=
sn.nclasses; ++r) {
353 const ProcessType p =
sn.procid(i, r);
354 if (p == ProcessType::MAP || p == ProcessType::MMPP2) {
362 bool preempt =
false, psprio =
false, hol =
false;
364 if (s.
sched == SchedStrategy::FCFSPRPRIO || s.
sched == SchedStrategy::FCFSPIPRIO ||
365 s.
sched == SchedStrategy::LCFSPRPRIO || s.
sched == SchedStrategy::LCFSPIPRIO)
367 if (s.
sched == SchedStrategy::PSPRIO || s.
sched == SchedStrategy::DPSPRIO ||
368 s.
sched == SchedStrategy::GPSPRIO)
370 if (s.
sched == SchedStrategy::HOL || s.
sched == SchedStrategy::LCFSPRIO ||
371 s.
sched == SchedStrategy::SRPTPRIO)
374 if (preempt) t.
prio =
"preempt";
375 else if (psprio) t.
prio =
"ps";
376 else if (hol) t.
prio =
"hol";
404 const std::size_t M =
sn.nstations, K =
sn.nclasses;
405 if (M == 0 || K == 0)
return 0.0;
407 cutoff = std::ceil(std::pow(6000.0, 1.0 /
static_cast<double>(M * K)));
409 const auto is_share = [](SchedStrategy s) {
410 return s == SchedStrategy::INF || s == SchedStrategy::PS || s == SchedStrategy::DPS ||
411 s == SchedStrategy::GPS || s == SchedStrategy::PSPRIO ||
412 s == SchedStrategy::DPSPRIO || s == SchedStrategy::GPSPRIO ||
413 s == SchedStrategy::LPS;
415 std::vector<bool> is_buffered(K,
true);
417 for (std::size_t r = 0; r < K; ++r) {
418 if (r <
sn.issignal.size() &&
sn.issignal[r]) is_buffered[r] =
false;
419 if (is_buffered[r]) ++Kb;
421 std::size_t n_ord = 0;
423 for (std::size_t i = 0; i < M; ++i) {
424 const SchedStrategy s =
sn.stations[i].sched;
425 if (s == SchedStrategy::EXT || is_share(s))
continue;
429 const std::size_t m_place = (M > n_ord) ? (M - n_ord) : 0;
432 std::vector<double> nk_eff(K, 0.0);
433 for (std::size_t r = 0; r < K; ++r) {
434 const double nk = std::isinf(
sn.classes[r].population) ? cutoff :
sn.classes[r].population;
437 const double m =
static_cast<double>(m_place);
438 log_n += std::lgamma(1.0 + nk + m - 1.0) - std::lgamma(m) - std::lgamma(1.0 + nk);
443 double tot_jobs = 0.0;
444 for (std::size_t r = 0; r < K; ++r)
445 if (is_buffered[r]) tot_jobs += nk_eff[r];
446 const double log_k = std::log(
static_cast<double>(Kb));
447 const double log_seq = (tot_jobs + 1.0) * log_k - std::log(
static_cast<double>(Kb) - 1.0) +
448 std::log1p(-std::exp(-(tot_jobs + 1.0) * log_k));
449 log_n +=
static_cast<double>(n_ord) * log_seq;
452 for (std::size_t i = 1; i <= M; ++i) {
453 const SchedStrategy sched =
sn.stations[i - 1].sched;
454 const bool shares = sched == SchedStrategy::INF || sched == SchedStrategy::PS ||
455 sched == SchedStrategy::DPS || sched == SchedStrategy::GPS ||
456 sched == SchedStrategy::PSPRIO || sched == SchedStrategy::DPSPRIO ||
457 sched == SchedStrategy::GPSPRIO || sched == SchedStrategy::LPS;
458 for (std::size_t r = 1; r <= K; ++r) {
459 const double p =
static_cast<double>(std::max<std::size_t>(
sn.phases_of(i, r), 1));
460 if (p <= 1.0)
continue;
462 if (sched == SchedStrategy::EXT) m = 1.0;
463 else if (shares) m = nk_eff[r - 1];
464 else m = std::min(nk_eff[r - 1],
sn.stations[i - 1].nservers);
465 if (!std::isfinite(m)) m = nk_eff[r - 1];
466 log_n += std::lgamma(1.0 + m + p - 1.0) - std::lgamma(p) - std::lgamma(1.0 + m);
474 const std::size_t N =
sn.nodes.size(), R =
sn.nclasses;
475 if (
sn.rtnodes.rows() >= N * R) {
476 for (std::size_t i = 1; i <= N; ++i) {
477 std::size_t nout = 0;
478 for (std::size_t j = 1; j <= N; ++j) {
480 for (std::size_t r = 0; r < R && !linked; ++r)
481 for (std::size_t s = 0; s < R && !linked; ++s)
483 sn.rtnodes((i - 1) * R + r, (j - 1) * R + s)) > 0.0)
487 if (nout <= 1)
continue;
489 const std::vector<RoutingStrategy>& rt =
sn.nodes[i - 1].routing;
490 for (std::size_t r = 0; r < rt.size(); ++r)
491 if (rt[r] == RoutingStrategy::RROBIN || rt[r] == RoutingStrategy::WRROBIN) ++nrr;
492 if (nrr > 0) log_n +=
static_cast<double>(nrr) * std::log(
static_cast<double>(nout));
524 const std::string method = token.empty() ? std::string(
"default") : token;
545 if (!
sn.has_product_form()) {
552 if (st.
sched == SchedStrategy::OI || st.
sched == SchedStrategy::PAS) oi =
true;
553 if (!oi)
return false;
578 std::vector<AutoSolver> skipped;
579 for (std::size_t k = 0; k < order.size(); ++k) {
581 skipped.push_back(order[k]);
587 out.
method = (token ==
"default") ? std::string() : token;
602 std::vector<AutoSolver> out;
603 for (std::size_t i = 0; i <
sizeof(kSlots) /
sizeof(*kSlots); ++i)
605 out.push_back(kSlots[i]);
622inline std::vector<AutoSolver> avg_order(
const AutoTraits& t,
bool homogeneous_inf) {
630 if (t.prio ==
"preempt")
647inline UnsupportedError no_solver(
const std::string& what,
648 const std::vector<AutoSolver>& skipped) {
649 std::string msg =
"SolverAUTO: no solver supports this model" + what;
650 if (!skipped.empty()) {
651 msg +=
" (the ranking preferred ";
652 for (std::size_t i = 0; i < skipped.size(); ++i) {
656 msg +=
", which this port does not build)";
681 const std::vector<AutoSolver> exact_order =
688 const std::vector<AutoSolver> order =
689 detail::avg_order(t,
sn.has_homogeneous_scheduling(SchedStrategy::INF));
698 throw detail::no_solver(
"", pool);
712inline bool in_list(
const std::string& m,
const char*
const* tab, std::size_t n) {
713 for (std::size_t i = 0; i < n; ++i)
714 if (m == tab[i])
return true;
719inline bool is_avg_method(
const std::string& m) {
720 static const char* kAvg[] = {
721 "getAvgChainTable",
"getAvgTputTable",
"getAvgRespTTable",
"getAvgUtilTable",
722 "getAvgSysTable",
"getAvgNodeTable",
"getAvgTable",
"getAvgTableLayered",
"getAvg",
723 "getAvgChain",
"getAvgSys",
"getAvgNode",
"getAvgNodeChain",
"getAvgArvRChain",
724 "getAvgQLenChain",
"getAvgUtilChain",
"getAvgRespTChain",
"getAvgTputChain",
725 "getAvgSysRespT",
"getAvgSysTput",
"getAvgQLen",
"getAvgUtil",
"getAvgRespT",
726 "getAvgResidT",
"getAvgWaitT",
"getAvgTput",
"getAvgArvR",
"getAvgQLenTable",
727 "getAvgResidTChain",
"getAvgNodeQLenChain",
"getAvgNodeUtilChain",
728 "getAvgNodeRespTChain",
"getAvgNodeResidTChain",
"getAvgNodeTputChain",
729 "getAvgNodeArvRChain",
"getAvgNodeChainTable",
"getResults",
"hasResults",
730 "getAvgHandles",
"getTranHandles",
"getAvgQLenHandles",
"getAvgUtilHandles",
731 "getAvgRespTHandles",
"getAvgTputHandles",
"getAvgArvRHandles",
"getAvgResidTHandles",
732 "getMethodFeatureSet",
"supportsModelMethod",
"isStochasticMethod",
"libraries",
733 "showLibraryAttribution",
"citations"};
734 return in_list(m, kAvg,
sizeof(kAvg) /
sizeof(*kAvg));
738inline bool is_ensemble_method(
const std::string& m) {
739 static const char* kEns[] = {
"getEnsembleAvg",
"getEnsembleAvgTables",
"getSolver",
740 "setSolver",
"getNumberOfModels",
"getIteration",
741 "get_state",
"set_state",
"update_solver"};
742 return in_list(m, kEns,
sizeof(kEns) /
sizeof(*kEns));
745inline bool is_cdf_method(
const std::string& m) {
746 return m ==
"getCdfRespT" || m ==
"getCdfPassT" || m ==
"getPerctRespT";
749inline bool is_tran_prob_method(
const std::string& m) {
750 return m ==
"getTranProb" || m ==
"getTranProbSys" || m ==
"getTranProbAggr" ||
751 m ==
"getTranProbSysAggr";
754inline bool is_prob_method(
const std::string& m) {
755 return m ==
"getProb" || m ==
"getProbAggr" || m ==
"getProbSys" || m ==
"getProbSysAggr" ||
756 m ==
"getProbMarg" || m ==
"getProbNormConstAggr";
759inline bool is_sample_method(
const std::string& m) {
return m ==
"sample" || m ==
"sampleSys"; }
761inline bool is_sample_aggr_method(
const std::string& m) {
762 return m ==
"sampleAggr" || m ==
"sampleSysAggr";
765inline bool is_cache_metric_method(
const std::string& m) {
766 static const char* kTab[] = {
"getAvgCacheTable",
"getAvgCacheT",
"getAvgItemTable",
767 "getAvgItemT",
"cacheAvgT",
"itemAvgT",
"aCaT",
"aIT"};
768 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
771inline bool is_loss_metric_method(
const std::string& m) {
772 static const char* kTab[] = {
"getAvgLossTable",
"getAvgLossT",
"getAvgRegionLossTable",
773 "getAvgRegionLossT",
"lossAvgT",
"regionLossAvgT",
775 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
778inline bool is_orbit_metric_method(
const std::string& m) {
779 static const char* kTab[] = {
"getAvgOrbitTable",
"getAvgOrbitT",
"getAvgOrbit",
"orbitAvgT",
781 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
784inline bool is_moment_method(
const std::string& m) {
785 static const char* kTab[] = {
"getMomentTable",
"getMomentChainTable",
"getMomentStationTable",
786 "getMomentT",
"getMomentChainT",
"getMomentStationT",
787 "momentT",
"momentChainT",
"momentStationT",
789 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
792inline bool is_sens_method(
const std::string& m) {
793 static const char* kTab[] = {
"getSensitivityTable",
"getSensitivityT",
"sensitivityT",
"sT",
794 "supportsExactSensitivity"};
795 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
810 if (detail::is_ensemble_method(method))
811 throw InputError(
"SolverAUTO: method '" + method +
812 "' is only available for LayeredNetwork models");
814 std::vector<AutoSolver> order;
815 if (method ==
"getTranAvg") {
817 }
else if (detail::is_cdf_method(method)) {
820 if (
sn.has_homogeneous_scheduling(SchedStrategy::FCFS) &&
sn.has_product_form())
824 }
else if (method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT") {
826 }
else if (detail::is_tran_prob_method(method)) {
828 }
else if (detail::is_sample_method(method)) {
830 }
else if (detail::is_sample_aggr_method(method)) {
832 }
else if (detail::is_prob_method(method)) {
833 if (
sn.has_product_form())
837 }
else if (detail::is_cache_metric_method(method)) {
840 }
else if (detail::is_loss_metric_method(method)) {
842 }
else if (detail::is_orbit_metric_method(method)) {
844 }
else if (detail::is_moment_method(method)) {
846 }
else if (detail::is_sens_method(method)) {
863 std::vector<AutoSolver> order;
864 if (detail::is_tran_prob_method(method) || detail::is_cdf_method(method) ||
865 method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT" || method ==
"getTranAvg") {
867 }
else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
870 }
else if (detail::is_prob_method(method)) {
880 throw detail::no_solver(
" exactly for method '" + method +
881 "'; use the default heuristic for the approximation",
888 const std::vector<AutoSolver> order =
889 detail::is_sample_method(method)
894 throw detail::no_solver(
" by simulation", order);
940 const std::string& method,
AutoMode mode) {
942 std::vector<AutoSolver> out(1, chosen.
solver);
944 for (std::size_t i = 0; i < cand.size(); ++i)
945 if (std::find(out.begin(), out.end(), cand[i]) == out.end()) out.push_back(cand[i]);
960inline bool layered_ranked(
const std::vector<AutoLayered>& order,
AutoLayeredChoice& out) {
961 std::vector<AutoLayered> skipped;
962 for (std::size_t k = 0; k < order.size(); ++k) {
964 skipped.push_back(order[k]);
988 std::vector<AutoLayered> order;
995 }
else if (method ==
"getTranAvg" || detail::is_cdf_method(method) ||
996 method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT") {
998 }
else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
1000 }
else if (detail::is_prob_method(method) || detail::is_tran_prob_method(method)) {
1002 }
else if (detail::is_ensemble_method(method)) {
1005 }
else if (has_cache_task) {
1018 for (std::size_t i = 0; i < order.size(); ++i)
1022 if (detail::layered_ranked(order, out))
return out;
1024 "SolverAUTO: no LayeredNetwork solver in this port serves method '" + method +
1025 "'; the reference's ranking is served by SolverLQNS, which shells out to an external "
1026 "binary this port does not wrap");
1045 std::vector<AutoEnv> order;
1051 std::vector<AutoEnv> skipped;
1052 for (std::size_t k = 0; k < order.size(); ++k) {
1054 skipped.push_back(order[k]);
1062 "SolverAUTO: the Environment ranking for method '" + method +
1063 "' selects an MVA or NC stage solver, and SolverENV in this port solves a stage with the "
1064 "fluid analyzer (or, under the state-vector coupling, an explicit chain); rerun with "
1065 "-s env, whose default coupling is the mean-field one");
UnsupportedError(const std::string &what)
A subset of the registry: MATLAB's SolverFeatureSet, whose list is a flag per field.
A network plus its refreshed NetworkStruct.
The exception types the port throws.
Where the LDES engine is, and whether this machine can run it.
Is a usable lqns installed on this machine?
bool auto_supports(AutoSolver s, const qn::NetworkStruct< T > &sn, const std::string &token)
Solver.supports(model) for a candidate slot, tightened by the two method-level rules a flat feature s...
bool auto_layered_is_available(AutoLayered s)
The LN layer engines are the four --layer-solver takes (mva, nc, fluid, ssa), so there is no MAM-laye...
AutoEnvChoice auto_choose_env_solver(const std::string &method, AutoMode mode=AutoMode::HEUR)
The Environment arm of chooseSolverHeur / chooseSolverExact / chooseSolverSim.
AutoSolver
The Network candidate slots, in the reference's slot order (SolverAUTO.m:41-50), which is also the or...
bool auto_ctmc_is_tractable(const qn::NetworkStruct< T > &sn, double cutoff=-1.0)
double auto_ctmc_state_space_logsize(const qn::NetworkStruct< T > &sn, double cutoff=-1.0)
Worst-case log-size of the CTMC state space induced by sn, summed in log space over the reference's f...
AutoChoice auto_choose_solver_exact(const qn::NetworkStruct< T > &sn, const std::string &method)
chooseSolverExact: the ranking restricted to solvers that answer exactly.
AutoSolver auto_choose_solver(const qn::NetworkStruct< T > &sn, const std::string &method)
The heuristic Network arm, by getter name.
std::string auto_family_alias(const std::string &name)
SolverAUTO.familyAlias: the canonical family of a method name, or "" when the method name names none.
AutoTraits auto_traits(const qn::NetworkStruct< T > &sn)
bool auto_solver_is_available(AutoSolver s)
True when this port has an engine behind the slot at all.
bool auto_is_selection_intent(const std::string &token)
SolverAUTO.selectionIntents, less 'bound'.
const double kAutoExactPopulationMax
Population at or below which an exact solver is preferred (EXACT_POPULATION_MAX).
AutoSolver auto_choose_avg_solver(const qn::NetworkStruct< T > &sn)
AutoChoice auto_choose_avg_solver_ex(const qn::NetworkStruct< T > &sn)
chooseAvgSolverHeur, the Network arm: exact first at small populations, then the trait-keyed ranking,...
AutoLayeredChoice auto_choose_layered_solver(const std::string &method, bool has_cache_task, AutoMode mode=AutoMode::HEUR)
chooseLayeredSolver plus the LayeredNetwork arm of chooseAvgSolverHeur.
AutoMode auto_mode_of_token(const std::string &token)
bool auto_env_is_available(AutoEnv s)
SolverENV solves a stage with the fluid analyzer under every coupling but the state-vector one,...
const double kAutoCtmcStateCap
The cap reachable_space_generator enforces (solver_ctmc.h, maxst).
AutoChoice auto_choose_solver_sim(const qn::NetworkStruct< T > &sn, const std::string &method)
chooseSolverSim: the ranking restricted to simulators.
const char * auto_solver_name(AutoSolver s)
std::vector< AutoSolver > auto_candidates(const qn::NetworkStruct< T > &sn)
The candidate pool in slot order, filtered by supports: what the delegate retries through after the c...
std::vector< AutoSolver > auto_proposed_solvers(const qn::NetworkStruct< T > &sn, const std::string &method, AutoMode mode)
delegate's proposed order: the chosen solver, then every feasible candidate in slot order.
AutoChoice auto_choose_solver_mode(const qn::NetworkStruct< T > &sn, const std::string &method, AutoMode mode)
chooseSolver: the selection mode picks the ranking, and every mode but the two learned ones keeps the...
const char * auto_env_name(AutoEnv s)
AutoChoice auto_choose_solver_heur(const qn::NetworkStruct< T > &sn, const std::string &method)
chooseNetworkSolver in chooseSolverHeur.m: the getter names the metric family, the family names a ran...
bool auto_ranked(const std::vector< AutoSolver > &order, const qn::NetworkStruct< T > &sn, const std::string &token, AutoChoice &out)
chooseSolverRanked: the first slot in ORDER that exists and accepts the model.
AutoEnv
The Environment candidate slots (SolverAUTO.m:58-60).
AutoLayered
The LayeredNetwork candidate slots (SolverAUTO.m:52-56).
AutoToken auto_resolve_token(const std::string &raw)
AutoMode
The selection intents of SolverAUTO.selectionIntents, less 'bound'.
const char * auto_layered_name(AutoLayered s)
The layered names are the CLI's own tokens, because that is what the choice is spent on: ln....
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
NodeType
Node kinds, with the values of MATLAB NodeType.
bool ldes_is_available()
True when this machine can run the engine at all, by either image.
bool lqns_is_available()
True when lqns is installed AND is a release this port speaks.
FeatureSet ssa_feature_set(const std::string &)
SolverSSA.getFeatureSet, 98 MATLAB names.
FeatureSet ldes_feature_set(const std::string &)
SolverLDES.getFeatureSet, transcribed WHOLE.
FeatureSet fluid_feature_set(const std::string &method)
SolverFLD.getFeatureSet, transcribed, MINUS what the requested method cannot evaluate – the port of @...
FeatureSet used_lang_features(const NetworkStruct< T > &sn)
FeatureSet nc_feature_set(const std::string &method)
SolverNC.getFeatureSet, 48 names, transcribed unchanged.
FeatureSet ctmc_feature_set(const std::string &method)
SolverCTMC.getFeatureSet, the reference's 104 MATLAB names in full.
SupportResult feature_set_supports(const std::string &solver, const FeatureSet &declared, const FeatureSet &used)
SolverFeatureSet.supports: is every feature the model uses declared?
FeatureSet mam_feature_set(const std::string &method)
SolverMAM.getFeatureSet, the union of its four setTrue calls: 55 MATLAB names, WIDENED for 'default'/...
FeatureSet mva_feature_set(const std::string &raw_method)
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
The DECLARED side of the gate: one feature set per solver.
What a ranking resolved to, and what it had to skip to get there.
std::string method
The method the choice was gated on: "" for the default, "exact".
std::vector< AutoSolver > skipped
Slots that outranked solver and have no engine in this port.
std::vector< AutoEnv > skipped
std::vector< AutoLayered > skipped
resolveMethodToken, minus the unqualified-algorithm-name arm.
std::string submethod
the method handed to the family, "default" when bare
std::string family
empty when is_intent
The structural traits the rankings are keyed on.
std::string prio
"none", "preempt", "ps" or "hol", in the reference's own precedence.
One job class of the network.
double population
infinite for an open class
One station of the network.