LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mmapph1fcfs.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_MMAPPH1FCFS_H
6#define LINE_API_MAM_MMAPPH1FCFS_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * The MMAP[K]/PH[K]/1 FCFS queue: per-class mean number in system and per-class
12 * queue-length distribution.
13 *
14 * This is the workhorse `solver_mam_basic.m` calls at every FCFS station, where
15 * MATLAB reaches BUTools' `MMAPPH1FCFS`. The algorithm is He's age process for
16 * the SM[K]/PH[K]/1/FCFS queue (Qiming He, "Analysis of a continuous time
17 * SM[K]/PH[K]/1/FCFS queue: age process, sojourn times, and queue lengths",
18 * Journal of Systems Science and Complexity 25(1), 133-155, 2012), which is the
19 * algorithm BUTools implements and which is what makes the two agree.
20 *
21 * WHY THE AGE PROCESS AND NOT A QBD. A level-independent QBD over (number in
22 * system, arrival phase, in-service phase) is NOT Markovian here: under FCFS
23 * with class-dependent service, the law of the next service depends on the
24 * class of the job at the head of the queue, so the phase would have to carry
25 * the whole waiting sequence of classes. `qsys_mapmap1.h` gets away with a QBD
26 * precisely because it has one class. He's construction sidesteps this by
27 * tracking the AGE of the job in service against the arrival process, which
28 * closes as a fluid queue whose first-return matrix Psi is exactly what
29 * `mfq_fundamental` computes.
30 *
31 * THE PIECES, and where each comes from in this port:
32 * Psi -- the fluid first-return matrix of the age process, ADDA doubling
33 * (`mfq_solve.h`), the same routine BUTools reaches through
34 * FluidFundamentalMatrices(..., 'P').
35 * T -- kron(I_N, Sa) + Psi iVec, the age generator.
36 * pi0 -- the age density at zero, a linear functional of the arrival
37 * stationary vector `theta` (`ctmc_solve`) and the per-class
38 * equilibrium service vectors `beta` (also `ctmc_solve`).
39 * the queue-length recursion -- a chain of Sylvester equations
40 * T X + X kron(D0+Da-Dk, I_Ns) + C = 0 that all share their left and
41 * right coefficient matrices, so `SylvesterFactor` factors the
42 * Kronecker operator once and reuses it (`util/sylvester.h`).
43 *
44 * ARITHMETIC. `mfq_fundamental` runs a tolerance-terminated doubling iteration
45 * and is gated on `num_traits<T>::has_transcendental`, so this function is
46 * {Double, Real} and refuses by name under exact/Rational. Everything else in
47 * it -- the Kronecker assembly, the linear solves, the Sylvester chain -- is
48 * field arithmetic and would be exact; the Riccati root is what is not.
49 *
50 * MEASURED AGAINST MATLAB'S BUTools MMAPPH1FCFS: see cpp/tests/test_mam.cpp,
51 * which pins both entry points on a two-class MMAP/PH/1 fixture.
52 */
53
54#include <cstddef>
55#include <vector>
56
60#include "line/num/number.h"
61#include "line/util/error.h"
62#include "line/util/linalg.h"
63#include "line/util/matrix.h"
64#include "line/util/sylvester.h"
65
66namespace line {
67namespace mam {
68
69/** One class's phase-type service law, He's (sigma_k, S_k). */
70template <class T>
71struct PhService {
72 std::vector<T> sigma; ///< initial probability row vector
73 Matrix<T> S; ///< transient generator
74};
75
76namespace mmapph1_detail {
77
78/**
79 * Everything the two entry points share: the age generator T, its density at
80 * zero pi0, the per-class block layout of the stacked service generator, and
81 * the aggregate load rho.
82 */
83template <class T>
84struct AgeProcess {
85 Matrix<T> T_; ///< age generator, (N*Ns) x (N*Ns)
86 std::vector<T> pi0; ///< age density at zero, length N*Ns
87 Matrix<T> D0, Da; ///< arrival D0 and the summed arrival matrix
88 std::vector<Matrix<T>> Dk; ///< per-class arrival matrices
89 std::vector<std::size_t> Nsk; ///< per-class service order
90 std::size_t N = 0, Ns = 0; ///< arrival order, total service order
91 T rho = num_traits<T>::from_int(0);
92};
93
94/** Block-diagonal stacking of the per-class service generators. */
95template <class T>
96Matrix<T> blkdiag(const std::vector<PhService<T>>& svc, std::size_t Ns) {
97 const T zero = num_traits<T>::from_int(0);
98 Matrix<T> Sa(Ns, Ns, zero);
99 std::size_t off = 0;
100 for (const PhService<T>& s : svc) {
101 for (std::size_t i = 0; i < s.S.rows(); ++i)
102 for (std::size_t j = 0; j < s.S.cols(); ++j) Sa(off + i, off + j) = s.S(i, j);
103 off += s.S.rows();
104 }
105 return Sa;
106}
107
108/**
109 * Equilibrium (stationary) vector of the PH renewal chain, MATLAB's
110 * `CTMCSolve(S - sum(S,2) sigma)`.
111 *
112 * THE SIGN IS THE WHOLE FUNCTION. `sum(S,2)` is the ROW SUM of a subgenerator,
113 * so it is the NEGATIVE exit rate, and the generator of the renewal chain is
114 * `S - rowsum sigma` = `S + exitrate sigma`. Adding the row sum instead builds a
115 * matrix that is not a generator at all: on an Erlang-2 it gives
116 * [[-6 6],[-6 -6]], whose "stationary" vector makes the mean service rate
117 * non-positive downstream, and every Sylvester system built on it is singular.
118 */
119template <class T>
120std::vector<T> ph_equilibrium(const PhService<T>& s) {
121 const T zero = num_traits<T>::from_int(0);
122 const std::size_t n = s.S.rows();
123 Matrix<T> G(n, n, zero);
124 for (std::size_t i = 0; i < n; ++i) {
125 T rowsum = zero;
126 for (std::size_t j = 0; j < n; ++j) rowsum += s.S(i, j);
127 for (std::size_t j = 0; j < n; ++j) G(i, j) = s.S(i, j) - rowsum * s.sigma[j];
128 }
129 return mc::ctmc_solve(G);
130}
131
132/** Build the shared age process. */
133template <class T>
134AgeProcess<T> build(const Mmap<T>& arrival, const std::vector<PhService<T>>& svc) {
135 static_assert(num_traits<T>::has_transcendental,
136 "mmapph1fcfs runs the ADDA doubling iteration of mfq_fundamental");
137 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
138 const std::size_t K = svc.size();
139 if (arrival.classes() != K)
140 throw InputError("mmapph1fcfs: the arrival MMAP and the service list disagree on the "
141 "number of classes");
142 AgeProcess<T> a;
143 a.N = arrival.order();
144 a.D0 = arrival.D0;
145 a.Dk = arrival.Dc;
146 a.Da = Matrix<T>(a.N, a.N, zero);
147 for (std::size_t k = 0; k < K; ++k)
148 for (std::size_t i = 0; i < a.N; ++i)
149 for (std::size_t j = 0; j < a.N; ++j) a.Da(i, j) += a.Dk[k](i, j);
150
151 Matrix<T> Q(a.N, a.N, zero);
152 for (std::size_t i = 0; i < a.N; ++i)
153 for (std::size_t j = 0; j < a.N; ++j) Q(i, j) = a.D0(i, j) + a.Da(i, j);
154 const std::vector<T> theta = mc::ctmc_solve(Q);
155
156 a.Nsk.resize(K);
157 a.Ns = 0;
158 for (std::size_t k = 0; k < K; ++k) {
159 a.Nsk[k] = svc[k].S.rows();
160 a.Ns += a.Nsk[k];
161 }
162 std::vector<T> lambda(K, zero), mu(K, zero);
163 std::vector<std::vector<T>> beta(K);
164 a.rho = zero;
165 for (std::size_t k = 0; k < K; ++k) {
166 const std::vector<T> td = vecmul(theta, a.Dk[k]);
167 for (const T& v : td) lambda[k] += v;
168 beta[k] = ph_equilibrium(svc[k]);
169 for (std::size_t i = 0; i < a.Nsk[k]; ++i)
170 for (std::size_t j = 0; j < a.Nsk[k]; ++j) mu[k] -= beta[k][i] * svc[k].S(i, j);
171 if (!(mu[k] > zero)) throw NumericError("mmapph1fcfs: a service law has zero rate");
172 a.rho += lambda[k] / mu[k];
173 }
174
175 const Matrix<T> Sa = blkdiag(svc, a.Ns);
176 const Matrix<T> Ia = eye<T>(a.N);
177 const Matrix<T> Is = eye<T>(a.Ns);
178
179 // sa{q}, ba{q} and sv{q} place class q's vectors in its own block of the
180 // stacked service space and leave the other blocks at zero.
181 std::vector<std::vector<T>> sa(K, std::vector<T>(a.Ns, zero));
182 std::vector<std::vector<T>> ba(K, std::vector<T>(a.Ns, zero));
183 std::size_t off = 0;
184 for (std::size_t k = 0; k < K; ++k) {
185 for (std::size_t i = 0; i < a.Nsk[k]; ++i) {
186 sa[k][off + i] = svc[k].sigma[i];
187 ba[k][off + i] = beta[k][i];
188 }
189 off += a.Nsk[k];
190 }
191
192 // Fpp = kron(Ia,Sa), Fpm = kron(Ia,-Sa e), Fmp = iVec, Fmm = D0.
193 const Matrix<T> Fpp = kron(Ia, Sa);
194 Matrix<T> sexit(a.Ns, 1, zero);
195 for (std::size_t i = 0; i < a.Ns; ++i) {
196 T s = zero;
197 for (std::size_t j = 0; j < a.Ns; ++j) s += Sa(i, j);
198 sexit(i, 0) = -s;
199 }
200 const Matrix<T> Fpm = kron(Ia, sexit);
201 Matrix<T> iVec(a.N, a.N * a.Ns, zero);
202 for (std::size_t k = 0; k < K; ++k) {
203 Matrix<T> row(1, a.Ns, zero);
204 for (std::size_t i = 0; i < a.Ns; ++i) row(0, i) = sa[k][i];
205 const Matrix<T> blk = kron(a.Dk[k], row);
206 for (std::size_t i = 0; i < iVec.rows(); ++i)
207 for (std::size_t j = 0; j < iVec.cols(); ++j) iVec(i, j) += blk(i, j);
208 }
209
210 const FluidFundamental<T> ff =
211 mfq_fundamental(Fpp, Fpm, iVec, a.D0, T(num_traits<T>::from_double(1e-14)), 150u,
213 const Matrix<T> Y0 = ff.Psi;
214
215 a.T_ = matmul(Y0, iVec);
216 for (std::size_t i = 0; i < a.T_.rows(); ++i)
217 for (std::size_t j = 0; j < a.T_.cols(); ++j) a.T_(i, j) += Fpp(i, j);
218
219 a.pi0.assign(a.N * a.Ns, zero);
220 for (std::size_t k = 0; k < K; ++k) {
221 const std::vector<T> tD = vecmul(theta, a.Dk[k]);
222 for (std::size_t i = 0; i < a.N; ++i)
223 for (std::size_t j = 0; j < a.Ns; ++j)
224 a.pi0[i * a.Ns + j] += tD[i] * T(ba[k][j] / mu[k]);
225 }
226 const std::vector<T> tmp = vecmul(a.pi0, a.T_);
227 for (std::size_t i = 0; i < a.pi0.size(); ++i) a.pi0[i] = -tmp[i];
228 (void)one;
229 return a;
230}
231
232/** The (Ns x 1) indicator of class k's block, or its complement. */
233template <class T>
234Matrix<T> class_indicator(const AgeProcess<T>& a, std::size_t k, bool complement) {
235 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
236 Matrix<T> jm(a.Ns, 1, complement ? one : zero);
237 std::size_t off = 0;
238 for (std::size_t q = 0; q < k; ++q) off += a.Nsk[q];
239 for (std::size_t i = 0; i < a.Nsk[k]; ++i) jm(off + i, 0) = complement ? zero : one;
240 return jm;
241}
242
243/** kron(ones(N,1), v) for an (Ns x 1) column, i.e. the vector pi0 contracts against. */
244template <class T>
245std::vector<T> lift(const AgeProcess<T>& a, const Matrix<T>& jm) {
246 std::vector<T> out(a.N * a.Ns);
247 for (std::size_t i = 0; i < a.N; ++i)
248 for (std::size_t j = 0; j < a.Ns; ++j) out[i * a.Ns + j] = jm(j, 0);
249 return out;
250}
251
252/** pi0 M v for a matrix M and a column v. */
253template <class T>
254T contract(const std::vector<T>& pi0, const Matrix<T>& M, const std::vector<T>& v) {
255 const std::vector<T> row = vecmul(pi0, M);
256 T s = num_traits<T>::from_int(0);
257 for (std::size_t i = 0; i < row.size(); ++i) s += row[i] * v[i];
258 return s;
259}
260
261/** The (Ns x 1) exit-rate column -S_k e placed in class k's block, He's sv{k}. */
262template <class T>
263Matrix<T> class_exit(const AgeProcess<T>& a, const std::vector<PhService<T>>& svc, std::size_t k) {
264 const T zero = num_traits<T>::from_int(0);
265 Matrix<T> sv(a.Ns, 1, zero);
266 std::size_t off = 0;
267 for (std::size_t q = 0; q < k; ++q) off += a.Nsk[q];
268 for (std::size_t i = 0; i < a.Nsk[k]; ++i) {
269 T s = zero;
270 for (std::size_t j = 0; j < a.Nsk[k]; ++j) s += svc[k].S(i, j);
271 sv(off + i, 0) = -s;
272 }
273 return sv;
274}
275
276} // namespace mmapph1_detail
277
278/** A phase-type law (alpha, A) as BUTools' `'stDistrPH'` returns it. */
279template <class T>
280struct StDistrPh {
281 std::vector<T> alpha;
283};
284
285/**
286 * Per-class SOJOURN TIME as a continuous phase-type law, BUTools' `'stDistrPH'`.
287 *
288 * The age process is already the sojourn-time engine: `T` generates it and
289 * `pi0` starts it, so the sojourn time of a class-k job is the absorption time
290 * of `T` with the class-k exit column as its closing vector. What this routine
291 * does beyond that is the SIMILARITY TRANSFORM BUTools applies, which turns the
292 * matrix-exponential pair into a genuine PH pair: it drops the states carrying
293 * no probability (`vv > precision`), rescales by `delta = diag(vv(nz))`, and
294 * TRANSPOSES the generator. Without the transpose the pair still has the right
295 * transform but is not a subgenerator, and `map_cdf` on it returns values
296 * outside [0,1].
297 *
298 * The result feeds `solver_mam_passage_time`, which reads it as the MAP
299 * `{A, (-A e) alpha}` and evaluates the response-time CDF on a grid.
300 */
301template <class T>
302std::vector<StDistrPh<T>> mmapph1fcfs_stdistr_ph(const Mmap<T>& arrival,
303 const std::vector<PhService<T>>& svc,
304 double precision = 1e-14) {
305 using namespace mmapph1_detail;
306 const T zero = num_traits<T>::from_int(0);
307 const AgeProcess<T> a = build(arrival, svc);
308 const std::size_t K = svc.size(), n = a.N * a.Ns;
309
310 Matrix<T> negT(n, n, zero);
311 for (std::size_t i = 0; i < n; ++i)
312 for (std::size_t j = 0; j < n; ++j) negT(i, j) = -a.T_(i, j);
313 const Matrix<T> iT = inverse(negT);
314 const std::vector<T> vv = vecmul(a.pi0, iT);
315
316 // The support of the age density: states outside it contribute nothing and
317 // would make delta singular.
318 std::vector<std::size_t> nz;
319 for (std::size_t i = 0; i < n; ++i)
320 if (num_traits<T>::to_double(vv[i]) > precision) nz.push_back(i);
321 if (nz.empty())
322 throw NumericError("mmapph1fcfs_stdistr_ph: the age density has empty support");
323
324 std::vector<StDistrPh<T>> out(K);
325 for (std::size_t k = 0; k < K; ++k) {
326 const std::vector<T> clo = mulvec(iT, lift(a, class_exit(a, svc, k)));
327 T norm = zero;
328 for (std::size_t i = 0; i < n; ++i) norm += a.pi0[i] * clo[i];
329 if (!(num_traits<T>::to_double(norm) > 0.0))
330 throw NumericError("mmapph1fcfs_stdistr_ph: class " + std::to_string(k + 1) +
331 " carries no sojourn-time mass");
332 // cl = -T clo / (pi0 clo)
333 const std::vector<T> Tclo = mulvec(a.T_, clo);
334 std::vector<T> cl(n);
335 for (std::size_t i = 0; i < n; ++i) cl[i] = T(-Tclo[i] / norm);
336
337 out[k].alpha.assign(nz.size(), zero);
338 for (std::size_t i = 0; i < nz.size(); ++i) out[k].alpha[i] = T(cl[nz[i]] * vv[nz[i]]);
339 out[k].A = Matrix<T>(nz.size(), nz.size(), zero);
340 for (std::size_t i = 0; i < nz.size(); ++i)
341 for (std::size_t j = 0; j < nz.size(); ++j)
342 // inv(delta) T(nz,nz)' delta, the transpose included
343 out[k].A(i, j) = T(a.T_(nz[j], nz[i]) * vv[nz[j]] / vv[nz[i]]);
344 }
345 return out;
346}
347
348/**
349 * Per-class mean number of customers in the system, BUTools' `'ncMoms', 1`.
350 *
351 * @param arrival the MMAP (D0, D1, D1^(1)..D1^(K)) of the arrival stream
352 * @param svc the per-class phase-type service laws, K of them
353 */
354template <class T>
355std::vector<T> mmapph1fcfs_ncmean(const Mmap<T>& arrival, const std::vector<PhService<T>>& svc) {
356 using namespace mmapph1_detail;
357 const T zero = num_traits<T>::from_int(0);
358 const AgeProcess<T> a = build(arrival, svc);
359 const std::size_t K = svc.size();
360 const Matrix<T> Is = eye<T>(a.Ns);
361 Matrix<T> QA(a.N, a.N, zero);
362 for (std::size_t i = 0; i < a.N; ++i)
363 for (std::size_t j = 0; j < a.N; ++j) QA(i, j) = a.D0(i, j) + a.Da(i, j);
364 const SylvesterFactor<T> F(a.T_, kron(QA, Is));
365 const Matrix<T> EL1 = F.solve_lyap(eye<T>(a.N * a.Ns));
366
367 std::vector<T> out(K, zero);
368 for (std::size_t k = 0; k < K; ++k) {
369 // n = 1 of the moment recursion: Btag is EL1 alone, and the moment is
370 // pi0 EL2 e + pi0 Btag kron(e, jm).
371 const Matrix<T> EL2 = F.solve_lyap(matmul(EL1, kron(a.Dk[k], Is)));
372 const std::vector<T> row = vecmul(a.pi0, EL2);
373 T s = zero;
374 for (const T& v : row) s += v;
375 const Matrix<T> jm = class_indicator(a, k, false);
376 out[k] = T(s + contract(a.pi0, EL1, lift(a, jm)));
377 }
378 return out;
379}
380
381/**
382 * Per-class queue-length distribution, BUTools' `'ncDistr', n`: P(N_k = 0..n-1).
383 *
384 * @param levels the number of probabilities per class, n
385 * @param arrival the marked arrival process
386 * @param svc per-class phase-type service processes
387 */
388template <class T>
389std::vector<std::vector<T>> mmapph1fcfs_ncdistr(const Mmap<T>& arrival,
390 const std::vector<PhService<T>>& svc,
391 std::size_t levels) {
392 using namespace mmapph1_detail;
393 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
394 if (levels == 0) throw InputError("mmapph1fcfs_ncdistr: at least one level is required");
395 const AgeProcess<T> a = build(arrival, svc);
396 const std::size_t K = svc.size();
397 const Matrix<T> Is = eye<T>(a.Ns);
398 const Matrix<T> I = eye<T>(a.N * a.Ns);
399
400 std::vector<std::vector<T>> out(K, std::vector<T>(levels, zero));
401 for (std::size_t k = 0; k < K; ++k) {
402 // The coefficient of the recursion excludes class k's own arrivals: a
403 // class-k arrival advances the level, every other arrival does not.
404 Matrix<T> B(a.N, a.N, zero);
405 for (std::size_t i = 0; i < a.N; ++i)
406 for (std::size_t j = 0; j < a.N; ++j)
407 B(i, j) = a.D0(i, j) + a.Da(i, j) - a.Dk[k](i, j);
408 const SylvesterFactor<T> F(a.T_, kron(B, Is));
409 const Matrix<T> Dkl = kron(a.Dk[k], Is);
410 const std::vector<T> ejm = lift(a, class_indicator(a, k, false));
411 const std::vector<T> ejmc = lift(a, class_indicator(a, k, true));
412
413 Matrix<T> LmCurr = F.solve_lyap(I);
414 out[k][0] = T(one - a.rho + contract(a.pi0, LmCurr, ejmc));
415 for (std::size_t i = 1; i < levels; ++i) {
416 const Matrix<T> LmPrev = LmCurr;
417 LmCurr = F.solve_lyap(matmul(LmPrev, Dkl));
418 out[k][i] = T(contract(a.pi0, LmCurr, ejmc) + contract(a.pi0, LmPrev, ejm));
419 }
420 }
421 return out;
422}
423
424} // namespace mam
425} // namespace line
426
427#endif // LINE_API_MAM_MMAPPH1FCFS_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
The Kronecker operator of a FIXED (A,B) pair, factorized once.
Definition sylvester.h:60
Matrix< T > solve_lyap(const Matrix< T > &C) const
Solve A X + X B + C = 0, MATLAB's lyap(A,B,C).
Definition sylvester.h:94
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.
Dense matrix and non-owning view.
Core of the Markovian fluid queue: the fundamental matrices Psi, K, U and the matrix-exponential stat...
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
std::vector< T > mmapph1fcfs_ncmean(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc)
Per-class mean number of customers in the system, BUTools' 'ncMoms', 1.
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
Definition mmap_lambda.h:57
FluidFundamental< T > mfq_fundamental(const Matrix< T > &Fpp, const Matrix< T > &Fpm, const Matrix< T > &Fmp, const Matrix< T > &Fmm, const T &precision, unsigned maxNumIt, RiccatiMethod method)
Psi, K and U of a fluid queue whose drifts have been normalized to +-1.
Definition mfq_solve.h:196
std::vector< std::vector< T > > mmapph1fcfs_ncdistr(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, std::size_t levels)
Per-class queue-length distribution, BUTools' 'ncDistr', n: P(N_k = 0..n-1).
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'.
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
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
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
An MMAP: the underlying MAP plus the per-class arrival matrices.
Definition mmap_lambda.h:45
One class's phase-type service law, He's (sigma_k, S_k).
Definition mmapph1fcfs.h:71
Matrix< T > S
transient generator
Definition mmapph1fcfs.h:73
std::vector< T > sigma
initial probability row vector
Definition mmapph1fcfs.h:72
A phase-type law (alpha, A) as BUTools' 'stDistrPH' returns it.
std::vector< T > alpha
The Sylvester equation A X + X B = C, and MATLAB's lyap(A,B,C).