LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
env_dispatch.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_ENV_DISPATCH_H
6#define LINE_SOLVERS_ENV_ENV_DISPATCH_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The SolverENV entry surface: a port of the analyzer selection that
12 * `@@SolverENV/SolverENV.m`'s `init` performs (lines 311-327) and of the
13 * compression switch `setCompression` arms (line 160, applied at line 412).
14 *
15 * WHAT SELECTS WHAT. The reference holds ONE solver object and swaps the
16 * function handle it delegates `pre`/`analyze`/`post`/`finish` to:
17 * `method` in {statevec, blend} installs `solver_env_statevec_analyzer`, and
18 * every other method installs `solver_env_meanfield_analyzer`. This port has
19 * two solver CLASSES instead of one object with two handles, because the two
20 * couplings carry different objects across an environment switch -- the
21 * mean-field one carries the marginal mean queue lengths, the state-vector one
22 * the whole joint distribution -- and so they need different options, different
23 * stage solvers and different result types. This file is the switch that stands
24 * in front of them, and it is the only place that knows both exist.
25 *
26 * WHY `SolverEnv` STILL REFUSES `statevec` BY NAME. It is the mean-field
27 * coupling and nothing else; asked for the state-vector one it cannot answer,
28 * and answering with the mean-field collapse would be the silent fallback the
29 * whole port avoids. The refusal is therefore kept where it is and this entry
30 * routes AROUND it rather than through it.
31 *
32 * THE THIRD THING THIS SWITCH SELECTS is neither coupling: `avg` and `dec`, the
33 * closed-form fast/slow limits of `solveEnvLimit`, which bypass the fixed point
34 * entirely and are ported as `SolverEnvLimit`. The reference routes them the
35 * same way, at the TOP of `runAnalyzer` and before `iterate`, because there is
36 * no inter-stage coupling to choose when nothing crosses a switch.
37 *
38 * WHICH COUPLINGS TAKE A LAYERED STAGE. Only the mean-field one, and that is
39 * the reference's split rather than this port's: a `statevec`/`blend` run needs
40 * one generator per stage to propagate a joint law across, which an LQN does not
41 * have (it decomposes into a network per layer), and the `avg`/`dec` limits read
42 * a stage's station rate table, which an LQN does not carry. Both refuse a
43 * LayeredNetwork stage by name, in their own `init`, through
44 * `Environment::reject_lqn_stages`; so does the compression, whose macro-state
45 * is a network at averaged rates. The one thing still refused everywhere is the
46 * cache aggregation of the two analyzers.
47 *
48 * WHAT COMPRESSION IS, AND WHY IT IS A SEPARATE OVERLOAD. `setCompression` is
49 * an explicit opt-in flag in the reference, defaulting to false, and the knobs
50 * it reads live in `options.config` rather than beside `method`. Passing the
51 * `EnvCompressOptions` is that opt-in here: there is no way to ask for the
52 * compressed solve without saying which decomposition kernel and which
53 * partition search it should use. The reference applies compression inside
54 * `init`, i.e. BEFORE the analyzer runs and independently of which analyzer was
55 * installed, so both couplings can run on a compressed environment and both do
56 * here.
57 */
58
59#include <algorithm>
60#include <cstddef>
61#include <string>
62#include <vector>
63
65#include "line/num/number.h"
70#include "line/util/matrix.h"
71
72namespace line {
73namespace env {
74
75/**
76 * What the ENV entry reports: the environment-blended metrics, and the whole
77 * result of whichever coupling produced them.
78 *
79 * The sub-result is kept rather than reduced away because the two couplings
80 * report different things beside the means -- the mean-field one the per-stage
81 * exit metrics and the entry queue lengths its fixed point converged to, the
82 * state-vector one the entry and sojourn-end DISTRIBUTIONS and the cache blend
83 * -- and a caller who chose a coupling chose it for those.
84 */
85template <class T>
87 /** Environment-averaged metrics, (nstations x nclasses). */
89 /** What ran: `meanfield`, `statevec`, or the limit `avg` / `dec`. */
90 std::string method;
91 /** True when the environment was aggregated before the coupling ran. */
92 bool compressed = false;
93 /** The aggregation, when there was one; its `eps` against `epsMAX` says whether it was meaningful. */
95 /** Populated on the mean-field path. */
97 /** Populated on the state-vector path. */
99 /** Populated on the closed-form limit path (`avg`, `dec`). */
101 /**
102 * ZERO ON THE LIMIT PATH, and that is the answer rather than a gap: a limit
103 * reads the environment once and iterates nothing, so reporting an
104 * iteration count would describe a fixed point that never ran.
105 */
106 int iterations = 0;
107 /**
108 * True on the limit path: a closed form has converged by construction. The
109 * flag says whether the numbers can be trusted as the method's own answer,
110 * not whether a loop ended, and a limit's answer is always its own.
111 */
112 bool converged = false;
113};
114
115namespace dispatch_detail {
116
117/** The mean-field metrics, which are double by construction, in the caller's arithmetic. */
118template <class T>
119Matrix<T> env_widen(const Matrix<double>& A) {
121 for (std::size_t i = 0; i < A.rows(); ++i)
122 for (std::size_t j = 0; j < A.cols(); ++j) B(i, j) = num_traits<T>::from_double(A(i, j));
123 return B;
124}
125
126/**
127 * `EnvOptions` as the state-vector coupling reads it.
128 *
129 * `stage_solver` is carried through UNTRANSLATED, so a caller who left it at
130 * the mean-field default of `fluid` is refused by name inside
131 * `SolverEnvStatevec::init` -- which is the reference's own error, "this method
132 * requires SolverENV to be instantiated with the CTMC solver"
133 * (`@@SolverENV/SolverENV.m` line 763). Silently substituting `ctmc` would run a
134 * different inner solver from the one asked for.
135 *
136 * TWO THINGS `EnvOptions` CANNOT CARRY, and neither is invented here: the
137 * per-transition state reset maps (functions on a state vector, which have no
138 * counterpart among the mean-field resets, as `ResetStateVec` explains) and the
139 * per-stage horizon overrides. A caller who needs either builds an
140 * `EnvStatevecOptions` and calls `solver_env_statevec` directly. The horizon
141 * that IS carried is `timespan_end`, and it should be a few mean holding times
142 * rather than a large safe number, for the ode23 reason `EnvStatevecOptions`
143 * gives at length.
144 */
145template <class T>
146EnvStatevecOptions<T> env_statevec_options(const EnvOptions& o) {
147 EnvStatevecOptions<T> s;
148 s.iter_max = o.iter_max;
149 s.iter_tol = o.iter_tol;
150 s.stage_solver = o.stage_solver;
151 s.sojourn = o.sojourn;
152 s.timespan_end = o.timespan_end;
153 // `stage_cutoff` bounds an OPEN stage's level count under either backend:
154 // it truncates the enumerated chain for the CTMC one and the LD-QBD level
155 // recursion for the MAM one. A closed stage takes its population instead
156 // and ignores it in both.
157 if (o.stage_cutoff > 0) s.mam_cutoff = static_cast<std::size_t>(o.stage_cutoff);
158 return s;
159}
160
161/** Read the state-vector result into the common shape. */
162template <class T>
163void env_take_statevec(EnvAnalyzerSolution<T>& out) {
164 out.method = "statevec";
165 out.QN = out.statevec.QN;
166 out.UN = out.statevec.UN;
167 out.TN = out.statevec.TN;
168 out.iterations = out.statevec.iterations;
169 out.converged = out.statevec.converged;
170}
171
172/**
173 * Read the mean-field result into the common shape.
174 *
175 * `asked` is reported rather than the bare analyzer name, because `smp` and
176 * `statedep` also run this analyzer and are not the same run: `statedep`
177 * rewrote the environment as it went, and a result labelled `meanfield` would
178 * describe an environment that was an input when it was an output.
179 */
180template <class T>
181void env_take_meanfield(EnvAnalyzerSolution<T>& out, const std::string& asked = "meanfield") {
182 out.method = asked.empty() ? "meanfield" : asked;
183 out.QN = env_widen<T>(out.meanfield.avg.QN);
184 out.UN = env_widen<T>(out.meanfield.avg.UN);
185 out.TN = env_widen<T>(out.meanfield.avg.TN);
186 out.iterations = out.meanfield.avg.iterations;
187 out.converged = out.meanfield.avg.converged;
188}
189
190/** Read a closed-form limit result into the common shape. */
191template <class T>
192void env_take_limit(EnvAnalyzerSolution<T>& out) {
193 out.method = out.limit.method;
194 out.QN = env_widen<T>(out.limit.QN);
195 out.UN = env_widen<T>(out.limit.UN);
196 out.TN = env_widen<T>(out.limit.TN);
197 out.iterations = 0;
198 out.converged = true;
199}
200
201/** True for the two names the reference's `init` installs the state-vector analyzer on. */
202inline bool env_is_statevec(const std::string& m) { return m == "statevec" || m == "blend"; }
203
204/** True for the two names `runAnalyzer` sends to `solveEnvLimit`. */
205inline bool env_is_limit(const std::string& m) { return m == "avg" || m == "dec"; }
206
207} // namespace dispatch_detail
208
209/**
210 * Port of `SolverENV.listValidMethods`.
211 *
212 * Every name here selects a COUPLING -- what crosses an environment switch --
213 * and each is dispatched by this file or by `SolverEnv`'s own ladder:
214 * `default`/`meanfield` carry the marginal means, `statevec`/`blend` the whole
215 * joint distribution, `smp` lifts the Markovian-arc check for a semi-Markov
216 * environment, `statedep` makes the transition depend on the state it leaves,
217 * and `avg`/`dec` are the closed-form fast/slow limits of `solveEnvLimit`.
218 *
219 * `default` is the reference's spelling and `meanfield` this port's; both name
220 * the mean-field coupling and `SolverEnv::init` accepts either.
221 */
222inline std::vector<std::string> env_list_valid_methods() {
223 return {"default", "meanfield", "smp", "statedep", "statevec", "blend", "avg", "dec"};
224}
225
226/** Port of `runAnalyzerChecks`' method gate: an unlisted method is refused. */
227inline void env_check_method(const std::string& method) {
228 const std::vector<std::string> valid = env_list_valid_methods();
229 if (std::find(valid.begin(), valid.end(), method) != valid.end()) return;
230 throw UnsupportedError("SolverENV: the '" + method + "' method is unsupported by this solver");
231}
232
233/**
234 * `SolverENV.init`'s analyzer selection: solve the environment with the
235 * coupling `o.method` names.
236 *
237 * An unknown method is refused by `SolverEnv`'s ladder, which is reached
238 * because everything that is not the state-vector coupling IS the mean-field
239 * one in the reference -- the `else` of its `if`, not a separate case.
240 */
241template <class T>
244 if (dispatch_detail::env_is_limit(o.method)) {
245 out.limit = solver_env_limit(e, o);
246 dispatch_detail::env_take_limit(out);
247 return out;
248 }
249 if (dispatch_detail::env_is_statevec(o.method)) {
250 out.statevec = solver_env_statevec(e, dispatch_detail::env_statevec_options<T>(o));
251 dispatch_detail::env_take_statevec(out);
252 return out;
253 }
255 dispatch_detail::env_take_meanfield(out, o.method);
256 return out;
257}
258
259/**
260 * The same, on a COMPRESSED environment: aggregate the stages first, then run
261 * the coupling over the macro-states.
262 *
263 * The mean-field path delegates to `solver_env_meanfield`, which already owns
264 * the construct-then-apply-then-solve order that `env_apply_macro_probabilities`
265 * requires. The state-vector path repeats that order here rather than reaching
266 * for a wrapper of its own: `SolverEnvStatevec`'s constructor calls
267 * `Environment::init()`, which recomputes probEnv and probOrig from the macro
268 * arcs, so the macro probabilities have to be written back AFTER it and BEFORE
269 * `solve()`, exactly as on the mean-field side.
270 */
271template <class T>
273 const EnvCompressOptions& c) {
275 out.compressed = true;
276 // A limit reads probEnv and the stage models, both of which the aggregation
277 // rewrites, so it runs on the macro-states like the couplings do; the macro
278 // probabilities must still be written back after `SolverEnvLimit`'s own
279 // `Environment::init()` and before it reads them, which is why the construct
280 // and the solve are separated here.
281 if (dispatch_detail::env_is_limit(o.method)) {
282 out.compression = env_compress(e, c);
283 SolverEnvLimit<T> s(*out.compression.env, o);
285 out.limit = s.solve();
286 dispatch_detail::env_take_limit(out);
287 return out;
288 }
289 if (dispatch_detail::env_is_statevec(o.method)) {
290 out.compression = env_compress(e, c);
291 SolverEnvStatevec<T> s(*out.compression.env, dispatch_detail::env_statevec_options<T>(o));
293 out.statevec = s.solve();
294 dispatch_detail::env_take_statevec(out);
295 return out;
296 }
297 out.meanfield = solver_env_meanfield(e, o, c);
298 out.compression = out.meanfield.compression;
299 dispatch_detail::env_take_meanfield(out, o.method);
300 return out;
301}
302
303} // namespace env
304} // namespace line
305
306#endif // LINE_SOLVERS_ENV_ENV_DISPATCH_H
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 state-vector environment solver.
EnvStatevecSolution< T > solve()
A random environment: a port of matlab/src/lang/Environment.m, restricted to what SolverENV reads out...
Dense matrix and non-owning view.
EnvStatevecSolution< T > solver_env_statevec(Environment< T > &e, const EnvStatevecOptions< T > &o)
Solve in one call, for a caller with no use for the solver object.
EnvLimitSolution solver_env_limit(Environment< T > &e, const EnvOptions &o)
solveEnvLimit on the original stages.
void env_check_method(const std::string &method)
Port of runAnalyzerChecks' method gate: an unlisted method is refused.
EnvMeanfieldSolution< T > solver_env_meanfield(Environment< T > &e, const EnvOptions &o)
The mean-field solve on the original stages, with no compression.
EnvCompression< T > env_compress(const Environment< T > &e0, const EnvCompressOptions &opt)
Port of applyCompression: pick a partition, decompose, and build the macro-state environment.
std::vector< std::string > env_list_valid_methods()
Port of SolverENV.listValidMethods.
void env_apply_macro_probabilities(Environment< T > &e, const EnvCompression< T > &c)
probEnv = pMacro and probOrig = newEmbweight, the two quantities applyCompression overwrites on the e...
EnvAnalyzerSolution< T > solver_env(Environment< T > &e, const EnvOptions &o)
SolverENV.init's analyzer selection: solve the environment with the coupling o.method names.
Number-type abstraction for the templated API port.
SolverENV: a queueing network in a random environment.
The two CLOSED-FORM environment limits: SolverENV.solveEnvLimit, reached by options....
SolverENV, the default mean-field path: ENVIRONMENT COMPRESSION.
SolverENV, method = "statevec": a port of matlab/src/solvers/ENV/solver_env_statevec_analyzer....
What the ENV entry reports: the environment-blended metrics, and the whole result of whichever coupli...
EnvMeanfieldSolution< T > meanfield
Populated on the mean-field path.
std::string method
What ran: meanfield, statevec, or the limit avg / dec.
EnvLimitSolution limit
Populated on the closed-form limit path (avg, dec).
EnvStatevecSolution< T > statevec
Populated on the state-vector path.
bool converged
True on the limit path: a closed form has converged by construction.
Matrix< T > QN
Environment-averaged metrics, (nstations x nclasses).
EnvCompression< T > compression
The aggregation, when there was one; its eps against epsMAX says whether it was meaningful.
bool compressed
True when the environment was aggregated before the coupling ran.
int iterations
ZERO ON THE LIMIT PATH, and that is the answer rather than a gap: a limit reads the environment once ...
The knobs applyCompression reads out of options.config.
Everything applyCompression computes, plus the compressed environment.
What a limit solve reports.
Options of SolverENV.
Definition solver_env.h:110
std::string method
The inter-stage coupling: meanfield is the reference's default.
Definition solver_env.h:122
What the state-vector coupling reports.