LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_retrial_isstable.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_RETRIAL_ISSTABLE_H
6#define LINE_API_QSYS_QSYS_RETRIAL_ISSTABLE_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Stability (positive recurrence) of the M/M/c/c retrial queue.
12 *
13 * Port of matlab/src/api/qsys/qsys_retrial_isstable.m. Poisson arrivals of
14 * rate lambda_k, c exponential servers of rate mu_k, exponential retrials of
15 * rate alpha_k (per orbiting customer under LINEAR, of the whole orbit under
16 * CONSTANT), join probability p_k, persistence probability q_k, orbit
17 * abandonment rate gamma_k and orbit capacity maxOrbit (-1 = unbounded).
18 *
19 * CONDITIONS:
20 * A finite orbit, gamma > 0, or q < 1 under LINEAR always gives a stable
21 * chain: the orbit then loses customers at a rate that grows with its size.
22 * LINEAR with q = 1 and gamma = 0: stable iff sum_k lambda_k p_k / mu_k < c
23 * (Falin and Templeton 1997, Sec. 2.1 for p = 1).
24 * CONSTANT, single class, gamma = 0: with x = B((lambda+alpha)/mu, c) the
25 * orbit drift is negative iff lambda p x < alpha (1-x) + alpha (1-q) x
26 * (Artalejo, Gomez-Corral and Neuts 2001 for p = q = 1).
27 *
28 * A multiclass queue under CONSTANT retrial, or with some classes self-
29 * limiting and others not, has no known necessary-and-sufficient condition
30 * and is refused (LINE:unsupported:Retrial).
31 *
32 * The margin is infinite when the orbit cannot grow without bound, so the
33 * function needs a number type that holds an infinity (double or real).
34 */
35
36#include <cmath>
37#include <cstddef>
38#include <limits>
39#include <string>
40#include <vector>
41
44#include "line/num/number.h"
45#include "line/util/error.h"
46
47namespace line {
48namespace qsys {
49
50/** Return value of qsys_retrial_isstable, mirroring the three MATLAB outputs. */
51template <class T>
53 bool isStable = false; ///< the orbit size is positive recurrent
54 T margin; ///< slack of the condition (positive iff stable; +Inf when the orbit cannot grow)
55 std::string reason; ///< the condition applied, as text
56};
57
59
60/** A scalar broadcast to K entries, or a K-vector left as it is. */
61template <class T>
62std::vector<T> expand(const std::vector<T>& v, std::size_t K, const char* name, const T& def) {
63 if (v.empty()) return std::vector<T>(K, def);
64 if (v.size() == 1) return std::vector<T>(K, v[0]);
65 if (v.size() != K)
66 throw InputError(std::string("qsys_retrial_isstable: ") + name +
67 " must be a scalar or have one entry per class (" + std::to_string(K) + ").");
68 return v;
69}
70
71} // namespace retrial_isstable_detail
72
73/**
74 * @param lambda arrival rate(s), one per class
75 * @param mu service rate(s) per server (scalar or one per class)
76 * @param c number of servers (no waiting room)
77 * @param alpha retrial rate(s); empty = 0
78 * @param policy LINEAR (default) or CONSTANT
79 * @param p join probability(ies), empty = 1
80 * @param q persistence probability(ies), empty = 1
81 * @param gamma orbit abandonment rate(s) per orbiting customer, empty = 0
82 * @param maxOrbit orbit capacity, negative or infinite for unbounded (default -1)
83 */
84template <class T>
85RetrialStabilityResult<T> qsys_retrial_isstable(const std::vector<T>& lambda, const std::vector<T>& mu,
86 unsigned c, const std::vector<T>& alpha,
88 const std::vector<T>& p = std::vector<T>(),
89 const std::vector<T>& q = std::vector<T>(),
90 const std::vector<T>& gamma = std::vector<T>(),
91 double maxOrbit = -1.0) {
93 "qsys_retrial_isstable reports an infinite margin, which needs a type holding Inf");
95 const T zero = num_traits<T>::from_int(0);
96 const T one = num_traits<T>::from_int(1);
97 const T inf = num_traits<T>::from_double(std::numeric_limits<double>::infinity());
98 const std::size_t K = lambda.size();
99 if (K == 0) throw InputError("qsys_retrial_isstable: lambda must have at least one entry.");
100 const std::vector<T> muv = expand(mu, K, "mu", one);
101 const std::vector<T> pv = expand(p, K, "p", one);
102 const std::vector<T> qv = expand(q, K, "q", one);
103 const std::vector<T> gv = expand(gamma, K, "gamma", zero);
104 const std::vector<T> av = expand(alpha, K, "alpha", zero);
105 bool bad = c < 1;
106 for (std::size_t k = 0; k < K; ++k)
107 bad = bad || lambda[k] < zero || !(muv[k] > zero) || pv[k] < zero || pv[k] > one ||
108 qv[k] < zero || qv[k] > one || gv[k] < zero;
109 if (bad)
110 throw InputError("qsys_retrial_isstable: Invalid input: rates must be positive, c >= 1, and p, q in [0, 1].");
111
113 if (std::isfinite(maxOrbit) && maxOrbit >= 0) {
114 out.isStable = true;
115 out.margin = inf;
116 out.reason = "finite orbit: the chain is finite";
117 return out;
118 }
119 std::vector<bool> selfLimiting(K);
120 bool all = true, anyLim = false;
121 for (std::size_t k = 0; k < K; ++k) {
122 selfLimiting[k] = gv[k] > zero || (policy == RetrialPolicy::LINEAR && qv[k] < one) ||
123 pv[k] == zero || lambda[k] == zero;
124 all = all && selfLimiting[k];
125 anyLim = anyLim || selfLimiting[k];
126 }
127 if (all) {
128 out.isStable = true;
129 out.margin = inf;
130 out.reason = "every orbit loses customers at a rate growing with its size";
131 return out;
132 }
133 if (policy == RetrialPolicy::LINEAR) {
134 if (anyLim)
135 throw UnsupportedError(ErrorKind{"Retrial"},
136 "qsys_retrial_isstable: No known stability condition for a multiclass retrial queue "
137 "that mixes persistent classes with self-limiting ones (q < 1 or orbit impatience).");
138 for (std::size_t k = 0; k < K; ++k)
139 if (!(av[k] > zero)) {
140 out.isStable = false;
141 out.margin = T(zero - inf);
142 out.reason = "LINEAR retrial with zero retrial rate: the orbit never empties";
143 return out;
144 }
145 T load = zero;
146 for (std::size_t k = 0; k < K; ++k) load = T(load + lambda[k] * pv[k] / muv[k]);
147 out.margin = T(num_traits<T>::from_int(static_cast<long>(c)) - load);
148 out.isStable = out.margin > zero;
149 out.reason = "LINEAR retrial, persistent orbit: sum(lambda.*p./mu) < c";
150 return out;
151 }
152 // CONSTANT
153 if (K > 1)
154 throw UnsupportedError(ErrorKind{"Retrial"},
155 "qsys_retrial_isstable: No known necessary-and-sufficient stability condition for a "
156 "multiclass retrial queue under the CONSTANT policy.");
157 if (!(av[0] > zero)) {
158 out.isStable = false;
159 out.margin = T(zero - inf);
160 out.reason = "CONSTANT retrial with zero retrial rate: the orbit never empties";
161 return out;
162 }
163 const T x = erlang_b_recursive(T((lambda[0] + av[0]) / muv[0]), c);
164 out.margin = T(av[0] * (one - x) + av[0] * (one - qv[0]) * x - lambda[0] * pv[0] * x);
165 out.isStable = out.margin > zero;
166 out.reason = "CONSTANT retrial: lambda*p*x < alpha*(1-x) + alpha*(1-q)*x, x = ErlangB((lambda+alpha)/mu, c)";
167 return out;
168}
169
170} // namespace qsys
171} // namespace line
172
173#endif // LINE_API_QSYS_QSYS_RETRIAL_ISSTABLE_H
InputError(const std::string &what)
Definition error.h:105
UnsupportedError(const std::string &what)
Definition error.h:119
The exception types the port throws.
std::vector< T > expand(const std::vector< T > &v, std::size_t K, const char *name, const T &def)
A scalar broadcast to K entries, or a K-vector left as it is.
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,...
RetrialStabilityResult< T > qsys_retrial_isstable(const std::vector< T > &lambda, const std::vector< T > &mu, unsigned c, const std::vector< T > &alpha, RetrialPolicy policy=RetrialPolicy::LINEAR, const std::vector< T > &p=std::vector< T >(), const std::vector< T > &q=std::vector< T >(), const std::vector< T > &gamma=std::vector< T >(), double maxOrbit=-1.0)
RetrialPolicy
Retrial admission policy (RetrialPolicy in MATLAB).
@ LINEAR
each orbiting customer retries at rate alpha (orbit rate i*alpha)
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
Fixed-point approximation for the M/M/c/c retrial queue.
The retrial policy shared by the qsys retrial functions (MATLAB RetrialPolicy).
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Return value of qsys_retrial_isstable, mirroring the three MATLAB outputs.
T margin
slack of the condition (positive iff stable; +Inf when the orbit cannot grow)
std::string reason
the condition applied, as text
bool isStable
the orbit size is positive recurrent