5#ifndef LINE_SOLVERS_SSA_SOLVER_SSA_SERIAL_H
6#define LINE_SOLVERS_SSA_SOLVER_SSA_SERIAL_H
127 std::vector<std::vector<std::size_t>>
hash;
145 std::vector<std::vector<std::vector<std::size_t>>>
buf;
147 std::vector<double>
pi;
176 std::vector<std::vector<std::vector<double>>>
dly_rates;
246namespace serial_detail {
270 if (!
sn.regions.empty()) {
280 if (!raise)
return false;
290 if (!skip_fork &&
sn.has_fork() && !
sn.isfjaugmented) {
291 if (!raise)
return false;
293 "SolverSSA(method='serial'): the model contains a Fork node and has not been "
294 "tag-augmented. A fork fires through `sn.fjsync` / `State.afterFJEvent`, which only "
295 "`fj_tag` builds; call `solver_ssa_serial_analyzer`, which augments and folds the "
296 "sibling classes back, rather than the engine directly");
298 for (std::size_t i = 0; i <
sn.nstations; ++i) {
299 const typename std::map<std::size_t, qn::RetrialParam<T>>::const_iterator rit =
300 sn.retrialparam.find(i + 1);
301 if (rit ==
sn.retrialparam.end())
continue;
303 std::size_t served = 0;
304 for (std::size_t r = 0; r < rit->second.retrial_proc.size(); ++r) {
305 if (rit->second.retrial_proc[r].disabled)
continue;
308 if (!raise)
return false;
310 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
311 "' retries with non-exponential patience. SOLVER_SSA supports only "
312 "exponential (memoryless) retrial delay in every codebase, because the orbit "
313 "carries no remaining-delay phase");
315 if (r < rit->second.max_attempts.size() && rit->second.max_attempts[r] > 0) {
316 if (!raise)
return false;
318 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
319 "' declares a finite retrial max-attempts count. SOLVER_SSA supports only "
320 "unlimited retrials in every codebase, because the attempt counter is not "
321 "part of the state");
325 for (std::size_t r = 0; r <
sn.nclasses; ++r)
326 if (!
sn.disabled[i][r] &&
sn.service[i][r].D0.rows() > 0) ++served;
328 if (!raise)
return false;
330 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
331 "' is a multi-class retrial station. SOLVER_SSA supports retrial only for "
332 "single-class stations in every codebase");
349 return serial_check(
sn,
false,
true);
372std::size_t max_row_width(
const qn::NetworkStruct<T>& sn, std::size_t ind,
373 const std::vector<std::size_t>& cutoff) {
374 const std::size_t R = sn.nclasses;
375 const std::size_t ist = sn.nodes[ind - 1].station;
376 std::vector<std::size_t> ph(R, 1);
378 for (std::size_t r = 0; r < R; ++r) ph[r] = sn.phasessz_of(ist, r + 1);
386 throw UnsupportedError(
"SolverSSA(method='serial'): node '" + sn.nodes[ind - 1].name +
387 "' admits no state at all, so no sample path can start");
393 std::vector<std::size_t> bound(R, 0);
394 for (std::size_t r = 0; r < R; ++r) {
395 const double nj = sn.njobs()[r];
396 double b = std::isfinite(nj) ? nj :
static_cast<double>(cutoff[r]);
397 const double cc = sn.classcap[ist - 1][r];
400 bound[r] =
static_cast<std::size_t
>(b);
402 double tcapd = sn.cap[ist - 1];
403 std::size_t total = 0;
404 for (std::size_t r = 0; r < R; ++r) total += bound[r];
405 if (std::isfinite(tcapd) && tcapd <
static_cast<double>(total))
406 total =
static_cast<std::size_t
>(tcapd);
416 return total + sn.nvars_of(ind);
418 for (std::size_t t = total + 1; t-- > 0;) {
419 std::vector<std::size_t> n(R, 0);
420 std::size_t left = t;
421 for (std::size_t r = 0; r < R && left > 0; ++r) {
422 n[r] = std::min(left, bound[r]);
425 if (left > 0)
continue;
427 if (!rows.empty())
return rows[0].size();
429 throw UnsupportedError(
"SolverSSA(method='serial'): station '" + sn.stations[ist - 1].name +
430 "' admits no state at all, so no sample path can start");
444qn::NetState<T> wide_init_state(
const qn::NetworkStruct<T>& sn,
445 const std::vector<std::size_t>& cutoff) {
446 qn::NetState<T> init;
447 if (!ctmc::analyzer_detail::default_init_state(sn, init))
449 "SolverSSA(method='serial'): the model's initial state admits no state; check the "
450 "class populations against their reference stations");
451 for (std::size_t f = 0; f < sn.stateful_nodes.size(); ++f) {
452 const std::size_t w = max_row_width(sn, sn.stateful_nodes[f], cutoff);
453 if (init.local[f].size() > w)
continue;
454 init.local[f].insert(init.local[f].begin(), w - init.local[f].size(),
455 num_traits<T>::from_int(0));
481 serial_detail::serial_check(
sn);
485 const std::vector<std::size_t> cutoff = ctmc::analyzer_detail::resolve_cutoff(
sn, copt);
498 "solver_ssa_reachability: the model declares a WAITQ finite capacity region, whose "
499 "states carry a per-region token FIFO outside every node; this decomposition is "
500 "per-node and cannot represent it. Use `solver_ssa_serial_analyzer`, whose run carries "
501 "the FIFO, or `solver_ctmc_waitq` for the exact augmented space");
512 const std::size_t NF =
sn.stateful_nodes.size();
513 out.
node_space.assign(NF, std::vector<std::vector<T>>());
514 out.
hash.assign(out.
space.size(), std::vector<std::size_t>(NF, 0));
518 std::vector<std::map<std::vector<double>, std::size_t>> seen(NF);
519 for (std::size_t s = 0; s < out.
space.size(); ++s)
520 for (std::size_t f = 0; f < NF; ++f) {
521 std::vector<double> key(out.
space[s].local[f].size(), 0.0);
522 for (std::size_t j = 0; j < key.size(); ++j)
524 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
526 if (it != seen[f].end()) {
527 out.
hash[s][f] = it->second;
535 std::size_t width = 0;
536 for (std::size_t f = 0; f < NF; ++f)
537 width += out.
space.empty() ? 0 : out.
space[0].local[f].size();
539 for (std::size_t s = 0; s < out.
space.size(); ++s) {
541 for (std::size_t f = 0; f < NF; ++f)
542 for (std::size_t j = 0; j < out.
space[s].local[f].size(); ++j)
543 out.
ssq(s, col++) = out.
space[s].local[f][j];
567 : sn_(
sn), opt_(
opt), rng_(
opt.seed), fjsync_(fjsync) {
568 serial_detail::serial_check(
sn);
572 const std::vector<std::size_t> cutoff = ctmc::analyzer_detail::resolve_cutoff(
sn, copt);
575 sdr_ =
sn.has_sdr_routing();
584 if (!
sn.regions.empty()) {
586 caps_ = ctmc::waitq_detail::extract_caps(
sn);
587 ctmc::waitq_detail::resolve_lmax(
sn, cutoff, caps_);
590 init_.net = serial_detail::wide_init_state(
sn, cutoff);
591 init_.buf.assign(caps_.size(), std::vector<std::size_t>());
592 if (!
sn.regions.empty() && !region_admissible(init_.net))
594 "SolverSSA(method='serial'): the model's initial state violates a finite capacity "
595 "region; the region cannot hold the model's initial population, so no sample path "
603 const std::vector<qn::Sync<T>>&
sync()
const {
return sync_; }
610 std::size_t
sync = 0;
616 SsaSerialOptions opt_;
618 std::vector<qn::Sync<T>> sync_;
619 std::vector<qn::GlobalSync<T>> gsync_;
620 std::vector<qn::FjSync<T>> fjsync_;
621 std::vector<ctmc::waitq_detail::RegionCaps<T>> caps_;
629 if (sn_.
regions.empty())
return true;
630 std::vector<qn::NetState<T>> one(1, st);
632 std::vector<T> nir(A.
cols());
633 for (std::size_t c = 0; c < A.
cols(); ++c) nir[c] = A(0, c);
641 void enabled(
const ctmc::WaitqState<T>& st, std::vector<Move>& moves,
642 std::vector<std::vector<double>>& arv, std::vector<std::vector<double>>& dep,
643 std::vector<std::vector<double>>& dly,
644 std::vector<std::vector<double>>& start,
645 std::vector<std::vector<double>>& preempt)
const;
651 std::vector<T> gd_factor_now(
const qn::NetState<T>& ns)
const {
653 const T zero = num_traits<T>::from_int(0);
654 std::vector<T> npop(M * K, zero);
655 for (std::size_t ist = 1; ist <= M; ++ist) {
657 if (isf == 0)
continue;
660 std::vector<std::size_t> ph(K, 1), shift(K, 0);
662 for (std::size_t k = 0; k < K; ++k) {
667 const qn::Marginal<T> m =
668 qn::to_marginal(sn_, ist, ns.local[isf - 1], ph, shift, sn_.nvars_of(ind));
669 for (std::size_t k = 0; k < K; ++k) npop[(ist - 1) * K + k] = m.nir[k];
671 const std::vector<T> v = sn_.gdscaling(npop);
672 std::vector<T> out(M * K, num_traits<T>::from_int(1));
673 for (std::size_t i = 0; i < M; ++i)
674 for (std::size_t r = 0; r < K; ++r) {
675 const T f = v.size() == 1 ? v[0] : (v.size() == M ? v[i] : v[i * K + r]);
676 if (!(num_traits<T>::to_double(f) >= 0))
678 "the global dependence handle returned a non-finite or negative scaling");
686void SsaSerialEngine<T>::enabled(
const ctmc::WaitqState<T>& st, std::vector<Move>& moves,
687 std::vector<std::vector<double>>& arv,
688 std::vector<std::vector<double>>& dep,
689 std::vector<std::vector<double>>& dly,
690 std::vector<std::vector<double>>& start,
691 std::vector<std::vector<double>>& preempt)
const {
692 const std::size_t local = sn_.
nodes.size() + 1;
695 for (std::size_t f = 0; f < arv.size(); ++f)
696 for (std::size_t r = 0; r < R; ++r) {
713 std::vector<ctmc::waitq_detail::Successor<T>> succ;
714 ctmc::waitq_detail::waitq_successors(sn_, sync_, caps_, st, succ);
715 for (std::size_t i = 0; i < succ.size(); ++i) {
716 const ctmc::waitq_detail::Successor<T>& su = succ[i];
717 const double w = num_traits<T>::to_double(su.w);
718 if (!(w > 0))
continue;
724 if (su.dep_isf != 0) dep[su.dep_isf - 1][su.dep_cls - 1] += w;
725 for (std::size_t q = 0; q < su.arv.size(); ++q)
726 arv[su.arv[q].first - 1][su.arv[q].second - 1] += w;
731 const qn::NetState<T>& base = st.net;
737 std::vector<T> gd_now;
738 const bool has_gd =
static_cast<bool>(sn_.
gdscaling);
739 if (has_gd) gd_now = gd_factor_now(base);
746 for (std::size_t a = 0; a < sync_.size(); ++a) {
747 const qn::Sync<T>& sy = sync_[a];
749 if (isf_a == 0)
continue;
750 const std::size_t isf_p =
751 sy.passive.node == local ? 0 : sn_.
stateful_index(sy.passive.node);
752 if (sy.passive.node != local && isf_p == 0)
continue;
754 const qn::EventOutcome<T> oa =
qn::after_event(sn_, sy.active.node, base.local[isf_a - 1],
755 sy.active.event, sy.active.cls);
757 const bool gd_here = has_gd && sn_.
nodes[sy.active.node - 1].station != 0 &&
761 gd_here ? num_traits<T>::to_double(
762 gd_now[(sn_.
nodes[sy.active.node - 1].station - 1) * R +
763 (sy.active.cls - 1)])
771 double srv_pre = 0.0;
773 for (std::size_t r = 0; r < R && r < base.local[isf_a - 1].size(); ++r)
774 srv_pre += num_traits<T>::to_double(base.local[isf_a - 1][r]);
777 for (std::size_t ia = 0; ia < oa.space.size(); ++ia) {
778 const double rate = num_traits<T>::to_double(oa.rate[ia]) * gd_f;
779 const double pa = num_traits<T>::to_double(oa.prob[ia]);
780 if (!(rate > 0) || !(pa > 0))
continue;
783 double srv_post = 0.0;
784 for (std::size_t r = 0; r < R && r < oa.space[ia].size(); ++r)
785 srv_post += num_traits<T>::to_double(oa.space[ia][r]);
786 merged = srv_post - srv_pre == -1.0;
789 if (sy.passive.node == local) {
792 m.weight = rate * pa;
794 m.next.net.local[isf_a - 1] = oa.space[ia];
800 if (!region_admissible(m.next.net))
continue;
802 if (merged) dly[isf_a - 1][sy.active.cls - 1] += m.weight;
807 ssa_detail::add_tag_rates(start, preempt, sn_, sy.active.node, oa, ia, m.weight);
813 const std::vector<T>& src =
814 sy.passive.node == sy.active.node ? oa.space[ia] : base.local[isf_p - 1];
815 const qn::EventOutcome<T> op =
qn::after_event(sn_, sy.passive.node, src,
816 sy.passive.event, sy.passive.cls);
825 ? num_traits<T>::to_double(rt_now(sy.passive.rt_row, sy.passive.rt_col))
826 : num_traits<T>::to_double(sy.passive.prob);
833 sn_.
rr_var_slot(sy.active.node, sy.active.cls) != 0) {
834 const std::size_t w = sn_.nvars_of(sy.active.node);
835 const std::vector<T>& arow = oa.space[ia];
836 std::size_t dest = 0;
837 if (arow.size() >= w) {
838 const std::vector<T> var(arow.end() - w, arow.end());
839 dest = sn_.rr_dest(sy.active.node, sy.active.cls, var);
841 proute = (dest == sy.passive.node && sy.passive.cls == sy.active.cls) ? 1.0 : 0.0;
843 for (std::size_t ip = 0; ip < op.space.size(); ++ip) {
844 const double pp = num_traits<T>::to_double(op.prob[ip]);
845 if (!(pp > 0))
continue;
848 m.weight = rate * pa * proute * pp;
849 if (!(m.weight > 0))
continue;
851 m.next.net.local[isf_a - 1] = oa.space[ia];
852 m.next.net.local[isf_p - 1] = op.space[ip];
853 if (!region_admissible(m.next.net))
continue;
855 if (merged) dly[isf_a - 1][sy.active.cls - 1] += m.weight;
858 ssa_detail::add_tag_rates(start, preempt, sn_, sy.active.node, oa, ia, m.weight);
859 ssa_detail::add_tag_rates(start, preempt, sn_, sy.passive.node, op, ip, m.weight);
868 dep[isf_a - 1][sy.active.cls - 1] += fired;
869 if (isf_p != 0) arv[isf_p - 1][sy.passive.cls - 1] += fired;
873 for (std::size_t g = 0; g < gsync_.size(); ++g) {
875 for (std::size_t io = 0; io < go.space.size(); ++io) {
876 const double w = num_traits<T>::to_double(go.rate[io]) *
877 num_traits<T>::to_double(go.prob[io]);
878 if (!(w > 0))
continue;
880 m.sync = sync_.size() + g;
883 m.next.net = go.space[io];
884 if (!region_admissible(m.next.net))
continue;
890 for (std::size_t j = 0; j < gsync_[g].passive.size(); ++j) {
891 const qn::ModeEvent<T>& pev = gsync_[g].passive[j];
893 if (pisf == 0 || pev.cls == 0 || pev.cls > R)
continue;
909 for (std::size_t k = 0; k < fjsync_.size(); ++k) {
910 const qn::FjSync<T>& e = fjsync_[k];
912 if (isf_f == 0)
continue;
915 for (std::size_t io = 0; io < fo.space.size(); ++io) {
916 const double w = num_traits<T>::to_double(fo.rate[io]) *
917 num_traits<T>::to_double(fo.prob[io]);
918 if (!(w > 0))
continue;
920 m.sync = sync_.size() + gsync_.size() + k;
923 m.next.net = fo.space[io];
924 if (!region_admissible(m.next.net))
continue;
928 if (!(fired > 0))
continue;
929 dep[isf_f - 1][e.cls - 1] += fired;
930 for (std::size_t b = 0; b < e.branchheads.size(); ++b) {
932 if (isf_b != 0) arv[isf_b - 1][e.auxclasses[b] - 1] += fired;
945 "solver_ssa_serial: an SSA sample path is generated from exponential clocks "
946 "drawn as -log(u)/rate, which needs transcendental arithmetic");
948 const std::size_t NF = sn_.stateful_nodes.size();
949 const std::size_t R = sn_.nclasses;
951 out.
seed = opt_.seed;
952 out.
warmup =
static_cast<std::size_t
>(
953 std::floor(std::max(0.0, std::min(0.99, opt_.warmupfrac)) *
954 static_cast<double>(opt_.samples)));
957 std::vector<Move> moves;
958 std::vector<std::vector<double>> arv(NF, std::vector<double>(R, 0.0));
959 std::vector<std::vector<double>> dep(NF, std::vector<double>(R, 0.0));
960 std::vector<std::vector<double>> dly(NF, std::vector<double>(R, 0.0));
964 std::vector<std::vector<double>> start(NF, std::vector<double>(R, 0.0));
965 std::vector<std::vector<double>> preempt(NF, std::vector<double>(R, 0.0));
966 std::vector<double> weights;
967 std::map<std::vector<double>, std::size_t> index;
969 double cur_time = 0.0;
972 for (std::size_t n = 0; n < opt_.samples; ++n) {
973 enabled(cur, moves, arv, dep, dly, start, preempt);
976 "solver_ssa_serial: the sample path entered a deadlock before collecting all "
977 "samples, no synchronization is enabled");
979 weights.resize(moves.size());
981 for (std::size_t i = 0; i < moves.size(); ++i) {
982 weights[i] = moves[i].weight;
985 const std::size_t sel = rng_.draw(weights);
989 const double dt = -std::log(rng_.uniform()) / tot;
1001 const std::vector<double> key = ctmc::waitq_detail::waitq_key(cur);
1003 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
1005 if (it != index.end()) {
1008 si = out.
space.size();
1011 out.
buf.push_back(cur.
buf);
1012 out.
pi.push_back(0.0);
1031 cur = moves[sel].next;
1035 double tot_pi = 0.0;
1036 for (std::size_t s = 0; s < out.
pi.size(); ++s) tot_pi += out.
pi[s];
1038 for (std::size_t s = 0; s < out.
pi.size(); ++s) out.
pi[s] /= tot_pi;
1043namespace serial_detail {
1049 if (d.
disabled || d.
D0.rows() == 0)
return -1.0;
1055 }
catch (
const Error&) {
1096 if constexpr (!std::is_same<T, double>::value) {
1100 "solver_ssa_serial: an SSA sample path is generated from exponential clocks, which "
1101 "are logarithms of uniform draws; there is no exact value to compute and a wider "
1102 "float carries no information the Monte Carlo error does not swamp. Rerun with "
1106 const std::size_t M =
sn.nstations, K =
sn.nclasses;
1116 out.
parked.assign(K, 0.0);
1117 for (std::size_t s = 0; s < r.
buf.size() && s < r.
pi.size(); ++s)
1118 for (std::size_t f = 0; f < r.
buf[s].size(); ++f)
1119 for (std::size_t j = 0; j < r.
buf[s][f].size(); ++j) {
1120 const std::size_t cls = (r.
buf[s][f][j] - 1) % K + 1;
1132 a.
XN.assign(K, 0.0);
1133 a.
CN.assign(K, 0.0);
1139 for (std::size_t k = 1; k <= K; ++k) {
1140 const std::size_t refsf =
sn.stateful_of_station(
sn.classes[k - 1].refstat);
1141 if (refsf == 0)
continue;
1142 for (std::size_t s = 0; s < r.
space.size(); ++s)
1152 bool scaled =
static_cast<bool>(
sn.gdscaling);
1153 for (std::size_t i = 0; i < M; ++i)
1154 if (!
sn.stations[i].lldscaling.empty() ||
sn.stations[i].cdscaling) scaled =
true;
1156 for (std::size_t ist = 1; ist <= M; ++ist) {
1157 const std::size_t isf =
sn.stateful_of_station(ist);
1158 if (isf == 0)
continue;
1159 const SchedStrategy sched =
sn.stations[ist - 1].sched;
1160 const double S =
sn.stations[ist - 1].nservers;
1161 for (std::size_t k = 1; k <= K; ++k) {
1162 for (std::size_t s = 0; s < r.
space.size(); ++s) {
1163 a.
TN(ist - 1, k - 1) += r.
pi[s] * r.
dep_rates[s][isf - 1][k - 1];
1164 a.
QN(ist - 1, k - 1) +=
1174 const bool is_ps = sched == SchedStrategy::PS || sched == SchedStrategy::DPS ||
1175 sched == SchedStrategy::GPS || sched == SchedStrategy::LPS;
1185 sched == SchedStrategy::EXT)
1187 if (sched == SchedStrategy::INF) {
1188 for (std::size_t k = 1; k <= K; ++k) a.
UN(ist - 1, k - 1) = a.
QN(ist - 1, k - 1);
1197 const std::vector<T>& lld =
sn.stations[ist - 1].lldscaling;
1198 for (std::size_t j = 0; j < lld.size(); ++j)
1200 const bool is_cd =
static_cast<bool>(
sn.stations[ist - 1].cdscaling);
1201 const bool is_jd =
static_cast<bool>(
sn.stations[ist - 1].jdscaling);
1204 const bool is_gd =
static_cast<bool>(
sn.gdscaling);
1205 std::vector<T> gdpk;
1207 gdpk.assign(
sn.gdscalingpeak.begin() + (ist - 1) * K,
1208 sn.gdscalingpeak.begin() + ist * K);
1209 for (std::size_t k = 1; k <= K; ++k) {
1210 const double mean = serial_detail::service_mean(
sn, ist, k);
1211 if (mean < 0)
continue;
1218 if (is_cd || is_jd || is_gd) {
1220 const std::vector<T>* pks[3] = {&
sn.stations[ist - 1].cdscalingpeak,
1221 &
sn.stations[ist - 1].jdscalingpeak,
1223 const char* names[3] = {
"setClassDependence",
"setJointDependence",
1224 "setGlobalDependence"};
1225 const bool on[3] = {is_cd, is_jd, is_gd};
1226 for (std::size_t h = 0; h < 3; ++h) {
1227 if (!on[h])
continue;
1228 const std::vector<T>& pk = *pks[h];
1231 "SolverSSA(method='serial'): station '" +
1232 sn.stations[ist - 1].name +
1233 "' declares a dependent scaling with no declared peak rate. "
1234 "Utilization there is T*E[S]/peak, so pass the peak to " +
1239 a.
UN(ist - 1, k - 1) = cdiv > 0 ? a.
TN(ist - 1, k - 1) * mean / cdiv : 0.0;
1247 for (std::size_t k = 1; k <= K; ++k) {
1248 const double mean = serial_detail::service_mean(
sn, ist, k);
1249 if (mean < 0)
continue;
1250 const bool can_drop = !std::isfinite(
sn.njobs()[k - 1]) &&
1251 (std::isfinite(
sn.cap[ist - 1]) ||
1252 std::isfinite(
sn.classcap[ist - 1][k - 1]));
1255 for (std::size_t s = 0; s < r.
space.size(); ++s)
1259 if (!(mu > 0))
continue;
1260 a.
UN(ist - 1, k - 1) =
1261 (can_drop ? a.
TN(ist - 1, k - 1) / mu : arv / mu) / S;
1263 a.
UN(ist - 1, k - 1) =
1264 (can_drop ? a.
TN(ist - 1, k - 1) * mean : arv * mean) / S;
1270 for (std::size_t k = 1; k <= K; ++k) {
1271 for (std::size_t ist = 1; ist <= M; ++ist)
1272 a.
RN(ist - 1, k - 1) = a.
TN(ist - 1, k - 1) > 0
1273 ? a.
QN(ist - 1, k - 1) / a.
TN(ist - 1, k - 1)
1278 if (a.
XN[k - 1] > 0) a.
CN[k - 1] =
sn.classes[k - 1].population / a.
XN[k - 1];
1286 const double nan = std::numeric_limits<double>::quiet_NaN();
1288 sn.nodeparam.begin();
1289 ci !=
sn.nodeparam.end(); ++ci) {
1290 const std::size_t ind = ci->first;
1291 if (ind == 0 || ind >
sn.nodes.size())
continue;
1293 const std::size_t isf =
sn.stateful_index(ind);
1294 if (isf == 0)
continue;
1299 cr.
residt.assign(K, nan);
1300 std::vector<double> dly(K, 0.0);
1301 bool any_delayed =
false;
1302 for (std::size_t k = 1; k <= K; ++k) {
1303 if (ci->second.hitclass.size() < k || ci->second.missclass.size() < k)
continue;
1304 const std::size_t h = ci->second.hitclass[k - 1];
1305 const std::size_t mi = ci->second.missclass[k - 1];
1306 if (h == 0 || mi == 0 || h > K || mi > K)
continue;
1307 double th = 0.0, tm = 0.0, td = 0.0;
1308 for (std::size_t s = 0; s < r.
space.size(); ++s) {
1317 cr.
hitprob[k - 1] = std::max(th - td, 0.0) / (th + tm);
1318 cr.
missprob[k - 1] = tm / (th + tm);
1319 dly[k - 1] = td / (th + tm);
1320 if (td > 0) any_delayed =
true;
1324 out.
cache.push_back(cr);
1350 if constexpr (!std::is_same<T, double>::value) {
1377 const std::string& m =
opt.method;
1379 if (m ==
"para" || m ==
"parallel")
1381 "SolverSSA: the '" + m +
1382 "' method runs the serial engine on several workers and averages the replicas "
1383 "(solver_ssa_analyzer_parallel.m). The engine is ported; the replication is not, and "
1384 "one replica has a different variance from the average of many. Use 'serial'");
1386 "' is not a method this entry accepts; it implements 'serial' and the "
1387 "'default' and 'ssa' aliases that reach it");
Base error for the multiprecision C++ port.
NumericError(const std::string &what)
Requested feature or arithmetic mode is not ported yet.
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::size_t stateful_index(std::size_t ind) const
1-based stateful index of node ind, 0 when the node is not stateful.
std::size_t stateful_of_station(std::size_t st) const
GdScaling< T > gdscaling
sn.gdscaling: the network-level globally state-dependent (Whittle) rate scaling phi(n).
std::size_t rr_var_slot(std::size_t ind, std::size_t r) const
1-BASED index of the pointer of (ind, r) INSIDE the node's local-variable block, or 0 when that pair ...
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::size_t node_of_station(std::size_t st) const
1-based node index of a station, and the reverse; 0 when absent.
std::vector< NodeDef > nodes
every node, in creation order
std::size_t phasessz_of(std::size_t ist, std::size_t r) const
sn.phasessz(i,r) = max(sn.phases(i,r),1): THE WIDTH of class r's phase block in a state row,...
std::vector< Region > regions
The serial engine: the sample path of solver_ssa.m's main loop.
const std::vector< qn::Sync< T > > & sync() const
The synchronization list the trace's tran_sync indexes.
SsaSerialEngine(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt, const std::vector< qn::FjSync< T > > &fjsync=std::vector< qn::FjSync< T > >())
fjsync is the fork firing list of the TAG-AUGMENTED struct, empty for a model with no Fork.
const qn::NetState< T > & init_state() const
The state the path starts from, at full encoding width.
SsaSerialRun< T > run()
Run opt.samples firings and return the path with its statistics.
The exception types the port throws.
Port of matlab/src/api/fj/sn_fj_validate.m and matlab/src/io/@@ModelAdapter/fjtag....
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
bool ctmc_region_admissible(const NetworkStruct< T > &sn, const std::vector< T > &nir)
True when nir – the per-(station, class) counts of one state, in (ist-1)*K + k order – satisfies ever...
std::vector< NetState< T > > ctmc_filter_regions(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
The states of space a DROP region admits, in their original order.
void ctmc_check_region_rules(const NetworkStruct< T > &sn)
Refuse the region rules this port does not implement.
Matrix< T > ctmc_state_space_aggr(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Port of StateSpaceAggr: the per-(station, class) job counts of every state, as an (nstates x nstation...
bool ctmc_has_waitq_region(const NetworkStruct< T > &sn)
True when the model declares a region that applies anything other than DROP.
std::vector< NetState< T > > reachable_space_generator(const NetworkStruct< T > &sn, const NetState< T > &init, const std::vector< Sync< T > > &sync, const std::vector< qn::GlobalSync< T > > &gsync=std::vector< qn::GlobalSync< T > >(), std::size_t maxst=3000000, const std::vector< qn::FjSync< T > > &fjsync=std::vector< qn::FjSync< T > >(), const std::vector< std::size_t > &cutoff=std::vector< std::size_t >(), const std::vector< std::vector< std::size_t > > &cutoff_mat=std::vector< std::vector< std::size_t > >())
Port of State.reachableSpaceGenerator: the states reachable from init.
void ctmc_check_waitq_support(const NetworkStruct< T > &sn)
The combinations the reference gates, plus the two this port cannot represent.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
@ PHASE
service advances a phase WITHOUT departing
@ READ
a cache item is read
@ POST
produce to a place or queue buffer
@ PRE
consume from a place or queue buffer, no server effect
NodeType
Node kinds, with the values of MATLAB NodeType.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Marginal< T > to_marginal(const NetworkStruct< T > &sn, std::size_t ist, const std::vector< T > &state_i, const std::vector< std::size_t > &phasesz, const std::vector< std::size_t > &phaseshift, std::size_t nvar=0)
Port of State.toMarginal for a STATION, one state row at a time.
std::vector< std::vector< T > > from_marginal_node(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< std::size_t > &n, const std::vector< std::size_t > &phases)
Port of State.fromMarginal at its OWN signature: the reference indexes by NODE, not by station,...
bool from_marginal_node_first(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< std::size_t > &n, const std::vector< std::size_t > &phases, std::vector< T > &out)
The FIRST row from_marginal_node emits, BUILT rather than enumerated.
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.
std::vector< Sync< T > > refresh_sync(const NetworkStruct< T > &sn, const std::vector< std::vector< bool > > &impatience_classes=std::vector< std::vector< bool > >(), const std::vector< std::size_t > &breakdown_nodes=std::vector< std::size_t >())
Port of MNetwork.refreshSync: the synchronization list.
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< GlobalSync< T > > refresh_global_sync(const NetworkStruct< T > &sn)
Port of MNetwork.refreshGlobalSync: the ENABLE and FIRE synchronizations.
FjTagged< T > fj_tag(const NetworkStruct< T > &sn)
Port of ModelAdapter.fjtag.
GlobalOutcome< T > after_fj_event(const NetworkStruct< T > &sn, const FjSync< T > &e, const NetState< T > &gl)
Port of State.afterFJEvent: fire ONE entry of the fork firing list.
Matrix< T > rt_state(const NetworkStruct< T > &sn, const std::vector< std::vector< T > > &local)
Port of sn.rtfun: the routing over the stateful nodes AT ONE STATE.
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....
SsaSerialSolution< T > solver_ssa_serial(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt)
The serial entry of solver_ssa_analyzer.m.
SsaReachability< T > solver_ssa_reachability(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt=SsaSerialOptions())
Port of solver_ssa_reachability.m: the states the DYNAMICS can occupy, decomposed per stateful node.
SsaSerialSolution< T > solver_ssa_serial_on_struct(const qn::NetworkStruct< T > &sn, const SsaSerialOptions &opt, const std::vector< qn::FjSync< T > > &fjsync)
Port of solver_ssa_analyzer_serial.m: run the serial engine and reduce its path to the metric table.
void fj_foldback(const qn::NetworkStruct< T > &sn, Avg &a, const std::vector< std::size_t > &fjclassmap, std::size_t korig)
Reduce the augmented metrics onto the original classes.
bool has_fork_join(const qn::NetworkStruct< T > &sn)
Whether the model needs the tag augmentation at all.
A queueing network and its refreshed NetworkStruct.
Port of solver_ctmc.m: the infinitesimal generator of a queueing network, assembled from the enumerat...
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
Finite Capacity Regions in SolverCTMC: the DROP rule, as a filter on the enumerated state space,...
Port of solver_ctmc_fcr_waitq.m: the reachability-built generator of a model whose finite capacity re...
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...
The SolverCTMC knobs this port honours.
std::size_t state_max
refuse a space larger than this
double cutoff
< 0 = not given
One augmented state: the network state, plus the token FIFO of every region.
std::vector< std::vector< std::size_t > > buf
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.
A MAP as the pair of matrices (D0, D1).
One fork firing synchronization: sn.fjsync{k}.
The augmented struct and everything needed to read its results back.
std::vector< FjSync< T > > fjsync
std::vector< std::size_t > fjclassmap
fjclassmap[a-1] is the ORIGINAL class of auxiliary class a, 0 for originals.
One network state: the per-stateful-node local rows it is composed of.
What the cache write-back of solver_ssa_analyzer_serial.m produces.
std::size_t node
1-based Cache node index
std::vector< double > delayedprob
The delayed-hit share, EMPTY off a retrieval system.
std::vector< double > missprob
per class, NaN where undefined
std::vector< double > hitprob
std::vector< double > residt
actualresidt: NaN, and NOT a port gap.
Controls, defaulting to SolverOptions('SSA') in the reference.
Port of solver_ssa_reachability.m's return: [SSq, SSh, sn.space].
std::vector< std::vector< std::vector< T > > > node_space
sn.space, per stateful node
Matrix< T > ssq
SSq, states x concatenated width
std::vector< qn::NetState< T > > space
the reachable states
std::vector< std::vector< std::size_t > > hash
SSh, 1-based per node
The serial engine's knobs: SsaOptions plus the three the serial path reads and the NRM has no use for...
std::size_t state_max
refuse a reachable space larger than this
double cutoff
< 0 = the reference's automatic value
One sample path, in the shape solver_ssa.m returns it.
std::vector< qn::NetState< T > > space
The DISTINCT states visited, in first-visit order (the reference's u).
std::vector< double > tran_time
tranSysState{1}: the cumulative time at each firing.
std::vector< std::size_t > tran_state
The row of space the path OCCUPIED over [t-dt, t], one per firing.
Matrix< T > ssq
SSq: the per-(station, class) job counts of each distinct state.
std::vector< std::vector< std::vector< double > > > start_rates
The DERIVED rates per state, laid out like arv_rates: how fast the transitions enabled in that state ...
std::size_t warmup
leading firings excluded from pi
unsigned long seed
the stream this path came from
std::vector< std::vector< std::vector< double > > > arv_rates
arvRates / depRates, indexed [distinct state][stateful-1][class-1].
std::vector< std::vector< std::vector< std::size_t > > > buf
The region token FIFOs of each of those states, the reference's fcrBuf.
std::size_t samples
firings actually performed
std::vector< std::vector< std::vector< double > > > dly_rates
The rate of the cache MERGE transitions, i.e.
std::vector< std::vector< std::vector< double > > > dep_rates
std::vector< std::vector< std::vector< double > > > preempt_rates
std::vector< double > pi
pi: the fraction of simulated time spent in each of them.
std::vector< std::size_t > tran_sync
tranSync: which synchronization fired, sync.size() + g for a global one.
The serial analyzer's return: the metric table, the path, and the stream.
std::vector< SsaCacheRatio > cache
std::vector< double > parked
Mean number of jobs parked in a region FIFO, per class of the struct that ran.
SsaSolution avg
QN, UN, RN, TN, XN, CN; method = "serial".
unsigned long seed
carried beside the numbers, never implied
std::vector< std::size_t > fjclassmap
fjclassmap of the tag augmentation, empty on a model with no Fork.
What the analyzer returns, in the same shape as the MVA and fluid results.
Matrix< double > StartN
The DERIVED rates, (nstations x nclasses): how often per unit time a class-r service STARTS at statio...
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.
Matrix< double > PreemptN