5#ifndef LINE_SOLVERS_SSA_SOLVER_SSA_NRM_SPN_H
6#define LINE_SOLVERS_SSA_SOLVER_SSA_NRM_SPN_H
109 std::size_t node = 0;
110 std::size_t mode = 0;
112 std::vector<double> S;
113 std::vector<std::size_t> en_slot;
114 std::vector<double> en_w;
115 std::vector<std::size_t> inh_slot;
116 std::vector<double> inh_thr;
117 double base_rate = 0.0;
118 double nservers = 1.0;
121 std::vector<std::size_t> dep_slots;
123 std::vector<std::size_t> flow_q;
124 std::vector<double> flow_rate;
125 std::vector<std::size_t> guard_q;
126 std::vector<int> guard_sgn;
127 std::vector<double> guard_thr;
128 std::vector<std::size_t> imp_q;
129 std::vector<double> imp_d;
130 std::vector<std::size_t> fdep_q;
131 std::vector<std::function<double(
double)>> fdep_fun;
133 std::vector<std::function<double(
const std::vector<double>&)>> flow_fun;
134 std::vector<std::vector<std::size_t>> flow_read;
136 std::function<double(
const std::vector<double>&)> gdep;
137 std::vector<std::size_t> gdep_read;
146inline double spn_en_degree(
const std::vector<double>& n,
const SpnReaction& rx) {
147 for (std::size_t i = 0; i < rx.inh_slot.size(); ++i)
148 if (n[rx.inh_slot[i]] >= rx.inh_thr[i])
return 0.0;
149 if (rx.en_slot.empty())
return 1.0;
150 double d = std::numeric_limits<double>::infinity();
151 for (std::size_t i = 0; i < rx.en_slot.size(); ++i)
152 d = std::min(d, std::floor(n[rx.en_slot[i]] / rx.en_w[i]));
162inline double spn_propensity(
const std::vector<double>& n,
const SpnReaction& rx) {
163 const double eff = std::min(spn_en_degree(n, rx), rx.nservers);
164 return eff <= 0.0 ? 0.0 : rx.base_rate * eff;
188bool ssa_check_firingdep(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
189 if (sn.has_fluid_places())
return true;
190 for (
typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
191 sn.transparam.begin();
192 it != sn.transparam.end(); ++it) {
193 const qn::TransitionParam<T>& tp = it->second;
194 for (std::size_t m = 0; m < tp.nmodes && m < tp.firingdep.size(); ++m) {
195 if (!tp.firingdep[m])
continue;
196 if (!raise)
return false;
197 const std::size_t ind = it->first;
199 "SolverSSA: transition '" +
200 (ind >= 1 && ind <= sn.nodes.size() ? sn.nodes[ind - 1].name : std::string(
"?")) +
201 "' mode " + std::to_string(m + 1) +
202 " uses a marking-dependent firing rate (setFiringRateDependence), which the "
203 "NRM does not support: it builds one constant-propensity reaction per timed "
204 "mode and never evaluates the handle. Ask for method='serial', which applies "
205 "it, or use SolverCTMC or SolverLDES");
221bool spn_nrm_supported(
const qn::NetworkStruct<T>& sn,
bool raise =
true) {
222 const std::size_t I = sn.nodes.size();
223 const std::size_t K = sn.nclasses;
224 bool any_transition =
false;
225 for (std::size_t i = 0; i < I; ++i)
226 if (sn.nodes[i].nodetype == qn::NodeType::Transition) any_transition =
true;
227 if (!any_transition)
return true;
228 if (!ssa_check_firingdep(sn, raise))
return false;
230 for (std::size_t i = 0; i < I; ++i) {
231 if (sn.nodes[i].nodetype == qn::NodeType::Transition) {
232 const typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
233 sn.transparam.find(i + 1);
234 if (it == sn.transparam.end())
continue;
235 const qn::TransitionParam<T>& tp = it->second;
236 for (std::size_t m = 0; m < tp.nmodes; ++m) {
239 const bool one_phase = m < tp.firingphases.size() && tp.firingphases[m] == 1;
240 const bool has_proc = m < tp.firingproc.size() && tp.firingproc[m].D1.rows() > 0;
241 if (one_phase && has_proc)
continue;
242 if (!raise)
return false;
244 "SolverSSA(method='nrm'): transition '" + sn.nodes[i].name +
"' mode " +
245 std::to_string(m + 1) +
246 " has a non-exponential timed firing. The reaction network carries one "
247 "constant-rate reaction per mode and no per-mode phase, so an in-flight "
248 "firing has nowhere to keep its phase; use method='serial'");
250 }
else if (sn.nodes[i].nodetype == qn::NodeType::Source) {
251 const std::size_t ist = sn.nodes[i].station;
252 if (ist == 0)
continue;
253 for (std::size_t r = 0; r < K; ++r) {
254 const double lambda = num_traits<T>::to_double(sn.rates(ist - 1, r));
255 if (std::isnan(lambda) || lambda <= 0.0)
continue;
257 if (!raise)
return false;
259 "SolverSSA(method='nrm'): source '" + sn.nodes[i].name +
"' class '" +
261 "' has a non-exponential arrival. An arrival into a Place is one "
262 "constant-propensity reaction here, which only a Poisson stream is; "
263 "use method='serial'");
266 for (std::size_t j = 0; j < I && !feeds; ++j) {
267 if (sn.nodes[j].nodetype != qn::NodeType::Place)
continue;
268 for (std::size_t s = 0; s < K && !feeds; ++s)
269 if (num_traits<T>::to_double(sn.rtnodes(i * K + r, j * K + s)) > 0.0)
273 if (!raise)
return false;
275 "SolverSSA(method='nrm'): source '" + sn.nodes[i].name +
"' class '" +
277 "' routes to no Place. The SPN path deposits an arrival into a Place "
278 "slot and has nowhere else to put one; use method='serial'");
281 }
else if (sn.nodes[i].nodetype == qn::NodeType::Place) {
282 const typename std::map<std::size_t, std::vector<T>>::const_iterator im =
283 sn.initmarking.find(i + 1);
284 if (im == sn.initmarking.end())
continue;
285 for (std::size_t r = 0; r < im->second.size(); ++r) {
286 if (std::isfinite(num_traits<T>::to_double(im->second[r])))
continue;
287 if (!raise)
return false;
288 throw UnsupportedError(ErrorKind{
"Place"},
"SolverSSA(method='nrm'): place '" + sn.nodes[i].name +
289 "' declares an infinite initial marking, which the "
290 "reaction network cannot count; use method='serial'");
311api::SnFluidVerdict ssa_fluid_refusal(
const qn::NetworkStruct<T>& sn,
const std::string& method) {
312 api::SnFluidVerdict v;
313 if (!sn.has_fluid_places())
return v;
316 v.reason =
"SolverSSA: " + v.reason;
319 if (method ==
"serial" || method ==
"ssa") {
321 v.reason =
"SolverSSA: the model is a fluid stochastic Petri net (Place.setFluid), whose levels only the "
322 "NRM Petri runner moves. Use options.method='nrm' or 'default'.";
325 for (
const qn::NodeDef& nd : sn.nodes)
326 if (nd.nodetype != qn::NodeType::Place && nd.nodetype != qn::NodeType::Transition &&
327 nd.nodetype != qn::NodeType::Source && nd.nodetype != qn::NodeType::Sink) {
329 v.reason =
"SolverSSA: a fluid stochastic Petri net may hold only Place, Transition, Source and Sink "
330 "nodes; the NRM Petri runner has no hand-off between a level and a queueing station.";
333 if (!spn_nrm_supported(sn,
false)) {
335 v.reason =
"SolverSSA: the fluid net has a non-exponential firing or Source arrival, or an infinite "
336 "marking, which the NRM Petri runner (the only engine that moves fluid levels) has no form for.";
345inline bool spn_fluid_guard_holds(
const SpnReaction& rec,
const std::vector<double>& f,
346 const std::vector<double>& v) {
347 for (std::size_t g = 0; g < rec.guard_q.size(); ++g) {
348 const std::size_t q = rec.guard_q[g];
349 const double sg =
static_cast<double>(rec.guard_sgn[g]);
350 const double thr = rec.guard_thr[g];
351 const double tolf = 1e-12 * std::max(1.0, std::fabs(thr));
352 const double d = sg * (f[q] - thr);
353 if (d > tolf)
continue;
354 if (d >= -tolf && sg * v[q] >= 0.0)
continue;
361inline double spn_fluid_dep(
const SpnReaction& rec,
const std::vector<double>& f,
const std::vector<double>& v,
364 for (std::size_t i = 0; i < rec.fdep_q.size(); ++i) {
365 const std::size_t q = rec.fdep_q[i];
366 h *= rec.fdep_fun[i](std::max(0.0, f[q] + v[q] * t));
372inline bool spn_fluid_varying(
const SpnReaction& rec,
const std::vector<double>& v) {
373 for (std::size_t i = 0; i < rec.fdep_q.size(); ++i)
374 if (v[rec.fdep_q[i]] != 0.0)
return true;
380double spn_simpson_panel(
const F& h,
double a,
double b,
double fa,
double fm,
double fb,
double whole,
381 double tol,
int depth) {
382 const double m = 0.5 * (a + b);
383 const double lm = 0.5 * (a + m), rm = 0.5 * (m + b);
384 const double flm = h(lm), frm = h(rm);
385 const double left = (m - a) / 6.0 * (fa + 4.0 * flm + fm);
386 const double right = (b - m) / 6.0 * (fm + 4.0 * frm + fb);
387 const double err = left + right - whole;
388 if (depth <= 0 || std::fabs(err) <= 15.0 * tol)
return left + right +
err / 15.0;
389 return spn_simpson_panel(h, a, m, fa, flm, fm, left, 0.5 * tol, depth - 1) +
390 spn_simpson_panel(h, m, b, fm, frm, fb, right, 0.5 * tol, depth - 1);
398inline double spn_fluid_hazard(
const SpnReaction& rec,
double a0,
const std::vector<double>& f,
399 const std::vector<double>& v,
double dt) {
400 if (dt <= 0.0)
return 0.0;
401 if (!spn_fluid_varying(rec, v))
return a0 * spn_fluid_dep(rec, f, v, 0.0) * dt;
402 const auto h = [&](
double t) {
return spn_fluid_dep(rec, f, v, t); };
403 const double fa = h(0.0), fm = h(0.5 * dt), fb = h(dt);
404 const double whole = dt / 6.0 * (fa + 4.0 * fm + fb);
405 const double tol = std::max(1e-12, 1e-10 * std::fabs(whole));
406 return a0 * spn_simpson_panel(h, 0.0, dt, fa, fm, fb, whole, tol, 50);
414inline double spn_fluid_fire_time(
const SpnReaction& rec,
double a0,
const std::vector<double>& f,
415 const std::vector<double>& v,
double need,
double horizon) {
416 const double inf = std::numeric_limits<double>::infinity();
417 if (!spn_fluid_varying(rec, v)) {
418 const double a = a0 * spn_fluid_dep(rec, f, v, 0.0);
419 return a <= 0.0 ? inf : need / a;
421 if (need <= 0.0)
return 0.0;
423 if (std::isfinite(horizon)) {
425 if (spn_fluid_hazard(rec, a0, f, v, hi) < need)
return inf;
427 const double a = std::max(a0 * spn_fluid_dep(rec, f, v, 0.0), std::numeric_limits<double>::min());
430 while (spn_fluid_hazard(rec, a0, f, v, hi) < need) {
432 if (++grow > 200)
return inf;
435 const auto g = [&](
double t) {
return spn_fluid_hazard(rec, a0, f, v, t) - need; };
453 : sn_(
sn), opt_(
opt), rng_(
opt.seed) {
466 using Rx = detail::SpnReaction;
472 std::size_t I_ = 0, K_ = 0, M_ = 0, NS_ = 0;
475 std::vector<Rx> imm_;
477 std::vector<std::vector<std::vector<std::size_t>>> consumers_;
479 std::vector<std::vector<std::vector<std::size_t>>> producers_;
481 std::vector<double> nvec0_;
482 std::vector<double> pcap_slot_;
484 std::vector<std::pair<double, std::vector<std::size_t>>> place_total_caps_;
485 bool has_caps_ =
false;
488 std::vector<long> fq_;
489 std::vector<std::size_t> fl_node_;
490 std::vector<std::size_t> fl_ist_;
491 std::vector<double> fl_cap_;
492 std::vector<double> fl_f0_;
494 std::vector<std::vector<double>> fm_;
497 static const std::size_t kMaxImmSteps = 100000;
499 std::size_t slot(std::size_t node0, std::size_t cls)
const {
return node0 * K_ + cls; }
502 Rx build_mode(std::size_t ind0, std::size_t m)
const;
503 void apply_caps(std::vector<double>& n,
const std::vector<std::size_t>& deposited)
const;
504 void collapse(std::vector<double>& n);
507 SsaSolution run_fluid();
508 std::size_t fluid_index(std::size_t node1)
const;
509 void fluid_mark_mul(
const std::vector<double>& n, std::vector<double>& gk);
510 void fluid_resolve(
const std::vector<double>& n,
const std::vector<double>& f, std::vector<double>& eff,
511 std::vector<double>& v, std::vector<double>& vin, std::vector<double>& vout)
const;
512 void fluid_slope(
const std::vector<double>& f,
const std::vector<double>& eff, std::vector<double>& v,
513 std::vector<double>& vin, std::vector<double>& vout)
const;
514 void fluid_next_det(
const std::vector<double>& f,
const std::vector<double>& v,
double& tdet,
515 std::size_t& qdet,
double& fdet)
const;
516 void fluid_impulse(std::vector<double>& f,
const Rx& rec, std::vector<double>& OF)
const;
517 void collapse_fluid(std::vector<double>& n, std::vector<double>& f,
const std::vector<double>& v,
518 std::vector<double>& OF);
530void NrmSpnEngine<T>::build() {
531 I_ = sn_.nodes.size();
535 consumers_.assign(I_, std::vector<std::vector<std::size_t>>(K_));
536 producers_.assign(I_, std::vector<std::vector<std::size_t>>(K_));
542 detail::ssa_check_firingdep(sn_,
true);
547 if (sn_.has_fluid_places()) {
549 if (!fv.ok)
throw UnsupportedError(ErrorKind{
"Structure"},
"solver_ssa_nrm_spn: " + fv.reason);
550 for (std::size_t ind = 0; ind < I_; ++ind) {
551 if (sn_.nodes[ind].nodetype != qn::NodeType::Place || !sn_.is_fluid_node(ind + 1))
continue;
552 const std::size_t ist = sn_.nodes[ind].station;
553 const qn::Station<T>& st = sn_.stations[ist - 1];
554 fq_[ind] =
static_cast<long>(fl_node_.size());
555 fl_node_.push_back(ind);
556 fl_ist_.push_back(ist - 1);
557 fl_cap_.push_back(st.fluidcap);
558 fl_f0_.push_back(st.fluidinit);
562 for (std::size_t ind = 0; ind < I_; ++ind) {
563 if (sn_.nodes[ind].nodetype != qn::NodeType::Transition)
continue;
564 const typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
565 sn_.transparam.find(ind + 1);
566 if (it == sn_.transparam.end())
continue;
567 const qn::TransitionParam<T>& tp = it->second;
568 for (std::size_t m = 0; m < tp.nmodes; ++m) {
569 const Rx rec = build_mode(ind, m);
575 const std::size_t ridx = rx_.size() - 1;
576 for (std::size_t a = 0; a < rec.en_slot.size(); ++a)
577 consumers_[rec.en_slot[a] / K_][rec.en_slot[a] % K_].push_back(ridx);
586 for (std::size_t ind = 0; ind < I_; ++ind) {
587 if (sn_.nodes[ind].nodetype != qn::NodeType::Source)
continue;
588 const std::size_t ist = sn_.nodes[ind].station;
589 if (ist == 0)
continue;
590 for (std::size_t r = 0; r < K_; ++r) {
591 const double lambda = num_traits<T>::to_double(sn_.rates(ist - 1, r));
592 if (std::isnan(lambda) || lambda <= 0.0)
continue;
595 "solver_ssa_nrm_spn: source '" + sn_.nodes[ind].name +
"' class '" +
596 sn_.classes[r].name +
597 "' has a non-exponential arrival, which the SPN reaction network cannot "
598 "express; use method='serial' or SolverJMT");
599 bool found_place =
false;
600 for (std::size_t jnd = 0; jnd < I_; ++jnd) {
601 if (sn_.nodes[jnd].nodetype != qn::NodeType::Place)
continue;
602 for (std::size_t s = 0; s < K_; ++s) {
604 num_traits<T>::to_double(sn_.rtnodes(ind * K_ + r, jnd * K_ + s));
605 if (p <= 0.0)
continue;
610 rec.S.assign(NS_, 0.0);
611 rec.S[slot(jnd, s)] += 1.0;
612 rec.base_rate = lambda * p;
614 rec.dep_slots.push_back(slot(jnd, s));
616 producers_[ind][r].push_back(rx_.size() - 1);
620 throw UnsupportedError(ErrorKind{
"Topology"},
"solver_ssa_nrm_spn: source '" + sn_.nodes[ind].name +
621 "' class '" + sn_.classes[r].name +
622 "' does not route to any Place; the SPN path needs a "
623 "Source->Place arc");
629 "solver_ssa_nrm_spn: the stochastic Petri net has no timed reaction; there is "
630 "nothing to simulate");
646 nvec0_.assign(NS_, 0.0);
647 for (std::size_t r = 0; r < K_; ++r) {
648 const double pop = sn_.classes[r].population;
649 if (!std::isfinite(pop) || pop <= 0.0)
continue;
650 const std::size_t rs = sn_.classes[r].refstat;
651 if (rs < 1 || rs > M_)
continue;
652 const std::size_t ind = sn_.station_to_node[rs - 1] - 1;
653 if (sn_.nodes[ind].nodetype != qn::NodeType::Place)
continue;
654 nvec0_[slot(ind, r)] = pop;
656 for (std::size_t ind = 0; ind < I_; ++ind) {
657 if (sn_.nodes[ind].nodetype != qn::NodeType::Place)
continue;
658 const typename std::map<std::size_t, std::vector<T>>::const_iterator im =
659 sn_.initmarking.find(ind + 1);
660 if (im == sn_.initmarking.end())
continue;
661 for (std::size_t r = 0; r < K_; ++r) {
663 r < im->second.size() ? num_traits<T>::to_double(im->second[r]) : 0.0;
664 if (!std::isfinite(v))
665 throw UnsupportedError(ErrorKind{
"Place"},
"solver_ssa_nrm_spn: place '" + sn_.nodes[ind].name +
666 "' declares an infinite initial marking, which the "
667 "reaction network cannot count");
668 nvec0_[slot(ind, r)] = v > 0.0 ? v : 0.0;
677 const double inf = std::numeric_limits<double>::infinity();
678 pcap_slot_.assign(NS_, inf);
679 for (std::size_t ind = 0; ind < I_; ++ind) {
680 if (sn_.nodes[ind].nodetype != qn::NodeType::Place)
continue;
681 const std::size_t ist = sn_.nodes[ind].station;
682 if (ist == 0)
continue;
683 std::vector<std::size_t> slots_here;
684 for (std::size_t r = 0; r < K_; ++r) {
685 slots_here.push_back(slot(ind, r));
686 if (ist - 1 < sn_.classcap.size() && r < sn_.classcap[ist - 1].size()) {
687 const double cc = sn_.classcap[ist - 1][r];
688 if (std::isfinite(cc)) pcap_slot_[slot(ind, r)] = cc;
691 if (ist - 1 < sn_.cap.size() && std::isfinite(sn_.cap[ist - 1]))
692 place_total_caps_.push_back(std::make_pair(sn_.cap[ist - 1], slots_here));
694 has_caps_ = !place_total_caps_.empty();
695 for (std::size_t j = 0; j < NS_ && !has_caps_; ++j)
696 if (std::isfinite(pcap_slot_[j])) has_caps_ =
true;
709typename NrmSpnEngine<T>::Rx NrmSpnEngine<T>::build_mode(std::size_t ind0, std::size_t m)
const {
710 const qn::TransitionParam<T>& tp = sn_.transparam.at(ind0 + 1);
714 rec.S.assign(NS_, 0.0);
716 if (m < tp.enabling.size()) {
717 const Matrix<T>& en = tp.enabling[m];
718 for (std::size_t p = 0; p < en.rows() && p < I_; ++p)
719 for (std::size_t c = 0; c < en.cols() && c < K_; ++c) {
720 const double w = num_traits<T>::to_double(en(p, c));
721 if (w == 0.0)
continue;
722 rec.en_slot.push_back(slot(p, c));
723 rec.en_w.push_back(w);
724 rec.S[slot(p, c)] -= w;
727 if (m < tp.firing.size()) {
728 const Matrix<T>& fir = tp.firing[m];
729 for (std::size_t p = 0; p < fir.rows() && p < I_; ++p)
730 for (std::size_t c = 0; c < fir.cols() && c < K_; ++c) {
731 const double w = num_traits<T>::to_double(fir(p, c));
732 if (w == 0.0)
continue;
733 rec.S[slot(p, c)] += w;
736 if (m < tp.inhibiting.size()) {
737 const Matrix<T>& inh = tp.inhibiting[m];
738 for (std::size_t p = 0; p < inh.rows() && p < I_; ++p)
739 for (std::size_t c = 0; c < inh.cols() && c < K_; ++c) {
740 const double thr = num_traits<T>::to_double(inh(p, c));
741 if (!std::isfinite(thr))
continue;
742 rec.inh_slot.push_back(slot(p, c));
743 rec.inh_thr.push_back(thr);
746 for (std::size_t j = 0; j < NS_; ++j)
747 if (rec.S[j] > 0.0) rec.dep_slots.push_back(j);
753 const bool one_phase = m < tp.firingphases.size() && tp.firingphases[m] == 1;
754 if (!one_phase || m >= tp.firingproc.size() || tp.firingproc[m].D1.rows() == 0)
755 throw UnsupportedError(ErrorKind{
"NonExponential"},
"solver_ssa_nrm_spn: transition '" + sn_.nodes[ind0].name +
756 "' mode " + std::to_string(m + 1) +
757 " has a non-exponential firing, which the SPN reaction "
758 "network cannot express; use method='serial'");
759 const Matrix<T>& d1 = tp.firingproc[m].D1;
761 for (std::size_t a = 0; a < d1.rows(); ++a)
762 for (std::size_t b = 0; b < d1.cols(); ++b) s += num_traits<T>::to_double(d1(a, b));
765 rec.nservers = m < tp.nmodeservers.size() ? tp.nmodeservers[m] : 1.0;
767 rec.weight = m < tp.fireweight.size() ? num_traits<T>::to_double(tp.fireweight[m]) : 1.0;
768 rec.prio = m < tp.firingprio.size() ? tp.firingprio[m] : 1.0;
770 for (
const auto& a : tp.fluid_rates(m)) {
771 rec.flow_q.push_back(fluid_index(a.node));
772 rec.flow_rate.push_back(a.value);
774 for (
const auto& g : tp.fluid_guards(m)) {
775 rec.guard_q.push_back(fluid_index(g.node));
776 rec.guard_sgn.push_back(g.sgn);
777 rec.guard_thr.push_back(g.thr);
779 for (
const auto& a : tp.fluid_impulses(m)) {
780 rec.imp_q.push_back(fluid_index(a.node));
781 rec.imp_d.push_back(a.value);
783 for (
const auto& d : tp.fluid_deps(m)) {
784 rec.fdep_q.push_back(fluid_index(d.node));
785 rec.fdep_fun.push_back(d.fun);
788 const std::vector<typename qn::TransitionParam<T>::FluidArc>& fr = tp.fluid_rates(m);
789 rec.flow_fun.assign(fr.size(), std::function<
double(
const std::vector<double>&)>());
790 rec.flow_read.assign(fr.size(), std::vector<std::size_t>());
791 for (
const auto& fd : tp.fluid_flow_deps(m))
792 for (std::size_t t = 0; t < fr.size(); ++t) {
793 if (fr[t].node != fd.node)
continue;
794 rec.flow_fun[t] = fd.fun;
795 for (std::size_t q : fd.read) rec.flow_read[t].push_back(q - 1);
801 if (timed && m < tp.firingdep.size() && tp.firingdep[m]) {
802 const std::function<T(
const std::vector<T>&)> g = tp.firingdep[m];
803 rec.gdep = [g](
const std::vector<double>& mk) ->
double {
804 std::vector<T> mt(mk.size());
805 for (std::size_t i = 0; i < mk.size(); ++i) mt[i] = num_traits<T>::from_double(mk[i]);
806 return num_traits<T>::to_double(g(mt));
808 if (m < tp.enabling.size()) {
809 const Matrix<T>& en = tp.enabling[m];
810 for (std::size_t p = 0; p < en.rows() && p < I_; ++p)
811 for (std::size_t c = 0; c < en.cols(); ++c)
812 if (num_traits<T>::to_double(en(p, c)) > 0.0) {
813 rec.gdep_read.push_back(p);
828void NrmSpnEngine<T>::fluid_mark_mul(
const std::vector<double>& n, std::vector<double>& gk) {
829 const std::size_t nrx = rx_.size();
831 fm_.assign(nrx, std::vector<double>());
832 for (std::size_t k = 0; k < nrx; ++k) {
833 const Rx& rec = rx_[k];
835 std::vector<double> mk(NS_, 0.0);
836 for (std::size_t p : rec.gdep_read)
837 for (std::size_t c = 0; c < K_; ++c) mk[slot(p, c)] = n[slot(p, c)];
838 gk[k] = rec.gdep(mk);
840 for (std::size_t t = 0; t < rec.flow_fun.size(); ++t) {
841 if (!rec.flow_fun[t])
continue;
842 if (fm_[k].empty()) fm_[k].assign(rec.flow_q.size(), 1.0);
843 std::vector<double> mk(NS_, 0.0);
844 for (std::size_t p : rec.flow_read[t])
845 for (std::size_t c = 0; c < K_; ++c) mk[slot(p, c)] = n[slot(p, c)];
846 fm_[k][t] = rec.flow_fun[t](mk);
853std::size_t NrmSpnEngine<T>::fluid_index(std::size_t node1)
const {
854 if (node1 == 0 || node1 > I_ || fq_[node1 - 1] < 0)
856 "solver_ssa_nrm_spn: a fluid arc names node " + std::to_string(node1) +
", which is not a fluid place");
857 return static_cast<std::size_t
>(fq_[node1 - 1]);
868void NrmSpnEngine<T>::apply_caps(std::vector<double>& n,
869 const std::vector<std::size_t>& deposited)
const {
870 for (std::size_t a = 0; a < deposited.size(); ++a) {
871 const std::size_t j = deposited[a];
872 if (n[j] > pcap_slot_[j]) n[j] = pcap_slot_[j];
874 for (std::size_t p = 0; p < place_total_caps_.size(); ++p) {
875 const double tcap = place_total_caps_[p].first;
876 const std::vector<std::size_t>& slots = place_total_caps_[p].second;
878 for (std::size_t a = 0; a < slots.size(); ++a) total += n[slots[a]];
879 double excess = total - tcap;
880 for (std::size_t a = 0; a < deposited.size() && excess > 0.0; ++a) {
881 const std::size_t j = deposited[a];
882 if (std::find(slots.begin(), slots.end(), j) == slots.end())
continue;
883 if (n[j] <= 0.0)
continue;
884 const double d = std::min(excess, n[j]);
900void NrmSpnEngine<T>::collapse(std::vector<double>& n) {
901 if (imm_.empty())
return;
902 std::size_t steps = 0;
904 std::vector<std::size_t> enabled;
905 for (std::size_t m = 0; m < imm_.size(); ++m)
906 if (detail::spn_en_degree(n, imm_[m]) >= 1.0) enabled.push_back(m);
907 if (enabled.empty())
return;
908 double top_prio = -std::numeric_limits<double>::infinity();
909 for (std::size_t i = 0; i < enabled.size(); ++i)
910 top_prio = std::max(top_prio, imm_[enabled[i]].prio);
911 std::vector<std::size_t> top;
912 std::vector<double> w;
913 for (std::size_t i = 0; i < enabled.size(); ++i)
914 if (imm_[enabled[i]].prio == top_prio) {
915 top.push_back(enabled[i]);
916 w.push_back(imm_[enabled[i]].weight);
918 const std::size_t pick = top.size() == 1 ? top[0] : top[rng_.draw(w)];
919 for (std::size_t j = 0; j < NS_; ++j) n[j] += imm_[pick].S[j];
920 if (++steps > kMaxImmSteps)
922 "solver_ssa_nrm_spn: immediate-transition livelock -- the vanishing-marking "
923 "collapse did not reach a tangible marking");
942 if (!fl_node_.empty())
return run_fluid();
943 const std::size_t nrx = rx_.size();
949 out.
CN.assign(K_, 0.0);
950 out.
XN.assign(K_, 0.0);
955 std::vector<double> nvec = nvec0_;
958 std::vector<std::size_t> all(NS_);
959 for (std::size_t j = 0; j < NS_; ++j) all[j] = j;
960 apply_caps(nvec, all);
963 std::vector<double> Ak(nrx, 0.0), Pk(nrx, 0.0), Tk(nrx, 0.0), tau(nrx, 0.0);
964 const double inf = std::numeric_limits<double>::infinity();
965 for (std::size_t k = 0; k < nrx; ++k) {
966 Ak[k] = detail::spn_propensity(nvec, rx_[k]);
967 Pk[k] = -std::log(rng_.uniform());
968 tau[k] = Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : inf;
971 double total_time = 0.0;
974 static_cast<std::size_t
>(opt_.samples));
975 const std::size_t console_every = std::max<std::size_t>(1, opt_.samples / 20);
978 for (; n < opt_.samples; ++n) {
979 if (n == n_warm && n_warm > 0) {
985 if ((n + 1) % console_every == 0)
987 static_cast<long>((n + 1) / console_every),
988 "simulated %zu of %zu samples (%.0f%%), simulated time %.4g", n + 1,
989 static_cast<std::size_t
>(opt_.samples),
990 100.0 *
static_cast<double>(n + 1) /
static_cast<double>(opt_.samples),
992 std::size_t kfire = 0;
994 for (std::size_t k = 0; k < nrx; ++k)
1001 "solver_ssa_nrm_spn: deadlock -- no transition is enabled, so the sample path "
1005 for (std::size_t ist = 0; ist < M_; ++ist) {
1006 const std::size_t ind = sn_.station_to_node[ist] - 1;
1007 for (std::size_t c = 0; c < K_; ++c) {
1008 const double tokens = nvec[slot(ind, c)];
1009 out.
QN(ist, c) += tokens * dt;
1010 out.
UN(ist, c) += tokens * dt;
1012 const std::vector<std::size_t>& cons = consumers_[ind][c];
1013 for (std::size_t a = 0; a < cons.size(); ++a) depr += Ak[cons[a]];
1017 const std::vector<std::size_t>& prod = producers_[ind][c];
1018 for (std::size_t a = 0; a < prod.size(); ++a) depr += Ak[prod[a]];
1019 out.
TN(ist, c) += depr * dt;
1026 for (std::size_t j = 0; j < NS_; ++j) nvec[j] += rx_[kfire].S[j];
1027 if (has_caps_) apply_caps(nvec, rx_[kfire].dep_slots);
1032 for (std::size_t k = 0; k < nrx; ++k) Tk[k] += Ak[k] * dt;
1033 for (std::size_t k = 0; k < nrx; ++k) Ak[k] = detail::spn_propensity(nvec, rx_[k]);
1034 Pk[kfire] -= std::log(rng_.uniform());
1035 for (std::size_t k = 0; k < nrx; ++k)
1036 tau[k] = Ak[k] > 0.0 ? (Pk[k] - Tk[k]) / Ak[k] : inf;
1039 if (total_time > 0.0)
1040 for (std::size_t ist = 0; ist < M_; ++ist)
1041 for (std::size_t c = 0; c < K_; ++c) {
1042 out.
QN(ist, c) /= total_time;
1043 out.
UN(ist, c) /= total_time;
1044 out.
TN(ist, c) /= total_time;
1046 for (std::size_t c = 0; c < K_; ++c) {
1047 out.
XN[c] = out.
TN(sn_.classes[c].refstat - 1, c);
1048 for (std::size_t ist = 0; ist < M_; ++ist)
1049 out.
RN(ist, c) = out.
TN(ist, c) > 0.0 ? out.
QN(ist, c) / out.
TN(ist, c) : 0.0;
1050 if (out.
XN[c] > 0.0) out.
CN[c] = sn_.classes[c].population / out.
XN[c];
1069 const std::size_t nrx = rx_.size();
1070 const std::size_t nf = fl_node_.size();
1076 out.
CN.assign(K_, 0.0);
1077 out.
XN.assign(K_, 0.0);
1081 std::vector<bool> fluid_st(M_,
false);
1082 for (std::size_t q = 0; q < nf; ++q) fluid_st[fl_ist_[q]] =
true;
1084 std::vector<double> f = fl_f0_;
1085 std::vector<double> nvec = nvec0_;
1086 std::vector<double> QF(nf, 0.0), OF(nf, 0.0);
1089 std::vector<double> of0(nf, 0.0);
1090 collapse_fluid(nvec, f, std::vector<double>(nf, 0.0), of0);
1093 std::vector<std::size_t> all(NS_);
1094 for (std::size_t j = 0; j < NS_; ++j) all[j] = j;
1095 apply_caps(nvec, all);
1097 std::vector<double> Pk(nrx, 0.0), Tk(nrx, 0.0), a0(nrx, 0.0), Ik(nrx, 0.0), tau(nrx, 0.0);
1098 for (std::size_t k = 0; k < nrx; ++k) Pk[k] = -std::log(rng_.uniform());
1100 const double inf = std::numeric_limits<double>::infinity();
1101 double total_time = 0.0;
1102 std::size_t n = 0, zero_run = 0, ndet = 0;
1104 bool warm_pending = n_warm > 0;
1105 std::vector<double> eff, v, vin, vout;
1106 std::vector<double> gk(nrx, 1.0);
1107 fm_.assign(nrx, std::vector<double>());
1108 bool has_mark_dep =
false;
1109 for (std::size_t k = 0; k < nrx; ++k) {
1110 has_mark_dep = has_mark_dep ||
static_cast<bool>(rx_[k].gdep);
1111 for (
const auto& ff : rx_[k].flow_fun) has_mark_dep = has_mark_dep ||
static_cast<bool>(ff);
1114 static_cast<std::size_t
>(opt_.samples));
1115 const std::size_t console_every = std::max<std::size_t>(1, opt_.samples / 20);
1116 while (n < opt_.samples) {
1117 if (warm_pending && n == n_warm) {
1118 out.
QN = Matrix<double>(M_, K_, 0.0);
1119 out.
UN = Matrix<double>(M_, K_, 0.0);
1120 out.
TN = Matrix<double>(M_, K_, 0.0);
1121 std::fill(QF.begin(), QF.end(), 0.0);
1122 std::fill(OF.begin(), OF.end(), 0.0);
1124 warm_pending =
false;
1128 if (has_mark_dep) fluid_mark_mul(nvec, gk);
1129 fluid_resolve(nvec, f, eff, v, vin, vout);
1130 for (std::size_t k = 0; k < nrx; ++k) a0[k] = rx_[k].base_rate * eff[k] * gk[k];
1133 double tdet = inf, fdet = 0.0;
1134 std::size_t qdet = 0;
1135 fluid_next_det(f, v, tdet, qdet, fdet);
1139 std::size_t kfire = 0;
1140 for (std::size_t k = 0; k < nrx; ++k) {
1141 tau[k] = a0[k] > 0.0 ? detail::spn_fluid_fire_time(rx_[k], a0[k], f, v, Pk[k] - Tk[k], tdet) : inf;
1142 if (tau[k] < dt_r) {
1147 const bool fire = dt_r <= tdet;
1148 const double dt = fire ? dt_r : tdet;
1151 "solver_ssa_nrm_spn: deadlock -- no transition is enabled and no fluid level is moving, so the "
1152 "sample path cannot advance");
1155 for (std::size_t k = 0; k < nrx; ++k)
1156 Ik[k] = a0[k] > 0.0 ? detail::spn_fluid_hazard(rx_[k], a0[k], f, v, dt) : 0.0;
1158 for (std::size_t ist = 0; ist < M_; ++ist) {
1159 if (fluid_st[ist])
continue;
1160 const std::size_t ind = sn_.station_to_node[ist] - 1;
1161 for (std::size_t c = 0; c < K_; ++c) {
1162 const double tokens = nvec[slot(ind, c)];
1163 out.
QN(ist, c) += tokens * dt;
1164 out.
UN(ist, c) += tokens * dt;
1166 for (std::size_t k : consumers_[ind][c]) fired += Ik[k];
1167 for (std::size_t k : producers_[ind][c]) fired += Ik[k];
1168 out.
TN(ist, c) += fired;
1171 for (std::size_t q = 0; q < nf; ++q) {
1172 QF[q] += f[q] * dt + 0.5 * v[q] * dt * dt;
1174 if (v[q] == 0.0 && vin[q] < vout[q] && f[q] <= 0.0)
1175 OF[q] += vin[q] * dt;
1177 OF[q] += vout[q] * dt;
1179 for (std::size_t k = 0; k < nrx; ++k) Tk[k] += Ik[k];
1180 for (std::size_t q = 0; q < nf; ++q) f[q] = std::min(std::max(f[q] + v[q] * dt, 0.0), fl_cap_[q]);
1186 if (++zero_run > 1000)
1188 "solver_ssa_nrm_spn: the fluid level of place " + sn_.nodes[fl_node_[qdet]].name +
1189 " keeps re-crossing a guard at the same instant: a guarded flow drives its own level back "
1190 "across its own guard (a sliding mode), which a sample path has no value for");
1194 collapse_fluid(nvec, f, v, OF);
1195 if (ndet > 1000 * std::max<std::size_t>(opt_.samples, 1))
1197 "solver_ssa_nrm_spn: more than 1000 guard or bound events per firing: the fluid levels "
1198 "chatter. Check the guards against the flows they gate");
1204 for (std::size_t j = 0; j < NS_; ++j) nvec[j] += rx_[kfire].S[j];
1205 if (has_caps_) apply_caps(nvec, rx_[kfire].dep_slots);
1206 fluid_impulse(f, rx_[kfire], OF);
1207 collapse_fluid(nvec, f, v, OF);
1208 Pk[kfire] -= std::log(rng_.uniform());
1211 if (n % console_every == 0)
1213 static_cast<long>(n / console_every),
1214 "simulated %zu of %zu samples (%.0f%%), simulated time %.4g", n,
1215 static_cast<std::size_t
>(opt_.samples),
1216 100.0 *
static_cast<double>(n) /
static_cast<double>(opt_.samples), total_time);
1219 if (total_time > 0.0) {
1220 for (std::size_t ist = 0; ist < M_; ++ist)
1221 for (std::size_t c = 0; c < K_; ++c) {
1222 out.
QN(ist, c) /= total_time;
1223 out.
UN(ist, c) /= total_time;
1224 out.
TN(ist, c) /= total_time;
1226 for (std::size_t q = 0; q < nf; ++q) {
1227 QF[q] /= total_time;
1228 OF[q] /= total_time;
1233 for (std::size_t q = 0; q < nf; ++q) {
1234 const std::size_t ist = fl_ist_[q];
1235 for (std::size_t c = 0; c < K_; ++c) out.
QN(ist, c) = out.
UN(ist, c) = out.
TN(ist, c) = 0.0;
1236 if (K_ == 0)
continue;
1237 out.
QN(ist, 0) = QF[q];
1238 out.
UN(ist, 0) = std::isfinite(fl_cap_[q]) ? QF[q] / fl_cap_[q] : QF[q];
1239 out.
TN(ist, 0) = OF[q];
1241 for (std::size_t c = 0; c < K_; ++c) {
1242 out.
XN[c] = out.
TN(sn_.classes[c].refstat - 1, c);
1243 for (std::size_t ist = 0; ist < M_; ++ist)
1244 out.
RN(ist, c) = out.
TN(ist, c) > 0.0 ? out.
QN(ist, c) / out.
TN(ist, c) : 0.0;
1245 if (out.
XN[c] > 0.0) out.
CN[c] = sn_.classes[c].population / out.
XN[c];
1259void NrmSpnEngine<T>::fluid_resolve(
const std::vector<double>& n,
const std::vector<double>& f,
1260 std::vector<double>& eff, std::vector<double>& v, std::vector<double>& vin,
1261 std::vector<double>& vout)
const {
1262 const std::size_t nrx = rx_.size();
1263 const std::vector<double> still(f.size(), 0.0);
1264 std::vector<double> deg(nrx, 0.0);
1265 std::vector<bool> gok(nrx,
true), gnew(nrx,
true);
1266 for (std::size_t k = 0; k < nrx; ++k) {
1267 deg[k] = std::min(detail::spn_en_degree(n, rx_[k]), rx_[k].nservers);
1268 gok[k] = detail::spn_fluid_guard_holds(rx_[k], f, still);
1270 eff.assign(nrx, 0.0);
1271 for (
int it = 0; it < 20; ++it) {
1272 for (std::size_t k = 0; k < nrx; ++k) eff[k] = gok[k] ? deg[k] : 0.0;
1273 fluid_slope(f, eff, v, vin, vout);
1274 for (std::size_t k = 0; k < nrx; ++k) gnew[k] = detail::spn_fluid_guard_holds(rx_[k], f, v);
1275 if (gnew == gok)
return;
1279 "solver_ssa_nrm_spn: a guarded fluid flow switches its own guard on and off at the threshold (a sliding "
1280 "mode), which a sample path has no value for");
1288void NrmSpnEngine<T>::fluid_slope(
const std::vector<double>& f,
const std::vector<double>& eff,
1289 std::vector<double>& v, std::vector<double>& vin,
1290 std::vector<double>& vout)
const {
1291 const std::size_t nf = fl_node_.size();
1292 vin.assign(nf, 0.0);
1293 vout.assign(nf, 0.0);
1294 for (std::size_t k = 0; k < rx_.size(); ++k) {
1295 if (eff[k] <= 0.0)
continue;
1296 for (std::size_t t = 0; t < rx_[k].flow_q.size(); ++t) {
1297 const std::size_t q = rx_[k].flow_q[t];
1298 double rt = rx_[k].flow_rate[t] * eff[k];
1299 if (k < fm_.size() && !fm_[k].empty()) rt *= fm_[k][t];
1307 for (std::size_t q = 0; q < nf; ++q) {
1308 v[q] = vin[q] - vout[q];
1309 if ((f[q] <= 0.0 && v[q] < 0.0) || (f[q] >= fl_cap_[q] && v[q] > 0.0)) v[q] = 0.0;
1315void NrmSpnEngine<T>::fluid_next_det(
const std::vector<double>& f,
const std::vector<double>& v,
double& tdet,
1316 std::size_t& qdet,
double& fdet)
const {
1317 tdet = std::numeric_limits<double>::infinity();
1320 for (std::size_t q = 0; q < fl_node_.size(); ++q) {
1321 if (v[q] > 0.0 && std::isfinite(fl_cap_[q])) {
1322 const double t = (fl_cap_[q] - f[q]) / v[q];
1328 }
else if (v[q] < 0.0) {
1329 const double t = f[q] / (-v[q]);
1337 const std::vector<Rx>* sets[2] = {&rx_, &imm_};
1338 for (
int s = 0; s < 2; ++s)
1339 for (
const Rx& rec : *sets[s])
1340 for (std::size_t g = 0; g < rec.guard_q.size(); ++g) {
1341 const std::size_t q = rec.guard_q[g];
1342 const double thr = rec.guard_thr[g];
1343 if (v[q] == 0.0 || std::fabs(f[q] - thr) <= 1e-12 * std::max(1.0, std::fabs(thr)))
continue;
1344 const double t = (thr - f[q]) / v[q];
1345 if (t > 0.0 && t < tdet) {
1351 tdet = std::max(tdet, 0.0);
1359void NrmSpnEngine<T>::fluid_impulse(std::vector<double>& f,
const Rx& rec, std::vector<double>& OF)
const {
1360 for (std::size_t t = 0; t < rec.imp_q.size(); ++t) {
1361 const std::size_t q = rec.imp_q[t];
1362 const double fnew = std::min(std::max(f[q] + rec.imp_d[t], 0.0), fl_cap_[q]);
1363 if (fnew < f[q]) OF[q] += f[q] - fnew;
1373void NrmSpnEngine<T>::collapse_fluid(std::vector<double>& n, std::vector<double>& f,
const std::vector<double>& v,
1374 std::vector<double>& OF) {
1375 if (imm_.empty())
return;
1376 std::size_t steps = 0;
1378 std::vector<std::size_t> enabled;
1379 for (std::size_t m = 0; m < imm_.size(); ++m)
1380 if (detail::spn_en_degree(n, imm_[m]) >= 1.0 && detail::spn_fluid_guard_holds(imm_[m], f, v))
1381 enabled.push_back(m);
1382 if (enabled.empty())
return;
1383 double top_prio = -std::numeric_limits<double>::infinity();
1384 for (std::size_t i = 0; i < enabled.size(); ++i) top_prio = std::max(top_prio, imm_[enabled[i]].prio);
1385 std::vector<std::size_t> top;
1386 std::vector<double> w;
1387 for (std::size_t i = 0; i < enabled.size(); ++i)
1388 if (imm_[enabled[i]].prio == top_prio) {
1389 top.push_back(enabled[i]);
1390 w.push_back(imm_[enabled[i]].weight);
1392 const std::size_t pick = top.size() == 1 ? top[0] : top[rng_.draw(w)];
1393 for (std::size_t j = 0; j < NS_; ++j) n[j] += imm_[pick].S[j];
1394 fluid_impulse(f, imm_[pick], OF);
1395 if (++steps > kMaxImmSteps)
1397 "solver_ssa_nrm_spn: immediate-transition livelock -- the vanishing-marking collapse did not reach "
1398 "a tangible marking");
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
NrmSpnEngine(const qn::NetworkStruct< T > &sn, const SsaOptions &opt)
SsaSolution run()
Run opt.samples firings and return the time-averaged metrics.
std::size_t nimmediate() const
The immediate-mode count, likewise.
std::size_t nreactions() const
The reaction count, for the tests that assert the builder.
The uniform source, MATLAB's rand.
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.
The exception types the port throws.
Running progress log of a LINE solver run (the "solver console").
Dense matrix and non-owning view.
SnFluidVerdict sn_fluid_refusal(const qn::NetworkStruct< T > &sn)
Whether the net breaks a structural rule of an FSPN.
@ IMMEDIATE
fires with zero delay, resolved by weight and priority
InputError err(const std::string &msg, int line=-1)
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.
RootResult< T > root_brent(F f, const T &a0, const T &b0, const T &tol, unsigned maxiter=200)
Brent's method on a bracket with a sign change.
A queueing network and its refreshed NetworkStruct.
Deterministic scalar root finding.
Port of matlab/src/api/sn/sn_fluid_refusal.m.
Controls, results and the random source of SolverSSA.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
static constexpr double MaxInt
Stand-in for an unbounded COUNT, MATLAB GlobalConstants.MaxInt.
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