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;
576 std::vector<AutoSolver> skipped;
577 for (std::size_t k = 0; k < order.size(); ++k) {
579 skipped.push_back(order[k]);
585 out.
method = (token ==
"default") ? std::string() : token;
600 std::vector<AutoSolver> out;
601 for (std::size_t i = 0; i <
sizeof(kSlots) /
sizeof(*kSlots); ++i)
603 out.push_back(kSlots[i]);
620inline std::vector<AutoSolver> avg_order(
const AutoTraits& t,
bool homogeneous_inf) {
628 if (t.prio ==
"preempt")
645inline UnsupportedError no_solver(
const std::string& what,
646 const std::vector<AutoSolver>& skipped) {
647 std::string msg =
"SolverAUTO: no solver supports this model" + what;
648 if (!skipped.empty()) {
649 msg +=
" (the ranking preferred ";
650 for (std::size_t i = 0; i < skipped.size(); ++i) {
654 msg +=
", which this port does not build)";
679 const std::vector<AutoSolver> exact_order =
686 const std::vector<AutoSolver> order =
687 detail::avg_order(t,
sn.has_homogeneous_scheduling(SchedStrategy::INF));
696 throw detail::no_solver(
"", pool);
710inline bool in_list(
const std::string& m,
const char*
const* tab, std::size_t n) {
711 for (std::size_t i = 0; i < n; ++i)
712 if (m == tab[i])
return true;
717inline bool is_avg_method(
const std::string& m) {
718 static const char* kAvg[] = {
719 "getAvgChainTable",
"getAvgTputTable",
"getAvgRespTTable",
"getAvgUtilTable",
720 "getAvgSysTable",
"getAvgNodeTable",
"getAvgTable",
"getAvgTableLayered",
"getAvg",
721 "getAvgChain",
"getAvgSys",
"getAvgNode",
"getAvgNodeChain",
"getAvgArvRChain",
722 "getAvgQLenChain",
"getAvgUtilChain",
"getAvgRespTChain",
"getAvgTputChain",
723 "getAvgSysRespT",
"getAvgSysTput",
"getAvgQLen",
"getAvgUtil",
"getAvgRespT",
724 "getAvgResidT",
"getAvgWaitT",
"getAvgTput",
"getAvgArvR",
"getAvgQLenTable",
725 "getAvgResidTChain",
"getAvgNodeQLenChain",
"getAvgNodeUtilChain",
726 "getAvgNodeRespTChain",
"getAvgNodeResidTChain",
"getAvgNodeTputChain",
727 "getAvgNodeArvRChain",
"getAvgNodeChainTable",
"getResults",
"hasResults",
728 "getAvgHandles",
"getTranHandles",
"getAvgQLenHandles",
"getAvgUtilHandles",
729 "getAvgRespTHandles",
"getAvgTputHandles",
"getAvgArvRHandles",
"getAvgResidTHandles",
730 "getMethodFeatureSet",
"supportsModelMethod",
"isStochasticMethod",
"libraries",
731 "showLibraryAttribution",
"citations"};
732 return in_list(m, kAvg,
sizeof(kAvg) /
sizeof(*kAvg));
736inline bool is_ensemble_method(
const std::string& m) {
737 static const char* kEns[] = {
"getEnsembleAvg",
"getEnsembleAvgTables",
"getSolver",
738 "setSolver",
"getNumberOfModels",
"getIteration",
739 "get_state",
"set_state",
"update_solver"};
740 return in_list(m, kEns,
sizeof(kEns) /
sizeof(*kEns));
743inline bool is_cdf_method(
const std::string& m) {
744 return m ==
"getCdfRespT" || m ==
"getCdfPassT" || m ==
"getPerctRespT";
747inline bool is_tran_prob_method(
const std::string& m) {
748 return m ==
"getTranProb" || m ==
"getTranProbSys" || m ==
"getTranProbAggr" ||
749 m ==
"getTranProbSysAggr";
752inline bool is_prob_method(
const std::string& m) {
753 return m ==
"getProb" || m ==
"getProbAggr" || m ==
"getProbSys" || m ==
"getProbSysAggr" ||
754 m ==
"getProbMarg" || m ==
"getProbNormConstAggr";
757inline bool is_sample_method(
const std::string& m) {
return m ==
"sample" || m ==
"sampleSys"; }
759inline bool is_sample_aggr_method(
const std::string& m) {
760 return m ==
"sampleAggr" || m ==
"sampleSysAggr";
763inline bool is_cache_metric_method(
const std::string& m) {
764 static const char* kTab[] = {
"getAvgCacheTable",
"getAvgCacheT",
"getAvgItemTable",
765 "getAvgItemT",
"cacheAvgT",
"itemAvgT",
"aCaT",
"aIT"};
766 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
769inline bool is_loss_metric_method(
const std::string& m) {
770 static const char* kTab[] = {
"getAvgLossTable",
"getAvgLossT",
"getAvgRegionLossTable",
771 "getAvgRegionLossT",
"lossAvgT",
"regionLossAvgT",
773 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
776inline bool is_orbit_metric_method(
const std::string& m) {
777 static const char* kTab[] = {
"getAvgOrbitTable",
"getAvgOrbitT",
"getAvgOrbit",
"orbitAvgT",
779 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
782inline bool is_moment_method(
const std::string& m) {
783 static const char* kTab[] = {
"getMomentTable",
"getMomentChainTable",
"getMomentStationTable",
784 "getMomentT",
"getMomentChainT",
"getMomentStationT",
785 "momentT",
"momentChainT",
"momentStationT",
787 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
790inline bool is_sens_method(
const std::string& m) {
791 static const char* kTab[] = {
"getSensitivityTable",
"getSensitivityT",
"sensitivityT",
"sT",
792 "supportsExactSensitivity"};
793 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
808 if (detail::is_ensemble_method(method))
809 throw InputError(
"SolverAUTO: method '" + method +
810 "' is only available for LayeredNetwork models");
812 std::vector<AutoSolver> order;
813 if (method ==
"getTranAvg") {
815 }
else if (detail::is_cdf_method(method)) {
818 if (
sn.has_homogeneous_scheduling(SchedStrategy::FCFS) &&
sn.has_product_form())
822 }
else if (method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT") {
824 }
else if (detail::is_tran_prob_method(method)) {
826 }
else if (detail::is_sample_method(method)) {
828 }
else if (detail::is_sample_aggr_method(method)) {
830 }
else if (detail::is_prob_method(method)) {
831 if (
sn.has_product_form())
835 }
else if (detail::is_cache_metric_method(method)) {
838 }
else if (detail::is_loss_metric_method(method)) {
840 }
else if (detail::is_orbit_metric_method(method)) {
842 }
else if (detail::is_moment_method(method)) {
844 }
else if (detail::is_sens_method(method)) {
861 std::vector<AutoSolver> order;
862 if (detail::is_tran_prob_method(method) || detail::is_cdf_method(method) ||
863 method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT" || method ==
"getTranAvg") {
865 }
else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
868 }
else if (detail::is_prob_method(method)) {
878 throw detail::no_solver(
" exactly for method '" + method +
879 "'; use the default heuristic for the approximation",
886 const std::vector<AutoSolver> order =
887 detail::is_sample_method(method)
892 throw detail::no_solver(
" by simulation", order);
938 const std::string& method,
AutoMode mode) {
940 std::vector<AutoSolver> out(1, chosen.
solver);
942 for (std::size_t i = 0; i < cand.size(); ++i)
943 if (std::find(out.begin(), out.end(), cand[i]) == out.end()) out.push_back(cand[i]);
958inline bool layered_ranked(
const std::vector<AutoLayered>& order,
AutoLayeredChoice& out) {
959 std::vector<AutoLayered> skipped;
960 for (std::size_t k = 0; k < order.size(); ++k) {
962 skipped.push_back(order[k]);
986 std::vector<AutoLayered> order;
993 }
else if (method ==
"getTranAvg" || detail::is_cdf_method(method) ||
994 method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT") {
996 }
else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
998 }
else if (detail::is_prob_method(method) || detail::is_tran_prob_method(method)) {
1000 }
else if (detail::is_ensemble_method(method)) {
1003 }
else if (has_cache_task) {
1016 for (std::size_t i = 0; i < order.size(); ++i)
1020 if (detail::layered_ranked(order, out))
return out;
1022 "SolverAUTO: no LayeredNetwork solver in this port serves method '" + method +
1023 "'; the reference's ranking is served by SolverLQNS, which shells out to an external "
1024 "binary this port does not wrap");
1043 std::vector<AutoEnv> order;
1049 std::vector<AutoEnv> skipped;
1050 for (std::size_t k = 0; k < order.size(); ++k) {
1052 skipped.push_back(order[k]);
1060 "SolverAUTO: the Environment ranking for method '" + method +
1061 "' selects an MVA or NC stage solver, and SolverENV in this port solves a stage with the "
1062 "fluid analyzer (or, under the state-vector coupling, an explicit chain); rerun with "
1063 "-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.