186 : envObj(e), opt(o), stage_fn_(stage_fn) {
191 if (occ_)
return solve_occupation();
192 return dec_ ? solve_dec() : solve_avg();
201 std::vector<double>& occ, std::vector<double>& w) {
202 const std::size_t E =
env.nstages();
204 const double inf = std::numeric_limits<double>::infinity();
208 if (std::isfinite(hz)) {
212 for (std::size_t e = 0; e < E; ++e) {
214 for (std::size_t h = 0; h < E; ++h) {
215 if (h == e)
continue;
226 for (std::size_t h = 0; h < E; ++h) {
228 for (std::size_t e = 0; e < E; ++e) s += pi0[e] * Z(e, E + h);
234 std::vector<char> absorb(E, 0);
236 for (std::size_t e = 0; e < E; ++e) {
237 absorb[e] = !(
env.rate[e] > 0.0);
238 any = any || absorb[e];
241 for (std::size_t e = 0; e < E; ++e) {
242 w[e] =
env.prob_env[e];
243 occ[e] = w[e] == 0.0 ? 0.0 : inf;
249 std::vector<std::size_t>
tr;
250 for (std::size_t e = 0; e < E; ++e)
251 if (!absorb[e])
tr.push_back(e);
252 const std::size_t nt =
tr.size();
254 for (std::size_t a = 0; a < nt; ++a)
255 for (std::size_t b = 0; b < nt; ++b)
256 N(a, b) = -
env.rate[
tr[a]] * (
env.pemb(
tr[a],
tr[b]) - (a == b ? 1.0 : 0.0));
261 const double n1 = norm1(N), ni = norm1(Ninv);
262 rc = (n1 > 0.0 && std::isfinite(ni)) ? 1.0 / (n1 * ni) : 0.0;
268 "SolverENV: method 'occupation' with timespan(2) = Inf needs every non-absorbing "
269 "stage to reach an absorbing one: some stages form a closed class. Set a finite "
270 "horizon, timespan [0,T].");
271 for (std::size_t b = 0; b < nt; ++b) {
273 for (std::size_t a = 0; a < nt; ++a) s += pi0[
tr[a]] * Ninv(a, b);
276 for (std::size_t h = 0; h < E; ++h) {
277 if (!absorb[h])
continue;
279 for (std::size_t a = 0; a < nt; ++a)
282 if (s > 0.0) occ[h] = inf;
293 const std::size_t E =
env.nstages();
295 for (std::size_t e = 0; e < E; ++e) {
296 if (
env.is_kernel_stage(e))
298 "Stage " + std::to_string(e + 1) +
" has kernel transitions, but method "
299 "'occupation' over a finite timespan needs exponential transitions to "
300 "integrate the stage law. Use timespan(2) = Inf.");
301 for (std::size_t h = 0; h < E; ++h) {
307 "The environment transition from stage " + std::to_string(e + 1) +
308 " to " + std::to_string(h + 1) +
" is a " +
310 ": method 'occupation' over a finite timespan needs exponential "
311 "transitions. Expand it into exponential stages, or use timespan(2) = "
319 for (
double v : pi0) {
321 if (!(v >= 0.0)) neg =
true;
324 throw InputError(
"options.config.env_init must be a probability vector over the " +
325 std::to_string(E) +
" stages of the environment.");
331 std::vector<double> pi0(E, 0.0);
340 return std::numeric_limits<double>::infinity();
347 for (std::size_t j = 0; j < A.
cols(); ++j) {
349 for (std::size_t i = 0; i < A.
rows(); ++i) s += std::fabs(A(i, j));
350 if (!std::isfinite(s))
return std::numeric_limits<double>::infinity();
358 throw UnsupportedError(ErrorKind{
"Method"},
"SolverENV limit: '" + opt.method +
359 "' is not a closed-form environment limit; the two are 'avg' "
360 "(fast environment) and 'dec' (slow environment), and "
361 "'occupation' is the occupation-weighted 'dec'");
362 dec_ = (opt.method ==
"dec");
363 occ_ = (opt.method ==
"occupation");
364 if (!stage_fn_ && opt.stage_solver !=
"fluid" && opt.stage_solver !=
"ctmc" &&
365 opt.stage_solver !=
"mva" && opt.stage_solver !=
"nc")
367 "SolverENV limit: stage solver '" + opt.stage_solver +
368 "' is not available; the limits solve each stage in STEADY STATE, by the fluid "
369 "analyzer, the enumerated CTMC, MVA or NC. Hand a stage solver to the "
370 "EnvStageAvgFn constructor to run another one");
371 if (!std::is_same<T, double>::value)
373 "SolverENV limit: a fluid stage integrates its drift with LSODA, which is double "
374 "only; rerun with --arith double");
382 if (!occ_) envObj.reject_lqn_stages(
384 "the fast/slow limits read a stage's station rates directly -- 'dec' solves each "
385 "stage network in steady state and 'avg' builds one network at the probEnv-weighted "
386 "rates -- and a layered model has no such rate table, only the layers SolverLN "
391 envObj.refuse_semi_markov(opt.method);
392 envObj.refuse_absorbing(opt.method);
395 const std::size_t E = envObj.nstages();
396 lqn_ = envObj.has_lqn_stages();
398 for (std::size_t e = 0; e < E; ++e)
399 if (!envObj.is_lqn(e))
401 "SolverENV occupation: stage " + std::to_string(e + 1) +
" is a Network and "
402 "another stage a LayeredNetwork; the metrics are blended entrywise, so "
403 "every stage must be read in one layout");
404 M = envObj.stage(0).lqn_model.nidx;
408 M = envObj.stage(0).model.nstations;
409 K = envObj.stage(0).model.nclasses;
410 for (std::size_t e = 1; e < E; ++e)
411 if (envObj.stage(e).model.nstations != M || envObj.stage(e).model.nclasses != K)
413 "SolverENV limit: every stage must have the same stations and classes; the "
414 "metrics are blended entrywise across them");
430 const std::vector<double> arv_all = cache_arrival(
sn, stage.
TN);
431 for (std::size_t c = 0; c < stage.
cache.caches.size(); ++c) {
433 Entry& en = entry(cm);
435 std::vector<double> arv(std::max(K, en.arv.size()), 0.0);
436 for (std::size_t r = 0; r < arv.size(); ++r) {
437 const std::size_t ind = cm.
node;
438 const std::size_t off = (ind >= 1 ? (ind - 1) *
sn.nclasses + r : arv_all.size());
439 if (off < arv_all.size()) arv[r] = w * arv_all[off];
441 grow(en.arv, arv.size());
442 for (std::size_t r = 0; r < arv.size(); ++r) en.arv[r] += arv[r];
443 accum(en.hit, cm.
hitprob, arv);
453 for (std::size_t c = 0; c < order_.size(); ++c) {
454 const Entry& en = order_[c];
460 cm.
hitprob = divide(en.hit, en.arv);
461 cm.
missprob = divide(en.miss, en.arv);
463 if (en.hitl.
rows() > 0) {
465 for (std::size_t r = 0; r < en.hitl.
rows(); ++r) {
466 const double d = r < en.arv.size() ? en.arv[r] : 0.0;
467 for (std::size_t l = 0; l < en.hitl.
cols(); ++l)
468 cm.
hitproblist(r, l) = d > 0.0 ? en.hitl(r, l) / d : nan_();
478 std::size_t node = 0;
480 std::vector<double> itemcap;
481 std::size_t nitems = 0;
482 std::vector<double> arv;
484 std::vector<double> hit, miss, dhit;
488 static double nan_() {
return std::numeric_limits<double>::quiet_NaN(); }
490 static void grow(std::vector<double>& v, std::size_t n) {
491 if (v.size() < n) v.resize(n, 0.0);
494 Entry& entry(
const solvers::CacheNodeMetrics<T>& cm) {
495 for (std::size_t i = 0; i < order_.size(); ++i)
496 if (order_[i].name == cm.name)
return order_[i];
500 en.itemcap = cm.itemcap;
501 en.nitems = cm.nitems;
502 order_.push_back(en);
503 return order_.back();
507 static void accum(std::vector<double>& a,
const std::vector<T>& ratio,
508 const std::vector<double>& arv) {
509 if (ratio.empty())
return;
510 grow(a, ratio.size());
511 for (std::size_t r = 0; r < ratio.size() && r < arv.size(); ++r) {
512 const double v = num_traits<T>::to_double(ratio[r]);
513 if (std::isfinite(v)) a[r] += arv[r] * v;
517 static void accum_rows(Matrix<double>& a,
const Matrix<T>& hl,
518 const std::vector<double>& arv) {
519 if (hl.rows() == 0 || hl.cols() == 0)
return;
520 if (a.rows() == 0) a = Matrix<double>(hl.rows(), hl.cols(), 0.0);
521 for (std::size_t r = 0; r < hl.rows() && r < a.rows(); ++r) {
522 const double w = r < arv.size() ? arv[r] : 0.0;
523 for (std::size_t l = 0; l < hl.cols() && l < a.cols(); ++l) {
524 const double v = num_traits<T>::to_double(hl(r, l));
525 if (std::isfinite(v)) a(r, l) += w * v;
534 static std::vector<double> divide(
const std::vector<double>& a,
535 const std::vector<double>& d) {
536 std::vector<double> r;
537 if (a.empty())
return r;
538 r.assign(a.size(), nan_());
539 for (std::size_t i = 0; i < a.size(); ++i)
540 if (i < d.size() && d[i] > 0.0) r[i] = a[i] / d[i];
553 static std::vector<double> cache_arrival(
const qn::NetworkStruct<T>& sn,
554 const Matrix<double>& TN) {
555 const std::size_t I = sn.nodes.size(), R = sn.nclasses, M = sn.nstations;
556 std::vector<double> out(I * R, 0.0);
557 if (TN.rows() == 0)
return out;
558 Matrix<T> TNt(TN.rows(), TN.cols(), num_traits<T>::from_int(0));
559 for (std::size_t i = 0; i < TN.rows(); ++i)
560 for (std::size_t r = 0; r < TN.cols(); ++r)
561 TNt(i, r) = num_traits<T>::from_double(TN(i, r));
562 const Matrix<T> AN(M, R, num_traits<T>::from_int(0));
564 if (ANn.rows() != I)
return out;
565 for (std::size_t ind = 0; ind < I; ++ind)
566 for (std::size_t r = 0; r < R; ++r) {
567 const double v = num_traits<T>::to_double(ANn(ind, r));
568 out[ind * R + r] = std::isfinite(v) ? v : 0.0;
573 std::vector<Entry> order_;
585 out.
QN = to_double(r.
QN);
586 out.
UN = to_double(r.
UN);
587 out.
TN = to_double(r.
TN);
596 for (std::size_t i = 0; i < A.
rows(); ++i)
602 EnvLimitSolution solve_dec() {
603 const std::size_t E = envObj.nstages();
604 EnvLimitSolution out;
606 out.prob_env = envObj.prob_env;
614 for (std::size_t e = 0; e < E; ++e) {
615 const EnvStageAvg<T> s = stage_steady_state(envObj.stage(e).model);
616 const double p = envObj.prob_env[e];
617 acc.add(envObj.stage(e).model, s, p);
618 for (std::size_t i = 0; i < M; ++i)
619 for (std::size_t r = 0; r < K; ++r) {
620 out.QStage[e](i, r) = s.QN(i, r);
621 out.UStage[e](i, r) = s.UN(i, r);
622 out.TStage[e](i, r) = s.TN(i, r);
623 out.QN(i, r) += p * s.QN(i, r);
624 out.UN(i, r) += p * s.UN(i, r);
625 out.TN(i, r) += p * s.TN(i, r);
628 out.cache = acc.finish();
637 EnvLimitSolution solve_occupation() {
638 const std::size_t E = envObj.nstages();
639 EnvLimitSolution out;
640 out.method =
"occupation";
642 out.prob_env = out.occupation_weight;
643 out.QN = Matrix<double>(M, K, 0.0);
644 out.UN = Matrix<double>(M, K, 0.0);
645 out.TN = Matrix<double>(M, K, 0.0);
646 out.QStage.assign(E, Matrix<double>(M, K, 0.0));
647 out.UStage.assign(E, Matrix<double>(M, K, 0.0));
648 out.TStage.assign(E, Matrix<double>(M, K, 0.0));
649 for (std::size_t e = 0; e < E; ++e) {
650 const double w = out.occupation_weight[e];
651 if (w == 0.0)
continue;
652 const EnvStageAvg<T> s = lqn_ ? lqn_stage_steady_state(e) : stage_steady_state(envObj.stage(e).model);
653 for (std::size_t i = 0; i < M; ++i)
654 for (std::size_t r = 0; r < K; ++r) {
655 out.QStage[e](i, r) = s.QN(i, r);
656 out.UStage[e](i, r) = s.UN(i, r);
657 out.TStage[e](i, r) = s.TN(i, r);
658 if (std::isfinite(s.QN(i, r))) out.QN(i, r) += w * s.QN(i, r);
659 if (std::isfinite(s.UN(i, r))) out.UN(i, r) += w * s.UN(i, r);
660 if (std::isfinite(s.TN(i, r))) out.TN(i, r) += w * s.TN(i, r);
672 EnvStageAvg<T> lqn_stage_steady_state(std::size_t e)
const {
673 const lqn::LqnStruct<T>& model = envObj.stage(e).lqn_model;
676 "SolverENV occupation: stage " + std::to_string(e + 1) +
" has " +
677 std::to_string(model.nidx) +
" LQN elements where stage 1 has " + std::to_string(M) +
678 "; the layered stages are blended element by element, so they must have the same "
679 "hosts, tasks, entries and activities");
680 ln::SolverLN<T> s(model, opt.lqn);
681 const ln::LnSolution<T> r = s.get_ensemble_avg();
682 const double nan = std::numeric_limits<double>::quiet_NaN();
684 out.QN = Matrix<double>(M, 1, nan);
685 out.UN = Matrix<double>(M, 1, nan);
686 out.TN = Matrix<double>(M, 1, nan);
687 for (std::size_t i = 1; i <= M; ++i) {
688 if (r.defined_Q[i]) out.QN(i - 1, 0) = num_traits<T>::to_double(r.QN[i]);
689 if (r.defined_U[i]) out.UN(i - 1, 0) = num_traits<T>::to_double(r.UN[i]);
690 if (r.defined_T[i]) out.TN(i - 1, 0) = num_traits<T>::to_double(r.TN[i]);
696 EnvLimitSolution solve_avg() {
697 EnvLimitSolution out;
699 out.prob_env = envObj.prob_env;
700 const qn::NetworkStruct<T> avg = rate_averaged_model();
701 const EnvStageAvg<T> s = stage_steady_state(avg);
705 acc.
add(avg, s, 1.0);
706 out.cache = acc.finish();
707 out.QN = Matrix<double>(M, K, 0.0);
708 out.UN = Matrix<double>(M, K, 0.0);
709 out.TN = Matrix<double>(M, K, 0.0);
710 for (std::size_t i = 0; i < M; ++i)
711 for (std::size_t r = 0; r < K; ++r) {
712 out.QN(i, r) = s.QN(i, r);
713 out.UN(i, r) = s.UN(i, r);
714 out.TN(i, r) = s.TN(i, r);
743 EnvStageAvg<T> stage_steady_state(
const qn::NetworkStruct<T>& sn)
const {
744 if (stage_fn_)
return stage_fn_(sn);
746 if (opt.stage_solver ==
"mva" || opt.stage_solver ==
"nc") {
749 const mva::AvgResult<T> r =
750 opt.stage_solver ==
"mva"
754 out.QN = to_double(r.QN);
755 out.UN = to_double(r.UN);
756 out.TN = to_double(r.TN);
760 fluid::FluidOptions fo = opt.stage;
765 const fluid::FluidSolution s =
766 fluid::detail::fluid_has_cache(sn)
783 qn::NetworkStruct<T> rate_averaged_model()
const {
784 const std::size_t E = envObj.nstages();
785 qn::NetworkStruct<T> sn = envObj.stage(0).model;
786 std::vector<double> r(E, 0.0);
787 for (std::size_t i = 0; i < M; ++i) {
792 for (std::size_t k = 0; k < K; ++k) {
794 for (std::size_t e = 0; e < E && ok; ++e) {
795 const qn::NetworkStruct<T>& se = envObj.stage(e).model;
799 if (se.disabled[i][k]) {
803 r[e] = num_traits<T>::to_double(se.rates(i, k));
804 if (!(r[e] > 0.0) || !std::isfinite(r[e])) ok =
false;
807 double lo = r[0], hi = r[0], avg = 0.0;
808 for (std::size_t e = 0; e < E; ++e) {
809 lo = std::min(lo, r[e]);
810 hi = std::max(hi, r[e]);
811 avg += envObj.prob_env[e] * r[e];
815 if (hi - lo <= 1e-12 * std::max(1.0, hi))
continue;
816 sn.set_service(i + 1, k + 1,
824 Environment<T>& envObj;
827 std::size_t M = 0, K = 0;