LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mtginf.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_MTGINF_H
6#define LINE_API_QSYS_MTGINF_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Exact time-varying analysis of the Mt/G/infinity queue.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_mtginf.m, cross-checked against
14 * jar/src/main/java/jline/api/qsys/Qsys_mtginf.java.
15 *
16 * THE RESULT IS EXACT, not an approximation. With infinitely many servers
17 * customers never interact, so the model is a Poisson random measure and the
18 * number in system at time t is POISSON with mean
19 *
20 * m(t) = E[ int_{t-S}^{t} lambda(u) du ] = ES E[lambda(t - Se)]
21 * = int_0^Inf lambda(t-x) P(S > x) dx
22 *
23 * where Se is the STATIONARY-EXCESS (equilibrium) law of the service time, with
24 * density P(S>x)/ES. Because the law is Poisson the variance equals the mean.
25 *
26 * THE PHYSICS. Reading m(t) as ES E[lambda(t-Se)] says the time-varying load is
27 * the stationary load ES lambda(t) subjected to a TIME LAG and a SPACE SHIFT: to
28 * first order m(t) ~ ES lambda(t - E[Se]) with E[Se] = E[S^2]/(2 ES), so peak
29 * congestion LAGS peak arrival rate, and by more than the mean service time when
30 * the service law is variable. The pointwise stationary approximation is the
31 * zeroth-order term of the same expansion.
32 *
33 * ARITHMETIC. The age integral is a Simpson quadrature against a tail cut, so
34 * the answer is a quadrature approximation whatever the arithmetic; the
35 * instantiation is restricted to the transcendental types.
36 *
37 * Reference: S. G. Eick, W. A. Massey, W. Whitt (1993). The physics of the
38 * Mt/G/infinity queue. Operations Research 41(4), 731-742.
39 */
40
41#include <algorithm>
42#include <cmath>
43#include <cstddef>
44#include <functional>
45#include <limits>
46#include <vector>
47
49#include "line/num/number.h"
50#include "line/util/error.h"
51
52namespace line {
53namespace qsys {
54
55/** Time-varying measures of the Mt/G/infinity queue. */
56template <class T>
58 std::vector<T> times; ///< the evaluation times
59 std::vector<T> meanNumber; ///< m(t), the Poisson mean
60 std::vector<T> varNumber; ///< equal to meanNumber, the law being Poisson
61 std::vector<T> arrivalRate; ///< lambda(t)
62 std::vector<T> departureRate; ///< delta(t) = E[lambda(t-S)]
63 std::vector<T> offeredLoadPSA; ///< ES lambda(t), the pointwise stationary approximation
64 T meanLag; ///< E[Se], set only when ES2 was supplied
65 std::vector<T> lagApproximation; ///< ES lambda(t-E[Se]), likewise
66 bool hasLag = false; ///< whether the two fields above are set
67};
68
69namespace detail {
70
71/** Nodes and weights of the composite Simpson rule on an even panel count. */
72template <class T>
73void mtginf_simpson(const T& a, const T& b, std::size_t n, std::vector<T>& x, std::vector<T>& w) {
74 if (n % 2 == 1) ++n;
75 if (b <= a) {
76 x.assign(1, a);
77 w.assign(1, num_traits<T>::from_int(0));
78 return;
79 }
80 const T h = (b - a) / num_traits<T>::from_int(static_cast<long>(n));
81 x.resize(n + 1);
82 w.resize(n + 1);
83 for (std::size_t i = 0; i <= n; ++i) {
84 x[i] = a + num_traits<T>::from_int(static_cast<long>(i)) * h;
85 const long c = (i == 0 || i == n) ? 1 : (i % 2 == 1 ? 4 : 2);
87 }
88}
89
90/** Smallest doubling point at which the service ccdf is below tol. */
91template <class T, class Ccdf>
92T mtginf_tail_cut(Ccdf&& ccdf, double tol, double cap) {
94 const T tolT = num_traits<T>::from_double(tol);
95 const T capT = num_traits<T>::from_double(cap);
96 while (ccdf(x) > tolT) {
98 if (x > capT) return capT;
99 }
100 return x;
101}
102
103/**
104 * The Poisson mean m(t), shared by the public entry point and the
105 * finite-difference departure rate so that neither re-derives the other. With an
106 * infinite past the age grid does not move with t, so the service ccdf is
107 * evaluated once rather than once per time point.
108 */
109template <class T, class Lam, class Ccdf>
110std::vector<T> mtginf_mean(Lam&& lambdaFun, Ccdf&& serviceCcdf, const std::vector<T>& t,
111 double startTime, const T& cut, std::size_t panels, bool unbounded) {
112 std::vector<T> xs, ws, gcs;
113 if (unbounded) {
114 mtginf_simpson(num_traits<T>::from_int(0), cut, panels, xs, ws);
115 gcs.resize(xs.size());
116 for (std::size_t j = 0; j < xs.size(); ++j) gcs[j] = serviceCcdf(xs[j]);
117 }
118 std::vector<T> m(t.size(), num_traits<T>::from_int(0));
119 std::vector<T> x, w, gc;
120 for (std::size_t i = 0; i < t.size(); ++i) {
121 const std::vector<T>*px;
122 const std::vector<T>*pw;
123 const std::vector<T>*pgc;
124 if (unbounded) {
125 px = &xs;
126 pw = &ws;
127 pgc = &gcs;
128 } else {
129 T hi = t[i] - num_traits<T>::from_double(startTime);
130 if (hi < num_traits<T>::from_int(0)) hi = num_traits<T>::from_int(0);
131 if (hi > cut) hi = cut;
132 mtginf_simpson(num_traits<T>::from_int(0), hi, panels, x, w);
133 gc.resize(x.size());
134 for (std::size_t j = 0; j < x.size(); ++j) gc[j] = serviceCcdf(x[j]);
135 px = &x;
136 pw = &w;
137 pgc = &gc;
138 }
139 T acc = num_traits<T>::from_int(0);
140 for (std::size_t j = 0; j < px->size(); ++j)
141 // m(t) = int lambda(t-x) P(S>x) dx: arrivals of age x still in service.
142 acc += (*pw)[j] * lambdaFun(T(t[i] - (*px)[j])) * (*pgc)[j];
143 m[i] = acc;
144 }
145 return m;
146}
147
148} // namespace detail
149
150/**
151 * @brief Exact time-varying analysis of the Mt/G/infinity queue.
152 *
153 * @param lambdaFun the arrival rate; must accept arguments in the past when
154 * startTime is infinite
155 * @param serviceCcdf G^c(x) = P(S > x)
156 * @param ES the mean service time
157 * @param tvals the times at which to evaluate
158 * @param startTime time the system started empty; -Inf assumes an infinite past
159 * @param ES2 the second moment of the service time; NaN skips the lag
160 * @param servicePdf the service density for the exact departure rate, or empty
161 * @param tol service-tail cut for the age integral
162 * @param panels Simpson panels for that integral
163 * @param maxAge cap on the age integrated over
164 */
165template <class T>
167 const std::function<T(const T&)>& lambdaFun, const std::function<T(const T&)>& serviceCcdf,
168 const T& ES, const std::vector<T>& tvals,
169 double startTime = -std::numeric_limits<double>::infinity(),
170 double ES2 = std::numeric_limits<double>::quiet_NaN(),
171 const std::function<T(const T&)>& servicePdf = std::function<T(const T&)>(),
172 double tol = 1e-12, std::size_t panels = 4000, double maxAge = 1e12) {
174 "qsys_mtginf integrates against a tail cut, so it needs inexact arithmetic");
175 if (ES <= num_traits<T>::from_int(0))
176 throw InputError("qsys_mtginf: the mean service time ES must be positive");
177 if (!lambdaFun || !serviceCcdf)
178 throw InputError("qsys_mtginf: the arrival rate and the service ccdf must be callable");
179
180 const T cut = detail::mtginf_tail_cut<T>(serviceCcdf, tol, maxAge);
181 const bool unbounded = std::isinf(startTime);
182
184 res.times = tvals;
185 res.meanNumber = detail::mtginf_mean<T>(lambdaFun, serviceCcdf, tvals, startTime, cut, panels,
186 unbounded);
187 res.varNumber = res.meanNumber; // Poisson: the variance is the mean
188 res.arrivalRate.resize(tvals.size());
189 res.offeredLoadPSA.resize(tvals.size());
190 for (std::size_t i = 0; i < tvals.size(); ++i) {
191 res.arrivalRate[i] = lambdaFun(tvals[i]);
192 res.offeredLoadPSA[i] = ES * res.arrivalRate[i];
193 }
194
195 res.departureRate.resize(tvals.size());
196 if (servicePdf) {
197 std::vector<T> x, w;
198 for (std::size_t i = 0; i < tvals.size(); ++i) {
199 T hi = cut;
200 if (!unbounded) {
201 hi = tvals[i] - num_traits<T>::from_double(startTime);
203 if (hi > cut) hi = cut;
204 }
205 detail::mtginf_simpson(num_traits<T>::from_int(0), hi, panels, x, w);
206 T acc = num_traits<T>::from_int(0);
207 for (std::size_t j = 0; j < x.size(); ++j)
208 acc += w[j] * lambdaFun(T(tvals[i] - x[j])) * servicePdf(x[j]);
209 res.departureRate[i] = acc;
210 }
211 } else {
212 // Flow balance m'(t) = lambda(t) - delta(t), differentiated centrally.
213 double tmax = 1.0;
214 for (const T& v : tvals) tmax = std::max(tmax, std::abs(num_traits<T>::to_double(v)));
215 const T h = num_traits<T>::from_double(1e-5 * tmax);
216 std::vector<T> tu(tvals.size()), td(tvals.size());
217 for (std::size_t i = 0; i < tvals.size(); ++i) {
218 tu[i] = tvals[i] + h;
219 td[i] = tvals[i] - h;
220 }
221 const std::vector<T> up =
222 detail::mtginf_mean<T>(lambdaFun, serviceCcdf, tu, startTime, cut, panels, unbounded);
223 const std::vector<T> dn =
224 detail::mtginf_mean<T>(lambdaFun, serviceCcdf, td, startTime, cut, panels, unbounded);
225 for (std::size_t i = 0; i < tvals.size(); ++i)
226 res.departureRate[i] =
227 res.arrivalRate[i] - (up[i] - dn[i]) / (num_traits<T>::from_int(2) * h);
228 }
229
230 if (!std::isnan(ES2)) {
231 res.hasLag = true;
233 res.lagApproximation.resize(tvals.size());
234 for (std::size_t i = 0; i < tvals.size(); ++i)
235 res.lagApproximation[i] = ES * lambdaFun(T(tvals[i] - res.meanLag));
236 } else {
238 }
239 return res;
240}
241
242} // namespace qsys
243} // namespace line
244
245#endif // LINE_API_QSYS_MTGINF_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
QsysMtginfResult< T > qsys_mtginf(const std::function< T(const T &)> &lambdaFun, const std::function< T(const T &)> &serviceCcdf, const T &ES, const std::vector< T > &tvals, double startTime=-std::numeric_limits< double >::infinity(), double ES2=std::numeric_limits< double >::quiet_NaN(), const std::function< T(const T &)> &servicePdf=std::function< T(const T &)>(), double tol=1e-12, std::size_t panels=4000, double maxAge=1e12)
Exact time-varying analysis of the Mt/G/infinity queue.
Number-type abstraction for the templated API port.
Shared return type and arithmetic helpers for the templated qsys port.
Time-varying measures of the Mt/G/infinity queue.
Definition qsys_mtginf.h:57
std::vector< T > meanNumber
m(t), the Poisson mean
Definition qsys_mtginf.h:59
std::vector< T > departureRate
delta(t) = E[lambda(t-S)]
Definition qsys_mtginf.h:62
std::vector< T > varNumber
equal to meanNumber, the law being Poisson
Definition qsys_mtginf.h:60
std::vector< T > times
the evaluation times
Definition qsys_mtginf.h:58
std::vector< T > arrivalRate
lambda(t)
Definition qsys_mtginf.h:61
std::vector< T > offeredLoadPSA
ES lambda(t), the pointwise stationary approximation.
Definition qsys_mtginf.h:63
bool hasLag
whether the two fields above are set
Definition qsys_mtginf.h:66
T meanLag
E[Se], set only when ES2 was supplied.
Definition qsys_mtginf.h:64
std::vector< T > lagApproximation
ES lambda(t-E[Se]), likewise.
Definition qsys_mtginf.h:65