5#ifndef LINE_SOLVERS_UQ_SOLVER_UQ_H
6#define LINE_SOLVERS_UQ_SOLVER_UQ_H
120 std::vector<lang::Distrib<T>>
dists;
150 if (m.empty() || m ==
"default" || m ==
"discrete" || m ==
"quadrature")
return "quadrature";
151 if (m ==
"montecarlo")
return "montecarlo";
152 throw InputError(
"SolverUQ: unknown method '" + m +
153 "'; the valid names are default, discrete, quadrature and montecarlo");
158 return std::vector<std::string>{
"default",
"discrete",
"quadrature",
"montecarlo"};
181 f.
set(qn::Feature::Prior);
197 std::vector<PriorSite<T>> sites;
198 for (std::size_t nd = 1; nd <=
sn.nodes.size(); ++nd) {
199 const std::size_t ist =
sn.nodes[nd - 1].station;
200 if (ist == 0 || ist >
sn.service.size())
continue;
202 const bool servicer = (ty == qn::NodeType::Queue || ty == qn::NodeType::Delay);
203 const bool source = (ty == qn::NodeType::Source);
204 for (std::size_t r = 1; r <=
sn.service[ist - 1].size(); ++r) {
207 if (!servicer && !source)
209 "SolverUQ: node '" +
sn.nodes[nd - 1].name +
210 "' carries a Prior, but a Prior is expanded only where the reference expands "
211 "one: the service process of a Queue or a Delay, or the arrival process of a "
236inline std::vector<std::size_t> unrank_index(std::size_t i,
237 const std::vector<std::size_t>& counts) {
238 std::vector<std::size_t> idx(counts.size(), 0);
240 for (std::size_t l = 0; l < counts.size(); ++l) {
241 idx[l] = rem % counts[l];
261 const std::size_t n =
opt.samples;
262 if (n < 1)
throw InputError(
"SolverUQ: options.samples must be at least 1");
263 std::vector<UqDesignPoint<T>> design;
268 const std::size_t L = sites.size();
271 if (method ==
"montecarlo") {
276 for (std::size_t i = 0; i < n; ++i) {
279 for (std::size_t l = 0; l < L; ++l) {
289 std::vector<lang::PriorDesign<T>> marg(L);
290 std::vector<std::size_t> counts(L, 0);
291 std::size_t total = 1;
292 for (std::size_t l = 0; l < L; ++l) {
294 counts[l] = marg[l].dists.size();
295 if (counts[l] == 0)
throw NumericError(
"SolverUQ: a Prior discretized to no alternative");
306 "SolverUQ: the tensor-product design has " + std::to_string(total) +
308 "; use method 'montecarlo', whose cost does not grow with the number of Priors, or "
309 "lower options.samples");
311 for (std::size_t i = 0; i < total; ++i) {
312 const std::vector<std::size_t> idx = detail::unrank_index(i, counts);
315 for (std::size_t l = 0; l < L; ++l) {
316 p.
dists.push_back(marg[l].dists[idx[l]]);
339 if (A.
empty())
return;
342 throw NumericError(
"SolverUQ: two design points reported metrics of different shape");
343 for (std::size_t i = 0; i < A.
rows(); ++i)
344 for (std::size_t j = 0; j < A.
cols(); ++j) M(i, j) = T(M(i, j) + w * A(i, j));
348void accumulate(std::vector<T>& v,
const std::vector<T>& a,
const T& w) {
349 if (a.empty())
return;
351 if (v.size() != a.size())
352 throw NumericError(
"SolverUQ: two design points reported vectors of different length");
353 for (std::size_t i = 0; i < a.size(); ++i) v[i] = T(v[i] + w * a[i]);
371 for (std::size_t e = 0; e < sol.
points.size(); ++e) {
374 detail::accumulate(sol.
avg.QN, r.
QN, w);
375 detail::accumulate(sol.
avg.UN, r.
UN, w);
376 detail::accumulate(sol.
avg.RN, r.
RN, w);
377 detail::accumulate(sol.
avg.TN, r.
TN, w);
378 detail::accumulate(sol.
avg.AN, r.
AN, w);
379 detail::accumulate(sol.
avg.WN, r.
WN, w);
380 detail::accumulate(sol.
avg.CN, r.
CN, w);
381 detail::accumulate(sol.
avg.XN, r.
XN, w);
383 if (sol.
points.empty())
return;
389 sol.
avg.actualmethod = sol.
points[0].actualmethod;
392 sol.
avg.actualmethod =
"mixed";
395 for (std::size_t e = 0; e < sol.
points.size(); ++e)
396 if (!sol.
points[e].warning.empty()) {
398 "design point " + std::to_string(e + 1) +
": " + sol.
points[e].warning;
434 : net(&n), stage(s) {
437 "SolverUQ: no stage solver was given. UQ solves nothing itself; it needs the "
438 "solver that runs at each design point, the C++ spelling of UQ(model, "
466 if (e < 1 || e > sol.design.size())
467 throw InputError(
"SolverUQ::analyze: design point " + std::to_string(e) +
468 " is outside the design of " + std::to_string(sol.design.size()) +
471 sol.points.push_back(stage(copy.
get_struct()));
472 sol.weights.push_back(sol.design[e - 1].weight);
473 return sol.points.back();
490 for (std::size_t e = 1; e <= sol.design.size(); ++e)
analyze(it, e);
493 throw NumericError(
"SolverUQ: the design did not converge in one iteration");
507 if (e < 1 || e > sol.design.size())
508 throw InputError(
"SolverUQ::expand: design point " + std::to_string(e) +
509 " is outside the design of " + std::to_string(sol.design.size()) +
512 for (std::size_t l = 0; l < sol.sites.size(); ++l) {
534 w.reserve(sol.design.size());
535 for (std::size_t e = 0; e < sol.design.size(); ++e) w.push_back(sol.design[e].weight);
555 const std::vector<UqDesignPoint<T>>&
get_design()
const {
return sol.design; }
587 if (metric ==
"Q")
return r.
QN;
588 if (metric ==
"U")
return r.
UN;
589 if (metric ==
"R")
return r.
RN;
590 if (metric ==
"T")
return r.
TN;
591 if (metric ==
"A")
return r.
AN;
592 if (metric ==
"W")
return r.
WN;
593 throw InputError(
"SolverUQ: unknown metric '" + metric +
"'; use Q, U, R, T, A or W");
605 for (std::size_t e = 0; e < sol.
points.size(); ++e) {
607 if (M.
empty() || ist == 0 || r == 0 || ist > M.
rows() || r > M.
cols())
608 throw InputError(
"SolverUQ: metric " + metric +
" is unavailable at station " +
609 std::to_string(ist) +
", class " + std::to_string(r) +
610 " for design point " + std::to_string(e + 1));
611 vals.push_back(M(ist - 1, r - 1));
634 const std::vector<T> vals =
uq_samples(sol, metric, ist, r);
636 for (std::size_t e = 0; e < vals.size(); ++e) out.
mean += T(sol.
weights[e] * vals[e]);
637 for (std::size_t e = 0; e < vals.size(); ++e) {
638 const T dv = T(vals[e] - out.
mean);
655 std::size_t ist, std::size_t r) {
656 const std::vector<T> vals =
uq_samples(sol, metric, ist, r);
657 std::vector<std::size_t> ord(vals.size());
658 for (std::size_t i = 0; i < ord.size(); ++i) ord[i] = i;
659 std::stable_sort(ord.begin(), ord.end(),
660 [&vals](std::size_t a, std::size_t b) { return vals[a] < vals[b]; });
663 for (std::size_t k = 0; k < ord.size(); ++k) {
664 out.
values.push_back(vals[ord[k]]);
667 out.
cdf.push_back(acc);
683 std::size_t ist, std::size_t r,
double level = 0.95) {
684 if (!(level > 0.0) || !(level < 1.0))
685 throw InputError(
"SolverUQ: the coverage level must lie strictly between 0 and 1");
690 const double alpha = (1.0 - level) / 2.0;
692 bool lo_set =
false, hi_set =
false;
693 for (std::size_t k = 0; k < ec.
values.size(); ++k) {
695 if (!lo_set && cw >= alpha) {
699 if (!hi_set && cw >= 1.0 - alpha) {
704 return std::make_pair(lo, hi);
753 if (g.
dists.empty())
throw NumericError(
"uq_prior_mean_range: the Prior has no alternative");
759 return std::make_pair(lo, up);
775 return std::make_pair(
false,
"a Prior sits on an arrival process, so the model is open");
776 if (
sn.nclasses != 1)
777 return std::make_pair(
false,
"the theorems are proved for a single class only");
778 if (!(
sn.nclosedjobs() > 0.0))
return std::make_pair(
false,
"the class is not closed");
779 if (
sn.nodes.size() !=
sn.nstations)
780 return std::make_pair(
false,
"the model has nodes that are not stations");
781 for (std::size_t i = 0; i <
sn.nstations; ++i) {
783 if (!inf &&
sn.stations[i].nservers > 1.0)
784 return std::make_pair(
false,
"a queueing station has more than one server");
787 return std::make_pair(
false,
"a station is neither delay, PS nor FCFS");
789 for (std::size_t a = 0; a < sites.size(); ++a)
790 for (std::size_t b = a + 1; b < sites.size(); ++b)
791 if (sites[a].station == sites[b].station)
792 return std::make_pair(
false,
"two Priors sit on the same station");
793 return std::make_pair(
true, std::string());
806 const std::vector<
PriorSite<T>>& sites, std::size_t nodes) {
808 const std::size_t M =
sn.nstations;
809 if (
sn.visits.empty())
throw NumericError(
"uq_interval_by_mva: the model carries no visits");
810 std::vector<T> V(M, zero), STlo(M, zero), STup(M, zero);
811 std::vector<bool> inf(M,
false);
812 for (std::size_t i = 0; i < M; ++i) {
813 V[i] =
sn.visits[0](i, 0);
818 const T st = (std::isnan(rate) || rate == 0.0) ? zero : T(one /
sn.rates(i, 0));
824 STlo[s.station - 1] = mr.first;
825 STup[s.station - 1] = mr.second;
828 std::vector<std::size_t> qidx;
829 T zlo = zero, zup = zero;
830 for (std::size_t i = 0; i < M; ++i) {
832 zlo += T(V[i] * STlo[i]);
833 zup += T(V[i] * STup[i]);
840 "uq_interval_by_mva: the model has no queueing station, so there is no MVA recursion "
841 "to bound; a pure delay model's metrics are the demand intervals themselves");
844 for (std::size_t k = 0; k < qidx.size(); ++k) {
845 L(k, 0) = T(V[qidx[k]] * STlo[qidx[k]]);
846 L(k, 1) = T(V[qidx[k]] * STup[qidx[k]]);
848 const int n =
static_cast<int>(std::lround(
sn.nclosedjobs()));
862 for (std::size_t k = 0; k < qidx.size(); ++k) {
863 const std::size_t i = qidx[k];
864 out.
Qlo(i, 0) = iv.
Q(k, 0);
865 out.
Qup(i, 0) = iv.
Q(k, 1);
866 out.
Ulo(i, 0) = iv.
U(k, 0);
867 out.
Uup(i, 0) = iv.
U(k, 1);
868 out.
Wlo(i, 0) = iv.
R(k, 0);
869 out.
Wup(i, 0) = iv.
R(k, 1);
871 out.
Rlo(i, 0) = V[i] > zero ? T(iv.
R(k, 0) / V[i]) : zero;
872 out.
Rup(i, 0) = V[i] > zero ? T(iv.
R(k, 1) / V[i]) : zero;
874 for (std::size_t i = 0; i < M; ++i) {
875 if (!inf[i])
continue;
879 out.
Wlo(i, 0) = T(V[i] * STlo[i]);
880 out.
Wup(i, 0) = T(V[i] * STup[i]);
881 out.
Rlo(i, 0) = STlo[i];
882 out.
Rup(i, 0) = STup[i];
883 out.
Qlo(i, 0) = T(iv.
Xlo * V[i] * STlo[i]);
884 out.
Qup(i, 0) = T(iv.
Xup * V[i] * STup[i]);
885 out.
Ulo(i, 0) = out.
Qlo(i, 0);
886 out.
Uup(i, 0) = out.
Qup(i, 0);
888 for (std::size_t i = 0; i < M; ++i) {
889 out.
Tlo(i, 0) = T(V[i] * iv.
Xlo);
890 out.
Tup(i, 0) = T(V[i] * iv.
Xup);
898 out.
method =
"mvainterval";
920 if (v.
empty())
continue;
926 for (std::size_t i = 0; i < v.
rows(); ++i)
927 for (std::size_t j = 0; j < v.
cols(); ++j) {
928 if (v(i, j) < lo(i, j)) lo(i, j) = v(i, j);
929 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.
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.
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.
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 | discrete | quadrature | montecarlo, MATLAB UQ.listValidMethods.
std::size_t samples
Nodes per continuous Prior, or design points under montecarlo; the reference's options....
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: quadrature or montecarlo.
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).