5#ifndef LINE_SOLVERS_MAM_SOLVER_MAM_RUNNER_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_RUNNER_H
76using lang::GlobalConstants;
95 return ::line::method_registry::methods(
"MAM");
109 if (method !=
"inap" && method !=
"inaprc" && method !=
"inapinf"
110 && method !=
"exact") {
113 why =
"SolverMAM: the '" + method +
"' method moved to SolverAG: RCAT decomposes the "
114 "model into cooperating agents rather than decomposing traffic, and no MAM "
115 "algorithm shares its machinery. Solve it with -s ag";
122 if (std::find(valid.begin(), valid.end(), method) != valid.end())
return;
128namespace runner_detail {
145 if (is_discrete)
return;
146 for (std::size_t i = 0; i < L.
nstations; ++i)
147 for (std::size_t r = 0; r < L.
nclasses; ++r) {
150 if (basic_detail::is_markovian_type(p) || p == ProcessType::DET)
continue;
152 std::string(
"SolverMAM: station '") + L.
stations[i].name +
"' class '" +
154 " service process, which sn_nonmarkov_toph has no Markovian surrogate for: its "
155 "content is a piecewise-constant SCHEDULE, and collapsing it to one homogeneous "
156 "generator would answer for a stationary model the caller did not describe. Solve "
157 "it with SolverFLD or SolverLDES, which integrate the schedule");
167bool has_load_dependence(
const qn::NetworkStruct<T>& L) {
168 for (
const qn::Station<T>& st : L.stations)
169 if (!st.lldscaling.empty())
return true;
175bool has_signal_class(
const qn::NetworkStruct<T>& L) {
176 for (std::size_t r = 0; r < L.issignal.size(); ++r)
177 if (L.issignal[r])
return true;
189std::string mam_ldqbd_refusal(
const qn::NetworkStruct<T>& L) {
191 if (L.nclasses != 1)
return "SolverMAM: the ldqbd method requires a single-class model";
192 std::size_t nDelay = 0, nQueue = 0, nSource = 0, src = 0;
193 for (std::size_t i = 0; i < L.nstations; ++i) {
195 if (sc == SchedStrategy::INF) ++nDelay;
196 else if (sc == SchedStrategy::FCFS) ++nQueue;
197 else if (sc == SchedStrategy::EXT) {
198 if (nSource == 0) src = i + 1;
202 const double pop = L.classes[0].population;
203 if (!std::isfinite(pop)) {
204 if (nSource != 1 || nQueue != 1 || L.nstations != 2)
205 return "SolverMAM: open LDQBD method requires exactly one Source and one Queue station";
207 return "SolverMAM: open LDQBD method currently supports Poisson (exponential) arrivals "
208 "only; the Source uses a MAP/MMPP process";
209 return std::string();
212 return "SolverMAM: closed LDQBD method requires a positive population";
213 if (nDelay != 1 || nQueue != 1 || L.nstations != 2)
214 return "SolverMAM: closed LDQBD method requires exactly one Delay and one Queue station";
215 return std::string();
220void check_model_method(
const qn::NetworkStruct<T>& L,
const std::string& method,
221 bool transient =
false, std::size_t sync_phases = 0) {
234 if (has_signal_class(L)) {
236 "SolverMAM: the " + method +
" method does not support G-network signals: no MAM "
237 "algorithm reads issignal, so this method would solve the model with every signal "
238 "turned into an ordinary customer. Use SolverAG (-s ag), whose RCAT methods are "
239 "the only ones that model signals");
241 if (method ==
"mna") {
242 bool open =
false, closed =
false;
243 for (
const qn::JobClass& c : L.classes) {
244 if (std::isinf(c.population)) open =
true;
249 "SolverMAM: the mna method does not support mixed open/closed models");
256 for (
const qn::NodeDef& nd : L.nodes)
258 if (rs == qn::RoutingStrategy::RROBIN)
260 "SolverMAM: the mna method supports round-robin routing in open "
263 }
else if (method ==
"default") {
272 if (has_load_dependence(L) &&
273 !(L.setupparam.empty() && dispatch_detail::is_closed_delay_queue(L)))
275 "SolverMAM: this model uses load-dependent service rates outside the "
276 "single-class closed Delay+Queue shape that solver_mam_analyzer.m routes to the "
277 "level-dependent QBD (solver_mam_ldqbd); the 'default' method would otherwise "
278 "fall through to solver_mam_basic's dec.source decomposition, which does not "
279 "read the load-dependent scaling and would solve every level at the nominal "
280 "rate. Call method 'ldqbd' directly, which refuses by name if the shape still "
281 "does not fit, or restructure the model to the required shape");
288 }
else if (method ==
"ldqbd") {
292 const std::string ldWhy = mam_ldqbd_refusal(L);
293 if (!ldWhy.empty())
throw UnsupportedError(ErrorKind{
"LoadDependence"}, ldWhy);
295 }
else if (method ==
"retrial") {
305 if (!retrialWhy.empty())
throw UnsupportedError(ErrorKind{
"Retrial"}, retrialWhy);
306 }
else if (method ==
"sync.faster" || method ==
"sync.slower") {
310 if (!syncWhy.empty())
312 }
else if (method ==
"bgchain") {
317 bool hasClosed =
false;
318 for (std::size_t k = 0; k < L.nclasses; ++k) {
319 if (!std::isinf(L.classes[k].population)) hasClosed =
true;
323 "SolverMAM: the bgchain method requires at least one closed class: the background "
324 "chain IS the closed population vector, which a purely open model does not have. "
325 "Use the dec.source method");
328 bool prioSched =
false;
329 for (std::size_t i = 0; i < L.nstations; ++i) {
334 if (prioSched && L.has_distinct_priorities())
336 "SolverMAM: the bgchain method does not support class priorities: it aggregates "
337 "the open classes into one phase-type mixture per station, which cannot express a "
338 "priority order. Use the dec.source method");
341 "SolverMAM: the bgchain method does not support fork-join: the background chain "
342 "conserves the closed population per station, which a fork violates. Use the "
343 "dec.source method");
362 runner_detail::check_model_method(L, method);
363 }
catch (
const std::exception& e) {
364 return std::string(e.what());
366 return std::string();
376 runner_detail::check_model_method(L,
opt.method,
false,
opt.sync_phases);
380 bool is_discrete =
false;
401 const bool preserve_det =
opt.preserve_det_resolved();
417 runner_detail::check_processes(Lc, is_discrete);
437 const std::string origmethod =
opt.method;
442 std::vector<std::vector<bool>> mask(M, std::vector<bool>(K,
false));
443 for (std::size_t i = 0; i < M; ++i)
444 for (std::size_t k = 0; k < K; ++k)
446 std::vector<std::vector<bool>> srcmask(M, std::vector<bool>(K,
false));
447 for (std::size_t i = 0; i < M; ++i)
448 if (L.
stations[i].nodetype == qn::NodeType::Source)
449 for (std::size_t k = 0; k < K; ++k) srcmask[i][k] =
true;
499 std::size_t ist, std::size_t jobclass,
522 runner_detail::check_model_method(L,
opt.method);
527 const bool preserve_det =
opt.preserve_det_resolved();
541 runner_detail::check_processes(*Lp,
false);
574 if (
opt.method !=
"default" &&
opt.method !=
"dec.source")
return false;
575 if (L.
fj.empty())
return false;
590 const std::vector<double>& percentiles) {
596 std::string why =
"the model is solved in an exact field, where the FJ_codes phase-type fit is unavailable.";
598 if (
opt.method !=
"default" &&
opt.method !=
"dec.source")
599 why =
"method '" +
opt.method +
"' stores no fork-join percentile table.";
604 "SolverMAM computes fork-join response-time percentiles only on the Source->Fork->K identical "
605 "queues->Join->Sink topology (Qiu-Perez-Harrison FJ_codes): " + why +
606 " Use fj::fj_tail_forktail for the topology-driven approximation.");
609 std::vector<std::vector<T>> out;
610 out.reserve(rd.size());
630 opt.method =
"ldqbd";
631 runner_detail::check_model_method(L,
opt.method,
true);
634 runner_detail::check_processes(L,
false);
645 tranSet.
set(qn::Feature::FiniteCapacity);
UnsupportedError(const std::string &what)
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.
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< std::vector< bool > > disabled
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::vector< std::pair< std::size_t, std::size_t > > fj
fj(f,j): the Join node j that closes the Fork node f, 1-based.
The exception types the port throws.
Port of solver_mam_analyzer.m: one inner solve, choosing the analyzer that fits the model and the req...
The option and result types SolverMAM shares with its analyzers.
The method names of each solver, in listing order.
bool sn_has_nonmarkov(const qn::NetworkStruct< T > &sn, bool preserve_det=false)
Whether any law in the struct would be replaced, so a caller can skip copying the struct when there i...
bool nonmkv_enabled(const std::string &method)
Resolve options.config.nonmkv to NonmarkovOptions::enabled.
@ Ph
Bernstein density fit: a genuine phase-type, shape-carrying.
@ Cme
concentrated ME plus exponential tail: two moments, exactly
void sn_nonmarkov_toph(qn::NetworkStruct< T > &sn, const NonmarkovOptions &opts=NonmarkovOptions())
Replace every non-Markovian service and firing law by a Markovian surrogate.
bool sn_is_discrete_time(const qn::NetworkStruct< T > &sn, const DiscreteTimeOptions &options, double *slot_length, DiscreteTimeInfo *info)
True when every law of sn is lattice-valued on slot_length.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
const char * process_to_text(ProcessType p)
The MATLAB ProcessType name, as sn.procid prints it.
std::vector< RespTCdf< T > > solver_mam_get_sjrn_t(const qn::NetworkStruct< T > &L, const MamOptions &opt)
@@SolverMAM/getSjrnT.m and sjrnT.m, both aliases of getCdfRespT.
qsys::BmapM1Result< T > solver_mam_getmamresult(const qn::NetworkStruct< T > &L)
Port of @@SolverMAM/getMAMResult.m: the matrix-analytic internals of a single-queue model,...
bool rcat_moved_to_ag(const std::string &method, std::string &why)
True for the four RCAT names that moved to SolverAG, with the redirect message.
void check_method(const std::string &method)
An unlisted method is refused; one that MOVED is redirected by name.
MamSolution< T > mam_dispatch(const qn::NetworkStruct< T > &L, const MamOptions &opt_in)
The ladder.
std::vector< T > mam_percentiles_from_cdf(const RespTCdf< T > &cdf, const std::vector< double > &pcts)
Port of the CDF path of @@SolverMAM/getPerctRespT.m: linear interpolation of the response-time CDF at...
TranResult< T > solver_mam_get_tran_avg(const qn::NetworkStruct< T > &L, const MamOptions &opt_in)
Port of @@SolverMAM/getTranAvg.m: transient queue length, utilization and throughput.
std::vector< std::string > list_valid_methods()
Port of SolverMAM.listValidMethods.
TranResult< T > solver_mam_transient_qbd(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_transient_qbd.m.
MamSolution< T > solver_mam_solve(const qn::NetworkStruct< T > &L, const MamOptions &opt)
The gates and the dispatch of @@SolverMAM/runAnalyzer.m, without the metric filter.
std::vector< RespTCdf< T > > solver_mam_get_cdf_respt(const qn::NetworkStruct< T > &L, const MamOptions &opt)
@@SolverMAM/getCdfRespT.m: the response-time CDF per class.
std::vector< RespTCdf< T > > solver_mam_passage_time(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_passage_time.m.
mva::AvgResult< T > solver_mam_run_analyzer(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of @@SolverMAM/runAnalyzer.m for the lang='matlab' path: solve, then apply the metric filter @@N...
ProbTable< T > solver_mam_get_prob(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t node, const mva::AvgResult< T > &avg)
@@SolverMAM/getProb.m: the joint (level, phase) table at a node.
bool mam_transient_qbd_applicable(const qn::NetworkStruct< T > &L)
Port of mam_transient_qbd_applicable.m: true when the Laplace-domain transient QBD should run instead...
MamFjInfo mam_fj_is_homogeneous(const qn::NetworkStruct< T > &L)
Port of fj_is_homogeneous.m.
std::vector< std::vector< T > > solver_mam_get_perct_respt(const qn::NetworkStruct< T > &L, const MamOptions &opt, const std::vector< double > &percentiles)
@@SolverMAM/getPerctRespT.m: response-time percentiles per class.
bool mam_has_fj_percentiles(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Whether getPerctRespT reads the FJ_codes table rather than inverting a CDF.
std::vector< T > solver_mam_get_prob_marg(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t ist, std::size_t jobclass, const mva::AvgResult< T > &avg)
@@SolverMAM/getProbMarg.m: P(n jobs of one class) at a station.
TranResult< T > solver_mam_ldqbd_transient(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_ldqbd_transient.m.
std::string mam_sync_refusal(const qn::NetworkStruct< T > &L, std::size_t nph, MamSyncParams< T > *par=nullptr)
mam_sync_applicable.m: why the sync methods cannot answer this model, or "" when they can,...
std::string mam_reneging_default_refusal(const qn::NetworkStruct< T > &L)
The sentence supportsModelMethod('default') refuses a declared reneging with when the model is not th...
ProbTable< T > solver_mam_getprob(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t node, const mva::AvgResult< T > &avg)
Port of @@SolverMAM/getProb.m.
std::string mam_model_method_refusal(const qn::NetworkStruct< T > &L, const std::string &method)
check_model_method asked WITHOUT raising: the same verdict as a sentence.
std::vector< std::vector< T > > solver_mam_fj_percentiles(const qn::NetworkStruct< T > &L, const MamOptions &opt, const std::vector< double > &percentiles)
@@SolverMAM/getPerctRespT.m's fork-join path: the percentiles solver_mam_fj.m stores in percResults....
std::string mam_retrial_refusal(const qn::NetworkStruct< T > &L)
The 'retrial' method's applicability as one sentence; empty when applicable.
std::vector< T > solver_mam_getprobmarg(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t ist, std::size_t jobclass, const mva::AvgResult< T > &avg)
Port of @@SolverMAM/getProbMarg.m: P(n jobs of class jobclass) at station ist, for n = 0....
qsys::BmapM1Result< T > solver_mam_get_mam_result(const qn::NetworkStruct< T > &L)
@@SolverMAM/getMAMResult.m: the M/G/1-type internals of a single queue.
Matrix< T > sn_get_residt_from_respt(const qn::NetworkStruct< T > &L, const Matrix< T > &RN)
Port of sn_get_residt_from_respt: the per-JOB residence time.
Matrix< T > filter_metric(const qn::NetworkStruct< T > &L, const Matrix< T > &metric, MetricKind kind, const std::vector< std::vector< bool > > *zero_mask)
Port of filterMetric: what @@NetworkSolver/getAvg does between the analyzer and the caller.
bool cap_unstable_open_stations(const qn::NetworkStruct< T > &L, Matrix< T > &QN, Matrix< T > &UN, Matrix< T > &RN, const Matrix< T > &TN)
The saturated open-station rule of @@NetworkSolver/getAvg.m:221-308.
const char * unstable_open_warning()
The warning getAvg.m raises when cap_unstable_open_stations() fires.
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
Port of sn_get_arvr_from_tput: the arrival rate each station sees, from the throughputs and the class...
FeatureSet mam_feature_set(const std::string &method)
SolverMAM.getFeatureSet, the union of its four setTrue calls: 55 MATLAB names, WIDENED for 'default'/...
void feature_gate(const std::string &solver, const FeatureSet &declared, const NetworkStruct< T > &sn, const std::string &requested_method="", const std::string &resolved_method="")
runAnalyzerChecks: refuse a model the solver does not declare, by name.
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
Port of matlab/src/api/sn/sn_is_discrete_time.m.
Replace every non-Markovian service and firing law by a Markovian surrogate.
The DECLARED side of the gate: one feature set per solver.
Port of solver_mam_fj.m, the fork-join route of SolverMAM.
Port of solver_mam_ldqbd_transient.m: transient queue length, utilization and throughput of a single-...
Port of solver_mam_passage_time.m: the response-time (sojourn-time) distribution of a single open que...
Port of @@SolverMAM/getProb.m and @@SolverMAM/getProbMarg.m: the joint (level, phase) and marginal qu...
Transient analysis of a single-class open queue by the Laplace-domain transient QBD plus numerical in...
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....
Which laws the model carries, and why it was refused when it was.
How the caller may override the automatic decision.
std::string timescale
"auto", "discrete" or "continuous".
double slotlength
Slot length in model time units.
options.config.nonmkv and friends.
std::size_t order
nonmkvorder, the phase budget
PhFit phfit
which surrogate family
bool preserve_det
Leave Det alone for the exact MAP/D/c branch, options.config.preserveDet.
static constexpr double FineTol
std::string why
the reference's errorMsg, empty when ok
The options SolverMAM reads.
What the MAM dispatch returns: the metrics plus the algorithm that ran.
std::string actualmethod
The concrete algorithm, as the reference's actualmethod.
mva::MvaSolution< T > sol
The joint (level, phase) table getProb returns: rows levels, cols phases.
One class's response-time CDF, the reference's RD{station, class} = [F, X].
What getTranAvg returns: queue length, utilization and throughput curves.
The metrics getAvg returns, after filtering.
Matrix< T > RN
response time, per visit
std::string warning
The reference's own warning text, verbatim, empty when it did not warn.
Matrix< T > UN
utilization
Matrix< T > WN
residence time, per job
std::string method
the method asked for
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
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Everything qsys_bmapm1 returns, mirroring the MATLAB result struct.