LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mapmap1.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_MAPMAP1_H
6#define LINE_API_QSYS_QSYS_MAPMAP1_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * The MAP/MAP/1 FCFS queue: mean number in system, waiting time, sojourn time,
12 * utilization and the queue-length distribution.
13 *
14 * ALGORITHM, AND HOW IT DIFFERS FROM THE MATLAB REFERENCE.
15 * matlab/src/api/qsys/qsys_mapmap1.m obtains these quantities by calling
16 * BUTools' MMAPPH1FCFS, which is not transcribed here; this port computes the
17 * SAME quantities from the port's own
18 * quasi-birth-death machinery (line/api/mam/qbd_mapmap1.h), which solves the
19 * level-independent QBD whose level is the number in system and whose phase is
20 * the pair (arrival phase, service phase). The algorithm is therefore
21 * different, the quantity is the same.
22 *
23 * THE SERVICE MAP IS COLLAPSED TO A PHASE-TYPE LAW, as the reference does.
24 * Before calling BUTools, qsys_mapmap1.m replaces the service MAP (D0,D1) by
25 * sigma = theta D1 / (theta D1 e), S = D0, theta (D0+D1) = 0,
26 * i.e. by the stationary marginal of a service time (its mapToPh). That
27 * embedded PH keeps the service-time MARGINAL exactly but DISCARDS the serial
28 * correlation between consecutive service times, so the reference solves a
29 * MAP/PH/1 queue in place of the MAP/MAP/1 queue it names. This port
30 * follows the reference: the service MAP is collapsed the same way and the
31 * resulting MAP/PH/1 queue is solved as a QBD, so the two agree also on a
32 * correlated service MAP (case 3 below). A caller who wants the genuine
33 * MAP/MAP/1 answer, with the service correlation honoured, has it in
34 * line::mam::qbd_mapmap1 (line/api/mam/qbd_mapmap1.h).
35 *
36 * The higher moments the reference obtains from MMAPPH1FCFS are returned too:
37 * queueLengthMoments (numQLMoms raw moments of the number in system, from
38 * pi_0 and R in closed form, qbd_qlen_moment) and sojournTimeMoments
39 * (numSTMoms raw moments, from the phase-type sojourn-time law of
40 * mmapph1fcfs_stdistr_ph, the same age-process construction BUTools uses).
41 *
42 * MEASURED AGREEMENT (MATLAB R2025a, T = double). Metric order is
43 * meanQueueLength / meanWaitingTime / meanSojournTime / utilization. Arrival
44 * MAPs used below:
45 * P(2) = Poisson, D0 = [-2], D1 = [2]
46 * C = correlated MMPP2, D0 = [-2.5 0.2; 0.1 -0.7], D1 = diag(2.3, 0.6),
47 * lambda = 7/6
48 *
49 * 1. M/M/1 collapse, qsys_mapmap1(P(2), service D0 = [-3], D1 = [3]). MATLAB
50 * 1.999999999999999 / 0.6666666666666656 / 0.9999999999999989 /
51 * 0.6666666666666666; port 2 / 0.666666666666668 / 1 / 0.666666666666667,
52 * the textbook values. Relative differences 5e-16, 1.8e-15, 1.1e-15.
53 *
54 * 2. Renewal service, where the reference's PH reduction is exact. Arrival C,
55 * service the Erlang-2 renewal MAP D0 = [-6 6; 0 -6], D1 = [0 0; 6 0].
56 * MATLAB 0.8364637529649164 / 0.3836355977794533 / 0.7169689311127866 /
57 * 0.3888888888888888; port 0.836463752964917 / 0.383635597779453 /
58 * 0.716968931112786 / 0.388888888888889. Relative differences below 1e-15
59 * on every metric: with a renewal service the two model the same system and
60 * the port reproduces the reference to machine precision.
61 *
62 * 3. Correlated service, where the reference's PH reduction is NOT exact.
63 * Arrival C, service MAP D0 = [-5 0.4; 0.2 -1.4], D1 = diag(4.6, 1.2)
64 * (lambda_s = 7/3, so rho = 1/2). MATLAB qsys_mapmap1 reports
65 * 2.0305355320573 / 1.311887598906254 / 1.740459027477683 / 0.5. With the
66 * service collapsed to its PH marginal as the reference does, the port
67 * returns the same MAP/PH/1 answer. The genuine MAP/MAP/1 value, which
68 * LINE's own MATLAB qbd_mapmap1 gives as QN = 2.547554867631855, RN =
69 * 2.183618457970162, is 20% away: that gap is the service correlation the
70 * reference's PH reduction throws away, and it is available through
71 * line::mam::qbd_mapmap1.
72 * meanWaitingTime is meanSojournTime minus the mean service time 1/lambda_s,
73 * and utilization is lambda_a/lambda_s, both matching the reference's own
74 * definitions.
75 *
76 * ARITHMETIC. The function is gated on num_traits<T>::has_transcendental
77 * because qbd_mapmap1 reaches R by cyclic reduction, a fixed-point iteration
78 * driven to a tolerance that does not terminate in a finite number of field
79 * operations; running it at exact rational arithmetic would produce a rational
80 * with a denominator doubling every step and still not the exact R. See
81 * qbd_r.h. Everything downstream of R here -- the mean, the level
82 * probabilities, the Little's-law conversions -- is finite exact matrix
83 * algebra and carries no additional error.
84 */
85
86#include <cstddef>
87#include <vector>
88
93#include "line/api/mam/qbd_r.h"
95#include "line/num/number.h"
96#include "line/util/error.h"
97#include "line/util/matrix.h"
98
99namespace line {
100namespace qsys {
101
102/**
103 * Return value of the MAP/MAP/1 family (qsys_mapmap1, qsys_mapph1, qsys_phph1),
104 * carrying the same quantities as the MATLAB result struct.
105 */
106template <class T>
108 T meanQueueLength; ///< E[N], number in system
109 T meanWaitingTime; ///< E[Wq], time in queue
110 T meanSojournTime; ///< E[W] = E[Wq] + E[S]
111 T utilization; ///< rho = lambda_a / lambda_s
112 std::vector<T> queueLengthDist; ///< P(N = n), n = 0, 1, ...
113 std::vector<T> queueLengthMoments; ///< raw moments E[N^k], k = 1..numQLMoms
114 std::vector<T> sojournTimeMoments; ///< raw moments E[W^k], k = 1..numSTMoms
115};
116
117namespace detail {
118
119/**
120 * The reference's mapToPh: the stationary marginal of a service time of the
121 * MAP (D0, D1) as the PH (sigma, S) with sigma = theta D1 / (theta D1 e) and
122 * S = D0; uniform sigma when the MAP never fires.
123 */
124template <class T>
125void mapmap1_map_to_ph(const mam::Map<T>& service, std::vector<T>& sigma, Matrix<T>& S) {
126 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
127 const std::size_t m = service.D0.rows();
128 Matrix<T> Q(m, m, zero);
129 for (std::size_t i = 0; i < m; ++i)
130 for (std::size_t j = 0; j < m; ++j) Q(i, j) = service.D0(i, j) + service.D1(i, j);
131 const std::vector<T> theta = mc::ctmc_solve(Q);
132 sigma = vecmul(theta, service.D1);
133 T total = zero;
134 for (std::size_t j = 0; j < m; ++j) total += sigma[j];
135 if (total > zero) {
136 for (std::size_t j = 0; j < m; ++j) sigma[j] = sigma[j] / total;
137 } else {
138 sigma.assign(m, T(one / num_traits<T>::from_int(static_cast<long>(m))));
139 }
140 S = service.D0;
141}
142
143/** Raw moments k! alpha (-A)^-k e, k = 1..n, of the PH (alpha, A). */
144template <class T>
145std::vector<T> mapmap1_ph_moments(const std::vector<T>& alpha, const Matrix<T>& A, std::size_t n) {
146 std::vector<T> out;
147 if (n == 0) return out;
148 const std::size_t m = A.rows();
149 Matrix<T> negA(m, m);
150 for (std::size_t i = 0; i < m; ++i)
151 for (std::size_t j = 0; j < m; ++j) negA(i, j) = -A(i, j);
152 const Matrix<T> iA = inverse(negA);
153 std::vector<T> v = alpha;
154 for (std::size_t k = 1; k <= n; ++k) {
155 v = vecmul(v, iA);
157 for (std::size_t j = 0; j < m; ++j) s += v[j];
158 out.push_back(T(num_factorial<T>(static_cast<unsigned>(k)) * s));
159 }
160 return out;
161}
162
163} // namespace detail
164
165/**
166 * MAP/MAP/1 as the reference solves it: the service MAP collapsed to its PH
167 * marginal (mapToPh), then the MAP/PH/1 queue by the exact QBD solution.
168 * @param arrival arrival MAP (C0, C1)
169 * @param service service MAP (D0, D1); its correlation is discarded, as in
170 * the reference (see the file comment)
171 * @param dist_size how many entries of queueLengthDist to materialize
172 * (the reference's numQLProbs)
173 * @param numQLMoms how many raw queue-length moments to return
174 * @param numSTMoms how many raw sojourn-time moments to return
175 */
176template <class T>
178 std::size_t dist_size, std::size_t numQLMoms,
179 std::size_t numSTMoms) {
181 "qsys_mapmap1 requires transcendental arithmetic");
182 if (dist_size == 0) throw InputError("qsys_mapmap1: dist_size must be positive");
183 const T zero = num_traits<T>::from_int(0);
184
185 // mapToPh, then the PH as its renewal MAP {S, (-S e) sigma}
186 std::vector<T> sigma;
187 Matrix<T> S;
188 detail::mapmap1_map_to_ph(service, sigma, S);
189 const std::size_t m = S.rows();
190 mam::Map<T> service_ph;
191 service_ph.D0 = S;
192 service_ph.D1 = Matrix<T>(m, m, zero);
193 for (std::size_t i = 0; i < m; ++i) {
194 T exit = zero;
195 for (std::size_t j = 0; j < m; ++j) exit -= S(i, j);
196 for (std::size_t j = 0; j < m; ++j) service_ph.D1(i, j) = exit * sigma[j];
197 }
198
199 const mam::QbdMapMap1Result<T> q = mam::qbd_mapmap1(arrival, service_ph, T(zero), dist_size);
200
201 // utilization from the arrival and service rates, rho = lambda / mu
202 const T lambda_a = mam::map_lambda(arrival);
203 const T mu = mam::map_lambda(service);
204
205 // mean service time from the PH, sigma (-S)^-1 e
206 const std::vector<T> svcMoms = detail::mapmap1_ph_moments(sigma, S, 1);
207 const T mean_service = svcMoms[0];
208
210 r.queueLengthMoments.clear();
211 for (std::size_t k = 1; k <= numQLMoms; ++k)
212 r.queueLengthMoments.push_back(mam::qbd_qlen_moment(q.pi0, q.R, static_cast<unsigned>(k)));
213 r.sojournTimeMoments.clear();
214 if (numSTMoms > 0) {
215 mam::Mmap<T> mm;
216 mm.D0 = arrival.D0;
217 mm.D1 = arrival.D1;
218 mm.Dc.assign(1, arrival.D1);
219 std::vector<mam::PhService<T>> svc(1);
220 svc[0].sigma = sigma;
221 svc[0].S = S;
222 const std::vector<mam::StDistrPh<T>> st = mam::mmapph1fcfs_stdistr_ph(mm, svc);
223 r.sojournTimeMoments = detail::mapmap1_ph_moments(st[0].alpha, st[0].A, numSTMoms);
224 }
225
226 // meanQL = ncMoms(1), meanST = stMoms(1), each 0 when not requested
227 r.meanQueueLength = r.queueLengthMoments.empty() ? zero : r.queueLengthMoments[0];
228 r.meanSojournTime = r.sojournTimeMoments.empty() ? zero : r.sojournTimeMoments[0];
229 const T wait = T(r.meanSojournTime - mean_service);
230 r.meanWaitingTime = wait < zero ? zero : wait;
231 r.utilization = lambda_a / mu;
232 r.queueLengthDist.assign(q.pqueue.rows(), zero);
233 for (std::size_t k = 0; k < q.pqueue.rows(); ++k) {
234 T s = zero;
235 for (std::size_t j = 0; j < q.pqueue.cols(); ++j) s += q.pqueue(k, j);
236 r.queueLengthDist[k] = s;
237 }
238 return r;
239}
240
241/** qsys_mapmap1 with the reference's defaults numQLMoms = 3, numSTMoms = 3. */
242template <class T>
244 std::size_t dist_size) {
245 return qsys_mapmap1(arrival, service, dist_size, static_cast<std::size_t>(3),
246 static_cast<std::size_t>(3));
247}
248
249/** qsys_mapmap1 with 100 materialized levels, the reference's numQLProbs. */
250template <class T>
251MapMap1Result<T> qsys_mapmap1(const mam::Map<T>& arrival, const mam::Map<T>& service) {
252 return qsys_mapmap1(arrival, service, static_cast<std::size_t>(100));
253}
254
255} // namespace qsys
256} // namespace line
257
258#endif // LINE_API_QSYS_QSYS_MAPMAP1_H
InputError(const std::string &what)
Definition error.h:105
std::size_t rows() const
Definition matrix.h:89
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
The MMAP[K]/PH[K]/1 FCFS queue: per-class mean number in system and per-class queue-length distributi...
QbdMapMap1Result< T > qbd_mapmap1(const Map< T > &arrival, const Map< T > &service_in, const T &util, std::size_t max_levels)
MAP/MAP/1 queue (qbd_mapmap1.m).
T qbd_qlen_moment(const std::vector< T > &pi0, const Matrix< T > &R, unsigned m)
Raw moment of order m of the number in system, E[N^m], assembled from the factorial moments with the ...
std::vector< StDistrPh< T > > mmapph1fcfs_stdistr_ph(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, double precision=1e-14)
Per-class SOJOURN TIME as a continuous phase-type law, BUTools' 'stDistrPH'.
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
Definition map_moment.h:79
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
MapMap1Result< T > qsys_mapmap1(const mam::Map< T > &arrival, const mam::Map< T > &service, std::size_t dist_size, std::size_t numQLMoms, std::size_t numSTMoms)
MAP/MAP/1 as the reference solves it: the service MAP collapsed to its PH marginal (mapToPh),...
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:196
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
Number-type abstraction for the templated API port.
The MAP/MAP/1 queue solved as a quasi-birth-death process.
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
An MMAP: the underlying MAP plus the per-class arrival matrices.
Definition mmap_lambda.h:45
Matrix< T > D0
Definition mmap_lambda.h:46
Matrix< T > D1
Definition mmap_lambda.h:47
std::vector< Matrix< T > > Dc
per-class matrices, sum_c Dc = D1
Definition mmap_lambda.h:48
Result of qbd_mapmap1, mirroring the MATLAB return list.
std::vector< T > pi0
boundary vector, normalized
Matrix< T > pqueue
level distribution, row k = pi_k
Return value of the MAP/MAP/1 family (qsys_mapmap1, qsys_mapph1, qsys_phph1), carrying the same quant...
std::vector< T > sojournTimeMoments
raw moments E[W^k], k = 1..numSTMoms
T utilization
rho = lambda_a / lambda_s
std::vector< T > queueLengthMoments
raw moments E[N^k], k = 1..numQLMoms
T meanQueueLength
E[N], number in system.
T meanSojournTime
E[W] = E[Wq] + E[S].
std::vector< T > queueLengthDist
P(N = n), n = 0, 1, ...
T meanWaitingTime
E[Wq], time in queue.