5#ifndef LINE_SOLVERS_CTMC_SOLVER_CTMC_ANALYZER_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_ANALYZER_H
96 double nominal = std::numeric_limits<double>::quiet_NaN();
289 return ::line::method_registry::methods(
"CTMC");
294 return method ==
"mdd";
308 if (method.compare(0, 3,
"qrf") == 0)
310 method +
"') moved to SolverBA. Use SolverBA with method '" + method +
311 "' (or the 'qr'/'lr' aliases).");
312 if (std::find(valid.begin(), valid.end(), method) == valid.end())
314 "' method is unsupported by this solver");
317 "' method does not enumerate a state space and is served by its "
318 "own analyzer, not by solver_ctmc_analyzer");
342 for (std::size_t ist = 0; ist <
sn.nstations; ++ist)
343 for (std::size_t r = 0; r <
sn.nclasses; ++r) {
344 if (ist <
sn.disabled.size() && r <
sn.disabled[ist].size() &&
sn.disabled[ist][r])
348 if (api::detail::nonmkv_is_markovian(d.
type) || api::detail::nonmkv_is_scheduled(d.
type))
352 "SolverCTMC: config.nonmkv='none' leaves the " + nm +
" service of class '" +
353 sn.classes[r].name +
"' at station '" +
sn.stations[ist].name +
354 "' without a phase-type representation, which a CTMC cannot carry; use "
355 "nonmkv='bernstein' or declare a phase-type law");
358 sn.transparam.begin();
359 it !=
sn.transparam.end(); ++it)
360 for (std::size_t m = 0; m < it->second.firingproc.size(); ++m) {
361 if (api::detail::nonmkv_firing_ready(it->second, m))
continue;
364 if (api::detail::nonmkv_is_markovian(d.
type) || api::detail::nonmkv_is_scheduled(d.
type))
368 "SolverCTMC: config.nonmkv='none' leaves the " + nm +
" firing law of mode " +
369 std::to_string(m + 1) +
" of a transition without a phase-type representation, "
370 "which a CTMC cannot carry; use nonmkv='bernstein' or declare a phase-type law");
388 for (std::size_t i = 0; i <
sn.stations.size(); ++i) {
389 if (
sn.stations[i].cdscaling &&
sn.stations[i].cdscalingpeak.empty())
391 "SolverCTMC: station '" +
sn.stations[i].name +
392 "' declares class-dependent service without a peak rate. Utilization at a "
393 "class-dependent station is reported as T/mu/peak, so pass the peak to "
394 "setClassDependence");
395 if (
sn.stations[i].jdscaling &&
sn.stations[i].jdscalingpeak.empty())
397 "SolverCTMC: station '" +
sn.stations[i].name +
398 "' declares joint-dependent service without a peak rate; pass the peak to "
399 "setJointDependence");
421namespace analyzer_detail {
439std::vector<std::size_t> resolve_cutoff(
const NetworkStruct<T>&
sn,
const CtmcOptions&
opt) {
440 const std::size_t M =
sn.nstations, K =
sn.nclasses;
441 std::vector<std::size_t> cut(K, 0);
442 bool any_open =
false;
443 for (std::size_t k = 0; k < K; ++k)
444 if (!std::isfinite(
sn.njobs()[k])) any_open =
true;
445 if (!any_open)
return cut;
447 if (!
opt.cutoff_mat.empty()) {
451 if (
opt.cutoff_mat.size() != M)
452 throw InputError(
"SolverCTMC: the cutoff matrix must have one row per station");
453 for (std::size_t k = 0; k < K; ++k) {
454 if (std::isfinite(
sn.njobs()[k]))
continue;
455 for (std::size_t i = 0; i < M; ++i) {
456 if (
opt.cutoff_mat[i].size() != K)
458 "SolverCTMC: the cutoff matrix must have one column per class");
459 cut[k] = std::max(cut[k],
opt.cutoff_mat[i][k]);
464 if (!opt.cutoff_vec.empty()) {
465 if (opt.cutoff_vec.size() != K)
466 throw InputError(
"SolverCTMC: the per-class cutoff must have one entry per class");
467 return opt.cutoff_vec;
470 if (opt.cutoff > 0 && std::isfinite(opt.cutoff)) {
471 c =
static_cast<std::size_t
>(opt.cutoff);
472 }
else if (opt.cutoff < 0) {
475 const double e = 1.0 /
static_cast<double>(M * K);
476 c =
static_cast<std::size_t
>(std::ceil(std::pow(6000.0, e)));
479 for (std::size_t k = 0; k < K; ++k)
480 if (!std::isfinite(sn.njobs()[k])) cut[k] = c;
497bool set_retrieval_truncation(
const NetworkStruct<T>& sn,
const std::vector<std::size_t>& cutoff,
498 NetworkStruct<T>& out) {
500 for (
typename std::map<std::size_t, qn::CacheParam<T>>::const_iterator ci =
501 sn.nodeparam.begin();
502 ci != sn.nodeparam.end(); ++ci)
503 if (ci->second.retrieval_capacity > 0 && !ci->second.retrieval_classes.empty()) any =
true;
504 if (!any)
return false;
506 if (sn.nclosedjobs() > 0) {
507 lvl =
static_cast<long>(sn.nclosedjobs()) - 1;
510 for (std::size_t k = 0; k < cutoff.size(); ++k) mx = std::max(mx, cutoff[k]);
511 lvl =
static_cast<long>(mx) - 1;
513 if (lvl < 0) lvl = 0;
515 for (
typename std::map<std::size_t, qn::CacheParam<T>>::iterator ci = out.nodeparam.begin();
516 ci != out.nodeparam.end(); ++ci)
517 if (ci->second.retrieval_capacity > 0) ci->second.max_pending_retrieval = lvl;
545bool annihilate_signal_capacity(
const NetworkStruct<T>& sn, NetworkStruct<T>& out) {
546 std::vector<bool> annihilated(sn.nclasses,
false);
548 for (std::size_t k = 0; k < sn.nclasses && k < sn.issignal.size(); ++k) {
549 if (!sn.issignal[k])
continue;
551 annihilated[k] =
true;
554 if (!any)
return false;
556 for (std::size_t i = 0; i < out.stations.size() && i < out.classcap.size(); ++i) {
559 if (out.stations[i].sched == SchedStrategy::EXT)
continue;
560 for (std::size_t k = 0; k < annihilated.size() && k < out.classcap[i].size(); ++k)
561 if (annihilated[k]) out.classcap[i][k] = 0.0;
604solvers::CacheMetrics<T> cache_metrics(
const NetworkStruct<T>& sn,
const CtmcResult<T>& r,
605 const std::vector<T>& pi,
const Matrix<T>& QN) {
606 solvers::CacheMetrics<T> out =
608 std::vector<T>(), Matrix<T>(), Matrix<T>(), std::vector<T>());
609 if (out.caches.empty() || r.space.empty())
return out;
610 const std::size_t K = sn.nclasses, ns = r.space.size();
611 const double dnan = std::numeric_limits<double>::quiet_NaN();
613 for (std::size_t c = 0; c < out.caches.size(); ++c) {
614 solvers::CacheNodeMetrics<T>& m = out.caches[c];
615 const std::size_t isf = sn.stateful_index(m.node);
616 if (isf == 0)
continue;
617 const qn::CacheParam<T>& cp = sn.nodeparam.find(m.node)->second;
619 std::vector<double> tn(K, 0.0);
620 for (std::size_t s = 0; s < ns && s < pi.size(); ++s) {
621 const double p = num_traits<T>::to_double(pi[s]);
622 if (p == 0)
continue;
623 if (isf - 1 >= r.dep_rates[s].size())
continue;
624 for (std::size_t k = 0; k < K && k < r.dep_rates[s][isf - 1].size(); ++k)
625 tn[k] += p * num_traits<T>::to_double(r.dep_rates[s][isf - 1][k]);
628 const std::size_t n = cp.nitems;
630 for (std::size_t l = 0; l < cp.itemcap.size(); ++l)
631 if (cp.itemcap[l] > 0) tcc +=
static_cast<std::size_t
>(cp.itemcap[l]);
632 std::vector<std::size_t> rcl, rci, rco;
634 const std::size_t lvw = r.space[0].local[isf - 1].size();
635 const bool retr = !rcl.empty() && lvw >= tcc + n + rcl.size();
636 std::vector<double> drate(K, 0.0), dclass(K, 0.0), inflight(K, 0.0);
645 const std::size_t tail = cp.retrieval_capacity > 0 ? n + rcl.size() : 0;
646 if (n > 0 && tcc > 0 && lvw >= tcc + tail) {
647 const std::size_t coff = lvw - (tcc + tail);
648 const std::size_t h = cp.itemcap.size();
649 Matrix<T> ip(n, h + 1);
650 std::vector<double> acc(n * h, 0.0);
651 std::vector<char> present(n, 0);
652 for (std::size_t s = 0; s < ns && s < pi.size(); ++s) {
653 const double p = num_traits<T>::to_double(pi[s]);
654 if (p == 0)
continue;
655 const std::vector<T>& lv = r.space[s].local[isf - 1];
656 std::size_t off = coff;
657 for (std::size_t l = 0; l < h; ++l) {
658 const std::size_t cap =
659 cp.itemcap[l] > 0 ?
static_cast<std::size_t
>(cp.itemcap[l]) : 0;
660 std::fill(present.begin(), present.end(), 0);
661 for (std::size_t q = 0; q < cap; ++q) {
662 const long it =
static_cast<long>(num_traits<T>::to_double(lv[off + q]));
663 if (it >= 1 &&
static_cast<std::size_t
>(it) <= n)
664 present[
static_cast<std::size_t
>(it) - 1] = 1;
666 for (std::size_t i = 0; i < n; ++i)
667 if (present[i]) acc[i * h + l] += p;
671 for (std::size_t i = 0; i < n; ++i) {
673 for (std::size_t l = 0; l < h; ++l) {
674 const double v = acc[i * h + l];
675 ip(i, l + 1) = num_traits<T>::from_double(v);
678 ip(i, 0) = num_traits<T>::from_double(miss);
684 const std::size_t aoff = lvw - (n + rcl.size()), boff = aoff + n;
685 std::vector<double> phi(n, 0.0), d1(n, 0.0);
686 for (std::size_t s = 0; s < ns && s < pi.size(); ++s) {
687 const double p = num_traits<T>::to_double(pi[s]);
688 if (p == 0)
continue;
689 const std::vector<T>& lv = r.space[s].local[isf - 1];
690 for (std::size_t i = 0; i < n; ++i)
691 if (num_traits<T>::to_double(lv[aoff + i]) != 0) phi[i] += p;
692 for (std::size_t j = 0; j < rcl.size(); ++j)
693 d1[rci[j] - 1] += p * num_traits<T>::to_double(lv[boff + j]);
695 m.delayedhitqlen.assign(n, num_traits<T>::from_int(0));
696 m.delayedhitqlenfull.assign(n, num_traits<T>::from_int(0));
697 for (std::size_t i = 0; i < n; ++i) {
698 m.delayedhitqlen[i] = num_traits<T>::from_double(d1[i]);
699 m.delayedhitqlenfull[i] = num_traits<T>::from_double(d1[i] + phi[i]);
701 for (std::size_t s = 0; s < ns && s < pi.size(); ++s) {
702 const double p = num_traits<T>::to_double(pi[s]);
703 if (p == 0)
continue;
704 const std::vector<T>& lv = r.space[s].local[isf - 1];
705 for (std::size_t j = 0; j < rcl.size(); ++j) {
706 const std::size_t i = rci[j] - 1;
707 const double held = num_traits<T>::to_double(lv[boff + j]);
708 if (held <= 0 || num_traits<T>::to_double(lv[aoff + i]) == 0)
continue;
709 double completes = 0.0;
710 for (std::size_t b = 0; b < ns; ++b) {
711 if (b == s)
continue;
712 const double q = num_traits<T>::to_double(r.Q(s, b));
713 if (q == 0)
continue;
714 if (num_traits<T>::to_double(r.space[b].local[isf - 1][aoff + i]) == 0)
717 if (rco[j] - 1 < K) drate[rco[j] - 1] += p * held * completes;
722 for (std::size_t s = 0; s < ns && s < pi.size(); ++s) {
723 const double p = num_traits<T>::to_double(pi[s]);
724 if (p == 0)
continue;
725 const std::vector<T>& lv = r.space[s].local[isf - 1];
726 for (std::size_t j = 0; j < rcl.size(); ++j)
728 dclass[rco[j] - 1] += p * num_traits<T>::to_double(lv[boff + j]);
730 for (std::size_t k = 0; k < K; ++k)
731 for (std::size_t i = 0; i < cp.retrieval_classes.size(); ++i) {
732 if (k >= cp.retrieval_classes[i].size())
continue;
733 const std::size_t rc = cp.retrieval_classes[i][k];
734 if (rc == 0 || rc >
static_cast<std::size_t
>(
QN.cols()))
continue;
735 for (std::size_t st = 0; st < static_cast<std::size_t>(
QN.rows()); ++st)
736 inflight[k] += num_traits<T>::to_double(
QN(st, rc - 1));
740 m.hitprob.assign(K, num_traits<T>::from_double(dnan));
741 m.missprob.assign(K, num_traits<T>::from_double(dnan));
742 if (retr) m.delayedprob.assign(K, num_traits<T>::from_double(dnan));
743 for (std::size_t k = 0; k < K; ++k) {
744 if (k >= cp.hitclass.size() || k >= cp.missclass.size())
continue;
745 const std::size_t h = cp.hitclass[k], mi = cp.missclass[k];
746 if (h == 0 || mi == 0 || h > K || mi > K)
continue;
747 const double denom = tn[h - 1] + tn[mi - 1];
748 if (denom <= 0)
continue;
752 const double d = retr ? std::min(drate[k], tn[h - 1]) : 0.0;
753 m.hitprob[k] = num_traits<T>::from_double((tn[h - 1] - d) / denom);
754 m.missprob[k] = num_traits<T>::from_double(tn[mi - 1] / denom);
755 if (retr) m.delayedprob[k] = num_traits<T>::from_double(d / denom);
756 if (retr && cp.retrieval_queues.find(k) != cp.retrieval_queues.end() &&
757 !cp.retrieval_queues.find(k)->second.empty()) {
758 if (m.latency.empty()) m.latency.assign(K, num_traits<T>::from_double(dnan));
760 m.latency[k] = num_traits<T>::from_double((inflight[k] + dclass[k]) / denom);
778std::vector<double> cache_delayed_hit_rate(
const NetworkStruct<T>& sn, std::size_t ind,
779 const CtmcResult<T>& r,
const std::vector<T>& pi) {
780 const std::size_t K = sn.nclasses;
781 std::vector<double> drate(K, 0.0);
782 const std::size_t isf = sn.stateful_index(ind);
783 if (isf == 0 || r.space.empty())
return drate;
784 const auto it = sn.nodeparam.find(ind);
787 const qn::CacheParam<T>& cp = it->second;
788 if (!(cp.retrieval_capacity > 0))
return drate;
789 std::vector<std::size_t> rcl, rci, rco;
791 if (rcl.empty())
return drate;
792 const std::size_t n = cp.nitems, ns = r.space.size();
794 for (std::size_t l = 0; l < cp.itemcap.size(); ++l)
795 if (cp.itemcap[l] > 0) tcc +=
static_cast<std::size_t
>(cp.itemcap[l]);
796 const std::size_t lvw = r.space[0].local[isf - 1].size();
797 if (lvw < tcc + n + rcl.size())
return drate;
798 const std::size_t aoff = lvw - (n + rcl.size()), boff = aoff + n;
799 for (std::size_t s = 0; s < ns && s < pi.size(); ++s) {
800 const double p = num_traits<T>::to_double(pi[s]);
801 if (p == 0)
continue;
802 const std::vector<T>& lv = r.space[s].local[isf - 1];
803 for (std::size_t j = 0; j < rcl.size(); ++j) {
804 const std::size_t i = rci[j] - 1;
805 const double held = num_traits<T>::to_double(lv[boff + j]);
806 if (held <= 0 || num_traits<T>::to_double(lv[aoff + i]) == 0)
continue;
807 double completes = 0.0;
808 for (std::size_t b = 0; b < ns; ++b) {
809 if (b == s)
continue;
810 const double q = num_traits<T>::to_double(r.Q(s, b));
811 if (q == 0)
continue;
812 if (num_traits<T>::to_double(r.space[b].local[isf - 1][aoff + i]) == 0)
815 if (rco[j] - 1 < K) drate[rco[j] - 1] += p * held * completes;
842void strip_reference_source_column(
const NetworkStruct<T>& sn, std::size_t ind,
843 std::vector<T>& row) {
844 const std::size_t ist = sn.nodes[ind - 1].station;
845 if (ist == 0 || ist > sn.stations.size())
return;
846 if (sn.stations[ist - 1].nodetype != NodeType::Source &&
847 sn.stations[ist - 1].sched != SchedStrategy::EXT)
850 for (std::size_t r = 0; r < sn.nclasses; ++r) w += sn.phasessz_of(ist, r + 1);
854 if (row.size() == w + 1) row.erase(row.begin());
869bool default_init_state(
const NetworkStruct<T>& sn, NetState<T>& init) {
870 const std::size_t R = sn.nclasses;
871 const std::vector<std::size_t>& sfn = sn.stateful_nodes;
872 init.local.assign(sfn.size(), std::vector<T>());
873 for (std::size_t f = 0; f < sfn.size(); ++f) {
874 const std::size_t ind = sfn[f];
883 const typename std::map<std::size_t, Matrix<T>>::const_iterator sp =
884 sn.statespace.find(ind);
885 if (sp != sn.statespace.end() && sp->second.rows() == 1 && sp->second.cols() > 0) {
886 std::vector<T> row(sp->second.cols());
887 for (std::size_t c = 0; c < sp->second.cols(); ++c) row[c] = sp->second(0, c);
888 strip_reference_source_column(sn, ind, row);
902 std::vector<std::size_t> marg(R, 0), mph(R, 1);
903 const std::size_t mist = sn.nodes[ind - 1].station;
904 bool rebuilt =
false;
905 if (row.size() == R && mist != 0) {
906 for (std::size_t r = 0; r < R; ++r) {
907 const double v = num_traits<T>::to_double(row[r]);
908 marg[r] = v > 0.0 ?
static_cast<std::size_t
>(v + 0.5) : 0;
909 mph[r] = sn.phasessz_of(mist, r + 1);
911 std::vector<T> built;
913 init.local[f] = built;
917 if (!rebuilt) init.local[f] = row;
920 const std::size_t ist = sn.nodes[ind - 1].station;
921 std::vector<std::size_t> nmarg(R, 0), ph(R, 1);
923 for (std::size_t r = 0; r < R; ++r) ph[r] = sn.phasessz_of(ist, r + 1);
924 if (sn.stations[ist - 1].nodetype == NodeType::Source) {
932 for (std::size_t r = 0; r < R; ++r)
933 if (!sn.disabled[ist - 1][r]) nmarg[r] = 1;
935 for (std::size_t r = 0; r < R; ++r) {
936 const double nj = sn.njobs()[r];
937 if (std::isfinite(nj) && sn.classes[r].refstat == ist)
938 nmarg[r] =
static_cast<std::size_t
>(nj);
954 const bool declared = sn.initmarking.find(ind) != sn.initmarking.end();
955 for (std::size_t r = 0; r < R; ++r) {
957 if (declared || m0 > nmarg[r]) nmarg[r] = m0;
968std::size_t init_state_index(
const NetworkStruct<T>& sn,
const std::vector<NetState<T>>& space) {
969 const std::size_t npos =
static_cast<std::size_t
>(-1);
970 if (space.empty())
return npos;
972 if (!default_init_state(sn, init))
return npos;
973 for (std::size_t f = 0; f < init.local.size(); ++f) {
976 const std::size_t w = space[0].local[f].size();
977 if (init.local[f].size() > w)
return npos;
978 if (init.local[f].size() < w)
979 init.local[f].insert(init.local[f].begin(), w - init.local[f].size(),
980 num_traits<T>::from_int(0));
982 const std::vector<double> key = ctmc_detail::state_key(init);
983 for (std::size_t s = 0; s < space.size(); ++s)
984 if (ctmc_detail::state_key(space[s]) == key)
return s;
1008bool init_state_distribution(
const NetworkStruct<T>& sn,
const std::vector<NetState<T>>& space,
1009 std::vector<T>& pi0) {
1010 const std::size_t npos =
static_cast<std::size_t
>(-1);
1011 pi0.assign(space.size(), num_traits<T>::from_int(0));
1012 if (space.empty())
return false;
1014 if (!default_init_state(sn, base))
return false;
1015 const std::vector<std::size_t>& sfn = sn.stateful_nodes;
1019 std::vector<std::vector<std::vector<T>>> rows(sfn.size());
1020 std::vector<std::vector<double>> wts(sfn.size());
1021 for (std::size_t f = 0; f < sfn.size(); ++f) {
1022 const typename std::map<std::size_t, Matrix<T>>::const_iterator ss =
1023 sn.statespace.find(sfn[f]);
1024 const typename std::map<std::size_t, std::vector<T>>::const_iterator sp =
1025 sn.stateprior.find(sfn[f]);
1026 if (ss == sn.statespace.end() || sp == sn.stateprior.end() || ss->second.rows() == 0 ||
1027 ss->second.rows() != sp->second.size()) {
1028 rows[f].push_back(base.local[f]);
1029 wts[f].push_back(1.0);
1032 for (std::size_t r = 0; r < ss->second.rows(); ++r) {
1033 const double w = num_traits<T>::to_double(sp->second[r]);
1034 if (!(w > 0.0))
continue;
1035 std::vector<T> row(ss->second.cols());
1036 for (std::size_t c = 0; c < ss->second.cols(); ++c) row[c] = ss->second(r, c);
1037 strip_reference_source_column(sn, sfn[f], row);
1038 rows[f].push_back(row);
1039 wts[f].push_back(w);
1041 if (rows[f].empty())
return false;
1044 std::vector<std::size_t> pick(sfn.size(), 0);
1047 NetState<T> cand = base;
1049 for (std::size_t f = 0; f < sfn.size(); ++f) {
1050 cand.local[f] = rows[f][pick[f]];
1051 w *= wts[f][pick[f]];
1054 const std::size_t wd = space[0].local[f].size();
1055 if (cand.local[f].size() > wd)
return false;
1056 if (cand.local[f].size() < wd)
1057 cand.local[f].insert(cand.local[f].begin(), wd - cand.local[f].size(),
1058 num_traits<T>::from_int(0));
1060 std::size_t idx = npos;
1061 const std::vector<double> key = ctmc_detail::state_key(cand);
1062 for (std::size_t s = 0; s < space.size() && idx == npos; ++s)
1063 if (ctmc_detail::state_key(space[s]) == key) idx = s;
1064 if (idx == npos)
return false;
1065 pi0[idx] = T(pi0[idx] + num_traits<T>::from_double(w));
1069 for (; f < sfn.size(); ++f) {
1070 if (++pick[f] < rows[f].size())
break;
1073 if (f == sfn.size())
break;
1075 if (!(total > 0.0))
return false;
1085std::vector<std::size_t> weak_components(
const Matrix<T>& Q, std::size_t& ncomp) {
1086 const std::size_t n = Q.rows();
1087 const std::size_t npos =
static_cast<std::size_t
>(-1);
1088 std::vector<std::size_t> comp(n, npos);
1090 for (std::size_t s = 0; s < n; ++s) {
1091 if (comp[s] != npos)
continue;
1092 std::vector<std::size_t> stack{s};
1094 while (!stack.empty()) {
1095 const std::size_t u = stack.back();
1097 for (std::size_t v = 0; v < n; ++v) {
1098 if (comp[v] != npos)
continue;
1101 if (v == u)
continue;
1102 if (num_traits<T>::to_double(Q(u, v)) != 0 ||
1103 num_traits<T>::to_double(Q(v, u)) != 0) {
1116CtmcResult<T> restrict_to(
const CtmcResult<T>& r,
const std::vector<std::size_t>& wset) {
1118 out.Q =
Matrix<T>(wset.size(), wset.size(), num_traits<T>::from_int(0));
1119 for (std::size_t a = 0; a < wset.size(); ++a)
1120 for (std::size_t b = 0; b < wset.size(); ++b) out.Q(a, b) = r.Q(wset[a], wset[b]);
1121 out.space.reserve(wset.size());
1122 out.arv_rates.reserve(wset.size());
1123 out.dep_rates.reserve(wset.size());
1124 for (std::size_t a = 0; a < wset.size(); ++a) {
1125 out.space.push_back(r.space[wset[a]]);
1126 out.arv_rates.push_back(r.arv_rates[wset[a]]);
1127 out.dep_rates.push_back(r.dep_rates[wset[a]]);
1133 out.filt.reserve(r.filt.size());
1134 for (std::size_t f = 0; f < r.filt.size(); ++f) {
1135 Matrix<T> F(wset.size(), wset.size(), num_traits<T>::from_int(0));
1136 for (std::size_t a = 0; a < wset.size(); ++a)
1137 for (std::size_t b = 0; b < wset.size(); ++b) F(a, b) = r.filt[f](wset[a], wset[b]);
1138 out.filt.push_back(F);
1168 const bool pooled =
ctmc_pools(sn_arg, sn_pool);
1173 const bool mapc =
ctmc_mapc(sn_pl, sn_mapc);
1175 if (pooled || mapc) out.
solved_sn = std::make_shared<const NetworkStruct<T>>(sn_in);
1176 out.
cutoff = analyzer_detail::resolve_cutoff(sn_in,
opt);
1180 const bool retr = analyzer_detail::set_retrieval_truncation(sn_in, out.
cutoff, sn_trunc);
1186 const bool sig = analyzer_detail::annihilate_signal_capacity(sn_r, sn_sig);
1204 const std::vector<Sync<T>> sync = refresh_sync(
sn);
1205 const std::vector<qn::GlobalSync<T>> gsync = refresh_global_sync(
sn);
1221 std::vector<NetState<T>> space;
1222 if (!gsync.empty() || !fjsync.empty()) {
1224 if (!analyzer_detail::default_init_state(
sn, init))
1226 "SolverCTMC: the model's initial marking admits no state; check the Place "
1227 "populations against the class reference stations");
1234 space = space_generator(
sn, out.
cutoff,
opt.state_max,
opt.cutoff_mat);
1238 "SolverCTMC: the state space is empty; no state satisfies the model's capacities");
1255 const std::size_t npos =
static_cast<std::size_t
>(-1);
1258 std::size_t init = analyzer_detail::init_state_index(
sn, r.
space);
1260 std::size_t ncomp = 0;
1261 const std::vector<std::size_t> comp = analyzer_detail::weak_components(r.
Q, ncomp);
1270 std::vector<std::size_t> sizes(ncomp, 0);
1271 for (std::size_t s = 0; s < comp.size(); ++s) ++sizes[comp[s]];
1272 pick =
static_cast<std::size_t
>(
1273 std::max_element(sizes.begin(), sizes.end()) - sizes.begin());
1275 std::vector<std::size_t> wset;
1276 for (std::size_t s = 0; s < comp.size(); ++s)
1277 if (comp[s] == pick) wset.push_back(s);
1280 std::size_t moved = npos;
1282 for (std::size_t k = 0; k < wset.size(); ++k)
1283 if (wset[k] == init) {
1288 r = analyzer_detail::restrict_to(r, wset);
1298 static_cast<std::size_t
>(r.
Q.rows()));
1305 out.
cache = analyzer_detail::cache_metrics(
sn, r, out.
pi, out.
avg.QN);
1390 const std::string& method) {
1391 const std::size_t M =
sn.nstations, K =
sn.nclasses;
1393 std::vector<std::vector<bool>> srcmask(M, std::vector<bool>(K,
false));
1394 for (std::size_t i = 0; i < M; ++i)
1395 if (
sn.stations[i].nodetype == NodeType::Source)
1396 for (std::size_t k = 0; k < K; ++k) srcmask[i][k] =
true;
1455 sub.chain_aggregation =
false;
1474 sub.load_concealment =
false;
1478 opt.method,
opt.transform_iter_max);
1506 const std::size_t M =
sn.nstations, K =
sn.nclasses;
1507 const std::vector<std::size_t>& subset =
opt.fes_stations;
1508 if (subset.size() < 2)
1510 "options.config.fes_stations must name at least two stations: collapsing one "
1511 "station into a flow-equivalent server saves nothing.");
1512 if (subset.size() >= M)
1514 "options.config.fes_stations names every station: there is no complement left "
1516 for (std::size_t i : subset)
1518 throw InputError(
"options.config.fes_stations must be 1-based station indices in 1.." +
1519 std::to_string(M) +
".");
1526 sub.fes_stations.clear();
1533 const std::size_t fesIst = snRed.
nodes[info.
fesNode - 1].station;
1534 std::size_t tableSize = 1;
1535 for (
int c : info.
cutoffs) tableSize *=
static_cast<std::size_t
>(c + 1);
1536 std::vector<double> Pn(tableSize, 0.0);
1537 for (std::size_t r = 0; r < SSq.
rows(); ++r) {
1538 std::vector<int> nvec(K, 0);
1539 for (std::size_t k = 0; k < K; ++k)
1540 nvec[k] =
static_cast<int>(
1558 for (std::size_t k = 0; k < K; ++k) {
1559 out.
QN(i, k) = redAvg.
QN(a, k);
1560 out.
UN(i, k) = redAvg.
UN(a, k);
1561 out.
TN(i, k) = redAvg.
TN(a, k);
1566 Matrix<T> Qsub(Msub, K, zero), Usub(Msub, K, zero);
1567 for (std::size_t idx = 0; idx < tableSize; ++idx) {
1568 if (!(Pn[idx] > 0.0))
continue;
1570 for (std::size_t a = 0; a < Msub && a < cm.
QN[idx].rows(); ++a)
1571 for (std::size_t k = 0; k < K; ++k) {
1572 Qsub(a, k) += T(w * cm.
QN[idx](a, k));
1573 Usub(a, k) += T(w * cm.
UN[idx](a, k));
1576 for (std::size_t a = 0; a < Msub; ++a) {
1578 for (std::size_t k = 0; k < K; ++k) {
1579 out.
QN(i, k) = Qsub(a, k);
1580 out.
UN(i, k) = Usub(a, k);
1584 for (std::size_t c = 0; c <
sn.nchains; ++c) {
1585 const std::size_t isf =
sn.stateful_of_station(i + 1) - 1;
1587 if (isf <
sn.visits[c].rows() && isfFes < snRed.
visits[c].rows() &&
1588 snRed.
visits[c](isfFes, k) > zero)
1589 ratio += T(
sn.visits[c](isf, k) / snRed.
visits[c](isfFes, k));
1591 out.
TN(i, k) = T(redAvg.
TN(fesIst - 1, k) * ratio);
1595 out.
CN.assign(K, zero);
1597 for (std::size_t i = 0; i < M; ++i)
1598 for (std::size_t k = 0; k < K; ++k) {
1599 if (out.
TN(i, k) > zero) out.
RN(i, k) = T(out.
QN(i, k) / out.
TN(i, k));
1600 out.
CN[k] += out.
RN(i, k);
1614 if (
opt.chain_aggregation &&
sn.nchains <
sn.nclasses)
What a solver observed about the Cache nodes of a model.
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::size_t stateful_of_station(std::size_t st) const
std::vector< NodeDef > nodes
every node, in creation order
std::vector< Matrix< T > > visits
(nchains) each (nstateful x nclasses)
static void step(const char *fmt,...)
Write one progress line.
Host-aware memory pre-gate for SolverCTMC.
Steady-state distribution of a continuous-time Markov chain.
Worst-case log-size of the CTMC state space induced by a NetworkStruct.
Port of matlab/src/solvers/CTMC/ctmc_stationary.m: the single entry point for the stationary distribu...
The exception types the port throws.
Flow-equivalent-server aggregation: replace a station subset by one station.
Per-station metrics of the ISOLATED subnetwork at every population state, the companion of fes_comput...
Port of matlab/src/api/fj/sn_fj_validate.m and matlab/src/io/@@ModelAdapter/fjtag....
Running progress log of a LINE solver run (the "solver console").
Dense matrix and non-owning view.
The method names of each solver, in listing order.
bool sn_has_nonmarkov(const qn::NetworkStruct< T > &sn, bool preserve_det=false)
Whether any law in the struct would be replaced, so a caller can skip copying the struct when there i...
bool nonmkv_enabled(const std::string &method)
Resolve options.config.nonmkv to NonmarkovOptions::enabled.
SnPnAvgRates< T > sn_pn_avg_rates(const qn::NetworkStruct< T > &sn, const Matrix< T > &QN, const Matrix< T > &TN, const Matrix< T > &AN, const Matrix< T > &RN)
Port of sn_pn_avg_rates: rewrite the place rows of TN, AN and RN so that they agree with the recovere...
@ Ph
Bernstein density fit: a genuine phase-type, shape-carrying.
void sn_nonmarkov_toph(qn::NetworkStruct< T > &sn, const NonmarkovOptions &opts=NonmarkovOptions())
Replace every non-Markovian service and firing law by a Markovian surrogate.
mva::AvgResult< T > solver_ctmc_fes_aggregation(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Solve with a station subset replaced by a FLOW-EQUIVALENT SERVER, then recover the collapsed stations...
bool ctmc_pools(const qn::NetworkStruct< T > &in, qn::NetworkStruct< T > &out)
Rewrite a copy of in into out when it has pooled stations; false, leaving out untouched,...
void ctmc_eliminate_vanishing(CtmcResult< T > &res)
Port of the "now remove immediate transitions" block of solver_ctmc.m (:812-870): eliminate the vanis...
mva::AvgResult< T > solver_ctmc_chain_aggregation(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Solve the CHAIN-AGGREGATED model and map its metrics back to the classes.
void check_method(const std::string &method)
Port of runAnalyzerChecks' method gate.
std::vector< std::string > list_valid_methods()
Port of SolverCTMC.listValidMethods.
void ctmc_check_support(const NetworkStruct< T > &sn)
bool ctmc_mapc(const qn::NetworkStruct< T > &in, qn::NetworkStruct< T > &out)
Rewrite a copy of in into out when it has a MAP service at an FCFS station; false,...
void make_infgen(Matrix< T > &Q)
Port of ctmc_makeinfgen: turn an off-diagonal rate matrix into a generator.
mva::AvgResult< T > solver_ctmc_avg_table(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const std::string &method)
Port of @@SolverCTMC/runAnalyzer.m's result assembly: solve, then apply the metric filter @@NetworkSo...
CtmcAvg< T > solver_ctmc_avg_from_pi(const NetworkStruct< T > &sn, const CtmcResult< T > &r, const std::vector< T > &pivec, bool stationary=true)
Port of solver_ctmc_avg_from_pi: map a state distribution to mean metrics.
mva::AvgResult< T > solver_ctmc_load_concealment(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Solve by LOAD CONCEALMENT, the iterated transformation.
CtmcSolution< T > solve_struct(const NetworkStruct< T > &sn_arg, const CtmcOptions &opt, const std::vector< qn::FjSync< T > > &fjsync)
Build the chain of ONE struct, solve it, reduce it.
std::vector< NetState< T > > ctmc_filter_regions(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
The states of space a DROP region admits, in their original order.
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.
CtmcSolution< T > solver_ctmc_analyzer(const NetworkStruct< T > &sn_in, const CtmcOptions &opt)
Port of solver_ctmc_analyzer.m plus the fork-join wrapper of @@SolverCTMC/runAnalyzer....
mva::AvgResult< T > solver_ctmc_run_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Solve and format in one call, for a caller with no use for the chain.
CtmcStationaryResult< T > ctmc_stationary(const Matrix< T > &Q, std::size_t init_index=static_cast< std::size_t >(-1))
constexpr std::size_t CTMC_DEFAULT_CUTOFF
SolverOptions.m:107: the per-class state-space cutoff SolverCTMC defaults an open or mixed model to.
void ctmc_refuse_nonmarkov(const NetworkStruct< T > &sn)
Refuse the constructs this port generates a chain for but does not MODEL.
bool is_stateless_method(const std::string &method)
True for a method whose analyzer is not the explicit-generator one.
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< 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::size_t ljd_linearize(const std::vector< int > &nvec, const std::vector< int > &cutoffs)
Linearized index of a per-class population vector, for Limited Joint Dependence (LJD) tables.
FesConditionalMetrics< T > fes_compute_metrics(const Matrix< T > &L, const std::vector< int > &mi, const std::vector< bool > &isDelay, const std::vector< int > &cutoffs)
Per-station metrics of the ISOLATED subnetwork at every population state, the companion of fes_comput...
FesAggregateResult< T > fes_aggregate(const qn::NetworkStruct< T > &sn, const std::vector< std::size_t > &subsetIndices, const FesOptions &options=FesOptions())
Flow-equivalent-server aggregation: replace a station subset by one station.
@ REPLY
completes a synchronous call, releasing a held server
constexpr double CTMC_DEFAULT_SAFETY_FRACTION
Fraction of available memory the solver may target.
CtmcGateResult ctmc_memory_gate(double log_nstates, bool force=false, double safety_fraction=CTMC_DEFAULT_SAFETY_FRACTION, bool verbose=false)
Decide whether a state space of log-size log_nstates can be solved here.
double ctmc_state_space_logsize(const qn::NetworkStruct< T > &sn, const CtmcSizeOptions &opt=CtmcSizeOptions())
Worst-case log state-space size of sn.
Matrix< T > sn_get_residt_from_respt(const qn::NetworkStruct< T > &L, const Matrix< T > &RN)
Port of sn_get_residt_from_respt: the per-JOB residence time.
Matrix< T > filter_metric(const qn::NetworkStruct< T > &L, const Matrix< T > &metric, MetricKind kind, const std::vector< std::vector< bool > > *zero_mask)
Port of filterMetric: what @@NetworkSolver/getAvg does between the analyzer and the caller.
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
Port of sn_get_arvr_from_tput: the arrival rate each station sees, from the throughputs and the class...
std::string state_impatience_unsupported(const NetworkStruct< T > &sn, const std::string &solver)
Port of solver_ctmc_state_supports.m: the impatience laws the state machinery cannot serve,...
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,...
bool from_marginal_node_first(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< std::size_t > &n, const std::vector< std::size_t > &phases, std::vector< T > &out)
The FIRST row from_marginal_node emits, BUILT rather than enumerated.
std::size_t state_initial_occupancy(const NetworkStruct< T > &sn, std::size_t ind, std::size_t r)
Port of State.initialOccupancy: the class-r jobs node ind holds in the DECLARED initial state,...
Feature feature_of_process(ProcessType p)
The feature name of a distribution, MATLAB serviceProcess{r}{3}.name.
FeatureSet ctmc_feature_set(const std::string &method)
SolverCTMC.getFeatureSet, the reference's 104 MATLAB names in full.
void feature_gate(const std::string &solver, const FeatureSet &declared, const NetworkStruct< T > &sn, const std::string &requested_method="", const std::string &resolved_method="")
runAnalyzerChecks: refuse a model the solver does not declare, by name.
FjTagged< T > fj_tag(const NetworkStruct< T > &sn)
Port of ModelAdapter.fjtag.
const char * feature_name(Feature f)
The canonical registry name, byte for byte the MATLAB field name.
CacheMetrics< T > cache_metrics_of(const qn::NetworkStruct< T > &sn, const std::vector< T > &hitprob, const std::vector< T > &missprob, const std::vector< T > &delayedprob, const std::vector< T > &latency, const Matrix< T > &hitproblist, const Matrix< T > &itemprob, const std::vector< T > &listcost)
Assemble CacheMetrics from what a cache analyzer returned.
mva::AvgResult< T > transform_solve_lc(const qn::NetworkStruct< T > &sn, InnerSolve inner_solve, const std::string &method, std::size_t iter_max=1000)
LOAD CONCEALMENT (Birman-Kogan Algorithm 2) as a transformation, and the first ITERATED one.
void fj_foldback(const qn::NetworkStruct< T > &sn, Avg &a, const std::vector< std::size_t > &fjclassmap, std::size_t korig)
Reduce the augmented metrics onto the original classes.
mva::AvgResult< T > transform_solve_chains(const qn::NetworkStruct< T > &sn, InnerSolve inner_solve, const std::string &method)
Chain aggregation: collapse every chain onto a single class, solve, and map the chain metrics back on...
bool has_fork_join(const qn::NetworkStruct< T > &sn)
Whether the model needs the tag augmentation at all.
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
Collapse every chain onto one class, port of ModelAdapter.aggregateChains.
Replace every non-Markovian service and firing law by a Markovian surrogate.
Ports of matlab/src/api/sn/sn_pn_firing_rates.m and sn_pn_avg_rates.m.
Port of solver_ctmc.m: the infinitesimal generator of a queueing network, assembled from the enumerat...
Finite Capacity Regions in SolverCTMC: the DROP rule, as a filter on the enumerated state space,...
Port of solver_ctmc_mapc.m: the pair form of a MAP (and MMPP2) service at an FCFS station,...
Port of solver_ctmc_pools.m: rewrite every heterogeneous-server station (Queue.addServerType) into th...
The DECLARED side of the gate: one feature set per solver.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
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 <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
options.config.nonmkv and friends.
std::size_t order
nonmkvorder, the phase budget
PhFit phfit
which surrogate family
What sn_pn_avg_rates rewrites in place; empty tables are left empty.
The mean performance metrics a stationary vector maps to.
The SolverCTMC knobs this port honours.
bool force
options.force: downgrade the memory pre-gate's refusal to a warning.
double memory_safety_fraction
options.memorySafetyFraction: share of available memory a solve may target.
std::vector< std::size_t > cutoff_vec
per-class override, or empty
std::size_t nonmkv_order
options.config.nonmkvorder: the phase budget sn_nonmarkov_toph spends on a non-Markovian service law.
bool load_concealment
options.config.transform='lc': solve by LOAD CONCEALMENT.
std::size_t transform_iter_max
Sweep cap for an iterated transformation; the kernel's own default.
bool stiff
options.stiff, which SolverOptions defaults to TRUE and the transient passes to ctmc_transient as use...
std::vector< std::vector< std::size_t > > cutoff_mat
options.cutoff AS A (station x class) MATRIX, or empty.
std::vector< std::size_t > fes_stations
options.config.fes_stations: 1-BASED station indices to collapse into a flow-equivalent server before...
std::size_t state_max
refuse a space larger than this
double fau_delta
options.config.fau_delta: occupancy below which a state is dropped.
double cutoff
< 0 = not given
double timestep
options.timestep: the FIXED OUTPUT STEP of a transient analysis.
std::string nonmkv
options.config.nonmkv: 'bernstein' (default) runs sn_nonmarkov_toph, 'none' skips it; any other name ...
std::string transient_method
options.config.transient_method: "ode" (the default) integrates the forward equation,...
std::size_t fau_ngrid
options.config.fau_ngrid: output grid size when timestep is unset.
std::vector< CtmcRateSched > rate_sched
options.config.rate_sched: the TIME-INHOMOGENEOUS transient.
bool chain_aggregation
options.config.chain_aggregation: solve the CHAIN-AGGREGATED model.
std::size_t ctmc_tv_ngrid
options.config.ctmc_tv_ngrid: uniform grid size of the rate_sched propagator.
double fau_epsilon
options.config.fau_epsilon: total probability mass the whole grid may discard under "fau".
bool keep_filtration
Keep the per-synchronization EVENT FILTRATION alongside Q.
One entry of options.config.rate_sched: the rate of (station, class) follows the piecewise-linear sch...
std::vector< double > rates
std::vector< double > tgrid
The generator, the state space it is indexed by, and the event rates.
std::vector< NetState< T > > space
row i of Q is space[i]
Matrix< T > Q
(n x n) infinitesimal generator
Everything one CTMC solve produces.
std::string warning
Set when the chain is a reducible mixture solved from an invented seed.
std::vector< std::size_t > cutoff
the per-class cutoff actually used
solvers::CacheMetrics< T > cache
What the chain says about the model's Cache nodes: exact, since the hit and miss shares are read off ...
std::shared_ptr< const NetworkStruct< T > > solved_sn
The struct the chain was built on, when it differs from the caller's in its state LAYOUT: set when he...
std::vector< std::size_t > fjclassmap
fjclassmap when the model was fork-join, empty otherwise: the ORIGINAL class of each auxiliary siblin...
std::vector< T > pi
stationary distribution over chain.space
std::vector< T > pi
stationary distribution, length N
std::string warning
Empty unless the chain is an UNSEEDED reducible mixture.
What fes_aggregate returns.
The per-population tables the conditional sum is taken over.
std::vector< Matrix< T > > QN
Indexed by the 0-based linearized population state; each (M_sub x K).
std::vector< Matrix< T > > UN
Indexed by the 0-based linearized population state; each (M_sub x K).
Everything needed to map an FES result back onto the original model.
Matrix< T > isolatedDemands
(M_sub x K)
std::vector< std::size_t > subsetIndices
1-based, as given
std::vector< std::size_t > complementIndices
1-based
std::vector< bool > isolatedIsDelay
std::vector< int > isolatedServers
std::size_t fesNode
1-based node index of the FES in the new model
std::vector< int > cutoffs
bool has_map() const
True when the type carries a (D0,D1) pair of its own.
The gate verdict, plus the message the caller reports either way.
Options the estimator reads; only the cutoff matters.
double cutoff
< 0 or non-finite = not given, take the solver default
The metrics getAvg returns, after filtering.
Matrix< T > RN
response time, per visit
Matrix< T > UN
utilization
Matrix< T > WN
residence time, per job
std::string method
the method asked for
std::string actualmethod
the algorithm that ran
Matrix< T > QN
queue length
std::vector< T > CN
system response time per class
std::vector< T > XN
system throughput per class
solvers::CacheMetrics< T > cache
What the cache branches observed, EMPTY on a model with no Cache node and on every solver that does n...
Matrix< T > AN
arrival rate
One fork firing synchronization: sn.fjsync{k}.
The augmented struct and everything needed to read its results back.
std::vector< FjSync< T > > fjsync
std::vector< std::size_t > fjclassmap
fjclassmap[a-1] is the ORIGINAL class of auxiliary class a, 0 for originals.
One network state: the per-stateful-node local rows it is composed of.
The parameters of a Transition node: MATLAB sn.nodeparam{ind} for an SPN transition,...
Every Cache node of the model, in node order; empty on a model with none.