5#ifndef LINE_SOLVERS_SSA_SOLVER_SSA_NRM_H
6#define LINE_SOLVERS_SSA_SOLVER_SSA_NRM_H
123 std::size_t node = 0;
125 std::size_t from = 0;
127 bool is_phase =
false;
128 std::size_t phase_from = 0, phase_to = 0;
129 std::size_t dep_phase = 0;
130 bool is_buf_svc =
false;
134 bool is_cache =
false;
137 std::vector<std::size_t> to_slots;
138 std::vector<double> cdf;
140 std::vector<std::size_t> from_slots;
141 std::size_t nnzP = 0;
143 std::size_t det_dest =
static_cast<std::size_t
>(-1);
161 std::vector<std::size_t> sd_node, sd_class;
163 std::vector<double> sd_weight;
165 std::vector<std::vector<std::size_t>> sd_slots;
166 std::vector<std::vector<double>> sd_pie;
184 : sn_(
sn), opt_(
opt), rng_(
opt.seed) {
189 rr_cursor_.assign(I_ * K_ + K_ + 1, 0);
190 build_dependencies();
191 build_initial_state();
202 const std::vector<SsaCacheRatio>&
cache()
const {
return cache_out_; }
206 using Rx = detail::NrmReaction;
213 std::size_t I_ = 0, K_ = 0, M_ = 0, NS_ = 0, maxnph_ = 1;
216 std::vector<std::vector<std::size_t>> phoff_, nph_;
217 std::vector<std::size_t> slot_node_, slot_class_;
220 std::vector<bool> is_station_;
221 std::vector<std::size_t> to_station_;
222 std::vector<double> mi_;
223 std::vector<std::vector<double>> rate_;
224 std::vector<std::vector<std::vector<double>>> pie_;
225 std::vector<std::vector<bool>> buf_ph_class_;
226 std::vector<bool> buf_ph_node_;
227 std::vector<SchedStrategy> sched_;
228 std::vector<std::vector<double>> lld_;
229 std::vector<std::vector<double>> wnorm_;
230 std::vector<double> classprio_;
231 std::vector<double> nservers_;
236 std::vector<std::vector<std::size_t>> D_;
238 std::vector<std::vector<std::vector<std::size_t>>> dep_rx_;
243 std::vector<std::vector<char>> blk_can_;
244 bool blk_on_ =
false;
247 std::vector<double> nvec0_;
248 std::vector<std::vector<std::size_t>> buffers0_;
249 std::vector<Matrix<double>> svcph0_;
277 std::vector<char> member_node;
278 std::vector<double> ccap;
279 double gcap = std::numeric_limits<double>::infinity();
280 double memcap = std::numeric_limits<double>::infinity();
281 std::vector<double> sz;
283 std::vector<double> b;
284 std::vector<char> waitq;
286 std::vector<FcrRegion> fcr_;
287 bool fcr_any_waitq_ =
false;
298 std::vector<char> is_cache_node_;
299 std::vector<std::vector<char>> cache_read_;
301 std::vector<std::vector<T>> cache_var0_, cache_var_;
304 std::vector<SsaCacheRatio> cache_out_;
305 void cache_fire(
const Rx& x, std::vector<double>& X, std::size_t& dest_pos,
bool& have_dest);
306 void cache_write_back();
314 std::vector<char> is_poll_;
315 std::vector<qn::PollingInfo<T>> poll_info_;
316 std::vector<PollCtrl> poll_ctrl_;
318 PollCtrl poll_land(std::size_t ind, std::size_t q,
int mode,
long budget,
bool raise =
true);
320 std::vector<long> poll_nbuf(
const std::vector<double>& X, std::size_t ind)
const {
321 std::vector<long> nb(K_, 0);
322 for (std::size_t r = 0; r < K_; ++r) nb[r] = std::lround(class_pop(X, ind, r));
328 void build_regions();
329 void build_reactions();
330 void build_dependencies();
331 void build_initial_state();
334 double class_pop(
const std::vector<double>& X, std::size_t ind, std::size_t r)
const {
336 for (std::size_t k = 0; k < nph_[ind][r]; ++k) s += X[phoff_[ind][r] + k];
339 std::vector<double> class_counts(
const std::vector<double>& X, std::size_t ind)
const {
340 std::vector<double> v(K_, 0.0);
341 for (std::size_t r = 0; r < K_; ++r) v[r] = class_pop(X, ind, r);
344 double kir_frac(
const std::vector<double>& X, std::size_t slot, std::size_t ind,
345 std::size_t r)
const {
346 const double nir = class_pop(X, ind, r);
347 return nir <= 0.0 ? 0.0 : X[slot] / nir;
349 double lldfac(std::size_t ist,
double ntot)
const {
350 if (ist == npos || lld_[ist].empty() || ntot < 1.0)
return 1.0;
351 const std::size_t lim = lld_[ist].size();
352 std::size_t k =
static_cast<std::size_t
>(std::llround(ntot));
353 if (k > lim) k = lim;
354 return lld_[ist][k - 1];
356 static double dpsshare(
const std::vector<double>& w,
const std::vector<double>& n,
359 for (std::size_t s = 0; s < n.size(); ++s) den += w[s] * n[s];
360 return den <= 0.0 ? 0.0 : w[r] * n[r] / den;
362 static double gpsshare(
const std::vector<double>& w,
const std::vector<double>& n,
364 if (n[r] <= 0.0)
return 0.0;
366 for (std::size_t s = 0; s < n.size(); ++s) den += n[s] > 0.0 ? w[s] : 0.0;
367 return den <= 0.0 ? 0.0 : w[r] / den;
382 bool urgent(
const std::vector<double>& n, std::size_t r)
const {
385 for (std::size_t s = 0; s < n.size(); ++s)
386 if (n[s] > 0.0 && (!any || classprio_[s] < best)) {
387 best = classprio_[s];
390 return any && classprio_[r] == best;
393 std::vector<double> prio_group(
const std::vector<double>& n, std::size_t r,
394 double* total =
nullptr)
const {
395 std::vector<double> act(n.size(), 0.0);
397 for (std::size_t s = 0; s < n.size(); ++s)
398 if (classprio_[s] == classprio_[r]) {
402 if (total) *total =
tot;
405 double psprioshare(
const std::vector<double>& n, std::size_t r,
double c)
const {
407 for (
double v : n) ni += v;
408 if (ni <= 0.0)
return 0.0;
409 if (ni <= c)
return (n[r] / ni) * std::min(ni, c);
410 if (!urgent(n, r))
return 0.0;
412 prio_group(n, r, &niprio);
413 return niprio <= 0.0 ? 0.0 : (n[r] / niprio) * std::min(niprio, c);
415 double dpsprioshare(
const std::vector<double>& w,
const std::vector<double>& n, std::size_t r,
418 for (
double v : n) ni += v;
419 if (ni <= 0.0)
return 0.0;
420 if (ni <= c)
return dpsshare(w, n, r);
421 if (!urgent(n, r))
return 0.0;
422 return dpsshare(w, prio_group(n, r), r);
424 double gpsprioshare(
const std::vector<double>& w,
const std::vector<double>& n, std::size_t r,
427 for (
double v : n) ni += v;
428 if (ni <= 0.0)
return 0.0;
429 if (ni <= c)
return gpsshare(w, n, r);
430 if (!urgent(n, r))
return 0.0;
431 return gpsshare(w, prio_group(n, r), r);
442 double prio_pop(
const std::vector<double>& n, std::size_t r,
double c)
const {
444 for (
double v : n) ni += v;
445 if (ni <= c || !urgent(n, r))
return ni;
447 prio_group(n, r, &niprio);
451 std::vector<double> prio_vec(
const std::vector<double>& n, std::size_t r,
double c)
const {
453 for (
double v : n) ni += v;
454 if (ni <= c || !urgent(n, r))
return n;
455 return prio_group(n, r);
458 void check_peaks()
const {
459 for (std::size_t ist = 0; ist < M_; ++ist) {
460 const auto& st = sn_.stations[ist];
461 const bool on[2] = {
static_cast<bool>(st.cdscaling),
static_cast<bool>(st.jdscaling)};
462 const std::vector<T>* pks[2] = {&st.cdscalingpeak, &st.jdscalingpeak};
463 const char* names[2] = {
"setClassDependence",
"setJointDependence"};
464 for (std::size_t h = 0; h < 2; ++h) {
465 if (!on[h])
continue;
466 bool ok = pks[h]->size() >= K_;
467 for (std::size_t k = 0; ok && k < K_; ++k)
468 ok = num_traits<T>::to_double((*pks[h])[k]) > 0.0;
470 throw InputError(
"SolverSSA(method='nrm'): station '" + st.name +
471 "' declares a dependent scaling with no declared peak rate. "
472 "Utilization there is T*E[S]/peak, so pass the peak to " +
483 double cdfac(std::size_t ist,
const std::vector<double>& n, std::size_t r)
const {
484 if (ist == npos)
return 1.0;
485 const auto& st = sn_.stations[ist];
486 if (!st.cdscaling && !st.jdscaling)
return 1.0;
487 std::vector<T> nt(n.size());
488 for (std::size_t s = 0; s < n.size(); ++s) nt[s] = T(n[s]);
490 for (
const auto* h : {&st.cdscaling, &st.jdscaling}) {
492 const std::vector<T> v = (*h)(nt);
493 f *= num_traits<T>::to_double(v[std::min(r, v.size() - 1)]);
499 double propensity(std::size_t j,
const std::vector<double>& X,
500 const std::vector<std::vector<std::size_t>>& bufs,
501 const std::vector<Matrix<double>>& svc)
const;
502 std::size_t pick_from_buffer(
const std::vector<std::size_t>& buf, std::size_t ist);
503 std::size_t draw_entry_phase(std::size_t ind, std::size_t r);
504 bool capacity_loss(
const std::vector<double>& X, std::size_t slot)
const;
506 const typename qn::NetworkStruct<T>::PasParam& pas_of(std::size_t ist)
const;
508 std::vector<double> pas_increments(std::size_t ist,
const std::vector<std::size_t>& c)
const;
510 double pas_rate(std::size_t ist,
const std::vector<std::size_t>& c, std::size_t r)
const;
512 double pas_in_service(std::size_t ist,
const std::vector<std::size_t>& c, std::size_t r)
const;
514 void pas_tag_started(std::size_t ind,
const std::vector<std::size_t>& cold,
515 const std::vector<std::size_t>& cnew);
517 void pas_depart(std::size_t ind, std::vector<std::size_t>& c, std::size_t r);
518 std::size_t fcr_refusing_region(
const std::vector<double>& X, std::size_t src_node,
519 std::size_t src_class, std::size_t dst_node,
520 std::size_t dst_class)
const;
521 void fcr_outcome(
const std::vector<double>& X,
const Rx& x, std::size_t dst_node,
522 std::size_t dst_class, std::vector<std::vector<std::size_t>>& fcr_buf,
523 bool& lost,
bool& blocked)
const;
524 std::size_t fcr_release_cascade(std::vector<double>& X,
525 std::vector<std::vector<std::size_t>>& bufs,
526 std::vector<std::vector<std::size_t>>& fcr_buf,
527 std::vector<Matrix<double>>& svc,
bool& svc_changed);
528 bool capacity_block(
const std::vector<double>& X, std::size_t slot,
529 std::size_t src_slot)
const;
530 std::size_t pick_preempted(
const std::vector<double>& X,
531 const std::vector<std::size_t>& buf, std::size_t jnd,
532 std::size_t arr_class);
533 void apply_arrival_buffer(std::size_t jnd, std::size_t s,
const std::vector<double>& X,
534 std::vector<std::vector<std::size_t>>& bufs,
535 std::vector<Matrix<double>>& svc,
bool& svc_changed);
536 void update_buffers(std::size_t kfire,
const std::vector<double>& X,
537 std::vector<std::vector<std::size_t>>& bufs, std::size_t dest_pos,
538 bool have_dest, std::vector<Matrix<double>>& svc,
bool& svc_changed);
541 void build_state_dependent_dest(Rx& x);
543 std::size_t resolve_state_dependent_dest(std::size_t kfire,
const std::vector<double>& X);
551 std::vector<std::size_t> rr_cursor_;
554 void tag(std::size_t ind, std::size_t r,
bool preempt) {
555 if (!is_station_[ind] || r >= K_)
return;
556 const std::size_t ist = to_station_[ind];
557 if (ist >= M_)
return;
560 if (sched_[ist] == SchedStrategy::EXT)
return;
561 (preempt ? preempt_cnt_ : start_cnt_)(ist, r) += 1.0;
564 static constexpr std::size_t npos =
static_cast<std::size_t
>(-1);
581void NrmEngine<T>::build_layout() {
586 is_station_.assign(I_,
false);
587 to_station_.assign(I_, npos);
588 for (std::size_t i = 0; i < I_; ++i)
589 if (sn_.
nodes[i].station != 0) {
590 is_station_[i] =
true;
591 to_station_[i] = sn_.
nodes[i].station - 1;
594 sched_.assign(M_, SchedStrategy::FCFS);
595 nservers_.assign(M_, 1.0);
596 for (std::size_t i = 0; i < M_; ++i) {
598 nservers_[i] = sn_.
stations[i].nservers;
603 nph_.assign(I_, std::vector<std::size_t>(K_, 1));
604 pie_.assign(I_, std::vector<std::vector<double>>(K_));
605 for (std::size_t i = 0; i < I_; ++i) {
606 for (std::size_t r = 0; r < K_; ++r) {
607 pie_[i][r].assign(1, 1.0);
608 if (!is_station_[i])
continue;
609 const std::size_t ist = to_station_[i];
612 const std::size_t n = m.D0.rows();
613 if (n <= 1)
continue;
616 std::vector<double> pd(n, 0.0);
618 for (std::size_t k = 0; k < n && k < p.size(); ++k) {
619 pd[k] = num_traits<T>::to_double(p[k]);
620 if (!(pd[k] > 0.0)) pd[k] = 0.0;
624 for (
double& x : pd) x /=
tot;
633 phoff_.assign(I_, std::vector<std::size_t>(K_, 0));
636 for (std::size_t i = 0; i < I_; ++i)
637 for (std::size_t r = 0; r < K_; ++r) {
640 maxnph_ = std::max(maxnph_, nph_[i][r]);
642 slot_node_.assign(NS_, 0);
643 slot_class_.assign(NS_, 0);
644 for (std::size_t i = 0; i < I_; ++i)
645 for (std::size_t r = 0; r < K_; ++r)
646 for (std::size_t k = 0; k < nph_[i][r]; ++k) {
647 slot_node_[phoff_[i][r] + k] = i;
648 slot_class_[phoff_[i][r] + k] = r;
653 buf_ph_class_.assign(I_, std::vector<bool>(K_,
false));
654 buf_ph_node_.assign(I_,
false);
655 for (std::size_t i = 0; i < I_; ++i) {
656 if (!is_station_[i] || !detail::sched_is_buf_ph(sched_[to_station_[i]]))
continue;
657 for (std::size_t r = 0; r < K_; ++r)
658 if (nph_[i][r] > 1) {
659 buf_ph_class_[i][r] =
true;
660 buf_ph_node_[i] =
true;
665 const double inf = std::numeric_limits<double>::infinity();
667 rate_.assign(I_, std::vector<double>(K_, 0.0));
668 for (std::size_t i = 0; i < I_; ++i) {
669 if (is_station_[i]) {
670 const std::size_t ist = to_station_[i];
671 mi_[i] = sn_.
stations[ist].nservers;
672 for (std::size_t r = 0; r < K_; ++r)
674 rate_[i][r] = num_traits<T>::to_double(sn_.
rates(ist, r));
676 for (std::size_t r = 0; r < K_; ++r)
681 lld_.assign(M_, std::vector<double>());
682 wnorm_.assign(M_, std::vector<double>(K_, 0.0));
683 for (std::size_t i = 0; i < M_; ++i) {
684 for (
const T& x : sn_.
stations[i].lldscaling)
685 lld_[i].push_back(num_traits<T>::to_double(x));
686 if (!detail::sched_is_weighted(sched_[i]))
continue;
688 for (std::size_t r = 0; r < K_ && r < sn_.
stations[i].schedparam.size(); ++r)
689 tot += num_traits<T>::to_double(sn_.
stations[i].schedparam[r]);
693 " scheduling with non-positive total weight");
697 " at station '" + sn_.
stations[i].name +
698 "' is not supported; the reference's State.afterEventStation rejects it too");
699 for (std::size_t r = 0; r < K_ && r < sn_.
stations[i].schedparam.size(); ++r)
700 wnorm_[i][r] = num_traits<T>::to_double(sn_.
stations[i].schedparam[r]) /
tot;
703 classprio_.assign(K_, 0.0);
704 for (std::size_t r = 0; r < K_; ++r) classprio_[r] = sn_.
classes[r].prio;
718void NrmEngine<T>::build_regions() {
719 const double inf = std::numeric_limits<double>::infinity();
721 fcr_any_waitq_ =
false;
722 for (
const typename qn::NetworkStruct<T>::Region& rg : sn_.
regions) {
724 f.member_node.assign(I_, 0);
725 f.ccap.assign(K_, inf);
726 f.sz.assign(K_, 1.0);
727 f.waitq.assign(K_, 0);
728 for (std::size_t i = 0; i < M_ && i < rg.members.size(); ++i) {
729 if (!rg.members[i])
continue;
731 for (std::size_t r = 0; r < K_; ++r)
732 if (rg.cap[i][r] != -1.0) f.ccap[r] = std::min(f.ccap[r], rg.cap[i][r]);
733 if (rg.cap[i][K_] != -1.0) f.gcap = std::min(f.gcap, rg.cap[i][K_]);
734 if (i < rg.maxmem.size() && rg.maxmem[i] != -1.0)
735 f.memcap = std::min(f.memcap, rg.maxmem[i]);
737 for (std::size_t r = 0; r < K_ && r < rg.size.size(); ++r)
738 f.sz[r] = num_traits<T>::to_double(rg.size[r]);
739 for (std::size_t r = 0; r < K_ && r < rg.rule.size(); ++r) {
741 if (d == qn::DropStrategy::BAS || d == qn::DropStrategy::BBS ||
742 d == qn::DropStrategy::RSRD)
744 "solver_ssa_nrm: finite capacity region '" + rg.name +
"' declares a "
745 "blocking rule for class '" + sn_.
classes[r].name +
"'; only DROP and WAITQ "
746 "are region rules in any codebase's NRM");
747 f.waitq[r] = d != qn::DropStrategy::DROP;
748 if (f.waitq[r]) fcr_any_waitq_ =
true;
750 if (rg.lincon_A.rows() > 0) {
751 f.A = Matrix<double>(rg.lincon_A.rows(), rg.lincon_A.cols(), 0.0);
752 for (std::size_t a = 0; a < rg.lincon_A.rows(); ++a)
753 for (std::size_t c = 0; c < rg.lincon_A.cols(); ++c)
754 f.A(a, c) = num_traits<T>::to_double(rg.lincon_A(a, c));
755 for (
const T& v : rg.lincon_b) f.b.push_back(num_traits<T>::to_double(v));
771std::size_t NrmEngine<T>::fcr_refusing_region(
const std::vector<double>& X, std::size_t src_node,
772 std::size_t src_class, std::size_t dst_node,
773 std::size_t dst_class)
const {
774 for (std::size_t f = 0; f < fcr_.size(); ++f) {
775 const FcrRegion& g = fcr_[f];
776 if (!g.member_node[dst_node])
continue;
777 std::vector<double> x(K_, 0.0);
778 for (std::size_t jnd = 0; jnd < I_; ++jnd)
779 if (g.member_node[jnd])
780 for (std::size_t r = 0; r < K_; ++r) x[r] += class_pop(X, jnd, r);
781 if (src_node != npos && g.member_node[src_node]) x[src_class] -= 1.0;
783 double tot = 0.0, mem = 0.0;
785 for (std::size_t r = 0; r < K_; ++r) {
787 mem += x[r] * g.sz[r];
788 if (x[r] > g.ccap[r]) bad =
true;
790 if (tot > g.gcap || mem > g.memcap) bad =
true;
791 for (std::size_t a = 0; !bad && a < g.A.rows() && a < g.b.size(); ++a) {
793 for (std::size_t c = 0; c < K_ && c < g.A.cols(); ++c) lhs += g.A(a, c) * x[c];
794 if (lhs > g.b[a]) bad =
true;
796 if (bad)
return f + 1;
802const typename qn::NetworkStruct<T>::PasParam& NrmEngine<T>::pas_of(std::size_t ist)
const {
803 const auto it = sn_.
pasparam.find(ist + 1);
804 if (it == sn_.
pasparam.end() || !it->second.svc_rate_fun)
806 "' has no service rate function mu(c); set one with set_pas");
811std::vector<double> NrmEngine<T>::pas_increments(std::size_t ist,
812 const std::vector<std::size_t>& c)
const {
813 const auto& mu = pas_of(ist).svc_rate_fun;
814 std::vector<double> inc(c.size(), 0.0);
815 std::vector<std::size_t> prefix;
817 for (std::size_t p = 0; p < c.size(); ++p) {
818 prefix.push_back(c[p] + 1);
819 const double cur = num_traits<T>::to_double(mu(prefix));
827double NrmEngine<T>::pas_rate(std::size_t ist,
const std::vector<std::size_t>& c,
828 std::size_t r)
const {
829 if (c.empty())
return 0.0;
831 std::vector<std::size_t> c1(c.size());
832 for (std::size_t p = 0; p < c.size(); ++p) c1[p] = c[p] + 1;
833 const auto& G = pas_of(ist).swap_graph;
835 for (std::size_t p = 0; p < c.size(); ++p) {
836 if (inc[p] <= 0.0)
continue;
843double NrmEngine<T>::pas_in_service(std::size_t ist,
const std::vector<std::size_t>& c,
844 std::size_t r)
const {
845 if (c.empty())
return 0.0;
848 for (std::size_t p = 0; p < c.size(); ++p)
849 if (inc[p] > 0.0 && c[p] == r) n += 1.0;
854void NrmEngine<T>::pas_tag_started(std::size_t ind,
const std::vector<std::size_t>& cold,
855 const std::vector<std::size_t>& cnew) {
856 const std::size_t ist = to_station_[ind];
859 for (std::size_t p = 0; p < inc_new.size(); ++p) {
860 if (inc_new[p] <= 0.0)
continue;
861 if (p < inc_old.size() && inc_old[p] > 0.0)
continue;
862 tag(ind, cnew[p],
false);
872void NrmEngine<T>::pas_depart(std::size_t ind, std::vector<std::size_t>& c, std::size_t r) {
873 const std::size_t ist = to_station_[ind];
875 std::vector<std::size_t> c1(c.size());
876 for (std::size_t p = 0; p < c.size(); ++p) c1[p] = c[p] + 1;
877 const auto& G = pas_of(ist).swap_graph;
878 std::vector<std::size_t> pos;
879 std::vector<double> w;
881 for (std::size_t p = 0; p < c.size(); ++p) {
882 if (inc[p] <= 0.0)
continue;
888 if (pos.empty())
return;
891 std::size_t pick = pos.back();
892 for (std::size_t x = 0; x < pos.size(); ++x) {
900 c.assign(nc.size(), 0);
901 for (std::size_t p = 0; p < nc.size(); ++p) c[p] = nc[p] - 1;
912typename NrmEngine<T>::PollCtrl NrmEngine<T>::poll_land(std::size_t ind, std::size_t q,
int mode,
913 long budget,
bool raise) {
919 if (raise) tag(ind, q - 1,
false);
920 }
else if (mode == 2) {
921 const std::vector<T>& pie = poll_info_[ind].sw_pie[q - 1];
922 std::vector<double> w(pie.size());
923 for (std::size_t k = 0; k < pie.size(); ++k) w[k] = num_traits<T>::to_double(pie[k]);
924 c.swk = rng_.
draw(w);
945void NrmEngine<T>::fcr_outcome(
const std::vector<double>& X,
const Rx& x, std::size_t dst_node,
946 std::size_t dst_class,
947 std::vector<std::vector<std::size_t>>& fcr_buf,
bool& lost,
948 bool& blocked)
const {
949 const std::size_t fref = fcr_refusing_region(X, x.node, x.cls, dst_node, dst_class);
950 if (fref == 0)
return;
951 if (fcr_[fref - 1].waitq[dst_class]) {
953 fcr_buf[fref - 1].push_back(dst_node * K_ + dst_class);
954 }
else if (std::isinf(sn_.
classes[dst_class].population)) {
969std::size_t NrmEngine<T>::fcr_release_cascade(std::vector<double>& X,
970 std::vector<std::vector<std::size_t>>& bufs,
971 std::vector<std::vector<std::size_t>>& fcr_buf,
972 std::vector<Matrix<double>>& svc,
974 std::size_t released = 0;
975 bool progress =
true;
978 for (std::size_t f = 0; f < fcr_buf.size(); ++f) {
979 if (fcr_buf[f].empty())
continue;
980 const std::size_t tok = fcr_buf[f].front();
981 const std::size_t jnd = tok / K_, r = tok % K_;
982 if (fcr_refusing_region(X, npos, r, jnd, r) != 0)
continue;
986 const std::size_t ke = buf_ph_node_[jnd] ? 0 : draw_entry_phase(jnd, r);
987 X[phoff_[jnd][r] + ke] += 1.0;
988 apply_arrival_buffer(jnd, r, X, bufs, svc, svc_changed);
989 fcr_buf[f].erase(fcr_buf[f].begin());
1007void NrmEngine<T>::build_reactions() {
1011 is_cache_node_.assign(I_, 0);
1012 cache_read_.assign(I_, std::vector<char>(K_, 0));
1013 for (std::size_t ind = 0; ind < I_; ++ind) {
1014 if (sn_.
nodes[ind].nodetype != qn::NodeType::Cache)
continue;
1015 const auto ci = sn_.
nodeparam.find(ind + 1);
1017 throw InputError(
"solver_ssa_nrm: Cache node '" + sn_.
nodes[ind].name +
1018 "' has no cache parameters");
1019 const qn::CacheParam<T>& cp = ci->second;
1020 is_cache_node_[ind] = 1;
1021 std::vector<std::size_t> rcl, rci, rco;
1023 for (std::size_t r = 0; r < K_; ++r) {
1024 if (r >= cp.pread.size() || cp.pread[r].empty())
continue;
1025 const bool is_retr = std::find(rcl.begin(), rcl.end(), r + 1) != rcl.end();
1026 const std::size_t hc = r < cp.hitclass.size() ? cp.hitclass[r] : 0;
1027 const std::size_t mc = r < cp.missclass.size() ? cp.missclass[r] : 0;
1028 if (hc == 0 && !is_retr)
continue;
1031 if (hc == r + 1 || mc == r + 1)
1033 "solver_ssa_nrm: class '" + sn_.
classes[r].name +
"' at Cache '" +
1034 sn_.
nodes[ind].name +
"' is its own hit or miss class, so the NRM's cache "
1035 "read would fire again at once; ask for method='serial'");
1036 cache_read_[ind][r] = 1;
1040 for (std::size_t ind = 0; ind < I_; ++ind) {
1041 for (std::size_t r = 0; r < K_; ++r) {
1042 for (std::size_t kk = 0; kk < nph_[ind][r]; ++kk) {
1047 x.is_buf_svc = buf_ph_class_[ind][r];
1051 x.from = buf_ph_class_[ind][r] ? phoff_[ind][r] : phoff_[ind][r] + kk;
1052 x.rate = rate_[ind][r];
1053 x.is_cache = is_cache_node_[ind] && cache_read_[ind][r];
1054 if (is_station_[ind] && nph_[ind][r] > 1) {
1055 const std::size_t ist = to_station_[ind];
1058 for (std::size_t c = 0; c < m.D1.cols(); ++c)
1059 s += num_traits<T>::to_double(m.D1(kk, c));
1066 const std::size_t ndep = rx_.size();
1069 for (std::size_t ind = 0; ind < I_; ++ind) {
1070 if (!is_station_[ind])
continue;
1071 const std::size_t ist = to_station_[ind];
1072 for (std::size_t r = 0; r < K_; ++r) {
1073 if (nph_[ind][r] <= 1 || sn_.
disabled[ist][r])
continue;
1075 for (std::size_t ka = 0; ka < nph_[ind][r]; ++ka)
1076 for (std::size_t kb = 0; kb < nph_[ind][r]; ++kb) {
1077 if (ka == kb)
continue;
1078 const double d = num_traits<T>::to_double(m.D0(ka, kb));
1079 if (!(d > 0.0))
continue;
1090 x.from = buf_ph_class_[ind][r] ? phoff_[ind][r] : phoff_[ind][r] + ka;
1102 is_poll_.assign(I_, 0);
1103 poll_info_.assign(I_, qn::PollingInfo<T>());
1104 for (std::size_t ind = 0; ind < I_; ++ind) {
1105 if (!is_station_[ind] || sched_[to_station_[ind]] != SchedStrategy::POLLING)
continue;
1108 for (std::size_t r = 0; r < K_; ++r)
1109 if (nph_[ind][r] > 1)
1111 "solver_ssa_nrm: polling station '" + sn_.
stations[to_station_[ind]].name +
1112 "' serves class '" + sn_.
classes[r].name +
"' with phase-type service. The "
1113 "NRM polling controller supports exponential service only, as the reference's "
1114 "does; ask for method='serial'");
1116 for (
bool h : poll_info_[ind].has_sw) anysw = anysw || h;
1117 if (!anysw)
continue;
1121 x.from = phoff_[ind][0];
1127 S_ = Matrix<double>(NS_, rx_.size(), 0.0);
1128 for (std::size_t k = 0; k < rx_.size(); ++k) {
1130 if (x.is_sw)
continue;
1133 S_(x.from, k) -= 1.0;
1137 if (!buf_ph_class_[x.node][x.cls]) {
1138 S_(phoff_[x.node][x.cls] + x.phase_from, k) -= 1.0;
1139 S_(phoff_[x.node][x.cls] + x.phase_to, k) += 1.0;
1143 S_(x.from, k) -= 1.0;
1144 for (std::size_t jnd = 0; jnd < I_; ++jnd)
1145 for (std::size_t s = 0; s < K_; ++s) {
1147 num_traits<T>::to_double(sn_.
rtnodes(x.node * K_ + x.cls, jnd * K_ + s));
1148 if (!(p > 0.0))
continue;
1149 if (buf_ph_class_[jnd][s]) {
1153 S_(phoff_[jnd][s], k) += p;
1155 for (std::size_t ke = 0; ke < nph_[jnd][s]; ++ke) {
1156 const double pe = pie_[jnd][s][ke];
1157 if (!(pe > 0.0))
continue;
1158 S_(phoff_[jnd][s] + ke, k) += p * pe;
1168 for (std::size_t k = 0; k < rx_.size(); ++k) {
1170 std::vector<double> Pcol(NS_, 0.0);
1171 for (std::size_t i = 0; i < NS_; ++i) {
1172 const double v = S_(i, k);
1173 Pcol[i] = v < 0.0 ? v + 1.0 : v;
1174 if (v < 0.0) x.from_slots.push_back(i);
1175 if (v > 0.0 && x.det_dest == npos) x.det_dest = i;
1177 for (std::size_t i = 0; i < NS_; ++i)
1178 if (Pcol[i] != 0.0) ++x.nnzP;
1181 for (std::size_t i = 0; i < NS_; ++i)
1182 if (Pcol[i] != 0.0) {
1183 x.to_slots.push_back(i);
1185 x.cdf.push_back(acc);
1188 if (!x.is_phase && !x.is_sw) build_state_dependent_dest(x);
1193 dep_rx_.assign(M_, std::vector<std::vector<std::size_t>>(K_));
1194 for (std::size_t k = 0; k < ndep; ++k)
1195 if (is_station_[rx_[k].node]) dep_rx_[to_station_[rx_[k].node]][rx_[k].cls].push_back(k);
1204 double closed_total = 0.0;
1205 bool any_open =
false;
1206 for (std::size_t r = 0; r < K_; ++r) {
1207 const double nj = sn_.
classes[r].population;
1213 blk_can_.assign(M_, std::vector<char>(K_, 0));
1215 for (std::size_t ist = 0; ist < M_; ++ist)
1216 for (std::size_t r = 0; r < K_; ++r) {
1217 const double nj = sn_.
classes[r].population;
1218 if (std::isinf(nj))
continue;
1219 const double ccap = sn_.
classcap[ist][r];
1220 const bool binds_st =
1221 !std::isinf(sn_.
cap[ist]) && (any_open || sn_.
cap[ist] < closed_total);
1222 const bool binds_cl = ccap > 0.0 && !std::isinf(ccap) && ccap < nj;
1223 if (binds_st || binds_cl) {
1224 blk_can_[ist][r] = 1;
1240void NrmEngine<T>::build_state_dependent_dest(Rx& x) {
1241 const qn::NodeDef& nd = sn_.
nodes[x.node];
1248 for (std::size_t s = 1; s <= K_; ++s)
1249 for (std::size_t j = 1; j <= I_; ++j) {
1250 if (!(num_traits<T>::to_double(sn_.
get_route(x.cls + 1, s, x.node + 1, j)) > 0.0))
1252 x.sd_node.push_back(j - 1);
1253 x.sd_class.push_back(s - 1);
1255 if (x.sd_node.size() < 2) {
1263 x.sd_slots.resize(x.sd_node.size());
1264 x.sd_pie.resize(x.sd_node.size());
1265 for (std::size_t d = 0; d < x.sd_node.size(); ++d) {
1266 const std::size_t jnd = x.sd_node[d], s = x.sd_class[d];
1267 if (buf_ph_class_[jnd][s]) {
1268 x.sd_slots[d].push_back(phoff_[jnd][s]);
1269 x.sd_pie[d].push_back(1.0);
1272 for (std::size_t ke = 0; ke < nph_[jnd][s]; ++ke) {
1273 const double pe = pie_[jnd][s][ke];
1275 x.sd_slots[d].push_back(phoff_[jnd][s] + ke);
1276 x.sd_pie[d].push_back(pe);
1281 const std::map<std::size_t, double>* w =
1282 nd.routing_weights.size() > x.cls ? &nd.routing_weights[x.cls] : NULL;
1284 x.sd_weight.assign(x.sd_node.size(), 0.0);
1286 for (std::size_t d = 0; d < x.sd_node.size(); ++d) {
1287 const std::map<std::size_t, double>::const_iterator it =
1288 w->find(x.sd_node[d] + 1);
1289 x.sd_weight[d] = (it == w->end()) ? 0.0 : it->second;
1290 total += x.sd_weight[d];
1292 if (!(total > 0.0)) {
1295 x.sd_weight.clear();
1298 for (std::size_t d = 0; d < x.sd_weight.size(); ++d) x.sd_weight[d] /= total;
1329std::size_t NrmEngine<T>::resolve_state_dependent_dest(std::size_t kfire,
1330 const std::vector<double>& X) {
1332 const std::size_t nd = x.sd_node.size();
1333 const std::size_t cursor_key = x.node * K_ + x.cls;
1334 std::size_t pick = 0;
1336 std::vector<double> load(nd, 0.0);
1338 for (std::size_t d = 0; d < nd; ++d) {
1339 const std::vector<double> cc = class_counts(X, x.sd_node[d]);
1340 for (
double v : cc) load[d] += v;
1341 if (d == 0 || load[d] < best) best = load[d];
1343 std::vector<std::size_t> amins;
1344 for (std::size_t d = 0; d < nd; ++d)
1345 if (load[d] == best) amins.push_back(d);
1346 pick = amins[std::min(amins.size() - 1,
1347 std::size_t(rng_.
uniform() *
double(amins.size())))];
1349 const std::vector<int>& rp = sn_.
nodes[x.node].routing_param;
1350 const int dp = rp.size() > x.cls ? rp[x.cls] : 0;
1351 const std::size_t dsample = std::max<std::size_t>(1, std::min<std::size_t>(dp > 0 ? dp : 2, nd));
1353 for (std::size_t t = 0; t < dsample; ++t) {
1354 const std::size_t cand =
1355 std::min(nd - 1, std::size_t(rng_.
uniform() *
double(nd)));
1356 const std::vector<double> cc = class_counts(X, x.sd_node[cand]);
1358 for (
double v : cc) load += v;
1359 if (t == 0 || load < best) {
1367 const std::size_t c = rr_cursor_[cursor_key];
1368 const double u = double(c % nd) / double(nd);
1371 for (std::size_t d = 0; d < nd; ++d) {
1372 acc += x.sd_weight[d];
1373 if (u < acc - 1e-12) {
1378 rr_cursor_[cursor_key] = c + 1;
1380 pick = rr_cursor_[cursor_key] % nd;
1381 rr_cursor_[cursor_key] = rr_cursor_[cursor_key] + 1;
1384 const std::vector<std::size_t>& slots = x.sd_slots[pick];
1385 if (slots.size() == 1)
return slots[0];
1386 const double u = rng_.
uniform();
1388 for (std::size_t k = 0; k + 1 < slots.size(); ++k) {
1389 acc += x.sd_pie[pick][k];
1390 if (u < acc)
return slots[k];
1392 return slots.back();
1405void NrmEngine<T>::build_dependencies() {
1406 const std::size_t nrx = rx_.size();
1407 D_.assign(nrx, std::vector<std::size_t>());
1408 for (std::size_t k = 0; k < nrx; ++k) {
1409 std::vector<bool> touched_node(I_,
false);
1410 for (std::size_t i = 0; i < NS_; ++i)
1411 if (S_(i, k) != 0.0) touched_node[slot_node_[i]] =
true;
1412 std::vector<bool> hit(nrx,
false);
1413 for (std::size_t ind = 0; ind < I_; ++ind) {
1414 if (!touched_node[ind])
continue;
1415 for (std::size_t r = 0; r < K_; ++r)
1416 for (std::size_t p = 0; p < nph_[ind][r]; ++p) {
1417 const std::size_t slot = phoff_[ind][r] + p;
1418 for (std::size_t j = 0; j < nrx; ++j)
1419 if (S_(slot, j) < 0.0) hit[j] =
true;
1422 for (std::size_t j = 0; j < nrx; ++j)
1423 if (hit[j]) D_[k].push_back(j);
1439void NrmEngine<T>::build_initial_state() {
1440 nvec0_.assign(NS_, 0.0);
1444 cache_var0_.assign(I_, std::vector<T>());
1445 for (std::size_t ind = 0; ind < I_; ++ind) {
1446 if (!is_cache_node_[ind])
continue;
1447 const qn::CacheParam<T>& cp = sn_.
nodeparam.find(ind + 1)->second;
1448 std::size_t tcc = 0;
1449 for (
int c : cp.itemcap)
1450 if (c > 0) tcc +=
static_cast<std::size_t
>(c);
1451 std::vector<std::size_t> rcl, rci, rco;
1452 const bool retr = cp.retrieval_capacity > 0 && !cp.retrieval_classes.empty();
1454 std::vector<T>& v = cache_var0_[ind];
1455 if (cp.initstate.size() > K_) {
1456 v.assign(cp.initstate.begin() +
static_cast<std::ptrdiff_t
>(K_), cp.initstate.end());
1458 for (std::size_t c = 0; c < tcc; ++c)
1459 v.push_back(num_traits<T>::from_int(
static_cast<long>(c + 1)));
1461 const std::size_t want = tcc + (retr ? cp.nitems + rcl.size() : 0);
1462 v.resize(want, num_traits<T>::from_int(0));
1464 buffers0_.assign(I_, std::vector<std::size_t>());
1465 svcph0_.assign(I_, Matrix<double>());
1466 for (std::size_t i = 0; i < I_; ++i)
1467 if (buf_ph_node_[i]) svcph0_[i] = Matrix<double>(K_, maxnph_, 0.0);
1469 std::vector<std::vector<double>> nir(I_, std::vector<double>(K_, 0.0));
1470 for (std::size_t r = 0; r < K_; ++r) {
1471 const double pop = sn_.
classes[r].population;
1472 if (std::isinf(pop)) {
1475 "solver_ssa_nrm: the open class '" + sn_.
classes[r].name +
1476 "' has no Source; an open model must carry one for the arrival reaction");
1478 }
else if (pop > 0.0) {
1479 const std::size_t rs = sn_.
classes[r].refstat;
1480 if (rs < 1 || rs > M_)
1482 "' has no reference station");
1487 for (std::size_t ind = 0; ind < I_; ++ind) {
1488 for (std::size_t r = 0; r < K_; ++r) {
1489 const double n = nir[ind][r];
1490 if (n <= 0.0)
continue;
1491 if (nph_[ind][r] <= 1 || buf_ph_class_[ind][r]) {
1492 nvec0_[phoff_[ind][r]] = n;
1495 for (std::size_t ke = 0; ke < nph_[ind][r]; ++ke) {
1496 const double take = (ke + 1 == nph_[ind][r])
1498 : std::min(left, std::round(n * pie_[ind][r][ke]));
1499 nvec0_[phoff_[ind][r] + ke] = take;
1505 if (is_station_[ind] && detail::sched_is_list(sched_[to_station_[ind]]))
1506 for (std::size_t r = 0; r < K_; ++r)
1507 for (std::size_t c = 0; c < static_cast<std::size_t>(nir[ind][r]); ++c)
1508 buffers0_[ind].push_back(r);
1512 if (is_station_[ind] && detail::sched_is_buffered(sched_[to_station_[ind]])) {
1514 for (std::size_t r = 0; r < K_; ++r) total += nir[ind][r];
1515 double waiting = std::max(0.0, total - mi_[ind]);
1516 for (std::size_t r = 0; r < K_ && waiting > 0.0; ++r) {
1517 double take = std::min(waiting, nir[ind][r]);
1518 for (std::size_t c = 0; c < static_cast<std::size_t>(take); ++c)
1519 buffers0_[ind].push_back(r);
1523 if (!buf_ph_node_[ind])
continue;
1524 for (std::size_t r = 0; r < K_; ++r) {
1525 double waiting_r = 0.0;
1526 for (std::size_t c : buffers0_[ind])
1527 if (c == r) waiting_r += 1.0;
1528 const double insvc = std::max(0.0, nir[ind][r] - waiting_r);
1529 if (nph_[ind][r] <= 1) {
1530 svcph0_[ind](r, 0) = insvc;
1532 double left = insvc;
1533 for (std::size_t ke = 0; ke < nph_[ind][r]; ++ke) {
1534 const double take = (ke + 1 == nph_[ind][r])
1536 : std::min(left, std::round(insvc * pie_[ind][r][ke]));
1537 svcph0_[ind](r, ke) = take;
1558double NrmEngine<T>::propensity(std::size_t j,
const std::vector<double>& X,
1559 const std::vector<std::vector<std::size_t>>& bufs,
1560 const std::vector<Matrix<double>>& svc)
const {
1561 const Rx& x = rx_[j];
1562 const std::size_t ind = x.node, r = x.cls;
1563 if (!is_station_[ind])
1564 return x.rate * kir_frac(X, x.from, ind, r) * std::min(1.0, class_pop(X, ind, r));
1566 const std::size_t ist = to_station_[ind];
1572 const PollCtrl& c = poll_ctrl_[ind];
1573 if (c.mode != 2)
return 0.0;
1574 return -num_traits<T>::to_double(poll_info_[ind].sw_d0[c.pos - 1](c.swk, c.swk));
1579 if (buf_ph_class_[ind][r]) {
1580 const std::size_t kk = x.is_phase ? x.phase_from : x.dep_phase;
1581 const std::vector<double> n = class_counts(X, ind);
1583 for (
double v : n)
tot += v;
1584 return x.rate * svc[ind](r, kk) * lldfac(ist, tot) * cdfac(ist, n, r);
1587 const double kf = kir_frac(X, x.from, ind, r);
1588 switch (sched_[ist]) {
1589 case SchedStrategy::EXT:
1594 case SchedStrategy::INF:
1595 return x.rate * kf * class_pop(X, ind, r) * cdfac(ist, class_counts(X, ind), r);
1596 case SchedStrategy::PS:
1597 case SchedStrategy::LPS: {
1599 return x.rate * kf * std::min(mi_[ind], class_pop(X, ind, r)) *
1600 lldfac(ist, class_pop(X, ind, r)) * cdfac(ist, class_counts(X, ind), r);
1601 const std::vector<double> n = class_counts(X, ind);
1603 for (
double v : n)
tot += v;
1604 return x.rate * kf * (n[r] / (eps +
tot)) * std::min(mi_[ind], eps + tot) *
1605 lldfac(ist, tot) * cdfac(ist, n, r);
1607 case SchedStrategy::DPS: {
1608 const std::vector<double> n = class_counts(X, ind);
1610 for (
double v : n)
tot += v;
1611 return x.rate * kf * dpsshare(wnorm_[ist], n, r) * lldfac(ist, tot) *
1614 case SchedStrategy::GPS: {
1615 const std::vector<double> n = class_counts(X, ind);
1617 for (
double v : n)
tot += v;
1618 return x.rate * kf * gpsshare(wnorm_[ist], n, r) * lldfac(ist, tot) *
1626 case SchedStrategy::PSPRIO: {
1627 const std::vector<double> n = class_counts(X, ind);
1629 for (
double v : n)
tot += v;
1630 return x.rate * kf * psprioshare(n, r, mi_[ind]) * lldfac(ist, tot) *
1633 case SchedStrategy::DPSPRIO: {
1634 const std::vector<double> n = class_counts(X, ind);
1635 return x.rate * kf * dpsprioshare(wnorm_[ist], n, r, mi_[ind]) *
1636 lldfac(ist,
prio_pop(n, r, mi_[ind])) *
1637 cdfac(ist, prio_vec(n, r, mi_[ind]), r);
1639 case SchedStrategy::GPSPRIO: {
1640 const std::vector<double> n = class_counts(X, ind);
1641 return x.rate * kf * gpsprioshare(wnorm_[ist], n, r, mi_[ind]) *
1642 lldfac(ist,
prio_pop(n, r, mi_[ind])) *
1643 cdfac(ist, prio_vec(n, r, mi_[ind]), r);
1645 case SchedStrategy::FCFS:
1646 case SchedStrategy::LCFS:
1647 case SchedStrategy::SIRO:
1648 case SchedStrategy::HOL:
1649 case SchedStrategy::SEPT:
1650 case SchedStrategy::LEPT:
1651 case SchedStrategy::LCFSPR: {
1654 double waiting = 0.0;
1655 for (std::size_t c : bufs[ind])
1656 if (c == r) waiting += 1.0;
1657 const std::vector<double> n = class_counts(X, ind);
1659 for (
double v : n)
tot += v;
1660 return x.rate * kf * std::max(0.0, n[r] - waiting) * lldfac(ist, tot) *
1663 case SchedStrategy::POLLING: {
1667 const PollCtrl& c = poll_ctrl_[ind];
1668 if (c.mode != 1 || c.pos != r + 1)
return 0.0;
1669 const std::vector<double> n = class_counts(X, ind);
1671 for (
double v : n)
tot += v;
1672 return x.rate * lldfac(ist, tot) * cdfac(ist, n, r);
1674 case SchedStrategy::PAS:
1675 case SchedStrategy::OI:
1679 return pas_rate(ist, bufs[ind], r);
1682 "solver_ssa_nrm: the scheduling policy '" +
1684 sn_.
stations[ist].name +
"' has no NRM rate law in this port");
1694std::size_t NrmEngine<T>::pick_from_buffer(
const std::vector<std::size_t>& buf, std::size_t ist) {
1695 switch (sched_[ist]) {
1696 case SchedStrategy::FCFS:
1697 return buf.size() - 1;
1698 case SchedStrategy::LCFS:
1699 case SchedStrategy::LCFSPR:
1701 case SchedStrategy::SIRO:
1702 return rng_.
index(buf.size());
1703 case SchedStrategy::HOL: {
1706 double best = std::numeric_limits<double>::infinity();
1707 for (std::size_t c : buf) best = std::min(best, classprio_[c]);
1708 for (std::size_t p = buf.size(); p-- > 0;)
1709 if (classprio_[buf[p]] == best)
return p;
1710 return buf.size() - 1;
1712 case SchedStrategy::SEPT:
1713 case SchedStrategy::LEPT: {
1717 double best = std::numeric_limits<double>::infinity();
1718 for (std::size_t c : buf)
1719 best = std::min(best, num_traits<T>::to_double(sn_.
stations[ist].schedparam[c]));
1720 for (std::size_t p = buf.size(); p-- > 0;)
1721 if (num_traits<T>::to_double(sn_.
stations[ist].schedparam[buf[p]]) == best)
1723 return buf.size() - 1;
1728 "' is not a buffered policy this port promotes from");
1734std::size_t NrmEngine<T>::draw_entry_phase(std::size_t ind, std::size_t r) {
1735 if (nph_[ind][r] <= 1)
return 0;
1736 return rng_.
draw(pie_[ind][r]);
1749bool NrmEngine<T>::capacity_loss(
const std::vector<double>& X, std::size_t slot)
const {
1750 const std::size_t jnd = slot_node_[slot], dst = slot_class_[slot];
1751 if (!is_station_[jnd])
return false;
1752 const std::size_t ist = to_station_[jnd];
1754 if (dr == qn::DropStrategy::WAITQ)
return false;
1755 if (dr == qn::DropStrategy::BAS || dr == qn::DropStrategy::BBS ||
1756 dr == qn::DropStrategy::RSRD)
1758 "' declares the blocking drop rule for class '" +
1760 "'; blocking-after-service is not ported to the C++ NRM");
1761 if (!std::isinf(sn_.
classes[dst].population))
return false;
1762 const std::vector<double> cc = class_counts(X, jnd);
1764 for (
double v : cc)
tot += v;
1765 if (!std::isinf(sn_.
cap[ist]) && tot >= sn_.
cap[ist])
return true;
1766 const double ccap = sn_.
classcap[ist][dst];
1767 return ccap > 0.0 && !std::isinf(ccap) && cc[dst] >= ccap;
1796bool NrmEngine<T>::capacity_block(
const std::vector<double>& X, std::size_t slot,
1797 std::size_t src_slot)
const {
1798 if (!blk_on_)
return false;
1799 const std::size_t jnd = slot_node_[slot], dst = slot_class_[slot];
1800 if (!is_station_[jnd])
return false;
1801 const std::size_t ist = to_station_[jnd];
1802 if (!blk_can_[ist][dst])
return false;
1803 const bool same_node = src_slot != npos && slot_node_[src_slot] == jnd;
1804 const std::vector<double> cc = class_counts(X, jnd);
1805 if (!std::isinf(sn_.
cap[ist])) {
1807 for (
double v : cc)
tot += v;
1808 if (same_node)
tot -= 1.0;
1809 if (tot >= sn_.
cap[ist])
return true;
1811 const double ccap = sn_.
classcap[ist][dst];
1812 if (ccap > 0.0 && !std::isinf(ccap)) {
1813 double pop = cc[dst];
1814 if (same_node && slot_class_[src_slot] == dst) pop -= 1.0;
1815 if (pop >= ccap)
return true;
1822std::size_t NrmEngine<T>::pick_preempted(
const std::vector<double>& X,
1823 const std::vector<std::size_t>& buf, std::size_t jnd,
1824 std::size_t arr_class) {
1825 std::vector<double> insvc = class_counts(X, jnd);
1826 for (std::size_t r = 0; r < K_; ++r) {
1827 for (std::size_t c : buf)
1828 if (c == r) insvc[r] -= 1.0;
1829 if (r == arr_class) insvc[r] -= 1.0;
1830 if (insvc[r] < 0.0) insvc[r] = 0.0;
1833 for (
double v : insvc)
tot += v;
1834 if (tot <= 0.0)
return npos;
1837 std::size_t last = npos;
1838 for (std::size_t r = 0; r < K_; ++r) {
1839 if (!(insvc[r] > 0.0))
continue;
1842 if (u < acc)
return r;
1849void NrmEngine<T>::apply_arrival_buffer(std::size_t jnd, std::size_t s,
1850 const std::vector<double>& X,
1851 std::vector<std::vector<std::size_t>>& bufs,
1852 std::vector<Matrix<double>>& svc,
bool& svc_changed) {
1853 if (is_station_[jnd] && detail::sched_is_list(sched_[to_station_[jnd]])) {
1857 const std::vector<std::size_t> old = bufs[jnd];
1858 bufs[jnd].push_back(s);
1863 if (!is_poll_.empty() && is_poll_[jnd])
return;
1864 if (!is_station_[jnd] || !detail::sched_is_buffered(sched_[to_station_[jnd]])) {
1867 if (is_station_[jnd]) tag(jnd, s,
false);
1870 const std::vector<double> cc = class_counts(X, jnd);
1872 for (
double v : cc) total += v;
1873 bool entered =
false;
1874 if (total > mi_[jnd]) {
1875 if (sched_[to_station_[jnd]] == SchedStrategy::LCFSPR) {
1879 const std::size_t c = pick_preempted(X, bufs[jnd], jnd, s);
1881 bufs[jnd].insert(bufs[jnd].begin(), c);
1886 bufs[jnd].insert(bufs[jnd].begin(), s);
1891 if (entered) tag(jnd, s,
false);
1892 if (entered && buf_ph_node_[jnd]) {
1893 svc[jnd](s, draw_entry_phase(jnd, s)) += 1.0;
1919void NrmEngine<T>::cache_fire(
const Rx& x, std::vector<double>& X, std::size_t& dest_pos,
1921 const std::size_t ind = x.node, r = x.cls;
1922 const qn::CacheParam<T>& cp = sn_.
nodeparam.find(ind + 1)->second;
1923 std::vector<T> row(K_, num_traits<T>::from_int(0));
1924 row[r] = num_traits<T>::from_int(1);
1925 row.insert(row.end(), cache_var_[ind].begin(), cache_var_[ind].end());
1926 const qn::EventOutcome<T> o =
1928 std::vector<double> w(o.space.size(), 0.0);
1930 for (std::size_t j = 0; j < o.space.size(); ++j) {
1931 w[j] = num_traits<T>::to_double(o.rate[j]) * num_traits<T>::to_double(o.prob[j]);
1932 if (!(w[j] > 0.0)) w[j] = 0.0;
1936 throw NumericError(ErrorKind{
"Degenerate"},
"solver_ssa_nrm: the READ of class '" + sn_.
classes[r].name +
1937 "' at Cache '" + sn_.
nodes[ind].name +
"' has no enabled outcome");
1938 const std::vector<T>& nx = o.space[rng_.
draw(w)];
1940 cache_var_[ind].assign(nx.begin() +
static_cast<std::ptrdiff_t
>(K_), nx.end());
1942 std::vector<std::size_t> rcl, rci, rco;
1944 const bool from_retr = std::find(rcl.begin(), rcl.end(), r + 1) != rcl.end();
1945 double produced = 0.0;
1946 for (std::size_t c = 0; c < K_; ++c) produced += num_traits<T>::to_double(nx[c]);
1947 if (produced == 0.0) {
1948 cache_dly_(ind, r) += 1.0;
1951 for (std::size_t c = 0; c < K_; ++c) {
1952 const double d = num_traits<T>::to_double(nx[c]);
1953 if (!(d > 0.0))
continue;
1954 const bool begins = !from_retr && std::find(rcl.begin(), rcl.end(), c + 1) != rcl.end();
1956 X[phoff_[ind][c]] += d;
1957 cache_prod_(ind, c) += d;
1959 dest_pos = phoff_[ind][c];
1965 std::size_t tcc = 0;
1966 for (
int m : cp.itemcap)
1967 if (m > 0) tcc +=
static_cast<std::size_t
>(m);
1968 for (std::size_t it = 0; it < cp.retrieval_classes.size(); ++it)
1969 if (r < cp.retrieval_classes[it].size() && cp.retrieval_classes[it][r] == c + 1 &&
1970 tcc + it < cache_var_[ind].size())
1971 cache_var_[ind][tcc + it] = num_traits<T>::from_int(1);
1974 std::vector<std::size_t> slots;
1975 std::vector<double> sw;
1976 for (std::size_t jnd = 0; jnd < I_; ++jnd)
1977 for (std::size_t s = 0; s < K_; ++s) {
1979 num_traits<T>::to_double(sn_.
rtnodes(ind * K_ + c, jnd * K_ + s));
1980 if (!(p > 0.0))
continue;
1981 if (buf_ph_class_[jnd][s]) {
1982 slots.push_back(phoff_[jnd][s]);
1986 for (std::size_t ke = 0; ke < nph_[jnd][s]; ++ke)
1987 if (pie_[jnd][s][ke] > 0.0) {
1988 slots.push_back(phoff_[jnd][s] + ke);
1989 sw.push_back(p * pie_[jnd][s][ke]);
1994 "' at Cache '" + sn_.
nodes[ind].name +
"' routes nowhere");
1995 const std::size_t slot = slots[rng_.
draw(sw)];
2009void NrmEngine<T>::cache_write_back() {
2011 const double nan = std::numeric_limits<double>::quiet_NaN();
2012 for (std::size_t ind = 0; ind < I_; ++ind) {
2013 if (!is_cache_node_[ind])
continue;
2014 const qn::CacheParam<T>& cp = sn_.
nodeparam.find(ind + 1)->second;
2017 cr.hitprob.assign(K_, nan);
2018 cr.missprob.assign(K_, nan);
2019 cr.residt.assign(K_, nan);
2020 std::vector<double> dly(K_, 0.0);
2021 bool any_delayed =
false;
2022 for (std::size_t k = 0; k < K_; ++k) {
2023 if (k >= cp.hitclass.size() || k >= cp.missclass.size())
continue;
2024 const std::size_t h = cp.hitclass[k], mi = cp.missclass[k];
2025 if (h == 0 || mi == 0 || h > K_ || mi > K_)
continue;
2026 const double th = cache_prod_(ind, h - 1), tm = cache_prod_(ind, mi - 1);
2027 const double td = cache_dly_(ind, k);
2028 if (th + tm > 0.0) {
2029 cr.hitprob[k] = std::max(th - td, 0.0) / (th + tm);
2030 cr.missprob[k] = tm / (th + tm);
2031 dly[k] = td / (th + tm);
2032 if (td > 0.0) any_delayed =
true;
2035 if (any_delayed) cr.delayedprob = dly;
2036 cache_out_.push_back(cr);
2042void NrmEngine<T>::update_buffers(std::size_t kfire,
const std::vector<double>& X,
2043 std::vector<std::vector<std::size_t>>& bufs,
2044 std::size_t dest_pos,
bool have_dest,
2045 std::vector<Matrix<double>>& svc,
bool& svc_changed) {
2046 const Rx& x = rx_[kfire];
2047 const std::size_t ind = x.node;
2050 if (buf_ph_node_[ind] && x.is_buf_svc && !x.is_phase) {
2051 svc[ind](x.cls, x.dep_phase) -= 1.0;
2059 if (is_station_[ind] && detail::sched_is_list(sched_[to_station_[ind]]) &&
2060 !bufs[ind].empty() && !x.is_phase) {
2061 const std::vector<std::size_t> old = bufs[ind];
2062 pas_depart(ind, bufs[ind], x.cls);
2064 }
else if (is_station_[ind] && detail::sched_is_buffered(sched_[to_station_[ind]]) &&
2065 !bufs[ind].empty() && !x.is_phase) {
2066 const std::size_t pos = pick_from_buffer(bufs[ind], to_station_[ind]);
2067 const std::size_t promoted = bufs[ind][pos];
2068 bufs[ind].erase(bufs[ind].begin() +
static_cast<std::ptrdiff_t
>(pos));
2069 tag(ind, promoted,
false);
2070 if (buf_ph_node_[ind]) {
2071 svc[ind](promoted, draw_entry_phase(ind, promoted)) += 1.0;
2076 apply_arrival_buffer(slot_node_[dest_pos], slot_class_[dest_pos], X, bufs, svc,
2087 const std::size_t nrx = rx_.size();
2093 out.
CN.assign(K_, 0.0);
2094 out.
XN.assign(K_, 0.0);
2101 if (nrx == 0)
return out;
2103 std::vector<double> nvec = nvec0_;
2104 std::vector<std::vector<std::size_t>> bufs = buffers0_;
2105 std::vector<Matrix<double>> svc = svcph0_;
2107 std::vector<std::vector<std::size_t>> fcr_buf(fcr_.size());
2108 cache_var_ = cache_var0_;
2113 poll_ctrl_.assign(I_, PollCtrl());
2114 for (std::size_t ind = 0; ind < I_; ++ind) {
2115 if (!is_poll_[ind])
continue;
2119 qn::polling_next(poll_info_[ind], 1, poll_nbuf(nvec, ind), K_,
true, q, mode, budget);
2120 poll_ctrl_[ind] = poll_land(ind, q, mode, budget,
false);
2123 std::vector<double> Ak(nrx, 0.0), Pk(nrx, 0.0), Tk(nrx, 0.0), tau(nrx, 0.0);
2124 for (std::size_t k = 0; k < nrx; ++k) {
2125 Ak[k] = propensity(k, nvec, bufs, svc);
2126 Pk[k] = -std::log(rng_.uniform());
2127 tau[k] = Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : std::numeric_limits<double>::infinity();
2130 double total_time = 0.0;
2133 static_cast<std::size_t
>(opt_.samples));
2134 const std::size_t console_every = std::max<std::size_t>(1, opt_.samples / 20);
2139 for (; n < opt_.samples; ++n) {
2140 if (n == n_warm && n_warm > 0) {
2151 if ((n + 1) % console_every == 0)
2153 static_cast<long>((n + 1) / console_every),
2154 "simulated %zu of %zu samples (%.0f%%), simulated time %.4g", n + 1,
2155 static_cast<std::size_t
>(opt_.samples),
2156 100.0 *
static_cast<double>(n + 1) /
static_cast<double>(opt_.samples),
2158 std::size_t kfire = 0;
2159 double dt = std::numeric_limits<double>::infinity();
2160 for (std::size_t k = 0; k < nrx; ++k)
2167 "solver_ssa_nrm: deadlock -- every reaction has propensity zero, so the sample "
2168 "path cannot advance");
2172 for (std::size_t ist = 0; ist < M_; ++ist) {
2173 const std::size_t ind = sn_.station_to_node[ist] - 1;
2174 const std::vector<double> npop = class_counts(nvec, ind);
2175 double totpop = 0.0;
2176 for (
double v : npop) totpop += v;
2189 const bool is_source = sched_[ist] == SchedStrategy::EXT;
2190 for (std::size_t k = 0; k < K_; ++k) {
2192 for (std::size_t jd : dep_rx_[ist][k]) dep += Ak[jd];
2193 out.
TN(ist, k) += dep * dt;
2194 if (is_source)
continue;
2195 out.
QN(ist, k) += npop[k] * dt;
2196 switch (sched_[ist]) {
2197 case SchedStrategy::INF:
2198 out.
UN(ist, k) += npop[k] * dt;
2200 case SchedStrategy::PS:
2201 case SchedStrategy::LPS:
2203 out.
UN(ist, k) += (npop[k] / totpop) *
2204 std::min(nservers_[ist], totpop) / nservers_[ist] *
2207 case SchedStrategy::DPS:
2208 out.
UN(ist, k) += dpsshare(wnorm_[ist], npop, k) / nservers_[ist] * dt;
2210 case SchedStrategy::GPS:
2211 out.
UN(ist, k) += gpsshare(wnorm_[ist], npop, k) / nservers_[ist] * dt;
2218 case SchedStrategy::PSPRIO:
2219 out.
UN(ist, k) += psprioshare(npop, k, nservers_[ist]) / nservers_[ist] * dt;
2221 case SchedStrategy::DPSPRIO:
2223 dpsprioshare(wnorm_[ist], npop, k, nservers_[ist]) / nservers_[ist] * dt;
2225 case SchedStrategy::GPSPRIO:
2227 gpsprioshare(wnorm_[ist], npop, k, nservers_[ist]) / nservers_[ist] * dt;
2230 case SchedStrategy::POLLING:
2231 if (poll_ctrl_[ind].mode == 1 && poll_ctrl_[ind].pos == k + 1)
2232 out.
UN(ist, k) += dt / nservers_[ist];
2236 case SchedStrategy::PAS:
2237 case SchedStrategy::OI:
2238 out.
UN(ist, k) += pas_in_service(ist, bufs[ind], k) / nservers_[ist] * dt;
2240 case SchedStrategy::FCFS:
2241 case SchedStrategy::LCFS:
2242 case SchedStrategy::SIRO:
2243 case SchedStrategy::HOL:
2244 case SchedStrategy::SEPT:
2245 case SchedStrategy::LEPT:
2246 case SchedStrategy::LCFSPR: {
2247 if (sn_.disabled[ist][k])
break;
2248 double waiting = 0.0;
2249 for (std::size_t c : bufs[ind])
2250 if (c == k) waiting += 1.0;
2251 out.
UN(ist, k) += ((npop[k] - waiting) / nservers_[ist]) * dt;
2261 const Rx& x = rx_[kfire];
2262 std::size_t dest_pos = npos;
2263 bool have_dest =
false;
2267 bool blocked =
false;
2269 cache_fire(x, nvec, dest_pos, have_dest);
2270 }
else if (x.nnzP > 1 || !x.sd_node.empty()) {
2272 if (!x.sd_node.empty()) {
2274 slot = resolve_state_dependent_dest(kfire, nvec);
2276 const double u = rng_.uniform();
2277 std::size_t sel = x.cdf.size() - 1;
2278 for (std::size_t i = 0; i < x.cdf.size(); ++i)
2283 slot = x.to_slots[sel];
2288 if (capacity_block(nvec, slot, x.from)) {
2294 bool lost = capacity_loss(nvec, slot);
2297 if (!lost && !fcr_.empty())
2298 fcr_outcome(nvec, x, slot_node_[slot], slot_class_[slot], fcr_buf, lost, blocked);
2300 for (std::size_t s : x.from_slots) nvec[s] -= 1.0;
2308 }
else if (x.det_dest != npos && !x.is_phase &&
2309 capacity_block(nvec, x.det_dest, x.from)) {
2312 bool lost = x.det_dest != npos && !x.is_phase && capacity_loss(nvec, x.det_dest);
2315 if (!lost && x.det_dest != npos && !x.is_phase && !fcr_.empty())
2316 fcr_outcome(nvec, x, slot_node_[x.det_dest], slot_class_[x.det_dest], fcr_buf, lost,
2321 nvec[x.from] -= 1.0;
2323 for (std::size_t i = 0; i < NS_; ++i)
2324 if (S_(i, kfire) != 0.0) nvec[i] += S_(i, kfire);
2325 if (x.det_dest != npos) {
2326 dest_pos = x.det_dest;
2332 bool svc_changed =
false;
2335 if (is_station_[x.node]) block_cnt_(to_station_[x.node], x.cls) += 1.0;
2336 }
else if (x.is_phase && buf_ph_node_[x.node]) {
2339 svc[x.node](x.cls, x.phase_from) -= 1.0;
2340 svc[x.node](x.cls, x.phase_to) += 1.0;
2342 }
else if (!x.is_sw) {
2343 update_buffers(kfire, nvec, bufs, dest_pos, have_dest, svc, svc_changed);
2346 if (x.is_cache) svc_changed =
true;
2353 if (!is_poll_.empty() && !blocked) {
2355 const std::size_t ind = x.node;
2356 PollCtrl& c = poll_ctrl_[ind];
2357 const Matrix<T>& D0 = poll_info_[ind].sw_d0[c.pos - 1];
2358 const std::size_t Ksw = D0.
rows();
2359 std::vector<double> w(Ksw + 1, 0.0);
2360 double rowsum = 0.0;
2361 for (std::size_t kd = 0; kd < Ksw; ++kd) {
2364 if (kd != c.swk && v > 0.0) w[kd] = v;
2366 w[Ksw] = std::max(0.0, -rowsum);
2367 const std::size_t pick = rng_.draw(w);
2368 if (pick < Ksw && pick != c.swk) {
2371 std::size_t q = c.pos;
2374 qn::polling_next(poll_info_[ind], c.pos, poll_nbuf(nvec, ind), K_,
true, q,
2376 c = poll_land(ind, q, mode, budget);
2379 }
else if (is_poll_[x.node] && !x.is_phase) {
2380 const std::size_t ind = x.node;
2381 PollCtrl& c = poll_ctrl_[ind];
2383 const std::vector<long> nb = poll_nbuf(nvec, ind);
2388 goon = nb[c.pos - 1] > 0;
2391 ctrnext = c.ctr - 1;
2395 ctrnext = c.ctr - 1;
2396 goon = ctrnext > 0 && nb[c.pos - 1] > 0;
2400 goon = nb[c.pos - 1] > c.ctr;
2406 c = poll_land(ind, c.pos, 1, ctrnext);
2408 std::size_t q = c.pos;
2412 c = poll_land(ind, q, mode, budget);
2416 if (have_dest && is_poll_[slot_node_[dest_pos]]) {
2417 const std::size_t jnd = slot_node_[dest_pos];
2418 PollCtrl& c = poll_ctrl_[jnd];
2420 std::size_t q = c.pos;
2423 qn::polling_next(poll_info_[jnd], c.pos, poll_nbuf(nvec, jnd), K_,
true, q,
2425 c = poll_land(jnd, q, mode, budget);
2434 if (fcr_any_waitq_ && !blocked &&
2435 fcr_release_cascade(nvec, bufs, fcr_buf, svc, svc_changed) > 0)
2438 for (std::size_t k = 0; k < nrx; ++k) Tk[k] += Ak[k] * dt;
2443 for (std::size_t k = 0; k < nrx; ++k) Ak[k] = propensity(k, nvec, bufs, svc);
2445 for (std::size_t k : D_[kfire]) Ak[k] = propensity(k, nvec, bufs, svc);
2448 Pk[kfire] -= std::log(rng_.uniform());
2449 for (std::size_t k = 0; k < nrx; ++k)
2451 Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : std::numeric_limits<double>::infinity();
2454 if (total_time > 0.0)
2455 for (std::size_t ist = 0; ist < M_; ++ist)
2456 for (std::size_t k = 0; k < K_; ++k) {
2457 out.
QN(ist, k) /= total_time;
2458 out.
UN(ist, k) /= total_time;
2460 out.
TN(ist, k) = (out.
TN(ist, k) - block_cnt_(ist, k)) / total_time;
2464 out.
StartN(ist, k) = start_cnt_(ist, k) / total_time;
2465 out.
PreemptN(ist, k) = preempt_cnt_(ist, k) / total_time;
2472 for (std::size_t ist = 0; ist < M_; ++ist) {
2473 const auto& st = sn_.stations[ist];
2474 if (!st.cdscaling && !st.jdscaling)
continue;
2475 for (std::size_t k = 0; k < K_; ++k) {
2480 out.
UN(ist, k) = (std::isfinite(rate) && rate > 0.0 && peak > 0.0)
2481 ? out.
TN(ist, k) / rate / peak
2494 for (std::size_t ist = 0; ist < M_; ++ist) {
2495 if (lld_[ist].empty())
continue;
2496 double peak = nservers_[ist];
2497 bool non_unit =
false;
2498 for (
double a : lld_[ist]) {
2499 if (a != 1.0) non_unit =
true;
2500 if (a > peak) peak = a;
2502 if (!non_unit)
continue;
2503 if (sched_[ist] == SchedStrategy::INF || sched_[ist] == SchedStrategy::EXT)
2505 for (std::size_t k = 0; k < K_; ++k) {
2507 out.
UN(ist, k) = (std::isfinite(rate) && rate > 0.0 && peak > 0.0)
2508 ? out.
TN(ist, k) / rate / peak
2513 for (std::size_t k = 0; k < K_; ++k) {
2514 out.
XN[k] = out.
TN(sn_.classes[k].refstat - 1, k);
2515 for (std::size_t ist = 0; ist < M_; ++ist)
2516 out.
RN(ist, k) = out.
TN(ist, k) > 0.0 ? out.
QN(ist, k) / out.
TN(ist, k) : 0.0;
2517 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::map< std::size_t, CacheParam< T > > nodeparam
Cache parameters by 1-based NODE index; only Cache nodes have an entry.
std::map< std::size_t, PasParam > pasparam
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::vector< Region > regions
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.
const std::vector< SsaCacheRatio > & cache() const
The cache write-back of the last run(), one entry per Cache node.
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)
The (D0,D1) pair that reaches sn.proc.
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.
@ READ
a cache item is read
@ KLIMITED
serve at most K per visit (K in pollingPar)
@ EXHAUSTIVE
serve until the queue empties
@ GATED
serve exactly the jobs present at the polling instant
@ DECREMENTING
serve until the queue is one shorter than at arrival
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)
double tot(const std::vector< double > &pops)
void cache_retrieval_class_map(const CacheParam< T > &cp, std::vector< std::size_t > &rc_list, std::vector< std::size_t > &rc_items, std::vector< std::size_t > &rc_orig)
Port of State.cacheRetrievalClassMap: the canonical order of a cache's retrieval classes,...
void polling_next(const PollingInfo< T > &pi, std::size_t pos, const std::vector< long > &nbuf, std::size_t R, bool arrived, std::size_t &q, int &mode, long &budget)
Port of State.pollingNext: where the server goes from buffer pos.
EventOutcome< T > after_event_cache(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< T > &inspace, EventType event, std::size_t cls)
Port of State.afterEventCache: events at a Cache node.
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.
void pas_tag_started(EventOutcome< T > &out, const F &mu_fun, const std::vector< std::size_t > &cold, const std::vector< std::size_t > &cnew)
Tag the successor just appended to OUT with the PAS positions that started service on it: those of CN...
std::pair< std::vector< std::size_t >, std::size_t > pass_and_swap(const std::vector< std::size_t > &c, std::size_t p, const std::vector< std::vector< bool > > &G)
Port of State.passAndSwap: the transition a service completion triggers at a pass-and-swap station (D...
std::vector< double > pas_increments(const F &mu_fun, const std::vector< std::size_t > &c)
Per-position service rate increments Delta_mu(c1..cp) = mu(c1..cp) - mu(c1..c_{p-1}...
PollingInfo< T > polling_info(const NetworkStruct< T > &sn, std::size_t ind)
std::size_t ssa_warmup_firings(double warmupfrac, std::size_t samples)
Firings whose tallies an engine discards: floor(warmupfrac * samples), with an unset (negative) fract...
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
State.pollingInfo and the controller description it returns.
Controls, results and the random source of SolverSSA.
Port of the event half of MATLAB's +State package: the successor states an event produces at one node...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
static constexpr double Immediate
Rate of an Immediate distribution; its mean is 1/Immediate = 1e-8.
static constexpr double Zero
Port of State.pollingInfo: the derived description of a polling controller.
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
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