5#ifndef LINE_SOLVERS_FLUID_FLUID_RUNNER_H
6#define LINE_SOLVERS_FLUID_FLUID_RUNNER_H
76 "matrix",
"fluid.matrix",
"pnorm",
"fluid.pnorm",
77 "softmin",
"fluid.softmin",
78 "statedep",
"fluid.statedep",
79 "closing",
"fluid.closing",
80 "minnormal",
"fluid.minnormal",
81 "refined",
"fluid.refined",
83 "diffusion",
"fluid.diffusion",
84 "mfq",
"fluid.mfq",
"butools",
93 "ggisgi.fluid",
"fluid.ggisgi",
"ggisgi",
94 "ggingi.tga",
"fluid.tga",
"tga",
96 "mtginf",
"fluid.mtginf",
103 if (std::find(valid.begin(), valid.end(), method) != valid.end())
return;
104 throw UnsupportedError(
"SolverFLD: the '" + method +
"' method is unsupported by this solver");
114 if (nd.
nodetype == qn::NodeType::Cache)
return true;
120bool fluid_has_dps(
const qn::NetworkStruct<T>& sn) {
121 for (
const auto& st : sn.stations)
134inline std::string fluid_unqualify(
const std::string& method) {
135 std::string m = method;
136 if (m.size() > 6 && m.compare(0, 6,
"fluid.") == 0) m = m.substr(6);
137 if (m ==
"butools" || m ==
"aoi")
return "mfq";
160bool fluid_minnormal_applicable(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt,
161 std::string& reason) {
162 const std::size_t M = sn.nstations, K = sn.nclasses;
168 for (std::size_t i = 0; i < M; ++i) {
170 for (std::size_t r = 0; r < K; ++r)
171 if (L.kic[i][r] > 1) {
172 reason =
"class " + std::to_string(r + 1) +
" has a " +
173 std::to_string(L.kic[i][r]) +
"-phase (non-Poisson) arrival process";
183 for (std::size_t nd = 0; nd < sn.nodes.size(); ++nd) {
184 if (sn.nodes[nd].nodetype != qn::NodeType::Cache)
continue;
185 const std::size_t key = nd + 1;
186 if (sn.nodeparam.count(key) == 0) {
187 reason =
"a cache uses a replacement strategy with no drift-based fluid model";
193 reason =
"a cache uses a replacement strategy with no drift-based fluid model";
199 for (std::size_t i = 0; i < M; ++i) {
206 ", which has no fluid drift branch";
213 if (L.nstates > opt.moment_maxstate) {
214 reason =
"the phase-resolved state has " + std::to_string(L.nstates) +
215 " coordinates, above the moment_maxstate limit of " +
216 std::to_string(opt.moment_maxstate);
223 if (fluid_has_time_varying_rates(opt)) {
224 reason =
"a rate schedule (nhpp_sched / rate_traj / rate_sched) makes the drift "
256bool fluid_dae_applicable(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt,
258 const FluidDaeOptions& dopt_in = FluidDaeOptions()) {
260 const std::size_t M = sn.nstations;
268 if (fluid_has_cache(sn)) {
269 reason =
"a cache model is answered by the decomposition analyzer, which has no dae route";
276 for (std::size_t i = 0; i < M; ++i) {
280 ", whose share closes on the covariance between its class coordinates "
281 "rather than on the station variance";
289 if (L.nstates > dopt.maxstate) {
290 reason =
"the phase-resolved state has " + std::to_string(L.nstates) +
291 " coordinates, above the dae_maxstate limit of " +
292 std::to_string(dopt.maxstate);
317std::string fluid_resolve_method(
const qn::NetworkStruct<T>& sn,
const std::string& method,
318 const FluidOptions& opt, std::string& reason) {
319 const std::string m = fluid_unqualify(method);
330 if (m ==
"rmf" && !fluid_has_cache(sn)) {
331 reason =
"the model has no cache node, so the refined mean field has nothing to decompose";
334 if (m !=
"default")
return m;
335 if (fluid_has_cache(sn)) {
336 reason =
"the model has cache nodes";
351 if (fluid_dae_applicable(sn, opt, dae_why)) {
352 reason = sn.regions.empty() ?
"the model has a binding finite buffer"
353 :
"the model has a finite capacity region";
357 if (fluid_minnormal_applicable(sn, opt, reason))
return "minnormal";
358 return fluid_has_dps(sn) ?
"closing" :
"matrix";
363std::string fluid_resolve_method(
const qn::NetworkStruct<T>& sn,
const std::string& method,
364 const FluidOptions& opt = FluidOptions()) {
366 return fluid_resolve_method(sn, method, opt, reason);
379void fluid_check_load_dependence(
const qn::NetworkStruct<T>& sn,
const std::string& m) {
380 if (m ==
"closing" || m ==
"minnormal" || m ==
"refined" || m ==
"dae")
return;
381 for (std::size_t i = 0; i < sn.stations.size(); ++i)
382 for (std::size_t k = 0; k < sn.stations[i].lldscaling.size(); ++k)
383 if (std::fabs(num_traits<T>::to_double(sn.stations[i].lldscaling[k]) - 1.0) >
386 "solver_fluid_run_analyzer: this model uses load dependence (setLoadDependence), which "
389 "' method does not evaluate. Use method 'closing' for the mean-field answer, or "
390 "'minnormal'/'refined' for the moment-closure correction");
417std::string fluid_forkjoin_supports(
const qn::NetworkStruct<T>& sn,
const std::string& m) {
418 if (m !=
"dae" && m !=
"fluid.dae")
return std::string();
419 if (!sn.has_fork())
return std::string();
420 bool any_open =
false;
421 for (std::size_t r = 0; r < sn.classes.size(); ++r)
422 if (!std::isfinite(sn.classes[r].population)) any_open =
true;
423 if (!any_open)
return std::string();
424 return "solver_fluid_run_analyzer: the dae method has no route through the fork-join fixed "
425 "point on an OPEN model: the transform hands the inner solve a mixed network whose "
426 "auxiliary open classes the DAE form carries no unknowns for. Use method 'minnormal', "
427 "which is the same closure and does run that fixed point";
448void fluid_check_dae(
const qn::NetworkStruct<T>& sn,
const std::string& m) {
449 if (m !=
"dae")
return;
450 if (fluid_has_cache(sn))
452 "solver_fluid_run_analyzer: the dae method does not support caching stations: a cache model is "
453 "solved by decomposition, so it has no single drift to constrain. Use "
454 "method 'minnormal' for the same closure, or 'rmf'");
455 const std::string fj = fluid_forkjoin_supports(sn, m);
475void fluid_check_finite_capacity(
const qn::NetworkStruct<T>& sn,
const std::string& m) {
476 if (m ==
"dae" || m ==
"fluid.dae" || m ==
"mol" || m ==
"fluid.mol")
return;
512 qn::NetworkStruct<T>* sn_out =
nullptr,
513 qn::NetworkStruct<T>* refreshed_out =
nullptr,
514 solvers::CacheMetrics<T>* cache_out =
nullptr);
529mva::MvaSolution<T> fluid_as_mva_solution(
const FluidSolution& f) {
530 mva::MvaSolution<T> s;
531 const std::size_t M = f.QN.rows(), K = f.QN.cols();
532 const T zero = num_traits<T>::from_int(0);
537 for (std::size_t i = 0; i < M; ++i)
538 for (std::size_t k = 0; k < K; ++k) {
539 s.Q(i, k) = num_traits<T>::from_double(f.QN(i, k));
540 s.U(i, k) = num_traits<T>::from_double(f.UN(i, k));
541 s.R(i, k) = num_traits<T>::from_double(f.RN(i, k));
542 s.Tp(i, k) = num_traits<T>::from_double(f.TN(i, k));
544 s.C.reserve(f.CN.size());
545 for (
double v : f.CN) s.C.push_back(num_traits<T>::from_double(v));
546 s.X.reserve(f.XN.size());
547 for (
double v : f.XN) s.X.push_back(num_traits<T>::from_double(v));
549 s.iter =
static_cast<int>(f.iters);
558FluidSolution fluid_from_mva_solution(
const mva::MvaSolution<T>& s,
const std::string& method) {
560 const std::size_t M = s.Q.rows(), K = s.Q.cols();
561 f.QN = Matrix<double>(M, K, 0.0);
562 f.UN = Matrix<double>(M, K, 0.0);
563 f.RN = Matrix<double>(M, K, 0.0);
564 f.TN = Matrix<double>(M, K, 0.0);
565 for (std::size_t i = 0; i < M; ++i)
566 for (std::size_t k = 0; k < K; ++k) {
567 f.QN(i, k) = num_traits<T>::to_double(s.Q(i, k));
568 f.UN(i, k) = num_traits<T>::to_double(s.U(i, k));
569 f.RN(i, k) = num_traits<T>::to_double(s.R(i, k));
570 f.TN(i, k) = num_traits<T>::to_double(s.Tp(i, k));
572 f.CN.reserve(s.C.size());
573 for (
const T& v : s.C) f.CN.push_back(num_traits<T>::to_double(v));
574 f.XN.reserve(s.X.size());
575 for (
const T& v : s.X) f.XN.push_back(num_traits<T>::to_double(v));
576 f.iters =
static_cast<std::size_t
>(s.iter < 0 ? 0 : s.iter);
582inline Matrix<double> fluid_leading_rows(
const Matrix<double>& m, std::size_t M) {
583 if (m.rows() <= M)
return m;
584 Matrix<double> out(M, m.cols(), 0.0);
585 for (std::size_t i = 0; i < M; ++i)
586 for (std::size_t k = 0; k < m.cols(); ++k) out(i, k) = m(i, k);
601FluidSolution fluid_fork_join_run(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt) {
603 std::vector<T> lam(tr.V.classes.size() + 1,
605 mva::MvaOptions mopt;
606 mopt.method = opt.method;
608 mopt.iter_tol = opt.iter_tol;
609 mopt.iter_max = opt.iter_max;
610 mopt.fork_join = opt.fork_join;
611 mopt.base_has_fork =
true;
612 FluidOptions inner = opt;
613 const mva::MvaSolution<T> merged =
617 FluidSolution out = fluid_from_mva_solution<T>(merged, opt.method);
623 out.QN = detail::fluid_leading_rows(out.QN, sn.nstations);
624 out.UN = detail::fluid_leading_rows(out.UN, sn.nstations);
625 out.RN = detail::fluid_leading_rows(out.RN, sn.nstations);
626 out.TN = detail::fluid_leading_rows(out.TN, sn.nstations);
636inline bool fluid_is_petri_net(
const qn::NetworkStruct<T>& sn) {
637 for (std::size_t i = 0; i < sn.nodes.size(); ++i)
652inline FluidSolution fluid_petri_run(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt) {
653 const std::string m = (opt.method.rfind(
"fluid.", 0) == 0) ? opt.method.substr(6) : opt.method;
654 if (!(m ==
"default" || m ==
"dae"))
656 "SolverFluid: method '" + opt.method +
657 "' cannot solve a Petri net: its conserved quantities are P-invariants rather than "
658 "chain populations, an immediate transition is an algebraic FLOW rather than an "
659 "event with a rate, and a bounded place is a linear inequality on the marking. Only "
660 "'dae' states those as equations; every other fluid method builds its drift from the "
661 "station/class/phase encoding, where a Place contributes no coordinate at all, and "
662 "would integrate the net as an empty model and report zeros without a warning");
670 out.iters = ps.iters;
672 const std::size_t M = ps.QN.rows(), K = ps.QN.cols();
673 out.CN.assign(K, 0.0);
674 out.XN.assign(K, 0.0);
675 for (std::size_t k = 0; k < K; ++k) {
676 double q = 0.0, x = 0.0;
677 for (std::size_t i = 0; i < M; ++i) {
679 x = std::max(x, ps.TN(i, k));
682 out.CN[k] = (x > 1e-14) ? q / x : 0.0;
686 out.has_moments =
true;
687 out.moments.Sigma = ps.Sigma;
688 out.moments.QVar = ps.QVar;
689 out.moments.QStd = ps.QStd;
701 if (sn_out) *sn_out =
sn;
709 if (detail::fluid_is_petri_net(
sn))
return detail::fluid_petri_run(
sn,
opt);
714 const std::string m = detail::fluid_resolve_method(
sn,
opt.method,
opt, why);
719 detail::fluid_check_load_dependence(
sn, m);
720 detail::fluid_check_dae(
sn, m);
721 detail::fluid_check_finite_capacity(
sn, m);
728 return detail::fluid_fork_join_run(
sn, fo);
749 if (m ==
"rmf" || (m ==
"minnormal" && detail::fluid_has_cache(
sn))) {
757 if (refreshed_out) *refreshed_out = cq.
refreshed;
759 }
else if (m ==
"closing" || m ==
"statedep" || m ==
"softmin" || m ==
"tbi") {
762 }
else if (m ==
"minnormal" || m ==
"refined") {
790 moments_solve, sn_out);
792 std::string dae_reason;
794 if (detail::fluid_dae_applicable(
sn, o, dae_reason)) {
807 o.
method = detail::fluid_has_dps(
sn) ?
"closing" :
"matrix";
809 detail::fluid_check_load_dependence(
sn, o.
method);
815 closing_solve, sn_out);
816 detail::fluid_analyzer_correct(
sn, out.
QN, out.
UN, out.
RN, out.
TN);
817 detail::fluid_snap_all(out.
QN, out.
UN, out.
RN, out.
TN);
821 }
else if (m ==
"dae") {
834 }
else if (m ==
"kp") {
836 }
else if (detail::fluid_qsys_handles(m)) {
847 detail::fluid_analyzer_correct(
sn, out.
QN, out.
UN, out.
RN, out.
TN);
848 detail::fluid_snap_all(out.
QN, out.
UN, out.
RN, out.
TN);
870 std::size_t points = 101) {
875 if (detail::fluid_qsys_handles(detail::fluid_unqualify(
opt.method))) {
877 o.
method = detail::fluid_unqualify(
opt.method);
879 std::vector<FluidTranPoint> traj;
883 if (detail::fluid_unqualify(
opt.method) !=
"dae")
886 detail::fluid_check_dae(
sn, std::string(
"dae"));
910 std::size_t points = 201) {
915 "solver_fluid_cdf_respt: the '" + r.
method +
916 "' method reports no fluid state vector to mark a job in, so it has no passage time; "
917 "use 'closing', 'matrix' or a moment closure");
918 std::vector<std::vector<FluidPassage> > RD(
920 for (std::size_t i = 0; i < snr.
nstations; ++i) {
921 if (snr.
stations[i].nodetype == qn::NodeType::Source)
continue;
922 for (std::size_t c = 0; c < snr.
nclasses; ++c) {
941 const std::string& notation =
"scalar",
942 const std::string& model_name =
"model") {
943 const std::string m = detail::fluid_resolve_method(
sn,
opt.method);
946 "solver_fluid_export_odes: a Cache model is solved by alternating a cache drift with a "
947 "queueing drift whose routing is rewritten at every sweep, so it has no one ODE system "
948 "to export; export the model without its Cache node, or read the sweeps through "
949 "solver_fld_cacheqn_tran");
What a solver observed about the Cache nodes of a model.
UnsupportedError(const std::string &what)
Raised when the moment closure cannot serve this model: the linearization at the fixed point is not h...
A network plus its refreshed NetworkStruct.
std::vector< std::vector< bool > > disabled
std::vector< Station< T > > stations
stations[k-1] is the k-th station
The exception types the port throws.
The fork-join fixed point that drives one inner MVA solve.
The Heidelberger-Trivedi fork-join transform, options.config.fork_join='ht'.
The INTEGRATED caching-queueing network under the fluid solver: ports of solver_fld_cacheqn_analyzer....
Port of solver_fluid_initsol.m, and of the entry point of solver_fluid_closing.m that consumes it.
The min-normal closure as a DIFFERENTIAL-ALGEBRAIC system: solver_fluid_dae.m.
@@SolverFLD/exportODEs.m: the fluid ODE system as a standalone LaTeX document, in a form meant to be ...
Port of solver_fluid_kp.m: the fluid AND diffusion limits of the (MAP_t/Ph_t/inf)^N network of Y.
The second-order fluid methods: fluid_moment_terms.m, fluid_lyapunov.m, fluid_drift_jacobian....
The one exception the fluid fallback ladder catches.
Response-time distribution by tagged fluid: a port of solver_fluid_passage_time.m,...
Fluid analysis of a stochastic Petri net: one simultaneous algebraic solve per active set.
Port of matlab/src/solvers/FLD/solver_fluid_qsys_analyzer.m: the single-station fluid limits.
Port of ode_eliminate_immediate.m, eliminate_immediate_matrix.m and ode_solve_stiff....
PetriSolution solver_fluid_petri(const qn::NetworkStruct< T > &sn, const PetriOptions &opt=PetriOptions())
Fluid analysis of a stochastic Petri net.
FluidCacheqnSolution< T > solver_fld_cacheqn_analyzer(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
Port of solver_fld_cacheqn_analyzer.m.
void fluid_check_method(const std::string &method)
Port of runAnalyzerChecks' method gate: an unlisted method is refused.
double fluid_default_horizon(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
The horizon a transient runs to when the caller gives none.
FluidSolution solver_fluid_kp(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
Port of solver_fluid_kp.m: the steady table at the horizon.
FluidLayout fluid_layout(const qn::NetworkStruct< T > &sn)
Port of the layout half of solver_fluid_odes.m.
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.
FluidSolution solver_fluid_closing(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
Port of solver_fluid_closing.m: the closing family's entry point.
std::vector< std::string > fluid_list_valid_methods()
Port of SolverFLD.listValidMethods.
std::string solver_fluid_export_odes(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, const std::string ¬ation="scalar", const std::string &model_name="model")
Port of @@SolverFLD/exportODEs.m at the runner's own method resolution, so the exported system is the...
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....
FluidSolution solver_fluid(const qn::NetworkStruct< T > &sn_in, const FluidOptions &opt, qn::NetworkStruct< T > *sn_out=nullptr)
Port of solver_fluid_analyzer.m: dispatch on the method, refit the non-exponential FCFS stations the ...
FluidSolution solver_fluid_qsys(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::vector< FluidTranPoint > *traj=nullptr)
Solve a single-station model with one of the closed-form fluid limits.
std::vector< FluidTranPoint > solver_fluid_tran_avg(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::size_t points=101)
getTranAvg on the first-order closing drift, over that horizon.
FluidPassage fluid_passage_time(const qn::NetworkStruct< T > &sn, const std::vector< double > &x_steady, std::size_t ist, std::size_t cls, double tol=1e-4, std::size_t points=201, const FluidClosure &closure=FluidClosure())
Response-time CDF at station ist for class cls, both 1-based.
FluidSolution solver_fluid_moments(const qn::NetworkStruct< T > &sn, const FluidOptions &opt)
Port of solver_fluid_moments.m: the second-order fluid analysis backing minnormal and refined.
FluidSolution solver_fluid_run_analyzer(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, qn::NetworkStruct< T > *sn_out=nullptr, qn::NetworkStruct< T > *refreshed_out=nullptr, solvers::CacheMetrics< T > *cache_out=nullptr)
Port of @@SolverFLD/runAnalyzer.m: resolve the method, route to the function the reference routes to,...
std::vector< std::vector< FluidPassage > > solver_fluid_cdf_respt(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::size_t points=201)
Port of @@SolverFLD/getCdfRespT: the response-time law of every (station, class) pair,...
std::string fluid_export_odes(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, const std::string ¬ation="scalar", const std::string &model_name="model")
Render the fluid ODE system of sn as a LaTeX document.
std::vector< FluidTranPoint > solver_fluid_run_transient(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::size_t points=101)
-a tran / @@SolverFLD/getTranAvg with the method HONOURED, which is the one place the reference does ...
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.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
const char * sched_to_text(SchedStrategy s)
ReplacementStrategy
Cache replacement policies, with the values of MATLAB ReplacementStrategy.
@ FIFO
first in, first out
FjMmt< T > fj_fork_join_transform(const qn::NetworkStruct< T > &L, const std::string &method)
options.config.fork_join -> the transform it names.
MvaSolution< T > fj_fixed_point(const qn::NetworkStruct< T > &L, FjMmt< T > &tr, std::vector< T > &lam, const MvaOptions &opt, InnerSolve inner)
Drive the fork-join fixed point of a transformed model to convergence.
FeatureSet fluid_feature_set(const std::string &method)
SolverFLD.getFeatureSet, transcribed, MINUS what the requested method cannot evaluate – the port of @...
void check_binding_capacity(const std::string &solver, const NetworkStruct< T > &sn)
bool has_binding_capacity(const NetworkStruct< T > &sn)
getUsedLangFeatures: the features the MODEL uses.
void feature_gate(const std::string &solver, const FeatureSet &declared, const NetworkStruct< T > &sn, const std::string &requested_method="", const std::string &resolved_method="")
runAnalyzerChecks: refuse a model the solver does not declare, by name.
CacheMetrics< T > cache_metrics_of_matrix(const qn::NetworkStruct< T > &sn, const Matrix< T > &hitprob, const Matrix< T > &missprob)
The same, for the integrated caching-queueing branch, whose hit and miss probabilities are (ncaches x...
A queueing network and its refreshed NetworkStruct.
The DECLARED side of the gate: one feature set per solver.
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
What the steady analyzer returns: the fluid metrics plus the converged split.
Matrix< T > missprob
(ncaches x nclasses)
qn::NetworkStruct< T > refreshed
The struct whose cache self-switch carries the CONVERGED split rather than the offered one,...
Matrix< T > hitprob
(ncaches x nclasses), cache order as the node scan
Controls, defaulting to SolverOptions('Fluid') in the reference.
What the analyzer returns, in the same shape as the MVA solver's result.
std::vector< double > xvec
the converged fluid state
static constexpr double FineTol
static constexpr double Zero
Every Cache node of the model, in node order; empty on a model with none.