LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_env_limit.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_SOLVERS_ENV_SOLVER_ENV_LIMIT_H
6#define LINE_SOLVERS_ENV_SOLVER_ENV_LIMIT_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The two CLOSED-FORM environment limits: `SolverENV.solveEnvLimit`, reached by
12 * `options.method` in {`avg`, `dec`}.
13 *
14 * Both replace the fixed point with a single reading, and each is exact at one
15 * end of the time-scale separation between the environment and the network:
16 *
17 * `avg`, the FAST-environment limit. The environment switches so much faster
18 * than the network responds that the network only ever sees the AVERAGE of the
19 * modulated rates. One rate-averaged model is built -- every station-class
20 * rate that varies across stages replaced by its probEnv-weighted mean, as an
21 * exponential -- and solved once, in steady state. Exact as the stage-switch
22 * rate goes to infinity.
23 *
24 * `dec`, the SLOW-environment limit, i.e. quasi-stationary decomposition. The
25 * environment stays in a stage long enough for the network to reach that
26 * stage's own steady state, so each stage is solved independently and the
27 * metrics are averaged with weights probEnv. Exact as the stage-switch rate
28 * goes to zero.
29 *
30 * NEITHER CARRIES ANYTHING ACROSS A SWITCH, which is what makes them closed
31 * form and also what they give up: no entry state, no reset policy, no
32 * transient. A reset policy declared on an arc is therefore inert here, exactly
33 * as it is in the reference, whose `solveEnvLimit` never reads `resetFun`.
34 *
35 * WHAT IS AVERAGED, AND WHAT IS LEFT ALONE (`buildRateAveragedModel`). Only a
36 * Source, a Queue or a Delay carries a rate to average. A station-class pair
37 * that is disabled in any stage, or whose rate does not actually vary across
38 * them, keeps its ORIGINAL distribution rather than being rewritten as an
39 * exponential of its own mean -- so a non-modulated Erlang stays an Erlang, and
40 * the base model is preserved exactly outside the modulated rates.
41 *
42 * A STAGE HOLDING A CACHE is solved, not refused. The hit, miss and delayed
43 * fractions each stage reports are blended the way the reference's
44 * `accumCacheMetric` does after 2026-09-13: as RATES weighted by that stage's
45 * per-class arrival INTO THE CACHE, normalised once at the end. Rates and not
46 * ratios, because a stage's hit ratio is conditional on arriving in that stage,
47 * so a probEnv-weighted mean of ratios disagrees with the Sink-row hit
48 * throughput -- `sum_e p_e*lambda_e*h_e` -- in the SAME node table. It is also
49 * the convention `aggregate_cache_meanfield` and the statevec `cache_blend`
50 * already hold, each with a comment saying so.
51 *
52 * WHICH SOLVER RUNS A STAGE. The reference hands `SolverENV` a FACTORY and calls
53 * it for every stage, so "the stage solver is the calling solver" is literally
54 * true there. The second constructor here takes an `EnvStageAvgFn`, the same
55 * thing as a callable, and the no-factory path keeps the fluid analyzer. Without
56 * a factory an MVA or NC caller would silently be answered by a fluid solve, and
57 * even an FLD caller would lose its own `FluidOptions` (method, timespan,
58 * tolerances) on the way in.
59 */
60
61#include <algorithm>
62#include <cmath>
63#include <cstddef>
64#include <functional>
65#include <limits>
66#include <string>
67#include <type_traits>
68#include <vector>
69
73#include "line/num/number.h"
78#include "line/util/error.h"
79#include "line/util/matrix.h"
80
81namespace line {
82namespace env {
83
84/**
85 * What one stage solve reports back to the limits.
86 *
87 * The literal translation of what the reference's `[Qe,Ue,~,Te] = se.getAvg()`
88 * plus `se.getAvgNode()` hand back, minus the node table: a Cache reports on the
89 * NODE, so `cache` carries what `getAvgNode`'s resync would have written there.
90 */
91template <class T>
96
97/**
98 * The stage solver, as a callable: the C++ spelling of the MATLAB function
99 * handle `SolverENV(renv, @(m) SolverX(m, opts))` passes.
100 *
101 * Keeping it a `std::function` is what lets `env/` name a stage solver without
102 * including one: this header sits ABOVE the MVA and NC runners in the include
103 * graph (`env/solver_env.h` -> `ln/solver_ln.h` -> `mva/solver_mva.h`), so a
104 * direct include would be a cycle.
105 */
106template <class T>
107using EnvStageAvgFn = std::function<EnvStageAvg<T>(const qn::NetworkStruct<T>&)>;
108
109/** What a limit solve reports. */
111 /** Environment-averaged metrics, (nstations x nclasses). */
113 /** The limit that ran: `avg` or `dec`. */
114 std::string method;
115 /** `dec` only: each stage's own steady state, before the probEnv blend. */
116 std::vector<Matrix<double> > QStage, UStage, TStage;
117 /** The stage probabilities the blend used. */
118 std::vector<double> prob_env;
119 /**
120 * The environment-blended cache surface, one entry per Cache node.
121 *
122 * EMPTY on a model with no cache. Within an entry every field is optional
123 * and ABSENT MEANS NOT COMPUTED, never zero, exactly as `CacheNodeMetrics`
124 * states: a class no stage measured stays NaN rather than reading as "never
125 * hits". Kept `double`, since `init()` refuses `T != double` anyway.
126 */
128};
129
130/**
131 * The limit solver.
132 *
133 * `envObj` must already carry a model per stage; `init()` is called here, as
134 * `solveEnvLimit` calls `self.init()` before reading probEnv.
135 */
136template <class T>
138public:
139 SolverEnvLimit(Environment<T>& e, const EnvOptions& o) : envObj(e), opt(o) { init(); }
140
141 /**
142 * With an injected stage solver: `stage_fn` runs every stage, exactly as the
143 * reference calls its factory. `opt.stage_solver` is then not consulted,
144 * since the caller has already named the solver by handing one over.
145 */
147 : envObj(e), opt(o), stage_fn_(stage_fn) {
148 init();
149 }
150
151 EnvLimitSolution solve() { return dec_ ? solve_dec() : solve_avg(); }
152
153private:
154 void init() {
155 if (opt.method != "avg" && opt.method != "dec")
156 throw UnsupportedError("SolverENV limit: '" + opt.method +
157 "' is not a closed-form environment limit; the two are 'avg' "
158 "(fast environment) and 'dec' (slow environment)");
159 dec_ = (opt.method == "dec");
160 if (!stage_fn_ && opt.stage_solver != "fluid")
161 throw UnsupportedError(
162 "SolverENV limit: stage solver '" + opt.stage_solver +
163 "' is not available; the limits solve each stage in STEADY STATE and the fluid "
164 "analyzer is the one this port wires into the environment. Hand a stage solver "
165 "to the EnvStageAvgFn constructor to run another one");
166 if (!std::is_same<T, double>::value)
167 throw UnsupportedError(
168 "SolverENV limit: a fluid stage integrates its drift with LSODA, which is double "
169 "only; rerun with --arith double");
170
171 // The two limits read the stage NETWORKS directly -- `dec` solves each
172 // in steady state, `avg` builds one network at the probEnv-weighted
173 // rates -- and a layered stage has no station rate table to weight. The
174 // reference has no branch for it either: `solveEnvLimit` reaches for
175 // `self.ensemble{1}.nodes`, which a LayeredNetwork does not carry.
176 envObj.reject_lqn_stages(
177 "SolverENV limit",
178 "the fast/slow limits read a stage's station rates directly -- 'dec' solves each "
179 "stage network in steady state and 'avg' builds one network at the probEnv-weighted "
180 "rates -- and a layered model has no such rate table, only the layers SolverLN "
181 "derives from it");
182 envObj.init();
183 const std::size_t E = envObj.nstages();
184 M = envObj.stage(0).model.nstations;
185 K = envObj.stage(0).model.nclasses;
186 for (std::size_t e = 1; e < E; ++e)
187 if (envObj.stage(e).model.nstations != M || envObj.stage(e).model.nclasses != K)
188 throw InputError(
189 "SolverENV limit: every stage must have the same stations and classes; the "
190 "metrics are blended entrywise across them");
191 }
192
193 /**
194 * The RATE accumulator behind the cache blend, `SolverENV.accumCacheMetric`.
195 *
196 * Keyed by the Cache node's NAME, never by its position: the struct's node
197 * order is not the declaration order, and indexing by position is what once
198 * made a host write cache results onto a Sink (see `cache_metrics.h`).
199 */
200 class CacheAccum {
201 public:
202 /** One stage's contribution, weighted by its arrival into each cache. */
203 void add(const qn::NetworkStruct<T>& sn, const EnvStageAvg<T>& stage, double w) {
204 const std::vector<double> arv_all = cache_arrival(sn, stage.TN);
205 for (std::size_t c = 0; c < stage.cache.caches.size(); ++c) {
206 const solvers::CacheNodeMetrics<T>& cm = stage.cache.caches[c];
207 Entry& en = entry(cm);
208 const std::size_t K = std::max(cm.hitprob.size(), cm.missprob.size());
209 std::vector<double> arv(std::max(K, en.arv.size()), 0.0);
210 for (std::size_t r = 0; r < arv.size(); ++r) {
211 const std::size_t ind = cm.node; // 1-based node index
212 const std::size_t off = (ind >= 1 ? (ind - 1) * sn.nclasses + r : arv_all.size());
213 if (off < arv_all.size()) arv[r] = w * arv_all[off];
214 }
215 grow(en.arv, arv.size());
216 for (std::size_t r = 0; r < arv.size(); ++r) en.arv[r] += arv[r];
217 accum(en.hit, cm.hitprob, arv);
218 accum(en.miss, cm.missprob, arv);
219 accum(en.dhit, cm.delayedprob, arv);
220 accum_rows(en.hitl, cm.hitproblist, arv);
221 }
222 }
223
224 /** Divide the accumulated rates by the accumulated arrivals, ONCE. */
225 solvers::CacheMetrics<double> finish() const {
226 solvers::CacheMetrics<double> out;
227 for (std::size_t c = 0; c < order_.size(); ++c) {
228 const Entry& en = order_[c];
229 solvers::CacheNodeMetrics<double> cm;
230 cm.node = en.node;
231 cm.name = en.name;
232 cm.itemcap = en.itemcap;
233 cm.nitems = en.nitems;
234 cm.hitprob = divide(en.hit, en.arv);
235 cm.missprob = divide(en.miss, en.arv);
236 cm.delayedprob = divide(en.dhit, en.arv);
237 if (en.hitl.rows() > 0) {
238 cm.hitproblist = Matrix<double>(en.hitl.rows(), en.hitl.cols(), nan_());
239 for (std::size_t r = 0; r < en.hitl.rows(); ++r) {
240 const double d = r < en.arv.size() ? en.arv[r] : 0.0;
241 for (std::size_t l = 0; l < en.hitl.cols(); ++l)
242 cm.hitproblist(r, l) = d > 0.0 ? en.hitl(r, l) / d : nan_();
243 }
244 }
245 out.caches.push_back(cm);
246 }
247 return out;
248 }
249
250 private:
251 struct Entry {
252 std::size_t node = 0;
253 std::string name;
254 std::vector<double> itemcap;
255 std::size_t nitems = 0;
256 std::vector<double> arv;
257 /** EMPTY stays empty: a quantity no stage measured must not become 0. */
258 std::vector<double> hit, miss, dhit;
259 Matrix<double> hitl;
260 };
261
262 static double nan_() { return std::numeric_limits<double>::quiet_NaN(); }
263
264 static void grow(std::vector<double>& v, std::size_t n) {
265 if (v.size() < n) v.resize(n, 0.0);
266 }
267
268 Entry& entry(const solvers::CacheNodeMetrics<T>& cm) {
269 for (std::size_t i = 0; i < order_.size(); ++i)
270 if (order_[i].name == cm.name) return order_[i];
271 Entry en;
272 en.node = cm.node;
273 en.name = cm.name;
274 en.itemcap = cm.itemcap;
275 en.nitems = cm.nitems;
276 order_.push_back(en);
277 return order_.back();
278 }
279
280 /** One quantity: an ABSENT ratio contributes nothing and leaves it absent. */
281 static void accum(std::vector<double>& a, const std::vector<T>& ratio,
282 const std::vector<double>& arv) {
283 if (ratio.empty()) return;
284 grow(a, ratio.size());
285 for (std::size_t r = 0; r < ratio.size() && r < arv.size(); ++r) {
286 const double v = num_traits<T>::to_double(ratio[r]);
287 if (std::isfinite(v)) a[r] += arv[r] * v;
288 }
289 }
290
291 static void accum_rows(Matrix<double>& a, const Matrix<T>& hl,
292 const std::vector<double>& arv) {
293 if (hl.rows() == 0 || hl.cols() == 0) return;
294 if (a.rows() == 0) a = Matrix<double>(hl.rows(), hl.cols(), 0.0);
295 for (std::size_t r = 0; r < hl.rows() && r < a.rows(); ++r) {
296 const double w = r < arv.size() ? arv[r] : 0.0;
297 for (std::size_t l = 0; l < hl.cols() && l < a.cols(); ++l) {
298 const double v = num_traits<T>::to_double(hl(r, l));
299 if (std::isfinite(v)) a(r, l) += w * v;
300 }
301 }
302 }
303
304 /**
305 * A class with no arrival anywhere divides by nothing and STAYS NaN: its
306 * ratio is undefined, and 0 would read as "never hits".
307 */
308 static std::vector<double> divide(const std::vector<double>& a,
309 const std::vector<double>& d) {
310 std::vector<double> r;
311 if (a.empty()) return r;
312 r.assign(a.size(), nan_());
313 for (std::size_t i = 0; i < a.size(); ++i)
314 if (i < d.size() && d[i] > 0.0) r[i] = a[i] / d[i];
315 return r;
316 }
317
318 /**
319 * The per-class arrival INTO EACH NODE, flattened node-major.
320 *
321 * The weight is the cache's own arrival and not the Source throughput,
322 * because the latter is 0 on a closed cache model. The station table
323 * handed in is all zeros on purpose: only the Cache rows are read, and
324 * those come from the chain reference throughput and the node visits,
325 * never from a station row.
326 */
327 static std::vector<double> cache_arrival(const qn::NetworkStruct<T>& sn,
328 const Matrix<double>& TN) {
329 const std::size_t I = sn.nodes.size(), R = sn.nclasses, M = sn.nstations;
330 std::vector<double> out(I * R, 0.0);
331 if (TN.rows() == 0) return out;
332 Matrix<T> TNt(TN.rows(), TN.cols(), num_traits<T>::from_int(0));
333 for (std::size_t i = 0; i < TN.rows(); ++i)
334 for (std::size_t r = 0; r < TN.cols(); ++r)
335 TNt(i, r) = num_traits<T>::from_double(TN(i, r));
336 const Matrix<T> AN(M, R, num_traits<T>::from_int(0));
337 const Matrix<T> ANn = api::sn_get_node_arvr_from_tput<T>(sn, TNt, AN);
338 if (ANn.rows() != I) return out;
339 for (std::size_t ind = 0; ind < I; ++ind)
340 for (std::size_t r = 0; r < R; ++r) {
341 const double v = num_traits<T>::to_double(ANn(ind, r));
342 out[ind * R + r] = std::isfinite(v) ? v : 0.0;
343 }
344 return out;
345 }
346
347 std::vector<Entry> order_;
348 };
349
350 /** The slow-environment limit: each stage on its own, blended by probEnv. */
351 EnvLimitSolution solve_dec() {
352 const std::size_t E = envObj.nstages();
353 EnvLimitSolution out;
354 out.method = "dec";
355 out.prob_env = envObj.prob_env;
356 out.QN = Matrix<double>(M, K, 0.0);
357 out.UN = Matrix<double>(M, K, 0.0);
358 out.TN = Matrix<double>(M, K, 0.0);
359 out.QStage.assign(E, Matrix<double>(M, K, 0.0));
360 out.UStage.assign(E, Matrix<double>(M, K, 0.0));
361 out.TStage.assign(E, Matrix<double>(M, K, 0.0));
362 CacheAccum acc;
363 for (std::size_t e = 0; e < E; ++e) {
364 const EnvStageAvg<T> s = stage_steady_state(envObj.stage(e).model);
365 const double p = envObj.prob_env[e];
366 acc.add(envObj.stage(e).model, s, p);
367 for (std::size_t i = 0; i < M; ++i)
368 for (std::size_t r = 0; r < K; ++r) {
369 out.QStage[e](i, r) = s.QN(i, r);
370 out.UStage[e](i, r) = s.UN(i, r);
371 out.TStage[e](i, r) = s.TN(i, r);
372 out.QN(i, r) += p * s.QN(i, r);
373 out.UN(i, r) += p * s.UN(i, r);
374 out.TN(i, r) += p * s.TN(i, r);
375 }
376 }
377 out.cache = acc.finish();
378 return out;
379 }
380
381 /** The fast-environment limit: one rate-averaged model, solved once. */
382 EnvLimitSolution solve_avg() {
383 EnvLimitSolution out;
384 out.method = "avg";
385 out.prob_env = envObj.prob_env;
386 const qn::NetworkStruct<T> avg = rate_averaged_model();
387 const EnvStageAvg<T> s = stage_steady_state(avg);
388 // One model, so the weight is the arrival itself and the normalisation
389 // returns exactly what the single solve reported.
390 CacheAccum acc;
391 acc.add(avg, s, 1.0);
392 out.cache = acc.finish();
393 out.QN = Matrix<double>(M, K, 0.0);
394 out.UN = Matrix<double>(M, K, 0.0);
395 out.TN = Matrix<double>(M, K, 0.0);
396 for (std::size_t i = 0; i < M; ++i)
397 for (std::size_t r = 0; r < K; ++r) {
398 out.QN(i, r) = s.QN(i, r);
399 out.UN(i, r) = s.UN(i, r);
400 out.TN(i, r) = s.TN(i, r);
401 }
402 return out;
403 }
404
405 /**
406 * One stage, in steady state: the injected solver when there is one, else
407 * the fluid analyzer, in the form that also REPORTS ITS CACHE SPLIT when the
408 * stage holds a Cache.
409 *
410 * WHICH ENTRY POINT IS A MODEL QUESTION AND NOT A PREFERENCE, because the
411 * two resolve `default` differently and therefore answer with DIFFERENT
412 * CLOSURES. `solver_fluid` is `solver_fluid_analyzer.m`: it hands the method
413 * to `fluid_dispatch`, which takes `default` to the matrix method (to
414 * `closing` on a DPS model). `solver_fluid_run_analyzer` is the port of
415 * `runAnalyzer`'s resolution and re-resolves `default` through the
416 * minnormal/dae ladder. On a stage sitting at its saturation knee the two
417 * disagree outright -- N/(Z+D) = 1/D is a continuum of first-order
418 * equilibria, and which point a closure picks is the whole answer -- so
419 * routing every stage through the runner silently moved the limits off the
420 * matrix closure they are pinned against.
421 *
422 * A CACHE STAGE IS THE ONE THAT MUST TAKE THE RUNNER, and for the reference's
423 * own reason rather than for a reporting convenience: `runAnalyzer` resolves
424 * a Cache model to `rmf`, which lives in `fluid_cacheqn.h` and which
425 * `fluid_dispatch` refuses BY NAME (it cannot call it without a cyclic
426 * include). That route is also the one that reports the split, which is the
427 * silence this file used to refuse a cache model over.
428 */
429 EnvStageAvg<T> stage_steady_state(const qn::NetworkStruct<T>& sn) const {
430 if (stage_fn_) return stage_fn_(sn);
431 fluid::FluidOptions fo = opt.stage;
432 // A limit starts from nothing carried over -- there is no entry state to
433 // seed with, which is the whole content of the approximation.
434 fo.init_sol.clear();
435 EnvStageAvg<T> out;
436 const fluid::FluidSolution s =
437 fluid::detail::fluid_has_cache(sn)
438 ? fluid::solver_fluid_run_analyzer<T>(sn, fo, nullptr, nullptr, &out.cache)
439 : fluid::solver_fluid<T>(sn, fo);
440 out.QN = s.QN;
441 out.UN = s.UN;
442 out.TN = s.TN;
443 return out;
444 }
445
446 /**
447 * `buildRateAveragedModel`: stage 1's network with every MODULATED rate
448 * replaced by its probEnv-weighted mean, as an exponential.
449 *
450 * The refresh chain is rerun because `rates` and everything derived from it
451 * are read off the service table; editing the table alone would leave the
452 * struct describing stage 1.
453 */
454 qn::NetworkStruct<T> rate_averaged_model() const {
455 const std::size_t E = envObj.nstages();
456 qn::NetworkStruct<T> sn = envObj.stage(0).model;
457 std::vector<double> r(E, 0.0);
458 for (std::size_t i = 0; i < M; ++i) {
459 const lang::NodeType nt = sn.stations[i].nodetype;
462 continue;
463 for (std::size_t k = 0; k < K; ++k) {
464 bool ok = true;
465 for (std::size_t e = 0; e < E && ok; ++e) {
466 const qn::NetworkStruct<T>& se = envObj.stage(e).model;
467 // The reference's `any(isnan(r)) || any(r<=0)`: a rate this
468 // port reports as disabled is MATLAB's NaN, and either way
469 // the pair is left as configured.
470 if (se.disabled[i][k]) {
471 ok = false;
472 break;
473 }
474 r[e] = num_traits<T>::to_double(se.rates(i, k));
475 if (!(r[e] > 0.0) || !std::isfinite(r[e])) ok = false;
476 }
477 if (!ok) continue;
478 double lo = r[0], hi = r[0], avg = 0.0;
479 for (std::size_t e = 0; e < E; ++e) {
480 lo = std::min(lo, r[e]);
481 hi = std::max(hi, r[e]);
482 avg += envObj.prob_env[e] * r[e];
483 }
484 // Not modulated: keep the original distribution, which may carry
485 // a shape the exponential of its mean would throw away.
486 if (hi - lo <= 1e-12 * std::max(1.0, hi)) continue;
487 sn.set_service(i + 1, k + 1,
488 lang::Distrib<T>::exp_rate(num_traits<T>::from_double(avg)));
489 }
490 }
491 sn.refresh_struct();
492 return sn;
493 }
494
495 Environment<T>& envObj;
496 EnvOptions opt;
497 EnvStageAvgFn<T> stage_fn_;
498 std::size_t M = 0, K = 0;
499 bool dec_ = false;
500};
501
502/** `solveEnvLimit` on the original stages. */
503template <class T>
505 SolverEnvLimit<T> s(e, o);
506 return s.solve();
507}
508
509/** `solveEnvLimit` with the CALLING solver running every stage. */
510template <class T>
512 EnvStageAvgFn<T> stage_fn) {
513 SolverEnvLimit<T> s(e, o, stage_fn);
514 return s.solve();
515}
516
517} // namespace env
518} // namespace line
519
520#endif // LINE_SOLVERS_ENV_SOLVER_ENV_LIMIT_H
What a solver observed about the Cache nodes of a model.
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
SolverEnvLimit(Environment< T > &e, const EnvOptions &o, EnvStageAvgFn< T > stage_fn)
With an injected stage solver: stage_fn runs every stage, exactly as the reference calls its factory.
SolverEnvLimit(Environment< T > &e, const EnvOptions &o)
A network plus its refreshed NetworkStruct.
A random environment: a port of matlab/src/lang/Environment.m, restricted to what SolverENV reads out...
The exception types the port throws.
The fluid solver's outermost entry point: @@SolverFLD/runAnalyzer.m's method resolution over solver_f...
Dense matrix and non-owning view.
Matrix< T > sn_get_node_arvr_from_tput(const qn::NetworkStruct< T > &sn, const Matrix< T > &TN, const Matrix< T > &AN)
Port of sn_get_node_arvr_from_tput.
EnvLimitSolution solver_env_limit(Environment< T > &e, const EnvOptions &o)
solveEnvLimit on the original stages.
std::function< EnvStageAvg< T >(const qn::NetworkStruct< T > &)> EnvStageAvgFn
The stage solver, as a callable: the C++ spelling of the MATLAB function handle SolverENV(renv,...
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_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,...
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:326
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Ports of matlab/src/api/sn/sn_get_node_arvr_from_tput.m and sn_get_node_tput_from_tput....
SolverENV: a queueing network in a random environment.
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
What a limit solve reports.
std::vector< Matrix< double > > UStage
std::vector< double > prob_env
The stage probabilities the blend used.
Matrix< double > QN
Environment-averaged metrics, (nstations x nclasses).
std::vector< Matrix< double > > QStage
dec only: each stage's own steady state, before the probEnv blend.
std::vector< Matrix< double > > TStage
solvers::CacheMetrics< double > cache
The environment-blended cache surface, one entry per Cache node.
std::string method
The limit that ran: avg or dec.
Options of SolverENV.
Definition solver_env.h:113
What one stage solve reports back to the limits.
solvers::CacheMetrics< T > cache
static Distrib exp_rate(const T &r)
Definition lang_types.h:945
Every Cache node of the model, in node order; empty on a model with none.
One Cache node's measured behaviour.
std::vector< T > hitprob
(K) TRUE hit fraction, EMPTY = not computed
std::vector< T > delayedprob
(K) delayed-hit fraction, EMPTY off a retrieval system
Matrix< T > hitproblist
(K x h) per-list hit fraction, EMPTY = not computed
std::size_t node
1-based node index of the Cache