224 const std::size_t E = envObj.nstages();
231 std::vector<std::vector<double>> qfirst_prev(E), qfirst_curr(E);
233 for (it = 1; it <= opt.iter_max; ++it) {
234 for (std::size_t e = 0; e < E; ++e) analyze(e);
237 qfirst_prev = qfirst_curr;
238 qfirst_curr.assign(E, std::vector<double>());
239 for (std::size_t e = 0; e < E; ++e) {
240 qfirst_curr[e].reserve(M * K);
241 for (std::size_t i = 0; i < M; ++i)
242 for (std::size_t r = 0; r < K; ++r) qfirst_curr[e].push_back(tranQ[e][i][r][0]);
246 for (std::size_t e = 0; e < E && conv; ++e) {
247 const double d = detail::env_maxpe(qfirst_curr[e], qfirst_prev[e]);
248 if (d < 0.0)
continue;
249 if (!std::isfinite(d) || d >= opt.iter_tol) conv =
false;
264 if (
opt.stage_solver !=
"fluid" &&
opt.stage_solver !=
"ctmc")
266 "SolverENV: stage solver '" +
opt.stage_solver +
267 "' is not available; the environment coupling needs a TRANSIENT stage solve and "
268 "only the fluid analyzer and the enumerated CTMC provide one in this port");
269 ctmc_stages = (
opt.stage_solver ==
"ctmc");
270 if (
opt.method ==
"statevec" ||
opt.method ==
"blend")
272 "SolverENV: the state-vector analyzer (solver_env_statevec_analyzer.m) carries the "
273 "full joint distribution across a switch, which THIS class does not: it is the "
274 "mean-field coupling and carries the marginal means. The state-vector coupling is "
275 "ported as env::SolverEnvStatevec, with its own options and its CTMC stage solver; "
276 "reach it through env::solver_env (env_dispatch.h), which is the analyzer "
277 "selection of SolverENV.init");
278 if (
opt.method ==
"avg" ||
opt.method ==
"dec")
280 "SolverENV: the closed-form fast/slow environment limits (SolverENV.solveEnvLimit) "
281 "replace this fixed point rather than configure it -- neither carries anything "
282 "across a switch. They are ported as env::SolverEnvLimit, with their own result "
283 "type; reach them through env::solver_env (env_dispatch.h), which is the method "
284 "dispatch of SolverENV.runAnalyzer");
290 statedep = (
opt.method ==
"statedep");
296 if (
opt.method !=
"meanfield" &&
opt.method !=
"default" &&
opt.method !=
"smp"
299 if (!(
opt.timespan_end > 0.0) || !std::isfinite(
opt.timespan_end))
301 "SolverENV: the stage transient needs a finite positive horizon, "
302 "options.timespan(2); the mean-field coupling integrates each stage's "
303 "TRAJECTORY against the holding-time CDF and has nothing to integrate over an "
305 if (
opt.tran_points < 2)
306 throw InputError(
"SolverENV: the transient grid needs at least two points");
307 if (!std::is_same<T, double>::value)
309 "SolverENV: a fluid stage integrates its drift with LSODA, which is double only; "
310 "rerun with --arith double");
313 const std::size_t E = envObj.nstages();
317 for (std::size_t e = 1; e < E; ++e)
318 if (stage_M(e) != M || stage_K(e) != K)
320 "SolverENV: every stage must have the same stations and classes; the metrics "
321 "are blended entrywise across them. A LAYERED stage counts the block-diagonal "
322 "union of the layers SolverLN builds for it, which is what layerBlocks "
323 "reports, so two layered stages agree exactly when they have the same layer "
324 "shape -- not merely the same LQN element count");
326 tranT.assign(E, std::vector<double>());
327 tranQ.assign(E, std::vector<std::vector<std::vector<double>>>());
328 tranU.assign(E, std::vector<std::vector<std::vector<double>>>());
329 tranTp.assign(E, std::vector<std::vector<std::vector<double>>>());
330 det_sojourn = (
opt.sojourn ==
"deterministic");
331 dvals.assign(E, 0.0);
332 refresh_sojourn_means();
335 for (std::size_t e = 0; e < E; ++e)
336 for (std::size_t h = 0; h < E; ++h)
337 if (envObj.arc(e, h).enabled && envObj.arc(e, h).reset_rates) any =
true;
340 "SolverENV: method 'statedep' updates each environment transition from the "
341 "state its stage is left in, and no arc carries a rate reset "
342 "(Environment::set_env_rate_reset, resetEnvRatesFun in the reference); the "
343 "run would be the mean-field fixed point under a different name");
361 void build_lqn_stages() {
362 const std::size_t E = envObj.nstages();
363 lnsolv.assign(E, std::shared_ptr<ln::SolverLN<T>>());
364 lnblk.assign(E, ln::LnLayerBlocks());
365 if (!envObj.has_lqn_stages())
return;
368 "SolverENV: a LayeredNetwork stage is solved by SolverLN over its layers, not by "
369 "an enumerated CTMC over one stage generator -- an LQN has no single generator to "
370 "enumerate. Leave stage_solver at 'fluid', which names the engine each LAYER is "
371 "integrated with (EnvOptions::lqn.layer_solver)");
374 "SolverENV: a LayeredNetwork stage is carried across an environment switch by its "
375 "queue lengths, so the coupling needs the stage's TRANSIENT; among the layer "
376 "engines only the fluid one produces one, and this ensemble asks for '" +
378 for (std::size_t e = 0; e < E; ++e) {
379 if (!envObj.is_lqn(e))
continue;
380 ln::LnOptions lo = opt.
lqn;
390 lo.tran_grid.clear();
391 lnsolv[e] = std::make_shared<ln::SolverLN<T>>(envObj.stage(e).lqn_model, lo);
392 lnblk[e] = lnsolv[e]->layer_blocks();
397 std::size_t stage_M(std::size_t e)
const {
398 return envObj.is_lqn(e) ? lnblk[e].M : envObj.stage(e).model.nstations;
402 std::size_t stage_K(std::size_t e)
const {
403 return envObj.is_lqn(e) ? lnblk[e].K : envObj.stage(e).model.nclasses;
424 void lqn_stage_transient(std::size_t e,
bool seed) {
425 ln::SolverLN<T>& s = *lnsolv[e];
426 s.init_from_marginal(seed ? entry[e] : Matrix<double>());
427 s.set_tran_grid(stage_grid(e));
428 const ln::LnTranSolution tr = s.get_tran_avg();
429 const ln::LnLayerBlocks& b = lnblk[e];
434 if (!tr.layers.empty()) tranT[e] = tr.layers[0].t;
435 const std::size_t P = std::max<std::size_t>(tranT[e].size(), 1);
436 tranQ[e].assign(M, std::vector<std::vector<double>>(K, std::vector<double>(P, 0.0)));
437 tranU[e].assign(M, std::vector<std::vector<double>>(K, std::vector<double>(P, 0.0)));
438 tranTp[e].assign(M, std::vector<std::vector<double>>(K, std::vector<double>(P, 0.0)));
439 if (tranT[e].empty())
return;
445 for (std::size_t l = 0; l < tr.layers.size(); ++l) {
446 const ln::LnTranLayer& L = tr.layers[l];
447 for (std::size_t i = 0; i < b.msz[l]; ++i)
448 for (std::size_t r = 0; r < b.ksz[l]; ++r) {
449 const std::size_t row = b.roff[l] + i, col = b.coff[l] + r;
450 copy_series(L.QN[i][r], tranQ[e][row][col]);
451 copy_series(L.UN[i][r], tranU[e][row][col]);
452 copy_series(L.TN[i][r], tranTp[e][row][col]);
465 static void copy_series(
const std::vector<double>& src, std::vector<double>& dst) {
466 for (std::size_t j = 0; j < dst.size() && j < src.size(); ++j) dst[j] = src[j];
470 void refresh_sojourn_means() {
471 if (!det_sojourn)
return;
472 for (std::size_t e = 0; e < envObj.nstages(); ++e)
491 const std::size_t E = envObj.nstages();
492 for (std::size_t e = 0; e < E; ++e) {
493 if (envObj.is_lqn(e)) {
497 lqn_stage_transient(e,
false);
498 if (tranT[e].empty())
continue;
499 const std::size_t last = tranT[e].size() - 1;
500 for (std::size_t i = 0; i < M; ++i)
501 for (std::size_t r = 0; r < K; ++r) entry[e](i, r) = tranQ[e][i][r][last];
509 const ctmc::CtmcTransient<T> tr = ctmc_stage_transient(e,
false, std::vector<double>());
510 if (tr.t.empty())
continue;
511 for (std::size_t i = 0; i < M; ++i)
512 for (std::size_t r = 0; r < K; ++r)
513 entry[e](i, r) = num_traits<T>::to_double(tr.QNt[i][r].back());
516 fluid::FluidOptions fo = opt.stage;
519 envObj.stage(e).model, fo, opt.timespan_end, opt.tran_points);
520 if (tr.empty())
continue;
521 for (std::size_t i = 0; i < M; ++i)
522 for (std::size_t r = 0; r < K; ++r) entry[e](i, r) = tr.back().QN(i, r);
527 void analyze(std::size_t e) {
528 if (envObj.is_lqn(e)) {
529 lqn_stage_transient(e,
true);
533 tranQ[e].assign(M, std::vector<std::vector<double>>(K));
534 tranU[e].assign(M, std::vector<std::vector<double>>(K));
535 tranTp[e].assign(M, std::vector<std::vector<double>>(K));
546 const ctmc::CtmcTransient<T> tr = ctmc_stage_transient(e,
true, stage_grid(e));
547 for (std::size_t j = 0; j < tr.t.size(); ++j) {
548 tranT[e].push_back(num_traits<T>::to_double(tr.t[j]));
549 for (std::size_t i = 0; i < M; ++i)
550 for (std::size_t r = 0; r < K; ++r) {
551 tranQ[e][i][r].push_back(num_traits<T>::to_double(tr.QNt[i][r][j]));
552 tranU[e][i][r].push_back(num_traits<T>::to_double(tr.UNt[i][r][j]));
553 tranTp[e][i][r].push_back(num_traits<T>::to_double(tr.TNt[i][r][j]));
558 const qn::NetworkStruct<T>& sn = envObj.stage(e).model;
559 fluid::FluidOptions fo = opt.stage;
560 fo.init_sol = initsol_from_marginal(sn, entry[e]);
562 sn, fo, opt.timespan_end, opt.tran_points, stage_grid(e));
563 for (
const fluid::FluidTranPoint& p : tr) {
564 tranT[e].push_back(p.t);
565 for (std::size_t i = 0; i < M; ++i)
566 for (std::size_t r = 0; r < K; ++r) {
567 tranQ[e][i][r].push_back(p.QN(i, r));
568 tranU[e][i][r].push_back(p.UN(i, r));
569 tranTp[e][i][r].push_back(p.TN(i, r));
590 ctmc::CtmcTransient<T> ctmc_stage_transient(std::size_t e,
bool seed,
591 const std::vector<double>& grid)
const {
592 qn::NetworkStruct<T> sn = envObj.stage(e).model;
596 if (seed) seed_ctmc_state(sn, entry[e]);
598 g.reserve(grid.size());
599 for (std::size_t j = 0; j < grid.size(); ++j) g.push_back(num_traits<T>::from_double(grid[j]));
601 sn, ctmc_stage_options(), num_traits<T>::from_int(0),
602 num_traits<T>::from_double(opt.timespan_end), g);
606 ctmc::CtmcOptions ctmc_stage_options()
const {
607 ctmc::CtmcOptions co;
608 co.cutoff = opt.stage_cutoff;
620 void seed_ctmc_state(qn::NetworkStruct<T>& sn,
const Matrix<double>& Q)
const {
621 if (Q.empty())
return;
622 const std::vector<std::vector<std::size_t>> nir = round_marginal(sn, Q);
623 for (std::size_t i = 0; i < sn.nstations && i < nir.size(); ++i) {
624 const std::size_t ind = sn.station_to_node[i];
625 std::vector<std::size_t> ph(sn.nclasses, 1);
626 for (std::size_t r = 0; r < sn.nclasses; ++r) ph[r] = sn.phasessz_of(i + 1, r + 1);
630 Matrix<T> space(1, row.size());
631 for (std::size_t c = 0; c < row.size(); ++c) space(0, c) = row[c];
632 sn.statespace[ind] = space;
633 sn.stateprior[ind] = std::vector<T>(1, num_traits<T>::from_int(1));
646 std::vector<std::vector<std::size_t>> round_marginal(
const qn::NetworkStruct<T>& sn,
647 const Matrix<double>& Q)
const {
648 std::vector<std::vector<std::size_t>> n(sn.nstations, std::vector<std::size_t>(sn.nclasses, 0));
649 for (std::size_t r = 0; r < sn.nclasses; ++r) {
650 const double pop = sn.classes[r].population;
651 std::vector<double> frac(sn.nstations, 0.0);
653 for (std::size_t i = 0; i < sn.nstations; ++i) {
654 const double q = (i < Q.rows() && r < Q.cols()) ? std::max(0.0, Q(i, r)) : 0.0;
655 const double fl = std::floor(q);
656 n[i][r] =
static_cast<std::size_t
>(fl);
658 placed +=
static_cast<long>(fl);
660 if (!std::isfinite(pop))
continue;
661 long want =
static_cast<long>(std::llround(pop));
662 while (placed < want) {
663 std::size_t best = 0;
665 for (std::size_t i = 0; i < sn.nstations; ++i)
666 if (frac[i] > bv) { bv = frac[i]; best = i; }
672 while (placed > want) {
673 std::size_t best = 0;
676 for (std::size_t i = 0; i < sn.nstations; ++i)
677 if (n[i][r] > 0 && frac[i] < bv) { bv = frac[i]; best = i; any =
true; }
692 const std::size_t E = envObj.nstages();
693 std::vector<std::vector<Matrix<double>>> Qexit(
694 E, std::vector<Matrix<double>>(E, Matrix<double>(M, K, 0.0)));
698 std::vector<std::vector<Matrix<double>>> Uexit, Texit;
700 Uexit.assign(E, std::vector<Matrix<double>>(E, Matrix<double>(M, K, 0.0)));
701 Texit.assign(E, std::vector<Matrix<double>>(E, Matrix<double>(M, K, 0.0)));
703 for (std::size_t e = 0; e < E; ++e) {
704 if (tranT[e].empty())
continue;
706 Matrix<double> Qd(M, K, 0.0), Ud(M, K, 0.0), Td(M, K, 0.0);
707 for (std::size_t i = 0; i < M; ++i)
708 for (std::size_t r = 0; r < K; ++r) {
709 Qd(i, r) = detail::env_det_eval(tranT[e], tranQ[e][i][r], dvals[e]);
710 if (!statedep)
continue;
711 Ud(i, r) = detail::env_det_eval(tranT[e], tranU[e][i][r], dvals[e]);
712 Td(i, r) = detail::env_det_eval(tranT[e], tranTp[e][i][r], dvals[e]);
714 for (std::size_t h = 0; h < E; ++h) {
716 if (!statedep)
continue;
733 const std::vector<double> w = cdf_weights(envObj.hold_time[e].map(), tranT[e]);
735 for (
double v : w) wsum += v;
736 if (!(wsum > 0.0))
continue;
737 for (std::size_t i = 0; i < M; ++i)
738 for (std::size_t r = 0; r < K; ++r) {
739 const double q = weighted(tranQ[e][i][r], w, wsum);
740 double u = 0.0, t = 0.0;
742 u = weighted(tranU[e][i][r], w, wsum);
743 t = weighted(tranTp[e][i][r], w, wsum);
745 for (std::size_t h = 0; h < E; ++h) {
746 Qexit[e][h](i, r) = q;
747 if (!statedep)
continue;
748 Uexit[e][h](i, r) = u;
749 Texit[e][h](i, r) = t;
754 for (std::size_t e = 0; e < E; ++e) {
755 if (tranT[e].empty())
continue;
756 Matrix<double> Qe(M, K, 0.0);
757 for (std::size_t h = 0; h < E; ++h) {
758 const double p = envObj.prob_orig(h, e);
759 if (!(p > 0.0))
continue;
761 const Matrix<double> reset = f ? f(Qexit[h][e]) : Qexit[h][e];
762 if (reset.rows() != M || reset.cols() != K)
764 "SolverENV: a reset policy returned a matrix of the wrong shape");
765 for (std::size_t i = 0; i < M; ++i)
766 for (std::size_t r = 0; r < K; ++r) Qe(i, r) += p * reset(i, r);
776 if (!statedep)
return;
777 bool touched =
false;
778 for (std::size_t e = 0; e < E; ++e)
779 for (std::size_t h = 0; h < E; ++h) {
780 const EnvArc<T>& a = envObj.arc(e, h);
781 if (!a.enabled || !a.reset_rates)
continue;
782 envObj.set_transition_dist(e, h,
783 a.reset_rates(a.dist, Qexit[e][h], Uexit[e][h],
787 if (!touched)
return;
793 refresh_sojourn_means();
800 void finish(EnvSolution& out) {
801 const std::size_t E = envObj.nstages();
802 out.QExit.assign(E, Matrix<double>(M, K, 0.0));
803 out.UExit.assign(E, Matrix<double>(M, K, 0.0));
804 out.TExit.assign(E, Matrix<double>(M, K, 0.0));
805 for (std::size_t e = 0; e < E; ++e) {
806 if (tranT[e].empty())
continue;
808 for (std::size_t i = 0; i < M; ++i)
809 for (std::size_t r = 0; r < K; ++r) {
810 out.QExit[e](i, r) = detail::env_det_eval(tranT[e], tranQ[e][i][r], dvals[e]);
811 out.UExit[e](i, r) = detail::env_det_eval(tranT[e], tranU[e][i][r], dvals[e]);
812 out.TExit[e](i, r) = detail::env_det_eval(tranT[e], tranTp[e][i][r], dvals[e]);
816 const std::vector<double> w = cdf_weights(envObj.hold_time[e].map(), tranT[e]);
818 for (
double v : w) wsum += v;
819 if (!(wsum > 0.0))
continue;
820 for (std::size_t i = 0; i < M; ++i)
821 for (std::size_t r = 0; r < K; ++r) {
822 out.QExit[e](i, r) = weighted(tranQ[e][i][r], w, wsum);
823 out.UExit[e](i, r) = weighted(tranU[e][i][r], w, wsum);
824 out.TExit[e](i, r) = weighted(tranTp[e][i][r], w, wsum);
827 for (std::size_t e = 0; e < E; ++e) {
828 const double p = envObj.prob_env[e];
829 for (std::size_t i = 0; i < M; ++i)
830 for (std::size_t r = 0; r < K; ++r) {
831 out.QN(i, r) += p * out.QExit[e](i, r);
832 out.UN(i, r) += p * out.UExit[e](i, r);
833 out.TN(i, r) += p * out.TExit[e](i, r);
859 static constexpr std::size_t kCdfInterp = 5000;
861 static std::vector<double> refine_grid(
const mam::Map<double>& m,
862 const std::vector<double>& t) {
863 if (t.size() < 2)
return t;
868 bool all_zero =
true;
869 for (std::size_t a = 0; a < m.D1.rows() && all_zero; ++a)
870 for (std::size_t b = 0; b < m.D1.cols() && all_zero; ++b)
871 if (m.D1(a, b) != 0.0) all_zero =
false;
872 if (all_zero)
return t;
873 const double t0 = t.front();
874 const double tend = t.back();
876 if (!(mean_sojourn > 0.0) || !std::isfinite(mean_sojourn))
877 mean_sojourn = (tend - t0) / 10.0;
878 double tcdf = std::min(tend, 5.0 * mean_sojourn);
879 if (tcdf <= t0) tcdf = tend;
880 const std::size_t ndense =
static_cast<std::size_t
>(0.9 * kCdfInterp);
881 const std::size_t ntail = kCdfInterp - ndense;
882 const bool with_tail = tcdf < tend && ntail > 1;
883 std::vector<double> fine;
884 fine.reserve(with_tail ? ndense + ntail : ndense);
885 for (std::size_t k = 0; k < ndense; ++k)
886 fine.push_back(t0 + (tcdf - t0) *
static_cast<double>(k) /
887 static_cast<double>(ndense - 1));
889 for (std::size_t k = 1; k <= ntail; ++k)
890 fine.push_back(tcdf + (tend - tcdf) *
static_cast<double>(k) /
891 static_cast<double>(ntail));
910 std::vector<double> stage_grid(std::size_t e)
const {
911 std::vector<double> ends(2);
913 ends[1] = opt.timespan_end;
914 const std::vector<double> g = refine_grid(envObj.hold_time[e].map(), ends);
917 if (g.size() < 3)
return std::vector<double>();
922 std::vector<double> cdf_weights(
const mam::Map<double>& m,
const std::vector<double>& t)
const {
923 std::vector<double> w(t.size(), 0.0);
924 if (t.size() < 2)
return w;
926 bool all_zero =
true;
927 for (std::size_t a = 0; a < m.D1.rows() && all_zero; ++a)
928 for (std::size_t b = 0; b < m.D1.cols() && all_zero; ++b)
929 if (m.D1(a, b) != 0.0) all_zero =
false;
930 if (all_zero)
return w;
932 for (std::size_t j = 1; j < t.size(); ++j) w[j] = F[j] - F[j - 1];
936 static double weighted(
const std::vector<double>& v,
const std::vector<double>& w,
939 for (std::size_t j = 0; j < v.size() && j < w.size(); ++j) s += v[j] * w[j];
952 std::vector<double> initsol_from_marginal(
const qn::NetworkStruct<T>& sn,
953 const Matrix<double>& Q)
const {
955 std::vector<double> y(L.nstates, 0.0);
956 for (std::size_t i = 0; i < sn.nstations && i < Q.rows(); ++i)
957 for (std::size_t r = 0; r < sn.nclasses && r < Q.cols(); ++r) {
958 if (!L.enabled[i][r])
continue;
959 y[L.qidx[i][r]] = std::max(0.0, Q(i, r));
964 Environment<T>& envObj;
966 std::size_t M = 0, K = 0;
967 bool det_sojourn =
false;
968 bool statedep =
false;
969 bool ctmc_stages =
false;
970 std::vector<double> dvals;
971 std::vector<Matrix<double>> entry;
972 std::vector<std::vector<double>> tranT;
973 std::vector<std::vector<std::vector<std::vector<double>>>> tranQ, tranU, tranTp;
975 std::vector<std::shared_ptr<ln::SolverLN<T>>> lnsolv;
977 std::vector<ln::LnLayerBlocks> lnblk;