5#ifndef LINE_SOLVERS_SSA_SOLVER_SSA_SERIAL_H
6#define LINE_SOLVERS_SSA_SOLVER_SSA_SERIAL_H
128 std::vector<std::vector<std::size_t>>
hash;
146 std::vector<std::vector<std::vector<std::size_t>>>
buf;
148 std::vector<double>
pi;
177 std::vector<std::vector<std::vector<double>>>
dly_rates;
232namespace serial_detail {
256 if (!
sn.regions.empty()) {
266 if (!raise)
return false;
276 if (!skip_fork &&
sn.has_fork() && !
sn.isfjaugmented) {
277 if (!raise)
return false;
279 "SolverSSA(method='serial'): the model contains a Fork node and has not been "
280 "tag-augmented. A fork fires through `sn.fjsync` / `State.afterFJEvent`, which only "
281 "`fj_tag` builds; call `solver_ssa_serial_analyzer`, which augments and folds the "
282 "sibling classes back, rather than the engine directly");
284 for (std::size_t i = 0; i <
sn.nstations; ++i) {
285 const typename std::map<std::size_t, qn::RetrialParam<T>>::const_iterator rit =
286 sn.retrialparam.find(i + 1);
287 if (rit ==
sn.retrialparam.end())
continue;
289 std::size_t served = 0;
290 for (std::size_t r = 0; r < rit->second.retrial_proc.size(); ++r) {
291 if (rit->second.retrial_proc[r].disabled)
continue;
294 if (!raise)
return false;
296 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
297 "' retries with non-exponential patience. SOLVER_SSA supports only "
298 "exponential (memoryless) retrial delay in every codebase, because the orbit "
299 "carries no remaining-delay phase");
301 if (r < rit->second.max_attempts.size() && rit->second.max_attempts[r] > 0) {
302 if (!raise)
return false;
304 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
305 "' declares a finite retrial max-attempts count. SOLVER_SSA supports only "
306 "unlimited retrials in every codebase, because the attempt counter is not "
307 "part of the state");
311 for (std::size_t r = 0; r <
sn.nclasses; ++r)
312 if (!
sn.disabled[i][r] &&
sn.service[i][r].D0.rows() > 0) ++served;
314 if (!raise)
return false;
316 "SolverSSA(method='serial'): station '" +
sn.stations[i].name +
317 "' is a multi-class retrial station. SOLVER_SSA supports retrial only for "
318 "single-class stations in every codebase");
343 const std::size_t M =
sn.nstations, K =
sn.nclasses;
345 if (
sn.replyblock.empty())
return held;
346 for (std::size_t ist = 1; ist <= M; ++ist) {
347 const std::size_t ind =
sn.node_of_station(ist);
348 const std::size_t isf =
sn.stateful_of_station(ist);
349 if (isf == 0 ||
sn.replyblock.size() < ind)
continue;
351 for (std::size_t k = 0; k <
sn.replyblock[ind - 1].size(); ++k)
352 any = any ||
sn.replyblock[ind - 1][k];
355 if (info.
width == 0)
continue;
356 for (std::size_t s = 0; s < r.space.size() && s < r.pi.size(); ++s) {
357 if (r.pi[s] == 0)
continue;
358 const std::vector<T>& row = r.space[s].local[isf - 1];
360 for (std::size_t pos = 0; pos < info.
classes.size(); ++pos) {
361 const std::size_t col = row.size() - info.
width + pos;
370bool serial_can_run(
const qn::NetworkStruct<T>& sn) {
371 return serial_check(sn,
false,
true);
394std::size_t max_row_width(
const qn::NetworkStruct<T>& sn, std::size_t ind,
395 const std::vector<std::size_t>& cutoff) {
396 const std::size_t R = sn.nclasses;
397 const std::size_t ist = sn.nodes[ind - 1].station;
398 std::vector<std::size_t> ph(R, 1);
400 for (std::size_t r = 0; r < R; ++r) ph[r] = sn.phasessz_of(ist, r + 1);
408 throw UnsupportedError(
"SolverSSA(method='serial'): node '" + sn.nodes[ind - 1].name +
409 "' admits no state at all, so no sample path can start");
415 std::vector<std::size_t> bound(R, 0);
416 for (std::size_t r = 0; r < R; ++r) {
417 const double nj = sn.njobs()[r];
418 double b = std::isfinite(nj) ? nj :
static_cast<double>(cutoff[r]);
419 const double cc = sn.classcap[ist - 1][r];
422 bound[r] =
static_cast<std::size_t
>(b);
424 double tcapd = sn.cap[ist - 1];
425 std::size_t total = 0;
426 for (std::size_t r = 0; r < R; ++r) total += bound[r];
427 if (std::isfinite(tcapd) && tcapd <
static_cast<double>(total))
428 total =
static_cast<std::size_t
>(tcapd);
438 return total + sn.nvars_of(ind);
440 for (std::size_t t = total + 1; t-- > 0;) {
441 std::vector<std::size_t> n(R, 0);
442 std::size_t left = t;
443 for (std::size_t r = 0; r < R && left > 0; ++r) {
444 n[r] = std::min(left, bound[r]);
447 if (left > 0)
continue;
449 if (!rows.empty())
return rows[0].size();
451 throw UnsupportedError(
"SolverSSA(method='serial'): station '" + sn.stations[ist - 1].name +
452 "' admits no state at all, so no sample path can start");
466qn::NetState<T> wide_init_state(
const qn::NetworkStruct<T>& sn,
467 const std::vector<std::size_t>& cutoff) {
468 qn::NetState<T> init;
469 if (!ctmc::analyzer_detail::default_init_state(sn, init))
471 "SolverSSA(method='serial'): the model's initial state admits no state; check the "
472 "class populations against their reference stations");
473 for (std::size_t f = 0; f < sn.stateful_nodes.size(); ++f) {
474 const std::size_t w = max_row_width(sn, sn.stateful_nodes[f], cutoff);
475 if (init.local[f].size() > w)
continue;
476 init.local[f].insert(init.local[f].begin(), w - init.local[f].size(),
477 num_traits<T>::from_int(0));
503 serial_detail::serial_check(
sn);
507 const std::vector<std::size_t> cutoff = ctmc::analyzer_detail::resolve_cutoff(
sn, copt);
520 "solver_ssa_reachability: the model declares a WAITQ finite capacity region, whose "
521 "states carry a per-region token FIFO outside every node; this decomposition is "
522 "per-node and cannot represent it. Use `solver_ssa_serial_analyzer`, whose run carries "
523 "the FIFO, or `solver_ctmc_waitq` for the exact augmented space");
534 const std::size_t NF =
sn.stateful_nodes.size();
535 out.
node_space.assign(NF, std::vector<std::vector<T>>());
536 out.
hash.assign(out.
space.size(), std::vector<std::size_t>(NF, 0));
540 std::vector<std::map<std::vector<double>, std::size_t>> seen(NF);
541 for (std::size_t s = 0; s < out.
space.size(); ++s)
542 for (std::size_t f = 0; f < NF; ++f) {
543 std::vector<double> key(out.
space[s].local[f].size(), 0.0);
544 for (std::size_t j = 0; j < key.size(); ++j)
546 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
548 if (it != seen[f].end()) {
549 out.
hash[s][f] = it->second;
557 std::size_t width = 0;
558 for (std::size_t f = 0; f < NF; ++f)
559 width += out.
space.empty() ? 0 : out.
space[0].local[f].size();
561 for (std::size_t s = 0; s < out.
space.size(); ++s) {
563 for (std::size_t f = 0; f < NF; ++f)
564 for (std::size_t j = 0; j < out.
space[s].local[f].size(); ++j)
565 out.
ssq(s, col++) = out.
space[s].local[f][j];
589 : sn_(
sn), opt_(
opt), rng_(
opt.seed), fjsync_(fjsync) {
590 serial_detail::serial_check(
sn);
594 const std::vector<std::size_t> cutoff = ctmc::analyzer_detail::resolve_cutoff(
sn, copt);
597 sdr_ =
sn.has_sdr_routing();
606 if (!
sn.regions.empty()) {
608 caps_ = ctmc::waitq_detail::extract_caps(
sn);
609 ctmc::waitq_detail::resolve_lmax(
sn, cutoff, caps_);
612 init_.net = serial_detail::wide_init_state(
sn, cutoff);
613 init_.buf.assign(caps_.size(), std::vector<std::size_t>());
614 if (!
sn.regions.empty() && !region_admissible(init_.net))
616 "SolverSSA(method='serial'): the model's initial state violates a finite capacity "
617 "region; the region cannot hold the model's initial population, so no sample path "
625 const std::vector<qn::Sync<T>>&
sync()
const {
return sync_; }
632 std::size_t
sync = 0;
638 SsaSerialOptions opt_;
640 std::vector<qn::Sync<T>> sync_;
641 std::vector<qn::GlobalSync<T>> gsync_;
642 std::vector<qn::FjSync<T>> fjsync_;
643 std::vector<ctmc::waitq_detail::RegionCaps<T>> caps_;
651 if (sn_.
regions.empty())
return true;
652 std::vector<qn::NetState<T>> one(1, st);
654 std::vector<T> nir(A.
cols());
655 for (std::size_t c = 0; c < A.
cols(); ++c) nir[c] = A(0, c);
663 void enabled(
const ctmc::WaitqState<T>& st, std::vector<Move>& moves,
664 std::vector<std::vector<double>>& arv, std::vector<std::vector<double>>& dep,
665 std::vector<std::vector<double>>& dly,
666 std::vector<std::vector<double>>& start,
667 std::vector<std::vector<double>>& preempt)
const;
673 std::vector<T> gd_factor_now(
const qn::NetState<T>& ns)
const {
675 const T zero = num_traits<T>::from_int(0);
676 std::vector<T> npop(M * K, zero);
677 for (std::size_t ist = 1; ist <= M; ++ist) {
679 if (isf == 0)
continue;
682 std::vector<std::size_t> ph(K, 1), shift(K, 0);
684 for (std::size_t k = 0; k < K; ++k) {
689 const qn::Marginal<T> m =
690 qn::to_marginal(sn_, ist, ns.local[isf - 1], ph, shift, sn_.nvars_of(ind));
691 for (std::size_t k = 0; k < K; ++k) npop[(ist - 1) * K + k] = m.nir[k];
693 const std::vector<T> v = sn_.gdscaling(npop);
694 std::vector<T> out(M * K, num_traits<T>::from_int(1));
695 for (std::size_t i = 0; i < M; ++i)
696 for (std::size_t r = 0; r < K; ++r) {
697 const T f = v.size() == 1 ? v[0] : (v.size() == M ? v[i] : v[i * K + r]);
698 if (!(num_traits<T>::to_double(f) >= 0))
700 "the global dependence handle returned a non-finite or negative scaling");
708void SsaSerialEngine<T>::enabled(
const ctmc::WaitqState<T>& st, std::vector<Move>& moves,
709 std::vector<std::vector<double>>& arv,
710 std::vector<std::vector<double>>& dep,
711 std::vector<std::vector<double>>& dly,
712 std::vector<std::vector<double>>& start,
713 std::vector<std::vector<double>>& preempt)
const {
714 const std::size_t local = sn_.
nodes.size() + 1;
717 for (std::size_t f = 0; f < arv.size(); ++f)
718 for (std::size_t r = 0; r < R; ++r) {
735 std::vector<ctmc::waitq_detail::Successor<T>> succ;
736 ctmc::waitq_detail::waitq_successors(sn_, sync_, caps_, st, succ);
737 for (std::size_t i = 0; i < succ.size(); ++i) {
738 const ctmc::waitq_detail::Successor<T>& su = succ[i];
739 const double w = num_traits<T>::to_double(su.w);
740 if (!(w > 0))
continue;
746 if (su.dep_isf != 0) dep[su.dep_isf - 1][su.dep_cls - 1] += w;
747 for (std::size_t q = 0; q < su.arv.size(); ++q)
748 arv[su.arv[q].first - 1][su.arv[q].second - 1] += w;
753 const qn::NetState<T>& base = st.net;
759 std::vector<T> gd_now;
760 const bool has_gd =
static_cast<bool>(sn_.
gdscaling);
761 if (has_gd) gd_now = gd_factor_now(base);
768 for (std::size_t a = 0; a < sync_.size(); ++a) {
769 const qn::Sync<T>& sy = sync_[a];
771 if (isf_a == 0)
continue;
772 const std::size_t isf_p =
773 sy.passive.node == local ? 0 : sn_.
stateful_index(sy.passive.node);
774 if (sy.passive.node != local && isf_p == 0)
continue;
780 const qn::EventOutcome<T> oa =
781 qn::after_event(sn_, sy.active.node, base.local[isf_a - 1], sy.active.event,
784 const bool gd_here = has_gd && sn_.
nodes[sy.active.node - 1].station != 0 &&
788 gd_here ? num_traits<T>::to_double(
789 gd_now[(sn_.
nodes[sy.active.node - 1].station - 1) * R +
790 (sy.active.cls - 1)])
798 double srv_pre = 0.0;
800 for (std::size_t r = 0; r < R && r < base.local[isf_a - 1].size(); ++r)
801 srv_pre += num_traits<T>::to_double(base.local[isf_a - 1][r]);
804 for (std::size_t ia = 0; ia < oa.space.size(); ++ia) {
805 const double rate = num_traits<T>::to_double(oa.rate[ia]) * gd_f;
806 const double pa = num_traits<T>::to_double(oa.prob[ia]);
807 if (!(rate > 0) || !(pa > 0))
continue;
810 double srv_post = 0.0;
811 for (std::size_t r = 0; r < R && r < oa.space[ia].size(); ++r)
812 srv_post += num_traits<T>::to_double(oa.space[ia][r]);
813 merged = srv_post - srv_pre == -1.0;
816 if (sy.passive.node == local) {
819 m.weight = rate * pa;
821 m.next.net.local[isf_a - 1] = oa.space[ia];
827 if (!region_admissible(m.next.net))
continue;
829 if (merged) dly[isf_a - 1][sy.active.cls - 1] += m.weight;
834 ssa_detail::add_tag_rates(start, preempt, sn_, sy.active.node, oa, ia, m.weight);
840 const std::vector<T>& src =
841 sy.passive.node == sy.active.node ? oa.space[ia] : base.local[isf_p - 1];
842 const qn::EventOutcome<T> op =
qn::after_event(sn_, sy.passive.node, src,
843 sy.passive.event, sy.passive.cls);
852 ? num_traits<T>::to_double(rt_now(sy.passive.rt_row, sy.passive.rt_col))
853 : num_traits<T>::to_double(sy.passive.prob);
860 sn_.
rr_var_slot(sy.active.node, sy.active.cls) != 0) {
861 const std::size_t w = sn_.nvars_of(sy.active.node);
862 const std::vector<T>& arow = oa.space[ia];
863 std::size_t dest = 0;
864 if (arow.size() >= w) {
865 const std::vector<T> var(arow.end() - w, arow.end());
866 dest = sn_.rr_dest(sy.active.node, sy.active.cls, var);
868 proute = (dest == sy.passive.node && sy.passive.cls == sy.active.cls) ? 1.0 : 0.0;
870 for (std::size_t ip = 0; ip < op.space.size(); ++ip) {
871 const double pp = num_traits<T>::to_double(op.prob[ip]);
872 if (!(pp > 0))
continue;
875 m.weight = rate * pa * proute * pp;
876 if (!(m.weight > 0))
continue;
878 m.next.net.local[isf_a - 1] = oa.space[ia];
879 m.next.net.local[isf_p - 1] = op.space[ip];
880 if (!region_admissible(m.next.net))
continue;
882 if (merged) dly[isf_a - 1][sy.active.cls - 1] += m.weight;
885 ssa_detail::add_tag_rates(start, preempt, sn_, sy.active.node, oa, ia, m.weight);
886 ssa_detail::add_tag_rates(start, preempt, sn_, sy.passive.node, op, ip, m.weight);
895 dep[isf_a - 1][sy.active.cls - 1] += fired;
896 if (isf_p != 0) arv[isf_p - 1][sy.passive.cls - 1] += fired;
900 for (std::size_t g = 0; g < gsync_.size(); ++g) {
902 for (std::size_t io = 0; io < go.space.size(); ++io) {
903 const double w = num_traits<T>::to_double(go.rate[io]) *
904 num_traits<T>::to_double(go.prob[io]);
905 if (!(w > 0))
continue;
907 m.sync = sync_.size() + g;
910 m.next.net = go.space[io];
911 if (!region_admissible(m.next.net))
continue;
917 for (std::size_t j = 0; j < gsync_[g].passive.size(); ++j) {
918 const qn::ModeEvent<T>& pev = gsync_[g].passive[j];
920 if (pisf == 0 || pev.cls == 0 || pev.cls > R)
continue;
936 for (std::size_t k = 0; k < fjsync_.size(); ++k) {
937 const qn::FjSync<T>& e = fjsync_[k];
939 if (isf_f == 0)
continue;
942 for (std::size_t io = 0; io < fo.space.size(); ++io) {
943 const double w = num_traits<T>::to_double(fo.rate[io]) *
944 num_traits<T>::to_double(fo.prob[io]);
945 if (!(w > 0))
continue;
947 m.sync = sync_.size() + gsync_.size() + k;
950 m.next.net = fo.space[io];
951 if (!region_admissible(m.next.net))
continue;
955 if (!(fired > 0))
continue;
956 dep[isf_f - 1][e.cls - 1] += fired;
957 for (std::size_t b = 0; b < e.branchheads.size(); ++b) {
959 if (isf_b != 0) arv[isf_b - 1][e.auxclasses[b] - 1] += fired;
972 "solver_ssa_serial: an SSA sample path is generated from exponential clocks "
973 "drawn as -log(u)/rate, which needs transcendental arithmetic");
975 const std::size_t NF = sn_.stateful_nodes.size();
976 const std::size_t R = sn_.nclasses;
978 out.
seed = opt_.seed;
979 out.
warmup =
static_cast<std::size_t
>(
980 std::floor(std::max(0.0, std::min(0.99, opt_.warmupfrac)) *
981 static_cast<double>(opt_.samples)));
984 std::vector<Move> moves;
985 std::vector<std::vector<double>> arv(NF, std::vector<double>(R, 0.0));
986 std::vector<std::vector<double>> dep(NF, std::vector<double>(R, 0.0));
987 std::vector<std::vector<double>> dly(NF, std::vector<double>(R, 0.0));
991 std::vector<std::vector<double>> start(NF, std::vector<double>(R, 0.0));
992 std::vector<std::vector<double>> preempt(NF, std::vector<double>(R, 0.0));
993 std::vector<double> weights;
994 std::map<std::vector<double>, std::size_t> index;
996 double cur_time = 0.0;
999 for (std::size_t n = 0; n < opt_.samples; ++n) {
1000 enabled(cur, moves, arv, dep, dly, start, preempt);
1003 "solver_ssa_serial: the sample path entered a deadlock before collecting all "
1004 "samples, no synchronization is enabled");
1006 weights.resize(moves.size());
1008 for (std::size_t i = 0; i < moves.size(); ++i) {
1009 weights[i] = moves[i].weight;
1012 const std::size_t sel = rng_.draw(weights);
1016 const double dt = -std::log(rng_.uniform()) / tot;
1028 const std::vector<double> key = ctmc::waitq_detail::waitq_key(cur);
1030 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
1032 if (it != index.end()) {
1035 si = out.
space.size();
1038 out.
buf.push_back(cur.
buf);
1039 out.
pi.push_back(0.0);
1058 cur = moves[sel].next;
1062 double tot_pi = 0.0;
1063 for (std::size_t s = 0; s < out.
pi.size(); ++s) tot_pi += out.
pi[s];
1065 for (std::size_t s = 0; s < out.
pi.size(); ++s) out.
pi[s] /= tot_pi;
1070namespace serial_detail {
1076 if (d.
disabled || d.
D0.rows() == 0)
return -1.0;
1082 }
catch (
const Error&) {
1123 if constexpr (!std::is_same<T, double>::value) {
1127 "solver_ssa_serial: an SSA sample path is generated from exponential clocks, which "
1128 "are logarithms of uniform draws; there is no exact value to compute and a wider "
1129 "float carries no information the Monte Carlo error does not swamp. Rerun with "
1138 const std::size_t M =
sn.nstations, K =
sn.nclasses;
1141 if (mapc) out.
solved_sn = std::make_shared<const qn::NetworkStruct<T>>(
sn);
1144 out.
run = eng.run();
1149 out.
parked.assign(K, 0.0);
1150 for (std::size_t s = 0; s < r.
buf.size() && s < r.
pi.size(); ++s)
1151 for (std::size_t f = 0; f < r.
buf[s].size(); ++f)
1152 for (std::size_t j = 0; j < r.
buf[s][f].size(); ++j) {
1153 const std::size_t cls = (r.
buf[s][f][j] - 1) % K + 1;
1165 a.
XN.assign(K, 0.0);
1166 a.
CN.assign(K, 0.0);
1172 for (std::size_t k = 1; k <= K; ++k) {
1173 const std::size_t refsf =
sn.stateful_of_station(
sn.classes[k - 1].refstat);
1174 if (refsf == 0)
continue;
1175 for (std::size_t s = 0; s < r.
space.size(); ++s)
1185 bool scaled =
static_cast<bool>(
sn.gdscaling);
1186 for (std::size_t i = 0; i < M; ++i)
1187 if (!
sn.stations[i].lldscaling.empty() ||
sn.stations[i].cdscaling) scaled =
true;
1189 for (std::size_t ist = 1; ist <= M; ++ist) {
1190 const std::size_t isf =
sn.stateful_of_station(ist);
1191 if (isf == 0)
continue;
1192 const SchedStrategy sched =
sn.stations[ist - 1].sched;
1193 const double S =
sn.stations[ist - 1].nservers;
1194 for (std::size_t k = 1; k <= K; ++k) {
1195 for (std::size_t s = 0; s < r.
space.size(); ++s) {
1196 a.
TN(ist - 1, k - 1) += r.
pi[s] * r.
dep_rates[s][isf - 1][k - 1];
1197 a.
QN(ist - 1, k - 1) +=
1207 const bool is_ps = sched == SchedStrategy::PS || sched == SchedStrategy::DPS ||
1208 sched == SchedStrategy::GPS || sched == SchedStrategy::LPS;
1218 sched == SchedStrategy::EXT)
1220 if (sched == SchedStrategy::INF) {
1221 for (std::size_t k = 1; k <= K; ++k) a.
UN(ist - 1, k - 1) = a.
QN(ist - 1, k - 1);
1230 const std::vector<T>& lld =
sn.stations[ist - 1].lldscaling;
1231 for (std::size_t j = 0; j < lld.size(); ++j)
1233 const bool is_cd =
static_cast<bool>(
sn.stations[ist - 1].cdscaling);
1234 const bool is_jd =
static_cast<bool>(
sn.stations[ist - 1].jdscaling);
1237 const bool is_gd =
static_cast<bool>(
sn.gdscaling);
1238 std::vector<T> gdpk;
1240 gdpk.assign(
sn.gdscalingpeak.begin() + (ist - 1) * K,
1241 sn.gdscalingpeak.begin() + ist * K);
1242 for (std::size_t k = 1; k <= K; ++k) {
1243 const double mean = serial_detail::service_mean(
sn, ist, k);
1244 if (mean < 0)
continue;
1251 if (is_cd || is_jd || is_gd) {
1253 const std::vector<T>* pks[3] = {&
sn.stations[ist - 1].cdscalingpeak,
1254 &
sn.stations[ist - 1].jdscalingpeak,
1256 const char* names[3] = {
"setClassDependence",
"setJointDependence",
1257 "setGlobalDependence"};
1258 const bool on[3] = {is_cd, is_jd, is_gd};
1259 for (std::size_t h = 0; h < 3; ++h) {
1260 if (!on[h])
continue;
1261 const std::vector<T>& pk = *pks[h];
1264 "SolverSSA(method='serial'): station '" +
1265 sn.stations[ist - 1].name +
1266 "' declares a dependent scaling with no declared peak rate. "
1267 "Utilization there is T*E[S]/peak, so pass the peak to " +
1272 a.
UN(ist - 1, k - 1) = cdiv > 0 ? a.
TN(ist - 1, k - 1) * mean / cdiv : 0.0;
1280 for (std::size_t k = 1; k <= K; ++k) {
1281 const double mean = serial_detail::service_mean(
sn, ist, k);
1282 if (mean < 0)
continue;
1283 const bool can_drop = !std::isfinite(
sn.njobs()[k - 1]) &&
1284 (std::isfinite(
sn.cap[ist - 1]) ||
1285 std::isfinite(
sn.classcap[ist - 1][k - 1]));
1288 for (std::size_t s = 0; s < r.
space.size(); ++s)
1292 if (!(mu > 0))
continue;
1293 a.
UN(ist - 1, k - 1) =
1294 (can_drop ? a.
TN(ist - 1, k - 1) / mu : arv / mu) / S;
1296 a.
UN(ist - 1, k - 1) =
1297 (can_drop ? a.
TN(ist - 1, k - 1) * mean : arv * mean) / S;
1307 for (std::size_t ist = 1; ist <= M; ++ist) {
1308 const double S =
sn.stations[ist - 1].nservers;
1309 for (std::size_t k = 1; k <= K; ++k) {
1310 if (held(ist - 1, k - 1) == 0)
continue;
1311 a.
QN(ist - 1, k - 1) += held(ist - 1, k - 1);
1312 a.
UN(ist - 1, k - 1) += held(ist - 1, k - 1) / S;
1317 for (std::size_t k = 1; k <= K; ++k) {
1318 for (std::size_t ist = 1; ist <= M; ++ist)
1319 a.
RN(ist - 1, k - 1) =
1320 a.
TN(ist - 1, k - 1) > 0
1321 ? (a.
QN(ist - 1, k - 1) - held(ist - 1, k - 1)) / a.
TN(ist - 1, k - 1)
1326 if (a.
XN[k - 1] > 0) a.
CN[k - 1] =
sn.classes[k - 1].population / a.
XN[k - 1];
1334 const double nan = std::numeric_limits<double>::quiet_NaN();
1336 sn.nodeparam.begin();
1337 ci !=
sn.nodeparam.end(); ++ci) {
1338 const std::size_t ind = ci->first;
1339 if (ind == 0 || ind >
sn.nodes.size())
continue;
1341 const std::size_t isf =
sn.stateful_index(ind);
1342 if (isf == 0)
continue;
1347 cr.
residt.assign(K, nan);
1348 std::vector<double> dly(K, 0.0);
1349 bool any_delayed =
false;
1350 for (std::size_t k = 1; k <= K; ++k) {
1351 if (ci->second.hitclass.size() < k || ci->second.missclass.size() < k)
continue;
1352 const std::size_t h = ci->second.hitclass[k - 1];
1353 const std::size_t mi = ci->second.missclass[k - 1];
1354 if (h == 0 || mi == 0 || h > K || mi > K)
continue;
1355 double th = 0.0, tm = 0.0, td = 0.0;
1356 for (std::size_t s = 0; s < r.
space.size(); ++s) {
1365 cr.
hitprob[k - 1] = std::max(th - td, 0.0) / (th + tm);
1366 cr.
missprob[k - 1] = tm / (th + tm);
1367 dly[k - 1] = td / (th + tm);
1368 if (td > 0) any_delayed =
true;
1372 out.
cache.push_back(cr);
1398 if constexpr (!std::is_same<T, double>::value) {
1425 const std::string& m =
opt.method;
1427 if (m ==
"para" || m ==
"pana" || m ==
"parallel")
1429 "SolverSSA: the '" + m +
1430 "' method runs the serial engine on several workers and averages the replicas "
1431 "(solver_ssa_analyzer_parallel.m). The engine is ported; the replication is not, and "
1432 "one replica has a different variance from the average of many. Use 'serial'");
1434 "' is not a method this entry accepts; it implements 'serial' and the "
1435 "'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...
bool ctmc_mapc(const qn::NetworkStruct< T > &in, qn::NetworkStruct< T > &out)
Rewrite a copy of in into out when it has a MAP service at an FCFS station; false,...
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_arg, 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".
std::shared_ptr< const qn::NetworkStruct< T > > solved_sn
The struct the path was simulated on when it differs from the caller's (ctmc_mapc widens a multiserve...
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