LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_mam_retrial.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_MAM_SOLVER_MAM_RETRIAL_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_RETRIAL_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `solver_mam_retrial.m`, the customer-impatience analyzer of SolverMAM.
12 *
13 * The reference file carries TWO analyzers behind one name, and tries them in
14 * this order:
15 *
16 * 1. RENEGING, a MAP/M/s+G queue whose waiting jobs abandon after a generally
17 * distributed patience (Gursoy, Mehr and Akar). Solved as a multi-regime
18 * Markov fluid queue through `MRMFQSolver`.
19 * 2. RETRIAL, the BMAP/PH/N/N bufferless orbit queue with flexible retrials
20 * admission control (Dudin et al., Mathematics 2025, 13(9), 1434). Solved
21 * by `qsys_bmapphnn_retrial`, which IS ported (api/qsys).
22 *
23 * ONLY ROUTE 2 IS REACHABLE FROM C++, AND ROUTE 1 IS REFUSED BY NAME. The
24 * reneging gate reads `sn.impatienceClass`, `sn.patienceProc` and
25 * `ImpatienceType.RENEGING`, none of which the C++ `NetworkStruct` carries, so
26 * no model this port can BUILD is a reneging model. `mam_retrial_detect` says so
27 * explicitly instead of letting such a model fall through to the retrial
28 * generator, which would answer a queue with a waiting line as if it had an
29 * orbit.
30 *
31 * WHAT THE ORBIT IS. A retrial station has no waiting room: the buffer equals
32 * the server count. A job that finds every server busy joins an ORBIT and
33 * re-attempts at rate alpha per orbiting job; a completion does NOT promote
34 * from the orbit. That is why the queue length reported here is
35 * `L_orbit + N_server` and not the marginal of an ordered buffer.
36 *
37 * THE TRUNCATION IS THE ANSWER'S ACCURACY, AND IT IS ITERATED HERE. The orbit
38 * is unbounded, so `qsys_bmapphnn_retrial` truncates it; the reference does not
39 * accept the first truncation but doubles the level until the relative tail
40 * contribution `truncLevel * mass(top level) / L_orbit` falls under
41 * `TailTolerance`. The ported engine implements ONE solve at a given level and
42 * returns `truncLevel`, `topLevelMass` and `L_orbit`, which is exactly the
43 * triple that refinement test needs, so the loop lives here rather than being
44 * dropped. Without it a model is answered at the reference's FIXED default
45 * level, whose formula depends on neither alpha nor gamma -- the two parameters
46 * that actually govern the tail decay -- and the error is silent.
47 *
48 * WHERE THIS IS STRICTER THAN THE REFERENCE, and why each is the honest outcome:
49 *
50 * - A NON-CONVERGED TRUNCATION IS AN ERROR, NOT A WARNING. When doubling hits
51 * the dimension cap before reaching the tolerance, `solver_mam_retrial.m`
52 * emits `line_warning` and returns the underestimate. The C++ port has no
53 * warning channel, so the same situation would return a number with nothing
54 * attached to it; it refuses instead, and names `orbit_maxlevel` as the way
55 * to ask for that number deliberately.
56 * - A NON-PHASE-TYPE SERVICE IS REFUSED, NOT APPROXIMATED. The reference's
57 * `warnIfApproximated` lets a Det, Replayer, Uniform, Gamma, Pareto, Weibull
58 * or Lognormal service through under an Erlang fit of matching mean and SCV,
59 * and says so in a warning. Everything but Det is already gated out by
60 * `runner_detail::check_processes`; Det reaches here, and `dist_to_map` would
61 * silently give it a 20-phase Erlang -- which is not only a different model
62 * but blows the per-level block up by C(n + 19, 19) states per busy server.
63 * - A BOUNDED RETRY COUNT IS REFUSED. `RetrialParam::max_attempts` and
64 * `DropStrategy::RETRIAL_WITH_LIMIT` say a job gives up after k failed
65 * retries. The generator has no attempt counter in its state descriptor, so
66 * there is no level at which that job could be told apart from a persistent
67 * one.
68 *
69 * WHAT THE C++ MODEL LAYER CANNOT SAY, so what is pinned at its reference
70 * default rather than read: the orbit abandonment rate gamma (`sn.orbitImpatience`,
71 * default 0), the batch rejection probability p (`sn.batchRejectProb`, default
72 * 0), the finite orbit `sn.orbitMaxJobs`, the CONSTANT retrial policy
73 * (`sn.retrialPolicy`; the ported engine is LINEAR-only, which is the
74 * reference's own default), and the admission threshold R, which the reference
75 * lowers below N-1 only from a Finite Capacity Region and `NetworkStruct` has no
76 * region field. Each is stated here so that a later model-layer addition has a
77 * list of what to wire, and none of them is invented from a capacity heuristic.
78 *
79 * REFERENCE DEFECTS in solver_mam_retrial.m and qsys_bmapphnn_retrial.m:
80 *
81 * 1. `Tolerance` IS PARSED AND NEVER USED. `solver_mam_retrial.m` reads
82 * `options.tol`, defaults it to 1e-10 and forwards it as 'Tolerance';
83 * `qsys_bmapphnn_retrial.m` assigns it on line 83 and no later line reads
84 * it. The single dense solve behind the generator is direct, so there is no
85 * iteration for it to terminate. Not propagated: `MamOptions::tol` is
86 * likewise not forwarded here, and the comment records why rather than
87 * leaving a caller to wonder why lowering the tolerance changes nothing.
88 * 2. THE DEFAULT RETRIAL RATE IS A MAGIC 0.1. When no retrial process is
89 * attached, `alpha = 0.1` -- a rate with no relation to the model. It is
90 * reproduced, because a station carrying only a RETRIAL drop rule is a
91 * model the reference accepts and would otherwise be answered differently
92 * by the two codebases, but a caller reaching it has almost certainly
93 * forgotten `setRetrial`.
94 * 3. `extractPHParams` NORMALIZES A BAD beta INSTEAD OF REPORTING IT. When the
95 * entry vector recovered from D1 does not sum to one it is rescaled, and
96 * when it sums to zero it is replaced by the uniform vector. Both hide a
97 * malformed service representation. Not reproduced: `map_pie` is the
98 * codebase's own embedded entry vector and is exact for any Markovian pair,
99 * and a pair that is not Markovian is refused above by name.
100 *
101 * ARITHMETIC. The generator is a rational expression in its inputs and the
102 * stationary law is one linear solve, so nothing here needs a transcendental
103 * function and this analyzer carries no `has_transcendental` gate -- unlike
104 * `solver_mam_basic`, whose station solves run tolerance-terminated Riccati
105 * iterations. `qsys_bmapphnn_retrial` documents the same property, and the
106 * exact instantiation returns the stationary law of the truncated chain
107 * exactly.
108 */
109
110#include <algorithm>
111#include <cmath>
112#include <cstddef>
113#include <limits>
114#include <map>
115#include <string>
116#include <vector>
117
124#include "line/util/error.h"
125#include "line/util/matrix.h"
126
127namespace line {
128namespace mam {
129
130/**
131 * The knobs `solver_mam_retrial.m` reads from `options.config` and from model
132 * fields the C++ `NetworkStruct` does not carry.
133 *
134 * Kept as a separate argument rather than added to `MamOptions`, so that the
135 * options every MAM analyzer shares do not grow a field only one of them reads.
136 * The defaults are the reference's.
137 */
139 /** `options.config.orbit_maxlevel`; 0 asks for the adaptive refinement. */
140 std::size_t orbit_maxlevel = 0;
141 /** `options.config.orbit_tailtol`, the relative orbit-truncation target. */
142 double orbit_tailtol = 1e-6;
143 /** `'MaxDim'`: the largest generator dimension the refinement may reach. */
144 double max_dim = 2e5;
145 /** `'MaxBlockSize'`: the largest per-level block V*d that may be assembled. */
146 double max_block = 5e3;
147};
148
149/** What `qsys_is_retrial.m` returns: the station it found, or why it found none. */
151 bool ok = false;
152 std::size_t station = 0; ///< 1-based station index of the retrial queue
153 std::size_t source = 0; ///< 1-based station index of the Source
154 std::size_t cls = 1; ///< 1-based class index; the reference is single-class
155 int nservers = 0; ///< N
156 long R = 0; ///< admission threshold, N-1 unless an FCR lowers it
157 std::string why; ///< the reference's `errorMsg`, empty when ok
158};
159
160namespace retrial_detail {
161
162/** Does any class at this station declare a retrial process? */
163template <class T>
164bool has_retrial_proc(const qn::NetworkStruct<T>& L, std::size_t ist) {
165 const typename std::map<std::size_t, qn::RetrialParam<T> >::const_iterator it =
166 L.retrialparam.find(ist);
167 if (it == L.retrialparam.end()) return false;
168 for (std::size_t r = 0; r < it->second.retrial_proc.size(); ++r)
169 if (!it->second.retrial_proc[r].disabled) return true;
170 return false;
171}
172
173/**
174 * The reference's `assertMarkovian`, as a refusal naming the station and the
175 * class. A process that is not a (D0, D1) pair as declared has no place in a
176 * matrix-analytic generator, and the C++ layer would hand it over as an Erlang
177 * of matching moments instead of reporting it.
178 */
179template <class T>
180void assert_markovian(const qn::NetworkStruct<T>& L, std::size_t ist, std::size_t r,
181 const char* role) {
182 const lang::Distrib<T>& d = L.service[ist - 1][r - 1];
183 if (L.disabled[ist - 1][r - 1] || d.disabled)
184 throw UnsupportedError(std::string("SolverMAM: the ") + role + " process of class '" +
185 L.classes[r - 1].name + "' at station '" + L.stations[ist - 1].name +
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))
189 throw UnsupportedError(
190 std::string("SolverMAM: the ") + role + " process of class '" + L.classes[r - 1].name +
191 "' at station '" + L.stations[ist - 1].name + "' is " + lang::process_to_text(d.type) +
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");
196 const Map<T> m = lang::dist_to_map(d);
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 '" +
200 L.stations[ist - 1].name +
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 "
203 "zero)");
204}
205
206/**
207 * The per-level block width V*d of the generator, where d counts the multisets
208 * of service phases over the busy servers.
209 *
210 * Computed BEFORE the first solve, because the engine assembles one dense
211 * (truncLevel + 1) * V*d square matrix and an oversized block has to be
212 * reported rather than attempted. In double throughout: the binomials overflow
213 * an integer long before they reach a block size anyone would wait for.
214 */
215inline double retrial_block_width(std::size_t V, std::size_t M, int N) {
216 double d = 0.0, term = 1.0; // term = C(n + M - 1, M - 1), starting at n = 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);
219 d += term;
220 if (d > 1e12) return 1e12; // already past any usable cap; stop growing
221 }
222 return static_cast<double>(V) * d;
223}
224
225/**
226 * `orbitTruncationError`: the relative contribution the truncated tail would
227 * add to the mean orbit length, which the mass sitting at the top level bounds.
228 */
229template <class T>
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()
234 ? lorbit
235 : std::numeric_limits<double>::min();
236 return static_cast<double>(r.truncLevel) * top / denom;
237}
238
239} // namespace retrial_detail
240
241/**
242 * Port of `qsys_is_retrial.m`, plus the reneging gate of `solver_mam_retrial.m`.
243 *
244 * Returns rather than throws, because `solver_mam_analyzer.m` uses it as the
245 * 2c/2d branch predicate and only errors once every branch has declined. The
246 * rejection reason travels in `why` so the dispatch can quote it.
247 */
248template <class T>
250 using lang::DropStrategy;
251 MamRetrialInfo info;
252
253 // A mixed model has a closed chain whose population the orbit generator has
254 // no level for, so the predicate must be "every class open", not "any".
255 if (!L.is_open_model()) {
256 info.why = "the BMAP/PH/N/N retrial solver requires an open queueing model";
257 return info;
258 }
259 if (L.nclasses != 1) {
260 info.why = "the BMAP/PH/N/N retrial solver supports a single class only";
261 return info;
262 }
263 info.cls = 1;
264
265 // A retrial station is one that DECLARES an orbit, or one that is
266 // bufferless: capacity equal to the server count leaves no waiting line, so
267 // a blocked job has nowhere to go but an orbit.
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);
274 }
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)";
278 return info;
279 }
280
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) {
285 info.station = i;
286 break;
287 }
288 if (info.station != 0) break;
289 }
290 if (info.station == 0) {
291 info.why = "no RETRIAL drop strategy is configured on a bufferless queue";
292 return info;
293 }
294
295 for (std::size_t i = 1; i <= L.nstations; ++i)
296 if (L.stations[i - 1].nodetype == qn::NodeType::Source) {
297 info.source = i;
298 break;
299 }
300 if (info.source == 0) {
301 info.why = "no Source node found";
302 return info;
303 }
304
305 const double ns = L.stations[info.station - 1].nservers;
306 if (!std::isfinite(ns) || ns < 1.0) {
307 info.why = "the retrial station must have a finite, positive server count";
308 return info;
309 }
310 info.nservers = static_cast<int>(ns);
311 // The reference lowers R below N-1 only from a Finite Capacity Region, and
312 // NetworkStruct has no region field, so N-1 is the only reachable value:
313 // a retrial succeeds exactly when a server is free.
314 info.R = static_cast<long>(info.nservers) - 1;
315 info.ok = true;
316 return info;
317}
318
319/**
320 * The 'retrial' method's applicability as one sentence; empty when applicable.
321 *
322 * A "MUST BE PRESENT" RULE, which is why it cannot live in a feature set: a
323 * FeatureSet says "I accept this construct", so it can refuse a model for HAVING
324 * something and never for LACKING it. This analyzer needs the BMAP/PH/N/N
325 * bufferless retrial topology to work on, and a model without one is not a
326 * smaller retrial model, it is a different one.
327 *
328 * ONE PREDICATE, THREE CALLERS: `mam_dispatch`'s 'retrial' arm raises it,
329 * `runner_detail::check_model_method` raises it ahead of the dispatch, and
330 * `autosolver::auto_family_refusal` returns it through
331 * `mam_model_method_refusal`, so the method the report offers and the method
332 * that runs are the same set.
333 */
334template <class T>
336 const MamRetrialInfo info = mam_retrial_detect(L);
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'";
340}
341
342/**
343 * Port of `solver_mam_retrial.m`.
344 *
345 * @param L the refreshed struct, with non-Markovian processes already gated
346 * out by the runner
347 * @param opt the MAM options; none of them reaches the engine, see defect 1
348 * @param cfg the orbit truncation controls
349 */
350template <class T>
352 const MamRetrialConfig& cfg = MamRetrialConfig()) {
353 using lang::DropStrategy;
354 (void)opt; // see defect 1: the reference forwards options.tol and nothing reads it
355 const T zero = num_traits<T>::from_int(0);
356
357 const MamRetrialInfo info = mam_retrial_detect(L);
358 if (!info.ok)
359 throw UnsupportedError(
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");
365
366 const std::size_t q = info.station, src = info.source, r = info.cls;
367
368 retrial_detail::assert_markovian(L, src, r, "arrival");
369 retrial_detail::assert_markovian(L, q, r, "service");
370
371 // A bounded retry count is a per-job attribute; the orbit level counts jobs
372 // and nothing else, so a job on its last attempt is indistinguishable there.
373 if (L.droprule[q - 1][r - 1] == DropStrategy::RETRIAL_WITH_LIMIT)
374 throw UnsupportedError(
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");
379
380 // ---- the arrival BMAP ---------------------------------------------------
381 // A genuine BATCH arrival would give D = {D0, D1, ..., DK}; lang::Distrib
382 // holds a single (D0, D1) pair, so every model the C++ layer can build is
383 // the K = 1 case. The engine takes the general list, so nothing is lost
384 // here beyond the batch models the model layer cannot express.
385 const Map<T> arv = lang::dist_to_map(L.service[src - 1][r - 1]);
386 std::vector<Matrix<T> > D;
387 D.push_back(arv.D0);
388 D.push_back(arv.D1);
389
390 // ---- the PH service -----------------------------------------------------
391 const Map<T> svc = lang::dist_to_map(L.service[q - 1][r - 1]);
392 const Matrix<T> S = svc.D0;
393 const std::vector<T> beta = map_pie(svc);
394
395 // ---- the orbit parameters ----------------------------------------------
396 // The retrial process is an inter-retry TIME, and the generator carries a
397 // single scalar rate per orbiting job, so only a one-phase process can be
398 // read. The reference takes -D0(1,1) whatever the order is, which for a
399 // multi-phase process is the exit rate of its FIRST phase and not the mean
400 // retry rate of anything.
401 T alpha = num_traits<T>::from_double(0.1); // see defect 2
402 const typename std::map<std::size_t, qn::RetrialParam<T> >::const_iterator rit =
403 L.retrialparam.find(q);
404 if (rit != L.retrialparam.end()) {
405 const qn::RetrialParam<T>& rp = rit->second;
406 if (rp.max_attempts.size() >= r && rp.max_attempts[r - 1] > 0)
407 throw UnsupportedError(
408 "SolverMAM: station '" + L.stations[q - 1].name + "' bounds the retrial count at " +
409 std::to_string(rp.max_attempts[r - 1]) +
410 " attempts. The BMAP/PH/N/N generator has no per-job attempt counter in its state "
411 "descriptor. Use SolverCTMC or SolverSSA");
412 if (rp.retrial_proc.size() >= r && !rp.retrial_proc[r - 1].disabled) {
413 const Map<T> ret = lang::dist_to_map(rp.retrial_proc[r - 1]);
414 if (ret.order() != 1)
415 throw UnsupportedError(
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));
424 } else if (rp.retrial_rate.size() >= r && rp.retrial_rate[r - 1] > zero) {
425 alpha = rp.retrial_rate[r - 1];
426 }
427 }
428 if (!(alpha > zero))
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");
432
433 const T gamma = zero; // sn.orbitImpatience, absent from NetworkStruct
434 const T p = zero; // sn.batchRejectProb, absent from NetworkStruct
435
436 // ---- the truncation, and the refinement the engine leaves to its caller --
437 const double blockw =
438 retrial_detail::retrial_block_width(arv.order(), svc.order(), info.nservers);
439 if (blockw > cfg.max_block)
440 throw UnsupportedError(
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");
449
450 const std::vector<long> Rv(1, info.R);
452 qopt.maxLevel = cfg.orbit_maxlevel;
454 qsys::qsys_bmapphnn_retrial(D, beta, S, info.nservers, alpha, gamma, p, Rv, qopt);
455
456 if (cfg.orbit_maxlevel == 0) {
457 // The reference doubles the level until the tail contribution is under
458 // TailTolerance. `blockw` is the per-level width, so (level + 1) * blockw
459 // is the dimension the next solve would assemble.
460 while (retrial_detail::orbit_trunc_error(res) > cfg.orbit_tailtol) {
461 const std::size_t next = 2 * res.truncLevel;
462 if (static_cast<double>(next + 1) * blockw > cfg.max_dim)
463 throw NumericError(
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");
473 qopt.maxLevel = next;
474 res = qsys::qsys_bmapphnn_retrial(D, beta, S, info.nservers, alpha, gamma, p, Rv, qopt);
475 }
476 }
477
478 // ---- the LINE metric tuple ---------------------------------------------
480 const std::size_t M = L.nstations, K = L.nclasses;
481 out.Q = Matrix<T>(M, K, zero);
482 out.U = Matrix<T>(M, K, zero);
483 out.R = Matrix<T>(M, K, zero);
484 out.Tp = Matrix<T>(M, K, zero);
485 out.C.assign(K, zero);
486 out.X.assign(K, zero);
487
488 // The orbit is part of the station: a job waiting to retry has not left it.
489 out.Q(q - 1, r - 1) = T(res.L_orbit + res.N_server);
490 out.U(q - 1, r - 1) = res.utilization;
491 out.Tp(q - 1, r - 1) = res.throughput;
492 if (res.throughput > zero)
493 out.R(q - 1, r - 1) = T(out.Q(q - 1, r - 1) / res.throughput);
494 // else the reference writes Inf, and the NaN sweep at the end of
495 // mam_dispatch zeroes every non-finite entry before any caller sees it; a
496 // zero written here is the same value by a route that also holds at
497 // T = Rational, which has no infinity to write.
498
499 out.X[r - 1] = res.throughput;
500 out.C[r - 1] = out.R(q - 1, r - 1);
501 // The reference's `totiter` for this analyzer is the truncation level that
502 // produced the answer, not an iteration count.
503 out.iter = static_cast<int>(res.truncLevel);
504 return out;
505}
506
507} // namespace mam
508} // namespace line
509
510#endif // LINE_SOLVERS_MAM_SOLVER_MAM_RETRIAL_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
UnsupportedError(const std::string &what)
Definition error.h:51
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.
Definition lang_types.h:424
const char * process_to_text(ProcessType p)
The MATLAB ProcessType name, as sn.procid prints it.
Definition lang_types.h:560
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Definition map_moment.h:89
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.
Definition mam_types.h:29
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).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54
std::size_t order() const
Definition map_moment.h:57
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:96
std::vector< T > X
Definition mva_types.h:98
std::vector< T > C
Definition mva_types.h:98
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 N_server
mean number of busy servers