5#ifndef LINE_SOLVERS_FLUID_FLUID_RUNNER_H
6#define LINE_SOLVERS_FLUID_FLUID_RUNNER_H
76 "matrix",
"fld.matrix",
"pnorm",
"fld.pnorm",
77 "softmin",
"fld.softmin",
78 "statedep",
"fld.statedep",
79 "closing",
"fld.closing",
80 "minnormal",
"fld.minnormal",
81 "refined",
"fld.refined",
83 "diffusion",
"fld.diffusion",
91 "ggisgi",
"fld.ggisgi",
92 "ggingi",
"fld.ggingi",
94 "mtginf",
"fld.mtginf",
101 if (std::find(valid.begin(), valid.end(), method) != valid.end())
return;
102 throw UnsupportedError(
"SolverFLD: the '" + method +
"' method is unsupported by this solver");
112 if (nd.
nodetype == qn::NodeType::Cache)
return true;
118bool fluid_has_dps(
const qn::NetworkStruct<T>& sn) {
119 for (
const auto& st : sn.stations)
132inline std::string fluid_unqualify(
const std::string& method) {
133 std::string m = method;
134 if (m.size() > 4 && m.compare(0, 4,
"fld.") == 0) m = m.substr(4);
135 if (m ==
"aoi")
return "mfq";
158bool fluid_minnormal_applicable(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt,
159 std::string& reason) {
160 const std::size_t M = sn.nstations, K = sn.nclasses;
166 for (std::size_t i = 0; i < M; ++i) {
168 for (std::size_t r = 0; r < K; ++r)
169 if (L.kic[i][r] > 1) {
170 reason =
"class " + std::to_string(r + 1) +
" has a " +
171 std::to_string(L.kic[i][r]) +
"-phase (non-Poisson) arrival process";
181 for (std::size_t nd = 0; nd < sn.nodes.size(); ++nd) {
182 if (sn.nodes[nd].nodetype != qn::NodeType::Cache)
continue;
183 const std::size_t key = nd + 1;
184 if (sn.nodeparam.count(key) == 0) {
185 reason =
"a cache uses a replacement strategy with no drift-based fluid model";
191 reason =
"a cache uses a replacement strategy with no drift-based fluid model";
197 for (std::size_t i = 0; i < M; ++i) {
204 ", which has no fluid drift branch";
211 if (L.nstates > opt.moment_maxstate) {
212 reason =
"the phase-resolved state has " + std::to_string(L.nstates) +
213 " coordinates, above the moment_maxstate limit of " +
214 std::to_string(opt.moment_maxstate);
221 if (fluid_has_time_varying_rates(opt)) {
222 reason =
"a rate schedule (nhpp_sched / rate_traj / rate_sched) makes the drift "
254bool fluid_dae_applicable(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt,
256 const FluidDaeOptions& dopt_in = FluidDaeOptions()) {
258 const std::size_t M = sn.nstations;
266 if (fluid_has_cache(sn)) {
267 reason =
"a cache model is answered by the decomposition analyzer, which has no dae route";
274 for (std::size_t i = 0; i < M; ++i) {
278 ", whose share closes on the covariance between its class coordinates "
279 "rather than on the station variance";
287 if (L.nstates > dopt.maxstate) {
288 reason =
"the phase-resolved state has " + std::to_string(L.nstates) +
289 " coordinates, above the dae_maxstate limit of " +
290 std::to_string(dopt.maxstate);
315std::string fluid_resolve_method(
const qn::NetworkStruct<T>& sn,
const std::string& method,
316 const FluidOptions& opt, std::string& reason) {
317 const std::string m = fluid_unqualify(method);
322 if (m ==
"rmf" || m ==
"refined") {
323 if (fluid_has_cache(sn)) {
324 reason =
"the model has cache nodes";
327 reason =
"the model has no cache nodes";
330 if (m !=
"default")
return m;
331 if (fluid_has_cache(sn)) {
332 reason =
"the model has cache nodes";
347 if (fluid_dae_applicable(sn, opt, dae_why)) {
348 reason = sn.regions.empty() ?
"the model has a binding finite buffer"
349 :
"the model has a finite capacity region";
353 if (fluid_minnormal_applicable(sn, opt, reason))
return "minnormal";
354 return fluid_has_dps(sn) ?
"closing" :
"matrix";
359std::string fluid_resolve_method(
const qn::NetworkStruct<T>& sn,
const std::string& method,
360 const FluidOptions& opt = FluidOptions()) {
362 return fluid_resolve_method(sn, method, opt, reason);
375void fluid_check_load_dependence(
const qn::NetworkStruct<T>& sn,
const std::string& m) {
376 if (m ==
"closing" || m ==
"minnormal" || m ==
"refined" || m ==
"dae")
return;
377 for (std::size_t i = 0; i < sn.stations.size(); ++i)
378 for (std::size_t k = 0; k < sn.stations[i].lldscaling.size(); ++k)
379 if (std::fabs(num_traits<T>::to_double(sn.stations[i].lldscaling[k]) - 1.0) >
382 "solver_fluid_run_analyzer: this model uses load dependence (setLoadDependence), which "
385 "' method does not evaluate. Use method 'closing' for the mean-field answer, or "
386 "'minnormal'/'refined' for the moment-closure correction");
413std::string fluid_forkjoin_supports(
const qn::NetworkStruct<T>& sn,
const std::string& m) {
414 if (m !=
"dae" && m !=
"fld.dae")
return std::string();
415 if (!sn.has_fork())
return std::string();
416 bool any_open =
false;
417 for (std::size_t r = 0; r < sn.classes.size(); ++r)
418 if (!std::isfinite(sn.classes[r].population)) any_open =
true;
419 if (!any_open)
return std::string();
420 return "solver_fluid_run_analyzer: the dae method has no route through the fork-join fixed "
421 "point on an OPEN model: the transform hands the inner solve a mixed network whose "
422 "auxiliary open classes the DAE form carries no unknowns for. Use method 'minnormal', "
423 "which is the same closure and does run that fixed point";
444void fluid_check_dae(
const qn::NetworkStruct<T>& sn,
const std::string& m) {
445 if (m !=
"dae")
return;
446 if (fluid_has_cache(sn))
448 "solver_fluid_run_analyzer: the dae method does not support caching stations: a cache model is "
449 "solved by decomposition, so it has no single drift to constrain. Use "
450 "method 'minnormal' for the same closure, or 'rmf'");
451 const std::string fj = fluid_forkjoin_supports(sn, m);
471void fluid_check_finite_capacity(
const qn::NetworkStruct<T>& sn,
const std::string& m) {
472 if (m ==
"dae" || m ==
"fld.dae" || m ==
"mol" || m ==
"fld.mol")
return;
508 qn::NetworkStruct<T>* sn_out =
nullptr,
509 qn::NetworkStruct<T>* refreshed_out =
nullptr,
510 solvers::CacheMetrics<T>* cache_out =
nullptr);
525mva::MvaSolution<T> fluid_as_mva_solution(
const FluidSolution& f) {
526 mva::MvaSolution<T> s;
527 const std::size_t M = f.QN.rows(), K = f.QN.cols();
528 const T zero = num_traits<T>::from_int(0);
533 for (std::size_t i = 0; i < M; ++i)
534 for (std::size_t k = 0; k < K; ++k) {
535 s.Q(i, k) = num_traits<T>::from_double(f.QN(i, k));
536 s.U(i, k) = num_traits<T>::from_double(f.UN(i, k));
537 s.R(i, k) = num_traits<T>::from_double(f.RN(i, k));
538 s.Tp(i, k) = num_traits<T>::from_double(f.TN(i, k));
540 s.C.reserve(f.CN.size());
541 for (
double v : f.CN) s.C.push_back(num_traits<T>::from_double(v));
542 s.X.reserve(f.XN.size());
543 for (
double v : f.XN) s.X.push_back(num_traits<T>::from_double(v));
545 s.iter =
static_cast<int>(f.iters);
554FluidSolution fluid_from_mva_solution(
const mva::MvaSolution<T>& s,
const std::string& method) {
556 const std::size_t M = s.Q.rows(), K = s.Q.cols();
557 f.QN = Matrix<double>(M, K, 0.0);
558 f.UN = Matrix<double>(M, K, 0.0);
559 f.RN = Matrix<double>(M, K, 0.0);
560 f.TN = Matrix<double>(M, K, 0.0);
561 for (std::size_t i = 0; i < M; ++i)
562 for (std::size_t k = 0; k < K; ++k) {
563 f.QN(i, k) = num_traits<T>::to_double(s.Q(i, k));
564 f.UN(i, k) = num_traits<T>::to_double(s.U(i, k));
565 f.RN(i, k) = num_traits<T>::to_double(s.R(i, k));
566 f.TN(i, k) = num_traits<T>::to_double(s.Tp(i, k));
568 f.CN.reserve(s.C.size());
569 for (
const T& v : s.C) f.CN.push_back(num_traits<T>::to_double(v));
570 f.XN.reserve(s.X.size());
571 for (
const T& v : s.X) f.XN.push_back(num_traits<T>::to_double(v));
572 f.iters =
static_cast<std::size_t
>(s.iter < 0 ? 0 : s.iter);
578inline Matrix<double> fluid_leading_rows(
const Matrix<double>& m, std::size_t M) {
579 if (m.rows() <= M)
return m;
580 Matrix<double> out(M, m.cols(), 0.0);
581 for (std::size_t i = 0; i < M; ++i)
582 for (std::size_t k = 0; k < m.cols(); ++k) out(i, k) = m(i, k);
597FluidSolution fluid_fork_join_run(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt) {
599 std::vector<T> lam(tr.V.classes.size() + 1,
601 mva::MvaOptions mopt;
602 mopt.method = opt.method;
604 mopt.iter_tol = opt.iter_tol;
605 mopt.iter_max = opt.iter_max;
606 mopt.fork_join = opt.fork_join;
607 mopt.base_has_fork =
true;
608 FluidOptions inner = opt;
609 const mva::MvaSolution<T> merged =
613 FluidSolution out = fluid_from_mva_solution<T>(merged, opt.method);
619 out.QN = detail::fluid_leading_rows(out.QN, sn.nstations);
620 out.UN = detail::fluid_leading_rows(out.UN, sn.nstations);
621 out.RN = detail::fluid_leading_rows(out.RN, sn.nstations);
622 out.TN = detail::fluid_leading_rows(out.TN, sn.nstations);
632inline bool fluid_is_petri_net(
const qn::NetworkStruct<T>& sn) {
633 for (std::size_t i = 0; i < sn.nodes.size(); ++i)
648inline FluidSolution fluid_petri_run(
const qn::NetworkStruct<T>& sn,
const FluidOptions& opt) {
649 const std::string m = (opt.method.rfind(
"fld.", 0) == 0) ? opt.method.substr(4) : opt.method;
650 if (!(m ==
"default" || m ==
"dae"))
652 "SolverFluid: method '" + opt.method +
653 "' cannot solve a Petri net: its conserved quantities are P-invariants rather than "
654 "chain populations, an immediate transition is an algebraic FLOW rather than an "
655 "event with a rate, and a bounded place is a linear inequality on the marking. Only "
656 "'dae' states those as equations; every other fluid method builds its drift from the "
657 "station/class/phase encoding, where a Place contributes no coordinate at all, and "
658 "would integrate the net as an empty model and report zeros without a warning");
666 out.iters = ps.iters;
668 const std::size_t M = ps.QN.rows(), K = ps.QN.cols();
669 out.CN.assign(K, 0.0);
670 out.XN.assign(K, 0.0);
671 for (std::size_t k = 0; k < K; ++k) {
672 double q = 0.0, x = 0.0;
673 for (std::size_t i = 0; i < M; ++i) {
675 x = std::max(x, ps.TN(i, k));
678 out.CN[k] = (x > 1e-14) ? q / x : 0.0;
682 out.has_moments =
true;
683 out.moments.Sigma = ps.Sigma;
684 out.moments.QVar = ps.QVar;
685 out.moments.QStd = ps.QStd;
697 if (sn_out) *sn_out =
sn;
705 if (detail::fluid_is_petri_net(
sn))
return detail::fluid_petri_run(
sn,
opt);
710 const std::string m = detail::fluid_resolve_method(
sn,
opt.method,
opt, why);
715 detail::fluid_check_load_dependence(
sn, m);
716 detail::fluid_check_dae(
sn, m);
717 detail::fluid_check_finite_capacity(
sn, m);
724 return detail::fluid_fork_join_run(
sn, fo);
745 if (m ==
"rmf" || (m ==
"minnormal" && detail::fluid_has_cache(
sn))) {
753 if (refreshed_out) *refreshed_out = cq.
refreshed;
755 }
else if (m ==
"closing" || m ==
"statedep" || m ==
"softmin" || m ==
"tbi") {
758 }
else if (m ==
"minnormal" || m ==
"refined") {
786 moments_solve, sn_out);
788 std::string dae_reason;
790 if (detail::fluid_dae_applicable(
sn, o, dae_reason)) {
803 o.
method = detail::fluid_has_dps(
sn) ?
"closing" :
"matrix";
805 detail::fluid_check_load_dependence(
sn, o.
method);
811 closing_solve, sn_out);
812 detail::fluid_analyzer_correct(
sn, out.
QN, out.
UN, out.
RN, out.
TN);
813 detail::fluid_snap_all(out.
QN, out.
UN, out.
RN, out.
TN);
817 }
else if (m ==
"dae") {
830 }
else if (m ==
"kp") {
832 }
else if (detail::fluid_qsys_handles(m)) {
843 detail::fluid_analyzer_correct(
sn, out.
QN, out.
UN, out.
RN, out.
TN);
844 detail::fluid_snap_all(out.
QN, out.
UN, out.
RN, out.
TN);
866 std::size_t points = 101) {
871 if (detail::fluid_qsys_handles(detail::fluid_unqualify(
opt.method))) {
873 o.
method = detail::fluid_unqualify(
opt.method);
875 std::vector<FluidTranPoint> traj;
879 if (detail::fluid_unqualify(
opt.method) !=
"dae")
882 detail::fluid_check_dae(
sn, std::string(
"dae"));
906 std::size_t points = 201) {
911 "solver_fluid_cdf_respt: the '" + r.
method +
912 "' method reports no fluid state vector to mark a job in, so it has no passage time; "
913 "use 'closing', 'matrix' or a moment closure");
914 std::vector<std::vector<FluidPassage> > RD(
916 for (std::size_t i = 0; i < snr.
nstations; ++i) {
917 if (snr.
stations[i].nodetype == qn::NodeType::Source)
continue;
918 for (std::size_t c = 0; c < snr.
nclasses; ++c) {
937 const std::string& notation =
"scalar",
938 const std::string& model_name =
"model") {
939 const std::string m = detail::fluid_resolve_method(
sn,
opt.method);
942 "solver_fluid_export_odes: a Cache model is solved by alternating a cache drift with a "
943 "queueing drift whose routing is rewritten at every sweep, so it has no one ODE system "
944 "to export; export the model without its Cache node, or read the sweeps through "
945 "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...
Conservation laws of a layered queueing network, enumerated from its structure.
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.