5#ifndef LINE_SOLVERS_FLUID_SOLVER_FLUID_H
6#define LINE_SOLVERS_FLUID_SOLVER_FLUID_H
233 std::vector<std::pair<std::size_t, std::size_t> >
nhpp_sched;
359 const std::size_t M =
sn.nstations, K =
sn.nclasses;
360 const double inf = std::numeric_limits<double>::infinity();
361 std::vector<std::vector<double>> nplace(M, std::vector<double>(K, 0.0));
362 std::vector<double> totplace(M, 0.0);
365 const bool has_cap =
sn.cap.size() == M &&
sn.classcap.size() == M;
367 for (std::size_t r = 0; r < K; ++r) {
368 const double pop =
sn.classes[r].population;
369 if (!std::isfinite(pop))
continue;
372 std::vector<std::size_t> order;
373 const std::size_t rs =
sn.classes[r].refstat;
374 if (rs >= 1 && rs <= M) order.push_back(rs - 1);
375 for (std::size_t i = 0; i < M; ++i)
376 if (order.empty() || i != order[0]) order.push_back(i);
384 nplace[rs - 1][r] = pop;
385 totplace[rs - 1] += pop;
389 double remaining = pop;
390 for (std::size_t oi = 0; oi < order.size() && remaining > 0.0; ++oi) {
391 const std::size_t j = order[oi];
400 if (!L.
enabled[j][r])
continue;
401 const double ccap = (has_cap &&
sn.classcap[j].size() > r) ?
sn.classcap[j][r] : inf;
402 const double scap = has_cap ?
sn.cap[j] : inf;
403 const double avail = std::min(ccap - nplace[j][r], scap - totplace[j]);
404 const double take = std::min(remaining, std::max(0.0, avail));
405 nplace[j][r] += take;
410 throw InputError(
"solver_fluid_initsol: cannot place the population of class '" +
412 "': the total capacity of the stations that serve it is insufficient");
463 const std::vector<std::vector<double>>& nplace,
464 std::vector<double>& y0) {
465 const std::size_t M =
sn.nstations, K =
sn.nclasses;
467 std::vector<double> out(L.nstates, 0.0);
468 for (std::size_t i = 0; i < M; ++i) {
469 const std::size_t ind =
sn.node_of_station(i + 1);
471 const typename std::map<std::size_t, Matrix<T>>::const_iterator ss =
472 sn.statespace.find(ind);
473 const typename std::map<std::size_t, std::vector<T>>::const_iterator sp =
474 sn.stateprior.find(ind);
475 std::size_t pick =
static_cast<std::size_t
>(-1);
476 if (ss !=
sn.statespace.end() && sp !=
sn.stateprior.end() && ss->second.cols() > 0 &&
477 ss->second.rows() == sp->second.size()) {
478 for (std::size_t r = 0; r < ss->second.rows() && pick ==
static_cast<std::size_t
>(-1);
483 bool decoded =
false;
484 if (pick !=
static_cast<std::size_t
>(-1)) {
485 std::vector<T> row(ss->second.cols());
486 for (std::size_t c = 0; c < ss->second.cols(); ++c) row[c] = ss->second(pick, c);
487 std::vector<std::size_t> ph(K, 1), shift(K, 0);
489 for (std::size_t r = 0; r < K; ++r) {
496 decoded = m.nir.size() == K && m.kir.size() == K;
497 }
catch (
const Error&) {
501 for (std::size_t r = 0; r < K; ++r) {
502 if (!L.enabled[i][r])
continue;
506 if (!ext && nplace[i][r] > 0.0) out[L.qidx[i][r]] = nplace[i][r];
507 if (ext && !std::isfinite(sn.
classes[r].population)) out[L.qidx[i][r]] = 1.0;
510 const std::size_t np = L.kic[i][r];
511 for (std::size_t k = 0; k < np; ++k) {
513 if (k < m.kir[r].size()) v = num_traits<T>::to_double(m.kir[r][k]);
514 if (k == 0 && !ext) {
517 for (std::size_t j = 1; j < m.kir[r].size(); ++j)
518 served += num_traits<T>::to_double(m.kir[r][j]);
519 v = num_traits<T>::to_double(m.nir[r]) - served;
523 if (!std::isfinite(v)) v = 0.0;
524 out[L.qidx[i][r] + k] = v;
529 if (!any)
return false;
536std::vector<double> fluid_default_initsol(
const qn::NetworkStruct<T>& sn,
const FluidLayout& L) {
537 const std::size_t M = sn.nstations, K = sn.nclasses;
538 std::vector<double> y0(L.nstates, 0.0);
539 const std::vector<std::vector<double>> nplace = fluid_initsol_placement(sn, L);
540 if (fluid_declared_initsol(sn, L, nplace, y0))
return y0;
541 for (std::size_t r = 0; r < K; ++r) {
542 if (std::isfinite(sn.classes[r].population)) {
549 for (std::size_t i = 0; i < M; ++i)
550 if (L.enabled[i][r] && nplace[i][r] > 0.0) y0[L.qidx[i][r]] = nplace[i][r];
553 for (std::size_t i = 0; i < M; ++i)
555 y0[L.qidx[i][r]] = 1.0;
575inline void fluid_snap_fine(Matrix<double>& m) {
576 for (std::size_t i = 0; i < m.rows(); ++i)
577 for (std::size_t j = 0; j < m.cols(); ++j)
590inline void fluid_snap_all(Matrix<double>& q, Matrix<double>& u, Matrix<double>& r,
596 for (std::size_t i = 0; i < r.rows(); ++i)
597 for (std::size_t j = 0; j < r.cols(); ++j)
598 if (t(i, j) == 0.0 && q(i, j) == 0.0) r(i, j) = 0.0;
619std::vector<char> fluid_visited_pairs(
const qn::NetworkStruct<T>& sn, std::size_t M, std::size_t K) {
620 std::vector<char> visited(M * K, 0);
622 std::size_t max_cols = 0;
623 for (std::size_t c = 0; c < sn.visits.size(); ++c) {
624 const Matrix<T>& Vc = sn.visits[c];
625 if (Vc.rows() == 0 || Vc.cols() == 0)
continue;
627 max_cols = std::max(max_cols, Vc.cols());
628 for (std::size_t i = 0; i < M; ++i) {
629 const std::size_t isf = sn.stateful_of_station(i + 1);
630 if (isf == 0 || isf > Vc.rows()) {
631 for (std::size_t r = 0; r < K; ++r) visited[i * K + r] = 1;
634 for (std::size_t r = 0; r < K && r < Vc.cols(); ++r)
635 if (std::fabs(num_traits<T>::to_double(Vc(isf - 1, r))) >
637 visited[i * K + r] = 1;
641 std::fill(visited.begin(), visited.end(),
static_cast<char>(1));
645 for (std::size_t i = 0; i < M; ++i)
646 for (std::size_t r = max_cols; r < K; ++r) visited[i * K + r] = 1;
676void fluid_analyzer_correct(
const qn::NetworkStruct<T>& sn,
const Matrix<double>& Q,
677 Matrix<double>& U, Matrix<double>& R,
const Matrix<double>& T_) {
678 const std::size_t M = Q.rows(), K = Q.cols();
681 const std::vector<char> visited = fluid_visited_pairs(sn, M, K);
682 const Matrix<double> U0 = U;
683 for (std::size_t i = 0; i < M; ++i) {
684 double u0sum = 0.0, share_den = 0.0;
685 for (std::size_t r = 0; r < K; ++r) {
686 if (!(Q(i, r) > 0.0) || !visited[i * K + r])
continue;
688 const double rate = num_traits<T>::to_double(sn.rates(i, r));
689 if (rate != 0.0) share_den += T_(i, r) / rate;
696 double c = sn.stations[i].nservers;
697 for (std::size_t k = 0; k < sn.stations[i].lldscaling.size(); ++k)
698 c = std::max(c, num_traits<T>::to_double(sn.stations[i].lldscaling[k]));
700 for (std::size_t r = 0; r < K; ++r) {
701 if (!(Q(i, r) > 0.0) || !visited[i * K + r]) {
711 if (std::isfinite(c) && c > 0.0) best = std::min(best, Q(i, r) / c);
712 const double rate = num_traits<T>::to_double(sn.rates(i, r));
713 if (rate != 0.0 && share_den != 0.0)
714 best = std::min(best, u0sum * (T_(i, r) / rate) / share_den);
716 if (T_(i, r) != 0.0) R(i, r) = Q(i, r) / T_(i, r);
719 for (std::size_t i = 0; i < M; ++i)
720 for (std::size_t r = 0; r < K; ++r) {
721 if (std::isnan(U(i, r))) U(i, r) = 0.0;
722 if (std::isnan(R(i, r))) R(i, r) = 0.0;
739 const std::string& m,
const std::vector<double>& xs,
Matrix<double>& Q,
741 const std::size_t M =
sn.nstations, K =
sn.nclasses;
748 for (std::size_t i = 0; i < M; ++i)
749 for (std::size_t r = 0; r < K; ++r) {
751 for (std::size_t k = 0; k < L.
kic[i][r]; ++k) q += xs[L.
qidx[i][r] + k];
756 std::vector<std::vector<std::vector<double>>> xservice(M, std::vector<std::vector<double>>(K));
757 for (std::size_t i = 0; i < M; ++i) {
760 for (std::size_t r = 0; r < K; ++r) xi += Q(i, r);
762 for (std::size_t r = 0; r < K; ++r)
763 wxi += (r <
sn.stations[i].schedparam.size()
767 const double c =
sn.stations[i].nservers;
780 std::vector<double> wmean(K, 0.0);
783 for (std::size_t r = 0; r < K; ++r) {
784 if (!L.
enabled[i][r])
continue;
786 const std::size_t nn =
sn.service[i][r].D0.rows();
789 for (std::size_t a = 0; a < nn; ++a)
790 for (std::size_t bb = 0; bb < nn; ++bb) {
795 wni += wmean[r] * Q(i, r);
799 for (std::size_t r = 0; r < K; ++r) {
800 xservice[i][r].assign(L.
kic[i][r], 0.0);
801 if (!L.
enabled[i][r])
continue;
802 std::vector<double> mu, phi;
803 detail::fluid_mu_phi(
sn.service[i][r], mu, phi);
804 const std::size_t b = L.
qidx[i][r], n = L.
kic[i][r];
807 for (std::size_t k = 0; k < n; ++k)
808 xservice[i][r][k] = xs[b + k] * mu[k] * wmean[r] / wni * served;
810 for (std::size_t k = 0; k < n; ++k) s2 += xservice[i][r][k];
814 for (std::size_t k = 0; k < n; ++k) {
815 double mass = xs[b + k];
821 for (std::size_t p = 1; p < n; ++p) rest += xs[b + p];
824 tn += mass * mu[k] * phi[k];
825 xservice[i][r][k] = mass * mu[k];
828 tn += mass * mu[k] * phi[k];
829 xservice[i][r][k] = mass * mu[k];
832 const double w = r <
sn.stations[i].schedparam.size()
836 tn += mass * mu[k] * phi[k] * w / wxi * served;
837 xservice[i][r][k] = mass * mu[k] * w / wxi * served;
843 tn += mass * mu[k] * phi[k] / xi * served;
844 xservice[i][r][k] = mass * mu[k] / xi * served;
855 for (std::size_t i = 0; i < M; ++i) {
857 for (std::size_t r = 0; r < K; ++r) {
858 if (!L.
enabled[i][r])
continue;
859 std::vector<double> mu, phi;
860 detail::fluid_mu_phi(
sn.service[i][r], mu, phi);
862 for (std::size_t k = 0; k < xservice[i][r].size(); ++k)
863 if (xservice[i][r][k] > 0.0 && mu[k] > 0.0) u += xservice[i][r][k] / mu[k];
866 double c =
sn.stations[i].nservers;
867 for (std::size_t k = 0; k < sys.
lld[i].size(); ++k) c = std::max(c, sys.
lld[i][k]);
868 U(i, r) = (is_delay || !std::isfinite(c) || c <= 0.0) ? u : u / c;
873 for (std::size_t i = 0; i < M; ++i)
874 for (std::size_t r = 0; r < K; ++r)
875 if (T_(i, r) > 0.0) R(i, r) = Q(i, r) / T_(i, r);
883 for (std::size_t i = 0; i < M; ++i) {
885 if (nt != qn::NodeType::Source && nt != qn::NodeType::Sink)
continue;
886 for (std::size_t r = 0; r < K; ++r) {
893 detail::fluid_snap_all(Q, U, R, T_);
910inline void fluid_nhpp_steps(
const std::vector<double>& bp,
const std::vector<double>& seg_rate,
911 bool cyclic,
double t0,
double thi, std::vector<double>& seg_t,
912 std::vector<double>& seg_r) {
915 if (bp.size() < 2 || seg_rate.empty() || !(thi > t0))
return;
916 const double period = bp.back() - bp.front();
917 std::vector<double> bounds;
918 bounds.push_back(t0);
919 if (cyclic && period > 0.0) {
920 const long kmax =
static_cast<long>(std::ceil((thi - t0) / period)) + 2;
921 for (
long k = -1; k <= kmax; ++k)
922 for (std::size_t a = 0; a < bp.size(); ++a)
923 bounds.push_back(bp[a] +
static_cast<double>(k) * period);
925 for (std::size_t a = 0; a < bp.size(); ++a) bounds.push_back(bp[a]);
927 bounds.push_back(thi);
928 std::sort(bounds.begin(), bounds.end());
929 bounds.erase(std::remove_if(bounds.begin(), bounds.end(),
930 [&](
double v) { return v < t0 || v > thi; }),
932 bounds.erase(std::unique(bounds.begin(), bounds.end()), bounds.end());
933 if (bounds.size() < 2)
return;
937 const auto rate_at = [&](
double t) ->
double {
938 double offset = t - bp.front();
941 offset = std::fmod(offset, period);
942 if (offset < 0.0) offset += period;
946 }
else if (offset < 0.0 || offset >= period) {
949 const double pos = bp.front() + offset;
950 std::size_t idx = seg_rate.size() - 1;
951 for (std::size_t k = 1; k < bp.size(); ++k)
956 return idx < seg_rate.size() ? seg_rate[idx] : 0.0;
959 const double neps = std::max(1e-9, 1e-6 * (thi - t0));
960 for (std::size_t k = 0; k + 1 < bounds.size(); ++k) {
961 const double a = bounds[k], b = bounds[k + 1];
962 const double r = rate_at(0.5 * (a + b));
965 seg_t.push_back(std::max(a + neps, b - neps));
971inline FluidRateMult fluid_ratemult_merge(
const FluidRateMult& a,
const FluidRateMult& b,
972 std::size_t nevents) {
973 if (a.empty())
return b;
974 if (b.empty())
return a;
975 std::vector<double> tg = a.tgrid;
976 tg.insert(tg.end(), b.tgrid.begin(), b.tgrid.end());
977 std::sort(tg.begin(), tg.end());
978 tg.erase(std::unique(tg.begin(), tg.end()), tg.end());
981 out.Mmat = Matrix<double>(nevents, tg.size(), 1.0);
982 std::vector<double> ca, cb;
983 for (std::size_t j = 0; j < tg.size(); ++j) {
986 for (std::size_t e = 0; e < nevents; ++e) {
987 const double va = e < ca.size() ? ca[e] : 1.0;
988 const double vb = e < cb.size() ? cb[e] : 1.0;
989 out.Mmat(e, j) = va * vb;
1004inline bool fluid_has_time_varying_rates(
const FluidOptions& opt) {
1005 return !opt.rate_traj.empty() || !opt.nhpp_sched.empty() || !opt.rate_sched.empty();
1009inline std::vector<std::size_t> fluid_events_of(
const FluidOdeSystem& sys, std::size_t i,
1011 std::vector<std::size_t> rows;
1012 const std::size_t lo = sys.layout.qidx[i][c];
1013 const std::size_t hi = lo + sys.layout.kic[i][c];
1014 for (std::size_t e = 0; e < sys.events.size(); ++e)
1015 if (sys.events[e].event_idx >= lo && sys.events[e].event_idx < hi) rows.push_back(e);
1020inline FluidRateMult fluid_ratemult_rows(
const FluidOdeSystem& sys, std::size_t i, std::size_t c,
1021 const std::vector<double>& seg_t,
1022 const std::vector<double>& seg_r,
double nominal) {
1024 if (seg_t.empty() || !(nominal > 0.0))
return out;
1025 const std::vector<std::size_t> rows = fluid_events_of(sys, i, c);
1026 if (rows.empty())
return out;
1028 out.Mmat = Matrix<double>(sys.events.size(), seg_t.size(), 1.0);
1029 for (std::size_t j = 0; j < seg_t.size(); ++j)
1030 for (std::size_t e : rows) out.Mmat(e, j) = seg_r[j] / nominal;
1049FluidRateMult fluid_ratemult(
const qn::NetworkStruct<T>& sn,
const FluidOdeSystem& sys,
1050 const FluidOptions& opt) {
1051 const std::size_t nev = sys.events.size();
1053 if (!opt.rate_traj.empty()) {
1054 if (opt.rate_traj.Mmat.rows() != nev)
1055 throw InputError(
"solver_fluid_ratemult: rate_traj has " +
1056 std::to_string(opt.rate_traj.Mmat.rows()) +
1057 " rows but the closing ODE has " + std::to_string(nev) +
" events");
1058 out = opt.rate_traj;
1065 const double tend = opt.timespan_end;
1068 for (std::size_t a = 0; a < opt.nhpp_sched.size(); ++a) {
1069 const std::size_t i = opt.nhpp_sched[a].first - 1, c = opt.nhpp_sched[a].second - 1;
1070 if (i >= sn.nstations || c >= sn.nclasses)
continue;
1071 if (!sys.layout.enabled[i][c])
continue;
1072 const lang::Distrib<T>& d = sn.service[i][c];
1073 if (!d.has_schedule())
continue;
1074 const std::vector<T> mu = d.mu_vec();
1075 if (mu.empty())
continue;
1076 const double nominal = num_traits<T>::to_double(mu[0]);
1077 if (!(nominal > 0.0))
continue;
1079 std::vector<double> bp, seg_r;
1080 for (std::size_t k = 0; k < d.sched_bp.size(); ++k)
1081 bp.push_back(num_traits<T>::to_double(d.sched_bp[k]));
1084 for (std::size_t k = 0; k < d.sched_D0.size(); ++k) {
1086 m.D0 = d.sched_D0[k];
1087 m.D1 = d.sched_D1[k];
1090 const double period = bp.empty() ? 0.0 : bp.back() - bp.front();
1092 if (!std::isfinite(thi))
1093 thi = (std::isfinite(period) && period > 0.0) ? t0 + 3.0 * period : t0 + 1.0;
1094 std::vector<double> seg_t, seg_v;
1095 fluid_nhpp_steps(bp, seg_r, d.sched_cyclic, t0, thi, seg_t, seg_v);
1096 nh = fluid_ratemult_merge(nh, fluid_ratemult_rows(sys, i, c, seg_t, seg_v, nominal), nev);
1100 for (std::size_t a = 0; a < opt.rate_sched.size(); ++a) {
1101 const FluidOptions::RateSched& e = opt.rate_sched[a];
1102 const std::size_t i = e.station - 1, c = e.cls - 1;
1103 if (i >= sn.nstations || c >= sn.nclasses)
continue;
1104 if (!sys.layout.enabled[i][c])
continue;
1105 if (e.tgrid.size() != e.rates.size() || e.tgrid.empty())
1106 throw InputError(
"solver_fluid_ratemult: rate_sched tgrid and rates must be "
1107 "non-empty and of equal length");
1108 double nominal = e.nominal;
1109 if (!(nominal > 0.0)) {
1110 const std::vector<T> mu = sn.service[i][c].mu_vec();
1111 if (mu.empty())
continue;
1112 nominal = num_traits<T>::to_double(mu[0]);
1114 if (!(nominal > 0.0))
continue;
1115 rs = fluid_ratemult_merge(rs, fluid_ratemult_rows(sys, i, c, e.tgrid, e.rates, nominal),
1119 out = fluid_ratemult_merge(out, nh, nev);
1120 out = fluid_ratemult_merge(out, rs, nev);
1131double fluid_slow_rate(
const qn::NetworkStruct<T>& sn,
const FluidLayout& L,
double tol) {
1132 double min_rate = std::numeric_limits<double>::infinity();
1133 for (std::size_t i = 0; i < sn.nstations; ++i)
1134 for (std::size_t r = 0; r < sn.nclasses; ++r) {
1135 if (!L.enabled[i][r])
continue;
1136 const lang::Distrib<T>& d = sn.service[i][r];
1137 for (std::size_t k = 0; k < d.D0.rows(); ++k) {
1138 const double mu = -num_traits<T>::to_double(d.D0(k, k));
1139 if (mu > tol && std::isfinite(mu)) min_rate = std::min(min_rate, mu);
1142 return std::isfinite(min_rate) ? min_rate : 1.0;
1155FluidSolution fluid_dispatch(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt) {
1156 if (!std::is_same<T, double>::value)
1158 "solver_fluid: the fluid solver integrates its drift with LSODA, whose coefficients "
1159 "assume double precision; rerun with --arith double");
1161 std::string m = opt.method;
1162 if (m.compare(0, 6,
"fluid.") == 0) m = m.substr(6);
1164 bool statedep_family =
false;
1176 bool has_dps =
false, has_cache =
false;
1177 for (
const auto& st : sn.stations)
1179 for (
const qn::NodeDef& nd : sn.nodes)
1180 if (nd.nodetype == qn::NodeType::Cache) has_cache =
true;
1181 if ((m ==
"matrix" || m ==
"pnorm") && has_dps)
1183 "solver_fluid: the matrix method does not support DPS scheduling; use method "
1184 "'closing' (which is what 'default' selects on a DPS model)");
1185 if (m ==
"default") {
1188 "solver_fluid: a Cache model resolves to the 'rmf' fluid method, which is ported "
1189 "in fluid_cacheqn.h and reached through solver_fluid_run_analyzer (fluid_runner.h); this "
1190 "function is solver_fluid_analyzer alone and cannot call it without a cyclic "
1192 if (has_dps) m =
"closing";
1194 const bool matrix_family = (m ==
"default" || m ==
"matrix" || m ==
"pnorm");
1195 if (m ==
"statedep") {
1196 statedep_family =
true;
1198 }
else if (m ==
"softmin") {
1199 statedep_family =
true;
1201 }
else if (!(matrix_family || m ==
"closing" || m ==
"tbi" || m ==
"diffusion" || m ==
"mfq")) {
1203 "' fluid method is not solved here; available are 'closing', "
1204 "'statedep', 'softmin', 'pnorm', 'matrix', 'tbi', 'diffusion' and "
1205 "'mfq', while 'rmf', 'minnormal', 'refined', 'dae' and 'kp' are "
1206 "reached through solver_fluid_run_analyzer (fluid_runner.h), which is the "
1207 "port of runAnalyzer's resolution");
1210 const std::size_t M = sn.nstations, K = sn.nclasses;
1215 sys.closure = opt.closure;
1219 sys.ratemult = detail::fluid_ratemult(sn, sys, opt);
1220 const FluidLayout& L = sys.layout;
1222 throw InputError(
"solver_fluid: no station serves any class, so the drift is empty");
1225 const double min_rate = fluid_slow_rate(sn, L, opt.tol);
1228 std::vector<double> x = opt.init_sol.empty() ? detail::fluid_default_initsol(sn, L) : opt.init_sol;
1229 if (x.size() != L.nstates)
1230 throw InputError(
"solver_fluid: init_sol has " + std::to_string(x.size()) +
1231 " entries but the fluid state has " + std::to_string(L.nstates));
1240 const FluidAoiResult ar =
fluid_aoi(sn, atop, opt.aoi_preemption);
1246 out.QN = Matrix<double>(M, K, 0.0);
1247 out.UN = Matrix<double>(M, K, 0.0);
1248 out.RN = Matrix<double>(M, K, 0.0);
1249 out.TN = Matrix<double>(M, K, 0.0);
1250 out.XN.assign(K, 0.0);
1251 out.CN.assign(K, 0.0);
1252 for (std::size_t r = 0; r < K; ++r) {
1253 out.QN(atop.queue, r) = ar.QN[r];
1254 out.UN(atop.queue, r) = ar.UN[r];
1255 out.RN(atop.queue, r) = ar.RN[r];
1256 out.TN(atop.queue, r) = ar.TN[r];
1257 out.TN(atop.source, r) = ar.TN[r];
1258 out.XN[r] = ar.TN[r];
1259 out.CN[r] = ar.RN[r];
1267 bool mixed_prio =
false;
1269 for (std::size_t j = 1; j < top.open_classes.size(); ++j)
1270 if (sn.classes[top.open_classes[j]].prio != sn.classes[top.open_classes[0]].prio)
1276 FluidOptions fb = opt;
1277 fb.method =
"matrix";
1283 out.QN = Matrix<double>(M, K, 0.0);
1284 out.UN = Matrix<double>(M, K, 0.0);
1285 out.RN = Matrix<double>(M, K, 0.0);
1286 out.TN = Matrix<double>(M, K, 0.0);
1287 out.XN.assign(K, 0.0);
1288 out.CN.assign(K, 0.0);
1289 for (std::size_t r = 0; r < K; ++r) {
1290 out.QN(top.queue, r) = pr.QN[r];
1291 out.TN(top.queue, r) = pr.TN[r];
1292 out.TN(top.source, r) = pr.TN[r];
1293 out.XN[r] = pr.TN[r];
1298 double ufull = 0.0, tsum = 0.0;
1299 for (std::size_t r = 0; r < K; ++r)
1300 if (pr.QN[r] > 0.0) {
1302 tsum += pr.TN[r] / num_traits<T>::to_double(sn.rates(top.queue, r));
1304 const double servers = sn.stations[top.queue].nservers;
1305 for (std::size_t r = 0; r < K; ++r) {
1306 if (!(pr.QN[r] > 0.0))
continue;
1307 const double share =
1308 ufull * (pr.TN[r] / num_traits<T>::to_double(sn.rates(top.queue, r))) / tsum;
1309 out.UN(top.queue, r) = std::min(1.0, std::min(pr.QN[r] / servers, share));
1310 out.RN(top.queue, r) = pr.QN[r] / pr.TN[r];
1311 out.CN[r] = out.RN(top.queue, r);
1315 if (top.ok && top.open_classes.size() > 1)
1317 "fluid mfq: the single fluid-fluid queue analyzes ONE open class, and this model "
1318 "has several at equal priority; the reference silently reports class 1 only");
1328 FluidOptions mopt = opt;
1329 mopt.method =
"matrix";
1330 return fluid_dispatch(sn, mopt);
1332 const MfqResult r =
fluid_mfq(sn, top, opt.tol);
1336 out.QN = Matrix<double>(M, K, 0.0);
1337 out.UN = Matrix<double>(M, K, 0.0);
1338 out.RN = Matrix<double>(M, K, 0.0);
1339 out.TN = Matrix<double>(M, K, 0.0);
1340 out.QN(top.queue, top.cls) = r.QN;
1341 out.UN(top.queue, top.cls) = r.UN;
1342 out.RN(top.queue, top.cls) = r.RN;
1343 out.TN(top.queue, top.cls) = r.TN;
1344 out.TN(top.source, top.cls) = r.TN;
1345 out.XN.assign(K, 0.0);
1346 out.CN.assign(K, 0.0);
1347 out.XN[top.cls] = r.TN;
1348 out.CN[top.cls] = r.RN;
1353 if (m ==
"diffusion") {
1354 DiffusionOptions dopt;
1355 dopt.steps = opt.iter_max > 2 ? opt.iter_max : 10000;
1356 dopt.dt = opt.timestep;
1357 dopt.seed = opt.seed;
1361 out.method =
"diffusion";
1363 out.UN = Matrix<double>(M, K, 0.0);
1364 out.RN = Matrix<double>(M, K, 0.0);
1365 out.TN = Matrix<double>(M, K, 0.0);
1366 for (std::size_t i = 0; i < M; ++i) {
1367 const double c = sn.stations[i].nservers;
1368 const bool inf_server = !std::isfinite(c);
1369 for (std::size_t r = 0; r < K; ++r) {
1370 const double rate = num_traits<T>::to_double(sn.rates(i, r));
1371 if (rate > 0.0 && std::isfinite(rate)) {
1374 out.TN(i, r) = inf_server ? out.QN(i, r) * rate
1375 : std::min(out.QN(i, r), 1.0) * rate;
1377 out.UN(i, r) = inf_server ? out.QN(i, r) : std::min(out.QN(i, r) / c, 1.0);
1381 out.RN(i, r) = out.QN(i, r) / out.TN(i, r);
1384 detail::fluid_snap_all(out.QN, out.UN, out.RN, out.TN);
1385 out.XN.assign(K, 0.0);
1386 out.CN.assign(K, 0.0);
1387 for (std::size_t r = 0; r < K; ++r) {
1388 const std::size_t rs = sn.classes[r].refstat;
1389 if (rs >= 1 && rs <= M) out.XN[r] = out.TN(rs - 1, r);
1391 for (std::size_t i = 0; i < M; ++i) q += out.QN(i, r);
1392 if (out.XN[r] > 0.0) out.CN[r] = q / out.XN[r];
1398 lopt.rtol = opt.tol;
1399 lopt.atol = opt.tol;
1402 if (matrix_family) {
1406 const double ps = (m ==
"pnorm" || opt.pstar_set) ? opt.pstar : 0.0;
1408 const std::function<void(
double,
const double*,
double*)> mdrift =
fluid_matrix_drift(ms);
1410 std::min(opt.timespan_end,
1411 10.0 *
static_cast<double>(opt.iter_max) / ms.min_rate);
1413 for (
double& v : xm)
1414 if (v < 0.0) v = 0.0;
1436 FluidMatrixSystem msr = ms;
1438 msr.var_closure =
true;
1439 const std::function<void(
double,
const double*,
double*)> cdrift =
1442 bool finite = xc.size() == xm.size();
1443 for (std::size_t a = 0; finite && a < xc.size(); ++a)
1444 if (!std::isfinite(xc[a])) finite =
false;
1446 for (
double& v : xc)
1447 if (v < 0.0) v = 0.0;
1452 msr.var_closure =
false;
1457 std::vector<double> theta(ms.nstates, 0.0);
1458 detail::fluid_matrix_theta(msr, xm.data(), theta);
1462 out.method = (m ==
"pnorm") ?
"pnorm" :
"matrix";
1464 out.QN = Matrix<double>(M, K, 0.0);
1465 out.UN = Matrix<double>(M, K, 0.0);
1466 out.RN = Matrix<double>(M, K, 0.0);
1467 out.TN = Matrix<double>(M, K, 0.0);
1468 for (std::size_t i = 0; i < M; ++i)
1469 for (std::size_t r = 0; r < K; ++r) {
1470 double q = 0.0, u = 0.0, t = 0.0;
1471 for (std::size_t a = 0; a < ms.nstates; ++a) {
1472 q += ms.sqc(i * K + r, a) * xm[a];
1473 u += ms.suc(i * K + r, a) * theta[a];
1474 t += ms.stc(i * K + r, a) * theta[a];
1484 !std::isfinite(sn.stations[i].nservers))
1498 for (std::size_t i = 0; i < M; ++i) {
1500 if (nt != qn::NodeType::Source && nt != qn::NodeType::Sink)
continue;
1501 for (std::size_t r = 0; r < K; ++r) {
1505 if (nt == qn::NodeType::Source && ms.src_arrival.rows() == M)
1506 out.TN(i, r) = ms.src_arrival(i, r);
1509 detail::fluid_snap_all(out.QN, out.UN, out.RN, out.TN);
1510 out.XN.assign(K, 0.0);
1511 out.CN.assign(K, 0.0);
1512 for (std::size_t r = 0; r < K; ++r) {
1513 const std::size_t rs = sn.classes[r].refstat;
1514 if (rs >= 1 && rs <= M) out.XN[r] = out.TN(rs - 1, r);
1516 for (std::size_t i = 0; i < M; ++i) q += out.QN(i, r);
1517 if (out.XN[r] > 0.0) out.CN[r] = q / out.XN[r];
1533 lopt.rtol = lopt.atol =
1534 opt.tol / std::max<double>(1.0,
static_cast<double>(opt.iter_max));
1540 FluidOdeSystem dsys = sys;
1541 FluidImmediateResult imm_result;
1544 if (imm_result.eliminated) {
1545 dsys = imm_result.sys;
1550 std::vector<double> xp(x.size(), 0.0);
1551 for (std::size_t f = 0; f < x.size() && f < imm_result.absorb.rows(); ++f)
1552 for (std::size_t sidx = 0; sidx < x.size() && sidx < imm_result.absorb.cols();
1554 xp[sidx] += x[f] * imm_result.absorb(f, sidx);
1555 for (std::size_t a = 0; a < x.size(); ++a) x[a] = xp[a];
1559 const std::function<void(
double,
const double*,
double*)> drift =
1564 const std::vector<std::vector<std::size_t>> tbi_cells =
1566 : std::vector<std::vector<std::size_t>>();
1570 const double drift_tol = std::max(opt.iter_tol, opt.tol);
1571 const double drift_safety = 0.01;
1572 const double min_horizon = 10.0 / min_rate;
1573 double moved_prev = std::numeric_limits<double>::infinity();
1574 std::vector<double> rho_hist(3, std::numeric_limits<double>::quiet_NaN());
1575 int drift_below = 0;
1576 std::vector<double> drift_buf(x.size(), 0.0);
1579 std::size_t iter = 0;
1580 for (; iter < opt.iter_max; ++iter) {
1581 const double horizon = 10.0 *
static_cast<double>(iter + 1) / min_rate;
1582 const double t1 = std::min(opt.timespan_end, horizon);
1583 if (!(t1 > t0))
break;
1584 const std::vector<double> prev = x;
1612 const bool fp_armed = opt.earlystop && !std::isfinite(opt.timespan_end)
1613 && !fluid_has_time_varying_rates(opt);
1615 drift(t0, x.data(), drift_buf.data());
1616 double dn = 0.0, dtot = 0.0;
1617 for (std::size_t i = 0; i < x.size(); ++i) {
1618 dn += std::fabs(drift_buf[i]);
1644 lopt.step_stop = {};
1645 if (fp_armed && m !=
"tbi") {
1646 const double fp_rate = min_rate;
1647 lopt.step_stop = [&drift, fp_rate](
double tt,
const std::vector<double>& yy) {
1648 if (yy.empty() || !(fp_rate > 0.0))
return false;
1649 std::vector<double> dy(yy.size(), 0.0);
1650 drift(tt, yy.data(), dy.data());
1651 double dn = 0.0, dtot = 0.0;
1652 for (std::size_t i = 0; i < yy.size(); ++i) {
1653 dn += std::fabs(dy[i]);
1661 x =
tbi_advance(sys, tbi_cells, x, t0, t1, TbiOptions(), lopt);
1662 }
else if (opt.stiff) {
1666 FluidStiffOptions sopt;
1667 sopt.rtol = lopt.rtol;
1668 sopt.atol = lopt.atol;
1671 if (lopt.step_stop) {
1672 const std::function<bool(
double,
const std::vector<double>&)> ss_stop =
1674 sopt.step_stop = [ss_stop](
const double& tt,
const std::vector<double>& yy) {
1675 return ss_stop(tt, yy);
1679 x = ss.final_state();
1687 if (v < 0.0) v = 0.0;
1697 if (opt.closure.gaussian()) {
1701 "The moment-closure drift left the model: closed chain " +
1702 std::to_string(bad) +
" moved more than " +
1704 "% of a population the drift conserves exactly, by t = " +
1705 std::to_string(t1) +
1706 ", so the excursion is a divergence rather than a solution. "
1707 "Falling back to a first-order closure.");
1712 double moved = 0.0, total = 0.0;
1713 for (std::size_t i = 0; i < x.size(); ++i) {
1714 moved += std::fabs(x[i] - prev[i]);
1717 const double ratio = (total > 0.0) ? moved / 2.0 / total : 0.0;
1723 if (opt.iter_tol > 0.0 && ratio < opt.iter_tol && !std::isfinite(opt.timespan_end)) {
1734 if (opt.earlystop && iter > 0 && !std::isfinite(opt.timespan_end) && t1 >= min_horizon) {
1735 rho_hist[iter % rho_hist.size()] =
1738 for (
double v : rho_hist)
1739 if (std::isfinite(v) && v > rho) rho = v;
1740 drift(t1, x.data(), drift_buf.data());
1741 double dn = 0.0, dtot = 0.0;
1742 for (std::size_t i = 0; i < x.size(); ++i) {
1743 dn += std::fabs(drift_buf[i]);
1746 const double drift_displ = (dtot > 0.0) ? dn / 2.0 / dtot / min_rate : 0.0;
1759 if (rho < 1.0 && ratio * rho / (1.0 - rho) < drift_safety * drift_tol
1760 && drift_displ < drift_tol) {
1761 if (++drift_below >= 2) {
1770 if (t1 >= opt.timespan_end) {
1779 out.method = (statedep_family || m ==
"tbi") ? m : std::string(
"closing");
1781 out.QN = Matrix<double>(M, K, 0.0);
1782 out.UN = Matrix<double>(M, K, 0.0);
1783 out.RN = Matrix<double>(M, K, 0.0);
1784 out.TN = Matrix<double>(M, K, 0.0);
1795 if (imm_result.eliminated) {
1796 const FluidLayout& LL = sys.layout;
1797 std::vector<std::size_t> cs(LL.nstates, 0), cc(LL.nstates, 0);
1798 for (std::size_t i = 0; i < M; ++i)
1799 for (std::size_t r = 0; r < K; ++r)
1800 for (std::size_t k = 0; k < LL.kic[i][r]; ++k) {
1801 cs[LL.qidx[i][r] + k] = i;
1802 cc[LL.qidx[i][r] + k] = r;
1804 std::vector<bool> kept(LL.nstates,
false);
1805 for (std::size_t a = 0; a < imm_result.state_map.size(); ++a)
1806 kept[imm_result.state_map[a]] =
true;
1807 std::vector<double> rr(x.begin(), x.end());
1809 for (std::size_t o = 0; o < sys.n_departures && o < sys.events.size(); ++o) {
1810 const std::size_t c = sys.events[o].event_idx;
1811 if (c >= LL.nstates || kept[c])
continue;
1813 for (std::size_t e = 0; e < rr.size() && e < imm_result.emap.rows(); ++e)
1814 extra += imm_result.emap(e, o) * rr[e];
1815 out.TN(cs[c], cc[c]) += extra;
1817 for (std::size_t i = 0; i < M; ++i)
1818 for (std::size_t r = 0; r < K; ++r)
1820 out.RN(i, r) = out.QN(i, r) / out.TN(i, r);
1822 detail::fluid_snap_all(out.QN, out.UN, out.RN, out.TN);
1825 out.XN.assign(K, 0.0);
1826 out.CN.assign(K, 0.0);
1827 for (std::size_t r = 0; r < K; ++r) {
1828 const std::size_t rs = sn.classes[r].refstat;
1829 if (rs >= 1 && rs <= M) out.XN[r] = out.TN(rs - 1, r);
1831 for (std::size_t i = 0; i < M; ++i) q += out.QN(i, r);
1832 if (out.XN[r] > 0.0) out.CN[r] = q / out.XN[r];
1894inline bool fluid_method_refits_fcfs(
const std::string& method) {
1895 std::string m = method;
1896 if (m.size() > 6 && m.compare(0, 6,
"fluid.") == 0) m = m.substr(6);
1897 return m ==
"matrix" || m ==
"closing" || m ==
"tbi" || m ==
"minnormal" || m ==
"refined" ||
1903Matrix<T> fluid_station_visits(
const qn::NetworkStruct<T>& sn) {
1904 const T zero = num_traits<T>::from_int(0);
1905 Matrix<T> V(sn.nstations, sn.nclasses, zero);
1906 for (std::size_t c = 0; c < sn.nchains; ++c)
1907 for (std::size_t i = 0; i < sn.nstations; ++i) {
1908 const std::size_t sf = sn.stateful_of_station(i + 1);
1909 for (std::size_t k = 0; k < sn.nclasses; ++k)
1910 V(i, k) = T(V(i, k) + sn.visits[c](sf - 1, k));
1923inline double fluid_eta_gap(
const std::vector<double>& eta,
const std::vector<double>& eta_1) {
1924 double best = -std::numeric_limits<double>::infinity();
1926 for (std::size_t i = 0; i < eta.size(); ++i) {
1927 const double g = std::fabs(1.0 - eta[i] / eta_1[i]);
1928 if (std::isnan(g))
continue;
1930 best = std::max(best, g);
1932 return any ? best : 0.0;
1938Matrix<T> fluid_reciprocal_guarded(
const Matrix<T>& A) {
1939 const T one = num_traits<T>::from_int(1);
1940 Matrix<T> B(A.rows(), A.cols(), num_traits<T>::from_int(0));
1941 for (std::size_t i = 0; i < A.rows(); ++i)
1942 for (std::size_t r = 0; r < A.cols(); ++r) {
1943 const double a = num_traits<T>::to_double(A(i, r));
1946 else if (a == 0.0 || std::isinf(1.0 / a))
1954 B(i, r) = T(one / A(i, r));
1967bool fluid_refit_fcfs_stations(qn::NetworkStruct<T>& sn,
const Matrix<T>& rates,
1968 const Matrix<T>& SCV,
1969 std::vector<std::vector<std::size_t>>& phases) {
1970 const T zero = num_traits<T>::from_int(0);
1971 const T one = num_traits<T>::from_int(1);
1972 const std::size_t M = sn.nstations, K = sn.nclasses;
1973 bool changed =
false;
1974 for (std::size_t i = 0; i < M; ++i) {
1976 for (std::size_t r = 0; r < K; ++r) {
1977 if (!(rates(i, r) > zero) || !(SCV(i, r) > zero))
continue;
1984 if (cx.mu.size() != phases[i][r]) changed =
true;
1985 phases[i][r] = cx.mu.size();
2008template <
class T,
class Solve>
2009FluidSolution fluid_fcfs_nonexp_refit(
const qn::NetworkStruct<T>& sn0,
const FluidOptions& opt,
2010 const FluidSolution& seed, Solve
solve,
2011 qn::NetworkStruct<T>* sn_out =
nullptr) {
2012 const std::size_t M = sn0.nstations, K = sn0.nclasses;
2018 if (sn_out) *sn_out = sn0;
2019 if (!fluid_method_refits_fcfs(opt.method))
return seed;
2020 bool any_fcfs =
false;
2021 for (std::size_t i = 0; i < M; ++i)
2023 if (!any_fcfs)
return seed;
2025 const T zero = num_traits<T>::from_int(0);
2026 const T one = num_traits<T>::from_int(1);
2027 const Matrix<T>& rates0 = sn0.rates;
2028 const Matrix<T>& SCV = sn0.scv;
2029 const Matrix<T> V = fluid_station_visits(sn0);
2030 const Matrix<T> ST0 = fluid_reciprocal_guarded(rates0);
2032 std::vector<bool> isFCFS(M,
false);
2033 std::vector<T> nservers(M, one), gamma(M, zero);
2034 for (std::size_t i = 0; i < M; ++i) {
2036 nservers[i] = num_traits<T>::from_double(sn0.stations[i].nservers);
2039 qn::NetworkStruct<T> sn = sn0;
2040 std::vector<std::vector<std::size_t>> phases(M, std::vector<std::size_t>(K, 0));
2041 for (std::size_t i = 0; i < M; ++i)
2042 for (std::size_t r = 0; r < K; ++r) phases[i][r] = sn0.service[i][r].D0.rows();
2044 FluidSolution cur = seed;
2045 std::vector<double> eta(M, std::numeric_limits<double>::infinity()), eta_1(M, 0.0);
2046 std::size_t iter = 0;
2052 Matrix<T> U(M, K, zero), TN(M, K, zero);
2053 for (std::size_t i = 0; i < M; ++i)
2054 for (std::size_t r = 0; r < K; ++r) {
2055 TN(i, r) = num_traits<T>::from_double(cur.TN(i, r));
2056 if (rates0(i, r) > zero) U(i, r) = T(TN(i, r) / rates0(i, r));
2060 opt.highvar, isFCFS, rates0, ST0, V, SCV, TN, U, gamma, nservers);
2062 for (std::size_t i = 0; i < M; ++i) eta[i] = num_traits<T>::to_double(na.eta[i]);
2064 const Matrix<T> rates = fluid_reciprocal_guarded(na.ST);
2065 const bool phase_change = fluid_refit_fcfs_stations(sn, rates, SCV, phases);
2067 FluidOptions o = opt;
2068 const std::vector<double> fresh = fluid_default_initsol(sn,
fluid_layout(sn));
2069 o.init_sol = (!phase_change && cur.xvec.size() == fresh.size()) ? cur.xvec : fresh;
2076 FluidOptions o = opt;
2077 o.init_sol = fluid_default_initsol(sn,
fluid_layout(sn));
2078 FluidSolution out =
solve(sn, o);
2079 out.refit_sweeps = iter;
2080 if (sn_out) *sn_out = sn;
2116 out = detail::fluid_fcfs_nonexp_refit(
2120 detail::fluid_analyzer_correct(
sn, out.
QN, out.
UN, out.
RN, out.
TN);
2121 detail::fluid_snap_all(out.
QN, out.
UN, out.
RN, out.
TN);
2142 std::vector<std::pair<std::size_t, std::size_t> > out;
2143 for (std::size_t i = 0; i <
sn.nstations; ++i) {
2145 for (std::size_t r = 0; r <
sn.nclasses; ++r)
2146 if (!
sn.disabled[i][r] &&
sn.service[i][r].has_schedule())
2147 out.push_back(std::make_pair(i + 1, r + 1));
2181 std::size_t points = 101,
2182 const std::vector<double>& out_grid =
2183 std::vector<double>()) {
2184 if (!std::is_same<T, double>::value)
2186 "solver_fluid_transient: the fluid drift is integrated by LSODA, which is double "
2187 "precision by construction; rerun with --arith double");
2188 if (!(t_end > 0.0))
throw InputError(
"solver_fluid_transient: t_end must be positive");
2189 if (points < 2)
throw InputError(
"solver_fluid_transient: need at least two output points");
2194 sys.
ratemult = detail::fluid_ratemult(
sn, sys, o);
2197 throw InputError(
"solver_fluid_transient: no station serves any class");
2199 std::vector<double> y0 =
2202 throw InputError(
"solver_fluid_transient: init_sol has the wrong length");
2204 std::vector<double> grid = out_grid;
2206 grid.resize(points);
2207 for (std::size_t j = 0; j < points; ++j)
2208 grid[j] = t_end *
static_cast<double>(j) /
static_cast<double>(points - 1);
2214 const std::function<void(
double,
const double*,
double*)> tdrift =
fluid_drift(sys);
2217 std::vector<FluidTranPoint> out;
2218 out.reserve(sol.
y.size());
2219 for (std::size_t j = 0; j < sol.
y.size(); ++j) {
2220 std::vector<double> xs = sol.
y[j];
2221 for (
double& v : xs)
2222 if (v < 0.0) v = 0.0;
2227 detail::fluid_snap_all(pt.
QN, pt.
UN, R, pt.
TN);
2258 if (std::isfinite(
opt.timespan_end) &&
opt.timespan_end > 0.0)
return opt.timespan_end;
2259 double min_rate = std::numeric_limits<double>::infinity();
2260 for (std::size_t i = 0; i <
sn.nstations; ++i)
2261 for (std::size_t r = 0; r <
sn.nclasses; ++r) {
2262 if (
sn.disabled[i][r])
continue;
2264 if (std::isfinite(rate) && rate >
opt.tol) min_rate = std::min(min_rate, rate);
2266 if (!std::isfinite(min_rate)) min_rate = 1.0;
2267 return 30.0 / min_rate;
2283 std::size_t points = 101) {
2300 if (x.size() != n)
throw InputError(
"fluid_jacobian: the state has the wrong length");
2301 const std::function<void(
double,
const double*,
double*)> f =
fluid_drift(sys);
2303 std::vector<double> xp(x), xm(x), fp(n, 0.0), fm(n, 0.0);
2304 for (std::size_t j = 0; j < n; ++j) {
2306 const double h = 1e-6 * std::max(1.0, std::fabs(x[j]));
2311 f(0.0, xp.data(), fp.data());
2312 f(0.0, xm.data(), fm.data());
2313 for (std::size_t i = 0; i < n; ++i) J(i, j) = (fp[i] - fm[i]) / (2.0 * h);
2336 std::size_t i,
const std::vector<double>& nir,
2337 double* logp_out =
nullptr) {
2338 const std::size_t K =
sn.nclasses;
2341 std::vector<std::size_t> idx;
2342 std::vector<double> m, a, b;
2343 for (std::size_t r = 0; r < K; ++r) {
2344 if (cb[r].empty()) {
2348 if (logp_out) *logp_out = -std::numeric_limits<double>::infinity();
2354 m.push_back(sol.
QN(i, r));
2355 a.push_back(nir[r] <= 0.0 ? -std::numeric_limits<double>::infinity() : nir[r] - 0.5);
2356 const double pop =
sn.classes[r].population;
2357 b.push_back((std::isfinite(pop) && nir[r] >= pop) ? std::numeric_limits<double>::infinity()
2362 if (logp_out) *logp_out = 0.0;
2366 const std::size_t nr = idx.size();
2368 for (std::size_t u = 0; u < nr; ++u)
2369 for (std::size_t v = u; v < nr; ++v) {
2371 for (std::size_t p = 0; p < cb[idx[u]].size(); ++p)
2372 for (std::size_t q = 0; q < cb[idx[v]].size(); ++q)
2373 acc += sol.
moments.
Sigma(cb[idx[u]][p], cb[idx[v]][q]);
2379 if (logp_out) *logp_out = p > 0.0 ? std::log(p) : -std::numeric_limits<double>::infinity();
2398 double* logp_out =
nullptr) {
2399 const std::size_t M =
sn.nstations, K =
sn.nclasses;
2400 if (ist == 0 || ist > M)
2401 throw InputError(
"fluid_prob_aggr: station number exceeds the number of stations");
2402 const std::size_t i = ist - 1;
2407 std::vector<double> nir(K, 0.0);
2408 for (std::size_t r = 0; r < K; ++r) {
2409 const double pop =
sn.classes[r].population;
2410 if (std::isfinite(pop) &&
sn.classes[r].refstat == ist) nir[r] = pop;
2414 bool minus_inf =
false;
2429 bool open_here =
false;
2430 for (std::size_t r = 0; r < K; ++r)
2438 bool any_open =
false;
2439 for (std::size_t r = 0; r < K; ++r)
2440 if (!std::isfinite(
sn.classes[r].population)) any_open =
true;
2442 for (std::size_t r = 0; r < K; ++r) {
2443 if (std::isfinite(
sn.classes[r].population))
continue;
2444 const double q = sol.
QN(i, r);
2446 logp += nir[r] * std::log(q) - q - std::lgamma(nir[r] + 1.0);
2447 else if (nir[r] > 0.0)
2451 double rho_total = 0.0, n_total = 0.0;
2452 for (std::size_t r = 0; r < K; ++r) {
2453 if (std::isfinite(
sn.classes[r].population))
continue;
2454 rho_total += sol.
UN(i, r);
2457 if (rho_total < 1.0) {
2458 logp += std::log(1.0 - rho_total) + std::lgamma(n_total + 1.0);
2459 for (std::size_t r = 0; r < K; ++r) {
2460 if (std::isfinite(
sn.classes[r].population) || !(nir[r] > 0.0))
continue;
2461 const double rho_r = sol.
UN(i, r);
2463 logp += nir[r] * std::log(rho_r) - std::lgamma(nir[r] + 1.0);
2473 for (std::size_t r = 0; r < K; ++r) {
2474 const double N =
sn.classes[r].population;
2475 if (!std::isfinite(N))
continue;
2476 const double q = sol.
QN(i, r);
2477 const double p = (N > 0.0) ? q / N : 0.0;
2479 logp += std::lgamma(N + 1.0) - std::lgamma(nir[r] + 1.0) - std::lgamma(N - nir[r] + 1.0);
2481 logp += nir[r] * std::log(p);
2482 }
else if (nir[r] > 0.0) {
2486 logp += (N - nir[r]) * std::log(1.0 - p);
2487 }
else if (N - nir[r] > 0.0) {
2493 if (logp_out) *logp_out = -std::numeric_limits<double>::infinity();
2496 if (logp_out) *logp_out = logp;
2497 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< 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.
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...
std::size_t iter_max
cap on outer integrations
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 > 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).