214 const std::size_t E = envObj.nstages();
219 for (it = 1; it <= opt.iter_max; ++it) {
220 for (std::size_t e = 0; e < E; ++e) analyze(e);
234 mam_backend = (
opt.stage_solver ==
"mam");
235 if (!mam_backend &&
opt.stage_solver !=
"ctmc")
237 "SolverENV statevec: stage solver '" +
opt.stage_solver +
238 "' is not available; the state-vector coupling needs an EXPLICIT generator over a "
239 "state space it can propagate a distribution across, which SolverCTMC exposes by "
240 "enumeration and SolverMAM by flattening its level-dependent QBD blocks");
241 if (
opt.sojourn !=
"stochastic" &&
opt.sojourn !=
"deterministic")
242 throw InputError(
"SolverENV statevec: unknown sojourn '" +
opt.sojourn +
"'");
250 envObj.reject_lqn_stages(
251 "SolverENV statevec",
252 "the state-vector coupling propagates a joint distribution over ONE stage "
253 "generator, which a layered model does not have -- it decomposes into a network "
255 envObj.refuse_semi_markov(
"statevec");
256 envObj.refuse_absorbing(
"statevec");
258 const std::size_t E = envObj.nstages();
259 M = envObj.stage(0).model.nstations;
260 K = envObj.stage(0).model.nclasses;
261 for (std::size_t e = 1; e < E; ++e)
262 if (envObj.stage(e).model.nstations != M || envObj.stage(e).model.nclasses != K)
264 "SolverENV statevec: every stage must have the same stations and classes; the "
265 "metrics are blended entrywise across them");
269 tspan_end.assign(E,
opt.timespan_end);
270 for (std::size_t e = 0; e < E; ++e) {
271 if (e <
opt.stage_timespan_end.size()) tspan_end[e] =
opt.stage_timespan_end[e];
272 if (!std::isfinite(tspan_end[e]) || !(tspan_end[e] >
opt.timespan_start))
274 "SolverENV statevec: the statevec analyzer requires a finite inner-solver "
275 "timespan for stage " +
276 std::to_string(e + 1) +
", e.g. CTMC(model,'timespan',[0,T])");
279 det_sojourn = (opt.sojourn ==
"deterministic");
280 stages.assign(E, ctmc::CtmcSolution<T>());
281 mam_ld.assign(E, mam::LdqbdBlocks<T>());
282 mam_flat.assign(E, mam::LdqbdFlat<T>());
283 pi_enter.assign(E, std::vector<T>());
284 pi_enter_prev.assign(E, std::vector<T>());
285 pi_exit.assign(E, std::vector<std::vector<T>>());
286 pi_timeavg.assign(E, std::vector<T>());
287 dvals.assign(E, 0.0);
289 for (std::size_t e = 0; e < E; ++e)
290 dvals[e] = std::max(envObj.hold_time_mean(e),
291 std::numeric_limits<double>::epsilon());
307 const std::size_t E = envObj.nstages();
308 for (std::size_t e = 0; e < E; ++e) {
316 mo.method =
"default";
317 mo.cutoff = opt.mam_cutoff;
324 seed_entry_distributions();
325 pi_enter_prev = pi_enter;
350 void seed_entry_distributions() {
351 const std::size_t E = envObj.nstages();
360 std::vector<std::vector<T>> own(E);
361 for (std::size_t e = 0; e < E; ++e) {
365 : normalized(stages[e].pi);
368 bool sameSpace = E > 1;
369 for (std::size_t e = 1; e < E && sameSpace; ++e)
370 sameSpace = (state_count(e) == state_count(0));
372 std::vector<T> shared;
374 std::vector<double> w(E, 1.0 /
static_cast<double>(E));
376 bool usable = envObj.prob_env.size() == E;
377 for (std::size_t e = 0; e < E && usable; ++e) {
378 if (!std::isfinite(envObj.prob_env[e]) || envObj.prob_env[e] < 0.0) usable =
false;
379 else wsum += envObj.prob_env[e];
382 if (usable && wsum > 0.0)
383 for (std::size_t e = 0; e < E; ++e) w[e] = envObj.prob_env[e] / wsum;
385 const std::size_t n = state_count(0);
386 Matrix<T> Qbar(n, n, num_traits<T>::from_double(0.0));
387 for (std::size_t e = 0; e < E; ++e) {
388 const Matrix<T>& Qe = gen(e);
389 const T we = num_traits<T>::from_double(w[e]);
390 for (std::size_t i = 0; i < n; ++i)
391 for (std::size_t j = 0; j < n; ++j) Qbar(i, j) += we * Qe(i, j);
398 std::vector<T> pi0(n, num_traits<T>::from_double(0.0));
399 for (std::size_t e = 0; e < E; ++e) {
400 const T we = num_traits<T>::from_double(w[e]);
401 for (std::size_t i = 0; i < n; ++i) pi0[i] += we * own[e][i];
406 for (std::size_t e = 0; e < E; ++e) {
407 pi_enter[e] = shared.empty() ? own[e] : shared;
412 const Matrix<T>& gen(std::size_t e)
const {
413 return mam_backend ? mam_flat[e].Q : stages[e].chain.Q;
417 std::size_t state_count(std::size_t e)
const {
418 return mam_backend ? mam_flat[e].levelOf.size() : stages[e].chain.space.size();
425 void analyze(std::size_t e) {
426 const std::size_t E = envObj.nstages();
427 const Matrix<T>& Q = gen(e);
428 const std::vector<T> pi0 = pi_enter[e];
436 std::vector<T> avg, ex;
437 time_average(pi0, Q, dvals[e], avg, ex);
438 pi_exit[e].assign(E, std::vector<T>());
439 for (std::size_t h = 0; h < E; ++h)
440 if (envObj.arc(e, h).enabled) pi_exit[e][h] = ex;
446 if (exp_sojourn(e, s_e)) {
451 const std::vector<T> res = resolvent(pi0, Q, s_e);
452 pi_exit[e].assign(E, std::vector<T>());
453 for (std::size_t h = 0; h < E; ++h)
454 if (envObj.arc(e, h).enabled) pi_exit[e][h] = res;
461 if constexpr (!num_traits<T>::has_transcendental) {
463 "SolverENV statevec: a non-exponential environment sojourn needs the transient "
464 "pi0 exp(Qt) from ctmc_transient, which is an adaptive approximation governed by "
465 "a tolerance and has no exact value in rational arithmetic; rerun with "
466 "--arith double, or use exponential transitions, whose resolvent is exact");
468 const mc::TransientResult<T> tr =
470 num_traits<T>::from_double(tspan_end[e]));
471 std::vector<double> t(tr.t.size());
472 for (std::size_t j = 0; j < tr.t.size(); ++j) t[j] = num_traits<T>::to_double(tr.t[j]);
474 pi_exit[e].assign(E, std::vector<T>());
475 for (std::size_t h = 0; h < E; ++h)
476 pi_exit[e][h] = stieltjes(tr.pi, cdf_weights(envObj.proc[e][h].map(), t));
478 const std::vector<T> avg =
479 stieltjes(tr.pi, cdf_weights(envObj.hold_time[e].map(), t));
485 const std::size_t last = tr.pi.rows() - 1, n = tr.pi.cols();
486 std::vector<T> tail(n);
487 for (std::size_t j = 0; j < n; ++j) tail[j] = tr.pi(last, j);
488 pi_timeavg[e] = tail;
498 const std::size_t E = envObj.nstages();
499 pi_enter_prev = pi_enter;
500 std::vector<std::vector<T>> next(E);
502 for (std::size_t e = 0; e < E; ++e) {
503 const std::size_t n = state_count(e);
504 std::vector<T> acc(n, num_traits<T>::from_int(0));
506 for (std::size_t h = 0; h < E; ++h) {
507 const double po = envObj.prob_orig(h, e);
508 if (!(po > 0.0))
continue;
509 if (pi_exit[h].size() <= e || pi_exit[h][e].empty())
continue;
510 const std::vector<T> pex = reset_apply(h, e, pi_exit[h][e]);
513 "SolverENV statevec: reset_state[" + std::to_string(h) +
"][" +
514 std::to_string(e) +
"] returned a " + std::to_string(pex.size()) +
515 "-element vector but stage " + std::to_string(e + 1) +
" has " +
517 " states; supply a reset that maps the state space of stage " +
518 std::to_string(h + 1) +
" onto that of stage " + std::to_string(e + 1));
519 const T w = num_traits<T>::from_double(po);
520 for (std::size_t s = 0; s < n; ++s) acc[s] += T(w * pex[s]);
524 const T w = num_traits<T>::from_double(wsum);
525 for (std::size_t s = 0; s < n; ++s) acc[s] = T(acc[s] / w);
529 next[e] = normalized(acc);
539 bool converged()
const {
540 const std::size_t E = envObj.nstages();
542 for (std::size_t e = 0; e < E; ++e) {
543 const std::vector<T>& a = pi_enter[e];
544 const std::vector<T>& b = pi_enter_prev[e];
545 if (a.empty() || b.empty() || a.size() != b.size())
return false;
547 for (std::size_t s = 0; s < a.size(); ++s)
548 d += std::fabs(num_traits<T>::to_double(a[s]) - num_traits<T>::to_double(b[s]));
549 l1 = std::max(l1, d);
551 if (!std::isfinite(l1))
return false;
552 return l1 < opt.iter_tol;
559 void finish(EnvStatevecSolution<T>& out) {
560 const std::size_t E = envObj.nstages();
561 const T zero = num_traits<T>::from_int(0);
565 out.QStage.assign(E, Matrix<T>(M, K, zero));
566 out.UStage.assign(E, Matrix<T>(M, K, zero));
567 out.TStage.assign(E, Matrix<T>(M, K, zero));
569 for (std::size_t e = 0; e < E; ++e) {
570 if (pi_timeavg[e].empty())
continue;
571 Matrix<T> QNe, UNe, TNe;
575 const mam::LdqbdAvg<T> a =
585 envObj.stage(e).model, stages[e].chain, pi_timeavg[e],
false);
593 const T p = num_traits<T>::from_double(envObj.prob_env[e]);
594 for (std::size_t i = 0; i < M; ++i)
595 for (std::size_t r = 0; r < K && r < QNe.cols(); ++r) {
596 out.QN(i, r) += T(p * QNe(i, r));
597 out.UN(i, r) += T(p * UNe(i, r));
598 out.TN(i, r) += T(p * TNe(i, r));
601 out.pi_enter = pi_enter;
602 out.pi_timeavg = pi_timeavg;
615 void cache_blend(EnvStatevecSolution<T>& out)
const {
620 if (mam_backend)
return;
621 const std::size_t E = envObj.nstages();
622 const qn::NetworkStruct<T>& sn1 = envObj.stage(0).model;
623 const T zero = num_traits<T>::from_int(0);
626 std::vector<T>(), std::vector<T>(), Matrix<T>(),
627 Matrix<T>(), std::vector<T>());
628 for (std::size_t c = 0; c < out.cache.caches.size(); ++c) {
629 const std::size_t ind = out.cache.caches[c].node;
630 const std::size_t isf = sn1.stateful_index(ind);
631 if (isf == 0)
continue;
632 std::vector<T> hitT(K, zero), missT(K, zero), dhitT(K, zero);
633 bool any_delayed =
false;
634 for (std::size_t e = 0; e < E; ++e) {
635 if (pi_timeavg[e].empty())
continue;
636 const std::vector<T> pv = normalized(pi_timeavg[e]);
637 const qn::NetworkStruct<T>& sne = envObj.stage(e).model;
638 const auto it = sne.nodeparam.find(ind);
639 if (it == sne.nodeparam.end())
continue;
640 const qn::CacheParam<T>& np = it->second;
641 const T w = num_traits<T>::from_double(envObj.prob_env[e]);
642 const auto& dr = stages[e].chain.dep_rates;
645 const std::vector<double> drate =
646 ctmc::analyzer_detail::cache_delayed_hit_rate(sne, ind, stages[e].chain, pv);
647 for (std::size_t k = 0; k < K && k < np.hitclass.size(); ++k) {
648 const std::size_t hc = np.hitclass[k];
649 const std::size_t mc = k < np.missclass.size() ? np.missclass[k] : 0;
650 if (hc == 0 || mc == 0)
continue;
651 T hit_rate = zero, miss_rate = zero;
652 for (std::size_t s = 0; s < pv.size(); ++s) {
653 hit_rate += T(pv[s] * dr[s][isf - 1][hc - 1]);
654 miss_rate += T(pv[s] * dr[s][isf - 1][mc - 1]);
657 if (k < drate.size())
658 dk = num_traits<T>::from_double(
659 std::min(drate[k], num_traits<T>::to_double(hit_rate)));
660 if (num_traits<T>::to_double(dk) > 0) any_delayed =
true;
661 hitT[k] += T(w * T(hit_rate - dk));
662 dhitT[k] += T(w * dk);
663 missT[k] += T(w * miss_rate);
666 const T nan = num_traits<T>::from_double(std::numeric_limits<double>::quiet_NaN());
667 std::vector<T> hp(K, nan), mp(K, nan), dp(K, nan);
668 for (std::size_t k = 0; k < K; ++k) {
669 const T
tot = T(hitT[k] + missT[k] + dhitT[k]);
670 if (num_traits<T>::to_double(tot) > 0) {
671 hp[k] = T(hitT[k] / tot);
672 mp[k] = T(missT[k] / tot);
673 dp[k] = T(dhitT[k] / tot);
676 out.cache.caches[c].hitprob = hp;
677 out.cache.caches[c].missprob = mp;
678 if (any_delayed) out.cache.caches[c].delayedprob = dp;
685 bool exp_sojourn(std::size_t e,
double& s)
const {
686 const std::size_t E = envObj.nstages();
689 for (std::size_t h = 0; h < E; ++h) {
690 const EnvArc<T>& a = envObj.arc(e, h);
691 if (!a.enabled)
continue;
693 s += num_traits<T>::to_double(a.dist.D1(0, 0));
696 return any && s > 0.0;
700 static std::vector<T> resolvent(
const std::vector<T>& pi0,
const Matrix<T>& Q,
double s) {
701 const std::size_t n = Q.rows();
702 const T sT = num_traits<T>::from_double(s);
703 Matrix<T> A(n, n, num_traits<T>::from_int(0));
704 for (std::size_t i = 0; i < n; ++i)
705 for (std::size_t j = 0; j < n; ++j)
706 A(j, i) = T((i == j ? sT : num_traits<T>::from_int(0)) - Q(i, j));
708 for (std::size_t i = 0; i < n; ++i) b[i] = T(sT * pi0[i]);
713 static void time_average(
const std::vector<T>& pi0,
const Matrix<T>& Q,
double d,
714 std::vector<T>& avg, std::vector<T>& ex) {
715 if constexpr (!num_traits<T>::has_transcendental) {
717 "SolverENV statevec: a deterministic sojourn needs ctmc_timeaverage, whose "
718 "uniformization carries Poisson weights exp(-qt) that are not rational; rerun "
719 "with --arith double");
721 const mc::TimeAverageResult<T> r =
736 static std::vector<double> cdf_weights(
const mam::Map<double>& m,
737 const std::vector<double>& t) {
738 std::vector<double> w(t.size(), 0.0);
739 if (t.size() < 2)
return w;
741 bool all_zero =
true;
742 for (std::size_t a = 0; a < m.D1.rows() && all_zero; ++a)
743 for (std::size_t b = 0; b < m.D1.cols() && all_zero; ++b)
744 if (m.D1(a, b) != 0.0) all_zero =
false;
745 if (all_zero)
return w;
747 for (std::size_t j = 1; j < t.size(); ++j) w[j] = F[j] - F[j - 1];
752 static std::vector<T> stieltjes(
const Matrix<T>& pit,
const std::vector<double>& w) {
755 if (std::isnan(v))
return std::vector<T>();
758 if (!(sw > 0.0))
return std::vector<T>();
759 const std::size_t n = pit.cols();
760 std::vector<T> out(n, num_traits<T>::from_int(0));
761 for (std::size_t j = 0; j < w.size() && j < pit.rows(); ++j) {
762 if (w[j] == 0.0)
continue;
763 const T wj = num_traits<T>::from_double(w[j] / sw);
764 for (std::size_t s = 0; s < n; ++s) out[s] += T(wj * pit(j, s));
771 std::vector<T> reset_apply(std::size_t h, std::size_t e,
const std::vector<T>& p)
const {
772 if (h < opt.reset_state.size() && e < opt.reset_state[h].size() && opt.reset_state[h][e])
773 return opt.reset_state[h][e](p);
778 static std::vector<T> normalized(
const std::vector<T>& p) {
779 const T zero = num_traits<T>::from_int(0);
780 std::vector<T> q = p;
782 for (std::size_t s = 0; s < q.size(); ++s) {
783 if (num_traits<T>::to_double(q[s]) < 0) q[s] = zero;
786 if (num_traits<T>::to_double(tot) > 0)
787 for (std::size_t s = 0; s < q.size(); ++s) q[s] = T(q[s] / tot);
791 Environment<T>& envObj;
792 EnvStatevecOptions<T> opt;
793 std::size_t M = 0, K = 0;
794 bool det_sojourn =
false;
795 std::vector<double> dvals, tspan_end;
796 std::vector<ctmc::CtmcSolution<T>> stages;
797 bool mam_backend =
false;
798 std::vector<mam::LdqbdBlocks<T>> mam_ld;
799 std::vector<mam::LdqbdFlat<T>> mam_flat;
800 std::vector<std::vector<T>> pi_enter, pi_enter_prev, pi_timeavg;
801 std::vector<std::vector<std::vector<T>>> pi_exit;