276 if (!std::is_same<T, double>::value)
278 "solver_fluid_kp: the covariance equation is integrated with LSODA, whose coefficients "
279 "assume double precision; rerun with --arith double");
281 const std::size_t M =
sn.nstations, K =
sn.nclasses;
282 for (std::size_t r = 0; r < K; ++r)
283 if (std::isfinite(
sn.classes[r].population))
285 "solver_fluid_kp: the 'kp' method analyses the OPEN (MAP_t/Ph_t/inf)^N network of "
286 "Ko and Pender (2017); a closed class has no arrival process to modulate. Use "
287 "'closing' or 'matrix' for closed models");
290 std::vector<KpBlock> ublocks, xblocks;
292 std::vector<std::vector<std::size_t>> uof(M, std::vector<std::size_t>(K, 0));
293 std::vector<std::vector<std::size_t>> xof(M, std::vector<std::size_t>(K, 0));
294 std::vector<std::vector<bool>> is_u(M, std::vector<bool>(K,
false));
295 std::vector<std::vector<bool>> is_x(M, std::vector<bool>(K,
false));
296 for (std::size_t i = 0; i < M; ++i) {
298 for (std::size_t r = 0; r < K; ++r) {
299 if (
sn.disabled[i][r])
continue;
300 const std::size_t h =
sn.service[i][r].D0.rows();
302 if (h == 0 || !std::isfinite(rate) || rate <= 0.0)
continue;
309 ublocks.push_back(b);
313 xblocks.push_back(b);
320 const std::size_t dim = off;
323 "solver_fluid_kp: the 'kp' method needs at least one Source with an arrival process");
325 bool linear_model =
true;
326 for (std::size_t b = 0; b < xblocks.size(); ++b) {
327 const std::size_t i = xblocks[b].station;
329 std::isfinite(
sn.stations[i].nservers))
330 linear_model =
false;
339 std::vector<std::vector<Matrix<double>>> D0(M, std::vector<
Matrix<double>>(K)),
341 std::vector<std::vector<std::vector<double>>> pie(M, std::vector<std::vector<double>>(K));
346 std::vector<kp_detail::KpSchedule> sched;
347 for (std::size_t i = 0; i < M; ++i)
348 for (std::size_t r = 0; r < K; ++r) {
349 if (!is_u[i][r] && !is_x[i][r])
continue;
350 kp_detail::kp_pair(
sn, i, r, D0[i][r], D1[i][r]);
355 pie[i][r] = kp_detail::kp_pie(D0[i][r], D1[i][r]);
358 kp_detail::KpSchedule ks;
362 for (std::size_t a = 0; a < sc.
breakpoints.size(); ++a)
364 for (std::size_t k = 0; k < sc.
segD0.size(); ++k) {
365 const std::size_t nph = sc.
segD0[k].rows();
367 for (std::size_t a = 0; a < nph; ++a)
368 for (std::size_t b = 0; b < nph; ++b) {
372 ks.segD0.push_back(A);
373 ks.segD1.push_back(B);
382 std::vector<std::vector<Matrix<double>>> Dt0 = D0, Dt1 = D1;
383 const bool time_varying = !sched.empty();
384 const auto pairs_at = [&](
double t) {
385 if (!time_varying)
return;
386 for (std::size_t e = 0; e < sched.size(); ++e) {
387 const std::size_t i = sched[e].station, r = sched[e].cls;
388 kp_detail::kp_pair_at(sched, D0[i][r], D1[i][r], i, r, t, Dt0[i][r], Dt1[i][r]);
393 const std::size_t S =
sn.nof_stateful();
394 const bool have_rt =
sn.rt.rows() == S * K;
395 std::vector<std::size_t> sf(M, 0);
396 for (std::size_t i = 0; i < M; ++i) sf[i] =
sn.stateful_of_station(i + 1) - 1;
397 const auto route = [&](std::size_t i, std::size_t c, std::size_t j, std::size_t l) ->
double {
398 if (!have_rt)
return 0.0;
404 std::vector<std::vector<double>> pout(M, std::vector<double>(K, 0.0));
405 for (std::size_t i = 0; i < M; ++i)
406 for (std::size_t r = 0; r < K; ++r) {
407 if (!is_x[i][r])
continue;
409 for (std::size_t j = 0; j < M; ++j)
410 for (std::size_t l = 0; l < K; ++l)
411 if (!is_x[j][l]) acc += route(i, r, j, l);
416 std::vector<KpEvent> ev;
417 const auto push = [&](
KpEvent e) {
420 for (std::size_t b = 0; b < ublocks.size(); ++b) {
422 for (std::size_t k = 0; k < u.
nphases; ++k)
423 for (std::size_t j = 0; j < u.
nphases; ++j) {
424 if (k == j)
continue;
437 for (std::size_t b = 0; b < ublocks.size(); ++b) {
439 for (std::size_t d = 0; d < xblocks.size(); ++d) {
440 const KpBlock& xb = xblocks[d];
442 if (!(p > 0.0))
continue;
443 for (std::size_t k = 0; k < u.
nphases; ++k)
444 for (std::size_t j = 0; j < u.
nphases; ++j)
445 for (std::size_t ip = 0; ip < xb.
nphases; ++ip) {
467 for (std::size_t b = 0; b < xblocks.size(); ++b) {
468 const KpBlock& xb = xblocks[b];
469 for (std::size_t p = 0; p < xb.
nphases; ++p)
470 for (std::size_t q = 0; q < xb.
nphases; ++q) {
471 if (p == q)
continue;
484 for (std::size_t b = 0; b < xblocks.size(); ++b) {
485 const KpBlock& xb = xblocks[b];
487 for (std::size_t p = 0; p < xb.
nphases; ++p) {
498 for (std::size_t d = 0; d < xblocks.size(); ++d) {
499 const KpBlock& nb = xblocks[d];
501 if (!(p > 0.0))
continue;
502 for (std::size_t q = 0; q < xb.
nphases; ++q)
503 for (std::size_t ip = 0; ip < nb.
nphases; ++ip) {
522 const std::size_t nev = ev.size();
525 const auto capacity = [&](
const double* q, std::size_t i) ->
double {
527 !std::isfinite(
sn.stations[i].nservers))
530 for (std::size_t b = 0; b < xblocks.size(); ++b) {
531 if (xblocks[b].station != i)
continue;
532 for (std::size_t p = 0; p < xblocks[b].nphases; ++p)
533 ni += std::max(q[xblocks[b].offset + p], 0.0);
535 const double c =
sn.stations[i].nservers;
536 return (ni <= c) ? 1.0 : c / ni;
539 const auto rates = [&](
const double* q, std::vector<double>& f) {
541 std::vector<double> cap(M, 1.0);
542 for (std::size_t i = 0; i < M; ++i) cap[i] = capacity(q, i);
543 for (std::size_t e = 0; e < nev; ++e) {
545 const double mass = std::max(q[s.
off_src + s.
k], 0.0);
548 f[e] = Dt0[s.
i][s.
c](s.
k, s.
j) * mass;
551 f[e] = Dt1[s.
i][s.
c](s.
k, s.
j) * s.
weight * mass;
554 f[e] = Dt0[s.
i][s.
c](s.
k, s.
j) * mass * cap[s.
i];
559 for (std::size_t b = 0; b < Dt1[s.
i][s.
c].cols(); ++b)
560 rowsum += Dt1[s.
i][s.
c](s.
k, b);
561 f[e] = rowsum * s.
weight * mass * cap[s.
i];
567 const auto apply_jumps = [&](
const std::vector<double>& f,
double* dq) {
568 for (std::size_t a = 0; a < dim; ++a) dq[a] = 0.0;
569 for (std::size_t e = 0; e < nev; ++e) {
570 if (f[e] == 0.0)
continue;
571 for (std::size_t a = 0; a < ev[e].minus.size(); ++a) dq[ev[e].minus[a]] -= f[e];
572 for (std::size_t a = 0; a < ev[e].plus.size(); ++a) dq[ev[e].plus[a]] += f[e];
578 double tend =
opt.timespan_end;
579 const bool unbounded = !std::isfinite(tend);
584 for (std::size_t e = 0; e < sched.size(); ++e)
585 if (sched[e].cyclic) period = std::max(period, sched[e].bp.back() - sched[e].bp.front());
587 double slow = std::numeric_limits<double>::infinity();
588 for (std::size_t i = 0; i < M; ++i)
589 for (std::size_t r = 0; r < K; ++r) {
591 if (std::isfinite(rate) && rate > 0.0) slow = std::min(slow, rate);
593 if (!std::isfinite(slow)) slow = 1.0;
594 tend = t0 + std::max(10.0, 30.0 / slow);
595 if (period > 0.0) tend = std::max(tend, t0 + 10.0 * period);
599 std::vector<double> z(dim + dim * dim, 0.0);
604 for (std::size_t b = 0; b < ublocks.size(); ++b) {
606 const std::vector<double> theta =
608 for (std::size_t a = 0; a < u.
nphases; ++a) z[u.
offset + a] = theta[a];
609 for (std::size_t a = 0; a < u.
nphases; ++a)
610 for (std::size_t c2 = 0; c2 < u.
nphases; ++c2)
612 (a == c2 ? theta[a] : 0.0) - theta[a] * theta[c2];
621 if (!
opt.kp_init_sol.empty()) {
622 if (
opt.kp_init_sol.size() != dim)
623 throw InputError(
"solver_fluid_kp: config.kp_init_sol has " +
624 std::to_string(
opt.kp_init_sol.size()) +
625 " entries but the 'kp' state vector of this model has " +
626 std::to_string(dim) +
627 ", laid out station-major over the (station, class) blocks. "
628 "It is NOT laid out like init_sol.");
629 for (std::size_t a = 0; a < dim; ++a) z[a] =
opt.kp_init_sol[a];
634 if (
opt.init_cov.rows() > 0 ||
opt.init_cov.cols() > 0) {
635 if (
opt.init_cov.rows() != dim ||
opt.init_cov.cols() != dim)
636 throw InputError(
"solver_fluid_kp: config.init_cov is " +
637 std::to_string(
opt.init_cov.rows()) +
"x" +
638 std::to_string(
opt.init_cov.cols()) +
639 " but the 'kp' state vector of this model has " +
640 std::to_string(dim) +
" entries, so the covariance must be " +
641 std::to_string(dim) +
"x" + std::to_string(dim) +
".");
642 double asym = 0.0, scale = 0.0;
643 for (std::size_t a = 0; a < dim; ++a)
644 for (std::size_t c2 = 0; c2 < dim; ++c2) {
645 const double d =
opt.init_cov(a, c2) -
opt.init_cov(c2, a);
647 scale +=
opt.init_cov(a, c2) *
opt.init_cov(a, c2);
651 if (std::sqrt(asym) > 1e-6 * std::max(1.0, std::sqrt(scale)))
652 throw InputError(
"solver_fluid_kp: config.init_cov must be symmetric.");
653 for (std::size_t a = 0; a < dim; ++a)
654 for (std::size_t c2 = 0; c2 < dim; ++c2)
655 z[dim + a * dim + c2] =
opt.init_cov(a, c2);
662 const LsodaRhs rhs = [&](
double t,
const double* zz,
double* dz) {
664 std::vector<double> f;
668 for (std::size_t a = 0; a < dim; ++a) qmax = std::max(qmax, std::fabs(zz[a]));
669 const double hstep = 1e-6 * qmax;
671 std::vector<double> qp(zz, zz + dim), fp, fm, dp(dim, 0.0), dm(dim, 0.0);
672 for (std::size_t m = 0; m < dim; ++m) {
673 const double keep = qp[m];
674 qp[m] = keep + hstep;
675 rates(qp.data(), fp);
676 apply_jumps(fp, dp.data());
677 qp[m] = keep - hstep;
678 rates(qp.data(), fm);
679 apply_jumps(fm, dm.data());
681 for (std::size_t a = 0; a < dim; ++a) J(a, m) = (dp[a] - dm[a]) / (2.0 * hstep);
685 std::vector<double> col(dim, 0.0);
686 for (std::size_t e = 0; e < nev; ++e) {
687 if (f[e] == 0.0)
continue;
688 std::fill(col.begin(), col.end(), 0.0);
689 for (std::size_t a = 0; a < ev[e].minus.size(); ++a) col[ev[e].minus[a]] -= 1.0;
690 for (std::size_t a = 0; a < ev[e].plus.size(); ++a) col[ev[e].plus[a]] += 1.0;
691 for (std::size_t a = 0; a < dim; ++a) {
692 if (col[a] == 0.0)
continue;
693 for (std::size_t b = 0; b < dim; ++b)
694 if (col[b] != 0.0) G(a, b) += col[a] * f[e] * col[b];
697 for (std::size_t a = 0; a < dim; ++a)
698 for (std::size_t b = 0; b < dim; ++b) {
699 double acc = G(a, b);
700 for (std::size_t c2 = 0; c2 < dim; ++c2)
701 acc += J(a, c2) * zz[dim + c2 * dim + b] + zz[dim + a * dim + c2] * J(b, c2);
702 dz[dim + a * dim + b] = acc;
709 lopt.
h_max = (tend - t0) / 10.0;
713 double narrowest = std::numeric_limits<double>::infinity();
714 for (std::size_t e = 0; e < sched.size(); ++e)
715 for (std::size_t k = 1; k < sched[e].bp.size(); ++k)
716 narrowest = std::min(narrowest, sched[e].bp[k] - sched[e].bp[k - 1]);
717 if (std::isfinite(narrowest) && narrowest > 0.0)
718 lopt.
h_max = std::min(lopt.
h_max, narrowest / 4.0);
724 const bool averaging = unbounded && period > 0.0;
725 const double w0 = averaging ? std::max(t0, tend - period) : t0;
726 std::vector<double> grid;
728 const std::size_t ngrid = 201;
729 for (std::size_t a = 0; a < ngrid; ++a)
730 grid.push_back(t0 + (tend - t0) *
static_cast<double>(a) /
731 static_cast<double>(ngrid - 1));
733 const std::size_t nref = 2001;
734 for (std::size_t a = 0; a < nref; ++a)
735 grid.push_back(w0 + (tend - w0) *
static_cast<double>(a) /
736 static_cast<double>(nref - 1));
737 std::vector<double> bounds;
738 for (std::size_t e = 0; e < sched.size(); ++e) {
739 const std::vector<double>& bp = sched[e].bp;
740 const double per = bp.back() - bp.front();
741 if (sched[e].cyclic && per > 0.0) {
742 const long kmax =
static_cast<long>(std::ceil((tend - w0) / per)) + 2;
743 for (
long kk = -1; kk <= kmax; ++kk)
744 for (std::size_t a = 0; a < bp.size(); ++a)
745 bounds.push_back(bp[a] +
static_cast<double>(kk) * per);
747 for (std::size_t a = 0; a < bp.size(); ++a) bounds.push_back(bp[a]);
750 const double eps_b = std::max(1e-9, 1e-7 * (tend - w0));
751 for (std::size_t a = 0; a < bounds.size(); ++a) {
752 const double b = bounds[a];
753 if (!(b > w0 && b < tend))
continue;
754 grid.push_back(b - eps_b);
756 grid.push_back(b + eps_b);
759 std::sort(grid.begin(), grid.end());
760 grid.erase(std::remove_if(grid.begin(), grid.end(),
761 [&](
double v) { return v < t0 || v > tend; }),
763 grid.erase(std::unique(grid.begin(), grid.end()), grid.end());
764 if (grid.empty() || grid.front() > t0) grid.insert(grid.begin(), t0);
774 const std::vector<double>& zend = sol.
final_state();
782 out.
xvec.assign(zend.begin(), zend.begin() + dim);
784 const std::size_t nt = sol.
t.size();
786 for (std::size_t b = 0; b < xblocks.size(); ++b) {
787 const KpBlock& xb = xblocks[b];
788 std::vector<double> qser(nt, 0.0), user(nt, 0.0), tser(nt, 0.0);
790 for (std::size_t n = 0; n < nt; ++n) {
791 const std::vector<double>& zs = sol.
y[n];
793 for (std::size_t p = 0; p < xb.
nphases; ++p) q += zs[xb.
offset + p];
795 const double cap = capacity(zs.data(), xb.
station);
797 for (std::size_t p = 0; p < xb.
nphases; ++p) {
799 for (std::size_t c2 = 0; c2 < Dt1[xb.
station][xb.
cls].cols(); ++c2)
801 tn += rowsum * std::max(zs[xb.
offset + p], 0.0) * cap;
803 const double c =
sn.stations[xb.
station].nservers;
809 : std::min(q, c) / c;
811 for (std::size_t p = 0; p < xb.
nphases; ++p)
812 for (std::size_t p2 = 0; p2 < xb.
nphases; ++p2)
813 vend += zend[dim + (xb.
offset + p) * dim + (xb.
offset + p2)];
814 out.
QN(xb.
station, xb.
cls) = kp_detail::kp_summarise(qser, sol.
t, w0, tend, averaging);
815 out.
UN(xb.
station, xb.
cls) = kp_detail::kp_summarise(user, sol.
t, w0, tend, averaging);
816 out.
TN(xb.
station, xb.
cls) = kp_detail::kp_summarise(tser, sol.
t, w0, tend, averaging);
823 for (std::size_t b = 0; b < ublocks.size(); ++b) {
825 std::vector<double> aser(nt, 0.0);
826 for (std::size_t n = 0; n < nt; ++n) {
829 for (std::size_t p = 0; p < u.
nphases; ++p) {
831 for (std::size_t c2 = 0; c2 < Dt1[u.
station][u.
cls].cols(); ++c2)
833 tn += rowsum * std::max(sol.
y[n][u.
offset + p], 0.0);
837 out.
TN(u.
station, u.
cls) = kp_detail::kp_summarise(aser, sol.
t, w0, tend, averaging);
845 for (std::size_t a = 0; a < dim; ++a)
846 for (std::size_t b = 0; b < dim; ++b) rep.
Sigma(a, b) = zend[dim + a * dim + b];
849 for (std::size_t i = 0; i < M; ++i)
850 for (std::size_t r = 0; r < K; ++r)
851 rep.
QStd(i, r) = std::sqrt(std::max(0.0, QVar(i, r)));
856 out.
XN.assign(K, 0.0);
857 out.
CN.assign(K, 0.0);
858 for (std::size_t r = 0; r < K; ++r) {
859 const std::size_t rs =
sn.classes[r].refstat;
860 if (rs >= 1 && rs <= M) out.
XN[r] = out.
TN(rs - 1, r);
862 for (std::size_t i = 0; i < M; ++i) q += out.
QN(i, r);
863 if (out.
XN[r] > 0.0) out.
CN[r] = q / out.
XN[r];
866 if (tran !=
nullptr) {
871 for (std::size_t s = 0; s < sol.
y.size(); ++s) {
872 const std::vector<double>& zs = sol.
y[s];
873 tran->
q.push_back(std::vector<double>(zs.begin(), zs.begin() + dim));
875 for (std::size_t a = 0; a < dim; ++a)
876 for (std::size_t b = 0; b < dim; ++b) Sg(a, b) = zs[dim + a * dim + b];
877 for (std::size_t b = 0; b < xblocks.size(); ++b) {
878 const KpBlock& xb = xblocks[b];
880 for (std::size_t p = 0; p < xb.
nphases; ++p)
881 for (std::size_t p2 = 0; p2 < xb.
nphases; ++p2)
885 tran->
QVar.push_back(V);
886 tran->
Sigma.push_back(Sg);