LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_tandem_ub_ciucu.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_TANDEM_UB_CIUCU_H
6#define LINE_API_QSYS_QSYS_TANDEM_UB_CIUCU_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Tail bounds for a GI/Hn/1 -> ./Hn/1 tandem of two FCFS single servers.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_tandem_ub_ciucu.m, cross-checked
14 * against jar/src/main/java/jline/api/qsys/Qsys_tandem_ub_ciucu.java. Both
15 * stations serve the same hyperexponential law Y, Z ~ sum_i p_i Exp(mu_i), a
16 * single phase giving exponential service, and the arrivals are renewal with a
17 * light-tailed interarrival time supplied through its Laplace-Stieltjes
18 * transform E[e^{-s X}].
19 *
20 * With theta the positive root of E[e^{theta (Y-X)}] = 1 and
21 * alpha = E[X e^{-theta X}], the test function
22 *
23 * gamma(u,v) = 1{0<=u<=v} [1 - A e^{-theta u} - (B + C u + D v) e^{-theta v}]
24 *
25 * satisfies the integral inequality of Theorem 1(b) of the reference once the
26 * five sufficient conditions of its Lemma 4 fix
27 *
28 * A = 1, C = theta sum_i p_i/(mu_i-theta) / sum_i p_i mu_i/(mu_i-theta)^2,
29 * D = (-C E[U e^{theta V}]/E[V e^{theta V}]) v 0, U = Y-X, V = Z-X,
30 * B = C (1/mu_1 - alpha E[e^{theta Z}]) if D = 0,
31 * = (C+D)/(mu_1-theta) - theta/mu_1 if D > 0,
32 *
33 * with mu_1 the smallest service rate. Corollary 2 then turns gamma into
34 *
35 * P(S > x) <= sum_i p_i { e^{-mu_i x}
36 * + mu_i/(mu_i-theta) (A+B) (e^{-theta x} - e^{-mu_i x})
37 * + mu_i/(mu_i-theta)^2 (C+D) (((mu_i-theta)x-1) e^{-theta x}
38 * + e^{-mu_i x}) }
39 *
40 * and the corresponding closed form for W when the service is exponential.
41 * E[V e^{theta V}] is positive at any stable load, so D is always well defined:
42 * h(s) = E[e^{s(Z-X)}] is convex with h(0) = h(theta) = 1, hence h'(theta) > 0.
43 *
44 * The two exponentials mix a polynomial of degree one in x, which is what lets
45 * the bound follow the concave bend of the tail on a linear-log scale where a
46 * purely exponential bound cannot. In the M/M/1 -> ./M/1 case the five
47 * inequalities hold as equalities, so gamma is the exact joint distribution and
48 * both bounds are exact, P(S > x) = (1 + theta x) e^{-theta x}. Away from it the
49 * bound stays sharp: against an exact CTMC reference for the Erlang(2)/M/1 ->
50 * ./M/1 tandem it is within 2% at P(S>x) = 1e-2 and within 0.6% at 5e-10, with
51 * the correct asymptotic slope theta^2/(mu(1-alpha mu)). Accuracy degrades with
52 * service variability, to about a factor of two at CV(Y) = 2.
53 *
54 * Reference: F. Ciucu, S. Mehri, "On the Distribution of Sojourn Times in Tandem
55 * Queues", Proc. ACM Meas. Anal. Comput. Syst. 9(2), Article 27, 2025 (ACM
56 * SIGMETRICS 2025). Registered in .citations() as 'tandemub'.
57 *
58 * ARITHMETIC: transcendental. theta is a root of a transcendental equation and
59 * the bound is a mix of exponentials, so this instantiates only at backends
60 * carrying exp; the bracketing solve itself is field arithmetic and
61 * deterministic, so the digits do not depend on a starting point.
62 */
63
64#include <cstddef>
65#include <functional>
66#include <limits>
67#include <string>
68#include <vector>
69
71#include "line/num/number.h"
72#include "line/util/error.h"
73#include "line/util/rootfind.h"
74
75namespace line {
76namespace qsys {
77
78/** Mirrors the struct MATLAB returns from qsys_tandem_ub_ciucu. */
79template <class T>
81 std::vector<T> S; ///< upper bound on P(S > x), one per threshold, capped at one
82 std::vector<T> W; ///< upper bound on P(W > x), NaN unless the service is exponential
83 T theta; ///< tail decay rate, positive root of E[e^{theta (Y-X)}] = 1
84 T alpha; ///< E[X e^{-theta X}]
85 T A; ///< coefficient A of gamma, fixed by Lemma 4
86 T B; ///< coefficient B of gamma, fixed by Lemma 4
87 T C; ///< coefficient C of gamma, fixed by Lemma 4
88 T D; ///< coefficient D of gamma, fixed by Lemma 4
89 std::string analyzer;
90};
91
92/**
93 * @brief Tail bounds for a GI/Hn/1 -> ./Hn/1 tandem of two FCFS single
94 * servers.
95 *
96 * @param x thresholds at which the tails are bounded, nonnegative
97 * @param lst interarrival transform, s -> E[e^{-s X}] for s >= 0
98 * @param p service phase probabilities, nonnegative and summing to one
99 * @param mu service phase rates, positive
100 * @param dlst s -> E[X e^{-s X}], minus the derivative of lst; empty to obtain
101 * it by a Richardson-extrapolated central difference, which costs
102 * four extra transform evaluations and loses roughly four digits
103 */
104template <class T>
106 const std::function<T(const T&)>& lst,
107 const std::vector<T>& p, const std::vector<T>& mu,
108 const std::function<T(const T&)>& dlst =
109 std::function<T(const T&)>()) {
111 "qsys_tandem_ub_ciucu requires transcendental arithmetic");
112 const T zero = num_traits<T>::from_int(0);
113 const T one = num_traits<T>::from_int(1);
114 const T two = num_traits<T>::from_int(2);
115
116 if (x.empty()) throw InputError("qsys_tandem_ub_ciucu: x must hold at least one threshold");
117 if (!lst) throw InputError("qsys_tandem_ub_ciucu: lst must be supplied");
118 if (p.empty() || p.size() != mu.size())
119 throw InputError("qsys_tandem_ub_ciucu: p and mu must have the same number of phases");
120 T psum = zero;
121 for (std::size_t i = 0; i < p.size(); ++i) {
122 if (p[i] < zero)
123 throw InputError("qsys_tandem_ub_ciucu: the phase probabilities p must be nonnegative");
124 if (mu[i] <= zero)
125 throw InputError("qsys_tandem_ub_ciucu: the service rates mu must be positive");
126 psum = psum + p[i];
127 }
128 const T tolp = num_traits<T>::from_rational(1, 10000000000L);
129 if (psum - one > tolp || one - psum > tolp)
130 throw InputError("qsys_tandem_ub_ciucu: the phase probabilities p must sum to one");
131 for (std::size_t k = 0; k < x.size(); ++k)
132 if (x[k] < zero) throw InputError("qsys_tandem_ub_ciucu: the thresholds x must be nonnegative");
133
134 T mu1 = mu[0];
135 for (std::size_t i = 1; i < mu.size(); ++i)
136 if (mu[i] < mu1) mu1 = mu[i];
137
138 // E[e^{t Y}] of the hyperexponential service law, for t below every rate.
139 const auto mgfY = [&p, &mu](const T& t) {
140 T acc = num_traits<T>::from_int(0);
141 for (std::size_t i = 0; i < p.size(); ++i) acc = acc + p[i] * mu[i] / (mu[i] - t);
142 return acc;
143 };
144 const auto residual = [&mgfY, &lst, &one](const T& t) { return T(mgfY(t) * lst(t) - one); };
145
146 // Stability: E[X] > E[Y] is what makes E[e^{t(Y-X)}] - 1 cross zero on (0,mu1).
147 const T shrink = one - num_traits<T>::from_rational(1, 1000000000000L);
148 const T hi = mu1 * shrink;
149 if (residual(hi) <= zero)
150 throw InputError("qsys_tandem_ub_ciucu: no positive root of E[e^{theta(Y-X)}]=1 below "
151 "min(mu): the tandem is unstable or the service is not the lighter tail");
152 const T ten = num_traits<T>::from_int(10);
153 T lo = mu1 * num_traits<T>::from_rational(1, 1000000000000L);
154 const T lomin = mu1 * num_traits<T>::from_rational(1, 1000000000000L) /
156 while (residual(lo) >= zero && lo > lomin) lo = lo / ten; // walk below the root at zero
157 if (residual(lo) >= zero)
158 // E[e^{t(Y-X)}]-1 is convex and vanishes at t=0, so it stays positive on the
159 // whole of (0,mu1) exactly when its slope E[Y]-E[X] there is nonnegative.
160 throw InputError("qsys_tandem_ub_ciucu: the tandem is unstable, E[X] <= E[Y]: "
161 "theta = 0 is the only root of E[e^{theta(Y-X)}]=1");
162 const T tol = num_traits<T>::from_rational(1, 1000000000L) /
164 const RootResult<T> rr = root_brent<T, decltype(residual)>(residual, lo, hi, tol);
165 const T theta = rr.root;
166
167 T alpha;
168 if (dlst) {
169 alpha = dlst(theta);
170 } else {
171 // Richardson-extrapolated central difference of -lst at theta.
172 T h = num_traits<T>::from_rational(1, 1000) * (one + theta);
173 if (h > theta) h = theta / two;
174 const T d1 = (lst(T(theta - h)) - lst(T(theta + h))) / (two * h);
175 const T d2 = (lst(T(theta - h / two)) - lst(T(theta + h / two))) / h;
176 alpha = (num_traits<T>::from_int(4) * d2 - d1) / num_traits<T>::from_int(3);
177 }
178
179 const T EexpZ = mgfY(theta); // E[e^{theta Z}]
180 T EZexp = zero; // E[Z e^{theta Z}]
181 T EY = zero;
182 T sumPOverMuMinusTheta = zero;
183 for (std::size_t i = 0; i < p.size(); ++i) {
184 const T dm = mu[i] - theta;
185 EZexp = EZexp + p[i] * mu[i] / (dm * dm);
186 EY = EY + p[i] / mu[i];
187 sumPOverMuMinusTheta = sumPOverMuMinusTheta + p[i] / dm;
188 }
189 const T A = one;
190 const T C = theta * sumPOverMuMinusTheta / EZexp;
191 const T EUeV = EY - alpha * EexpZ; // E[U e^{theta V}]
192 const T EVeV = EZexp / EexpZ - alpha * EexpZ; // E[V e^{theta V}] > 0
193 T D = -C * EUeV / EVeV;
194 if (!(D > zero)) D = zero;
195 const T B = D > zero ? T((C + D) / (mu1 - theta) - theta / mu1)
196 : T(C * (one / mu1 - alpha * EexpZ));
197
199 r.theta = theta;
200 r.alpha = alpha;
201 r.A = A;
202 r.B = B;
203 r.C = C;
204 r.D = D;
205 r.analyzer = "qsys_tandem_ub_ciucu";
206 r.S.reserve(x.size());
207 r.W.reserve(x.size());
208 for (std::size_t k = 0; k < x.size(); ++k) {
209 T acc = zero;
210 const T et = detail::num_exp(T(-theta * x[k]));
211 for (std::size_t i = 0; i < p.size(); ++i) {
212 const T m = mu[i];
213 const T dm = m - theta;
214 const T em = detail::num_exp(T(-m * x[k]));
215 acc = acc + p[i] * (em + m / dm * (A + B) * (et - em) +
216 m / (dm * dm) * (C + D) * ((dm * x[k] - one) * et + em));
217 }
218 r.S.push_back(acc < one ? acc : one);
219 }
220 if (p.size() == 1) {
221 const T beta = lst(mu1); // E[e^{-mu X}]
222 for (std::size_t k = 0; k < x.size(); ++k) {
223 const T et = detail::num_exp(T(-theta * x[k]));
224 T val;
225 if (D == zero) {
226 val = (one - two * theta * theta / (mu1 * (mu1 + theta)) +
227 theta * (mu1 - theta) / (mu1 + theta) * x[k]) * et +
228 beta * (theta * mu1 * alpha / (two * (mu1 - theta)) - theta / (two * mu1)) *
229 detail::num_exp(T(-mu1 * x[k]));
230 } else {
231 val = (one - two * theta / mu1 +
232 two * theta * theta * (two - alpha * mu1) /
233 ((mu1 + theta) * (mu1 + theta) * (one - alpha * mu1)) +
234 theta * theta * (mu1 - theta) /
235 (mu1 * (mu1 + theta) * (one - alpha * mu1)) * x[k]) * et;
236 }
237 r.W.push_back(val < one ? val : one);
238 }
239 } else {
240 // The W form is Exp-service only; MATLAB returns NaN there and so does this.
241 for (std::size_t k = 0; k < x.size(); ++k)
242 r.W.push_back(num_traits<T>::from_double(std::numeric_limits<double>::quiet_NaN()));
243 }
244 return r;
245}
246
247} // namespace qsys
248} // namespace line
249
250#endif // LINE_API_QSYS_QSYS_TANDEM_UB_CIUCU_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
TandemUbResult< T > qsys_tandem_ub_ciucu(const std::vector< T > &x, const std::function< T(const T &)> &lst, const std::vector< T > &p, const std::vector< T > &mu, const std::function< T(const T &)> &dlst=std::function< T(const T &)>())
Tail bounds for a GI/Hn/1 -> .
RootResult< T > root_brent(F f, const T &a0, const T &b0, const T &tol, unsigned maxiter=200)
Brent's method on a bracket with a sign change.
Definition rootfind.h:130
Number-type abstraction for the templated API port.
Shared return type and arithmetic helpers for the templated qsys port.
Deterministic scalar root finding.
Outcome of a scalar solve.
Definition rootfind.h:52
T root
best estimate of the root
Definition rootfind.h:53
Mirrors the struct MATLAB returns from qsys_tandem_ub_ciucu.
std::vector< T > S
upper bound on P(S > x), one per threshold, capped at one
T theta
tail decay rate, positive root of E[e^{theta (Y-X)}] = 1
T B
coefficient B of gamma, fixed by Lemma 4
T D
coefficient D of gamma, fixed by Lemma 4
T C
coefficient C of gamma, fixed by Lemma 4
T A
coefficient A of gamma, fixed by Lemma 4
std::vector< T > W
upper bound on P(W > x), NaN unless the service is exponential