LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_facade.cpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5
6/**
7 * @file
8 * The one translation unit that instantiates the solver stack for the facade.
9 *
10 * `line/solvers/solver.h` declares a `double`-only, non-template solver API so
11 * that this file can carry every body: a caller including that header pays for
12 * none of the template instantiation below, and `line_mp_api` pays for it once
13 * for the CLI, the tests and the examples together.
14 *
15 * Every entry point forwards to the runner the CLI calls, so a program written
16 * against the facade and `line-cli -s <solver>` on the same model produce the
17 * same numbers by construction. Nothing is computed here beyond the reshaping
18 * the tables need.
19 */
20
21#include "line/solvers/solver.h"
22
23#include <algorithm>
24#include <cctype>
25#include <cstdio>
26#include <iomanip>
27#include <iostream>
28#include <limits>
29#include <map>
30#include <set>
31#include <sstream>
32#include <utility>
33
38#include "line/lang/qn/nodes.h"
60#include "line/lang/qn/state.h"
64
65namespace line {
66
67// ---------------------------------------------------------------------------
68// AvgTable
69// ---------------------------------------------------------------------------
70
71double AvgTable::get(const std::string& col, const std::string& station,
72 const std::string& jobclass) const {
73 for (std::size_t i = 0; i < Station.size(); ++i) {
74 if (Station[i] != station || JobClass[i] != jobclass) continue;
75 if (col == "QLen") return QLen[i];
76 if (col == "Util") return Util[i];
77 if (col == "RespT") return RespT[i];
78 if (col == "ResidT") return ResidT[i];
79 if (col == "ArvR") return ArvR[i];
80 if (col == "Tput") return Tput[i];
81 const std::vector<double>& ci = column(col);
82 return i < ci.size() ? ci[i] : std::numeric_limits<double>::quiet_NaN();
83 }
84 return std::numeric_limits<double>::quiet_NaN();
85}
86
87std::vector<double> AvgTable::column(const std::string& col) const {
88 if (col == "QLen") return QLen;
89 if (col == "Util") return Util;
90 if (col == "RespT") return RespT;
91 if (col == "ResidT") return ResidT;
92 if (col == "ArvR") return ArvR;
93 if (col == "Tput") return Tput;
94 if (col == "QLenCI") return QLenCI;
95 if (col == "UtilCI") return UtilCI;
96 if (col == "RespTCI") return RespTCI;
97 if (col == "ResidTCI") return ResidTCI;
98 if (col == "ArvRCI") return ArvRCI;
99 if (col == "TputCI") return TputCI;
100 throw InputError("AvgTable::column: unknown column '" + col + "'");
101}
102
103namespace {
104
105template <class Keep>
106AvgTable filter_avg_table(const AvgTable& source, Keep keep) {
107 AvgTable out;
108 out.solver = source.solver;
109 out.method = source.method;
110 out.iter = source.iter;
112 out.lognormconst = source.lognormconst;
113 out.ListCost = source.ListCost;
114 out.warning = source.warning;
115 out.SysClass = source.SysClass;
116 out.SysRespT = source.SysRespT;
117 out.SysTput = source.SysTput;
118 for (std::size_t i = 0; i < source.Station.size(); ++i) {
119 if (!keep(i)) continue;
120 out.Station.push_back(source.Station[i]);
121 out.JobClass.push_back(source.JobClass[i]);
122 out.QLen.push_back(source.QLen[i]);
123 out.Util.push_back(source.Util[i]);
124 out.RespT.push_back(source.RespT[i]);
125 out.ResidT.push_back(source.ResidT[i]);
126 out.ArvR.push_back(source.ArvR[i]);
127 out.Tput.push_back(source.Tput[i]);
128 if (!source.has_ci()) continue;
129 out.QLenCI.push_back(source.QLenCI[i]);
130 out.UtilCI.push_back(source.UtilCI[i]);
131 out.RespTCI.push_back(source.RespTCI[i]);
132 out.ResidTCI.push_back(source.ResidTCI[i]);
133 out.ArvRCI.push_back(source.ArvRCI[i]);
134 out.TputCI.push_back(source.TputCI[i]);
135 }
136 return out;
137}
138
139} // namespace
140
141AvgTable AvgTable::filter_by(const std::string& name) const {
142 return filter_avg_table(*this, [&](std::size_t i) {
143 return Station[i] == name || JobClass[i] == name;
144 });
145}
146
147AvgTable AvgTable::filter_by(const std::string& station, const std::string& jobclass) const {
148 return filter_avg_table(*this, [&](std::size_t i) {
149 return Station[i] == station && JobClass[i] == jobclass;
150 });
151}
152
154 return filter_avg_table(*this, [&](std::size_t i) {
155 return Station[i] == node.get_name();
156 });
157}
158
159AvgTable AvgTable::filter_by(const ::line::JobClass& jobclass) const {
160 return filter_avg_table(*this, [&](std::size_t i) {
161 return JobClass[i] == jobclass.get_name();
162 });
163}
164
165AvgTable AvgTable::filter_by(const Node& node, const ::line::JobClass& jobclass) const {
166 return filter_by(node.get_name(), jobclass.get_name());
167}
168
169AvgTable AvgTable::filter_by(const ::line::JobClass& jobclass, const Node& node) const {
170 return filter_by(node, jobclass);
171}
172
173AvgTable AvgTable::get(const Node& node) const { return filter_by(node); }
174AvgTable AvgTable::get(const ::line::JobClass& jobclass) const { return filter_by(jobclass); }
175AvgTable AvgTable::get(const Node& node, const ::line::JobClass& jobclass) const {
176 return filter_by(node, jobclass);
177}
178AvgTable AvgTable::get(const ::line::JobClass& jobclass, const Node& node) const {
179 return filter_by(node, jobclass);
180}
181AvgTable AvgTable::tget(const Node& node) const { return filter_by(node); }
182AvgTable AvgTable::tget(const ::line::JobClass& jobclass) const { return filter_by(jobclass); }
183AvgTable AvgTable::tget(const Node& node, const ::line::JobClass& jobclass) const {
184 return filter_by(node, jobclass);
185}
186AvgTable AvgTable::tget(const ::line::JobClass& jobclass, const Node& node) const {
187 return filter_by(node, jobclass);
188}
189AvgTable AvgTable::operator()(const Node& node) const { return filter_by(node); }
190AvgTable AvgTable::operator()(const ::line::JobClass& jobclass) const {
191 return filter_by(jobclass);
192}
193AvgTable AvgTable::operator()(const Node& node, const ::line::JobClass& jobclass) const {
194 return filter_by(node, jobclass);
195}
196AvgTable AvgTable::operator()(const ::line::JobClass& jobclass, const Node& node) const {
197 return filter_by(node, jobclass);
198}
199
200void AvgTable::print(std::ostream& out) const {
201 const std::ios::fmtflags flags = out.flags();
202 const std::streamsize precision = out.precision();
203 if (has_ci()) {
204 // MATLAB's getAvgTable under options.confint: every cell reads `mean +- half-width`.
205 const auto cell = [](double m, double h) {
206 std::ostringstream c;
207 c << std::setprecision(6) << m << " +- " << std::setprecision(3) << h;
208 return c.str();
209 };
210 out << std::left << std::setw(16) << "Station" << std::setw(14) << "JobClass" << std::right;
211 for (const char* h : {"QLen", "Util", "RespT", "ResidT", "ArvR", "Tput"}) out << std::setw(22) << h;
212 out << '\n';
213 for (std::size_t i = 0; i < Station.size(); ++i)
214 out << std::left << std::setw(16) << Station[i] << std::setw(14) << JobClass[i]
215 << std::right << std::setw(22) << cell(QLen[i], QLenCI[i])
216 << std::setw(22) << cell(Util[i], UtilCI[i]) << std::setw(22) << cell(RespT[i], RespTCI[i])
217 << std::setw(22) << cell(ResidT[i], ResidTCI[i]) << std::setw(22) << cell(ArvR[i], ArvRCI[i])
218 << std::setw(22) << cell(Tput[i], TputCI[i]) << '\n';
219 out.flags(flags);
220 return;
221 }
222 out << std::left << std::setw(16) << "Station" << std::setw(14) << "JobClass"
223 << std::right << std::setw(12) << "QLen" << std::setw(12) << "Util"
224 << std::setw(12) << "RespT" << std::setw(12) << "ResidT"
225 << std::setw(12) << "ArvR" << std::setw(12) << "Tput" << '\n';
226 out << std::setprecision(6);
227 for (std::size_t i = 0; i < Station.size(); ++i)
228 out << std::left << std::setw(16) << Station[i] << std::setw(14) << JobClass[i]
229 << std::right << std::setw(12) << QLen[i] << std::setw(12) << Util[i]
230 << std::setw(12) << RespT[i] << std::setw(12) << ResidT[i]
231 << std::setw(12) << ArvR[i] << std::setw(12) << Tput[i] << '\n';
232 out.flags(flags);
233 out.precision(precision);
234}
235
236void AvgTable::print() const { print(std::cout); }
237
238std::ostream& operator<<(std::ostream& out, const AvgTable& table) {
239 table.print(out);
240 return out;
241}
242
243void AvgSysTable::print(std::ostream& out) const {
244 const std::ios::fmtflags flags = out.flags();
245 const std::streamsize precision = out.precision();
246 std::size_t w = 12;
247 for (const std::string& s : JobClasses) w = std::max(w, s.size() + 2);
248 out << std::left << std::setw(10) << "Chain" << std::setw(int(w)) << "JobClasses" << std::right
249 << std::setw(12) << "SysRespT" << std::setw(12) << "SysTput" << '\n';
250 out << std::setprecision(6);
251 for (std::size_t c = 0; c < Chain.size(); ++c)
252 out << std::left << std::setw(10) << Chain[c] << std::setw(int(w)) << JobClasses[c] << std::right
253 << std::setw(12) << SysRespT[c] << std::setw(12) << SysTput[c] << '\n';
254 out.flags(flags);
255 out.precision(precision);
256}
257
258void AvgSysTable::print() const { print(std::cout); }
259
260std::ostream& operator<<(std::ostream& out, const AvgSysTable& table) {
261 table.print(out);
262 return out;
263}
264
265void AvgChainTable::print(std::ostream& out) const {
266 const std::ios::fmtflags flags = out.flags();
267 const std::streamsize precision = out.precision();
268 std::size_t ws = 10, wj = 12;
269 for (std::size_t i = 0; i < Station.size(); ++i) {
270 ws = std::max(ws, Station[i].size() + 2);
271 wj = std::max(wj, JobClasses[i].size() + 2);
272 }
273 out << std::left << std::setw(int(ws)) << "Station" << std::setw(10) << "Chain" << std::setw(int(wj))
274 << "JobClasses" << std::right;
275 for (const char* c : {"QLen", "Util", "RespT", "ResidT", "ArvR", "Tput"}) out << std::setw(12) << c;
276 out << '\n' << std::setprecision(6);
277 for (std::size_t i = 0; i < Station.size(); ++i)
278 out << std::left << std::setw(int(ws)) << Station[i] << std::setw(10) << Chain[i] << std::setw(int(wj))
279 << JobClasses[i] << std::right << std::setw(12) << QLen[i] << std::setw(12) << Util[i]
280 << std::setw(12) << RespT[i] << std::setw(12) << ResidT[i] << std::setw(12) << ArvR[i]
281 << std::setw(12) << Tput[i] << '\n';
282 out.flags(flags);
283 out.precision(precision);
284}
285
286void AvgChainTable::print() const { print(std::cout); }
287
288std::ostream& operator<<(std::ostream& out, const AvgChainTable& table) {
289 table.print(out);
290 return out;
291}
292
293void AvgFJTable::print(std::ostream& out) const {
294 const std::ios::fmtflags flags = out.flags();
295 const std::streamsize precision = out.precision();
296 std::size_t wf = 6, wj = 6, wc = 10;
297 for (std::size_t i = 0; i < Fork.size(); ++i) {
298 wf = std::max(wf, Fork[i].size() + 2);
299 wj = std::max(wj, Join[i].size() + 2);
300 wc = std::max(wc, JobClass[i].size() + 2);
301 }
302 out << std::left << std::setw(int(wf)) << "Fork" << std::setw(int(wj)) << "Join" << std::setw(int(wc))
303 << "JobClass" << std::right << std::setw(14) << "FJQLen" << std::setw(14) << "FJRespT" << std::setw(14)
304 << "FJTput" << '\n';
305 out << std::setprecision(6);
306 for (std::size_t i = 0; i < Fork.size(); ++i)
307 out << std::left << std::setw(int(wf)) << Fork[i] << std::setw(int(wj)) << Join[i] << std::setw(int(wc))
308 << JobClass[i] << std::right << std::setw(14) << FJQLen[i] << std::setw(14) << FJRespT[i]
309 << std::setw(14) << FJTput[i] << '\n';
310 out.flags(flags);
311 out.precision(precision);
312}
313
314void AvgFJTable::print() const { print(std::cout); }
315
316std::ostream& operator<<(std::ostream& out, const AvgFJTable& table) {
317 table.print(out);
318 return out;
319}
320
321namespace {
322
323/** Fill the label columns and the per-class system metrics of a table. */
324void fill_table(AvgTable& t, const qn::NetworkStruct<double>& sn, const mva::AvgResult<double>& r) {
325 for (std::size_t i = 0; i < sn.nstations; ++i)
326 for (std::size_t c = 0; c < sn.nclasses; ++c) {
327 const double q = r.QN(i, c), u = r.UN(i, c), rt = r.RN(i, c);
328 const double w = r.WN(i, c), a = r.AN(i, c), x = r.TN(i, c);
329 if (q == 0.0 && u == 0.0 && rt == 0.0 && w == 0.0 && a == 0.0 && x == 0.0) continue;
330 t.Station.push_back(sn.stations[i].name);
331 t.JobClass.push_back(sn.classes[c].name);
332 t.QLen.push_back(q);
333 t.Util.push_back(u);
334 t.RespT.push_back(rt);
335 t.ResidT.push_back(w);
336 t.ArvR.push_back(a);
337 t.Tput.push_back(x);
338 }
339 for (std::size_t c = 0; c < sn.nclasses && c < r.CN.size(); ++c) {
340 t.SysClass.push_back(sn.classes[c].name);
341 t.SysRespT.push_back(r.CN[c]);
342 t.SysTput.push_back(c < r.XN.size() ? r.XN[c] : 0.0);
343 }
344 t.iter = r.iter;
345 t.warning = r.warning;
346 t.ListCost = r.listcost;
347 if (r.lognormconst.has_value()) {
348 t.has_lognormconst = true;
349 t.lognormconst = r.lognormconst.value();
350 }
351}
352
353/**
354 * The same fill for a solver whose solution type is not `mva::AvgResult`.
355 *
356 * THE SSA AND FLUID ARMS OWE BOTH DERIVED COLUMNS. Neither analyzer reports a
357 * residence time or an arrival rate of its own, and neither is a copy of its
358 * neighbour: ResidT is the per-JOB time and RespT the per-VISIT one, and they
359 * agree only where every station is visited once per cycle. ArvR is a FLOW and
360 * parts from the throughput at any station a job leaves by another route.
361 * `sn_get_residt_from_respt` and `sn_get_arvr_from_tput` are the reference's own
362 * conversions and are what the CLI's `-s fluid` and `-s ssa` arms apply. A
363 * Source's ArvR stays zero: nothing arrives TO it.
364 */
365template <class Sol>
366void fill_table_sim(AvgTable& t, const qn::NetworkStruct<double>& sn, const Sol& r) {
367 const std::size_t M = sn.nstations, K = sn.nclasses;
368 Matrix<double> RN(M, K), TN(M, K);
369 for (std::size_t i = 0; i < M; ++i)
370 for (std::size_t c = 0; c < K; ++c) {
371 RN(i, c) = r.RN(i, c);
372 TN(i, c) = r.TN(i, c);
373 }
376 for (std::size_t i = 0; i < M; ++i)
377 for (std::size_t c = 0; c < K; ++c) {
378 const double q = r.QN(i, c), u = r.UN(i, c), rt = r.RN(i, c), x = r.TN(i, c);
379 const bool is_source = sn.stations[i].sched == lang::SchedStrategy::EXT;
380 const double w = WN(i, c), a = is_source ? 0.0 : AN(i, c);
381 // The all-zero row test over ALL SIX columns, as `avg_rows` and the
382 // reference make it: a station only ARRIVALS reach still has a row.
383 if (q == 0.0 && u == 0.0 && rt == 0.0 && w == 0.0 && a == 0.0 && x == 0.0) continue;
384 t.Station.push_back(sn.stations[i].name);
385 t.JobClass.push_back(sn.classes[c].name);
386 t.QLen.push_back(q);
387 t.Util.push_back(u);
388 t.RespT.push_back(rt);
389 t.ResidT.push_back(w);
390 t.ArvR.push_back(a);
391 t.Tput.push_back(x);
392 }
393 for (std::size_t c = 0; c < sn.nclasses && c < r.CN.size(); ++c) {
394 t.SysClass.push_back(sn.classes[c].name);
395 t.SysRespT.push_back(r.CN[c]);
396 t.SysTput.push_back(c < r.XN.size() ? r.XN[c] : 0.0);
397 }
398}
399
400/**
401 * The LDES fill. Its solution type carries the six metrics separately and its
402 * per-class CN/XN are (1 x nclasses) MATRICES rather than vectors, so neither
403 * `fill_table` nor `fill_table_sim` can read it.
404 *
405 * ResidT IS RECOMPUTED RATHER THAN TAKEN, which is what the CLI's `-a avg` LDES
406 * arm does and for the same reason: the engine counts ONE VISIT PER STATION, so
407 * the residence time it reports is its response time wherever a visit ratio is
408 * not 1.
409 */
410void fill_table_ldes(AvgTable& t, const qn::NetworkStruct<double>& sn,
411 const ldes::LdesResult& r) {
413 for (std::size_t i = 0; i < sn.nstations; ++i)
414 for (std::size_t c = 0; c < sn.nclasses; ++c) {
415 const double q = r.QN(i, c), u = r.UN(i, c), rt = r.RN(i, c);
416 const double w = WNfix(i, c), a = r.AN(i, c), x = r.TN(i, c);
417 if (q == 0.0 && u == 0.0 && rt == 0.0 && w == 0.0 && a == 0.0 && x == 0.0) continue;
418 t.Station.push_back(sn.stations[i].name);
419 t.JobClass.push_back(sn.classes[c].name);
420 t.QLen.push_back(q);
421 t.Util.push_back(u);
422 t.RespT.push_back(rt);
423 t.ResidT.push_back(w);
424 t.ArvR.push_back(a);
425 t.Tput.push_back(x);
426 }
427 for (std::size_t c = 0; c < sn.nclasses && c < r.CN.cols(); ++c) {
428 t.SysClass.push_back(sn.classes[c].name);
429 t.SysRespT.push_back(r.CN(0, c));
430 t.SysTput.push_back(c < r.XN.cols() ? r.XN(0, c) : 0.0);
431 }
432}
433
434/**
435 * Append the confidence half-widths of the rows a fill just kept.
436 *
437 * The rows are re-selected by the SAME all-zero test over the six means, so row k here is row k
438 * of the table. The ResidT half-width is the RespT one scaled by W/R: the residence time is the
439 * response time times a constant visit ratio, so the interval scales with it.
440 */
441void fill_table_ci(AvgTable& t, const qn::NetworkStruct<double>& sn, const Matrix<double>& QN,
442 const Matrix<double>& UN, const Matrix<double>& RN, const Matrix<double>& WN,
443 const Matrix<double>& AN, const Matrix<double>& TN, const Matrix<double>& QCI,
444 const Matrix<double>& UCI, const Matrix<double>& RCI, const Matrix<double>& ACI,
445 const Matrix<double>& TCI) {
446 const auto at = [](const Matrix<double>& m, std::size_t i, std::size_t c) {
447 return i < m.rows() && c < m.cols() ? m(i, c) : 0.0;
448 };
449 for (std::size_t i = 0; i < sn.nstations; ++i)
450 for (std::size_t c = 0; c < sn.nclasses; ++c) {
451 const double q = QN(i, c), u = UN(i, c), rt = RN(i, c), w = WN(i, c), a = AN(i, c), x = TN(i, c);
452 if (q == 0.0 && u == 0.0 && rt == 0.0 && w == 0.0 && a == 0.0 && x == 0.0) continue;
453 t.QLenCI.push_back(at(QCI, i, c));
454 t.UtilCI.push_back(at(UCI, i, c));
455 t.RespTCI.push_back(at(RCI, i, c));
456 t.ResidTCI.push_back(rt > 0.0 ? at(RCI, i, c) * w / rt : 0.0);
457 t.ArvRCI.push_back(at(ACI, i, c));
458 t.TputCI.push_back(at(TCI, i, c));
459 }
460}
461
462/**
463 * The caller-facing `map_env` knobs, as the gate wants them.
464 *
465 * They live on `SolverOptions` and NOT on `MvaOptions`/`NcSolverOptions`/
466 * `FluidOptions`, because no `runAnalyzer` in the reference reads them: the
467 * fallback is decided one level ABOVE the runner, in `getAvg`, which is what
468 * this facade's `avg_table` plays the part of here.
469 */
470solvers::MapEnvConfig map_env_config(const SolverOptions& o) {
472 if (!o.map_env.empty()) c.mode = o.map_env;
473 if (!o.map_env_method.empty()) c.method = o.map_env_method;
474 if (o.map_env_maxstages) c.max_stages = o.map_env_maxstages;
475 return c;
476}
477
478ctmc::CtmcOptions ctmc_options(const SolverOptions& o) {
480 if (!o.method.empty()) c.method = o.method;
481 if (o.cutoff > 0.0) c.cutoff = o.cutoff;
482 if (!o.cutoff_vec.empty()) c.cutoff_vec = o.cutoff_vec;
483 if (o.state_max) c.state_max = o.state_max;
484 if (o.timestep > 0.0) c.timestep = o.timestep;
485 c.force = o.force;
486 return c;
487}
488
489/**
490 * The symbolic knobs, which live on their own struct rather than on
491 * CtmcOptions: the backend and its timeout configure the computer-algebra
492 * request, not the generator build. An empty or non-positive field keeps the
493 * engine default, as everywhere else here.
494 */
495ctmc::CtmcSymbolicOptions ctmc_symbolic_options(const SolverOptions& o) {
497 if (!o.symbolic.empty()) s.backend = o.symbolic;
498 if (o.symbolic_timeout > 0) s.timeout_s = o.symbolic_timeout;
499 return s;
500}
501
502fluid::FluidOptions fluid_options(const SolverOptions& o) {
504 if (!o.method.empty()) f.method = o.method;
505 if (o.tol >= 0.0) f.tol = o.tol;
506 if (o.iter_tol >= 0.0) f.iter_tol = o.iter_tol;
507 if (o.iter_max >= 0) f.iter_max = static_cast<std::size_t>(o.iter_max);
508 if (o.timespan_end > 0.0) f.timespan_end = o.timespan_end;
509 if (!o.init_sol.empty()) f.init_sol = o.init_sol;
510 if (o.seed) f.seed = o.seed;
511 if (!o.highvar.empty()) f.highvar = o.highvar;
512 if (o.timestep > 0.0) f.timestep = o.timestep;
513 f.stiff = o.stiff;
514 return f;
515}
516
517/**
518 * The JMT client's options.
519 *
520 * `timespan_end` becomes `max_simulated_time`, which is the finite horizon the
521 * replicated arms REQUIRE, and `replications` is `options.config.replications`, the
522 * size of their ensemble. `iter_max` means nothing to JMT and is not read.
523 */
524jmt::JmtOptions jmt_options(const SolverOptions& o) {
526 if (!o.method.empty()) j.method = o.method;
527 if (o.samples) j.samples = static_cast<double>(o.samples);
528 if (o.seed) j.seed = static_cast<long>(o.seed);
529 if (o.replications > 0) j.replications = o.replications;
530 if (o.timespan_end > 0.0) j.max_simulated_time = o.timespan_end;
531 j.confint = o.confint;
532 j.keep = o.keep;
533 j.verbose = o.verbose;
534 return j;
535}
536
537/** The LDES engine's options, shared by the average solve and `cdf_respt`. */
538ldes::LdesOptions ldes_options(const SolverOptions& o) {
540 if (!o.method.empty()) opt.method = o.method;
541 if (o.samples) opt.samples = o.samples;
542 if (o.events) opt.events = o.events;
543 if (o.seed) opt.seed = static_cast<long>(o.seed);
544 if (o.timeout > 0.0) opt.timeout = o.timeout;
545 if (o.confint > 0.0) opt.confint = o.confint;
546 if (!o.cimethod.empty()) opt.cimethod = o.cimethod;
547 if (o.ciminbatch >= 0) opt.ciminbatch = o.ciminbatch;
548 if (o.ciminobs >= 0) opt.ciminobs = o.ciminobs;
549 if (o.obmoverlap >= 0.0) opt.obmoverlap = o.obmoverlap;
550 if (o.spectral_low_freq_frac > 0.0) opt.spectral_low_freq_frac = o.spectral_low_freq_frac;
551 if (!o.tranfilter.empty()) opt.tranfilter = o.tranfilter;
552 if (o.mserbatch > 0) opt.mserbatch = o.mserbatch;
553 if (o.warmupfrac >= 0.0) opt.warmupfrac = o.warmupfrac;
554 opt.cnvgon = o.cnvgon;
555 if (o.cnvgtol > 0.0) opt.cnvgtol = o.cnvgtol;
556 if (o.cnvgbatch > 0) opt.cnvgbatch = o.cnvgbatch;
557 if (o.cnvgchk >= 0) opt.cnvgchk = o.cnvgchk;
558 if (!o.init_sol.empty()) opt.init_sol = o.init_sol;
559 if (o.init_jobs.rows() > 0) opt.init_jobs = o.init_jobs;
560 opt.track_jobs = o.track_jobs;
561 opt.verbose = o.verbose;
562 return opt;
563}
564
565/**
566 * The facade name of an AUTO method family, or a refusal naming what the family needs.
567 *
568 * Mirrors the CLI's `auto_cli_token_of_family`: a family names its engine outright. ln, env and
569 * uq take a LayeredNetwork, an Environment or a sweep, none of which a Network facade holds.
570 */
571std::string facade_name_of_family(const std::string& fam) {
572 if (fam == "mva" || fam == "nc" || fam == "ctmc" || fam == "mam" || fam == "ag" ||
573 fam == "ba" || fam == "ssa" || fam == "jmt" || fam == "lqns") {
574 std::string up = fam;
575 std::transform(up.begin(), up.end(), up.begin(), ::toupper);
576 return up;
577 }
578 if (fam == "fld") return "FLD";
579 if (fam == "ldes") {
581 throw ExternalError(ErrorKind{"LDES"},
582 "SolverAUTO: method family 'ldes' names the discrete-event engine, and none was "
583 "found (common/ldes or common/ldes.jar, or $LINE_LDES_DIR); 'ssa' is the simulator "
584 "this port builds in process");
585 return "LDES";
586 }
587 if (fam == "ln")
588 throw UnsupportedError(ErrorKind{"Option"},
589 "SolverAUTO: method family 'ln' names the layered solver, which takes a "
590 "LayeredNetwork rather than a Network");
591 if (fam == "env")
592 throw UnsupportedError(ErrorKind{"Option"},
593 "SolverAUTO: method family 'env' names the random-environment solver, which takes an "
594 "Environment rather than a Network");
595 if (fam == "uq")
596 throw UnsupportedError(ErrorKind{"Option"},
597 "SolverAUTO: method family 'uq' names the uncertainty-quantification solver, which "
598 "this facade does not wrap");
599 throw InputError("SolverAUTO: no engine stands behind method family '" + fam + "'");
600}
601
602/** A row matrix as a vector; empty stays empty, meaning not computed. */
603std::vector<double> row_of(const Matrix<double>& m) {
604 std::vector<double> v;
605 if (m.rows() > 0)
606 for (std::size_t c = 0; c < m.cols(); ++c) v.push_back(m(0, c));
607 return v;
608}
609
610/** The model's Cache nodes with nothing measured yet: caps, item counts and item sizes only. */
612 return solvers::cache_metrics_of(sn, std::vector<double>(), std::vector<double>(),
613 std::vector<double>(), std::vector<double>(), Matrix<double>(),
614 Matrix<double>(), std::vector<double>());
615}
616
617/** JMT's per-node hit probabilities; the miss is 1 - hit, as SolverJMT/runAnalyzer.m derives it. */
619 const std::map<std::size_t, std::vector<double> >& hit) {
620 solvers::CacheMetrics<double> out = empty_cache_metrics(sn);
621 for (std::size_t c = 0; c < out.caches.size(); ++c) {
622 solvers::CacheNodeMetrics<double>& m = out.caches[c];
623 const std::map<std::size_t, std::vector<double> >::const_iterator it = hit.find(m.node);
624 if (it == hit.end()) continue;
625 m.hitprob = it->second;
626 for (std::size_t r = 0; r < m.hitprob.size(); ++r) m.missprob.push_back(1.0 - m.hitprob[r]);
628 }
629 return out;
630}
631
632/** The LDES `cacheMetrics` block, keyed by Cache NAME (see `CacheNodeMetrics::name`). */
634 const std::map<std::string, ldes::LdesCacheMetrics>& cm) {
635 solvers::CacheMetrics<double> out = empty_cache_metrics(sn);
636 for (std::size_t c = 0; c < out.caches.size(); ++c) {
637 solvers::CacheNodeMetrics<double>& m = out.caches[c];
638 const std::map<std::string, ldes::LdesCacheMetrics>::const_iterator it = cm.find(m.name);
639 if (it == cm.end()) continue;
640 m.hitprob = row_of(it->second.hit);
641 m.missprob = row_of(it->second.miss);
642 m.delayedprob = row_of(it->second.delayed);
643 m.latency = row_of(it->second.latency);
644 m.hitproblist = it->second.hitList;
645 m.itemprob = it->second.itemProb;
646 m.listcost = row_of(it->second.listCost);
648 }
649 return out;
650}
651
652/**
653 * One engine's average table, by facade name. `o.method` is that engine's own method name.
654 * `cache` receives what the engine observed at the Cache nodes, empty where it analyzes none.
655 */
656void solve_named(const std::string& name, const SolverOptions& o,
661 t.solver = name;
662 if (o.confint < 0.0 || o.confint >= 1.0)
663 throw InputError("SolverOptions: confint must be a confidence level in (0,1), or 0 to disable; got " +
664 std::to_string(o.confint));
665 if (name == "MVA") {
667 if (!o.method.empty()) opt.method = o.method;
668 if (o.tol >= 0.0) opt.tol = o.tol;
669 if (o.iter_tol >= 0.0) opt.iter_tol = o.iter_tol;
670 if (o.iter_max >= 0) opt.iter_max = o.iter_max;
671 if (!o.multiserver.empty()) opt.multiserver = o.multiserver;
672 if (!o.highvar.empty()) opt.highvar = o.highvar;
673 if (!o.np_priority.empty()) opt.np_priority = o.np_priority;
674 if (!o.fork_join.empty()) opt.fork_join = o.fork_join;
675 Matrix<double> init;
677 sn, "SolverMVA", qn::mva_feature_set(mva::resolve_method(sn, opt.method), sn),
678 map_env_config(o),
679 [&opt, &init](const qn::NetworkStruct<double>& m) {
680 return mva::solver_mva_run_analyzer(m, opt, init);
681 },
682 solvers::mva_stage_fn(opt), opt.method);
683 t.method = r.actualmethod;
684 fill_table(t, sn, r);
685 cache = r.cache;
686 if (fj) *fj = r.fj;
687 } else if (name == "NC") {
689 if (!o.method.empty()) opt.method = o.method;
690 if (o.tol >= 0.0) opt.tol = o.tol;
691 if (o.iter_tol >= 0.0) opt.iter_tol = o.iter_tol;
692 if (o.iter_max >= 0) opt.iter_max = o.iter_max;
693 if (!o.highvar.empty()) opt.highvar = o.highvar;
694 if (!o.multiserver.empty()) opt.multiserver = o.multiserver;
695 if (!o.fork_join.empty()) opt.fork_join = o.fork_join;
696 if (o.samples) opt.samples = o.samples;
697 if (o.seed) opt.seed = o.seed;
699 sn, "SolverNC", qn::nc_feature_set(opt.method), map_env_config(o),
700 [&opt](const qn::NetworkStruct<double>& m) { return nc::solver_nc_run_analyzer(m, opt); },
701 solvers::nc_stage_fn(opt), opt.method);
702 t.method = r.actualmethod;
703 fill_table(t, sn, r);
704 cache = r.cache;
705 if (fj) *fj = r.fj;
706 } else if (name == "CTMC") {
707 const ctmc::CtmcOptions opt = ctmc_options(o);
708 // `mdd` never forms the generator, so it has its own analyzer, as the CLI's `-s ctmc` arm routes it.
712 t.method = r.actualmethod;
713 fill_table(t, sn, r);
714 cache = r.cache;
715 } else if (name == "MAM") {
717 if (!o.method.empty()) opt.method = o.method;
718 if (o.tol >= 0.0) opt.tol = o.tol;
719 if (o.iter_max >= 0) opt.iter_max = o.iter_max;
721 t.method = r.actualmethod;
722 fill_table(t, sn, r);
723 cache = r.cache;
724 } else if (name == "BA") {
725 // DELIBERATELY NOT WRAPPED IN `run_avg`. A bound request must be answered
726 // with a bound, and the environment image is an APPROXIMATION of the
727 // model, so bounds computed on it do not bracket the original. The
728 // reference refuses SolverBA inside `needsMapEnv` itself; here the
729 // exclusion is the absence of the wrapper, so do not "fix" the
730 // inconsistency with the arms above.
732 if (!o.method.empty()) opt.method = o.method;
733 opt.level = o.level;
735 t.method = r.actualmethod;
736 fill_table(t, sn, r);
737 cache = r.cache;
738 } else if (name == "SSA") {
740 if (!o.method.empty()) opt.method = o.method;
741 if (o.samples) opt.samples = o.samples;
742 if (o.events) opt.samples = o.events;
743 if (o.seed) opt.seed = o.seed;
744 if (o.cutoff > 0.0) opt.cutoff = o.cutoff;
745 if (o.warmupfrac >= 0.0) opt.warmupfrac = o.warmupfrac;
746 if (!o.state_space_gen.empty()) opt.state_space_gen = o.state_space_gen;
747 opt.verbose = o.verbose;
748 // The derived columns are taken on the MEASURED cache split, as the
749 // CLI's `-s ssa` arm does: the base struct carries `link()`'s even
750 // hit/miss offer, so ArvR and ResidT read off it are not the model's.
751 std::vector<ssa::SsaCacheRatio> cacheratio;
752 const ssa::SsaSolution r = ssa::solver_ssa(sn, opt, &cacheratio);
753 t.method = r.method;
754 fill_table_sim(t, ssa::sn_with_ssa_cache_split<double>(sn, cacheratio), r);
756 } else if (name == "FLD" || name == "FLUID") {
757 const fluid::FluidOptions opt = fluid_options(o);
758 // The fluid arm cannot go through `run_avg`: its runner reports a
759 // `FluidSolution` and the driver an `AvgResult`, so the two halves fill
760 // the table through different helpers. The DECISION is the same one
761 // `run_avg` makes, asked here directly rather than duplicated.
762 const solvers::MapEnvConfig mecfg = map_env_config(o);
765 if (d.needed) {
767 sn, "SolverFLD", mecfg, solvers::fluid_stage_fn(opt), opt.method);
768 t.method = r.actualmethod;
769 fill_table(t, sn, r);
770 cache = r.cache;
771 if (fj) *fj = r.fj;
772 } else {
773 // The refreshed struct carries the CONVERGED cache split; ArvR read off `sn` would report
774 // link()'s offered one instead (cache_replc_routing: 0.5/0.5 for 0.4/0.6), as in `-s fluid`.
777 sn, opt, static_cast<qn::NetworkStruct<double>*>(nullptr), &refreshed, &cache);
778 t.method = r.method;
779 t.iter = static_cast<int>(r.iters);
780 t.warning = r.warning;
781 if (fj) *fj = r.fj;
782 const bool has_ref = refreshed.nstations == sn.nstations && refreshed.nclasses == sn.nclasses;
783 fill_table_sim(t, has_ref ? refreshed : sn, r);
784 }
785 } else if (name == "JMT") {
786 const jmt::JmtOptions opt = jmt_options(o);
788 t.method = r.avg.actualmethod;
789 fill_table(t, sn, r.avg);
790 cache = r.cache_hit_prob.empty() ? r.avg.cache : cache_metrics_of_jmt(sn, r.cache_hit_prob);
791 if (fj) *fj = r.avg.fj;
792 if (o.confint > 0.0 && !r.QCI.empty())
793 fill_table_ci(t, sn, r.avg.QN, r.avg.UN, r.avg.RN, r.avg.WN, r.avg.AN, r.avg.TN, r.QCI,
794 r.UCI, r.RCI, r.ACI, r.TCI);
795 } else if (name == "LDES") {
796 const ldes::LdesOptions opt = ldes_options(o);
798 t.method = opt.method;
799 fill_table_ldes(t, sn, r);
800 cache = cache_metrics_of_ldes(sn, r.cache_metrics);
801 if (fj) *fj = ldes::ldes_fj_metrics(sn, r);
802 if (o.confint > 0.0 && !r.QNCI.empty())
803 fill_table_ci(t, sn, r.QN, r.UN, r.RN, mva::sn_get_residt_from_respt<double>(sn, r.RN), r.AN,
804 r.TN, r.QNCI, r.UNCI, r.RNCI, r.ANCI, r.TNCI);
805 } else if (name == "AG") {
807 if (!o.method.empty() && o.method != "default") opt.method = o.method;
808 if (o.tol >= 0.0) opt.tol = o.tol;
809 if (o.iter_max >= 0) opt.iter_max = o.iter_max;
811 t.method = r.actualmethod;
812 fill_table(t, sn, r);
813 cache = r.cache;
814 } else if (name == "LQNS") {
815 // On a flat Network the lqns family's methods are the qns ones, served by `qnsolver`.
817 if (!o.method.empty()) opt.method = o.method;
818 if (!o.multiserver.empty()) opt.multiserver = o.multiserver;
819 opt.keep = o.keep;
821 t.method = r.actualmethod;
822 fill_table(t, sn, r);
823 cache = r.cache;
824 } else {
825 throw UnsupportedError(ErrorKind{"Solver"}, "avg_table: unknown solver '" + name + "'");
826 }
827}
828
829/**
830 * SolverAUTO's average table, as `-s auto` computes it (line_cli.cpp, `choose_auto_plan`).
831 *
832 * `o.method` is resolved first: a method FAMILY ('nc', 'nc.comom', 'bound') names the engine and
833 * passes the submethod to it; an INTENT ('default', 'sim', 'exact', 'fast', 'accurate', 'heur')
834 * picks the ranking, whose proposed order is a RETRY LIST. Only the first attempt carries the
835 * method the ranking gated on. Input and internal errors propagate; the other categories move on,
836 * and when every candidate fails the chosen engine's error is reported.
837 */
838void solve_auto(const SolverOptions& o, const qn::NetworkStruct<double>& sn, AvgTable& t,
841 SolverOptions oo = o;
842 if (!tok.is_intent) {
843 oo.method = tok.submethod;
844 solve_named(facade_name_of_family(tok.family), oo, sn, t, cache, fj);
845 return;
846 }
847 const autosolver::AutoChoice c = autosolver::auto_choose_solver_mode(sn, "getAvgTable", tok.mode);
848 const std::vector<autosolver::AutoSolver> order =
849 autosolver::auto_proposed_solvers(sn, "getAvgTable", tok.mode);
850 std::string first_error, first_category, first_kind;
851 for (std::size_t i = 0; i < order.size(); ++i) {
852 std::string name = autosolver::auto_solver_name(order[i]);
853 std::transform(name.begin(), name.end(), name.begin(), ::toupper);
854 if (name == "FLUID") name = "FLD";
855 oo.method = (i == 0 && !c.method.empty()) ? c.method : std::string("default");
856 try {
857 AvgTable attempt;
858 solvers::CacheMetrics<double> attempt_cache;
860 solve_named(name, oo, sn, attempt, attempt_cache, &attempt_fj);
861 t = attempt;
862 cache = attempt_cache;
863 if (fj) *fj = attempt_fj;
864 return;
865 } catch (const Error& e) {
866 const std::string cat = e.category();
867 if (cat == "input" || cat == "internal") throw;
868 if (first_error.empty()) {
869 first_error = name + ": " + e.what();
870 first_category = cat;
871 first_kind = e.kind();
872 }
873 } catch (const std::exception& e) {
874 if (first_error.empty()) first_error = name + ": " + e.what();
875 }
876 }
877 const std::string all_failed =
878 "SolverAUTO: every candidate refused this run. The chosen engine reported -- " + first_error;
879 if (first_category == "numerical") throw NumericError(ErrorKind{first_kind}, all_failed);
880 if (first_category == "external") throw ExternalError(ErrorKind{first_kind}, all_failed);
881 throw UnsupportedError(ErrorKind{first_kind.empty() ? std::string("Solver") : first_kind},
882 all_failed);
883}
884
885/** Defined below `avg_result_of_table`, which it reads its solve back through. */
886void solve_zero_capacity(const std::string& name, const SolverOptions& o,
890
891} // namespace
892
893// ---------------------------------------------------------------------------
894// NetworkSolver
895// ---------------------------------------------------------------------------
896
898 // options.cache: a stored result is reused only while caching is on, as getAvg does.
899 if (solved_ && opts_.cache) return table_;
900 AvgTable t;
902 // a pairwise switchover at a non-polling queue is kept in the model but no solver simulates it, as the reference warns
903 for (const auto& st : sn.stations)
904 for (const auto& row : st.switchover_pair)
905 if (std::any_of(row.begin(), row.end(), [](const lang::Distrib<double>& d) {
906 return !d.disabled && d.type != lang::ProcessType::IMMEDIATE;
907 })) {
908 std::cerr << "[LINE] Warning: Station " << st.name << " declares class-to-class switchover "
909 << "times, which no LINE solver simulates at a non-polling queue: every solver "
910 << "answers as if they were zero. Only POLLING switchovers are solved." << std::endl;
911 break;
912 }
915 // A closed chain visiting a station with zero servers is trapped there;
916 // see sn_zero_capacity. This is `@NetworkSolver/getAvg`'s hook, ahead of the
917 // map_env funnel each engine applies, so every Network solver takes it.
919 if (!zc.reason.empty()) throw UnsupportedError(ErrorKind{"Structure"}, zc.reason);
920 if (zc.any())
921 solve_zero_capacity(name_, opts_, sn, zc, t, cache, &fj);
922 else if (name_ == "AUTO")
923 solve_auto(opts_, sn, t, cache, &fj);
924 else
925 solve_named(name_, opts_, sn, t, cache, &fj);
926 table_ = t;
927 cache_ = cache;
928 fj_ = fj;
929 solved_ = true;
930 return table_;
931}
932
933std::string NetworkSolver::method_used() { return avg_table().method; }
934
935namespace {
936
937/**
938 * The solved table laid back onto the station x class matrices that getAvgSys and getAvgChain read;
939 * a row the table dropped was all-zero, which is what the fresh matrices already hold.
940 */
941mva::AvgResult<double> avg_result_of_table(const qn::NetworkStruct<double>& sn, const AvgTable& t) {
942 const std::size_t M = sn.nstations, K = sn.nclasses;
944 r.QN = Matrix<double>(M, K);
945 r.UN = Matrix<double>(M, K);
946 r.RN = Matrix<double>(M, K);
947 r.TN = Matrix<double>(M, K);
948 r.AN = Matrix<double>(M, K);
949 r.WN = Matrix<double>(M, K);
950 for (std::size_t row = 0; row < t.size(); ++row)
951 for (std::size_t i = 0; i < M; ++i) {
952 if (sn.stations[i].name != t.Station[row]) continue;
953 for (std::size_t c = 0; c < K; ++c) {
954 if (sn.classes[c].name != t.JobClass[row]) continue;
955 r.QN(i, c) = t.QLen[row];
956 r.UN(i, c) = t.Util[row];
957 r.RN(i, c) = t.RespT[row];
958 r.TN(i, c) = t.Tput[row];
959 r.AN(i, c) = t.ArvR[row];
960 r.WN(i, c) = t.ResidT[row];
961 }
962 }
963 return r;
964}
965
966/**
967 * `getAvgZeroCapacity`: solve the model without the chains trapped at a
968 * zero-capacity station, whose jobs hold no other resource, then park each
969 * trapped population there.
970 *
971 * The reduced model is a COPY of the struct, refreshed as `get_struct` does,
972 * where the reference edits the model and restores it on cleanup: the user's
973 * model is never touched, so there is nothing to restore. The trapped classes
974 * have population 0 and a zero-server station one server (no remaining chain
975 * visits it); a rate-zero station keeps its servers. When every class is
976 * trapped nothing is solved. At the dead station the trapped chain holds its
977 * whole population, split by visits, with RespT = ResidT = Inf for the classes
978 * that visit it; every other metric of the chain is 0, its system response
979 * time Inf and its system throughput 0.
980 */
981void solve_zero_capacity(const std::string& name, const SolverOptions& o,
985 const std::size_t M = sn.nstations, K = sn.nclasses;
986 const double inf = std::numeric_limits<double>::infinity();
987 std::vector<bool> trappedcls(K, false);
988 std::size_t ntrapped = 0;
989 for (std::size_t c = 0; c < sn.nchains; ++c)
990 if (zc.deadstat[c] > 0)
991 for (std::size_t r = 0; r < K; ++r)
992 if (sn.chains[c][r] && !trappedcls[r]) {
993 trappedcls[r] = true;
994 ++ntrapped;
995 }
997 AvgTable solved;
998 if (ntrapped == K) {
999 r.QN = Matrix<double>(M, K, 0.0);
1000 r.UN = r.QN;
1001 r.RN = r.QN;
1002 r.TN = r.QN;
1003 r.AN = r.QN;
1004 r.WN = r.QN;
1005 solved.solver = name;
1006 solved.method = o.method.empty() ? std::string("default") : o.method;
1009 } else {
1010 qn::NetworkStruct<double> reduced = sn;
1011 for (std::size_t k = 0; k < K; ++k)
1012 if (trappedcls[k]) reduced.classes[k].population = 0.0;
1013 for (std::size_t c = 0; c < sn.nchains; ++c) {
1014 const std::size_t i = zc.deadstat[c];
1015 if (i > 0 && sn.stations[i - 1].nservers == 0.0) reduced.stations[i - 1].nservers = 1.0;
1016 }
1017 reduced.refresh_struct();
1019 if (name == "AUTO")
1020 solve_auto(o, reduced, solved, cache, fj);
1021 else
1022 solve_named(name, o, reduced, solved, cache, fj);
1023 r = avg_result_of_table(sn, solved);
1024 }
1025 std::vector<double> CN(K, 0.0), XN(K, 0.0);
1026 for (std::size_t row = 0; row < solved.SysClass.size(); ++row)
1027 for (std::size_t k = 0; k < K; ++k)
1028 if (sn.classes[k].name == solved.SysClass[row]) {
1029 CN[k] = solved.SysRespT[row];
1030 XN[k] = solved.SysTput[row];
1031 }
1032 const std::vector<double> st2sf = sn.station_to_stateful();
1033 const std::vector<double> njobs = sn.njobs();
1034 for (std::size_t c = 0; c < sn.nchains; ++c) {
1035 if (zc.deadstat[c] == 0) continue;
1036 const std::size_t i = zc.deadstat[c] - 1;
1037 const std::size_t sf = static_cast<std::size_t>(st2sf[i]) - 1;
1038 double pop = 0.0, vtot = 0.0;
1039 for (std::size_t k = 0; k < K; ++k)
1040 if (sn.chains[c][k]) {
1041 pop += njobs[k];
1042 vtot += sn.visits[c](sf, k);
1043 }
1044 for (std::size_t k = 0; k < K; ++k) {
1045 if (!sn.chains[c][k]) continue;
1046 for (std::size_t j = 0; j < M; ++j) {
1047 r.QN(j, k) = 0.0;
1048 r.UN(j, k) = 0.0;
1049 r.RN(j, k) = 0.0;
1050 r.TN(j, k) = 0.0;
1051 r.AN(j, k) = 0.0;
1052 r.WN(j, k) = 0.0;
1053 }
1054 const double v = sn.visits[c](sf, k);
1055 r.QN(i, k) = pop * v / vtot;
1056 if (v > 0.0) {
1057 r.RN(i, k) = inf;
1058 r.WN(i, k) = inf;
1059 }
1060 XN[k] = 0.0;
1061 CN[k] = inf;
1062 }
1063 }
1064 r.CN = CN;
1065 r.XN = XN;
1066 r.iter = solved.iter;
1067 r.warning = solved.warning;
1068 r.listcost = solved.ListCost;
1069 if (solved.has_lognormconst) r.lognormconst = solved.lognormconst;
1070 t = AvgTable();
1071 t.solver = solved.solver;
1072 t.method = solved.method;
1073 fill_table(t, sn, r);
1074}
1075
1076} // namespace
1077
1079 const AvgTable& t = avg_table();
1081 const mva::AvgResult<double> r = avg_result_of_table(sn, t);
1083 AvgSysTable out;
1084 out.Chain = solvers::chain_names(sn.nchains);
1086 out.SysRespT = sys.CN;
1087 out.SysTput = sys.XN;
1088 return out;
1089}
1090
1092 const AvgTable& t = avg_table();
1094 const solvers::ChainResult<double> c = solvers::solver_get_avg_chain<double>(sn, avg_result_of_table(sn, t));
1095 AvgChain out;
1096 out.QN = c.QN;
1097 out.UN = c.UN;
1098 out.RN = c.RN;
1099 out.WN = c.WN;
1100 out.AN = c.AN;
1101 out.TN = c.TN;
1102 return out;
1103}
1104
1106 // getAvgChainTable.m: row-major over (station, chain), every row kept.
1107 const AvgChain c = avg_chain();
1109 const std::vector<std::string> chains = solvers::chain_names(sn.nchains);
1110 const std::vector<std::string> labels = solvers::chain_class_labels<double>(sn);
1111 AvgChainTable out;
1112 for (std::size_t i = 0; i < sn.nstations; ++i)
1113 for (std::size_t k = 0; k < sn.nchains; ++k) {
1114 out.Station.push_back(sn.stations[i].name);
1115 out.Chain.push_back(chains[k]);
1116 out.JobClasses.push_back(labels[k]);
1117 out.QLen.push_back(c.QN(i, k));
1118 out.Util.push_back(c.UN(i, k));
1119 out.RespT.push_back(c.RN(i, k));
1120 out.ResidT.push_back(c.WN(i, k));
1121 out.ArvR.push_back(c.AN(i, k));
1122 out.Tput.push_back(c.TN(i, k));
1123 }
1124 return out;
1125}
1126
1131
1136
1139 AvgFJTable out;
1140 if (fj.empty()) return out;
1142 for (std::size_t p = 0; p < fj.npairs(); ++p)
1143 for (std::size_t r = 0; r < fj.QLen.cols(); ++r) {
1144 const double q = fj.QLen(p, r), rt = fj.RespT(p, r), x = fj.Tput(p, r);
1145 if (!(q > 0.0 || rt > 0.0 || x > 0.0)) continue; // getAvgFJTable keeps a row when any value is > 0
1146 out.Fork.push_back(sn.nodes[fj.forks[p] - 1].name);
1147 out.Join.push_back(sn.nodes[fj.joins[p] - 1].name);
1148 out.JobClass.push_back(sn.classes[r].name);
1149 out.FJQLen.push_back(q);
1150 out.FJRespT.push_back(rt);
1151 out.FJTput.push_back(x);
1152 }
1153 return out;
1154}
1155
1156namespace {
1157
1158/** The named Cache's per-class hit (or miss) vector, refused when the solve measured none. */
1159std::vector<double> cache_ratio(const solvers::CacheMetrics<double>& cm, const std::string& solver,
1160 const std::string& cache, bool hit) {
1161 for (std::size_t c = 0; c < cm.caches.size(); ++c) {
1162 if (cm.caches[c].name != cache) continue;
1163 const std::vector<double>& v = hit ? cm.caches[c].hitprob : cm.caches[c].missprob;
1164 if (v.empty())
1165 throw UnsupportedError(ErrorKind{"Analysis"},
1166 "Solver" + solver + " measured no " + (hit ? "hit" : "miss") +
1167 " ratio at Cache '" + cache + "' with this method");
1168 return v;
1169 }
1170 throw InputError("'" + cache + "' is not a Cache node of this model");
1171}
1172
1173} // namespace
1174
1175std::vector<double> NetworkSolver::hit_ratio(const std::string& cache) {
1176 return cache_ratio(cache_metrics(), name_, cache, true);
1177}
1178
1179std::vector<double> NetworkSolver::miss_ratio(const std::string& cache) {
1180 return cache_ratio(cache_metrics(), name_, cache, false);
1181}
1182
1183std::vector<double> NetworkSolver::hit_ratio(const Node& cache) { return hit_ratio(cache.get_name()); }
1184
1185std::vector<double> NetworkSolver::miss_ratio(const Node& cache) { return miss_ratio(cache.get_name()); }
1186
1187std::vector<std::string> NetworkSolver::list_valid_methods() const {
1189 if (name_ == "MVA") return mva::list_valid_methods(sn);
1190 if (name_ == "NC") return nc::list_valid_methods();
1191 if (name_ == "CTMC") return ctmc::list_valid_methods();
1192 if (name_ == "MAM") return mam::list_valid_methods();
1193 // The model-aware overload, not the bare one: the reduction bounds are
1194 // derived for a single-class closed network of single servers and the three
1195 // open-network bounds for its mirror image, so the list a caller may act on
1196 // depends on the model. `SolverBA` gates on exactly this list.
1197 if (name_ == "BA") return ba::list_valid_methods(sn);
1198 if (name_ == "FLD") return fluid::fluid_list_valid_methods();
1199 if (name_ == "SSA") return ssa::list_valid_methods();
1200 if (name_ == "JMT") return jmt::jmt_list_valid_methods();
1201 if (name_ == "LDES") return ldes::list_valid_methods();
1202 // AUTO answers about every family it can delegate to, gated by each one's
1203 // feature set on this model; see `auto_methods.h`.
1204 if (name_ == "AUTO") return autosolver::auto_list_valid_methods(sn);
1205 throw UnsupportedError(ErrorKind{"Solver"}, "list_valid_methods: unknown solver '" + name_ + "'");
1206}
1207
1208// ---------------------------------------------------------------------------
1209// SolverCTMC
1210// ---------------------------------------------------------------------------
1211
1217
1220 ctmc::CtmcOptions opt = ctmc_options(opts_);
1221 opt.keep_filtration = true;
1223 return ctmc::ctmc_get_infgen(sn, d).Q;
1224}
1225
1226double SolverCTMC::prob_aggr(std::size_t node, const std::vector<double>& state) {
1230 const std::size_t ist = sn.nodes[node - 1].station;
1231 if (ist == 0) throw InputError("prob_aggr: the node is not a station");
1232 const std::size_t K = sn.nclasses;
1233 if (state.size() != K) throw InputError("prob_aggr: the state must have one entry per class");
1234 double p = 0.0;
1235 for (std::size_t s = 0; s < A.rows(); ++s) {
1236 bool hit = true;
1237 for (std::size_t k = 0; k < K && hit; ++k) hit = A(s, (ist - 1) * K + k) == state[k];
1238 if (hit) p += d.pi[s];
1239 }
1240 return p;
1241}
1242
1243std::vector<double> SolverCTMC::marg_aggr(std::size_t node) {
1247 const std::size_t ist = sn.nodes[node - 1].station;
1248 if (ist == 0) throw InputError("marg_aggr: the node is not a station");
1249 const std::size_t K = sn.nclasses;
1250 std::size_t nmax = 0;
1251 for (std::size_t s = 0; s < A.rows(); ++s) {
1252 double tot = 0.0;
1253 for (std::size_t k = 0; k < K; ++k) tot += A(s, (ist - 1) * K + k);
1254 nmax = std::max(nmax, static_cast<std::size_t>(tot));
1255 }
1256 std::vector<double> pmf(nmax + 1, 0.0);
1257 for (std::size_t s = 0; s < A.rows(); ++s) {
1258 double tot = 0.0;
1259 for (std::size_t k = 0; k < K; ++k) tot += A(s, (ist - 1) * K + k);
1260 pmf[static_cast<std::size_t>(tot)] += d.pi[s];
1261 }
1262 return pmf;
1263}
1264
1265std::vector<std::vector<CdfCurve> > SolverCTMC::cdf_respt() {
1267 std::vector<std::vector<CdfCurve> > out(sn.nstations, std::vector<CdfCurve>(sn.nclasses));
1268 const std::vector<std::vector<ctmc::CdfCurve<double> > > R =
1269 ctmc::solver_ctmc_cdf_respt(sn, ctmc_options(opts_));
1270 for (std::size_t i = 0; i < R.size() && i < out.size(); ++i)
1271 for (std::size_t c = 0; c < R[i].size() && c < out[i].size(); ++c) {
1272 out[i][c].t = R[i][c].t;
1273 out[i][c].F = R[i][c].F;
1274 }
1275 return out;
1276}
1277
1283
1284std::size_t StateView::column(std::size_t node, std::size_t cls) const {
1285 if (node == 0 || node > sn_->nodes.size())
1286 throw InputError("StateView: node index " + std::to_string(node) + " does not exist");
1287 const std::size_t ist = sn_->nodes[node - 1].station;
1288 if (ist == 0)
1289 throw InputError("StateView: node '" + sn_->nodes[node - 1].name +
1290 "' is not a station, so the aggregate state holds no count for it");
1291 if (cls == 0 || cls > sn_->nclasses)
1292 throw InputError("StateView: class index " + std::to_string(cls) + " does not exist");
1293 return (ist - 1) * sn_->nclasses + (cls - 1);
1294}
1295
1296double StateView::at(std::size_t node, std::size_t cls) const {
1297 return (*row_)[column(node, cls)];
1298}
1299
1300double StateView::total(std::size_t node) const {
1301 double tot = 0.0;
1302 for (std::size_t k = 1; k <= sn_->nclasses; ++k) tot += (*row_)[column(node, k)];
1303 return tot;
1304}
1305
1306namespace {
1307
1308/**
1309 * The 1-based rows on which `fn` holds, as the n x 1 index matrix the CTMC
1310 * getters resolve; empty for an empty predicate or when no state satisfies it,
1311 * as the reference's `ctmc_predicate_states` returns `[]`.
1312 */
1313Matrix<double> predicate_states(const StatePredicate& fn, const qn::NetworkStruct<double>& sn,
1314 const Matrix<double>& aggr, std::size_t n, const char* name) {
1315 if (!fn) return Matrix<double>();
1316 if (aggr.rows() != n)
1317 throw InputError(std::string("The aggregated state space has ") +
1318 std::to_string(aggr.rows()) + " rows where the generator has " +
1319 std::to_string(n) + ", so the predicate for set " + name +
1320 " cannot be resolved state by state.");
1321 std::vector<double> hit;
1322 std::vector<double> row(aggr.cols());
1323 for (std::size_t s = 0; s < n; ++s) {
1324 for (std::size_t c = 0; c < aggr.cols(); ++c) row[c] = aggr(s, c);
1325 if (fn(StateView(row, sn))) hit.push_back(static_cast<double>(s + 1));
1326 }
1327 Matrix<double> out(hit.size(), hit.empty() ? 0 : 1);
1328 for (std::size_t i = 0; i < hit.size(); ++i) out(i, 0) = hit[i];
1329 return out;
1330}
1331
1332FirstPassage to_first_passage(const ctmc::CtmcFirstPassage& r) {
1333 FirstPassage out;
1334 out.t = r.t;
1335 out.F = r.F;
1336 out.f = r.f;
1337 out.reachprob = r.reachprob;
1338 out.tabooprob = r.tabooprob;
1339 out.source = r.source;
1340 out.target = r.target;
1341 out.taboo = r.taboo;
1342 return out;
1343}
1344
1345FirstPassageMoments to_first_passage_moments(const ctmc::CtmcFirstPassageMoments<double>& r) {
1347 out.m = r.m;
1348 out.mall = r.mall;
1349 out.p = r.p;
1350 out.source = r.source;
1351 out.target = r.target;
1352 out.taboo = r.taboo;
1353 return out;
1354}
1355
1356} // namespace
1357
1359 const Matrix<double>& C,
1360 const std::vector<double>& tset) {
1363 return to_first_passage(ctmc::ctmc_cdf_firstpasst(sn, d, A, B, C, tset));
1364}
1365
1367 const StatePredicate& C,
1368 const std::vector<double>& tset) {
1371 const qn::NetworkStruct<double>& ssn = d.solved_sn ? *d.solved_sn : sn;
1373 const std::size_t n = d.chain.Q.rows();
1374 return to_first_passage(ctmc::ctmc_cdf_firstpasst(
1375 sn, d, predicate_states(A, ssn, aggr, n, "A"), predicate_states(B, ssn, aggr, n, "B"),
1376 predicate_states(C, ssn, aggr, n, "C"), tset));
1377}
1378
1380 const Matrix<double>& B, std::size_t nmax,
1381 const Matrix<double>& C) {
1384 return to_first_passage_moments(ctmc::ctmc_firstpasst_moments(sn, d, A, B, nmax, C));
1385}
1386
1388 const StatePredicate& B, std::size_t nmax,
1389 const StatePredicate& C) {
1392 const qn::NetworkStruct<double>& ssn = d.solved_sn ? *d.solved_sn : sn;
1394 const std::size_t n = d.chain.Q.rows();
1395 return to_first_passage_moments(ctmc::ctmc_firstpasst_moments(
1396 sn, d, predicate_states(A, ssn, aggr, n, "A"), predicate_states(B, ssn, aggr, n, "B"),
1397 nmax, predicate_states(C, ssn, aggr, n, "C")));
1398}
1399
1403 ctmc::ctmc_symbolic_solution(sn, ctmc_options(opts_), ctmc_symbolic_options(opts_));
1404 SymbolicSolution out;
1405 out.pi = r.pi;
1406 out.num = r.num;
1407 out.den = r.den;
1408 out.engine = r.engine;
1409 out.symbols = r.symbols;
1410 out.rate0 = r.rate0;
1411 return out;
1412}
1413
1415 std::printf("%8s %-28s %s\n", "State", "Marking", "Rates (to: rate)");
1416 for (std::size_t i = 0; i < Q.rows(); ++i) {
1417 std::string mark;
1418 if (i < space.rows()) {
1419 std::ostringstream os;
1420 os << "[";
1421 for (std::size_t c = 0; c < space.cols(); ++c) os << (c ? " " : "") << space(i, c);
1422 os << "]";
1423 mark = os.str();
1424 }
1425 std::printf("%8zu %-28s", i + 1, mark.c_str());
1426 for (std::size_t j = 0; j < Q.cols(); ++j)
1427 if (i != j && Q(i, j) != 0.0) std::printf(" %zu: %.6g", j + 1, Q(i, j));
1428 std::printf("\n");
1429 }
1430}
1431
1432// ---------------------------------------------------------------------------
1433// SolverFLD
1434// ---------------------------------------------------------------------------
1435
1438 const std::vector<fluid::FluidTranPoint> pts =
1439 fluid::solver_fluid_tran_avg(sn, fluid_options(opts_), 100);
1440 TranAvg out;
1441 for (std::size_t i = 0; i < sn.nstations; ++i)
1442 for (std::size_t c = 0; c < sn.nclasses; ++c)
1443 out.label.push_back(sn.stations[i].name + "/" + sn.classes[c].name);
1444 for (std::size_t s = 0; s < pts.size(); ++s) {
1445 out.t.push_back(pts[s].t);
1446 std::vector<double> row;
1447 for (std::size_t i = 0; i < sn.nstations; ++i)
1448 for (std::size_t c = 0; c < sn.nclasses; ++c) row.push_back(pts[s].QN(i, c));
1449 out.QNt.push_back(row);
1450 }
1451 return out;
1452}
1453
1454std::vector<std::vector<CdfCurve> > SolverFLD::cdf_respt() {
1456 std::vector<std::vector<CdfCurve> > out(sn.nstations, std::vector<CdfCurve>(sn.nclasses));
1457 const std::vector<std::vector<fluid::FluidPassage> > R =
1458 fluid::solver_fluid_cdf_respt(sn, fluid_options(opts_));
1459 for (std::size_t i = 0; i < R.size() && i < out.size(); ++i)
1460 for (std::size_t c = 0; c < R[i].size() && c < out[i].size(); ++c) {
1461 out[i][c].t = R[i][c].t;
1462 out[i][c].F = R[i][c].cdf;
1463 }
1464 return out;
1465}
1466
1467// ---------------------------------------------------------------------------
1468// SolverLDES
1469// ---------------------------------------------------------------------------
1470
1471std::vector<std::vector<CdfCurve> > SolverLDES::cdf_respt() {
1473 const ldes::LdesResult r =
1474 ldes::solver_ldes(sn, ldes_options(opts_), std::vector<std::string>(1, "--respt-samples"));
1475 if (r.respTimeSamples.empty())
1476 throw ExternalError(ErrorKind{"LDES"},
1477 "SolverLDES::cdf_respt needs the per-job response times the engine records under "
1478 "--respt-samples and the run returned none; raise the samples so completions are observed");
1479 std::vector<std::vector<CdfCurve> > out(sn.nstations, std::vector<CdfCurve>(sn.nclasses));
1480 for (std::size_t i = 0; i < sn.nstations; ++i)
1481 for (std::size_t c = 0; c < sn.nclasses; ++c) {
1482 const Matrix<double> m = ldes::ldes_cdf_respt(r, i, c);
1483 for (std::size_t j = 0; j < m.rows(); ++j) {
1484 out[i][c].F.push_back(m(j, 0));
1485 out[i][c].t.push_back(m(j, 1));
1486 }
1487 }
1488 return out;
1489}
1490
1493 const std::size_t M = sn.nstations, K = sn.nclasses;
1495 if (aux.get_name() == "CTMC") {
1496 P = ldes::warm_start_placement_from_ctmc(sn, ctmc_options(aux.options()));
1497 } else {
1498 // The table drops all-zero rows, so the means are matched back by name.
1499 const AvgTable& t = aux.avg_table();
1500 Matrix<double> QN(M, K, 0.0);
1501 for (std::size_t row = 0; row < t.QLen.size(); ++row)
1502 for (std::size_t i = 0; i < M; ++i)
1503 for (std::size_t c = 0; c < K; ++c)
1504 if (sn.stations[i].name == t.Station[row] && sn.classes[c].name == t.JobClass[row])
1505 QN(i, c) = t.QLen[row];
1507 }
1508 // `initFromMarginal`: every state of each station with that many jobs, the first one
1509 // weighed, declared as the model initial state every state-driven solver starts from.
1510 for (std::size_t i = 1; i <= M; ++i) {
1511 if (sn.stations[i - 1].nodetype == lang::NodeType::Source) continue;
1512 std::vector<std::size_t> n(K, 0), ph(K, 1);
1513 for (std::size_t c = 0; c < K; ++c) {
1514 n[c] = static_cast<std::size_t>(P(i - 1, c) + 0.5);
1515 ph[c] = sn.phases_of(i, c + 1);
1516 }
1517 const std::vector<std::vector<double> > rows = qn::from_marginal(sn, i, n, ph);
1518 if (rows.empty())
1519 throw InputError("SolverLDES::init_from_solver: station '" + sn.stations[i - 1].name +
1520 "' admits no state with the warm-start placement");
1521 Matrix<double> space(rows.size(), rows[0].size());
1522 for (std::size_t r = 0; r < rows.size(); ++r)
1523 for (std::size_t c = 0; c < rows[r].size(); ++c) space(r, c) = rows[r][c];
1524 std::vector<double> prior(rows.size(), 0.0);
1525 prior[0] = 1.0;
1526 model_->set_state_prior(sn.node_of_station(i), space, prior);
1527 }
1528 opts_.tranfilter = "fixed";
1529 opts_.warmupfrac = 0.0;
1530 reset();
1531 jobs_ready_ = false;
1532 return *this;
1533}
1534
1536 if (jobs.rows() == 0 || jobs.cols() < 7)
1537 throw InputError("SolverLDES::init_from_jobs needs one row per job with the columns [station, class, "
1538 "remaining, elapsed, qage, sage, order, id], id optional");
1539 opts_.init_jobs = jobs;
1540 opts_.track_jobs = true;
1541 reset();
1542 jobs_ready_ = false;
1543 return *this;
1544}
1545
1546void SolverLDES::run_tracked() {
1547 if (jobs_ready_ && opts_.cache) return;
1549 ldes::LdesOptions opt = ldes_options(opts_);
1550 opt.track_jobs = true;
1552 final_jobs_ = r.finalJobs;
1553 job_departures_ = r.jobDepartures;
1554 jobs_ready_ = true;
1555}
1556
1558 run_tracked();
1559 return final_jobs_;
1560}
1561
1563 run_tracked();
1564 return job_departures_;
1565}
1566
1567// ---------------------------------------------------------------------------
1568// SolverJMT
1569// ---------------------------------------------------------------------------
1570
1571std::vector<std::vector<CdfCurve> > SolverJMT::cdf_respt() {
1573 std::vector<std::vector<CdfCurve> > out(sn.nstations, std::vector<CdfCurve>(sn.nclasses));
1574 // `true` is the getCdfRespT contract: the model is first solved for its
1575 // steady-state queue lengths and the logged run starts preloaded at their
1576 // rounded values, which is what makes the seeded curve comparable to the
1577 // reference's for the same seed.
1578 const std::map<std::pair<std::size_t, std::size_t>, std::vector<std::pair<double, double> > >
1579 rd = jmt::jmt_get_cdf_resp_t(sn, jmt_options(opts_), true);
1580 for (std::map<std::pair<std::size_t, std::size_t>,
1581 std::vector<std::pair<double, double> > >::const_iterator it = rd.begin();
1582 it != rd.end(); ++it) {
1583 const std::size_t i = it->first.first, c = it->first.second;
1584 if (i == 0 || i > out.size() || c == 0 || c > sn.nclasses) continue;
1585 // THE PAIRS ARE (F, X), as `ecdf` returns them, and the curve stores
1586 // the two apart: reading them the other way round would report a
1587 // probability as a time.
1588 for (std::size_t j = 0; j < it->second.size(); ++j) {
1589 out[i - 1][c - 1].F.push_back(it->second[j].first);
1590 out[i - 1][c - 1].t.push_back(it->second[j].second);
1591 }
1592 }
1593 return out;
1594}
1595
1599 TranAvg out;
1600 for (std::size_t i = 0; i < sn.nstations; ++i)
1601 for (std::size_t c = 0; c < sn.nclasses; ++c)
1602 out.label.push_back(sn.stations[i].name + "/" + sn.classes[c].name);
1603 out.t = r.t;
1604 for (std::size_t g = 0; g < r.t.size(); ++g) {
1605 std::vector<double> row;
1606 for (std::size_t i = 0; i < sn.nstations; ++i)
1607 for (std::size_t c = 0; c < sn.nclasses; ++c)
1608 row.push_back(i < r.QNt.size() && c < r.QNt[i].size() && g < r.QNt[i][c].size()
1609 ? r.QNt[i][c][g]
1610 : 0.0);
1611 out.QNt.push_back(row);
1612 }
1613 return out;
1614}
1615
1616double SolverJMT::prob_aggr(std::size_t node, const std::vector<double>& state) {
1618 if (node == 0 || node > sn.nodes.size()) throw InputError("prob_aggr: node index out of range");
1619 const std::size_t ist = sn.nodes[node - 1].station;
1620 if (ist == 0) throw InputError("prob_aggr: the node is not a station");
1621 const jmt::JmtProbAggr r = jmt::jmt_prob_aggr(sn, jmt_options(opts_), ist, state);
1622 return r.station[ist - 1];
1623}
1624
1625SamplePath SolverJMT::sample(std::size_t events, std::size_t node) {
1627 const jmt::JmtOptions opt = jmt_options(opts_);
1628 SamplePath out;
1629 if (node) {
1630 if (node > sn.nodes.size() || sn.nodes[node - 1].station == 0)
1631 throw InputError("sample: the node is not a station, so nothing is logged for it");
1632 const jmt::JmtNodeTrace<double> tr = jmt::jmt_sample_aggr(sn, node, events, opt);
1633 for (std::size_t c = 0; c < sn.nclasses; ++c) out.label.push_back(sn.classes[c].name);
1634 out.t = tr.t;
1635 out.state = tr.qlen;
1636 return out;
1637 }
1639 for (std::size_t i = 0; i < sn.nstations; ++i)
1640 for (std::size_t c = 0; c < sn.nclasses; ++c)
1641 out.label.push_back(sn.stations[i].name + "/" + sn.classes[c].name);
1642 out.t = tr.t;
1643 for (std::size_t g = 0; g < tr.t.size(); ++g) {
1644 std::vector<double> row;
1645 for (std::size_t i = 0; i < sn.nstations; ++i)
1646 for (std::size_t c = 0; c < sn.nclasses; ++c)
1647 row.push_back(i < tr.state.size() && g < tr.state[i].size() &&
1648 c < tr.state[i][g].size()
1649 ? tr.state[i][g][c]
1650 : 0.0);
1651 out.state.push_back(row);
1652 }
1653 return out;
1654}
1655
1656// ---------------------------------------------------------------------------
1657// SolverBA
1658// ---------------------------------------------------------------------------
1659
1663 if (!opts_.method.empty()) opt.method = opts_.method;
1664 opt.level = opts_.level;
1666 BoundsTable t;
1667 t.method = opt.method;
1668 for (std::size_t i = 0; i < sn.nstations; ++i)
1669 for (std::size_t c = 0; c < sn.nclasses; ++c) {
1670 if (i < b.keep.size() && c < b.keep[i].size() && !b.keep[i][c]) continue;
1671 t.Station.push_back(sn.stations[i].name);
1672 t.JobClass.push_back(sn.classes[c].name);
1673 t.Qlower.push_back(b.Qlower(i, c));
1674 t.Qupper.push_back(b.Qupper(i, c));
1675 t.Tlower.push_back(b.Tlower(i, c));
1676 t.Tupper.push_back(b.Tupper(i, c));
1677 }
1678 return t;
1679}
1680
1681// ---------------------------------------------------------------------------
1682// SolverAUTO
1683// ---------------------------------------------------------------------------
1684
1685namespace {
1686
1687/** A ranking slot's facade name: "fld" is SolverFLD, the rest are their upper-case selves. */
1688std::string facade_name_of_slot(autosolver::AutoSolver s) {
1689 std::string name = autosolver::auto_solver_name(s);
1690 std::transform(name.begin(), name.end(), name.begin(), ::toupper);
1691 return name;
1692}
1693
1694/** The facade name's method family, the inverse of `facade_name_of_family`. */
1695std::string family_of_facade_name(const std::string& tag) {
1696 std::string fam = tag;
1697 std::transform(fam.begin(), fam.end(), fam.begin(), ::tolower);
1698 return fam;
1699}
1700
1701/**
1702 * Who answers `getter` under the configured method, in `delegate.m`'s order. A family names its
1703 * engine outright and is the whole list; an intent names a ranking, whose first slot alone is asked
1704 * with the method the ranking gated on ('exact' under the exact intent).
1705 */
1706struct AutoDelegatePlan {
1707 bool family = false;
1708 std::string family_name;
1709 std::vector<std::string> tags;
1710 std::string first_method;
1711};
1712
1713AutoDelegatePlan auto_delegate_plan(const qn::NetworkStruct<double>& sn, const std::string& method,
1714 const std::string& getter) {
1715 AutoDelegatePlan plan;
1717 if (!tok.is_intent) {
1718 plan.family = true;
1719 plan.family_name = tok.family;
1720 plan.tags.push_back(facade_name_of_family(tok.family));
1721 plan.first_method = tok.submethod;
1722 return plan;
1723 }
1725 const std::vector<autosolver::AutoSolver> order = autosolver::auto_proposed_solvers(sn, getter, tok.mode);
1726 for (std::size_t i = 0; i < order.size(); ++i) plan.tags.push_back(facade_name_of_slot(order[i]));
1727 plan.first_method = c.method.empty() ? std::string("default") : c.method;
1728 return plan;
1729}
1730
1731/**
1732 * `delegate.m` for a getter the facade answers on some solvers only. `answers` names the facades
1733 * that expose the call; a proposed solver outside it is passed over, since this port has nothing to
1734 * ask it. Input and internal errors propagate; any other moves on to the next candidate, and when
1735 * all fail the LAST error is reported under its own category, as the reference does.
1736 */
1737template <class R, class Call>
1738R auto_delegate(Network& model, const SolverOptions& o, const std::string& getter,
1739 const std::set<std::string>& answers, Call call, std::string& last) {
1740 const qn::NetworkStruct<double>& sn = model.get_struct();
1741 const AutoDelegatePlan plan = auto_delegate_plan(sn, o.method, getter);
1742 std::string last_error, last_cat, last_kind, proposed;
1743 bool tried = false;
1744 for (std::size_t i = 0; i < plan.tags.size(); ++i) {
1745 const std::string& tag = plan.tags[i];
1746 proposed += (proposed.empty() ? "" : ", ") + tag;
1747 if (!answers.count(tag)) continue;
1748 tried = true;
1749 SolverOptions oo = o;
1750 oo.method = i == 0 ? plan.first_method : std::string("default");
1751 try {
1752 R r = call(tag, model, oo);
1753 last = tag;
1754 return r;
1755 } catch (const Error& e) {
1756 const std::string cat = e.category();
1757 if (cat == "input" || cat == "internal") throw;
1758 last_error = tag + ": " + e.what();
1759 last_cat = cat;
1760 last_kind = e.kind();
1761 }
1762 }
1763 if (!tried) {
1764 if (plan.family)
1765 throw UnsupportedError(ErrorKind{"Method"},
1766 "SolverAUTO: method family '" + plan.family_name + "' does not answer " + getter +
1767 " in this port");
1768 throw UnsupportedError(ErrorKind{"Method"},
1769 "SolverAUTO: none of the solvers proposed for " + getter + " (" + proposed +
1770 ") answers it in this port");
1771 }
1772 const std::string msg = "SolverAUTO: All candidate solvers failed to execute method '" + getter +
1773 "'. Last error: " + last_error;
1774 if (last_cat == "numerical") throw NumericError(ErrorKind{last_kind}, msg);
1775 if (last_cat == "external") throw ExternalError(ErrorKind{last_kind}, msg);
1776 throw UnsupportedError(ErrorKind{last_kind.empty() ? std::string("Solver") : last_kind}, msg);
1777}
1778
1779/** True for a facade whose answer is an estimate from a simulated run under this method. */
1780bool facade_is_stochastic(const std::string& tag, const std::string& method) {
1781 if (tag == "SSA" || tag == "LDES") return true;
1782 if (tag == "JMT") return method.compare(0, 4, "jmva") != 0;
1783 return false;
1784}
1785
1786} // namespace
1787
1789 // The constructor resolves the method as the reference's does, so an unknown name fails here
1790 // and not at the first getter. A bare method name needs the model to resolve, and is checked
1791 // against it only when the model-free resolution cannot place it.
1792 try {
1794 } catch (const InputError&) {
1796 }
1797}
1798
1799SolverAUTO::SolverAUTO(Network& m, const std::string& method)
1800 : SolverAUTO(m, SolverOptions().set_method(method)) {}
1801
1803 if (!last_.empty()) return last_;
1804 if (solved_) return table_.solver;
1805 try {
1806 return choose_solver("getAvg");
1807 } catch (const Error&) {
1808 // No feasible delegate: the caller learns this from the analysis itself.
1809 return std::string();
1810 }
1811}
1812
1814 const std::string tag = selected_solver_name();
1815 return tag.empty() ? std::string() : family_of_facade_name(tag);
1816}
1817
1818std::vector<std::string> SolverAUTO::candidate_solver_names() const {
1819 std::vector<std::string> out;
1820 const std::vector<autosolver::AutoSolver> c = autosolver::auto_candidates(model_->get_struct());
1821 for (std::size_t i = 0; i < c.size(); ++i) out.push_back(facade_name_of_slot(c[i]));
1822 return out;
1823}
1824
1825std::string SolverAUTO::choose_solver(const std::string& getter) const {
1826 return auto_delegate_plan(model_->get_struct(), opts_.method, getter).tags.front();
1827}
1828
1829std::vector<std::string> SolverAUTO::proposed_solver_names(const std::string& getter) const {
1830 return auto_delegate_plan(model_->get_struct(), opts_.method, getter).tags;
1831}
1832
1834 if (!last_.empty()) return facade_is_stochastic(last_, std::string());
1835 if (solved_) return facade_is_stochastic(table_.solver, table_.method);
1836 const std::vector<std::string> c = candidate_solver_names();
1837 for (std::size_t i = 0; i < c.size(); ++i)
1838 if (facade_is_stochastic(c[i], std::string())) return true;
1839 return false;
1840}
1841
1842std::vector<std::string> SolverAUTO::list_all_methods() const {
1843 std::vector<std::string> out = selection_intents();
1844 out.push_back("auto");
1845 const std::vector<std::string> fams = family_names();
1846 for (std::size_t f = 0; f < fams.size(); ++f) {
1847 // A family contributes only when it can be instantiated on a Network here: ln, env and uq
1848 // take another model class, ldes needs its engine and lqns its qnsolver binary.
1849 std::string tag;
1850 try {
1851 tag = facade_name_of_family(fams[f]);
1852 } catch (const Error&) {
1853 continue;
1854 }
1855 if (tag == "LQNS" && !lqns::qnsolver_is_available()) continue;
1856 out.push_back(fams[f]);
1857 std::string key = fams[f];
1858 std::transform(key.begin(), key.end(), key.begin(), ::toupper);
1859 const std::vector<std::string> declared = method_registry::methods(key);
1860 for (std::size_t m = 0; m < declared.size(); ++m) out.push_back(fams[f] + "." + declared[m]);
1861 }
1862 std::sort(out.begin(), out.end());
1863 out.erase(std::unique(out.begin(), out.end()), out.end());
1864 return out;
1865}
1866
1867std::vector<AutoCandidate> SolverAUTO::find_solver(const std::string& metric, bool show_all) const {
1868 const std::vector<autosolver::SolverCandidate> rows =
1869 autosolver::auto_find_solver(model_->get_struct(), metric, show_all);
1870 std::vector<AutoCandidate> out;
1871 for (std::size_t i = 0; i < rows.size(); ++i) {
1872 AutoCandidate c;
1873 c.solver = rows[i].solver;
1874 c.method = rows[i].method;
1875 c.runnable = rows[i].runnable;
1876 c.method_class = rows[i].method_class;
1877 c.metrics = rows[i].metrics;
1878 c.reason = rows[i].reason;
1879 out.push_back(c);
1880 }
1881 return out;
1882}
1883
1884std::vector<std::vector<CdfCurve> > SolverAUTO::cdf_respt() {
1885 std::set<std::string> answers;
1886 answers.insert("CTMC");
1887 answers.insert("FLD");
1888 answers.insert("LDES");
1889 answers.insert("JMT");
1890 return auto_delegate<std::vector<std::vector<CdfCurve> > >(
1891 *model_, opts_, "getCdfRespT", answers,
1892 [](const std::string& tag, Network& m, const SolverOptions& oo) {
1893 if (tag == "CTMC") return SolverCTMC(m, oo).cdf_respt();
1894 if (tag == "FLD") return SolverFLD(m, oo).cdf_respt();
1895 if (tag == "LDES") return SolverLDES(m, oo).cdf_respt();
1896 return SolverJMT(m, oo).cdf_respt();
1897 },
1898 last_);
1899}
1900
1902 std::set<std::string> answers;
1903 answers.insert("FLD");
1904 answers.insert("JMT");
1905 return auto_delegate<TranAvg>(
1906 *model_, opts_, "getTranAvg", answers,
1907 [](const std::string& tag, Network& m, const SolverOptions& oo) {
1908 if (tag == "FLD") return SolverFLD(m, oo).tran_avg();
1909 return SolverJMT(m, oo).tran_avg();
1910 },
1911 last_);
1912}
1913
1914double SolverAUTO::prob_aggr(std::size_t node, const std::vector<double>& state) {
1915 std::set<std::string> answers;
1916 answers.insert("CTMC");
1917 answers.insert("JMT");
1918 return auto_delegate<double>(
1919 *model_, opts_, "getProbAggr", answers,
1920 [node, &state](const std::string& tag, Network& m, const SolverOptions& oo) {
1921 if (tag == "CTMC") return SolverCTMC(m, oo).prob_aggr(node, state);
1922 return SolverJMT(m, oo).prob_aggr(node, state);
1923 },
1924 last_);
1925}
1926
1927namespace {
1928
1929/**
1930 * The options a `ctmcSolver()`-style accessor builds its solver with: the family's submethod when
1931 * the method names that family, its default otherwise (an intent names no CTMC method).
1932 */
1933SolverOptions family_solver_options(const SolverOptions& o, const std::string& family,
1934 const std::string& fallback) {
1935 SolverOptions oo = o;
1936 const std::size_t dot = o.method.find('.');
1937 const std::string head = dot == std::string::npos ? o.method : o.method.substr(0, dot);
1938 if (autosolver::auto_family_alias(head) == family && dot != std::string::npos)
1939 oo.method = o.method.substr(dot + 1);
1940 else
1941 oo.method = fallback;
1942 return oo;
1943}
1944
1945} // namespace
1946
1948 return SolverCTMC(*model_, family_solver_options(opts_, "ctmc", "default"));
1949}
1950
1952 return SolverFLD(*model_, family_solver_options(opts_, "fld", "default"));
1953}
1954
1955SolverNC SolverAUTO::nc_solver() const {
1956 return SolverNC(*model_, family_solver_options(opts_, "nc", "default"));
1957}
1958
1960 // baSolver(): a bound method named outright is kept, every other name asks SolverBA for 'auto'.
1961 return SolverBA(*model_, family_solver_options(opts_, "ba", "auto"));
1962}
1963
1964std::vector<std::string> SolverAUTO::selection_intents() {
1965 return {"default", "heur", "sim", "exact", "fast", "accurate", "bound"};
1966}
1967
1968std::vector<std::string> SolverAUTO::family_names() {
1969 return {"mva", "nc", "ctmc", "fld", "mam", "ag", "ba", "ssa", "ldes", "jmt", "ln", "env", "lqns", "uq"};
1970}
1971
1972std::vector<std::string> SolverAUTO::metric_groups() { return autosolver::auto_metric_groups(); }
1973
1974std::string SolverAUTO::metric_group_of(const std::string& name) {
1976}
1977
1978std::vector<std::string> SolverAUTO::family_metrics(const std::string& family) {
1979 return autosolver::auto_family_metrics(family);
1980}
1981
1982namespace {
1983
1984qn::FeatureSet auto_feature_union() {
1985 const qn::FeatureSet sets[] = {qn::mva_feature_set("default"), qn::nc_feature_set("default"),
1987 qn::mam_feature_set("default"), qn::ssa_feature_set("default"),
1988 qn::ldes_feature_set("default"), qn::jmt_feature_set("default")};
1989 qn::FeatureSet out;
1990 for (std::size_t s = 0; s < sizeof(sets) / sizeof(*sets); ++s) {
1991 const std::vector<qn::Feature> fs = sets[s].list();
1992 for (std::size_t i = 0; i < fs.size(); ++i) out.set(fs[i]);
1993 }
1994 return out;
1995}
1996
1997} // namespace
1998
1999std::vector<std::string> SolverAUTO::feature_set() {
2000 const std::vector<qn::Feature> fs = auto_feature_union().list();
2001 std::vector<std::string> out;
2002 for (std::size_t i = 0; i < fs.size(); ++i) out.push_back(qn::feature_name(fs[i]));
2003 return out;
2004}
2005
2007 return qn::feature_set_supports("SolverAUTO", auto_feature_union(),
2009 .ok;
2010}
2011
2012} // namespace line
The -s ag entry point: gates, fixed point, mean measures.
SolverAUTO.listValidMethods: the method names THIS MODEL can actually run.
Base error for the multiprecision C++ port.
Definition error.h:73
ExternalError(const std::string &what)
Definition error.h:126
Malformed or inconsistent input (dimensions, negative populations, ...): LINE:input:<kind>.
Definition error.h:103
InputError(const std::string &what)
Definition error.h:105
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
AvgTable table_
Definition solver.h:212
const SolverOptions & options() const
The options this solver was built with.
Definition solver.h:130
const solvers::CacheMetrics< double > & cache_metrics()
What the same solve observed at each Cache node, the data behind getAvgCacheTable() and getAvgItemTab...
std::vector< double > miss_ratio(const std::string &cache)
cache.getMissRatio() after the solve, with hit_ratio's conventions.
AvgFJTable avg_fj_table()
getAvgFJTable() (aliases getAvgFJT, fjAvgT, aFJT): the same metrics as a labelled table.
const solvers::FjMetrics< double > & avg_fj()
getAvgFJ(): the fork-join section metrics of the same solve, one row per Fork/Join pair (fj_sorted_pa...
solvers::FjMetrics< double > fj_
Definition solver.h:214
const std::string & get_name() const
getName(): the solver's own name, e.g.
Definition solver.h:128
AvgChain avg_chain()
getAvgChain(): the station table of the same solve aggregated by chain, (nstations x nchains).
solvers::CacheMetrics< double > cache_
Definition solver.h:213
SolverOptions opts_
Definition solver.h:211
std::string method_used()
getMethodUsed(): the method the solve actually resolved to.
AvgSysTable avg_sys_table()
getAvgSysTable(): per-chain system response time and throughput of the same solve.
Network * model_
Definition solver.h:209
std::vector< std::string > list_valid_methods() const
listValidMethods(): the methods this solver advertises on this model.
const AvgTable & avg_table()
getAvgTable(): the average table, solving on first demand.
std::vector< double > hit_ratio(const std::string &cache)
cache.getHitRatio() after the solve: the per-class hit probability at the named Cache,...
Network & model() const
The model this solver was built on.
Definition solver.h:132
void reset()
reset(): discard the cached solve.
Definition solver.h:170
NetworkSolver(Network &m, const std::string &name, const SolverOptions &o)
Definition solver.h:206
AvgChainTable avg_chain_table()
getAvgChainTable(): avg_chain() as a labelled table, one row per (station, chain).
std::string name_
Definition solver.h:210
A node of the model: the index it was given, and the model that owns it.
Definition nodes.h:59
const std::string & get_name() const
getName().
Definition nodes.h:64
NumericError(const std::string &what)
Definition error.h:112
std::vector< std::string > proposed_solver_names(const std::string &getter="getAvg") const
delegate's retry order for that getter, as facade names; the chosen solver comes first.
static std::vector< std::string > family_names()
familyNames(): the method families, in the order a bare method name is looked up.
std::string choose_solver(const std::string &getter="getAvg") const
chooseSolver(getter): the facade name the configured method picks for that getter.
SolverBA ba_solver() const
baSolver(): the SolverBA the bound calls go to, method 'auto' unless a bound method was named.
static std::vector< std::string > metric_groups()
metricGroups(), metricGroupOf(name) and familyMetrics(family).
std::vector< std::string > list_all_methods() const
listAllMethods(): every method name the constructor accepts, independent of the model.
std::vector< std::vector< CdfCurve > > cdf_respt()
getCdfRespT(), delegated through the 'getCdfRespT' ranking.
static bool supports(Network &model)
supports(model): whether the model uses only features some family declares.
std::vector< std::string > candidate_solver_names() const
getCandidateSolverNames(): the facade names of the feasible candidates, in slot order.
SolverNC nc_solver() const
ncSolver(): the SolverNC the normalizing-constant calls go to.
SolverFLD fld_solver() const
fldSolver(): the SolverFLD the fluid-only calls go to.
std::string selected_solver_name()
getSelectedSolverName(): the facade name ("MVA", "FLD", ...) of the delegate that answered the last r...
SolverCTMC ctmc_solver() const
ctmcSolver(): the SolverCTMC the generator and state-space calls go to, with these options.
static std::vector< std::string > family_metrics(const std::string &family)
static std::string metric_group_of(const std::string &name)
static std::vector< std::string > feature_set()
getFeatureSet(): the union of the feature sets of SolverMVA, NC, CTMC, FLD, MAM, SSA,...
std::vector< AutoCandidate > find_solver(const std::string &metric=std::string(), bool show_all=false) const
findSolver(metric, showAll): which (family, method) pairs answer metric on this model.
std::string selected_method()
getSelectedMethod(): the method FAMILY of that delegate, lowercase ("mva", "fld").
bool is_stochastic()
isStochastic(): by the delegate that ran, else whether any candidate is a simulator.
TranAvg tran_avg()
getTranAvg(), delegated through the 'getTranAvg' ranking.
SolverAUTO(Network &m, const SolverOptions &o=SolverOptions())
double prob_aggr(std::size_t node, const std::vector< double > &state)
getProbAggr(node, state), delegated through the 'getProbAggr' ranking.
static std::vector< std::string > selection_intents()
selectionIntents().
SolverBA: the bounding solver, whose result is a bounds table.
Definition solver.h:431
BoundsTable bounds_table()
getBoundsTable(): the per-class queue-length and throughput bounds.
SolverBA(Network &m, const SolverOptions &o=SolverOptions())
Definition solver.h:433
SolverCTMC: the average table plus the chain it was computed from.
Definition solver.h:287
static void print_inf_gen(const Matrix< double > &Q, const Matrix< double > &space)
CTMC.printInfGen(Q, SS): the generator beside the state it belongs to.
Matrix< double > generator()
getGenerator(): the infinitesimal generator over that space.
double prob_aggr(std::size_t node, const std::vector< double > &state)
getProbAggr(node, state): the aggregate marginal of one state.
Matrix< double > state_space()
getStateSpace(): the aggregate state space, one row per state.
FirstPassage cdf_first_passt(const Matrix< double > &A, const Matrix< double > &B, const Matrix< double > &C=Matrix< double >(), const std::vector< double > &tset=std::vector< double >())
getCdfFirstPassT(A, B, C, tset): the FIRST PASSAGE TIME from the state set A into the state set B.
SolverCTMC(Network &m, const SolverOptions &o=SolverOptions())
Definition solver.h:289
SymbolicSolution symbolic_solution()
getSymbolicSolution(): the stationary law over the rate symbols x1..xE.
FirstPassageMoments first_passt_moments(const Matrix< double > &A, const Matrix< double > &B, std::size_t nmax=3, const Matrix< double > &C=Matrix< double >())
getFirstPassTMoments(A, B, nmax, C): moments of order 1..nmax of the same passage,...
std::vector< double > marg_aggr(std::size_t node)
getProbStateAggr(node): the marginal over every state of one station.
std::vector< std::vector< CdfCurve > > cdf_respt()
getCdfRespT(): the response-time CDF per (station, class).
Matrix< double > state_space_aggr()
getStateSpaceAggr(): per-(station, class) job counts of every state, in (ist-1)*K + k column order,...
SolverFLD: the fluid solver, which also answers a transient.
Definition solver.h:417
std::vector< std::vector< CdfCurve > > cdf_respt()
getCdfRespT(): the response-time CDF per (station, class).
TranAvg tran_avg()
getTranAvg(): the transient mean queue length per station.
SolverFLD(Network &m, const SolverOptions &o=SolverOptions())
Definition solver.h:419
TranAvg tran_avg()
getTranAvg(): E[N](t), averaged over options.config.replications (SolverOptions::replications,...
SolverJMT(Network &m, const SolverOptions &o=SolverOptions())
Definition solver.h:253
SamplePath sample(std::size_t events=0, std::size_t node=0)
sampleSysAggr(events), or sampleAggr(node, events) when node is given: one logged trajectory.
std::vector< std::vector< CdfCurve > > cdf_respt()
getCdfRespT(): the EMPIRICAL response-time CDF per (station, class).
double prob_aggr(std::size_t node, const std::vector< double > &state)
getProbAggr(node, state): the time the declared state is held for.
SolverLDES(Network &m, const SolverOptions &o=SolverOptions())
Definition solver.h:361
SolverLDES & init_from_jobs(const Matrix< double > &jobs)
initFromJobs(jobs): start from a job-level initial state, one row per job [station,...
std::vector< std::vector< CdfCurve > > cdf_respt()
getCdfRespT(): the EMPIRICAL response-time CDF per (station, class), from the per-job samples.
const Matrix< double > & job_departures()
The [id, station, class, time] of every departure of a tagged job, from the same run.
const Matrix< double > & final_jobs()
getFinalJobs(): the jobs present at the end of the run, in the init_from_jobs layout.
SolverLDES & init_from_solver(NetworkSolver &aux)
initFromSolver(initSolver): warm-start from an auxiliary solver's steady state.
double total(std::size_t node) const
s.at(node).total(): jobs of every class at a station node; a handle converts.
double at(std::size_t node, std::size_t cls) const
s.at(node, cls): jobs of class cls (1-based; a handle converts) at a station node.
StateView(const std::vector< double > &row, const qn::NetworkStruct< double > &sn)
Definition solver.h:73
UnsupportedError(const std::string &what)
Definition error.h:119
std::vector< NodeDef > nodes
every node, in creation order
A subset of the registry: MATLAB's SolverFeatureSet, whose list is a flag per field.
std::vector< Feature > list() const
The features held, in registry order.
A network plus its refreshed NetworkStruct.
void set_state_prior(std::size_t node, const Matrix< T > &space, const std::vector< T > &prior)
StatefulNode.setStatePrior(space, prior): a distribution over the rows of a DECLARED state space.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
The fluid solver's outermost entry point: @@SolverFLD/runAnalyzer.m's method resolution over solver_f...
The log-driven half of SolverJMT: linkAndLog, parseLogs, parseTranState, parseTranRespT,...
The LDES warm start: SolverLDES(model, initSolver) / initFromSolver.
The flat-Network path of @@SolverLQNS: its qns methods, served by qnsolver of the RADS/LQNS distribut...
Port of @NetworkSolver/mapEnvApprox.m: the solver-agnostic random-environment approximation of a netw...
The stage solvers map_env_approx injects, one per runner that can be a caller.
The method names of each solver, in listing order.
mva::AvgResult< T > solver_ag_run_analyzer(const qn::NetworkStruct< T > &L, const AgOptions &opt)
SolverAG.runAnalyzer: the converged agents as mean measures.
Definition ag_dispatch.h:34
void sn_fj_nodevisits_mmt(qn::NetworkStruct< T > &sn)
Rewrite sn.nodevisits with the MMT correction.
ZeroCapacity sn_zero_capacity(const qn::NetworkStruct< T > &sn)
AutoSolver
The Network candidate slots, in the reference's slot order (SolverAUTO.m:41-50), which is also the or...
std::string auto_metric_group_of(const std::string &name)
The measure group an accessor belongs to, "" when the name belongs to none.
std::string auto_family_alias(const std::string &name)
SolverAUTO.familyAlias: the canonical family of a method name, or "" when the method name names none.
std::vector< std::string > auto_family_metrics(const std::string &family)
The measure groups a method family can answer.
std::vector< std::string > auto_metric_groups()
The measure groups auto_find_solver reports on, in report order.
const char * auto_solver_name(AutoSolver s)
AutoToken auto_resolve_token(const std::string &raw, const qn::NetworkStruct< T > &sn)
resolveMethodToken with its third arm: an intent, a family[.submethod], or else a BARE method name,...
std::vector< AutoSolver > auto_candidates(const qn::NetworkStruct< T > &sn)
The candidate pool in slot order, filtered by supports: what the delegate retries through after the c...
std::vector< AutoSolver > auto_proposed_solvers(const qn::NetworkStruct< T > &sn, const std::string &method, AutoMode mode)
delegate's proposed order: the chosen solver, then every feasible candidate in slot order.
AutoChoice auto_choose_solver_mode(const qn::NetworkStruct< T > &sn, const std::string &method, AutoMode mode)
chooseSolver: the selection mode picks the ranking, and every mode but the two learned ones keeps the...
std::vector< SolverCandidate > auto_find_solver(const qn::NetworkStruct< T > &sn, const std::string &metric=std::string(), bool show_all=false)
SolverAUTO.findSolver: which solvers and solver methods can analyze this model, and for the ones that...
std::vector< std::string > auto_list_valid_methods(const qn::NetworkStruct< T > &sn)
SolverAUTO.listValidMethods: every method name this model can be asked for.
BaBounds< T > ba_bounds(const qn::NetworkStruct< T > &L, const BaOptions &opt)
Port of SolverBA.getBounds.
mva::AvgResult< T > solver_ba_run_analyzer(const qn::NetworkStruct< T > &L, const BaOptions &opt_in)
Port of @@SolverBA/runAnalyzer.m for the lang='matlab' path.
std::vector< std::string > list_valid_methods()
Port of SolverBA.listValidMethods.
std::vector< std::string > list_valid_methods()
Port of SolverCTMC.listValidMethods.
CtmcGenerator< T > ctmc_get_infgen(const NetworkStruct< T > &sn, const CtmcSolution< T > &d)
@@SolverCTMC/getInfGen.m, a pure alias of getGenerator in the reference.
std::vector< std::vector< CdfCurve< T > > > solver_ctmc_cdf_respt(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Port of @@SolverCTMC/getCdfRespT.m: the per-(station, class) response-time CDF, indexed [ist-1][r-1].
CtmcStateSpace< T > ctmc_get_state_space(const NetworkStruct< T > &, const CtmcSolution< T > &d)
Port of @@SolverCTMC/getStateSpace.m.
CtmcFirstPassageMoments< T > ctmc_firstpasst_moments(const NetworkStruct< T > &, const CtmcSolution< T > &d, const Matrix< double > &A, const Matrix< double > &B, std::size_t nmax, const Matrix< double > &C)
Port of @@SolverCTMC/getFirstPassTMoments.m: moments of order 1..nmax of the first passage time from ...
Matrix< T > ctmc_get_state_space_aggr(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Port of @@SolverCTMC/getStateSpaceAggr.m: the per-(station, class) job counts of every state,...
CtmcFirstPassage ctmc_cdf_firstpasst(const NetworkStruct< T > &, const CtmcSolution< T > &d, const Matrix< double > &A, const Matrix< double > &B, const Matrix< double > &C, std::vector< double > tset, const std::string &method="expm")
Port of @@SolverCTMC/getCdfFirstPassT.m: the distribution of the FIRST PASSAGE TIME from state set A ...
CtmcSolution< T > solver_ctmc_analyzer(const NetworkStruct< T > &sn_in, const CtmcOptions &opt)
Port of solver_ctmc_analyzer.m plus the fork-join wrapper of @@SolverCTMC/runAnalyzer....
mva::AvgResult< T > solver_ctmc_mdd_run_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const mdd::MddMcdOptions &mcdopt=mdd::MddMcdOptions())
Solve with mdd and format, so a caller with no use for the diagram has one call.
CtmcSymbolicSolution< T > ctmc_symbolic_solution(const NetworkStruct< T > &sn, const CtmcOptions &opt, const CtmcSymbolicOptions &symopt=CtmcSymbolicOptions())
Port of @@SolverCTMC/getSymbolicSolution.m: pi Q = 0 with sum(pi) = 1 over the field of rational func...
bool is_stateless_method(const std::string &method)
True for a method whose analyzer is not the explicit-generator one.
mva::AvgResult< T > solver_ctmc_run_analyzer_any(const NetworkStruct< T > &sn, const CtmcOptions &opt)
Solve on whichever path applies and format, mirroring solver_ctmc_run_analyzer.
std::vector< std::string > fluid_list_valid_methods()
Port of SolverFLD.listValidMethods.
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.
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,...
JmtReplication< T > jmt_transient_replications(const qn::NetworkStruct< T > &sn, const JmtOptions &opt)
Port of the transient ensemble of @@SolverJMT/runAnalyzer.m (default over a finite timespan).
Definition jmt_logs.h:912
JmtSysTrace< T > jmt_sample_sys_aggr(const qn::NetworkStruct< T > &sn, std::size_t num_events, const JmtOptions &opt)
Port of sampleSysAggr: every station's trajectory on one time grid.
Definition jmt_logs.h:501
JmtNodeTrace< T > jmt_sample_aggr(const qn::NetworkStruct< T > &sn, std::size_t node, std::size_t num_events, const JmtOptions &opt)
Port of sampleAggr: the queue-length trajectory of one node.
Definition jmt_logs.h:450
std::vector< std::string > jmt_list_valid_methods()
Port of SolverJMT.listValidMethods.
JmtResult< T > solver_jmt_run_analyzer(const qn::NetworkStruct< T > &sn, const JmtOptions &opt_in)
Port of @@SolverJMT/runAnalyzer.m, the jsim and jmva arms.
std::map< std::pair< std::size_t, std::size_t >, std::vector< std::pair< double, double > > > jmt_get_cdf_resp_t(const qn::NetworkStruct< T > &sn, const JmtOptions &opt, bool seed_from_steady=true)
Port of getCdfRespT: the empirical response-time distribution per (station, class),...
Definition jmt_logs.h:573
JmtProbAggr jmt_prob_aggr(const qn::NetworkStruct< T > &sn, const JmtOptions &opt, std::size_t target_station=0, const std::vector< double > &target=std::vector< double >())
Port of getProbAggr and getProbSysAggr, both off ONE instrumented run.
Definition jmt_logs.h:816
solvers::FjMetrics< double > ldes_fj_metrics(const qn::NetworkStruct< T > &sn, const LdesResult &r)
ldesParseForkJoin: the engine's "forkJoin" rows mapped onto the Fork/Join pairs of sn by (fork name,...
bool ldes_is_available()
True when this machine can run the engine at all, by either image.
Definition ldes_probe.h:253
std::vector< std::string > list_valid_methods()
Port of SolverLDES.listValidMethods.
Matrix< double > warm_start_placement_from_qlen(const qn::NetworkStruct< T > &sn, const Matrix< T > &QN)
Port of placementFromMeanQLen: an integer placement from mean queue lengths.
Matrix< double > ldes_cdf_respt(const LdesResult &r, std::size_t station, std::size_t job_class)
Port of getCdfRespT: the EMPIRICAL response time CDF of one (station, class) pair,...
Matrix< double > warm_start_placement_from_ctmc(const qn::NetworkStruct< T > &sn, const ctmc::CtmcOptions &opt)
Port of placementFromCtmcSteadyState: the mode of the aggregate stationary law.
LdesResult solver_ldes(const qn::NetworkStruct< T > &sn, const LdesOptions &o, const std::vector< std::string > &extra_flags=std::vector< std::string >())
std::string name(const Element *el, const std::string &key="name")
mva::AvgResult< T > solve_network_run_analyzer(const qn::NetworkStruct< T > &L, const QnsOptions &opt)
Port of @@SolverLQNS/runAnalyzerNetwork.m and solver_qns.m: SolverLQNS on a flat Network.
bool qnsolver_is_available()
Port of SolverLQNS.hasQnsolver: a native qnsolver binary on the PATH.
std::vector< std::string > list_valid_methods()
Port of SolverMAM.listValidMethods.
mva::AvgResult< T > solver_mam_run_analyzer(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of @@SolverMAM/runAnalyzer.m for the lang='matlab' path: solve, then apply the metric filter @@N...
std::vector< std::string > methods(const std::string &solver)
Every method name the solver can be asked for, in listing order.
Matrix< T > sn_get_residt_from_respt(const qn::NetworkStruct< T > &L, const Matrix< T > &RN)
Port of sn_get_residt_from_respt: the per-JOB residence time.
std::string resolve_method(const qn::NetworkStruct< T > &L, const std::string &method)
Port of SolverMVA.resolveMethod: the feature-driven default -> rqna upgrade for a bursty single-class...
std::vector< std::string > list_valid_methods(const qn::NetworkStruct< T > &L)
Port of SolverMVA.listValidMethods.
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
Port of sn_get_arvr_from_tput: the arrival rate each station sees, from the throughputs and the class...
AvgResult< T > solver_mva_run_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt_in, const Matrix< T > &init_sol)
Port of @@SolverMVA/runAnalyzer.m for the lang='matlab' path: gate, solve, convert,...
mva::AvgResult< T > solver_nc_run_analyzer(const qn::NetworkStruct< T > &L_in, const NcSolverOptions &opt_in)
std::vector< std::string > list_valid_methods()
Port of SolverNC.listValidMethods.
std::vector< std::vector< T > > from_marginal(const NetworkStruct< T > &sn, std::size_t ist, const std::vector< std::size_t > &n, const std::vector< std::size_t > &phases)
Definition state.h:1460
FeatureSet ldes_feature_set(const std::string &)
SolverLDES.getFeatureSet, transcribed WHOLE.
FeatureSet fluid_feature_set(const std::string &method)
FeatureSet ssa_feature_set(const std::string &method)
FeatureSet jmt_feature_set(const std::string &method)
Port of SolverJMT.getFeatureSet (@@SolverJMT/SolverJMT.m:180-290).
FeatureSet used_lang_features(const NetworkStruct< T > &sn)
FeatureSet nc_feature_set(const std::string &method)
SolverNC.getFeatureSet, 48 names, transcribed unchanged.
FeatureSet fluid_class_feature_set()
SolverFLD.getFeatureSet, transcribed, MINUS what the requested method cannot evaluate – the port of @...
FeatureSet ctmc_feature_set(const std::string &method)
SolverCTMC.getFeatureSet, the reference's 104 MATLAB names in full.
SupportResult feature_set_supports(const std::string &solver, const FeatureSet &declared, const FeatureSet &used)
SolverFeatureSet.supports: is every feature the model uses declared?
FeatureSet mam_feature_set(const std::string &method)
SolverMAM.getFeatureSet, the union of its four setTrue calls: 55 MATLAB names, WIDENED for 'default'/...
FeatureSet mva_feature_set(const std::string &raw_method)
SolverMVA.getFeatureSet, plus getMethodFeatureSet's per-method deltas.
const char * feature_name(Feature f)
The canonical registry name, byte for byte the MATLAB field name.
MapEnvDecision needs_map_env(const qn::FeatureSet &declared, const qn::NetworkStruct< T > &sn, const MapEnvConfig &cfg=MapEnvConfig())
needsMapEnv: does this model need the environment image, and would the image make it solvable?
CacheMetrics< T > cache_metrics_of(const qn::NetworkStruct< T > &sn, const std::vector< T > &hitprob, const std::vector< T > &missprob, const std::vector< T > &delayedprob, const std::vector< T > &latency, const Matrix< T > &hitproblist, const Matrix< T > &itemprob, const std::vector< T > &listcost)
Assemble CacheMetrics from what a cache analyzer returned.
void cache_mark_nonreading(const qn::NetworkStruct< T > &sn, CacheNodeMetrics< T > &m)
A CLASS THAT DOES NOT READ THE CACHE READS NaN, in every solver and every codebase (BUG-101): NaN is ...
std::vector< std::string > chain_class_labels(const qn::NetworkStruct< T > &sn)
(ClassA ClassB), the JobClasses column: which classes a chain holds.
env::EnvStageAvgFn< double > nc_stage_fn(const nc::NcSolverOptions &opt)
NC stages, bound to the caller's own NcSolverOptions.
SysResult< T > solver_get_avg_sys(const qn::NetworkStruct< T > &sn, const mva::AvgResult< T > &r)
Port of @@NetworkSolver/getAvgSys.m.
mva::AvgResult< T > run_avg(const qn::NetworkStruct< T > &sn, const std::string &solver, const qn::FeatureSet &declared, const MapEnvConfig &cfg, Run run, StageFn stage_fn, const std::string &requested_method="default")
The getAvg funnel: run the model, or its environment image when the ONLY thing in the way is a non-re...
Definition map_env.h:332
ChainResult< T > solver_get_avg_chain(const qn::NetworkStruct< T > &sn, const mva::AvgResult< T > &r)
Port of @@NetworkSolver/getAvgChain.m: the station table aggregated by chain.
mva::AvgResult< T > map_env_approx(const qn::NetworkStruct< T > &sn, const std::string &solver, const MapEnvConfig &cfg, StageFn stage_fn, const std::string &requested_method="default")
mapEnvApprox: solve the model through the random-environment image of its non-renewal processes.
Definition map_env.h:186
std::vector< std::string > chain_names(std::size_t nchains)
Chain1, Chain2, ... – the reference's own chain labels.
env::EnvStageAvgFn< double > fluid_stage_fn(const fluid::FluidOptions &opt)
Fluid stages, bound to the caller's own FluidOptions.
env::EnvStageAvgFn< double > mva_stage_fn(const mva::MvaOptions &opt)
MVA stages, bound to the caller's own MvaOptions.
std::vector< std::string > list_valid_methods()
Port of SolverSSA.listValidMethods.
SsaSolution solver_ssa(const qn::NetworkStruct< T > &sn, const SsaOptions &opt, std::vector< SsaCacheRatio > *cache=nullptr)
qn::NetworkStruct< T > sn_with_ssa_cache_split(const qn::NetworkStruct< T > &base, const std::vector< SsaCacheRatio > &cache)
@@SolverSSA/runAnalyzer itself: the engine the method selects, then the result assembly the reference...
solvers::CacheMetrics< T > cache_metrics_of_ssa(const qn::NetworkStruct< T > &sn, const std::vector< SsaCacheRatio > &cache)
CacheMetrics from the serial engine's cache write-back.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
qn::Network< double > Network
Network as a user names it: the double model, Python's Network.
Definition solver.h:57
std::ostream & operator<<(std::ostream &out, const AvgTable &table)
std::function< bool(const StateView &)> StatePredicate
A STATE SET named by a predicate, the reference's function-handle form, e.g.
Definition solver.h:93
The model API a user writes, spelled as its Python twin.
Post-MMT node visits of a fork-join model.
Port of matlab/src/api/sn/sn_zero_capacity.m: the closed chains trapped at a zero-capacity station.
The solver API a user writes, spelled as its Python twin.
The SolverAUTO chooser: which solver a model is handed to.
The SolverBA class surface: @@SolverBA/runAnalyzer.m, listValidMethods, getBounds and getBoundsTable.
The CHAIN-level and SYSTEM-level views of a solved model.
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
Port of @@SolverCTMC/getCdfRespT.m and @@SolverCTMC/getCdfSysRespT.m: the exact distribution of the r...
The remaining @@SolverCTMC accessors: getGenerator / getInfGen, getStateSpace / getStateSpaceAggr and...
The mdd method of SolverCTMC: stationary analysis of a closed single-class network whose state space ...
The SolverCTMC probability family: solver_ctmc_joint, _jointaggr, _marg, _margaggr,...
Port of @@SolverCTMC/getSymbolicGenerator and getSymbolicSolution.
Port of solver_ctmc_fcr_waitq.m: the reachability-built generator of a model whose finite capacity re...
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
Port of SolverJMT, the Java Modelling Tools client.
Port of SolverLDES, the discrete-event simulator, as its C++ client.
The SolverMAM class surface: @@SolverMAM/runAnalyzer.m and the gates around it.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
The SolverNC class surface: @@SolverNC/runAnalyzer.m and the gates around it.
The SolverSSA entry surface: a port of @@SolverSSA/runAnalyzer.m's method whitelist,...
Port of the MATLAB +State package: the encoding that turns a station's state row into marginal job co...
One row of SolverAUTO::find_solver: a (family, method) pair and whether this model can run it.
Definition solver.h:443
bool runnable
the model passes this method's own support gate
Definition solver.h:446
std::string solver
the method family: "mva", "ctmc", "ldes", ...
Definition solver.h:444
std::string method
the qualified method name to pass, "mva.exact"
Definition solver.h:445
std::string method_class
"exact", "approx", "bound" or "simulation"
Definition solver.h:447
std::string reason
why a refused pair was refused, "" when runnable
Definition solver.h:449
std::string metrics
the measure groups the family answers, comma-joined
Definition solver.h:448
getAvgChainTable: the station table aggregated by CHAIN, one row per (station, chain),...
Definition avg_table.h:144
std::vector< double > QLen
Definition avg_table.h:146
std::vector< double > ArvR
Definition avg_table.h:146
std::vector< double > RespT
Definition avg_table.h:146
std::vector< double > Util
Definition avg_table.h:146
std::vector< double > ResidT
Definition avg_table.h:146
std::size_t size() const
Definition avg_table.h:150
void print(std::ostream &out) const
Print the station-chain table.
std::vector< std::string > Chain
Definition avg_table.h:145
std::vector< std::string > JobClasses
Definition avg_table.h:145
std::vector< std::string > Station
Definition avg_table.h:145
std::vector< double > Tput
Definition avg_table.h:146
getAvgChain(): the station x chain matrices, @NetworkSolver/getAvgChain.m's outputs.
Definition solver.h:62
Matrix< double > QN
Definition solver.h:63
Matrix< double > TN
Definition solver.h:63
Matrix< double > RN
Definition solver.h:63
Matrix< double > AN
Definition solver.h:63
Matrix< double > UN
Definition solver.h:63
Matrix< double > WN
Definition solver.h:63
getAvgFJTable(): one row per (Fork/Join pair, class) with a nonzero metric, the fork-join SECTION met...
Definition avg_table.h:162
std::vector< std::string > Fork
Definition avg_table.h:163
std::size_t size() const
Definition avg_table.h:168
void print(std::ostream &out) const
Print the labelled pair-class table.
std::vector< double > FJTput
Definition avg_table.h:164
std::vector< std::string > JobClass
Definition avg_table.h:163
std::vector< double > FJRespT
Definition avg_table.h:164
std::vector< double > FJQLen
Definition avg_table.h:164
std::vector< std::string > Join
Definition avg_table.h:163
getAvgSysTable(): one row per CHAIN, the cycle time and the completing throughput at the chain's refe...
Definition avg_table.h:128
std::vector< double > SysRespT
Definition avg_table.h:130
void print(std::ostream &out) const
Print the chain table.
std::vector< std::string > JobClasses
Definition avg_table.h:129
std::vector< double > SysTput
Definition avg_table.h:130
std::vector< std::string > Chain
Definition avg_table.h:129
getAvgTable, one row per (station, class) that carries a metric.
Definition avg_table.h:38
std::vector< double > ArvR
Definition avg_table.h:40
std::vector< double > TputCI
Definition avg_table.h:56
std::vector< double > UtilCI
Definition avg_table.h:56
std::vector< double > Tput
Definition avg_table.h:40
std::vector< double > ResidT
Definition avg_table.h:40
void print(std::ostream &out) const
Print the labelled station-class table.
std::vector< double > RespT
Definition avg_table.h:40
std::size_t size() const
The number of rows the table carries.
Definition avg_table.h:95
double lognormconst
Definition avg_table.h:47
std::vector< double > Util
Definition avg_table.h:40
void print() const
std::vector< std::string > Station
Definition avg_table.h:39
std::vector< double > column(const std::string &column) const
A complete metric column.
std::vector< double > SysTput
Definition avg_table.h:43
bool has_ci() const
Whether the half-width columns are filled.
Definition avg_table.h:58
std::vector< double > ResidTCI
Definition avg_table.h:56
std::vector< std::string > JobClass
Definition avg_table.h:39
std::string warning
The reference's own warning text, empty when it did not warn.
Definition avg_table.h:51
double get(const std::string &column, const std::string &station, const std::string &jobclass) const
One cell of the table, by station and class NAME; NaN when absent.
std::string solver
Definition avg_table.h:44
std::vector< double > QLenCI
Confidence half-widths of the six metric columns, row for row; EMPTY unless SolverOptions::confint as...
Definition avg_table.h:56
AvgTable tget(const Node &node) const
AvgTable filter_by(const std::string &name) const
Rows whose station or class has name, MATLAB's one-argument filterBy.
std::vector< double > RespTCI
Definition avg_table.h:56
std::string method
Definition avg_table.h:44
std::vector< double > ArvRCI
Definition avg_table.h:56
AvgTable operator()(const Node &node) const
Direct object indexing: table(queue, jobs).
bool has_lognormconst
Definition avg_table.h:46
std::vector< double > ListCost
getAvgCacheTable's ListCost column; empty on a model without item sizes.
Definition avg_table.h:49
std::vector< double > SysRespT
Definition avg_table.h:43
std::vector< std::string > SysClass
getAvgSysTable: system response time and throughput, per class.
Definition avg_table.h:42
std::vector< double > QLen
Definition avg_table.h:40
SolverBA(model, method).getBoundsTable().
Definition avg_table.h:175
std::vector< double > Tlower
Definition avg_table.h:177
std::string method
Definition avg_table.h:178
std::vector< double > Qupper
Definition avg_table.h:177
std::vector< std::string > Station
Definition avg_table.h:176
std::vector< double > Qlower
Definition avg_table.h:177
std::vector< std::string > JobClass
Definition avg_table.h:176
std::vector< double > Tupper
Definition avg_table.h:177
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
getFirstPassTMoments(A, B, nmax, C): [m, mall, p] with the resolved sets.
Definition solver.h:108
std::vector< double > m
moments 1..nmax from A (conditional on B before C)
Definition solver.h:109
getCdfFirstPassT(A, B, C, tset): the curve, the reach probabilities and the resolved sets.
Definition solver.h:96
std::vector< double > t
the grid: the caller's, else 1000 points to the horizon
Definition solver.h:97
sampleSysAggr / sampleAggr: ONE simulated trajectory, not a mean.
Definition avg_table.h:218
std::vector< std::vector< double > > state
[step][column]
Definition avg_table.h:220
std::vector< std::string > label
what each column counts
Definition avg_table.h:221
std::vector< double > t
event times, ascending
Definition avg_table.h:219
The knobs a solver reads; a negative or empty field keeps the engine default.
double warmupfrac
options.warmupfrac, in [0,1): LDES under tranfilter=fixed, and SSA's discarded prefix.
Matrix< double > init_jobs
LDES options.init_jobs: one row per job, [station, class, remaining, elapsed, qage,...
int level
SolverBA options.level.
bool track_jobs
LDES options.track_jobs: return the end-of-run job state; implied by init_jobs.
bool cache
options.cache: reuse the solved tables on later calls to the same solver object.
std::string tranfilter
LDES options.tranfilter: mser5 | fixed | none; empty keeps mser5.
getSymbolicSolution: the stationary law as a function of the rate symbols.
Definition avg_table.h:194
std::vector< double > rate0
nominal value of each symbol
Definition avg_table.h:199
std::string den
common denominator of the vector
Definition avg_table.h:197
std::vector< std::string > pi
stationary probability of each state
Definition avg_table.h:195
std::vector< std::string > symbols
x1..xE, empty where an event has no positive rate
Definition avg_table.h:198
std::string engine
backend that answered, e.g. "sage"
Definition avg_table.h:200
std::vector< std::string > num
numerator of each entry over den
Definition avg_table.h:196
getTranAvg: the transient mean queue length per (station, class).
Definition avg_table.h:204
std::vector< std::string > label
the (station, class) of each column
Definition avg_table.h:207
std::vector< std::vector< double > > QNt
[step][station*class]
Definition avg_table.h:206
std::vector< double > t
the time axis
Definition avg_table.h:205
What sn_zero_capacity reports.
std::string reason
Non-empty when the model has no steady state of this form: an open class reaches a zero-capacity stat...
bool any() const
True when some chain is trapped.
What a ranking resolved to, and what it had to skip to get there.
resolveMethodToken, minus the unqualified-algorithm-name arm.
Port of SolverBA.getBounds: the {lower,upper} bracket of a family.
std::vector< std::vector< bool > > keep
getBoundsTable's row filter, (M x K): whether the (station, class) pair earns a row.
Matrix< T > Qupper
(M x K), all-NaN on a side the family lacks
The options SolverBA reads.
The answer of @@SolverCTMC/getFirstPassTMoments.m.
The answer of @@SolverCTMC/getCdfFirstPassT.m: the [F(t), t] curve with its grid, density and resolve...
The SolverCTMC knobs this port honours.
Everything one CTMC solve produces.
std::shared_ptr< const NetworkStruct< T > > solved_sn
The struct the chain was built on, when it differs from the caller's in its state LAYOUT: set when he...
std::vector< T > pi
stationary distribution over chain.space
Backend selection, mirroring options.config.symbolic and its timeout.
std::string backend
auto to search, a URL, an image name, or none to stay local.
What @@SolverCTMC/getSymbolicSolution.m returns, plus the engine that answered.
std::string engine
backend that solved it, e.g. sage
std::vector< T > rate0
Nominal value of each symbol, i.e.
std::vector< std::string > symbols
x1..xE, empty for an event with no positive rate.
std::vector< std::string > num
numerator of each entry over den
std::vector< std::string > pi
stationary probability of each state
std::string den
common denominator of the vector
Controls, defaulting to SolverOptions('Fluid') in the reference.
What the analyzer returns, in the same shape as the MVA solver's result.
The per-class queue-length trajectory of one node, plus its event stream.
Definition jmt_logs.h:227
The options of one JMT solve, SolverOptions('JMT') restricted to what is read.
Definition solver_jmt.h:121
std::string method
default | jsim | jmva | jmva.<alg>
Definition solver_jmt.h:122
What jmt_prob_aggr reports: the system probability and the per-station ones.
Definition jmt_logs.h:779
std::vector< double > station
P(station i holds its declared per-class counts).
Definition jmt_logs.h:781
Transient averages over independent replications, on one time grid.
Definition jmt_logs.h:880
std::vector< double > t
Definition jmt_logs.h:881
std::vector< std::vector< std::vector< double > > > QNt
QNt[ist-1][r] over t.
Definition jmt_logs.h:883
The result of a JMT solve: the shared AvgResult plus what only JMT reports.
Definition solver_jmt.h:759
The system trajectory: one per-class block per station, on a common grid.
Definition jmt_logs.h:483
The knobs of one LDES run.
One ldes-result document, parsed.
std::vector< std::vector< std::vector< double > > > respTimeSamples
[station][class] -> the per-job response times the engine recorded.
Matrix< double > jobDepartures
[id, station, class, time] of every departure of a tagged init_jobs job (track_jobs).
Matrix< double > finalJobs
The jobs present at the end of the run, in the init_jobs layout (track_jobs).
SolverLQNS.defaultOptions on a flat Network plus the knobs the JMVA document carries.
The options SolverMAM reads.
Definition mam_types.h:30
The metrics getAvg returns, after filtering.
Matrix< T > TN
throughput
Matrix< T > RN
response time, per visit
Matrix< T > UN
utilization
Matrix< T > WN
residence time, per job
std::string method
the method asked for
Matrix< T > QN
queue length
Matrix< T > AN
arrival rate
The options SolverMVA reads.
Definition mva_types.h:32
Controls, defaulting to SolverOptions('NC') in the reference.
Definition nc_types.h:33
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< T > hitprob
(K) TRUE hit fraction, EMPTY = not computed
The station- or node-level table aggregated by chain.
Matrix< T > TN
(rows x nchains), rows = stations or nodes
self.result.FJ: one row per Fork/Join pair, one column per class.
Definition fj_metrics.h:43
The caller-facing map_env knobs, options.config.map_env and friends.
What the gate decided, and what it decided it about.
@@NetworkSolver/getAvgSys: one response time and one throughput per chain.
std::vector< T > XN
(nchains) system throughput at the reference station
std::vector< T > CN
(nchains) system response time, i.e. the cycle time
Controls, defaulting to SolverOptions('SSA') in the reference.
Definition ssa_types.h:71
What the analyzer returns, in the same shape as the MVA and fluid results.
Definition ssa_types.h:147