LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mapmapc.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_MAPMAPC_H
6#define LINE_API_QSYS_QSYS_MAPMAPC_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * The MAP/MAP/c FCFS queue whose service MAP is CARRIED in service-start order.
12 *
13 * THE SERVICE LAW. The service MAP is one station-level process whose successive
14 * draws are chained in service-START order, idle periods included: each job starts
15 * in the phase the previous draw ENDED in, which is how JMT and both LDES engines
16 * sample a MAP service and the law SolverCTMC follows through its pair form. A
17 * renewal reading of the marginal (qsys_mapphc, or the single-fast-server
18 * surrogate) discards the autocorrelation of the service times.
19 *
20 * THE STATE SPACE. With V = (-S0)^-1 S1, a draw started in h lands in j with
21 * probability V(h,j); a busy server is a PAIR (i,j) of current phase i and landing
22 * j, run under S0 conditioned on ending in j. A level holds the MULTISET of busy
23 * pairs and the landing phase of the draw started last. `ldqbd_mphc(..., carry)`
24 * builds those blocks; this header lifts them to a MAP arrival stream and solves
25 * the level-independent QBD above level c matrix-geometrically, exactly as
26 * qsys_mapphc does for a renewal service. At c = 1 it is the frozen-phase
27 * MAP/MAP/1 queue.
28 *
29 * References:
30 * S. Asmussen and J.R. Moller, "Calculation of the steady state waiting time
31 * distribution in GI/PH/c and MAP/PH/c queues", Queueing Systems 37(1):9-29,
32 * 2001.
33 * M.F. Neuts, "Matrix-geometric solutions in stochastic models", Johns Hopkins
34 * University Press, 1981.
35 */
36
37#include <cstddef>
38#include <vector>
39
42#include "line/api/mam/qbd_r.h"
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/linalg.h"
47#include "line/util/lu.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace qsys {
52
53/**
54 * MAP/MAP/c FCFS with the service phase carried in start order, exactly.
55 *
56 * @param arrival arrival MAP (D0, D1) of order ma
57 * @param service service MAP (S0, S1) of order p
58 * @param c number of servers, c >= 1
59 * @param dist_size cap on the queue length probabilities materialized
60 * @param num_w_moms how many waiting-time moments to return (0: E[Wq] by Little)
61 *
62 * Returns the MapPhcResult struct; phaseCount is the repeating configuration count.
63 */
64template <class T>
65MapPhcResult<T> qsys_mapmapc(const mam::Map<T>& arrival, const mam::Map<T>& service, unsigned c,
66 std::size_t dist_size = 500, std::size_t num_w_moms = 3) {
68 "qsys_mapmapc requires transcendental arithmetic");
69 using mapphcdetail::madd2;
70 using mapphcdetail::mkron;
71
72 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
73 const Matrix<T>& D0 = arrival.D0;
74 const Matrix<T>& D1 = arrival.D1;
75 const std::size_t ma = D0.rows();
76 const std::size_t p = service.D0.rows();
77 if (D0.cols() != ma || D1.rows() != ma || D1.cols() != ma)
78 throw InputError("qsys_mapmapc: D0 and D1 must be square and of equal order");
79 if (service.D0.cols() != p || service.D1.rows() != p || service.D1.cols() != p)
80 throw InputError("qsys_mapmapc: S0 and S1 must be square and of equal order");
81 if (c < 1) throw InputError("qsys_mapmapc: c must be a positive integer");
82 if (dist_size == 0) throw InputError("qsys_mapmapc: dist_size must be positive");
83
84 const T lambda = mam::map_lambda(arrival);
85 if (lambda <= zero) throw InputError("qsys_mapmapc: non-positive arrival rate");
86 const T mean_service = one / mam::map_lambda(service);
87 const T ct = num_traits<T>::from_int(static_cast<long>(c));
88 const T rho = lambda * mean_service / ct;
89 if (rho >= one) throw InputError("qsys_mapmapc: load rho must be strictly less than 1");
90
91 // Carried-phase blocks at unit arrival rate; Q2[n] leaves level n, so DN_k = Q2[k]
92 // for k <= c and Q2[c+1] is the repeating completion that hands the server on.
93 const std::vector<T> alpha(p, one / num_traits<T>::from_int(static_cast<long>(p)));
94 const std::vector<T> arr(c + 2, one);
96 mam::ldqbd_mphc(service.D0, service.D1, alpha, static_cast<double>(c), arr,
97 std::vector<T>(), true);
98 std::vector<Matrix<T> > LOC(c + 1);
99 for (unsigned k = 0; k <= c; ++k) {
100 LOC[k] = B.Q1[k];
101 for (std::size_t i = 0; i < LOC[k].rows(); ++i) LOC[k](i, i) += one;
102 }
103 const Matrix<T>& CDEP = B.Q2[c + 1];
104 const std::size_t nc = CDEP.rows();
105
106 const Matrix<T> Ima = eye<T>(ma);
107 const Matrix<T> Inc = eye<T>(nc);
108 const Matrix<T> A_up = mkron(D1, Inc);
109 const Matrix<T> A_loc = madd2(mkron(D0, Inc), mkron(Ima, LOC[c]));
110 const Matrix<T> A_dn = mkron(Ima, CDEP);
111 const Matrix<T> R = mam::qbd_R_logred(A_dn, A_loc, A_up);
112
113 const std::size_t n_op = nc * ma;
114 Matrix<T> ImR(n_op, n_op, zero);
115 for (std::size_t i = 0; i < n_op; ++i)
116 for (std::size_t j = 0; j < n_op; ++j) ImR(i, j) = (i == j ? one : zero) - R(i, j);
117 const Matrix<T> ImRinv = inverse(ImR);
118 const std::vector<T> sum_geom = mulvec(ImRinv, ones<T>(n_op));
119
120 std::vector<std::size_t> sz(c + 1), off(c + 2, 0);
121 for (unsigned k = 0; k <= c; ++k) {
122 sz[k] = ma * LOC[k].rows();
123 off[k + 1] = off[k] + sz[k];
124 }
125 const std::size_t tot = off[c + 1];
126 Matrix<T> Q(tot, tot, zero);
127 const Matrix<T> RA_dn = matmul(R, A_dn);
128 for (unsigned k = 0; k <= c; ++k) {
129 const Matrix<T> Ick = eye<T>(LOC[k].rows());
130 const Matrix<T> diag = (k < c) ? madd2(mkron(D0, Ick), mkron(Ima, LOC[k]))
131 : madd2(A_loc, RA_dn);
132 for (std::size_t i = 0; i < sz[k]; ++i)
133 for (std::size_t j = 0; j < sz[k]; ++j) Q(off[k] + i, off[k] + j) += diag(i, j);
134 if (k < c) {
135 const Matrix<T> up = mkron(D1, B.Q0[k]);
136 for (std::size_t i = 0; i < up.rows(); ++i)
137 for (std::size_t j = 0; j < up.cols(); ++j) Q(off[k] + i, off[k + 1] + j) += up(i, j);
138 }
139 if (k > 0) {
140 const Matrix<T> dn = mkron(Ima, B.Q2[k]);
141 for (std::size_t i = 0; i < dn.rows(); ++i)
142 for (std::size_t j = 0; j < dn.cols(); ++j) Q(off[k] + i, off[k - 1] + j) += dn(i, j);
143 }
144 }
145
146 // pi Q = 0 with the normalization over boundary and tail replacing the last equation
147 Matrix<T> M(tot, tot, zero);
148 for (std::size_t i = 0; i < tot; ++i)
149 for (std::size_t j = 0; j < tot; ++j) M(j, i) = Q(i, j);
150 for (std::size_t col = 0; col < tot; ++col) M(tot - 1, col) = zero;
151 for (std::size_t i = 0; i < off[c]; ++i) M(tot - 1, i) = one;
152 for (std::size_t i = 0; i < sz[c]; ++i) M(tot - 1, off[c] + i) = sum_geom[i];
153 std::vector<T> b(tot, zero);
154 b[tot - 1] = one;
155 const std::vector<T> pi_vec = line::solve(M, b);
156
157 std::vector<T> pi_c(n_op);
158 for (std::size_t i = 0; i < n_op; ++i) pi_c[i] = pi_vec[off[c] + i];
159
160 std::vector<T> ql;
161 T acc = zero;
162 for (unsigned k = 0; k < c; ++k) {
163 T s = zero;
164 for (std::size_t i = 0; i < sz[k]; ++i) s += pi_vec[off[k] + i];
165 ql.push_back(s);
166 acc += s;
167 }
168 std::vector<T> tail = pi_c;
169 {
170 T s = zero;
171 for (std::size_t i = 0; i < n_op; ++i) s += tail[i];
172 ql.push_back(s);
173 acc += s;
174 }
175 while (acc < one - num_traits<T>::from_double(1e-12) && ql.size() < dist_size) {
176 std::vector<T> next(n_op, zero);
177 for (std::size_t j = 0; j < n_op; ++j)
178 for (std::size_t i = 0; i < n_op; ++i) next[j] += tail[i] * R(i, j);
179 tail = next;
180 T s = zero;
181 for (std::size_t i = 0; i < n_op; ++i) s += tail[i];
182 ql.push_back(s);
183 acc += s;
184 }
185 // E[N] in closed form over the matrix-geometric tail, as in qsys_mapphc
186 T meanQL = zero;
187 for (unsigned k = 0; k < c; ++k)
188 meanQL += num_traits<T>::from_int(static_cast<long>(k)) * ql[k];
189 const std::vector<T> r_tail = mulvec(R, mulvec(ImRinv, sum_geom));
190 for (std::size_t i = 0; i < n_op; ++i) meanQL += pi_c[i] * (ct * sum_geom[i] + r_tail[i]);
191
192 // Waiting time: an arrival finding j waiting waits for j+1 completions of the
193 // full-bank configuration process (LOC_c, CDEP), started at its arrival epoch.
194 const Matrix<T> Lj = mkron(Ima, LOC[c]);
195 const Matrix<T> Cj = mkron(Ima, CDEP);
196 Matrix<T> G0 = matmul(ImRinv, mkron(D1, Inc));
197 for (std::size_t i = 0; i < n_op; ++i)
198 for (std::size_t j = 0; j < n_op; ++j) G0(i, j) = G0(i, j) / lambda;
199 T probWait = zero;
200 {
201 const std::vector<T> v = mulvec(G0, ones<T>(n_op));
202 for (std::size_t i = 0; i < n_op; ++i) probWait += pi_c[i] * v[i];
203 }
204 std::vector<T> wMoms;
205 if (num_w_moms > 0) {
206 // X(-Lj) - R X Cj = rhs, vectorized column-major
207 const std::size_t n2 = n_op * n_op;
208 Matrix<T> Kop(n2, n2, zero);
209 for (std::size_t a = 0; a < n_op; ++a) {
210 for (std::size_t bcol = 0; bcol < n_op; ++bcol) {
211 for (std::size_t i = 0; i < n_op; ++i)
212 Kop(a * n_op + i, bcol * n_op + i) += -Lj(bcol, a);
213 for (std::size_t i = 0; i < n_op; ++i)
214 for (std::size_t q = 0; q < n_op; ++q)
215 Kop(a * n_op + i, bcol * n_op + q) += -R(i, q) * Cj(bcol, a);
216 }
217 }
218 Matrix<T> gPrev(n_op, n_op, zero);
219 for (std::size_t k = 1; k <= num_w_moms; ++k) {
220 std::vector<T> rhs(n2, zero);
221 for (std::size_t j = 0; j < n_op; ++j)
222 for (std::size_t i = 0; i < n_op; ++i)
223 rhs[j * n_op + i] = (k == 1)
224 ? G0(i, j)
225 : -num_traits<T>::from_int(static_cast<long>(k - 1)) * gPrev(i, j);
226 const std::vector<T> gv = line::solve(Kop, rhs);
227 Matrix<T> g(n_op, n_op, zero);
228 for (std::size_t j = 0; j < n_op; ++j)
229 for (std::size_t i = 0; i < n_op; ++i) g(i, j) = gv[j * n_op + i];
230 const std::vector<T> v = mulvec(g, ones<T>(n_op));
231 T mk = zero;
232 for (std::size_t i = 0; i < n_op; ++i) mk += pi_c[i] * v[i];
233 mk = mk * num_traits<T>::from_int(static_cast<long>(k));
234 if (k % 2 == 0) mk = -mk;
235 wMoms.push_back(mk);
236 gPrev = g;
237 }
238 }
239
241 r.meanQueueLength = meanQL;
242 r.meanWaitingTime = wMoms.empty() ? meanQL / lambda - mean_service : wMoms[0];
243 r.meanSojournTime = r.meanWaitingTime + mean_service;
244 r.utilization = rho;
245 r.queueLengthDist = ql;
246 r.waitingTimeMoments = wMoms;
247 r.probWait = probWait;
248 r.phaseCount = nc;
249 return r;
250}
251
252} // namespace qsys
253} // namespace line
254
255#endif // LINE_API_QSYS_QSYS_MAPMAPC_H
InputError(const std::string &what)
Definition error.h:105
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Port of ldqbd_mphc.m and ph_multisets.m: the exact level-dependent QBD blocks of an M/PH/c queue.
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.
LdqbdMphcBlocks< T > ldqbd_mphc(const Matrix< T > &D0, const Matrix< T > &D1, const std::vector< T > &alpha, double c, const std::vector< T > &arrRate, const std::vector< T > &sf, bool carry=false)
Block-tridiagonal generator of an M/PH/c queue with level-dependent arrivals.
Definition ldqbd_mphc.h:272
Matrix< T > qbd_R_logred(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
R by logarithmic reduction (qbd_R_logred.m).
Definition qbd_r.h:217
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
Definition map_moment.h:79
MapPhcResult< T > qsys_mapmapc(const mam::Map< T > &arrival, const mam::Map< T > &service, unsigned c, std::size_t dist_size=500, std::size_t num_w_moms=3)
MAP/MAP/c FCFS with the service phase carried in start order, exactly.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
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
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
Quasi-birth-death processes: the rate matrix R, the fundamental matrix G, the caudal characteristic,...
The MAP/PH/c FCFS queue, solved exactly.
The three block lists of a level-dependent QBD, as ldqbd takes them.
Definition ldqbd_mphc.h:90
std::vector< Matrix< T > > Q0
Definition ldqbd_mphc.h:91
std::vector< Matrix< T > > Q1
Definition ldqbd_mphc.h:92
std::vector< Matrix< T > > Q2
Definition ldqbd_mphc.h:93
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_mapphc, mirroring the MATLAB struct.
Definition qsys_mapphc.h:75
T meanWaitingTime
E[Wq], time in queue.
Definition qsys_mapphc.h:77
T probWait
P(Wq > 0), an arrival finds every server busy.
Definition qsys_mapphc.h:84
std::size_t phaseCount
binomial(ms+c-1,c), the repeating config count
Definition qsys_mapphc.h:85
std::vector< T > waitingTimeMoments
E[Wq^k], k = 1..num_w_moms.
Definition qsys_mapphc.h:81
T utilization
rho = lambda E[service] / c, per server
Definition qsys_mapphc.h:79
T meanQueueLength
E[N], number in system.
Definition qsys_mapphc.h:76
std::vector< T > queueLengthDist
P(N = n), n = 0, 1, ...
Definition qsys_mapphc.h:80
T meanSojournTime
E[Wq] + E[service].
Definition qsys_mapphc.h:78