5#ifndef LINE_SOLVERS_FLUID_SOLVER_FLUID_H
6#define LINE_SOLVERS_FLUID_SOLVER_FLUID_H
286 std::vector<std::pair<std::size_t, std::size_t> >
nhpp_sched;
427 const std::size_t M =
sn.nstations, K =
sn.nclasses;
428 const double inf = std::numeric_limits<double>::infinity();
429 std::vector<std::vector<double>> nplace(M, std::vector<double>(K, 0.0));
430 std::vector<double> totplace(M, 0.0);
433 const bool has_cap =
sn.cap.size() == M &&
sn.classcap.size() == M;
435 for (std::size_t r = 0; r < K; ++r) {
436 const double pop =
sn.classes[r].population;
437 if (!std::isfinite(pop))
continue;
440 std::vector<std::size_t> order;
441 const std::size_t rs =
sn.classes[r].refstat;
442 if (rs >= 1 && rs <= M) order.push_back(rs - 1);
443 for (std::size_t i = 0; i < M; ++i)
444 if (order.empty() || i != order[0]) order.push_back(i);
452 nplace[rs - 1][r] = pop;
453 totplace[rs - 1] += pop;
457 double remaining = pop;
458 for (std::size_t oi = 0; oi < order.size() && remaining > 0.0; ++oi) {
459 const std::size_t j = order[oi];
468 if (!L.
enabled[j][r])
continue;
469 const double ccap = (has_cap &&
sn.classcap[j].size() > r) ?
sn.classcap[j][r] : inf;
470 const double scap = has_cap ?
sn.cap[j] : inf;
471 const double avail = std::min(ccap - nplace[j][r], scap - totplace[j]);
472 const double take = std::min(remaining, std::max(0.0, avail));
473 nplace[j][r] += take;
478 throw InputError(
"solver_fluid_initsol: cannot place the population of class '" +
480 "': the total capacity of the stations that serve it is insufficient");
531 const std::vector<std::vector<double>>& nplace,
532 std::vector<double>& y0) {
533 const std::size_t M =
sn.nstations, K =
sn.nclasses;
535 std::vector<double> out(L.nstates, 0.0);
536 for (std::size_t i = 0; i < M; ++i) {
537 const std::size_t ind =
sn.node_of_station(i + 1);
539 const typename std::map<std::size_t, Matrix<T>>::const_iterator ss =
540 sn.statespace.find(ind);
541 const typename std::map<std::size_t, std::vector<T>>::const_iterator sp =
542 sn.stateprior.find(ind);
543 std::size_t pick =
static_cast<std::size_t
>(-1);
544 if (ss !=
sn.statespace.end() && sp !=
sn.stateprior.end() && ss->second.cols() > 0 &&
545 ss->second.rows() == sp->second.size()) {
546 for (std::size_t r = 0; r < ss->second.rows() && pick ==
static_cast<std::size_t
>(-1);
551 bool decoded =
false;
552 if (pick !=
static_cast<std::size_t
>(-1)) {
553 std::vector<T> row(ss->second.cols());
554 for (std::size_t c = 0; c < ss->second.cols(); ++c) row[c] = ss->second(pick, c);
555 std::vector<std::size_t> ph(K, 1), shift(K, 0);
557 for (std::size_t r = 0; r < K; ++r) {
564 decoded = m.nir.size() == K && m.kir.size() == K;
565 }
catch (
const Error&) {
569 for (std::size_t r = 0; r < K; ++r) {
570 if (!L.enabled[i][r])
continue;
574 if (!ext && nplace[i][r] > 0.0) out[L.qidx[i][r]] = nplace[i][r];
575 if (ext && !std::isfinite(sn.
classes[r].population)) out[L.qidx[i][r]] = 1.0;
578 const std::size_t np = L.kic[i][r];
579 for (std::size_t k = 0; k < np; ++k) {
581 if (k < m.kir[r].size()) v = num_traits<T>::to_double(m.kir[r][k]);
582 if (k == 0 && !ext) {
585 for (std::size_t j = 1; j < m.kir[r].size(); ++j)
586 served += num_traits<T>::to_double(m.kir[r][j]);
587 v = num_traits<T>::to_double(m.nir[r]) - served;
591 if (!std::isfinite(v)) v = 0.0;
592 out[L.qidx[i][r] + k] = v;
597 if (!any)
return false;
604std::vector<double> fluid_default_initsol(
const qn::NetworkStruct<T>& sn,
const FluidLayout& L) {
605 const std::size_t M = sn.nstations, K = sn.nclasses;
606 std::vector<double> y0(L.nstates, 0.0);
607 const std::vector<std::vector<double>> nplace = fluid_initsol_placement(sn, L);
608 if (fluid_declared_initsol(sn, L, nplace, y0))
return y0;
609 for (std::size_t r = 0; r < K; ++r) {
610 if (std::isfinite(sn.classes[r].population)) {
617 for (std::size_t i = 0; i < M; ++i)
618 if (L.enabled[i][r] && nplace[i][r] > 0.0) y0[L.qidx[i][r]] = nplace[i][r];
621 for (std::size_t i = 0; i < M; ++i)
623 y0[L.qidx[i][r]] = 1.0;
643inline void fluid_snap_fine(Matrix<double>& m) {
644 for (std::size_t i = 0; i < m.rows(); ++i)
645 for (std::size_t j = 0; j < m.cols(); ++j)
658inline void fluid_snap_all(Matrix<double>& q, Matrix<double>& u, Matrix<double>& r,
664 for (std::size_t i = 0; i < r.rows(); ++i)
665 for (std::size_t j = 0; j < r.cols(); ++j)
666 if (t(i, j) == 0.0 && q(i, j) == 0.0) r(i, j) = 0.0;
687std::vector<char> fluid_visited_pairs(
const qn::NetworkStruct<T>& sn, std::size_t M, std::size_t K) {
688 std::vector<char> visited(M * K, 0);
690 std::size_t max_cols = 0;
691 for (std::size_t c = 0; c < sn.visits.size(); ++c) {
692 const Matrix<T>& Vc = sn.visits[c];
693 if (Vc.rows() == 0 || Vc.cols() == 0)
continue;
695 max_cols = std::max(max_cols, Vc.cols());
696 for (std::size_t i = 0; i < M; ++i) {
697 const std::size_t isf = sn.stateful_of_station(i + 1);
698 if (isf == 0 || isf > Vc.rows()) {
699 for (std::size_t r = 0; r < K; ++r) visited[i * K + r] = 1;
702 for (std::size_t r = 0; r < K && r < Vc.cols(); ++r)
703 if (std::fabs(num_traits<T>::to_double(Vc(isf - 1, r))) >
705 visited[i * K + r] = 1;
709 std::fill(visited.begin(), visited.end(),
static_cast<char>(1));
713 for (std::size_t i = 0; i < M; ++i)
714 for (std::size_t r = max_cols; r < K; ++r) visited[i * K + r] = 1;
744void fluid_analyzer_correct(
const qn::NetworkStruct<T>& sn,
const Matrix<double>& Q,
745 Matrix<double>& U, Matrix<double>& R,
const Matrix<double>& T_) {
746 const std::size_t M = Q.rows(), K = Q.cols();
749 const std::vector<char> visited = fluid_visited_pairs(sn, M, K);
750 const Matrix<double> U0 = U;
751 for (std::size_t i = 0; i < M; ++i) {
752 double u0sum = 0.0, share_den = 0.0;
753 for (std::size_t r = 0; r < K; ++r) {
754 if (!(Q(i, r) > 0.0) || !visited[i * K + r])
continue;
756 const double rate = num_traits<T>::to_double(sn.rates(i, r));
757 if (rate != 0.0) share_den += T_(i, r) / rate;
764 double c = sn.stations[i].nservers;
765 for (std::size_t k = 0; k < sn.stations[i].lldscaling.size(); ++k)
766 c = std::max(c, num_traits<T>::to_double(sn.stations[i].lldscaling[k]));
768 for (std::size_t r = 0; r < K; ++r) {
769 if (!(Q(i, r) > 0.0) || !visited[i * K + r]) {
779 if (std::isfinite(c) && c > 0.0) best = std::min(best, Q(i, r) / c);
780 const double rate = num_traits<T>::to_double(sn.rates(i, r));
781 if (rate != 0.0 && share_den != 0.0)
782 best = std::min(best, u0sum * (T_(i, r) / rate) / share_den);
784 if (T_(i, r) != 0.0) R(i, r) = Q(i, r) / T_(i, r);
787 for (std::size_t i = 0; i < M; ++i)
788 for (std::size_t r = 0; r < K; ++r) {
789 if (std::isnan(U(i, r))) U(i, r) = 0.0;
790 if (std::isnan(R(i, r))) R(i, r) = 0.0;
807 const std::string& m,
const std::vector<double>& xs,
Matrix<double>& Q,
809 const std::size_t M =
sn.nstations, K =
sn.nclasses;
816 for (std::size_t i = 0; i < M; ++i)
817 for (std::size_t r = 0; r < K; ++r) {
819 for (std::size_t k = 0; k < L.
kic[i][r]; ++k) q += xs[L.
qidx[i][r] + k];
824 std::vector<std::vector<std::vector<double>>> xservice(M, std::vector<std::vector<double>>(K));
825 for (std::size_t i = 0; i < M; ++i) {
828 for (std::size_t r = 0; r < K; ++r) xi += Q(i, r);
830 for (std::size_t r = 0; r < K; ++r)
831 wxi += (r <
sn.stations[i].schedparam.size()
835 const double c =
sn.stations[i].nservers;
848 std::vector<double> wmean(K, 0.0);
851 for (std::size_t r = 0; r < K; ++r) {
852 if (!L.
enabled[i][r])
continue;
854 const std::size_t nn =
sn.service[i][r].D0.rows();
857 for (std::size_t a = 0; a < nn; ++a)
858 for (std::size_t bb = 0; bb < nn; ++bb) {
863 wni += wmean[r] * Q(i, r);
867 for (std::size_t r = 0; r < K; ++r) {
868 xservice[i][r].assign(L.
kic[i][r], 0.0);
869 if (!L.
enabled[i][r])
continue;
870 std::vector<double> mu, phi;
871 detail::fluid_mu_phi(
sn.service[i][r], mu, phi);
872 const std::size_t b = L.
qidx[i][r], n = L.
kic[i][r];
875 for (std::size_t k = 0; k < n; ++k)
876 xservice[i][r][k] = xs[b + k] * mu[k] * wmean[r] / wni * served;
878 for (std::size_t k = 0; k < n; ++k) s2 += xservice[i][r][k];
882 for (std::size_t k = 0; k < n; ++k) {
883 double mass = xs[b + k];
889 for (std::size_t p = 1; p < n; ++p) rest += xs[b + p];
892 tn += mass * mu[k] * phi[k];
893 xservice[i][r][k] = mass * mu[k];
896 tn += mass * mu[k] * phi[k];
897 xservice[i][r][k] = mass * mu[k];
900 const double w = r <
sn.stations[i].schedparam.size()
904 tn += mass * mu[k] * phi[k] * w / wxi * served;
905 xservice[i][r][k] = mass * mu[k] * w / wxi * served;
911 tn += mass * mu[k] * phi[k] / xi * served;
912 xservice[i][r][k] = mass * mu[k] / xi * served;
923 for (std::size_t i = 0; i < M; ++i) {
925 for (std::size_t r = 0; r < K; ++r) {
926 if (!L.
enabled[i][r])
continue;
927 std::vector<double> mu, phi;
928 detail::fluid_mu_phi(
sn.service[i][r], mu, phi);
930 for (std::size_t k = 0; k < xservice[i][r].size(); ++k)
931 if (xservice[i][r][k] > 0.0 && mu[k] > 0.0) u += xservice[i][r][k] / mu[k];
934 double c =
sn.stations[i].nservers;
935 for (std::size_t k = 0; k < sys.
lld[i].size(); ++k) c = std::max(c, sys.
lld[i][k]);
936 U(i, r) = (is_delay || !std::isfinite(c) || c <= 0.0) ? u : u / c;
941 for (std::size_t i = 0; i < M; ++i)
942 for (std::size_t r = 0; r < K; ++r)
943 if (T_(i, r) > 0.0) R(i, r) = Q(i, r) / T_(i, r);
951 for (std::size_t i = 0; i < M; ++i) {
953 if (nt != qn::NodeType::Source && nt != qn::NodeType::Sink)
continue;
954 for (std::size_t r = 0; r < K; ++r) {
961 detail::fluid_snap_all(Q, U, R, T_);
978inline void fluid_nhpp_steps(
const std::vector<double>& bp,
const std::vector<double>& seg_rate,
979 bool cyclic,
double t0,
double thi, std::vector<double>& seg_t,
980 std::vector<double>& seg_r) {
983 if (bp.size() < 2 || seg_rate.empty() || !(thi > t0))
return;
984 const double period = bp.back() - bp.front();
985 std::vector<double> bounds;
986 bounds.push_back(t0);
987 if (cyclic && period > 0.0) {
988 const long kmax =
static_cast<long>(std::ceil((thi - t0) / period)) + 2;
989 for (
long k = -1; k <= kmax; ++k)
990 for (std::size_t a = 0; a < bp.size(); ++a)
991 bounds.push_back(bp[a] +
static_cast<double>(k) * period);
993 for (std::size_t a = 0; a < bp.size(); ++a) bounds.push_back(bp[a]);
995 bounds.push_back(thi);
996 std::sort(bounds.begin(), bounds.end());
997 bounds.erase(std::remove_if(bounds.begin(), bounds.end(),
998 [&](
double v) { return v < t0 || v > thi; }),
1000 bounds.erase(std::unique(bounds.begin(), bounds.end()), bounds.end());
1001 if (bounds.size() < 2)
return;
1005 const auto rate_at = [&](
double t) ->
double {
1006 double offset = t - bp.front();
1009 offset = std::fmod(offset, period);
1010 if (offset < 0.0) offset += period;
1014 }
else if (offset < 0.0 || offset >= period) {
1017 const double pos = bp.front() + offset;
1018 std::size_t idx = seg_rate.size() - 1;
1019 for (std::size_t k = 1; k < bp.size(); ++k)
1024 return idx < seg_rate.size() ? seg_rate[idx] : 0.0;
1027 const double neps = std::max(1e-9, 1e-6 * (thi - t0));
1028 for (std::size_t k = 0; k + 1 < bounds.size(); ++k) {
1029 const double a = bounds[k], b = bounds[k + 1];
1030 const double r = rate_at(0.5 * (a + b));
1033 seg_t.push_back(std::max(a + neps, b - neps));
1039inline FluidRateMult fluid_ratemult_merge(
const FluidRateMult& a,
const FluidRateMult& b,
1040 std::size_t nevents) {
1041 if (a.empty())
return b;
1042 if (b.empty())
return a;
1043 std::vector<double> tg = a.tgrid;
1044 tg.insert(tg.end(), b.tgrid.begin(), b.tgrid.end());
1045 std::sort(tg.begin(), tg.end());
1046 tg.erase(std::unique(tg.begin(), tg.end()), tg.end());
1049 out.Mmat = Matrix<double>(nevents, tg.size(), 1.0);
1050 std::vector<double> ca, cb;
1051 for (std::size_t j = 0; j < tg.size(); ++j) {
1054 for (std::size_t e = 0; e < nevents; ++e) {
1055 const double va = e < ca.size() ? ca[e] : 1.0;
1056 const double vb = e < cb.size() ? cb[e] : 1.0;
1057 out.Mmat(e, j) = va * vb;
1072inline bool fluid_has_time_varying_rates(
const FluidOptions& opt) {
1073 return !opt.rate_traj.empty() || !opt.nhpp_sched.empty() || !opt.rate_sched.empty();
1077inline std::vector<std::size_t> fluid_events_of(
const FluidOdeSystem& sys, std::size_t i,
1079 std::vector<std::size_t> rows;
1080 const std::size_t lo = sys.layout.qidx[i][c];
1081 const std::size_t hi = lo + sys.layout.kic[i][c];
1082 for (std::size_t e = 0; e < sys.events.size(); ++e)
1083 if (sys.events[e].event_idx >= lo && sys.events[e].event_idx < hi) rows.push_back(e);
1088inline FluidRateMult fluid_ratemult_rows(
const FluidOdeSystem& sys, std::size_t i, std::size_t c,
1089 const std::vector<double>& seg_t,
1090 const std::vector<double>& seg_r,
double nominal) {
1092 if (seg_t.empty() || !(nominal > 0.0))
return out;
1093 const std::vector<std::size_t> rows = fluid_events_of(sys, i, c);
1094 if (rows.empty())
return out;
1096 out.Mmat = Matrix<double>(sys.events.size(), seg_t.size(), 1.0);
1097 for (std::size_t j = 0; j < seg_t.size(); ++j)
1098 for (std::size_t e : rows) out.Mmat(e, j) = seg_r[j] / nominal;
1117FluidRateMult fluid_ratemult(
const qn::NetworkStruct<T>& sn,
const FluidOdeSystem& sys,
1118 const FluidOptions& opt) {
1119 const std::size_t nev = sys.events.size();
1121 if (!opt.rate_traj.empty()) {
1122 if (opt.rate_traj.Mmat.rows() != nev)
1123 throw InputError(
"solver_fluid_ratemult: rate_traj has " +
1124 std::to_string(opt.rate_traj.Mmat.rows()) +
1125 " rows but the closing ODE has " + std::to_string(nev) +
" events");
1126 out = opt.rate_traj;
1133 const double tend = opt.timespan_end;
1136 for (std::size_t a = 0; a < opt.nhpp_sched.size(); ++a) {
1137 const std::size_t i = opt.nhpp_sched[a].first - 1, c = opt.nhpp_sched[a].second - 1;
1138 if (i >= sn.nstations || c >= sn.nclasses)
continue;
1139 if (!sys.layout.enabled[i][c])
continue;
1140 const lang::Distrib<T>& d = sn.service[i][c];
1141 if (!d.has_schedule())
continue;
1142 const std::vector<T> mu = d.mu_vec();
1143 if (mu.empty())
continue;
1144 const double nominal = num_traits<T>::to_double(mu[0]);
1145 if (!(nominal > 0.0))
continue;
1147 std::vector<double> bp, seg_r;
1148 for (std::size_t k = 0; k < d.sched_bp.size(); ++k)
1149 bp.push_back(num_traits<T>::to_double(d.sched_bp[k]));
1152 for (std::size_t k = 0; k < d.sched_D0.size(); ++k) {
1154 m.D0 = d.sched_D0[k];
1155 m.D1 = d.sched_D1[k];
1158 const double period = bp.empty() ? 0.0 : bp.back() - bp.front();
1160 if (!std::isfinite(thi))
1161 thi = (std::isfinite(period) && period > 0.0) ? t0 + 3.0 * period : t0 + 1.0;
1162 std::vector<double> seg_t, seg_v;
1163 fluid_nhpp_steps(bp, seg_r, d.sched_cyclic, t0, thi, seg_t, seg_v);
1164 nh = fluid_ratemult_merge(nh, fluid_ratemult_rows(sys, i, c, seg_t, seg_v, nominal), nev);
1168 for (std::size_t a = 0; a < opt.rate_sched.size(); ++a) {
1169 const FluidOptions::RateSched& e = opt.rate_sched[a];
1170 const std::size_t i = e.station - 1, c = e.cls - 1;
1171 if (i >= sn.nstations || c >= sn.nclasses)
continue;
1172 if (!sys.layout.enabled[i][c])
continue;
1173 if (e.tgrid.size() != e.rates.size() || e.tgrid.empty())
1174 throw InputError(
"solver_fluid_ratemult: rate_sched tgrid and rates must be "
1175 "non-empty and of equal length");
1176 double nominal = e.nominal;
1177 if (!(nominal > 0.0)) {
1178 const std::vector<T> mu = sn.service[i][c].mu_vec();
1179 if (mu.empty())
continue;
1180 nominal = num_traits<T>::to_double(mu[0]);
1182 if (!(nominal > 0.0))
continue;
1183 rs = fluid_ratemult_merge(rs, fluid_ratemult_rows(sys, i, c, e.tgrid, e.rates, nominal),
1187 out = fluid_ratemult_merge(out, nh, nev);
1188 out = fluid_ratemult_merge(out, rs, nev);
1199double fluid_slow_rate(
const qn::NetworkStruct<T>& sn,
const FluidLayout& L,
double tol) {
1200 double min_rate = std::numeric_limits<double>::infinity();
1201 for (std::size_t i = 0; i < sn.nstations; ++i)
1202 for (std::size_t r = 0; r < sn.nclasses; ++r) {
1203 if (!L.enabled[i][r])
continue;
1204 const lang::Distrib<T>& d = sn.service[i][r];
1205 for (std::size_t k = 0; k < d.D0.rows(); ++k) {
1206 const double mu = -num_traits<T>::to_double(d.D0(k, k));
1207 if (mu > tol && std::isfinite(mu)) min_rate = std::min(min_rate, mu);
1210 return std::isfinite(min_rate) ? min_rate : 1.0;
1223FluidSolution fluid_dispatch(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt) {
1224 if (!std::is_same<T, double>::value)
1226 "solver_fluid: the fluid solver integrates its drift with LSODA, whose coefficients "
1227 "assume double precision; rerun with --arith double");
1229 std::string m = opt.method;
1230 if (m.compare(0, 4,
"fld.") == 0) m = m.substr(4);
1232 bool statedep_family =
false;
1244 bool has_dps =
false, has_cache =
false;
1245 for (
const auto& st : sn.stations)
1247 for (
const qn::NodeDef& nd : sn.nodes)
1248 if (nd.nodetype == qn::NodeType::Cache) has_cache =
true;
1249 if ((m ==
"matrix" || m ==
"pnorm") && has_dps)
1251 "solver_fluid: the matrix method does not support DPS scheduling; use method "
1252 "'closing' (which is what 'default' selects on a DPS model)");
1253 if (m ==
"default") {
1256 "solver_fluid: a Cache model resolves to the 'rmf' fluid method, which is ported "
1257 "in fluid_cacheqn.h and reached through solver_fluid_run_analyzer (fluid_runner.h); this "
1258 "function is solver_fluid_analyzer alone and cannot call it without a cyclic "
1260 if (has_dps) m =
"closing";
1262 const bool matrix_family = (m ==
"default" || m ==
"matrix" || m ==
"pnorm");
1263 if (m ==
"statedep") {
1264 statedep_family =
true;
1266 }
else if (m ==
"softmin") {
1267 statedep_family =
true;
1269 }
else if (!(matrix_family || m ==
"closing" || m ==
"tbi" || m ==
"diffusion" || m ==
"mfq")) {
1270 throw UnsupportedError(ErrorKind{
"Method"},
"solver_fluid: the '" + opt.method +
1271 "' fluid method is not solved here; available are 'closing', "
1272 "'statedep', 'softmin', 'pnorm', 'matrix', 'tbi', 'diffusion' and "
1273 "'mfq', while 'rmf', 'minnormal', 'refined', 'dae' and 'kp' are "
1274 "reached through solver_fluid_run_analyzer (fluid_runner.h), which is the "
1275 "port of runAnalyzer's resolution");
1278 const std::size_t M = sn.nstations, K = sn.nclasses;
1283 sys.closure = opt.closure;
1287 sys.ratemult = detail::fluid_ratemult(sn, sys, opt);
1288 const FluidLayout& L = sys.layout;
1290 throw InputError(
"solver_fluid: no station serves any class, so the drift is empty");
1293 const double min_rate = fluid_slow_rate(sn, L, opt.tol);
1296 std::vector<double> x = opt.init_sol.empty() ? detail::fluid_default_initsol(sn, L) : opt.init_sol;
1297 if (x.size() != L.nstates)
1298 throw InputError(
"solver_fluid: init_sol has " + std::to_string(x.size()) +
1299 " entries but the fluid state has " + std::to_string(L.nstates));
1308 const FluidAoiResult ar =
fluid_aoi(sn, atop, opt.aoi_preemption);
1314 out.QN = Matrix<double>(M, K, 0.0);
1315 out.UN = Matrix<double>(M, K, 0.0);
1316 out.RN = Matrix<double>(M, K, 0.0);
1317 out.TN = Matrix<double>(M, K, 0.0);
1318 out.XN.assign(K, 0.0);
1319 out.CN.assign(K, 0.0);
1320 for (std::size_t r = 0; r < K; ++r) {
1321 out.QN(atop.queue, r) = ar.QN[r];
1322 out.UN(atop.queue, r) = ar.UN[r];
1323 out.RN(atop.queue, r) = ar.RN[r];
1324 out.TN(atop.queue, r) = ar.TN[r];
1325 out.TN(atop.source, r) = ar.TN[r];
1326 out.XN[r] = ar.TN[r];
1327 out.CN[r] = ar.RN[r];
1338 FluidOptions mopt = opt;
1339 mopt.method =
"matrix";
1340 return fluid_dispatch(sn, mopt);
1345 bool mixed_prio =
false;
1347 for (std::size_t j = 1; j < top.open_classes.size(); ++j)
1348 if (sn.classes[top.open_classes[j]].prio != sn.classes[top.open_classes[0]].prio)
1354 FluidOptions fb = opt;
1355 fb.method =
"matrix";
1361 out.QN = Matrix<double>(M, K, 0.0);
1362 out.UN = Matrix<double>(M, K, 0.0);
1363 out.RN = Matrix<double>(M, K, 0.0);
1364 out.TN = Matrix<double>(M, K, 0.0);
1365 out.XN.assign(K, 0.0);
1366 out.CN.assign(K, 0.0);
1367 for (std::size_t r = 0; r < K; ++r) {
1368 out.QN(top.queue, r) = pr.QN[r];
1369 out.TN(top.queue, r) = pr.TN[r];
1370 out.TN(top.source, r) = pr.TN[r];
1371 out.XN[r] = pr.TN[r];
1380 for (std::size_t r = 0; r < K; ++r) {
1381 out.UN(top.queue, r) = pr.UN[r];
1382 out.RN(top.queue, r) = pr.RN[r];
1383 if (pr.QN[r] > 0.0) out.CN[r] = inf ? pr.RN[r] : pr.QN[r] / pr.TN[r];
1391 FluidOptions mopt = opt;
1392 mopt.method =
"matrix";
1393 return fluid_dispatch(sn, mopt);
1395 const MfqResult r =
fluid_mfq(sn, top, opt.tol);
1399 out.QN = Matrix<double>(M, K, 0.0);
1400 out.UN = Matrix<double>(M, K, 0.0);
1401 out.RN = Matrix<double>(M, K, 0.0);
1402 out.TN = Matrix<double>(M, K, 0.0);
1403 out.QN(top.queue, top.cls) = r.QN;
1404 out.UN(top.queue, top.cls) = r.UN;
1405 out.RN(top.queue, top.cls) = r.RN;
1406 out.TN(top.queue, top.cls) = r.TN;
1407 out.TN(top.source, top.cls) = r.TN;
1408 out.XN.assign(K, 0.0);
1409 out.CN.assign(K, 0.0);
1410 out.XN[top.cls] = r.TN;
1411 out.CN[top.cls] = r.RN;
1416 if (m ==
"diffusion") {
1417 DiffusionOptions dopt;
1419 if (std::isfinite(opt.timespan_end) && opt.timestep > 0.0)
1420 dopt.steps = std::max<std::size_t>(2,
static_cast<std::size_t
>(std::ceil(opt.timespan_end / opt.timestep)));
1421 dopt.dt = opt.timestep;
1422 dopt.seed = opt.seed;
1426 out.method =
"diffusion";
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.
Fork-join section metrics: FJQLen, FJRespT and FJTput of every Fork/Join pair.
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....
Port of fluid_mfq_admits.m and fluid_is_single_queue.m: can the mfq fluid method (and its alias aoi) ...
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...
bool nonmkv_enabled(const std::string &method)
Resolve options.config.nonmkv to NonmarkovOptions::enabled.
@ 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.
FluidMfqAdmits fluid_mfq_admits(const qn::NetworkStruct< T > &sn)
Port of fluid_mfq_admits.m.
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())
Stochastic complementation of the INSTANTANEOUS coordinates of a fluid drift, the twin of ode_elimina...
bool fluid_hide_immediate(const qn::NetworkStruct< T > &sn, const Opt &opt)
Whether this model's fluid drift is built on the stochastic complement of its INSTANTANEOUS coordinat...
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...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
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
The assembled drift: the layout, the events, and the per-station schedule.
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 ...
std::string fluid_boundary
options.config.fluid_boundary: how the Petri dae route reports a fluid place level held at a bound,...
std::string nonmkv
options.config.nonmkv: 'bernstein' (default) runs sn_nonmarkov_toph, 'none' skips it; any other name ...
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...
Port of solver_fluid_ratemult.m's output: a per-event MULTIPLIER on a time grid, evaluated by fluid_i...
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::string warning
The reference's line_warning text, verbatim, empty when it did not warn.
std::vector< double > xvec
the converged fluid state
solvers::FjMetrics< double > fj
self.result.FJ, set by the fork-join arm (fluid_fork_join_run); empty otherwise.
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).
self.result.FJ: one row per Fork/Join pair, one column per class.