5#ifndef LINE_SOLVERS_FLUID_FLUID_DAE_H
6#define LINE_SOLVERS_FLUID_FLUID_DAE_H
114 std::vector<double>
b;
124 static std::size_t
none() {
return static_cast<std::size_t
>(-1); }
137 const std::vector<std::size_t>& coord_class,
138 const std::vector<double>& njobs, std::size_t K) {
140 for (std::size_t k = 0; k < K; ++k) {
143 for (std::size_t s = 0; s < row.size(); ++s)
144 if (coord_class[s] == k) { w = std::max(w, row[s]); any =
true; }
145 if (!any || w <= 0.0)
continue;
146 const double nk = (k < njobs.size()) ? njobs[k] : std::numeric_limits<double>::infinity();
147 if (!std::isfinite(nk))
return std::numeric_limits<double>::infinity();
157 const std::size_t nstate = terms.
nstate;
159 const std::size_t K = M ? terms.
class_block[0].size() : 0;
163 std::vector<std::size_t> coord_station(nstate, NONE);
164 for (std::size_t i = 0; i < M; ++i)
165 for (std::size_t k = 0; k < K; ++k)
168 coord_station[s] = i;
170 con.
member.assign(std::max<std::size_t>(con.
nregions, 1), std::vector<bool>(nstate,
false));
172 const double UNB = -1.0;
173 std::vector<std::vector<double> > rows;
174 std::vector<std::size_t> rgs, sts, kls;
175 std::vector<bool> sdg;
176 for (std::size_t f = 0; f < con.
nregions; ++f) {
178 std::vector<bool> inR(nstate,
false);
179 bool any_member =
false;
180 for (std::size_t s = 0; s < nstate; ++s) {
181 const std::size_t i = coord_station[s];
182 if (i != NONE && i < R.
members.size() && R.
members[i]) { inR[s] =
true; any_member =
true; }
184 if (!any_member)
continue;
187 for (std::size_t k = 0; k < K && k < R.
rule.size(); ++k)
190 "fluid_dae_constraints: region " + std::to_string(f + 1) +
191 " applies a drop rule other than a waiting queue to class " +
192 std::to_string(k + 1) +
193 ". Only a waiting queue conserves the population and throttles the admission "
194 "flow, which is what an algebraic equation on this drift can express.");
195 for (std::size_t k = 0; k < K && k < R.
weight.size(); ++k)
196 if (std::fabs(
static_cast<double>(R.
weight[k]) - 1.0) > 1e-12)
198 "fluid_dae_constraints: region " + std::to_string(f + 1) +
199 " sets per-class admission weights, which decide WHICH blocked class enters "
200 "when capacity frees up. The throttle carries no such priority and would "
201 "ignore them silently.");
203 std::size_t member_row = 0;
204 for (std::size_t i = 0; i < R.
members.size(); ++i)
205 if (R.
members[i]) { member_row = i;
break; }
208 if (member_row < R.
cap.size() && R.
cap[member_row].size() > K) {
209 const double g =
static_cast<double>(R.
cap[member_row][K]);
210 if (std::isfinite(g) && g != UNB && g >= 0.0) {
211 std::vector<double> row(nstate, 0.0);
212 for (std::size_t s = 0; s < nstate; ++s)
if (inR[s]) row[s] = 1.0;
213 rows.push_back(row); con.
b.push_back(g);
214 rgs.push_back(f); sts.push_back(NONE); kls.push_back(NONE); sdg.push_back(
true);
215 con.
label.push_back(
"region " + std::to_string(f + 1) +
" global job cap");
219 if (member_row < R.
cap.size())
220 for (std::size_t k = 0; k < K && k < R.
cap[member_row].size(); ++k) {
221 const double c =
static_cast<double>(R.
cap[member_row][k]);
222 if (!std::isfinite(c) || c == UNB || c < 0.0)
continue;
223 std::vector<double> row(nstate, 0.0);
225 for (std::size_t s = 0; s < nstate; ++s)
226 if (inR[s] && con.
coord_class[s] == k) { row[s] = 1.0; any =
true; }
228 rows.push_back(row); con.
b.push_back(c);
229 rgs.push_back(f); sts.push_back(NONE); kls.push_back(k); sdg.push_back(
true);
230 con.
label.push_back(
"region " + std::to_string(f + 1) +
" class " +
231 std::to_string(k + 1) +
" job cap");
234 if (member_row < R.
maxmem.size()) {
235 const double mem =
static_cast<double>(R.
maxmem[member_row]);
236 if (std::isfinite(mem) && mem != UNB && mem >= 0.0) {
237 std::vector<double> row(nstate, 0.0);
239 for (std::size_t s = 0; s < nstate; ++s) {
240 if (!inR[s])
continue;
242 const double w = (k < R.
size.size()) ?
static_cast<double>(R.
size[k]) : 1.0;
243 if (w != 0.0) { row[s] = w; any =
true; }
246 rows.push_back(row); con.
b.push_back(mem);
247 rgs.push_back(f); sts.push_back(NONE); kls.push_back(NONE); sdg.push_back(
true);
248 con.
label.push_back(
"region " + std::to_string(f + 1) +
" memory budget");
253 for (std::size_t c = 0; c < R.
lincon_A.rows(); ++c) {
254 std::vector<double> row(nstate, 0.0);
256 for (std::size_t k = 0; k < K && k < R.
lincon_A.cols(); ++k) {
257 const double a =
static_cast<double>(R.
lincon_A(c, k));
258 if (a == 0.0)
continue;
259 for (std::size_t s = 0; s < nstate; ++s)
260 if (inR[s] && con.
coord_class[s] == k) { row[s] = a; any =
true; }
264 con.
b.push_back(
static_cast<double>(R.
lincon_b[c]));
265 rgs.push_back(f); sts.push_back(NONE); kls.push_back(NONE); sdg.push_back(
true);
266 con.
label.push_back(
"region " + std::to_string(f + 1) +
" linear constraint " +
267 std::to_string(c + 1));
276 for (std::size_t i = 0; i < M; ++i) {
277 if (terms.
is_ext[i])
continue;
279 for (std::size_t s = 0; s < nstate; ++s) any_at |= coord_station[s] == i;
280 if (!any_at)
continue;
281 if (i <
sn.cap.size()) {
282 const double g =
static_cast<double>(
sn.cap[i]);
283 if (std::isfinite(g) && g >= 0.0) {
284 std::vector<double> row(nstate, 0.0);
285 for (std::size_t s = 0; s < nstate; ++s)
if (coord_station[s] == i) row[s] = 1.0;
286 rows.push_back(row); con.
b.push_back(g);
287 rgs.push_back(NONE); sts.push_back(i); kls.push_back(NONE); sdg.push_back(
false);
288 con.
label.push_back(
"station " + std::to_string(i + 1) +
" buffer");
291 if (i <
sn.classcap.size())
292 for (std::size_t k = 0; k < K && k <
sn.classcap[i].size(); ++k) {
293 const double c =
static_cast<double>(
sn.classcap[i][k]);
294 if (!std::isfinite(c) || c < 0.0)
continue;
295 std::vector<double> row(nstate, 0.0);
297 for (std::size_t s = 0; s < nstate; ++s)
298 if (coord_station[s] == i && con.
coord_class[s] == k) { row[s] = 1.0; any =
true; }
300 rows.push_back(row); con.
b.push_back(c);
301 rgs.push_back(NONE); sts.push_back(i); kls.push_back(k); sdg.push_back(
false);
302 con.
label.push_back(
"station " + std::to_string(i + 1) +
" class " +
303 std::to_string(k + 1) +
" buffer");
307 const std::size_t ncand = rows.size();
308 std::vector<bool> keep(ncand,
true);
309 const std::vector<double> njobs =
sn.njobs();
314 for (std::size_t c = 0; c < ncand; ++c)
327 for (std::size_t c = 0; c < ncand; ++c) {
328 if (!keep[c])
continue;
330 for (std::size_t s = 0; s < nstate && !live; ++s)
332 if (!live) keep[c] =
false;
340 for (std::size_t c = 0; c < ncand; ++c) {
341 if (!keep[c])
continue;
342 for (std::size_t d = c + 1; d < ncand; ++d) {
343 if (!keep[d])
continue;
345 for (std::size_t s = 0; s < nstate && same; ++s)
346 same = std::fabs(rows[c][s] - rows[d][s]) <= 1e-14;
348 const bool takeover = con.
b[d] < con.
b[c] - 1e-14 ||
349 (std::fabs(con.
b[d] - con.
b[c]) <= 1e-14 && sdg[d] && !sdg[c]);
351 con.
b[c] = con.
b[d]; rgs[c] = rgs[d]; sts[c] = sts[d]; kls[c] = kls[d];
363 for (std::size_t c = 0; c < ncand; ++c) {
364 if (!keep[c] || kls[c] != NONE)
continue;
365 std::vector<std::size_t> classes;
366 for (std::size_t k = 0; k < K; ++k)
367 for (std::size_t s = 0; s < nstate; ++s)
368 if (con.
coord_class[s] == k && coord_station[s] != NONE && rows[c][s] > 0.0) {
369 classes.push_back(k);
372 if (classes.empty())
continue;
375 for (std::size_t ci = 0; ci < classes.size() && implied; ++ci) {
376 const std::size_t k = classes[ci];
377 std::size_t part = NONE;
378 for (std::size_t d = 0; d < ncand; ++d) {
379 if (!keep[d] || d == c || kls[d] != k || rgs[d] != rgs[c] || sts[d] != sts[c])
382 for (std::size_t s = 0; s < nstate && covers; ++s)
383 if (con.
coord_class[s] == k && coord_station[s] != NONE &&
384 rows[c][s] > 0.0 && rows[d][s] < rows[c][s] - 1e-14)
386 if (covers) { part = d;
break; }
388 if (part == NONE) implied =
false;
389 else budget += con.
b[part];
391 if (implied && budget <= con.
b[c] + 1e-14) keep[c] =
false;
394 std::vector<std::vector<double> > kept_rows;
395 std::vector<double> kept_b;
396 std::vector<std::string> kept_lab;
397 for (std::size_t c = 0; c < ncand; ++c) {
398 if (!keep[c])
continue;
399 kept_rows.push_back(rows[c]);
400 kept_b.push_back(con.
b[c]);
401 kept_lab.push_back(con.
label[c]);
402 con.
region.push_back(rgs[c]);
405 con.
staged.push_back(sdg[c]);
408 con.
label = kept_lab;
410 for (std::size_t r = 0; r < kept_rows.size(); ++r)
411 for (std::size_t s = 0; s < nstate; ++s) con.
A(r, s) = kept_rows[r][s];
421 for (std::size_t c = 0; c < con.
b.size(); ++c) {
422 const std::size_t i = con.
station[c];
423 if (i == NONE || i >=
sn.droprule.size())
continue;
424 for (std::size_t k = 0; k < K && k <
sn.droprule[i].size(); ++k) {
426 for (std::size_t s = 0; s < nstate; ++s)
427 if (coord_station[s] == i && con.
coord_class[s] == k && con.
A(c, s) > 0.0)
429 if (!weighs)
continue;
433 "fluid_dae_constraints: station " + std::to_string(i + 1) +
434 " applies a blocking or retrial rule to class " + std::to_string(k + 1) +
435 ", and its buffer binds. Only a waiting queue or a drop is a constraint on "
436 "this drift: BAS/BBS/RSRD add a blocked-server state to the upstream station "
437 "and the retrial rules add an orbit, so each needs a different drift rather "
438 "than an algebraic equation on this one.");
458 std::vector<std::vector<bool> >
gate;
459 std::vector<std::vector<bool> >
held;
460 std::vector<std::vector<bool> >
loss;
469 const std::size_t ncon = con.
b.size(), nev = t.
D.
cols(), nstate = t.
nstate;
470 g.
gate.assign(ncon, std::vector<bool>(nev,
false));
471 g.
held.assign(ncon, std::vector<bool>(nev,
false));
472 g.
loss.assign(ncon, std::vector<bool>(nev,
false));
473 if (ncon == 0)
return g;
478 std::vector<bool> ext_coord(nstate,
false);
481 for (std::size_t s : t.
station_block[i]) ext_coord[s] =
true;
482 for (std::size_t s = 0; s < nstate; ++s)
483 for (std::size_t e = 0; e < nev; ++e) {
484 const double d = t.
D(s, e);
488 if (d < 0.0) {
if (ext_coord[s]) g.
DnExt(s, e) = d;
else g.
Dn(s, e) = d; }
489 if (d > 0.0) g.
Dp(s, e) = d;
491 const double tol = 1e-7;
492 const std::vector<double> njobs =
sn.njobs();
493 for (std::size_t c = 0; c < ncon; ++c) {
495 for (std::size_t e = 0; e < nev; ++e) {
497 for (std::size_t s = 0; s < nstate; ++s) delta += con.
A(c, s) * t.
D(s, e);
498 if (delta <= tol)
continue;
501 if (con.
staged[c])
continue;
505 std::size_t k =
static_cast<std::size_t
>(-1);
506 for (std::size_t s = 0; s < nstate; ++s)
507 if (g.
Dp(s, e) > tol && con.
A(c, s) > 0.0) { k = con.
coord_class[s];
break; }
508 const bool isopen = k !=
static_cast<std::size_t
>(-1) && k < njobs.size() &&
509 !std::isfinite(njobs[k]);
510 g.
loss[c][e] = isopen;
511 g.
held[c][e] = !isopen;
515 "fluid_dae_gates: no event increases " + con.
label[c] +
516 ", so the cap can never be approached and there is no admission flow for the "
517 "constraint to throttle. This is a malformed limit rather than a solvable one.");
556 const std::size_t nev = t.
D.
cols();
557 const std::size_t ncon = con.
b.size();
558 stg.
adm.assign(nev,
false);
561 stg.
gated_by.assign(ncon, std::vector<bool>());
562 bool any_staged =
false;
563 for (std::size_t c = 0; c < ncon; ++c) any_staged = any_staged || con.
staged[c];
567 if (con.
empty() || con.
nregions == 0 || !any_staged)
return stg;
569 const double tol = 1e-9;
572 for (std::size_t s = 0; s < t.
nstate; ++s)
573 for (std::size_t e = 0; e < nev; ++e) {
574 const double d = t.
D(s, e);
575 if (d < 0.0) stg.
Dn(s, e) = d;
576 if (d > 0.0) stg.
Dp(s, e) = d;
579 std::vector<bool> staged_region(con.
nregions,
false);
580 for (std::size_t c = 0; c < ncon; ++c)
584 std::vector<std::vector<std::size_t> > idx(con.
nregions, std::vector<std::size_t>(K, 0));
585 for (std::size_t f = 0; f < con.
nregions; ++f) {
586 if (f >= con.
member.size() || !staged_region[f])
continue;
587 for (std::size_t e = 0; e < nev; ++e) {
591 for (std::size_t s = 0; s < t.
nstate; ++s)
592 if (con.
member[f][s]) delta += t.
D(s, e);
593 if (delta <= tol)
continue;
598 for (std::size_t s = 0; s < t.
nstate; ++s)
600 if (k >= K)
continue;
601 if (idx[f][k] == 0) {
603 stg.
klass.push_back(k);
604 idx[f][k] = stg.
region.size();
617 stg.
gated_by.assign(ncon, std::vector<bool>(stg.
n,
false));
618 for (std::size_t c = 0; c < ncon; ++c) {
620 for (std::size_t j = 0; j < stg.
n; ++j) {
623 for (std::size_t s = 0; s < t.
nstate; ++s)
625 w = std::max(w, con.
A(c, s));
645 const std::size_t ncon = con.
b.size();
647 if (ncon == 0 || stg.
n == 0)
return;
648 const std::size_t nev = t.
D.
cols();
649 const double tol = 1e-9;
650 std::vector<std::vector<std::size_t> > feeds(stg.
n);
651 std::vector<std::size_t> dest(stg.
n,
static_cast<std::size_t
>(-1));
652 for (std::size_t e = 0; e < nev; ++e) {
653 if (!stg.
adm[e])
continue;
655 for (std::size_t s = 0; s < t.
nstate; ++s) {
656 if (t.
D(s, e) < -tol &&
657 std::find(feeds[j].begin(), feeds[j].end(), s) == feeds[j].end())
658 feeds[j].push_back(s);
659 if (dest[j] ==
static_cast<std::size_t
>(-1) && t.
D(s, e) > tol &&
664 for (std::size_t c = 0; c < ncon; ++c)
665 for (std::size_t j = 0; j < stg.
n; ++j) {
666 if (dest[j] ==
static_cast<std::size_t
>(-1) || feeds[j].empty())
continue;
667 const double w = con.
A(c, dest[j]);
668 if (w <= 0.0)
continue;
669 bool all_inside =
true;
670 for (std::size_t a = 0; a < feeds[j].size() && all_inside; ++a)
671 all_inside = con.
A(c, feeds[j][a]) > 0.0;
672 if (all_inside) con.
As(c, j) = w;
680 std::vector<double>
ds;
700 const std::vector<std::size_t>& active,
701 const std::vector<double>& sg,
const std::vector<double>& r,
702 const std::vector<double>& mult,
bool staged_flow) {
703 const std::size_t nev = r.size(), ns = stg.
n, nact = active.size();
705 std::vector<double> whole(nev, 1.0), entry(nev, 1.0);
706 std::vector<double> inv_theta(ns, 0.0), flow_of(ns, 0.0);
707 std::vector<bool> throttled(ns,
false);
708 for (std::size_t k = 0; k < nact; ++k) {
709 const std::size_t c = active[k];
710 const double m = mult[k];
712 for (std::size_t j = 0; j < ns; ++j)
716 inv_theta[j] += (m > 1e-14) ? 1.0 / m
717 : std::numeric_limits<double>::infinity();
720 for (std::size_t e = 0; e < nev; ++e) {
721 if (gates.
held[c][e]) whole[e] *= m;
722 if (gates.
loss[c][e]) entry[e] *= m;
726 std::vector<bool> split(nev,
false);
727 for (std::size_t e = 0; e < nev; ++e)
728 if (ns && stg.
adm[e] && throttled[stg.
adm_stage[e]]) split[e] =
true;
730 out.
rup.assign(nev, 0.0);
731 out.
rin.assign(nev, 0.0);
732 for (std::size_t e = 0; e < nev; ++e) {
736 out.
rup[e] = r[e] * (split[e] ? 1.0 : whole[e]);
737 out.
rin[e] = out.
rup[e] * entry[e];
739 std::vector<double> inflow(ns, 0.0), R(ns, 0.0);
740 for (std::size_t e = 0; e < nev; ++e)
746 for (std::size_t k = 0; k < nact; ++k) {
747 const std::size_t c = active[k];
748 if (!con.
staged[c])
continue;
749 double mass = 0.0, tot = 0.0;
750 std::size_t nrooms = 0;
751 for (std::size_t j = 0; j < ns; ++j)
752 if (stg.
gated_by[c][j]) { mass += sg[j]; tot += inflow[j]; ++nrooms; }
753 if (nrooms == 0)
continue;
754 for (std::size_t j = 0; j < ns; ++j) {
757 if (mass > 1e-8) w = sg[j] / mass;
758 else if (tot > 1e-8) w = inflow[j] / tot;
759 else w = 1.0 /
static_cast<double>(nrooms);
760 flow_of[j] += mult[k] * w;
764 for (std::size_t j = 0; j < ns; ++j) {
765 const double theta = inv_theta[j] > 0.0 ? 1.0 / inv_theta[j] : 0.0;
766 flow_of[j] = theta * sg[j];
770 std::vector<double> left(ns, 0.0);
771 for (std::size_t e = 0; e < nev; ++e) {
772 if (!split[e])
continue;
774 const double q = R[j] > 1e-8 ? flow_of[j] * out.
rup[e] / R[j] : 0.0;
777 out.
rin[e] = q * whole[e] * entry[e];
778 left[j] += q * whole[e];
780 for (std::size_t j = 0; j < ns; ++j)
781 if (R[j] > 1e-8) out.
drain[j] = left[j];
782 out.
ds.assign(ns, 0.0);
783 for (std::size_t j = 0; j < ns; ++j)
785 out.
ds[j] = throttled[j] ? inflow[j] - out.
drain[j] : sg[j];
792 std::vector<double>
N;
806 const std::size_t nstate = terms.
nstate;
808 const std::size_t K = M ? terms.
class_block[0].size() : 0;
810 std::vector<std::size_t> coord_class(nstate, 0), coord_station(nstate, 0);
811 for (std::size_t i = 0; i < M; ++i)
812 for (std::size_t k = 0; k < K; ++k)
815 coord_station[s] = i;
818 std::vector<std::vector<double> > rows;
819 std::vector<double> nvec;
820 const std::size_t nchains =
sn.chains.size();
821 const std::vector<double> njobs =
sn.njobs();
822 for (std::size_t ch = 0; ch < nchains; ++ch) {
823 std::vector<std::size_t> inch;
826 for (std::size_t k = 0; k < K; ++k) {
827 if (k >=
sn.chains[ch].size() || !
sn.chains[ch][k])
continue;
829 const double nj = (k < njobs.size()) ? njobs[k] : 0.0;
830 if (!std::isfinite(nj)) finite =
false;
833 if (inch.empty() || !finite || Nch <= 0.0)
continue;
834 std::vector<double> row(nstate + stg.
n, 0.0);
836 for (std::size_t s = 0; s < nstate; ++s) {
837 if (terms.
is_ext[coord_station[s]])
continue;
838 if (std::find(inch.begin(), inch.end(), coord_class[s]) == inch.end())
continue;
843 for (std::size_t j = 0; j < stg.
n; ++j)
844 if (std::find(inch.begin(), inch.end(), stg.
klass[j]) != inch.end())
845 row[nstate + j] = 1.0;
853 for (std::size_t r = 0; r < rows.size(); ++r)
854 for (std::size_t s = 0; s < nstate + stg.
n; ++s) out.
C(r, s) = rows[r][s];
920 std::vector<std::size_t> idx;
922 for (std::size_t i = 0; i < M; ++i) {
925 if (!std::isfinite(t.
S[i]))
continue;
934 const std::vector<std::size_t>& cidx) {
936 for (std::size_t j = 0; j < cidx.size(); ++j) {
937 const std::vector<std::size_t>& blk = t.
station_block[cidx[j]];
939 for (std::size_t a = 0; a < blk.
size(); ++a)
940 for (std::size_t b = 0; b < blk.
size(); ++b) acc += Sigma(blk[a], blk[b]);
941 s2[cidx[j]] = std::max(0.0, acc);
976 const std::vector<std::size_t>& active,
978 std::vector<std::size_t> rows;
979 for (std::size_t k = 0; k < active.size(); ++k)
980 if (!con.
staged[active[k]]) rows.push_back(active[k]);
985 for (std::size_t i = 0; i < rows.size(); ++i)
986 for (std::size_t j = 0; j <
nc; ++j) {
987 R(i, j) = con.
A(rows[i], t.
cov_idx[j]);
988 if (std::fabs(R(i, j)) > 1e-14) any =
true;
992 const std::size_t m = rows.size();
994 for (std::size_t a = 0; a < m; ++a) {
995 for (std::size_t b = 0; b < m; ++b) {
997 for (std::size_t j = 0; j <
nc; ++j) acc += R(a, j) * R(b, j);
1002 for (std::size_t a = 0; a < m; ++a) {
1003 std::size_t piv = a;
1004 for (std::size_t b = a + 1; b < m; ++b)
1005 if (std::fabs(G(b, a)) > std::fabs(G(piv, a))) piv = b;
1006 if (std::fabs(G(piv, a)) < 1e-300)
return Matrix<double>(0, 0, 0.0);
1008 for (std::size_t j = 0; j < 2 * m; ++j) std::swap(G(a, j), G(piv, j));
1009 const double d = G(a, a);
1010 for (std::size_t j = 0; j < 2 * m; ++j) G(a, j) /= d;
1011 for (std::size_t b = 0; b < m; ++b) {
1012 if (b == a)
continue;
1013 const double f = G(b, a);
1014 if (f == 0.0)
continue;
1015 for (std::size_t j = 0; j < 2 * m; ++j) G(b, j) -= f * G(a, j);
1019 for (std::size_t i = 0; i <
nc; ++i) T(i, i) = 1.0;
1020 for (std::size_t i = 0; i <
nc; ++i)
1021 for (std::size_t j = 0; j <
nc; ++j) {
1023 for (std::size_t a = 0; a < m; ++a)
1024 for (std::size_t b = 0; b < m; ++b) acc += R(a, i) * G(a, m + b) * R(b, j);
1042 std::vector<double>& G, std::vector<double>* rates_out,
1043 bool rethrow =
false, std::vector<double>* fire_out =
nullptr) {
1046 const std::size_t ncl = sysd.
cidx.size(), nact = sysd.
active.size();
1047 const std::vector<double> x(u.begin(), u.begin() + n);
1048 const std::vector<double> sg(u.begin() + n, u.begin() + n + ns);
1056 for (std::size_t j = 0; j < ncl; ++j)
1057 cl.
sigma2[sysd.
cidx[j]] = std::max(0.0, u[n + ns + j]);
1058 std::vector<double> mult(nact, 0.0);
1059 for (std::size_t k = 0; k < nact; ++k) mult[k] = std::max(0.0, u[n + ns + ncl + k]);
1061 std::vector<double> r;
1072 }
catch (
const std::exception&) {
1082 const std::size_t nev = r.size();
1083 std::vector<double> drift(n, 0.0);
1084 for (std::size_t s = 0; s < n; ++s) {
1086 for (std::size_t e = 0; e < nev; ++e) {
1101 G.insert(G.end(), drift.begin(), drift.end());
1102 G.insert(G.end(), lg.
ds.begin(), lg.
ds.end());
1104 for (std::size_t c = 0; c < C.
rows(); ++c) {
1106 for (std::size_t s = 0; s < n; ++s) acc += C(c, s) * x[s];
1107 for (std::size_t j = 0; j < ns; ++j) acc += C(c, n + j) * sg[j];
1108 G.push_back(acc - sysd.
cons->
N[c]);
1110 for (std::size_t j = 0; j < ncl; ++j)
1115 for (std::size_t k = 0; k < nact; ++k) {
1117 for (std::size_t s = 0; s < n; ++s) acc += sysd.
con->
A(sysd.
active[k], s) * x[s];
1118 for (std::size_t j = 0; j < ns; ++j) acc += sysd.
con->
As(sysd.
active[k], j) * sg[j];
1119 G.push_back(acc - sysd.
con->
b[sysd.
active[k]]);
1124 if (rates_out) *rates_out = lg.
rin;
1125 if (fire_out) *fire_out = lg.
rup;
1131 for (std::size_t i = nfree; i < u.size(); ++i) u[i] = std::max(0.0, u[i]);
1137 double residual = std::numeric_limits<double>::infinity();
1159 double tol, std::size_t maxit) {
1161 const std::size_t nfree = sysd.
nstate;
1163 std::vector<double> G;
1165 double resnorm = 0.0;
1166 for (
double g : G) resnorm = std::max(resnorm, std::fabs(g));
1168 const std::size_t nun = u.size(), m = G.size();
1169 while (resnorm >= tol && info.
iters < maxit) {
1172 std::vector<double> Gp;
1173 for (std::size_t j = 0; j < nun; ++j) {
1174 const double h = std::max(1e-7 * std::fabs(u[j]), 1e-9);
1175 std::vector<double> up = u;
1178 for (std::size_t i = 0; i < m; ++i) J(i, j) = (Gp[i] - G[i]) / h;
1180 std::vector<double> du;
1183 }
catch (
const std::exception&) {
1186 for (
double& d : du) d = -d;
1189 bool stepped =
false;
1190 for (
int ls = 0; ls < 25; ++ls) {
1191 std::vector<double> un(nun);
1192 for (std::size_t j = 0; j < nun; ++j) un[j] = u[j] + lam * du[j];
1194 std::vector<double> Gn;
1198 for (
double g : Gn) {
1199 if (!std::isfinite(g)) { ok =
false;
break; }
1200 rn = std::max(rn, std::fabs(g));
1202 if (ok && rn < resnorm * (1.0 - 1e-4 * lam)) {
1203 u = un; G = Gn; resnorm = rn;
1210 if (!stepped)
break;
1228struct FluidDaeTransient {
1229 const FluidMomentTerms* terms =
nullptr;
1230 const FluidDaeConservation* cons =
nullptr;
1231 FluidClosure closure;
1232 std::vector<std::size_t> alg_row;
1233 std::size_t nstate = 0;
1244 bool withcov =
false;
1245 std::vector<std::size_t> cov_idx;
1247 std::vector<std::size_t> closable;
1251 std::vector<double> grid;
1252 std::size_t cursor = 0;
1253 std::vector<std::vector<double> > out;
1266 const FluidDaeConstraints* con =
nullptr;
1267 const FluidDaeGates* gates =
nullptr;
1268 const FluidDaeStaging* stg =
nullptr;
1269 std::vector<std::size_t> active;
1270 std::vector<char> armed;
1271 std::vector<char> gated_rooms;
1272 std::vector<double> inert;
1273 std::size_t ncon = 0, nstg = 0, o_sg = 0, o_m = 0, o_cov = 0;
1275 std::vector<double> gprev;
1278 std::vector<double> hit_z;
1290inline std::vector<double> fluid_dae_cap_events(
const FluidDaeTransient& S,
1292 std::vector<double> g(S.ncon, 1.0);
1293 for (std::size_t c = 0; c < S.ncon; ++c) {
1294 const bool is_active =
1295 std::find(S.active.begin(), S.active.end(), c) != S.active.end();
1297 if (S.con->staged[c]) {
1299 for (std::size_t j = 0; j < S.nstg; ++j)
1300 if (S.stg->gated_by[c][j]) mass += z[S.o_sg + j];
1301 g[c] = S.armed[c] ? mass : 1.0;
1303 g[c] = 1.0 - z[S.o_m + c];
1307 for (std::size_t s = 0; s < S.nstate; ++s) val += S.con->A(c, s) * z[s];
1308 for (std::size_t j = 0; j < S.nstg; ++j) val += S.con->As(c, j) * z[S.o_sg + j];
1309 g[c] = S.con->b[c] - val;
1314inline FluidDaeTransient*& fluid_dae_active() {
1315 static FluidDaeTransient* p =
nullptr;
1320inline FluidClosure fluid_dae_closure_at(
const FluidDaeTransient& S,
1321 const rodas_impl::doublereal* y) {
1322 if (!S.withcov)
return S.closure;
1324 cl.sigma2.assign(S.terms->station_block.size(), 0.0);
1325 cl.cov.assign(S.terms->station_block.size(), Matrix<double>(0, 0, 0.0));
1326 Matrix<double> Sigma(S.terms->nstate, S.terms->nstate, 0.0);
1327 for (std::size_t a = 0; a < S.nc; ++a)
1328 for (std::size_t b = 0; b < S.nc; ++b)
1330 Sigma(S.cov_idx[a], S.cov_idx[b]) =
1331 0.5 * (y[S.o_cov + a * S.nc + b] + y[S.o_cov + b * S.nc + a]);
1333 for (std::size_t i = 0; i < s2.size(); ++i) cl.sigma2[i] = s2[i];
1338inline int fluid_dae_fcn(rodas_impl::integer*, rodas_impl::doublereal*,
1339 rodas_impl::doublereal* y, rodas_impl::doublereal* f,
1340 rodas_impl::doublereal*, rodas_impl::integer*) {
1341 FluidDaeTransient& S = *fluid_dae_active();
1342 const std::vector<double> x(y, y + S.nstate);
1343 const FluidClosure cl = fluid_dae_closure_at(S, y);
1344 std::vector<double> dx;
1349 const std::vector<double> sg(y + S.o_sg, y + S.o_sg + S.nstg);
1350 std::vector<double> mact(S.active.size(), 0.0);
1351 for (std::size_t k = 0; k < S.active.size(); ++k) mact[k] = y[S.o_m + S.active[k]];
1353 lg =
fluid_dae_legs(*S.terms, *S.gates, *S.stg, *S.con, S.active, sg, r, mact,
true);
1354 dx.assign(S.nstate, 0.0);
1355 for (std::size_t s = 0; s < S.nstate; ++s) {
1357 for (std::size_t e = 0; e < r.size(); ++e)
1358 acc += S.gates->Dn(s, e) * lg.rup[e] + S.gates->DnExt(s, e) * lg.rin[e] +
1359 S.gates->Dp(s, e) * lg.rin[e];
1363 for (std::size_t i = 0; i < S.nstate; ++i) f[i] = dx[i];
1364 const Matrix<double>& C = S.cons->C;
1365 for (std::size_t c = 0; c < C.rows(); ++c) {
1367 for (std::size_t s = 0; s < S.nstate; ++s) acc += C(c, s) * x[s];
1368 for (std::size_t j = 0; j < S.nstg; ++j) acc += C(c, S.nstate + j) * y[S.o_sg + j];
1369 f[S.alg_row[c]] = acc - S.cons->N[c];
1372 for (std::size_t j = 0; j < S.nstg; ++j)
1373 f[S.o_sg + j] = S.gated_rooms[j] ? lg.ds[j] : y[S.o_sg + j];
1374 for (std::size_t c = 0; c < S.ncon; ++c) {
1375 const bool is_active =
1376 std::find(S.active.begin(), S.active.end(), c) != S.active.end();
1378 f[S.o_m + c] = y[S.o_m + c] - S.inert[c];
1382 for (std::size_t s = 0; s < S.nstate; ++s) acc += S.con->A(c, s) * dx[s];
1383 for (std::size_t j = 0; j < S.nstg; ++j) acc += S.con->As(c, j) * lg.ds[j];
1392 const std::vector<double> r =
1394 for (std::size_t a = 0; a < S.nc; ++a)
1395 for (std::size_t b = 0; b < S.nc; ++b) {
1397 for (std::size_t l = 0; l < S.nc; ++l)
1398 acc += A(S.cov_idx[a], S.cov_idx[l]) * y[S.o_cov + l * S.nc + b]
1399 + y[S.o_cov + a * S.nc + l] * A(S.cov_idx[b], S.cov_idx[l]);
1400 for (std::size_t e = 0; e < r.size(); ++e)
1401 acc += S.terms->D(S.cov_idx[a], e) * r[e] * S.terms->D(S.cov_idx[b], e);
1402 f[S.o_cov + a * S.nc + b] = acc;
1409inline int fluid_dae_jac(rodas_impl::integer*, rodas_impl::doublereal*,
1410 rodas_impl::doublereal* y, rodas_impl::doublereal* dfy,
1411 rodas_impl::integer* ldfy, rodas_impl::doublereal*,
1412 rodas_impl::integer*) {
1413 FluidDaeTransient& S = *fluid_dae_active();
1414 const std::vector<double> x(y, y + S.nstate);
1416 const int ld = *ldfy;
1417 for (std::size_t i = 0; i < S.nstate; ++i)
1418 for (std::size_t j = 0; j < S.nstate; ++j) dfy[i + j * ld] = A(i, j);
1419 const Matrix<double>& C = S.cons->C;
1420 for (std::size_t c = 0; c < C.rows(); ++c)
1421 for (std::size_t j = 0; j < S.nstate; ++j) dfy[S.alg_row[c] + j * ld] = C(c, j);
1426inline int fluid_dae_mas(rodas_impl::integer* n, rodas_impl::doublereal* am,
1427 rodas_impl::integer* lmas, rodas_impl::doublereal*,
1428 rodas_impl::integer*) {
1429 FluidDaeTransient& S = *fluid_dae_active();
1430 const int ld = *lmas;
1431 for (
int j = 0; j < *n; ++j) am[0 + j * ld] = 1.0;
1432 for (std::size_t c = 0; c < S.alg_row.size(); ++c) am[0 + S.alg_row[c] * ld] = 0.0;
1435 for (std::size_t j = 0; j < S.nstg; ++j)
1436 if (!S.gated_rooms[j]) am[0 + (S.o_sg + j) * ld] = 0.0;
1437 for (std::size_t c = 0; c < S.ncon; ++c) am[0 + (S.o_m + c) * ld] = 0.0;
1455inline int fluid_dae_solout(rodas_impl::integer* nr, rodas_impl::doublereal* xold,
1456 rodas_impl::doublereal* x, rodas_impl::doublereal* y,
1457 rodas_impl::doublereal* cont, rodas_impl::integer* lrc,
1458 rodas_impl::integer*, rodas_impl::doublereal*,
1459 rodas_impl::integer*, rodas_impl::integer* irtrn) {
1460 FluidDaeTransient& S = *fluid_dae_active();
1461 const std::size_t nz = S.o_cov + (S.withcov ? S.nc * S.nc : 0);
1462 double stop_at = std::numeric_limits<double>::infinity();
1463 if (S.ncon && *nr > 1) {
1465 for (std::size_t k = 0; k < S.active.size(); ++k) {
1466 const std::size_t c = S.active[k];
1467 if (!S.con->staged[c] || S.armed[c])
continue;
1469 for (std::size_t j = 0; j < S.nstg; ++j)
1470 if (S.stg->gated_by[c][j]) mass += y[S.o_sg + j];
1471 if (mass > 1e-8) S.armed[c] = 1;
1473 const std::vector<double> gcur = fluid_dae_cap_events(S, y);
1475 for (std::size_t c = 0; c < S.ncon; ++c)
1476 if (S.gprev.size() == S.ncon && S.gprev[c] > 0.0 && gcur[c] <= 0.0) {
1477 cross =
static_cast<int>(c);
1487 std::vector<double> ymid(nz, 0.0);
1488 for (
int bit = 0; bit < 60; ++bit) {
1489 const double mid = 0.5 * (lo + hi);
1490 for (std::size_t i = 0; i < nz; ++i) {
1491 rodas_impl::integer ii =
static_cast<rodas_impl::integer
>(i + 1);
1493 ymid[i] = rodas_impl::contro_(&ii, &tq, cont, lrc);
1495 const std::vector<double> gmid = fluid_dae_cap_events(S, ymid.data());
1496 if (gmid[
static_cast<std::size_t
>(cross)] > 0.0) lo = mid;
1498 if (hi - lo <= 1e-12 * std::max(1.0, std::fabs(hi)))
break;
1500 std::vector<double> yhit(nz, 0.0);
1501 for (std::size_t i = 0; i < nz; ++i) {
1502 rodas_impl::integer ii =
static_cast<rodas_impl::integer
>(i + 1);
1504 yhit[i] = rodas_impl::contro_(&ii, &tq, cont, lrc);
1511 }
else if (S.ncon) {
1512 S.gprev = fluid_dae_cap_events(S, y);
1516 while (S.cursor < S.grid.size() && S.grid[S.cursor] <= *x + 1e-13 &&
1517 S.grid[S.cursor] <= stop_at + 1e-13) {
1518 double tq = S.grid[S.cursor];
1519 std::vector<double> xs(nz, 0.0);
1520 for (std::size_t i = 0; i < nz; ++i) {
1524 rodas_impl::integer ii =
static_cast<rodas_impl::integer
>(i + 1);
1525 xs[i] = rodas_impl::contro_(&ii, &tq, cont, lrc);
1528 S.out.push_back(xs);
1535 if (S.hit >= 0 && irtrn) *irtrn = -1;
1538inline int fluid_dae_dfx(rodas_impl::integer*, rodas_impl::doublereal*,
1539 rodas_impl::doublereal*, rodas_impl::doublereal*,
1540 rodas_impl::doublereal*, rodas_impl::integer*) {
return 0; }
1563 const FluidClosure& closure,
const std::vector<double>& x0,
1564 const std::vector<double>& grid,
double tol,
bool withcov =
false,
1565 const std::vector<std::size_t>& closable = std::vector<std::size_t>()) {
1566 using namespace rodas_impl;
1567 const std::size_t n = terms.
nstate;
1569 detail::FluidDaeTransient S;
1572 S.closure = closure;
1574 S.withcov = withcov;
1577 S.closable = closable;
1578 if (S.nc == 0) S.withcov =
false;
1582 const std::size_t nz = n + (S.withcov ? S.nc * S.nc : 0);
1583 for (std::size_t c = 0; c < cons.
C.
rows(); ++c) {
1584 std::size_t best = n;
1585 double bestv = -1.0;
1586 for (std::size_t s = 0; s < n; ++s) {
1587 if (cons.
C(c, s) == 0.0)
continue;
1589 for (std::size_t d = 0; d < S.alg_row.size(); ++d)
1590 if (S.alg_row[d] == s) taken =
true;
1591 if (taken)
continue;
1592 if (x0[s] > bestv) { bestv = x0[s]; best = s; }
1594 if (best == n)
throw NumericError(
"fluid_dae_integrate: a chain has no free coordinate");
1595 S.alg_row.push_back(best);
1597 if (grid.empty())
throw InputError(
"fluid_dae_integrate: the output grid is empty");
1598 for (std::size_t j = 1; j < grid.size(); ++j)
1599 if (!(grid[j] > grid[j - 1]))
1600 throw InputError(
"fluid_dae_integrate: the output grid must be increasing");
1602 detail::fluid_dae_active() = &S;
1607 const integer N =
static_cast<integer
>(nz);
1608 const std::size_t lwork = nz * (nz + 1 + nz + 14) + 20 + 32;
1609 const std::size_t liwork = nz + 20 + 32;
1610 std::vector<doublereal> y(nz, 0.0), work(lwork, 0.0), rpar(1, 0.0);
1611 for (std::size_t i = 0; i < n && i < x0.size(); ++i) y[i] = x0[i];
1616 std::vector<integer> iwork(liwork, 0), ipar(1, 0);
1617 doublereal rtol = tol, atol = tol * 1e-2, x = 0.0, xend = grid.back(), h = 1e-6;
1625 integer itol = 0, ifcn = 0, ijac = S.withcov ? 0 : 1, mljac = N, mujac = N, idfx = 0;
1626 integer imas = 1, mlmas = 0, mumas = 0, iout = 1, idid = 0;
1627 integer lw =
static_cast<integer
>(lwork), liw =
static_cast<integer
>(liwork);
1630 rodas_(
const_cast<integer*
>(&N), (U_fp)detail::fluid_dae_fcn, &ifcn, &x, y.data(),
1631 &xend, &h, &rtol, &atol, &itol,
1632 (U_fp)detail::fluid_dae_jac, &ijac, &mljac, &mujac,
1633 (U_fp)detail::fluid_dae_dfx, &idfx,
1634 (U_fp)detail::fluid_dae_mas, &imas, &mlmas, &mumas,
1635 (U_fp)detail::fluid_dae_solout, &iout,
1636 work.data(), &lw, iwork.data(), &liw, rpar.data(), ipar.data(), &idid);
1640 detail::fluid_dae_active() =
nullptr;
1643 detail::fluid_dae_active() =
nullptr;
1646 throw NumericError(
"fluid_dae_integrate: RODAS returned idid=" + std::to_string(idid));
1650 if (S.out.size() + 1 == grid.size()) S.out.push_back(std::vector<double>(y.begin(), y.end()));
1651 if (S.out.size() != grid.size())
1652 throw NumericError(
"fluid_dae_integrate: RODAS reported " + std::to_string(S.out.size()) +
1653 " of the " + std::to_string(grid.size()) +
" requested output points");
1654 S.out.back().assign(y.begin(), y.end());
1672 const std::vector<double>& x,
const std::vector<double>& sg,
const FluidClosure& cl,
1673 const std::vector<double>& m0,
bool& ok) {
1674 std::vector<double> m = m0;
1676 if (active.empty())
return m;
1677 const std::size_t n = terms.
nstate;
1678 auto resid = [&](
const std::vector<double>& mm) {
1681 std::vector<double> dx(n, 0.0);
1682 for (std::size_t s = 0; s < n; ++s) {
1684 for (std::size_t e = 0; e < r.size(); ++e)
1685 acc += gates.
Dn(s, e) * lg.
rup[e] + gates.
DnExt(s, e) * lg.
rin[e] +
1686 gates.
Dp(s, e) * lg.
rin[e];
1689 std::vector<double> F(active.size(), 0.0);
1690 for (std::size_t k = 0; k < active.size(); ++k) {
1692 for (std::size_t s = 0; s < n; ++s) acc += con.
A(active[k], s) * dx[s];
1693 for (std::size_t j = 0; j < stg.
n; ++j) acc += con.
As(active[k], j) * lg.
ds[j];
1698 auto inf_norm = [](
const std::vector<double>& v) {
1700 for (
double e : v) a = std::max(a, std::fabs(e));
1703 std::vector<double> F = resid(m);
1704 for (
int it = 0; it < 40; ++it) {
1705 if (inf_norm(F) < std::max(1e-12, 1e-10 * (inf_norm(m) + 1.0)))
break;
1707 for (std::size_t j = 0; j < m.size(); ++j) {
1708 const double h = std::max(1e-7 * std::fabs(m[j]), 1e-9);
1709 std::vector<double> mp = m;
1711 const std::vector<double> Fp = resid(mp);
1712 for (std::size_t i = 0; i < F.size(); ++i) J(i, j) = (Fp[i] - F[i]) / h;
1714 std::vector<double> step;
1716 step =
lstsq(J, F).x;
1717 }
catch (
const std::exception&) {
1721 bool stepped =
false;
1722 for (
int ls = 0; ls < 20; ++ls) {
1723 std::vector<double> mn(m.size(), 0.0);
1724 for (std::size_t j = 0; j < m.size(); ++j) mn[j] = std::max(0.0, m[j] - lam * step[j]);
1725 const std::vector<double> Fn = resid(mn);
1726 if (inf_norm(Fn) < inf_norm(F)) {
1734 if (!stepped)
break;
1736 ok = inf_norm(F) < 1e-6;
1755 const FluidClosure& closure,
const std::vector<double>& x0In,
1756 const std::vector<double>& grid,
double tol, std::vector<FluidDaeSwitch>* switches =
nullptr) {
1757 using namespace rodas_impl;
1758 const std::size_t n = terms.
nstate;
1759 const std::size_t ncon = con.
b.size(), nstg = stg.
n;
1761 detail::FluidDaeTransient S;
1764 S.closure = closure;
1776 S.o_cov = n + nstg + ncon;
1777 S.armed.assign(std::max<std::size_t>(ncon, 1), 0);
1778 S.inert.assign(ncon, 1.0);
1779 for (std::size_t c = 0; c < ncon; ++c)
1780 if (con.
staged[c]) S.inert[c] = 0.0;
1781 const std::size_t nz = S.o_cov;
1788 std::vector<double> x0 = x0In;
1790 std::vector<double> sg0(nstg, 0.0), excess(std::max<std::size_t>(ncon, 1), 0.0);
1791 std::vector<std::size_t> over;
1792 for (std::size_t c = 0; c < ncon; ++c) {
1793 double val = 0.0, tot = 0.0;
1794 for (std::size_t s = 0; s < n; ++s) {
1795 val += con.
A(c, s) * x0[s];
1796 if (con.
A(c, s) > 0.0) tot += x0[s];
1798 if (val > con.
b[c] + std::max(1e-9, tol) && con.
b[c] > 0.0) {
1799 excess[c] = tot * (1.0 - con.
b[c] / val);
1800 for (std::size_t s = 0; s < n; ++s)
1801 if (con.
A(c, s) > 0.0) x0[s] *= con.
b[c] / val;
1807 std::vector<double> m_init = S.inert;
1808 if (!over.empty()) {
1809 std::vector<double> m0(over.size(), 0.0);
1810 for (std::size_t a = 0; a < over.size(); ++a) m0[a] = con.
staged[over[a]] ? 0.0 : 1.0;
1813 x0, sg0, closure, m0, ok);
1815 for (std::size_t a = 0; a < over.size(); ++a)
1816 if (!con.
staged[over[a]] && mm[a] > 1.0 + 1e-9) feasible =
false;
1819 for (std::size_t a = 0; a < over.size(); ++a) m_init[over[a]] = mm[a];
1822 for (std::size_t oi = 0; oi < over.size(); ++oi) {
1823 const std::size_t c = over[oi];
1824 if (excess[c] <= 0.0)
continue;
1825 std::size_t rooms = 0;
1826 const bool staged_active =
1827 con.
staged[c] && std::find(S.active.begin(), S.active.end(), c) != S.active.end();
1829 for (std::size_t j = 0; j < nstg; ++j)
1832 for (std::size_t j = 0; j < nstg; ++j)
1833 if (stg.
gated_by[c][j]) sg0[j] += excess[c] /
static_cast<double>(rooms);
1838 std::vector<bool> pool(n,
false);
1839 bool any_feeder =
false;
1840 std::vector<bool> feeders(n,
false);
1841 for (std::size_t e = 0; e < terms.
D.
cols(); ++e) {
1842 if (!gates.
gate[c][e])
continue;
1843 for (std::size_t s = 0; s < n; ++s)
1844 if (gates.
Dn(s, e) < 0.0 || gates.
DnExt(s, e) < 0.0) feeders[s] =
true;
1846 for (std::size_t s = 0; s < n; ++s) {
1847 pool[s] = con.
A(c, s) <= 0.0;
1848 any_feeder = any_feeder || (pool[s] && feeders[s]);
1851 for (std::size_t s = 0; s < n; ++s) pool[s] = pool[s] && feeders[s];
1853 std::size_t cnt = 0;
1854 for (std::size_t s = 0; s < n; ++s)
1855 if (pool[s]) { w += x0[s]; ++cnt; }
1857 for (std::size_t s = 0; s < n; ++s)
1858 if (pool[s]) x0[s] += excess[c] * x0[s] / w;
1860 for (std::size_t s = 0; s < n; ++s)
1861 if (pool[s]) x0[s] += excess[c] /
static_cast<double>(cnt);
1864 for (std::size_t k = 0; k < S.active.size(); ++k) {
1865 const std::size_t c = S.active[k];
1866 if (!con.
staged[c])
continue;
1868 for (std::size_t j = 0; j < nstg; ++j)
1869 if (stg.
gated_by[c][j]) mass += sg0[j];
1870 if (mass > 1e-8) S.armed[c] = 1;
1873 for (std::size_t c = 0; c < cons.
C.
rows(); ++c) {
1874 std::size_t best = n;
1875 double bestv = -1.0;
1876 for (std::size_t s = 0; s < n; ++s) {
1877 if (cons.
C(c, s) == 0.0)
continue;
1879 for (std::size_t d = 0; d < S.alg_row.size(); ++d)
1880 if (S.alg_row[d] == s) taken =
true;
1881 if (taken)
continue;
1882 if (x0[s] > bestv) { bestv = x0[s]; best = s; }
1885 throw NumericError(
"fluid_dae_integrate_hybrid: a chain has no free coordinate");
1886 S.alg_row.push_back(best);
1888 if (grid.empty())
throw InputError(
"fluid_dae_integrate_hybrid: the output grid is empty");
1891 std::vector<double> z(nz, 0.0);
1892 for (std::size_t s = 0; s < n; ++s) z[s] = x0[s];
1893 for (std::size_t j = 0; j < nstg; ++j) z[S.o_sg + j] = sg0[j];
1894 for (std::size_t c = 0; c < ncon; ++c) z[S.o_m + c] = m_init[c];
1897 const double tend = grid.back();
1898 const std::size_t seg_max = 4 * ncon + 8;
1899 for (std::size_t seg = 0; seg < seg_max; ++seg) {
1900 S.gated_rooms.assign(nstg, 0);
1901 for (std::size_t k = 0; k < S.active.size(); ++k)
1902 if (con.
staged[S.active[k]])
1903 for (std::size_t j = 0; j < nstg; ++j)
1904 if (stg.
gated_by[S.active[k]][j]) S.gated_rooms[j] = 1;
1908 detail::fluid_dae_active() = &S;
1910 const integer N =
static_cast<integer
>(nz);
1911 const std::size_t lwork = nz * (nz + 1 + nz + 14) + 20 + 32;
1912 const std::size_t liwork = nz + 20 + 32;
1913 std::vector<doublereal> y(z.begin(), z.end()), work(lwork, 0.0), rpar(1, 0.0);
1914 std::vector<integer> iwork(liwork, 0), ipar(1, 0);
1915 doublereal rtol = tol, atol = tol * 1e-2, x = tcur, xend = tend, h = 1e-6;
1919 integer itol = 0, ifcn = 0, ijac = 0, mljac = N, mujac = N, idfx = 0;
1920 integer imas = 1, mlmas = 0, mumas = 0, iout = 1, idid = 0;
1921 integer lw =
static_cast<integer
>(lwork), liw =
static_cast<integer
>(liwork);
1923 rodas_(
const_cast<integer*
>(&N), (U_fp)detail::fluid_dae_fcn, &ifcn, &x, y.data(),
1924 &xend, &h, &rtol, &atol, &itol,
1925 (U_fp)detail::fluid_dae_jac, &ijac, &mljac, &mujac,
1926 (U_fp)detail::fluid_dae_dfx, &idfx,
1927 (U_fp)detail::fluid_dae_mas, &imas, &mlmas, &mumas,
1928 (U_fp)detail::fluid_dae_solout, &iout,
1929 work.data(), &lw, iwork.data(), &liw, rpar.data(), ipar.data(), &idid);
1931 detail::fluid_dae_active() =
nullptr;
1934 detail::fluid_dae_active() =
nullptr;
1935 if (idid != 1 && idid != 2 && S.hit < 0)
1936 throw NumericError(
"fluid_dae_integrate_hybrid: RODAS returned idid=" +
1937 std::to_string(
static_cast<long>(idid)));
1938 if (S.hit < 0)
break;
1940 const std::size_t c =
static_cast<std::size_t
>(S.hit);
1943 const std::vector<double> xh(z.begin(), z.begin() + n);
1944 const std::vector<double> sgh(z.begin() + S.o_sg, z.begin() + S.o_sg + nstg);
1945 const bool was_active =
1946 std::find(S.active.begin(), S.active.end(), c) != S.active.end();
1948 S.active.erase(std::remove(S.active.begin(), S.active.end(), c), S.active.end());
1950 z[S.o_m + c] = S.inert[c];
1953 std::vector<std::size_t> trial = S.active;
1955 std::sort(trial.begin(), trial.end());
1956 std::vector<double> m0(trial.size(), 0.0);
1957 for (std::size_t a = 0; a < trial.size(); ++a)
1958 m0[a] = std::find(S.active.begin(), S.active.end(), trial[a]) != S.active.end()
1959 ? z[S.o_m + trial[a]]
1960 : (con.
staged[trial[a]] ? 0.0 : 1.0);
1963 terms, gates, stg, con, trial, xh, sgh, closure, m0, ok);
1965 for (std::size_t a = 0; a < trial.size(); ++a)
1966 if (!con.
staged[trial[a]] && mm[a] > 1.0 + 1e-9) feasible =
false;
1972 for (std::size_t a = 0; a < trial.size(); ++a) z[S.o_m + trial[a]] = mm[a];
1975 if (tcur >= tend - 1e-12)
break;
1980 while (S.out.size() < grid.size()) S.out.push_back(z);
1981 std::vector<std::vector<double> > out;
1982 for (std::size_t i = 0; i < S.out.size(); ++i)
1983 out.push_back(std::vector<double>(S.out[i].begin(), S.out[i].begin() + n));
1998 const std::vector<double>* rates =
nullptr) {
2000 const std::size_t K = M ? terms.
class_block[0].size() : 0;
2010 for (std::size_t i = 0; i < M; ++i)
2011 for (std::size_t k = 0; k < K; ++k) {
2012 const std::vector<std::size_t>& blk = terms.
class_block[i][k];
2013 if (blk.
empty())
continue;
2014 double q = 0.0, gg = 0.0;
2015 for (std::size_t s : blk) { q += x[s]; gg += g[s]; }
2019 for (std::size_t e = 0; e < r.size(); ++e)
2046 const FluidMomentTerms& terms,
2047 const FluidOptions&
opt) {
2048 std::vector<double> x0 =
opt.init_sol.empty()
2049 ? detail::fluid_default_initsol(
sn, terms.sys.layout)
2051 if (x0.size() != terms.nstate)
2052 throw InputError(
"solver_fluid_dae: the initial condition has " +
2053 std::to_string(x0.size()) +
" entries where the closure state has " +
2054 std::to_string(terms.nstate));
2073 const std::size_t n = terms.
nstate;
2075 const std::size_t K = M ? terms.
class_block[0].size() : 0;
2079 "solver_fluid_dae: the dae method solves a " + std::to_string(n) +
2080 "-unknown algebraic system with a finite-difference Jacobian, above the limit of " +
2082 " set by options.config.dae_maxstate. Raise it, or use options.method='minnormal' for "
2083 "the same closure by successive substitution.");
2088 for (std::size_t i = 0; i < M; ++i)
2092 "solver_fluid_dae: the dae method closes on the per-station variance only, and "
2093 "DPS/GPS close on the covariance between their class coordinates. Use "
2094 "options.method='minnormal'.");
2104 const std::size_t ncon = con.
b.size();
2109 for (std::size_t c = 0; c < cons.
C.
rows(); ++c)
2110 for (std::size_t e = 0; e < terms.
D.
cols(); ++e) {
2112 for (std::size_t s = 0; s < n; ++s) acc += cons.
C(c, s) * terms.
D(s, e);
2113 if (std::fabs(acc) > 1e-7)
2115 "solver_fluid_dae: the event set does not conserve a closed chain");
2119 sysd.
terms = &terms;
2123 sysd.
gates = &gates;
2133 mo.
timespan_end = std::numeric_limits<double>::infinity();
2135 std::vector<double> xcur(seed.
xvec.begin(), seed.
xvec.end());
2136 xcur.resize(n, 0.0);
2142 std::vector<double> s2seed(sysd.
cidx.size(), 0.0);
2143 for (std::size_t j = 0; j < sysd.
cidx.size(); ++j) {
2146 s2seed[j] = std::max(1e-8, acc);
2149 const double tol = (
opt.tol > 0.0 && std::isfinite(
opt.tol)) ?
opt.tol : 1e-8;
2151 std::vector<double> u, sgv(stg.
n, 0.0), mult;
2172 bool seeded_from_failure =
false;
2178 std::vector<double> x_ok = xcur, s2_ok = s2seed;
2179 bool have_best =
false, best_feasible =
false;
2180 std::vector<double> best_x, best_sg, best_s2, best_mult;
2181 std::vector<std::size_t> best_active;
2183 bool clamped =
false, best_clamped =
false;
2184 const std::size_t aset_max = std::max<std::size_t>(4, 2 * ncon + 2);
2185 for (std::size_t aset = 0; aset < aset_max; ++aset) {
2186 bool has_clamp =
false;
2187 for (std::size_t k = 0; k < sysd.
active.size(); ++k)
2193 std::vector<double> sg0(stg.
n, 0.0);
2194 for (std::size_t k = 0; k < sysd.
active.size(); ++k) {
2195 const std::size_t c = sysd.
active[k];
2196 if (!con.
staged[c])
continue;
2198 for (std::size_t sIdx = 0; sIdx < n; ++sIdx) cur += con.
A(c, sIdx) * xcur[sIdx];
2199 const double excess = std::max(0.0, cur - con.
b[c]);
2200 std::size_t cnt = 0;
2201 for (std::size_t j = 0; j < stg.
n; ++j)
if (stg.
gated_by[c][j]) ++cnt;
2203 for (std::size_t j = 0; j < stg.
n; ++j)
2204 if (stg.
gated_by[c][j]) sg0[j] = excess /
static_cast<double>(cnt);
2206 std::vector<double> u0;
2207 u0.assign(xcur.begin(), xcur.end());
2208 u0.insert(u0.end(), sg0.begin(), sg0.end());
2209 u0.insert(u0.end(), s2seed.begin(), s2seed.end());
2210 u0.insert(u0.end(), sysd.
active.size(), 1.0);
2222 bool solved =
false;
2230 bool nohyp_typed =
false;
2231 std::string nohyp_what;
2232 for (
int attempt = 0; attempt < (has_clamp ? 2 : 1); ++attempt) {
2239 if (attempt == 1 || !has_clamp) {
2242 nohyp_what = e.what();
2246 }
catch (
const std::exception& e) {
2247 if (attempt == 1 || !has_clamp) {
2249 nohyp_what = e.what();
2254 clamped = attempt == 1;
2265 std::vector<std::size_t> cand;
2266 for (std::size_t c = 0; c < ncon; ++c) {
2271 for (std::size_t s = 0; s < n; ++s) {
2272 if (con.
A(c, s) > 0.0 && !std::isfinite(xcur[s])) bad =
true;
2273 acc += con.
A(c, s) * xcur[s];
2275 if (bad || acc > con.
b[c] + std::max(1e-9, tol)) cand.push_back(c);
2277 if (seeded_from_failure || cand.empty()) {
2281 for (std::size_t ci = 0; ci < cand.size(); ++ci) {
2282 const std::size_t c = cand[ci];
2284 std::size_t cnt = 0;
2285 for (std::size_t s = 0; s < n; ++s) {
2286 val += con.
A(c, s) * xcur[s];
2287 if (con.
A(c, s) > 0.0) ++cnt;
2289 if (!cnt || !(con.
b[c] > 0.0))
continue;
2290 if (std::isfinite(val) && val > con.
b[c]) {
2291 const double f = con.
b[c] / val;
2292 for (std::size_t s = 0; s < n; ++s)
2293 if (con.
A(c, s) > 0.0) xcur[s] *= f;
2294 }
else if (!std::isfinite(val)) {
2295 for (std::size_t s = 0; s < n; ++s)
2296 if (con.
A(c, s) > 0.0) xcur[s] = con.
b[c] /
static_cast<double>(cnt);
2299 for (std::size_t s = 0; s < n; ++s)
2300 if (!std::isfinite(xcur[s])) xcur[s] = 0.0;
2301 for (std::size_t ci = 0; ci < cand.size(); ++ci) sysd.
active.push_back(cand[ci]);
2305 seeded_from_failure =
true;
2308 if (!solved)
throw NumericError(
"solver_fluid_dae: the closure could not be evaluated");
2309 xcur.assign(u.begin(), u.begin() + n);
2310 sgv.assign(u.begin() + n, u.begin() + n + stg.
n);
2311 for (std::size_t j = 0; j < sysd.
cidx.size(); ++j)
2312 s2seed[j] = std::max(0.0, u[n + stg.
n + j]);
2313 mult.assign(u.begin() + n + stg.
n + sysd.
cidx.size(), u.end());
2314 if (ncon == 0)
break;
2316 std::vector<double> slack(ncon, 0.0);
2317 bool feasible =
true;
2318 for (std::size_t c = 0; c < ncon; ++c) {
2320 for (std::size_t sIdx = 0; sIdx < n; ++sIdx) acc += con.
A(c, sIdx) * xcur[sIdx];
2321 for (std::size_t j = 0; j < stg.
n; ++j) acc += con.
As(c, j) * sgv[j];
2322 slack[c] = con.
b[c] - acc;
2323 if (slack[c] < -std::max(1e-9, tol)) feasible =
false;
2329 best_x = xcur; best_sg = sgv; best_s2 = s2seed; best_mult = mult;
2330 best_active = sysd.
active; best_info = info; best_clamped = clamped;
2331 best_feasible = feasible;
2333 std::vector<std::size_t> violated;
2334 for (std::size_t c = 0; c < ncon; ++c) {
2335 if (std::find(sysd.
active.begin(), sysd.
active.end(), c) != sysd.
active.end())
continue;
2336 if (slack[c] < -std::max(1e-9, tol)) violated.push_back(c);
2343 std::vector<std::size_t> released;
2344 for (std::size_t k = 0; k < sysd.
active.size(); ++k) {
2345 const std::size_t c = sysd.
active[k];
2348 std::size_t rooms = 0;
2349 for (std::size_t j = 0; j < stg.
n; ++j)
2350 if (stg.
gated_by[c][j]) { held += sgv[j]; ++rooms; }
2351 if (!rooms || held < 1e-9) released.push_back(c);
2352 }
else if (mult[k] > 1.0 + std::max(1e-9, tol)) {
2353 released.push_back(c);
2363 if (released.empty())
break;
2365 if (violated.empty() && released.empty())
break;
2366 std::vector<std::size_t> next;
2367 for (std::size_t a : sysd.
active)
2368 if (std::find(released.begin(), released.end(), a) == released.end()) next.push_back(a);
2369 for (std::size_t v : violated) next.push_back(v);
2370 std::sort(next.begin(), next.end());
2371 next.erase(std::unique(next.begin(), next.end()), next.end());
2378 xcur = best_x; sgv = best_sg; s2seed = best_s2; mult = best_mult;
2379 sysd.
active = best_active; info = best_info; clamped = best_clamped;
2386 "solver_fluid_dae: no fixed point of the closure satisfies every cap. The closure "
2387 "wants more jobs there than the cap allows and no admission multiplier holds it. "
2388 "Use SolverCTMC, SolverJMT, SolverSSA or SolverLDES for this model.");
2393 std::vector<double> x = xcur;
2395 cl.
sigma2.assign(M, 0.0);
2397 for (std::size_t j = 0; j < sysd.
cidx.size(); ++j)
2398 cl.
sigma2[sysd.
cidx[j]] = std::max(0.0, u[n + stg.
n + j]);
2411 if (std::isfinite(
opt.timespan_end) &&
opt.timespan_end > 0.0) {
2412 const std::vector<double> x0 = detail::fluid_dae_init_state(
sn, terms,
opt);
2413 const std::vector<double> grid(1,
opt.timespan_end);
2422 const std::vector<double> zend =
2427 x.assign(zend.begin(), zend.begin() + n);
2437 const std::vector<double> r = lgf.
rin;
2458 if (!sysd.
active.empty()) {
2459 for (std::size_t i = 0; i < M; ++i) {
2461 for (std::size_t k = 0; k < K; ++k) {
2464 if (std::isfinite(mu) && mu > 0.0)
2465 out.
UN(i, k) = out.
TN(i, k) / (mu * terms.
S[i]);
2469 out.
CN.assign(K, 0.0);
2470 out.
XN.assign(K, 0.0);
2471 for (std::size_t k = 0; k < K; ++k) {
2472 const std::size_t rs = (k <
sn.classes.size()) ?
sn.classes[k].refstat : 0;
2473 if (rs >= 1 && rs <= M) out.
XN[k] = out.
TN(rs - 1, k);
2475 for (std::size_t i = 0; i < M; ++i) q += out.
QN(i, k);
2476 if (out.
XN[k] > 0.0) out.
CN[k] = q / out.
XN[k];
2490 for (std::size_t i = 0; i < M; ++i)
2491 for (std::size_t k = 0; k < K; ++k) {
2492 const std::vector<std::size_t>& blk = terms.
class_block[i][k];
2494 for (std::size_t a = 0; a < blk.
size(); ++a)
2495 for (std::size_t b = 0; b < blk.
size(); ++b) acc += Sigma(blk[a], blk[b]);
2529 std::size_t points = 101,
const std::vector<double>& out_grid = std::vector<double>(),
2531 if (!(t_end > 0.0))
throw InputError(
"solver_fluid_dae_transient: t_end must be positive");
2532 if (points < 2)
throw InputError(
"solver_fluid_dae_transient: need at least two output points");
2536 os.
timespan_end = std::numeric_limits<double>::infinity();
2558 const std::size_t
nc = terms.
cov_idx.size();
2559 const bool withcov =
nc > 0 &&
nc <= dopt.
maxcov;
2561 std::vector<double> grid = out_grid;
2563 grid.resize(points);
2564 for (std::size_t j = 0; j < points; ++j)
2565 grid[j] = t_end *
static_cast<double>(j) /
static_cast<double>(points - 1);
2568 const bool has_zero = !grid.empty() && grid.front() <= 0.0;
2569 std::vector<double> inner(grid.begin() + (has_zero ? 1 : 0), grid.end());
2571 const double tol = (
opt.tol > 0.0 && std::isfinite(
opt.tol)) ?
opt.tol : 1e-8;
2572 const std::vector<double> x0 = detail::fluid_dae_init_state(
sn, terms,
opt);
2573 const std::size_t nz = terms.
nstate + (withcov ?
nc *
nc : 0);
2574 std::vector<std::vector<double> > xs;
2576 std::vector<double> z0(nz, 0.0);
2577 for (std::size_t i = 0; i < terms.
nstate; ++i) z0[i] = x0[i];
2580 if (!inner.empty()) {
2586 const std::vector<std::vector<double> > got =
2591 xs.insert(xs.end(), got.begin(), got.end());
2594 std::vector<FluidTranPoint> out;
2595 out.reserve(xs.size());
2597 const std::size_t K = M ? terms.
class_block[0].size() : 0;
2598 for (std::size_t j = 0; j < xs.size(); ++j) {
2599 std::vector<double> x(xs[j].begin(), xs[j].begin() + terms.
nstate);
2604 if (v < 0.0) v = 0.0;
2611 for (std::size_t a = 0; a <
nc; ++a)
2612 for (std::size_t b = 0; b <
nc; ++b)
2614 0.5 * (xs[j][terms.
nstate + a *
nc + b]
2615 + xs[j][terms.
nstate + b *
nc + a]);
2616 cl.
sigma2.assign(M, 0.0);
2619 for (std::size_t i = 0; i < s2.size(); ++i) cl.
sigma2[i] = s2[i];
2625 detail::fluid_snap_all(pt.
QN, pt.
UN, R, pt.
TN);
2628 for (std::size_t i = 0; i < M; ++i)
2629 for (std::size_t k = 0; k < K; ++k) {
2630 const std::vector<std::size_t>& blk = terms.
class_block[i][k];
2632 for (std::size_t a = 0; a < blk.
size(); ++a)
2633 for (std::size_t b = 0; b < blk.
size(); ++b)
2634 acc += Sigma(blk[a], blk[b]);
2635 pt.
QVar(i, k) = std::max(0.0, acc);
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
Raised when the moment closure cannot serve this model: the linearization at the fixed point is not h...
FluidNonHyperbolicError(const std::string &what)
A network plus its refreshed NetworkStruct.
The exception types the port throws.
The second-order fluid methods: fluid_moment_terms.m, fluid_lyapunov.m, fluid_drift_jacobian....
The one exception the fluid fallback ladder catches.
Least squares for a rectangular system, exact-capable.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
std::vector< double > fluid_dae_hold_multipliers(const FluidMomentTerms &terms, const FluidDaeGates &gates, const FluidDaeStaging &stg, const FluidDaeConstraints &con, const std::vector< std::size_t > &active, const std::vector< double > &x, const std::vector< double > &sg, const FluidClosure &cl, const std::vector< double > &m0, bool &ok)
The multipliers that hold the active caps at this state, by small Newton.
double fluid_dae_reach(const std::vector< double > &row, const std::vector< std::size_t > &coord_class, const std::vector< double > &njobs, std::size_t K)
The largest row x the population can produce, ignoring the coupling.
Matrix< double > fluid_dae_clamp_tangent(const FluidDaeConstraints &con, const std::vector< std::size_t > &active, const FluidMomentTerms &t)
Orthogonal projector onto the subspace the CLAMPING caps leave free: I - R'(RR')^-1 R over the covari...
Matrix< double > fluid_drift_jacobian(const FluidMomentTerms &t, const std::vector< double > &x, const FluidClosure &cl)
Port of fluid_drift_jacobian.m: the analytic Jacobian of the fluid drift.
FluidSolution solver_fluid_dae(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, const FluidDaeOptions &dopt_in=FluidDaeOptions())
solver_fluid_dae.m: the min-normal closure solved as one system.
FluidDaeConstraints fluid_dae_constraints(const qn::NetworkStruct< T > &sn, const FluidMomentTerms &terms)
std::vector< std::vector< double > > fluid_dae_integrate_hybrid(const FluidMomentTerms &terms, const FluidDaeConservation &cons, const FluidDaeConstraints &con, const FluidDaeGates &gates, const FluidDaeStaging &stg, const FluidClosure &closure, const std::vector< double > &x0In, const std::vector< double > &grid, double tol, std::vector< FluidDaeSwitch > *switches=nullptr)
The transient UNDER CAPS: one index-1 DAE per segment, restarted at every located crossing.
std::vector< std::size_t > fluid_dae_closable(const FluidMomentTerms &t)
Stations whose variance enters the drift.
FluidDaeOptions fluid_dae_options(const FluidOptions &opt, const FluidDaeOptions &dopt)
The controls the DAE route actually reads: the struct a caller pinned, with whatever options....
std::vector< double > fluid_moment_drift(const FluidMomentTerms &t, const std::vector< double > &x, const FluidClosure &cl)
The drift F(x) = D r(x) under a closure: terms.driftFcn.
std::vector< double > fluid_dae_sigma_from(const Matrix< double > &Sigma, const FluidMomentTerms &t, const std::vector< std::size_t > &cidx)
Project a state-level covariance onto the per-station variances the drift reads.
std::vector< double > fluid_moment_rates(const FluidMomentTerms &t, const std::vector< double > &x, const FluidClosure &cl)
The event rates r(x) under a closure: terms.ratesFcn.
FluidDaeLegs fluid_dae_legs(const FluidMomentTerms &t, const FluidDaeGates &gates, const FluidDaeStaging &stg, const FluidDaeConstraints &con, const std::vector< std::size_t > &active, const std::vector< double > &sg, const std::vector< double > &r, const std::vector< double > &mult, bool staged_flow)
Shared by the steady-state residual and the transient right-hand side so that the two solve the SAME ...
std::vector< std::vector< double > > fluid_dae_integrate(const FluidMomentTerms &terms, const FluidDaeConservation &cons, const FluidClosure &closure, const std::vector< double > &x0, const std::vector< double > &grid, double tol, bool withcov=false, const std::vector< std::size_t > &closable=std::vector< std::size_t >())
Integrate the closure as an index-1 DAE, with RODAS, and report the state at every point of grid.
FluidMomentTerms fluid_moment_terms(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
FluidDaeGates fluid_dae_gates(const qn::NetworkStruct< T > &sn, const FluidMomentTerms &t, const FluidDaeConstraints &con)
void fluid_dae_project(std::vector< double > &u, std::size_t nfree)
Onto the feasible box: the state is free, the variances are not.
void fluid_dae_extend(FluidDaeConstraints &con, const FluidDaeStaging &stg, const FluidMomentTerms &t)
Extend every cap to the staging coordinates that hold mass INSIDE it.
FluidDaeStaging fluid_dae_staging(const FluidMomentTerms &t, const FluidDaeConstraints &con)
std::vector< double > fluid_moment_factors(const FluidMomentTerms &t, const std::vector< double > &x, const FluidClosure &cl)
The rate factors g(x) under a closure: terms.factorFcn.
bool fluid_dae_residual(const FluidDaeSystem &sysd, const std::vector< double > &u, std::vector< double > &G, std::vector< double > *rates_out, bool rethrow=false, std::vector< double > *fire_out=nullptr)
The coupled algebraic system, stacked: drift, conservation, closure consistency.
void fluid_dae_metrics(const FluidMomentTerms &terms, const std::vector< double > &x, const FluidClosure &cl, Matrix< double > &QN, Matrix< double > &UN, Matrix< double > &RN, Matrix< double > &TN, const std::vector< double > *rates=nullptr)
The metrics of one state, read exactly as the steady-state table reads them.
std::vector< FluidTranPoint > solver_fluid_dae_transient(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, double t_end, std::size_t points=101, const std::vector< double > &out_grid=std::vector< double >(), const FluidDaeOptions &dopt_in=FluidDaeOptions())
@@SolverFLD/getTranAvg for the DAE route: the metrics ALONG the trajectory.
bool fluid_coord_eliminated(const FluidMomentTerms &t, std::size_t s)
True when the immediate reduction folded coordinate s away, so the reduced drift holds no mass there ...
Matrix< double > fluid_moment_lyapunov(const FluidMomentTerms &t, const Matrix< double > &A, const std::vector< double > &r, const Matrix< double > *clampT=nullptr)
local_lyapunov of solver_fluid_moments.m: the covariance on the coordinates that carry a real populat...
FluidDaeConservation fluid_dae_conservation(const qn::NetworkStruct< T > &sn, const FluidMomentTerms &terms, const FluidDaeStaging &stg)
The conserved chains as equations.
FluidDaeNewtonInfo fluid_dae_newton(const FluidDaeSystem &sysd, std::vector< double > &u, double tol, std::size_t maxit)
Damped PROJECTED Newton with a finite-difference Jacobian and an Armijo backtrack on the residual nor...
DropStrategy
Blocking and loss rules, with the values of MATLAB DropStrategy.
LstsqResult< T > lstsq(const Matrix< T > &A, const std::vector< T > &b, const T &tol)
Least-squares solution of A x = b, minimum-norm when A is rank deficient.
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
The second moment the drift closes its non-linear terms with, i.e.
std::vector< Matrix< double > > cov
per station, 0x0 keeps the plug-in share
std::vector< double > sigma2
per station; empty selects first order
Population conservation, one row per CLOSED chain, in state space.
std::vector< double > N
chain populations
Matrix< double > C
(nchain x nstate)
Finite capacity regions as linear admission constraints on the fluid state.
std::vector< bool > staged
true where the job waits in a room
std::vector< std::size_t > klass_row
which class, npos when several
Matrix< double > As
(ncon x nstaging), see fluid_dae_extend
std::vector< std::string > label
std::vector< std::size_t > region
which region, npos for a station cap
Matrix< double > A
(ncon x nstate)
std::vector< std::vector< bool > > member
(nregions x nstate)
std::vector< std::size_t > station
which station, npos for a region cap
std::vector< std::size_t > coord_class
static std::size_t none()
Which events each cap throttles, and what happens to the mass it stops.
std::vector< std::vector< bool > > gate
(ncon x nevents)
std::vector< std::vector< bool > > loss
Matrix< double > Dp
the jump matrix split in three
std::vector< std::vector< bool > > held
The two legs of every event under the active caps, and the waiting rooms.
std::vector< double > ds
the derivative of each room
std::vector< double > rup
the rate each event FIRES at
std::vector< double > drain
each room's total outflow
std::vector< double > rin
the rate mass LANDS at
What the Newton reports about where it stopped.
Options that only the DAE route reads.
std::size_t maxcov
Largest covariance dimension integrated ALONGSIDE the mean on the transient.
std::size_t maxstate
The simultaneous solve carries one Lyapunov solve per residual evaluation and takes a finite-differen...
The waiting room outside a capped region, as fluid coordinates.
std::vector< std::size_t > adm_region
std::vector< std::size_t > adm_stage
std::vector< std::size_t > region
std::vector< std::size_t > klass
per staging coordinate
std::vector< bool > adm
per event: an admission?
std::vector< std::vector< bool > > gated_by
(ncon x n) which rows gate which room
Matrix< double > Dp
negative and positive parts of D
What a hybrid transient did, beside the trajectory.
int kind
0 release, 1 activate, 2 a crossing the cap could not hold
Everything the residual needs, gathered so the Newton can stay generic.
std::vector< std::size_t > active
binding capacity constraints
std::size_t nstaging() const
u = [x; staging; sigma2; mult]
const FluidDaeConservation * cons
const FluidDaeStaging * stg
const FluidMomentTerms * terms
Matrix< double > clampT
The tangent space of the caps that CLAMP, or an empty matrix.
const FluidDaeConstraints * con
const FluidDaeGates * gates
std::vector< std::size_t > cidx
closable stations
std::vector< std::vector< std::vector< std::size_t > > > class_block
state coordinates of each (station,class): Sigma is indexed by SERVICE PHASE, so reading a per-class ...
Matrix< double > Sigma
state-level covariance, on range(D)
Matrix< double > QStd
per station and class queue-length variance
std::vector< double > sigma2
per-station population variance
Port of fluid_moment_terms.m: the event representation of the fluid population process,...
std::vector< bool > ev_is_departure
the leading n_departures events
std::vector< bool > is_ext
std::vector< double > S
servers, INF substituted, lld peak folded
std::vector< std::vector< std::vector< std::size_t > > > class_block
std::vector< std::size_t > cov_idx
coordinates carrying a real population
std::vector< bool > min_exact
stations whose occupancy cannot reach their server count, where min(n,c) is the identity and the clos...
std::vector< std::vector< std::size_t > > station_block
std::vector< std::size_t > ev_class
0-based, from the event's coordinate
Matrix< double > D
(nstate x nevents)
std::vector< std::size_t > ev_station
std::vector< lang::SchedStrategy > sched
per station
Controls, defaulting to SolverOptions('Fluid') in the reference.
FluidClosure closure
options.config.moment_sigma2 and options.config.moment_cov: the second moment the drift's non-linear ...
What the analyzer returns, in the same shape as the MVA solver's result.
bool has_moments
result.solverSpecific.moments: set only by minnormal and refined.
FluidMomentReport moments
std::vector< double > xvec
the converged fluid state
One point of a transient trajectory: the metrics at time t.
Matrix< double > QVar
Per-(station,class) queue-length VARIANCE at this instant, empty where the method carries no second m...
static constexpr double Zero
FINITE CAPACITY REGIONS, MATLAB's refreshRegions output.
std::vector< T > lincon_b
Matrix< T > lincon_A
optional linear constraint A n <= b
std::vector< std::vector< double > > cap
(nstations x nclasses+1), -1 = unbounded
std::vector< double > maxmem
per member station, -1 = unbounded
std::vector< DropStrategy > rule
per class
std::vector< T > size
per class; size is the memory footprint
std::vector< bool > members
membership, independent of the caps