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");
444 for (
const qn::NodeDef& nd : sn_.
nodes) {
445 switch (nd.nodetype) {
448 "solver_ssa_nrm_space: node '" + nd.name +
449 "' is a Cache. The aggregate reaction network is built from sn.rtnodes and "
450 "sn.rates alone and carries no cache contents, so the hit/miss class switch "
451 "cannot be resolved at firing time");
455 "solver_ssa_nrm_space: node '" + nd.name +
456 "' makes this model a stochastic Petri net. A firing is atomic across every "
457 "arc it touches and is not a (node, class) departure, which is the only "
458 "reaction shape this variant builds");
462 "solver_ssa_nrm_space: node '" + nd.name +
463 "' makes this a fork-join model. A fork emits on several branches at once, "
464 "which no single-destination reaction can express, and the NRM does not "
465 "handle fork-join in any codebase");
476void NrmSpaceEngine<T>::build_layout() {
482 is_station_.assign(I_,
false);
483 to_station_.assign(I_, npos);
484 for (std::size_t i = 0; i < I_; ++i)
485 if (sn_.
nodes[i].station != 0) {
486 is_station_[i] =
true;
487 to_station_[i] = sn_.
nodes[i].station - 1;
489 sched_.assign(M_, SchedStrategy::FCFS);
490 for (std::size_t i = 0; i < M_; ++i) sched_[i] = sn_.
stations[i].sched;
496 rate_.assign(I_, std::vector<double>(K_, 0.0));
497 for (std::size_t i = 0; i < I_; ++i) {
498 if (is_station_[i]) {
499 const std::size_t ist = to_station_[i];
500 mi_[i] = sn_.
stations[ist].nservers;
502 for (std::size_t r = 0; r < K_; ++r)
504 rate_[i][r] = num_traits<T>::to_double(sn_.
rates(ist, r));
521void NrmSpaceEngine<T>::build_reactions() {
523 rx_node_.assign(nrx_, 0);
524 rx_class_.assign(nrx_, 0);
525 rx_from_.assign(nrx_, 0);
526 rx_det_dest_.assign(nrx_, npos);
527 rx_to_.assign(nrx_, std::vector<std::size_t>());
528 rx_cdf_.assign(nrx_, std::vector<double>());
529 rx_nnzp_.assign(nrx_, 0);
530 S_ = Matrix<double>(NS_, nrx_, 0.0);
532 for (std::size_t ind = 0; ind < I_; ++ind)
533 for (std::size_t r = 0; r < K_; ++r) {
534 const std::size_t k = ind * K_ + r;
539 for (std::size_t jnd = 0; jnd < I_; ++jnd)
540 for (std::size_t s = 0; s < K_; ++s) {
542 num_traits<T>::to_double(sn_.
rtnodes(ind * K_ + r, jnd * K_ + s));
543 if (!(p > 0.0))
continue;
544 S_(jnd * K_ + s, k) += p;
551 for (std::size_t k = 0; k < nrx_; ++k) {
552 std::vector<double> Pcol(NS_, 0.0);
553 for (std::size_t i = 0; i < NS_; ++i) {
554 const double v = S_(i, k);
555 Pcol[i] = v < 0.0 ? v + 1.0 : v;
556 if (v > 0.0 && rx_det_dest_[k] == npos) rx_det_dest_[k] = i;
558 for (std::size_t i = 0; i < NS_; ++i)
559 if (Pcol[i] != 0.0) ++rx_nnzp_[k];
567 if (rx_nnzp_[k] == 1 && rx_det_dest_[k] == npos && Pcol[rx_from_[k]] != 0.0)
568 rx_det_dest_[k] = rx_from_[k];
569 if (rx_nnzp_[k] > 1) {
571 for (std::size_t i = 0; i < NS_; ++i)
572 if (Pcol[i] != 0.0) {
573 rx_to_[k].push_back(i);
575 rx_cdf_[k].push_back(acc);
589void NrmSpaceEngine<T>::build_dependencies() {
590 D_.assign(nrx_, std::vector<std::size_t>());
591 for (std::size_t k = 0; k < nrx_; ++k) {
592 std::vector<bool> touched(I_,
false);
593 for (std::size_t i = 0; i < NS_; ++i)
594 if (S_(i, k) != 0.0) touched[i / K_] =
true;
595 for (std::size_t ind = 0; ind < I_; ++ind) {
596 if (!touched[ind])
continue;
597 for (std::size_t r = 0; r < K_; ++r) D_[k].push_back(ind * K_ + r);
599 std::sort(D_[k].begin(), D_[k].end());
600 D_[k].erase(std::unique(D_[k].begin(), D_[k].end()), D_[k].end());
614void NrmSpaceEngine<T>::build_initial_state() {
615 init_.
n.assign(NS_, 0.0);
616 init_.
buf.assign(I_, std::vector<std::size_t>());
617 for (std::size_t r = 0; r < K_; ++r) {
618 const double pop = sn_.
classes[r].population;
619 if (!(pop > 0.0))
continue;
620 const std::size_t rs = sn_.
classes[r].refstat;
621 if (rs < 1 || rs > M_)
623 "' has no reference station");
629 for (std::size_t ind = 0; ind < I_; ++ind) {
630 if (!is_station_[ind] || !space_detail::space_sched_buffered(sched_[to_station_[ind]]))
632 double waiting = std::max(0.0, node_pop(init_.
n, ind) - mi_[ind]);
633 for (std::size_t r = 0; r < K_ && waiting > 0.0; ++r) {
634 const double take = std::min(waiting, init_.
n[ind * K_ + r]);
635 for (std::size_t c = 0; c < static_cast<std::size_t>(take); ++c)
636 init_.
buf[ind].push_back(r);
644double NrmSpaceEngine<T>::propensity(std::size_t j,
const NrmSpaceState& st)
const {
645 const std::size_t ind = rx_node_[j], r = rx_class_[j];
646 const std::vector<double>& X = st.n;
647 if (!is_station_[ind]) {
651 return rate_[ind][r] * std::min(1.0, class_pop(X, ind, r));
653 const std::size_t ist = to_station_[ind];
655 switch (sched_[ist]) {
656 case SchedStrategy::EXT:
657 return rate_[ind][r];
658 case SchedStrategy::INF:
659 return rate_[ind][r] * class_pop(X, ind, r);
660 case SchedStrategy::PS: {
661 if (K_ == 1)
return rate_[ind][r] * std::min(mi_[ind], class_pop(X, ind, r));
662 const double tot = node_pop(X, ind);
663 return rate_[ind][r] * (class_pop(X, ind, r) / (eps + tot)) *
664 std::min(mi_[ind], eps + tot);
666 case SchedStrategy::FCFS:
667 case SchedStrategy::LCFS: {
671 double waiting = 0.0;
672 for (std::size_t c : st.buf[ind])
673 if (c == r) waiting += 1.0;
674 return rate_[ind][r] * std::max(0.0, class_pop(X, ind, r) - waiting);
678 "solver_ssa_nrm_space: the scheduling policy '" +
680 sn_.
stations[ist].name +
"' has no rate law in the explicit state-space variant");
694void NrmSpaceEngine<T>::update_buffers(std::size_t kfire,
const std::vector<double>& n,
695 std::vector<std::vector<std::size_t>>& bufs,
696 std::size_t dest_pos,
bool have_dest)
const {
697 const std::size_t ind = rx_node_[kfire];
698 if (is_station_[ind] && !bufs[ind].empty()) {
700 if (s == SchedStrategy::FCFS) bufs[ind].pop_back();
701 else if (s == SchedStrategy::LCFS) bufs[ind].erase(bufs[ind].begin());
703 if (!have_dest || dest_pos == npos)
return;
704 const std::size_t jnd = dest_pos / K_, s = dest_pos % K_;
705 if (!is_station_[jnd] || !space_detail::space_sched_buffered(sched_[to_station_[jnd]]))
return;
706 if (node_pop(n, jnd) > mi_[jnd]) bufs[jnd].insert(bufs[jnd].begin(), s);
709namespace space_detail {
712inline std::vector<double> space_key(
const NrmSpaceState& s) {
713 std::vector<double> key = s.n;
714 for (std::size_t i = 0; i < s.buf.size(); ++i) {
718 for (std::size_t j = 0; j < s.buf[i].size(); ++j)
719 key.push_back(
static_cast<double>(s.buf[i][j]) + 1.0);
728 std::vector<NrmSpaceState> out;
729 std::map<std::vector<double>, std::size_t> seen;
730 std::vector<std::size_t> stack;
731 seen[space_detail::space_key(init_)] = 0;
732 out.push_back(init_);
735 while (!stack.empty()) {
736 const std::size_t si = stack.back();
739 for (std::size_t k = 0; k < nrx_; ++k) {
740 if (!(propensity(k, st) > 0.0))
continue;
744 std::vector<std::size_t> dests;
745 if (rx_nnzp_[k] > 1) dests = rx_to_[k];
746 else if (rx_det_dest_[k] != npos) dests.push_back(rx_det_dest_[k]);
747 else dests.push_back(npos);
748 for (std::size_t d = 0; d < dests.size(); ++d) {
750 ns.
n[rx_from_[k]] -= 1.0;
751 if (dests[d] != npos) ns.
n[dests[d]] += 1.0;
752 update_buffers(k, ns.
n, ns.
buf, dests[d], dests[d] != npos);
753 const std::vector<double> key = space_detail::space_key(ns);
754 if (seen.find(key) != seen.end())
continue;
755 if (out.size() >= opt_.state_max)
757 "solver_ssa_nrm_space: the reachable aggregate state space exceeds the "
758 "cap of " + std::to_string(opt_.state_max) +
759 " states. The explicit state-space variant tabulates one row per state, "
760 "so a larger space is refused rather than truncated: a pi normalized over "
761 "the states that happened to fit is the law of a different chain. Raise "
762 "state_max, or use method='nrm', which stores no states");
763 seen[key] = out.size();
765 stack.push_back(out.size() - 1);
779 "solver_ssa_nrm_space: the Next Reaction Method draws its clocks as -log(u), "
780 "which needs transcendental arithmetic");
783 out.
seed = opt_.seed;
784 if (nrx_ == 0)
return out;
787 std::vector<double> Ak(nrx_, 0.0), Pk(nrx_, 0.0), Tk(nrx_, 0.0), tau(nrx_, 0.0);
788 for (std::size_t k = 0; k < nrx_; ++k) {
789 Ak[k] = propensity(k, cur);
790 Pk[k] = -std::log(rng_.uniform());
791 tau[k] = Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : std::numeric_limits<double>::infinity();
796 std::map<std::vector<double>, std::size_t> index;
797 std::vector<std::vector<double>> cached;
798 double total_time = 0.0;
801 out.
tran_rx.reserve(opt_.samples);
802 for (std::size_t n = 0; n < opt_.samples; ++n) {
803 std::size_t kfire = 0;
804 double dt = std::numeric_limits<double>::infinity();
805 for (std::size_t k = 0; k < nrx_; ++k)
812 "solver_ssa_nrm_space: deadlock -- every reaction has propensity zero, so the "
813 "sample path cannot advance");
817 const std::vector<double> key = space_detail::space_key(cur);
818 const std::map<std::vector<double>, std::size_t>::const_iterator it = index.find(key);
820 if (it != index.end()) {
823 if (out.
space.size() >= opt_.state_max)
825 "solver_ssa_nrm_space: the path has visited more than the cap of " +
826 std::to_string(opt_.state_max) +
827 " distinct states. The explicit state-space variant tabulates one row per "
828 "state, so it is refused rather than truncated: a pi normalized over the "
829 "states that happened to fit is the law of a different chain. Raise "
830 "state_max, or use method='nrm', which stores no states");
831 si = out.
space.size();
833 out.
space.push_back(cur);
834 out.
pi.push_back(0.0);
835 cached.push_back(Ak);
843 std::size_t dest = npos;
844 if (rx_nnzp_[kfire] > 1) {
847 const double u = rng_.uniform();
848 std::size_t sel = rx_cdf_[kfire].size() - 1;
849 for (std::size_t i = 0; i < rx_cdf_[kfire].size(); ++i)
850 if (rx_cdf_[kfire][i] > u) {
854 dest = rx_to_[kfire][sel];
855 cur.
n[rx_from_[kfire]] -= 1.0;
858 for (std::size_t i = 0; i < NS_; ++i)
859 if (S_(i, kfire) != 0.0) cur.
n[i] += S_(i, kfire);
860 dest = rx_det_dest_[kfire];
862 update_buffers(kfire, cur.
n, cur.
buf, dest, dest != npos);
864 for (std::size_t k = 0; k < nrx_; ++k) Tk[k] += Ak[k] * dt;
873 for (std::size_t k : D_[kfire]) Ak[k] = propensity(k, cur);
874 if (is_station_[rx_node_[kfire]] &&
875 space_detail::space_sched_buffered(sched_[to_station_[rx_node_[kfire]]]))
876 for (std::size_t k = 0; k < nrx_; ++k) Ak[k] = propensity(k, cur);
878 Pk[kfire] -= std::log(rng_.uniform());
879 for (std::size_t k = 0; k < nrx_; ++k)
881 Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : std::numeric_limits<double>::infinity();
886 for (std::size_t s = 0; s < out.
pi.size(); ++s) tot += out.
pi[s];
888 for (std::size_t s = 0; s < out.
pi.size(); ++s) out.
pi[s] /= tot;
894 for (std::size_t s = 0; s < out.
space.size(); ++s)
895 for (std::size_t k = 0; k < nrx_; ++k)
896 out.
dep_rates(s, rx_from_[k]) += cached[s][k];
935 if constexpr (!std::is_same<T, double>::value) {
939 "solver_ssa_nrm_space: an SSA sample path is generated from exponential clocks, which "
940 "are logarithms of uniform draws; there is no exact value to compute and a wider "
941 "float carries no information the Monte Carlo error does not swamp. Rerun with "
945 const std::size_t M =
sn.nstations, K =
sn.nclasses;
964 for (std::size_t k = 0; k < K; ++k) {
965 const std::size_t refnd =
sn.station_to_node[
sn.classes[k].refstat - 1];
966 for (std::size_t s = 0; s < r.
space.size(); ++s)
970 for (std::size_t ist = 0; ist < M; ++ist) {
971 const std::size_t ind =
sn.station_to_node[ist];
972 for (std::size_t k = 0; k < K; ++k)
973 for (std::size_t s = 0; s < r.
space.size(); ++s) {
975 a.
QN(ist, k) += r.
pi[s] * r.
space[s].n[(ind - 1) * K + k];
978 const SchedStrategy sched =
sn.stations[ist].sched;
979 if (sched == SchedStrategy::EXT ||
981 for (std::size_t k = 0; k < K; ++k) a.
QN(ist, k) = 0.0;
984 if (sched == SchedStrategy::INF) {
985 for (std::size_t k = 0; k < K; ++k) a.
UN(ist, k) = a.
QN(ist, k);
991 const bool is_cd =
static_cast<bool>(
sn.stations[ist].cdscaling);
992 const bool is_jd =
static_cast<bool>(
sn.stations[ist].jdscaling);
993 for (std::size_t k = 0; k < K; ++k) {
994 if (
sn.disabled[ist][k])
continue;
996 if (!(mu > 0))
continue;
1000 double sdiv =
sn.stations[ist].nservers;
1001 if (is_cd || is_jd) {
1003 const std::vector<T>* pks[2] = {&
sn.stations[ist].cdscalingpeak,
1004 &
sn.stations[ist].jdscalingpeak};
1005 const char* names[2] = {
"setClassDependence",
"setJointDependence"};
1006 const bool on[2] = {is_cd, is_jd};
1007 for (std::size_t h = 0; h < 2; ++h) {
1008 if (!on[h])
continue;
1009 const std::vector<T>& pk = *pks[h];
1012 "solver_ssa_nrm_space: station '" +
sn.stations[ist].name +
1013 "' declares a dependent scaling with no declared peak rate. "
1014 "Utilization there is T/mu/peak, so pass the peak to " + names[h]);
1018 a.
UN(ist, k) = sdiv > 0 ? a.
TN(ist, k) / mu / sdiv : 0.0;
1022 for (std::size_t k = 0; k < K; ++k) {
1023 for (std::size_t ist = 0; ist < M; ++ist)
1024 a.
RN(ist, k) = a.
TN(ist, k) > 0 ? a.
QN(ist, k) / a.
TN(ist, k) : 0.0;
1025 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
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.
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.