5#ifndef LINE_SOLVERS_MAM_SOLVER_MAM_RETRIAL_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_RETRIAL_H
160namespace retrial_detail {
165 const typename std::map<std::size_t, qn::RetrialParam<T> >::const_iterator it =
168 for (std::size_t r = 0; r < it->second.retrial_proc.size(); ++r)
169 if (!it->second.retrial_proc[r].disabled)
return true;
184 throw UnsupportedError(std::string(
"SolverMAM: the ") + role +
" process of class '" +
186 "' is disabled; the matrix-analytic retrial solver has no state for "
187 "a class that does not visit its own station");
188 if (!basic_detail::is_markovian_type(d.
type))
190 std::string(
"SolverMAM: the ") + role +
" process of class '" + L.
classes[r - 1].name +
192 ", which has no Markovian (D0,D1) representation. The reference replaces it by an "
193 "Erlang of matching mean and squared coefficient of variation and warns that the "
194 "answer is approximate; this port refuses instead, because the substitute is a "
195 "different model under the same method name. Use SolverCTMC, SolverSSA or SolverLDES");
197 if (!basic_detail::is_markovian_map(m))
198 throw UnsupportedError(std::string(
"SolverMAM: the ") + role +
" process of class '" +
199 L.
classes[r - 1].name +
"' at station '" +
201 "' has a (D0,D1) pair that is not a Markovian generator (a negative "
202 "off-diagonal, a negative arrival entry, or rows that do not sum to "
215inline double retrial_block_width(std::size_t V, std::size_t M,
int N) {
216 double d = 0.0, term = 1.0;
217 for (
int n = 0; n <= N; ++n) {
218 if (n > 0) term *=
static_cast<double>(n +
static_cast<int>(M) - 1) /
static_cast<double>(n);
220 if (d > 1e12)
return 1e12;
222 return static_cast<double>(V) * d;
230double orbit_trunc_error(
const qsys::BmapPhNnRetrialResult<T>& r) {
231 const double lorbit = num_traits<T>::to_double(r.L_orbit);
232 const double top = num_traits<T>::to_double(r.topLevelMass);
233 const double denom = lorbit > std::numeric_limits<double>::min()
235 : std::numeric_limits<double>::min();
236 return static_cast<double>(r.truncLevel) * top / denom;
256 info.
why =
"the BMAP/PH/N/N retrial solver requires an open queueing model";
260 info.
why =
"the BMAP/PH/N/N retrial solver supports a single class only";
268 std::vector<std::size_t> bufferless;
269 for (std::size_t i = 1; i <= L.
nstations; ++i) {
270 if (L.
stations[i - 1].nodetype != qn::NodeType::Queue)
continue;
271 const double cap = L.
cap[i - 1];
272 const bool isbufferless = std::isfinite(cap) && cap == L.
stations[i - 1].nservers;
273 if (retrial_detail::has_retrial_proc(L, i) || isbufferless) bufferless.push_back(i);
275 if (bufferless.empty()) {
276 info.
why =
"no retrial queue found (declare one with setRetrial, or give a station a "
277 "capacity equal to its server count)";
281 for (std::size_t i : bufferless) {
282 for (std::size_t r = 0; r < L.
nclasses; ++r)
283 if (L.
droprule[i - 1][r] == DropStrategy::RETRIAL ||
284 L.
droprule[i - 1][r] == DropStrategy::RETRIAL_WITH_LIMIT) {
291 info.
why =
"no RETRIAL drop strategy is configured on a bufferless queue";
295 for (std::size_t i = 1; i <= L.
nstations; ++i)
296 if (L.
stations[i - 1].nodetype == qn::NodeType::Source) {
301 info.
why =
"no Source node found";
306 if (!std::isfinite(ns) || ns < 1.0) {
307 info.
why =
"the retrial station must have a finite, positive server count";
310 info.
nservers =
static_cast<int>(ns);
314 info.
R =
static_cast<long>(info.
nservers) - 1;
337 if (info.
ok)
return std::string();
338 return "SolverMAM: the 'retrial' method needs a BMAP/PH/N/N bufferless retrial topology; "
339 "this model declares no orbit (" + info.
why +
"). Use method 'default'";
360 "SolverMAM: no valid impatience configuration was detected (" + info.
why +
361 "). The reneging route of solver_mam_retrial.m, the MAP/M/s+G call-centre model of "
362 "Gursoy, Mehr and Akar solved through MRMFQSolver, is not reachable from C++ at all: "
363 "it is gated on sn.impatienceClass, sn.patienceProc and ImpatienceType.RENEGING, and "
364 "the C++ NetworkStruct carries none of the three");
368 retrial_detail::assert_markovian(L, src, r,
"arrival");
369 retrial_detail::assert_markovian(L, q, r,
"service");
373 if (L.
droprule[q - 1][r - 1] == DropStrategy::RETRIAL_WITH_LIMIT)
375 "SolverMAM: station '" + L.
stations[q - 1].name +
376 "' declares DropStrategy.RETRIAL_WITH_LIMIT. The BMAP/PH/N/N generator indexes its "
377 "levels by the orbit population alone and has no per-job attempt counter, so a job "
378 "about to give up cannot be told from a persistent one. Use SolverCTMC or SolverSSA");
386 std::vector<Matrix<T> > D;
393 const std::vector<T> beta =
map_pie(svc);
402 const typename std::map<std::size_t, qn::RetrialParam<T> >::const_iterator rit =
408 "SolverMAM: station '" + L.
stations[q - 1].name +
"' bounds the retrial count at " +
410 " attempts. The BMAP/PH/N/N generator has no per-job attempt counter in its state "
411 "descriptor. Use SolverCTMC or SolverSSA");
414 if (ret.
order() != 1)
416 "SolverMAM: the retrial process of class '" + L.
classes[r - 1].name +
417 "' at station '" + L.
stations[q - 1].name +
"' has " +
418 std::to_string(ret.
order()) +
419 " phases. The generator retries each orbiting job at a single scalar rate, so "
420 "only an exponential inter-retry time can be represented; the reference reads "
421 "-D0(1,1) regardless, which is the exit rate of the first phase and not the "
422 "retry rate of the process");
423 alpha = T(zero - ret.
D0(0, 0));
429 throw InputError(
"SolverMAM: the retrial rate at station '" + L.
stations[q - 1].name +
430 "' must be positive; a zero rate means an orbiting job never retries and "
431 "the orbit is not positive recurrent at any load");
433 const T gamma = zero;
437 const double blockw =
439 if (blockw > cfg.max_block)
441 "SolverMAM: the per-level block of the retrial generator at station '" +
442 L.
stations[q - 1].name +
"' is " + std::to_string(
static_cast<long long>(blockw)) +
443 " states wide, past the " + std::to_string(
static_cast<long long>(cfg.max_block)) +
444 "-state cap: the service has " + std::to_string(svc.
order()) +
445 " phases and the station " + std::to_string(info.
nservers) +
446 " servers, and the busy servers' phase multisets grow as C(n + phases - 1, phases - 1). "
447 "Fit the service with fewer phases, reduce the server count, or raise "
448 "MamRetrialConfig::max_block if the memory cost is acceptable");
450 const std::vector<long> Rv(1, info.
R);
456 if (cfg.orbit_maxlevel == 0) {
460 while (retrial_detail::orbit_trunc_error(res) > cfg.orbit_tailtol) {
462 if (
static_cast<double>(next + 1) * blockw > cfg.max_dim)
464 "SolverMAM: the orbit truncation of station '" + L.
stations[q - 1].name +
465 "' did not reach the requested accuracy: the tail still contributes " +
466 std::to_string(retrial_detail::orbit_trunc_error(res)) +
" of the mean orbit "
467 "length at level " + std::to_string(res.
truncLevel) +
468 ", against a target of " + std::to_string(cfg.orbit_tailtol) +
469 ", and doubling would pass the dimension cap. The orbit measures would be "
470 "underestimated, which the reference reports as a warning and this port has no "
471 "channel for. Raise MamRetrialConfig::max_dim, or set "
472 "MamRetrialConfig::orbit_maxlevel to accept a fixed truncation");
485 out.
C.assign(K, zero);
486 out.
X.assign(K, zero);
493 out.
R(q - 1, r - 1) = T(out.
Q(q - 1, r - 1) / res.
throughput);
500 out.
C[r - 1] = out.
R(q - 1, r - 1);
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
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::map< std::size_t, RetrialParam< T > > retrialparam
Retrial parameters, keyed by 1-based STATION index.
std::vector< double > cap
sn.cap and sn.classcap: the total and per-class buffers.
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
bool is_open_model() const
sn_is_open_model: EVERY class is open, which is not has_open_classes.
std::vector< std::vector< DropStrategy > > droprule
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
The option and result types SolverMAM shares with its analyzers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
mam::Map< T > dist_to_map(const Distrib< T > &d)
DropStrategy
Blocking and loss rules, with the values of MATLAB DropStrategy.
const char * process_to_text(ProcessType p)
The MATLAB ProcessType name, as sn.procid prints it.
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
MamRetrialInfo mam_retrial_detect(const qn::NetworkStruct< T > &L)
Port of qsys_is_retrial.m, plus the reneging gate of solver_mam_retrial.m.
mva::MvaSolution< T > solver_mam_retrial(const qn::NetworkStruct< T > &L, const MamOptions &opt, const MamRetrialConfig &cfg=MamRetrialConfig())
Port of solver_mam_retrial.m.
std::string mam_retrial_refusal(const qn::NetworkStruct< T > &L)
The 'retrial' method's applicability as one sentence; empty when applicable.
BmapPhNnRetrialResult< T > qsys_bmapphnn_retrial(const std::vector< Matrix< T > > &D, const std::vector< T > &beta, const Matrix< T > &S, int N, const T &alpha, const T &gamma, const T &p, const std::vector< long > &R, const BmapPhNnRetrialOptions &opt)
The BMAP/PH/N/N bufferless retrial queue.
A queueing network and its refreshed NetworkStruct.
The BMAP/PH/N/N bufferless retrial queue with flexible retrial admission control.
Port of solver_mam_basic.m, the dec.source analyzer and the default algorithm of SolverMAM.
The options SolverMAM reads.
The knobs solver_mam_retrial.m reads from options.config and from model fields the C++ NetworkStruct ...
double max_dim
'MaxDim': the largest generator dimension the refinement may reach.
double orbit_tailtol
options.config.orbit_tailtol, the relative orbit-truncation target.
std::size_t orbit_maxlevel
options.config.orbit_maxlevel; 0 asks for the adaptive refinement.
double max_block
'MaxBlockSize': the largest per-level block V*d that may be assembled.
What qsys_is_retrial.m returns: the station it found, or why it found none.
std::string why
the reference's errorMsg, empty when ok
std::size_t station
1-based station index of the retrial queue
std::size_t cls
1-based class index; the reference is single-class
long R
admission threshold, N-1 unless an FCR lowers it
std::size_t source
1-based station index of the Source
A MAP as the pair of matrices (D0, D1).
std::size_t order() const
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
The parameters of a retrial station: MATLAB sn.retrialProc and friends.
std::vector< int > max_attempts
0 = unbounded
std::vector< T > retrial_rate
mu_r, the per-class orbit retry rate
std::vector< lang::Distrib< T > > retrial_proc
retrial_proc[r] is the class-r retrial process; empty = not a retrial class.
Options of qsys_bmapphnn_retrial.
std::size_t maxLevel
Orbit truncation level; 0 selects the reference's automatic choice.
Result of qsys_bmapphnn_retrial.
T L_orbit
mean number of customers in orbit
std::size_t truncLevel
truncation level used
T utilization
N_server / N.
T throughput
N_server / b1.
T N_server
mean number of busy servers