5#ifndef LINE_SOLVERS_FLUID_FLUID_STIFF_H
6#define LINE_SOLVERS_FLUID_FLUID_STIFF_H
150 for (std::size_t i = 0; i < n; ++i) {
152 for (std::size_t j = 0; j < n; ++j)
153 if (j != i) s += W(i, j);
167inline std::vector<FluidEvent> fluid_events_from_generator(
168 const Matrix<double>& W,
const std::vector<std::size_t>& state_map) {
169 std::vector<FluidEvent> out;
170 for (std::size_t i = 0; i < W.
rows(); ++i)
171 for (std::size_t j = 0; j < W.
cols(); ++j) {
172 if (i == j || !(W(i, j) > 0.0))
continue;
174 e.minus = state_map[i];
175 e.plus = state_map[j];
176 e.event_idx = state_map[i];
177 e.rate_base = W(i, j);
241template <
class T,
class Opt>
243 if (!
opt.hide_immediate)
return false;
244 for (std::size_t i = 0; i <
sn.nodes.size(); ++i)
252 const std::size_t ne = sys.
events.size();
256 for (std::size_t i = 0; i < n; ++i) out.
state_map[i] = i;
258 for (std::size_t e = 0; e < ne; ++e) out.
emap(e, e) = 1.0;
260 for (std::size_t i = 0; i < n; ++i) out.
absorb(i, i) = 1.0;
264 std::vector<bool> is_imm(n,
false);
277 "an event draws its rate from a coordinate other than the one it removes mass "
278 "from, so the drift is not a generator and cannot be complemented";
285 for (std::size_t f = 0; f < n; ++f) {
286 if (!is_imm[f])
continue;
292 if (e.
plus != f) leaves =
true;
294 if (!(tot > 0.0) || !leaves) is_imm[f] =
false;
297 std::vector<std::size_t> Fidx, Sidx;
298 for (std::size_t i = 0; i < n; ++i) (is_imm[i] ? Fidx : Sidx).push_back(i);
299 if (Fidx.empty() || Sidx.size() <= 1) {
301 "every fluid coordinate but at most one sources an immediate transition, so the "
302 "complement would be a trivial system; integrating the original stiff drift instead";
305 const std::size_t nF = Fidx.size(), nS = Sidx.size();
306 std::vector<std::size_t> posF(n, 0), posS(n, 0);
307 for (std::size_t a = 0; a < nF; ++a) posF[Fidx[a]] = a;
308 for (std::size_t b = 0; b < nS; ++b) posS[Sidx[b]] = b;
314 Matrix<double> PFF(nF, nF, 0.0), PFS(nF, nS, 0.0), cnt(nF, ne, 0.0);
315 for (std::size_t a = 0; a < nF; ++a) {
316 const std::size_t f = Fidx[a];
320 for (std::size_t o = 0; o < ne; ++o) {
325 if (is_imm[e.
plus]) PFF(a, posF[e.
plus]) += p;
326 else PFS(a, posS[e.
plus]) += p;
333 for (std::size_t a = 0; a < nF; ++a)
334 for (std::size_t b = 0; b < nF; ++b)
335 ImP(a, b) = (a == b ? 1.0 : 0.0) - PFF(a, b);
339 }
catch (
const std::exception& ex) {
340 out.
fallback = std::string(
"the immediate coordinates form a closed cycle, so they have "
341 "no absorption distribution: ") + ex.what();
346 for (std::size_t a = 0; a < nF; ++a) {
348 for (std::size_t b = 0; b < nS; ++b) {
349 if (!std::isfinite(Aabs(a, b))) rowsum = std::numeric_limits<double>::quiet_NaN();
350 rowsum += Aabs(a, b);
352 if (!(rowsum > 0.5)) {
354 "stochastic complementation produced no absorption distribution, which is what a "
355 "closed set of immediate coordinates looks like once it has been solved";
364 std::vector<FluidEvent> kept_dep, kept_other;
365 std::vector<std::vector<double>> emap_dep, emap_other;
366 for (std::size_t o = 0; o < ne; ++o) {
370 std::vector<FluidEvent>& bucket = is_dep ? kept_dep : kept_other;
371 std::vector<std::vector<double>>& rows = is_dep ? emap_dep : emap_other;
372 if (!is_imm[e.
plus]) {
374 std::vector<double> row(ne, 0.0);
382 const std::size_t a = posF[e.
plus];
383 for (std::size_t b = 0; b < nS; ++b) {
384 if (!(Aabs(a, b) > 0.0))
continue;
388 bucket.push_back(ce);
393 std::vector<double> row(ne, 0.0);
394 for (std::size_t oo = 0; oo < ne; ++oo) row[oo] = expcnt(a, oo);
405 const std::size_t nnew = out.
sys.
events.size();
407 for (std::size_t e = 0; e < emap_dep.size(); ++e)
408 for (std::size_t o = 0; o < ne; ++o)
409 if (emap_dep[e][o] != 0.0) out.
emap(e, o) = emap_dep[e][o];
410 for (std::size_t e = 0; e < emap_other.size(); ++e)
411 for (std::size_t o = 0; o < ne; ++o)
412 if (emap_other[e][o] != 0.0) out.
emap(emap_dep.size() + e, o) = emap_other[e][o];
415 for (std::size_t b = 0; b < nS; ++b) out.
absorb(Sidx[b], Sidx[b]) = 1.0;
416 for (std::size_t a = 0; a < nF; ++a)
417 for (std::size_t b = 0; b < nS; ++b)
418 if (Aabs(a, b) > 0.0) out.
absorb(Fidx[a], Sidx[b]) = Aabs(a, b);
440 if (!std::is_same<T, double>::value)
442 "fluid_eliminate_immediate: the fluid solver integrates its drift with LSODA, whose "
443 "coefficients assume double precision; rerun with --arith double");
484 const std::size_t n = W.
rows();
486 throw InputError(
"fluid_eliminate_immediate_matrix: the generator is not square");
491 for (std::size_t i = 0; i < n; ++i) out.
state_map[i] = i;
493 std::vector<std::size_t> timed;
494 std::size_t n_imm = 0;
495 for (std::size_t i = 0; i < n; ++i) {
497 for (std::size_t j = 0; j < n; ++j)
499 if (mx >= imm_tol) ++n_imm;
500 else timed.push_back(i);
502 if (n_imm == 0)
return out;
503 if (timed.size() <= 1) {
505 "at most one state of the generator is timed, so the complement would be a trivial "
506 "system; the original generator is returned unreduced";
512 for (std::size_t i = 0; i < S.
rows(); ++i)
513 for (std::size_t j = 0; j < S.
cols(); ++j)
516 "stochastic complementation produced non-finite rates, which is what a "
517 "singular immediate block looks like once it has been solved";
523 }
catch (
const std::exception& ex) {
524 out.
fallback = std::string(
"stochastic complementation failed: ") + ex.what();
550 std::function<bool(
const double&,
const std::vector<double>&)>
step_stop;
571 const std::function<
void(
double,
const double*,
double*)>& f,
double t0,
double t1,
573 if (!(
opt.solver ==
"default" ||
opt.solver ==
"rosenbrock4"))
575 "fluid_ode_solve_stiff: the '" +
opt.solver +
576 "' integrator is a MATLAB solver handle this port does not carry; the stiff arm here "
577 "is one four-stage L-stable Rosenbrock method (util/ode.h), asked for as 'default'");
578 if (y0.empty())
throw InputError(
"fluid_ode_solve_stiff: the initial state is empty");
581 "fluid_ode_solve_stiff: the horizon must be positive; the fluid iteration marches "
582 "forwards and a reversed range is a caller error rather than a backwards solve");
591 const std::size_t n = y0.size();
594 const auto g = [&f, n](
const double& t,
const std::vector<double>& y) {
595 std::vector<double> dy(n, 0.0);
596 f(t, y.data(), dy.data());
601 for (std::size_t i = 0; i < sol.
y.size(); ++i)
602 for (std::size_t j = 0; j < sol.
y[i].size(); ++j)
603 if (sol.
y[i][j] < 0.0) sol.
y[i][j] = 0.0;
654 const std::function<
void(
double,
const double*,
double*)>& f,
double t0,
double t1,
655 const std::vector<double>& y0,
const LsodaOptions& lopt) {
680 const std::function<
void(
double,
const double*,
double*)>& f,
681 const std::vector<double>& y0,
const std::vector<double>& grid,
686 out.
t.assign(1, grid[0]);
688 for (std::size_t j = 1; j < grid.size(); ++j) {
689 out.
y.push_back(grid[j] == grid[j - 1]
692 out.
t.push_back(grid[j]);
694 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 reference's two thresholds, which do NOT agree and are not meant to.
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)
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.