LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mmcc_retrial_fp.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_API_QSYS_QSYS_MMCC_RETRIAL_FP_H
6#define LINE_API_QSYS_QSYS_MMCC_RETRIAL_FP_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Fixed-point approximation for the M/M/c/c retrial queue.
12 *
13 * Port of matlab/src/api/qsys/qsys_mmcc_retrial_fp.m. Blocked customers join an
14 * orbit and retry; under the assumption that the retrial rate is small relative
15 * to the service rate the superposition of fresh and retrial arrivals is
16 * approximated by a Poisson stream of rate lambda + r, with r the solution of
17 *
18 * r = (lambda + r) B((lambda + r)/mu, c),
19 *
20 * B being Erlang's loss formula. Cohen (1957); Phung-Duc, "Retrial Queueing
21 * Models: A Survey on Theory and Applications" (2019), eq. (1).
22 *
23 * With non-persistent customers (Phung-Duc 2019, Sec. 3.2), a blocked fresh
24 * customer joins the orbit with probability p and a blocked retrial customer
25 * returns to it with probability q. The orbit then sends retrials at the rate
26 *
27 * r = lambda p B / (1 - q B), B = B((lambda + r)/mu, c),
28 *
29 * iterated as r <- lambda p B + q B r, which is Cohen's iteration when
30 * p = q = 1 (so the iteration count is unchanged there). A fresh customer is
31 * lost with probability B(1-p) + B p (1-q) B/(1 - q B), zero when p = q = 1.
32 *
33 * The reference evaluates B by the rational recursion
34 * B_k = a B_{k-1}/(k + a B_{k-1}), which is exactly what line::lossn::erlang_b
35 * declines to do (it goes through log/exp and says so). This port keeps the
36 * reference's recursion, so B itself is a rational function of a and needs no
37 * transcendental arithmetic. The static_assert is nonetheless present because
38 * the OUTER iteration is a fixed point tested against a tolerance: it converges
39 * geometrically but not in a finite number of field operations, and at Rational
40 * the iterates would grow without bound in representation size while never
41 * meeting an exact stopping rule.
42 *
43 * The iteration is a monotone increasing map of r started from 0 and bounded
44 * above by lambda/(1-B) at the fixed point, so it converges from below and the
45 * successive-difference test is a genuine stopping criterion rather than a
46 * heuristic. The reference does NOT report non-convergence; when maxiter is
47 * exhausted it silently returns the last iterate, and the port reports the
48 * iteration count so the caller can tell the two apart, exactly as the third
49 * MATLAB output niter does.
50 */
51
52#include <cstddef>
53#include <string>
54
55#include "line/num/number.h"
56#include "line/util/error.h"
57
58namespace line {
59namespace qsys {
60
61/** Return value of qsys_mmcc_retrial_fp, mirroring the four MATLAB outputs. */
62template <class T>
64 T blockingProbability; ///< B((lambda + r)/mu, c)
65 T retrialRate; ///< r, the extra arrival rate contributed by the orbit
66 T lossProbability; ///< probability that a fresh customer is eventually lost
67 std::size_t iterations; ///< iterations performed, = maxiter when unconverged
68 bool converged; ///< whether the successive-difference test was met
69};
70
71/**
72 * Erlang's loss formula B(a, c) by the numerically stable rational recursion
73 * B_0 = 1, B_k = a B_{k-1}/(k + a B_{k-1}). Exact in any field: no logs, no
74 * factorials, no cancellation.
75 */
76template <class T>
77T erlang_b_recursive(const T& a, unsigned c) {
79 for (unsigned i = 1; i <= c; ++i) b = a * b / (num_traits<T>::from_int(static_cast<long>(i)) + a * b);
80 return b;
81}
82
83/**
84 * M/M/c/c with retrials by the Cohen fixed point.
85 *
86 * @param lambda fresh arrival rate
87 * @param mu service rate of one server
88 * @param c number of servers, which is also the capacity
89 * @param tol stopping tolerance on |r_{k+1} - r_k|
90 * @param maxiter iteration cap
91 * @param p probability that a blocked fresh customer joins the orbit (default 1)
92 * @param q probability that a blocked retrial customer stays in the orbit (default 1)
93 */
94template <class T>
95MmccRetrialFpResult<T> qsys_mmcc_retrial_fp(const T& lambda, const T& mu, unsigned c, const T& tol,
96 std::size_t maxiter,
97 const T& p = num_traits<T>::from_int(1),
98 const T& q = num_traits<T>::from_int(1)) {
100 "qsys_mmcc_retrial_fp is a tolerance-terminated fixed point");
101 const T zero = num_traits<T>::from_int(0);
102 if (lambda <= zero) throw InputError("qsys_mmcc_retrial_fp: arrival rate must be positive");
103 if (mu <= zero) throw InputError("qsys_mmcc_retrial_fp: service rate must be positive");
104 if (c == 0) throw InputError("qsys_mmcc_retrial_fp: at least one server is required");
105 if (tol <= zero) throw InputError("qsys_mmcc_retrial_fp: tolerance must be positive");
106 if (maxiter == 0) throw InputError("qsys_mmcc_retrial_fp: maxiter must be positive");
107 const T one = num_traits<T>::from_int(1);
108 if (p < zero || p > one || q < zero || q > one)
109 throw InputError("qsys_mmcc_retrial_fp: The join and persistence probabilities P and Q must be in [0, 1].");
110 if (!(q < one) && p > zero &&
111 !(lambda * p / mu < num_traits<T>::from_int(static_cast<long>(c)))) {
112 // a persistent orbit grows without bound: no fixed point exists
113 throw InputError(ErrorKind("Unstable"),
114 "qsys_mmcc_retrial_fp: unstable retrial queue: lambda*p/mu = " +
115 std::to_string(num_traits<T>::to_double(T(lambda * p / mu))) + " >= c = " +
116 std::to_string(c) + ", the persistent orbit grows without bound");
117 }
118
119 T r = zero;
120 std::size_t it = 0;
121 bool converged = false;
122 for (; it < maxiter;) {
123 ++it;
124 const T a = (lambda + r) / mu;
125 const T b = erlang_b_recursive(a, c);
126 // r = lambda*p*B + q*B*r, the same iteration as Cohen's r = (lambda + r)*B,
127 // to which it reduces when p = q = 1
128 const T rnew = lambda * p * b + q * b * r;
129 const T gap = num_abs(T(rnew - r));
130 r = rnew;
131 if (gap < tol) {
132 converged = true;
133 break;
134 }
135 }
136
138 out.retrialRate = r;
139 out.blockingProbability = erlang_b_recursive(T((lambda + r) / mu), c);
140 const T B = out.blockingProbability;
141 out.lossProbability = T(B * (one - p) + B * p * (one - q) * B / (one - q * B));
142 out.iterations = it;
143 out.converged = converged;
144 return out;
145}
146
147/** qsys_mmcc_retrial_fp with the reference defaults tol = 1e-10, maxiter = 10000. */
148template <class T>
149MmccRetrialFpResult<T> qsys_mmcc_retrial_fp(const T& lambda, const T& mu, unsigned c) {
150 return qsys_mmcc_retrial_fp(lambda, mu, c, T(num_traits<T>::from_double(1e-10)),
151 static_cast<std::size_t>(10000));
152}
153
154} // namespace qsys
155} // namespace line
156
157#endif // LINE_API_QSYS_QSYS_MMCC_RETRIAL_FP_H
InputError(const std::string &what)
Definition error.h:105
The exception types the port throws.
T erlang_b_recursive(const T &a, unsigned c)
Erlang's loss formula B(a, c) by the numerically stable rational recursion B_0 = 1,...
MmccRetrialFpResult< T > qsys_mmcc_retrial_fp(const T &lambda, const T &mu, unsigned c, const T &tol, std::size_t maxiter, const T &p=num_traits< T >::from_int(1), const T &q=num_traits< T >::from_int(1))
M/M/c/c with retrials by the Cohen fixed point.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_abs(const T &v)
Definition number.h:184
Number-type abstraction for the templated API port.
ErrorKind(std::string n)
Definition error.h:39
Return value of qsys_mmcc_retrial_fp, mirroring the four MATLAB outputs.
bool converged
whether the successive-difference test was met
T blockingProbability
B((lambda + r)/mu, c).
T retrialRate
r, the extra arrival rate contributed by the orbit
T lossProbability
probability that a fresh customer is eventually lost
std::size_t iterations
iterations performed, = maxiter when unconverged