5#ifndef LINE_SOLVERS_SSA_SOLVER_SSA_NRM_H
6#define LINE_SOLVERS_SSA_SOLVER_SSA_NRM_H
111 std::size_t node = 0;
113 std::size_t from = 0;
115 bool is_phase =
false;
116 std::size_t phase_from = 0, phase_to = 0;
117 std::size_t dep_phase = 0;
118 bool is_buf_svc =
false;
121 std::vector<std::size_t> to_slots;
122 std::vector<double> cdf;
124 std::vector<std::size_t> from_slots;
125 std::size_t nnzP = 0;
127 std::size_t det_dest =
static_cast<std::size_t
>(-1);
145 std::vector<std::size_t> sd_node, sd_class;
147 std::vector<double> sd_weight;
149 std::vector<std::vector<std::size_t>> sd_slots;
150 std::vector<std::vector<double>> sd_pie;
168 : sn_(
sn), opt_(
opt), rng_(
opt.seed) {
171 rr_cursor_.assign(I_ * K_ + K_ + 1, 0);
172 build_dependencies();
173 build_initial_state();
186 using Rx = detail::NrmReaction;
193 std::size_t I_ = 0, K_ = 0, M_ = 0, NS_ = 0, maxnph_ = 1;
196 std::vector<std::vector<std::size_t>> phoff_, nph_;
197 std::vector<std::size_t> slot_node_, slot_class_;
200 std::vector<bool> is_station_;
201 std::vector<std::size_t> to_station_;
202 std::vector<double> mi_;
203 std::vector<std::vector<double>> rate_;
204 std::vector<std::vector<std::vector<double>>> pie_;
205 std::vector<std::vector<bool>> buf_ph_class_;
206 std::vector<bool> buf_ph_node_;
207 std::vector<SchedStrategy> sched_;
208 std::vector<std::vector<double>> lld_;
209 std::vector<std::vector<double>> wnorm_;
210 std::vector<double> classprio_;
211 std::vector<double> nservers_;
216 std::vector<std::vector<std::size_t>> D_;
218 std::vector<std::vector<std::vector<std::size_t>>> dep_rx_;
223 std::vector<std::vector<char>> blk_can_;
224 bool blk_on_ =
false;
227 std::vector<double> nvec0_;
228 std::vector<std::vector<std::size_t>> buffers0_;
229 std::vector<Matrix<double>> svcph0_;
253 void build_reactions();
254 void build_dependencies();
255 void build_initial_state();
258 double class_pop(
const std::vector<double>& X, std::size_t ind, std::size_t r)
const {
260 for (std::size_t k = 0; k < nph_[ind][r]; ++k) s += X[phoff_[ind][r] + k];
263 std::vector<double> class_counts(
const std::vector<double>& X, std::size_t ind)
const {
264 std::vector<double> v(K_, 0.0);
265 for (std::size_t r = 0; r < K_; ++r) v[r] = class_pop(X, ind, r);
268 double kir_frac(
const std::vector<double>& X, std::size_t slot, std::size_t ind,
269 std::size_t r)
const {
270 const double nir = class_pop(X, ind, r);
271 return nir <= 0.0 ? 0.0 : X[slot] / nir;
273 double lldfac(std::size_t ist,
double ntot)
const {
274 if (ist == npos || lld_[ist].empty() || ntot < 1.0)
return 1.0;
275 const std::size_t lim = lld_[ist].size();
276 std::size_t k =
static_cast<std::size_t
>(std::llround(ntot));
277 if (k > lim) k = lim;
278 return lld_[ist][k - 1];
280 static double dpsshare(
const std::vector<double>& w,
const std::vector<double>& n,
283 for (std::size_t s = 0; s < n.size(); ++s) den += w[s] * n[s];
284 return den <= 0.0 ? 0.0 : w[r] * n[r] / den;
286 static double gpsshare(
const std::vector<double>& w,
const std::vector<double>& n,
288 if (n[r] <= 0.0)
return 0.0;
290 for (std::size_t s = 0; s < n.size(); ++s) den += n[s] > 0.0 ? w[s] : 0.0;
291 return den <= 0.0 ? 0.0 : w[r] / den;
306 bool urgent(
const std::vector<double>& n, std::size_t r)
const {
309 for (std::size_t s = 0; s < n.size(); ++s)
310 if (n[s] > 0.0 && (!any || classprio_[s] < best)) {
311 best = classprio_[s];
314 return any && classprio_[r] == best;
317 std::vector<double> prio_group(
const std::vector<double>& n, std::size_t r,
318 double* total =
nullptr)
const {
319 std::vector<double> act(n.size(), 0.0);
321 for (std::size_t s = 0; s < n.size(); ++s)
322 if (classprio_[s] == classprio_[r]) {
326 if (total) *total = tot;
329 double psprioshare(
const std::vector<double>& n, std::size_t r,
double c)
const {
331 for (
double v : n) ni += v;
332 if (ni <= 0.0)
return 0.0;
333 if (ni <= c)
return (n[r] / ni) * std::min(ni, c);
334 if (!urgent(n, r))
return 0.0;
336 prio_group(n, r, &niprio);
337 return niprio <= 0.0 ? 0.0 : (n[r] / niprio) * std::min(niprio, c);
339 double dpsprioshare(
const std::vector<double>& w,
const std::vector<double>& n, std::size_t r,
342 for (
double v : n) ni += v;
343 if (ni <= 0.0)
return 0.0;
344 if (ni <= c)
return dpsshare(w, n, r);
345 if (!urgent(n, r))
return 0.0;
346 return dpsshare(w, prio_group(n, r), r);
348 double gpsprioshare(
const std::vector<double>& w,
const std::vector<double>& n, std::size_t r,
351 for (
double v : n) ni += v;
352 if (ni <= 0.0)
return 0.0;
353 if (ni <= c)
return gpsshare(w, n, r);
354 if (!urgent(n, r))
return 0.0;
355 return gpsshare(w, prio_group(n, r), r);
366 double prio_pop(
const std::vector<double>& n, std::size_t r,
double c)
const {
368 for (
double v : n) ni += v;
369 if (ni <= c || !urgent(n, r))
return ni;
371 prio_group(n, r, &niprio);
376 double propensity(std::size_t j,
const std::vector<double>& X,
377 const std::vector<std::vector<std::size_t>>& bufs,
378 const std::vector<Matrix<double>>& svc)
const;
379 std::size_t pick_from_buffer(
const std::vector<std::size_t>& buf, std::size_t ist);
380 std::size_t draw_entry_phase(std::size_t ind, std::size_t r);
381 bool capacity_loss(
const std::vector<double>& X, std::size_t slot)
const;
382 bool capacity_block(
const std::vector<double>& X, std::size_t slot,
383 std::size_t src_slot)
const;
384 std::size_t pick_preempted(
const std::vector<double>& X,
385 const std::vector<std::size_t>& buf, std::size_t jnd,
386 std::size_t arr_class);
387 void apply_arrival_buffer(std::size_t jnd, std::size_t s,
const std::vector<double>& X,
388 std::vector<std::vector<std::size_t>>& bufs,
389 std::vector<Matrix<double>>& svc,
bool& svc_changed);
390 void update_buffers(std::size_t kfire,
const std::vector<double>& X,
391 std::vector<std::vector<std::size_t>>& bufs, std::size_t dest_pos,
392 bool have_dest, std::vector<Matrix<double>>& svc,
bool& svc_changed);
395 void build_state_dependent_dest(Rx& x);
397 std::size_t resolve_state_dependent_dest(std::size_t kfire,
const std::vector<double>& X);
405 std::vector<std::size_t> rr_cursor_;
408 void tag(std::size_t ind, std::size_t r,
bool preempt) {
409 if (!is_station_[ind] || r >= K_)
return;
410 const std::size_t ist = to_station_[ind];
411 if (ist >= M_)
return;
414 if (sched_[ist] == SchedStrategy::EXT)
return;
415 (preempt ? preempt_cnt_ : start_cnt_)(ist, r) += 1.0;
418 static constexpr std::size_t npos =
static_cast<std::size_t
>(-1);
435void NrmEngine<T>::build_layout() {
440 is_station_.assign(I_,
false);
441 to_station_.assign(I_, npos);
442 for (std::size_t i = 0; i < I_; ++i)
443 if (sn_.
nodes[i].station != 0) {
444 is_station_[i] =
true;
445 to_station_[i] = sn_.
nodes[i].station - 1;
448 sched_.assign(M_, SchedStrategy::FCFS);
449 nservers_.assign(M_, 1.0);
450 for (std::size_t i = 0; i < M_; ++i) {
452 nservers_[i] = sn_.
stations[i].nservers;
457 nph_.assign(I_, std::vector<std::size_t>(K_, 1));
458 pie_.assign(I_, std::vector<std::vector<double>>(K_));
459 for (std::size_t i = 0; i < I_; ++i) {
460 for (std::size_t r = 0; r < K_; ++r) {
461 pie_[i][r].assign(1, 1.0);
462 if (!is_station_[i])
continue;
463 const std::size_t ist = to_station_[i];
466 const std::size_t n = m.D0.rows();
467 if (n <= 1)
continue;
470 std::vector<double> pd(n, 0.0);
472 for (std::size_t k = 0; k < n && k < p.size(); ++k) {
473 pd[k] = num_traits<T>::to_double(p[k]);
474 if (!(pd[k] > 0.0)) pd[k] = 0.0;
478 for (
double& x : pd) x /= tot;
487 phoff_.assign(I_, std::vector<std::size_t>(K_, 0));
490 for (std::size_t i = 0; i < I_; ++i)
491 for (std::size_t r = 0; r < K_; ++r) {
494 maxnph_ = std::max(maxnph_, nph_[i][r]);
496 slot_node_.assign(NS_, 0);
497 slot_class_.assign(NS_, 0);
498 for (std::size_t i = 0; i < I_; ++i)
499 for (std::size_t r = 0; r < K_; ++r)
500 for (std::size_t k = 0; k < nph_[i][r]; ++k) {
501 slot_node_[phoff_[i][r] + k] = i;
502 slot_class_[phoff_[i][r] + k] = r;
507 buf_ph_class_.assign(I_, std::vector<bool>(K_,
false));
508 buf_ph_node_.assign(I_,
false);
509 for (std::size_t i = 0; i < I_; ++i) {
510 if (!is_station_[i] || !detail::sched_is_buf_ph(sched_[to_station_[i]]))
continue;
511 for (std::size_t r = 0; r < K_; ++r)
512 if (nph_[i][r] > 1) {
513 buf_ph_class_[i][r] =
true;
514 buf_ph_node_[i] =
true;
519 const double inf = std::numeric_limits<double>::infinity();
521 rate_.assign(I_, std::vector<double>(K_, 0.0));
522 for (std::size_t i = 0; i < I_; ++i) {
523 if (is_station_[i]) {
524 const std::size_t ist = to_station_[i];
525 mi_[i] = sn_.
stations[ist].nservers;
526 for (std::size_t r = 0; r < K_; ++r)
528 rate_[i][r] = num_traits<T>::to_double(sn_.
rates(ist, r));
530 for (std::size_t r = 0; r < K_; ++r)
535 lld_.assign(M_, std::vector<double>());
536 wnorm_.assign(M_, std::vector<double>(K_, 0.0));
537 for (std::size_t i = 0; i < M_; ++i) {
538 for (
const T& x : sn_.
stations[i].lldscaling)
539 lld_[i].push_back(num_traits<T>::to_double(x));
540 if (!detail::sched_is_weighted(sched_[i]))
continue;
542 for (std::size_t r = 0; r < K_ && r < sn_.
stations[i].schedparam.size(); ++r)
543 tot += num_traits<T>::to_double(sn_.
stations[i].schedparam[r]);
547 " scheduling with non-positive total weight");
551 " at station '" + sn_.
stations[i].name +
552 "' is not supported; the reference's State.afterEventStation rejects it too");
553 for (std::size_t r = 0; r < K_ && r < sn_.
stations[i].schedparam.size(); ++r)
554 wnorm_[i][r] = num_traits<T>::to_double(sn_.
stations[i].schedparam[r]) / tot;
557 classprio_.assign(K_, 0.0);
558 for (std::size_t r = 0; r < K_; ++r) classprio_[r] = sn_.
classes[r].prio;
571void NrmEngine<T>::build_reactions() {
574 for (std::size_t ind = 0; ind < I_; ++ind) {
575 for (std::size_t r = 0; r < K_; ++r) {
576 for (std::size_t kk = 0; kk < nph_[ind][r]; ++kk) {
581 x.is_buf_svc = buf_ph_class_[ind][r];
585 x.from = buf_ph_class_[ind][r] ? phoff_[ind][r] : phoff_[ind][r] + kk;
586 x.rate = rate_[ind][r];
587 if (is_station_[ind] && nph_[ind][r] > 1) {
588 const std::size_t ist = to_station_[ind];
591 for (std::size_t c = 0; c < m.D1.cols(); ++c)
592 s += num_traits<T>::to_double(m.D1(kk, c));
599 const std::size_t ndep = rx_.size();
602 for (std::size_t ind = 0; ind < I_; ++ind) {
603 if (!is_station_[ind])
continue;
604 const std::size_t ist = to_station_[ind];
605 for (std::size_t r = 0; r < K_; ++r) {
606 if (nph_[ind][r] <= 1 || sn_.
disabled[ist][r])
continue;
608 for (std::size_t ka = 0; ka < nph_[ind][r]; ++ka)
609 for (std::size_t kb = 0; kb < nph_[ind][r]; ++kb) {
610 if (ka == kb)
continue;
611 const double d = num_traits<T>::to_double(m.D0(ka, kb));
612 if (!(d > 0.0))
continue;
623 x.from = buf_ph_class_[ind][r] ? phoff_[ind][r] : phoff_[ind][r] + ka;
630 S_ = Matrix<double>(NS_, rx_.size(), 0.0);
631 for (std::size_t k = 0; k < rx_.size(); ++k) {
634 if (!buf_ph_class_[x.node][x.cls]) {
635 S_(phoff_[x.node][x.cls] + x.phase_from, k) -= 1.0;
636 S_(phoff_[x.node][x.cls] + x.phase_to, k) += 1.0;
640 S_(x.from, k) -= 1.0;
641 for (std::size_t jnd = 0; jnd < I_; ++jnd)
642 for (std::size_t s = 0; s < K_; ++s) {
644 num_traits<T>::to_double(sn_.
rtnodes(x.node * K_ + x.cls, jnd * K_ + s));
645 if (!(p > 0.0))
continue;
646 if (buf_ph_class_[jnd][s]) {
650 S_(phoff_[jnd][s], k) += p;
652 for (std::size_t ke = 0; ke < nph_[jnd][s]; ++ke) {
653 const double pe = pie_[jnd][s][ke];
654 if (!(pe > 0.0))
continue;
655 S_(phoff_[jnd][s] + ke, k) += p * pe;
665 for (std::size_t k = 0; k < rx_.size(); ++k) {
667 std::vector<double> Pcol(NS_, 0.0);
668 for (std::size_t i = 0; i < NS_; ++i) {
669 const double v = S_(i, k);
670 Pcol[i] = v < 0.0 ? v + 1.0 : v;
671 if (v < 0.0) x.from_slots.push_back(i);
672 if (v > 0.0 && x.det_dest == npos) x.det_dest = i;
674 for (std::size_t i = 0; i < NS_; ++i)
675 if (Pcol[i] != 0.0) ++x.nnzP;
678 for (std::size_t i = 0; i < NS_; ++i)
679 if (Pcol[i] != 0.0) {
680 x.to_slots.push_back(i);
682 x.cdf.push_back(acc);
685 if (!x.is_phase) build_state_dependent_dest(x);
690 dep_rx_.assign(M_, std::vector<std::vector<std::size_t>>(K_));
691 for (std::size_t k = 0; k < ndep; ++k)
692 if (is_station_[rx_[k].node]) dep_rx_[to_station_[rx_[k].node]][rx_[k].cls].push_back(k);
701 double closed_total = 0.0;
702 bool any_open =
false;
703 for (std::size_t r = 0; r < K_; ++r) {
704 const double nj = sn_.
classes[r].population;
710 blk_can_.assign(M_, std::vector<char>(K_, 0));
712 for (std::size_t ist = 0; ist < M_; ++ist)
713 for (std::size_t r = 0; r < K_; ++r) {
714 const double nj = sn_.
classes[r].population;
715 if (std::isinf(nj))
continue;
716 const double ccap = sn_.
classcap[ist][r];
717 const bool binds_st =
718 !std::isinf(sn_.
cap[ist]) && (any_open || sn_.
cap[ist] < closed_total);
719 const bool binds_cl = ccap > 0.0 && !std::isinf(ccap) && ccap < nj;
720 if (binds_st || binds_cl) {
721 blk_can_[ist][r] = 1;
737void NrmEngine<T>::build_state_dependent_dest(Rx& x) {
738 const qn::NodeDef& nd = sn_.
nodes[x.node];
745 for (std::size_t s = 1; s <= K_; ++s)
746 for (std::size_t j = 1; j <= I_; ++j) {
747 if (!(num_traits<T>::to_double(sn_.
get_route(x.cls + 1, s, x.node + 1, j)) > 0.0))
749 x.sd_node.push_back(j - 1);
750 x.sd_class.push_back(s - 1);
752 if (x.sd_node.size() < 2) {
760 x.sd_slots.resize(x.sd_node.size());
761 x.sd_pie.resize(x.sd_node.size());
762 for (std::size_t d = 0; d < x.sd_node.size(); ++d) {
763 const std::size_t jnd = x.sd_node[d], s = x.sd_class[d];
764 if (buf_ph_class_[jnd][s]) {
765 x.sd_slots[d].push_back(phoff_[jnd][s]);
766 x.sd_pie[d].push_back(1.0);
769 for (std::size_t ke = 0; ke < nph_[jnd][s]; ++ke) {
770 const double pe = pie_[jnd][s][ke];
772 x.sd_slots[d].push_back(phoff_[jnd][s] + ke);
773 x.sd_pie[d].push_back(pe);
778 const std::map<std::size_t, double>* w =
779 nd.routing_weights.size() > x.cls ? &nd.routing_weights[x.cls] : NULL;
781 x.sd_weight.assign(x.sd_node.size(), 0.0);
783 for (std::size_t d = 0; d < x.sd_node.size(); ++d) {
784 const std::map<std::size_t, double>::const_iterator it =
785 w->find(x.sd_node[d] + 1);
786 x.sd_weight[d] = (it == w->end()) ? 0.0 : it->second;
787 total += x.sd_weight[d];
789 if (!(total > 0.0)) {
795 for (std::size_t d = 0; d < x.sd_weight.size(); ++d) x.sd_weight[d] /= total;
826std::size_t NrmEngine<T>::resolve_state_dependent_dest(std::size_t kfire,
827 const std::vector<double>& X) {
829 const std::size_t nd = x.sd_node.size();
830 const std::size_t cursor_key = x.node * K_ + x.cls;
831 std::size_t pick = 0;
833 std::vector<double> load(nd, 0.0);
835 for (std::size_t d = 0; d < nd; ++d) {
836 const std::vector<double> cc = class_counts(X, x.sd_node[d]);
837 for (
double v : cc) load[d] += v;
838 if (d == 0 || load[d] < best) best = load[d];
840 std::vector<std::size_t> amins;
841 for (std::size_t d = 0; d < nd; ++d)
842 if (load[d] == best) amins.push_back(d);
843 pick = amins[std::min(amins.size() - 1,
844 std::size_t(rng_.
uniform() *
double(amins.size())))];
846 const std::size_t dsample = std::min<std::size_t>(2, nd);
848 for (std::size_t t = 0; t < dsample; ++t) {
849 const std::size_t cand =
850 std::min(nd - 1, std::size_t(rng_.
uniform() *
double(nd)));
851 const std::vector<double> cc = class_counts(X, x.sd_node[cand]);
853 for (
double v : cc) load += v;
854 if (t == 0 || load < best) {
862 const std::size_t c = rr_cursor_[cursor_key];
863 const double u = double(c % nd) / double(nd);
866 for (std::size_t d = 0; d < nd; ++d) {
867 acc += x.sd_weight[d];
868 if (u < acc - 1e-12) {
873 rr_cursor_[cursor_key] = c + 1;
875 pick = rr_cursor_[cursor_key] % nd;
876 rr_cursor_[cursor_key] = rr_cursor_[cursor_key] + 1;
879 const std::vector<std::size_t>& slots = x.sd_slots[pick];
880 if (slots.size() == 1)
return slots[0];
881 const double u = rng_.
uniform();
883 for (std::size_t k = 0; k + 1 < slots.size(); ++k) {
884 acc += x.sd_pie[pick][k];
885 if (u < acc)
return slots[k];
900void NrmEngine<T>::build_dependencies() {
901 const std::size_t nrx = rx_.size();
902 D_.assign(nrx, std::vector<std::size_t>());
903 for (std::size_t k = 0; k < nrx; ++k) {
904 std::vector<bool> touched_node(I_,
false);
905 for (std::size_t i = 0; i < NS_; ++i)
906 if (S_(i, k) != 0.0) touched_node[slot_node_[i]] =
true;
907 std::vector<bool> hit(nrx,
false);
908 for (std::size_t ind = 0; ind < I_; ++ind) {
909 if (!touched_node[ind])
continue;
910 for (std::size_t r = 0; r < K_; ++r)
911 for (std::size_t p = 0; p < nph_[ind][r]; ++p) {
912 const std::size_t slot = phoff_[ind][r] + p;
913 for (std::size_t j = 0; j < nrx; ++j)
914 if (S_(slot, j) < 0.0) hit[j] =
true;
917 for (std::size_t j = 0; j < nrx; ++j)
918 if (hit[j]) D_[k].push_back(j);
934void NrmEngine<T>::build_initial_state() {
935 nvec0_.assign(NS_, 0.0);
936 buffers0_.assign(I_, std::vector<std::size_t>());
937 svcph0_.assign(I_, Matrix<double>());
938 for (std::size_t i = 0; i < I_; ++i)
939 if (buf_ph_node_[i]) svcph0_[i] = Matrix<double>(K_, maxnph_, 0.0);
941 std::vector<std::vector<double>> nir(I_, std::vector<double>(K_, 0.0));
942 for (std::size_t r = 0; r < K_; ++r) {
943 const double pop = sn_.
classes[r].population;
944 if (std::isinf(pop)) {
947 "solver_ssa_nrm: the open class '" + sn_.
classes[r].name +
948 "' has no Source; an open model must carry one for the arrival reaction");
950 }
else if (pop > 0.0) {
951 const std::size_t rs = sn_.
classes[r].refstat;
952 if (rs < 1 || rs > M_)
954 "' has no reference station");
959 for (std::size_t ind = 0; ind < I_; ++ind) {
960 for (std::size_t r = 0; r < K_; ++r) {
961 const double n = nir[ind][r];
962 if (n <= 0.0)
continue;
963 if (nph_[ind][r] <= 1 || buf_ph_class_[ind][r]) {
964 nvec0_[phoff_[ind][r]] = n;
967 for (std::size_t ke = 0; ke < nph_[ind][r]; ++ke) {
968 const double take = (ke + 1 == nph_[ind][r])
970 : std::min(left, std::round(n * pie_[ind][r][ke]));
971 nvec0_[phoff_[ind][r] + ke] = take;
979 if (is_station_[ind] && detail::sched_is_buffered(sched_[to_station_[ind]])) {
981 for (std::size_t r = 0; r < K_; ++r) total += nir[ind][r];
982 double waiting = std::max(0.0, total - mi_[ind]);
983 for (std::size_t r = 0; r < K_ && waiting > 0.0; ++r) {
984 double take = std::min(waiting, nir[ind][r]);
985 for (std::size_t c = 0; c < static_cast<std::size_t>(take); ++c)
986 buffers0_[ind].push_back(r);
990 if (!buf_ph_node_[ind])
continue;
991 for (std::size_t r = 0; r < K_; ++r) {
992 double waiting_r = 0.0;
993 for (std::size_t c : buffers0_[ind])
994 if (c == r) waiting_r += 1.0;
995 const double insvc = std::max(0.0, nir[ind][r] - waiting_r);
996 if (nph_[ind][r] <= 1) {
997 svcph0_[ind](r, 0) = insvc;
1000 for (std::size_t ke = 0; ke < nph_[ind][r]; ++ke) {
1001 const double take = (ke + 1 == nph_[ind][r])
1003 : std::min(left, std::round(insvc * pie_[ind][r][ke]));
1004 svcph0_[ind](r, ke) = take;
1025double NrmEngine<T>::propensity(std::size_t j,
const std::vector<double>& X,
1026 const std::vector<std::vector<std::size_t>>& bufs,
1027 const std::vector<Matrix<double>>& svc)
const {
1028 const Rx& x = rx_[j];
1029 const std::size_t ind = x.node, r = x.cls;
1030 if (!is_station_[ind])
1031 return x.rate * kir_frac(X, x.from, ind, r) * std::min(1.0, class_pop(X, ind, r));
1033 const std::size_t ist = to_station_[ind];
1038 if (buf_ph_class_[ind][r]) {
1039 const std::size_t kk = x.is_phase ? x.phase_from : x.dep_phase;
1040 const std::vector<double> n = class_counts(X, ind);
1042 for (
double v : n) tot += v;
1043 return x.rate * svc[ind](r, kk) * lldfac(ist, tot);
1046 const double kf = kir_frac(X, x.from, ind, r);
1047 switch (sched_[ist]) {
1048 case SchedStrategy::EXT:
1053 case SchedStrategy::INF:
1054 return x.rate * kf * class_pop(X, ind, r);
1055 case SchedStrategy::PS:
1056 case SchedStrategy::LPS: {
1058 return x.rate * kf * std::min(mi_[ind], class_pop(X, ind, r)) *
1059 lldfac(ist, class_pop(X, ind, r));
1060 const std::vector<double> n = class_counts(X, ind);
1062 for (
double v : n) tot += v;
1063 return x.rate * kf * (n[r] / (eps + tot)) * std::min(mi_[ind], eps + tot) *
1066 case SchedStrategy::DPS: {
1067 const std::vector<double> n = class_counts(X, ind);
1069 for (
double v : n) tot += v;
1070 return x.rate * kf * dpsshare(wnorm_[ist], n, r) * lldfac(ist, tot);
1072 case SchedStrategy::GPS: {
1073 const std::vector<double> n = class_counts(X, ind);
1075 for (
double v : n) tot += v;
1076 return x.rate * kf * gpsshare(wnorm_[ist], n, r) * lldfac(ist, tot);
1083 case SchedStrategy::PSPRIO: {
1084 const std::vector<double> n = class_counts(X, ind);
1086 for (
double v : n) tot += v;
1087 return x.rate * kf * psprioshare(n, r, mi_[ind]) * lldfac(ist, tot);
1089 case SchedStrategy::DPSPRIO: {
1090 const std::vector<double> n = class_counts(X, ind);
1091 return x.rate * kf * dpsprioshare(wnorm_[ist], n, r, mi_[ind]) *
1092 lldfac(ist,
prio_pop(n, r, mi_[ind]));
1094 case SchedStrategy::GPSPRIO: {
1095 const std::vector<double> n = class_counts(X, ind);
1096 return x.rate * kf * gpsprioshare(wnorm_[ist], n, r, mi_[ind]) *
1097 lldfac(ist,
prio_pop(n, r, mi_[ind]));
1099 case SchedStrategy::FCFS:
1100 case SchedStrategy::LCFS:
1101 case SchedStrategy::SIRO:
1102 case SchedStrategy::HOL:
1103 case SchedStrategy::SEPT:
1104 case SchedStrategy::LEPT:
1105 case SchedStrategy::LCFSPR: {
1108 double waiting = 0.0;
1109 for (std::size_t c : bufs[ind])
1110 if (c == r) waiting += 1.0;
1111 const std::vector<double> n = class_counts(X, ind);
1113 for (
double v : n) tot += v;
1114 return x.rate * kf * std::max(0.0, n[r] - waiting) * lldfac(ist, tot);
1118 "solver_ssa_nrm: the scheduling policy '" +
1120 sn_.
stations[ist].name +
"' has no NRM rate law in this port");
1130std::size_t NrmEngine<T>::pick_from_buffer(
const std::vector<std::size_t>& buf, std::size_t ist) {
1131 switch (sched_[ist]) {
1132 case SchedStrategy::FCFS:
1133 return buf.size() - 1;
1134 case SchedStrategy::LCFS:
1135 case SchedStrategy::LCFSPR:
1137 case SchedStrategy::SIRO:
1138 return rng_.
index(buf.size());
1139 case SchedStrategy::HOL: {
1142 double best = std::numeric_limits<double>::infinity();
1143 for (std::size_t c : buf) best = std::min(best, classprio_[c]);
1144 for (std::size_t p = buf.size(); p-- > 0;)
1145 if (classprio_[buf[p]] == best)
return p;
1146 return buf.size() - 1;
1148 case SchedStrategy::SEPT:
1149 case SchedStrategy::LEPT: {
1153 double best = std::numeric_limits<double>::infinity();
1154 for (std::size_t c : buf)
1155 best = std::min(best, num_traits<T>::to_double(sn_.
stations[ist].schedparam[c]));
1156 for (std::size_t p = buf.size(); p-- > 0;)
1157 if (num_traits<T>::to_double(sn_.
stations[ist].schedparam[buf[p]]) == best)
1159 return buf.size() - 1;
1164 "' is not a buffered policy this port promotes from");
1170std::size_t NrmEngine<T>::draw_entry_phase(std::size_t ind, std::size_t r) {
1171 if (nph_[ind][r] <= 1)
return 0;
1172 return rng_.
draw(pie_[ind][r]);
1185bool NrmEngine<T>::capacity_loss(
const std::vector<double>& X, std::size_t slot)
const {
1186 const std::size_t jnd = slot_node_[slot], dst = slot_class_[slot];
1187 if (!is_station_[jnd])
return false;
1188 const std::size_t ist = to_station_[jnd];
1190 if (dr == qn::DropStrategy::WAITQ)
return false;
1191 if (dr == qn::DropStrategy::BAS || dr == qn::DropStrategy::BBS ||
1192 dr == qn::DropStrategy::RSRD)
1194 "' declares the blocking drop rule for class '" +
1196 "'; blocking-after-service is not ported to the C++ NRM");
1197 if (!std::isinf(sn_.
classes[dst].population))
return false;
1198 const std::vector<double> cc = class_counts(X, jnd);
1200 for (
double v : cc) tot += v;
1201 if (!std::isinf(sn_.
cap[ist]) && tot >= sn_.
cap[ist])
return true;
1202 const double ccap = sn_.
classcap[ist][dst];
1203 return ccap > 0.0 && !std::isinf(ccap) && cc[dst] >= ccap;
1232bool NrmEngine<T>::capacity_block(
const std::vector<double>& X, std::size_t slot,
1233 std::size_t src_slot)
const {
1234 if (!blk_on_)
return false;
1235 const std::size_t jnd = slot_node_[slot], dst = slot_class_[slot];
1236 if (!is_station_[jnd])
return false;
1237 const std::size_t ist = to_station_[jnd];
1238 if (!blk_can_[ist][dst])
return false;
1239 const bool same_node = src_slot != npos && slot_node_[src_slot] == jnd;
1240 const std::vector<double> cc = class_counts(X, jnd);
1241 if (!std::isinf(sn_.
cap[ist])) {
1243 for (
double v : cc) tot += v;
1244 if (same_node) tot -= 1.0;
1245 if (tot >= sn_.
cap[ist])
return true;
1247 const double ccap = sn_.
classcap[ist][dst];
1248 if (ccap > 0.0 && !std::isinf(ccap)) {
1249 double pop = cc[dst];
1250 if (same_node && slot_class_[src_slot] == dst) pop -= 1.0;
1251 if (pop >= ccap)
return true;
1258std::size_t NrmEngine<T>::pick_preempted(
const std::vector<double>& X,
1259 const std::vector<std::size_t>& buf, std::size_t jnd,
1260 std::size_t arr_class) {
1261 std::vector<double> insvc = class_counts(X, jnd);
1262 for (std::size_t r = 0; r < K_; ++r) {
1263 for (std::size_t c : buf)
1264 if (c == r) insvc[r] -= 1.0;
1265 if (r == arr_class) insvc[r] -= 1.0;
1266 if (insvc[r] < 0.0) insvc[r] = 0.0;
1269 for (
double v : insvc) tot += v;
1270 if (tot <= 0.0)
return npos;
1271 const double u = rng_.
uniform() * tot;
1273 std::size_t last = npos;
1274 for (std::size_t r = 0; r < K_; ++r) {
1275 if (!(insvc[r] > 0.0))
continue;
1278 if (u < acc)
return r;
1285void NrmEngine<T>::apply_arrival_buffer(std::size_t jnd, std::size_t s,
1286 const std::vector<double>& X,
1287 std::vector<std::vector<std::size_t>>& bufs,
1288 std::vector<Matrix<double>>& svc,
bool& svc_changed) {
1289 if (!is_station_[jnd] || !detail::sched_is_buffered(sched_[to_station_[jnd]])) {
1292 if (is_station_[jnd]) tag(jnd, s,
false);
1295 const std::vector<double> cc = class_counts(X, jnd);
1297 for (
double v : cc) total += v;
1298 bool entered =
false;
1299 if (total > mi_[jnd]) {
1300 if (sched_[to_station_[jnd]] == SchedStrategy::LCFSPR) {
1304 const std::size_t c = pick_preempted(X, bufs[jnd], jnd, s);
1306 bufs[jnd].insert(bufs[jnd].begin(), c);
1311 bufs[jnd].insert(bufs[jnd].begin(), s);
1316 if (entered) tag(jnd, s,
false);
1317 if (entered && buf_ph_node_[jnd]) {
1318 svc[jnd](s, draw_entry_phase(jnd, s)) += 1.0;
1325void NrmEngine<T>::update_buffers(std::size_t kfire,
const std::vector<double>& X,
1326 std::vector<std::vector<std::size_t>>& bufs,
1327 std::size_t dest_pos,
bool have_dest,
1328 std::vector<Matrix<double>>& svc,
bool& svc_changed) {
1329 const Rx& x = rx_[kfire];
1330 const std::size_t ind = x.node;
1333 if (buf_ph_node_[ind] && x.is_buf_svc && !x.is_phase) {
1334 svc[ind](x.cls, x.dep_phase) -= 1.0;
1337 if (is_station_[ind] && detail::sched_is_buffered(sched_[to_station_[ind]]) &&
1338 !bufs[ind].empty() && !x.is_phase) {
1339 const std::size_t pos = pick_from_buffer(bufs[ind], to_station_[ind]);
1340 const std::size_t promoted = bufs[ind][pos];
1341 bufs[ind].erase(bufs[ind].begin() +
static_cast<std::ptrdiff_t
>(pos));
1342 tag(ind, promoted,
false);
1343 if (buf_ph_node_[ind]) {
1344 svc[ind](promoted, draw_entry_phase(ind, promoted)) += 1.0;
1349 apply_arrival_buffer(slot_node_[dest_pos], slot_class_[dest_pos], X, bufs, svc,
1360 const std::size_t nrx = rx_.size();
1366 out.
CN.assign(K_, 0.0);
1367 out.
XN.assign(K_, 0.0);
1374 if (nrx == 0)
return out;
1376 std::vector<double> nvec = nvec0_;
1377 std::vector<std::vector<std::size_t>> bufs = buffers0_;
1378 std::vector<Matrix<double>> svc = svcph0_;
1380 std::vector<double> Ak(nrx, 0.0), Pk(nrx, 0.0), Tk(nrx, 0.0), tau(nrx, 0.0);
1381 for (std::size_t k = 0; k < nrx; ++k) {
1382 Ak[k] = propensity(k, nvec, bufs, svc);
1383 Pk[k] = -std::log(rng_.uniform());
1384 tau[k] = Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : std::numeric_limits<double>::infinity();
1387 double total_time = 0.0;
1390 static_cast<std::size_t
>(opt_.samples));
1391 const std::size_t console_every = std::max<std::size_t>(1, opt_.samples / 20);
1392 for (; n < opt_.samples; ++n) {
1393 if ((n + 1) % console_every == 0)
1395 static_cast<long>((n + 1) / console_every),
1396 "simulated %zu of %zu samples (%.0f%%), simulated time %.4g", n + 1,
1397 static_cast<std::size_t
>(opt_.samples),
1398 100.0 *
static_cast<double>(n + 1) /
static_cast<double>(opt_.samples),
1400 std::size_t kfire = 0;
1401 double dt = std::numeric_limits<double>::infinity();
1402 for (std::size_t k = 0; k < nrx; ++k)
1409 "solver_ssa_nrm: deadlock -- every reaction has propensity zero, so the sample "
1410 "path cannot advance");
1414 for (std::size_t ist = 0; ist < M_; ++ist) {
1415 const std::size_t ind = sn_.station_to_node[ist] - 1;
1416 const std::vector<double> npop = class_counts(nvec, ind);
1417 double totpop = 0.0;
1418 for (
double v : npop) totpop += v;
1431 const bool is_source = sched_[ist] == SchedStrategy::EXT;
1432 for (std::size_t k = 0; k < K_; ++k) {
1434 for (std::size_t jd : dep_rx_[ist][k]) dep += Ak[jd];
1435 out.
TN(ist, k) += dep * dt;
1436 if (is_source)
continue;
1437 out.
QN(ist, k) += npop[k] * dt;
1438 switch (sched_[ist]) {
1439 case SchedStrategy::INF:
1440 out.
UN(ist, k) += npop[k] * dt;
1442 case SchedStrategy::PS:
1443 case SchedStrategy::LPS:
1445 out.
UN(ist, k) += (npop[k] / totpop) *
1446 std::min(nservers_[ist], totpop) / nservers_[ist] *
1449 case SchedStrategy::DPS:
1450 out.
UN(ist, k) += dpsshare(wnorm_[ist], npop, k) / nservers_[ist] * dt;
1452 case SchedStrategy::GPS:
1453 out.
UN(ist, k) += gpsshare(wnorm_[ist], npop, k) / nservers_[ist] * dt;
1460 case SchedStrategy::PSPRIO:
1461 out.
UN(ist, k) += psprioshare(npop, k, nservers_[ist]) / nservers_[ist] * dt;
1463 case SchedStrategy::DPSPRIO:
1465 dpsprioshare(wnorm_[ist], npop, k, nservers_[ist]) / nservers_[ist] * dt;
1467 case SchedStrategy::GPSPRIO:
1469 gpsprioshare(wnorm_[ist], npop, k, nservers_[ist]) / nservers_[ist] * dt;
1471 case SchedStrategy::FCFS:
1472 case SchedStrategy::LCFS:
1473 case SchedStrategy::SIRO:
1474 case SchedStrategy::HOL:
1475 case SchedStrategy::SEPT:
1476 case SchedStrategy::LEPT:
1477 case SchedStrategy::LCFSPR: {
1478 if (sn_.disabled[ist][k])
break;
1479 double waiting = 0.0;
1480 for (std::size_t c : bufs[ind])
1481 if (c == k) waiting += 1.0;
1482 out.
UN(ist, k) += ((npop[k] - waiting) / nservers_[ist]) * dt;
1492 const Rx& x = rx_[kfire];
1493 std::size_t dest_pos = npos;
1494 bool have_dest =
false;
1498 bool blocked =
false;
1499 if (x.nnzP > 1 || !x.sd_node.empty()) {
1501 if (!x.sd_node.empty()) {
1503 slot = resolve_state_dependent_dest(kfire, nvec);
1505 const double u = rng_.uniform();
1506 std::size_t sel = x.cdf.size() - 1;
1507 for (std::size_t i = 0; i < x.cdf.size(); ++i)
1512 slot = x.to_slots[sel];
1517 if (capacity_block(nvec, slot, x.from)) {
1523 const bool lost = capacity_loss(nvec, slot);
1524 for (std::size_t s : x.from_slots) nvec[s] -= 1.0;
1531 }
else if (x.det_dest != npos && !x.is_phase &&
1532 capacity_block(nvec, x.det_dest, x.from)) {
1536 x.det_dest != npos && !x.is_phase && capacity_loss(nvec, x.det_dest);
1538 nvec[x.from] -= 1.0;
1540 for (std::size_t i = 0; i < NS_; ++i)
1541 if (S_(i, kfire) != 0.0) nvec[i] += S_(i, kfire);
1542 if (x.det_dest != npos) {
1543 dest_pos = x.det_dest;
1549 bool svc_changed =
false;
1552 if (is_station_[x.node]) block_cnt_(to_station_[x.node], x.cls) += 1.0;
1553 }
else if (x.is_phase && buf_ph_node_[x.node]) {
1556 svc[x.node](x.cls, x.phase_from) -= 1.0;
1557 svc[x.node](x.cls, x.phase_to) += 1.0;
1560 update_buffers(kfire, nvec, bufs, dest_pos, have_dest, svc, svc_changed);
1563 for (std::size_t k = 0; k < nrx; ++k) Tk[k] += Ak[k] * dt;
1568 for (std::size_t k = 0; k < nrx; ++k) Ak[k] = propensity(k, nvec, bufs, svc);
1570 for (std::size_t k : D_[kfire]) Ak[k] = propensity(k, nvec, bufs, svc);
1573 Pk[kfire] -= std::log(rng_.uniform());
1574 for (std::size_t k = 0; k < nrx; ++k)
1576 Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : std::numeric_limits<double>::infinity();
1579 if (total_time > 0.0)
1580 for (std::size_t ist = 0; ist < M_; ++ist)
1581 for (std::size_t k = 0; k < K_; ++k) {
1582 out.
QN(ist, k) /= total_time;
1583 out.
UN(ist, k) /= total_time;
1585 out.
TN(ist, k) = (out.
TN(ist, k) - block_cnt_(ist, k)) / total_time;
1589 out.
StartN(ist, k) = start_cnt_(ist, k) / total_time;
1590 out.
PreemptN(ist, k) = preempt_cnt_(ist, k) / total_time;
1601 for (std::size_t ist = 0; ist < M_; ++ist) {
1602 if (lld_[ist].empty())
continue;
1603 double peak = nservers_[ist];
1604 bool non_unit =
false;
1605 for (
double a : lld_[ist]) {
1606 if (a != 1.0) non_unit =
true;
1607 if (a > peak) peak = a;
1609 if (!non_unit)
continue;
1610 if (sched_[ist] == SchedStrategy::INF || sched_[ist] == SchedStrategy::EXT)
1612 for (std::size_t k = 0; k < K_; ++k) {
1614 out.
UN(ist, k) = (std::isfinite(rate) && rate > 0.0 && peak > 0.0)
1615 ? out.
TN(ist, k) / rate / peak
1620 for (std::size_t k = 0; k < K_; ++k) {
1621 out.
XN[k] = out.
TN(sn_.classes[k].refstat - 1, k);
1622 for (std::size_t ist = 0; ist < M_; ++ist)
1623 out.
RN(ist, k) = out.
TN(ist, k) > 0.0 ? out.
QN(ist, k) / out.
TN(ist, k) : 0.0;
1624 if (out.
XN[k] > 0.0) out.
CN[k] = sn_.classes[k].population / out.
XN[k];
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::size_t sourceIdx
1-based station index of the Source, 0 = none
T get_route(std::size_t r, std::size_t s, std::size_t i, std::size_t j) const
P{r,s}(i,j), AS THE USER SET IT.
std::size_t nof_nodes() const
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< std::vector< bool > > disabled
std::vector< double > cap
sn.cap and sn.classcap: the total and per-class buffers.
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< NodeDef > nodes
every node, in creation order
std::vector< std::vector< double > > classcap
std::vector< std::vector< DropStrategy > > droprule
std::vector< std::size_t > station_to_node
(nstations) 1-based node index
std::size_t nreactions() const
The reaction count, likewise.
NrmEngine(const qn::NetworkStruct< T > &sn, const SsaOptions &opt)
std::size_t nstates() const
The state-vector length, for the tests that assert the phase expansion.
SsaSolution run()
Run opt.samples firings and return the time-averaged metrics.
The uniform source, MATLAB's rand.
std::size_t draw(const std::vector< double > &p)
Index drawn from the unnormalized nonnegative weights p, the reference's drawFromDist: an all-zero we...
std::size_t index(std::size_t n)
Uniform index in [0, n), the reference's 1 + floor(rand*n).
static void loop(const char *fmt,...)
Announce an iteration loop and reset its reporting budget.
static void iter(long k, const char *fmt,...)
Report iteration k of the current loop.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Running progress log of a LINE solver run (the "solver console").
Dense matrix and non-owning view.
mam::Map< T > dist_to_map(const Distrib< T > &d)
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
DropStrategy
Blocking and loss rules, with the values of MATLAB DropStrategy.
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
std::vector< T > dist_pie(const Distrib< T > &d)
sn.pie: the phase distribution seen by an arriving job.
const char * sched_to_text(SchedStrategy s)
PrioPop< T > prio_pop(const NetworkStruct< T > &sn, std::size_t ist, const Marginal< T > &m, std::size_t cls, double ni, double S)
Compute the *PRIO effective population; a no-op for every other discipline.
A queueing network and its refreshed NetworkStruct.
Controls, results and the random source of SolverSSA.
static constexpr double Immediate
Rate of an Immediate distribution; its mean is 1/Immediate = 1e-8.
static constexpr double Zero
Controls, defaulting to SolverOptions('SSA') in the reference.
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