5#ifndef LINE_SOLVERS_SSA_SSA_DISPATCH_H
6#define LINE_SOLVERS_SSA_SSA_DISPATCH_H
126bool ssa_check_scheduling(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
128 for (std::size_t i = 0; i < sn.nstations; ++i) {
156 if (!raise)
return false;
157 throw UnsupportedError(ErrorKind{
"SchedStrategy"},
"SolverSSA(method='nrm'): the scheduling policy '" +
159 sn.stations[i].name +
160 "' is not supported by the NRM in any codebase");
177bool ssa_check_nodes(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
178 for (
const qn::NodeDef& nd : sn.nodes) {
179 switch (nd.nodetype) {
182 case qn::NodeType::Fork:
183 case qn::NodeType::Join:
184 if (!raise)
return false;
185 throw UnsupportedError(ErrorKind{
"Fork"},
"SolverSSA(method='nrm'): node '" + nd.name +
186 "' makes this a fork-join model, which the NRM does not "
187 "handle in any codebase (isNrmEligible excludes it); the "
188 "reference falls back to the serial engine, whose own "
189 "fork handler (sn.fjsync / State.afterFJEvent) is not "
214bool ssa_check_routing(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
215 for (
const qn::NodeDef& nd : sn.nodes)
216 for (std::size_t r = 0; r < nd.routing.size(); ++r) {
218 if (rs == qn::RoutingStrategy::PROB || rs == qn::RoutingStrategy::RAND ||
219 rs == qn::RoutingStrategy::DISABLED || rs == qn::RoutingStrategy::RROBIN ||
220 rs == qn::RoutingStrategy::WRROBIN || rs == qn::RoutingStrategy::JSQ ||
221 rs == qn::RoutingStrategy::SQ)
223 if (!raise)
return false;
225 "SolverSSA(method='nrm'): node '" + nd.name +
"' routes class '" +
227 "'. Its destination is a function the C++ NetworkStruct does not carry, so "
228 "there is nothing to resolve at firing time");
247bool ssa_check_phases(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
249 for (std::size_t i = 0; i < sn.nstations; ++i) {
261 for (std::size_t r = 0; r < sn.nclasses; ++r) {
262 if (sn.disabled[i][r])
continue;
265 if (!raise)
return false;
267 "SolverSSA(method='nrm'): class '" + sn.classes[r].name +
"' has MAP service "
268 "at FCFS station '" + sn.stations[i].name +
"', whose draws are chained "
269 "through a memory phase the NRM does not carry; ask for 'default', which "
270 "falls back to the serial engine");
273 for (std::size_t r = 0; r < sn.nclasses; ++r) {
274 if (sn.disabled[i][r])
continue;
279 if (!raise)
return false;
281 "SolverSSA(method='nrm'): class '" + sn.classes[r].name +
"' has non-exponential "
282 "service at station '" + sn.stations[i].name +
"', whose '" +
284 "' discipline the NRM phase expansion does not cover; ask for 'default', which "
285 "falls back to the serial engine as the reference does");
301bool ssa_check_cdscaling(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
305 if (!raise)
return false;
307 "SolverSSA(method='nrm'): the model declares a global dependence "
308 "(setGlobalDependence). The NRM builds its propensities from the per-station "
309 "population slice and never sees the whole population matrix the handle reads, so "
310 "the sample path would run at the unscaled rates; ask for method='serial', which "
326bool ssa_check_regions(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
327 for (
const typename qn::NetworkStruct<T>::Region& rg : sn.regions)
328 for (std::size_t r = 0; r < rg.rule.size(); ++r) {
330 if (d != qn::DropStrategy::BAS && d != qn::DropStrategy::BBS &&
331 d != qn::DropStrategy::RSRD)
333 if (!raise)
return false;
335 "SolverSSA(method='nrm'): finite capacity region '" + rg.name +
336 "' declares a blocking rule for class '" + sn.classes[r].name +
337 "'. The NRM region gate implements DROP and WAITQ only, as the reference's does");
352bool ssa_check_reply(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
353 for (std::size_t ind = 0; ind < sn.replyblock.size(); ++ind)
354 for (std::size_t k = 0; k < sn.replyblock[ind].size(); ++k) {
355 if (!sn.replyblock[ind][k])
continue;
356 if (!raise)
return false;
358 "SolverSSA(method='nrm'): class '" + sn.classes[k].name +
"' makes a synchronous "
359 "call (REPLY signal) from node '" + sn.nodes[ind].name +
"'. The NRM has no "
360 "held-server counter for a pending reply; use method='serial'");
372bool ssa_check_impatience(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
376 if (!raise)
return false;
379 for (std::size_t i = 0; i < sn.stations.size(); ++i) {
380 const qn::Station<T>& st = sn.stations[i];
382 for (std::size_t r = 0; r < st.balking.size(); ++r)
384 const auto rit = sn.retrialparam.find(i + 1);
385 if (rit != sn.retrialparam.end())
386 for (std::size_t r = 0; r < rit->second.retrial_proc.size(); ++r)
387 any = any || !rit->second.retrial_proc[r].disabled;
389 if (!raise)
return false;
391 "SolverSSA(method='nrm'): station '" + st.name +
"' declares reneging, balking or a "
392 "retrial orbit. The NRM has no reaction for any of them; use method='serial'");
408bool ssa_nrm_eligible(
const qn::NetworkStruct<T>& sn) {
417 if (sn.has_immediate_feedback())
return false;
418 return ssa_check_nodes(sn,
false) && ssa_check_scheduling(sn,
false) &&
419 ssa_check_routing(sn,
false) && ssa_check_phases(sn,
false) &&
420 ssa_check_cdscaling(sn,
false) && ssa_check_regions(sn,
false) &&
421 ssa_check_reply(sn,
false) && ssa_check_impatience(sn,
false) &&
422 spn_nrm_supported(sn,
false);
436std::string ssa_nrm_supports(
const qn::NetworkStruct<T>& sn) {
438 ssa_check_nodes(sn,
true);
439 ssa_check_scheduling(sn,
true);
440 ssa_check_routing(sn,
true);
441 ssa_check_phases(sn,
true);
442 ssa_check_cdscaling(sn,
true);
443 ssa_check_regions(sn,
true);
444 ssa_check_reply(sn,
true);
445 ssa_check_impatience(sn,
true);
446 spn_nrm_supported(sn,
true);
447 }
catch (
const UnsupportedError& e) {
461inline SsaNrmSpaceOptions ssa_space_options(
const SsaOptions& o) {
462 SsaNrmSpaceOptions s;
463 static_cast<SsaOptions&
>(s) = o;
477inline SsaSerialOptions ssa_serial_options(
const SsaOptions& o) {
479 static_cast<SsaOptions&
>(s) = o;
483inline SsaParallelOptions ssa_parallel_options(
const SsaOptions& o) {
484 SsaParallelOptions p;
485 static_cast<SsaOptions&
>(p) = o;
498SsaSolution ssa_serial_avg(
const qn::NetworkStruct<T>& sn,
const SsaOptions& opt,
499 std::vector<SsaCacheRatio>* cache) {
501 if (cache) *cache = s.cache;
532SsaSolution ssa_pn_token_rates(
const qn::NetworkStruct<T>& sn,
const SsaSolution& in) {
533 bool has_place =
false;
534 for (std::size_t a = 0; a < sn.nodes.size() && !has_place; ++a)
535 if (sn.nodes[a].nodetype == qn::NodeType::Place) has_place =
true;
536 if (!has_place ||
in.TN.rows() == 0)
return in;
538 const std::size_t M =
in.TN.rows(), K =
in.TN.cols();
539 Matrix<T>
QN(M, K), TN(M, K), RN(M, K);
540 for (std::size_t i = 0; i < M; ++i)
541 for (std::size_t k = 0; k < K; ++k) {
542 QN(i, k) = num_traits<T>::from_double(
in.QN(i, k));
543 TN(i, k) = num_traits<T>::from_double(
in.TN(i, k));
544 RN(i, k) = num_traits<T>::from_double(
in.RN(i, k));
548 SsaSolution out =
in;
549 for (std::size_t i = 0; i < M; ++i)
550 for (std::size_t k = 0; k < K; ++k) {
551 out.TN(i, k) = num_traits<T>::to_double(pn.TN(i, k));
552 out.RN(i, k) = num_traits<T>::to_double(pn.RN(i, k));
571 return ::line::method_registry::methods(
"SSA");
583 std::vector<SsaCacheRatio>*
cache =
nullptr) {
588 if constexpr (!std::is_same<T, double>::value) {
593 "solver_ssa_nrm: an SSA sample path is generated from exponential clocks, which are "
594 "logarithms of uniform draws; there is no exact value to compute and a wider float "
595 "carries no information the Monte Carlo error does not swamp. Rerun with --arith "
598 detail::ssa_check_nodes(
sn);
599 detail::ssa_check_scheduling(
sn);
600 detail::ssa_check_routing(
sn);
601 detail::ssa_check_phases(
sn);
602 detail::ssa_check_cdscaling(
sn);
603 detail::ssa_check_regions(
sn);
604 detail::ssa_check_reply(
sn);
605 detail::ssa_check_impatience(
sn);
606 detail::spn_nrm_supported(
sn);
615 if (nd.
nodetype == qn::NodeType::Transition) {
621 if (
opt.state_space_gen !=
"none" &&
opt.state_space_gen !=
"default")
668 std::vector<SsaCacheRatio>*
cache) {
678 if (
sn.has_fluid_places()) {
692 if (
sn.has_immediate_feedback()) {
693 const std::string& mm =
opt.method;
694 const bool runs_nrm =
695 mm ==
"nrm" || ((mm ==
"default" || mm ==
"para" || mm ==
"pana" || mm ==
"parallel") &&
696 detail::ssa_nrm_eligible(
sn));
698 std::cerr <<
"[LINE] Warning: SolverSSA(method=nrm) does not model immediate feedback "
699 "(immfeed); self-loops are treated as class-switching with re-queueing. "
700 "Use method='serial' for immediate feedback."
703 const std::string& m =
opt.method;
704 if (m ==
"default") {
712 if (serial_detail::serial_can_run(
sn))
713 return detail::ssa_serial_avg(
sn,
opt,
cache);
719 if (!detail::ssa_check_reply(
sn,
false)) {
720 std::cerr <<
"[LINE] Warning: SolverSSA: NRM does not support synchronous calls "
721 "(REPLY signals); falling back to the serial method."
723 return detail::ssa_serial_avg(
sn,
opt,
cache);
727 if (m ==
"ssa" || m ==
"serial")
return detail::ssa_serial_avg(
sn,
opt,
cache);
728 if (m ==
"para" || m ==
"pana" || m ==
"parallel") {
735 "' is not a valid method; the reference offers 'default', 'ssa', "
736 "'serial', 'para', 'parallel' and 'nrm', all of which this port "
753 const std::vector<SsaCacheRatio>&
cache);
759 std::vector<SsaCacheRatio>*
cache =
nullptr) {
760 if(
opt.method==
"mlmc")
761 throw UnsupportedError(
ErrorKind{
"Analysis"},
"SSA MLMC estimates finite-horizon rewards; use solver_ssa_get_tran_reward");
763 std::vector<SsaCacheRatio> local;
764 std::vector<SsaCacheRatio>* cr =
cache ?
cache : &local;
785 const std::vector<SsaCacheRatio>&
cache) {
787 sn, std::vector<T>(), std::vector<T>(), std::vector<T>(), std::vector<T>(),
Matrix<T>(),
789 for (std::size_t c = 0; c < out.
caches.size(); ++c) {
790 for (std::size_t j = 0; j <
cache.size(); ++j) {
797 for (std::size_t r = 0; r <
cache[j].hitprob.size(); ++r)
799 for (std::size_t r = 0; r <
cache[j].missprob.size(); ++r)
801 for (std::size_t r = 0; r <
cache[j].delayedprob.size(); ++r)
803 for (std::size_t r = 0; r <
cache[j].residt.size(); ++r)
833 const std::vector<SsaCacheRatio>&
cache) {
834 if (
cache.empty())
return base;
836 const std::size_t I =
sn.nodes.size(), K =
sn.nclasses;
837 if (I == 0 || K == 0 ||
sn.rtnodes.rows() < I * K)
return base;
841 std::vector<std::vector<bool> > conn(I, std::vector<bool>(I,
false));
842 for (std::size_t a = 0; a < I; ++a)
843 for (std::size_t b = 0; b < I; ++b) {
844 if (a == b)
continue;
845 for (std::size_t r = 0; r < K && !conn[a][b]; ++r)
846 for (std::size_t s = 0; s < K && !conn[a][b]; ++s)
847 if (
sn.rtnodes(a * K + r, b * K + s) > zero) conn[a][b] =
true;
849 bool rewrote =
false;
850 for (std::size_t j = 0; j <
cache.size(); ++j) {
851 const std::size_t ind =
cache[j].node;
852 if (ind == 0 || ind > I)
continue;
853 typename std::map<std::size_t, qn::CacheParam<T> >::const_iterator ci =
854 sn.nodeparam.find(ind);
855 if (ci ==
sn.nodeparam.end())
continue;
856 const std::size_t nd = ind - 1;
857 for (std::size_t r = 0; r < K; ++r) {
858 if (ci->second.hitclass.size() <= r || ci->second.missclass.size() <= r)
continue;
859 const std::size_t hc = ci->second.hitclass[r],
mc = ci->second.missclass[r];
860 if (hc == 0 ||
mc == 0 || hc > K ||
mc > K)
continue;
861 if (
cache[j].hitprob.size() <= r ||
cache[j].missprob.size() <= r)
continue;
862 const double hp =
cache[j].hitprob[r], mp =
cache[j].missprob[r];
863 if (!(hp == hp) || !(mp == mp))
continue;
864 for (std::size_t col = 0; col < I * K; ++col)
sn.rtnodes(nd * K + r, col) = zero;
865 for (std::size_t jnd = 0; jnd < I; ++jnd) {
866 if (!conn[nd][jnd])
continue;
873 if (!rewrote)
return base;
874 sn.da_recompute_visits_from_rtnodes();
901 const std::size_t M =
sn.nstations, R =
sn.nclasses;
902 if (in.TN.rows() != M || in.TN.cols() != R)
return in;
903 std::vector<std::vector<bool> > imm(M, std::vector<bool>(R,
false));
904 bool any_imm =
false;
905 for (std::size_t ist = 0; ist < M; ++ist) {
906 if (
sn.stations[ist].nodetype == qn::NodeType::Source)
continue;
907 for (std::size_t r = 0; r < R; ++r)
910 if (!any_imm)
return in;
912 for (std::size_t r = 0; r < nd.
routing.size(); ++r)
913 if (nd.
routing[r] == qn::RoutingStrategy::JSQ || nd.
routing[r] == qn::RoutingStrategy::SQ)
return in;
917 for (std::size_t i = 0; i < M; ++i)
919 for (std::size_t sweep = 0; sweep < M; ++sweep) {
921 if (AN.
rows() != M || AN.
cols() != R)
break;
923 for (std::size_t i = 0; i < M; ++i)
924 for (std::size_t r = 0; r < R; ++r)
929 for (std::size_t i = 0; i < M; ++i)
930 for (std::size_t r = 0; r < R; ++r)
931 if (imm[i][r]) TN(i, r) = AN(i, r);
933 for (std::size_t i = 0; i < M; ++i)
935 for (std::size_t r = 0; r < R; ++r) {
936 const std::size_t rs =
sn.classes[r].refstat;
937 if (rs > 0 && rs <= M && r < out.
XN.size()) out.
XN[r] = out.
TN(rs - 1, r);
938 for (std::size_t i = 0; i < M; ++i)
939 if (imm[i][r]) out.
RN(i, r) = out.
TN(i, r) > 0.0 ? out.
QN(i, r) / out.
TN(i, r) : 0.0;
940 if (r < out.
XN.size() && r < out.
CN.size() && out.
XN[r] > 0.0)
941 out.
CN[r] =
sn.classes[r].population / out.
XN[r];
What a solver observed about the Cache nodes of a model.
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
The NRM engine: the reaction network built from sn, and the sample path.
const std::vector< SsaCacheRatio > & cache() const
The cache write-back of the last run(), one entry per Cache node.
SsaSolution run()
Run opt.samples firings and return the time-averaged metrics.
The SPN Next-Reaction-Method engine, solver_ssa_nrm_spn of the reference.
The exception types the port throws.
The method names of each solver, in listing order.
SnPnAvgRates< T > sn_pn_avg_rates(const qn::NetworkStruct< T > &sn, const Matrix< T > &QN, const Matrix< T > &TN, const Matrix< T > &AN, const Matrix< T > &RN)
Port of sn_pn_avg_rates: rewrite the place rows of TN, AN and RN so that they agree with the recovere...
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
DropStrategy
Blocking and loss rules, with the values of MATLAB DropStrategy.
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
const char * sched_to_text(SchedStrategy s)
const char * routing_to_text(RoutingStrategy r)
bool in(const std::string &s, std::initializer_list< const char * > set)
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
Port of sn_get_arvr_from_tput: the arrival rate each station sees, from the throughputs and the class...
std::string state_impatience_unsupported(const NetworkStruct< T > &sn, const std::string &solver)
Port of solver_ctmc_state_supports.m: the impatience laws the state machinery cannot serve,...
bool station_has_reneging(const NetworkStruct< T > &sn, std::size_t ist)
True when station ist declares exponential reneging for some class.
CacheMetrics< T > cache_metrics_of(const qn::NetworkStruct< T > &sn, const std::vector< T > &hitprob, const std::vector< T > &missprob, const std::vector< T > &delayedprob, const std::vector< T > &latency, const Matrix< T > &hitproblist, const Matrix< T > &itemprob, const std::vector< T > &listcost)
Assemble CacheMetrics from what a cache analyzer returned.
SsaSerialSolution< T > solver_ssa_serial_analyzer(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt)
Port of solver_ssa_analyzer_serial.m plus the fork-join wrapper @@SolverSSA/runAnalyzer....
SsaSolution solver_ssa_nrm_analyzer(const qn::NetworkStruct< T > &sn, const SsaOptions &opt, std::vector< SsaCacheRatio > *cache=nullptr)
solver_ssa_analyzer_nrm.m: run the NRM and return the metric table.
SsaParallelSolution< T > solver_ssa_parallel_analyzer(const qn::NetworkStruct< T > &sn, const SsaParallelOptions &opt)
solver_ssa_analyzer_parallel.m: run R replicas of the serial engine and combine their estimates.
SsaNrmSpaceSolution< T > solver_ssa_nrm_space_analyzer(const qn::NetworkStruct< T > &sn, const SsaNrmSpaceOptions &opt)
Port of the else branch of solver_ssa_analyzer_nrm.m, the one state_space_gen selects: the means as p...
std::vector< std::string > list_valid_methods()
Port of SolverSSA.listValidMethods.
SsaSolution solver_ssa_engine(const qn::NetworkStruct< T > &sn, const SsaOptions &opt_in, std::vector< SsaCacheRatio > *cache)
solver_ssa_analyzer.m: choose the method.
SsaSolution solver_ssa(const qn::NetworkStruct< T > &sn, const SsaOptions &opt, std::vector< SsaCacheRatio > *cache=nullptr)
SsaSolution ssa_imm_tput_balance(const qn::NetworkStruct< T > &sn, const SsaSolution &in)
Port of ssa_imm_tput_balance.m: the throughput of every station-class pair whose service is Immediate...
qn::NetworkStruct< T > sn_with_ssa_cache_split(const qn::NetworkStruct< T > &base, const std::vector< SsaCacheRatio > &cache)
@@SolverSSA/runAnalyzer itself: the engine the method selects, then the result assembly the reference...
solvers::CacheMetrics< T > cache_metrics_of_ssa(const qn::NetworkStruct< T > &sn, const std::vector< SsaCacheRatio > &cache)
CacheMetrics from the serial engine's cache write-back.
double ssa_resolve_warmupfrac(const Struct &sn, double warmupfrac, bool steady=true)
ssa_warmupfrac.m: the effective warmup (initial-transient) discard fraction.
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
Ports of matlab/src/api/sn/sn_pn_firing_rates.m and sn_pn_avg_rates.m.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
SolverSSA, the nrm method: a port of solver_ssa_nrm.m and its analyzer solver_ssa_analyzer_nrm....
SolverSSA, the EXPLICIT STATE SPACE variant of the Next Reaction Method: a port of solver_ssa_nrm_spa...
SolverSSA on a STOCHASTIC PETRI NET: the port of solver_ssa_nrm_spn, the sub-engine solver_ssa_nrm....
SolverSSA, the para / parallel method: a port of solver_ssa_analyzer_parallel.m.
SolverSSA, the serial method: a port of solver_ssa_reachability.m, of the run loop of solver_ssa....
Controls, results and the random source of SolverSSA.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
The verdict of sn_fluid_refusal: ok when no rule is broken, else the reason.
static constexpr double FineTol
std::vector< RoutingStrategy > routing
sn.routing, per class.
Every Cache node of the model, in node order; empty on a model with none.
std::vector< CacheNodeMetrics< T > > caches
One Cache node's measured behaviour.
std::vector< T > hitprob
(K) TRUE hit fraction, EMPTY = not computed
std::vector< T > delayedprob
(K) delayed-hit fraction, EMPTY off a retrieval system
std::vector< T > latency
(K) expected latency per read request (hits wait 0), EMPTY = not computed
std::vector< T > missprob
(K)
Controls, defaulting to SolverOptions('SSA') in the reference.
double warmupfrac
options.config.warmupfrac: the leading fraction of the path discarded before the means are taken.
What the analyzer returns, in the same shape as the MVA and fluid results.