632 const std::vector<std::vector<double>>& post,
633 std::vector<std::vector<double>> inh,
const std::vector<double>& rate,
634 std::vector<bool> imm, std::vector<double> srv,
const std::vector<double>& m0,
637 const double Inf = std::numeric_limits<double>::infinity();
638 if (
opt.extra < 3) badarg(
"OPTIONS.extra must be at least 3");
639 const std::size_t P = pre.size(), T = P > 0 ? pre[0].size() : 0;
640 if (post.size() != P) badarg(
"PRE and POST must both be P x T");
641 for (std::size_t p = 0; p < P; ++p)
642 if (pre[p].size() != T || post[p].size() != T) badarg(
"PRE and POST must both be P x T");
643 if (inh.empty()) inh.assign(P, Vec(T, Inf));
644 if (imm.empty()) imm.assign(T,
false);
645 if (srv.empty()) srv.assign(T, 1.0);
646 bool shape = inh.size() == P && rate.size() == T && imm.size() == T && srv.size() == T && m0.size() == P;
647 for (std::size_t p = 0; shape && p < P; ++p) shape = inh[p].size() == T;
648 if (!shape) badarg(
"INH must be P x T, RATE, IMM and SRV of length T and M0 of length P");
649 for (std::size_t p = 0; p < P; ++p) {
650 if (m0[p] < 0 || m0[p] != std::rint(m0[p]))
651 badarg(
"arc multiplicities and the initial marking must be non-negative integers");
652 for (std::size_t t = 0; t < T; ++t)
653 if (pre[p][t] < 0 || post[p][t] < 0 || pre[p][t] != std::rint(pre[p][t]) ||
654 post[p][t] != std::rint(post[p][t]))
655 badarg(
"arc multiplicities and the initial marking must be non-negative integers");
657 for (std::size_t t = 0; t < T; ++t)
658 if (rate[t] < 0 || srv[t] < 1)
659 badarg(
"arc multiplicities, the initial marking and the rates must be non-negative, servers >= 1");
660 if (p0 >= P) badarg(
"P0 must be the index of a place");
662 for (std::size_t t = 0; t < T; ++t) {
663 const double d = post[p0][t] - pre[p0][t];
664 if (std::fabs(d) > 1)
665 refuse(fmt(
"iSPN requirement 2 violated: transition %.0f changes the unbounded place %.0f by %.0f "
666 "tokens, but a firing may move the level by at most one",
667 static_cast<double>(t),
static_cast<double>(p0), d));
669 std::vector<std::size_t> other;
670 for (std::size_t p = 0; p < P; ++p)
671 if (p != p0) other.push_back(p);
674 std::vector<bool> infdeg(T,
false);
675 for (std::size_t t = 0; t < T; ++t) {
676 if (pre[p0][t] > 0) {
677 Kstr = std::max(Kstr,
static_cast<int>(pre[p0][t]));
678 if (!std::isinf(srv[t])) {
679 Kstr = std::max(Kstr,
static_cast<int>(pre[p0][t] * srv[t]));
681 bool any_other =
false;
682 for (std::size_t p : other) any_other = any_other || pre[p][t] > 0;
686 refuse(fmt(
"iSPN requirement 3 violated: transition %.0f is infinite-server and consumes "
687 "only from the unbounded place %.0f, so its rate grows with the level and never "
688 "becomes level independent",
689 static_cast<double>(t),
static_cast<double>(p0)));
692 if (!std::isinf(inh[p0][t])) Kstr = std::max(Kstr,
static_cast<int>(inh[p0][t]));
708 int Ktop = Kneed +
opt.extra;
712 if (Ktop >
opt.maxlevel)
713 refuse(fmt(
"iSPN requirement 1/3 violated: the levels of place %.0f do not become identical (same "
714 "phase markings and level-independent rates) up to level %.0f; either another place is "
715 "unbounded or the rates depend on the level. Raise OPTIONS.maxlevel if the threshold is "
717 static_cast<double>(p0),
opt.maxlevel));
718 const Walk W = explore(net, m0, Ktop + 2,
opt);
719 for (std::size_t t = 0; t < T; ++t) {
720 if (!infdeg[t])
continue;
722 for (
const Vec& m : W.S) {
724 for (std::size_t p : other)
725 if (pre[p][t] > 0) dg = std::min(dg, std::floor(m[p] / pre[p][t]));
726 mx = std::max(mx, dg);
728 Kneed = std::max(Kneed,
static_cast<int>(pre[p0][t] * mx));
730 if (Ktop < Kneed +
opt.extra) {
731 Ktop = Kneed +
opt.extra;
734 E = eliminate(W, net, Ktop);
735 K = find_K(E, Ktop,
opt, p0);
740 const auto& lv = E.levidx;
742 const Mat A0 = sub(Qt, lv[K], lv[K + 1]);
743 const Mat A1 = sub(Qt, lv[K + 1], lv[K + 1]);
744 const Mat A2 = sub(Qt, lv[K + 1], lv[K]);
745 const std::size_t nL = lv[K].size();
747 refuse(fmt(
"place %.0f is bounded: no transition raises its marking above level %.0f. The net has a "
748 "finite state space; solve it with SolverCTMC",
749 static_cast<double>(p0), K));
751 unstable(fmt(
"the iSPN is unstable: no transition lowers the marking of place %.0f above level %.0f, "
752 "so it grows without bound",
753 static_cast<double>(p0), K));
756 Mat Aa = plus(plus(A0, A1), A2);
757 for (std::size_t i = 0; i < nL; ++i) Aa[i][nL - 1] = 1.0;
760 const Vec piA = solve_left(Aa, eLast);
761 const double up = dot(piA, row_sums(A0));
762 const double down = dot(piA, row_sums(A2));
763 if (!(up < down * (1 - 1e-12)))
764 unstable(fmt(
"the iSPN is unstable: the mean drift of place %.0f above level %.0f is up %.6g >= down "
765 "%.6g (Haverkort (17.7)); no steady state exists",
766 static_cast<double>(p0), K, up, down));
768 std::string qm =
opt.qbdmethod;
769 std::transform(qm.begin(), qm.end(), qm.begin(), [](
unsigned char c) { return std::tolower(c); });
772 Rmat =
mam::qbd_R_logred(to_matrix(A2), to_matrix(A1), to_matrix(A0), 100000u, 1e-12);
773 else if (qm ==
"successive")
774 Rmat =
mam::qbd_R(to_matrix(A2), to_matrix(A1), to_matrix(A0));
776 badarg(
"unknown OPTIONS.qbdmethod '" +
opt.qbdmethod +
"'; use 'logred' or 'successive'");
777 const Mat Rm = from_matrix(Rmat);
779 if (!(spR < 1)) unstable(fmt(
"the iSPN is unstable: the spectral radius of R is %.6g >= 1", spR));
780 const Mat I =
eye(nL);
781 const Mat Ni = inv(minus(I, Rm));
784 std::vector<std::size_t> bidx;
785 for (
int i = 0; i <= K; ++i) bidx.insert(bidx.end(), lv[i].begin(), lv[i].end());
786 const std::size_t nb = bidx.size();
787 const Mat B = sub(Qt, bidx, bidx);
789 const Mat RA2 = mult(Rm, A2);
790 for (std::size_t i = 0; i < nL; ++i)
791 for (std::size_t j = 0; j < nL; ++j) Bm[nb - nL + i][nb - nL + j] += RA2[i][j];
792 const Vec NiOne = row_sums(Ni);
793 for (std::size_t i = 0; i < nb; ++i) Bm[i][nb - 1] = 1.0;
794 for (std::size_t i = 0; i < nL; ++i) Bm[nb - nL + i][nb - 1] = NiOne[i];
797 Vec xb = solve_left(Bm, rhs);
799 if (std::fabs(v) < 1e-300) v = 0.0;
801 for (
int i = 0, o = 0; i <= K; ++i) {
802 res.
x.push_back(Vec(xb.begin() + o, xb.begin() + o +
static_cast<long>(lv[i].size())));
803 o +=
static_cast<int>(lv[i].size());
805 const Vec& xK = res.
x[K];
806 const Mat RN = mult(Rm, Ni);
807 const Vec c1 = vec_mult(xK, RN);
808 const Vec c2 = vec_mult(xK, mult(mult(Rm, Rm), mult(Ni, Ni)));
811 const std::size_t nT = S[0].size();
813 for (std::size_t p = 0; p < P; ++p)
814 mm[p] = expect(S[p],
"marking of place " + std::to_string(p), res.
x, lv, K, Ktop, c1, c2,
opt.tol);
816 for (
int i = 0; i <= K; ++i) EL2 += static_cast<double>(i) * i *
sum(res.
x[i]);
817 const Mat N2 = mult(Ni, Ni);
818 const Mat N3 = mult(N2, Ni);
819 const Mat term = plus(plus(scale(RN,
static_cast<double>(K) * K), scale(mult(Rm, N2), 2.0 * K)),
820 mult(mult(Rm, plus(I, Rm)), N3));
821 EL2 += dot(xK, row_sums(term));
822 res.
varlevel = std::max(EL2 - mm[p0] * mm[p0], 0.0);
826 for (
int i = 0; i <= K; ++i) {
827 dist.push_back(
sum(res.
x[i]));
830 Vec v = vec_mult(xK, Rm);
832 while (lvl < opt.distmax && tail > 1e-12) {
834 const double s =
sum(v);
839 if (!std::isinf(
opt.distmax))
840 while (
static_cast<double>(dist.size()) > std::floor(
opt.distmax) + 1) dist.pop_back();
844 for (std::size_t p = 0; p < P; ++p) {
849 std::size_t nmax = 0;
850 for (std::size_t s = 0; s < nT; ++s) nmax = std::max(nmax, static_cast<std::size_t>(S[p][s]));
852 for (std::size_t n = 0; n <= nmax; ++n) {
854 for (std::size_t s = 0; s < nT; ++s) ind[s] = S[p][s] == static_cast<double>(n) ? 1.0 : 0.0;
855 mg[n] = expect(ind,
"marginal of place " + std::to_string(p), res.
x, lv, K, Ktop, c1, c2,
opt.tol);
860 res.
tput.assign(T, 0.0);
861 for (std::size_t t = 0; t < T; ++t) {
863 for (std::size_t s = 0; s < nT; ++s) col[s] = E.trate[s][t];
864 res.
tput[t] = expect(col,
"firing rate of transition " + std::to_string(t), res.
x, lv, K, Ktop, c1, c2,
868 res.
reward.assign(
opt.rewards.size(), 0.0);
869 for (std::size_t j = 0; j <
opt.rewards.size(); ++j) {
871 for (std::size_t s = 0; s < nT; ++s) {
873 for (std::size_t p = 0; p < P; ++p) m[p] = S[p][s];
874 rv[s] =
opt.rewards[j](m);
876 res.
reward[j] = expect(rv,
"reward " + std::to_string(j), res.
x, lv, K, Ktop, c1, c2,
opt.tol);
886 for (
int i = 0; i <= K; ++i) {
887 Mat lm = zeros(P, lv[i].size());
888 for (std::size_t p = 0; p < P; ++p)
889 for (std::size_t j = 0; j < lv[i].size(); ++j) lm[p][j] = S[p][lv[i][j]];
892 res.
drift = {up, down};