LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mapmc.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_MAPMC_H
6#define LINE_API_QSYS_QSYS_MAPMC_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * The MAP/M/c FCFS queue: c identical exponential servers of rate mu fed by a
12 * Markovian arrival process.
13 *
14 * ALGORITHM, AND HOW IT DIFFERS FROM THE MATLAB REFERENCE.
15 * matlab/src/api/qsys/qsys_mapmc.m obtains these quantities by calling Q-MAM's
16 * Q_CT_MAP_M_C, which is not transcribed here. This port computes the SAME
17 * quantities from the port's own quasi-birth-death machinery, by building the
18 * QBD directly: the level is the
19 * number in system and the phase is the arrival phase, so for levels n >= c
20 * the process is level independent with
21 *
22 * A0 = D1 (an arrival), A1 = D0 - c mu I (local), A2 = c mu I (a
23 * completion),
24 *
25 * while levels 0..c-1 form the boundary, where level k has downward rate
26 * k mu instead of c mu. R is the minimal non-negative solution of
27 * R^2 A2 + R A1 + A0 = 0, reached by cyclic reduction (qbd_fundmat called with
28 * B = A2, L = A1, F = A0), and pi_{c+k} = pi_c R^k. The boundary vectors
29 * pi_0..pi_c come from the level balance equations with the last one replaced
30 * by the normalization
31 *
32 * sum_{n<c} pi_n e + pi_c (I - R)^-1 e = 1,
33 *
34 * after which the number in system is
35 *
36 * L = sum_{n<c} n pi_n e + pi_c ( c (I-R)^-1 + R (I-R)^-2 ) e
37 *
38 * and the queued jobs are Lq = pi_c R (I-R)^-2 e. This is the same
39 * construction as qsys_phmc.h with the PH arrival replaced by a general MAP:
40 * a PH arrival is the special case D0 = T, D1 = (-T e) alpha. Utilization is
41 * lambda/(c mu), the reference's per-server convention.
42 *
43 * meanWaitingTime is Lq/lambda by Little's law rather than the mean of the
44 * waiting-time PH representation that the reference extracts from Q-MAM; the
45 * two are the same quantity and the measurements below confirm they agree.
46 *
47 * MEASURED AGREEMENT (MATLAB R2025a, T = double). Metric order is
48 * meanQueueLength / meanWaitingTime / meanSojournTime / utilization. Every
49 * relative difference below is limited by the REFERENCE, whose maxNumComp =
50 * 500 level truncation shows at the 1e-9 level; this port sums the geometric
51 * tail in closed form.
52 * - M/M/c collapse, qsys_mapmc(D0 = [-1.2], D1 = [1.2], mu = 1, c = 2):
53 * MATLAB 1.874999996296936 / 0.5624999998489539 / 1.562499999848954 / 0.6.
54 * The port returns the exact Erlang-C values 1.875 / 0.5625 / 1.5625 / 0.6
55 * to 1e-15, so the differences against MATLAB are 2.0e-9, 2.7e-10 and
56 * 9.7e-11; the port matches the textbook formulas and MATLAB does not.
57 * - Correlated MMPP2 arrivals D0 = [-2.5 0.2; 0.1 -0.7], D1 = diag(2.3, 0.6)
58 * (lambda = 7/6), mu = 1, c = 2: MATLAB 3.121850390835039 /
59 * 1.675871772757968 / 2.675871772757968 / 0.5833333333333333; port
60 * 3.12185040294165 / 1.67587177394999 / 2.67587177394999 /
61 * 0.583333333333333. Relative differences 3.9e-9, 7.1e-10, 4.5e-10.
62 * - Erlang-2 arrivals D0 = [-4 4; 0 -4], D1 = [0 0; 4 0] (lambda = 2),
63 * mu = 1.5, c = 2: MATLAB 2.022056024561071 / 0.3443613471500779 /
64 * 1.011028013816745 / 0.6666666666666666; port 2.02205602772974 /
65 * 0.344361347198204 / 1.01102801386487 / 0.666666666666667. Relative
66 * differences 1.6e-9, 1.4e-10, 4.8e-11.
67 * - Three servers, correlated arrivals, mu = 0.5, c = 3 (rho = 7/9): MATLAB
68 * 9.441571472754669 / 6.092775552492106 / 8.092775552492107 /
69 * 0.7777777777777777; port 9.44157149906991 / 6.09277557063135 /
70 * 8.09277557063135 / 0.777777777777778. Relative differences 2.8e-9,
71 * 3.0e-9, 2.2e-9.
72 *
73 * That the port and not the reference is the accurate one was checked without
74 * MATLAB: at c = 1 the same instance is also a MAP/MAP/1 queue with an
75 * exponential service MAP, and qsys_mapmap1 -- a completely different route,
76 * cyclic reduction on the Kronecker-product QBD followed by the closed-form
77 * factorial moment -- reproduces this function to 5e-16 on both the correlated
78 * and the Erlang-2 arrival instances. The test file carries that cross-check.
79 *
80 * ARITHMETIC. Gated on num_traits<T>::has_transcendental: cyclic reduction
81 * drives R to a tolerance and never terminates in a finite number of field
82 * operations, exactly as in qbd_r.h and qsys_phmc.h. The boundary solve, the
83 * geometric-tail sums and the moment formulas that consume R are finite exact
84 * matrix algebra and add no error of their own.
85 */
86
87#include <cstddef>
88#include <vector>
89
91#include "line/api/mam/qbd_r.h"
92#include "line/num/number.h"
93#include "line/util/error.h"
94#include "line/util/linalg.h"
95#include "line/util/lu.h"
96#include "line/util/matrix.h"
97
98namespace line {
99namespace qsys {
100
101/** Return value of qsys_mapmc and qsys_mapm1, mirroring the MATLAB struct. */
102template <class T>
104 T meanQueueLength; ///< L, number in system
105 T meanWaitingTime; ///< Wq, time in queue
106 T meanSojournTime; ///< W = Wq + 1/mu
107 T utilization; ///< rho = lambda / (c mu), per server
108 std::vector<T> queueLengthDist; ///< P(N = n), n = 0, 1, ...
109};
110
111/**
112 * MAP/M/c by the matrix-geometric solution.
113 *
114 * @param arrival arrival MAP (D0, D1)
115 * @param mu exponential service rate of one server
116 * @param c number of servers, c >= 1
117 * @param dist_size how many entries of queueLengthDist to materialize
118 */
119template <class T>
120MapMcResult<T> qsys_mapmc(const mam::Map<T>& arrival, const T& mu, unsigned c,
121 std::size_t dist_size) {
123 "qsys_mapmc requires transcendental arithmetic");
124 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
125 if (mu <= zero) throw InputError("qsys_mapmc: service rate mu must be positive");
126 if (c < 1) throw InputError("qsys_mapmc: c must be a positive integer");
127 if (dist_size == 0) throw InputError("qsys_mapmc: dist_size must be positive");
128 const std::size_t k = arrival.D0.rows();
129 if (arrival.D0.cols() != k || arrival.D1.rows() != k || arrival.D1.cols() != k)
130 throw InputError("qsys_mapmc: D0 and D1 must be square and of equal order");
131 const T ct = num_traits<T>::from_int(static_cast<long>(c));
132
133 const Matrix<T>& D0 = arrival.D0;
134 const Matrix<T>& D1 = arrival.D1;
135 const T lambda = mam::map_lambda(arrival);
136 if (lambda <= zero) throw InputError("qsys_mapmc: non-positive arrival rate");
137 const T rho = lambda / (ct * mu);
138 if (rho >= one) throw InputError("qsys_mapmc: load rho must be strictly less than 1");
139
140 // cyclic-reduction R rationale: see _kb/03-api-layer.md (cpp port notes: qsys)
141 const Matrix<T>& A0 = D1;
142 Matrix<T> A1(k, k), A2(k, k, zero);
143 for (std::size_t i = 0; i < k; ++i) {
144 for (std::size_t j = 0; j < k; ++j) A1(i, j) = D0(i, j);
145 A1(i, i) -= ct * mu;
146 A2(i, i) = ct * mu;
147 }
148 const Matrix<T> R = mam::qbd_fundmat(A2, A1, A0).R;
149
150 // Boundary system on the stacked unknowns [pi_0 ... pi_c].
151 const std::size_t nvar = (c + 1) * k;
152 Matrix<T> M(nvar, nvar, zero);
153 // Level 0: pi_0 D0 + pi_1 (mu I) = 0.
154 for (std::size_t j = 0; j < k; ++j) {
155 for (std::size_t i = 0; i < k; ++i) M(j, i) += D0(i, j);
156 M(j, k + j) += mu;
157 }
158 // Levels 1..c-1: pi_{n-1} D1 + pi_n (D0 - n mu I) + pi_{n+1} ((n+1) mu I) = 0.
159 for (unsigned n = 1; n < c; ++n) {
160 const T nt = num_traits<T>::from_int(static_cast<long>(n));
161 for (std::size_t j = 0; j < k; ++j) {
162 const std::size_t row = n * k + j;
163 for (std::size_t i = 0; i < k; ++i) {
164 M(row, (n - 1) * k + i) += D1(i, j);
165 M(row, n * k + i) += D0(i, j);
166 }
167 M(row, n * k + j) -= nt * mu;
168 M(row, (n + 1) * k + j) += (nt + one) * mu;
169 }
170 }
171 // Level c: pi_{c-1} D1 + pi_c (D0 - c mu I + R (c mu I)) = 0.
172 const Matrix<T> RA2c = matmul(R, A2);
173 for (std::size_t j = 0; j < k; ++j) {
174 const std::size_t row = c * k + j;
175 for (std::size_t i = 0; i < k; ++i) {
176 M(row, (c - 1) * k + i) += D1(i, j);
177 M(row, c * k + i) += D0(i, j) + RA2c(i, j);
178 }
179 M(row, c * k + j) -= ct * mu;
180 }
181 // Replace the last equation with the normalization.
182 Matrix<T> IR(k, k);
183 for (std::size_t i = 0; i < k; ++i)
184 for (std::size_t j = 0; j < k; ++j) IR(i, j) = (i == j ? one : zero) - R(i, j);
185 const std::vector<T> sum_geom = line::solve(IR, ones<T>(k));
186 for (std::size_t col = 0; col < nvar; ++col) M(nvar - 1, col) = zero;
187 for (unsigned n = 0; n < c; ++n)
188 for (std::size_t i = 0; i < k; ++i) M(nvar - 1, n * k + i) = one;
189 for (std::size_t i = 0; i < k; ++i) M(nvar - 1, c * k + i) = sum_geom[i];
190 std::vector<T> b(nvar, zero);
191 b[nvar - 1] = one;
192 const std::vector<T> x = line::solve(M, b);
193
194 std::vector<T> pi_c(k);
195 for (std::size_t i = 0; i < k; ++i) pi_c[i] = x[c * k + i];
196
197 const Matrix<T> IRinv = inverse(IR);
198 const Matrix<T> IRinv2 = matmul(IRinv, IRinv);
199 const Matrix<T> R_IRinv2 = matmul(R, IRinv2);
200 const std::vector<T> e = ones<T>(k);
201 const std::vector<T> v1 = mulvec(R_IRinv2, e);
202 T Lq = zero;
203 for (std::size_t i = 0; i < k; ++i) Lq += pi_c[i] * v1[i];
204 Matrix<T> bulk(k, k);
205 for (std::size_t i = 0; i < k; ++i)
206 for (std::size_t j = 0; j < k; ++j) bulk(i, j) = ct * IRinv(i, j) + R_IRinv2(i, j);
207 const std::vector<T> v2 = mulvec(bulk, e);
208 T L = zero;
209 for (std::size_t i = 0; i < k; ++i) L += pi_c[i] * v2[i];
210 for (unsigned n = 0; n < c; ++n) {
211 T s = zero;
212 for (std::size_t i = 0; i < k; ++i) s += x[n * k + i];
213 L += num_traits<T>::from_int(static_cast<long>(n)) * s;
214 }
215
217 r.meanQueueLength = L;
218 r.meanWaitingTime = Lq / lambda;
219 r.meanSojournTime = r.meanWaitingTime + one / mu;
220 r.utilization = rho;
221
222 // Level distribution: pi_n from the boundary for n <= c, pi_c R^(n-c) above.
223 r.queueLengthDist.assign(dist_size, zero);
224 std::vector<T> cur(k, zero);
225 for (std::size_t n = 0; n < dist_size; ++n) {
226 if (n <= c) {
227 for (std::size_t i = 0; i < k; ++i) cur[i] = x[n * k + i];
228 } else {
229 cur = vecmul(cur, R);
230 }
231 T s = zero;
232 for (std::size_t i = 0; i < k; ++i) s += cur[i];
233 r.queueLengthDist[n] = s;
234 }
235 return r;
236}
237
238/** qsys_mapmc with 100 materialized levels, the reference's maxNumComp scale. */
239template <class T>
240MapMcResult<T> qsys_mapmc(const mam::Map<T>& arrival, const T& mu, unsigned c) {
241 return qsys_mapmc(arrival, mu, c, static_cast<std::size_t>(100));
242}
243
244} // namespace qsys
245} // namespace line
246
247#endif // LINE_API_QSYS_QSYS_MAPMC_H
InputError(const std::string &what)
Definition error.h:39
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.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
QbdFundMat< T > qbd_fundmat(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
G and R by cyclic reduction (qbd_fundmat.m, the Bini-Meini logarithmic reduction on the raw level blo...
Definition qbd_r.h:280
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
Definition map_moment.h:79
MapMcResult< T > qsys_mapmc(const mam::Map< T > &arrival, const T &mu, unsigned c, std::size_t dist_size)
MAP/M/c by the matrix-geometric solution.
Definition qsys_mapmc.h:120
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
Definition linalg.h:50
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
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
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
Definition linalg.h:104
Number-type abstraction for the templated API port.
Quasi-birth-death processes: the rate matrix R, the fundamental matrix G, the caudal characteristic,...
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54
Return value of qsys_mapmc and qsys_mapm1, mirroring the MATLAB struct.
Definition qsys_mapmc.h:103
T utilization
rho = lambda / (c mu), per server
Definition qsys_mapmc.h:107
T meanWaitingTime
Wq, time in queue.
Definition qsys_mapmc.h:105
T meanSojournTime
W = Wq + 1/mu.
Definition qsys_mapmc.h:106
T meanQueueLength
L, number in system.
Definition qsys_mapmc.h:104
std::vector< T > queueLengthDist
P(N = n), n = 0, 1, ...
Definition qsys_mapmc.h:108