LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_dmc.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_DMC_H
6#define LINE_API_QSYS_QSYS_DMC_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * D/M/c: deterministic interarrival times, exponential service.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_dmc.m, cross-checked against
14 * jar/src/main/java/jline/api/qsys/Qsys_dmc.java.
15 *
16 * The system is embedded at arrival epochs. Between two arrivals only
17 * departures occur, so the sub-generator is the death-only bidiagonal matrix
18 * A[m,m-1] = min(m,c) mu, A[m,m] = -min(m,c) mu, and the embedded chain is
19 * X_{n+1} = exp(A s)[X_n + 1, :] with s = 1/lambda the interarrival time. The
20 * stationary vector at arrival epochs is then converted to time averages by
21 * integrating the state-count expectations over one interarrival cycle with
22 * the trapezoid rule on quadSteps subintervals.
23 *
24 * The truncation level is max(200, min(2500, floor(15/(1-rho)) + 200)) unless
25 * given, as in MATLAB. That default is expensive here because the exponential
26 * and the cycle integration are dense; callers benchmarking against MATLAB on
27 * a specific instance should pass the same explicit truncation to both.
28 *
29 * ARITHMETIC. The matrix exponential and the trapezoid rule are both inexact,
30 * so the function is gated on transcendental arithmetic.
31 *
32 * At c = 1 the D/M/1 mean waiting time must agree with qsys_gm1 evaluated at
33 * the root of sigma = exp(-mu(1-sigma)/lambda), which is the check the tests
34 * apply.
35 */
36
37#include <algorithm>
38#include <cstddef>
39#include <vector>
40
43#include "line/num/number.h"
44#include "line/util/error.h"
45#include "line/util/linalg.h"
46#include "line/util/lu.h"
47#include "line/util/matrix.h"
48
49namespace line {
50namespace qsys {
51
52template <class T>
53struct DmcResult {
54 T meanQueueLength; ///< time-average E[N]
55 T meanWaitingQueue; ///< time-average Lq = E[(N-c)+]
56 T meanWaitingTime; ///< Wq = Lq/lambda
57 T meanSojournTime; ///< W = Wq + 1/mu
58 T utilization; ///< rho = lambda/(c mu)
59};
60
61namespace detail {
62
63/**
64 * Matrix exponential by scaling and squaring around a Taylor series.
65 *
66 * MATLAB uses expm, i.e. scaling and squaring around a Pade approximant. The
67 * two agree to round-off: after scaling the argument to infinity-norm below
68 * 1/2 the truncated Taylor series of 30 terms has a remainder below 2^-30/30!,
69 * far under any working precision, so the squaring stage is what determines
70 * the accuracy in both cases.
71 */
72template <class T>
73Matrix<T> expm(const Matrix<T>& A) {
74 static_assert(num_traits<T>::has_transcendental, "expm requires transcendental arithmetic");
75 const std::size_t n = A.rows();
76 if (A.cols() != n) throw InputError("expm: matrix is not square");
77 T nrm = num_traits<T>::from_int(0);
78 for (std::size_t i = 0; i < n; ++i) {
80 for (std::size_t j = 0; j < n; ++j) s += num_abs(A(i, j));
81 if (s > nrm) nrm = s;
82 }
83 unsigned sq = 0;
84 T scaled = nrm;
85 const T half = num_traits<T>::from_rational(1, 2);
86 const T two = num_traits<T>::from_int(2);
87 while (scaled > half) {
88 scaled /= two;
89 ++sq;
90 }
91 Matrix<T> B = A;
92 const T factor = num_pow_int(half, sq);
93 for (std::size_t i = 0; i < n; ++i)
94 for (std::size_t j = 0; j < n; ++j) B(i, j) *= factor;
95
96 Matrix<T> E = eye<T>(n);
97 Matrix<T> term = eye<T>(n);
98 for (unsigned q = 1; q <= 30u; ++q) {
99 term = matmul(term, B);
100 const T inv = num_traits<T>::from_int(1) / num_traits<T>::from_int(static_cast<long>(q));
101 for (std::size_t i = 0; i < n; ++i)
102 for (std::size_t j = 0; j < n; ++j) term(i, j) *= inv;
103 for (std::size_t i = 0; i < n; ++i)
104 for (std::size_t j = 0; j < n; ++j) E(i, j) += term(i, j);
105 }
106 for (unsigned r = 0; r < sq; ++r) E = matmul(E, E);
107 return E;
108}
109
110} // namespace detail
111
112/**
113 * @brief D/M/c: deterministic interarrival times, exponential service.
114 *
115 * @param lambda deterministic arrival rate, interarrival time 1/lambda
116 * @param mu exponential service rate of one server
117 * @param c number of servers, c >= 1
118 * @param truncation state-space truncation; 0 selects the MATLAB default
119 * @param quadSteps trapezoid steps over one interarrival cycle (MATLAB 200)
120 */
121template <class T>
122DmcResult<T> qsys_dmc(const T& lambda, const T& mu, unsigned c, unsigned truncation,
123 unsigned quadSteps) {
125 "qsys_dmc requires transcendental arithmetic");
126 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
127 if (lambda <= zero) throw InputError("qsys_dmc: arrival rate must be positive");
128 if (mu <= zero) throw InputError("qsys_dmc: service rate must be positive");
129 if (c < 1) throw InputError("qsys_dmc: number of servers must be at least 1");
130 if (quadSteps < 1) throw InputError("qsys_dmc: quadSteps must be at least 1");
131 const T ct = num_traits<T>::from_int(static_cast<long>(c));
132 const T rho = lambda / (ct * mu);
133 if (rho >= one) throw InputError("qsys_dmc: load rho must be strictly less than 1");
134
135 const T s = one / lambda;
136 unsigned nMax;
137 if (truncation > 0) {
138 nMax = truncation;
139 } else {
140 const double gap = num_traits<T>::to_double(T(one - rho));
141 const long guess = static_cast<long>(std::floor(15.0 / gap)) + 200;
142 nMax = static_cast<unsigned>(std::max(200L, std::min(2500L, guess)));
143 }
144 const std::size_t n = nMax + 1;
145
146 Matrix<T> A(n, n, zero);
147 for (std::size_t m = 0; m < n; ++m) {
148 const unsigned busy = std::min<unsigned>(static_cast<unsigned>(m), c);
149 const T rate = num_traits<T>::from_int(static_cast<long>(busy)) * mu;
150 A(m, m) = -rate;
151 if (m > 0) A(m, m - 1) = rate;
152 }
153
154 Matrix<T> As = A, Adt = A;
155 const T dt = s / num_traits<T>::from_int(static_cast<long>(quadSteps));
156 for (std::size_t i = 0; i < n; ++i)
157 for (std::size_t j = 0; j < n; ++j) {
158 As(i, j) *= s;
159 Adt(i, j) *= dt;
160 }
161 const Matrix<T> expAs = detail::expm(As);
162 const Matrix<T> expAdt = detail::expm(Adt);
163
164 // Row r represents "number in system before the arrival = r"; after the
165 // arrival the state is min(r+1, n-1).
166 std::vector<std::size_t> yIdx(n);
167 for (std::size_t r = 0; r < n; ++r) yIdx[r] = std::min(r + 1, n - 1);
168
169 // Stationary at arrival epochs: (P' - I) pi = 0 with the last row replaced
170 // by the normalization.
171 Matrix<T> M(n, n, zero);
172 for (std::size_t r = 0; r + 1 < n; ++r)
173 for (std::size_t col = 0; col < n; ++col) M(r, col) = expAs(yIdx[col], r);
174 for (std::size_t r = 0; r + 1 < n; ++r) M(r, r) -= one;
175 for (std::size_t col = 0; col < n; ++col) M(n - 1, col) = one;
176 std::vector<T> b(n, zero);
177 b[n - 1] = one;
178 const std::vector<T> piArr = line::solve(M, b);
179
180 std::vector<T> wLq(n), wN(n);
181 for (std::size_t m = 0; m < n; ++m) {
182 wN[m] = num_traits<T>::from_int(static_cast<long>(m));
183 wLq[m] = m > c ? num_traits<T>::from_int(static_cast<long>(m - c)) : zero;
184 }
185
186 std::vector<T> ts(quadSteps + 1);
187 for (unsigned q = 0; q <= quadSteps; ++q)
188 ts[q] = s * num_traits<T>::from_int(static_cast<long>(q)) /
189 num_traits<T>::from_int(static_cast<long>(quadSteps));
190 std::vector<std::vector<T>> LqAtT(quadSteps + 1), NAtT(quadSteps + 1);
191 Matrix<T> expAt = eye<T>(n);
192 for (unsigned q = 0; q <= quadSteps; ++q) {
193 if (q > 0) expAt = matmul(expAt, expAdt);
194 LqAtT[q] = mulvec(expAt, wLq);
195 NAtT[q] = mulvec(expAt, wN);
196 }
197 std::vector<T> LqInt(n, zero), NInt(n, zero);
198 std::vector<T> colLq(quadSteps + 1), colN(quadSteps + 1);
199 for (std::size_t r = 0; r < n; ++r) {
200 for (unsigned q = 0; q <= quadSteps; ++q) {
201 colLq[q] = LqAtT[q][r];
202 colN[q] = NAtT[q][r];
203 }
204 LqInt[r] = detail::num_trapz(ts, colLq) / s;
205 NInt[r] = detail::num_trapz(ts, colN) / s;
206 }
207
208 T LqTime = zero, NTime = zero;
209 for (std::size_t r = 0; r < n; ++r) {
210 LqTime += piArr[r] * LqInt[yIdx[r]];
211 NTime += piArr[r] * NInt[yIdx[r]];
212 }
213
214 DmcResult<T> res;
215 res.meanQueueLength = NTime;
216 res.meanWaitingQueue = LqTime;
217 res.meanWaitingTime = LqTime / lambda;
218 res.meanSojournTime = res.meanWaitingTime + one / mu;
219 res.utilization = rho;
220 return res;
221}
222
223/** qsys_dmc with the MATLAB defaults, automatic truncation and 200 steps. */
224template <class T>
225DmcResult<T> qsys_dmc(const T& lambda, const T& mu, unsigned c) {
226 return qsys_dmc(lambda, mu, c, 0u, 200u);
227}
228
229} // namespace qsys
230} // namespace line
231
232#endif // LINE_API_QSYS_QSYS_DMC_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
DmcResult< T > qsys_dmc(const T &lambda, const T &mu, unsigned c, unsigned truncation, unsigned quadSteps)
D/M/c: deterministic interarrival times, exponential service.
Definition qsys_dmc.h:122
T num_abs(const T &v)
Definition number.h:172
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:192
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
std::vector< T > mulvec(const Matrix< T > &A, const std::vector< T > &v)
Matrix times column vector, A v.
Definition linalg.h:62
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Matrix< T > expm(const Matrix< T > &A)
Matrix exponential exp(A).
Definition expm.h:141
Number-type abstraction for the templated API port.
Adaptive quadrature for the qsys functions whose MATLAB originals call integral(),...
Shared return type and arithmetic helpers for the templated qsys port.
T meanSojournTime
W = Wq + 1/mu.
Definition qsys_dmc.h:57
T meanQueueLength
time-average E[N]
Definition qsys_dmc.h:54
T utilization
rho = lambda/(c mu)
Definition qsys_dmc.h:58
T meanWaitingTime
Wq = Lq/lambda.
Definition qsys_dmc.h:56
T meanWaitingQueue
time-average Lq = E[(N-c)+]
Definition qsys_dmc.h:55