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 runs `stage_solver`: the fluid
56 * analyzer, or with `ctmc` the enumerated chain at `stage_cutoff` (an ensemble built
57 * on SolverCTMC stages). Without a factory an MVA or NC caller would silently be
58 * answered by a fluid solve, and even an FLD caller would lose its own
59 * `FluidOptions` (method, timespan, tolerances) on the way in.
60 */
61
62#include <algorithm>
63#include <cmath>
64#include <cstddef>
65#include <functional>
66#include <limits>
67#include <string>
68#include <type_traits>
69#include <vector>
70
74#include "line/num/number.h"
80#include "line/util/error.h"
81#include "line/util/matrix.h"
82
83namespace line {
84namespace env {
85
86/**
87 * What one stage solve reports back to the limits.
88 *
89 * The literal translation of what the reference's `[Qe,Ue,~,Te] = se.getAvg()`
90 * plus `se.getAvgNode()` hand back, minus the node table: a Cache reports on the
91 * NODE, so `cache` carries what `getAvgNode`'s resync would have written there.
92 */
93template <class T>
98
99/**
100 * The stage solver, as a callable: the C++ spelling of the MATLAB function
101 * handle `SolverENV(renv, @(m) SolverX(m, opts))` passes.
102 *
103 * Keeping it a `std::function` is what lets `env/` name a stage solver without
104 * including one: this header sits ABOVE the MVA and NC runners in the include
105 * graph (`env/solver_env.h` -> `ln/solver_ln.h` -> `mva/solver_mva.h`), so a
106 * direct include would be a cycle.
107 */
108template <class T>
109using EnvStageAvgFn = std::function<EnvStageAvg<T>(const qn::NetworkStruct<T>&)>;
110
111/** What a limit solve reports. */
113 /** Environment-averaged metrics, (nstations x nclasses). */
115 /** The limit that ran: `avg` or `dec`. */
116 std::string method;
117 /** `dec` only: each stage's own steady state, before the probEnv blend. */
118 std::vector<Matrix<double> > QStage, UStage, TStage;
119 /** The stage probabilities the blend used. */
120 std::vector<double> prob_env;
121 /**
122 * The environment-blended cache surface, one entry per Cache node.
123 *
124 * EMPTY on a model with no cache. Within an entry every field is optional
125 * and ABSENT MEANS NOT COMPUTED, never zero, exactly as `CacheNodeMetrics`
126 * states: a class no stage measured stays NaN rather than reading as "never
127 * hits". Kept `double`, since `init()` refuses `T != double` anyway.
128 */
130};
131
132/**
133 * The limit solver.
134 *
135 * `envObj` must already carry a model per stage; `init()` is called here, as
136 * `solveEnvLimit` calls `self.init()` before reading probEnv.
137 */
138template <class T>
140public:
141 SolverEnvLimit(Environment<T>& e, const EnvOptions& o) : envObj(e), opt(o) { init(); }
142
143 /**
144 * With an injected stage solver: `stage_fn` runs every stage, exactly as the
145 * reference calls its factory. `opt.stage_solver` is then not consulted,
146 * since the caller has already named the solver by handing one over.
147 */
149 : envObj(e), opt(o), stage_fn_(stage_fn) {
150 init();
151 }
152
153 EnvLimitSolution solve() { return dec_ ? solve_dec() : solve_avg(); }
154
155private:
156 void init() {
157 if (opt.method != "avg" && opt.method != "dec")
158 throw UnsupportedError("SolverENV limit: '" + opt.method +
159 "' is not a closed-form environment limit; the two are 'avg' "
160 "(fast environment) and 'dec' (slow environment)");
161 dec_ = (opt.method == "dec");
162 if (!stage_fn_ && opt.stage_solver != "fluid" && opt.stage_solver != "ctmc")
163 throw UnsupportedError(
164 "SolverENV limit: stage solver '" + opt.stage_solver +
165 "' is not available; the limits solve each stage in STEADY STATE, by the fluid "
166 "analyzer or the enumerated CTMC. Hand a stage solver to the EnvStageAvgFn "
167 "constructor to run another one");
168 if (!std::is_same<T, double>::value)
169 throw UnsupportedError(
170 "SolverENV limit: a fluid stage integrates its drift with LSODA, which is double "
171 "only; rerun with --arith double");
172
173 // The two limits read the stage NETWORKS directly -- `dec` solves each
174 // in steady state, `avg` builds one network at the probEnv-weighted
175 // rates -- and a layered stage has no station rate table to weight. The
176 // reference has no branch for it either: `solveEnvLimit` reaches for
177 // `self.ensemble{1}.nodes`, which a LayeredNetwork does not carry.
178 envObj.reject_lqn_stages(
179 "SolverENV limit",
180 "the fast/slow limits read a stage's station rates directly -- 'dec' solves each "
181 "stage network in steady state and 'avg' builds one network at the probEnv-weighted "
182 "rates -- and a layered model has no such rate table, only the layers SolverLN "
183 "derives from it");
184 envObj.init();
185 const std::size_t E = envObj.nstages();
186 M = envObj.stage(0).model.nstations;
187 K = envObj.stage(0).model.nclasses;
188 for (std::size_t e = 1; e < E; ++e)
189 if (envObj.stage(e).model.nstations != M || envObj.stage(e).model.nclasses != K)
190 throw InputError(
191 "SolverENV limit: every stage must have the same stations and classes; the "
192 "metrics are blended entrywise across them");
193 }
194
195public:
196 /**
197 * The RATE accumulator behind the cache blend, `SolverENV.accumCacheMetric`. Public, since
198 * the mean-field coupling's `substituteCacheBlend_` weights by the same rule.
199 *
200 * Keyed by the Cache node's NAME, never by its position: the struct's node
201 * order is not the declaration order, and indexing by position is what once
202 * made a host write cache results onto a Sink (see `cache_metrics.h`).
203 */
205 public:
206 /** One stage's contribution, weighted by its arrival into each cache. */
207 void add(const qn::NetworkStruct<T>& sn, const EnvStageAvg<T>& stage, double w) {
208 const std::vector<double> arv_all = cache_arrival(sn, stage.TN);
209 for (std::size_t c = 0; c < stage.cache.caches.size(); ++c) {
210 const solvers::CacheNodeMetrics<T>& cm = stage.cache.caches[c];
211 Entry& en = entry(cm);
212 const std::size_t K = std::max(cm.hitprob.size(), cm.missprob.size());
213 std::vector<double> arv(std::max(K, en.arv.size()), 0.0);
214 for (std::size_t r = 0; r < arv.size(); ++r) {
215 const std::size_t ind = cm.node; // 1-based node index
216 const std::size_t off = (ind >= 1 ? (ind - 1) * sn.nclasses + r : arv_all.size());
217 if (off < arv_all.size()) arv[r] = w * arv_all[off];
218 }
219 grow(en.arv, arv.size());
220 for (std::size_t r = 0; r < arv.size(); ++r) en.arv[r] += arv[r];
221 accum(en.hit, cm.hitprob, arv);
222 accum(en.miss, cm.missprob, arv);
223 accum(en.dhit, cm.delayedprob, arv);
224 accum_rows(en.hitl, cm.hitproblist, arv);
225 }
226 }
227
228 /** Divide the accumulated rates by the accumulated arrivals, ONCE. */
231 for (std::size_t c = 0; c < order_.size(); ++c) {
232 const Entry& en = order_[c];
234 cm.node = en.node;
235 cm.name = en.name;
236 cm.itemcap = en.itemcap;
237 cm.nitems = en.nitems;
238 cm.hitprob = divide(en.hit, en.arv);
239 cm.missprob = divide(en.miss, en.arv);
240 cm.delayedprob = divide(en.dhit, en.arv);
241 if (en.hitl.rows() > 0) {
242 cm.hitproblist = Matrix<double>(en.hitl.rows(), en.hitl.cols(), nan_());
243 for (std::size_t r = 0; r < en.hitl.rows(); ++r) {
244 const double d = r < en.arv.size() ? en.arv[r] : 0.0;
245 for (std::size_t l = 0; l < en.hitl.cols(); ++l)
246 cm.hitproblist(r, l) = d > 0.0 ? en.hitl(r, l) / d : nan_();
247 }
248 }
249 out.caches.push_back(cm);
250 }
251 return out;
252 }
253
254 private:
255 struct Entry {
256 std::size_t node = 0;
257 std::string name;
258 std::vector<double> itemcap;
259 std::size_t nitems = 0;
260 std::vector<double> arv;
261 /** EMPTY stays empty: a quantity no stage measured must not become 0. */
262 std::vector<double> hit, miss, dhit;
263 Matrix<double> hitl;
264 };
265
266 static double nan_() { return std::numeric_limits<double>::quiet_NaN(); }
267
268 static void grow(std::vector<double>& v, std::size_t n) {
269 if (v.size() < n) v.resize(n, 0.0);
270 }
271
272 Entry& entry(const solvers::CacheNodeMetrics<T>& cm) {
273 for (std::size_t i = 0; i < order_.size(); ++i)
274 if (order_[i].name == cm.name) return order_[i];
275 Entry en;
276 en.node = cm.node;
277 en.name = cm.name;
278 en.itemcap = cm.itemcap;
279 en.nitems = cm.nitems;
280 order_.push_back(en);
281 return order_.back();
282 }
283
284 /** One quantity: an ABSENT ratio contributes nothing and leaves it absent. */
285 static void accum(std::vector<double>& a, const std::vector<T>& ratio,
286 const std::vector<double>& arv) {
287 if (ratio.empty()) return;
288 grow(a, ratio.size());
289 for (std::size_t r = 0; r < ratio.size() && r < arv.size(); ++r) {
290 const double v = num_traits<T>::to_double(ratio[r]);
291 if (std::isfinite(v)) a[r] += arv[r] * v;
292 }
293 }
294
295 static void accum_rows(Matrix<double>& a, const Matrix<T>& hl,
296 const std::vector<double>& arv) {
297 if (hl.rows() == 0 || hl.cols() == 0) return;
298 if (a.rows() == 0) a = Matrix<double>(hl.rows(), hl.cols(), 0.0);
299 for (std::size_t r = 0; r < hl.rows() && r < a.rows(); ++r) {
300 const double w = r < arv.size() ? arv[r] : 0.0;
301 for (std::size_t l = 0; l < hl.cols() && l < a.cols(); ++l) {
302 const double v = num_traits<T>::to_double(hl(r, l));
303 if (std::isfinite(v)) a(r, l) += w * v;
304 }
305 }
306 }
307
308 /**
309 * A class with no arrival anywhere divides by nothing and STAYS NaN: its
310 * ratio is undefined, and 0 would read as "never hits".
311 */
312 static std::vector<double> divide(const std::vector<double>& a,
313 const std::vector<double>& d) {
314 std::vector<double> r;
315 if (a.empty()) return r;
316 r.assign(a.size(), nan_());
317 for (std::size_t i = 0; i < a.size(); ++i)
318 if (i < d.size() && d[i] > 0.0) r[i] = a[i] / d[i];
319 return r;
320 }
321
322 /**
323 * The per-class arrival INTO EACH NODE, flattened node-major.
324 *
325 * The weight is the cache's own arrival and not the Source throughput,
326 * because the latter is 0 on a closed cache model. The station table
327 * handed in is all zeros on purpose: only the Cache rows are read, and
328 * those come from the chain reference throughput and the node visits,
329 * never from a station row.
330 */
331 static std::vector<double> cache_arrival(const qn::NetworkStruct<T>& sn,
332 const Matrix<double>& TN) {
333 const std::size_t I = sn.nodes.size(), R = sn.nclasses, M = sn.nstations;
334 std::vector<double> out(I * R, 0.0);
335 if (TN.rows() == 0) return out;
336 Matrix<T> TNt(TN.rows(), TN.cols(), num_traits<T>::from_int(0));
337 for (std::size_t i = 0; i < TN.rows(); ++i)
338 for (std::size_t r = 0; r < TN.cols(); ++r)
339 TNt(i, r) = num_traits<T>::from_double(TN(i, r));
340 const Matrix<T> AN(M, R, num_traits<T>::from_int(0));
341 const Matrix<T> ANn = api::sn_get_node_arvr_from_tput<T>(sn, TNt, AN);
342 if (ANn.rows() != I) return out;
343 for (std::size_t ind = 0; ind < I; ++ind)
344 for (std::size_t r = 0; r < R; ++r) {
345 const double v = num_traits<T>::to_double(ANn(ind, r));
346 out[ind * R + r] = std::isfinite(v) ? v : 0.0;
347 }
348 return out;
349 }
350
351 std::vector<Entry> order_;
352 };
353
354 /**
355 * One stage in steady state under the enumerated CTMC at `cutoff`, with its cache split,
356 * which is exact (read off the chain). An ensemble built on SolverCTMC stages.
357 */
360 co.cutoff = cutoff;
362 EnvStageAvg<T> out;
363 out.QN = to_double(r.QN);
364 out.UN = to_double(r.UN);
365 out.TN = to_double(r.TN);
366 out.cache = r.cache;
367 return out;
368 }
369
370private:
371 /** A stage table widened to double, which is what the limits blend in. */
372 static Matrix<double> to_double(const Matrix<T>& A) {
373 Matrix<double> out(A.rows(), A.cols(), 0.0);
374 for (std::size_t i = 0; i < A.rows(); ++i)
375 for (std::size_t j = 0; j < A.cols(); ++j) out(i, j) = num_traits<T>::to_double(A(i, j));
376 return out;
377 }
378
379 /** The slow-environment limit: each stage on its own, blended by probEnv. */
380 EnvLimitSolution solve_dec() {
381 const std::size_t E = envObj.nstages();
382 EnvLimitSolution out;
383 out.method = "dec";
384 out.prob_env = envObj.prob_env;
385 out.QN = Matrix<double>(M, K, 0.0);
386 out.UN = Matrix<double>(M, K, 0.0);
387 out.TN = Matrix<double>(M, K, 0.0);
388 out.QStage.assign(E, Matrix<double>(M, K, 0.0));
389 out.UStage.assign(E, Matrix<double>(M, K, 0.0));
390 out.TStage.assign(E, Matrix<double>(M, K, 0.0));
391 CacheAccum acc;
392 for (std::size_t e = 0; e < E; ++e) {
393 const EnvStageAvg<T> s = stage_steady_state(envObj.stage(e).model);
394 const double p = envObj.prob_env[e];
395 acc.add(envObj.stage(e).model, s, p);
396 for (std::size_t i = 0; i < M; ++i)
397 for (std::size_t r = 0; r < K; ++r) {
398 out.QStage[e](i, r) = s.QN(i, r);
399 out.UStage[e](i, r) = s.UN(i, r);
400 out.TStage[e](i, r) = s.TN(i, r);
401 out.QN(i, r) += p * s.QN(i, r);
402 out.UN(i, r) += p * s.UN(i, r);
403 out.TN(i, r) += p * s.TN(i, r);
404 }
405 }
406 out.cache = acc.finish();
407 return out;
408 }
409
410 /** The fast-environment limit: one rate-averaged model, solved once. */
411 EnvLimitSolution solve_avg() {
412 EnvLimitSolution out;
413 out.method = "avg";
414 out.prob_env = envObj.prob_env;
415 const qn::NetworkStruct<T> avg = rate_averaged_model();
416 const EnvStageAvg<T> s = stage_steady_state(avg);
417 // One model, so the weight is the arrival itself and the normalisation
418 // returns exactly what the single solve reported.
419 CacheAccum acc;
420 acc.add(avg, s, 1.0);
421 out.cache = acc.finish();
422 out.QN = Matrix<double>(M, K, 0.0);
423 out.UN = Matrix<double>(M, K, 0.0);
424 out.TN = Matrix<double>(M, K, 0.0);
425 for (std::size_t i = 0; i < M; ++i)
426 for (std::size_t r = 0; r < K; ++r) {
427 out.QN(i, r) = s.QN(i, r);
428 out.UN(i, r) = s.UN(i, r);
429 out.TN(i, r) = s.TN(i, r);
430 }
431 return out;
432 }
433
434 /**
435 * One stage, in steady state: the injected solver when there is one, else
436 * the fluid analyzer, in the form that also REPORTS ITS CACHE SPLIT when the
437 * stage holds a Cache.
438 *
439 * WHICH ENTRY POINT IS A MODEL QUESTION AND NOT A PREFERENCE, because the
440 * two resolve `default` differently and therefore answer with DIFFERENT
441 * CLOSURES. `solver_fluid` is `solver_fluid_analyzer.m`: it hands the method
442 * to `fluid_dispatch`, which takes `default` to the matrix method (to
443 * `closing` on a DPS model). `solver_fluid_run_analyzer` is the port of
444 * `runAnalyzer`'s resolution and re-resolves `default` through the
445 * minnormal/dae ladder. On a stage sitting at its saturation knee the two
446 * disagree outright -- N/(Z+D) = 1/D is a continuum of first-order
447 * equilibria, and which point a closure picks is the whole answer -- so
448 * routing every stage through the runner silently moved the limits off the
449 * matrix closure they are pinned against.
450 *
451 * A CACHE STAGE IS THE ONE THAT MUST TAKE THE RUNNER, and for the reference's
452 * own reason rather than for a reporting convenience: `runAnalyzer` resolves
453 * a Cache model to `rmf`, which lives in `fluid_cacheqn.h` and which
454 * `fluid_dispatch` refuses BY NAME (it cannot call it without a cyclic
455 * include). That route is also the one that reports the split, which is the
456 * silence this file used to refuse a cache model over.
457 */
458 EnvStageAvg<T> stage_steady_state(const qn::NetworkStruct<T>& sn) const {
459 if (stage_fn_) return stage_fn_(sn);
460 if (opt.stage_solver == "ctmc") return ctmc_stage_steady(sn, opt.stage_cutoff);
461 fluid::FluidOptions fo = opt.stage;
462 // A limit starts from nothing carried over -- there is no entry state to
463 // seed with, which is the whole content of the approximation.
464 fo.init_sol.clear();
465 EnvStageAvg<T> out;
466 const fluid::FluidSolution s =
467 fluid::detail::fluid_has_cache(sn)
468 ? fluid::solver_fluid_run_analyzer<T>(sn, fo, nullptr, nullptr, &out.cache)
469 : fluid::solver_fluid<T>(sn, fo);
470 out.QN = s.QN;
471 out.UN = s.UN;
472 out.TN = s.TN;
473 return out;
474 }
475
476 /**
477 * `buildRateAveragedModel`: stage 1's network with every MODULATED rate
478 * replaced by its probEnv-weighted mean, as an exponential.
479 *
480 * The refresh chain is rerun because `rates` and everything derived from it
481 * are read off the service table; editing the table alone would leave the
482 * struct describing stage 1.
483 */
484 qn::NetworkStruct<T> rate_averaged_model() const {
485 const std::size_t E = envObj.nstages();
486 qn::NetworkStruct<T> sn = envObj.stage(0).model;
487 std::vector<double> r(E, 0.0);
488 for (std::size_t i = 0; i < M; ++i) {
489 const lang::NodeType nt = sn.stations[i].nodetype;
492 continue;
493 for (std::size_t k = 0; k < K; ++k) {
494 bool ok = true;
495 for (std::size_t e = 0; e < E && ok; ++e) {
496 const qn::NetworkStruct<T>& se = envObj.stage(e).model;
497 // The reference's `any(isnan(r)) || any(r<=0)`: a rate this
498 // port reports as disabled is MATLAB's NaN, and either way
499 // the pair is left as configured.
500 if (se.disabled[i][k]) {
501 ok = false;
502 break;
503 }
504 r[e] = num_traits<T>::to_double(se.rates(i, k));
505 if (!(r[e] > 0.0) || !std::isfinite(r[e])) ok = false;
506 }
507 if (!ok) continue;
508 double lo = r[0], hi = r[0], avg = 0.0;
509 for (std::size_t e = 0; e < E; ++e) {
510 lo = std::min(lo, r[e]);
511 hi = std::max(hi, r[e]);
512 avg += envObj.prob_env[e] * r[e];
513 }
514 // Not modulated: keep the original distribution, which may carry
515 // a shape the exponential of its mean would throw away.
516 if (hi - lo <= 1e-12 * std::max(1.0, hi)) continue;
517 sn.set_service(i + 1, k + 1,
518 lang::Distrib<T>::exp_rate(num_traits<T>::from_double(avg)));
519 }
520 }
521 sn.refresh_struct();
522 return sn;
523 }
524
525 Environment<T>& envObj;
526 EnvOptions opt;
527 EnvStageAvgFn<T> stage_fn_;
528 std::size_t M = 0, K = 0;
529 bool dec_ = false;
530};
531
532/** `solveEnvLimit` on the original stages. */
533template <class T>
535 SolverEnvLimit<T> s(e, o);
536 return s.solve();
537}
538
539/** `solveEnvLimit` with the CALLING solver running every stage. */
540template <class T>
542 EnvStageAvgFn<T> stage_fn) {
543 SolverEnvLimit<T> s(e, o, stage_fn);
544 return s.solve();
545}
546
547} // namespace env
548} // namespace line
549
550#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
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
UnsupportedError(const std::string &what)
Definition error.h:51
The RATE accumulator behind the cache blend, SolverENV.accumCacheMetric.
solvers::CacheMetrics< double > finish() const
Divide the accumulated rates by the accumulated arrivals, ONCE.
void add(const qn::NetworkStruct< T > &sn, const EnvStageAvg< T > &stage, double w)
One stage's contribution, weighted by its arrival into each cache.
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)
static EnvStageAvg< T > ctmc_stage_steady(const qn::NetworkStruct< T > &sn, double cutoff)
One stage in steady state under the enumerated CTMC at cutoff, with its cache split,...
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.
mva::AvgResult< T > solver_ctmc_run_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Solve and format in one call, for a caller with no use for the chain.
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....
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
SolverENV: a queueing network in a random environment.
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
The SolverCTMC knobs this port honours.
double cutoff
< 0 = not given
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
The metrics getAvg returns, after filtering.
Matrix< T > TN
throughput
Matrix< T > UN
utilization
Matrix< T > QN
queue length
solvers::CacheMetrics< T > cache
What the cache branches observed, EMPTY on a model with no Cache node and on every solver that does n...
Every Cache node of the model, in node order; empty on a model with none.
std::vector< CacheNodeMetrics< T > > caches
One Cache node's measured behaviour.
std::vector< double > itemcap
(h) capacity of each list
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
std::string name
The Cache node's NAME, which is what a cross-language payload must key on.