LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_mam_bmap.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_SOLVERS_MAM_SOLVER_MAM_BMAP_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_BMAP_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The batch-arrival and batch-service queues of the MAM solver, and the two
12 * finite-capacity helpers `solver_mam_basic.m` shares with them:
13 * `solver_mam_bmap_map_1.m`, `solver_mam_map_bmap_1.m`, `mam_detect_mmck.m`
14 * and `mam_truncate_renorm.m`.
15 *
16 * NEITHER SOLVER IS A CONVERSION. Both take the batch process AS a batch
17 * process and build the exact level-transition blocks of the resulting chain;
18 * neither one replaces a BMAP by a MAP of the same rate, and neither is
19 * expressible as the other. What differs between them is the SHAPE of the
20 * chain, and that shape is forced by which side carries the batches:
21 *
22 * BMAP/MAP/1 the level rises by the batch size and falls by exactly one,
23 * so the chain is M/G/1-TYPE (skip-free to the left)
24 * MAP/BMAP/1 the level rises by exactly one and falls by the batch size,
25 * so the chain is GI/M/1-TYPE (skip-free to the right)
26 *
27 * Both block assemblies are EXACT: no batch is ever resolved into independent
28 * single arrivals, which is the approximation that would destroy the whole
29 * point. A batch of k is one epoch at which k jobs appear together, and the
30 * queue-length law it induces is not the law induced by k Poisson epochs of
31 * the same total rate -- the batch correlates the arrivals perfectly, so the
32 * second moment of the queue length is strictly larger. That is why `A_{k+1}`
33 * moves the level by k in one step rather than being folded into `A_2`.
34 *
35 * WHERE AN APPROXIMATION DOES ENTER, it is at the BOUNDARY of the GI/M/1-type
36 * chain, and it is a clipping rather than a loss. A batch service of size k at
37 * a level j < k cannot take the level to j - k, so `solver_mam_map_bmap_1.m`
38 * routes every batch of size k >= j from level j to level ZERO:
39 *
40 * B_{j+1} = sum_{k >= j} I (x) D_k
41 *
42 * and at level 0 it folds the whole service mass back onto the local block.
43 * THE TAIL IS LUMPED, NOT DISCARDED, which is the same convention
44 * `State.signalBatchPMF` uses for a negative signal (see `signal_batch_pmf` in
45 * `lang/qn/state_events.h`: the whole tail P(B >= n) is assigned to "remove
46 * all n"). The invariant it buys is that the boundary rows still sum to zero,
47 * so no probability is created or destroyed by the clipping; the price is that
48 * the boundary over-reports emptying events relative to a chain that could
49 * represent the missing customers.
50 *
51 * `mam_truncate_renorm` uses the OPPOSITE convention on purpose, and the
52 * contrast is the reason both are in this header. It truncates the marginal
53 * queue length at the buffer capacity and RENORMALIZES, so the tail above capK
54 * is deleted and its mass is redistributed over levels 0..capK in proportion,
55 * rather than piled onto level capK. For an M/M/1 input that is not an
56 * approximation at all -- the M/M/1/K law IS the truncated renormalized
57 * geometric -- which is what makes it the right convention for a buffer, where
58 * a blocked arrival leaves the system rather than joining at the top. Lumping
59 * would instead report a boundary mass that no finite-buffer queue has.
60 *
61 * THE MEAN MEASURES ARE THE ETAQA SOLVE, and both reference files hand it to
62 * third-party MAMSolver: `MG1_G_ETAQA`, `MG1_pi_ETAQA` and `MG1_qlen_ETAQA` on
63 * the M/G/1-type side, `GIM1_R_ETAQA`, `GIM1_pi_ETAQA` and `GIM1_qlen_ETAQA` on
64 * the GI/M/1-type one, routing in turn into `MG1_CR` (Bini-Meini cyclic
65 * reduction with the shift technique and FFT polynomial products) and `GIM1_R`
66 * (the Bright/Ramaswami dual plus functional iterations). All of it is now
67 * ported, under `lib/smc/mg1.h` and `lib/smc/etaqa.h`, so these two entry
68 * points answer instead of refusing. Read those headers before touching the
69 * numbers: the ported code reproduces several reference defects verbatim and
70 * says which.
71 *
72 * ARITHMETIC. The block assemblies are Kronecker products and one stationary
73 * solve, so they are exact at Rational and are NOT gated. The ETAQA solve on
74 * top of them is DOUBLE ONLY -- LAPACK eigenvalues in the caudal and decay
75 * bisections, a complex FFT in cyclic reduction, an SVD in the rank test that
76 * picks the redundant column -- so `solver_mam_bmap_map_1` and
77 * `solver_mam_map_bmap_1` refuse at any other arithmetic rather than
78 * down-convert behind the caller's back. `mam_truncate_renorm` is gated too,
79 * because MMAP[K]/PH[K]/1 runs the ADDA doubling iteration.
80 */
81
82#include <algorithm>
83#include <cmath>
84#include <cstddef>
85#include <string>
86#include <type_traits>
87#include <vector>
88
92#include "line/api/mam/qbd_r.h"
94#include "line/lib/smc/etaqa.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
103namespace bmap_detail {
104
105using qbd_detail::madd;
106
107/** D1 + ... + DK, the epoch-marginal counterpart of a MAP's D1. */
108template <class T>
109Matrix<T> batch_d1(const std::vector<Matrix<T>>& D) {
110 Matrix<T> tot(D[0].rows(), D[0].cols(), num_traits<T>::from_int(0));
111 for (std::size_t k = 1; k < D.size(); ++k) tot = madd(tot, D[k]);
112 return tot;
113}
114
115/** D0 + D1 + ... + DK, the generator of the batch process's phase chain. */
116template <class T>
117Matrix<T> batch_infgen(const std::vector<Matrix<T>>& D) {
118 return madd(D[0], batch_d1(D));
119}
120
121/**
122 * Customers per unit time, sum_k k theta D_k e.
123 *
124 * This is NOT the epoch rate theta (sum_k D_k) e: a batch of k counts k times.
125 * Confusing the two is the error that makes a BMAP look like a MAP of the same
126 * epoch rate, and it is the reason the two rates are computed by different
127 * functions here rather than by one with a flag.
128 */
129template <class T>
130T batch_customer_rate(const std::vector<Matrix<T>>& D) {
131 const T zero = num_traits<T>::from_int(0);
132 Map<T> phase;
133 phase.D0 = D[0];
134 phase.D1 = batch_d1(D);
135 const std::vector<T> theta = map_prob(phase);
136 T rate = zero;
137 for (std::size_t k = 1; k < D.size(); ++k) {
138 const std::vector<T> tD = vecmul(theta, D[k]);
139 T s = zero;
140 for (const T& v : tD) s += v;
141 rate += T(num_traits<T>::from_int(static_cast<long>(k)) * s);
142 }
143 return rate;
144}
145
146/** Every matrix square, of one common order, and at least {D0, D1}. */
147template <class T>
148std::size_t check_batch_shape(const std::vector<Matrix<T>>& D, const std::string& who,
149 const std::string& what) {
150 if (D.size() < 2)
151 throw InputError(who + ": the " + what +
152 " must be given as {D0, D1, ..., DK} with at least D0 and D1");
153 const std::size_t n = D[0].rows();
154 for (std::size_t k = 0; k < D.size(); ++k)
155 if (D[k].rows() != n || D[k].cols() != n)
156 throw InputError(who + ": all " + what + " matrices must be " + std::to_string(n) +
157 "x" + std::to_string(n) + ", but D{" + std::to_string(k) + "} is " +
158 std::to_string(D[k].rows()) + "x" + std::to_string(D[k].cols()));
159 return n;
160}
161
162/** The reference's `max(abs(sum(Q,2))) > 1e-10` generator test. */
163template <class T>
164void check_zero_rowsums(const Matrix<T>& Q, const std::string& msg) {
165 for (std::size_t i = 0; i < Q.rows(); ++i) {
166 T s = num_traits<T>::from_int(0);
167 for (std::size_t j = 0; j < Q.cols(); ++j) s += Q(i, j);
168 if (std::fabs(num_traits<T>::to_double(s)) > 1e-10) throw InputError(msg);
169 }
170}
171
172} // namespace bmap_detail
173
174// ---------------------------------------------------------------------------
175// mam_detect_mmck
176// ---------------------------------------------------------------------------
177
178/** What `mam_detect_mmck` returns; muRate is meaningful only when isMmck. */
179template <class T>
181 bool isMmck = false;
183};
184
185/**
186 * Port of `mam_detect_mmck.m`: is the exact M/M/c/K closed form legitimate at
187 * this station?
188 *
189 * All three conditions are about losing nothing, not about convenience. A
190 * multi-phase arrival MMAP is not Poisson, so the M/M/c/K birth-death chain
191 * would answer a different arrival process; a non-exponential service breaks
192 * the same chain's death rates; and per-class rates that differ leave the
193 * aggregate service non-exponential even when each class is. A class the
194 * station never serves is skipped rather than failing the test, because the
195 * reference reads its NaN rate as "no inflow" -- the C++ `disabled` flag is
196 * that same sentinel.
197 *
198 * @param ist 1-based station index
199 * @param arv the assembled arrival stream at that station
200 * @param L the refreshed struct whose station ist is being tested
201 */
202template <class T>
204 const Mmap<T>& arv) {
206 if (ist == 0 || ist > L.nstations)
207 throw InputError("mam_detect_mmck: station index " + std::to_string(ist) +
208 " is out of range");
209 const std::size_t i0 = ist - 1;
210 if (arv.order() != 1) return out;
211
212 bool any = false;
213 double lo = 0.0, hi = 0.0;
214 for (std::size_t r = 0; r < L.nclasses; ++r) {
215 if (L.disabled[i0][r]) continue;
216 if (L.service[i0][r].type != lang::ProcessType::EXP) return out;
217 const double v = num_traits<T>::to_double(L.rates(i0, r));
218 if (!(v > 0.0)) continue;
219 if (!any) {
220 lo = hi = v;
221 out.muRate = L.rates(i0, r);
222 any = true;
223 } else {
224 lo = std::min(lo, v);
225 hi = std::max(hi, v);
226 }
227 }
228 if (!any) return out;
229 if (hi - lo > 1e-9 * std::max(1.0, hi)) return out;
230 out.isMmck = true;
231 return out;
232}
233
234// ---------------------------------------------------------------------------
235// mam_truncate_renorm
236// ---------------------------------------------------------------------------
237
238/**
239 * Port of `mam_truncate_renorm.m`: the finite-buffer marginal of an
240 * MMAP[K]/PH[K]/1 FCFS queue, by truncation and renormalization.
241 *
242 * The body is `solver_mam_basic.h`'s `basic_detail::truncate_renorm`, which is
243 * the same function under the name the analyzer that first needed it gave it.
244 * It is re-exposed here under the REFERENCE's name so that a caller reading
245 * `mam_truncate_renorm.m` finds it, and so that the tail convention documented
246 * at the top of this file has one place to be documented.
247 *
248 * Multi-class input is aggregated first: `ncDistr` returns the per-class
249 * marginal P(N_k = n) and the truncation needs the joint P(N_total = n).
250 */
251template <class T>
252basic_detail::TruncRenorm<T> mam_truncate_renorm(const Mmap<T>& arv,
253 const std::vector<PhService<T>>& svc,
254 std::size_t capK) {
255 if constexpr (!num_traits<T>::has_transcendental) {
256 (void)arv;
257 (void)svc;
258 (void)capK;
259 throw UnsupportedError(
260 "mam_truncate_renorm: the infinite-buffer marginal comes from MMAP[K]/PH[K]/1 FCFS, "
261 "whose ADDA doubling iteration terminates on a tolerance; rerun this model with "
262 "--arith double or --arith real");
263 } else {
264 if (capK < 1) throw InputError("mam_truncate_renorm: the buffer capacity must be positive");
265 return basic_detail::truncate_renorm(arv, svc, capK);
266 }
267}
268
269// ---------------------------------------------------------------------------
270// BMAP/MAP/1, an M/G/1-type chain
271// ---------------------------------------------------------------------------
272
273/**
274 * The level blocks of `solver_mam_bmap_map_1.m`.
275 *
276 * The phase is the pair (arrival phase, service phase) with the ARRIVAL phase
277 * major, so every block is a Kronecker product in that order.
278 */
279template <class T>
281 std::size_t ma = 0; ///< BMAP phases
282 std::size_t ms = 0; ///< service MAP phases
283 std::size_t m = 0; ///< ma * ms
284 std::size_t K = 0; ///< largest batch size
285
286 Matrix<T> A0; ///< level -1: a service completion
287 Matrix<T> A1; ///< level 0: phase changes only
288 std::vector<Matrix<T>> Aup; ///< Aup[k-1]: level +k, a batch of k
289
290 Matrix<T> B0; ///< level 0 local block, at the empty queue
291 std::vector<Matrix<T>> Bup; ///< Bup[k-1]: level 0 -> +k
292
293 T lambda = num_traits<T>::from_int(0); ///< customers per unit time
294 T mu = num_traits<T>::from_int(0); ///< service completions per unit time
296 /** The reference WARNS rather than errors when rho >= 1; recorded, not thrown. */
297 bool stable = true;
298};
299
300/**
301 * Port of the block assembly and the stability test of
302 * `solver_mam_bmap_map_1.m`.
303 *
304 * B0 IS NOT `qbd_mapmap1_blocks`'s Lbar. The reference adds the service
305 * completion back onto the level-zero local block, `kron(I, S0 + S1)`, so the
306 * service phase process keeps running while the queue is empty and the
307 * completion it would have made is absorbed as a self-loop. That is a
308 * modelling choice about an idle server, not an oversight, and it makes
309 * B0 = A1 + A0 exactly. `qbd_mapmap1.h` instead stops the service process at
310 * level zero with `kron(D0, I)`. The two chains differ, and both are here
311 * under their own names.
312 */
313template <class T>
315 const Map<T>& service) {
316 using namespace bmap_detail;
318 b.ma = check_batch_shape(D, "solver_mam_bmap_map_1", "BMAP");
319 b.K = D.size() - 1;
320 b.ms = service.D0.rows();
321 if (service.D0.cols() != b.ms || service.D1.rows() != b.ms || service.D1.cols() != b.ms)
322 throw InputError("solver_mam_bmap_map_1: the service MAP matrices must be " +
323 std::to_string(b.ms) + "x" + std::to_string(b.ms));
324 b.m = b.ma * b.ms;
325
326 const Matrix<T> Ia = eye<T>(b.ma), Is = eye<T>(b.ms);
327 b.A0 = kron(Ia, service.D1);
328 b.A1 = madd(kron(D[0], Is), kron(Ia, service.D0));
329 for (std::size_t k = 1; k <= b.K; ++k) b.Aup.push_back(kron(D[k], Is));
330
331 b.B0 = madd(kron(D[0], Is), kron(Ia, madd(service.D0, service.D1)));
332 b.Bup = b.Aup;
333
334 b.lambda = batch_customer_rate(D);
335 b.mu = map_lambda(service);
336 const T zero = num_traits<T>::from_int(0);
337 b.rho = (b.mu > zero) ? T(b.lambda / b.mu) : zero;
339 return b;
340}
341
342// ---------------------------------------------------------------------------
343// MAP/BMAP/1, a GI/M/1-type chain
344// ---------------------------------------------------------------------------
345
346/** The level blocks of `solver_mam_map_bmap_1.m`. */
347template <class T>
349 std::size_t ma = 0; ///< arrival MAP phases
350 std::size_t ms = 0; ///< service BMAP phases
351 std::size_t m = 0;
352 std::size_t K = 0; ///< largest service batch
353
354 Matrix<T> A0; ///< level +1: an arrival
355 Matrix<T> A1; ///< level 0: phase changes only
356 std::vector<Matrix<T>> Adown; ///< Adown[k-1]: level -k, a batch service of k
357
358 Matrix<T> B1; ///< level 0 local block, service folded back
359 std::vector<Matrix<T>> Bto0; ///< Bto0[j-1]: level j -> level 0, j = 1..K
360
362 T mu = num_traits<T>::from_int(0); ///< customers served per unit time
364 bool stable = true;
365};
366
367/**
368 * Port of the block assembly, the generator validation and the stability test
369 * of `solver_mam_map_bmap_1.m`.
370 *
371 * THE BOUNDARY IS WHERE THE CLIPPING LIVES. `Bto0[j-1]` collects every batch
372 * of size k >= j, so an oversized batch empties the queue instead of driving
373 * the level negative, and `B1` collects the whole service mass at level zero
374 * as a self-loop. Nothing is dropped: for every boundary level j the total
375 * outflow A0 + A1 + sum_{k<j} Adown[k-1] + Bto0[j-1] is again
376 * kron(C0+C1, I) + kron(I, sum_k D_k), whose rows sum to zero. That identity
377 * is the whole justification for the convention and is asserted in the tests.
378 *
379 * ONE REFERENCE BRANCH IS UNREACHABLE HERE and is therefore not transcribed:
380 * `K = bmapSvc.getNumberOfPhases() - 1` reads the PHASE count where the batch
381 * count is meant, so the BMAP-object path mis-sizes D for any process whose
382 * order differs from its largest batch. This port takes the matrices directly,
383 * which is the reference's own cell-array path and the correct one.
384 */
385template <class T>
387 const std::vector<Matrix<T>>& D) {
388 using namespace bmap_detail;
390 b.ma = arrival.D0.rows();
391 if (arrival.D0.cols() != b.ma || arrival.D1.rows() != b.ma || arrival.D1.cols() != b.ma)
392 throw InputError("solver_mam_map_bmap_1: the arrival MAP matrices must be " +
393 std::to_string(b.ma) + "x" + std::to_string(b.ma));
394 b.ms = check_batch_shape(D, "solver_mam_map_bmap_1", "service BMAP");
395 b.K = D.size() - 1;
396 b.m = b.ma * b.ms;
397
398 check_zero_rowsums(madd(arrival.D0, arrival.D1),
399 "solver_mam_map_bmap_1: MAP matrices C0 + C1 must have zero row sums");
400 const Matrix<T> Dtot = batch_infgen(D);
401 check_zero_rowsums(Dtot,
402 "solver_mam_map_bmap_1: BMAP matrices D0 + D1 + ... + DK must have zero "
403 "row sums");
404
405 const Matrix<T> Ia = eye<T>(b.ma), Is = eye<T>(b.ms);
406 b.A0 = kron(arrival.D1, Is);
407 b.A1 = madd(kron(arrival.D0, Is), kron(Ia, D[0]));
408 for (std::size_t k = 1; k <= b.K; ++k) b.Adown.push_back(kron(Ia, D[k]));
409
410 b.B1 = b.A1;
411 for (std::size_t k = 1; k <= b.K; ++k) b.B1 = madd(b.B1, kron(Ia, D[k]));
412 for (std::size_t j = 1; j <= b.K; ++j) {
414 for (std::size_t k = j; k <= b.K; ++k) Bj = madd(Bj, kron(Ia, D[k]));
415 b.Bto0.push_back(Bj);
416 }
417
418 b.lambda = map_lambda(arrival);
419 b.mu = batch_customer_rate(D);
420 const T zero = num_traits<T>::from_int(0);
421 b.rho = (b.mu > zero) ? T(b.lambda / b.mu) : zero;
423 return b;
424}
425
426// ---------------------------------------------------------------------------
427// The mean measures, which are the third-party solve
428// ---------------------------------------------------------------------------
429
430/** What both reference files return once the ETAQA solve has run. */
431template <class T>
435 Matrix<T> piAgg; ///< the ETAQA-aggregated stationary vector
436 /** G for the M/G/1-type solve, R for the GI/M/1-type one. */
438 /** Moments 1..nMoments of the queue length; `QN` is the first of them. */
439 std::vector<T> qlenMoments;
440};
441
442namespace bmap_detail {
443
444/** The ETAQA solve is double only; say so instead of down-converting. */
445template <class T>
446void require_double_arith(const std::string& who) {
447 if (!std::is_same<T, double>::value)
448 throw UnsupportedError(
449 who +
450 ": the ETAQA mean measures run in double precision only. The solve bisects on a "
451 "Perron-Frobenius eigenvalue (LAPACK), evaluates the cyclic reduction at complex "
452 "roots of unity (FFT) and picks the redundant balance equation by a numerical rank "
453 "test (SVD), none of which this tree provides at exact or multiprecision arithmetic. "
454 "The level blocks, the rates and the stability test ARE available at every "
455 "arithmetic from " +
456 who + "_blocks; rerun the measures with --arith double");
457}
458
459/** Copy of a templated matrix as doubles, for the third-party solve. */
460template <class T>
461Matrix<double> as_double(const Matrix<T>& A) {
462 Matrix<double> out(A.rows(), A.cols(), 0.0);
463 for (std::size_t i = 0; i < A.rows(); ++i)
464 for (std::size_t j = 0; j < A.cols(); ++j) out(i, j) = num_traits<T>::to_double(A(i, j));
465 return out;
466}
467
468/** `[M0 M1 ... Mk]`, the wide block sequence MAMSolver's M/G/1 side takes. */
469inline Matrix<double> hstack(const std::vector<Matrix<double>>& blk) {
470 return smc::hcat(blk);
471}
472
473/** `[M0; M1; ...; Mk]`, the stacked sequence its GI/M/1 side takes. */
474inline Matrix<double> vstack(const std::vector<Matrix<double>>& blk) {
475 return smc::vcat(blk);
476}
477
478} // namespace bmap_detail
479
480/**
481 * Port of `solver_mam_bmap_map_1.m`, mean measures included.
482 *
483 * The blocks and the stability test are assembled first, so a malformed input
484 * is reported as such before any numerics run. The chain is then handed to
485 * ETAQA exactly as the reference hands it: `A = [A0 A1 A2 ... A_{K+1}]` for the
486 * repetitive levels and `B = [B0 B1 ... BK]` for the boundary, G from
487 * `MG1_G_ETAQA`, the three aggregates from `MG1_pi_ETAQA`, and the moments from
488 * `MG1_qlen_ETAQA`. `nMoments` matches the reference's default of 3; the mean
489 * queue length is the first of them and the response time follows by Little.
490 */
491template <class T>
492BmapQueueResult<T> solver_mam_bmap_map_1(const std::vector<Matrix<T>>& D, const Map<T>& service,
493 std::size_t nMoments = 3) {
495 bmap_detail::require_double_arith<T>("solver_mam_bmap_map_1");
496 if (nMoments < 1) throw InputError("solver_mam_bmap_map_1: nMoments must be positive");
497
498 std::vector<Matrix<double>> Ablk, Bblk;
499 Ablk.push_back(bmap_detail::as_double(b.A0));
500 Ablk.push_back(bmap_detail::as_double(b.A1));
501 for (std::size_t k = 0; k < b.Aup.size(); ++k) Ablk.push_back(bmap_detail::as_double(b.Aup[k]));
502 Bblk.push_back(bmap_detail::as_double(b.B0));
503 for (std::size_t k = 0; k < b.Bup.size(); ++k) Bblk.push_back(bmap_detail::as_double(b.Bup[k]));
504
505 const Matrix<double> A = bmap_detail::hstack(Ablk);
506 const Matrix<double> B = bmap_detail::hstack(Bblk);
507
508 const Matrix<double> G = smc::mg1_g_etaqa(A);
509 const std::vector<double> pi = smc::mg1_pi_etaqa(B, A, G);
510
512 out.qlenMoments.reserve(nMoments);
513 for (std::size_t n = 1; n <= nMoments; ++n)
514 out.qlenMoments.push_back(
516
517 out.QN = out.qlenMoments[0];
518 out.UN = b.rho;
519 out.TN = b.lambda;
520 out.RN = T(out.QN / out.TN);
521 out.piAgg = Matrix<T>(1, pi.size(), num_traits<T>::from_int(0));
522 for (std::size_t j = 0; j < pi.size(); ++j) out.piAgg(0, j) = num_traits<T>::from_double(pi[j]);
523 out.fund = Matrix<T>(G.rows(), G.cols(), num_traits<T>::from_int(0));
524 for (std::size_t i = 0; i < G.rows(); ++i)
525 for (std::size_t j = 0; j < G.cols(); ++j)
526 out.fund(i, j) = num_traits<T>::from_double(G(i, j));
527 return out;
528}
529
530/**
531 * Port of `solver_mam_map_bmap_1.m`, mean measures included.
532 *
533 * The GI/M/1-type chain is stacked as `A = [A0; A1; ...; A_{K+1}]` and
534 * `B = [B1; B2; ...; B_{K+1}; 0]`, which is the reference's own layout down to
535 * the trailing zero block its `zeros(m*(K+1)+m, m)` allocation leaves unfilled.
536 * R comes from `GIM1_R_ETAQA`, the aggregates and the mean queue length from
537 * `GIM1_pi_ETAQA` and `GIM1_qlen_ETAQA`, both with A0 as the Boundary block.
538 * The reference asks for the FIRST moment only on this side, and so does this.
539 */
540template <class T>
541BmapQueueResult<T> solver_mam_map_bmap_1(const Map<T>& arrival, const std::vector<Matrix<T>>& D) {
543 bmap_detail::require_double_arith<T>("solver_mam_map_bmap_1");
544
545 std::vector<Matrix<double>> Ablk, Bblk;
546 Ablk.push_back(bmap_detail::as_double(b.A0));
547 Ablk.push_back(bmap_detail::as_double(b.A1));
548 for (std::size_t k = 0; k < b.Adown.size(); ++k)
549 Ablk.push_back(bmap_detail::as_double(b.Adown[k]));
550 Bblk.push_back(bmap_detail::as_double(b.B1));
551 for (std::size_t k = 0; k < b.Bto0.size(); ++k)
552 Bblk.push_back(bmap_detail::as_double(b.Bto0[k]));
553 Bblk.push_back(Matrix<double>(b.m, b.m, 0.0)); // the reference's unfilled tail block
554
555 const Matrix<double> A = bmap_detail::vstack(Ablk);
556 const Matrix<double> B = bmap_detail::vstack(Bblk);
557 const Matrix<double> A0 = bmap_detail::as_double(b.A0);
558
560 const std::vector<double> pi = smc::gim1_pi_etaqa(B, A, R, A0);
561 const double QN = smc::gim1_qlen_etaqa(B, A, R, pi, 1, A0);
562
564 out.qlenMoments.push_back(num_traits<T>::from_double(QN));
565 out.QN = out.qlenMoments[0];
566 out.UN = b.rho;
567 out.TN = b.lambda;
568 out.RN = T(out.QN / out.TN);
569 out.piAgg = Matrix<T>(1, pi.size(), num_traits<T>::from_int(0));
570 for (std::size_t j = 0; j < pi.size(); ++j) out.piAgg(0, j) = num_traits<T>::from_double(pi[j]);
571 out.fund = Matrix<T>(R.rows(), R.cols(), num_traits<T>::from_int(0));
572 for (std::size_t i = 0; i < R.rows(); ++i)
573 for (std::size_t j = 0; j < R.cols(); ++j)
574 out.fund(i, j) = num_traits<T>::from_double(R(i, j));
575 return out;
576}
577
578} // namespace mam
579} // namespace line
580
581#endif // LINE_SOLVERS_MAM_SOLVER_MAM_BMAP_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
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< std::vector< bool > > disabled
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
The exception types the port throws.
ETAQA: the aggregated stationary vector and the queue-length moments of an M/G/1-type and of a GI/M/1...
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.
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...
MmckDetection< T > mam_detect_mmck(const qn::NetworkStruct< T > &L, std::size_t ist, const Mmap< T > &arv)
Port of mam_detect_mmck.m: is the exact M/M/c/K closed form legitimate at this station?
BmapMap1Blocks< T > solver_mam_bmap_map_1_blocks(const std::vector< Matrix< T > > &D, const Map< T > &service)
Port of the block assembly and the stability test of solver_mam_bmap_map_1.m.
basic_detail::TruncRenorm< T > mam_truncate_renorm(const Mmap< T > &arv, const std::vector< PhService< T > > &svc, std::size_t capK)
Port of mam_truncate_renorm.m: the finite-buffer marginal of an MMAP[K]/PH[K]/1 FCFS queue,...
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
Definition mmap_lambda.h:57
std::vector< T > map_prob(const Map< T > &m)
Stationary distribution of the phase process, pi (D0 + D1) = 0.
Definition map_moment.h:73
BmapQueueResult< T > solver_mam_bmap_map_1(const std::vector< Matrix< T > > &D, const Map< T > &service, std::size_t nMoments=3)
Port of solver_mam_bmap_map_1.m, mean measures included.
MapBmap1Blocks< T > solver_mam_map_bmap_1_blocks(const Map< T > &arrival, const std::vector< Matrix< T > > &D)
Port of the block assembly, the generator validation and the stability test of solver_mam_map_bmap_1....
BmapQueueResult< T > solver_mam_map_bmap_1(const Map< T > &arrival, const std::vector< Matrix< T > > &D)
Port of solver_mam_map_bmap_1.m, mean measures included.
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
Definition map_moment.h:79
Matrix< double > hcat(const Blocks &blk)
Re-assembles a block sequence into the wide [A0 A1 ... Amax].
Definition mg1.h:170
double mg1_qlen_etaqa(const Matrix< double > &Bin, const Matrix< double > &Ain, const std::vector< double > &pi, std::size_t n, const Matrix< double > &C0in=Matrix< double >())
n-th moment of the level (the queue length) of an M/G/1-type chain from the ETAQA aggregates.
Definition etaqa.h:382
Matrix< double > mg1_g_etaqa(const Matrix< double > &A)
G of an M/G/1-type chain, uniformized first.
Definition etaqa.h:199
double gim1_qlen_etaqa(const Matrix< double > &Bin, const Matrix< double > &Ain, const Matrix< double > &R, const std::vector< double > &pi, std::size_t n, const Matrix< double > &B0in=Matrix< double >())
n-th moment of the level of a GI/M/1-type chain from the ETAQA aggregates.
Definition etaqa.h:677
Matrix< double > gim1_r_etaqa(const Matrix< double > &A)
R of a GI/M/1-type chain, uniformized first.
Definition etaqa.h:531
std::vector< double > mg1_pi_etaqa(const Matrix< double > &Bin, const Matrix< double > &Ain, const Matrix< double > &G, const Matrix< double > &C0in=Matrix< double >())
Aggregated stationary vector [pi0, pi1, pi2+pi3+...] of an M/G/1-type chain.
Definition etaqa.h:230
std::vector< double > gim1_pi_etaqa(const Matrix< double > &Bin, const Matrix< double > &Ain, const Matrix< double > &R, const Matrix< double > &B0in=Matrix< double >())
Aggregated stationary vector [pi0, pi1, pi2+pi3+...] of a GI/M/1-type chain.
Definition etaqa.h:559
Matrix< double > vcat(const Blocks &blk)
Stacks a block sequence vertically, [A0; A1; ...; Amax].
Definition mg1.h:181
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 > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
A queueing network and its refreshed NetworkStruct.
Quasi-birth-death processes: the rate matrix R, the fundamental matrix G, the caudal characteristic,...
Port of solver_mam_basic.m, the dec.source analyzer and the default algorithm of SolverMAM.
The level blocks of solver_mam_bmap_map_1.m.
std::vector< Matrix< T > > Bup
Bup[k-1]: level 0 -> +k.
T mu
service completions per unit time
std::size_t ms
service MAP phases
std::vector< Matrix< T > > Aup
Aup[k-1]: level +k, a batch of k.
std::size_t ma
BMAP phases.
T lambda
customers per unit time
std::size_t K
largest batch size
Matrix< T > B0
level 0 local block, at the empty queue
bool stable
The reference WARNS rather than errors when rho >= 1; recorded, not thrown.
Matrix< T > A1
level 0: phase changes only
Matrix< T > A0
level -1: a service completion
What both reference files return once the ETAQA solve has run.
Matrix< T > fund
G for the M/G/1-type solve, R for the GI/M/1-type one.
Matrix< T > piAgg
the ETAQA-aggregated stationary vector
std::vector< T > qlenMoments
Moments 1..nMoments of the queue length; QN is the first of them.
The level blocks of solver_mam_map_bmap_1.m.
std::size_t ms
service BMAP phases
Matrix< T > A0
level +1: an arrival
std::vector< Matrix< T > > Adown
Adown[k-1]: level -k, a batch service of k.
std::vector< Matrix< T > > Bto0
Bto0[j-1]: level j -> level 0, j = 1..K.
std::size_t ma
arrival MAP phases
Matrix< T > B1
level 0 local block, service folded back
std::size_t K
largest service batch
T mu
customers served per unit time
Matrix< T > A1
level 0: phase changes only
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
std::size_t order() const
Definition mmap_lambda.h:50
What mam_detect_mmck returns; muRate is meaningful only when isMmck.
One class's phase-type service law, He's (sigma_k, S_k).
Definition mmapph1fcfs.h:71