LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_phmc.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_PHMC_H
6#define LINE_API_QSYS_QSYS_PHMC_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Exact PH/M/c by Neuts' matrix-geometric method.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_phmc.m, cross-checked against
14 * jar/src/main/java/jline/api/qsys/Qsys_phmc.java.
15 *
16 * The level is the number in system and the phase is the arrival PH phase.
17 * Above level c the QBD is level independent with
18 *
19 * A0 = (-T e) alpha, A1 = T - c mu I, A2 = c mu I,
20 *
21 * and pi_n = pi_c R^(n-c) for n >= c, R the minimal solution of
22 * R^2 A2 + R A1 + A0 = 0 reached by the fixed point R <- -A0 (A1 + R A2)^-1.
23 * The boundary vectors pi_0..pi_c come from the level balance equations with
24 * the last one replaced by the normalization
25 *
26 * sum_{n<c} pi_n e + pi_c (I-R)^-1 e = 1,
27 *
28 * after which Lq = pi_c R (I-R)^-2 e and L = sum_{n<c} n pi_n e +
29 * pi_c (c (I-R)^-1 + R (I-R)^-2) e.
30 *
31 * ARITHMETIC. R is a fixed point driven to a tolerance and never terminates in
32 * a finite number of field operations, so the function is gated on
33 * transcendental arithmetic, for the same reason qbd_R is (see qbd_r.h).
34 *
35 * At k = 1 with T = [-lambda] the arrival process is Poisson and every metric
36 * must collapse onto M/M/c, which is the check the tests apply.
37 */
38
39#include <cstddef>
40#include <vector>
41
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 PhMcResult {
56 T meanWaitingTime; ///< Wq
58 T utilization; ///< rho = lambda/(c mu)
59};
60
61namespace detail {
62
63/**
64 * Determinant through the LU factorization, standing in for MATLAB's det in
65 * the singularity guard of the R iteration. A zero pivot is reported as a zero
66 * determinant rather than an error, which is the branch MATLAB takes.
67 */
68template <class T>
69T matrix_det(const Matrix<T>& A) {
70 Matrix<T> LU = A;
71 std::vector<std::size_t> piv;
72 try {
73 piv = lu_factor(LU);
74 } catch (const NumericError&) {
76 }
78 for (std::size_t i = 0; i < LU.rows(); ++i) d *= LU(i, i);
79 for (std::size_t i = 0; i < piv.size(); ++i)
80 if (piv[i] != i) d = -d;
81 return d;
82}
83
84} // namespace detail
85
86/**
87 * @brief Exact PH/M/c by Neuts' matrix-geometric method.
88 *
89 * @param alpha PH entry probability vector, length k
90 * @param Tm PH sub-generator, k x k
91 * @param mu exponential service rate of one server
92 * @param c number of servers, c >= 1
93 * @param maxIter iteration budget for the R fixed point (MATLAB 50000)
94 * @param tol convergence tolerance on R (MATLAB 1e-14)
95 */
96template <class T>
97PhMcResult<T> qsys_phmc(const std::vector<T>& alpha, const Matrix<T>& Tm, const T& mu, unsigned c,
98 unsigned maxIter, const T& tol) {
100 "qsys_phmc requires transcendental arithmetic");
101 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
102 if (mu <= zero) throw InputError("qsys_phmc: service rate mu must be positive");
103 if (c < 1) throw InputError("qsys_phmc: c must be a positive integer");
104 const std::size_t k = Tm.rows();
105 if (Tm.cols() != k || alpha.size() != k)
106 throw InputError("qsys_phmc: alpha and T dimensions are inconsistent");
107 const T ct = num_traits<T>::from_int(static_cast<long>(c));
108
109 std::vector<T> t_vec(k, zero);
110 for (std::size_t i = 0; i < k; ++i)
111 for (std::size_t j = 0; j < k; ++j) t_vec[i] -= Tm(i, j);
112 Matrix<T> D1(k, k);
113 for (std::size_t i = 0; i < k; ++i)
114 for (std::size_t j = 0; j < k; ++j) D1(i, j) = t_vec[i] * alpha[j];
115
116 Matrix<T> negT(k, k);
117 for (std::size_t i = 0; i < k; ++i)
118 for (std::size_t j = 0; j < k; ++j) negT(i, j) = -Tm(i, j);
119 const std::vector<T> mvec = line::solve(negT, ones<T>(k));
120 T mean_ia = zero;
121 for (std::size_t i = 0; i < k; ++i) mean_ia += alpha[i] * mvec[i];
122 if (mean_ia <= zero) throw InputError("qsys_phmc: non-positive mean interarrival time");
123 const T lambda = one / mean_ia;
124 const T rho = lambda / (ct * mu);
125 if (rho >= one) throw InputError("qsys_phmc: load rho must be strictly less than 1");
126
127 // R by successive substitution: R <- -A0 (A1 + R A2)^-1.
128 const Matrix<T>& A0 = D1;
129 Matrix<T> A1(k, k), A2(k, k, zero);
130 for (std::size_t i = 0; i < k; ++i) {
131 for (std::size_t j = 0; j < k; ++j) A1(i, j) = Tm(i, j);
132 A1(i, i) -= ct * mu;
133 A2(i, i) = ct * mu;
134 }
135 Matrix<T> R(k, k, zero);
136 const T det_floor = T(num_traits<T>::from_double(1e-30));
137 for (unsigned it = 0; it < maxIter; ++it) {
138 Matrix<T> M = A1;
139 const Matrix<T> RA2 = matmul(R, A2);
140 for (std::size_t i = 0; i < k; ++i)
141 for (std::size_t j = 0; j < k; ++j) M(i, j) += RA2(i, j);
142 if (num_abs(detail::matrix_det(M)) < det_floor) break;
143 Matrix<T> Rn = matmul(A0, inverse(M));
144 for (std::size_t i = 0; i < k; ++i)
145 for (std::size_t j = 0; j < k; ++j) Rn(i, j) = -Rn(i, j);
146 T gap = zero;
147 for (std::size_t i = 0; i < k; ++i)
148 for (std::size_t j = 0; j < k; ++j) {
149 const T d = num_abs(T(Rn(i, j) - R(i, j)));
150 if (d > gap) gap = d;
151 }
152 R = Rn;
153 if (gap < tol) break;
154 }
155
156 // Boundary system on the stacked unknowns [pi_0 ... pi_c].
157 const std::size_t nvar = (c + 1) * k;
158 Matrix<T> M(nvar, nvar, zero);
159 // Level 0: pi_0 T + pi_1 (mu I) = 0.
160 for (std::size_t j = 0; j < k; ++j) {
161 for (std::size_t i = 0; i < k; ++i) M(j, i) += Tm(i, j);
162 M(j, k + j) += mu;
163 }
164 // Levels 1..c-1: pi_{n-1} D1 + pi_n (T - n mu I) + pi_{n+1} ((n+1) mu I) = 0.
165 for (unsigned n = 1; n < c; ++n) {
166 const T nt = num_traits<T>::from_int(static_cast<long>(n));
167 for (std::size_t j = 0; j < k; ++j) {
168 const std::size_t row = n * k + j;
169 for (std::size_t i = 0; i < k; ++i) {
170 M(row, (n - 1) * k + i) += D1(i, j);
171 M(row, n * k + i) += Tm(i, j);
172 }
173 M(row, n * k + j) -= nt * mu;
174 M(row, (n + 1) * k + j) += (nt + one) * mu;
175 }
176 }
177 // Level c: pi_{c-1} D1 + pi_c (T - c mu I + R (c mu I)) = 0.
178 const Matrix<T> RA2c = matmul(R, A2);
179 for (std::size_t j = 0; j < k; ++j) {
180 const std::size_t row = c * k + j;
181 for (std::size_t i = 0; i < k; ++i) {
182 M(row, (c - 1) * k + i) += D1(i, j);
183 M(row, c * k + i) += Tm(i, j) + RA2c(i, j);
184 }
185 M(row, c * k + j) -= ct * mu;
186 }
187 // Replace the last equation with the normalization.
188 Matrix<T> IR(k, k);
189 for (std::size_t i = 0; i < k; ++i)
190 for (std::size_t j = 0; j < k; ++j) IR(i, j) = (i == j ? one : zero) - R(i, j);
191 const std::vector<T> sum_geom = line::solve(IR, ones<T>(k));
192 for (std::size_t col = 0; col < nvar; ++col) M(nvar - 1, col) = zero;
193 for (unsigned n = 0; n < c; ++n)
194 for (std::size_t i = 0; i < k; ++i) M(nvar - 1, n * k + i) = one;
195 for (std::size_t i = 0; i < k; ++i) M(nvar - 1, c * k + i) = sum_geom[i];
196 std::vector<T> b(nvar, zero);
197 b[nvar - 1] = one;
198 const std::vector<T> x = line::solve(M, b);
199
200 std::vector<T> pi_c(k);
201 for (std::size_t i = 0; i < k; ++i) pi_c[i] = x[c * k + i];
202
203 const Matrix<T> IRinv = inverse(IR);
204 const Matrix<T> IRinv2 = matmul(IRinv, IRinv);
205 const Matrix<T> R_IRinv2 = matmul(R, IRinv2);
206 const std::vector<T> e = ones<T>(k);
207 const std::vector<T> v1 = mulvec(R_IRinv2, e);
208 T Lq = zero;
209 for (std::size_t i = 0; i < k; ++i) Lq += pi_c[i] * v1[i];
210 Matrix<T> bulk(k, k);
211 for (std::size_t i = 0; i < k; ++i)
212 for (std::size_t j = 0; j < k; ++j) bulk(i, j) = ct * IRinv(i, j) + R_IRinv2(i, j);
213 const std::vector<T> v2 = mulvec(bulk, e);
214 T L = zero;
215 for (std::size_t i = 0; i < k; ++i) L += pi_c[i] * v2[i];
216 for (unsigned n = 0; n < c; ++n) {
217 T s = zero;
218 for (std::size_t i = 0; i < k; ++i) s += x[n * k + i];
219 L += num_traits<T>::from_int(static_cast<long>(n)) * s;
220 }
221
223 r.meanQueueLength = L;
224 r.meanWaitingQueue = Lq;
225 r.meanWaitingTime = Lq / lambda;
226 r.meanSojournTime = r.meanWaitingTime + one / mu;
227 r.utilization = rho;
228 return r;
229}
230
231/** qsys_phmc with the MATLAB defaults, 50000 iterations and tolerance 1e-14. */
232template <class T>
233PhMcResult<T> qsys_phmc(const std::vector<T>& alpha, const Matrix<T>& Tm, const T& mu,
234 unsigned c) {
235 return qsys_phmc(alpha, Tm, mu, c, 50000u, T(num_traits<T>::from_double(1e-14)));
236}
237
238} // namespace qsys
239} // namespace line
240
241#endif // LINE_API_QSYS_QSYS_PHMC_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 algorithm cannot proceed on this instance (singular matrix, ...).
Definition error.h:43
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.
PhMcResult< T > qsys_phmc(const std::vector< T > &alpha, const Matrix< T > &Tm, const T &mu, unsigned c, unsigned maxIter, const T &tol)
Exact PH/M/c by Neuts' matrix-geometric method.
Definition qsys_phmc.h:97
T num_abs(const T &v)
Definition number.h:172
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition lu.h:48
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.
Shared return type and arithmetic helpers for the templated qsys port.
T utilization
rho = lambda/(c mu)
Definition qsys_phmc.h:58