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 "fluid";
228 if (name ==
"jmt")
return "jmt";
229 if (name ==
"ssa")
return "ssa";
230 if (name ==
"ctmc")
return "ctmc";
231 if (name ==
"ldes" || name ==
"des")
return "ldes";
232 if (name ==
"ba")
return "ba";
233 if (name ==
"env")
return "env";
234 if (name ==
"ln")
return "ln";
235 if (name ==
"lqns" || name ==
"lqsim")
return "lqns";
236 if (name ==
"qns")
return "qns";
237 if (name ==
"uq")
return "uq";
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);
282 "SolverAUTO: '" + token +
283 "' is neither a selection intent (default, heur, exact, sim, fast, accurate, "
285 "nor a method family (mva, nc, ctmc, fluid, mam, ag, ba, ssa, ldes, jmt, qns, ln, "
286 "env, lqns, uq). A bare algorithm name must be qualified by its family here, as in "
287 "'nc.comom': resolving it needs the per-family method registry "
288 "(listValidMethods) that this port does not carry");
291 t.
submethod = rest.empty() ? std::string(
"default") : rest;
332 bool has_open =
false, has_closed =
false;
334 if (std::isinf(c.
population)) has_open =
true;
335 else has_closed =
true;
337 t.
is_open = has_open && !has_closed;
339 t.
is_mixed = has_open && has_closed;
346 for (std::size_t i = 1; i <=
sn.nstations && !t.
has_map; ++i)
347 for (std::size_t r = 1; r <=
sn.nclasses; ++r) {
348 const ProcessType p =
sn.procid(i, r);
349 if (p == ProcessType::MAP || p == ProcessType::MMPP2) {
357 bool preempt =
false, psprio =
false, hol =
false;
359 if (s.
sched == SchedStrategy::FCFSPRPRIO || s.
sched == SchedStrategy::FCFSPIPRIO ||
360 s.
sched == SchedStrategy::LCFSPRPRIO || s.
sched == SchedStrategy::LCFSPIPRIO)
362 if (s.
sched == SchedStrategy::PSPRIO || s.
sched == SchedStrategy::DPSPRIO ||
363 s.
sched == SchedStrategy::GPSPRIO)
365 if (s.
sched == SchedStrategy::HOL || s.
sched == SchedStrategy::LCFSPRIO ||
366 s.
sched == SchedStrategy::SRPTPRIO)
369 if (preempt) t.
prio =
"preempt";
370 else if (psprio) t.
prio =
"ps";
371 else if (hol) t.
prio =
"hol";
399 const std::size_t M =
sn.nstations, K =
sn.nclasses;
400 if (M == 0 || K == 0)
return 0.0;
402 cutoff = std::ceil(std::pow(6000.0, 1.0 /
static_cast<double>(M * K)));
404 const auto is_share = [](SchedStrategy s) {
405 return s == SchedStrategy::INF || s == SchedStrategy::PS || s == SchedStrategy::DPS ||
406 s == SchedStrategy::GPS || s == SchedStrategy::PSPRIO ||
407 s == SchedStrategy::DPSPRIO || s == SchedStrategy::GPSPRIO ||
408 s == SchedStrategy::LPS;
410 std::vector<bool> is_buffered(K,
true);
412 for (std::size_t r = 0; r < K; ++r) {
413 if (r <
sn.issignal.size() &&
sn.issignal[r]) is_buffered[r] =
false;
414 if (is_buffered[r]) ++Kb;
416 std::size_t n_ord = 0;
418 for (std::size_t i = 0; i < M; ++i) {
419 const SchedStrategy s =
sn.stations[i].sched;
420 if (s == SchedStrategy::EXT || is_share(s))
continue;
424 const std::size_t m_place = (M > n_ord) ? (M - n_ord) : 0;
427 std::vector<double> nk_eff(K, 0.0);
428 for (std::size_t r = 0; r < K; ++r) {
429 const double nk = std::isinf(
sn.classes[r].population) ? cutoff :
sn.classes[r].population;
432 const double m =
static_cast<double>(m_place);
433 log_n += std::lgamma(1.0 + nk + m - 1.0) - std::lgamma(m) - std::lgamma(1.0 + nk);
438 double tot_jobs = 0.0;
439 for (std::size_t r = 0; r < K; ++r)
440 if (is_buffered[r]) tot_jobs += nk_eff[r];
441 const double log_k = std::log(
static_cast<double>(Kb));
442 const double log_seq = (tot_jobs + 1.0) * log_k - std::log(
static_cast<double>(Kb) - 1.0) +
443 std::log1p(-std::exp(-(tot_jobs + 1.0) * log_k));
444 log_n +=
static_cast<double>(n_ord) * log_seq;
447 for (std::size_t i = 1; i <= M; ++i) {
448 const SchedStrategy sched =
sn.stations[i - 1].sched;
449 const bool shares = sched == SchedStrategy::INF || sched == SchedStrategy::PS ||
450 sched == SchedStrategy::DPS || sched == SchedStrategy::GPS ||
451 sched == SchedStrategy::PSPRIO || sched == SchedStrategy::DPSPRIO ||
452 sched == SchedStrategy::GPSPRIO || sched == SchedStrategy::LPS;
453 for (std::size_t r = 1; r <= K; ++r) {
454 const double p =
static_cast<double>(std::max<std::size_t>(
sn.phases_of(i, r), 1));
455 if (p <= 1.0)
continue;
457 if (sched == SchedStrategy::EXT) m = 1.0;
458 else if (shares) m = nk_eff[r - 1];
459 else m = std::min(nk_eff[r - 1],
sn.stations[i - 1].nservers);
460 if (!std::isfinite(m)) m = nk_eff[r - 1];
461 log_n += std::lgamma(1.0 + m + p - 1.0) - std::lgamma(p) - std::lgamma(1.0 + m);
469 const std::size_t N =
sn.nodes.size(), R =
sn.nclasses;
470 if (
sn.rtnodes.rows() >= N * R) {
471 for (std::size_t i = 1; i <= N; ++i) {
472 std::size_t nout = 0;
473 for (std::size_t j = 1; j <= N; ++j) {
475 for (std::size_t r = 0; r < R && !linked; ++r)
476 for (std::size_t s = 0; s < R && !linked; ++s)
478 sn.rtnodes((i - 1) * R + r, (j - 1) * R + s)) > 0.0)
482 if (nout <= 1)
continue;
484 const std::vector<RoutingStrategy>& rt =
sn.nodes[i - 1].routing;
485 for (std::size_t r = 0; r < rt.size(); ++r)
486 if (rt[r] == RoutingStrategy::RROBIN || rt[r] == RoutingStrategy::WRROBIN) ++nrr;
487 if (nrr > 0) log_n +=
static_cast<double>(nrr) * std::log(
static_cast<double>(nout));
519 const std::string method = token.empty() ? std::string(
"default") : token;
540 if (!
sn.has_product_form()) {
547 if (st.
sched == SchedStrategy::OI || st.
sched == SchedStrategy::PAS) oi =
true;
548 if (!oi)
return false;
571 std::vector<AutoSolver> skipped;
572 for (std::size_t k = 0; k < order.size(); ++k) {
574 skipped.push_back(order[k]);
580 out.
method = (token ==
"default") ? std::string() : token;
595 std::vector<AutoSolver> out;
596 for (std::size_t i = 0; i <
sizeof(kSlots) /
sizeof(*kSlots); ++i)
598 out.push_back(kSlots[i]);
615inline std::vector<AutoSolver> avg_order(
const AutoTraits& t,
bool homogeneous_inf) {
623 if (t.prio ==
"preempt")
640inline UnsupportedError no_solver(
const std::string& what,
641 const std::vector<AutoSolver>& skipped) {
642 std::string msg =
"SolverAUTO: no solver supports this model" + what;
643 if (!skipped.empty()) {
644 msg +=
" (the ranking preferred ";
645 for (std::size_t i = 0; i < skipped.size(); ++i) {
649 msg +=
", which this port does not build)";
674 const std::vector<AutoSolver> exact_order =
681 const std::vector<AutoSolver> order =
682 detail::avg_order(t,
sn.has_homogeneous_scheduling(SchedStrategy::INF));
691 throw detail::no_solver(
"", pool);
705inline bool in_list(
const std::string& m,
const char*
const* tab, std::size_t n) {
706 for (std::size_t i = 0; i < n; ++i)
707 if (m == tab[i])
return true;
712inline bool is_avg_method(
const std::string& m) {
713 static const char* kAvg[] = {
714 "getAvgChainTable",
"getAvgTputTable",
"getAvgRespTTable",
"getAvgUtilTable",
715 "getAvgSysTable",
"getAvgNodeTable",
"getAvgTable",
"getAvgTableLayered",
"getAvg",
716 "getAvgChain",
"getAvgSys",
"getAvgNode",
"getAvgNodeChain",
"getAvgArvRChain",
717 "getAvgQLenChain",
"getAvgUtilChain",
"getAvgRespTChain",
"getAvgTputChain",
718 "getAvgSysRespT",
"getAvgSysTput",
"getAvgQLen",
"getAvgUtil",
"getAvgRespT",
719 "getAvgResidT",
"getAvgWaitT",
"getAvgTput",
"getAvgArvR",
"getAvgQLenTable",
720 "getAvgResidTChain",
"getAvgNodeQLenChain",
"getAvgNodeUtilChain",
721 "getAvgNodeRespTChain",
"getAvgNodeResidTChain",
"getAvgNodeTputChain",
722 "getAvgNodeArvRChain",
"getAvgNodeChainTable",
"getResults",
"hasResults",
723 "getAvgHandles",
"getTranHandles",
"getAvgQLenHandles",
"getAvgUtilHandles",
724 "getAvgRespTHandles",
"getAvgTputHandles",
"getAvgArvRHandles",
"getAvgResidTHandles",
725 "getMethodFeatureSet",
"supportsModelMethod",
"isStochasticMethod",
"libraries",
726 "showLibraryAttribution",
"citations"};
727 return in_list(m, kAvg,
sizeof(kAvg) /
sizeof(*kAvg));
731inline bool is_ensemble_method(
const std::string& m) {
732 static const char* kEns[] = {
"getEnsembleAvg",
"getEnsembleAvgTables",
"getSolver",
733 "setSolver",
"getNumberOfModels",
"getIteration",
734 "get_state",
"set_state",
"update_solver"};
735 return in_list(m, kEns,
sizeof(kEns) /
sizeof(*kEns));
738inline bool is_cdf_method(
const std::string& m) {
739 return m ==
"getCdfRespT" || m ==
"getCdfPassT" || m ==
"getPerctRespT";
742inline bool is_tran_prob_method(
const std::string& m) {
743 return m ==
"getTranProb" || m ==
"getTranProbSys" || m ==
"getTranProbAggr" ||
744 m ==
"getTranProbSysAggr";
747inline bool is_prob_method(
const std::string& m) {
748 return m ==
"getProb" || m ==
"getProbAggr" || m ==
"getProbSys" || m ==
"getProbSysAggr" ||
749 m ==
"getProbMarg" || m ==
"getProbNormConstAggr";
752inline bool is_sample_method(
const std::string& m) {
return m ==
"sample" || m ==
"sampleSys"; }
754inline bool is_sample_aggr_method(
const std::string& m) {
755 return m ==
"sampleAggr" || m ==
"sampleSysAggr";
758inline bool is_cache_metric_method(
const std::string& m) {
759 static const char* kTab[] = {
"getAvgCacheTable",
"getAvgCacheT",
"getAvgItemTable",
760 "getAvgItemT",
"cacheAvgT",
"itemAvgT",
"aCaT",
"aIT"};
761 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
764inline bool is_loss_metric_method(
const std::string& m) {
765 static const char* kTab[] = {
"getAvgLossTable",
"getAvgLossT",
"getAvgRegionLossTable",
766 "getAvgRegionLossT",
"lossAvgT",
"regionLossAvgT",
768 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
771inline bool is_orbit_metric_method(
const std::string& m) {
772 static const char* kTab[] = {
"getAvgOrbitTable",
"getAvgOrbitT",
"getAvgOrbit",
"orbitAvgT",
774 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
777inline bool is_moment_method(
const std::string& m) {
778 static const char* kTab[] = {
"getMomentTable",
"getMomentChainTable",
"getMomentStationTable",
779 "getMomentT",
"getMomentChainT",
"getMomentStationT",
780 "momentT",
"momentChainT",
"momentStationT",
782 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
785inline bool is_sens_method(
const std::string& m) {
786 static const char* kTab[] = {
"getSensitivityTable",
"getSensitivityT",
"sensitivityT",
"sT",
787 "supportsExactSensitivity"};
788 return in_list(m, kTab,
sizeof(kTab) /
sizeof(*kTab));
803 if (detail::is_ensemble_method(method))
804 throw InputError(
"SolverAUTO: method '" + method +
805 "' is only available for LayeredNetwork models");
807 std::vector<AutoSolver> order;
808 if (method ==
"getTranAvg") {
810 }
else if (detail::is_cdf_method(method)) {
813 if (
sn.has_homogeneous_scheduling(SchedStrategy::FCFS) &&
sn.has_product_form())
817 }
else if (method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT") {
819 }
else if (detail::is_tran_prob_method(method)) {
821 }
else if (detail::is_sample_method(method)) {
823 }
else if (detail::is_sample_aggr_method(method)) {
825 }
else if (detail::is_prob_method(method)) {
826 if (
sn.has_product_form())
830 }
else if (detail::is_cache_metric_method(method)) {
833 }
else if (detail::is_loss_metric_method(method)) {
835 }
else if (detail::is_orbit_metric_method(method)) {
837 }
else if (detail::is_moment_method(method)) {
839 }
else if (detail::is_sens_method(method)) {
856 std::vector<AutoSolver> order;
857 if (detail::is_tran_prob_method(method) || detail::is_cdf_method(method) ||
858 method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT" || method ==
"getTranAvg") {
860 }
else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
863 }
else if (detail::is_prob_method(method)) {
873 throw detail::no_solver(
" exactly for method '" + method +
874 "'; use the default heuristic for the approximation",
881 const std::vector<AutoSolver> order =
882 detail::is_sample_method(method)
887 throw detail::no_solver(
" by simulation", order);
933 const std::string& method,
AutoMode mode) {
935 std::vector<AutoSolver> out(1, chosen.
solver);
937 for (std::size_t i = 0; i < cand.size(); ++i)
938 if (std::find(out.begin(), out.end(), cand[i]) == out.end()) out.push_back(cand[i]);
953inline bool layered_ranked(
const std::vector<AutoLayered>& order,
AutoLayeredChoice& out) {
954 std::vector<AutoLayered> skipped;
955 for (std::size_t k = 0; k < order.size(); ++k) {
957 skipped.push_back(order[k]);
981 std::vector<AutoLayered> order;
988 }
else if (method ==
"getTranAvg" || detail::is_cdf_method(method) ||
989 method ==
"getTranCdfPassT" || method ==
"getTranCdfRespT") {
991 }
else if (detail::is_sample_method(method) || detail::is_sample_aggr_method(method)) {
993 }
else if (detail::is_prob_method(method) || detail::is_tran_prob_method(method)) {
995 }
else if (detail::is_ensemble_method(method)) {
998 }
else if (has_cache_task) {
1011 for (std::size_t i = 0; i < order.size(); ++i)
1015 if (detail::layered_ranked(order, out))
return out;
1017 "SolverAUTO: no LayeredNetwork solver in this port serves method '" + method +
1018 "'; the reference's ranking is served by SolverLQNS, which shells out to an external "
1019 "binary this port does not wrap");
1038 std::vector<AutoEnv> order;
1044 std::vector<AutoEnv> skipped;
1045 for (std::size_t k = 0; k < order.size(); ++k) {
1047 skipped.push_back(order[k]);
1055 "SolverAUTO: the Environment ranking for method '" + method +
1056 "' selects an MVA or NC stage solver, and SolverENV in this port solves a stage with the "
1057 "fluid analyzer (or, under the state-vector coupling, an explicit chain); rerun with "
1058 "-s env, whose default coupling is the mean-field one");
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)
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.