5#ifndef LINE_SOLVERS_SSA_SOLVER_SSA_NRM_SPACE_H
6#define LINE_SOLVERS_SSA_SOLVER_SSA_NRM_SPACE_H
137 std::vector<std::vector<double>>* arv =
nullptr,
138 std::vector<std::vector<double>>* dep =
nullptr) {
139 const std::size_t local =
sn.nodes.size() + 1;
140 const std::size_t R =
sn.nclasses;
141 const std::size_t NF =
sn.stateful_nodes.size();
142 std::vector<EnabledEvent<T>> out;
143 if (arv) arv->assign(NF, std::vector<double>(R, 0.0));
144 if (dep) dep->assign(NF, std::vector<double>(R, 0.0));
146 for (std::size_t a = 0; a < sync.size(); ++a) {
148 const std::size_t isf_a =
sn.stateful_index(sy.
active.node);
149 if (isf_a == 0)
continue;
150 const std::size_t isf_p =
152 if (sy.
passive.node != local && isf_p == 0)
continue;
157 for (std::size_t ia = 0; ia < oa.
space.size(); ++ia) {
160 if (!(rate > 0) || !(pa > 0))
continue;
162 if (sy.
passive.node == local) {
174 const std::vector<T>& src =
181 for (std::size_t ip = 0; ip < op.
space.size(); ++ip) {
183 if (!(pp > 0))
continue;
187 if (!(e.
rate > 0))
continue;
198 if (dep) (*dep)[isf_a - 1][sy.
active.cls - 1] += fired;
199 if (arv && isf_p != 0) (*arv)[isf_p - 1][sy.
passive.cls - 1] += fired;
203 for (std::size_t g = 0; g < gsync.size(); ++g) {
205 for (std::size_t
io = 0;
io < go.
space.size(); ++
io) {
208 if (!(w > 0))
continue;
210 e.
sync = sync.size() + g;
215 for (std::size_t j = 0; j < gsync[g].passive.size(); ++j) {
217 const std::size_t pisf =
sn.stateful_index(pev.
node);
218 if (pisf == 0 || pev.
cls == 0 || pev.
cls > R)
continue;
220 (*dep)[pisf - 1][pev.
cls - 1] += w;
222 (*arv)[pisf - 1][pev.
cls - 1] += w;
257 std::vector<double>
n;
258 std::vector<std::vector<std::size_t>>
buf;
267 std::vector<double>
pi;
294namespace space_detail {
322 : sn_(
sn), opt_(
opt), rng_(
opt.seed) {
326 build_dependencies();
327 build_initial_state();
345 std::size_t
nslots()
const {
return NS_; }
349 std::vector<double> A(nrx_, 0.0);
350 for (std::size_t k = 0; k < nrx_; ++k) A[k] = propensity(k, s);
361 std::size_t I_ = 0, K_ = 0, M_ = 0, NS_ = 0, nrx_ = 0;
362 std::vector<bool> is_station_;
363 std::vector<std::size_t> to_station_;
364 std::vector<SchedStrategy> sched_;
365 std::vector<double> mi_;
366 std::vector<std::vector<double>> rate_;
370 std::vector<std::size_t> rx_node_, rx_class_, rx_from_, rx_det_dest_;
371 std::vector<std::vector<std::size_t>> rx_to_;
372 std::vector<std::vector<double>> rx_cdf_;
373 std::vector<std::size_t> rx_nnzp_;
374 std::vector<std::vector<std::size_t>> D_;
380 void build_reactions();
381 void build_dependencies();
382 void build_initial_state();
384 double class_pop(
const std::vector<double>& X, std::size_t ind, std::size_t r)
const {
385 return X[ind * K_ + r];
387 double node_pop(
const std::vector<double>& X, std::size_t ind)
const {
389 for (std::size_t r = 0; r < K_; ++r) s += X[ind * K_ + r];
392 double propensity(std::size_t j,
const NrmSpaceState& s)
const;
393 void update_buffers(std::size_t kfire,
const std::vector<double>& n,
394 std::vector<std::vector<std::size_t>>& bufs, std::size_t dest_pos,
395 bool have_dest)
const;
397 static constexpr std::size_t npos =
static_cast<std::size_t
>(-1);
408void NrmSpaceEngine<T>::check() {
409 for (std::size_t i = 0; i < sn_.
nstations; ++i) {
411 if (s == SchedStrategy::INF || s == SchedStrategy::EXT || s == SchedStrategy::PS ||
412 s == SchedStrategy::FCFS || s == SchedStrategy::LCFS)
415 "solver_ssa_nrm_space: station '" + sn_.
stations[i].name +
"' uses '" +
417 "' scheduling, which has no rate law in the explicit state-space variant. Its "
418 "propensity switch covers EXT, INF, PS, FCFS and LCFS only; use method='nrm', whose "
419 "engine carries the full set");
421 for (std::size_t i = 0; i < sn_.
nstations; ++i)
422 for (std::size_t r = 0; r < sn_.
nclasses; ++r) {
429 "solver_ssa_nrm_space: class '" + sn_.
classes[r].name +
430 "' has non-exponential service at station '" + sn_.
stations[i].name +
431 "'. The explicit state-space variant carries no phase dimension -- its state is "
432 "the (node, class) population vector and it reads sn.rates alone -- so a "
433 "phase-type process cannot be represented; use method='nrm', which expands it");
435 for (std::size_t r = 0; r < sn_.
nclasses; ++r)
436 if (!std::isfinite(sn_.
classes[r].population))
438 "solver_ssa_nrm_space: class '" + sn_.
classes[r].name +
439 "' is open. In the aggregate reaction network a Sink has no outgoing routing and "
440 "a Source's slot is a fictitious token that every arrival consumes, so the "
441 "aggregate state space of an open model is unbounded: every firing would add a "
442 "row to the table and pi would become the uniform law over the trace. Use "
443 "method='nrm', which integrates the metrics and stores no states");
446 for (std::size_t i = 0; i < sn_.
nstations; ++i)
449 "solver_ssa_nrm_space: station '" + sn_.
stations[i].name +
450 "' declares a class- or joint-dependent scaling. The explicit state-space "
451 "variant builds its propensities from sn.rates without evaluating the handle; "
452 "use method='nrm', whose engine applies it");
455 "solver_ssa_nrm_space: the model declares a finite capacity region. The explicit "
456 "state-space variant carries no region gate and no WAITQ FIFO; use method='nrm', "
457 "whose engine carries both");
458 for (
const qn::NodeDef& nd : sn_.
nodes) {
459 switch (nd.nodetype) {
462 "solver_ssa_nrm_space: node '" + nd.name +
463 "' is a Cache. The aggregate reaction network is built from sn.rtnodes and "
464 "sn.rates alone and carries no cache contents, so the hit/miss class switch "
465 "cannot be resolved at firing time");
469 "solver_ssa_nrm_space: node '" + nd.name +
470 "' makes this model a stochastic Petri net. A firing is atomic across every "
471 "arc it touches and is not a (node, class) departure, which is the only "
472 "reaction shape this variant builds");
476 "solver_ssa_nrm_space: node '" + nd.name +
477 "' makes this a fork-join model. A fork emits on several branches at once, "
478 "which no single-destination reaction can express, and the NRM does not "
479 "handle fork-join in any codebase");
490void NrmSpaceEngine<T>::build_layout() {
496 is_station_.assign(I_,
false);
497 to_station_.assign(I_, npos);
498 for (std::size_t i = 0; i < I_; ++i)
499 if (sn_.
nodes[i].station != 0) {
500 is_station_[i] =
true;
501 to_station_[i] = sn_.
nodes[i].station - 1;
503 sched_.assign(M_, SchedStrategy::FCFS);
504 for (std::size_t i = 0; i < M_; ++i) sched_[i] = sn_.
stations[i].sched;
510 rate_.assign(I_, std::vector<double>(K_, 0.0));
511 for (std::size_t i = 0; i < I_; ++i) {
512 if (is_station_[i]) {
513 const std::size_t ist = to_station_[i];
514 mi_[i] = sn_.
stations[ist].nservers;
516 for (std::size_t r = 0; r < K_; ++r)
518 rate_[i][r] = num_traits<T>::to_double(sn_.
rates(ist, r));
535void NrmSpaceEngine<T>::build_reactions() {
537 rx_node_.assign(nrx_, 0);
538 rx_class_.assign(nrx_, 0);
539 rx_from_.assign(nrx_, 0);
540 rx_det_dest_.assign(nrx_, npos);
541 rx_to_.assign(nrx_, std::vector<std::size_t>());
542 rx_cdf_.assign(nrx_, std::vector<double>());
543 rx_nnzp_.assign(nrx_, 0);
544 S_ = Matrix<double>(NS_, nrx_, 0.0);
546 for (std::size_t ind = 0; ind < I_; ++ind)
547 for (std::size_t r = 0; r < K_; ++r) {
548 const std::size_t k = ind * K_ + r;
553 for (std::size_t jnd = 0; jnd < I_; ++jnd)
554 for (std::size_t s = 0; s < K_; ++s) {
556 num_traits<T>::to_double(sn_.
rtnodes(ind * K_ + r, jnd * K_ + s));
557 if (!(p > 0.0))
continue;
558 S_(jnd * K_ + s, k) += p;
565 for (std::size_t k = 0; k < nrx_; ++k) {
566 std::vector<double> Pcol(NS_, 0.0);
567 for (std::size_t i = 0; i < NS_; ++i) {
568 const double v = S_(i, k);
569 Pcol[i] = v < 0.0 ? v + 1.0 : v;
570 if (v > 0.0 && rx_det_dest_[k] == npos) rx_det_dest_[k] = i;
572 for (std::size_t i = 0; i < NS_; ++i)
573 if (Pcol[i] != 0.0) ++rx_nnzp_[k];
581 if (rx_nnzp_[k] == 1 && rx_det_dest_[k] == npos && Pcol[rx_from_[k]] != 0.0)
582 rx_det_dest_[k] = rx_from_[k];
583 if (rx_nnzp_[k] > 1) {
585 for (std::size_t i = 0; i < NS_; ++i)
586 if (Pcol[i] != 0.0) {
587 rx_to_[k].push_back(i);
589 rx_cdf_[k].push_back(acc);
603void NrmSpaceEngine<T>::build_dependencies() {
604 D_.assign(nrx_, std::vector<std::size_t>());
605 for (std::size_t k = 0; k < nrx_; ++k) {
606 std::vector<bool> touched(I_,
false);
607 for (std::size_t i = 0; i < NS_; ++i)
608 if (S_(i, k) != 0.0) touched[i / K_] =
true;
609 for (std::size_t ind = 0; ind < I_; ++ind) {
610 if (!touched[ind])
continue;
611 for (std::size_t r = 0; r < K_; ++r) D_[k].push_back(ind * K_ + r);
613 std::sort(D_[k].begin(), D_[k].end());
614 D_[k].erase(std::unique(D_[k].begin(), D_[k].end()), D_[k].end());
628void NrmSpaceEngine<T>::build_initial_state() {
629 init_.
n.assign(NS_, 0.0);
630 init_.
buf.assign(I_, std::vector<std::size_t>());
631 for (std::size_t r = 0; r < K_; ++r) {
632 const double pop = sn_.
classes[r].population;
633 if (!(pop > 0.0))
continue;
634 const std::size_t rs = sn_.
classes[r].refstat;
635 if (rs < 1 || rs > M_)
637 "' has no reference station");
643 for (std::size_t ind = 0; ind < I_; ++ind) {
644 if (!is_station_[ind] || !space_detail::space_sched_buffered(sched_[to_station_[ind]]))
646 double waiting = std::max(0.0, node_pop(init_.
n, ind) - mi_[ind]);
647 for (std::size_t r = 0; r < K_ && waiting > 0.0; ++r) {
648 const double take = std::min(waiting, init_.
n[ind * K_ + r]);
649 for (std::size_t c = 0; c < static_cast<std::size_t>(take); ++c)
650 init_.
buf[ind].push_back(r);
658double NrmSpaceEngine<T>::propensity(std::size_t j,
const NrmSpaceState& st)
const {
659 const std::size_t ind = rx_node_[j], r = rx_class_[j];
660 const std::vector<double>& X = st.n;
661 if (!is_station_[ind]) {
665 return rate_[ind][r] * std::min(1.0, class_pop(X, ind, r));
667 const std::size_t ist = to_station_[ind];
669 switch (sched_[ist]) {
670 case SchedStrategy::EXT:
671 return rate_[ind][r];
672 case SchedStrategy::INF:
673 return rate_[ind][r] * class_pop(X, ind, r);
674 case SchedStrategy::PS: {
675 if (K_ == 1)
return rate_[ind][r] * std::min(mi_[ind], class_pop(X, ind, r));
676 const double tot = node_pop(X, ind);
677 return rate_[ind][r] * (class_pop(X, ind, r) / (eps + tot)) *
678 std::min(mi_[ind], eps + tot);
680 case SchedStrategy::FCFS:
681 case SchedStrategy::LCFS: {
685 double waiting = 0.0;
686 for (std::size_t c : st.buf[ind])
687 if (c == r) waiting += 1.0;
688 return rate_[ind][r] * std::max(0.0, class_pop(X, ind, r) - waiting);
692 "solver_ssa_nrm_space: the scheduling policy '" +
694 sn_.
stations[ist].name +
"' has no rate law in the explicit state-space variant");
708void NrmSpaceEngine<T>::update_buffers(std::size_t kfire,
const std::vector<double>& n,
709 std::vector<std::vector<std::size_t>>& bufs,
710 std::size_t dest_pos,
bool have_dest)
const {
711 const std::size_t ind = rx_node_[kfire];
712 if (is_station_[ind] && !bufs[ind].empty()) {
714 if (s == SchedStrategy::FCFS) bufs[ind].pop_back();
715 else if (s == SchedStrategy::LCFS) bufs[ind].erase(bufs[ind].begin());
717 if (!have_dest || dest_pos == npos)
return;
718 const std::size_t jnd = dest_pos / K_, s = dest_pos % K_;
719 if (!is_station_[jnd] || !space_detail::space_sched_buffered(sched_[to_station_[jnd]]))
return;
720 if (node_pop(n, jnd) > mi_[jnd]) bufs[jnd].insert(bufs[jnd].begin(), s);
723namespace space_detail {
726inline std::vector<double> space_key(
const NrmSpaceState& s) {
727 std::vector<double> key = s.n;
728 for (std::size_t i = 0; i < s.buf.size(); ++i) {
732 for (std::size_t j = 0; j < s.buf[i].size(); ++j)
733 key.push_back(
static_cast<double>(s.buf[i][j]) + 1.0);
742 std::vector<NrmSpaceState> out;
743 std::map<std::vector<double>, std::size_t> seen;
744 std::vector<std::size_t> stack;
745 seen[space_detail::space_key(init_)] = 0;
746 out.push_back(init_);
749 while (!stack.empty()) {
750 const std::size_t si = stack.back();
753 for (std::size_t k = 0; k < nrx_; ++k) {
754 if (!(propensity(k, st) > 0.0))
continue;
758 std::vector<std::size_t> dests;
759 if (rx_nnzp_[k] > 1) dests = rx_to_[k];
760 else if (rx_det_dest_[k] != npos) dests.push_back(rx_det_dest_[k]);
761 else dests.push_back(npos);
762 for (std::size_t d = 0; d < dests.size(); ++d) {
764 ns.
n[rx_from_[k]] -= 1.0;
765 if (dests[d] != npos) ns.
n[dests[d]] += 1.0;
766 update_buffers(k, ns.
n, ns.
buf, dests[d], dests[d] != npos);
767 const std::vector<double> key = space_detail::space_key(ns);
768 if (seen.find(key) != seen.end())
continue;
769 if (out.size() >= opt_.state_max)
771 "solver_ssa_nrm_space: the reachable aggregate state space exceeds the "
772 "cap of " + std::to_string(opt_.state_max) +
773 " states. The explicit state-space variant tabulates one row per state, "
774 "so a larger space is refused rather than truncated: a pi normalized over "
775 "the states that happened to fit is the law of a different chain. Raise "
776 "state_max, or use method='nrm', which stores no states");
777 seen[key] = out.size();
779 stack.push_back(out.size() - 1);
793 "solver_ssa_nrm_space: the Next Reaction Method draws its clocks as -log(u), "
794 "which needs transcendental arithmetic");
797 out.
seed = opt_.seed;
798 if (nrx_ == 0)
return out;
801 std::vector<double> Ak(nrx_, 0.0), Pk(nrx_, 0.0), Tk(nrx_, 0.0), tau(nrx_, 0.0);
802 for (std::size_t k = 0; k < nrx_; ++k) {
803 Ak[k] = propensity(k, cur);
804 Pk[k] = -std::log(rng_.uniform());
805 tau[k] = Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : std::numeric_limits<double>::infinity();
810 std::map<std::vector<double>, std::size_t> index;
811 std::vector<std::vector<double>> cached;
812 double total_time = 0.0;
815 out.
tran_rx.reserve(opt_.samples);
816 for (std::size_t n = 0; n < opt_.samples; ++n) {
817 std::size_t kfire = 0;
818 double dt = std::numeric_limits<double>::infinity();
819 for (std::size_t k = 0; k < nrx_; ++k)
826 "solver_ssa_nrm_space: deadlock -- every reaction has propensity zero, so the "
827 "sample path cannot advance");
831 const std::vector<double> key = space_detail::space_key(cur);
832 const std::map<std::vector<double>, std::size_t>::const_iterator it = index.find(key);
834 if (it != index.end()) {
837 if (out.
space.size() >= opt_.state_max)
839 "solver_ssa_nrm_space: the path has visited more than the cap of " +
840 std::to_string(opt_.state_max) +
841 " distinct states. The explicit state-space variant tabulates one row per "
842 "state, so it is refused rather than truncated: a pi normalized over the "
843 "states that happened to fit is the law of a different chain. Raise "
844 "state_max, or use method='nrm', which stores no states");
845 si = out.
space.size();
847 out.
space.push_back(cur);
848 out.
pi.push_back(0.0);
849 cached.push_back(Ak);
857 std::size_t dest = npos;
858 if (rx_nnzp_[kfire] > 1) {
861 const double u = rng_.uniform();
862 std::size_t sel = rx_cdf_[kfire].size() - 1;
863 for (std::size_t i = 0; i < rx_cdf_[kfire].size(); ++i)
864 if (rx_cdf_[kfire][i] > u) {
868 dest = rx_to_[kfire][sel];
869 cur.
n[rx_from_[kfire]] -= 1.0;
872 for (std::size_t i = 0; i < NS_; ++i)
873 if (S_(i, kfire) != 0.0) cur.
n[i] += S_(i, kfire);
874 dest = rx_det_dest_[kfire];
876 update_buffers(kfire, cur.
n, cur.
buf, dest, dest != npos);
878 for (std::size_t k = 0; k < nrx_; ++k) Tk[k] += Ak[k] * dt;
887 for (std::size_t k : D_[kfire]) Ak[k] = propensity(k, cur);
888 if (is_station_[rx_node_[kfire]] &&
889 space_detail::space_sched_buffered(sched_[to_station_[rx_node_[kfire]]]))
890 for (std::size_t k = 0; k < nrx_; ++k) Ak[k] = propensity(k, cur);
892 Pk[kfire] -= std::log(rng_.uniform());
893 for (std::size_t k = 0; k < nrx_; ++k)
895 Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : std::numeric_limits<double>::infinity();
900 for (std::size_t s = 0; s < out.
pi.size(); ++s) tot += out.
pi[s];
902 for (std::size_t s = 0; s < out.
pi.size(); ++s) out.
pi[s] /= tot;
908 for (std::size_t s = 0; s < out.
space.size(); ++s)
909 for (std::size_t k = 0; k < nrx_; ++k)
910 out.
dep_rates(s, rx_from_[k]) += cached[s][k];
949 if constexpr (!std::is_same<T, double>::value) {
953 "solver_ssa_nrm_space: an SSA sample path is generated from exponential clocks, which "
954 "are logarithms of uniform draws; there is no exact value to compute and a wider "
955 "float carries no information the Monte Carlo error does not swamp. Rerun with "
959 const std::size_t M =
sn.nstations, K =
sn.nclasses;
978 for (std::size_t k = 0; k < K; ++k) {
979 const std::size_t refnd =
sn.station_to_node[
sn.classes[k].refstat - 1];
980 for (std::size_t s = 0; s < r.
space.size(); ++s)
984 for (std::size_t ist = 0; ist < M; ++ist) {
985 const std::size_t ind =
sn.station_to_node[ist];
986 for (std::size_t k = 0; k < K; ++k)
987 for (std::size_t s = 0; s < r.
space.size(); ++s) {
989 a.
QN(ist, k) += r.
pi[s] * r.
space[s].n[(ind - 1) * K + k];
992 const SchedStrategy sched =
sn.stations[ist].sched;
993 if (sched == SchedStrategy::EXT ||
995 for (std::size_t k = 0; k < K; ++k) a.
QN(ist, k) = 0.0;
998 if (sched == SchedStrategy::INF) {
999 for (std::size_t k = 0; k < K; ++k) a.
UN(ist, k) = a.
QN(ist, k);
1005 const bool is_cd =
static_cast<bool>(
sn.stations[ist].cdscaling);
1006 const bool is_jd =
static_cast<bool>(
sn.stations[ist].jdscaling);
1007 for (std::size_t k = 0; k < K; ++k) {
1008 if (
sn.disabled[ist][k])
continue;
1010 if (!(mu > 0))
continue;
1014 double sdiv =
sn.stations[ist].nservers;
1015 if (is_cd || is_jd) {
1017 const std::vector<T>* pks[2] = {&
sn.stations[ist].cdscalingpeak,
1018 &
sn.stations[ist].jdscalingpeak};
1019 const char* names[2] = {
"setClassDependence",
"setJointDependence"};
1020 const bool on[2] = {is_cd, is_jd};
1021 for (std::size_t h = 0; h < 2; ++h) {
1022 if (!on[h])
continue;
1023 const std::vector<T>& pk = *pks[h];
1026 "solver_ssa_nrm_space: station '" +
sn.stations[ist].name +
1027 "' declares a dependent scaling with no declared peak rate. "
1028 "Utilization there is T/mu/peak, so pass the peak to " + names[h]);
1032 a.
UN(ist, k) = sdiv > 0 ? a.
TN(ist, k) / mu / sdiv : 0.0;
1036 for (std::size_t k = 0; k < K; ++k) {
1037 for (std::size_t ist = 0; ist < M; ++ist)
1038 a.
RN(ist, k) = a.
TN(ist, k) > 0 ? a.
QN(ist, k) / a.
TN(ist, k) : 0.0;
1039 if (a.
XN[k] > 0) a.
CN[k] =
sn.classes[k].population / a.
XN[k];
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::size_t nof_nodes() const
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< std::vector< bool > > disabled
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< NodeDef > nodes
every node, in creation order
std::vector< std::size_t > station_to_node
(nstations) 1-based node index
std::vector< Region > regions
The aggregate NRM engine of solver_ssa_nrm_space.m.
NrmSpaceEngine(const qn::NetworkStruct< T > &sn, const SsaNrmSpaceOptions &opt)
std::vector< double > propensities(const NrmSpaceState &s) const
The whole propensity vector at a state, the reference's reactCache entry.
SsaNrmSpaceRun< T > run()
Run opt.samples firings and return the tabulated path.
std::vector< NrmSpaceState > enumerate_space() const
The reachable aggregate state space, closed forward from the initial state over the REACTIONS alone.
std::size_t nreactions() const
std::size_t nslots() const
const NrmSpaceState & initial() const
The uniform source, MATLAB's rand.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Dense matrix and non-owning view.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
@ POST
produce to a place or queue buffer
@ PRE
consume from a place or queue buffer, no server effect
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
const char * sched_to_text(SchedStrategy s)
NodeType
Node kinds, with the values of MATLAB NodeType.
SchedStrategy
The three scheduling disciplines the AMVA and Schmidt recursions branch on.
GlobalOutcome< T > after_global_event(const NetworkStruct< T > &sn, const NetState< T > &glspace, const GlobalSync< T > &gl)
Port of State.afterGlobalEvent: an SPN mode ENABLEs or FIREs.
EventOutcome< T > after_event(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< T > &inspace, EventType event, std::size_t cls, bool no_promote=false, const T &aux_rate=num_traits< T >::from_int(0))
Port of State.afterEvent: the successors of one event at one NODE.
std::vector< EnabledEvent< T > > ssa_find_enabled(const qn::NetworkStruct< T > &sn, const std::vector< qn::Sync< T > > &sync, const std::vector< qn::GlobalSync< T > > &gsync, const qn::NetState< T > &state, std::vector< std::vector< double > > *arv=nullptr, std::vector< std::vector< double > > *dep=nullptr)
Port of solver_ssa_findenabled.m: every synchronization that can fire in state, with the arrival and ...
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...
SsaNrmSpaceRun< T > solver_ssa_nrm_space(const qn::NetworkStruct< T > &sn, const SsaNrmSpaceOptions &opt)
solver_ssa_nrm_space.m: run the tabulating engine.
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
Controls, results and the random source of SolverSSA.
Port of the MATLAB +State package: the encoding that turns a station's state row into marginal job co...
Port of the event half of MATLAB's +State package: the successor states an event produces at one node...
static constexpr double Immediate
Rate of an Immediate distribution; its mean is 1/Immediate = 1e-8.
static constexpr double Zero
static constexpr double MaxInt
Stand-in for an unbounded COUNT, MATLAB GlobalConstants.MaxInt.
What one event produces at one node: the successor rows, their rates and their probabilities,...
std::vector< T > prob
per-row probability of the choice
std::vector< std::vector< T > > space
successor local state rows
std::vector< T > rate
per-row rate, -1 on a passive half
What one global event produces: a whole network state per outcome.
std::vector< NetState< T > > space
A GLOBAL synchronization: an SPN mode event and the place arcs it drives.
One half of a GLOBAL synchronization: a mode event at a node.
std::size_t node
1-based node index (a Transition, or a place)
std::size_t cls
1-based class the arc moves
One network state: the per-stateful-node local rows it is composed of.
std::vector< std::vector< T > > local
local[isf] is that node's state row
One synchronization: an ACTIVE event and the PASSIVE event it drives.
One transition the enabled scan found: where it goes, and at what rate.
qn::NetState< T > next
enabled_next_states{act}: the whole network state after it fires.
double rate
enabled_rates: rate * p_active * p_route * p_passive.
std::size_t sync
enabled_sync: index into sync, or sync.size() + g for a global one.
One state of the aggregate chain: the (node, class) populations, and the ordered buffer contents of e...
std::vector< std::vector< std::size_t > > buf
The knobs the space variant reads.
What solver_ssa_nrm_space.m returns, plus what makes it a measurement.
std::vector< double > tran_time
t: the cumulative time at each firing.
std::vector< double > pi
pi: the fraction of simulated time spent in each of them.
std::vector< std::size_t > tran_rx
kfires: which reaction fired.
Matrix< double > dep_rates
depRates, (states x nnodes*nclasses): the departure rate of each (node, class) in each state,...
std::vector< NrmSpaceState > space
outspace: the distinct states visited, in first-visit order.
The metric table, the table it came from, and the stream that produced it.
Controls, defaulting to SolverOptions('SSA') in the reference.
What the analyzer returns, in the same shape as the MVA and fluid results.
double simulated_time
Simulated time the metrics are averaged over; the reference's totalTime.
std::size_t samples
Reaction firings actually performed.
std::string method
The concrete algorithm, as the reference's method.