LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
map_m1ps.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_MAM_MAP_M1PS_H
6#define LINE_API_MAM_MAP_M1PS_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Sojourn time distribution in a MAP/M/1 processor-sharing queue.
12 *
13 * Templated port of matlab/src/api/mam/map_compute_R.m,
14 * map_m1ps_h_recursive.m, map_m1ps_sojourn.m and map_m1ps_cdfrespt.m, which
15 * implement Theorem 1 of H. Masuyama and T. Takine, "Sojourn time distribution
16 * in a MAP/M/1 processor-sharing queue", Operations Research Letters 31(6),
17 * 2003, 406-412.
18 *
19 * The arrival process is the MAP (C, D) -- C carries the hidden transitions,
20 * D the arrivals -- and service is exponential of rate mu shared equally, so
21 * with n jobs present each is served at rate mu/n. The queue length is a QBD
22 * whose rate matrix R is the minimal nonnegative solution of
23 *
24 * D + R (C - mu I) + mu R^2 = 0,
25 *
26 * and the complementary sojourn time distribution is, by uniformization at
27 * theta + mu with theta = max_i |C_ii|,
28 *
29 * W^c(x) = (1/lambda) sum_n pi_0 R^n D sum_k e^-(theta+mu)x
30 * ((theta+mu) x)^k / k! h_{n,k},
31 *
32 * with pi_0 = pi (I - R) and the vectors h_{n,k} from the recursion
33 *
34 * h_{n,0} = e
35 * h_{n,k+1} = [ n mu/(n+1) h_{n-1,k} + (theta I + C) h_{n,k}
36 * + D h_{n+1,k} ] / (theta + mu), h_{-1,k} = 0.
37 *
38 * ARITHMETIC.
39 * - map_m1ps_h_recursive is a FINITE recursion in the entries of C and D,
40 * with theta a maximum of absolute diagonal entries, so it instantiates at
41 * every arithmetic including Rational and returns exact fractions.
42 * - map_compute_R is a fixed-point iteration driven to a tolerance and is
43 * gated on num_traits<T>::has_transcendental, for the same reason as
44 * qbd_R (see qbd_r.h).
45 * - map_m1ps_sojourn and map_m1ps_cdfrespt need the Poisson weights
46 * e^-a a^k / k! and are gated as well.
47 *
48 * THE TWO ENTRY POINTS ARE NOT THE SAME FUNCTION, despite identical
49 * signatures and identical documentation in the reference. They differ in
50 * three ways, all reproduced here:
51 * 1. The queue-length truncation. map_m1ps_sojourn finds the smallest N with
52 * (1/lambda) sum_{n<=N} pi_0 R^n D e > 1 - epsilon, scanning n = 0..1000,
53 * and falls back to N = 100 when the scan never gets there.
54 * map_m1ps_cdfrespt instead estimates N from the spectral radius of R,
55 * N = ceil(log(epsilon (1 - sp)) / log(sp)), clamps it to [10, 10000] and
56 * then truncates AGAIN at run time as soon as ||pi_0 R^n D||_inf drops
57 * below epsilon/100.
58 * 2. The stationary vector. map_m1ps_sojourn solves the (M+1) x M
59 * overdetermined system [Q; e'] x = [0; 1] in the least-squares sense,
60 * whereas map_m1ps_cdfrespt replaces the LAST ROW of Q' by e' and solves
61 * the resulting square system. Both give the stationary vector of an
62 * irreducible generator; the port uses the square solve of ctmc_solve for
63 * both, which is the same vector.
64 * 3. R itself. map_m1ps_sojourn calls map_compute_R, iterating
65 * R <- -D (C - mu I + mu R)^-1. map_m1ps_cdfrespt has a PRIVATE
66 * compute_R_matrix that iterates the different splitting
67 * R <- (D + mu R^2) (mu I - C)^-1 from the warm start -D (C - mu I)^-1,
68 * with 5000 rather than 1000 iterations, and for M = 1 solves the scalar
69 * quadratic in closed form. The two fixed points coincide -- both are the
70 * minimal nonnegative solution -- so the two are exposed here as
71 * map_compute_R and map_compute_R_quadratic and the tests check they
72 * agree to the residual of the defining equation.
73 *
74 * REFERENCE DEFECT. The second output of both functions, W_bar_n, is
75 * documented as "the conditional complementary distribution for customers
76 * finding n customers in the system", but both compute it as
77 * sum(sum_k)/M -- the arithmetic MEAN OVER PHASES of the uniformized
78 * h-weighted sum, with no reference to the phase distribution at an arrival
79 * and no normalization by the probability of finding n customers. It is not a
80 * conditional distribution: on the M/M/1-PS instance of the tests (M = 1,
81 * lambda = 0.8, mu = 1) the n = 0 curve at x = 0 is 1.0 and DECREASES
82 * correctly, but for M > 1 the phases are weighted uniformly rather than by
83 * pie, so it is not a probability of anything. The port returns it under the
84 * name w_bar_n_unweighted to make the meaning explicit, and computes it
85 * identically so a caller comparing against MATLAB sees the same numbers.
86 */
87
88#include <cmath>
89#include <cstddef>
90#include <vector>
91
93#include "line/api/mam/qbd_r.h"
95#include "line/num/number.h"
96#include "line/util/error.h"
97#include "line/util/linalg.h"
98#include "line/util/matrix.h"
99
100namespace line {
101namespace mam {
102
103/** Residual ||D + R (C - mu I) + mu R^2||_inf of the MAP/M/1 rate equation. */
104template <class T>
105T map_compute_R_residual(const Matrix<T>& C, const Matrix<T>& D, const T& mu, const Matrix<T>& R) {
106 const std::size_t m = C.rows();
107 Matrix<T> res = D;
108 const Matrix<T> CmuI = [&]() {
109 Matrix<T> X = C;
110 for (std::size_t i = 0; i < m; ++i) X(i, i) -= mu;
111 return X;
112 }();
113 const Matrix<T> t1 = matmul(R, CmuI);
114 const Matrix<T> t2 = matmul(R, R);
115 for (std::size_t i = 0; i < m; ++i)
116 for (std::size_t j = 0; j < m; ++j) res(i, j) += t1(i, j) + mu * t2(i, j);
117 T worst = num_traits<T>::from_int(0);
118 for (std::size_t i = 0; i < m; ++i) {
120 for (std::size_t j = 0; j < m; ++j) s += num_abs(T(res(i, j)));
121 if (s > worst) worst = s;
122 }
123 return worst;
124}
125
126/**
127 * Rate matrix R of a MAP/M/1 queue, the minimal nonnegative solution of
128 * D + R (C - mu I) + mu R^2 = 0, by the iteration
129 * R <- -D (C - mu I + mu R)^-1 (map_compute_R.m).
130 *
131 * Negative entries produced by rounding are clamped to zero, as in the
132 * reference; the clamp is a no-op whenever the iteration has converged.
133 */
134template <class T>
135Matrix<T> map_compute_R(const Matrix<T>& C, const Matrix<T>& D, const T& mu, unsigned max_iter,
136 const T& tol) {
138 "map_compute_R requires transcendental arithmetic");
139 const std::size_t m = C.rows();
140 if (C.cols() != m || D.rows() != m || D.cols() != m)
141 throw InputError("map_compute_R: C and D must be square and of equal order");
142 const T zero = num_traits<T>::from_int(0);
143 Matrix<T> R(m, m, zero);
144 for (unsigned it = 0; it < max_iter; ++it) {
145 Matrix<T> X(m, m);
146 for (std::size_t i = 0; i < m; ++i)
147 for (std::size_t j = 0; j < m; ++j) X(i, j) = C(i, j) + mu * R(i, j);
148 for (std::size_t i = 0; i < m; ++i) X(i, i) -= mu;
149 Matrix<T> Rn = matmul(D, inverse(X));
150 for (std::size_t i = 0; i < m; ++i)
151 for (std::size_t j = 0; j < m; ++j) Rn(i, j) = -Rn(i, j);
152 T diff = zero;
153 for (std::size_t i = 0; i < m; ++i) {
154 T s = zero;
155 for (std::size_t j = 0; j < m; ++j) s += num_abs(T(Rn(i, j) - R(i, j)));
156 if (s > diff) diff = s;
157 }
158 R = Rn;
159 if (diff < tol) break;
160 }
161 for (std::size_t i = 0; i < m; ++i)
162 for (std::size_t j = 0; j < m; ++j)
163 if (R(i, j) < zero) R(i, j) = zero;
164 return R;
165}
166
167/** map_compute_R with the reference defaults, 1000 iterations and tolerance 1e-10. */
168template <class T>
169Matrix<T> map_compute_R(const Matrix<T>& C, const Matrix<T>& D, const T& mu) {
170 return map_compute_R(C, D, mu, 1000u, T(num_traits<T>::from_double(1e-10)));
171}
172
173/**
174 * The same R by the other splitting, R <- (D + mu R^2) (mu I - C)^-1, warm
175 * started at -D (C - mu I)^-1 and with the scalar case solved in closed form
176 * (the private compute_R_matrix of map_m1ps_cdfrespt.m).
177 *
178 * For M = 1 the equation is mu R^2 + (C - mu) R + D = 0 and the root in [0, 1)
179 * is selected, which for Poisson arrivals (C = -lambda, D = lambda) is exactly
180 * the utilization rho = lambda/mu. That closed form is the sharpest available
181 * oracle for the matrix iteration.
182 */
183template <class T>
184Matrix<T> map_compute_R_quadratic(const Matrix<T>& C, const Matrix<T>& D, const T& mu,
185 unsigned max_iter, const T& tol) {
187 "map_compute_R_quadratic requires transcendental arithmetic");
188 const std::size_t m = C.rows();
189 if (C.cols() != m || D.rows() != m || D.cols() != m)
190 throw InputError("map_compute_R_quadratic: C and D must be square and of equal order");
191 const T zero = num_traits<T>::from_int(0);
192 const T one = num_traits<T>::from_int(1);
193 Matrix<T> R(m, m, zero);
194
195 if (m == 1) {
196 const T a = mu, b = T(C(0, 0) - mu), c = D(0, 0);
197 const T disc = b * b - num_traits<T>::from_int(4) * a * c;
198 if (disc < zero) throw NumericError("map_compute_R: no real solution for R");
199 using std::sqrt;
200 const T sd = T(sqrt(disc));
201 const T r1 = T((-b - sd) / (num_traits<T>::from_int(2) * a));
202 const T r2 = T((-b + sd) / (num_traits<T>::from_int(2) * a));
203 if (r1 >= zero && r1 < one)
204 R(0, 0) = r1;
205 else if (r2 >= zero && r2 < one)
206 R(0, 0) = r2;
207 else
208 throw NumericError("map_compute_R: no valid solution in [0,1) for R");
209 return R;
210 }
211
212 Matrix<T> CmuI = C;
213 for (std::size_t i = 0; i < m; ++i) CmuI(i, i) -= mu;
214 R = matmul(D, inverse(CmuI));
215 for (std::size_t i = 0; i < m; ++i)
216 for (std::size_t j = 0; j < m; ++j) R(i, j) = -R(i, j);
217
218 Matrix<T> muImC(m, m);
219 for (std::size_t i = 0; i < m; ++i)
220 for (std::size_t j = 0; j < m; ++j) muImC(i, j) = -C(i, j);
221 for (std::size_t i = 0; i < m; ++i) muImC(i, i) += mu;
222 const Matrix<T> muImCinv = inverse(muImC);
223
224 for (unsigned it = 0; it < max_iter; ++it) {
225 const Matrix<T> R2 = matmul(R, R);
226 Matrix<T> X(m, m);
227 for (std::size_t i = 0; i < m; ++i)
228 for (std::size_t j = 0; j < m; ++j) X(i, j) = D(i, j) + mu * R2(i, j);
229 const Matrix<T> Rn = matmul(X, muImCinv);
230 T diff = zero;
231 for (std::size_t i = 0; i < m; ++i) {
232 T s = zero;
233 for (std::size_t j = 0; j < m; ++j) s += num_abs(T(Rn(i, j) - R(i, j)));
234 if (s > diff) diff = s;
235 }
236 R = Rn;
237 if (diff < tol) break;
238 }
239 for (std::size_t i = 0; i < m; ++i)
240 for (std::size_t j = 0; j < m; ++j)
241 if (R(i, j) < zero) R(i, j) = zero;
242 return R;
243}
244
245/** map_compute_R_quadratic with the reference defaults, 5000 iterations, 1e-10. */
246template <class T>
247Matrix<T> map_compute_R_quadratic(const Matrix<T>& C, const Matrix<T>& D, const T& mu) {
248 return map_compute_R_quadratic(C, D, mu, 5000u, T(num_traits<T>::from_double(1e-10)));
249}
250
251/**
252 * The vectors h_{n,k} of Theorem 1 (map_m1ps_h_recursive.m).
253 *
254 * @return h[n][k], each of length M, for n = 0..N and k = 0..K
255 */
256template <class T>
257std::vector<std::vector<std::vector<T>>> map_m1ps_h_recursive(const Matrix<T>& C,
258 const Matrix<T>& D, const T& mu,
259 std::size_t N, std::size_t K) {
260 const std::size_t M = C.rows();
261 if (C.cols() != M || D.rows() != M || D.cols() != M)
262 throw InputError("map_m1ps_h_recursive: C and D must be square and of equal order");
263 const T zero = num_traits<T>::from_int(0);
264
265 T theta = zero;
266 for (std::size_t i = 0; i < M; ++i) {
267 const T a = num_abs(T(C(i, i)));
268 if (a > theta) theta = a;
269 }
270 const T theta_plus_mu = theta + mu;
271 if (theta_plus_mu == zero) throw NumericError("map_m1ps_h_recursive: theta + mu is zero");
272 Matrix<T> thetaIplusC = C;
273 for (std::size_t i = 0; i < M; ++i) thetaIplusC(i, i) += theta;
274
275 std::vector<std::vector<std::vector<T>>> h(N + 1,
276 std::vector<std::vector<T>>(K + 1, std::vector<T>()));
277 for (std::size_t n = 0; n <= N; ++n) h[n][0] = ones<T>(M);
278
279 for (std::size_t k = 0; k + 1 <= K; ++k) {
280 for (std::size_t n = 0; n <= N; ++n) {
281 std::vector<T> acc = mulvec(thetaIplusC, h[n][k]);
282 if (n > 0) {
283 const T c = num_traits<T>::from_int(static_cast<long>(n)) * mu /
284 num_traits<T>::from_int(static_cast<long>(n + 1));
285 for (std::size_t i = 0; i < M; ++i) acc[i] += c * h[n - 1][k][i];
286 }
287 if (n < N) {
288 const std::vector<T> t3 = mulvec(D, h[n + 1][k]);
289 for (std::size_t i = 0; i < M; ++i) acc[i] += t3[i];
290 }
291 for (std::size_t i = 0; i < M; ++i) acc[i] /= theta_plus_mu;
292 h[n][k + 1] = acc;
293 }
294 }
295 return h;
296}
297
298/** What the two MAP/M/1-PS sojourn entry points return. */
299template <class T>
301 std::vector<T> w_bar; ///< Pr[W > x] at each requested point
302 /// The reference's second output, sum_i (sum_k)_i / M, per level n and
303 /// per point. NOT a conditional distribution; see the header note.
304 std::vector<std::vector<T>> w_bar_n_unweighted;
305 std::size_t n_levels; ///< levels actually summed
306 std::size_t k_max; ///< largest uniformization index used
307};
308
309namespace m1ps_detail {
310
311/** Poisson pmf a^k e^-a / k!, evaluated stably through logs. */
312template <class T>
313T poisson_pmf(const T& a, std::size_t k) {
314 using std::exp;
315 using std::log;
316 if (a == num_traits<T>::from_int(0)) return k == 0 ? num_traits<T>::from_int(1)
318 T lg = num_traits<T>::from_int(0);
319 for (std::size_t j = 2; j <= k; ++j) lg += T(log(num_traits<T>::from_int(static_cast<long>(j))));
320 const T lp = num_traits<T>::from_int(static_cast<long>(k)) * T(log(a)) - a - lg;
321 return T(exp(lp));
322}
323
324/** The reference's L and R window for the Poisson weights at mean a. */
325template <class T>
326void poisson_window(const T& a, const T& eps_prime, std::size_t& L, std::size_t& K) {
327 if (!(a > num_traits<T>::from_int(0))) {
328 L = 0;
329 K = 0;
330 return;
331 }
332 const double ad = num_traits<T>::to_double(a);
333 const double lo = ad - 10.0 * std::sqrt(ad);
334 L = lo > 0.0 ? static_cast<std::size_t>(std::floor(lo)) : 0;
335 std::size_t hi = static_cast<std::size_t>(std::ceil(ad + 10.0 * std::sqrt(ad)));
336 const T one = num_traits<T>::from_int(1);
337 for (;;) {
338 T s = num_traits<T>::from_int(0);
339 for (std::size_t k = L; k <= hi; ++k) s += poisson_pmf(a, k);
340 if (s >= one - eps_prime || hi >= 10000) break;
341 hi += 10;
342 }
343 K = hi;
344}
345
346/** Stationary vector of the MAP phase process, pi (C + D) = 0. */
347template <class T>
348std::vector<T> m1ps_pi(const Matrix<T>& C, const Matrix<T>& D) {
349 Matrix<T> Q(C.rows(), C.cols());
350 for (std::size_t i = 0; i < C.rows(); ++i)
351 for (std::size_t j = 0; j < C.cols(); ++j) Q(i, j) = C(i, j) + D(i, j);
352 return mc::ctmc_solve(Q);
353}
354
355} // namespace m1ps_detail
356
357/**
358 * Complementary sojourn time distribution of a MAP/M/1-PS queue
359 * (map_m1ps_sojourn.m).
360 *
361 * The queue-length truncation is the reference's: the smallest N with
362 * (1/lambda) sum_{n<=N} pi_0 R^n D e > 1 - epsilon over n = 0..1000, falling
363 * back to N = 100. The uniformization window is recomputed per evaluation
364 * point, as in the reference, so the h recursion is rebuilt for each point.
365 */
366template <class T>
367MapM1psResult<T> map_m1ps_sojourn(const Matrix<T>& C, const Matrix<T>& D, const T& mu,
368 const std::vector<T>& x, const T& epsilon,
369 const T& epsilon_prime) {
371 "map_m1ps_sojourn requires transcendental arithmetic");
372 const std::size_t M = C.rows();
373 if (C.cols() != M || D.rows() != M || D.cols() != M)
374 throw InputError("map_m1ps_sojourn: C and D must be square and of equal order");
375 if (!(mu > num_traits<T>::from_int(0)))
376 throw InputError("map_m1ps_sojourn: the service rate must be positive");
377 const T zero = num_traits<T>::from_int(0);
378 const T one = num_traits<T>::from_int(1);
379
380 const std::vector<T> pi = m1ps_detail::m1ps_pi(C, D);
381 const std::vector<T> e = ones<T>(M);
382 T lambda = zero;
383 {
384 const std::vector<T> t = vecmul(pi, D);
385 for (const T& v : t) lambda += v;
386 }
387 if (!(lambda > zero)) throw NumericError("map_m1ps_sojourn: zero arrival rate");
388 if (!(T(lambda / mu) < one))
389 throw NumericError("map_m1ps_sojourn: the system is unstable, rho >= 1");
390
391 const Matrix<T> R = map_compute_R(C, D, mu);
392 std::vector<T> pi0 = pi;
393 {
394 const std::vector<T> t = vecmul(pi, R);
395 for (std::size_t i = 0; i < M; ++i) pi0[i] = pi[i] - t[i];
396 }
397
398 // N(epsilon) by the reference's cumulative scan.
399 std::size_t N_epsilon = 0;
400 {
401 T cum = zero;
402 std::vector<T> row = pi0;
403 bool found = false;
404 for (std::size_t n = 0; n <= 1000; ++n) {
405 const std::vector<T> t = vecmul(row, D);
406 T add = zero;
407 for (const T& v : t) add += v;
408 cum += add / lambda;
409 if (cum > one - epsilon) {
410 N_epsilon = n;
411 found = true;
412 break;
413 }
414 row = vecmul(row, R);
415 }
416 if (!found || N_epsilon == 0) N_epsilon = 100;
417 }
418
419 T theta = zero;
420 for (std::size_t i = 0; i < M; ++i) {
421 const T a = num_abs(T(C(i, i)));
422 if (a > theta) theta = a;
423 }
424 const T theta_plus_mu = theta + mu;
425
426 // Weights pi_0 R^n D, one row per level.
427 std::vector<std::vector<T>> weights(N_epsilon + 1);
428 {
429 std::vector<T> row = pi0;
430 for (std::size_t n = 0; n <= N_epsilon; ++n) {
431 weights[n] = vecmul(row, D);
432 row = vecmul(row, R);
433 }
434 }
435
437 out.n_levels = N_epsilon + 1;
438 out.k_max = 0;
439 out.w_bar.assign(x.size(), zero);
440 out.w_bar_n_unweighted.assign(N_epsilon + 1, std::vector<T>(x.size(), zero));
441
442 for (std::size_t idx = 0; idx < x.size(); ++idx) {
443 const T xv = x[idx];
444 if (xv < zero) throw InputError("map_m1ps_sojourn: the evaluation points must be >= 0");
445 std::size_t L = 0, K = 0;
446 m1ps_detail::poisson_window(T(theta_plus_mu * xv), epsilon_prime, L, K);
447 if (K > out.k_max) out.k_max = K;
448 const std::vector<std::vector<std::vector<T>>> h =
449 map_m1ps_h_recursive(C, D, mu, N_epsilon, K);
450
451 std::vector<T> pois(K + 1 - L);
452 for (std::size_t k = L; k <= K; ++k)
453 pois[k - L] = m1ps_detail::poisson_pmf(T(theta_plus_mu * xv), k);
454
455 for (std::size_t n = 0; n <= N_epsilon; ++n) {
456 std::vector<T> sum_k(M, zero);
457 for (std::size_t k = L; k <= K; ++k)
458 for (std::size_t i = 0; i < M; ++i) sum_k[i] += pois[k - L] * h[n][k][i];
459 T term = zero;
460 for (std::size_t i = 0; i < M; ++i) term += weights[n][i] * sum_k[i];
461 out.w_bar[idx] += term / lambda;
462 T s = zero;
463 for (const T& v : sum_k) s += v;
464 out.w_bar_n_unweighted[n][idx] = s / num_traits<T>::from_int(static_cast<long>(M));
465 }
466 }
467 (void)e;
468 return out;
469}
470
471/** map_m1ps_sojourn with the reference defaults, epsilon 1e-11 and 1e-10. */
472template <class T>
473MapM1psResult<T> map_m1ps_sojourn(const Matrix<T>& C, const Matrix<T>& D, const T& mu,
474 const std::vector<T>& x) {
475 return map_m1ps_sojourn(C, D, mu, x, T(num_traits<T>::from_double(1e-11)),
477}
478
479/**
480 * Complementary sojourn time distribution of a MAP/M/1-PS queue by the
481 * spectral-radius truncation (map_m1ps_cdfrespt.m).
482 *
483 * Differences from map_m1ps_sojourn are listed in the header note: R comes
484 * from the other splitting, the level truncation is estimated from Sp(R) and
485 * then cut again at ||pi_0 R^n D||_inf < epsilon/100, and the h recursion is
486 * built ONCE at the largest uniformization index over all evaluation points
487 * instead of once per point.
488 *
489 * Sp(R) is obtained from the caudal-characteristic bracket on the nonnegative
490 * R (qbd_caudal) rather than from a double-precision eigensolve, so the
491 * truncation estimate is computed at the working precision.
492 */
493template <class T>
495 const std::vector<T>& x, const T& epsilon,
496 const T& epsilon_prime) {
498 "map_m1ps_cdfrespt requires transcendental arithmetic");
499 const std::size_t M = C.rows();
500 if (C.cols() != M || D.rows() != M || D.cols() != M)
501 throw InputError("map_m1ps_cdfrespt: C and D must be square and of equal order");
502 if (!(mu > num_traits<T>::from_int(0)))
503 throw InputError("map_m1ps_cdfrespt: the service rate must be positive");
504 const T zero = num_traits<T>::from_int(0);
505 const T one = num_traits<T>::from_int(1);
506
507 const std::vector<T> pi = m1ps_detail::m1ps_pi(C, D);
508 T lambda = zero;
509 {
510 const std::vector<T> t = vecmul(pi, D);
511 for (const T& v : t) lambda += v;
512 }
513 if (!(lambda > zero)) throw NumericError("map_m1ps_cdfrespt: zero arrival rate");
514 if (!(T(lambda / mu) < one))
515 throw NumericError("map_m1ps_cdfrespt: the system is unstable, rho >= 1");
516
517 const Matrix<T> R = map_compute_R_quadratic(C, D, mu);
518 std::vector<T> pi0(M);
519 {
520 const std::vector<T> t = vecmul(pi, R);
521 for (std::size_t i = 0; i < M; ++i) pi0[i] = pi[i] - t[i];
522 }
523
524 const T rho_R = qbd_caudal(R);
525 if (!(rho_R < one))
526 throw NumericError("map_m1ps_cdfrespt: R has spectral radius >= 1");
527
528 std::size_t N_epsilon;
529 if (rho_R > zero) {
530 const double e_ = num_traits<T>::to_double(epsilon);
531 const double s_ = num_traits<T>::to_double(rho_R);
532 const double est = std::ceil(std::log(e_ * (1.0 - s_)) / std::log(s_));
533 const long n_est = static_cast<long>(est);
534 N_epsilon = static_cast<std::size_t>(n_est < 10 ? 10 : (n_est > 10000 ? 10000 : n_est));
535 } else {
536 N_epsilon = 10;
537 }
538
539 T theta = zero;
540 for (std::size_t i = 0; i < M; ++i) {
541 const T a = num_abs(T(C(i, i)));
542 if (a > theta) theta = a;
543 }
544 const T theta_plus_mu = theta + mu;
545
546 // One uniformization window per point, and the global maximum over them.
547 std::vector<std::size_t> Lp(x.size(), 0), Kp(x.size(), 0);
548 std::size_t K_global = 0;
549 for (std::size_t idx = 0; idx < x.size(); ++idx) {
550 if (x[idx] < zero) throw InputError("map_m1ps_cdfrespt: the points must be >= 0");
551 m1ps_detail::poisson_window(T(theta_plus_mu * x[idx]), epsilon_prime, Lp[idx], Kp[idx]);
552 if (Kp[idx] > K_global) K_global = Kp[idx];
553 }
554
555 const std::vector<std::vector<std::vector<T>>> h =
556 map_m1ps_h_recursive(C, D, mu, N_epsilon, K_global);
557
558 // Weights with the reference's early truncation.
559 const T weight_tol = epsilon * num_traits<T>::from_double(1e-2);
560 std::vector<std::vector<T>> weights(N_epsilon + 1);
561 std::size_t N_actual = N_epsilon;
562 {
563 std::vector<T> row = pi0;
564 for (std::size_t n = 0; n <= N_epsilon; ++n) {
565 weights[n] = vecmul(row, D);
566 T wn = zero;
567 for (const T& v : weights[n]) {
568 const T a = num_abs(T(v));
569 if (a > wn) wn = a;
570 }
571 if (n > 0 && wn < weight_tol) {
572 N_actual = n;
573 break;
574 }
575 row = vecmul(row, R);
576 }
577 }
578
580 out.n_levels = N_actual + 1;
581 out.k_max = K_global;
582 out.w_bar.assign(x.size(), zero);
583 out.w_bar_n_unweighted.assign(N_actual + 1, std::vector<T>(x.size(), zero));
584
585 for (std::size_t idx = 0; idx < x.size(); ++idx) {
586 const std::size_t L = Lp[idx], K = Kp[idx];
587 std::vector<T> pois(K + 1 - L);
588 for (std::size_t k = L; k <= K; ++k)
589 pois[k - L] = m1ps_detail::poisson_pmf(T(theta_plus_mu * x[idx]), k);
590 for (std::size_t n = 0; n <= N_actual; ++n) {
591 std::vector<T> sum_k(M, zero);
592 for (std::size_t k = L; k <= K; ++k)
593 for (std::size_t i = 0; i < M; ++i) sum_k[i] += pois[k - L] * h[n][k][i];
594 T term = zero;
595 for (std::size_t i = 0; i < M; ++i) term += weights[n][i] * sum_k[i];
596 out.w_bar[idx] += term / lambda;
597 T s = zero;
598 for (const T& v : sum_k) s += v;
599 out.w_bar_n_unweighted[n][idx] = s / num_traits<T>::from_int(static_cast<long>(M));
600 }
601 }
602 return out;
603}
604
605/** map_m1ps_cdfrespt with the reference defaults, epsilon 1e-11 and 1e-10. */
606template <class T>
608 const std::vector<T>& x) {
609 return map_m1ps_cdfrespt(C, D, mu, x, T(num_traits<T>::from_double(1e-11)),
611}
612
613} // namespace mam
614} // namespace line
615
616#endif // LINE_API_MAM_MAP_M1PS_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
NumericError(const std::string &what)
Definition error.h:45
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
MapM1psResult< T > map_m1ps_sojourn(const Matrix< T > &C, const Matrix< T > &D, const T &mu, const std::vector< T > &x, const T &epsilon, const T &epsilon_prime)
Complementary sojourn time distribution of a MAP/M/1-PS queue (map_m1ps_sojourn.m).
Definition map_m1ps.h:367
std::vector< std::vector< std::vector< T > > > map_m1ps_h_recursive(const Matrix< T > &C, const Matrix< T > &D, const T &mu, std::size_t N, std::size_t K)
The vectors h_{n,k} of Theorem 1 (map_m1ps_h_recursive.m).
Definition map_m1ps.h:257
Matrix< T > map_compute_R(const Matrix< T > &C, const Matrix< T > &D, const T &mu, unsigned max_iter, const T &tol)
Rate matrix R of a MAP/M/1 queue, the minimal nonnegative solution of D + R (C - mu I) + mu R^2 = 0,...
Definition map_m1ps.h:135
T qbd_caudal(const Matrix< T > &R, unsigned iter_max, const T &tol)
Caudal characteristic eta = sp(R), the decay rate of the queue-length tail.
Definition qbd_r.h:355
T map_compute_R_residual(const Matrix< T > &C, const Matrix< T > &D, const T &mu, const Matrix< T > &R)
Residual ||D + R (C - mu I) + mu R^2||_inf of the MAP/M/1 rate equation.
Definition map_m1ps.h:105
Matrix< T > map_compute_R_quadratic(const Matrix< T > &C, const Matrix< T > &D, const T &mu, unsigned max_iter, const T &tol)
The same R by the other splitting, R <- (D + mu R^2) (mu I - C)^-1, warm started at -D (C - mu I)^-1 ...
Definition map_m1ps.h:184
MapM1psResult< T > map_m1ps_cdfrespt(const Matrix< T > &C, const Matrix< T > &D, const T &mu, const std::vector< T > &x, const T &epsilon, const T &epsilon_prime)
Complementary sojourn time distribution of a MAP/M/1-PS queue by the spectral-radius truncation (map_...
Definition map_m1ps.h:494
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
T num_abs(const T &v)
Definition number.h:172
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 > 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,...
What the two MAP/M/1-PS sojourn entry points return.
Definition map_m1ps.h:300
std::vector< T > w_bar
Pr[W > x] at each requested point.
Definition map_m1ps.h:301
std::vector< std::vector< T > > w_bar_n_unweighted
The reference's second output, sum_i (sum_k)_i / M, per level n and per point.
Definition map_m1ps.h:304
std::size_t k_max
largest uniformization index used
Definition map_m1ps.h:306
std::size_t n_levels
levels actually summed
Definition map_m1ps.h:305