5#ifndef LINE_SOLVERS_UQ_SOLVER_UQ_H
6#define LINE_SOLVERS_UQ_SOLVER_UQ_H
121 std::vector<lang::Distrib<T>>
dists;
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");
160 return ::line::method_registry::methods(
"UQ");
183 f.
set(qn::Feature::Prior);
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;
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) {
209 if (!servicer && !source)
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 "
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);
242 for (std::size_t l = 0; l < counts.size(); ++l) {
243 idx[l] = rem % counts[l];
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;
270 const std::size_t L = sites.size();
273 if (method ==
"mc") {
278 for (std::size_t i = 0; i < n; ++i) {
281 for (std::size_t l = 0; l < L; ++l) {
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) {
296 counts[l] = marg[l].dists.size();
297 if (counts[l] == 0)
throw NumericError(
ErrorKind{
"Degenerate"},
"SolverUQ: a Prior discretized to no alternative");
308 "SolverUQ: the tensor-product design has " + std::to_string(total) +
310 "; use method 'mc', whose cost does not grow with the number of Priors, or "
311 "lower options.samples");
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]]);
341 if (A.
empty())
return;
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));
350void accumulate(std::vector<T>& v,
const std::vector<T>& a,
const T& w) {
351 if (a.empty())
return;
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]);
373 for (std::size_t e = 0; e < sol.
points.size(); ++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);
385 if (sol.
points.empty())
return;
391 sol.
avg.actualmethod = sol.
points[0].actualmethod;
394 sol.
avg.actualmethod =
"mixed";
397 for (std::size_t e = 0; e < sol.
points.size(); ++e)
398 if (!sol.
points[e].warning.empty()) {
400 "design point " + std::to_string(e + 1) +
": " + sol.
points[e].warning;
436 : net(&n), stage(s) {
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, "
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()) +
473 sol.points.push_back(stage(copy.
get_struct()));
474 sol.weights.push_back(sol.design[e - 1].weight);
475 return sol.points.back();
492 for (std::size_t e = 1; e <= sol.design.size(); ++e)
analyze(it, e);
495 throw NumericError(
ErrorKind{
"NonConvergence"},
"SolverUQ: the design did not converge in one iteration");
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()) +
514 for (std::size_t l = 0; l < sol.sites.size(); ++l) {
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);
557 const std::vector<UqDesignPoint<T>>&
get_design()
const {
return sol.design; }
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");
607 for (std::size_t e = 0; e < sol.
points.size(); ++e) {
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));
636 const std::vector<T> vals =
uq_samples(sol, metric, ist, r);
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);
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]; });
665 for (std::size_t k = 0; k < ord.size(); ++k) {
666 out.
values.push_back(vals[ord[k]]);
669 out.
cdf.push_back(acc);
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");
692 const double alpha = (1.0 - level) / 2.0;
694 bool lo_set =
false, hi_set =
false;
695 for (std::size_t k = 0; k < ec.
values.size(); ++k) {
697 if (!lo_set && cw >= alpha) {
701 if (!hi_set && cw >= 1.0 - alpha) {
706 return std::make_pair(lo, hi);
761 return std::make_pair(lo, up);
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) {
785 if (!inf &&
sn.stations[i].nservers > 1.0)
786 return std::make_pair(
false,
"a queueing station has more than one server");
789 return std::make_pair(
false,
"a station is neither delay, PS nor FCFS");
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());
808 const std::vector<
PriorSite<T>>& sites, std::size_t nodes) {
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);
820 const T st = (std::isnan(rate) || rate == 0.0) ? zero : T(one /
sn.rates(i, 0));
826 STlo[s.station - 1] = mr.first;
827 STup[s.station - 1] = mr.second;
830 std::vector<std::size_t> qidx;
831 T zlo = zero, zup = zero;
832 for (std::size_t i = 0; i < M; ++i) {
834 zlo += T(V[i] * STlo[i]);
835 zup += T(V[i] * STup[i]);
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");
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]]);
850 const int n =
static_cast<int>(std::lround(
sn.nclosedjobs()));
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);
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;
876 for (std::size_t i = 0; i < M; ++i) {
877 if (!inf[i])
continue;
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);
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);
900 out.
method =
"mvainterval";
922 if (v.
empty())
continue;
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);
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
The uniform stream the Monte Carlo design draws from.
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.
std::size_t get_num_alternatives() const
UQ.getNumAlternatives: design points, 1 when the model carries no Prior.
void init()
UQ.init: resolve the design; the ensemble itself is expanded per point.
const std::vector< PriorSite< T > > & get_prior_info() const
Where the Priors were found, the reference's priorInfo.
const mva::AvgResult< T > & analyze(int, std::size_t e)
UQ.analyze: solve design point e (1-BASED, as the reference indexes it).
void pre(int)
UQ.pre: nothing to seed – the design points are independent models.
std::size_t get_uq_nodes() const
UQ.getUQNodes: nodes per continuous Prior.
bool has_prior_distribution() const
UQ.hasPriorDistribution.
SolverUq(qn::Network< T > &n, const UqStageSolver< T > &s, const UqOptions &o=UqOptions())
std::vector< T > get_probabilities() const
UQ.getProbabilities: the design weights, from the DESIGN not the solve.
const mva::AvgResult< T > & get_avg() const
UQ.getAvg: the aggregate.
const std::vector< UqDesignPoint< T > > & get_design() const
The design itself, one entry per point.
bool converged(int it) const
UQ.converged: one iteration is all UQ needs, the design being fixed.
const UqSolution< T > & get_solution() const
Everything above in one value, which is what the CLI and the tests read.
void post(int)
UQ.post: the prior-weighted expectation over the points solved so far.
const UqSolution< T > & iterate()
UQ.runAnalyzer: the whole lifecycle, in the reference's order.
const std::string & get_uq_method() const
UQ.getUQMethod: the RESOLVED design name.
qn::Network< T > expand(std::size_t e) const
self.ensemble{e}: the model of design point e (1-based), Prior gone.
std::size_t get_number_of_models() const
UQ.getNumberOfModels: the same count, under the EnsembleSolver's name.
void finish()
UQ.finish: nothing to release.
const std::vector< mva::AvgResult< T > > & get_ensemble_avg() const
UQ.getEnsembleAvg: the per-point results, in the order they were solved.
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.
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.
NodeType
Node kinds, with the values of MATLAB NodeType.
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.
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.
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.
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.
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.
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.
std::vector< PriorSite< T > > uq_detect_priors(const qn::NetworkStruct< T > &sn)
UQ.detectPriors: find every Prior, in node order and then class order.
void uq_aggregate(UqSolution< T > &sol)
UQ.aggregateResults: the prior-weighted expectation of the solved points.
std::function< mva::AvgResult< T >(const qn::NetworkStruct< T > &)> UqStageSolver
What solves one design point: the C++ spelling of @(m) SolverXXX(m).
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.
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.
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 ...
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.
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.
std::vector< std::string > uq_list_valid_methods()
UQ.listValidMethods.
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.
qn::FeatureSet uq_feature_set()
UQ.getFeatureSet: the one construct UQ adds, and nothing else.
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.
std::string uq_resolve_method(const std::string &m)
UQ.getUQMethod: resolve the discretization method.
constexpr std::size_t kMaxDesignPoints
The cap on the tensor-product design, MATLAB UQ.MaxDesignPoints.
Conservation laws of a layered queueing network, enumerated from its structure.
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....
A Prior reduced to alternatives and weights; the output of prior_discretize.
std::vector< Distrib< T > > dists
The metrics getAvg returns, after filtering.
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
T Rtot_up
total response time
Where a Prior sits in the model, MATLAB's priorInfo entry.
lang::Distrib< T > prior
The Prior itself, copied out of the service table.
bool arrival
True at a Source, where the Prior is an ARRIVAL process, not a service one.
std::size_t station
1-based station index
std::size_t node
1-based node index
std::size_t cls
1-based class index
One design point: a concrete distribution for every Prior, and its weight.
std::vector< lang::Distrib< T > > dists
one per site, in site order
The weighted empirical law of a metric, sorted ascending; MATLAB's EmpiricalCDF.
std::vector< T > probabilities
the weight of each value, in the same order
std::vector< T > values
the metric at each design point, ascending
std::vector< T > cdf
the running sum of probabilities
UQ.getInterval: the RANGE of every metric over the support of the Priors.
std::string method
mvainterval or sampled.
bool exact
True when the interval is the attained hull rather than a sampled range.
Matrix< T > Qlo
(nstations x nclasses) lower and upper endpoints of each metric.
std::string why
On the sampled path, the condition that disqualified the exact one.
T Xlo
System throughput and total response time; the EXACT path only.
The weighted mean and variance of a metric over the design.
UQ.defaultOptions plus the stream the Monte Carlo design draws from.
std::string method
default | dd | quad | mc, MATLAB UQ.listValidMethods.
std::size_t samples
Nodes per continuous Prior, or design points under mc; the reference's options.samples,...
unsigned long seed
The Monte Carlo stream; unread by a quadrature design, which draws nothing.
What solver_uq_run_analyzer returns.
std::vector< T > weights
The design weights, summing to 1.
std::string method
The RESOLVED discretization method: quad or mc.
std::vector< mva::AvgResult< T > > points
The result at each design point, in design order.
UqOptions options
The options the design was built with; uq_interval reads samples.
std::vector< PriorSite< T > > sites
Where the Priors were found.
std::vector< UqDesignPoint< T > > design
The alternatives each point substituted, one per site.
mva::AvgResult< T > avg
The prior-weighted expectation of every metric, (nstations x nclasses).