5#ifndef LINE_SOLVERS_FLUID_FLUID_STIFF_H
6#define LINE_SOLVERS_FLUID_FLUID_STIFF_H
154 for (std::size_t i = 0; i < n; ++i) {
156 for (std::size_t j = 0; j < n; ++j)
157 if (j != i) s += W(i, j);
171inline std::vector<FluidEvent> fluid_events_from_generator(
172 const Matrix<double>& W,
const std::vector<std::size_t>& state_map) {
173 std::vector<FluidEvent> out;
174 for (std::size_t i = 0; i < W.
rows(); ++i)
175 for (std::size_t j = 0; j < W.
cols(); ++j) {
176 if (i == j || !(W(i, j) > 0.0))
continue;
178 e.minus = state_map[i];
179 e.plus = state_map[j];
180 e.event_idx = state_map[i];
181 e.rate_base = W(i, j);
245template <
class T,
class Opt>
247 if (!
opt.hide_immediate)
return false;
248 for (std::size_t i = 0; i <
sn.nodes.size(); ++i)
256 const std::size_t ne = sys.
events.size();
260 for (std::size_t i = 0; i < n; ++i) out.
state_map[i] = i;
262 for (std::size_t e = 0; e < ne; ++e) out.
emap(e, e) = 1.0;
264 for (std::size_t i = 0; i < n; ++i) out.
absorb(i, i) = 1.0;
269 std::vector<double> exit_total(n, 0.0);
272 std::vector<bool> is_imm(n,
false);
273 for (std::size_t f = 0; f < n; ++f)
274 if (exit_total[f] >= imm_tol) {
285 "an event draws its rate from a coordinate other than the one it removes mass "
286 "from, so the drift is not a generator and cannot be complemented";
293 for (std::size_t f = 0; f < n; ++f) {
294 if (!is_imm[f])
continue;
300 if (e.
plus != f) leaves =
true;
302 if (!(tot > 0.0) || !leaves) is_imm[f] =
false;
305 std::vector<std::size_t> Fidx, Sidx;
306 for (std::size_t i = 0; i < n; ++i) (is_imm[i] ? Fidx : Sidx).push_back(i);
307 if (Fidx.empty() || Sidx.size() <= 1) {
309 "every fluid coordinate but at most one sources an immediate transition, so the "
310 "complement would be a trivial system; integrating the original stiff drift instead";
313 const std::size_t nF = Fidx.size(), nS = Sidx.size();
314 std::vector<std::size_t> posF(n, 0), posS(n, 0);
315 for (std::size_t a = 0; a < nF; ++a) posF[Fidx[a]] = a;
316 for (std::size_t b = 0; b < nS; ++b) posS[Sidx[b]] = b;
322 Matrix<double> PFF(nF, nF, 0.0), PFS(nF, nS, 0.0), cnt(nF, ne, 0.0);
323 for (std::size_t a = 0; a < nF; ++a) {
324 const std::size_t f = Fidx[a];
328 for (std::size_t o = 0; o < ne; ++o) {
333 if (is_imm[e.
plus]) PFF(a, posF[e.
plus]) += p;
334 else PFS(a, posS[e.
plus]) += p;
341 for (std::size_t a = 0; a < nF; ++a)
342 for (std::size_t b = 0; b < nF; ++b)
343 ImP(a, b) = (a == b ? 1.0 : 0.0) - PFF(a, b);
347 }
catch (
const std::exception& ex) {
348 out.
fallback = std::string(
"the immediate coordinates form a closed cycle, so they have "
349 "no absorption distribution: ") + ex.what();
354 for (std::size_t a = 0; a < nF; ++a) {
356 for (std::size_t b = 0; b < nS; ++b) {
357 if (!std::isfinite(Aabs(a, b))) rowsum = std::numeric_limits<double>::quiet_NaN();
358 rowsum += Aabs(a, b);
360 if (!(rowsum > 0.5)) {
362 "stochastic complementation produced no absorption distribution, which is what a "
363 "closed set of immediate coordinates looks like once it has been solved";
372 std::vector<FluidEvent> kept_dep, kept_other;
373 std::vector<std::vector<double>> emap_dep, emap_other;
374 for (std::size_t o = 0; o < ne; ++o) {
378 std::vector<FluidEvent>& bucket = is_dep ? kept_dep : kept_other;
379 std::vector<std::vector<double>>& rows = is_dep ? emap_dep : emap_other;
380 if (!is_imm[e.
plus]) {
382 std::vector<double> row(ne, 0.0);
390 const std::size_t a = posF[e.
plus];
391 for (std::size_t b = 0; b < nS; ++b) {
392 if (!(Aabs(a, b) > 0.0))
continue;
396 bucket.push_back(ce);
401 std::vector<double> row(ne, 0.0);
402 for (std::size_t oo = 0; oo < ne; ++oo) row[oo] = expcnt(a, oo);
413 const std::size_t nnew = out.
sys.
events.size();
415 for (std::size_t e = 0; e < emap_dep.size(); ++e)
416 for (std::size_t o = 0; o < ne; ++o)
417 if (emap_dep[e][o] != 0.0) out.
emap(e, o) = emap_dep[e][o];
418 for (std::size_t e = 0; e < emap_other.size(); ++e)
419 for (std::size_t o = 0; o < ne; ++o)
420 if (emap_other[e][o] != 0.0) out.
emap(emap_dep.size() + e, o) = emap_other[e][o];
423 for (std::size_t b = 0; b < nS; ++b) out.
absorb(Sidx[b], Sidx[b]) = 1.0;
424 for (std::size_t a = 0; a < nF; ++a)
425 for (std::size_t b = 0; b < nS; ++b)
426 if (Aabs(a, b) > 0.0) out.
absorb(Fidx[a], Sidx[b]) = Aabs(a, b);
448 if (!std::is_same<T, double>::value)
450 "fluid_eliminate_immediate: the fluid solver integrates its drift with LSODA, whose "
451 "coefficients assume double precision; rerun with --arith double");
492 const std::size_t n = W.
rows();
494 throw InputError(
"fluid_eliminate_immediate_matrix: the generator is not square");
499 for (std::size_t i = 0; i < n; ++i) out.
state_map[i] = i;
501 std::vector<std::size_t> timed;
502 std::size_t n_imm = 0;
503 for (std::size_t i = 0; i < n; ++i) {
505 for (std::size_t j = 0; j < n; ++j)
507 if (mx >= imm_tol) ++n_imm;
508 else timed.push_back(i);
510 if (n_imm == 0)
return out;
511 if (timed.size() <= 1) {
513 "at most one state of the generator is timed, so the complement would be a trivial "
514 "system; the original generator is returned unreduced";
520 for (std::size_t i = 0; i < S.
rows(); ++i)
521 for (std::size_t j = 0; j < S.
cols(); ++j)
524 "stochastic complementation produced non-finite rates, which is what a "
525 "singular immediate block looks like once it has been solved";
531 }
catch (
const std::exception& ex) {
532 out.
fallback = std::string(
"stochastic complementation failed: ") + ex.what();
558 std::function<bool(
const double&,
const std::vector<double>&)>
step_stop;
579 const std::function<
void(
double,
const double*,
double*)>& f,
double t0,
double t1,
581 if (!(
opt.solver ==
"default" ||
opt.solver ==
"rosenbrock4"))
583 "fluid_ode_solve_stiff: the '" +
opt.solver +
584 "' integrator is a MATLAB solver handle this port does not carry; the stiff arm here "
585 "is one four-stage L-stable Rosenbrock method (util/ode.h), asked for as 'default'");
586 if (y0.empty())
throw InputError(
"fluid_ode_solve_stiff: the initial state is empty");
589 "fluid_ode_solve_stiff: the horizon must be positive; the fluid iteration marches "
590 "forwards and a reversed range is a caller error rather than a backwards solve");
599 const std::size_t n = y0.size();
602 const auto g = [&f, n](
const double& t,
const std::vector<double>& y) {
603 std::vector<double> dy(n, 0.0);
604 f(t, y.data(), dy.data());
609 for (std::size_t i = 0; i < sol.
y.size(); ++i)
610 for (std::size_t j = 0; j < sol.
y[i].size(); ++j)
611 if (sol.
y[i][j] < 0.0) sol.
y[i][j] = 0.0;
662 const std::function<
void(
double,
const double*,
double*)>& f,
double t0,
double t1,
663 const std::vector<double>& y0,
const LsodaOptions& lopt) {
688 const std::function<
void(
double,
const double*,
double*)>& f,
689 const std::vector<double>& y0,
const std::vector<double>& grid,
694 out.
t.assign(1, grid[0]);
696 for (std::size_t j = 1; j < grid.size(); ++j) {
697 out.
y.push_back(grid[j] == grid[j - 1]
700 out.
t.push_back(grid[j]);
702 out.
method =
"rosenbrock4";
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
The exception types the port throws.
The fluid drift: a port of solver_fluid_odes.m and the ode_jumps_new / ode_rate_base / ode_rates_clos...
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
LSODA: the LINE-facing wrapper over the vendored solver in third_party/lsoda.hpp.
Dense matrix and non-owning view.
std::vector< double > fluid_integrate_leg(const std::function< void(double, const double *, double *)> &f, double t0, double t1, const std::vector< double > &y0, const LsodaOptions &lopt)
One integration leg, with the reference's retry on a failed solve.
double fluid_immediate_state_tol()
LsodaOptions fluid_lsoda(const LsodaOptions &lopt)
The step budget LSODA gets on a fluid leg before the stiff arm takes over.
LsodaSolution fluid_integrate_grid(const std::function< void(double, const double *, double *)> &f, const std::vector< double > &y0, const std::vector< double > &grid, const LsodaOptions &lopt)
The same retry over a whole output grid, for the callers that ask LSODA for a trajectory rather than ...
FluidImmediateMatrix< T > fluid_eliminate_immediate_matrix(const Matrix< T > &W, double imm_tol=fluid_immediate_state_tol())
Port of eliminate_immediate_matrix.m: the same elimination on a generator that is already assembled.
double fluid_immediate_transition_tol()
The two thresholds, one per form of the same test.
FluidImmediateResult fluid_eliminate_immediate(const FluidOdeSystem &sys, double imm_tol=fluid_immediate_transition_tol())
bool fluid_hide_immediate(const qn::NetworkStruct< T > &sn, const Opt &opt)
Stochastic complementation of the INSTANTANEOUS coordinates of a fluid drift, the twin of ode_elimina...
OdeSolution< double > fluid_ode_solve_stiff(const std::function< void(double, const double *, double *)> &f, double t0, double t1, const std::vector< double > &y0, const FluidStiffOptions &opt=FluidStiffOptions())
Port of ode_solve_stiff.m.
StochCompResult< T > ctmc_stochcomp(const Matrix< T > &Q, const std::vector< std::size_t > &I)
Conservation laws of a layered queueing network, enumerated from its structure.
OdeSolution< T > ode_rosenbrock4(const F &f, const J &jac, const T &t0, const T &t1, const std::vector< T > &y0, const OdeOptions< T > &opt)
Integrate y' = f(t,y) from t0 to t1 with an analytic Jacobian.
LsodaSolution lsoda_integrate(const LsodaRhs &f, const std::vector< double > &y0, const std::vector< double > &t_eval, const LsodaOptions &opt=LsodaOptions())
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
A queueing network and its refreshed NetworkStruct.
Adaptive stiff ODE integrator: a four-stage Rosenbrock method of order four with an embedded order-th...
std::size_t max_steps
internal steps between output points
double atol
absolute tolerance, applied to every component
double rtol
relative tolerance, applied to every component
Result of an integration, mirroring OdeSolution in ode.h.
bool success
false when LSODA returned istate < 0
const std::vector< double > & final_state() const
std::vector< std::vector< double > > y
y[i] is the state at t[i]
std::vector< double > t
output times, t[0] = t_eval[0]
std::string method
The method in force at the end: "adams" (nonstiff) or "bdf" (stiff).
bool store_trajectory
keep every accepted point, not just the last
T atol
absolute tolerance per component
std::function< bool(const T &, const std::vector< T > &)> step_stop
A test consulted after every ACCEPTED STEP; true ends the integration there, holding that state as th...
std::size_t max_steps
abort after this many accepted steps
T rtol
relative tolerance per component
Result of an integration.
std::vector< std::vector< T > > y
y[i] is the state at t[i]
const std::vector< T > & final_state() const
std::size_t event_idx
state entry whose g(x) drives this rate
double rate_base
the model-fixed part of the rate
std::size_t nstates
length of the state vector
std::size_t n_departures
How many leading entries of events are DEPARTURES (a job completing at one block and starting at anot...
std::vector< FluidEvent > events
Controls for the stiff arm.
std::function< bool(const double &, const std::vector< double > &)> step_stop
Per-accepted-step stop; see OdeOptions::step_stop and solver_fluid.h.
std::string solver
Refuses any named MATLAB solver: see the gate below.
double rtol
the value solver_fluid.h hands LSODA, FluidOptions::tol
bool stiff
options.stiff: keep the nonnegativity projection
static constexpr double Immediate
Rate of an Immediate distribution; its mean is 1/Immediate = 1e-8.