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;
226namespace serial_detail {
250 if (!
sn.regions.empty()) {
260 if (!raise)
return false;
270 if (!skip_fork &&
sn.has_fork() && !
sn.isfjaugmented) {
271 if (!raise)
return false;
273 "SolverSSA(method='serial'): the model contains a Fork node and has not been "
274 "tag-augmented. A fork fires through `sn.fjsync` / `State.afterFJEvent`, which only "
275 "`fj_tag` builds; call `solver_ssa_serial_analyzer`, which augments and folds the "
276 "sibling classes back, rather than the engine directly");
278 for (std::size_t i = 0; i <
sn.nstations; ++i) {
279 const typename std::map<std::size_t, qn::RetrialParam<T>>::const_iterator rit =
280 sn.retrialparam.find(i + 1);
281 if (rit ==
sn.retrialparam.end())
continue;
283 std::size_t served = 0;
284 for (std::size_t r = 0; r < rit->second.retrial_proc.size(); ++r) {
285 if (rit->second.retrial_proc[r].disabled)
continue;
288 if (!raise)
return false;
290 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
291 "' retries with non-exponential patience. SOLVER_SSA supports only "
292 "exponential (memoryless) retrial delay in every codebase, because the orbit "
293 "carries no remaining-delay phase");
295 if (r < rit->second.max_attempts.size() && rit->second.max_attempts[r] > 0) {
296 if (!raise)
return false;
298 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
299 "' declares a finite retrial max-attempts count. SOLVER_SSA supports only "
300 "unlimited retrials in every codebase, because the attempt counter is not "
301 "part of the state");
305 for (std::size_t r = 0; r <
sn.nclasses; ++r)
306 if (!
sn.disabled[i][r] &&
sn.service[i][r].D0.rows() > 0) ++served;
308 if (!raise)
return false;
310 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
311 "' is a multi-class retrial station. SOLVER_SSA supports retrial only for "
312 "single-class stations in every codebase");
337 const std::size_t M =
sn.nstations, K =
sn.nclasses;
339 if (
sn.replyblock.empty())
return held;
340 for (std::size_t ist = 1; ist <= M; ++ist) {
341 const std::size_t ind =
sn.node_of_station(ist);
342 const std::size_t isf =
sn.stateful_of_station(ist);
343 if (isf == 0 ||
sn.replyblock.size() < ind)
continue;
345 for (std::size_t k = 0; k <
sn.replyblock[ind - 1].size(); ++k)
346 any = any ||
sn.replyblock[ind - 1][k];
349 if (info.
width == 0)
continue;
350 for (std::size_t s = 0; s < r.space.size() && s < r.pi.size(); ++s) {
351 if (r.pi[s] == 0)
continue;
352 const std::vector<T>& row = r.space[s].local[isf - 1];
354 for (std::size_t pos = 0; pos < info.
classes.size(); ++pos) {
355 const std::size_t col = row.size() - info.
width + pos;
364bool serial_can_run(
const qn::NetworkStruct<T>& sn) {
365 return serial_check(sn,
false,
true);
388std::size_t max_row_width(
const qn::NetworkStruct<T>& sn, std::size_t ind,
389 const std::vector<std::size_t>& cutoff) {
390 const std::size_t R = sn.nclasses;
391 const std::size_t ist = sn.nodes[ind - 1].station;
392 std::vector<std::size_t> ph(R, 1);
394 for (std::size_t r = 0; r < R; ++r) ph[r] = sn.phasessz_of(ist, r + 1);
402 throw UnsupportedError(
"SolverSSA(method='serial'): node '" + sn.nodes[ind - 1].name +
403 "' admits no state at all, so no sample path can start");
409 std::vector<std::size_t> bound(R, 0);
410 for (std::size_t r = 0; r < R; ++r) {
411 const double nj = sn.njobs()[r];
412 double b = std::isfinite(nj) ? nj :
static_cast<double>(cutoff[r]);
413 const double cc = sn.classcap[ist - 1][r];
416 bound[r] =
static_cast<std::size_t
>(b);
418 double tcapd = sn.cap[ist - 1];
419 std::size_t total = 0;
420 for (std::size_t r = 0; r < R; ++r) total += bound[r];
421 if (std::isfinite(tcapd) && tcapd <
static_cast<double>(total))
422 total =
static_cast<std::size_t
>(tcapd);
432 return total + sn.nvars_of(ind);
434 for (std::size_t t = total + 1; t-- > 0;) {
435 std::vector<std::size_t> n(R, 0);
436 std::size_t left = t;
437 for (std::size_t r = 0; r < R && left > 0; ++r) {
438 n[r] = std::min(left, bound[r]);
441 if (left > 0)
continue;
443 if (!rows.empty())
return rows[0].size();
445 throw UnsupportedError(
"SolverSSA(method='serial'): station '" + sn.stations[ist - 1].name +
446 "' admits no state at all, so no sample path can start");
460qn::NetState<T> wide_init_state(
const qn::NetworkStruct<T>& sn,
461 const std::vector<std::size_t>& cutoff) {
462 qn::NetState<T> init;
463 if (!ctmc::analyzer_detail::default_init_state(sn, init))
465 "SolverSSA(method='serial'): the model's initial state admits no state; check the "
466 "class populations against their reference stations");
467 for (std::size_t f = 0; f < sn.stateful_nodes.size(); ++f) {
468 const std::size_t w = max_row_width(sn, sn.stateful_nodes[f], cutoff);
469 if (init.local[f].size() > w)
continue;
470 init.local[f].insert(init.local[f].begin(), w - init.local[f].size(),
471 num_traits<T>::from_int(0));
497 serial_detail::serial_check(
sn);
501 const std::vector<std::size_t> cutoff = ctmc::analyzer_detail::resolve_cutoff(
sn, copt);
514 "solver_ssa_reachability: the model declares a WAITQ finite capacity region, whose "
515 "states carry a per-region token FIFO outside every node; this decomposition is "
516 "per-node and cannot represent it. Use `solver_ssa_serial_analyzer`, whose run carries "
517 "the FIFO, or `solver_ctmc_waitq` for the exact augmented space");
528 const std::size_t NF =
sn.stateful_nodes.size();
529 out.
node_space.assign(NF, std::vector<std::vector<T>>());
530 out.
hash.assign(out.
space.size(), std::vector<std::size_t>(NF, 0));
534 std::vector<std::map<std::vector<double>, std::size_t>> seen(NF);
535 for (std::size_t s = 0; s < out.
space.size(); ++s)
536 for (std::size_t f = 0; f < NF; ++f) {
537 std::vector<double> key(out.
space[s].local[f].size(), 0.0);
538 for (std::size_t j = 0; j < key.size(); ++j)
540 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
542 if (it != seen[f].end()) {
543 out.
hash[s][f] = it->second;
551 std::size_t width = 0;
552 for (std::size_t f = 0; f < NF; ++f)
553 width += out.
space.empty() ? 0 : out.
space[0].local[f].size();
555 for (std::size_t s = 0; s < out.
space.size(); ++s) {
557 for (std::size_t f = 0; f < NF; ++f)
558 for (std::size_t j = 0; j < out.
space[s].local[f].size(); ++j)
559 out.
ssq(s, col++) = out.
space[s].local[f][j];
583 : sn_(
sn), opt_(
opt), rng_(
opt.seed), fjsync_(fjsync) {
584 serial_detail::serial_check(
sn);
588 const std::vector<std::size_t> cutoff = ctmc::analyzer_detail::resolve_cutoff(
sn, copt);
591 sdr_ =
sn.has_sdr_routing();
600 if (!
sn.regions.empty()) {
602 caps_ = ctmc::waitq_detail::extract_caps(
sn);
603 ctmc::waitq_detail::resolve_lmax(
sn, cutoff, caps_);
606 init_.net = serial_detail::wide_init_state(
sn, cutoff);
607 init_.buf.assign(caps_.size(), std::vector<std::size_t>());
608 if (!
sn.regions.empty() && !region_admissible(init_.net))
610 "SolverSSA(method='serial'): the model's initial state violates a finite capacity "
611 "region; the region cannot hold the model's initial population, so no sample path "
619 const std::vector<qn::Sync<T>>&
sync()
const {
return sync_; }
626 std::size_t
sync = 0;
632 SsaSerialOptions opt_;
634 std::vector<qn::Sync<T>> sync_;
635 std::vector<qn::GlobalSync<T>> gsync_;
636 std::vector<qn::FjSync<T>> fjsync_;
637 std::vector<ctmc::waitq_detail::RegionCaps<T>> caps_;
645 if (sn_.
regions.empty())
return true;
646 std::vector<qn::NetState<T>> one(1, st);
648 std::vector<T> nir(A.
cols());
649 for (std::size_t c = 0; c < A.
cols(); ++c) nir[c] = A(0, c);
657 void enabled(
const ctmc::WaitqState<T>& st, std::vector<Move>& moves,
658 std::vector<std::vector<double>>& arv, std::vector<std::vector<double>>& dep,
659 std::vector<std::vector<double>>& dly,
660 std::vector<std::vector<double>>& start,
661 std::vector<std::vector<double>>& preempt)
const;
667 std::vector<T> gd_factor_now(
const qn::NetState<T>& ns)
const {
669 const T zero = num_traits<T>::from_int(0);
670 std::vector<T> npop(M * K, zero);
671 for (std::size_t ist = 1; ist <= M; ++ist) {
673 if (isf == 0)
continue;
676 std::vector<std::size_t> ph(K, 1), shift(K, 0);
678 for (std::size_t k = 0; k < K; ++k) {
683 const qn::Marginal<T> m =
684 qn::to_marginal(sn_, ist, ns.local[isf - 1], ph, shift, sn_.nvars_of(ind));
685 for (std::size_t k = 0; k < K; ++k) npop[(ist - 1) * K + k] = m.nir[k];
687 const std::vector<T> v = sn_.gdscaling(npop);
688 std::vector<T> out(M * K, num_traits<T>::from_int(1));
689 for (std::size_t i = 0; i < M; ++i)
690 for (std::size_t r = 0; r < K; ++r) {
691 const T f = v.size() == 1 ? v[0] : (v.size() == M ? v[i] : v[i * K + r]);
692 if (!(num_traits<T>::to_double(f) >= 0))
694 "the global dependence handle returned a non-finite or negative scaling");
702void SsaSerialEngine<T>::enabled(
const ctmc::WaitqState<T>& st, std::vector<Move>& moves,
703 std::vector<std::vector<double>>& arv,
704 std::vector<std::vector<double>>& dep,
705 std::vector<std::vector<double>>& dly,
706 std::vector<std::vector<double>>& start,
707 std::vector<std::vector<double>>& preempt)
const {
708 const std::size_t local = sn_.
nodes.size() + 1;
711 for (std::size_t f = 0; f < arv.size(); ++f)
712 for (std::size_t r = 0; r < R; ++r) {
729 std::vector<ctmc::waitq_detail::Successor<T>> succ;
730 ctmc::waitq_detail::waitq_successors(sn_, sync_, caps_, st, succ);
731 for (std::size_t i = 0; i < succ.size(); ++i) {
732 const ctmc::waitq_detail::Successor<T>& su = succ[i];
733 const double w = num_traits<T>::to_double(su.w);
734 if (!(w > 0))
continue;
740 if (su.dep_isf != 0) dep[su.dep_isf - 1][su.dep_cls - 1] += w;
741 for (std::size_t q = 0; q < su.arv.size(); ++q)
742 arv[su.arv[q].first - 1][su.arv[q].second - 1] += w;
747 const qn::NetState<T>& base = st.net;
753 std::vector<T> gd_now;
754 const bool has_gd =
static_cast<bool>(sn_.
gdscaling);
755 if (has_gd) gd_now = gd_factor_now(base);
762 for (std::size_t a = 0; a < sync_.size(); ++a) {
763 const qn::Sync<T>& sy = sync_[a];
765 if (isf_a == 0)
continue;
766 const std::size_t isf_p =
767 sy.passive.node == local ? 0 : sn_.
stateful_index(sy.passive.node);
768 if (sy.passive.node != local && isf_p == 0)
continue;
774 const qn::EventOutcome<T> oa =
775 qn::after_event(sn_, sy.active.node, base.local[isf_a - 1], sy.active.event,
778 const bool gd_here = has_gd && sn_.
nodes[sy.active.node - 1].station != 0 &&
782 gd_here ? num_traits<T>::to_double(
783 gd_now[(sn_.
nodes[sy.active.node - 1].station - 1) * R +
784 (sy.active.cls - 1)])
792 double srv_pre = 0.0;
794 for (std::size_t r = 0; r < R && r < base.local[isf_a - 1].size(); ++r)
795 srv_pre += num_traits<T>::to_double(base.local[isf_a - 1][r]);
798 for (std::size_t ia = 0; ia < oa.space.size(); ++ia) {
799 const double rate = num_traits<T>::to_double(oa.rate[ia]) * gd_f;
800 const double pa = num_traits<T>::to_double(oa.prob[ia]);
801 if (!(rate > 0) || !(pa > 0))
continue;
804 double srv_post = 0.0;
805 for (std::size_t r = 0; r < R && r < oa.space[ia].size(); ++r)
806 srv_post += num_traits<T>::to_double(oa.space[ia][r]);
807 merged = srv_post - srv_pre == -1.0;
810 if (sy.passive.node == local) {
813 m.weight = rate * pa;
815 m.next.net.local[isf_a - 1] = oa.space[ia];
821 if (!region_admissible(m.next.net))
continue;
823 if (merged) dly[isf_a - 1][sy.active.cls - 1] += m.weight;
828 ssa_detail::add_tag_rates(start, preempt, sn_, sy.active.node, oa, ia, m.weight);
834 const std::vector<T>& src =
835 sy.passive.node == sy.active.node ? oa.space[ia] : base.local[isf_p - 1];
836 const qn::EventOutcome<T> op =
qn::after_event(sn_, sy.passive.node, src,
837 sy.passive.event, sy.passive.cls);
846 ? num_traits<T>::to_double(rt_now(sy.passive.rt_row, sy.passive.rt_col))
847 : num_traits<T>::to_double(sy.passive.prob);
854 sn_.
rr_var_slot(sy.active.node, sy.active.cls) != 0) {
855 const std::size_t w = sn_.nvars_of(sy.active.node);
856 const std::vector<T>& arow = oa.space[ia];
857 std::size_t dest = 0;
858 if (arow.size() >= w) {
859 const std::vector<T> var(arow.end() - w, arow.end());
860 dest = sn_.rr_dest(sy.active.node, sy.active.cls, var);
862 proute = (dest == sy.passive.node && sy.passive.cls == sy.active.cls) ? 1.0 : 0.0;
864 for (std::size_t ip = 0; ip < op.space.size(); ++ip) {
865 const double pp = num_traits<T>::to_double(op.prob[ip]);
866 if (!(pp > 0))
continue;
869 m.weight = rate * pa * proute * pp;
870 if (!(m.weight > 0))
continue;
872 m.next.net.local[isf_a - 1] = oa.space[ia];
873 m.next.net.local[isf_p - 1] = op.space[ip];
874 if (!region_admissible(m.next.net))
continue;
876 if (merged) dly[isf_a - 1][sy.active.cls - 1] += m.weight;
879 ssa_detail::add_tag_rates(start, preempt, sn_, sy.active.node, oa, ia, m.weight);
880 ssa_detail::add_tag_rates(start, preempt, sn_, sy.passive.node, op, ip, m.weight);
889 dep[isf_a - 1][sy.active.cls - 1] += fired;
890 if (isf_p != 0) arv[isf_p - 1][sy.passive.cls - 1] += fired;
894 for (std::size_t g = 0; g < gsync_.size(); ++g) {
896 for (std::size_t io = 0; io < go.space.size(); ++io) {
897 const double w = num_traits<T>::to_double(go.rate[io]) *
898 num_traits<T>::to_double(go.prob[io]);
899 if (!(w > 0))
continue;
901 m.sync = sync_.size() + g;
904 m.next.net = go.space[io];
905 if (!region_admissible(m.next.net))
continue;
911 for (std::size_t j = 0; j < gsync_[g].passive.size(); ++j) {
912 const qn::ModeEvent<T>& pev = gsync_[g].passive[j];
914 if (pisf == 0 || pev.cls == 0 || pev.cls > R)
continue;
930 for (std::size_t k = 0; k < fjsync_.size(); ++k) {
931 const qn::FjSync<T>& e = fjsync_[k];
933 if (isf_f == 0)
continue;
936 for (std::size_t io = 0; io < fo.space.size(); ++io) {
937 const double w = num_traits<T>::to_double(fo.rate[io]) *
938 num_traits<T>::to_double(fo.prob[io]);
939 if (!(w > 0))
continue;
941 m.sync = sync_.size() + gsync_.size() + k;
944 m.next.net = fo.space[io];
945 if (!region_admissible(m.next.net))
continue;
949 if (!(fired > 0))
continue;
950 dep[isf_f - 1][e.cls - 1] += fired;
951 for (std::size_t b = 0; b < e.branchheads.size(); ++b) {
953 if (isf_b != 0) arv[isf_b - 1][e.auxclasses[b] - 1] += fired;
966 "solver_ssa_serial: an SSA sample path is generated from exponential clocks "
967 "drawn as -log(u)/rate, which needs transcendental arithmetic");
969 const std::size_t NF = sn_.stateful_nodes.size();
970 const std::size_t R = sn_.nclasses;
972 out.
seed = opt_.seed;
973 out.
warmup =
static_cast<std::size_t
>(
974 std::floor(std::max(0.0, std::min(0.99, opt_.warmupfrac)) *
975 static_cast<double>(opt_.samples)));
978 std::vector<Move> moves;
979 std::vector<std::vector<double>> arv(NF, std::vector<double>(R, 0.0));
980 std::vector<std::vector<double>> dep(NF, std::vector<double>(R, 0.0));
981 std::vector<std::vector<double>> dly(NF, std::vector<double>(R, 0.0));
985 std::vector<std::vector<double>> start(NF, std::vector<double>(R, 0.0));
986 std::vector<std::vector<double>> preempt(NF, std::vector<double>(R, 0.0));
987 std::vector<double> weights;
988 std::map<std::vector<double>, std::size_t> index;
990 double cur_time = 0.0;
993 for (std::size_t n = 0; n < opt_.samples; ++n) {
994 enabled(cur, moves, arv, dep, dly, start, preempt);
997 "solver_ssa_serial: the sample path entered a deadlock before collecting all "
998 "samples, no synchronization is enabled");
1000 weights.resize(moves.size());
1002 for (std::size_t i = 0; i < moves.size(); ++i) {
1003 weights[i] = moves[i].weight;
1006 const std::size_t sel = rng_.draw(weights);
1010 const double dt = -std::log(rng_.uniform()) / tot;
1022 const std::vector<double> key = ctmc::waitq_detail::waitq_key(cur);
1024 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
1026 if (it != index.end()) {
1029 si = out.
space.size();
1032 out.
buf.push_back(cur.
buf);
1033 out.
pi.push_back(0.0);
1052 cur = moves[sel].next;
1056 double tot_pi = 0.0;
1057 for (std::size_t s = 0; s < out.
pi.size(); ++s) tot_pi += out.
pi[s];
1059 for (std::size_t s = 0; s < out.
pi.size(); ++s) out.
pi[s] /= tot_pi;
1064namespace serial_detail {
1070 if (d.
disabled || d.
D0.rows() == 0)
return -1.0;
1076 }
catch (
const Error&) {
1117 if constexpr (!std::is_same<T, double>::value) {
1121 "solver_ssa_serial: an SSA sample path is generated from exponential clocks, which "
1122 "are logarithms of uniform draws; there is no exact value to compute and a wider "
1123 "float carries no information the Monte Carlo error does not swamp. Rerun with "
1127 const std::size_t M =
sn.nstations, K =
sn.nclasses;
1137 out.
parked.assign(K, 0.0);
1138 for (std::size_t s = 0; s < r.
buf.size() && s < r.
pi.size(); ++s)
1139 for (std::size_t f = 0; f < r.
buf[s].size(); ++f)
1140 for (std::size_t j = 0; j < r.
buf[s][f].size(); ++j) {
1141 const std::size_t cls = (r.
buf[s][f][j] - 1) % K + 1;
1153 a.
XN.assign(K, 0.0);
1154 a.
CN.assign(K, 0.0);
1160 for (std::size_t k = 1; k <= K; ++k) {
1161 const std::size_t refsf =
sn.stateful_of_station(
sn.classes[k - 1].refstat);
1162 if (refsf == 0)
continue;
1163 for (std::size_t s = 0; s < r.
space.size(); ++s)
1173 bool scaled =
static_cast<bool>(
sn.gdscaling);
1174 for (std::size_t i = 0; i < M; ++i)
1175 if (!
sn.stations[i].lldscaling.empty() ||
sn.stations[i].cdscaling) scaled =
true;
1177 for (std::size_t ist = 1; ist <= M; ++ist) {
1178 const std::size_t isf =
sn.stateful_of_station(ist);
1179 if (isf == 0)
continue;
1180 const SchedStrategy sched =
sn.stations[ist - 1].sched;
1181 const double S =
sn.stations[ist - 1].nservers;
1182 for (std::size_t k = 1; k <= K; ++k) {
1183 for (std::size_t s = 0; s < r.
space.size(); ++s) {
1184 a.
TN(ist - 1, k - 1) += r.
pi[s] * r.
dep_rates[s][isf - 1][k - 1];
1185 a.
QN(ist - 1, k - 1) +=
1195 const bool is_ps = sched == SchedStrategy::PS || sched == SchedStrategy::DPS ||
1196 sched == SchedStrategy::GPS || sched == SchedStrategy::LPS;
1206 sched == SchedStrategy::EXT)
1208 if (sched == SchedStrategy::INF) {
1209 for (std::size_t k = 1; k <= K; ++k) a.
UN(ist - 1, k - 1) = a.
QN(ist - 1, k - 1);
1218 const std::vector<T>& lld =
sn.stations[ist - 1].lldscaling;
1219 for (std::size_t j = 0; j < lld.size(); ++j)
1221 const bool is_cd =
static_cast<bool>(
sn.stations[ist - 1].cdscaling);
1222 const bool is_jd =
static_cast<bool>(
sn.stations[ist - 1].jdscaling);
1225 const bool is_gd =
static_cast<bool>(
sn.gdscaling);
1226 std::vector<T> gdpk;
1228 gdpk.assign(
sn.gdscalingpeak.begin() + (ist - 1) * K,
1229 sn.gdscalingpeak.begin() + ist * K);
1230 for (std::size_t k = 1; k <= K; ++k) {
1231 const double mean = serial_detail::service_mean(
sn, ist, k);
1232 if (mean < 0)
continue;
1239 if (is_cd || is_jd || is_gd) {
1241 const std::vector<T>* pks[3] = {&
sn.stations[ist - 1].cdscalingpeak,
1242 &
sn.stations[ist - 1].jdscalingpeak,
1244 const char* names[3] = {
"setClassDependence",
"setJointDependence",
1245 "setGlobalDependence"};
1246 const bool on[3] = {is_cd, is_jd, is_gd};
1247 for (std::size_t h = 0; h < 3; ++h) {
1248 if (!on[h])
continue;
1249 const std::vector<T>& pk = *pks[h];
1252 "SolverSSA(method='serial'): station '" +
1253 sn.stations[ist - 1].name +
1254 "' declares a dependent scaling with no declared peak rate. "
1255 "Utilization there is T*E[S]/peak, so pass the peak to " +
1260 a.
UN(ist - 1, k - 1) = cdiv > 0 ? a.
TN(ist - 1, k - 1) * mean / cdiv : 0.0;
1268 for (std::size_t k = 1; k <= K; ++k) {
1269 const double mean = serial_detail::service_mean(
sn, ist, k);
1270 if (mean < 0)
continue;
1271 const bool can_drop = !std::isfinite(
sn.njobs()[k - 1]) &&
1272 (std::isfinite(
sn.cap[ist - 1]) ||
1273 std::isfinite(
sn.classcap[ist - 1][k - 1]));
1276 for (std::size_t s = 0; s < r.
space.size(); ++s)
1280 if (!(mu > 0))
continue;
1281 a.
UN(ist - 1, k - 1) =
1282 (can_drop ? a.
TN(ist - 1, k - 1) / mu : arv / mu) / S;
1284 a.
UN(ist - 1, k - 1) =
1285 (can_drop ? a.
TN(ist - 1, k - 1) * mean : arv * mean) / S;
1295 for (std::size_t ist = 1; ist <= M; ++ist) {
1296 const double S =
sn.stations[ist - 1].nservers;
1297 for (std::size_t k = 1; k <= K; ++k) {
1298 if (held(ist - 1, k - 1) == 0)
continue;
1299 a.
QN(ist - 1, k - 1) += held(ist - 1, k - 1);
1300 a.
UN(ist - 1, k - 1) += held(ist - 1, k - 1) / S;
1305 for (std::size_t k = 1; k <= K; ++k) {
1306 for (std::size_t ist = 1; ist <= M; ++ist)
1307 a.
RN(ist - 1, k - 1) =
1308 a.
TN(ist - 1, k - 1) > 0
1309 ? (a.
QN(ist - 1, k - 1) - held(ist - 1, k - 1)) / a.
TN(ist - 1, k - 1)
1314 if (a.
XN[k - 1] > 0) a.
CN[k - 1] =
sn.classes[k - 1].population / a.
XN[k - 1];
1322 const double nan = std::numeric_limits<double>::quiet_NaN();
1324 sn.nodeparam.begin();
1325 ci !=
sn.nodeparam.end(); ++ci) {
1326 const std::size_t ind = ci->first;
1327 if (ind == 0 || ind >
sn.nodes.size())
continue;
1329 const std::size_t isf =
sn.stateful_index(ind);
1330 if (isf == 0)
continue;
1335 cr.
residt.assign(K, nan);
1336 std::vector<double> dly(K, 0.0);
1337 bool any_delayed =
false;
1338 for (std::size_t k = 1; k <= K; ++k) {
1339 if (ci->second.hitclass.size() < k || ci->second.missclass.size() < k)
continue;
1340 const std::size_t h = ci->second.hitclass[k - 1];
1341 const std::size_t mi = ci->second.missclass[k - 1];
1342 if (h == 0 || mi == 0 || h > K || mi > K)
continue;
1343 double th = 0.0, tm = 0.0, td = 0.0;
1344 for (std::size_t s = 0; s < r.
space.size(); ++s) {
1353 cr.
hitprob[k - 1] = std::max(th - td, 0.0) / (th + tm);
1354 cr.
missprob[k - 1] = tm / (th + tm);
1355 dly[k - 1] = td / (th + tm);
1356 if (td > 0) any_delayed =
true;
1360 out.
cache.push_back(cr);
1386 if constexpr (!std::is_same<T, double>::value) {
1413 const std::string& m =
opt.method;
1415 if (m ==
"para" || m ==
"pana" || m ==
"parallel")
1417 "SolverSSA: the '" + m +
1418 "' method runs the serial engine on several workers and averages the replicas "
1419 "(solver_ssa_analyzer_parallel.m). The engine is ported; the replication is not, and "
1420 "one replica has a different variance from the average of many. Use 'serial'");
1422 "' is not a method this entry accepts; it implements 'serial' and the "
1423 "'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.
ReplyBlockInfo reply_block_info(const NetworkStruct< T > &sn, std::size_t ind)
Defined below; the departure branch records a server held for a reply.
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.
bool immfeed_self_loop(const NetworkStruct< T > &sn, const Sync< T > &sy)
True when a synchronization is an IMMEDIATE-FEEDBACK SELF-LOOP: a departure whose passive half is an ...
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.
Conservation laws of a layered queueing network, enumerated from its structure.
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.
Where node ind keeps its reply-block counters inside the local vars.
std::vector< std::size_t > classes
1-based calling classes holding a block
What the cache write-back of solver_ssa_analyzer_serial.m produces (and the NRM's).
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