5#ifndef LINE_SOLVERS_CTMC_SOLVER_CTMC_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_H
46using qn::EventOutcome;
48using qn::NetworkStruct;
51using lang::GlobalConstants;
82 std::vector<Matrix<T>>
filt;
125namespace ctmc_detail {
129std::vector<double> state_key(
const NetState<T>& ns) {
130 std::vector<double> key;
131 for (std::size_t i = 0; i < ns.
local.size(); ++i) {
135 for (std::size_t j = 0; j < ns.
local[i].size(); ++j)
153 std::vector<double> pk(
sn.nclasses, 0.0);
154 const std::vector<std::size_t>& sfn =
sn.stateful_nodes;
155 for (std::size_t f = 0; f < sfn.size() && f < st.
local.size(); ++f) {
156 if (
sn.nodes[sfn[f] - 1].nodetype == NodeType::Transition)
continue;
158 for (std::size_t r = 0; r <
sn.nclasses && r < mg.second.size(); ++r) {
160 if (v > pk[r]) pk[r] = v;
178bool within_cutoff(
const NetworkStruct<T>& sn,
const NetState<T>& st,
179 const std::vector<double>& njobs,
const std::vector<std::size_t>& lim,
180 const std::vector<std::vector<std::size_t>>& lim_mat =
181 std::vector<std::vector<std::size_t>>()) {
183 for (std::size_t f = 0; f < sfn.size() && f < st.
local.size(); ++f) {
184 if (sn.
nodes[sfn[f] - 1].nodetype == NodeType::Transition)
continue;
188 const std::size_t ist = sn.
nodes[sfn[f] - 1].station;
190 for (std::size_t r = 0; r < sn.
nclasses && r < njobs.size(); ++r) {
191 if (std::isfinite(njobs[r]))
continue;
192 std::size_t bound = r < lim.size() ? lim[r] : 0;
193 if (!lim_mat.empty() && ist != 0 && ist - 1 < lim_mat.size() &&
194 r < lim_mat[ist - 1].size())
195 bound = lim_mat[ist - 1][r];
196 if (bound == 0)
continue;
197 if (r < mg.second.size() &&
198 num_traits<T>::to_double(mg.second[r]) >
static_cast<double>(bound))
211template <
class T,
class R>
212inline void add_aux_filt(R& res,
const NetworkStruct<T>& sn, std::size_t node, std::size_t s,
213 std::size_t ns,
const T& w,
const qn::EventOutcome<T>& oc,
215 if (num_traits<T>::to_double(w) == 0)
return;
216 if (node == 0 || node > sn.
nodes.size())
return;
217 const std::size_t ist = sn.
nodes[node - 1].station;
218 if (ist == 0 || ist > res.start_filt.size())
return;
219 if (row < oc.start.size())
220 for (std::size_t j = 0; j < oc.start[row].size(); ++j) {
221 const std::size_t cls = oc.start[row][j];
222 if (cls >= 1 && cls <= res.start_filt[ist - 1].size())
223 res.start_filt[ist - 1][cls - 1](s, ns) += w;
225 if (row < oc.preempt.size())
226 for (std::size_t j = 0; j < oc.preempt[row].size(); ++j) {
227 const std::size_t cls = oc.preempt[row][j];
228 if (cls >= 1 && cls <= res.preempt_filt[ist - 1].size())
229 res.preempt_filt[ist - 1][cls - 1](s, ns) += w;
243Matrix<T> censored_solve(
const Matrix<T>& Q22,
const Matrix<T>& B) {
244 const std::size_t nd = Q22.rows();
245 if (B.rows() != nd)
throw InputError(
"censored_solve: the right-hand side is misshapen");
246 Matrix<T> A(nd, nd, num_traits<T>::from_int(0));
247 for (std::size_t a = 0; a < nd; ++a)
248 for (std::size_t b = 0; b < nd; ++b) A(a, b) = T(-Q22(a, b));
249 const std::vector<std::size_t> piv =
lu_factor(A);
251 std::vector<T> rhs(nd);
252 for (std::size_t c = 0; c < B.cols(); ++c) {
253 for (std::size_t d = 0; d < nd; ++d) rhs[d] = B(d, c);
255 for (std::size_t d = 0; d < nd; ++d) X(d, c) = rhs[d];
268Matrix<T> complement_one_filt(
const Matrix<T>& D,
const std::vector<std::size_t>& nonimm,
269 const std::vector<std::size_t>& imm,
270 const mc::StochCompResult<T>& sc) {
271 const T zero = num_traits<T>::from_int(0);
272 const std::size_t nk = nonimm.size(), nd = imm.size();
273 Matrix<T> out(nk, nk, zero);
274 for (std::size_t a = 0; a < nk; ++a)
275 for (std::size_t b = 0; b < nk; ++b) out(a, b) = D(nonimm[a], nonimm[b]);
276 if (nd == 0)
return out;
277 Matrix<T> B(nd, nk, zero);
279 for (std::size_t d = 0; d < nd; ++d)
280 for (std::size_t b = 0; b < nk; ++b) {
281 B(d, b) = D(imm[d], nonimm[b]);
282 if (num_traits<T>::to_double(B(d, b)) != 0) any =
true;
284 if (!any)
return out;
285 const Matrix<T> X = censored_solve(sc.Q22, B);
286 for (std::size_t a = 0; a < nk; ++a)
287 for (std::size_t b = 0; b < nk; ++b) {
289 for (std::size_t d = 0; d < nd; ++d) acc = T(acc + sc.Q12(a, d) * X(d, b));
290 out(a, b) = T(out(a, b) + acc);
296template <
class T,
class R>
297void complement_filtrations(R& res,
const std::vector<std::size_t>& nonimm,
298 const std::vector<std::size_t>& imm,
299 const mc::StochCompResult<T>& sc) {
300 for (std::size_t a = 0; a < res.filt.size(); ++a)
301 res.filt[a] = complement_one_filt(res.filt[a], nonimm, imm, sc);
302 for (std::size_t i = 0; i < res.start_filt.size(); ++i)
303 for (std::size_t r = 0; r < res.start_filt[i].size(); ++r)
304 res.start_filt[i][r] = complement_one_filt(res.start_filt[i][r], nonimm, imm, sc);
305 for (std::size_t i = 0; i < res.preempt_filt.size(); ++i)
306 for (std::size_t r = 0; r < res.preempt_filt[i].size(); ++r)
307 res.preempt_filt[i][r] = complement_one_filt(res.preempt_filt[i][r], nonimm, imm, sc);
321 for (std::size_t i = 0; i < Q.
rows(); ++i) Q(i, i) = zero;
322 for (std::size_t i = 0; i < Q.
rows(); ++i) {
324 for (std::size_t j = 0; j < Q.
cols(); ++j) s += Q(i, j);
346 if (!
static_cast<bool>(
sn.gdscaling))
return Matrix<T>(0, 0, zero);
347 if (!
sn.regions.empty())
349 "setGlobalDependence cannot be combined with finite capacity regions: the region "
350 "generator builds its own transitions and would ignore the scaling");
351 const std::size_t M =
sn.stations.size(), K =
sn.nclasses, n = space.size();
352 const std::size_t max_entries = 30000000u;
353 if (n * M * K > max_entries)
355 "the global dependence table would exceed the state budget; lower the cutoff");
358 std::vector<T> npop(M * K, zero);
359 for (std::size_t s = 0; s < n; ++s) {
360 for (std::size_t i = 0; i < M * K; ++i) npop[i] = aggr(s, i);
361 const std::vector<T> v =
sn.gdscaling(npop);
362 for (std::size_t i = 0; i < M; ++i)
363 for (std::size_t r = 0; r < K; ++r) {
364 const T f = v.size() == 1 ? v[0] : (v.size() == M ? v[i] : v[i * K + r]);
367 "the global dependence handle returned a non-finite or negative scaling");
368 out(s, i * K + r) = f;
403 const std::size_t n = space.size();
404 const std::size_t R =
sn.nclasses;
405 std::vector<bool> mark(n,
false);
408 for (std::size_t ind = 1; ind <=
sn.nodes.size(); ++ind) {
409 const NodeType nt =
sn.nodes[ind - 1].nodetype;
410 if (nt != NodeType::Router && nt != NodeType::Fork)
continue;
411 if (
sn.nodes[ind - 1].station != 0)
continue;
412 const std::size_t isf =
sn.stateful_index(ind);
413 if (isf == 0)
continue;
414 for (std::size_t s = 0; s < n; ++s) {
415 if (mark[s])
continue;
416 const std::pair<T, std::vector<T>> mg =
418 for (std::size_t r = 0; r < R && r < mg.second.size(); ++r) {
420 if (std::isfinite(v) && v > 0.0) {
430 for (std::size_t ind = 1; ind <=
sn.nodes.size(); ++ind) {
431 if (
sn.nodes[ind - 1].nodetype != NodeType::Join)
continue;
432 const std::size_t isf =
sn.stateful_index(ind);
433 if (isf == 0)
continue;
434 const typename std::map<std::size_t, qn::FjJoinParam>::const_iterator jit =
435 sn.fjjoinparam.find(ind);
436 if (jit ==
sn.fjjoinparam.end())
continue;
437 const std::vector<std::size_t>& origcl = jit->second.origclasses;
438 for (std::size_t s = 0; s < n; ++s) {
439 if (mark[s])
continue;
440 for (std::size_t x = 0; x < origcl.size(); ++x) {
442 sn, ind, space[s].local[isf - 1], EventType::DEP, origcl[x]);
443 if (!oj.
space.empty()) {
453 for (std::size_t g = 0; g < gsync.size(); ++g) {
455 const std::size_t isf_t =
sn.stateful_index(ae.
node);
456 if (isf_t == 0)
continue;
457 bool is_enable = ae.
event == EventType::ENABLE;
458 bool is_imm_fire =
false;
459 if (!is_enable && ae.
event == EventType::FIRE) {
460 const typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
461 sn.transparam.find(ae.
node);
462 is_imm_fire = it !=
sn.transparam.end() && ae.
mode >= 1 &&
463 ae.
mode <= it->second.timing.size() &&
466 if (!is_enable && !is_imm_fire)
continue;
467 for (std::size_t s = 0; s < n; ++s) {
468 if (mark[s])
continue;
470 for (std::size_t
io = 0;
io < go.
space.size(); ++
io) {
477 if (is_imm_fire || go.
space[
io].local[isf_t - 1] != space[s].local[isf_t - 1]) {
485 std::vector<std::size_t> imm;
486 for (std::size_t s = 0; s < n; ++s)
487 if (mark[s]) imm.push_back(s);
503 const std::vector<
Sync<T>>& sync,
506 bool want_filtration =
false,
509 const std::size_t n = space.size();
510 const std::size_t local =
sn.nodes.size() + 1;
516 const std::size_t NF =
sn.stateful_nodes.size();
517 const std::size_t R =
sn.nclasses;
518 res.
arv_rates.assign(n, std::vector<std::vector<T>>(NF, std::vector<T>(R, zero)));
519 res.
dep_rates.assign(n, std::vector<std::vector<T>>(NF, std::vector<T>(R, zero)));
521 if (want_filtration) {
531 const bool isfjaug = !fjsync.empty();
532 std::vector<bool> imm_action(sync.size(),
false);
533 bool has_imm = !fjsync.empty();
534 for (std::size_t a = 0; a < sync.size(); ++a) {
535 const std::size_t na = sync[a].active.node;
536 if (na == 0 || na >
sn.nodes.size())
continue;
537 const NodeType nt =
sn.nodes[na - 1].nodetype;
538 imm_action[a] = nt == NodeType::Router || nt == NodeType::Fork ||
539 (isfjaug && nt == NodeType::Join);
540 if (imm_action[a]) has_imm =
true;
542 std::vector<bool> imm_gsync(gsync.size(),
false);
543 for (std::size_t g = 0; g < gsync.size(); ++g) {
545 if (ae.
event == EventType::ENABLE) {
547 }
else if (ae.
event == EventType::FIRE) {
548 const typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
549 sn.transparam.find(ae.
node);
550 imm_gsync[g] = it !=
sn.transparam.end() && ae.
mode >= 1 &&
551 ae.
mode <= it->second.timing.size() &&
554 if (imm_gsync[g]) has_imm =
true;
558 res.
arv_rates_imm.assign(n, std::vector<std::vector<T>>(NF, std::vector<T>(R, zero)));
559 res.
dep_rates_imm.assign(n, std::vector<std::vector<T>>(NF, std::vector<T>(R, zero)));
564 bool any_bas =
false;
565 for (std::size_t i = 0; i <
sn.isbasblocking.size(); ++i)
566 if (
sn.isbasblocking[i]) any_bas =
true;
567 Matrix<T> bas_block(any_bas ? n : 0, any_bas ? n : 0, zero);
569 std::map<std::vector<double>, std::size_t> index;
570 for (std::size_t s = 0; s < n; ++s) index[ctmc_detail::state_key(space[s])] = s;
574 const bool has_gd = gd.
rows() != 0;
580 std::vector<Matrix<T>> rt_by_state;
581 if (
sn.has_sdr_routing()) {
582 rt_by_state.reserve(n);
583 for (std::size_t s = 0; s < n; ++s) rt_by_state.push_back(
qn::rt_state(
sn, space[s].local));
586 for (std::size_t a = 0; a < sync.size(); ++a) {
588 const std::size_t node_a = sy.
active.node;
589 const std::size_t isf_a =
sn.stateful_index(node_a);
590 if (isf_a == 0)
continue;
591 const std::size_t node_p = sy.
passive.node;
592 const std::size_t isf_p = node_p == local ? 0 :
sn.stateful_index(node_p);
593 if (node_p != local && isf_p == 0)
continue;
596 const bool gd_here = has_gd &&
sn.nodes[node_a - 1].station != 0 &&
597 (sy.
active.event == EventType::DEP ||
598 sy.
active.event == EventType::PHASE);
599 const std::size_t gd_col = gd_here ? (
sn.nodes[node_a - 1].station - 1) *
sn.nclasses +
604 const bool rr_here = sy.
active.event == EventType::DEP &&
605 sn.rr_var_slot(node_a, sy.
active.cls) != 0;
606 for (std::size_t s = 0; s < n; ++s) {
611 for (std::size_t ia = 0; ia < oa.
space.size(); ++ia) {
612 const T rate = gd_here ? T(oa.
rate[ia] * gd(s, gd_col)) : oa.
rate[ia];
618 if (node_p == local) {
623 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
624 index.find(ctmc_detail::state_key(nsx));
625 if (it == index.end())
continue;
626 res.
Q(s, it->second) += T(rate * oa.
prob[ia]);
627 if (imm_action[a]) res.
Qimm(s, it->second) += T(rate * oa.
prob[ia]);
628 if (want_filtration) {
629 res.
filt[a](s, it->second) += T(rate * oa.
prob[ia]);
631 ctmc_detail::add_aux_filt(res,
sn, node_a, s, it->second,
632 T(rate * oa.
prob[ia]), oa, ia);
634 fired += T(rate * oa.
prob[ia]);
641 const std::vector<T>& src =
642 node_p == node_a ? oa.
space[ia] : st.
local[isf_p - 1];
661 const std::size_t w =
sn.nvars_of(node_a);
662 const std::vector<T>& arow = oa.
space[ia];
663 std::size_t dest = 0;
664 if (arow.size() >= w) {
665 const std::vector<T> var(arow.end() - w, arow.end());
666 dest =
sn.rr_dest(node_a, sy.
active.cls, var);
673 for (std::size_t ip = 0; ip < op.
space.size(); ++ip) {
677 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
678 index.find(ctmc_detail::state_key(nsx));
679 if (it == index.end())
continue;
692 const T w = T(rate * oa.
prob[ia] * proute * op.
prob[ip]);
693 res.
Q(s, it->second) += w;
694 if (imm_action[a]) res.
Qimm(s, it->second) += w;
695 if (want_filtration) {
696 res.
filt[a](s, it->second) += w;
700 ctmc_detail::add_aux_filt(res,
sn, node_a, s, it->second, w, oa, ia);
701 ctmc_detail::add_aux_filt(res,
sn, node_p, s, it->second, w, op, ip);
716 if (!placed && sy.
active.event == EventType::DEP &&
717 node_a <=
sn.isbasblocking.size() &&
sn.isbasblocking[node_a - 1] &&
718 !st.
local[isf_a - 1].empty() &&
722 const typename std::map<std::vector<double>, std::size_t>::const_iterator ib =
723 index.find(ctmc_detail::state_key(nsb));
729 if (ib != index.end()) bas_block(s, ib->second) += rate;
743 if (isfjaug &&
sn.nodes[node_a - 1].nodetype == NodeType::Join) {
769 for (std::size_t g = 0; g < gsync.size(); ++g) {
770 const bool is_fire = gsync[g].active.event == EventType::FIRE;
771 for (std::size_t s = 0; s < n; ++s) {
774 for (std::size_t
io = 0;
io < go.
space.size(); ++
io) {
777 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
778 index.find(ctmc_detail::state_key(go.
space[
io]));
779 if (it == index.end())
continue;
780 res.
Q(s, it->second) += contrib;
781 if (imm_gsync[g]) res.
Qimm(s, it->second) += contrib;
785 for (std::size_t j = 0; j < gsync[g].passive.size(); ++j) {
787 if (pev.
node == 0 || pev.
node >
sn.nodes.size())
continue;
788 const std::size_t isf_v =
sn.stateful_index(pev.
node);
789 if (isf_v == 0 || pev.
cls == 0 || pev.
cls >
sn.nclasses)
continue;
799 const T flow = T(completed * pev.
weight);
804 if (pev.
event == EventType::PRE) {
807 }
else if (pev.
event == EventType::POST) {
823 for (std::size_t k = 0; k < fjsync.size(); ++k) {
825 const std::size_t isf_f =
sn.stateful_index(e.
fork);
826 if (isf_f == 0)
continue;
827 for (std::size_t s = 0; s < n; ++s) {
830 for (std::size_t
io = 0;
io < fo.
space.size(); ++
io) {
833 const typename std::map<std::vector<double>, std::size_t>::const_iterator it =
834 index.find(ctmc_detail::state_key(fo.
space[
io]));
835 if (it == index.end())
continue;
836 res.
Q(s, it->second) += contrib;
837 res.
Qimm(s, it->second) += contrib;
843 for (std::size_t b = 0; b < e.
branchheads.size(); ++b) {
844 const std::size_t isf_b =
sn.stateful_index(e.
branchheads[b]);
845 if (isf_b == 0)
continue;
853 for (std::size_t i = 0; i < n; ++i)
854 for (std::size_t j = 0; j < n; ++j) res.
Q(i, j) += bas_block(i, j);
869 std::vector<std::size_t> keep_van;
871 for (std::size_t x = 0; x < res.
vanishing.size(); ++x) {
874 for (std::size_t j = 0; j < n; ++j)
875 if (j != s) out_imm += res.
Qimm(s, j);
877 keep_van.push_back(s);
883 "CTMC: %zu vanishing state(s) have no immediate outgoing arc; the vanishing "
884 "predicate and the immediate-arc tagging disagree, so those rows keep their "
888 for (std::size_t x = 0; x < res.
vanishing.size(); ++x) {
890 for (std::size_t j = 0; j < n; ++j) res.
Q(s, j) = res.
Qimm(s, j);
893 for (std::size_t a = 0; a < res.
filt.size(); ++a)
895 for (std::size_t j = 0; j < n; ++j) res.
filt[a](s, j) = zero;
938 const std::size_t n = res.
Q.rows();
939 std::vector<bool> is_van(n,
false);
940 for (std::size_t x = 0; x < res.
vanishing.size(); ++x) is_van[res.
vanishing[x]] =
true;
941 std::vector<std::size_t> nonimm;
942 nonimm.reserve(n - res.
vanishing.size());
943 for (std::size_t s = 0; s < n; ++s)
944 if (!is_van[s]) nonimm.push_back(s);
947 "SolverCTMC: every state is vanishing; the chain has no tangible state to observe");
950 const std::size_t nk = nonimm.size(), nd = res.
vanishing.size();
956 const std::size_t R = NF == 0 ? 0 : res.
arv_rates[0][0].size();
957 const std::size_t ncol = 2 * NF * R;
961 for (std::size_t d = 0; d < nd; ++d) {
963 for (std::size_t f = 0; f < NF; ++f)
964 for (std::size_t r = 0; r < R; ++r) {
969 const Matrix<T> X = ctmc_detail::censored_solve(sc.
Q22, B);
971 for (std::size_t a = 0; a < nk; ++a)
972 for (std::size_t c = 0; c < ncol; ++c) {
974 for (std::size_t d = 0; d < nd; ++d) acc = T(acc + sc.
Q12(a, d) * X(d, c));
979 std::vector<NetState<T>> space;
980 std::vector<std::vector<std::vector<T>>> arv, dep;
984 for (std::size_t a = 0; a < nk; ++a) {
985 space.push_back(res.
space[nonimm[a]]);
988 for (std::size_t f = 0; f < NF; ++f)
989 for (std::size_t r = 0; r < R; ++r) {
990 arv[a][f][r] = T(arv[a][f][r] + corr(a, f * R + r));
991 dep[a][f][r] = T(dep[a][f][r] + corr(a, NF * R + f * R + r));
1000 ctmc_detail::complement_filtrations(res, nonimm, res.
vanishing, sc);
1003 res.
space.swap(space);
1058 std::size_t maxst = 3000000,
1060 const std::vector<std::size_t>& cutoff = std::vector<std::size_t>(),
1061 const std::vector<std::vector<std::size_t>>& cutoff_mat =
1062 std::vector<std::vector<std::size_t>>()) {
1063 const std::size_t local =
sn.nodes.size() + 1;
1064 std::vector<NetState<T>> out;
1065 std::map<std::vector<double>, std::size_t> seen;
1066 std::vector<std::size_t> stack;
1067 const std::vector<double> njobs =
sn.njobs();
1070 for (std::size_t r = 0; r <
sn.nclasses && r < njobs.size(); ++r)
1071 if (!std::isfinite(njobs[r]) && r < cutoff.size() && cutoff[r] > 0) bound =
true;
1072 const bool bounded = bound;
1073 std::vector<std::size_t> lim = cutoff;
1075 const std::vector<double> n0 = ctmc_detail::state_peak_occupancy(
sn, init);
1076 for (std::size_t r = 0; r < lim.size() && r < n0.size(); ++r) {
1077 const std::size_t p0 =
1078 n0[r] > 0 ?
static_cast<std::size_t
>(std::floor(n0[r] + 0.5)) : 0;
1079 if (p0 > lim[r]) lim[r] = p0;
1083 seen[ctmc_detail::state_key(init)] = 0;
1084 out.push_back(init);
1087 while (!stack.empty()) {
1088 const std::size_t si = stack.back();
1092 for (std::size_t a = 0; a < sync.size(); ++a) {
1094 const std::size_t isf_a =
sn.stateful_index(sy.
active.node);
1095 if (isf_a == 0)
continue;
1096 const std::size_t isf_p =
1098 if (sy.
passive.node != local && isf_p == 0)
continue;
1102 for (std::size_t ia = 0; ia < oa.
space.size(); ++ia) {
1105 std::vector<NetState<T>> cand;
1106 if (sy.
passive.node == local) {
1109 cand.push_back(nsx);
1111 const std::vector<T>& src =
1115 for (std::size_t ip = 0; ip < op.
space.size(); ++ip) {
1120 cand.push_back(nsx);
1123 for (std::size_t c = 0; c < cand.size(); ++c) {
1124 if (bounded && !ctmc_detail::within_cutoff(
sn, cand[c], njobs, lim, cutoff_mat))
1126 const std::vector<double> key = ctmc_detail::state_key(cand[c]);
1127 if (seen.find(key) != seen.end())
continue;
1128 if (out.size() >= maxst)
1130 "reachable_space_generator: the reachable state space exceeds the "
1131 "cap of " + std::to_string(maxst) +
" states");
1132 seen[key] = out.size();
1133 out.push_back(cand[c]);
1134 stack.push_back(out.size() - 1);
1142 for (std::size_t g = 0; g < gsync.size(); ++g) {
1144 for (std::size_t
io = 0;
io < go.
space.size(); ++
io) {
1147 if (bounded && !ctmc_detail::within_cutoff(
sn, go.
space[
io], njobs, lim, cutoff_mat))
1149 const std::vector<double> key = ctmc_detail::state_key(go.
space[
io]);
1150 if (seen.find(key) != seen.end())
continue;
1151 if (out.size() >= maxst)
1153 "reachable_space_generator: the reachable state space exceeds the cap of " +
1154 std::to_string(maxst) +
" states");
1155 seen[key] = out.size();
1157 stack.push_back(out.size() - 1);
1164 for (std::size_t k = 0; k < fjsync.size(); ++k) {
1166 for (std::size_t
io = 0;
io < fo.
space.size(); ++
io) {
1169 if (bounded && !ctmc_detail::within_cutoff(
sn, fo.
space[
io], njobs, lim, cutoff_mat))
1171 const std::vector<double> key = ctmc_detail::state_key(fo.
space[
io]);
1172 if (seen.find(key) != seen.end())
continue;
1173 if (out.size() >= maxst)
1175 "reachable_space_generator: the reachable state space exceeds the cap of " +
1176 std::to_string(maxst) +
" states");
1177 seen[key] = out.size();
1179 stack.push_back(out.size() - 1);
1203 const std::size_t M =
sn.stations.size(), K =
sn.nclasses;
1206 for (std::size_t ist = 1; ist <= M; ++ist) {
1207 const std::size_t isf =
sn.stateful_of_station(ist);
1208 const std::size_t ind =
sn.node_of_station(ist);
1209 if (isf == 0)
continue;
1210 if (
sn.stations[ist - 1].nodetype == NodeType::Source)
continue;
1211 std::vector<std::size_t> ph(K, 1), shift(K, 0);
1213 for (std::size_t k = 0; k < K; ++k) {
1214 ph[k] =
sn.phasessz_of(ist, k + 1);
1218 const std::size_t nvar =
sn.nvars_of(ind);
1219 for (std::size_t s = 0; s < space.size(); ++s) {
1222 for (std::size_t k = 0; k < K; ++k) A(s, (ist - 1) * K + k) = m.
nir[k];
1243 const std::vector<T>& p, std::size_t isf) {
1244 const std::size_t K =
sn.nclasses;
1245 std::vector<bool> lossy(K,
false);
1247 for (std::size_t k = 0; k < K && k <
sn.issignal.size(); ++k) any = any ||
sn.issignal[k];
1248 if (!any)
return lossy;
1250 for (std::size_t r2 = 1; r2 <= K; ++r2) {
1251 if (
sn.issignal.size() < r2 || !
sn.issignal[r2 - 1])
continue;
1253 for (std::size_t s = 0; s < r.
space.size(); ++s)
1254 arv += T(p[s] * r.
arv_rates[s][isf - 1][r2 - 1]);
1256 const std::size_t tgt =
1257 sn.signaltarget.size() >= r2 ?
sn.signaltarget[r2 - 1] : 0;
1258 if (tgt >= 1 && tgt <= K) {
1259 lossy[tgt - 1] =
true;
1261 for (std::size_t k = 0; k < K; ++k)
1262 if (k >=
sn.issignal.size() || !
sn.issignal[k]) lossy[k] =
true;
1288 const std::size_t K =
sn.nclasses;
1290 std::vector<T> unb(K, zero);
1292 const bool is_ps = sched == SchedStrategy::PS || sched == SchedStrategy::DPS ||
1293 sched == SchedStrategy::GPS || sched == SchedStrategy::LPS;
1294 const double S =
sn.stations[ist - 1].nservers;
1295 const std::size_t ind =
sn.node_of_station(ist);
1297 std::vector<std::size_t> ph(K, 1), shift(K, 0);
1299 for (std::size_t k = 0; k < K; ++k) {
1300 ph[k] =
sn.phasessz_of(ist, k + 1);
1304 const std::size_t nvar =
sn.nvars_of(ind);
1306 for (std::size_t s = 0; s < r.
space.size(); ++s) {
1311 for (std::size_t k = 0; k < K; ++k) ni += num_traits<T>::to_double(m.
nir[k]);
1312 if (ni <= 0)
continue;
1315 for (std::size_t k = 0; k < K; ++k)
1316 wtot += T(m.
nir[k] *
sn.stations[ist - 1].schedparam[k]);
1318 const double busy = std::min(ni, S) / S;
1319 for (std::size_t k = 0; k < K; ++k)
1320 unb[k] += T(p[s] * (m.
nir[k] *
sn.stations[ist - 1].schedparam[k] / wtot) *
1323 for (std::size_t k = 0; k < K; ++k)
1340 for (std::size_t i = 0; i <
sn.service.size(); ++i)
1341 for (std::size_t r = 0; r <
sn.service[i].size(); ++r) {
1343 if (d.
disabled || d.
D0.rows() == 0)
continue;
1344 for (std::size_t a = 0; a < d.
D0.rows(); ++a)
1345 for (std::size_t b = 0; b < d.
D0.cols(); ++b)
1347 for (std::size_t a = 0; a < d.
D1.rows(); ++a)
1348 for (std::size_t b = 0; b < d.
D1.cols(); ++b)
1382 const std::vector<T>& pivec) {
1383 const std::size_t M =
sn.stations.size(), R =
sn.nclasses, n = r.
space.size();
1390 a.
XN.assign(R, zero);
1391 a.
CN.assign(R, zero);
1403 std::vector<T> p = pivec;
1405 for (std::size_t s = 0; s < n; ++s) {
1410 for (std::size_t s = 0; s < n; ++s) p[s] = T(p[s] / tot);
1414 for (std::size_t k = 1; k <= R; ++k) {
1415 const std::size_t refsf =
sn.stateful_of_station(
sn.classes[k - 1].refstat);
1416 for (std::size_t s = 0; s < n; ++s) a.
XN[k - 1] += T(p[s] * r.
arv_rates[s][refsf - 1][k - 1]);
1421 for (std::size_t ist = 1; ist <= M && ist <= r.
start_filt.size(); ++ist)
1422 for (std::size_t k = 1; k <= R && k <= r.
start_filt[ist - 1].size(); ++k) {
1423 T accs = zero, accp = zero;
1424 for (std::size_t s = 0; s < n; ++s) {
1425 T rows = zero, rowp = zero;
1426 for (std::size_t ns = 0; ns < n; ++ns) {
1430 accs += T(p[s] * rows);
1431 accp += T(p[s] * rowp);
1433 a.
StartN(ist - 1, k - 1) = accs;
1437 for (std::size_t ist = 1; ist <= M; ++ist) {
1438 const std::size_t isf =
sn.stateful_of_station(ist);
1439 const std::size_t ind =
sn.node_of_station(ist);
1440 const bool is_source =
sn.stations[ist - 1].nodetype == NodeType::Source;
1441 const double S =
sn.stations[ist - 1].nservers;
1442 std::vector<std::size_t> ph(R, 1), shift(R, 0);
1444 for (std::size_t r2 = 0; r2 < R; ++r2) {
1445 ph[r2] =
sn.phasessz_of(ist, r2 + 1);
1449 const std::size_t nvar =
sn.nvars_of(ind);
1451 for (std::size_t k = 1; k <= R; ++k)
1452 for (std::size_t s = 0; s < n; ++s)
1453 a.
TN(ist - 1, k - 1) += T(p[s] * r.
dep_rates[s][isf - 1][k - 1]);
1462 for (std::size_t s = 0; s < n; ++s) {
1466 for (std::size_t k = 1; k <= R; ++k) a.
QN(ist - 1, k - 1) += T(p[s] * m.
nir[k - 1]);
1473 if (sched == SchedStrategy::PAS) {
1474 for (std::size_t s = 0; s < n; ++s) {
1478 for (std::size_t k = 0; k < R; ++k)
1494 const std::vector<T>& lld =
sn.stations[ist - 1].lldscaling;
1495 if (!lld.empty() ||
sn.stations[ist - 1].cdscaling ||
sn.stations[ist - 1].jdscaling) {
1497 for (std::size_t j = 0; j < lld.size(); ++j)
1499 const bool share = sched == SchedStrategy::PS || sched == SchedStrategy::DPS ||
1500 sched == SchedStrategy::GPS || sched == SchedStrategy::LPS;
1501 for (std::size_t s = 0; s < n; ++s) {
1506 for (std::size_t k = 0; k < R; ++k) ni += num_traits<T>::to_double(m.
nir[k]);
1507 if (ni <= 0)
continue;
1508 double lldnow = 1.0;
1510 const std::size_t li = std::min<std::size_t>(
1511 lld.size(), std::max<std::size_t>(1, static_cast<std::size_t>(ni)));
1512 lldnow = num_traits<T>::to_double(lld[li - 1]);
1516 for (std::size_t k = 0; k < R; ++k)
1517 wtot += T(m.
nir[k] *
sn.stations[ist - 1].schedparam[k]);
1519 for (std::size_t k = 0; k < R; ++k)
1521 T(p[s] * (m.
nir[k] *
sn.stations[ist - 1].schedparam[k] / wtot) *
1525 for (std::size_t k = 0; k < R; ++k)
1527 if (sirtot <= 0)
continue;
1528 for (std::size_t k = 0; k < R; ++k)
1531 sirtot * lldnow / ceff));
1536 if (sched == SchedStrategy::INF) {
1539 for (std::size_t k = 1; k <= R; ++k) a.
UN(ist - 1, k - 1) = a.
QN(ist - 1, k - 1);
1555 bool in_drop =
false;
1556 for (std::size_t f = 0; f <
sn.regions.size() && !in_drop; ++f) {
1557 bool has_drop =
false;
1558 for (std::size_t rr = 0; rr <
sn.regions[f].rule.size(); ++rr)
1560 if (has_drop && ist - 1 <
sn.regions[f].members.size() &&
1561 sn.regions[f].members[ist - 1])
1564 for (std::size_t k = 1; k <= R; ++k) {
1565 const bool can_drop =
1566 (!std::isfinite(
sn.njobs()[k - 1]) &&
1567 (std::isfinite(
sn.cap[ist - 1]) ||
1568 std::isfinite(
sn.classcap[ist - 1][k - 1]) || in_drop)) ||
1571 if (d.
disabled || d.
D0.rows() == 0)
continue;
1578 a.
UN(ist - 1, k - 1) = u_dep;
1582 for (std::size_t s = 0; s < n; ++s)
1583 arv += T(p[s] * r.
arv_rates[s][isf - 1][k - 1]);
1585 a.
UN(ist - 1, k - 1) =
1592 bool anylossy =
false;
1593 for (std::size_t k = 0; k < R; ++k) anylossy = anylossy || lossy[k];
1596 for (std::size_t k = 0; k < R; ++k)
1597 if (lossy[k]) a.
UN(ist - 1, k) = unb[k];
1610 for (std::size_t ist = 1; ist <= M && !
sn.isbasblocking.empty(); ++ist) {
1611 const std::size_t ind =
sn.node_of_station(ist);
1612 const std::size_t isf =
sn.stateful_of_station(ist);
1613 if (ind == 0 || isf == 0 || ind >
sn.isbasblocking.size())
continue;
1614 if (!
sn.isbasblocking[ind - 1])
continue;
1615 const std::vector<std::size_t> dests =
sn.downstream_stations(ind);
1616 if (dests.size() != 1)
continue;
1617 const std::size_t jst =
sn.nodes[dests[0] - 1].station;
1618 if (jst == 0)
continue;
1619 std::vector<std::size_t> ph2(R, 1), sh2(R, 0);
1621 for (std::size_t k = 0; k < R; ++k) {
1622 ph2[k] =
sn.phasessz_of(ist, k + 1);
1626 const std::size_t nv2 =
sn.nvars_of(ind);
1627 for (std::size_t s = 0; s < n; ++s) {
1629 const std::vector<T>& row = r.
space[s].local[isf - 1];
1632 for (std::size_t k = 0; k < R; ++k) {
1637 if (!(nk > 0))
continue;
1639 a.
QN(ist - 1, k) -= shift;
1640 a.
QN(jst - 1, k) += shift;
1665 bool all_closed =
true;
1666 for (std::size_t k = 0; k < R; ++k)
1667 if (!std::isfinite(
sn.njobs()[k])) all_closed =
false;
1669 for (std::size_t ist = 1; ist <= M; ++ist) {
1671 const bool has_cd =
static_cast<bool>(st.
cdscaling);
1672 const bool has_jd =
static_cast<bool>(st.
jdscaling);
1673 if (!has_cd && !has_jd)
continue;
1674 for (std::size_t k = 0; k < R; ++k) {
1685 a.
UN(ist - 1, k) = (std::isfinite(mu) && mu > 0 && bmax > 0)
1686 ? T(a.
TN(ist - 1, k) /
1698 if (!
sn.replyblock.empty()) {
1699 for (std::size_t ist = 1; ist <= M; ++ist) {
1700 const std::size_t ind =
sn.node_of_station(ist);
1701 const std::size_t isf =
sn.stateful_of_station(ist);
1702 if (isf == 0 ||
sn.replyblock.size() < ind)
continue;
1704 for (std::size_t k = 0; k <
sn.replyblock[ind - 1].size(); ++k)
1705 any = any ||
sn.replyblock[ind - 1][k];
1708 if (info.
width == 0)
continue;
1709 const double S2 =
sn.stations[ist - 1].nservers;
1712 for (std::size_t s = 0; s < n; ++s) {
1714 const std::vector<T>& row = r.
space[s].local[isf - 1];
1715 for (std::size_t pos = 0; pos < info.
classes.size(); ++pos) {
1716 const std::size_t col = row.size() - info.
width + pos;
1717 const std::size_t cls = info.
classes[pos];
1718 qn_blocked(ist - 1, cls - 1) += T(p[s] * row[col]);
1721 for (std::size_t k = 0; k < R; ++k) {
1722 a.
QN(ist - 1, k) += qn_blocked(ist - 1, k);
1729 for (std::size_t k = 1; k <= R; ++k) {
1730 for (std::size_t ist = 1; ist <= M; ++ist)
1731 a.
RN(ist - 1, k - 1) =
1733 ? T((a.
QN(ist - 1, k - 1) - qn_blocked(ist - 1, k - 1)) /
1734 a.
TN(ist - 1, k - 1))
1736 const double nk =
sn.njobs()[k - 1];
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::vector< std::size_t > stateful_nodes
1-based node indices, ascending
std::vector< NodeDef > nodes
every node, in creation order
A network plus its refreshed NetworkStruct.
static void step(const char *fmt,...)
Write one progress line.
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
The exception types the port throws.
Running progress log of a LINE solver run (the "solver console").
LU factorization with partial pivoting, templated on the number type.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
void ctmc_eliminate_vanishing(CtmcResult< T > &res)
Port of the "now remove immediate transitions" block of solver_ctmc.m (:812-870): eliminate the vanis...
CtmcAvg< T > solver_ctmc_avg_from_pi(const NetworkStruct< T > &sn, const CtmcResult< T > &r, const std::vector< T > &pivec)
Port of solver_ctmc_avg_from_pi: map a state distribution to mean metrics.
void make_infgen(Matrix< T > &Q)
Port of ctmc_makeinfgen: turn an off-diagonal rate matrix into a generator.
CtmcResult< T > solver_ctmc(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space, const std::vector< Sync< T > > &sync, const std::vector< qn::GlobalSync< T > > &gsync=std::vector< qn::GlobalSync< T > >(), bool want_filtration=false, const std::vector< qn::FjSync< T > > &fjsync=std::vector< qn::FjSync< T > >())
Port of the generator assembly of solver_ctmc.m.
bool ctmc_all_phasetype(const NetworkStruct< T > &sn)
MATLAB's all(sn.isph(:)), read off the matrices instead of off a flag.
std::vector< T > ctmc_signal_busy(const NetworkStruct< T > &sn, std::size_t ist, const CtmcResult< T > &r, const std::vector< T > &p, std::size_t isf)
Port of ctmc_signal_busy: the exact per-class busy-server fraction, read off the enumerated state spa...
Matrix< T > ctmc_gd_factor(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Tabulates the globally state-dependent rate scaling phi(n) declared through set_global_dependence,...
Matrix< T > ctmc_state_space_aggr(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Port of StateSpaceAggr: the per-(station, class) job counts of every state, as an (nstates x nstation...
std::vector< std::size_t > ctmc_find_vanishing_states(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space, const std::vector< qn::GlobalSync< T > > &gsync, bool isfjaug)
Port of ctmc_find_vanishing_states (solver_ctmc.m:928): the indices of the VANISHING (zero-sojourn) g...
std::vector< NetState< T > > reachable_space_generator(const NetworkStruct< T > &sn, const NetState< T > &init, const std::vector< Sync< T > > &sync, const std::vector< qn::GlobalSync< T > > &gsync=std::vector< qn::GlobalSync< T > >(), std::size_t maxst=3000000, const std::vector< qn::FjSync< T > > &fjsync=std::vector< qn::FjSync< T > >(), const std::vector< std::size_t > &cutoff=std::vector< std::size_t >(), const std::vector< std::vector< std::size_t > > &cutoff_mat=std::vector< std::vector< std::size_t > >())
Port of State.reachableSpaceGenerator: the states reachable from init.
std::vector< bool > ctmc_signal_lossy(const NetworkStruct< T > &sn, const CtmcResult< T > &r, const std::vector< T > &p, std::size_t isf)
Port of ctmc_signal_lossy: classes a G-network signal can annihilate here.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
@ IMMEDIATE
fires with zero delay, resolved by weight and priority
EventType
The events a state can undergo, with the values of MATLAB EventType.
NodeType
Node kinds, with the values of MATLAB NodeType.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
StochCompResult< T > ctmc_stochcomp(const Matrix< T > &Q, const std::vector< std::size_t > &I)
EventOutcome< T > after_event_join(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< T > &inspace, EventType event, std::size_t cls)
Port of State.afterEventJoin: an event at a Join node of an FJ-augmented struct.
ReplyBlockInfo reply_block_info(const NetworkStruct< T > &sn, std::size_t ind)
Defined below; the departure branch records a server held for a reply.
Marginal< T > to_marginal(const NetworkStruct< T > &sn, std::size_t ist, const std::vector< T > &state_i, const std::vector< std::size_t > &phasesz, const std::vector< std::size_t > &phaseshift, std::size_t nvar=0)
Port of State.toMarginal for a STATION, one state row at a time.
GlobalOutcome< T > after_global_event(const NetworkStruct< T > &sn, const NetState< T > &glspace, const GlobalSync< T > &gl)
Port of State.afterGlobalEvent: an SPN mode ENABLEs or FIREs.
EventOutcome< T > after_event(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< T > &inspace, EventType event, std::size_t cls, bool no_promote=false, const T &aux_rate=num_traits< T >::from_int(0))
Port of State.afterEvent: the successors of one event at one NODE.
std::pair< T, std::vector< T > > to_marginal_aggr(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< T > &state_i)
Port of State.toMarginalAggr: the job counts of one node's state row, without the per-phase detail to...
GlobalOutcome< T > after_fj_event(const NetworkStruct< T > &sn, const FjSync< T > &e, const NetState< T > &gl)
Port of State.afterFJEvent: fire ONE entry of the fork firing list.
Matrix< T > rt_state(const NetworkStruct< T > &sn, const std::vector< std::vector< T > > &local)
Port of sn.rtfun: the routing over the stateful nodes AT ONE STATE.
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
A queueing network and its refreshed NetworkStruct.
Port of the MATLAB +State package: the encoding that turns a station's state row into marginal job co...
Port of the event half of MATLAB's +State package: the successor states an event produces at one node...
The mean performance metrics a stationary vector maps to.
std::vector< T > CN
(nclasses) system throughput and response time
Matrix< T > StartN
The DERIVED rates, (nstations x nclasses): how often per unit time a class-r service STARTS at statio...
Matrix< T > TN
(nstations x nclasses)
The generator, the state space it is indexed by, and the event rates.
std::vector< std::vector< std::vector< T > > > dep_rates_imm
std::vector< std::vector< Matrix< T > > > start_filt
The DERIVED START and PREEMPT filtrations, indexed [station-1][class-1]: the rate at which a transiti...
std::vector< NetState< T > > space
row i of Q is space[i]
std::vector< std::vector< std::vector< T > > > arv_rates
arvRates / depRates, indexed [state][stateful-1][class-1]: the total rate of arrivals into,...
std::vector< std::vector< Matrix< T > > > preempt_filt
std::vector< std::vector< std::vector< T > > > dep_rates
std::vector< Matrix< T > > filt
Dfilt, MATLAB's EVENT FILTRATION: filt[a] holds only the rates that synchronization a contributed,...
std::vector< std::vector< std::vector< T > > > arv_rates_imm
The parts of arv_rates / dep_rates contributed by those same immediate sources.
std::vector< std::size_t > vanishing
The rows the purge restated, i.e.
Matrix< T > Q
(n x n) infinitesimal generator
Matrix< T > Qimm
Qimm, the IMMEDIATE-ONLY part of Q: the arcs contributed by a Router or Fork pass-through,...
static constexpr double Zero
One network state: the per-stateful-node local rows it is composed of.
std::vector< std::vector< T > > local
local[isf] is that node's state row
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.
A MAP as the pair of matrices (D0, D1).
Matrix< T > S
stochastic complement on the selected states
What one event produces at one node: the successor rows, their rates and their probabilities,...
std::vector< T > prob
per-row probability of the choice
std::vector< std::vector< T > > space
successor local state rows
std::vector< T > rate
per-row rate, -1 on a passive half
One fork firing synchronization: sn.fjsync{k}.
std::size_t fork
1-based Fork node
std::vector< std::size_t > branchheads
1-based node per branch
std::size_t cls
1-based ORIGINAL class being forked
std::vector< std::size_t > auxclasses
the tag's auxiliary class per branch
What one global event produces: a whole network state per outcome.
std::vector< NetState< T > > space
std::vector< bool > completion
True where the outcome is a firing COMPLETION, i.e.
A GLOBAL synchronization: an SPN mode event and the place arcs it drives.
What State.toMarginal returns for one station and one state row.
std::vector< T > nir
jobs per class
std::vector< T > sir
jobs in service per class
One half of a GLOBAL synchronization: a mode event at a node.
std::size_t mode
1-based mode index
std::size_t node
1-based node index (a Transition, or a place)
std::size_t cls
1-based class the arc moves
One network state: the per-stateful-node local rows it is composed of.
std::vector< std::vector< T > > local
local[isf] is that node's state row
Where node ind keeps its reply-block counters inside the local vars.
std::vector< std::size_t > classes
1-based calling classes holding a block
One station of the network.
std::vector< T > jdscalingpeak
sn.jdscalingpeak for this station: the declared peak joint-dependent scaling per class.
CdScaling< T > jdscaling
sn.jdscaling for this station: MATLAB's Station.ljdScaling, the JOINT dependence map eta_i(n),...
std::vector< T > cdscalingpeak
sn.cdscalingpeak for this station: the DECLARED peak rate scaling per class, empty when the station i...
CdScaling< T > cdscaling
sn.cdscaling for this station: the class-dependence map, empty when unset.
One synchronization: an ACTIVE event and the PASSIVE event it drives.