5#ifndef LINE_SOLVERS_FLUID_SOLVER_FLUID_H
6#define LINE_SOLVERS_FLUID_SOLVER_FLUID_H
272 std::vector<std::pair<std::size_t, std::size_t> >
nhpp_sched;
406 const std::size_t M =
sn.nstations, K =
sn.nclasses;
407 const double inf = std::numeric_limits<double>::infinity();
408 std::vector<std::vector<double>> nplace(M, std::vector<double>(K, 0.0));
409 std::vector<double> totplace(M, 0.0);
412 const bool has_cap =
sn.cap.size() == M &&
sn.classcap.size() == M;
414 for (std::size_t r = 0; r < K; ++r) {
415 const double pop =
sn.classes[r].population;
416 if (!std::isfinite(pop))
continue;
419 std::vector<std::size_t> order;
420 const std::size_t rs =
sn.classes[r].refstat;
421 if (rs >= 1 && rs <= M) order.push_back(rs - 1);
422 for (std::size_t i = 0; i < M; ++i)
423 if (order.empty() || i != order[0]) order.push_back(i);
431 nplace[rs - 1][r] = pop;
432 totplace[rs - 1] += pop;
436 double remaining = pop;
437 for (std::size_t oi = 0; oi < order.size() && remaining > 0.0; ++oi) {
438 const std::size_t j = order[oi];
447 if (!L.
enabled[j][r])
continue;
448 const double ccap = (has_cap &&
sn.classcap[j].size() > r) ?
sn.classcap[j][r] : inf;
449 const double scap = has_cap ?
sn.cap[j] : inf;
450 const double avail = std::min(ccap - nplace[j][r], scap - totplace[j]);
451 const double take = std::min(remaining, std::max(0.0, avail));
452 nplace[j][r] += take;
457 throw InputError(
"solver_fluid_initsol: cannot place the population of class '" +
459 "': the total capacity of the stations that serve it is insufficient");
510 const std::vector<std::vector<double>>& nplace,
511 std::vector<double>& y0) {
512 const std::size_t M =
sn.nstations, K =
sn.nclasses;
514 std::vector<double> out(L.nstates, 0.0);
515 for (std::size_t i = 0; i < M; ++i) {
516 const std::size_t ind =
sn.node_of_station(i + 1);
518 const typename std::map<std::size_t, Matrix<T>>::const_iterator ss =
519 sn.statespace.find(ind);
520 const typename std::map<std::size_t, std::vector<T>>::const_iterator sp =
521 sn.stateprior.find(ind);
522 std::size_t pick =
static_cast<std::size_t
>(-1);
523 if (ss !=
sn.statespace.end() && sp !=
sn.stateprior.end() && ss->second.cols() > 0 &&
524 ss->second.rows() == sp->second.size()) {
525 for (std::size_t r = 0; r < ss->second.rows() && pick ==
static_cast<std::size_t
>(-1);
530 bool decoded =
false;
531 if (pick !=
static_cast<std::size_t
>(-1)) {
532 std::vector<T> row(ss->second.cols());
533 for (std::size_t c = 0; c < ss->second.cols(); ++c) row[c] = ss->second(pick, c);
534 std::vector<std::size_t> ph(K, 1), shift(K, 0);
536 for (std::size_t r = 0; r < K; ++r) {
543 decoded = m.nir.size() == K && m.kir.size() == K;
544 }
catch (
const Error&) {
548 for (std::size_t r = 0; r < K; ++r) {
549 if (!L.enabled[i][r])
continue;
553 if (!ext && nplace[i][r] > 0.0) out[L.qidx[i][r]] = nplace[i][r];
554 if (ext && !std::isfinite(sn.
classes[r].population)) out[L.qidx[i][r]] = 1.0;
557 const std::size_t np = L.kic[i][r];
558 for (std::size_t k = 0; k < np; ++k) {
560 if (k < m.kir[r].size()) v = num_traits<T>::to_double(m.kir[r][k]);
561 if (k == 0 && !ext) {
564 for (std::size_t j = 1; j < m.kir[r].size(); ++j)
565 served += num_traits<T>::to_double(m.kir[r][j]);
566 v = num_traits<T>::to_double(m.nir[r]) - served;
570 if (!std::isfinite(v)) v = 0.0;
571 out[L.qidx[i][r] + k] = v;
576 if (!any)
return false;
583std::vector<double> fluid_default_initsol(
const qn::NetworkStruct<T>& sn,
const FluidLayout& L) {
584 const std::size_t M = sn.nstations, K = sn.nclasses;
585 std::vector<double> y0(L.nstates, 0.0);
586 const std::vector<std::vector<double>> nplace = fluid_initsol_placement(sn, L);
587 if (fluid_declared_initsol(sn, L, nplace, y0))
return y0;
588 for (std::size_t r = 0; r < K; ++r) {
589 if (std::isfinite(sn.classes[r].population)) {
596 for (std::size_t i = 0; i < M; ++i)
597 if (L.enabled[i][r] && nplace[i][r] > 0.0) y0[L.qidx[i][r]] = nplace[i][r];
600 for (std::size_t i = 0; i < M; ++i)
602 y0[L.qidx[i][r]] = 1.0;
622inline void fluid_snap_fine(Matrix<double>& m) {
623 for (std::size_t i = 0; i < m.rows(); ++i)
624 for (std::size_t j = 0; j < m.cols(); ++j)
637inline void fluid_snap_all(Matrix<double>& q, Matrix<double>& u, Matrix<double>& r,
643 for (std::size_t i = 0; i < r.rows(); ++i)
644 for (std::size_t j = 0; j < r.cols(); ++j)
645 if (t(i, j) == 0.0 && q(i, j) == 0.0) r(i, j) = 0.0;
666std::vector<char> fluid_visited_pairs(
const qn::NetworkStruct<T>& sn, std::size_t M, std::size_t K) {
667 std::vector<char> visited(M * K, 0);
669 std::size_t max_cols = 0;
670 for (std::size_t c = 0; c < sn.visits.size(); ++c) {
671 const Matrix<T>& Vc = sn.visits[c];
672 if (Vc.rows() == 0 || Vc.cols() == 0)
continue;
674 max_cols = std::max(max_cols, Vc.cols());
675 for (std::size_t i = 0; i < M; ++i) {
676 const std::size_t isf = sn.stateful_of_station(i + 1);
677 if (isf == 0 || isf > Vc.rows()) {
678 for (std::size_t r = 0; r < K; ++r) visited[i * K + r] = 1;
681 for (std::size_t r = 0; r < K && r < Vc.cols(); ++r)
682 if (std::fabs(num_traits<T>::to_double(Vc(isf - 1, r))) >
684 visited[i * K + r] = 1;
688 std::fill(visited.begin(), visited.end(),
static_cast<char>(1));
692 for (std::size_t i = 0; i < M; ++i)
693 for (std::size_t r = max_cols; r < K; ++r) visited[i * K + r] = 1;
723void fluid_analyzer_correct(
const qn::NetworkStruct<T>& sn,
const Matrix<double>& Q,
724 Matrix<double>& U, Matrix<double>& R,
const Matrix<double>& T_) {
725 const std::size_t M = Q.rows(), K = Q.cols();
728 const std::vector<char> visited = fluid_visited_pairs(sn, M, K);
729 const Matrix<double> U0 = U;
730 for (std::size_t i = 0; i < M; ++i) {
731 double u0sum = 0.0, share_den = 0.0;
732 for (std::size_t r = 0; r < K; ++r) {
733 if (!(Q(i, r) > 0.0) || !visited[i * K + r])
continue;
735 const double rate = num_traits<T>::to_double(sn.rates(i, r));
736 if (rate != 0.0) share_den += T_(i, r) / rate;
743 double c = sn.stations[i].nservers;
744 for (std::size_t k = 0; k < sn.stations[i].lldscaling.size(); ++k)
745 c = std::max(c, num_traits<T>::to_double(sn.stations[i].lldscaling[k]));
747 for (std::size_t r = 0; r < K; ++r) {
748 if (!(Q(i, r) > 0.0) || !visited[i * K + r]) {
758 if (std::isfinite(c) && c > 0.0) best = std::min(best, Q(i, r) / c);
759 const double rate = num_traits<T>::to_double(sn.rates(i, r));
760 if (rate != 0.0 && share_den != 0.0)
761 best = std::min(best, u0sum * (T_(i, r) / rate) / share_den);
763 if (T_(i, r) != 0.0) R(i, r) = Q(i, r) / T_(i, r);
766 for (std::size_t i = 0; i < M; ++i)
767 for (std::size_t r = 0; r < K; ++r) {
768 if (std::isnan(U(i, r))) U(i, r) = 0.0;
769 if (std::isnan(R(i, r))) R(i, r) = 0.0;
786 const std::string& m,
const std::vector<double>& xs,
Matrix<double>& Q,
788 const std::size_t M =
sn.nstations, K =
sn.nclasses;
795 for (std::size_t i = 0; i < M; ++i)
796 for (std::size_t r = 0; r < K; ++r) {
798 for (std::size_t k = 0; k < L.
kic[i][r]; ++k) q += xs[L.
qidx[i][r] + k];
803 std::vector<std::vector<std::vector<double>>> xservice(M, std::vector<std::vector<double>>(K));
804 for (std::size_t i = 0; i < M; ++i) {
807 for (std::size_t r = 0; r < K; ++r) xi += Q(i, r);
809 for (std::size_t r = 0; r < K; ++r)
810 wxi += (r <
sn.stations[i].schedparam.size()
814 const double c =
sn.stations[i].nservers;
827 std::vector<double> wmean(K, 0.0);
830 for (std::size_t r = 0; r < K; ++r) {
831 if (!L.
enabled[i][r])
continue;
833 const std::size_t nn =
sn.service[i][r].D0.rows();
836 for (std::size_t a = 0; a < nn; ++a)
837 for (std::size_t bb = 0; bb < nn; ++bb) {
842 wni += wmean[r] * Q(i, r);
846 for (std::size_t r = 0; r < K; ++r) {
847 xservice[i][r].assign(L.
kic[i][r], 0.0);
848 if (!L.
enabled[i][r])
continue;
849 std::vector<double> mu, phi;
850 detail::fluid_mu_phi(
sn.service[i][r], mu, phi);
851 const std::size_t b = L.
qidx[i][r], n = L.
kic[i][r];
854 for (std::size_t k = 0; k < n; ++k)
855 xservice[i][r][k] = xs[b + k] * mu[k] * wmean[r] / wni * served;
857 for (std::size_t k = 0; k < n; ++k) s2 += xservice[i][r][k];
861 for (std::size_t k = 0; k < n; ++k) {
862 double mass = xs[b + k];
868 for (std::size_t p = 1; p < n; ++p) rest += xs[b + p];
871 tn += mass * mu[k] * phi[k];
872 xservice[i][r][k] = mass * mu[k];
875 tn += mass * mu[k] * phi[k];
876 xservice[i][r][k] = mass * mu[k];
879 const double w = r <
sn.stations[i].schedparam.size()
883 tn += mass * mu[k] * phi[k] * w / wxi * served;
884 xservice[i][r][k] = mass * mu[k] * w / wxi * served;
890 tn += mass * mu[k] * phi[k] / xi * served;
891 xservice[i][r][k] = mass * mu[k] / xi * served;
902 for (std::size_t i = 0; i < M; ++i) {
904 for (std::size_t r = 0; r < K; ++r) {
905 if (!L.
enabled[i][r])
continue;
906 std::vector<double> mu, phi;
907 detail::fluid_mu_phi(
sn.service[i][r], mu, phi);
909 for (std::size_t k = 0; k < xservice[i][r].size(); ++k)
910 if (xservice[i][r][k] > 0.0 && mu[k] > 0.0) u += xservice[i][r][k] / mu[k];
913 double c =
sn.stations[i].nservers;
914 for (std::size_t k = 0; k < sys.
lld[i].size(); ++k) c = std::max(c, sys.
lld[i][k]);
915 U(i, r) = (is_delay || !std::isfinite(c) || c <= 0.0) ? u : u / c;
920 for (std::size_t i = 0; i < M; ++i)
921 for (std::size_t r = 0; r < K; ++r)
922 if (T_(i, r) > 0.0) R(i, r) = Q(i, r) / T_(i, r);
930 for (std::size_t i = 0; i < M; ++i) {
932 if (nt != qn::NodeType::Source && nt != qn::NodeType::Sink)
continue;
933 for (std::size_t r = 0; r < K; ++r) {
940 detail::fluid_snap_all(Q, U, R, T_);
957inline void fluid_nhpp_steps(
const std::vector<double>& bp,
const std::vector<double>& seg_rate,
958 bool cyclic,
double t0,
double thi, std::vector<double>& seg_t,
959 std::vector<double>& seg_r) {
962 if (bp.size() < 2 || seg_rate.empty() || !(thi > t0))
return;
963 const double period = bp.back() - bp.front();
964 std::vector<double> bounds;
965 bounds.push_back(t0);
966 if (cyclic && period > 0.0) {
967 const long kmax =
static_cast<long>(std::ceil((thi - t0) / period)) + 2;
968 for (
long k = -1; k <= kmax; ++k)
969 for (std::size_t a = 0; a < bp.size(); ++a)
970 bounds.push_back(bp[a] +
static_cast<double>(k) * period);
972 for (std::size_t a = 0; a < bp.size(); ++a) bounds.push_back(bp[a]);
974 bounds.push_back(thi);
975 std::sort(bounds.begin(), bounds.end());
976 bounds.erase(std::remove_if(bounds.begin(), bounds.end(),
977 [&](
double v) { return v < t0 || v > thi; }),
979 bounds.erase(std::unique(bounds.begin(), bounds.end()), bounds.end());
980 if (bounds.size() < 2)
return;
984 const auto rate_at = [&](
double t) ->
double {
985 double offset = t - bp.front();
988 offset = std::fmod(offset, period);
989 if (offset < 0.0) offset += period;
993 }
else if (offset < 0.0 || offset >= period) {
996 const double pos = bp.front() + offset;
997 std::size_t idx = seg_rate.size() - 1;
998 for (std::size_t k = 1; k < bp.size(); ++k)
1003 return idx < seg_rate.size() ? seg_rate[idx] : 0.0;
1006 const double neps = std::max(1e-9, 1e-6 * (thi - t0));
1007 for (std::size_t k = 0; k + 1 < bounds.size(); ++k) {
1008 const double a = bounds[k], b = bounds[k + 1];
1009 const double r = rate_at(0.5 * (a + b));
1012 seg_t.push_back(std::max(a + neps, b - neps));
1018inline FluidRateMult fluid_ratemult_merge(
const FluidRateMult& a,
const FluidRateMult& b,
1019 std::size_t nevents) {
1020 if (a.empty())
return b;
1021 if (b.empty())
return a;
1022 std::vector<double> tg = a.tgrid;
1023 tg.insert(tg.end(), b.tgrid.begin(), b.tgrid.end());
1024 std::sort(tg.begin(), tg.end());
1025 tg.erase(std::unique(tg.begin(), tg.end()), tg.end());
1028 out.Mmat = Matrix<double>(nevents, tg.size(), 1.0);
1029 std::vector<double> ca, cb;
1030 for (std::size_t j = 0; j < tg.size(); ++j) {
1033 for (std::size_t e = 0; e < nevents; ++e) {
1034 const double va = e < ca.size() ? ca[e] : 1.0;
1035 const double vb = e < cb.size() ? cb[e] : 1.0;
1036 out.Mmat(e, j) = va * vb;
1051inline bool fluid_has_time_varying_rates(
const FluidOptions& opt) {
1052 return !opt.rate_traj.empty() || !opt.nhpp_sched.empty() || !opt.rate_sched.empty();
1056inline std::vector<std::size_t> fluid_events_of(
const FluidOdeSystem& sys, std::size_t i,
1058 std::vector<std::size_t> rows;
1059 const std::size_t lo = sys.layout.qidx[i][c];
1060 const std::size_t hi = lo + sys.layout.kic[i][c];
1061 for (std::size_t e = 0; e < sys.events.size(); ++e)
1062 if (sys.events[e].event_idx >= lo && sys.events[e].event_idx < hi) rows.push_back(e);
1067inline FluidRateMult fluid_ratemult_rows(
const FluidOdeSystem& sys, std::size_t i, std::size_t c,
1068 const std::vector<double>& seg_t,
1069 const std::vector<double>& seg_r,
double nominal) {
1071 if (seg_t.empty() || !(nominal > 0.0))
return out;
1072 const std::vector<std::size_t> rows = fluid_events_of(sys, i, c);
1073 if (rows.empty())
return out;
1075 out.Mmat = Matrix<double>(sys.events.size(), seg_t.size(), 1.0);
1076 for (std::size_t j = 0; j < seg_t.size(); ++j)
1077 for (std::size_t e : rows) out.Mmat(e, j) = seg_r[j] / nominal;
1096FluidRateMult fluid_ratemult(
const qn::NetworkStruct<T>& sn,
const FluidOdeSystem& sys,
1097 const FluidOptions& opt) {
1098 const std::size_t nev = sys.events.size();
1100 if (!opt.rate_traj.empty()) {
1101 if (opt.rate_traj.Mmat.rows() != nev)
1102 throw InputError(
"solver_fluid_ratemult: rate_traj has " +
1103 std::to_string(opt.rate_traj.Mmat.rows()) +
1104 " rows but the closing ODE has " + std::to_string(nev) +
" events");
1105 out = opt.rate_traj;
1112 const double tend = opt.timespan_end;
1115 for (std::size_t a = 0; a < opt.nhpp_sched.size(); ++a) {
1116 const std::size_t i = opt.nhpp_sched[a].first - 1, c = opt.nhpp_sched[a].second - 1;
1117 if (i >= sn.nstations || c >= sn.nclasses)
continue;
1118 if (!sys.layout.enabled[i][c])
continue;
1119 const lang::Distrib<T>& d = sn.service[i][c];
1120 if (!d.has_schedule())
continue;
1121 const std::vector<T> mu = d.mu_vec();
1122 if (mu.empty())
continue;
1123 const double nominal = num_traits<T>::to_double(mu[0]);
1124 if (!(nominal > 0.0))
continue;
1126 std::vector<double> bp, seg_r;
1127 for (std::size_t k = 0; k < d.sched_bp.size(); ++k)
1128 bp.push_back(num_traits<T>::to_double(d.sched_bp[k]));
1131 for (std::size_t k = 0; k < d.sched_D0.size(); ++k) {
1133 m.D0 = d.sched_D0[k];
1134 m.D1 = d.sched_D1[k];
1137 const double period = bp.empty() ? 0.0 : bp.back() - bp.front();
1139 if (!std::isfinite(thi))
1140 thi = (std::isfinite(period) && period > 0.0) ? t0 + 3.0 * period : t0 + 1.0;
1141 std::vector<double> seg_t, seg_v;
1142 fluid_nhpp_steps(bp, seg_r, d.sched_cyclic, t0, thi, seg_t, seg_v);
1143 nh = fluid_ratemult_merge(nh, fluid_ratemult_rows(sys, i, c, seg_t, seg_v, nominal), nev);
1147 for (std::size_t a = 0; a < opt.rate_sched.size(); ++a) {
1148 const FluidOptions::RateSched& e = opt.rate_sched[a];
1149 const std::size_t i = e.station - 1, c = e.cls - 1;
1150 if (i >= sn.nstations || c >= sn.nclasses)
continue;
1151 if (!sys.layout.enabled[i][c])
continue;
1152 if (e.tgrid.size() != e.rates.size() || e.tgrid.empty())
1153 throw InputError(
"solver_fluid_ratemult: rate_sched tgrid and rates must be "
1154 "non-empty and of equal length");
1155 double nominal = e.nominal;
1156 if (!(nominal > 0.0)) {
1157 const std::vector<T> mu = sn.service[i][c].mu_vec();
1158 if (mu.empty())
continue;
1159 nominal = num_traits<T>::to_double(mu[0]);
1161 if (!(nominal > 0.0))
continue;
1162 rs = fluid_ratemult_merge(rs, fluid_ratemult_rows(sys, i, c, e.tgrid, e.rates, nominal),
1166 out = fluid_ratemult_merge(out, nh, nev);
1167 out = fluid_ratemult_merge(out, rs, nev);
1178double fluid_slow_rate(
const qn::NetworkStruct<T>& sn,
const FluidLayout& L,
double tol) {
1179 double min_rate = std::numeric_limits<double>::infinity();
1180 for (std::size_t i = 0; i < sn.nstations; ++i)
1181 for (std::size_t r = 0; r < sn.nclasses; ++r) {
1182 if (!L.enabled[i][r])
continue;
1183 const lang::Distrib<T>& d = sn.service[i][r];
1184 for (std::size_t k = 0; k < d.D0.rows(); ++k) {
1185 const double mu = -num_traits<T>::to_double(d.D0(k, k));
1186 if (mu > tol && std::isfinite(mu)) min_rate = std::min(min_rate, mu);
1189 return std::isfinite(min_rate) ? min_rate : 1.0;
1202FluidSolution fluid_dispatch(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt) {
1203 if (!std::is_same<T, double>::value)
1205 "solver_fluid: the fluid solver integrates its drift with LSODA, whose coefficients "
1206 "assume double precision; rerun with --arith double");
1208 std::string m = opt.method;
1209 if (m.compare(0, 4,
"fld.") == 0) m = m.substr(4);
1211 bool statedep_family =
false;
1223 bool has_dps =
false, has_cache =
false;
1224 for (
const auto& st : sn.stations)
1226 for (
const qn::NodeDef& nd : sn.nodes)
1227 if (nd.nodetype == qn::NodeType::Cache) has_cache =
true;
1228 if ((m ==
"matrix" || m ==
"pnorm") && has_dps)
1230 "solver_fluid: the matrix method does not support DPS scheduling; use method "
1231 "'closing' (which is what 'default' selects on a DPS model)");
1232 if (m ==
"default") {
1235 "solver_fluid: a Cache model resolves to the 'rmf' fluid method, which is ported "
1236 "in fluid_cacheqn.h and reached through solver_fluid_run_analyzer (fluid_runner.h); this "
1237 "function is solver_fluid_analyzer alone and cannot call it without a cyclic "
1239 if (has_dps) m =
"closing";
1241 const bool matrix_family = (m ==
"default" || m ==
"matrix" || m ==
"pnorm");
1242 if (m ==
"statedep") {
1243 statedep_family =
true;
1245 }
else if (m ==
"softmin") {
1246 statedep_family =
true;
1248 }
else if (!(matrix_family || m ==
"closing" || m ==
"tbi" || m ==
"diffusion" || m ==
"mfq")) {
1250 "' fluid method is not solved here; available are 'closing', "
1251 "'statedep', 'softmin', 'pnorm', 'matrix', 'tbi', 'diffusion' and "
1252 "'mfq', while 'rmf', 'minnormal', 'refined', 'dae' and 'kp' are "
1253 "reached through solver_fluid_run_analyzer (fluid_runner.h), which is the "
1254 "port of runAnalyzer's resolution");
1257 const std::size_t M = sn.nstations, K = sn.nclasses;
1262 sys.closure = opt.closure;
1266 sys.ratemult = detail::fluid_ratemult(sn, sys, opt);
1267 const FluidLayout& L = sys.layout;
1269 throw InputError(
"solver_fluid: no station serves any class, so the drift is empty");
1272 const double min_rate = fluid_slow_rate(sn, L, opt.tol);
1275 std::vector<double> x = opt.init_sol.empty() ? detail::fluid_default_initsol(sn, L) : opt.init_sol;
1276 if (x.size() != L.nstates)
1277 throw InputError(
"solver_fluid: init_sol has " + std::to_string(x.size()) +
1278 " entries but the fluid state has " + std::to_string(L.nstates));
1287 const FluidAoiResult ar =
fluid_aoi(sn, atop, opt.aoi_preemption);
1293 out.QN = Matrix<double>(M, K, 0.0);
1294 out.UN = Matrix<double>(M, K, 0.0);
1295 out.RN = Matrix<double>(M, K, 0.0);
1296 out.TN = Matrix<double>(M, K, 0.0);
1297 out.XN.assign(K, 0.0);
1298 out.CN.assign(K, 0.0);
1299 for (std::size_t r = 0; r < K; ++r) {
1300 out.QN(atop.queue, r) = ar.QN[r];
1301 out.UN(atop.queue, r) = ar.UN[r];
1302 out.RN(atop.queue, r) = ar.RN[r];
1303 out.TN(atop.queue, r) = ar.TN[r];
1304 out.TN(atop.source, r) = ar.TN[r];
1305 out.XN[r] = ar.TN[r];
1306 out.CN[r] = ar.RN[r];
1314 bool mixed_prio =
false;
1316 for (std::size_t j = 1; j < top.open_classes.size(); ++j)
1317 if (sn.classes[top.open_classes[j]].prio != sn.classes[top.open_classes[0]].prio)
1323 FluidOptions fb = opt;
1324 fb.method =
"matrix";
1330 out.QN = Matrix<double>(M, K, 0.0);
1331 out.UN = Matrix<double>(M, K, 0.0);
1332 out.RN = Matrix<double>(M, K, 0.0);
1333 out.TN = Matrix<double>(M, K, 0.0);
1334 out.XN.assign(K, 0.0);
1335 out.CN.assign(K, 0.0);
1336 for (std::size_t r = 0; r < K; ++r) {
1337 out.QN(top.queue, r) = pr.QN[r];
1338 out.TN(top.queue, r) = pr.TN[r];
1339 out.TN(top.source, r) = pr.TN[r];
1340 out.XN[r] = pr.TN[r];
1345 double ufull = 0.0, tsum = 0.0;
1346 for (std::size_t r = 0; r < K; ++r)
1347 if (pr.QN[r] > 0.0) {
1349 tsum += pr.TN[r] / num_traits<T>::to_double(sn.rates(top.queue, r));
1351 const double servers = sn.stations[top.queue].nservers;
1352 for (std::size_t r = 0; r < K; ++r) {
1353 if (!(pr.QN[r] > 0.0))
continue;
1354 const double share =
1355 ufull * (pr.TN[r] / num_traits<T>::to_double(sn.rates(top.queue, r))) / tsum;
1356 out.UN(top.queue, r) = std::min(1.0, std::min(pr.QN[r] / servers, share));
1357 out.RN(top.queue, r) = pr.QN[r] / pr.TN[r];
1358 out.CN[r] = out.RN(top.queue, r);
1362 if (top.ok && top.open_classes.size() > 1)
1364 "fluid mfq: the single fluid-fluid queue analyzes ONE open class, and this model "
1365 "has several at equal priority; the reference silently reports class 1 only");
1375 FluidOptions mopt = opt;
1376 mopt.method =
"matrix";
1377 return fluid_dispatch(sn, mopt);
1379 const MfqResult r =
fluid_mfq(sn, top, opt.tol);
1383 out.QN = Matrix<double>(M, K, 0.0);
1384 out.UN = Matrix<double>(M, K, 0.0);
1385 out.RN = Matrix<double>(M, K, 0.0);
1386 out.TN = Matrix<double>(M, K, 0.0);
1387 out.QN(top.queue, top.cls) = r.QN;
1388 out.UN(top.queue, top.cls) = r.UN;
1389 out.RN(top.queue, top.cls) = r.RN;
1390 out.TN(top.queue, top.cls) = r.TN;
1391 out.TN(top.source, top.cls) = r.TN;
1392 out.XN.assign(K, 0.0);
1393 out.CN.assign(K, 0.0);
1394 out.XN[top.cls] = r.TN;
1395 out.CN[top.cls] = r.RN;
1400 if (m ==
"diffusion") {
1401 DiffusionOptions dopt;
1402 dopt.steps = opt.iter_max > 2 ? opt.iter_max : 10000;
1403 dopt.dt = opt.timestep;
1404 dopt.seed = opt.seed;
1408 out.method =
"diffusion";
1410 out.UN = Matrix<double>(M, K, 0.0);
1411 out.RN = Matrix<double>(M, K, 0.0);
1412 out.TN = Matrix<double>(M, K, 0.0);
1413 for (std::size_t i = 0; i < M; ++i) {
1414 const double c = sn.stations[i].nservers;
1415 const bool inf_server = !std::isfinite(c);
1416 for (std::size_t r = 0; r < K; ++r) {
1417 const double rate = num_traits<T>::to_double(sn.rates(i, r));
1418 if (rate > 0.0 && std::isfinite(rate)) {
1421 out.TN(i, r) = inf_server ? out.QN(i, r) * rate
1422 : std::min(out.QN(i, r), 1.0) * rate;
1424 out.UN(i, r) = inf_server ? out.QN(i, r) : std::min(out.QN(i, r) / c, 1.0);
1428 out.RN(i, r) = out.QN(i, r) / out.TN(i, r);
1431 detail::fluid_snap_all(out.QN, out.UN, out.RN, out.TN);
1432 out.XN.assign(K, 0.0);
1433 out.CN.assign(K, 0.0);
1434 for (std::size_t r = 0; r < K; ++r) {
1435 const std::size_t rs = sn.classes[r].refstat;
1436 if (rs >= 1 && rs <= M) out.XN[r] = out.TN(rs - 1, r);
1438 for (std::size_t i = 0; i < M; ++i) q += out.QN(i, r);
1439 if (out.XN[r] > 0.0) out.CN[r] = q / out.XN[r];
1445 lopt.rtol = opt.tol;
1446 lopt.atol = opt.tol;
1449 if (matrix_family) {
1453 const double ps = (m ==
"pnorm" || opt.pstar_set) ? opt.pstar : 0.0;
1455 const std::function<void(
double,
const double*,
double*)> mdrift =
fluid_matrix_drift(ms);
1457 std::min(opt.timespan_end,
1458 10.0 *
static_cast<double>(opt.iter_max) / ms.min_rate);
1460 for (
double& v : xm)
1461 if (v < 0.0) v = 0.0;
1483 FluidMatrixSystem msr = ms;
1485 msr.var_closure =
true;
1486 const std::function<void(
double,
const double*,
double*)> cdrift =
1489 bool finite = xc.size() == xm.size();
1490 for (std::size_t a = 0; finite && a < xc.size(); ++a)
1491 if (!std::isfinite(xc[a])) finite =
false;
1493 for (
double& v : xc)
1494 if (v < 0.0) v = 0.0;
1499 msr.var_closure =
false;
1504 std::vector<double> theta(ms.nstates, 0.0);
1505 detail::fluid_matrix_theta(msr, xm.data(), theta);
1509 out.method = (m ==
"pnorm") ?
"pnorm" :
"matrix";
1511 out.QN = Matrix<double>(M, K, 0.0);
1512 out.UN = Matrix<double>(M, K, 0.0);
1513 out.RN = Matrix<double>(M, K, 0.0);
1514 out.TN = Matrix<double>(M, K, 0.0);
1515 for (std::size_t i = 0; i < M; ++i)
1516 for (std::size_t r = 0; r < K; ++r) {
1517 double q = 0.0, u = 0.0, t = 0.0;
1518 for (std::size_t a = 0; a < ms.nstates; ++a) {
1519 q += ms.sqc(i * K + r, a) * xm[a];
1520 u += ms.suc(i * K + r, a) * theta[a];
1521 t += ms.stc(i * K + r, a) * theta[a];
1531 !std::isfinite(sn.stations[i].nservers))
1545 for (std::size_t i = 0; i < M; ++i) {
1547 if (nt != qn::NodeType::Source && nt != qn::NodeType::Sink)
continue;
1548 for (std::size_t r = 0; r < K; ++r) {
1552 if (nt == qn::NodeType::Source && ms.src_arrival.rows() == M)
1553 out.TN(i, r) = ms.src_arrival(i, r);
1556 detail::fluid_snap_all(out.QN, out.UN, out.RN, out.TN);
1557 out.XN.assign(K, 0.0);
1558 out.CN.assign(K, 0.0);
1559 for (std::size_t r = 0; r < K; ++r) {
1560 const std::size_t rs = sn.classes[r].refstat;
1561 if (rs >= 1 && rs <= M) out.XN[r] = out.TN(rs - 1, r);
1563 for (std::size_t i = 0; i < M; ++i) q += out.QN(i, r);
1564 if (out.XN[r] > 0.0) out.CN[r] = q / out.XN[r];
1580 lopt.rtol = lopt.atol =
1581 opt.tol / std::max<double>(1.0,
static_cast<double>(opt.iter_max));
1587 FluidOdeSystem dsys = sys;
1588 FluidImmediateResult imm_result;
1591 if (imm_result.eliminated) {
1592 dsys = imm_result.sys;
1597 std::vector<double> xp(x.size(), 0.0);
1598 for (std::size_t f = 0; f < x.size() && f < imm_result.absorb.rows(); ++f)
1599 for (std::size_t sidx = 0; sidx < x.size() && sidx < imm_result.absorb.cols();
1601 xp[sidx] += x[f] * imm_result.absorb(f, sidx);
1602 for (std::size_t a = 0; a < x.size(); ++a) x[a] = xp[a];
1606 const std::function<void(
double,
const double*,
double*)> drift =
1611 const std::vector<std::vector<std::size_t>> tbi_cells =
1612 (m !=
"tbi") ? std::vector<std::vector<std::size_t>>()
1613 : opt.tbi_cells.empty() ?
tbi_partition(sn, opt.tbi_cellsize)
1618 const double drift_tol = std::max(opt.iter_tol, opt.tol);
1619 const double drift_safety = 0.01;
1620 const double min_horizon = 10.0 / min_rate;
1621 double moved_prev = std::numeric_limits<double>::infinity();
1622 std::vector<double> rho_hist(3, std::numeric_limits<double>::quiet_NaN());
1623 int drift_below = 0;
1624 std::vector<double> drift_buf(x.size(), 0.0);
1627 std::size_t iter = 0;
1628 for (; iter < opt.iter_max; ++iter) {
1629 const double horizon = 10.0 *
static_cast<double>(iter + 1) / min_rate;
1630 const double t1 = std::min(opt.timespan_end, horizon);
1631 if (!(t1 > t0))
break;
1632 const std::vector<double> prev = x;
1660 const bool fp_armed = opt.earlystop && !std::isfinite(opt.timespan_end)
1661 && !fluid_has_time_varying_rates(opt);
1663 drift(t0, x.data(), drift_buf.data());
1664 double dn = 0.0, dtot = 0.0;
1665 for (std::size_t i = 0; i < x.size(); ++i) {
1666 dn += std::fabs(drift_buf[i]);
1692 lopt.step_stop = {};
1693 if (fp_armed && m !=
"tbi") {
1694 const double fp_rate = min_rate;
1695 lopt.step_stop = [&drift, fp_rate](
double tt,
const std::vector<double>& yy) {
1696 if (yy.empty() || !(fp_rate > 0.0))
return false;
1697 std::vector<double> dy(yy.size(), 0.0);
1698 drift(tt, yy.data(), dy.data());
1699 double dn = 0.0, dtot = 0.0;
1700 for (std::size_t i = 0; i < yy.size(); ++i) {
1701 dn += std::fabs(dy[i]);
1709 x =
tbi_advance(sys, tbi_cells, x, t0, t1, TbiOptions(), lopt);
1710 }
else if (opt.stiff) {
1714 FluidStiffOptions sopt;
1715 sopt.rtol = lopt.rtol;
1716 sopt.atol = lopt.atol;
1719 if (lopt.step_stop) {
1720 const std::function<bool(
double,
const std::vector<double>&)> ss_stop =
1722 sopt.step_stop = [ss_stop](
const double& tt,
const std::vector<double>& yy) {
1723 return ss_stop(tt, yy);
1727 x = ss.final_state();
1735 if (v < 0.0) v = 0.0;
1745 if (opt.closure.gaussian()) {
1749 "The moment-closure drift left the model: closed chain " +
1750 std::to_string(bad) +
" moved more than " +
1752 "% of a population the drift conserves exactly, by t = " +
1753 std::to_string(t1) +
1754 ", so the excursion is a divergence rather than a solution. "
1755 "Falling back to a first-order closure.");
1760 double moved = 0.0, total = 0.0;
1761 for (std::size_t i = 0; i < x.size(); ++i) {
1762 moved += std::fabs(x[i] - prev[i]);
1765 const double ratio = (total > 0.0) ? moved / 2.0 / total : 0.0;
1771 if (opt.iter_tol > 0.0 && ratio < opt.iter_tol && !std::isfinite(opt.timespan_end)) {
1782 if (opt.earlystop && iter > 0 && !std::isfinite(opt.timespan_end) && t1 >= min_horizon) {
1783 rho_hist[iter % rho_hist.size()] =
1786 for (
double v : rho_hist)
1787 if (std::isfinite(v) && v > rho) rho = v;
1788 drift(t1, x.data(), drift_buf.data());
1789 double dn = 0.0, dtot = 0.0;
1790 for (std::size_t i = 0; i < x.size(); ++i) {
1791 dn += std::fabs(drift_buf[i]);
1794 const double drift_displ = (dtot > 0.0) ? dn / 2.0 / dtot / min_rate : 0.0;
1807 if (rho < 1.0 && ratio * rho / (1.0 - rho) < drift_safety * drift_tol
1808 && drift_displ < drift_tol) {
1809 if (++drift_below >= 2) {
1818 if (t1 >= opt.timespan_end) {
1827 out.method = (statedep_family || m ==
"tbi") ? m : std::string(
"closing");
1829 out.QN = Matrix<double>(M, K, 0.0);
1830 out.UN = Matrix<double>(M, K, 0.0);
1831 out.RN = Matrix<double>(M, K, 0.0);
1832 out.TN = Matrix<double>(M, K, 0.0);
1843 if (imm_result.eliminated) {
1844 const FluidLayout& LL = sys.layout;
1845 std::vector<std::size_t> cs(LL.nstates, 0), cc(LL.nstates, 0);
1846 for (std::size_t i = 0; i < M; ++i)
1847 for (std::size_t r = 0; r < K; ++r)
1848 for (std::size_t k = 0; k < LL.kic[i][r]; ++k) {
1849 cs[LL.qidx[i][r] + k] = i;
1850 cc[LL.qidx[i][r] + k] = r;
1852 std::vector<bool> kept(LL.nstates,
false);
1853 for (std::size_t a = 0; a < imm_result.state_map.size(); ++a)
1854 kept[imm_result.state_map[a]] =
true;
1855 std::vector<double> rr(x.begin(), x.end());
1857 for (std::size_t o = 0; o < sys.n_departures && o < sys.events.size(); ++o) {
1858 const std::size_t c = sys.events[o].event_idx;
1859 if (c >= LL.nstates || kept[c])
continue;
1861 for (std::size_t e = 0; e < rr.size() && e < imm_result.emap.rows(); ++e)
1862 extra += imm_result.emap(e, o) * rr[e];
1863 out.TN(cs[c], cc[c]) += extra;
1865 for (std::size_t i = 0; i < M; ++i)
1866 for (std::size_t r = 0; r < K; ++r)
1868 out.RN(i, r) = out.QN(i, r) / out.TN(i, r);
1870 detail::fluid_snap_all(out.QN, out.UN, out.RN, out.TN);
1873 out.XN.assign(K, 0.0);
1874 out.CN.assign(K, 0.0);
1875 for (std::size_t r = 0; r < K; ++r) {
1876 const std::size_t rs = sn.classes[r].refstat;
1877 if (rs >= 1 && rs <= M) out.XN[r] = out.TN(rs - 1, r);
1879 for (std::size_t i = 0; i < M; ++i) q += out.QN(i, r);
1880 if (out.XN[r] > 0.0) out.CN[r] = q / out.XN[r];
1942inline bool fluid_method_refits_fcfs(
const std::string& method) {
1943 std::string m = method;
1944 if (m.size() > 4 && m.compare(0, 4,
"fld.") == 0) m = m.substr(4);
1945 return m ==
"matrix" || m ==
"closing" || m ==
"tbi" || m ==
"minnormal" || m ==
"refined" ||
1951Matrix<T> fluid_station_visits(
const qn::NetworkStruct<T>& sn) {
1952 const T zero = num_traits<T>::from_int(0);
1953 Matrix<T> V(sn.nstations, sn.nclasses, zero);
1954 for (std::size_t c = 0; c < sn.nchains; ++c)
1955 for (std::size_t i = 0; i < sn.nstations; ++i) {
1956 const std::size_t sf = sn.stateful_of_station(i + 1);
1957 for (std::size_t k = 0; k < sn.nclasses; ++k)
1958 V(i, k) = T(V(i, k) + sn.visits[c](sf - 1, k));
1971inline double fluid_eta_gap(
const std::vector<double>& eta,
const std::vector<double>& eta_1) {
1972 double best = -std::numeric_limits<double>::infinity();
1974 for (std::size_t i = 0; i < eta.size(); ++i) {
1975 const double g = std::fabs(1.0 - eta[i] / eta_1[i]);
1976 if (std::isnan(g))
continue;
1978 best = std::max(best, g);
1980 return any ? best : 0.0;
1986Matrix<T> fluid_reciprocal_guarded(
const Matrix<T>& A) {
1987 const T one = num_traits<T>::from_int(1);
1988 Matrix<T> B(A.rows(), A.cols(), num_traits<T>::from_int(0));
1989 for (std::size_t i = 0; i < A.rows(); ++i)
1990 for (std::size_t r = 0; r < A.cols(); ++r) {
1991 const double a = num_traits<T>::to_double(A(i, r));
1994 else if (a == 0.0 || std::isinf(1.0 / a))
2002 B(i, r) = T(one / A(i, r));
2015bool fluid_refit_fcfs_stations(qn::NetworkStruct<T>& sn,
const Matrix<T>& rates,
2016 const Matrix<T>& SCV,
2017 std::vector<std::vector<std::size_t>>& phases) {
2018 const T zero = num_traits<T>::from_int(0);
2019 const T one = num_traits<T>::from_int(1);
2020 const std::size_t M = sn.nstations, K = sn.nclasses;
2021 bool changed =
false;
2022 for (std::size_t i = 0; i < M; ++i) {
2024 for (std::size_t r = 0; r < K; ++r) {
2025 if (!(rates(i, r) > zero) || !(SCV(i, r) > zero))
continue;
2032 if (cx.mu.size() != phases[i][r]) changed =
true;
2033 phases[i][r] = cx.mu.size();
2056template <
class T,
class Solve>
2057FluidSolution fluid_fcfs_nonexp_refit(
const qn::NetworkStruct<T>& sn0,
const FluidOptions& opt,
2058 const FluidSolution& seed, Solve
solve,
2059 qn::NetworkStruct<T>* sn_out =
nullptr) {
2060 const std::size_t M = sn0.nstations, K = sn0.nclasses;
2066 if (sn_out) *sn_out = sn0;
2067 if (!fluid_method_refits_fcfs(opt.method))
return seed;
2068 bool any_fcfs =
false;
2069 for (std::size_t i = 0; i < M; ++i)
2071 if (!any_fcfs)
return seed;
2073 const T zero = num_traits<T>::from_int(0);
2074 const T one = num_traits<T>::from_int(1);
2075 const Matrix<T>& rates0 = sn0.rates;
2076 const Matrix<T>& SCV = sn0.scv;
2077 const Matrix<T> V = fluid_station_visits(sn0);
2078 const Matrix<T> ST0 = fluid_reciprocal_guarded(rates0);
2080 std::vector<bool> isFCFS(M,
false);
2081 std::vector<T> nservers(M, one), gamma(M, zero);
2082 for (std::size_t i = 0; i < M; ++i) {
2084 nservers[i] = num_traits<T>::from_double(sn0.stations[i].nservers);
2087 qn::NetworkStruct<T> sn = sn0;
2088 std::vector<std::vector<std::size_t>> phases(M, std::vector<std::size_t>(K, 0));
2089 for (std::size_t i = 0; i < M; ++i)
2090 for (std::size_t r = 0; r < K; ++r) phases[i][r] = sn0.service[i][r].D0.rows();
2092 FluidSolution cur = seed;
2093 std::vector<double> eta(M, std::numeric_limits<double>::infinity()), eta_1(M, 0.0);
2094 std::size_t iter = 0;
2100 Matrix<T> U(M, K, zero), TN(M, K, zero);
2101 for (std::size_t i = 0; i < M; ++i)
2102 for (std::size_t r = 0; r < K; ++r) {
2103 TN(i, r) = num_traits<T>::from_double(cur.TN(i, r));
2104 if (rates0(i, r) > zero) U(i, r) = T(TN(i, r) / rates0(i, r));
2108 opt.highvar, isFCFS, rates0, ST0, V, SCV, TN, U, gamma, nservers);
2110 for (std::size_t i = 0; i < M; ++i) eta[i] = num_traits<T>::to_double(na.eta[i]);
2112 const Matrix<T> rates = fluid_reciprocal_guarded(na.ST);
2113 const bool phase_change = fluid_refit_fcfs_stations(sn, rates, SCV, phases);
2115 FluidOptions o = opt;
2116 const std::vector<double> fresh = fluid_default_initsol(sn,
fluid_layout(sn));
2117 o.init_sol = (!phase_change && cur.xvec.size() == fresh.size()) ? cur.xvec : fresh;
2124 FluidOptions o = opt;
2125 o.init_sol = fluid_default_initsol(sn,
fluid_layout(sn));
2126 FluidSolution out =
solve(sn, o);
2127 out.refit_sweeps = iter;
2128 if (sn_out) *sn_out = sn;
2164 out = detail::fluid_fcfs_nonexp_refit(
2168 detail::fluid_analyzer_correct(
sn, out.
QN, out.
UN, out.
RN, out.
TN);
2169 detail::fluid_snap_all(out.
QN, out.
UN, out.
RN, out.
TN);
2190 std::vector<std::pair<std::size_t, std::size_t> > out;
2191 for (std::size_t i = 0; i <
sn.nstations; ++i) {
2193 for (std::size_t r = 0; r <
sn.nclasses; ++r)
2194 if (!
sn.disabled[i][r] &&
sn.service[i][r].has_schedule())
2195 out.push_back(std::make_pair(i + 1, r + 1));
2229 std::size_t points = 101,
2230 const std::vector<double>& out_grid =
2231 std::vector<double>()) {
2232 if (!std::is_same<T, double>::value)
2234 "solver_fluid_transient: the fluid drift is integrated by LSODA, which is double "
2235 "precision by construction; rerun with --arith double");
2236 if (!(t_end > 0.0))
throw InputError(
"solver_fluid_transient: t_end must be positive");
2237 if (points < 2)
throw InputError(
"solver_fluid_transient: need at least two output points");
2242 sys.
ratemult = detail::fluid_ratemult(
sn, sys, o);
2245 throw InputError(
"solver_fluid_transient: no station serves any class");
2247 std::vector<double> y0 =
2250 throw InputError(
"solver_fluid_transient: init_sol has the wrong length");
2252 std::vector<double> grid = out_grid;
2254 grid.resize(points);
2255 for (std::size_t j = 0; j < points; ++j)
2256 grid[j] = t_end *
static_cast<double>(j) /
static_cast<double>(points - 1);
2262 const std::function<void(
double,
const double*,
double*)> tdrift =
fluid_drift(sys);
2265 std::vector<FluidTranPoint> out;
2266 out.reserve(sol.
y.size());
2267 for (std::size_t j = 0; j < sol.
y.size(); ++j) {
2268 std::vector<double> xs = sol.
y[j];
2269 for (
double& v : xs)
2270 if (v < 0.0) v = 0.0;
2275 detail::fluid_snap_all(pt.
QN, pt.
UN, R, pt.
TN);
2306 if (std::isfinite(
opt.timespan_end) &&
opt.timespan_end > 0.0)
return opt.timespan_end;
2307 double min_rate = std::numeric_limits<double>::infinity();
2308 for (std::size_t i = 0; i <
sn.nstations; ++i)
2309 for (std::size_t r = 0; r <
sn.nclasses; ++r) {
2310 if (
sn.disabled[i][r])
continue;
2312 if (std::isfinite(rate) && rate >
opt.tol) min_rate = std::min(min_rate, rate);
2314 if (!std::isfinite(min_rate)) min_rate = 1.0;
2315 return 30.0 / min_rate;
2331 std::size_t points = 101) {
2348 if (x.size() != n)
throw InputError(
"fluid_jacobian: the state has the wrong length");
2349 const std::function<void(
double,
const double*,
double*)> f =
fluid_drift(sys);
2351 std::vector<double> xp(x), xm(x), fp(n, 0.0), fm(n, 0.0);
2352 for (std::size_t j = 0; j < n; ++j) {
2354 const double h = 1e-6 * std::max(1.0, std::fabs(x[j]));
2359 f(0.0, xp.data(), fp.data());
2360 f(0.0, xm.data(), fm.data());
2361 for (std::size_t i = 0; i < n; ++i) J(i, j) = (fp[i] - fm[i]) / (2.0 * h);
2384 std::size_t i,
const std::vector<double>& nir,
2385 double* logp_out =
nullptr) {
2386 const std::size_t K =
sn.nclasses;
2389 std::vector<std::size_t> idx;
2390 std::vector<double> m, a, b;
2391 for (std::size_t r = 0; r < K; ++r) {
2392 if (cb[r].empty()) {
2396 if (logp_out) *logp_out = -std::numeric_limits<double>::infinity();
2402 m.push_back(sol.
QN(i, r));
2403 a.push_back(nir[r] <= 0.0 ? -std::numeric_limits<double>::infinity() : nir[r] - 0.5);
2404 const double pop =
sn.classes[r].population;
2405 b.push_back((std::isfinite(pop) && nir[r] >= pop) ? std::numeric_limits<double>::infinity()
2410 if (logp_out) *logp_out = 0.0;
2414 const std::size_t nr = idx.size();
2416 for (std::size_t u = 0; u < nr; ++u)
2417 for (std::size_t v = u; v < nr; ++v) {
2419 for (std::size_t p = 0; p < cb[idx[u]].size(); ++p)
2420 for (std::size_t q = 0; q < cb[idx[v]].size(); ++q)
2421 acc += sol.
moments.
Sigma(cb[idx[u]][p], cb[idx[v]][q]);
2427 if (logp_out) *logp_out = p > 0.0 ? std::log(p) : -std::numeric_limits<double>::infinity();
2446 double* logp_out =
nullptr) {
2447 const std::size_t M =
sn.nstations, K =
sn.nclasses;
2448 if (ist == 0 || ist > M)
2449 throw InputError(
"fluid_prob_aggr: station number exceeds the number of stations");
2450 const std::size_t i = ist - 1;
2455 std::vector<double> nir(K, 0.0);
2456 for (std::size_t r = 0; r < K; ++r) {
2457 const double pop =
sn.classes[r].population;
2458 if (std::isfinite(pop) &&
sn.classes[r].refstat == ist) nir[r] = pop;
2462 bool minus_inf =
false;
2477 bool open_here =
false;
2478 for (std::size_t r = 0; r < K; ++r)
2486 bool any_open =
false;
2487 for (std::size_t r = 0; r < K; ++r)
2488 if (!std::isfinite(
sn.classes[r].population)) any_open =
true;
2490 for (std::size_t r = 0; r < K; ++r) {
2491 if (std::isfinite(
sn.classes[r].population))
continue;
2492 const double q = sol.
QN(i, r);
2494 logp += nir[r] * std::log(q) - q - std::lgamma(nir[r] + 1.0);
2495 else if (nir[r] > 0.0)
2499 double rho_total = 0.0, n_total = 0.0;
2500 for (std::size_t r = 0; r < K; ++r) {
2501 if (std::isfinite(
sn.classes[r].population))
continue;
2502 rho_total += sol.
UN(i, r);
2505 if (rho_total < 1.0) {
2506 logp += std::log(1.0 - rho_total) + std::lgamma(n_total + 1.0);
2507 for (std::size_t r = 0; r < K; ++r) {
2508 if (std::isfinite(
sn.classes[r].population) || !(nir[r] > 0.0))
continue;
2509 const double rho_r = sol.
UN(i, r);
2511 logp += nir[r] * std::log(rho_r) - std::lgamma(nir[r] + 1.0);
2521 for (std::size_t r = 0; r < K; ++r) {
2522 const double N =
sn.classes[r].population;
2523 if (!std::isfinite(N))
continue;
2524 const double q = sol.
QN(i, r);
2525 const double p = (N > 0.0) ? q / N : 0.0;
2527 logp += std::lgamma(N + 1.0) - std::lgamma(nir[r] + 1.0) - std::lgamma(N - nir[r] + 1.0);
2529 logp += nir[r] * std::log(p);
2530 }
else if (nir[r] > 0.0) {
2534 logp += (N - nir[r]) * std::log(1.0 - p);
2535 }
else if (N - nir[r] > 0.0) {
2541 if (logp_out) *logp_out = -std::numeric_limits<double>::infinity();
2544 if (logp_out) *logp_out = logp;
2545 return std::exp(logp);
UnsupportedError(const std::string &what)
FluidNonHyperbolicError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::size_t nvars_of(std::size_t ind) const
Total local-variable width of node ind (1-based).
std::vector< JobClass > classes
std::size_t phasessz_of(std::size_t ist, std::size_t r) const
sn.phasessz(i,r) = max(sn.phases(i,r),1): THE WIDTH of class r's phase block in a state row,...
The exception types the port throws.
Age of Information by Markovian fluid queues: a port of solver_mfq_aoi.m (identical to solver_fluid_a...
Detects a moment-closure trajectory that has left the model.
The diffusion method: a port of solver_fluid_diffusion.m.
The matrix fluid method: a port of solver_fluid_matrix.m, the formulation of Ruuskanen,...
The mfq method: a port of solver_mfq.m and the single-queue gate fluid_is_single_queue....
The priority branch of the mfq method: a port of solver_mfq_prio.m.
Port of fluid_mvn_rectangle.m: the rectangle probability P(a <= Y <= b) for Y ~ Normal(m,...
The one exception the fluid fallback ladder catches.
The fluid drift: a port of solver_fluid_odes.m and the ode_jumps_new / ode_rate_base / ode_rates_clos...
The state-dependent fluid drifts: ports of ode_statedep.m, ode_softmin.m and ode_pnorm....
Port of ode_eliminate_immediate.m, eliminate_immediate_matrix.m and ode_solve_stiff....
The tbi method: a port of solver_fluid_tbi_iteration.m and tbi_partition.m.
LSODA: the LINE-facing wrapper over the vendored solver in third_party/lsoda.hpp.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
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...
@ 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.
double fluid_default_horizon(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
The horizon a transient runs to when the caller gives none.
std::vector< std::pair< std::size_t, std::size_t > > fluid_detect_nhpp(const qn::NetworkStruct< T > &sn)
Port of local_detect_nhpp in @@SolverFLD/getTranAvg.m: the (station, class) pairs whose SOURCE carrie...
FluidLayout fluid_layout(const qn::NetworkStruct< T > &sn)
Port of the layout half of solver_fluid_odes.m.
constexpr double kFluidConservationTol
Relative population drift that counts as having left the model.
std::vector< double > fluid_integrate_leg(const std::function< void(double, const double *, double *)> &f, double t0, double t1, const std::vector< double > &y0, const LsodaOptions &lopt)
One integration leg, with the reference's retry on a failed solve.
bool fluid_matrix_degenerate(const FluidMatrixSystem &s, const std::function< void(double, const double *, double *)> &drift, const std::vector< double > &x, std::size_t K)
Is the returned point one of a CONTINUUM of fixed points?
FluidMatrixSystem fluid_matrix_system(const qn::NetworkStruct< T > &sn, const std::vector< double > &init_sol, double pstar)
Assemble the matrix-form drift of sn.
void fluid_closing_metrics(const qn::NetworkStruct< T > &sn, const FluidOdeSystem &sys, const std::string &m, const std::vector< double > &xs, Matrix< double > &Q, Matrix< double > &U, Matrix< double > &R, Matrix< double > &T_)
Read Q/U/R/T off ONE fluid state, for the closing family.
ClosureValue fluid_capacity_closure(double n, double c, double s2, const std::vector< double > &lldrow, bool is_inf)
Port of fluid_capacity_closure.m: E[psi(X)] and its derivative, where psi(n) = min(n,...
double fluid_prob_aggr(const qn::NetworkStruct< T > &sn, const FluidSolution &sol, std::size_t ist, double *logp_out=nullptr)
Port of @@SolverFLD/getProbAggr: the probability that station ist holds the marginal population of th...
AoiTopology aoi_is_aoi(const qn::NetworkStruct< T > &sn)
Port of aoi_is_aoi.m.
StateDepKind
Which smoothing the drift applies at a saturated station.
double fluid_mvn_rectangle(const std::vector< double > &m, const Matrix< double > &C, const std::vector< double > &a, const std::vector< double > &b, std::size_t npoints=FLUID_MVN_POINTS)
P(a <= Y <= b) for Y ~ Normal(m, C).
DiffusionResult fluid_diffusion(const qn::NetworkStruct< T > &sn, const DiffusionOptions &opt)
Run the diffusion approximation of sn.
std::function< void(double, const double *, double *)> fluid_drift_statedep(const FluidStateDepSystem &s)
The drift dx/dt for the state-dependent family.
std::vector< std::vector< std::size_t > > tbi_check_partition(const qn::NetworkStruct< T > &sn, const std::vector< std::vector< std::size_t > > &cells)
The options.config.tbi_cells branch of tbi_partition.m: an explicit partition, returned as given once...
std::vector< FluidTranPoint > solver_fluid_transient(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, double t_end, std::size_t points=101, const std::vector< double > &out_grid=std::vector< double >())
Port of @@SolverFLD/getTranAvg: the metrics along the trajectory, not just at the fixed point.
FluidStateDepSystem fluid_statedep_system(const qn::NetworkStruct< T > &sn, StateDepKind kind, double alpha=20.0, double pstar=20.0)
Assemble what the state-dependent drifts need from sn.
FluidSolution solver_fluid(const qn::NetworkStruct< T > &sn_in, const FluidOptions &opt, qn::NetworkStruct< T > *sn_out=nullptr)
Port of solver_fluid_analyzer.m: dispatch on the method, refit the non-exponential FCFS stations the ...
MfqTopology mfq_is_single_queue(const qn::NetworkStruct< T > &sn)
Port of fluid_is_single_queue.m: the model must be one open class flowing Source -> Queue -> Sink and...
std::vector< FluidTranPoint > solver_fluid_tran_avg(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::size_t points=101)
getTranAvg on the first-order closing drift, over that horizon.
LsodaSolution fluid_integrate_grid(const std::function< void(double, const double *, double *)> &f, const std::vector< double > &y0, const std::vector< double > &grid, const LsodaOptions &lopt)
The same retry over a whole output grid, for the callers that ask LSODA for a trajectory rather than ...
FluidJacobian fluid_jacobian(const FluidSymSystem &sys, const FluidSymbolicOptions &opt=FluidSymbolicOptions())
Jacobian, drift and equilibria of the mean-field vector field.
int fluid_conservation_violation(const qn::NetworkStruct< T > &sn, const FluidLayout &L, const std::vector< double > &x, double tol=kFluidConservationTol)
The closed chain whose conserved population has drifted past tol, or -1.
MfqResult fluid_mfq(const qn::NetworkStruct< T > &sn, const MfqTopology &top, double tol)
Solve the single fluid queue of sn.
double fluid_prob_aggr_gaussian(const qn::NetworkStruct< T > &sn, const FluidSolution &sol, std::size_t i, const std::vector< double > &nir, double *logp_out=nullptr)
The joint probability of the per-class populations at station i (0-based) under the linear noise appr...
FluidOdeSystem fluid_ode_system(const qn::NetworkStruct< T > &sn)
Build the drift of sn: the port of ode_jumps_new and ode_rate_base fused into one pass.
std::function< void(double, const double *, double *)> fluid_drift(const FluidOdeSystem &sys)
The drift dx/dt, ready to hand to the integrator.
std::vector< std::vector< std::size_t > > tbi_partition(const qn::NetworkStruct< T > &sn, std::size_t cellsize=5)
Port of tbi_partition.m: stations grouped by routing coupling.
std::vector< double > tbi_advance(const FluidOdeSystem &sys, const std::vector< std::vector< std::size_t > > &cells, const std::vector< double > &y0, double t0, double t1, const TbiOptions &topt, const LsodaOptions &lopt)
Advance the state over [t0, t1] by time-based iteration.
void fluid_interpcols(const std::vector< double > &tg, const Matrix< double > &B, double tt, std::vector< double > &out)
Port of fluid_interpcols.m: clamped piecewise-linear interpolation of the columns of B at a scalar ti...
void fluid_rates_closing(const FluidOdeSystem &sys, const double *x, std::vector< double > &g)
The reference's ode_rates_closing name, kept for the first-order callers.
std::function< void(double, const double *, double *)> fluid_matrix_drift(const FluidMatrixSystem &s)
The drift dx/dt = W' theta(x) + A_lambda.
MfqPrioResult fluid_mfq_prio(const qn::NetworkStruct< T > &sn, const MfqTopology &top, double tol)
Solve the single priority fluid queue of sn.
FluidImmediateResult fluid_eliminate_immediate(const FluidOdeSystem &sys, double imm_tol=fluid_immediate_transition_tol())
bool fluid_hide_immediate(const qn::NetworkStruct< T > &sn, const Opt &opt)
Stochastic complementation of the INSTANTANEOUS coordinates of a fluid drift, the twin of ode_elimina...
OdeSolution< double > fluid_ode_solve_stiff(const std::function< void(double, const double *, double *)> &f, double t0, double t1, const std::vector< double > &y0, const FluidStiffOptions &opt=FluidStiffOptions())
Port of ode_solve_stiff.m.
FluidAoiResult fluid_aoi(const qn::NetworkStruct< T > &sn, const AoiTopology &top, double preempt_override)
Port of solver_mfq_aoi.m.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
NodeType
Node kinds, with the values of MATLAB NodeType.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
NonexpApproxResult< T > npfqn_nonexp_approx(const std::string &method, const std::vector< bool > &isFCFS, const Matrix< T > &rates, const Matrix< T > &ST, const Matrix< T > &V, const Matrix< T > &SCV, const Matrix< T > &Tput, const Matrix< T > &U, const std::vector< T > &gamma, const std::vector< T > &nservers)
Handler for non-exponential service and arrival processes in AMVA and NC.
MarieCoxFit< T > marie_cox_fit(const T &mean, const T &scv)
Closed-form Coxian fit of a mean and an SCV (matlab/src/lang/processes/Coxian.m, fitMeanAndSCV),...
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.
Conservation laws of a layered queueing network, enumerated from its structure.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
A queueing network and its refreshed NetworkStruct.
Handler for non-exponential service and arrival processes in AMVA and NC.
Marie's iterative aggregation-decomposition for closed networks with FCFS general (Coxian) service.
Replace every non-Markovian service and firing law by a Markovian surrogate.
Port of the MATLAB +State package: the encoding that turns a station's state row into marginal job co...
double atol
absolute tolerance, applied to every component
double rtol
relative tolerance, applied to every component
Result of an integration, mirroring OdeSolution in ode.h.
std::vector< std::vector< double > > y
y[i] is the state at t[i]
std::vector< double > t
output times, t[0] = t_eval[0]
options.config.nonmkv and friends.
std::size_t order
nonmkvorder, the phase budget
PhFit phfit
which surrogate family
Both age laws of one system, with the policy parameter that produced them.
The second moment the drift closes its non-linear terms with, i.e.
Where each (station, class) block sits in the state vector.
std::vector< std::vector< std::size_t > > qidx
0-based first index of (i,r)
std::size_t nstates
length of the state vector
std::vector< std::vector< bool > > enabled
whether (i,r) is served at all
std::vector< std::vector< std::size_t > > kic
phases held by (i,r); 0 when disabled
The second-order results of the moment-closure methods, i.e.
std::vector< std::vector< std::vector< std::size_t > > > class_block
state coordinates of each (station,class): Sigma is indexed by SERVICE PHASE, so reading a per-class ...
Matrix< double > Sigma
state-level covariance, on range(D)
Matrix< double > QStd
per station and class queue-length variance
std::vector< double > refinement
the 1/N correction, refined only
std::vector< double > sigma2
per-station population variance
FluidRateMult ratemult
solver_fluid_ratemult's multiplier; empty is the autonomous drift.
std::vector< std::vector< double > > lld
sn.lldscaling(i,:) per station, EMPTY when the station has none or when every entry is one – the refe...
options.config.rate_sched: explicit per-(station, class) rate trajectories, the third source solver_f...
std::size_t station
1-based
double nominal
The nominal baked into rate_base; <= 0 selects Mu{i}{c}(1).
std::vector< double > rates
std::vector< double > tgrid
Controls, defaulting to SolverOptions('Fluid') in the reference.
FluidClosure closure
options.config.moment_sigma2 and options.config.moment_cov: the second moment the drift's non-linear ...
std::vector< std::pair< std::size_t, std::size_t > > nhpp_sched
options.config.nhpp_sched: the (station, class) pairs whose SOURCE carries a non-homogeneous intensit...
std::vector< double > init_sol
initial state; empty selects the default below
double softmin_alpha
sharpness of the 'softmin' smoothing
std::string fork_join
options.config.fork_join: which fork-join arm the fixed point takes, 'default'/'mmt'/'fjt' or 'ht'.
std::vector< RateSched > rate_sched
std::string highvar
options.config.highvar: which non-exponential FCFS correction the analyzer's outer refit loop applies...
std::size_t nonmkv_order
options.config.nonmkvorder: the phase budget sn_nonmarkov_toph spends on a non-Markovian service law.
std::size_t dae_maxstate
options.config.dae_maxstate and options.config.dae_maxcov: the DAE route's own two refusal thresholds...
std::vector< double > kp_init_sol
options.config.kp_init_sol: the kp method's initial state, in the KO-PENDER layout – one offset count...
double pstar
exponent of the 'pnorm' smoothing
FluidRateMult rate_traj
options.config.rate_traj = {tgrid, Mmat}: a caller-supplied per-EVENT multiplier, which is what the c...
unsigned long seed
'diffusion' RNG seed
bool earlystop
options.config.fluid_earlystop: stop on the geometric tail of the window iteration
double iter_tol
>0 stops early when the moved-mass ratio falls below it; 0 runs to iter_max, as the reference does
double timestep
'diffusion' Euler-Maruyama step
double tol
absolute and relative tolerance handed to the integrator
bool hide_immediate
options.config.hide_immediate: fold the Immediate-rate transitions into the timed ones by stochastic ...
double aoi_preemption
options.config.aoi_preemption: the preemption (bufferless) or replacement (single buffer) probability...
Matrix< double > init_qcov
options.config.init_qcov: the companion covariance of init_qlen, over the station-class pairs,...
std::size_t iter_max
cap on outer integrations
std::vector< std::vector< std::size_t > > tbi_cells
options.config.tbi_cells: an explicit partition for method tbi, as disjoint 0-BASED station index set...
Matrix< double > init_qlen
options.config.init_qlen: the kp method's initial state over (station, class) PAIRS,...
bool pstar_set
Opt in to the p-norm under matrix/default too, which is what options.config.pstar does in MATLAB,...
bool stiff
options.stiff: integrate the closing family with the explicit stiff arm of fluid_stiff....
Matrix< double > init_cov
options.config.init_cov: the kp method's initial covariance Sigma(0), dim-by-dim in the same layout a...
std::size_t moment_maxstate
options.config.moment_maxstate: the largest phase-resolved state the moment-closure methods will buil...
The assembled drift: the layout, the events, and the per-station schedule.
What the analyzer returns, in the same shape as the MVA solver's result.
bool has_moments
result.solverSpecific.moments: set only by minnormal and refined.
bool has_aoi
result.solverSpecific.aoiResults: set only by the AoI branch of mfq, where the age laws,...
FluidMomentReport moments
std::vector< double > xvec
the converged fluid state
std::size_t refit_sweeps
iter of solver_fluid_analyzer.m: the FCFS non-exponential refit sweeps.
One point of a transient trajectory: the metrics at time t.
Matrix< double > QCov
The same second moment as a FULL (M*K)-by-(M*K) covariance, indexed ir = r*M + i, empty wherever QVar...
Matrix< double > QVar
Per-(station,class) queue-length VARIANCE at this instant, empty where the method carries no second m...
static Distrib coxian(const std::vector< T > &mu, const std::vector< T > &phi)
Coxian(mu, phi): phase i completes with probability phi(i) and otherwise moves to phase i+1.
static constexpr double Immediate
Rate of an Immediate distribution; its mean is 1/Immediate = 1e-8.
static constexpr double FineTol
static constexpr double Zero
static constexpr double CoarseTol
A MAP as the pair of matrices (D0, D1).