67 std::vector<double>
N;
72namespace cons_detail {
83 const std::size_t rows = A.
rows(), cols = A.
cols();
86 std::vector<std::size_t> piv;
88 const double tol = 1e-12;
89 for (std::size_t c = 0; c < cols && r < rows; ++c) {
91 double best = std::fabs(R(r, c));
92 for (std::size_t i = r + 1; i < rows; ++i)
93 if (std::fabs(R(i, c)) > best) {
94 best = std::fabs(R(i, c));
98 for (std::size_t i = r; i < rows; ++i) R(i, c) = 0.0;
102 for (std::size_t j = 0; j < cols; ++j) std::swap(R(r, j), R(k, j));
103 const double p = R(r, c);
104 for (std::size_t j = 0; j < cols; ++j) R(r, j) /= p;
105 for (std::size_t i = 0; i < rows; ++i) {
106 if (i == r)
continue;
107 const double f = R(i, c);
108 if (f == 0.0)
continue;
109 for (std::size_t j = 0; j < cols; ++j) R(i, j) -= f * R(r, j);
114 std::vector<std::size_t> free;
115 for (std::size_t c = 0; c < cols; ++c)
116 if (std::find(piv.begin(), piv.end(), c) == piv.end()) free.push_back(c);
118 for (std::size_t i = 0; i < free.size(); ++i) {
120 for (std::size_t rr = 0; rr < piv.size(); ++rr) Z(piv[rr], i) = -R(rr, free[i]);
126inline std::string trim(
double v) {
127 if (v == std::rint(v) && std::fabs(v) < 1e15)
128 return std::to_string(
static_cast<long long>(v));
130 std::snprintf(buf,
sizeof(buf),
"%g", v);
131 return std::string(buf);
135inline std::string row_label(
const PetriTerms& t,
const Matrix<double>& C, std::size_t r) {
137 for (std::size_t s = 0; s < C.
cols(); ++s) {
138 const double w = C(r, s);
139 if (w == 0.0)
continue;
142 nm = t.names_node[t.coord_node[s]] +
"(class " +
143 std::to_string(t.coord_class[s] + 1) +
")";
146 for (std::size_t q = 0; q < t.nf; ++q)
147 if (t.fluid_slot[q] == s) nm =
"level of " + t.fluid_name[q];
148 for (std::size_t j = 0; j < t.modes.size(); ++j)
149 for (std::size_t q = 0; q < t.modes[j].zblk.size(); ++q)
150 if (t.modes[j].zblk[q] == s)
151 nm = t.modes[j].label +
" phase " + std::to_string(q + 1);
153 if (!out.empty()) out +=
" + ";
154 out += (w == 1.0) ? nm : (trim(w) +
"*" + nm);
179 for (std::size_t i = 0; i < t.
D.
rows(); ++i)
180 for (std::size_t j = 0; j < t.
D.
cols(); ++j) Dt(j, i) = t.
D(i, j);
183 for (std::size_t i = 0; i < Z.
rows(); ++i)
184 for (std::size_t j = 0; j < Z.
cols(); ++j) C(j, i) = Z(i, j);
187 for (std::size_t i = 0; i < C.
rows(); ++i)
188 for (std::size_t j = 0; j < C.
cols(); ++j)
189 if (std::fabs(C(i, j)) < 1e-12) C(i, j) = 0.0;
192 std::vector<std::size_t> keep;
193 for (std::size_t i = 0; i < C.
rows(); ++i) {
194 double smallest = std::numeric_limits<double>::infinity();
195 for (std::size_t j = 0; j < C.
cols(); ++j)
196 if (C(i, j) != 0.0) smallest = std::min(smallest, std::fabs(C(i, j)));
197 if (!std::isfinite(smallest))
continue;
198 for (std::size_t j = 0; j < C.
cols(); ++j) C(i, j) /= smallest;
202 for (std::size_t r = 0; r < keep.size(); ++r)
203 for (std::size_t j = 0; j < t.
nstate; ++j) cons.
C(r, j) = C(keep[r], j);
204 cons.
N.assign(cons.
C.
rows(), 0.0);
205 for (std::size_t r = 0; r < cons.
C.
rows(); ++r) {
207 for (std::size_t j = 0; j < t.
nstate; ++j) v += cons.
C(r, j) * t.
x0[j];
210 for (std::size_t r = 0; r < cons.
C.
rows(); ++r)
211 for (std::size_t c = 0; c < t.
nev; ++c) {
213 for (std::size_t j = 0; j < t.
nstate; ++j) v += cons.
C(r, j) * t.
D(j, c);
214 cons.
leak = std::max(cons.
leak, std::fabs(v));
216 for (std::size_t r = 0; r < cons.
C.
rows(); ++r)
217 cons.
label.push_back(cons_detail::row_label(t, cons.
C, r));
232 std::vector<double>
b;
234 std::vector<std::vector<bool>>
cover;
255 std::vector<std::vector<double>> rows;
256 std::vector<double> bs;
257 std::vector<std::string> labels;
258 for (std::size_t pi = 0; pi < t.
places.size(); ++pi) {
259 const std::size_t ind = t.
places[pi];
260 const std::size_t ist =
sn.nodes[ind - 1].station;
261 std::vector<std::size_t> slots;
262 for (std::size_t k = 0; k < t.
K; ++k)
263 if (t.
pidx[ind - 1][k] >= 0)
264 slots.push_back(
static_cast<std::size_t
>(t.
pidx[ind - 1][k]));
265 if (slots.empty())
continue;
266 if (ist >= 1 && ist <=
sn.stations.size()) {
267 const double cap =
sn.stations[ist - 1].cap;
268 if (std::isfinite(cap)) {
269 std::vector<double> row(t.
nstate, 0.0);
270 for (std::size_t s : slots) row[s] = 1.0;
273 labels.push_back(
"capacity " + cons_detail::trim(cap) +
" of place " +
276 for (std::size_t k = 0; k < t.
K; ++k) {
277 if (t.
pidx[ind - 1][k] < 0)
continue;
278 if (k >=
sn.stations[ist - 1].classcap.size())
continue;
279 const double ccap =
sn.stations[ist - 1].classcap[k];
280 if (!std::isfinite(ccap))
continue;
281 std::vector<double> row(t.
nstate, 0.0);
282 row[
static_cast<std::size_t
>(t.
pidx[ind - 1][k])] = 1.0;
285 labels.push_back(
"class-" + std::to_string(k + 1) +
" capacity " +
286 cons_detail::trim(ccap) +
" of place " + t.
names_node[ind - 1]);
293 std::vector<bool> keep(rows.size(),
true);
294 for (std::size_t c = 0; c < rows.size(); ++c) {
295 if (!keep[c])
continue;
296 for (std::size_t d = c + 1; d < rows.size(); ++d) {
297 if (!keep[d] || rows[c] != rows[d])
continue;
305 std::vector<std::size_t> idx;
306 for (std::size_t i = 0; i < keep.size(); ++i)
307 if (keep[i]) idx.push_back(i);
308 std::vector<bool> lowers(idx.size(),
false), fluids(idx.size(),
false);
309 for (std::size_t q = 0; q < t.
nf; ++q) {
312 std::vector<double> row(t.
nstate, 0.0);
317 std::snprintf(buf,
sizeof(buf),
"%g", t.
fluid_cap[q]);
318 labels.push_back(
"fluid capacity " + std::string(buf) +
" of place " + t.
fluid_name[q]);
319 idx.push_back(rows.size() - 1);
320 lowers.push_back(
false);
321 fluids.push_back(
true);
323 std::vector<double> row(t.
nstate, 0.0);
327 labels.push_back(
"empty level of fluid place " + t.
fluid_name[q]);
328 idx.push_back(rows.size() - 1);
329 lowers.push_back(
true);
330 fluids.push_back(
true);
334 con.
b.assign(idx.size(), 0.0);
335 con.
cover.assign(idx.size(), std::vector<bool>(t.
nstate,
false));
336 for (std::size_t r = 0; r < idx.size(); ++r) {
337 for (std::size_t s = 0; s < t.
nstate; ++s) {
338 con.
A(r, s) = rows[idx[r]][s];
339 con.
cover[r][s] = rows[idx[r]][s] != 0.0;
341 con.
b[r] = bs[idx[r]];
342 con.
label.push_back(labels[idx[r]]);
356 std::size_t
a = 0,
b = 0;
361 std::vector<std::ptrdiff_t>
bind;
367namespace imm_detail {
377inline bool inhibited(
const PetriMode& md,
const std::vector<double>& x) {
378 for (std::size_t b = 0; b < md.
inh_slot.size(); ++b)
400 const std::size_t n = t.
imm_idx.size();
403 imm.
active.assign(n,
true);
404 imm.
bind.assign(n, -1);
410 for (std::size_t k = 0; k < n; ++k) {
414 " has no enabling arc, so nothing bounds its firing flow and "
415 "the net has no fluid limit. Give it an input place, or make "
417 if (imm_detail::inhibited(md, x)) imm.
active[k] =
false;
423 for (std::size_t k = 0; k < n; ++k) {
429 if (imm.
bind[k] >= 0 &&
431 static_cast<std::size_t
>(imm.
bind[k])) != md.
arc_slot.end())
435 for (std::size_t a = 1; a < md.
arc_slot.size(); ++a) {
437 if (lev < best_lev) {
442 imm.
bind[k] =
static_cast<std::ptrdiff_t
>(best);
446 std::set<std::size_t> pinset;
447 for (std::size_t k = 0; k < n; ++k)
449 pinset.insert(
static_cast<std::size_t
>(imm.
bind[k]));
450 imm.
pins.assign(pinset.begin(), pinset.end());
452 for (std::size_t p : imm.
pins) {
456 imm.
rows.push_back(row);
457 std::vector<std::size_t> grp;
458 for (std::size_t k = 0; k < n; ++k)
459 if (imm.
active[k] && imm.
bind[k] ==
static_cast<std::ptrdiff_t
>(p)) grp.push_back(k);
460 if (grp.size() <= 1)
continue;
461 int topprio = std::numeric_limits<int>::min();
462 for (std::size_t k : grp) topprio = std::max(topprio, t.
modes[t.
imm_idx[k]].prio);
463 std::vector<std::size_t> top, low;
464 for (std::size_t k : grp)
465 (t.
modes[t.
imm_idx[k]].prio == topprio ? top : low).push_back(k);
467 for (std::size_t i = 1; i < top.size(); ++i) {
474 imm.
rows.push_back(rr);
476 for (std::size_t k : low) {
480 imm.
rows.push_back(rr);
483 for (std::size_t k = 0; k < n; ++k)
488 imm.
rows.push_back(rr);
508 const std::size_t I =
sn.nodes.size();
509 bool has_transition =
false;
510 for (std::size_t i = 0; i < I; ++i)
512 if (!has_transition) {
514 v.
reason =
"the model has no Transition node, so it is not a Petri net";
517 for (std::size_t i = 0; i < I; ++i) {
522 v.
reason =
"node " +
sn.nodes[i].name +
" is a " +
524 ". The fluid Petri route solves the marking of a Petri net, and a model "
525 "that also holds queueing stations is two formalisms at once with no "
526 "reference semantics for the hand-off; use SolverCTMC, SolverJMT, "
527 "SolverSSA or SolverLDES";
533 for (std::size_t i = 0; i < I; ++i) {
535 const std::size_t ist =
sn.nodes[i].station;
536 if (ist < 1 || ist >
sn.nstations)
continue;
537 for (std::size_t k = 0; k <
sn.nclasses; ++k) {
539 if (!std::isnan(rate) && rate > 0) {
541 v.
reason =
"place " +
sn.nodes[i].name +
542 " is a QUEUEING place (it declares a service process), whose embedded "
543 "queue this drift does not carry; use SolverLDES";
558 for (
const auto& kv :
sn.transparam) {
560 for (std::size_t m = 0; m < tp.
nmodes; ++m) {
565 : (
"Mode" + std::to_string(m + 1));
567 v.
reason =
"immediate mode " + mname +
" of transition " +
sn.nodes[kv.first - 1].name +
568 " carries a fluid guard, which fires it on the guard surface itself: a sliding mode the "
569 "mean-field drift has no value for; use SolverSSA";
606 std::vector<double>
t;
609 std::vector<std::vector<std::vector<double>>>
QNt,
UNt,
TNt;
618 std::vector<double>
x;
620 double resnorm = std::numeric_limits<double>::infinity();
650namespace solver_detail {
656 std::vector<std::size_t> active;
659 std::vector<double> N;
669inline Ctx context(
const PetriTerms& terms,
const PetriConservation& cons,
670 const PetriConstraints& con,
const PetriImmediate& imm,
671 const std::vector<std::size_t>& active) {
678 std::vector<double> N = cons.N;
679 if (!active.empty() && C.
rows() > 0) {
680 std::vector<bool> hit(terms.nstate,
false);
681 for (std::size_t c : active)
682 for (std::size_t s = 0; s < terms.nstate; ++s)
683 if (con.cover[c][s]) hit[s] =
true;
684 std::vector<std::size_t> keep;
685 for (std::size_t r = 0; r < C.
rows(); ++r) {
687 for (std::size_t s = 0; s < terms.nstate && !drop; ++s)
688 if (hit[s] && C(r, s) != 0.0) drop =
true;
689 if (!drop) keep.push_back(r);
692 std::vector<double> Nk(keep.size(), 0.0);
693 for (std::size_t i = 0; i < keep.size(); ++i) {
694 for (std::size_t s = 0; s < terms.nstate; ++s) Ck(i, s) = C(keep[i], s);
702 ctx.Dp = Matrix<double>(terms.D.rows(), terms.D.cols(), 0.0);
703 ctx.Dn = Matrix<double>(terms.D.rows(), terms.D.cols(), 0.0);
704 for (std::size_t i = 0; i < terms.D.rows(); ++i)
705 for (std::size_t j = 0; j < terms.D.cols(); ++j) {
706 ctx.Dp(i, j) = std::max(terms.D(i, j), 0.0);
707 ctx.Dn(i, j) = std::min(terms.D(i, j), 0.0);
714 std::vector<double> x, s2, phi, mu, zeta;
717inline Unpacked unpack(
const std::vector<double>& u,
const PetriTerms& t,
718 const PetriImmediate& imm, std::size_t na) {
719 const std::size_t n = t.nstate, npair = t.npair, ni = imm.n, nl = t.latch_mode.size();
721 up.x.assign(u.begin(), u.begin() + n);
722 up.s2.assign(u.begin() + n, u.begin() + n + npair);
723 up.phi.assign(u.begin() + n + npair, u.begin() + n + npair + ni);
724 for (std::size_t i = 0; i < up.phi.size(); ++i) up.phi[i] = std::max(0.0, up.phi[i]);
725 up.mu.assign(u.begin() + n + npair + ni, u.begin() + n + npair + ni + nl);
726 up.zeta.assign(u.begin() + n + npair + ni + nl, u.begin() + n + npair + ni + nl + na);
727 for (std::size_t i = 0; i < up.zeta.size(); ++i) up.zeta[i] = std::max(0.0, up.zeta[i]);
731inline std::ptrdiff_t index_of(
const std::vector<std::size_t>& a, std::size_t v) {
732 for (std::size_t i = 0; i < a.size(); ++i)
733 if (a[i] == v)
return static_cast<std::ptrdiff_t
>(i);
762inline bool clamp_tangent(
const PetriTerms& terms,
const PetriImmediate& imm,
763 const PetriConstraints& con,
const std::vector<std::size_t>& active,
764 const PetriTheta* th, Matrix<double>& T) {
765 const std::vector<std::size_t>& idx = terms.cov_idx;
766 const std::size_t nc = idx.size();
767 T = Matrix<double>(nc, nc, 0.0);
768 for (std::size_t i = 0; i < nc; ++i) T(i, i) = 1.0;
770 std::vector<std::size_t> actk;
771 for (std::size_t k = 0; k < imm.n; ++k)
772 if (imm.active[k] && imm.bind[k] >= 0) actk.push_back(k);
773 const std::vector<std::size_t>& B = imm.pins;
774 if (!actk.empty() && !B.empty()) {
775 Matrix<double> Cf(nc, actk.size(), 0.0);
776 for (std::size_t a = 0; a < actk.size(); ++a) {
777 const std::vector<double>& cv = terms.modes[terms.imm_idx[actk[a]]].cvec;
778 for (std::size_t i = 0; i < nc; ++i) Cf(i, a) = cv[idx[i]];
780 Matrix<double> G0(actk.size(), B.size(), 0.0);
781 for (std::size_t jb = 0; jb < B.size(); ++jb) {
782 std::vector<std::size_t> grp;
784 for (std::size_t q = 0; q < actk.size(); ++q)
785 if (imm.bind[actk[q]] ==
static_cast<std::ptrdiff_t
>(B[jb])) {
787 wsum += terms.modes[terms.imm_idx[actk[q]]].weight;
789 if (grp.empty())
continue;
790 const bool uniform = !(wsum > 0);
791 for (std::size_t q : grp) {
793 uniform ? 1.0 : terms.modes[terms.imm_idx[actk[q]]].weight;
794 G0(q, jb) = w / (uniform ?
static_cast<double>(grp.size()) : wsum);
797 Matrix<double> EB(B.size(), nc, 0.0);
798 for (std::size_t jb = 0; jb < B.size(); ++jb) {
799 const std::ptrdiff_t at = index_of(idx, B[jb]);
800 if (at >= 0) EB(jb,
static_cast<std::size_t
>(at)) = 1.0;
804 for (std::size_t i = 0; i < nc; ++i)
805 for (std::size_t j = 0; j < nc; ++j) T(i, j) -= corr(i, j);
808 std::vector<std::vector<double>> R;
809 for (std::size_t c : active) {
810 std::vector<double> row(nc, 0.0);
811 for (std::size_t i = 0; i < nc; ++i) row[i] = con.A(c, idx[i]);
815 for (std::size_t j : terms.latch_mode) {
816 std::vector<double> row(nc, 0.0);
817 for (std::size_t z : terms.modes[j].zblk) {
818 const std::ptrdiff_t at = index_of(idx, z);
819 if (at >= 0) row[
static_cast<std::size_t
>(at)] = 1.0;
821 for (std::size_t q = 0; q < th->dslot[j].size(); ++q) {
822 const std::ptrdiff_t at = index_of(idx, th->dslot[j][q]);
823 if (at >= 0) row[
static_cast<std::size_t
>(at)] -= th->dval[j][q];
830 for (
const std::vector<double>& row : R)
832 if (std::fabs(v) > 1e-14) any =
true;
834 Matrix<double>
Rm(R.size(), nc, 0.0);
835 for (std::size_t i = 0; i < R.size(); ++i)
836 for (std::size_t j = 0; j < nc; ++j)
Rm(i, j) = R[i][j];
837 Matrix<double> Rt(nc, R.size(), 0.0);
838 for (std::size_t i = 0; i < R.size(); ++i)
839 for (std::size_t j = 0; j < nc; ++j) Rt(j, i) =
Rm(i, j);
840 const Matrix<double> RRt =
matmul(Rm, Rt);
842 Matrix<double>
P(nc, nc, 0.0);
843 for (std::size_t i = 0; i < nc; ++i)
844 for (std::size_t j = 0; j < nc; ++j)
P(i, j) = (i == j ? 1.0 : 0.0) - corr(i, j);
849 for (std::size_t i = 0; i < nc; ++i)
850 for (std::size_t j = 0; j < nc; ++j)
851 worst = std::max(worst, std::fabs(T(i, j) - (i == j ? 1.0 : 0.0)));
852 return worst > 1e-14;
862inline Matrix<double> sigma_of(
const PetriTerms& terms,
const Matrix<double>& A,
863 const std::vector<double>& r,
const Matrix<double>* clampT) {
864 const std::vector<std::size_t>& idx = terms.cov_idx;
865 const std::size_t nc = idx.size(), ns = terms.stoch_col.size(), nfl = terms.flow_col.size();
866 Matrix<double> Dc(nc, std::max<std::size_t>(ns, 1), 0.0);
867 for (std::size_t i = 0; i < nc; ++i)
868 for (std::size_t j = 0; j < ns; ++j) Dc(i, j) = terms.D(idx[i], terms.stoch_col[j]);
869 Matrix<double> Df(nc, nfl, 0.0);
870 for (std::size_t i = 0; i < nc; ++i)
871 for (std::size_t j = 0; j < nfl; ++j) Df(i, j) = terms.D(idx[i], terms.flow_col[j]);
872 Matrix<double> Am(nc, nc, 0.0);
873 for (std::size_t i = 0; i < nc; ++i)
874 for (std::size_t j = 0; j < nc; ++j) Am(i, j) = A(idx[i], idx[j]);
875 if (clampT !=
nullptr) {
881 if (nfl > 0) Df =
matmul(*clampT, Df);
884 Matrix<double> Q(nc, nc, 0.0);
885 for (std::size_t i = 0; i < nc; ++i)
886 for (std::size_t j = 0; j < nc; ++j) {
888 for (std::size_t e = 0; e < ns; ++e)
889 v += Dc(i, e) * r[terms.stoch_col[e]] * Dc(j, e);
895 Matrix<double> basis(nc, Dc.cols() + nfl, 0.0);
896 for (std::size_t i = 0; i < nc; ++i) {
897 for (std::size_t j = 0; j < Dc.cols(); ++j) basis(i, j) = Dc(i, j);
898 for (std::size_t j = 0; j < nfl; ++j) basis(i, Dc.cols() + j) = Df(i, j);
900 FluidLyapunovInfo info;
902 Matrix<double> Sigma(terms.nstate, terms.nstate, 0.0);
903 for (std::size_t i = 0; i < nc; ++i)
904 for (std::size_t j = 0; j < nc; ++j) Sigma(idx[i], idx[j]) = Sc(i, j);
913inline void gains(
const PetriConstraints& con,
const std::vector<std::size_t>& active,
914 const std::vector<double>& zeta, std::size_t n, std::vector<double>& gP,
915 std::vector<double>& gN) {
918 for (std::size_t k = 0; k < active.size(); ++k) {
919 const std::size_t c = active[k];
920 const double z = k < zeta.size() ? zeta[k] : 1.0;
921 const bool low = c < con.lower.size() && con.lower[c];
922 for (std::size_t s = 0; s < n; ++s)
923 if (con.cover[c][s]) (low ? gN[s] : gP[s]) *= z;
930 std::vector<double> G, r;
932 Matrix<double> Sigma;
942inline Residual residual(
const std::vector<double>& u,
const Ctx& ctx,
bool quiet) {
943 const PetriTerms& terms = *ctx.terms;
944 const PetriImmediate& imm = *ctx.imm;
945 const std::size_t n = terms.nstate;
946 const Unpacked up = unpack(u, terms, imm, ctx.active.size());
950 res.r =
petri_rates(terms, up.x, up.phi, up.mu, res.th);
954 std::vector<double> gP, gN;
955 gains(*ctx.con, ctx.active, up.zeta, n, gP, gN);
956 std::vector<double> drift(n, 0.0);
957 for (std::size_t s = 0; s < n; ++s) {
958 double neg = 0.0, pos = 0.0;
959 for (std::size_t e = 0; e < terms.nev; ++e) {
960 neg += ctx.Dn(s, e) * res.r[e];
961 pos += ctx.Dp(s, e) * res.r[e];
963 drift[s] = gN[s] * neg + gP[s] * pos;
970 clamp_tangent(terms, imm, *ctx.con, ctx.active, &res.th, T);
971 res.Sigma = sigma_of(terms, A, res.r, reduced ? &T :
nullptr);
972 }
catch (
const Error&) {
978 std::vector<double> G;
979 G.reserve(n + ctx.C.rows() + terms.npair + imm.rows.size() + terms.latch_mode.size() +
981 for (
double d : drift) G.push_back(d);
982 for (std::size_t r = 0; r < ctx.C.rows(); ++r) {
984 for (std::size_t s = 0; s < n; ++s) v += ctx.C(r, s) * up.x[s];
985 G.push_back(v - ctx.N[r]);
987 for (std::size_t p = 0; p < terms.npair; ++p)
988 G.push_back(up.s2[p] - res.Sigma(terms.cov_pairs[p].first, terms.cov_pairs[p].second));
989 for (
const PetriImmediate::Row& row : imm.rows) {
992 G.push_back(up.phi[row.a] * row.wb - up.phi[row.b] * row.wa);
993 else G.push_back(up.phi[row.a]);
995 for (std::size_t j : terms.latch_mode) {
997 for (std::size_t z : terms.modes[j].zblk) s += up.x[z];
998 G.push_back(s - res.th.theta[j]);
1000 for (std::size_t k = 0; k < ctx.active.size(); ++k) {
1002 for (std::size_t s = 0; s < n; ++s) v += ctx.con->A(ctx.active[k], s) * up.x[s];
1003 G.push_back(v - ctx.con->b[ctx.active[k]]);
1010inline double inf_norm(
const std::vector<double>& v) {
1012 for (
double d : v) m = std::max(m, std::fabs(d));
1023inline std::vector<double> project(
const std::vector<double>& u,
const std::vector<double>& lb) {
1024 if (lb.empty())
return u;
1025 if (lb.size() != u.size())
1026 throw InputError(
"fluid_petri: the bound vector has " + std::to_string(lb.size()) +
1027 " entries for " + std::to_string(u.size()) +
" unknowns");
1028 std::vector<double> w = u;
1029 for (std::size_t i = 0; i < w.size(); ++i)
1030 if (std::isfinite(lb[i])) w[i] = std::max(lb[i], w[i]);
1035inline Matrix<double> fdjac(
const Ctx& ctx,
const std::vector<double>& u,
1036 const std::vector<double>& G) {
1037 const std::size_t n = u.size(), m = G.size();
1038 Matrix<double> J(std::max<std::size_t>(m, 1), std::max<std::size_t>(n, 1), 0.0);
1039 for (std::size_t k = 0; k < n; ++k) {
1040 const double h = 1e-7 * std::max(1.0, std::fabs(u[k]));
1041 std::vector<double> up = u;
1043 Residual rp = residual(up, ctx,
true);
1045 for (std::size_t i = 0; i < m; ++i) J(i, k) = (rp.G[i] - G[i]) / h;
1049 Residual rm = residual(up, ctx,
true);
1050 if (!rm.ok)
continue;
1051 for (std::size_t i = 0; i < m; ++i) J(i, k) = (G[i] - rm.G[i]) / h;
1056struct NewtonResult {
1057 std::vector<double> u;
1058 std::size_t iterations = 0;
1059 bool converged =
false;
1060 double resnorm = std::numeric_limits<double>::infinity();
1064inline NewtonResult newton(
const Ctx& ctx,
const std::vector<double>& u0,
double tol,
1065 std::size_t maxit,
const std::vector<double>& lb) {
1067 std::vector<double> u = project(u0, lb);
1068 Residual res = residual(u, ctx,
false);
1073 std::vector<double> G = res.G;
1074 double resnorm = inf_norm(G);
1076 for (it = 1; it <= maxit; ++it) {
1077 if (resnorm <= tol) {
1079 out.iterations = it - 1;
1080 out.converged =
true;
1081 out.resnorm = resnorm;
1084 const Matrix<double> J = fdjac(ctx, u, G);
1085 Matrix<double> rhs(G.size(), 1, 0.0);
1086 for (std::size_t i = 0; i < G.size(); ++i) rhs(i, 0) = -G[i];
1087 const Matrix<double> du =
matmul(
pinv(J), rhs);
1089 bool improved =
false;
1090 for (
int b = 0; b < 30; ++b) {
1091 std::vector<double> un(u.size(), 0.0);
1092 for (std::size_t i = 0; i < u.size(); ++i) un[i] = u[i] + lam * du(i, 0);
1093 un = project(un, lb);
1094 Residual rn = residual(un, ctx,
true);
1096 const double v = inf_norm(rn.G);
1107 if (!improved)
break;
1110 out.iterations = it;
1111 out.converged = resnorm <= tol;
1112 out.resnorm = resnorm;
1123inline std::vector<double> seed_drift(
const PetriTerms& terms,
const std::vector<double>& xin,
1125 std::vector<double> x = xin;
1126 for (std::size_t i = 0; i < x.size(); ++i) x[i] = std::max(0.0, x[i]);
1129 terms, x, std::vector<double>(std::max<std::size_t>(terms.npair, 1), 0.0),
petri_coarse_tol());
1130 std::vector<double> phi(terms.imm_idx.size(), 0.0);
1131 for (std::size_t k = 0; k < phi.size(); ++k) {
1132 const std::size_t j = terms.imm_idx[k];
1133 phi[k] = lam * terms.modes[j].weight * th.theta[j];
1135 std::vector<double> mu(terms.latch_mode.size(), 0.0);
1136 for (std::size_t q = 0; q < mu.size(); ++q) {
1137 const std::size_t j = terms.latch_mode[q];
1139 for (std::size_t z : terms.modes[j].zblk) s += x[z];
1140 mu[q] = lam * (th.theta[j] - s);
1142 const std::vector<double> r =
petri_rates(terms, x, phi, mu, th);
1143 std::vector<double> d(terms.nstate, 0.0);
1144 for (std::size_t s = 0; s < terms.nstate; ++s) {
1146 for (std::size_t e = 0; e < terms.nev; ++e) v += terms.D(s, e) * r[e];
1151 for (std::size_t q = 0; q < terms.nf; ++q) {
1152 const std::size_t slot = terms.fluid_slot[q];
1153 if ((xin[slot] <= 0.0 && d[slot] < 0.0) || (xin[slot] >= terms.fluid_cap[q] && d[slot] > 0.0))
1169inline std::vector<std::size_t> fluid_seed_active(
const PetriTerms& terms,
const PetriConstraints& con,
1170 const std::vector<double>& x) {
1171 std::vector<std::size_t> active;
1172 if (terms.nf == 0)
return active;
1173 std::vector<double> xp = x;
1174 for (std::size_t i = 0; i < xp.size(); ++i) xp[i] = std::max(0.0, xp[i]);
1176 terms, xp, std::vector<double>(std::max<std::size_t>(terms.npair, 1), 0.0),
petri_coarse_tol());
1177 const std::vector<double> r =
petri_rates(terms, xp, std::vector<double>(terms.imm_idx.size(), 0.0),
1178 std::vector<double>(terms.latch_mode.size(), 0.0), th);
1181 double maxbase = 0.0;
1182 for (
double b : terms.rate_base) maxbase = std::max(maxbase, std::fabs(b));
1183 for (std::size_t q = 0; q < terms.nf; ++q) {
1184 const std::size_t slot = terms.fluid_slot[q];
1186 for (std::size_t e = 0; e < terms.nev; ++e) dfree += terms.D(slot, e) * r[e];
1188 for (
const PetriMode& md : terms.modes) {
1189 if (std::find(md.guard_slot.begin(), md.guard_slot.end(), slot) != md.guard_slot.end()) read =
true;
1190 if (std::find(md.fdep_slot.begin(), md.fdep_slot.end(), slot) != md.fdep_slot.end()) read =
true;
1192 const double cap = terms.fluid_cap[q];
1193 const double scale = std::max(1.0, std::max(std::fabs(dfree), maxbase));
1194 const double tolx =
petri_coarse_tol() * std::max(1.0, std::min(cap, std::max(std::fabs(x[slot]), 1.0)));
1196 std::ptrdiff_t low_row = -1, up_row = -1;
1197 for (std::size_t c = 0; c < con.b.size(); ++c) {
1198 if (!(con.fluid[c] && con.cover[c][slot]))
continue;
1199 if (con.lower[c]) low_row =
static_cast<std::ptrdiff_t
>(c);
1200 else up_row =
static_cast<std::ptrdiff_t
>(c);
1202 if (x[slot] <= tolx && dfree <= told) {
1203 if (low_row >= 0) active.push_back(
static_cast<std::size_t
>(low_row));
1204 }
else if (std::isfinite(cap) && x[slot] >= cap - tolx && dfree >= -told) {
1205 if (up_row >= 0) active.push_back(
static_cast<std::size_t
>(up_row));
1208 std::snprintf(buf,
sizeof(buf),
"%g", dfree);
1209 if (!std::isfinite(cap) && dfree > told)
1211 "solver_fluid_petri: Fluid place " + terms.fluid_name[q] +
" has a positive mean drift (" +
1212 std::string(buf) +
") and no capacity, so its level grows without bound and has no steady "
1213 "state. Give it a capacity with setFluidCapacity.");
1214 if (std::fabs(dfree) <= told)
1216 "solver_fluid_petri: Fluid place " + terms.fluid_name[q] +
" has a zero mean drift and "
1217 "nothing reads its level, so its stationary level is undetermined (the level is null "
1242inline PetriReport::Fluid fluid_levels(
const PetriTerms& terms,
const PetriConstraints& con,
1243 const PetriImmediate& imm,
const std::vector<std::size_t>& active,
1244 const std::vector<double>& x,
const std::vector<double>& s2,
1245 const std::vector<double>& phi,
const std::vector<double>& r,
1246 const Matrix<double>& Sigma,
const std::string& boundary) {
1247 std::string mode = boundary.empty() ? std::string(
"reflect") : boundary;
1248 for (
char& ch : mode) ch =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ch)));
1249 if (mode !=
"reflect" && mode !=
"diffusion")
1251 "solver_fluid_petri: options.config.fluid_boundary must be 'reflect' or 'diffusion', "
1252 "got '" + mode +
"'.");
1253 const std::size_t nf = terms.nf;
1254 PetriReport::Fluid fl;
1255 fl.names = terms.fluid_name;
1256 fl.level.assign(nf, 0.0);
1257 fl.level_var.assign(nf, 0.0);
1258 fl.drift.assign(nf, 0.0);
1259 fl.sigma2.assign(nf, 0.0);
1260 fl.bound.assign(nf, 0);
1262 if (nf == 0)
return fl;
1263 bool any_bound =
false;
1264 for (std::size_t q = 0; q < nf; ++q) {
1265 const std::size_t slot = terms.fluid_slot[q];
1266 fl.level[q] = x[slot];
1267 fl.level_var[q] = std::max(0.0, Sigma(slot, slot));
1269 for (std::size_t e = 0; e < terms.nev; ++e) d += terms.D(slot, e) * r[e];
1271 for (std::size_t c : active)
1272 if (con.fluid[c] && con.cover[c][slot]) fl.bound[q] = con.lower[c] ? -1 : 1;
1273 if (fl.bound[q] != 0) any_bound =
true;
1275 if (mode !=
"diffusion" || !any_bound)
return fl;
1282 std::vector<std::size_t> tokact;
1283 for (std::size_t c : active)
1284 if (!con.fluid[c]) tokact.push_back(c);
1286 clamp_tangent(terms, imm, con, tokact, &th, T);
1287 std::vector<bool> isfl(terms.nstate,
false);
1288 for (std::size_t q = 0; q < nf; ++q) isfl[terms.fluid_slot[q]] =
true;
1290 std::vector<std::size_t> tokpos, tokslot;
1291 for (std::size_t i = 0; i < terms.cov_idx.size(); ++i)
1292 if (!isfl[terms.cov_idx[i]]) {
1293 tokpos.push_back(i);
1294 tokslot.push_back(terms.cov_idx[i]);
1296 const std::size_t nt = tokslot.size(), ns = terms.stoch_col.size();
1297 Matrix<double> Ttok(nt, nt, 0.0), Dtok(nt, ns, 0.0), Atok(nt, nt, 0.0);
1298 for (std::size_t i = 0; i < nt; ++i) {
1299 for (std::size_t j = 0; j < nt; ++j) {
1300 Ttok(i, j) = T(tokpos[i], tokpos[j]);
1301 Atok(i, j) = A(tokslot[i], tokslot[j]);
1303 for (std::size_t e = 0; e < ns; ++e) Dtok(i, e) = terms.D(tokslot[i], terms.stoch_col[e]);
1305 const Matrix<double> Dc = (nt > 0 && ns > 0) ?
matmul(Ttok, Dtok) : Matrix<double>(nt, ns, 0.0);
1306 const Matrix<double> Ar = nt > 0 ?
matmul(Ttok, Atok) : Matrix<double>(0, 0, 0.0);
1307 const Matrix<double> V = (nt > 0 && ns > 0) ? fluid::detail::fluid_orth(Dc) : Matrix<double>(nt, 0, 0.0);
1308 Matrix<double> W(nt, ns, 0.0);
1310 const Matrix<double> Vt = V.transpose();
1314 for (std::size_t q = 0; q < nf; ++q) {
1315 if (fl.bound[q] == 0)
continue;
1316 const std::size_t slot = terms.fluid_slot[q];
1317 std::vector<double> h(ns, 0.0);
1318 for (std::size_t e = 0; e < ns; ++e) h[e] = terms.D(slot, terms.stoch_col[e]);
1320 for (std::size_t e = 0; e < ns; ++e) {
1322 for (std::size_t i = 0; i < nt; ++i) gw += A(slot, tokslot[i]) * W(i, e);
1326 for (std::size_t e = 0; e < ns; ++e) sig2 += h[e] * h[e] * r[terms.stoch_col[e]];
1327 fl.sigma2[q] = sig2;
1328 const double mu = fl.drift[q];
1329 const double B = terms.fluid_cap[q];
1330 if (!(sig2 > 1e-14))
continue;
1331 const double kappa = 2.0 * mu / sig2;
1332 if (!std::isfinite(B)) {
1333 if (kappa >= 0)
continue;
1334 fl.level[q] = -1.0 / kappa;
1335 fl.level_var[q] = 1.0 / (kappa * kappa);
1336 }
else if (std::fabs(kappa * B) < 1e-8) {
1337 fl.level[q] = B / 2.0;
1338 fl.level_var[q] = B * B / 12.0;
1340 fl.level[q] = B / (-std::expm1(-kappa * B)) - 1.0 / kappa;
1341 const double sh = std::sinh(kappa * B / 2.0);
1342 fl.level_var[q] = std::max(0.0, 1.0 / (kappa * kappa) - B * B / (4.0 * sh * sh));
1354inline std::vector<std::vector<double>> seed_flows(
const PetriTerms& terms,
1355 const std::vector<std::vector<double>>& xt,
double lam) {
1356 const std::size_t ni = terms.imm_idx.size();
1357 std::vector<std::vector<double>> phit(xt.size(), std::vector<double>(ni, 0.0));
1358 if (ni == 0)
return phit;
1359 for (std::size_t a = 0; a < xt.size(); ++a) {
1361 terms, xt[a], std::vector<double>(std::max<std::size_t>(terms.npair, 1), 0.0),
petri_coarse_tol());
1362 for (std::size_t k = 0; k < ni; ++k) {
1363 const std::size_t j = terms.imm_idx[k];
1364 phit[a][k] = lam * terms.modes[j].weight * th.theta[j];
1376inline bool petri_mldivide(std::vector<std::vector<double>> A, std::vector<double> b, std::vector<double>& xs) {
1377 const std::size_t m = A.size();
1378 const std::size_t nn = m == 0 ? 0 : A[0].size();
1379 std::vector<std::size_t> perm(nn);
1380 for (std::size_t j = 0; j < nn; ++j) perm[j] = j;
1381 const std::size_t kmax = std::min(m, nn);
1382 std::vector<double> diagR(kmax, 0.0);
1383 for (std::size_t k = 0; k < kmax; ++k) {
1386 for (std::size_t j = k; j < nn; ++j) {
1388 for (std::size_t i = k; i < m; ++i) nrm += A[i][j] * A[i][j];
1395 for (std::size_t i = 0; i < m; ++i) std::swap(A[i][k], A[i][p]);
1396 std::swap(perm[k], perm[p]);
1398 double alpha = std::sqrt(best);
1403 if (A[k][k] > 0) alpha = -alpha;
1404 std::vector<double> v(m, 0.0);
1405 for (std::size_t i = k; i < m; ++i) v[i] = A[i][k];
1408 for (std::size_t i = k; i < m; ++i) vv += v[i] * v[i];
1410 for (std::size_t j = k; j < nn; ++j) {
1412 for (std::size_t i = k; i < m; ++i) d += v[i] * A[i][j];
1414 for (std::size_t i = k; i < m; ++i) A[i][j] -= d * v[i];
1417 for (std::size_t i = k; i < m; ++i) d += v[i] * b[i];
1419 for (std::size_t i = k; i < m; ++i) b[i] -= d * v[i];
1423 const double tol = kmax > 0 ?
static_cast<double>(std::max(m, nn)) * std::numeric_limits<double>::epsilon() *
1426 std::size_t rank = 0;
1427 while (rank < kmax && std::fabs(diagR[rank]) > tol) ++rank;
1428 if (m == nn && rank < nn)
return false;
1429 std::vector<double> z(nn, 0.0);
1430 for (std::size_t ii = rank; ii-- > 0;) {
1432 for (std::size_t j = ii + 1; j < rank; ++j) acc -= A[ii][j] * z[j];
1433 z[ii] = acc / A[ii][ii];
1436 for (std::size_t j = 0; j < nn; ++j) xs[perm[j]] = z[j];
1445inline std::vector<double> collapse(
const PetriTerms& terms,
const PetriImmediate& imm,
1446 const std::vector<double>& x0) {
1447 std::vector<double> x = x0;
1448 std::vector<std::size_t> act;
1449 for (std::size_t k = 0; k < imm.n; ++k)
1450 if (imm.active[k]) act.push_back(k);
1451 const std::vector<std::size_t>& pins = imm.pins;
1452 if (act.empty() || pins.empty())
return x;
1453 const std::size_t n = terms.nstate;
1454 std::vector<std::vector<double>> Aimm(pins.size(), std::vector<double>(act.size(), 0.0));
1455 std::vector<double> b(pins.size(), 0.0);
1456 for (std::size_t i = 0; i < pins.size(); ++i) {
1457 for (std::size_t a = 0; a < act.size(); ++a)
1458 Aimm[i][a] = terms.modes[terms.imm_idx[act[a]]].cvec[pins[i]];
1459 b[i] = -x0[pins[i]];
1461 std::vector<double> s;
1462 if (!petri_mldivide(Aimm, b, s))
return x;
1463 for (
double& v : s) {
1464 if (!std::isfinite(v))
return x;
1465 v = std::max(0.0, v);
1467 for (std::size_t r = 0; r < n; ++r) {
1469 for (std::size_t a = 0; a < s.size(); ++a) v += terms.modes[terms.imm_idx[act[a]]].cvec[r] * s[a];
1472 for (std::size_t p : pins) x[p] = 0.0;
1473 for (std::size_t r = 0; r < terms.nm; ++r) x[r] = std::max(0.0, x[r]);
1475 const PetriTheta th =
1476 petri_theta(terms, x, std::vector<double>(std::max<std::size_t>(terms.npair, 1), 0.0));
1477 for (std::size_t j : terms.latch_mode) {
1478 const PetriMode& md = terms.modes[j];
1479 for (std::size_t h = 0; h < md.zblk.size(); ++h) x[md.zblk[h]] = th.theta[j] * md.pie[h];
1485inline std::vector<double> dae_rhs(
const double* z,
const Ctx& ctx,
bool withcov, std::size_t nc) {
1486 const PetriTerms& terms = *ctx.terms;
1487 const PetriImmediate& imm = *ctx.imm;
1488 const PetriConstraints& con = *ctx.con;
1489 const std::size_t n = terms.nstate, nimm = imm.n, nact = ctx.active.size(), nlatch = terms.latch_mode.size();
1490 const std::size_t nvar = n + nimm + nlatch + nact;
1491 const std::vector<double> x(z, z + n), phi(z + n, z + n + nimm), mu(z + n + nimm, z + n + nimm + nlatch),
1492 zeta(z + n + nimm + nlatch, z + nvar);
1494 terms, x, std::vector<double>(std::max<std::size_t>(terms.npair, 1), 0.0), 0.0,
petri_coarse_tol());
1495 const std::vector<double> r =
petri_rates(terms, x, phi, mu, th);
1496 std::vector<double> gP, gN;
1497 gains(con, ctx.active, zeta, n, gP, gN);
1498 std::vector<double> drift(n, 0.0);
1499 for (std::size_t s = 0; s < n; ++s) {
1500 double neg = 0.0, pos = 0.0;
1501 for (std::size_t e = 0; e < terms.nev; ++e) {
1502 neg += ctx.Dn(s, e) * r[e];
1503 pos += ctx.Dp(s, e) * r[e];
1505 drift[s] = gN[s] * neg + gP[s] * pos;
1507 std::vector<double> dz(nvar + (withcov ? nc * nc : 0), 0.0);
1508 for (std::size_t s = 0; s < n; ++s) dz[s] = drift[s];
1509 for (std::size_t p : imm.pins) dz[p] = x[p];
1511 for (
const PetriImmediate::Row& row : imm.rows) {
1514 else dz[at++] = phi[row.a];
1517 for (std::size_t j : terms.latch_mode) {
1519 for (std::size_t zc : terms.modes[j].zblk) acc += drift[zc];
1520 for (std::size_t q = 0; q < th.dslot[j].size(); ++q) acc -= th.dval[j][q] * drift[th.dslot[j][q]];
1523 for (std::size_t k = 0; k < nact; ++k) {
1525 for (std::size_t s = 0; s < n; ++s) v += con.A(ctx.active[k], s) * drift[s];
1529 const std::vector<std::size_t>& idx = terms.cov_idx;
1530 const std::vector<std::size_t>& sc = terms.stoch_col;
1532 Matrix<double> Dc(nc, std::max<std::size_t>(sc.size(), 1), 0.0), Am(nc, nc, 0.0);
1533 for (std::size_t i = 0; i < nc; ++i) {
1534 for (std::size_t e = 0; e < sc.size(); ++e) Dc(i, e) = terms.D(idx[i], sc[e]);
1535 for (std::size_t j = 0; j < nc; ++j) Am(i, j) = A(idx[i], idx[j]);
1538 if (clamp_tangent(terms, imm, con, ctx.active, &th, T)) {
1542 Matrix<double> S(nc, nc, 0.0);
1543 for (std::size_t i = 0; i < nc; ++i)
1544 for (std::size_t j = 0; j < nc; ++j) S(i, j) = z[nvar + i * nc + j];
1545 const Matrix<double> AS =
matmul(Am, S), SAt =
matmul(S, Am.transpose());
1546 for (std::size_t i = 0; i < nc; ++i)
1547 for (std::size_t j = 0; j < nc; ++j) {
1549 for (std::size_t e = 0; e < sc.size(); ++e) q += Dc(i, e) * r[sc[e]] * Dc(j, e);
1550 dz[nvar + i * nc + j] = AS(i, j) + SAt(i, j) + q;
1560inline std::vector<double> event_values(
const double* z,
const Ctx& ctx) {
1561 const PetriTerms& terms = *ctx.terms;
1562 const PetriImmediate& imm = *ctx.imm;
1563 const PetriConstraints& con = *ctx.con;
1564 const std::size_t n = terms.nstate, nimm = imm.n, nlatch = terms.latch_mode.size(), ncon = con.b.size();
1565 std::vector<double> val(nimm + ncon + terms.nm, 0.0);
1566 for (std::size_t k = 0; k < nimm; ++k) val[k] = imm.active[k] ? z[n + k] : 1.0;
1567 for (std::size_t c = 0; c < ncon; ++c) {
1568 const std::ptrdiff_t k = index_of(ctx.active, c);
1571 for (std::size_t s = 0; s < n; ++s) ax += con.A(c, s) * z[s];
1572 val[nimm + c] = con.b[c] - ax;
1574 val[nimm + c] = 1.0 - z[n + nimm + nlatch +
static_cast<std::size_t
>(k)];
1577 for (std::size_t s = 0; s < terms.nm; ++s)
1578 val[nimm + ncon + s] = index_of(imm.pins, s) >= 0 ? 1.0 : z[s] +
petri_fine_tol();
1583struct PetriTranSeg {
1584 const Ctx* ctx =
nullptr;
1585 bool withcov =
false;
1586 std::size_t nc = 0, nz = 0;
1587 std::vector<double> diag, gprev;
1588 const std::vector<double>* req =
nullptr;
1589 std::size_t jreq = 0;
1590 std::vector<double> segT;
1591 std::vector<std::vector<double>> segZ;
1594inline PetriTranSeg*& petri_tran_active() {
1595 static PetriTranSeg* p =
nullptr;
1599inline int petri_tran_fcn(rodas_impl::integer*, rodas_impl::doublereal*, rodas_impl::doublereal* y,
1600 rodas_impl::doublereal* f, rodas_impl::doublereal*, rodas_impl::integer*) {
1601 PetriTranSeg& S = *petri_tran_active();
1602 const std::vector<double> d = dae_rhs(y, *S.ctx, S.withcov, S.nc);
1603 for (std::size_t i = 0; i < S.nz; ++i) f[i] = d[i];
1606inline int petri_tran_jac(rodas_impl::integer*, rodas_impl::doublereal*, rodas_impl::doublereal*,
1607 rodas_impl::doublereal*, rodas_impl::integer*, rodas_impl::doublereal*,
1608 rodas_impl::integer*) {
return 0; }
1609inline int petri_tran_dfx(rodas_impl::integer*, rodas_impl::doublereal*, rodas_impl::doublereal*,
1610 rodas_impl::doublereal*, rodas_impl::doublereal*, rodas_impl::integer*) {
return 0; }
1612inline int petri_tran_mas(rodas_impl::integer* n, rodas_impl::doublereal* am, rodas_impl::integer* lmas,
1613 rodas_impl::doublereal*, rodas_impl::integer*) {
1614 PetriTranSeg& S = *petri_tran_active();
1615 for (
int j = 0; j < *n; ++j) am[0 + j * (*lmas)] = S.diag[
static_cast<std::size_t
>(j)];
1618inline std::vector<double> petri_tran_dense(
double tq, rodas_impl::doublereal* cont, rodas_impl::integer* lrc,
1620 std::vector<double> y(nz, 0.0);
1621 for (std::size_t i = 0; i <
nz; ++i) {
1622 rodas_impl::integer ii =
static_cast<rodas_impl::integer
>(i + 1);
1624 y[i] = rodas_impl::contro_(&ii, &t, cont, lrc);
1629inline void petri_tran_emit(PetriTranSeg& S,
double xold,
double xend, rodas_impl::doublereal* cont,
1630 rodas_impl::integer* lrc) {
1631 const std::vector<double>& req = *S.req;
1632 while (S.jreq < req.size() && req[S.jreq] < xend) {
1633 const double tq = req[S.jreq];
1634 if (tq > xold && (S.segT.empty() || tq > S.segT.back())) {
1635 S.segT.push_back(tq);
1636 S.segZ.push_back(petri_tran_dense(tq, cont, lrc, S.nz));
1646inline int petri_tran_solout(rodas_impl::integer* nr, rodas_impl::doublereal* xold, rodas_impl::doublereal* x,
1647 rodas_impl::doublereal* y, rodas_impl::doublereal* cont, rodas_impl::integer* lrc,
1648 rodas_impl::integer*, rodas_impl::doublereal*, rodas_impl::integer*,
1649 rodas_impl::integer* irtrn) {
1650 PetriTranSeg& S = *petri_tran_active();
1651 const std::vector<double> gcur = event_values(y, *S.ctx);
1653 for (std::size_t c = 0; c < gcur.size(); ++c)
1654 if (S.gprev[c] > 0 && gcur[c] <= 0) {
1655 cross =
static_cast<int>(c);
1660 if (*nr > 1) petri_tran_emit(S, *xold, *x, cont, lrc);
1661 S.segT.push_back(*x);
1662 S.segZ.push_back(std::vector<double>(y, y + S.nz));
1665 double lo = *xold, hi = *x;
1666 for (
int bit = 0; bit < 60; ++bit) {
1667 const double mid = 0.5 * (lo + hi);
1668 const std::vector<double> ymid = petri_tran_dense(mid, cont, lrc, S.nz);
1669 if (event_values(ymid.data(), *S.ctx)[
static_cast<std::size_t
>(cross)] > 0) lo = mid;
1671 if (hi - lo <= 1e-12 * std::max(1.0, std::fabs(hi)))
break;
1674 petri_tran_emit(S, *xold, hi, cont, lrc);
1675 S.segT.push_back(hi);
1676 S.segZ.push_back(petri_tran_dense(hi, cont, lrc, S.nz));
1678 if (irtrn) *irtrn = -1;
1683struct PetriTransient {
1684 std::vector<double> t;
1685 std::vector<std::vector<double>> xt, phit;
1686 std::vector<Matrix<double>> Sigmat;
1687 std::vector<double> tvar;
1688 std::vector<std::pair<double, int>> switches;
1703inline PetriTransient petri_transient(
const PetriTerms& terms,
const PetriConservation& cons,
1704 const PetriConstraints& con, PetriImmediate imm,
1705 const std::vector<std::size_t>& active_ss,
const PetriOptions& opt,
1706 const std::vector<double>& phiss,
const std::vector<double>& zetass,
1707 std::vector<std::string>& warnings) {
1708 using namespace rodas_impl;
1709 const std::size_t n = terms.nstate, nimm = imm.n, nlatch = terms.latch_mode.size(), ncon = con.b.size();
1710 const double tEnd = opt.timespan_end;
1711 const double tStart = std::isfinite(opt.timespan_start) ? opt.timespan_start : 0.0;
1712 const std::size_t maxcov = opt.dae_maxcov > 0 ? opt.dae_maxcov : 25;
1713 const std::size_t nc = terms.cov_idx.size();
1714 const bool withcov = nc > 0 && nc <= maxcov;
1715 if (!withcov && nc > maxcov)
1716 warnings.push_back(
"The transient covariance would add " + std::to_string(nc * nc) +
1717 " differential states, above the limit of " + std::to_string(maxcov * maxcov) +
1718 " set by options.config.dae_maxcov. Integrating the mean as a DAE with the variance "
1719 "held at its stationary value.");
1721 const std::vector<double> x0 = collapse(terms, imm, terms.x0);
1725 std::vector<std::size_t> active;
1726 std::vector<double> zeta;
1727 for (std::size_t k = 0; k < active_ss.size(); ++k) {
1728 const std::size_t c = active_ss[k];
1729 if (!con.fluid[c]) {
1730 active.push_back(c);
1731 zeta.push_back(k < zetass.size() ? zetass[k] : 1.0);
1734 for (std::size_t q = 0; q < terms.nf; ++q) {
1735 const std::size_t slot = terms.fluid_slot[q];
1736 for (std::size_t c = 0; c < ncon; ++c) {
1737 if (!con.fluid[c] || !con.cover[c][slot])
continue;
1738 if ((x0[slot] <= 0 && con.lower[c]) ||
1739 (x0[slot] > 0 && x0[slot] >= terms.fluid_cap[q] && !con.lower[c])) {
1740 active.push_back(c);
1741 zeta.push_back(1.0);
1747 std::vector<double> req = opt.tranpoints;
1748 std::sort(req.begin(), req.end());
1750 double tcur = tStart;
1751 std::vector<double> xcur = x0, phi = phiss, mu(nlatch, 0.0), Scur(withcov ? nc * nc : 0, 0.0);
1752 const std::size_t segMax = std::max<std::size_t>(8, 4 * (nimm + ncon + 1));
1753 for (std::size_t seg = 0; seg < segMax; ++seg) {
1755 const Ctx ctx = context(terms, cons, con, imm, active);
1756 const std::size_t nact = ctx.active.size();
1757 const std::size_t nvar = n + nimm + nlatch + nact;
1758 const std::size_t
nz = nvar + (withcov ? nc * nc : 0);
1761 S.withcov = withcov;
1764 S.diag.assign(nz, 0.0);
1765 for (std::size_t s = 0; s < n; ++s) S.diag[s] = 1.0;
1766 for (std::size_t p : imm.pins) S.diag[p] = 0.0;
1767 for (std::size_t i = nvar; i <
nz; ++i) S.diag[i] = 1.0;
1768 std::vector<doublereal> y(nz, 0.0);
1769 std::copy(xcur.begin(), xcur.end(), y.begin());
1770 std::copy(phi.begin(), phi.end(), y.begin() + n);
1771 std::copy(mu.begin(), mu.end(), y.begin() + n + nimm);
1772 std::copy(zeta.begin(), zeta.end(), y.begin() + n + nimm + nlatch);
1773 if (withcov) std::copy(Scur.begin(), Scur.end(), y.begin() + nvar);
1774 S.gprev = event_values(y.data(), ctx);
1776 while (S.jreq < req.size() && req[S.jreq] <= tcur) ++S.jreq;
1779 const integer N =
static_cast<integer
>(
nz);
1780 const std::size_t lwork =
nz * (
nz + 1 +
nz + 14) + 20 + 32, liwork = nz + 20 + 32;
1781 std::vector<doublereal> work(lwork, 0.0), rpar(1, 0.0);
1782 std::vector<integer> iwork(liwork, 0), ipar(1, 0);
1784 integer itol = 0, ifcn = 1, ijac = 0, mljac = N, mujac = N, idfx = 0, imas = 1, mlmas = 0, mumas = 0;
1785 integer iout = 1, idid = 0, lw =
static_cast<integer
>(lwork), liw =
static_cast<integer
>(liwork);
1786 bool failed =
false;
1787 petri_tran_active() = &S;
1789 rodas_(
const_cast<integer*
>(&N), (U_fp)petri_tran_fcn, &ifcn, &x, y.data(), &xend, &h, &rtol, &atol,
1790 &itol, (U_fp)petri_tran_jac, &ijac, &mljac, &mujac, (U_fp)petri_tran_dfx, &idfx,
1791 (U_fp)petri_tran_mas, &imas, &mlmas, &mumas, (U_fp)petri_tran_solout, &iout, work.data(), &lw,
1792 iwork.data(), &liw, rpar.data(), ipar.data(), &idid);
1793 if (idid < 0 && S.hit < 0) {
1794 warnings.push_back(
"The transient DAE failed (RODAS returned idid=" + std::to_string(idid) +
1795 "); the trajectory stops at the last accepted point.");
1798 }
catch (
const Error& ex) {
1799 warnings.push_back(std::string(
"The transient DAE failed (") + ex.what() +
1800 "); the trajectory stops at the last accepted point.");
1803 petri_tran_active() =
nullptr;
1804 if (!S.segT.empty()) {
1805 for (std::size_t a = 0; a < S.segT.size(); ++a) {
1806 const std::vector<double>& za = S.segZ[a];
1807 out.t.push_back(S.segT[a]);
1808 out.xt.push_back(std::vector<double>(za.begin(), za.begin() + n));
1809 out.phit.push_back(std::vector<double>(za.begin() + n, za.begin() + n + nimm));
1811 out.tvar.push_back(S.segT[a]);
1812 Matrix<double> Sg(nc, nc, 0.0);
1813 for (std::size_t i = 0; i < nc; ++i)
1814 for (std::size_t j = 0; j < nc; ++j) Sg(i, j) = za[nvar + i * nc + j];
1815 out.Sigmat.push_back(Sg);
1818 tcur = S.segT.back();
1819 const std::vector<double>& zend = S.segZ.back();
1820 xcur.assign(zend.begin(), zend.begin() + n);
1821 phi.assign(zend.begin() + n, zend.begin() + n + nimm);
1822 mu.assign(zend.begin() + n + nimm, zend.begin() + n + nimm + nlatch);
1823 zeta.assign(zend.begin() + n + nimm + nlatch, zend.begin() + nvar);
1824 if (withcov) Scur.assign(zend.begin() + nvar, zend.begin() + nz);
1826 if (failed || S.hit < 0 || tcur >= tEnd -
petri_fine_tol())
break;
1828 bool changed =
false;
1829 const std::size_t ev =
static_cast<std::size_t
>(S.hit);
1831 if (imm.active[ev]) {
1832 imm.active[ev] =
false;
1836 }
else if (ev - nimm < ncon) {
1837 const std::size_t c = ev - nimm;
1838 const std::ptrdiff_t k = index_of(active, c);
1840 active.push_back(c);
1841 zeta.push_back(1.0);
1843 active.erase(active.begin() + k);
1844 zeta.erase(zeta.begin() + k);
1848 const std::size_t s = ev - nimm - ncon;
1849 for (std::size_t k = 0; k < nimm; ++k) {
1850 const PetriMode& md = terms.modes[terms.imm_idx[k]];
1851 if (imm.active[k] && std::find(md.arc_slot.begin(), md.arc_slot.end(), s) != md.arc_slot.end() &&
1852 imm.bind[k] !=
static_cast<std::ptrdiff_t
>(s)) {
1853 imm.bind[k] =
static_cast<std::ptrdiff_t
>(s);
1860 out.switches.push_back(std::make_pair(tcur, S.hit));
1861 if (!changed)
break;
1863 if (out.t.empty()) {
1864 out.t.assign(1, tStart);
1865 out.xt.assign(1, x0);
1866 out.phit.assign(1, phiss);
1876inline void metrics_t(
const PetriTerms& terms,
const std::vector<std::vector<double>>& xt,
1877 const std::vector<double>& s2,
const std::vector<std::vector<double>>& phit, std::size_t M,
1878 std::size_t K, PetriSolution& out) {
1879 const std::size_t nt = xt.size();
1880 out.QNt.assign(M, std::vector<std::vector<double>>(K, std::vector<double>(nt, 0.0)));
1883 std::vector<std::vector<double>> Rt(nt);
1884 for (std::size_t a = 0; a < nt; ++a) {
1885 const PetriTheta th =
petri_theta(terms, xt[a], s2);
1886 Rt[a] =
petri_rates(terms, xt[a], a < phit.size() ? phit[a] : std::vector<double>(), std::vector<double>(),
1889 for (std::size_t s = 0; s < terms.nm; ++s) {
1890 if (terms.coord_station[s] < 0 ||
static_cast<std::size_t
>(terms.coord_station[s]) >= M)
continue;
1891 const std::size_t i =
static_cast<std::size_t
>(terms.coord_station[s]), k = terms.coord_class[s];
1892 for (std::size_t a = 0; a < nt; ++a) {
1893 out.QNt[i][k][a] += xt[a][s];
1894 out.UNt[i][k][a] = out.QNt[i][k][a];
1897 for (std::map<std::size_t, std::vector<std::size_t>>::const_iterator it = terms.consumers.begin();
1898 it != terms.consumers.end(); ++it) {
1899 const std::size_t i = it->first / K, k = it->first % K;
1900 const std::vector<double>& w = terms.consumer_w.at(it->first);
1901 for (std::size_t a = 0; a < nt; ++a)
1902 for (std::size_t q = 0; q < it->second.size(); ++q) out.TNt[i][k][a] += w[q] * Rt[a][it->second[q]];
1904 for (std::map<std::size_t, std::vector<std::size_t>>::const_iterator it = terms.producers.begin();
1905 it != terms.producers.end(); ++it) {
1906 const std::size_t i = it->first / K, k = it->first % K;
1907 for (std::size_t a = 0; a < nt; ++a)
1908 for (std::size_t e : it->second) out.TNt[i][k][a] += Rt[a][e];
1912 for (std::size_t q = 0; q < terms.nf; ++q) {
1913 if (terms.fluid_station[q] < 0 ||
static_cast<std::size_t
>(terms.fluid_station[q]) >= M || K == 0)
continue;
1914 const std::size_t i =
static_cast<std::size_t
>(terms.fluid_station[q]), slot = terms.fluid_slot[q];
1915 double levmax = 0.0;
1916 for (std::size_t a = 0; a < nt; ++a) levmax = std::max(levmax, std::fabs(xt[a][slot]));
1918 for (std::size_t a = 0; a < nt; ++a) {
1919 const double lev = xt[a][slot];
1920 out.QNt[i][0][a] = lev;
1921 out.UNt[i][0][a] = std::isfinite(terms.fluid_cap[q]) ? lev / terms.fluid_cap[q] : lev;
1922 double outf = 0.0, inf = 0.0;
1923 for (std::size_t e = 0; e < terms.nev; ++e) {
1924 const double d = terms.D(slot, e);
1925 if (d < 0) outf -= d * Rt[a][e];
1926 else if (d > 0) inf += d * Rt[a][e];
1928 if (lev <= tol_empty) outf = std::min(outf, inf);
1929 out.TNt[i][0][a] = outf;
1945 const std::size_t M =
sn.nstations, K =
sn.nclasses;
1950 "model: " + v.
reason +
".");
1953 const std::size_t n = terms.nstate, npair = terms.npair;
1954 if (n >
opt.dae_maxstate)
1956 "solver_fluid_petri: the fluid Petri route solves a " + std::to_string(n) +
1957 "-unknown algebraic system with a finite-difference Jacobian, above the limit of " +
1958 std::to_string(
opt.dae_maxstate) +
1959 " set by options.config.dae_maxstate. Raise that limit, or use SolverSSA for a net "
1963 if (cons.leak > 1e-7)
1964 throw NumericError(
ErrorKind{
"Degenerate"},
"solver_fluid_petri: the conserved directions and the jump matrix "
1965 "disagree: the largest leak per unit rate is " +
1966 std::to_string(cons.leak) +
", where it must be zero");
1968 const std::size_t ncon = con.b.size();
1977 for (std::size_t j : terms.timed_idx) {
1979 for (
double d : terms.modes[j].d1) s += d;
1980 rmax = std::max(rmax, s);
1982 for (std::size_t e = 0; e < terms.nev; ++e)
1983 if (terms.ev_kind[e] == 3 || terms.ev_kind[e] == 6) rmax = std::max(rmax, terms.rate_base[e]);
1984 if (!(rmax > 0)) rmax = 1.0;
1985 const double lam = std::min(1e8, 1e4 * rmax);
1987 for (std::size_t s = 0; s < terms.nm; ++s) mass += terms.x0[s];
1990 for (std::size_t q = 0; q < terms.nf; ++q) {
1991 mass += terms.fluid_init[q];
1992 if (std::isfinite(terms.fluid_cap[q])) mass += terms.fluid_cap[q];
1994 double Thor = 50.0 * (mass + 1.0) / rmax;
1996 const auto drift_fn = [&terms, lam](
const double&,
const std::vector<double>& y) {
1997 return solver_detail::seed_drift(terms, y, lam);
1999 std::vector<double> tseed;
2000 std::vector<std::vector<double>> xseed;
2001 for (
int attempt = 0; attempt < 6; ++attempt) {
2011 const std::vector<double> d = solver_detail::seed_drift(terms, xseed.back(), lam);
2012 if (solver_detail::inf_norm(d) <= 1e-6 * std::max(1.0, rmax * (mass + 1.0)))
break;
2015 std::vector<double> x = xseed.back();
2019 const std::size_t nimm = imm.
n;
2023 std::vector<double> s2(npair, 0.0);
2024 std::vector<bool> ondiag(npair,
false);
2025 for (std::size_t p = 0; p < npair; ++p) {
2026 ondiag[p] = (terms.cov_pairs[p].first == terms.cov_pairs[p].second);
2027 if (ondiag[p]) s2[p] = std::max(
petri_fine_tol(), x[terms.cov_pairs[p].first]);
2034 std::vector<std::size_t> active = solver_detail::fluid_seed_active(terms, con, x);
2035 std::vector<double> phi(nimm, 0.0);
2036 const std::size_t nlatch = terms.latch_mode.size();
2037 std::vector<double> mu(nlatch, 0.0), zeta;
2038 std::size_t iters = 0;
2039 const std::size_t aset_max = std::max<std::size_t>(6, 2 * (ncon + nimm) + 2);
2040 bool converged =
false;
2041 double resnorm = std::numeric_limits<double>::infinity();
2042 bool have_best =
false;
2043 std::vector<double> bx, bs2, bphi, bmu, bzeta;
2044 std::vector<std::size_t> bactive;
2046 double bres = std::numeric_limits<double>::infinity();
2049 for (std::size_t sweep = 0; sweep < aset_max; ++sweep) {
2050 const solver_detail::Ctx ctx =
2051 solver_detail::context(terms, cons, con, imm, active);
2052 std::vector<double> u0;
2053 u0.insert(u0.end(), x.begin(), x.end());
2054 u0.insert(u0.end(), s2.begin(), s2.end());
2055 u0.insert(u0.end(), phi.begin(), phi.end());
2056 u0.insert(u0.end(), mu.begin(), mu.end());
2057 u0.insert(u0.end(), active.size(), 1.0);
2058 std::vector<double> lb(u0.size(), -std::numeric_limits<double>::infinity());
2059 for (std::size_t k = 0; k < nimm; ++k) lb[n + npair + k] = 0.0;
2060 for (std::size_t k = 0; k < active.size(); ++k)
2061 lb[n + npair + nimm + nlatch + k] = 0.0;
2062 for (std::size_t p = 0; p < npair; ++p)
2063 if (ondiag[p]) lb[n + p] = 0.0;
2065 const solver_detail::NewtonResult nr =
2066 solver_detail::newton(ctx, u0,
opt.tol,
opt.newton_max, lb);
2067 iters += nr.iterations;
2068 resnorm = nr.resnorm;
2069 converged = nr.converged;
2070 const solver_detail::Unpacked up =
2071 solver_detail::unpack(nr.u, terms, imm, active.size());
2078 if (!have_best || resnorm < bres) {
2079 bx = x; bs2 = s2; bphi = phi; bmu = mu; bzeta = zeta;
2080 bactive = active; bimm = imm; bres = resnorm; bconv = converged;
2091 for (
double p : phi) scale = std::max(scale, std::fabs(p));
2092 const double thr = -std::max(
opt.tol, 1e-10) * scale;
2093 for (std::size_t k = 0; k < nimm && !moved; ++k)
2100 if (!moved && nimm > 0) {
2101 const double thr = -std::max(
opt.tol, 1e-10);
2102 for (std::size_t s = 0; s < terms.nm && !moved; ++s) {
2103 if (x[s] >= thr)
continue;
2104 for (std::size_t k = 0; k < nimm; ++k) {
2105 const PetriMode& md = terms.modes[terms.imm_idx[k]];
2109 imm.
bind[k] !=
static_cast<std::ptrdiff_t
>(s)) {
2110 imm.
bind[k] =
static_cast<std::ptrdiff_t
>(s);
2118 if (!moved && ncon > 0) {
2119 std::vector<std::size_t> over;
2120 for (std::size_t c = 0; c < ncon; ++c) {
2122 for (std::size_t s = 0; s < n; ++s) val += con.A(c, s) * x[s];
2123 if (val > con.b[c] + std::max(1e-9,
opt.tol) &&
2124 std::find(active.begin(), active.end(), c) == active.end())
2127 if (!over.empty()) {
2128 active.insert(active.end(), over.begin(), over.end());
2131 std::vector<std::size_t> keep;
2132 for (std::size_t i = 0; i < active.size(); ++i)
2133 if (!(i < zeta.size() && zeta[i] > 1 + std::max(1e-9,
opt.tol)))
2134 keep.push_back(active[i]);
2135 if (keep.size() != active.size()) {
2145 if (have_best && !converged && bconv) {
2146 x = bx; s2 = bs2; phi = bphi; mu = bmu; zeta = bzeta;
2147 active = bactive; imm = bimm; converged =
true; resnorm = bres;
2151 "The simultaneous closure solve stopped at residual " + std::to_string(resnorm) +
2152 " after " + std::to_string(iters) +
" Newton steps without reaching " +
2153 std::to_string(
opt.tol) +
". The reported point is the last iterate.");
2157 for (std::size_t s = 0; s < terms.nm; ++s)
2158 if (x[s] < -std::max(
opt.tol, 1e-10)) {
2160 "The fixed point holds " + std::to_string(x[s]) +
" tokens at " +
2161 terms.names_node[terms.coord_node[s]] +
2162 ", which is negative: no immediate transition could be rebound to pin that "
2163 "place at zero. Use SolverCTMC, SolverSSA or SolverLDES for this net.");
2167 const solver_detail::Ctx ctx = solver_detail::context(terms, cons, con, imm, active);
2168 std::vector<double> ufin;
2169 ufin.insert(ufin.end(), x.begin(), x.end());
2170 ufin.insert(ufin.end(), s2.begin(), s2.end());
2171 ufin.insert(ufin.end(), phi.begin(), phi.end());
2172 ufin.insert(ufin.end(), mu.begin(), mu.end());
2173 ufin.insert(ufin.end(), zeta.begin(), zeta.end());
2174 const solver_detail::Residual fin = solver_detail::residual(ufin, ctx,
false);
2185 for (std::size_t s = 0; s < terms.nm; ++s) {
2186 if (terms.coord_station[s] < 0)
continue;
2187 const std::size_t i =
static_cast<std::size_t
>(terms.coord_station[s]);
2188 const std::size_t k = terms.coord_class[s];
2189 out.
QN(i, k) += x[s];
2190 out.
UN(i, k) = out.
QN(i, k);
2192 for (std::map<std::size_t, std::vector<std::size_t>>::const_iterator it =
2193 terms.consumers.begin();
2194 it != terms.consumers.end(); ++it) {
2195 const std::size_t i = it->first / K, k = it->first % K;
2196 const std::vector<double>& w = terms.consumer_w.at(it->first);
2198 for (std::size_t q = 0; q < it->second.size(); ++q) val += w[q] * fin.r[it->second[q]];
2199 out.
TN(i, k) += val;
2201 for (std::map<std::size_t, std::vector<std::size_t>>::const_iterator it =
2202 terms.producers.begin();
2203 it != terms.producers.end(); ++it) {
2204 const std::size_t i = it->first / K, k = it->first % K;
2206 for (std::size_t e : it->second) val += fin.r[e];
2207 out.
TN(i, k) += val;
2209 for (std::size_t i = 0; i < M; ++i)
2210 for (std::size_t k = 0; k < K; ++k)
2211 if (out.
TN(i, k) > 1e-14) out.
RN(i, k) = out.
QN(i, k) / out.
TN(i, k);
2216 std::vector<double> gP, gN;
2217 solver_detail::gains(con, active, zeta, n, gP, gN);
2218 const PetriReport::Fluid fl = solver_detail::fluid_levels(terms, con, imm, active, x, s2, phi, fin.r,
2219 fin.Sigma,
opt.fluid_boundary);
2220 for (std::size_t q = 0; q < terms.nf; ++q) {
2221 if (terms.fluid_station[q] < 0 || K == 0)
continue;
2222 const std::size_t i =
static_cast<std::size_t
>(terms.fluid_station[q]);
2223 const std::size_t slot = terms.fluid_slot[q];
2226 const double lev = std::min(std::max(fl.
level[q], 0.0), terms.fluid_cap[q]);
2228 out.
UN(i, 0) = std::isfinite(terms.fluid_cap[q]) ? lev / terms.fluid_cap[q] : lev;
2229 double outflow = 0.0;
2230 for (std::size_t e = 0; e < terms.nev; ++e) outflow += std::min(terms.D(slot, e), 0.0) * fin.r[e];
2231 out.
TN(i, 0) = -gN[slot] * outflow;
2232 out.
RN(i, 0) = out.
TN(i, 0) > 0.0 ? out.
QN(i, 0) / out.
TN(i, 0) : 0.0;
2241 std::vector<std::vector<double>> phit = solver_detail::seed_flows(terms, xseed, lam);
2242 if (std::isfinite(
opt.timespan_end)) {
2243 solver_detail::PetriTransient
tr =
2244 solver_detail::petri_transient(terms, cons, con, imm, active,
opt, phi, zeta, out.
warnings);
2252 solver_detail::metrics_t(terms, out.
xvec_t, s2, phit, M, K, out);
2257 out.
Sigma = fin.Sigma;
2260 for (std::size_t s = 0; s < terms.nm; ++s) {
2261 if (terms.coord_station[s] < 0)
continue;
2262 const std::size_t i =
static_cast<std::size_t
>(terms.coord_station[s]);
2263 out.
QVar(i, terms.coord_class[s]) = std::max(0.0, fin.Sigma(s, s));
2265 for (std::size_t q = 0; q < terms.nf; ++q)
2266 if (terms.fluid_station[q] >= 0 && K > 0)
2267 out.
QVar(
static_cast<std::size_t
>(terms.fluid_station[q]), 0) = fl.
level_var[q];
2268 for (std::size_t i = 0; i < M; ++i)
2269 for (std::size_t k = 0; k < K; ++k) out.
QStd(i, k) = std::sqrt(out.
QVar(i, k));
2274 for (std::size_t s = 0; s < terms.nm; ++s) {
2275 out.
petri.
marking(terms.coord_node[s], terms.coord_class[s]) = x[s];
2277 std::max(0.0, fin.Sigma(s, s));
2281 for (std::size_t j = 0; j < terms.modes.size(); ++j) {
2283 for (std::size_t e = 0; e < terms.nev; ++e)
2284 if (terms.ev_mode[e] ==
static_cast<int>(j) &&
2285 (terms.ev_kind[e] == 1 || terms.ev_kind[e] == 4))
2293 for (std::size_t c = 0; c < cons.C.rows(); ++c) {
2295 for (std::size_t s = 0; s < terms.nstate; ++s) val += cons.C(c, s) * x[s];