5#ifndef LINE_SOLVERS_FLUID_FLUID_DAE_H
6#define LINE_SOLVERS_FLUID_FLUID_DAE_H
115 std::vector<double>
b;
125 static std::size_t
none() {
return static_cast<std::size_t
>(-1); }
138 const std::vector<std::size_t>& coord_class,
139 const std::vector<double>& njobs, std::size_t K) {
141 for (std::size_t k = 0; k < K; ++k) {
144 for (std::size_t s = 0; s < row.size(); ++s)
145 if (coord_class[s] == k) { w = std::max(w, row[s]); any =
true; }
146 if (!any || w <= 0.0)
continue;
147 const double nk = (k < njobs.size()) ? njobs[k] : std::numeric_limits<double>::infinity();
148 if (!std::isfinite(nk))
return std::numeric_limits<double>::infinity();
158 const std::size_t nstate = terms.
nstate;
160 const std::size_t K = M ? terms.
class_block[0].size() : 0;
164 std::vector<std::size_t> coord_station(nstate, NONE);
165 for (std::size_t i = 0; i < M; ++i)
166 for (std::size_t k = 0; k < K; ++k)
169 coord_station[s] = i;
171 con.
member.assign(std::max<std::size_t>(con.
nregions, 1), std::vector<bool>(nstate,
false));
173 const double UNB = -1.0;
174 std::vector<std::vector<double> > rows;
175 std::vector<std::size_t> rgs, sts, kls;
176 std::vector<bool> sdg;
177 for (std::size_t f = 0; f < con.
nregions; ++f) {
179 std::vector<bool> inR(nstate,
false);
180 bool any_member =
false;
181 for (std::size_t s = 0; s < nstate; ++s) {
182 const std::size_t i = coord_station[s];
183 if (i != NONE && i < R.
members.size() && R.
members[i]) { inR[s] =
true; any_member =
true; }
185 if (!any_member)
continue;
188 for (std::size_t k = 0; k < K && k < R.
rule.size(); ++k)
191 "fluid_dae_constraints: region " + std::to_string(f + 1) +
192 " applies a drop rule other than a waiting queue to class " +
193 std::to_string(k + 1) +
194 ". Only a waiting queue conserves the population and throttles the admission "
195 "flow, which is what an algebraic equation on this drift can express.");
196 for (std::size_t k = 0; k < K && k < R.
weight.size(); ++k)
197 if (std::fabs(
static_cast<double>(R.
weight[k]) - 1.0) > 1e-12)
199 "fluid_dae_constraints: region " + std::to_string(f + 1) +
200 " sets per-class admission weights, which decide WHICH blocked class enters "
201 "when capacity frees up. The throttle carries no such priority and would "
202 "ignore them silently.");
204 std::size_t member_row = 0;
205 for (std::size_t i = 0; i < R.
members.size(); ++i)
206 if (R.
members[i]) { member_row = i;
break; }
209 if (member_row < R.
cap.size() && R.
cap[member_row].size() > K) {
210 const double g =
static_cast<double>(R.
cap[member_row][K]);
211 if (std::isfinite(g) && g != UNB && g >= 0.0) {
212 std::vector<double> row(nstate, 0.0);
213 for (std::size_t s = 0; s < nstate; ++s)
if (inR[s]) row[s] = 1.0;
214 rows.push_back(row); con.
b.push_back(g);
215 rgs.push_back(f); sts.push_back(NONE); kls.push_back(NONE); sdg.push_back(
true);
216 con.
label.push_back(
"region " + std::to_string(f + 1) +
" global job cap");
220 if (member_row < R.
cap.size())
221 for (std::size_t k = 0; k < K && k < R.
cap[member_row].size(); ++k) {
222 const double c =
static_cast<double>(R.
cap[member_row][k]);
223 if (!std::isfinite(c) || c == UNB || c < 0.0)
continue;
224 std::vector<double> row(nstate, 0.0);
226 for (std::size_t s = 0; s < nstate; ++s)
227 if (inR[s] && con.
coord_class[s] == k) { row[s] = 1.0; any =
true; }
229 rows.push_back(row); con.
b.push_back(c);
230 rgs.push_back(f); sts.push_back(NONE); kls.push_back(k); sdg.push_back(
true);
231 con.
label.push_back(
"region " + std::to_string(f + 1) +
" class " +
232 std::to_string(k + 1) +
" job cap");
235 if (member_row < R.
maxmem.size()) {
236 const double mem =
static_cast<double>(R.
maxmem[member_row]);
237 if (std::isfinite(mem) && mem != UNB && mem >= 0.0) {
238 std::vector<double> row(nstate, 0.0);
240 for (std::size_t s = 0; s < nstate; ++s) {
241 if (!inR[s])
continue;
243 const double w = (k < R.
size.size()) ?
static_cast<double>(R.
size[k]) : 1.0;
244 if (w != 0.0) { row[s] = w; any =
true; }
247 rows.push_back(row); con.
b.push_back(mem);
248 rgs.push_back(f); sts.push_back(NONE); kls.push_back(NONE); sdg.push_back(
true);
249 con.
label.push_back(
"region " + std::to_string(f + 1) +
" memory budget");
254 for (std::size_t c = 0; c < R.
lincon_A.rows(); ++c) {
255 std::vector<double> row(nstate, 0.0);
257 for (std::size_t k = 0; k < K && k < R.
lincon_A.cols(); ++k) {
258 const double a =
static_cast<double>(R.
lincon_A(c, k));
259 if (a == 0.0)
continue;
260 for (std::size_t s = 0; s < nstate; ++s)
261 if (inR[s] && con.
coord_class[s] == k) { row[s] = a; any =
true; }
265 con.
b.push_back(
static_cast<double>(R.
lincon_b[c]));
266 rgs.push_back(f); sts.push_back(NONE); kls.push_back(NONE); sdg.push_back(
true);
267 con.
label.push_back(
"region " + std::to_string(f + 1) +
" linear constraint " +
268 std::to_string(c + 1));
277 for (std::size_t i = 0; i < M; ++i) {
278 if (terms.
is_ext[i])
continue;
280 for (std::size_t s = 0; s < nstate; ++s) any_at |= coord_station[s] == i;
281 if (!any_at)
continue;
282 if (i <
sn.cap.size()) {
283 const double g =
static_cast<double>(
sn.cap[i]);
284 if (std::isfinite(g) && g >= 0.0) {
285 std::vector<double> row(nstate, 0.0);
286 for (std::size_t s = 0; s < nstate; ++s)
if (coord_station[s] == i) row[s] = 1.0;
287 rows.push_back(row); con.
b.push_back(g);
288 rgs.push_back(NONE); sts.push_back(i); kls.push_back(NONE); sdg.push_back(
false);
289 con.
label.push_back(
"station " + std::to_string(i + 1) +
" buffer");
292 if (i <
sn.classcap.size())
293 for (std::size_t k = 0; k < K && k <
sn.classcap[i].size(); ++k) {
294 const double c =
static_cast<double>(
sn.classcap[i][k]);
295 if (!std::isfinite(c) || c < 0.0)
continue;
296 std::vector<double> row(nstate, 0.0);
298 for (std::size_t s = 0; s < nstate; ++s)
299 if (coord_station[s] == i && con.
coord_class[s] == k) { row[s] = 1.0; any =
true; }
301 rows.push_back(row); con.
b.push_back(c);
302 rgs.push_back(NONE); sts.push_back(i); kls.push_back(k); sdg.push_back(
false);
303 con.
label.push_back(
"station " + std::to_string(i + 1) +
" class " +
304 std::to_string(k + 1) +
" buffer");
308 const std::size_t ncand = rows.size();
309 std::vector<bool> keep(ncand,
true);
310 const std::vector<double> njobs =
sn.njobs();
315 for (std::size_t c = 0; c < ncand; ++c)
328 for (std::size_t c = 0; c < ncand; ++c) {
329 if (!keep[c])
continue;
331 for (std::size_t s = 0; s < nstate && !live; ++s)
333 if (!live) keep[c] =
false;
341 for (std::size_t c = 0; c < ncand; ++c) {
342 if (!keep[c])
continue;
343 for (std::size_t d = c + 1; d < ncand; ++d) {
344 if (!keep[d])
continue;
346 for (std::size_t s = 0; s < nstate && same; ++s)
347 same = std::fabs(rows[c][s] - rows[d][s]) <= 1e-14;
349 const bool takeover = con.
b[d] < con.
b[c] - 1e-14 ||
350 (std::fabs(con.
b[d] - con.
b[c]) <= 1e-14 && sdg[d] && !sdg[c]);
352 con.
b[c] = con.
b[d]; rgs[c] = rgs[d]; sts[c] = sts[d]; kls[c] = kls[d];
364 for (std::size_t c = 0; c < ncand; ++c) {
365 if (!keep[c] || kls[c] != NONE)
continue;
366 std::vector<std::size_t> classes;
367 for (std::size_t k = 0; k < K; ++k)
368 for (std::size_t s = 0; s < nstate; ++s)
369 if (con.
coord_class[s] == k && coord_station[s] != NONE && rows[c][s] > 0.0) {
370 classes.push_back(k);
373 if (classes.empty())
continue;
376 for (std::size_t ci = 0; ci < classes.size() && implied; ++ci) {
377 const std::size_t k = classes[ci];
378 std::size_t part = NONE;
379 for (std::size_t d = 0; d < ncand; ++d) {
380 if (!keep[d] || d == c || kls[d] != k || rgs[d] != rgs[c] || sts[d] != sts[c])
383 for (std::size_t s = 0; s < nstate && covers; ++s)
384 if (con.
coord_class[s] == k && coord_station[s] != NONE &&
385 rows[c][s] > 0.0 && rows[d][s] < rows[c][s] - 1e-14)
387 if (covers) { part = d;
break; }
389 if (part == NONE) implied =
false;
390 else budget += con.
b[part];
392 if (implied && budget <= con.
b[c] + 1e-14) keep[c] =
false;
395 std::vector<std::vector<double> > kept_rows;
396 std::vector<double> kept_b;
397 std::vector<std::string> kept_lab;
398 for (std::size_t c = 0; c < ncand; ++c) {
399 if (!keep[c])
continue;
400 kept_rows.push_back(rows[c]);
401 kept_b.push_back(con.
b[c]);
402 kept_lab.push_back(con.
label[c]);
403 con.
region.push_back(rgs[c]);
406 con.
staged.push_back(sdg[c]);
409 con.
label = kept_lab;
411 for (std::size_t r = 0; r < kept_rows.size(); ++r)
412 for (std::size_t s = 0; s < nstate; ++s) con.
A(r, s) = kept_rows[r][s];
422 for (std::size_t c = 0; c < con.
b.size(); ++c) {
423 const std::size_t i = con.
station[c];
424 if (i == NONE || i >=
sn.droprule.size())
continue;
425 for (std::size_t k = 0; k < K && k <
sn.droprule[i].size(); ++k) {
427 for (std::size_t s = 0; s < nstate; ++s)
428 if (coord_station[s] == i && con.
coord_class[s] == k && con.
A(c, s) > 0.0)
430 if (!weighs)
continue;
434 "fluid_dae_constraints: station " + std::to_string(i + 1) +
435 " applies a blocking or retrial rule to class " + std::to_string(k + 1) +
436 ", and its buffer binds. Only a waiting queue or a drop is a constraint on "
437 "this drift: BAS/BBS/RSRD add a blocked-server state to the upstream station "
438 "and the retrial rules add an orbit, so each needs a different drift rather "
439 "than an algebraic equation on this one.");
459 std::vector<std::vector<bool> >
gate;
460 std::vector<std::vector<bool> >
held;
461 std::vector<std::vector<bool> >
loss;
470 const std::size_t ncon = con.
b.size(), nev = t.
D.
cols(), nstate = t.
nstate;
471 g.
gate.assign(ncon, std::vector<bool>(nev,
false));
472 g.
held.assign(ncon, std::vector<bool>(nev,
false));
473 g.
loss.assign(ncon, std::vector<bool>(nev,
false));
474 if (ncon == 0)
return g;
479 std::vector<bool> ext_coord(nstate,
false);
482 for (std::size_t s : t.
station_block[i]) ext_coord[s] =
true;
483 for (std::size_t s = 0; s < nstate; ++s)
484 for (std::size_t e = 0; e < nev; ++e) {
485 const double d = t.
D(s, e);
489 if (d < 0.0) {
if (ext_coord[s]) g.
DnExt(s, e) = d;
else g.
Dn(s, e) = d; }
490 if (d > 0.0) g.
Dp(s, e) = d;
492 const double tol = 1e-7;
493 const std::vector<double> njobs =
sn.njobs();
494 for (std::size_t c = 0; c < ncon; ++c) {
496 for (std::size_t e = 0; e < nev; ++e) {
498 for (std::size_t s = 0; s < nstate; ++s) delta += con.
A(c, s) * t.
D(s, e);
499 if (delta <= tol)
continue;
502 if (con.
staged[c])
continue;
506 std::size_t k =
static_cast<std::size_t
>(-1);
507 for (std::size_t s = 0; s < nstate; ++s)
508 if (g.
Dp(s, e) > tol && con.
A(c, s) > 0.0) { k = con.
coord_class[s];
break; }
509 const bool isopen = k !=
static_cast<std::size_t
>(-1) && k < njobs.size() &&
510 !std::isfinite(njobs[k]);
511 g.
loss[c][e] = isopen;
512 g.
held[c][e] = !isopen;
516 "fluid_dae_gates: no event increases " + con.
label[c] +
517 ", so the cap can never be approached and there is no admission flow for the "
518 "constraint to throttle. This is a malformed limit rather than a solvable one.");
557 const std::size_t nev = t.
D.
cols();
558 const std::size_t ncon = con.
b.size();
559 stg.
adm.assign(nev,
false);
562 stg.
gated_by.assign(ncon, std::vector<bool>());
563 bool any_staged =
false;
564 for (std::size_t c = 0; c < ncon; ++c) any_staged = any_staged || con.
staged[c];
568 if (con.
empty() || con.
nregions == 0 || !any_staged)
return stg;
570 const double tol = 1e-9;
573 for (std::size_t s = 0; s < t.
nstate; ++s)
574 for (std::size_t e = 0; e < nev; ++e) {
575 const double d = t.
D(s, e);
576 if (d < 0.0) stg.
Dn(s, e) = d;
577 if (d > 0.0) stg.
Dp(s, e) = d;
580 std::vector<bool> staged_region(con.
nregions,
false);
581 for (std::size_t c = 0; c < ncon; ++c)
585 std::vector<std::vector<std::size_t> > idx(con.
nregions, std::vector<std::size_t>(K, 0));
586 for (std::size_t f = 0; f < con.
nregions; ++f) {
587 if (f >= con.
member.size() || !staged_region[f])
continue;
588 for (std::size_t e = 0; e < nev; ++e) {
592 for (std::size_t s = 0; s < t.
nstate; ++s)
593 if (con.
member[f][s]) delta += t.
D(s, e);
594 if (delta <= tol)
continue;
599 for (std::size_t s = 0; s < t.
nstate; ++s)
601 if (k >= K)
continue;
602 if (idx[f][k] == 0) {
604 stg.
klass.push_back(k);
605 idx[f][k] = stg.
region.size();
618 stg.
gated_by.assign(ncon, std::vector<bool>(stg.
n,
false));
619 for (std::size_t c = 0; c < ncon; ++c) {
621 for (std::size_t j = 0; j < stg.
n; ++j) {
624 for (std::size_t s = 0; s < t.
nstate; ++s)
626 w = std::max(w, con.
A(c, s));
646 const std::size_t ncon = con.
b.size();
648 if (ncon == 0 || stg.
n == 0)
return;
649 const std::size_t nev = t.
D.
cols();
650 const double tol = 1e-9;
651 std::vector<std::vector<std::size_t> > feeds(stg.
n);
652 std::vector<std::size_t> dest(stg.
n,
static_cast<std::size_t
>(-1));
653 for (std::size_t e = 0; e < nev; ++e) {
654 if (!stg.
adm[e])
continue;
656 for (std::size_t s = 0; s < t.
nstate; ++s) {
657 if (t.
D(s, e) < -tol &&
658 std::find(feeds[j].begin(), feeds[j].end(), s) == feeds[j].end())
659 feeds[j].push_back(s);
660 if (dest[j] ==
static_cast<std::size_t
>(-1) && t.
D(s, e) > tol &&
665 for (std::size_t c = 0; c < ncon; ++c)
666 for (std::size_t j = 0; j < stg.
n; ++j) {
667 if (dest[j] ==
static_cast<std::size_t
>(-1) || feeds[j].empty())
continue;
668 const double w = con.
A(c, dest[j]);
669 if (w <= 0.0)
continue;
670 bool all_inside =
true;
671 for (std::size_t a = 0; a < feeds[j].size() && all_inside; ++a)
672 all_inside = con.
A(c, feeds[j][a]) > 0.0;
673 if (all_inside) con.
As(c, j) = w;
681 std::vector<double>
ds;
701 const std::vector<std::size_t>& active,
702 const std::vector<double>& sg,
const std::vector<double>& r,
703 const std::vector<double>& mult,
bool staged_flow) {
704 const std::size_t nev = r.size(), ns = stg.
n, nact = active.size();
706 std::vector<double> whole(nev, 1.0), entry(nev, 1.0);
707 std::vector<double> inv_theta(ns, 0.0), flow_of(ns, 0.0);
708 std::vector<bool> throttled(ns,
false);
709 for (std::size_t k = 0; k < nact; ++k) {
710 const std::size_t c = active[k];
711 const double m = mult[k];
713 for (std::size_t j = 0; j < ns; ++j)
717 inv_theta[j] += (m > 1e-14) ? 1.0 / m
718 : std::numeric_limits<double>::infinity();
721 for (std::size_t e = 0; e < nev; ++e) {
722 if (gates.
held[c][e]) whole[e] *= m;
723 if (gates.
loss[c][e]) entry[e] *= m;
727 std::vector<bool> split(nev,
false);
728 for (std::size_t e = 0; e < nev; ++e)
729 if (ns && stg.
adm[e] && throttled[stg.
adm_stage[e]]) split[e] =
true;
731 out.
rup.assign(nev, 0.0);
732 out.
rin.assign(nev, 0.0);
733 for (std::size_t e = 0; e < nev; ++e) {
737 out.
rup[e] = r[e] * (split[e] ? 1.0 : whole[e]);
738 out.
rin[e] = out.
rup[e] * entry[e];
740 std::vector<double> inflow(ns, 0.0), R(ns, 0.0);
741 for (std::size_t e = 0; e < nev; ++e)
747 for (std::size_t k = 0; k < nact; ++k) {
748 const std::size_t c = active[k];
749 if (!con.
staged[c])
continue;
750 double mass = 0.0, tot = 0.0;
751 std::size_t nrooms = 0;
752 for (std::size_t j = 0; j < ns; ++j)
753 if (stg.
gated_by[c][j]) { mass += sg[j]; tot += inflow[j]; ++nrooms; }
754 if (nrooms == 0)
continue;
755 for (std::size_t j = 0; j < ns; ++j) {
758 if (mass > 1e-8) w = sg[j] / mass;
759 else if (tot > 1e-8) w = inflow[j] / tot;
760 else w = 1.0 /
static_cast<double>(nrooms);
761 flow_of[j] += mult[k] * w;
765 for (std::size_t j = 0; j < ns; ++j) {
766 const double theta = inv_theta[j] > 0.0 ? 1.0 / inv_theta[j] : 0.0;
767 flow_of[j] = theta * sg[j];
771 std::vector<double> left(ns, 0.0);
772 for (std::size_t e = 0; e < nev; ++e) {
773 if (!split[e])
continue;
775 const double q = R[j] > 1e-8 ? flow_of[j] * out.
rup[e] / R[j] : 0.0;
778 out.
rin[e] = q * whole[e] * entry[e];
779 left[j] += q * whole[e];
781 for (std::size_t j = 0; j < ns; ++j)
782 if (R[j] > 1e-8) out.
drain[j] = left[j];
783 out.
ds.assign(ns, 0.0);
784 for (std::size_t j = 0; j < ns; ++j)
786 out.
ds[j] = throttled[j] ? inflow[j] - out.
drain[j] : sg[j];
793 std::vector<double>
N;
807 const std::size_t nstate = terms.
nstate;
809 const std::size_t K = M ? terms.
class_block[0].size() : 0;
811 std::vector<std::size_t> coord_class(nstate, 0), coord_station(nstate, 0);
812 for (std::size_t i = 0; i < M; ++i)
813 for (std::size_t k = 0; k < K; ++k)
816 coord_station[s] = i;
819 std::vector<std::vector<double> > rows;
820 std::vector<double> nvec;
821 const std::size_t nchains =
sn.chains.size();
822 const std::vector<double> njobs =
sn.njobs();
823 for (std::size_t ch = 0; ch < nchains; ++ch) {
824 std::vector<std::size_t> inch;
827 for (std::size_t k = 0; k < K; ++k) {
828 if (k >=
sn.chains[ch].size() || !
sn.chains[ch][k])
continue;
830 const double nj = (k < njobs.size()) ? njobs[k] : 0.0;
831 if (!std::isfinite(nj)) finite =
false;
834 if (inch.empty() || !finite || Nch <= 0.0)
continue;
835 std::vector<double> row(nstate + stg.
n, 0.0);
837 for (std::size_t s = 0; s < nstate; ++s) {
838 if (terms.
is_ext[coord_station[s]])
continue;
839 if (std::find(inch.begin(), inch.end(), coord_class[s]) == inch.end())
continue;
844 for (std::size_t j = 0; j < stg.
n; ++j)
845 if (std::find(inch.begin(), inch.end(), stg.
klass[j]) != inch.end())
846 row[nstate + j] = 1.0;
854 for (std::size_t r = 0; r < rows.size(); ++r)
855 for (std::size_t s = 0; s < nstate + stg.
n; ++s) out.
C(r, s) = rows[r][s];
921 std::vector<std::size_t> idx;
923 for (std::size_t i = 0; i < M; ++i) {
926 if (!std::isfinite(t.
S[i]))
continue;
935 const std::vector<std::size_t>& cidx) {
937 for (std::size_t j = 0; j < cidx.size(); ++j) {
938 const std::vector<std::size_t>& blk = t.
station_block[cidx[j]];
940 for (std::size_t a = 0; a < blk.
size(); ++a)
941 for (std::size_t b = 0; b < blk.
size(); ++b) acc += Sigma(blk[a], blk[b]);
942 s2[cidx[j]] = std::max(0.0, acc);
977 const std::vector<std::size_t>& active,
979 std::vector<std::size_t> rows;
980 for (std::size_t k = 0; k < active.size(); ++k)
981 if (!con.
staged[active[k]]) rows.push_back(active[k]);
986 for (std::size_t i = 0; i < rows.size(); ++i)
987 for (std::size_t j = 0; j <
nc; ++j) {
988 R(i, j) = con.
A(rows[i], t.
cov_idx[j]);
989 if (std::fabs(R(i, j)) > 1e-14) any =
true;
993 const std::size_t m = rows.size();
995 for (std::size_t a = 0; a < m; ++a) {
996 for (std::size_t b = 0; b < m; ++b) {
998 for (std::size_t j = 0; j <
nc; ++j) acc += R(a, j) * R(b, j);
1003 for (std::size_t a = 0; a < m; ++a) {
1004 std::size_t piv = a;
1005 for (std::size_t b = a + 1; b < m; ++b)
1006 if (std::fabs(G(b, a)) > std::fabs(G(piv, a))) piv = b;
1007 if (std::fabs(G(piv, a)) < 1e-300)
return Matrix<double>(0, 0, 0.0);
1009 for (std::size_t j = 0; j < 2 * m; ++j) std::swap(G(a, j), G(piv, j));
1010 const double d = G(a, a);
1011 for (std::size_t j = 0; j < 2 * m; ++j) G(a, j) /= d;
1012 for (std::size_t b = 0; b < m; ++b) {
1013 if (b == a)
continue;
1014 const double f = G(b, a);
1015 if (f == 0.0)
continue;
1016 for (std::size_t j = 0; j < 2 * m; ++j) G(b, j) -= f * G(a, j);
1020 for (std::size_t i = 0; i <
nc; ++i) T(i, i) = 1.0;
1021 for (std::size_t i = 0; i <
nc; ++i)
1022 for (std::size_t j = 0; j <
nc; ++j) {
1024 for (std::size_t a = 0; a < m; ++a)
1025 for (std::size_t b = 0; b < m; ++b) acc += R(a, i) * G(a, m + b) * R(b, j);
1043 std::vector<double>& G, std::vector<double>* rates_out,
1044 bool rethrow =
false, std::vector<double>* fire_out =
nullptr) {
1047 const std::size_t ncl = sysd.
cidx.size(), nact = sysd.
active.size();
1048 const std::vector<double> x(u.begin(), u.begin() + n);
1049 const std::vector<double> sg(u.begin() + n, u.begin() + n + ns);
1057 for (std::size_t j = 0; j < ncl; ++j)
1058 cl.
sigma2[sysd.
cidx[j]] = std::max(0.0, u[n + ns + j]);
1059 std::vector<double> mult(nact, 0.0);
1060 for (std::size_t k = 0; k < nact; ++k) mult[k] = std::max(0.0, u[n + ns + ncl + k]);
1062 std::vector<double> r;
1073 }
catch (
const std::exception&) {
1083 const std::size_t nev = r.size();
1084 std::vector<double> drift(n, 0.0);
1085 for (std::size_t s = 0; s < n; ++s) {
1087 for (std::size_t e = 0; e < nev; ++e) {
1102 G.insert(G.end(), drift.begin(), drift.end());
1103 G.insert(G.end(), lg.
ds.begin(), lg.
ds.end());
1105 for (std::size_t c = 0; c < C.
rows(); ++c) {
1107 for (std::size_t s = 0; s < n; ++s) acc += C(c, s) * x[s];
1108 for (std::size_t j = 0; j < ns; ++j) acc += C(c, n + j) * sg[j];
1109 G.push_back(acc - sysd.
cons->
N[c]);
1111 for (std::size_t j = 0; j < ncl; ++j)
1116 for (std::size_t k = 0; k < nact; ++k) {
1118 for (std::size_t s = 0; s < n; ++s) acc += sysd.
con->
A(sysd.
active[k], s) * x[s];
1119 for (std::size_t j = 0; j < ns; ++j) acc += sysd.
con->
As(sysd.
active[k], j) * sg[j];
1120 G.push_back(acc - sysd.
con->
b[sysd.
active[k]]);
1125 if (rates_out) *rates_out = lg.
rin;
1126 if (fire_out) *fire_out = lg.
rup;
1132 for (std::size_t i = nfree; i < u.size(); ++i) u[i] = std::max(0.0, u[i]);
1138 double residual = std::numeric_limits<double>::infinity();
1160 double tol, std::size_t maxit) {
1162 const std::size_t nfree = sysd.
nstate;
1164 std::vector<double> G;
1166 double resnorm = 0.0;
1167 for (
double g : G) resnorm = std::max(resnorm, std::fabs(g));
1169 const std::size_t nun = u.size(), m = G.size();
1170 while (resnorm >= tol && info.
iters < maxit) {
1173 std::vector<double> Gp;
1174 for (std::size_t j = 0; j < nun; ++j) {
1175 const double h = std::max(1e-7 * std::fabs(u[j]), 1e-9);
1176 std::vector<double> up = u;
1179 for (std::size_t i = 0; i < m; ++i) J(i, j) = (Gp[i] - G[i]) / h;
1181 std::vector<double> du;
1184 }
catch (
const std::exception&) {
1187 for (
double& d : du) d = -d;
1190 bool stepped =
false;
1191 for (
int ls = 0; ls < 25; ++ls) {
1192 std::vector<double> un(nun);
1193 for (std::size_t j = 0; j < nun; ++j) un[j] = u[j] + lam * du[j];
1195 std::vector<double> Gn;
1199 for (
double g : Gn) {
1200 if (!std::isfinite(g)) { ok =
false;
break; }
1201 rn = std::max(rn, std::fabs(g));
1203 if (ok && rn < resnorm * (1.0 - 1e-4 * lam)) {
1204 u = un; G = Gn; resnorm = rn;
1211 if (!stepped)
break;
1229struct FluidDaeTransient {
1230 const FluidMomentTerms* terms =
nullptr;
1231 const FluidDaeConservation* cons =
nullptr;
1232 FluidClosure closure;
1233 std::vector<std::size_t> alg_row;
1234 std::size_t nstate = 0;
1245 bool withcov =
false;
1246 std::vector<std::size_t> cov_idx;
1248 std::vector<std::size_t> closable;
1252 std::vector<double> grid;
1253 std::size_t cursor = 0;
1254 std::vector<std::vector<double> > out;
1267 const FluidDaeConstraints* con =
nullptr;
1268 const FluidDaeGates* gates =
nullptr;
1269 const FluidDaeStaging* stg =
nullptr;
1270 std::vector<std::size_t> active;
1271 std::vector<char> armed;
1272 std::vector<char> gated_rooms;
1273 std::vector<double> inert;
1274 std::size_t ncon = 0, nstg = 0, o_sg = 0, o_m = 0, o_cov = 0;
1276 std::vector<double> gprev;
1279 std::vector<double> hit_z;
1291inline std::vector<double> fluid_dae_cap_events(
const FluidDaeTransient& S,
1293 std::vector<double> g(S.ncon, 1.0);
1294 for (std::size_t c = 0; c < S.ncon; ++c) {
1295 const bool is_active =
1296 std::find(S.active.begin(), S.active.end(), c) != S.active.end();
1298 if (S.con->staged[c]) {
1300 for (std::size_t j = 0; j < S.nstg; ++j)
1301 if (S.stg->gated_by[c][j]) mass += z[S.o_sg + j];
1302 g[c] = S.armed[c] ? mass : 1.0;
1304 g[c] = 1.0 - z[S.o_m + c];
1308 for (std::size_t s = 0; s < S.nstate; ++s) val += S.con->A(c, s) * z[s];
1309 for (std::size_t j = 0; j < S.nstg; ++j) val += S.con->As(c, j) * z[S.o_sg + j];
1310 g[c] = S.con->b[c] - val;
1315inline FluidDaeTransient*& fluid_dae_active() {
1316 static FluidDaeTransient* p =
nullptr;
1321inline FluidClosure fluid_dae_closure_at(
const FluidDaeTransient& S,
1322 const rodas_impl::doublereal* y) {
1323 if (!S.withcov)
return S.closure;
1325 cl.sigma2.assign(S.terms->station_block.size(), 0.0);
1326 cl.cov.assign(S.terms->station_block.size(), Matrix<double>(0, 0, 0.0));
1327 Matrix<double> Sigma(S.terms->nstate, S.terms->nstate, 0.0);
1328 for (std::size_t a = 0; a < S.nc; ++a)
1329 for (std::size_t b = 0; b < S.nc; ++b)
1331 Sigma(S.cov_idx[a], S.cov_idx[b]) =
1332 0.5 * (y[S.o_cov + a * S.nc + b] + y[S.o_cov + b * S.nc + a]);
1334 for (std::size_t i = 0; i < s2.size(); ++i) cl.sigma2[i] = s2[i];
1339inline int fluid_dae_fcn(rodas_impl::integer*, rodas_impl::doublereal*,
1340 rodas_impl::doublereal* y, rodas_impl::doublereal* f,
1341 rodas_impl::doublereal*, rodas_impl::integer*) {
1342 FluidDaeTransient& S = *fluid_dae_active();
1343 const std::vector<double> x(y, y + S.nstate);
1344 const FluidClosure cl = fluid_dae_closure_at(S, y);
1345 std::vector<double> dx;
1350 const std::vector<double> sg(y + S.o_sg, y + S.o_sg + S.nstg);
1351 std::vector<double> mact(S.active.size(), 0.0);
1352 for (std::size_t k = 0; k < S.active.size(); ++k) mact[k] = y[S.o_m + S.active[k]];
1354 lg =
fluid_dae_legs(*S.terms, *S.gates, *S.stg, *S.con, S.active, sg, r, mact,
true);
1355 dx.assign(S.nstate, 0.0);
1356 for (std::size_t s = 0; s < S.nstate; ++s) {
1358 for (std::size_t e = 0; e < r.size(); ++e)
1359 acc += S.gates->Dn(s, e) * lg.rup[e] + S.gates->DnExt(s, e) * lg.rin[e] +
1360 S.gates->Dp(s, e) * lg.rin[e];
1364 for (std::size_t i = 0; i < S.nstate; ++i) f[i] = dx[i];
1365 const Matrix<double>& C = S.cons->C;
1366 for (std::size_t c = 0; c < C.rows(); ++c) {
1368 for (std::size_t s = 0; s < S.nstate; ++s) acc += C(c, s) * x[s];
1369 for (std::size_t j = 0; j < S.nstg; ++j) acc += C(c, S.nstate + j) * y[S.o_sg + j];
1370 f[S.alg_row[c]] = acc - S.cons->N[c];
1373 for (std::size_t j = 0; j < S.nstg; ++j)
1374 f[S.o_sg + j] = S.gated_rooms[j] ? lg.ds[j] : y[S.o_sg + j];
1375 for (std::size_t c = 0; c < S.ncon; ++c) {
1376 const bool is_active =
1377 std::find(S.active.begin(), S.active.end(), c) != S.active.end();
1379 f[S.o_m + c] = y[S.o_m + c] - S.inert[c];
1383 for (std::size_t s = 0; s < S.nstate; ++s) acc += S.con->A(c, s) * dx[s];
1384 for (std::size_t j = 0; j < S.nstg; ++j) acc += S.con->As(c, j) * lg.ds[j];
1393 const std::vector<double> r =
1395 for (std::size_t a = 0; a < S.nc; ++a)
1396 for (std::size_t b = 0; b < S.nc; ++b) {
1398 for (std::size_t l = 0; l < S.nc; ++l)
1399 acc += A(S.cov_idx[a], S.cov_idx[l]) * y[S.o_cov + l * S.nc + b]
1400 + y[S.o_cov + a * S.nc + l] * A(S.cov_idx[b], S.cov_idx[l]);
1401 for (std::size_t e = 0; e < r.size(); ++e)
1402 acc += S.terms->D(S.cov_idx[a], e) * r[e] * S.terms->D(S.cov_idx[b], e);
1403 f[S.o_cov + a * S.nc + b] = acc;
1410inline int fluid_dae_jac(rodas_impl::integer*, rodas_impl::doublereal*,
1411 rodas_impl::doublereal* y, rodas_impl::doublereal* dfy,
1412 rodas_impl::integer* ldfy, rodas_impl::doublereal*,
1413 rodas_impl::integer*) {
1414 FluidDaeTransient& S = *fluid_dae_active();
1415 const std::vector<double> x(y, y + S.nstate);
1417 const int ld = *ldfy;
1418 for (std::size_t i = 0; i < S.nstate; ++i)
1419 for (std::size_t j = 0; j < S.nstate; ++j) dfy[i + j * ld] = A(i, j);
1420 const Matrix<double>& C = S.cons->C;
1421 for (std::size_t c = 0; c < C.rows(); ++c)
1422 for (std::size_t j = 0; j < S.nstate; ++j) dfy[S.alg_row[c] + j * ld] = C(c, j);
1427inline int fluid_dae_mas(rodas_impl::integer* n, rodas_impl::doublereal* am,
1428 rodas_impl::integer* lmas, rodas_impl::doublereal*,
1429 rodas_impl::integer*) {
1430 FluidDaeTransient& S = *fluid_dae_active();
1431 const int ld = *lmas;
1432 for (
int j = 0; j < *n; ++j) am[0 + j * ld] = 1.0;
1433 for (std::size_t c = 0; c < S.alg_row.size(); ++c) am[0 + S.alg_row[c] * ld] = 0.0;
1436 for (std::size_t j = 0; j < S.nstg; ++j)
1437 if (!S.gated_rooms[j]) am[0 + (S.o_sg + j) * ld] = 0.0;
1438 for (std::size_t c = 0; c < S.ncon; ++c) am[0 + (S.o_m + c) * ld] = 0.0;
1456inline int fluid_dae_solout(rodas_impl::integer* nr, rodas_impl::doublereal* xold,
1457 rodas_impl::doublereal* x, rodas_impl::doublereal* y,
1458 rodas_impl::doublereal* cont, rodas_impl::integer* lrc,
1459 rodas_impl::integer*, rodas_impl::doublereal*,
1460 rodas_impl::integer*, rodas_impl::integer* irtrn) {
1461 FluidDaeTransient& S = *fluid_dae_active();
1462 const std::size_t nz = S.o_cov + (S.withcov ? S.nc * S.nc : 0);
1463 double stop_at = std::numeric_limits<double>::infinity();
1464 if (S.ncon && *nr > 1) {
1466 for (std::size_t k = 0; k < S.active.size(); ++k) {
1467 const std::size_t c = S.active[k];
1468 if (!S.con->staged[c] || S.armed[c])
continue;
1470 for (std::size_t j = 0; j < S.nstg; ++j)
1471 if (S.stg->gated_by[c][j]) mass += y[S.o_sg + j];
1472 if (mass > 1e-8) S.armed[c] = 1;
1474 const std::vector<double> gcur = fluid_dae_cap_events(S, y);
1476 for (std::size_t c = 0; c < S.ncon; ++c)
1477 if (S.gprev.size() == S.ncon && S.gprev[c] > 0.0 && gcur[c] <= 0.0) {
1478 cross =
static_cast<int>(c);
1488 std::vector<double> ymid(nz, 0.0);
1489 for (
int bit = 0; bit < 60; ++bit) {
1490 const double mid = 0.5 * (lo + hi);
1491 for (std::size_t i = 0; i < nz; ++i) {
1492 rodas_impl::integer ii =
static_cast<rodas_impl::integer
>(i + 1);
1494 ymid[i] = rodas_impl::contro_(&ii, &tq, cont, lrc);
1496 const std::vector<double> gmid = fluid_dae_cap_events(S, ymid.data());
1497 if (gmid[
static_cast<std::size_t
>(cross)] > 0.0) lo = mid;
1499 if (hi - lo <= 1e-12 * std::max(1.0, std::fabs(hi)))
break;
1501 std::vector<double> yhit(nz, 0.0);
1502 for (std::size_t i = 0; i < nz; ++i) {
1503 rodas_impl::integer ii =
static_cast<rodas_impl::integer
>(i + 1);
1505 yhit[i] = rodas_impl::contro_(&ii, &tq, cont, lrc);
1512 }
else if (S.ncon) {
1513 S.gprev = fluid_dae_cap_events(S, y);
1517 while (S.cursor < S.grid.size() && S.grid[S.cursor] <= *x + 1e-13 &&
1518 S.grid[S.cursor] <= stop_at + 1e-13) {
1519 double tq = S.grid[S.cursor];
1520 std::vector<double> xs(nz, 0.0);
1521 for (std::size_t i = 0; i < nz; ++i) {
1525 rodas_impl::integer ii =
static_cast<rodas_impl::integer
>(i + 1);
1526 xs[i] = rodas_impl::contro_(&ii, &tq, cont, lrc);
1529 S.out.push_back(xs);
1536 if (S.hit >= 0 && irtrn) *irtrn = -1;
1539inline int fluid_dae_dfx(rodas_impl::integer*, rodas_impl::doublereal*,
1540 rodas_impl::doublereal*, rodas_impl::doublereal*,
1541 rodas_impl::doublereal*, rodas_impl::integer*) {
return 0; }
1567 const std::vector<double>& x0,
1570 const std::size_t K = M ? terms.
class_block[0].size() : 0;
1572 std::vector<std::vector<double> > pie(M * K);
1573 std::vector<double> mval(M * K, 0.0);
1574 for (std::size_t i = 0; i < M; ++i)
1575 for (std::size_t r = 0; r < K; ++r) {
1576 const std::vector<std::size_t>& blk = terms.
class_block[i][r];
1577 if (blk.
empty())
continue;
1579 for (std::size_t b = 0; b < blk.
size(); ++b) m += x0[blk[b]];
1580 mval[r * M + i] = std::max(m, 0.0);
1581 std::vector<double> p(blk.
size(), 0.0);
1583 for (std::size_t b = 0; b < blk.
size(); ++b) p[b] = x0[blk[b]] / m;
1589 for (std::size_t i = 0; i < M; ++i)
1590 for (std::size_t r = 0; r < K; ++r) {
1591 const std::vector<std::size_t>& bi = terms.
class_block[i][r];
1592 if (bi.empty())
continue;
1593 const std::size_t irb = r * M + i;
1594 const std::vector<double>& pb = pie[irb];
1595 for (std::size_t j = 0; j < M; ++j)
1596 for (std::size_t sc = 0; sc < K; ++sc) {
1597 const std::vector<std::size_t>& bj = terms.
class_block[j][sc];
1598 if (bj.empty())
continue;
1599 const std::size_t ird = sc * M + j;
1600 const std::vector<double>& pd = pie[ird];
1601 const double c = C0sc(irb, ird);
1602 for (std::size_t a = 0; a < bi.size(); ++a)
1603 for (std::size_t b = 0; b < bj.size(); ++b)
1604 Sigma0(bi[a], bj[b]) = c * pb[a] * pd[b];
1606 for (std::size_t a = 0; a < bi.size(); ++a)
1607 for (std::size_t b = 0; b < bi.size(); ++b)
1608 Sigma0(bi[a], bi[b]) +=
1609 mval[irb] * ((a == b ? pb[a] : 0.0) - pb[a] * pb[b]);
1611 for (std::size_t a = 0; a < terms.
nstate; ++a)
1612 for (std::size_t b = a + 1; b < terms.
nstate; ++b) {
1613 const double v = 0.5 * (Sigma0(a, b) + Sigma0(b, a));
1639 const FluidClosure& closure,
const std::vector<double>& x0,
1640 const std::vector<double>& grid,
double tol,
bool withcov =
false,
1641 const std::vector<std::size_t>& closable = std::vector<std::size_t>(),
1643 using namespace rodas_impl;
1644 const std::size_t n = terms.
nstate;
1646 detail::FluidDaeTransient S;
1649 S.closure = closure;
1651 S.withcov = withcov;
1654 S.closable = closable;
1655 if (S.nc == 0) S.withcov =
false;
1659 const std::size_t nz = n + (S.withcov ? S.nc * S.nc : 0);
1660 for (std::size_t c = 0; c < cons.
C.
rows(); ++c) {
1661 std::size_t best = n;
1662 double bestv = -1.0;
1663 for (std::size_t s = 0; s < n; ++s) {
1664 if (cons.
C(c, s) == 0.0)
continue;
1666 for (std::size_t d = 0; d < S.alg_row.size(); ++d)
1667 if (S.alg_row[d] == s) taken =
true;
1668 if (taken)
continue;
1669 if (x0[s] > bestv) { bestv = x0[s]; best = s; }
1671 if (best == n)
throw NumericError(
"fluid_dae_integrate: a chain has no free coordinate");
1672 S.alg_row.push_back(best);
1674 if (grid.empty())
throw InputError(
"fluid_dae_integrate: the output grid is empty");
1675 for (std::size_t j = 1; j < grid.size(); ++j)
1676 if (!(grid[j] > grid[j - 1]))
1677 throw InputError(
"fluid_dae_integrate: the output grid must be increasing");
1679 detail::fluid_dae_active() = &S;
1684 const integer N =
static_cast<integer
>(nz);
1685 const std::size_t lwork = nz * (nz + 1 + nz + 14) + 20 + 32;
1686 const std::size_t liwork = nz + 20 + 32;
1687 std::vector<doublereal> y(nz, 0.0), work(lwork, 0.0), rpar(1, 0.0);
1688 for (std::size_t i = 0; i < n && i < x0.size(); ++i) y[i] = x0[i];
1696 if (S.withcov && init_sigma.rows() == n && init_sigma.cols() == n)
1697 for (std::size_t a = 0; a < S.nc; ++a)
1698 for (std::size_t b = 0; b < S.nc; ++b)
1699 y[S.o_cov + a * S.nc + b] = init_sigma(S.cov_idx[a], S.cov_idx[b]);
1700 std::vector<integer> iwork(liwork, 0), ipar(1, 0);
1701 doublereal rtol = tol, atol = tol * 1e-2, x = 0.0, xend = grid.back(), h = 1e-6;
1709 integer itol = 0, ifcn = 0, ijac = S.withcov ? 0 : 1, mljac = N, mujac = N, idfx = 0;
1710 integer imas = 1, mlmas = 0, mumas = 0, iout = 1, idid = 0;
1711 integer lw =
static_cast<integer
>(lwork), liw =
static_cast<integer
>(liwork);
1714 rodas_(
const_cast<integer*
>(&N), (U_fp)detail::fluid_dae_fcn, &ifcn, &x, y.data(),
1715 &xend, &h, &rtol, &atol, &itol,
1716 (U_fp)detail::fluid_dae_jac, &ijac, &mljac, &mujac,
1717 (U_fp)detail::fluid_dae_dfx, &idfx,
1718 (U_fp)detail::fluid_dae_mas, &imas, &mlmas, &mumas,
1719 (U_fp)detail::fluid_dae_solout, &iout,
1720 work.data(), &lw, iwork.data(), &liw, rpar.data(), ipar.data(), &idid);
1724 detail::fluid_dae_active() =
nullptr;
1727 detail::fluid_dae_active() =
nullptr;
1730 throw NumericError(
"fluid_dae_integrate: RODAS returned idid=" + std::to_string(idid));
1734 if (S.out.size() + 1 == grid.size()) S.out.push_back(std::vector<double>(y.begin(), y.end()));
1735 if (S.out.size() != grid.size())
1736 throw NumericError(
"fluid_dae_integrate: RODAS reported " + std::to_string(S.out.size()) +
1737 " of the " + std::to_string(grid.size()) +
" requested output points");
1738 S.out.back().assign(y.begin(), y.end());
1756 const std::vector<double>& x,
const std::vector<double>& sg,
const FluidClosure& cl,
1757 const std::vector<double>& m0,
bool& ok) {
1758 std::vector<double> m = m0;
1760 if (active.empty())
return m;
1761 const std::size_t n = terms.
nstate;
1762 auto resid = [&](
const std::vector<double>& mm) {
1765 std::vector<double> dx(n, 0.0);
1766 for (std::size_t s = 0; s < n; ++s) {
1768 for (std::size_t e = 0; e < r.size(); ++e)
1769 acc += gates.
Dn(s, e) * lg.
rup[e] + gates.
DnExt(s, e) * lg.
rin[e] +
1770 gates.
Dp(s, e) * lg.
rin[e];
1773 std::vector<double> F(active.size(), 0.0);
1774 for (std::size_t k = 0; k < active.size(); ++k) {
1776 for (std::size_t s = 0; s < n; ++s) acc += con.
A(active[k], s) * dx[s];
1777 for (std::size_t j = 0; j < stg.
n; ++j) acc += con.
As(active[k], j) * lg.
ds[j];
1782 auto inf_norm = [](
const std::vector<double>& v) {
1784 for (
double e : v) a = std::max(a, std::fabs(e));
1787 std::vector<double> F = resid(m);
1788 for (
int it = 0; it < 40; ++it) {
1789 if (inf_norm(F) < std::max(1e-12, 1e-10 * (inf_norm(m) + 1.0)))
break;
1791 for (std::size_t j = 0; j < m.size(); ++j) {
1792 const double h = std::max(1e-7 * std::fabs(m[j]), 1e-9);
1793 std::vector<double> mp = m;
1795 const std::vector<double> Fp = resid(mp);
1796 for (std::size_t i = 0; i < F.size(); ++i) J(i, j) = (Fp[i] - F[i]) / h;
1798 std::vector<double> step;
1800 step =
lstsq(J, F).x;
1801 }
catch (
const std::exception&) {
1805 bool stepped =
false;
1806 for (
int ls = 0; ls < 20; ++ls) {
1807 std::vector<double> mn(m.size(), 0.0);
1808 for (std::size_t j = 0; j < m.size(); ++j) mn[j] = std::max(0.0, m[j] - lam * step[j]);
1809 const std::vector<double> Fn = resid(mn);
1810 if (inf_norm(Fn) < inf_norm(F)) {
1818 if (!stepped)
break;
1820 ok = inf_norm(F) < 1e-6;
1839 const FluidClosure& closure,
const std::vector<double>& x0In,
1840 const std::vector<double>& grid,
double tol, std::vector<FluidDaeSwitch>* switches =
nullptr) {
1841 using namespace rodas_impl;
1842 const std::size_t n = terms.
nstate;
1843 const std::size_t ncon = con.
b.size(), nstg = stg.
n;
1845 detail::FluidDaeTransient S;
1848 S.closure = closure;
1860 S.o_cov = n + nstg + ncon;
1861 S.armed.assign(std::max<std::size_t>(ncon, 1), 0);
1862 S.inert.assign(ncon, 1.0);
1863 for (std::size_t c = 0; c < ncon; ++c)
1864 if (con.
staged[c]) S.inert[c] = 0.0;
1865 const std::size_t nz = S.o_cov;
1872 std::vector<double> x0 = x0In;
1874 std::vector<double> sg0(nstg, 0.0), excess(std::max<std::size_t>(ncon, 1), 0.0);
1875 std::vector<std::size_t> over;
1876 for (std::size_t c = 0; c < ncon; ++c) {
1877 double val = 0.0, tot = 0.0;
1878 for (std::size_t s = 0; s < n; ++s) {
1879 val += con.
A(c, s) * x0[s];
1880 if (con.
A(c, s) > 0.0) tot += x0[s];
1882 if (val > con.
b[c] + std::max(1e-9, tol) && con.
b[c] > 0.0) {
1883 excess[c] = tot * (1.0 - con.
b[c] / val);
1884 for (std::size_t s = 0; s < n; ++s)
1885 if (con.
A(c, s) > 0.0) x0[s] *= con.
b[c] / val;
1891 std::vector<double> m_init = S.inert;
1892 if (!over.empty()) {
1893 std::vector<double> m0(over.size(), 0.0);
1894 for (std::size_t a = 0; a < over.size(); ++a) m0[a] = con.
staged[over[a]] ? 0.0 : 1.0;
1897 x0, sg0, closure, m0, ok);
1899 for (std::size_t a = 0; a < over.size(); ++a)
1900 if (!con.
staged[over[a]] && mm[a] > 1.0 + 1e-9) feasible =
false;
1903 for (std::size_t a = 0; a < over.size(); ++a) m_init[over[a]] = mm[a];
1906 for (std::size_t oi = 0; oi < over.size(); ++oi) {
1907 const std::size_t c = over[oi];
1908 if (excess[c] <= 0.0)
continue;
1909 std::size_t rooms = 0;
1910 const bool staged_active =
1911 con.
staged[c] && std::find(S.active.begin(), S.active.end(), c) != S.active.end();
1913 for (std::size_t j = 0; j < nstg; ++j)
1916 for (std::size_t j = 0; j < nstg; ++j)
1917 if (stg.
gated_by[c][j]) sg0[j] += excess[c] /
static_cast<double>(rooms);
1922 std::vector<bool> pool(n,
false);
1923 bool any_feeder =
false;
1924 std::vector<bool> feeders(n,
false);
1925 for (std::size_t e = 0; e < terms.
D.
cols(); ++e) {
1926 if (!gates.
gate[c][e])
continue;
1927 for (std::size_t s = 0; s < n; ++s)
1928 if (gates.
Dn(s, e) < 0.0 || gates.
DnExt(s, e) < 0.0) feeders[s] =
true;
1930 for (std::size_t s = 0; s < n; ++s) {
1931 pool[s] = con.
A(c, s) <= 0.0;
1932 any_feeder = any_feeder || (pool[s] && feeders[s]);
1935 for (std::size_t s = 0; s < n; ++s) pool[s] = pool[s] && feeders[s];
1937 std::size_t cnt = 0;
1938 for (std::size_t s = 0; s < n; ++s)
1939 if (pool[s]) { w += x0[s]; ++cnt; }
1941 for (std::size_t s = 0; s < n; ++s)
1942 if (pool[s]) x0[s] += excess[c] * x0[s] / w;
1944 for (std::size_t s = 0; s < n; ++s)
1945 if (pool[s]) x0[s] += excess[c] /
static_cast<double>(cnt);
1948 for (std::size_t k = 0; k < S.active.size(); ++k) {
1949 const std::size_t c = S.active[k];
1950 if (!con.
staged[c])
continue;
1952 for (std::size_t j = 0; j < nstg; ++j)
1953 if (stg.
gated_by[c][j]) mass += sg0[j];
1954 if (mass > 1e-8) S.armed[c] = 1;
1957 for (std::size_t c = 0; c < cons.
C.
rows(); ++c) {
1958 std::size_t best = n;
1959 double bestv = -1.0;
1960 for (std::size_t s = 0; s < n; ++s) {
1961 if (cons.
C(c, s) == 0.0)
continue;
1963 for (std::size_t d = 0; d < S.alg_row.size(); ++d)
1964 if (S.alg_row[d] == s) taken =
true;
1965 if (taken)
continue;
1966 if (x0[s] > bestv) { bestv = x0[s]; best = s; }
1969 throw NumericError(
"fluid_dae_integrate_hybrid: a chain has no free coordinate");
1970 S.alg_row.push_back(best);
1972 if (grid.empty())
throw InputError(
"fluid_dae_integrate_hybrid: the output grid is empty");
1975 std::vector<double> z(nz, 0.0);
1976 for (std::size_t s = 0; s < n; ++s) z[s] = x0[s];
1977 for (std::size_t j = 0; j < nstg; ++j) z[S.o_sg + j] = sg0[j];
1978 for (std::size_t c = 0; c < ncon; ++c) z[S.o_m + c] = m_init[c];
1981 const double tend = grid.back();
1982 const std::size_t seg_max = 4 * ncon + 8;
1983 for (std::size_t seg = 0; seg < seg_max; ++seg) {
1984 S.gated_rooms.assign(nstg, 0);
1985 for (std::size_t k = 0; k < S.active.size(); ++k)
1986 if (con.
staged[S.active[k]])
1987 for (std::size_t j = 0; j < nstg; ++j)
1988 if (stg.
gated_by[S.active[k]][j]) S.gated_rooms[j] = 1;
1992 detail::fluid_dae_active() = &S;
1994 const integer N =
static_cast<integer
>(nz);
1995 const std::size_t lwork = nz * (nz + 1 + nz + 14) + 20 + 32;
1996 const std::size_t liwork = nz + 20 + 32;
1997 std::vector<doublereal> y(z.begin(), z.end()), work(lwork, 0.0), rpar(1, 0.0);
1998 std::vector<integer> iwork(liwork, 0), ipar(1, 0);
1999 doublereal rtol = tol, atol = tol * 1e-2, x = tcur, xend = tend, h = 1e-6;
2003 integer itol = 0, ifcn = 0, ijac = 0, mljac = N, mujac = N, idfx = 0;
2004 integer imas = 1, mlmas = 0, mumas = 0, iout = 1, idid = 0;
2005 integer lw =
static_cast<integer
>(lwork), liw =
static_cast<integer
>(liwork);
2007 rodas_(
const_cast<integer*
>(&N), (U_fp)detail::fluid_dae_fcn, &ifcn, &x, y.data(),
2008 &xend, &h, &rtol, &atol, &itol,
2009 (U_fp)detail::fluid_dae_jac, &ijac, &mljac, &mujac,
2010 (U_fp)detail::fluid_dae_dfx, &idfx,
2011 (U_fp)detail::fluid_dae_mas, &imas, &mlmas, &mumas,
2012 (U_fp)detail::fluid_dae_solout, &iout,
2013 work.data(), &lw, iwork.data(), &liw, rpar.data(), ipar.data(), &idid);
2015 detail::fluid_dae_active() =
nullptr;
2018 detail::fluid_dae_active() =
nullptr;
2019 if (idid != 1 && idid != 2 && S.hit < 0)
2020 throw NumericError(
"fluid_dae_integrate_hybrid: RODAS returned idid=" +
2021 std::to_string(
static_cast<long>(idid)));
2022 if (S.hit < 0)
break;
2024 const std::size_t c =
static_cast<std::size_t
>(S.hit);
2027 const std::vector<double> xh(z.begin(), z.begin() + n);
2028 const std::vector<double> sgh(z.begin() + S.o_sg, z.begin() + S.o_sg + nstg);
2029 const bool was_active =
2030 std::find(S.active.begin(), S.active.end(), c) != S.active.end();
2032 S.active.erase(std::remove(S.active.begin(), S.active.end(), c), S.active.end());
2034 z[S.o_m + c] = S.inert[c];
2037 std::vector<std::size_t> trial = S.active;
2039 std::sort(trial.begin(), trial.end());
2040 std::vector<double> m0(trial.size(), 0.0);
2041 for (std::size_t a = 0; a < trial.size(); ++a)
2042 m0[a] = std::find(S.active.begin(), S.active.end(), trial[a]) != S.active.end()
2043 ? z[S.o_m + trial[a]]
2044 : (con.
staged[trial[a]] ? 0.0 : 1.0);
2047 terms, gates, stg, con, trial, xh, sgh, closure, m0, ok);
2049 for (std::size_t a = 0; a < trial.size(); ++a)
2050 if (!con.
staged[trial[a]] && mm[a] > 1.0 + 1e-9) feasible =
false;
2056 for (std::size_t a = 0; a < trial.size(); ++a) z[S.o_m + trial[a]] = mm[a];
2059 if (tcur >= tend - 1e-12)
break;
2064 while (S.out.size() < grid.size()) S.out.push_back(z);
2065 std::vector<std::vector<double> > out;
2066 for (std::size_t i = 0; i < S.out.size(); ++i)
2067 out.push_back(std::vector<double>(S.out[i].begin(), S.out[i].begin() + n));
2082 const std::vector<double>* rates =
nullptr) {
2084 const std::size_t K = M ? terms.
class_block[0].size() : 0;
2094 for (std::size_t i = 0; i < M; ++i)
2095 for (std::size_t k = 0; k < K; ++k) {
2096 const std::vector<std::size_t>& blk = terms.
class_block[i][k];
2097 if (blk.
empty())
continue;
2098 double q = 0.0, gg = 0.0;
2099 for (std::size_t s : blk) { q += x[s]; gg += g[s]; }
2103 for (std::size_t e = 0; e < r.size(); ++e)
2130 const FluidMomentTerms& terms,
2131 const FluidOptions&
opt) {
2132 std::vector<double> x0 =
opt.init_sol.empty()
2133 ? detail::fluid_default_initsol(
sn, terms.sys.layout)
2135 if (x0.size() != terms.nstate)
2136 throw InputError(
"solver_fluid_dae: the initial condition has " +
2137 std::to_string(x0.size()) +
" entries where the closure state has " +
2138 std::to_string(terms.nstate));
2157 const std::size_t n = terms.
nstate;
2159 const std::size_t K = M ? terms.
class_block[0].size() : 0;
2163 "solver_fluid_dae: the dae method solves a " + std::to_string(n) +
2164 "-unknown algebraic system with a finite-difference Jacobian, above the limit of " +
2166 " set by options.config.dae_maxstate. Raise it, or use options.method='minnormal' for "
2167 "the same closure by successive substitution.");
2172 for (std::size_t i = 0; i < M; ++i)
2176 "solver_fluid_dae: the dae method closes on the per-station variance only, and "
2177 "DPS/GPS close on the covariance between their class coordinates. Use "
2178 "options.method='minnormal'.");
2188 const std::size_t ncon = con.
b.size();
2193 for (std::size_t c = 0; c < cons.
C.
rows(); ++c)
2194 for (std::size_t e = 0; e < terms.
D.
cols(); ++e) {
2196 for (std::size_t s = 0; s < n; ++s) acc += cons.
C(c, s) * terms.
D(s, e);
2197 if (std::fabs(acc) > 1e-7)
2199 "solver_fluid_dae: the event set does not conserve a closed chain");
2203 sysd.
terms = &terms;
2207 sysd.
gates = &gates;
2217 mo.
timespan_end = std::numeric_limits<double>::infinity();
2219 std::vector<double> xcur(seed.
xvec.begin(), seed.
xvec.end());
2220 xcur.resize(n, 0.0);
2226 std::vector<double> s2seed(sysd.
cidx.size(), 0.0);
2227 for (std::size_t j = 0; j < sysd.
cidx.size(); ++j) {
2230 s2seed[j] = std::max(1e-8, acc);
2233 const double tol = (
opt.tol > 0.0 && std::isfinite(
opt.tol)) ?
opt.tol : 1e-8;
2235 std::vector<double> u, sgv(stg.
n, 0.0), mult;
2256 bool seeded_from_failure =
false;
2262 std::vector<double> x_ok = xcur, s2_ok = s2seed;
2263 bool have_best =
false, best_feasible =
false;
2264 std::vector<double> best_x, best_sg, best_s2, best_mult;
2265 std::vector<std::size_t> best_active;
2267 bool clamped =
false, best_clamped =
false;
2268 const std::size_t aset_max = std::max<std::size_t>(4, 2 * ncon + 2);
2269 for (std::size_t aset = 0; aset < aset_max; ++aset) {
2270 bool has_clamp =
false;
2271 for (std::size_t k = 0; k < sysd.
active.size(); ++k)
2277 std::vector<double> sg0(stg.
n, 0.0);
2278 for (std::size_t k = 0; k < sysd.
active.size(); ++k) {
2279 const std::size_t c = sysd.
active[k];
2280 if (!con.
staged[c])
continue;
2282 for (std::size_t sIdx = 0; sIdx < n; ++sIdx) cur += con.
A(c, sIdx) * xcur[sIdx];
2283 const double excess = std::max(0.0, cur - con.
b[c]);
2284 std::size_t cnt = 0;
2285 for (std::size_t j = 0; j < stg.
n; ++j)
if (stg.
gated_by[c][j]) ++cnt;
2287 for (std::size_t j = 0; j < stg.
n; ++j)
2288 if (stg.
gated_by[c][j]) sg0[j] = excess /
static_cast<double>(cnt);
2290 std::vector<double> u0;
2291 u0.assign(xcur.begin(), xcur.end());
2292 u0.insert(u0.end(), sg0.begin(), sg0.end());
2293 u0.insert(u0.end(), s2seed.begin(), s2seed.end());
2294 u0.insert(u0.end(), sysd.
active.size(), 1.0);
2306 bool solved =
false;
2314 bool nohyp_typed =
false;
2315 std::string nohyp_what;
2316 for (
int attempt = 0; attempt < (has_clamp ? 2 : 1); ++attempt) {
2323 if (attempt == 1 || !has_clamp) {
2326 nohyp_what = e.what();
2330 }
catch (
const std::exception& e) {
2331 if (attempt == 1 || !has_clamp) {
2333 nohyp_what = e.what();
2338 clamped = attempt == 1;
2349 std::vector<std::size_t> cand;
2350 for (std::size_t c = 0; c < ncon; ++c) {
2355 for (std::size_t s = 0; s < n; ++s) {
2356 if (con.
A(c, s) > 0.0 && !std::isfinite(xcur[s])) bad =
true;
2357 acc += con.
A(c, s) * xcur[s];
2359 if (bad || acc > con.
b[c] + std::max(1e-9, tol)) cand.push_back(c);
2361 if (seeded_from_failure || cand.empty()) {
2365 for (std::size_t ci = 0; ci < cand.size(); ++ci) {
2366 const std::size_t c = cand[ci];
2368 std::size_t cnt = 0;
2369 for (std::size_t s = 0; s < n; ++s) {
2370 val += con.
A(c, s) * xcur[s];
2371 if (con.
A(c, s) > 0.0) ++cnt;
2373 if (!cnt || !(con.
b[c] > 0.0))
continue;
2374 if (std::isfinite(val) && val > con.
b[c]) {
2375 const double f = con.
b[c] / val;
2376 for (std::size_t s = 0; s < n; ++s)
2377 if (con.
A(c, s) > 0.0) xcur[s] *= f;
2378 }
else if (!std::isfinite(val)) {
2379 for (std::size_t s = 0; s < n; ++s)
2380 if (con.
A(c, s) > 0.0) xcur[s] = con.
b[c] /
static_cast<double>(cnt);
2383 for (std::size_t s = 0; s < n; ++s)
2384 if (!std::isfinite(xcur[s])) xcur[s] = 0.0;
2385 for (std::size_t ci = 0; ci < cand.size(); ++ci) sysd.
active.push_back(cand[ci]);
2389 seeded_from_failure =
true;
2392 if (!solved)
throw NumericError(
"solver_fluid_dae: the closure could not be evaluated");
2393 xcur.assign(u.begin(), u.begin() + n);
2394 sgv.assign(u.begin() + n, u.begin() + n + stg.
n);
2395 for (std::size_t j = 0; j < sysd.
cidx.size(); ++j)
2396 s2seed[j] = std::max(0.0, u[n + stg.
n + j]);
2397 mult.assign(u.begin() + n + stg.
n + sysd.
cidx.size(), u.end());
2398 if (ncon == 0)
break;
2400 std::vector<double> slack(ncon, 0.0);
2401 bool feasible =
true;
2402 for (std::size_t c = 0; c < ncon; ++c) {
2404 for (std::size_t sIdx = 0; sIdx < n; ++sIdx) acc += con.
A(c, sIdx) * xcur[sIdx];
2405 for (std::size_t j = 0; j < stg.
n; ++j) acc += con.
As(c, j) * sgv[j];
2406 slack[c] = con.
b[c] - acc;
2407 if (slack[c] < -std::max(1e-9, tol)) feasible =
false;
2413 best_x = xcur; best_sg = sgv; best_s2 = s2seed; best_mult = mult;
2414 best_active = sysd.
active; best_info = info; best_clamped = clamped;
2415 best_feasible = feasible;
2417 std::vector<std::size_t> violated;
2418 for (std::size_t c = 0; c < ncon; ++c) {
2419 if (std::find(sysd.
active.begin(), sysd.
active.end(), c) != sysd.
active.end())
continue;
2420 if (slack[c] < -std::max(1e-9, tol)) violated.push_back(c);
2427 std::vector<std::size_t> released;
2428 for (std::size_t k = 0; k < sysd.
active.size(); ++k) {
2429 const std::size_t c = sysd.
active[k];
2432 std::size_t rooms = 0;
2433 for (std::size_t j = 0; j < stg.
n; ++j)
2434 if (stg.
gated_by[c][j]) { held += sgv[j]; ++rooms; }
2435 if (!rooms || held < 1e-9) released.push_back(c);
2436 }
else if (mult[k] > 1.0 + std::max(1e-9, tol)) {
2437 released.push_back(c);
2447 if (released.empty())
break;
2449 if (violated.empty() && released.empty())
break;
2450 std::vector<std::size_t> next;
2451 for (std::size_t a : sysd.
active)
2452 if (std::find(released.begin(), released.end(), a) == released.end()) next.push_back(a);
2453 for (std::size_t v : violated) next.push_back(v);
2454 std::sort(next.begin(), next.end());
2455 next.erase(std::unique(next.begin(), next.end()), next.end());
2462 xcur = best_x; sgv = best_sg; s2seed = best_s2; mult = best_mult;
2463 sysd.
active = best_active; info = best_info; clamped = best_clamped;
2470 "solver_fluid_dae: no fixed point of the closure satisfies every cap. The closure "
2471 "wants more jobs there than the cap allows and no admission multiplier holds it. "
2472 "Use SolverCTMC, SolverJMT, SolverSSA or SolverLDES for this model.");
2477 std::vector<double> x = xcur;
2479 cl.
sigma2.assign(M, 0.0);
2481 for (std::size_t j = 0; j < sysd.
cidx.size(); ++j)
2482 cl.
sigma2[sysd.
cidx[j]] = std::max(0.0, u[n + stg.
n + j]);
2495 if (std::isfinite(
opt.timespan_end) &&
opt.timespan_end > 0.0) {
2496 const std::vector<double> x0 = detail::fluid_dae_init_state(
sn, terms,
opt);
2497 const std::vector<double> grid(1,
opt.timespan_end);
2506 const std::vector<double> zend =
2511 x.assign(zend.begin(), zend.begin() + n);
2521 const std::vector<double> r = lgf.
rin;
2542 if (!sysd.
active.empty()) {
2543 for (std::size_t i = 0; i < M; ++i) {
2545 for (std::size_t k = 0; k < K; ++k) {
2548 if (std::isfinite(mu) && mu > 0.0)
2549 out.
UN(i, k) = out.
TN(i, k) / (mu * terms.
S[i]);
2553 out.
CN.assign(K, 0.0);
2554 out.
XN.assign(K, 0.0);
2555 for (std::size_t k = 0; k < K; ++k) {
2556 const std::size_t rs = (k <
sn.classes.size()) ?
sn.classes[k].refstat : 0;
2557 if (rs >= 1 && rs <= M) out.
XN[k] = out.
TN(rs - 1, k);
2559 for (std::size_t i = 0; i < M; ++i) q += out.
QN(i, k);
2560 if (out.
XN[k] > 0.0) out.
CN[k] = q / out.
XN[k];
2574 for (std::size_t i = 0; i < M; ++i)
2575 for (std::size_t k = 0; k < K; ++k) {
2576 const std::vector<std::size_t>& blk = terms.
class_block[i][k];
2578 for (std::size_t a = 0; a < blk.
size(); ++a)
2579 for (std::size_t b = 0; b < blk.
size(); ++b) acc += Sigma(blk[a], blk[b]);
2613 std::size_t points = 101,
const std::vector<double>& out_grid = std::vector<double>(),
2615 if (!(t_end > 0.0))
throw InputError(
"solver_fluid_dae_transient: t_end must be positive");
2616 if (points < 2)
throw InputError(
"solver_fluid_dae_transient: need at least two output points");
2620 os.
timespan_end = std::numeric_limits<double>::infinity();
2642 const std::size_t
nc = terms.
cov_idx.size();
2643 const bool withcov =
nc > 0 &&
nc <= dopt.
maxcov;
2645 std::vector<double> grid = out_grid;
2647 grid.resize(points);
2648 for (std::size_t j = 0; j < points; ++j)
2649 grid[j] = t_end *
static_cast<double>(j) /
static_cast<double>(points - 1);
2652 const bool has_zero = !grid.empty() && grid.front() <= 0.0;
2653 std::vector<double> inner(grid.begin() + (has_zero ? 1 : 0), grid.end());
2655 const double tol = (
opt.tol > 0.0 && std::isfinite(
opt.tol)) ?
opt.tol : 1e-8;
2656 const std::vector<double> x0 = detail::fluid_dae_init_state(
sn, terms,
opt);
2672 if (
opt.init_qcov.rows() > 0 ||
opt.init_qcov.cols() > 0) {
2674 const std::size_t K0 = M0 ? terms.
class_block[0].size() : 0;
2675 const std::size_t npair = M0 * K0;
2676 if (
opt.init_qcov.rows() != npair ||
opt.init_qcov.cols() != npair)
2677 throw InputError(
"solver_fluid_dae_transient: config.init_qcov is " +
2678 std::to_string(
opt.init_qcov.rows()) +
"x" +
2679 std::to_string(
opt.init_qcov.cols()) +
" but the model has " +
2680 std::to_string(npair) +
2681 " station-class pairs, so the covariance must be " +
2682 std::to_string(npair) +
"x" + std::to_string(npair) +
2683 ", indexed r*" + std::to_string(M0) +
"+i.");
2684 double asym = 0.0, scale = 0.0;
2685 for (std::size_t a = 0; a < npair; ++a)
2686 for (std::size_t b = 0; b < npair; ++b) {
2687 const double d =
opt.init_qcov(a, b) -
opt.init_qcov(b, a);
2688 asym = std::max(asym, std::abs(d));
2689 scale = std::max(scale, std::abs(
opt.init_qcov(a, b)));
2691 if (asym > 1e-6 * std::max(1.0, scale))
2692 throw InputError(
"solver_fluid_dae_transient: config.init_qcov must be symmetric.");
2700 if (!withcov || !con.
empty())
2701 std::cerr <<
"[LINE] Warning: config.init_qcov was supplied, but the transient "
2702 "covariance is held at its stationary value on this model (above "
2703 "config.dae_maxcov, or under a finite-capacity cap, where the hybrid "
2704 "run holds it across every located crossing), so the carried entry "
2705 "covariance cannot be used.\n";
2709 const std::size_t nz = terms.
nstate + (withcov ?
nc *
nc : 0);
2710 std::vector<std::vector<double> > xs;
2712 std::vector<double> z0(nz, 0.0);
2713 for (std::size_t i = 0; i < terms.
nstate; ++i) z0[i] = x0[i];
2714 if (withcov && Sigma0.
rows() == terms.
nstate)
2715 for (std::size_t a = 0; a <
nc; ++a)
2716 for (std::size_t b = 0; b <
nc; ++b)
2721 if (!inner.empty()) {
2727 const std::vector<std::vector<double> > got =
2733 xs.insert(xs.end(), got.begin(), got.end());
2736 std::vector<FluidTranPoint> out;
2737 out.reserve(xs.size());
2739 const std::size_t K = M ? terms.
class_block[0].size() : 0;
2740 for (std::size_t j = 0; j < xs.size(); ++j) {
2741 std::vector<double> x(xs[j].begin(), xs[j].begin() + terms.
nstate);
2746 if (v < 0.0) v = 0.0;
2753 for (std::size_t a = 0; a <
nc; ++a)
2754 for (std::size_t b = 0; b <
nc; ++b)
2756 0.5 * (xs[j][terms.
nstate + a *
nc + b]
2757 + xs[j][terms.
nstate + b *
nc + a]);
2758 cl.
sigma2.assign(M, 0.0);
2761 for (std::size_t i = 0; i < s2.size(); ++i) cl.
sigma2[i] = s2[i];
2767 detail::fluid_snap_all(pt.
QN, pt.
UN, R, pt.
TN);
2774 for (std::size_t i = 0; i < M; ++i)
2775 for (std::size_t k = 0; k < K; ++k) {
2776 const std::vector<std::size_t>& bi = terms.
class_block[i][k];
2777 for (std::size_t j = 0; j < M; ++j)
2778 for (std::size_t l = 0; l < K; ++l) {
2779 const std::vector<std::size_t>& bj = terms.
class_block[j][l];
2781 for (std::size_t a = 0; a < bi.size(); ++a)
2782 for (std::size_t b = 0; b < bj.size(); ++b)
2783 acc += Sigma(bi[a], bj[b]);
2784 pt.
QCov(k * M + i, l * M + j) = acc;
2786 pt.
QVar(i, k) = std::max(0.0, pt.
QCov(k * M + i, k * M + i));
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 ...
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< 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 >(), const Matrix< double > &init_sigma=Matrix< double >(0, 0, 0.0))
Integrate the closure as an index-1 DAE, with RODAS, and report the state at every point of grid.
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...
Matrix< double > fluid_dae_lift_qcov(const Matrix< double > &C0sc, const std::vector< double > &x0, const FluidMomentTerms &terms)
Lift a (station, class) covariance onto the closing-state phase layout: the inverse of the aggregatio...
DropStrategy
Blocking and loss rules, with the values of MATLAB DropStrategy.
Conservation laws of a layered queueing network, enumerated from its structure.
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 > QCov
The same second moment as a FULL (M*K)-by-(M*K) covariance, indexed ir = r*M + i, empty wherever QVar...
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