LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_uq.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_SOLVERS_UQ_SOLVER_UQ_H
6#define LINE_SOLVERS_UQ_SOLVER_UQ_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * SolverUQ: uncertainty quantification by expansion over a Prior.
12 *
13 * Port of `matlab/src/solvers/UQ/@@UQ/UQ.m`. The model carries one or more
14 * `Prior` distributions (lang/prior.h); UQ reduces them to a set of weighted
15 * DESIGN POINTS, each of which is a concrete model with every Prior replaced by
16 * one alternative, solves each with an ordinary solver, and reports the
17 * prior-weighted expectation of every metric together with the per-point
18 * results the expectation was formed from.
19 *
20 * WHAT THE WEIGHTS MEAN. E[Q] = sum_l w_l Q(theta_l) is the unconditional
21 * expectation of Trivedi and Bobbio (2017), Eq. (3.68): an average over MODELS,
22 * not over jobs. Its spread -- `uq_moments`, `uq_credible_interval` -- is the
23 * epistemic uncertainty in the answer, and it is the reason the per-point table
24 * is kept rather than reduced away: a mean of 4.2 over points at 1.1 and 12.4
25 * is a different statement from a mean of 4.2 over points at 4.1 and 4.3, and
26 * only the design carries the difference.
27 *
28 * THE DESIGN, and why it is a tensor product. Each Prior is discretized on its
29 * own and the design is the product of the per-Prior alternative sets, so the
30 * joint weight is the product of the marginal weights. That is the
31 * product-density case of f(theta_1, ..., theta_l) in Eq. (3.67) and it ASSUMES
32 * THE PRIORS ARE INDEPENDENT; a joint prior over several parameters is not
33 * expressible here, in this port or in the reference. The size is capped at
34 * `kMaxDesignPoints` because a design point is a full solver run, and beyond
35 * the cap the Monte Carlo design -- whose cost does not grow with the number of
36 * Priors -- is the right tool. The cap REFUSES rather than truncating: a design
37 * silently cut to 4096 of 20000 points would report an expectation against a
38 * prior nobody wrote.
39 *
40 * WHAT SOLVES A DESIGN POINT is supplied by the caller as a `UqStageSolver`,
41 * the C++ spelling of the reference's `solverFactory` argument (`UQ(model,
42 * @@SolverMVA)`). There is no default: the inner solver decides both the
43 * accuracy and the admissible feature set of every number reported here, and
44 * choosing one silently would answer a question the caller did not ask.
45 * `uq_dispatch.h` builds one from a solver name.
46 *
47 * THE FEATURE GATE IS THE INNER SOLVER'S. UQ itself declares only `Prior`
48 * (`uq_feature_set`) and applies no feature gate of its own; what `UQ.supports`
49 * decides is only whether the model carries a Prior at all. The real check
50 * happens per design point, inside the stage solver, on a model from which the
51 * Prior has already been removed; that is what makes "SolverMVA cannot solve
52 * this model" reach the caller as SolverMVA's own refusal instead of a UQ
53 * paraphrase of it.
54 */
55
56#include <algorithm>
57#include <cstddef>
58#include <functional>
59#include <string>
60#include <utility>
61#include <vector>
62
65#include "line/lang/prior.h"
69#include "line/num/number.h"
71#include "line/util/error.h"
73
74namespace line {
75namespace uq {
76
77/**
78 * The cap on the tensor-product design, MATLAB `UQ.MaxDesignPoints`.
79 *
80 * A design point is one full solver run, so this bounds the cost of a
81 * quadrature design over several Priors.
82 */
83inline constexpr std::size_t kMaxDesignPoints = 4096;
84
85/** `UQ.defaultOptions` plus the stream the Monte Carlo design draws from. */
86struct UqOptions {
87 /**
88 * `default` | `dd` | `quad` | `mc`, MATLAB
89 * `UQ.listValidMethods`. The first three all resolve to the quadrature
90 * design: a discrete Prior is expanded as given, a continuous one is placed
91 * at stratum medians. Only `mc` draws.
92 */
93 std::string method = "default";
94 /**
95 * Nodes per continuous Prior, or design points under `mc`; the
96 * reference's `options.samples`, defaulted to 11 rather than to the
97 * simulation-oriented default of `Solver.defaultOptions` because each node
98 * is a full solver run.
99 */
101 /** The Monte Carlo stream; unread by a quadrature design, which draws nothing. */
102 unsigned long seed = 23000;
103};
104
105/** Where a Prior sits in the model, MATLAB's `priorInfo` entry. */
106template <class T>
107struct PriorSite {
108 std::size_t node = 0; ///< 1-based node index
109 std::size_t station = 0; ///< 1-based station index
110 std::size_t cls = 0; ///< 1-based class index
111 /** True at a Source, where the Prior is an ARRIVAL process, not a service one. */
112 bool arrival = false;
113 /** The Prior itself, copied out of the service table. */
115};
116
117/** One design point: a concrete distribution for every Prior, and its weight. */
118template <class T>
121 std::vector<lang::Distrib<T>> dists; ///< one per site, in site order
122};
123
124/** What `solver_uq_run_analyzer` returns. */
125template <class T>
127 /** The prior-weighted expectation of every metric, (nstations x nclasses). */
129 /** The result at each design point, in design order. */
130 std::vector<mva::AvgResult<T>> points;
131 /** The design weights, summing to 1. */
132 std::vector<T> weights;
133 /** The alternatives each point substituted, one per site. */
134 std::vector<UqDesignPoint<T>> design;
135 /** Where the Priors were found. */
136 std::vector<PriorSite<T>> sites;
137 /** The RESOLVED discretization method: `quad` or `mc`. */
138 std::string method;
139 /** The options the design was built with; `uq_interval` reads `samples`. */
141};
142
143/**
144 * `UQ.getUQMethod`: resolve the discretization method.
145 *
146 * `default` and `dd` are aliases of `quad` and not separate rules:
147 * a discrete Prior is already exact, so expanding it as given IS the quadrature
148 * design for it, and the name survives only because the reference lists it.
149 */
150inline std::string uq_resolve_method(const std::string& m) {
151 if (m.empty() || m == "default" || m == "dd" || m == "quad") return "quad";
152 if (m == "mc") return "mc";
153 throw InputError("SolverUQ: unknown method '" + m +
154 "'; the valid names are default, dd, quad and mc");
155}
156
157/** `UQ.listValidMethods`. */
158inline std::vector<std::string> uq_list_valid_methods() {
159 // the names and their order are this solver's row of the method registry (doc/line-methods.json)
160 return ::line::method_registry::methods("UQ");
161}
162
163/**
164 * `UQ.getFeatureSet`: the one construct UQ adds, and nothing else.
165 *
166 * IT IS NOT USED AS A FEATURE GATE, here or in the reference: UQ solves nothing
167 * itself, so the set of models it admits is the inner solver's, applied per
168 * design point once the Prior is gone. It is declared because the registry is
169 * the vocabulary in which a capability is stated, and "SolverUQ is the solver
170 * that understands Prior" is a statement worth being able to make.
171 *
172 * THE ONE THING `UQ.supports` DOES DECIDE is whether the model carries a Prior
173 * at all: a model with no uncertain parameter is not a UQ model, and its
174 * posterior is a single design point equal to the point estimate the inner
175 * solver already returns. `has_prior_distribution()` is that test here. MATLAB
176 * used to return true unconditionally -- which made SolverAUTO offer every
177 * 'uq.*' method name on an ordinary network -- and now gates on `UQ.modelHasPrior`,
178 * matching the JAR (`detectPrior() != null`) and native python
179 * (`hasPriorDistribution`).
180 */
183 f.set(qn::Feature::Prior);
184 return f;
185}
186
187/**
188 * `UQ.detectPriors`: find every Prior, in node order and then class order.
189 *
190 * SERVICE AT A QUEUE OR DELAY, ARRIVAL AT A SOURCE, which is the reference's own
191 * pair of branches. A Prior anywhere else -- a Cache's read process, a
192 * Transition's firing law -- is REFUSED by name rather than skipped: the
193 * reference's loop would ignore it and then solve a model in which the
194 * uncertainty silently became the mixture moments, which is a confident answer
195 * to a question nobody asked.
196 */
197template <class T>
198std::vector<PriorSite<T>> uq_detect_priors(const qn::NetworkStruct<T>& sn) {
199 std::vector<PriorSite<T>> sites;
200 for (std::size_t nd = 1; nd <= sn.nodes.size(); ++nd) {
201 const std::size_t ist = sn.nodes[nd - 1].station;
202 if (ist == 0 || ist > sn.service.size()) continue;
203 const qn::NodeType ty = sn.nodes[nd - 1].nodetype;
204 const bool servicer = (ty == qn::NodeType::Queue || ty == qn::NodeType::Delay);
205 const bool source = (ty == qn::NodeType::Source);
206 for (std::size_t r = 1; r <= sn.service[ist - 1].size(); ++r) {
207 const lang::Distrib<T>& d = sn.service[ist - 1][r - 1];
208 if (!d.is_prior()) continue;
209 if (!servicer && !source)
210 throw UnsupportedError(ErrorKind{"Prior"},
211 "SolverUQ: node '" + sn.nodes[nd - 1].name +
212 "' carries a Prior, but a Prior is expanded only where the reference expands "
213 "one: the service process of a Queue or a Delay, or the arrival process of a "
214 "Source");
215 PriorSite<T> s;
216 s.node = nd;
217 s.station = ist;
218 s.cls = r;
219 s.arrival = source;
220 s.prior = d;
221 sites.push_back(s);
222 }
223 }
224 return sites;
225}
226
227namespace detail {
228
229/**
230 * `UQ.unrankIndex`: the linear index i in [0, prod(counts)) as a subscript
231 * vector over a mixed-radix grid, FIRST COORDINATE VARYING FASTEST.
232 *
233 * The order is the reference's and is kept because it is what makes design
234 * point k the same model in both codebases; any other unranking would permute
235 * the per-point table while leaving the expectation unchanged, which is the
236 * hardest kind of divergence to notice.
237 */
238inline std::vector<std::size_t> unrank_index(std::size_t i,
239 const std::vector<std::size_t>& counts) {
240 std::vector<std::size_t> idx(counts.size(), 0);
241 std::size_t rem = i;
242 for (std::size_t l = 0; l < counts.size(); ++l) {
243 idx[l] = rem % counts[l];
244 rem /= counts[l];
245 }
246 return idx;
247}
248
249} // namespace detail
250
251/**
252 * `UQ.buildDesign`: reduce the detected Priors to weighted design points.
253 *
254 * With no Prior there is ONE point of weight 1 substituting nothing, so the
255 * original model is solved once and the expectation is that solve -- the
256 * degenerate case the reference also carries, and the reason a model without a
257 * Prior is not an error here.
258 */
259template <class T>
260std::vector<UqDesignPoint<T>> uq_build_design(const std::vector<PriorSite<T>>& sites,
261 const UqOptions& opt) {
262 const std::string method = uq_resolve_method(opt.method);
263 const std::size_t n = opt.samples;
264 if (n < 1) throw InputError("SolverUQ: options.samples must be at least 1");
265 std::vector<UqDesignPoint<T>> design;
266 if (sites.empty()) {
267 design.push_back(UqDesignPoint<T>());
268 return design;
269 }
270 const std::size_t L = sites.size();
271 lang::PriorRng rng(opt.seed);
272
273 if (method == "mc") {
274 // ALL PRIORS ARE DRAWN JOINTLY at each point, so the cost is n runs
275 // whatever L is; that independence from L is the whole reason the
276 // method exists beside the tensor product.
277 const T w = T(num_traits<T>::from_int(1) / num_traits<T>::from_int(static_cast<long>(n)));
278 for (std::size_t i = 0; i < n; ++i) {
280 p.weight = w;
281 for (std::size_t l = 0; l < L; ++l) {
282 const lang::PriorDesign<T> g =
283 lang::prior_discretize(sites[l].prior, 1, "mc", rng);
284 p.dists.push_back(g.dists[0]);
285 }
286 design.push_back(p);
287 }
288 return design;
289 }
290
291 std::vector<lang::PriorDesign<T>> marg(L);
292 std::vector<std::size_t> counts(L, 0);
293 std::size_t total = 1;
294 for (std::size_t l = 0; l < L; ++l) {
295 marg[l] = lang::prior_discretize(sites[l].prior, n, "quad", rng);
296 counts[l] = marg[l].dists.size();
297 if (counts[l] == 0) throw NumericError(ErrorKind{"Degenerate"}, "SolverUQ: a Prior discretized to no alternative");
298 // SATURATE RATHER THAN OVERFLOW: the product of a dozen 11-node Priors
299 // does not fit a size_t, and a wrapped one would pass the cap check.
300 if (total > kMaxDesignPoints / counts[l]) {
301 total = kMaxDesignPoints + 1;
302 break;
303 }
304 total *= counts[l];
305 }
306 if (total > kMaxDesignPoints)
307 throw UnsupportedError(ErrorKind{"StateSpace"},
308 "SolverUQ: the tensor-product design has " + std::to_string(total) +
309 " points, above the limit of " + std::to_string(kMaxDesignPoints) +
310 "; use method 'mc', whose cost does not grow with the number of Priors, or "
311 "lower options.samples");
312
313 for (std::size_t i = 0; i < total; ++i) {
314 const std::vector<std::size_t> idx = detail::unrank_index(i, counts);
317 for (std::size_t l = 0; l < L; ++l) {
318 p.dists.push_back(marg[l].dists[idx[l]]);
319 p.weight = T(p.weight * marg[l].weights[idx[l]]);
320 }
321 design.push_back(p);
322 }
323 return design;
324}
325
326/**
327 * What solves one design point: the C++ spelling of `@(m) SolverXXX(m)`.
328 *
329 * It takes the REFRESHED struct of the expanded model and returns the same
330 * `AvgResult` every Network solver in this port returns, so the aggregation
331 * below is one loop rather than one loop per solver.
332 */
333template <class T>
334using UqStageSolver = std::function<mva::AvgResult<T>(const qn::NetworkStruct<T>&)>;
335
336namespace detail {
337
338/** M += w * A, sizing M from A on first use. */
339template <class T>
340void accumulate(Matrix<T>& M, const Matrix<T>& A, const T& w) {
341 if (A.empty()) return;
342 if (M.empty()) M = Matrix<T>(A.rows(), A.cols(), num_traits<T>::from_int(0));
343 if (M.rows() != A.rows() || M.cols() != A.cols())
344 throw NumericError(ErrorKind{"Degenerate"}, "SolverUQ: two design points reported metrics of different shape");
345 for (std::size_t i = 0; i < A.rows(); ++i)
346 for (std::size_t j = 0; j < A.cols(); ++j) M(i, j) = T(M(i, j) + w * A(i, j));
347}
348
349template <class T>
350void accumulate(std::vector<T>& v, const std::vector<T>& a, const T& w) {
351 if (a.empty()) return;
352 if (v.empty()) v.assign(a.size(), num_traits<T>::from_int(0));
353 if (v.size() != a.size())
354 throw NumericError(ErrorKind{"Degenerate"}, "SolverUQ: two design points reported vectors of different length");
355 for (std::size_t i = 0; i < a.size(); ++i) v[i] = T(v[i] + w * a[i]);
356}
357
358} // namespace detail
359
360/**
361 * `UQ.aggregateResults`: the prior-weighted expectation of the solved points.
362 *
363 * Free rather than private to the solver below because it is a pure function of
364 * `points` and `weights`, and `post()` is not the only caller that has those: a
365 * host that solved the design itself -- one point per process, one per machine --
366 * aggregates with this and needs nothing else from the class.
367 */
368template <class T>
370 sol.avg = mva::AvgResult<T>();
371 // The expectation, metric by metric. CN and XN are aggregated beside the
372 // station matrices because the reference's field list carries them.
373 for (std::size_t e = 0; e < sol.points.size(); ++e) {
374 const mva::AvgResult<T>& r = sol.points[e];
375 const T w = sol.weights[e];
376 detail::accumulate(sol.avg.QN, r.QN, w);
377 detail::accumulate(sol.avg.UN, r.UN, w);
378 detail::accumulate(sol.avg.RN, r.RN, w);
379 detail::accumulate(sol.avg.TN, r.TN, w);
380 detail::accumulate(sol.avg.AN, r.AN, w);
381 detail::accumulate(sol.avg.WN, r.WN, w);
382 detail::accumulate(sol.avg.CN, r.CN, w);
383 detail::accumulate(sol.avg.XN, r.XN, w);
384 }
385 if (sol.points.empty()) return;
386 // THE AGGREGATE NAMES THE ALGORITHM ONLY IF EVERY POINT RAN THE SAME ONE. A
387 // method that resolves per model -- SolverMVA's `default` picks `exact` on
388 // one alternative and an AMVA on another -- would otherwise have the
389 // expectation reported under one point's name.
390 sol.avg.method = sol.points[0].method;
391 sol.avg.actualmethod = sol.points[0].actualmethod;
392 for (const mva::AvgResult<T>& r : sol.points)
393 if (r.actualmethod != sol.avg.actualmethod) {
394 sol.avg.actualmethod = "mixed";
395 break;
396 }
397 for (std::size_t e = 0; e < sol.points.size(); ++e)
398 if (!sol.points[e].warning.empty()) {
399 sol.avg.warning =
400 "design point " + std::to_string(e + 1) + ": " + sol.points[e].warning;
401 break;
402 }
403}
404
405/**
406 * The ensemble surface of UQ: `@UQ`'s `EnsembleSolver` implementation.
407 *
408 * WHY A CLASS AND NOT ONLY `solver_uq_run_analyzer`. The reference is an
409 * `EnsembleSolver`, and the lifecycle it implements -- `init`, `pre`, `analyze`,
410 * `post`, `converged`, `finish` -- is not decoration: it is the surface a caller
411 * uses to drive the design POINT BY POINT, to inspect the ensemble before
412 * solving it, to substitute its own stage solver per point, or to distribute the
413 * points and aggregate afterwards. A single `run` function can do none of those,
414 * so the port carried the numbers of UQ without the way UQ is meant to be
415 * driven. `SolverEnv` in `env/solver_env.h` keeps the same lifecycle for the
416 * same reason.
417 *
418 * ONE DELIBERATE DEVIATION: the reference's `init` materializes the whole
419 * ensemble (`self.ensemble{i}` is a deep model copy per design point) and this
420 * one does not -- `expand(e)` builds the copy for point e on demand, because a
421 * 4096-point design would otherwise hold 4096 Networks alive to solve them one
422 * at a time. The expansion is a pure function of the design, so a caller that
423 * wants the model of point e asks for it and gets exactly what the reference's
424 * `self.ensemble{e}` holds.
425 *
426 * `net` is taken by non-const reference because the expansion COPIES it once per
427 * design point and each copy is then refreshed -- `get_struct()` re-derives
428 * rates, chains and visits, which is exactly what changing a service process
429 * requires and is why the substitution is done on the model rather than on a
430 * struct.
431 */
432template <class T>
433class SolverUq {
434public:
436 : net(&n), stage(s) {
437 if (!stage)
438 throw InputError(
439 "SolverUQ: no stage solver was given. UQ solves nothing itself; it needs the "
440 "solver that runs at each design point, the C++ spelling of UQ(model, "
441 "@SolverMVA)");
442 sol.options = o;
443 init();
444 }
445
446 /** `UQ.init`: resolve the design; the ensemble itself is expanded per point. */
447 void init() {
448 sol.method = uq_resolve_method(sol.options.method);
449 sol.sites = uq_detect_priors(net->get_struct());
450 sol.design = uq_build_design(sol.sites, sol.options);
451 sol.points.clear();
452 sol.weights.clear();
453 sol.avg = mva::AvgResult<T>();
454 }
455
456 /** `UQ.pre`: nothing to seed -- the design points are independent models. */
457 void pre(int /*it*/) {}
458
459 /**
460 * `UQ.analyze`: solve design point `e` (1-BASED, as the reference indexes it).
461 *
462 * The result is APPENDED to `points`, so driving the ensemble in order gives
463 * the same `points` vector `iterate()` builds. Calling it out of order is a
464 * caller's choice and the order of `points` then follows the calls, which is
465 * why `weights` is appended beside it rather than indexed into.
466 */
467 const mva::AvgResult<T>& analyze(int /*it*/, std::size_t e) {
468 if (e < 1 || e > sol.design.size())
469 throw InputError("SolverUQ::analyze: design point " + std::to_string(e) +
470 " is outside the design of " + std::to_string(sol.design.size()) +
471 " points");
472 qn::Network<T> copy = expand(e);
473 sol.points.push_back(stage(copy.get_struct()));
474 sol.weights.push_back(sol.design[e - 1].weight);
475 return sol.points.back();
476 }
477
478 /** `UQ.post`: the prior-weighted expectation over the points solved so far. */
479 void post(int /*it*/) { uq_aggregate(sol); }
480
481 /** `UQ.finish`: nothing to release. */
482 void finish() {}
483
484 /** `UQ.converged`: one iteration is all UQ needs, the design being fixed. */
485 bool converged(int it) const { return it >= 1; }
486
487 /** `UQ.runAnalyzer`: the whole lifecycle, in the reference's order. */
489 init();
490 int it = 1;
491 pre(it);
492 for (std::size_t e = 1; e <= sol.design.size(); ++e) analyze(it, e);
493 post(it);
494 if (!converged(it))
495 throw NumericError(ErrorKind{"NonConvergence"}, "SolverUQ: the design did not converge in one iteration");
496 finish();
497 return sol;
498 }
499
500 /**
501 * `self.ensemble{e}`: the model of design point `e` (1-based), Prior gone.
502 *
503 * The reference names each copy `<model>_alt<e>`; this port keeps the copy's
504 * name because a `qn::Network` name is read back by the JSON writer and the
505 * LQN bridge, and renaming it would put a name no model file carries into
506 * whatever a caller does next with the expanded model.
507 */
508 qn::Network<T> expand(std::size_t e) const {
509 if (e < 1 || e > sol.design.size())
510 throw InputError("SolverUQ::expand: design point " + std::to_string(e) +
511 " is outside the design of " + std::to_string(sol.design.size()) +
512 " points");
513 qn::Network<T> copy = *net;
514 for (std::size_t l = 0; l < sol.sites.size(); ++l) {
515 const PriorSite<T>& s = sol.sites[l];
516 if (s.arrival)
517 copy.set_arrival(s.node, s.cls, sol.design[e - 1].dists[l]);
518 else
519 copy.set_service(s.node, s.cls, sol.design[e - 1].dists[l]);
520 }
521 return copy;
522 }
523
524 /** `UQ.hasPriorDistribution`. */
525 bool has_prior_distribution() const { return !sol.sites.empty(); }
526
527 /** `UQ.getNumAlternatives`: design points, 1 when the model carries no Prior. */
528 std::size_t get_num_alternatives() const { return sol.design.size(); }
529
530 /** `UQ.getNumberOfModels`: the same count, under the EnsembleSolver's name. */
531 std::size_t get_number_of_models() const { return sol.design.size(); }
532
533 /** `UQ.getProbabilities`: the design weights, from the DESIGN not the solve. */
534 std::vector<T> get_probabilities() const {
535 std::vector<T> w;
536 w.reserve(sol.design.size());
537 for (std::size_t e = 0; e < sol.design.size(); ++e) w.push_back(sol.design[e].weight);
538 return w;
539 }
540
541 /** `UQ.getUQNodes`: nodes per continuous Prior. */
542 std::size_t get_uq_nodes() const { return sol.options.samples; }
543
544 /** `UQ.getUQMethod`: the RESOLVED design name. */
545 const std::string& get_uq_method() const { return sol.method; }
546
547 /** `UQ.getEnsembleAvg`: the per-point results, in the order they were solved. */
548 const std::vector<mva::AvgResult<T>>& get_ensemble_avg() const { return sol.points; }
549
550 /** `UQ.getAvg`: the aggregate. Valid once `post()` has run. */
551 const mva::AvgResult<T>& get_avg() const { return sol.avg; }
552
553 /** Where the Priors were found, the reference's `priorInfo`. */
554 const std::vector<PriorSite<T>>& get_prior_info() const { return sol.sites; }
555
556 /** The design itself, one entry per point. */
557 const std::vector<UqDesignPoint<T>>& get_design() const { return sol.design; }
558
559 /** Everything above in one value, which is what the CLI and the tests read. */
560 const UqSolution<T>& get_solution() const { return sol; }
561
562private:
563 qn::Network<T>* net;
564 UqStageSolver<T> stage;
565 UqSolution<T> sol;
566};
567
568/**
569 * `UQ.runAnalyzer` as a free call: expand, solve every design point, aggregate.
570 *
571 * The one-shot spelling of `SolverUq::iterate`, kept because most callers want
572 * the solution and not the lifecycle, and because it is what `line_cli.cpp` and
573 * `uq_interval_run` reach for.
574 */
575template <class T>
577 const UqOptions& opt = UqOptions()) {
578 SolverUq<T> s(net, stage, opt);
579 return s.iterate();
580}
581
582// ---------------------------------------------------------------------------
583// Posterior summaries
584// ---------------------------------------------------------------------------
585
586/** The metric matrix a name selects, MATLAB's @c res.Avg.(metric) field. */
587template <class T>
588const Matrix<T>& uq_metric_matrix(const mva::AvgResult<T>& r, const std::string& metric) {
589 if (metric == "Q") return r.QN;
590 if (metric == "U") return r.UN;
591 if (metric == "R") return r.RN;
592 if (metric == "T") return r.TN;
593 if (metric == "A") return r.AN;
594 if (metric == "W") return r.WN;
595 throw InputError("SolverUQ: unknown metric '" + metric + "'; use Q, U, R, T, A or W");
596}
597
598/**
599 * `UQ.getSamples`: the value of one metric at every design point, with weights.
600 *
601 * `ist` and `r` are 1-based, as everywhere in the readable surface of this port.
602 */
603template <class T>
604std::vector<T> uq_samples(const UqSolution<T>& sol, const std::string& metric, std::size_t ist,
605 std::size_t r) {
606 std::vector<T> vals;
607 for (std::size_t e = 0; e < sol.points.size(); ++e) {
608 const Matrix<T>& M = uq_metric_matrix(sol.points[e], metric);
609 if (M.empty() || ist == 0 || r == 0 || ist > M.rows() || r > M.cols())
610 throw InputError("SolverUQ: metric " + metric + " is unavailable at station " +
611 std::to_string(ist) + ", class " + std::to_string(r) +
612 " for design point " + std::to_string(e + 1));
613 vals.push_back(M(ist - 1, r - 1));
614 }
615 return vals;
616}
617
618/** The weighted mean and variance of a metric over the design. */
619template <class T>
624
625/**
626 * `UQ.getMoments`: the unconditional mean of Trivedi and Bobbio Eq. (3.68) and
627 * the second moment of the same weighting.
628 *
629 * Both are EXACT for a discrete Prior and quadrature- or sample-approximate for
630 * a continuous one, which is the only sense in which a variance over 11 stratum
631 * medians is a variance.
632 */
633template <class T>
634UqMoments<T> uq_moments(const UqSolution<T>& sol, const std::string& metric, std::size_t ist,
635 std::size_t r) {
636 const std::vector<T> vals = uq_samples(sol, metric, ist, r);
637 UqMoments<T> out;
638 for (std::size_t e = 0; e < vals.size(); ++e) out.mean += T(sol.weights[e] * vals[e]);
639 for (std::size_t e = 0; e < vals.size(); ++e) {
640 const T dv = T(vals[e] - out.mean);
641 out.var += T(sol.weights[e] * dv * dv);
642 }
643 return out;
644}
645
646/** The weighted empirical law of a metric, sorted ascending; MATLAB's `EmpiricalCDF`. */
647template <class T>
649 std::vector<T> values; ///< the metric at each design point, ascending
650 std::vector<T> probabilities; ///< the weight of each value, in the same order
651 std::vector<T> cdf; ///< the running sum of `probabilities`
652};
653
654/** `UQ.getPosteriorDist`: the posterior law of a metric across the design. */
655template <class T>
656UqEmpiricalCdf<T> uq_posterior_cdf(const UqSolution<T>& sol, const std::string& metric,
657 std::size_t ist, std::size_t r) {
658 const std::vector<T> vals = uq_samples(sol, metric, ist, r);
659 std::vector<std::size_t> ord(vals.size());
660 for (std::size_t i = 0; i < ord.size(); ++i) ord[i] = i;
661 std::stable_sort(ord.begin(), ord.end(),
662 [&vals](std::size_t a, std::size_t b) { return vals[a] < vals[b]; });
664 T acc = num_traits<T>::from_int(0);
665 for (std::size_t k = 0; k < ord.size(); ++k) {
666 out.values.push_back(vals[ord[k]]);
667 out.probabilities.push_back(sol.weights[ord[k]]);
668 acc += sol.weights[ord[k]];
669 out.cdf.push_back(acc);
670 }
671 return out;
672}
673
674/**
675 * `UQ.getCredibleInterval`: the equal-tailed interval of the weighted empirical
676 * law at coverage `level`.
677 *
678 * The endpoints are DESIGN-POINT VALUES, not interpolations between them: the
679 * design is a finite set of models and the interval names two of them, which is
680 * what the reference's `find(cw >= alpha, 1)` returns. On a coarse design the
681 * interval is therefore conservative rather than smooth.
682 */
683template <class T>
684std::pair<T, T> uq_credible_interval(const UqSolution<T>& sol, const std::string& metric,
685 std::size_t ist, std::size_t r, double level = 0.95) {
686 if (!(level > 0.0) || !(level < 1.0))
687 throw InputError("SolverUQ: the coverage level must lie strictly between 0 and 1");
688 const UqEmpiricalCdf<T> ec = uq_posterior_cdf(sol, metric, ist, r);
689 if (ec.values.empty()) throw InputError("SolverUQ: the design is empty");
690 T tot = num_traits<T>::from_int(0);
691 for (const T& w : ec.probabilities) tot += w;
692 const double alpha = (1.0 - level) / 2.0;
693 T lo = ec.values.front(), hi = ec.values.back();
694 bool lo_set = false, hi_set = false;
695 for (std::size_t k = 0; k < ec.values.size(); ++k) {
696 const double cw = num_traits<T>::to_double(T(ec.cdf[k] / tot));
697 if (!lo_set && cw >= alpha) {
698 lo = ec.values[k];
699 lo_set = true;
700 }
701 if (!hi_set && cw >= 1.0 - alpha) {
702 hi = ec.values[k];
703 hi_set = true;
704 }
705 }
706 return std::make_pair(lo, hi);
707}
708
709// ---------------------------------------------------------------------------
710// Support-only (interval) uncertainty
711// ---------------------------------------------------------------------------
712
713/**
714 * `UQ.getInterval`: the RANGE of every metric over the support of the Priors.
715 *
716 * A different epistemic question from the expectation above, and the one to ask
717 * when a parameter can be BOUNDED but not distributed: the weights are dropped
718 * and only the endpoints are kept. `exact` says which of the two regimes
719 * produced it, and the distinction is not a quality label but a change of
720 * meaning -- see `uq_interval`.
721 *
722 * THE INTERVAL IS CONDITIONAL on the true parameters lying inside the Prior
723 * supports. It is not a bound on the exact solution of the network, and it must
724 * not be composed with the brackets of SolverBA, which bracket the exact
725 * solution of a model whose parameters are known.
726 */
727template <class T>
729 /** (nstations x nclasses) lower and upper endpoints of each metric. */
731 /** System throughput and total response time; the EXACT path only. */
734 bool has_totals = false;
735 /** True when the interval is the attained hull rather than a sampled range. */
736 bool exact = false;
737 /** `mvainterval` or `sampled`. */
738 std::string method;
739 /** On the sampled path, the condition that disqualified the exact one. */
740 std::string why;
741};
742
743/**
744 * `UQ.priorMeanRange`: the range of a Prior's MEAN over its alternatives.
745 *
746 * Exact for a discrete Prior, whose alternatives ARE the support. A continuous
747 * Prior is discretized first, so the range is that of the discretized support:
748 * an unbounded parameter density is never reached at its tails, which is
749 * exactly why the interval built from it is an inner approximation.
750 */
751template <class T>
752std::pair<T, T> uq_prior_mean_range(const lang::Distrib<T>& prior, std::size_t n) {
754 const lang::PriorDesign<T> g = lang::prior_discretize(prior, n, "quad", rng);
755 if (g.dists.empty()) throw NumericError(ErrorKind{"Degenerate"}, "uq_prior_mean_range: the Prior has no alternative");
756 T lo = g.dists[0].mean, up = g.dists[0].mean;
757 for (const lang::Distrib<T>& d : g.dists) {
758 if (d.mean < lo) lo = d.mean;
759 if (up < d.mean) up = d.mean;
760 }
761 return std::make_pair(lo, up);
762}
763
764/**
765 * `UQ.qualifiesForIntervalMVA`: whether the monotonicity theorems behind
766 * `pfqn_mva_interval` hold for this model.
767 *
768 * The returned string NAMES the first violated condition rather than reporting
769 * a bare false, because "this model does not qualify" leaves the modeller
770 * guessing which of six conditions to change.
771 */
772template <class T>
773std::pair<bool, std::string> uq_qualifies_for_interval_mva(const qn::NetworkStruct<T>& sn,
774 const std::vector<PriorSite<T>>& sites) {
775 for (const PriorSite<T>& s : sites)
776 if (s.arrival)
777 return std::make_pair(false, "a Prior sits on an arrival process, so the model is open");
778 if (sn.nclasses != 1)
779 return std::make_pair(false, "the theorems are proved for a single class only");
780 if (!(sn.nclosedjobs() > 0.0)) return std::make_pair(false, "the class is not closed");
781 if (sn.nodes.size() != sn.nstations)
782 return std::make_pair(false, "the model has nodes that are not stations");
783 for (std::size_t i = 0; i < sn.nstations; ++i) {
784 const bool inf = sn.stations[i].sched == lang::SchedStrategy::INF;
785 if (!inf && sn.stations[i].nservers > 1.0)
786 return std::make_pair(false, "a queueing station has more than one server");
787 if (!inf && sn.stations[i].sched != lang::SchedStrategy::PS &&
788 sn.stations[i].sched != lang::SchedStrategy::FCFS)
789 return std::make_pair(false, "a station is neither delay, PS nor FCFS");
790 }
791 for (std::size_t a = 0; a < sites.size(); ++a)
792 for (std::size_t b = a + 1; b < sites.size(); ++b)
793 if (sites[a].station == sites[b].station)
794 return std::make_pair(false, "two Priors sit on the same station");
795 return std::make_pair(true, std::string());
796}
797
798/**
799 * `UQ.intervalByMVA`: the exact hull through `pfqn_mva_interval`.
800 *
801 * The demand box is the nominal demand vector with the prior-carrying stations
802 * widened to the range of mean service times over the Prior support. NO
803 * ENSEMBLE RUN HAPPENS: 2*(m+2) MVA calls replace the whole tensor design, and
804 * the answer is the attained range rather than the range of what was sampled.
805 */
806template <class T>
808 const std::vector<PriorSite<T>>& sites, std::size_t nodes) {
809 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
810 const std::size_t M = sn.nstations;
811 if (sn.visits.empty()) throw NumericError(ErrorKind{"Degenerate"}, "uq_interval_by_mva: the model carries no visits");
812 std::vector<T> V(M, zero), STlo(M, zero), STup(M, zero);
813 std::vector<bool> inf(M, false);
814 for (std::size_t i = 0; i < M; ++i) {
815 V[i] = sn.visits[0](i, 0);
816 inf[i] = sn.stations[i].sched == lang::SchedStrategy::INF;
817 // A disabled pair has a NaN rate; MATLAB's `ST(isnan(ST)) = 0` makes it
818 // a station the class does not visit rather than an error.
819 const double rate = num_traits<T>::to_double(sn.rates(i, 0));
820 const T st = (std::isnan(rate) || rate == 0.0) ? zero : T(one / sn.rates(i, 0));
821 STlo[i] = st;
822 STup[i] = st;
823 }
824 for (const PriorSite<T>& s : sites) {
825 const std::pair<T, T> mr = uq_prior_mean_range(s.prior, nodes);
826 STlo[s.station - 1] = mr.first;
827 STup[s.station - 1] = mr.second;
828 }
829
830 std::vector<std::size_t> qidx;
831 T zlo = zero, zup = zero;
832 for (std::size_t i = 0; i < M; ++i) {
833 if (inf[i]) {
834 zlo += T(V[i] * STlo[i]);
835 zup += T(V[i] * STup[i]);
836 } else {
837 qidx.push_back(i);
838 }
839 }
840 if (qidx.empty())
841 throw UnsupportedError(ErrorKind{"Topology"},
842 "uq_interval_by_mva: the model has no queueing station, so there is no MVA recursion "
843 "to bound; a pure delay model's metrics are the demand intervals themselves");
844
845 Matrix<T> L(qidx.size(), 2, zero);
846 for (std::size_t k = 0; k < qidx.size(); ++k) {
847 L(k, 0) = T(V[qidx[k]] * STlo[qidx[k]]);
848 L(k, 1) = T(V[qidx[k]] * STup[qidx[k]]);
849 }
850 const int n = static_cast<int>(std::lround(sn.nclosedjobs()));
851 const pfqn::MvaIntervalResult<T> iv = pfqn::pfqn_mva_interval(L, n, n, zlo, zup);
852
853 UqInterval<T> out;
854 out.Qlo = Matrix<T>(M, 1, zero);
855 out.Qup = Matrix<T>(M, 1, zero);
856 out.Ulo = Matrix<T>(M, 1, zero);
857 out.Uup = Matrix<T>(M, 1, zero);
858 out.Rlo = Matrix<T>(M, 1, zero);
859 out.Rup = Matrix<T>(M, 1, zero);
860 out.Wlo = Matrix<T>(M, 1, zero);
861 out.Wup = Matrix<T>(M, 1, zero);
862 out.Tlo = Matrix<T>(M, 1, zero);
863 out.Tup = Matrix<T>(M, 1, zero);
864 for (std::size_t k = 0; k < qidx.size(); ++k) {
865 const std::size_t i = qidx[k];
866 out.Qlo(i, 0) = iv.Q(k, 0);
867 out.Qup(i, 0) = iv.Q(k, 1);
868 out.Ulo(i, 0) = iv.U(k, 0);
869 out.Uup(i, 0) = iv.U(k, 1);
870 out.Wlo(i, 0) = iv.R(k, 0);
871 out.Wup(i, 0) = iv.R(k, 1);
872 // The RESIDENCE time is per visit; the response time divides it out.
873 out.Rlo(i, 0) = V[i] > zero ? T(iv.R(k, 0) / V[i]) : zero;
874 out.Rup(i, 0) = V[i] > zero ? T(iv.R(k, 1) / V[i]) : zero;
875 }
876 for (std::size_t i = 0; i < M; ++i) {
877 if (!inf[i]) continue;
878 // A delay station never queues, so its residence time IS its own demand
879 // interval and its population is the throughput times that demand,
880 // enclosed as a product of two intervals.
881 out.Wlo(i, 0) = T(V[i] * STlo[i]);
882 out.Wup(i, 0) = T(V[i] * STup[i]);
883 out.Rlo(i, 0) = STlo[i];
884 out.Rup(i, 0) = STup[i];
885 out.Qlo(i, 0) = T(iv.Xlo * V[i] * STlo[i]);
886 out.Qup(i, 0) = T(iv.Xup * V[i] * STup[i]);
887 out.Ulo(i, 0) = out.Qlo(i, 0);
888 out.Uup(i, 0) = out.Qup(i, 0);
889 }
890 for (std::size_t i = 0; i < M; ++i) {
891 out.Tlo(i, 0) = T(V[i] * iv.Xlo);
892 out.Tup(i, 0) = T(V[i] * iv.Xup);
893 }
894 out.Xlo = iv.Xlo;
895 out.Xup = iv.Xup;
896 out.Rtot_lo = iv.Rtot_lo;
897 out.Rtot_up = iv.Rtot_up;
898 out.has_totals = true;
899 out.exact = true;
900 out.method = "mvainterval";
901 return out;
902}
903
904/**
905 * `UQ.intervalBySampling`: the range of each metric across the design points
906 * that were actually solved.
907 *
908 * EXACT FOR A DISCRETE PRIOR, whose design visits the whole support, and an
909 * INNER approximation for a continuous one, since a quadrature node is a
910 * stratum median and never an endpoint. It is therefore not an enclosure, and
911 * `exact` is false to say so.
912 */
913template <class T>
915 UqInterval<T> out;
916 out.method = "sampled";
917 out.exact = false;
918 auto range = [&](const Matrix<T>& (*pick)(const mva::AvgResult<T>&), Matrix<T>& lo,
919 Matrix<T>& up) {
920 for (const mva::AvgResult<T>& r : sol.points) {
921 const Matrix<T>& v = pick(r);
922 if (v.empty()) continue;
923 if (lo.empty()) {
924 lo = v;
925 up = v;
926 continue;
927 }
928 for (std::size_t i = 0; i < v.rows(); ++i)
929 for (std::size_t j = 0; j < v.cols(); ++j) {
930 if (v(i, j) < lo(i, j)) lo(i, j) = v(i, j);
931 if (up(i, j) < v(i, j)) up(i, j) = v(i, j);
932 }
933 }
934 };
935 range([](const mva::AvgResult<T>& r) -> const Matrix<T>& { return r.QN; }, out.Qlo, out.Qup);
936 range([](const mva::AvgResult<T>& r) -> const Matrix<T>& { return r.UN; }, out.Ulo, out.Uup);
937 range([](const mva::AvgResult<T>& r) -> const Matrix<T>& { return r.RN; }, out.Rlo, out.Rup);
938 range([](const mva::AvgResult<T>& r) -> const Matrix<T>& { return r.TN; }, out.Tlo, out.Tup);
939 range([](const mva::AvgResult<T>& r) -> const Matrix<T>& { return r.WN; }, out.Wlo, out.Wup);
940 return out;
941}
942
943/**
944 * `UQ.getInterval`: the exact hull where the monotonicity theorems apply, the
945 * sampled range otherwise.
946 *
947 * THE TWO PATHS DO NOT MEAN THE SAME THING and the caller must read `exact`
948 * before quoting the numbers. The MVA path returns the ATTAINED range over the
949 * whole (continuous) demand box; the sampled path returns the range over the
950 * points that happened to be solved, which for a continuous Prior lies strictly
951 * inside the true range. `why` carries the condition that forced the fallback,
952 * which is the reference's `line_warning` text made into a returned value --
953 * this port has no warning channel, and a range that is not an enclosure must
954 * not be silently indistinguishable from one that is.
955 */
956template <class T>
958 const std::pair<bool, std::string> q = uq_qualifies_for_interval_mva(sn, sol.sites);
959 if (q.first) return uq_interval_by_mva(sn, sol.sites, sol.options.samples);
961 out.why = q.second;
962 return out;
963}
964
965/**
966 * `getInterval` from the model, running the ensemble ONLY when it is needed.
967 *
968 * The exact path costs 2*(m+2) MVA calls and reads no design point, so a caller
969 * who wants the range and not the expectation should not pay for the tensor
970 * design: `UQ.getInterval` calls `intervalByMVA` without touching `self.results`
971 * and only the sampling fallback calls `iterate`. The stage solver is therefore
972 * never invoked on a qualifying model, which also means a model whose stage
973 * solver would refuse it still has a computable interval.
974 */
975template <class T>
977 const UqOptions& opt = UqOptions()) {
978 const std::vector<PriorSite<T>> sites = uq_detect_priors(net.get_struct());
979 const std::pair<bool, std::string> q = uq_qualifies_for_interval_mva(net.get_struct(), sites);
980 if (q.first) return uq_interval_by_mva(net.get_struct(), sites, opt.samples);
982 out.why = q.second;
983 return out;
984}
985
986} // namespace uq
987} // namespace line
988
989#endif // LINE_SOLVERS_UQ_SOLVER_UQ_H
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
bool empty() const
Definition matrix.h:92
NumericError(const std::string &what)
Definition error.h:112
UnsupportedError(const std::string &what)
Definition error.h:119
The uniform stream the Monte Carlo design draws from.
Definition prior.h:81
A subset of the registry: MATLAB's SolverFeatureSet, whose list is a flag per field.
FeatureSet & set(Feature f)
A network plus its refreshed NetworkStruct.
A queueing network under construction.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
void set_service(std::size_t node, std::size_t cls, const Distrib< T > &d)
station.setService(class, dist).
void set_arrival(std::size_t node, std::size_t cls, const Distrib< T > &d)
source.setArrival(class, dist): the same table, at the Source.
The ensemble surface of UQ: @UQ's EnsembleSolver implementation.
Definition solver_uq.h:433
std::size_t get_num_alternatives() const
UQ.getNumAlternatives: design points, 1 when the model carries no Prior.
Definition solver_uq.h:528
void init()
UQ.init: resolve the design; the ensemble itself is expanded per point.
Definition solver_uq.h:447
const std::vector< PriorSite< T > > & get_prior_info() const
Where the Priors were found, the reference's priorInfo.
Definition solver_uq.h:554
const mva::AvgResult< T > & analyze(int, std::size_t e)
UQ.analyze: solve design point e (1-BASED, as the reference indexes it).
Definition solver_uq.h:467
void pre(int)
UQ.pre: nothing to seed – the design points are independent models.
Definition solver_uq.h:457
std::size_t get_uq_nodes() const
UQ.getUQNodes: nodes per continuous Prior.
Definition solver_uq.h:542
bool has_prior_distribution() const
UQ.hasPriorDistribution.
Definition solver_uq.h:525
SolverUq(qn::Network< T > &n, const UqStageSolver< T > &s, const UqOptions &o=UqOptions())
Definition solver_uq.h:435
std::vector< T > get_probabilities() const
UQ.getProbabilities: the design weights, from the DESIGN not the solve.
Definition solver_uq.h:534
const mva::AvgResult< T > & get_avg() const
UQ.getAvg: the aggregate.
Definition solver_uq.h:551
const std::vector< UqDesignPoint< T > > & get_design() const
The design itself, one entry per point.
Definition solver_uq.h:557
bool converged(int it) const
UQ.converged: one iteration is all UQ needs, the design being fixed.
Definition solver_uq.h:485
const UqSolution< T > & get_solution() const
Everything above in one value, which is what the CLI and the tests read.
Definition solver_uq.h:560
void post(int)
UQ.post: the prior-weighted expectation over the points solved so far.
Definition solver_uq.h:479
const UqSolution< T > & iterate()
UQ.runAnalyzer: the whole lifecycle, in the reference's order.
Definition solver_uq.h:488
const std::string & get_uq_method() const
UQ.getUQMethod: the RESOLVED design name.
Definition solver_uq.h:545
qn::Network< T > expand(std::size_t e) const
self.ensemble{e}: the model of design point e (1-based), Prior gone.
Definition solver_uq.h:508
std::size_t get_number_of_models() const
UQ.getNumberOfModels: the same count, under the EnsembleSolver's name.
Definition solver_uq.h:531
void finish()
UQ.finish: nothing to release.
Definition solver_uq.h:482
const std::vector< mva::AvgResult< T > > & get_ensemble_avg() const
UQ.getEnsembleAvg: the per-point results, in the order they were solved.
Definition solver_uq.h:548
The exception types the port throws.
The language-feature gate: what a MODEL uses against what a SOLVER declares.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
The method names of each solver, in listing order.
constexpr std::size_t kPriorDefaultNodes
The default number of nodes per continuous Prior, MATLAB options.samples.
Definition prior.h:72
PriorDesign< T > prior_discretize(const Distrib< T > &d, std::size_t n, const std::string &method, PriorRng &rng)
Reduce a Prior to n weighted alternatives, MATLAB Prior.discretize.
Definition prior.h:197
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:331
MvaIntervalResult< T > pfqn_mva_interval(const Matrix< T > &L, int nlo, int nup, const T &zlo, const T &zup)
Exact interval-valued MVA for single-class closed product-form networks.
UqSolution< T > solver_uq_run_analyzer(qn::Network< T > &net, const UqStageSolver< T > &stage, const UqOptions &opt=UqOptions())
UQ.runAnalyzer as a free call: expand, solve every design point, aggregate.
Definition solver_uq.h:576
UqInterval< T > uq_interval_by_mva(const qn::NetworkStruct< T > &sn, const std::vector< PriorSite< T > > &sites, std::size_t nodes)
UQ.intervalByMVA: the exact hull through pfqn_mva_interval.
Definition solver_uq.h:807
UqInterval< T > uq_interval_by_sampling(const UqSolution< T > &sol)
UQ.intervalBySampling: the range of each metric across the design points that were actually solved.
Definition solver_uq.h:914
UqEmpiricalCdf< T > uq_posterior_cdf(const UqSolution< T > &sol, const std::string &metric, std::size_t ist, std::size_t r)
UQ.getPosteriorDist: the posterior law of a metric across the design.
Definition solver_uq.h:656
UqInterval< T > uq_interval_run(qn::Network< T > &net, const UqStageSolver< T > &stage, const UqOptions &opt=UqOptions())
getInterval from the model, running the ensemble ONLY when it is needed.
Definition solver_uq.h:976
std::vector< UqDesignPoint< T > > uq_build_design(const std::vector< PriorSite< T > > &sites, const UqOptions &opt)
UQ.buildDesign: reduce the detected Priors to weighted design points.
Definition solver_uq.h:260
std::vector< PriorSite< T > > uq_detect_priors(const qn::NetworkStruct< T > &sn)
UQ.detectPriors: find every Prior, in node order and then class order.
Definition solver_uq.h:198
void uq_aggregate(UqSolution< T > &sol)
UQ.aggregateResults: the prior-weighted expectation of the solved points.
Definition solver_uq.h:369
std::function< mva::AvgResult< T >(const qn::NetworkStruct< T > &)> UqStageSolver
What solves one design point: the C++ spelling of @(m) SolverXXX(m).
Definition solver_uq.h:334
std::vector< T > uq_samples(const UqSolution< T > &sol, const std::string &metric, std::size_t ist, std::size_t r)
UQ.getSamples: the value of one metric at every design point, with weights.
Definition solver_uq.h:604
UqMoments< T > uq_moments(const UqSolution< T > &sol, const std::string &metric, std::size_t ist, std::size_t r)
UQ.getMoments: the unconditional mean of Trivedi and Bobbio Eq.
Definition solver_uq.h:634
std::pair< bool, std::string > uq_qualifies_for_interval_mva(const qn::NetworkStruct< T > &sn, const std::vector< PriorSite< T > > &sites)
UQ.qualifiesForIntervalMVA: whether the monotonicity theorems behind pfqn_mva_interval hold for this ...
Definition solver_uq.h:773
UqInterval< T > uq_interval(const UqSolution< T > &sol, const qn::NetworkStruct< T > &sn)
UQ.getInterval: the exact hull where the monotonicity theorems apply, the sampled range otherwise.
Definition solver_uq.h:957
std::pair< T, T > uq_credible_interval(const UqSolution< T > &sol, const std::string &metric, std::size_t ist, std::size_t r, double level=0.95)
UQ.getCredibleInterval: the equal-tailed interval of the weighted empirical law at coverage level.
Definition solver_uq.h:684
std::vector< std::string > uq_list_valid_methods()
UQ.listValidMethods.
Definition solver_uq.h:158
const Matrix< T > & uq_metric_matrix(const mva::AvgResult< T > &r, const std::string &metric)
The metric matrix a name selects, MATLAB's res.Avg.
Definition solver_uq.h:588
qn::FeatureSet uq_feature_set()
UQ.getFeatureSet: the one construct UQ adds, and nothing else.
Definition solver_uq.h:181
std::pair< T, T > uq_prior_mean_range(const lang::Distrib< T > &prior, std::size_t n)
UQ.priorMeanRange: the range of a Prior's MEAN over its alternatives.
Definition solver_uq.h:752
std::string uq_resolve_method(const std::string &m)
UQ.getUQMethod: resolve the discretization method.
Definition solver_uq.h:150
constexpr std::size_t kMaxDesignPoints
The cap on the tensor-product design, MATLAB UQ.MaxDesignPoints.
Definition solver_uq.h:83
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The Network constructor API: Queue, Delay, Source, Sink, Router, ClassSwitch, Cache,...
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Exact interval-valued MVA for single-class closed product-form networks.
Prior: parameter uncertainty as a weighted set of alternative models.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
bool is_prior() const
Definition lang_types.h:888
A Prior reduced to alternatives and weights; the output of prior_discretize.
Definition prior.h:96
std::vector< Distrib< T > > dists
Definition prior.h:97
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 actualmethod
the algorithm that ran
Matrix< T > QN
queue length
std::vector< T > CN
system response time per class
std::vector< T > XN
system throughput per class
Matrix< T > AN
arrival rate
Every output of pfqn_mva_interval, each a [lower, upper] pair.
Matrix< T > R
(M x 2) residence time per station
Matrix< T > Q
(M x 2) mean queue length per station
Matrix< T > U
(M x 2) utilization enclosure per station
Where a Prior sits in the model, MATLAB's priorInfo entry.
Definition solver_uq.h:107
lang::Distrib< T > prior
The Prior itself, copied out of the service table.
Definition solver_uq.h:114
bool arrival
True at a Source, where the Prior is an ARRIVAL process, not a service one.
Definition solver_uq.h:112
std::size_t station
1-based station index
Definition solver_uq.h:109
std::size_t node
1-based node index
Definition solver_uq.h:108
std::size_t cls
1-based class index
Definition solver_uq.h:110
One design point: a concrete distribution for every Prior, and its weight.
Definition solver_uq.h:119
std::vector< lang::Distrib< T > > dists
one per site, in site order
Definition solver_uq.h:121
The weighted empirical law of a metric, sorted ascending; MATLAB's EmpiricalCDF.
Definition solver_uq.h:648
std::vector< T > probabilities
the weight of each value, in the same order
Definition solver_uq.h:650
std::vector< T > values
the metric at each design point, ascending
Definition solver_uq.h:649
std::vector< T > cdf
the running sum of probabilities
Definition solver_uq.h:651
UQ.getInterval: the RANGE of every metric over the support of the Priors.
Definition solver_uq.h:728
std::string method
mvainterval or sampled.
Definition solver_uq.h:738
bool exact
True when the interval is the attained hull rather than a sampled range.
Definition solver_uq.h:736
Matrix< T > Qlo
(nstations x nclasses) lower and upper endpoints of each metric.
Definition solver_uq.h:730
std::string why
On the sampled path, the condition that disqualified the exact one.
Definition solver_uq.h:740
T Xlo
System throughput and total response time; the EXACT path only.
Definition solver_uq.h:732
The weighted mean and variance of a metric over the design.
Definition solver_uq.h:620
UQ.defaultOptions plus the stream the Monte Carlo design draws from.
Definition solver_uq.h:86
std::string method
default | dd | quad | mc, MATLAB UQ.listValidMethods.
Definition solver_uq.h:93
std::size_t samples
Nodes per continuous Prior, or design points under mc; the reference's options.samples,...
Definition solver_uq.h:100
unsigned long seed
The Monte Carlo stream; unread by a quadrature design, which draws nothing.
Definition solver_uq.h:102
What solver_uq_run_analyzer returns.
Definition solver_uq.h:126
std::vector< T > weights
The design weights, summing to 1.
Definition solver_uq.h:132
std::string method
The RESOLVED discretization method: quad or mc.
Definition solver_uq.h:138
std::vector< mva::AvgResult< T > > points
The result at each design point, in design order.
Definition solver_uq.h:130
UqOptions options
The options the design was built with; uq_interval reads samples.
Definition solver_uq.h:140
std::vector< PriorSite< T > > sites
Where the Priors were found.
Definition solver_uq.h:136
std::vector< UqDesignPoint< T > > design
The alternatives each point substituted, one per site.
Definition solver_uq.h:134
mva::AvgResult< T > avg
The prior-weighted expectation of every metric, (nstations x nclasses).
Definition solver_uq.h:128