316 const std::vector<double>& out_grid = std::vector<double>()) {
317 if (!std::is_same<T, double>::value)
319 "solver_fluid_kp: the covariance equation is integrated with LSODA, whose coefficients "
320 "assume double precision; rerun with --arith double");
322 const std::size_t M =
sn.nstations, K =
sn.nclasses;
323 for (std::size_t r = 0; r < K; ++r)
324 if (std::isfinite(
sn.classes[r].population))
326 "solver_fluid_kp: the 'kp' method analyses the OPEN (MAP_t/Ph_t/inf)^N network of "
327 "Ko and Pender (2017); a closed class has no arrival process to modulate. Use "
328 "'closing' or 'matrix' for closed models");
331 std::vector<KpBlock> ublocks, xblocks;
333 std::vector<std::vector<std::size_t>> uof(M, std::vector<std::size_t>(K, 0));
334 std::vector<std::vector<std::size_t>> xof(M, std::vector<std::size_t>(K, 0));
335 std::vector<std::vector<bool>> is_u(M, std::vector<bool>(K,
false));
336 std::vector<std::vector<bool>> is_x(M, std::vector<bool>(K,
false));
337 for (std::size_t i = 0; i < M; ++i) {
339 for (std::size_t r = 0; r < K; ++r) {
340 if (
sn.disabled[i][r])
continue;
341 const std::size_t h =
sn.service[i][r].D0.rows();
343 if (h == 0 || !std::isfinite(rate) || rate <= 0.0)
continue;
350 ublocks.push_back(b);
354 xblocks.push_back(b);
361 const std::size_t dim = off;
364 "solver_fluid_kp: the 'kp' method needs at least one Source with an arrival process");
366 bool linear_model =
true;
367 for (std::size_t b = 0; b < xblocks.size(); ++b) {
368 const std::size_t i = xblocks[b].station;
370 std::isfinite(
sn.stations[i].nservers))
371 linear_model =
false;
380 std::vector<std::vector<Matrix<double>>> D0(M, std::vector<
Matrix<double>>(K)),
382 std::vector<std::vector<std::vector<double>>> pie(M, std::vector<std::vector<double>>(K));
387 std::vector<kp_detail::KpSchedule> sched;
388 for (std::size_t i = 0; i < M; ++i)
389 for (std::size_t r = 0; r < K; ++r) {
390 if (!is_u[i][r] && !is_x[i][r])
continue;
391 kp_detail::kp_pair(
sn, i, r, D0[i][r], D1[i][r]);
396 pie[i][r] = kp_detail::kp_pie(D0[i][r], D1[i][r]);
399 kp_detail::KpSchedule ks;
403 for (std::size_t a = 0; a < sc.
breakpoints.size(); ++a)
405 for (std::size_t k = 0; k < sc.
segD0.size(); ++k) {
406 const std::size_t nph = sc.
segD0[k].rows();
408 for (std::size_t a = 0; a < nph; ++a)
409 for (std::size_t b = 0; b < nph; ++b) {
413 ks.segD0.push_back(A);
414 ks.segD1.push_back(B);
423 std::vector<std::vector<Matrix<double>>> Dt0 = D0, Dt1 = D1;
424 const bool time_varying = !sched.empty();
425 const auto pairs_at = [&](
double t) {
426 if (!time_varying)
return;
427 for (std::size_t e = 0; e < sched.size(); ++e) {
428 const std::size_t i = sched[e].station, r = sched[e].cls;
429 kp_detail::kp_pair_at(sched, D0[i][r], D1[i][r], i, r, t, Dt0[i][r], Dt1[i][r]);
434 const std::size_t S =
sn.nof_stateful();
435 const bool have_rt =
sn.rt.rows() == S * K;
436 std::vector<std::size_t> sf(M, 0);
437 for (std::size_t i = 0; i < M; ++i) sf[i] =
sn.stateful_of_station(i + 1) - 1;
438 const auto route = [&](std::size_t i, std::size_t c, std::size_t j, std::size_t l) ->
double {
439 if (!have_rt)
return 0.0;
445 std::vector<std::vector<double>> pout(M, std::vector<double>(K, 0.0));
446 for (std::size_t i = 0; i < M; ++i)
447 for (std::size_t r = 0; r < K; ++r) {
448 if (!is_x[i][r])
continue;
450 for (std::size_t j = 0; j < M; ++j)
451 for (std::size_t l = 0; l < K; ++l)
452 if (!is_x[j][l]) acc += route(i, r, j, l);
457 std::vector<KpEvent> ev;
458 const auto push = [&](
KpEvent e) {
461 for (std::size_t b = 0; b < ublocks.size(); ++b) {
463 for (std::size_t k = 0; k < u.
nphases; ++k)
464 for (std::size_t j = 0; j < u.
nphases; ++j) {
465 if (k == j)
continue;
478 for (std::size_t b = 0; b < ublocks.size(); ++b) {
480 for (std::size_t d = 0; d < xblocks.size(); ++d) {
481 const KpBlock& xb = xblocks[d];
483 if (!(p > 0.0))
continue;
484 for (std::size_t k = 0; k < u.
nphases; ++k)
485 for (std::size_t j = 0; j < u.
nphases; ++j)
486 for (std::size_t ip = 0; ip < xb.
nphases; ++ip) {
508 for (std::size_t b = 0; b < xblocks.size(); ++b) {
509 const KpBlock& xb = xblocks[b];
510 for (std::size_t p = 0; p < xb.
nphases; ++p)
511 for (std::size_t q = 0; q < xb.
nphases; ++q) {
512 if (p == q)
continue;
525 for (std::size_t b = 0; b < xblocks.size(); ++b) {
526 const KpBlock& xb = xblocks[b];
528 for (std::size_t p = 0; p < xb.
nphases; ++p) {
539 for (std::size_t d = 0; d < xblocks.size(); ++d) {
540 const KpBlock& nb = xblocks[d];
542 if (!(p > 0.0))
continue;
543 for (std::size_t q = 0; q < xb.
nphases; ++q)
544 for (std::size_t ip = 0; ip < nb.
nphases; ++ip) {
563 const std::size_t nev = ev.size();
566 const auto capacity = [&](
const double* q, std::size_t i) ->
double {
568 !std::isfinite(
sn.stations[i].nservers))
571 for (std::size_t b = 0; b < xblocks.size(); ++b) {
572 if (xblocks[b].station != i)
continue;
573 for (std::size_t p = 0; p < xblocks[b].nphases; ++p)
574 ni += std::max(q[xblocks[b].offset + p], 0.0);
576 const double c =
sn.stations[i].nservers;
577 return (ni <= c) ? 1.0 : c / ni;
580 const auto rates = [&](
const double* q, std::vector<double>& f) {
582 std::vector<double> cap(M, 1.0);
583 for (std::size_t i = 0; i < M; ++i) cap[i] = capacity(q, i);
584 for (std::size_t e = 0; e < nev; ++e) {
586 const double mass = std::max(q[s.
off_src + s.
k], 0.0);
589 f[e] = Dt0[s.
i][s.
c](s.
k, s.
j) * mass;
592 f[e] = Dt1[s.
i][s.
c](s.
k, s.
j) * s.
weight * mass;
595 f[e] = Dt0[s.
i][s.
c](s.
k, s.
j) * mass * cap[s.
i];
600 for (std::size_t b = 0; b < Dt1[s.
i][s.
c].cols(); ++b)
601 rowsum += Dt1[s.
i][s.
c](s.
k, b);
602 f[e] = rowsum * s.
weight * mass * cap[s.
i];
608 const auto apply_jumps = [&](
const std::vector<double>& f,
double* dq) {
609 for (std::size_t a = 0; a < dim; ++a) dq[a] = 0.0;
610 for (std::size_t e = 0; e < nev; ++e) {
611 if (f[e] == 0.0)
continue;
612 for (std::size_t a = 0; a < ev[e].minus.size(); ++a) dq[ev[e].minus[a]] -= f[e];
613 for (std::size_t a = 0; a < ev[e].plus.size(); ++a) dq[ev[e].plus[a]] += f[e];
619 double tend =
opt.timespan_end;
620 const bool unbounded = !std::isfinite(tend);
625 for (std::size_t e = 0; e < sched.size(); ++e)
626 if (sched[e].cyclic) period = std::max(period, sched[e].bp.back() - sched[e].bp.front());
628 double slow = std::numeric_limits<double>::infinity();
629 for (std::size_t i = 0; i < M; ++i)
630 for (std::size_t r = 0; r < K; ++r) {
632 if (std::isfinite(rate) && rate > 0.0) slow = std::min(slow, rate);
634 if (!std::isfinite(slow)) slow = 1.0;
635 tend = t0 + std::max(10.0, 30.0 / slow);
636 if (period > 0.0) tend = std::max(tend, t0 + 10.0 * period);
640 std::vector<double> z(dim + dim * dim, 0.0);
645 for (std::size_t b = 0; b < ublocks.size(); ++b) {
647 const std::vector<double> theta =
649 for (std::size_t a = 0; a < u.
nphases; ++a) z[u.
offset + a] = theta[a];
650 for (std::size_t a = 0; a < u.
nphases; ++a)
651 for (std::size_t c2 = 0; c2 < u.
nphases; ++c2)
653 (a == c2 ? theta[a] : 0.0) - theta[a] * theta[c2];
662 if (!
opt.kp_init_sol.empty()) {
663 if (
opt.kp_init_sol.size() != dim)
664 throw InputError(
"solver_fluid_kp: config.kp_init_sol has " +
665 std::to_string(
opt.kp_init_sol.size()) +
666 " entries but the 'kp' state vector of this model has " +
667 std::to_string(dim) +
668 ", laid out station-major over the (station, class) blocks. "
669 "It is NOT laid out like init_sol.");
670 for (std::size_t a = 0; a < dim; ++a) z[a] =
opt.kp_init_sol[a];
675 if (
opt.init_cov.rows() > 0 ||
opt.init_cov.cols() > 0) {
676 if (
opt.init_cov.rows() != dim ||
opt.init_cov.cols() != dim)
677 throw InputError(
"solver_fluid_kp: config.init_cov is " +
678 std::to_string(
opt.init_cov.rows()) +
"x" +
679 std::to_string(
opt.init_cov.cols()) +
680 " but the 'kp' state vector of this model has " +
681 std::to_string(dim) +
" entries, so the covariance must be " +
682 std::to_string(dim) +
"x" + std::to_string(dim) +
".");
683 double asym = 0.0, scale = 0.0;
684 for (std::size_t a = 0; a < dim; ++a)
685 for (std::size_t c2 = 0; c2 < dim; ++c2) {
686 const double d =
opt.init_cov(a, c2) -
opt.init_cov(c2, a);
688 scale +=
opt.init_cov(a, c2) *
opt.init_cov(a, c2);
692 if (std::sqrt(asym) > 1e-6 * std::max(1.0, std::sqrt(scale)))
693 throw InputError(
"solver_fluid_kp: config.init_cov must be symmetric.");
694 for (std::size_t a = 0; a < dim; ++a)
695 for (std::size_t c2 = 0; c2 < dim; ++c2)
696 z[dim + a * dim + c2] =
opt.init_cov(a, c2);
712 const bool has_qlen =
opt.init_qlen.rows() > 0 ||
opt.init_qlen.cols() > 0;
713 const bool has_qcov =
opt.init_qcov.rows() > 0 ||
opt.init_qcov.cols() > 0;
714 if (has_qlen || has_qcov) {
715 if (!
opt.kp_init_sol.empty() ||
opt.init_cov.rows() > 0 ||
opt.init_cov.cols() > 0)
717 "solver_fluid_kp: config.init_qlen/init_qcov and config.kp_init_sol/init_cov are "
718 "two spellings of the same initial condition, the first over (station,class) "
719 "pairs and the second over this method's own phase layout. Supply one or the "
721 const std::size_t n = M * K;
724 if (
opt.init_qlen.rows() != M ||
opt.init_qlen.cols() != K)
725 throw InputError(
"solver_fluid_kp: config.init_qlen is " +
726 std::to_string(
opt.init_qlen.rows()) +
"x" +
727 std::to_string(
opt.init_qlen.cols()) +
" but the model has " +
728 std::to_string(M) +
" stations and " + std::to_string(K) +
730 Q0sc =
opt.init_qlen;
733 if (
opt.init_qcov.rows() != n ||
opt.init_qcov.cols() != n)
734 throw InputError(
"solver_fluid_kp: config.init_qcov is " +
735 std::to_string(
opt.init_qcov.rows()) +
"x" +
736 std::to_string(
opt.init_qcov.cols()) +
" but the model has " +
738 " station-class pairs, so the covariance must be " +
739 std::to_string(n) +
"x" + std::to_string(n) +
740 ", indexed r*" + std::to_string(M) +
"+i.");
741 double asym = 0.0, scale = 0.0;
742 for (std::size_t a = 0; a < n; ++a)
743 for (std::size_t c2 = 0; c2 < n; ++c2) {
744 const double d =
opt.init_qcov(a, c2) -
opt.init_qcov(c2, a);
746 scale +=
opt.init_qcov(a, c2) *
opt.init_qcov(a, c2);
748 if (std::sqrt(asym) > 1e-6 * std::max(1.0, std::sqrt(scale)))
749 throw InputError(
"solver_fluid_kp: config.init_qcov must be symmetric.");
750 C0sc =
opt.init_qcov;
752 for (std::size_t b = 0; b < xblocks.size(); ++b) {
753 const KpBlock& xb = xblocks[b];
754 const std::vector<double> pib =
756 const std::size_t irb = xb.
cls * M + xb.
station;
757 const double mirb = std::max(0.0, Q0sc(xb.
station, xb.
cls));
758 for (std::size_t a = 0; a < xb.
nphases; ++a) z[xb.
offset + a] = mirb * pib[a];
759 for (std::size_t a = 0; a < xb.
nphases; ++a)
760 for (std::size_t c2 = 0; c2 < xb.
nphases; ++c2)
762 C0sc(irb, irb) * pib[a] * pib[c2] +
763 mirb * ((a == c2 ? pib[a] : 0.0) - pib[a] * pib[c2]);
764 for (std::size_t d = 0; d < xblocks.size(); ++d) {
765 if (d == b)
continue;
766 const KpBlock& xd = xblocks[d];
767 const std::vector<double> pid =
769 const std::size_t ird = xd.
cls * M + xd.
station;
770 for (std::size_t a = 0; a < xb.
nphases; ++a)
771 for (std::size_t c2 = 0; c2 < xd.
nphases; ++c2)
773 C0sc(irb, ird) * pib[a] * pid[c2];
782 const LsodaRhs rhs = [&](
double t,
const double* zz,
double* dz) {
784 std::vector<double> f;
788 for (std::size_t a = 0; a < dim; ++a) qmax = std::max(qmax, std::fabs(zz[a]));
789 const double hstep = 1e-6 * qmax;
791 std::vector<double> qp(zz, zz + dim), fp, fm, dp(dim, 0.0), dm(dim, 0.0);
792 for (std::size_t m = 0; m < dim; ++m) {
793 const double keep = qp[m];
794 qp[m] = keep + hstep;
795 rates(qp.data(), fp);
796 apply_jumps(fp, dp.data());
797 qp[m] = keep - hstep;
798 rates(qp.data(), fm);
799 apply_jumps(fm, dm.data());
801 for (std::size_t a = 0; a < dim; ++a) J(a, m) = (dp[a] - dm[a]) / (2.0 * hstep);
805 std::vector<double> col(dim, 0.0);
806 for (std::size_t e = 0; e < nev; ++e) {
807 if (f[e] == 0.0)
continue;
808 std::fill(col.begin(), col.end(), 0.0);
809 for (std::size_t a = 0; a < ev[e].minus.size(); ++a) col[ev[e].minus[a]] -= 1.0;
810 for (std::size_t a = 0; a < ev[e].plus.size(); ++a) col[ev[e].plus[a]] += 1.0;
811 for (std::size_t a = 0; a < dim; ++a) {
812 if (col[a] == 0.0)
continue;
813 for (std::size_t b = 0; b < dim; ++b)
814 if (col[b] != 0.0) G(a, b) += col[a] * f[e] * col[b];
817 for (std::size_t a = 0; a < dim; ++a)
818 for (std::size_t b = 0; b < dim; ++b) {
819 double acc = G(a, b);
820 for (std::size_t c2 = 0; c2 < dim; ++c2)
821 acc += J(a, c2) * zz[dim + c2 * dim + b] + zz[dim + a * dim + c2] * J(b, c2);
822 dz[dim + a * dim + b] = acc;
829 lopt.
h_max = (tend - t0) / 10.0;
833 double narrowest = std::numeric_limits<double>::infinity();
834 for (std::size_t e = 0; e < sched.size(); ++e)
835 for (std::size_t k = 1; k < sched[e].bp.size(); ++k)
836 narrowest = std::min(narrowest, sched[e].bp[k] - sched[e].bp[k - 1]);
837 if (std::isfinite(narrowest) && narrowest > 0.0)
838 lopt.
h_max = std::min(lopt.
h_max, narrowest / 4.0);
844 const bool averaging = unbounded && period > 0.0;
845 const double w0 = averaging ? std::max(t0, tend - period) : t0;
846 std::vector<double> grid;
847 if (!out_grid.empty()) {
853 for (std::size_t a = 0; a < out_grid.size(); ++a)
854 if (out_grid[a] >= t0 && out_grid[a] <= tend) grid.push_back(out_grid[a]);
855 std::sort(grid.begin(), grid.end());
856 grid.erase(std::unique(grid.begin(), grid.end()), grid.end());
857 if (grid.empty() || grid.front() > t0) grid.insert(grid.begin(), t0);
859 const std::size_t ngrid = 201;
860 for (std::size_t a = 0; a < ngrid; ++a)
861 grid.push_back(t0 + (tend - t0) *
static_cast<double>(a) /
862 static_cast<double>(ngrid - 1));
864 const std::size_t nref = 2001;
865 for (std::size_t a = 0; a < nref; ++a)
866 grid.push_back(w0 + (tend - w0) *
static_cast<double>(a) /
867 static_cast<double>(nref - 1));
868 std::vector<double> bounds;
869 for (std::size_t e = 0; e < sched.size(); ++e) {
870 const std::vector<double>& bp = sched[e].bp;
871 const double per = bp.back() - bp.front();
872 if (sched[e].cyclic && per > 0.0) {
873 const long kmax =
static_cast<long>(std::ceil((tend - w0) / per)) + 2;
874 for (
long kk = -1; kk <= kmax; ++kk)
875 for (std::size_t a = 0; a < bp.size(); ++a)
876 bounds.push_back(bp[a] +
static_cast<double>(kk) * per);
878 for (std::size_t a = 0; a < bp.size(); ++a) bounds.push_back(bp[a]);
881 const double eps_b = std::max(1e-9, 1e-7 * (tend - w0));
882 for (std::size_t a = 0; a < bounds.size(); ++a) {
883 const double b = bounds[a];
884 if (!(b > w0 && b < tend))
continue;
885 grid.push_back(b - eps_b);
887 grid.push_back(b + eps_b);
890 std::sort(grid.begin(), grid.end());
891 grid.erase(std::remove_if(grid.begin(), grid.end(),
892 [&](
double v) { return v < t0 || v > tend; }),
894 grid.erase(std::unique(grid.begin(), grid.end()), grid.end());
895 if (grid.empty() || grid.front() > t0) grid.insert(grid.begin(), t0);
905 const std::vector<double>& zend = sol.
final_state();
913 out.
xvec.assign(zend.begin(), zend.begin() + dim);
915 const std::size_t nt = sol.
t.size();
917 if (tran !=
nullptr) {
922 for (std::size_t b = 0; b < xblocks.size(); ++b) {
923 const KpBlock& xb = xblocks[b];
924 std::vector<double> qser(nt, 0.0), user(nt, 0.0), tser(nt, 0.0);
926 for (std::size_t n = 0; n < nt; ++n) {
927 const std::vector<double>& zs = sol.
y[n];
929 for (std::size_t p = 0; p < xb.
nphases; ++p) q += zs[xb.
offset + p];
931 const double cap = capacity(zs.data(), xb.
station);
933 for (std::size_t p = 0; p < xb.
nphases; ++p) {
935 for (std::size_t c2 = 0; c2 < Dt1[xb.
station][xb.
cls].cols(); ++c2)
937 tn += rowsum * std::max(zs[xb.
offset + p], 0.0) * cap;
939 const double c =
sn.stations[xb.
station].nservers;
945 : std::min(q, c) / c;
947 for (std::size_t p = 0; p < xb.
nphases; ++p)
948 for (std::size_t p2 = 0; p2 < xb.
nphases; ++p2)
949 vend += zend[dim + (xb.
offset + p) * dim + (xb.
offset + p2)];
951 for (std::size_t n = 0; n < nt; ++n) {
956 out.
QN(xb.
station, xb.
cls) = kp_detail::kp_summarise(qser, sol.
t, w0, tend, averaging);
957 out.
UN(xb.
station, xb.
cls) = kp_detail::kp_summarise(user, sol.
t, w0, tend, averaging);
958 out.
TN(xb.
station, xb.
cls) = kp_detail::kp_summarise(tser, sol.
t, w0, tend, averaging);
965 for (std::size_t b = 0; b < ublocks.size(); ++b) {
967 std::vector<double> aser(nt, 0.0);
968 for (std::size_t n = 0; n < nt; ++n) {
971 for (std::size_t p = 0; p < u.
nphases; ++p) {
973 for (std::size_t c2 = 0; c2 < Dt1[u.
station][u.
cls].cols(); ++c2)
975 tn += rowsum * std::max(sol.
y[n][u.
offset + p], 0.0);
980 for (std::size_t n = 0; n < nt; ++n) tran->
TN[n](u.
station, u.
cls) = aser[n];
981 out.
TN(u.
station, u.
cls) = kp_detail::kp_summarise(aser, sol.
t, w0, tend, averaging);
989 for (std::size_t a = 0; a < dim; ++a)
990 for (std::size_t b = 0; b < dim; ++b) rep.
Sigma(a, b) = zend[dim + a * dim + b];
993 for (std::size_t i = 0; i < M; ++i)
994 for (std::size_t r = 0; r < K; ++r)
995 rep.
QStd(i, r) = std::sqrt(std::max(0.0, QVar(i, r)));
1000 out.
XN.assign(K, 0.0);
1001 out.
CN.assign(K, 0.0);
1002 for (std::size_t r = 0; r < K; ++r) {
1003 const std::size_t rs =
sn.classes[r].refstat;
1004 if (rs >= 1 && rs <= M) out.
XN[r] = out.
TN(rs - 1, r);
1006 for (std::size_t i = 0; i < M; ++i) q += out.
QN(i, r);
1007 if (out.
XN[r] > 0.0) out.
CN[r] = q / out.
XN[r];
1010 if (tran !=
nullptr) {
1014 tran->
Sigma.clear();
1016 for (std::size_t s = 0; s < sol.
y.size(); ++s) {
1017 const std::vector<double>& zs = sol.
y[s];
1018 tran->
q.push_back(std::vector<double>(zs.begin(), zs.begin() + dim));
1020 for (std::size_t a = 0; a < dim; ++a)
1021 for (std::size_t b = 0; b < dim; ++b) Sg(a, b) = zs[dim + a * dim + b];
1028 for (std::size_t b = 0; b < xblocks.size(); ++b) {
1029 const KpBlock& xb = xblocks[b];
1031 for (std::size_t p = 0; p < xb.
nphases; ++p)
1032 for (std::size_t p2 = 0; p2 < xb.
nphases; ++p2)
1035 const std::size_t irb = xb.
cls * M + xb.
station;
1036 for (std::size_t d = 0; d < xblocks.size(); ++d) {
1037 const KpBlock& xd = xblocks[d];
1039 for (std::size_t p = 0; p < xb.
nphases; ++p)
1040 for (std::size_t p2 = 0; p2 < xd.
nphases; ++p2)
1045 tran->
QVar.push_back(V);
1046 tran->
Sigma.push_back(Sg);
1047 tran->
QCov.push_back(C);