LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mam_transient2.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_MAM_TRANSIENT2_H
6#define LINE_API_MAM_MAM_TRANSIENT2_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Laplace-domain transient level-to-level transform V(s,n,m) of a piecewise
12 * level-dependent QBD, the port of `matlab/src/solvers/MAM/mam_transient2.m`
13 * (finite, closed at a top level) and `mam_transient2_open.m` (the last regime
14 * repeating to infinity).
15 *
16 * V(s,n,m) is the Laplace transform, at complex argument s, of the transient
17 * transition-probability matrix from level n to level m. The construction
18 * follows the Horvath et al. formulation the reference is ported from: per
19 * regime, the fundamental matrices G and R of the shifted blocks (L - sI) are
20 * computed in both directions (up and down), the boundary levels are linked by
21 * the "SY / SYh" backward and forward recursions, the threshold-to-threshold
22 * transforms SV are assembled, and a level strictly inside a regime is reached
23 * by interpolating between its two enclosing thresholds with the R matrices.
24 *
25 * INDEXING. The block cell arrays are indexed by regime and are kept 1-based
26 * here, exactly as in the reference, because the recursions mix k, k+1 and k-1
27 * with the threshold vector T and every off-by-one is silent -- it returns a
28 * transform of the wrong level rather than failing. The accessors `B1`, `L1`,
29 * `F1`, `Lv1` and `T1` take the reference's own index.
30 *
31 * ARITHMETIC. Complex double only. The blocks are shifted by -sI at a complex
32 * quadrature node, so G, R and every intermediate are complex; see
33 * `line/num/complex_number.h` for why the generic linear algebra is available
34 * at that element type.
35 */
36
37#include <algorithm>
38#include <complex>
39#include <cstddef>
40#include <vector>
41
43#include "line/util/error.h"
44#include "line/util/linalg.h"
45#include "line/util/lu.h"
46#include "line/util/matrix.h"
47
48namespace line {
49namespace mam {
50
52
53namespace transient_detail {
54
55/** A \ B: LU of A once, then one back substitution per column of B. */
56inline CMat mldivide(const CMat& A, const CMat& B) {
57 if (A.rows() != A.cols()) throw InputError("mam_transient2: left division needs a square matrix");
58 if (A.rows() != B.rows()) throw InputError("mam_transient2: left division dimension mismatch");
59 CMat LU = A;
60 const std::vector<std::size_t> piv = lu_factor(LU);
61 CMat X(B.rows(), B.cols());
62 std::vector<Complex> col(B.rows());
63 for (std::size_t j = 0; j < B.cols(); ++j) {
64 for (std::size_t i = 0; i < B.rows(); ++i) col[i] = B(i, j);
65 lu_solve(LU, piv, col);
66 for (std::size_t i = 0; i < B.rows(); ++i) X(i, j) = col[i];
67 }
68 return X;
69}
70
71/** A / B, as the transposed left division so it is one factorization too. */
72inline CMat mrdivide(const CMat& A, const CMat& B) {
73 return mldivide(B.transpose(), A.transpose()).transpose();
74}
75
76/** MATLAB's mxpow: A^k, and the identity when k is zero. */
77inline CMat mxpow(const CMat& A, long k) {
78 if (k < 0) throw InputError("mam_transient2: mxpow requires a non-negative exponent");
79 if (k == 0) return eye<Complex>(A.rows());
80 return matpow(A, static_cast<unsigned>(k));
81}
82
83inline CMat madd(const CMat& A, const CMat& B) {
84 if (A.rows() != B.rows() || A.cols() != B.cols())
85 throw InputError("mam_transient2: addition shape mismatch");
86 CMat C(A.rows(), A.cols());
87 for (std::size_t i = 0; i < A.rows(); ++i)
88 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) + B(i, j);
89 return C;
90}
91
92inline CMat msub(const CMat& A, const CMat& B) {
93 if (A.rows() != B.rows() || A.cols() != B.cols())
94 throw InputError("mam_transient2: subtraction shape mismatch");
95 CMat C(A.rows(), A.cols());
96 for (std::size_t i = 0; i < A.rows(); ++i)
97 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) - B(i, j);
98 return C;
99}
100
101/** s*I(n) of complex order n. */
102inline CMat sI(const Complex& s, std::size_t n) {
103 CMat M(n, n, Complex(0.0, 0.0));
104 for (std::size_t i = 0; i < n; ++i) M(i, i) = s;
105 return M;
106}
107
108/** [A, B; C, D] with all four blocks of equal order. */
109inline CMat block2(const CMat& A, const CMat& B, const CMat& C, const CMat& D) {
110 const std::size_t n = A.rows();
111 CMat M(2 * n, 2 * n);
112 for (std::size_t i = 0; i < n; ++i)
113 for (std::size_t j = 0; j < n; ++j) {
114 M(i, j) = A(i, j);
115 M(i, n + j) = B(i, j);
116 M(n + i, j) = C(i, j);
117 M(n + i, n + j) = D(i, j);
118 }
119 return M;
120}
121
122/** [A; B] stacked vertically. */
123inline CMat vcat(const CMat& A, const CMat& B) {
124 if (A.cols() != B.cols()) throw InputError("mam_transient2: vcat width mismatch");
125 CMat M(A.rows() + B.rows(), A.cols());
126 for (std::size_t i = 0; i < A.rows(); ++i)
127 for (std::size_t j = 0; j < A.cols(); ++j) M(i, j) = A(i, j);
128 for (std::size_t i = 0; i < B.rows(); ++i)
129 for (std::size_t j = 0; j < B.cols(); ++j) M(A.rows() + i, j) = B(i, j);
130 return M;
131}
132
133/** Rows [r0, r0+nr) and columns [c0, c0+nc). */
134inline CMat sub(const CMat& A, std::size_t r0, std::size_t nr, std::size_t c0, std::size_t nc) {
135 CMat M(nr, nc);
136 for (std::size_t i = 0; i < nr; ++i)
137 for (std::size_t j = 0; j < nc; ++j) M(i, j) = A(r0 + i, c0 + j);
138 return M;
139}
140
141/** Maximum absolute row sum, MATLAB's norm(A, inf) at complex argument. */
142inline double norminf(const CMat& A) {
143 double best = 0.0;
144 for (std::size_t i = 0; i < A.rows(); ++i) {
145 double s = 0.0;
146 for (std::size_t j = 0; j < A.cols(); ++j) s += std::abs(A(i, j));
147 if (s > best) best = s;
148 }
149 return best;
150}
151
152} // namespace transient_detail
153
154/** G and R of a QBD whose local block is complex. */
158 unsigned iterations = 0;
159};
160
161/**
162 * Port of `qbd_fundmat.m` at complex argument: cyclic reduction (Bini-Meini
163 * logarithmic reduction) on the raw level blocks, with R recovered from G.
164 *
165 * The reference uniformizes by `lamb = max(-real(diag(L)))` -- the REAL part,
166 * because the shift -sI makes the diagonal complex while the uniformization
167 * constant must stay a positive real, and stops on `min(norm(BB,inf),
168 * norm(BF,inf))`, a real quantity too. Both are reproduced.
169 */
170inline CQbdFundMat qbd_fundmat_laplace(const CMat& B, const CMat& L, const CMat& F,
171 double precision = 1e-14, unsigned maxNumIt = 50) {
172 using namespace transient_detail;
173 const std::size_t m = L.rows();
174 if (L.cols() != m || B.rows() != m || B.cols() != m || F.rows() != m || F.cols() != m)
175 throw InputError("qbd_fundmat_laplace: B, L and F must be square and of equal order");
176 const CMat II = eye<Complex>(m);
177
178 double lamb = -std::numeric_limits<double>::infinity();
179 for (std::size_t i = 0; i < m; ++i) lamb = std::max(lamb, -L(i, i).real());
180 if (!(lamb > 0.0))
181 throw NumericError("qbd_fundmat_laplace: the local block has no negative real diagonal");
182
183 CMat Bm(m, m), Lm(m, m), Fm(m, m);
184 for (std::size_t i = 0; i < m; ++i)
185 for (std::size_t j = 0; j < m; ++j) {
186 Bm(i, j) = B(i, j) / lamb;
187 Lm(i, j) = L(i, j) / lamb + (i == j ? Complex(1.0, 0.0) : Complex(0.0, 0.0));
188 Fm(i, j) = F(i, j) / lamb;
189 }
190
191 CMat BF = mldivide(msub(II, Lm), II);
192 CMat BB = matmul(BF, Fm);
193 BF = matmul(BF, Bm);
194 CMat G = BF;
195 CMat PI = BB;
196 double check = 1.0;
197 unsigned numit = 0;
198 while (check > precision && numit < maxNumIt) {
199 const CMat Lstar = madd(matmul(BF, BB), matmul(BB, BF));
200 const CMat Bstar = matmul(BB, BB);
201 const CMat Fstar = matmul(BF, BF);
202 // The reference reuses BB for the inverse before overwriting it.
203 BB = mldivide(msub(II, Lstar), II);
204 BF = matmul(BB, Fstar);
205 BB = matmul(BB, Bstar);
206 G = madd(G, matmul(PI, BF));
207 PI = matmul(PI, BB);
208 check = std::min(norminf(BB), norminf(BF));
209 ++numit;
210 }
211
212 CQbdFundMat out;
213 out.G = G;
214 out.R = matmul(Fm, mldivide(msub(II, madd(Lm, matmul(Fm, G))), II));
215 out.iterations = numit;
216 return out;
217}
218
219/**
220 * The piecewise QBD blocks, 1-based exactly as the reference's cell arrays.
221 *
222 * `T` holds the regime thresholds. For the OPEN form there are K = |T| regimes
223 * and the last repeats to infinity; for the FINITE form there are K = |T| - 1
224 * regimes and `Lv[K+1]` is the top boundary level.
225 */
227 std::vector<CMat> B, L, F, Lv; ///< 1-based: entry 0 is unused padding
228 std::vector<long> T; ///< 1-based: entry 0 is unused padding
229
230 const CMat& B1(std::size_t k) const { return B.at(k); }
231 const CMat& L1(std::size_t k) const { return L.at(k); }
232 const CMat& F1(std::size_t k) const { return F.at(k); }
233 const CMat& Lv1(std::size_t k) const { return Lv.at(k); }
234 long T1(std::size_t k) const { return T.at(k); }
235 std::size_t nT() const { return T.size() - 1; }
236};
237
238/** Build the 1-based padded form from plain 0-based vectors. */
239inline TransientQbd make_transient_qbd(const std::vector<CMat>& B, const std::vector<CMat>& L,
240 const std::vector<CMat>& F, const std::vector<CMat>& Lv,
241 const std::vector<long>& T) {
242 TransientQbd q;
243 q.B.push_back(CMat());
244 q.L.push_back(CMat());
245 q.F.push_back(CMat());
246 q.Lv.push_back(CMat());
247 q.T.push_back(0);
248 q.B.insert(q.B.end(), B.begin(), B.end());
249 q.L.insert(q.L.end(), L.begin(), L.end());
250 q.F.insert(q.F.end(), F.begin(), F.end());
251 q.Lv.insert(q.Lv.end(), Lv.begin(), Lv.end());
252 q.T.insert(q.T.end(), T.begin(), T.end());
253 return q;
254}
255
256namespace transient_detail {
257
258/** The regime containing level n: the reference's `find(T>n,1)-1`, floored. */
259inline std::size_t regime_of(const TransientQbd& q, long n, std::size_t fallback) {
260 for (std::size_t i = 1; i <= q.nT(); ++i)
261 if (q.T1(i) > n) return i - 1;
262 return fallback;
263}
264
265/**
266 * The four H blocks of a d-step level range, shared by both forms.
267 *
268 * `num / den` with num = [Gh^(d-1), G; Gh, G^(d-1)] and den = [I, G^d; Gh^d, I],
269 * sliced into the (n->n, n->0, hat n->n, hat n->0) quadrants.
270 */
271struct HBlocks {
272 CMat nn, n0, hnn, hn0;
273};
274
275inline HBlocks h_blocks(const CMat& G, const CMat& Gh, long d, std::size_t NN) {
276 const CMat II = eye<Complex>(NN);
277 const CMat num = block2(mxpow(Gh, d - 1), G, Gh, mxpow(G, d - 1));
278 const CMat den = block2(II, mxpow(G, d), mxpow(Gh, d), II);
279 const CMat Tmp = mrdivide(num, den);
280 HBlocks h;
281 h.nn = sub(Tmp, 0, NN, 0, NN);
282 h.n0 = sub(Tmp, 0, NN, NN, NN);
283 h.hnn = sub(Tmp, NN, NN, 0, NN);
284 h.hn0 = sub(Tmp, NN, NN, NN, NN);
285 return h;
286}
287
288/**
289 * The R-interpolation that closes both forms: a level m strictly between the
290 * thresholds Ll and Lu is a convex combination, in the transform domain, of the
291 * transforms at those two thresholds.
292 */
293inline CMat interpolate(const CMat& Rk, const CMat& Rhk, const CMat& Vl, const CMat& Vu, long m,
294 long Ll, long Lu) {
295 if (Rk.rows() == 0)
296 throw NumericError(
297 "mam_transient2: the interpolation needs the repeating R of a regime wider than one "
298 "level, but that regime has none");
299 if (Vl.rows() == 0 || Vu.rows() == 0)
300 throw NumericError("mam_transient2: the interpolation needs both enclosing transforms");
301 const std::size_t NN = Rk.rows();
302 const CMat II = eye<Complex>(NN);
303 const CMat Zden = block2(II, mxpow(Rk, Lu - Ll), mxpow(Rhk, Lu - Ll), II);
304 const CMat Znum = vcat(mxpow(Rk, m - Ll), mxpow(Rhk, Lu - m));
305 const CMat Z = mldivide(Zden, Znum);
306 return madd(matmul(Vl, sub(Z, 0, NN, 0, Z.cols())),
307 matmul(Vu, sub(Z, NN, NN, 0, Z.cols())));
308}
309
310} // namespace transient_detail
311
312/**
313 * V(s,n,m) for an OPEN piecewise QBD, the port of `mam_transient2_open.m`.
314 *
315 * K = |T| regimes; regime K repeats to infinity, so a target level above the
316 * last threshold is reached by post-multiplying with a power of R{K}.
317 */
318inline CMat mam_transient2_open(const TransientQbd& q, long n, long m, const Complex& s) {
319 using namespace transient_detail;
320 const std::size_t K = q.nT();
321 if (K == 0) throw InputError("mam_transient2_open: at least one regime is required");
322 if (n < 0 || m < 0) throw InputError("mam_transient2_open: levels must be non-negative");
323
324 std::vector<CMat> Gs(K + 1), Rs(K + 1), Ghs(K + 1), Rhs(K + 1);
325 for (std::size_t k = 1; k <= K; ++k) {
326 if (k < K && q.T1(k + 1) - q.T1(k) == 1) continue; // no repeating level in the regime
327 const CMat Lk = msub(q.L1(k), sI(s, q.L1(k).rows()));
328 const CQbdFundMat gr = qbd_fundmat_laplace(q.B1(k), Lk, q.F1(k));
329 Gs[k] = gr.G;
330 Rs[k] = gr.R;
331 const CQbdFundMat grh = qbd_fundmat_laplace(q.F1(k), Lk, q.B1(k));
332 Ghs[k] = grh.G;
333 Rhs[k] = grh.R;
334 }
335
336 std::vector<CMat> SvHn(K + 1), SvH0(K + 1), SvHhn(K + 1), SvHh0(K + 1);
337 for (std::size_t k = 1; k + 1 <= K; ++k) {
338 const std::size_t NN = q.Lv1(k).rows();
339 if (q.T1(k + 1) - q.T1(k) > 1) {
340 const HBlocks h = h_blocks(Gs[k], Ghs[k], q.T1(k + 1) - q.T1(k), NN);
341 SvHn[k] = h.nn;
342 SvH0[k] = h.n0;
343 SvHhn[k] = h.hnn;
344 SvHh0[k] = h.hn0;
345 } else {
346 const std::size_t NN1 = q.Lv1(k + 1).rows();
347 SvH0[k] = CMat(NN1, NN, Complex(0.0, 0.0));
348 SvHh0[k] = eye<Complex>(NN);
349 SvHhn[k] = CMat(NN, NN1, Complex(0.0, 0.0));
350 SvHn[k] = eye<Complex>(NN1);
351 }
352 }
353
354 std::vector<CMat> SY(K + 2);
355 SY[K] = Gs[K];
356 for (std::size_t k = K - 1; k >= 1; --k) {
357 const std::size_t NNk1 = q.Lv1(k + 1).rows();
358 const CMat M = msub(msub(msub(sI(s, NNk1), q.Lv1(k + 1)), matmul(q.F1(k + 1), SY[k + 1])),
359 matmul(q.B1(k), SvHhn[k]));
360 SY[k] = madd(SvH0[k], matmul(SvHn[k], mldivide(M, matmul(q.B1(k), SvHh0[k]))));
361 if (k == 1) break;
362 }
363
364 const std::size_t NN1 = q.Lv1(1).rows();
365 std::vector<CMat> SYh(K + 1);
366 if (K >= 2) {
367 const CMat M = msub(msub(sI(s, NN1), q.Lv1(1)), matmul(q.F1(1), SvH0[1]));
368 SYh[1] = madd(SvHhn[1], matmul(SvHh0[1], mldivide(M, matmul(q.F1(1), SvHn[1]))));
369 }
370 for (std::size_t k = 2; k + 1 <= K; ++k) {
371 const std::size_t NNk = q.Lv1(k).rows();
372 const CMat M = msub(msub(msub(sI(s, NNk), q.Lv1(k)), matmul(q.B1(k - 1), SYh[k - 1])),
373 matmul(q.F1(k), SvH0[k]));
374 SYh[k] = madd(SvHhn[k], matmul(SvHh0[k], mldivide(M, matmul(q.F1(k), SvHn[k]))));
375 }
376
377 // SV[k][l] is the threshold-to-threshold transform, 1-based in both indices.
378 std::vector<std::vector<CMat>> SV(K + 2, std::vector<CMat>(K + 2));
379 for (std::size_t l = 0; l + 1 <= K; ++l) {
380 if (l == 0) {
381 SV[1][1] = mldivide(msub(msub(sI(s, NN1), q.Lv1(1)), matmul(q.F1(1), SY[1])),
382 eye<Complex>(NN1));
383 } else {
384 const std::size_t NNl1 = q.Lv1(l + 1).rows();
385 const CMat M = msub(msub(msub(sI(s, NNl1), q.Lv1(l + 1)), matmul(q.F1(l + 1), SY[l + 1])),
386 matmul(q.B1(l), SYh[l]));
387 SV[l + 1][l + 1] = mldivide(M, eye<Complex>(NNl1));
388 }
389 for (std::size_t k = l + 1; k + 1 <= K; ++k) {
390 const std::size_t NNk1 = q.Lv1(k + 1).rows();
391 const CMat M = msub(msub(msub(sI(s, NNk1), q.Lv1(k + 1)), matmul(q.F1(k + 1), SY[k + 1])),
392 matmul(q.B1(k), SvHhn[k]));
393 SV[k + 1][l + 1] = mldivide(M, matmul(matmul(q.B1(k), SvHh0[k]), SV[k][l + 1]));
394 }
395 for (std::size_t k = l; k-- > 1;) {
396 const std::size_t NNk1 = q.Lv1(k + 1).rows();
397 const CMat M = msub(msub(msub(sI(s, NNk1), q.Lv1(k + 1)), matmul(q.F1(k + 1), SvH0[k + 1])),
398 matmul(q.B1(k), SYh[k]));
399 SV[k + 1][l + 1] = mldivide(M, matmul(matmul(q.F1(k + 1), SvHn[k + 1]), SV[k + 2][l + 1]));
400 }
401 if (l > 0) {
402 const CMat M = msub(msub(sI(s, NN1), q.Lv1(1)), matmul(q.F1(1), SvH0[1]));
403 SV[1][l + 1] = mldivide(M, matmul(matmul(q.F1(1), SvHn[1]), SV[2][l + 1]));
404 }
405 }
406
407 const std::size_t kn = regime_of(q, n, K);
408 const std::size_t km = regime_of(q, m, K);
409
410 const std::size_t NNkn = q.Lv1(kn).rows();
411 const CMat IIkn = eye<Complex>(NNkn);
412
413 CMat Vu, Vl;
414 long Lu = 0, Ll = 0;
415
416 if (q.T1(kn) == n) {
417 if (q.T1(km) == m) return SV[kn][km];
418 Vl = SV[kn][km];
419 Ll = q.T1(km);
420 if (km < K) {
421 Vu = SV[kn][km + 1];
422 Lu = q.T1(km + 1);
423 }
424 } else {
425 HBlocks hT, hn;
426 CMat Yn;
427 if (kn < K) {
428 hT = h_blocks(Gs[kn], Ghs[kn], q.T1(kn + 1) - n, NNkn);
429 hn = h_blocks(Gs[kn], Ghs[kn], n - q.T1(kn), NNkn);
430 const std::size_t NNkn1 = q.Lv1(kn + 1).rows();
431 const CMat M = msub(msub(msub(sI(s, NNkn1), q.Lv1(kn + 1)), matmul(q.F1(kn + 1), SY[kn + 1])),
432 matmul(q.B1(kn), hT.hnn));
433 Yn = madd(hT.n0, matmul(hT.nn, mldivide(M, matmul(q.B1(kn), hT.hn0))));
434 } else {
435 hn = h_blocks(Gs[kn], Ghs[kn], n - q.T1(kn), NNkn);
436 Yn = Gs[kn];
437 }
438
439 CMat Yhn;
440 if (kn == 1) {
441 const CMat M = msub(msub(sI(s, NN1), q.Lv1(1)), matmul(q.F1(1), hn.n0));
442 Yhn = madd(hn.hnn, matmul(hn.hn0, mldivide(M, matmul(q.F1(1), hn.nn))));
443 } else {
444 const std::size_t NNk = q.Lv1(kn).rows();
445 const CMat M = msub(msub(msub(sI(s, NNk), q.Lv1(kn)), matmul(q.B1(kn - 1), SYh[kn - 1])),
446 matmul(q.F1(kn), hn.n0));
447 Yhn = madd(hn.hnn, matmul(hn.hn0, mldivide(M, matmul(q.F1(kn), hn.nn))));
448 }
449
450 const CMat Mkn = msub(sI(s, q.L1(kn).rows()), q.L1(kn));
451 CMat Vnl;
452 if (q.T1(km) < n) {
453 const CMat M = msub(msub(Mkn, matmul(q.B1(kn), hn.hnn)), matmul(q.F1(kn), Yn));
454 Vnl = mldivide(M, matmul(matmul(q.B1(kn), hn.hn0), SV[kn][km]));
455 } else {
456 const CMat M = msub(msub(Mkn, matmul(q.F1(kn), hT.n0)), matmul(q.B1(kn), Yhn));
457 Vnl = mldivide(M, matmul(matmul(q.F1(kn), hT.nn), SV[kn + 1][km]));
458 }
459 if (m == q.T1(km)) return Vnl;
460
461 CMat Vnu;
462 if (km < K) {
463 if (q.T1(km + 1) < n) {
464 const CMat M = msub(msub(Mkn, matmul(q.B1(kn), hn.hnn)), matmul(q.F1(kn), Yn));
465 Vnu = mldivide(M, matmul(matmul(q.B1(kn), hn.hn0), SV[kn][km + 1]));
466 } else {
467 const CMat M = msub(msub(Mkn, matmul(q.F1(kn), hT.n0)), matmul(q.B1(kn), Yhn));
468 Vnu = mldivide(M, matmul(matmul(q.F1(kn), hT.nn), SV[kn + 1][km + 1]));
469 }
470 }
471 const CMat Vnn = mldivide(
472 msub(msub(msub(sI(s, NNkn), q.L1(kn)), matmul(q.B1(kn), Yhn)), matmul(q.F1(kn), Yn)),
473 IIkn);
474 if (n == m) return Vnn;
475 if (km == K && n < m) {
476 if (kn < K) return matmul(Vnl, mxpow(Rs[km], m - q.T1(km)));
477 return matmul(Vnn, mxpow(Rs[km], m - n));
478 }
479 if (kn != km) {
480 Vu = Vnu; Vl = Vnl; Lu = q.T1(km + 1); Ll = q.T1(km);
481 } else if (n <= m) {
482 Vu = Vnu; Vl = Vnn; Lu = q.T1(km + 1); Ll = n;
483 } else {
484 Vu = Vnn; Vl = Vnl; Lu = n; Ll = q.T1(km);
485 }
486 }
487
488 if (km == K && n < m) {
489 if (kn < K) return matmul(Vl, mxpow(Rs[km], m - q.T1(km)));
490 return matmul(SV[kn][kn], mxpow(Rs[km], m - n));
491 }
492
493 return interpolate(Rs[km], Rhs[km], Vl, Vu, m, Ll, Lu);
494}
495
496/**
497 * V(s,n,m) for a FINITE piecewise QBD, the port of `mam_transient2.m`.
498 *
499 * K = |T| - 1 regimes and `Lv[K+1]` is the top boundary level, so the chain is
500 * closed above and there is no R tail.
501 */
502inline CMat mam_transient2(const TransientQbd& q, long n, long m, const Complex& s) {
503 using namespace transient_detail;
504 if (q.nT() < 2) throw InputError("mam_transient2: the finite form needs at least two thresholds");
505 const std::size_t K = q.nT() - 1;
506 if (n < 0 || m < 0) throw InputError("mam_transient2: levels must be non-negative");
507
508 std::vector<CMat> Gs(K + 1), Rs(K + 1), Ghs(K + 1), Rhs(K + 1);
509 for (std::size_t k = 1; k <= K; ++k) {
510 if (q.T1(k + 1) - q.T1(k) == 1) continue;
511 const CMat Lk = msub(q.L1(k), sI(s, q.L1(k).rows()));
512 const CQbdFundMat gr = qbd_fundmat_laplace(q.B1(k), Lk, q.F1(k));
513 Gs[k] = gr.G;
514 Rs[k] = gr.R;
515 const CQbdFundMat grh = qbd_fundmat_laplace(q.F1(k), Lk, q.B1(k));
516 Ghs[k] = grh.G;
517 Rhs[k] = grh.R;
518 }
519
520 std::vector<CMat> SvHn(K + 1), SvH0(K + 1), SvHhn(K + 1), SvHh0(K + 1);
521 for (std::size_t k = 1; k <= K; ++k) {
522 const std::size_t NN = q.Lv1(k).rows();
523 if (q.T1(k + 1) - q.T1(k) > 1) {
524 const HBlocks h = h_blocks(Gs[k], Ghs[k], q.T1(k + 1) - q.T1(k), NN);
525 SvHn[k] = h.nn;
526 SvH0[k] = h.n0;
527 SvHhn[k] = h.hnn;
528 SvHh0[k] = h.hn0;
529 } else {
530 const std::size_t NN1 = q.Lv1(k + 1).rows();
531 SvH0[k] = CMat(NN1, NN, Complex(0.0, 0.0));
532 SvHh0[k] = eye<Complex>(NN);
533 SvHhn[k] = CMat(NN, NN1, Complex(0.0, 0.0));
534 SvHn[k] = eye<Complex>(NN1);
535 }
536 }
537
538 const std::size_t NNK = q.Lv1(K + 1).rows();
539 std::vector<CMat> SY(K + 2);
540 {
541 const CMat M = msub(msub(sI(s, NNK), q.Lv1(K + 1)), matmul(q.B1(K), SvHhn[K]));
542 SY[K] = madd(SvH0[K], matmul(SvHn[K], mldivide(M, matmul(q.B1(K), SvHh0[K]))));
543 }
544 for (std::size_t k = K; k-- > 1;) {
545 const std::size_t NNk1 = q.Lv1(k + 1).rows();
546 const CMat M = msub(msub(msub(sI(s, NNk1), q.Lv1(k + 1)), matmul(q.F1(k + 1), SY[k + 1])),
547 matmul(q.B1(k), SvHhn[k]));
548 SY[k] = madd(SvH0[k], matmul(SvHn[k], mldivide(M, matmul(q.B1(k), SvHh0[k]))));
549 }
550
551 const std::size_t NN1 = q.Lv1(1).rows();
552 std::vector<CMat> SYh(K + 1);
553 {
554 const CMat M = msub(msub(sI(s, NN1), q.Lv1(1)), matmul(q.F1(1), SvH0[1]));
555 SYh[1] = madd(SvHhn[1], matmul(SvHh0[1], mldivide(M, matmul(q.F1(1), SvHn[1]))));
556 }
557 for (std::size_t k = 2; k <= K; ++k) {
558 const std::size_t NNk = q.Lv1(k).rows();
559 const CMat M = msub(msub(msub(sI(s, NNk), q.Lv1(k)), matmul(q.B1(k - 1), SYh[k - 1])),
560 matmul(q.F1(k), SvH0[k]));
561 SYh[k] = madd(SvHhn[k], matmul(SvHh0[k], mldivide(M, matmul(q.F1(k), SvHn[k]))));
562 }
563
564 std::vector<std::vector<CMat>> SV(K + 3, std::vector<CMat>(K + 3));
565 for (std::size_t l = 0; l <= K; ++l) {
566 if (l == 0) {
567 SV[1][1] = mldivide(msub(msub(sI(s, NN1), q.Lv1(1)), matmul(q.F1(1), SY[1])),
568 eye<Complex>(NN1));
569 } else if (l == K) {
570 SV[K + 1][K + 1] = mldivide(
571 msub(msub(sI(s, NNK), q.Lv1(K + 1)), matmul(q.B1(K), SYh[K])), eye<Complex>(NNK));
572 } else {
573 const std::size_t NNl1 = q.Lv1(l + 1).rows();
574 const CMat M = msub(msub(msub(sI(s, NNl1), q.Lv1(l + 1)), matmul(q.F1(l + 1), SY[l + 1])),
575 matmul(q.B1(l), SYh[l]));
576 SV[l + 1][l + 1] = mldivide(M, eye<Complex>(NNl1));
577 }
578 for (std::size_t k = l + 1; k + 1 <= K; ++k) {
579 const std::size_t NNk1 = q.Lv1(k + 1).rows();
580 const CMat M = msub(msub(msub(sI(s, NNk1), q.Lv1(k + 1)), matmul(q.F1(k + 1), SY[k + 1])),
581 matmul(q.B1(k), SvHhn[k]));
582 SV[k + 1][l + 1] = mldivide(M, matmul(matmul(q.B1(k), SvHh0[k]), SV[k][l + 1]));
583 }
584 if (l < K) {
585 const CMat M = msub(msub(sI(s, NNK), q.Lv1(K + 1)), matmul(q.B1(K), SvHhn[K]));
586 SV[K + 1][l + 1] = mldivide(M, matmul(matmul(q.B1(K), SvHh0[K]), SV[K][l + 1]));
587 }
588 for (std::size_t k = l; k-- > 1;) {
589 const std::size_t NNk1 = q.Lv1(k + 1).rows();
590 const CMat M = msub(msub(msub(sI(s, NNk1), q.Lv1(k + 1)), matmul(q.F1(k + 1), SvH0[k + 1])),
591 matmul(q.B1(k), SYh[k]));
592 SV[k + 1][l + 1] = mldivide(M, matmul(matmul(q.F1(k + 1), SvHn[k + 1]), SV[k + 2][l + 1]));
593 }
594 if (l > 0) {
595 const CMat M = msub(msub(sI(s, NN1), q.Lv1(1)), matmul(q.F1(1), SvH0[1]));
596 SV[1][l + 1] = mldivide(M, matmul(matmul(q.F1(1), SvHn[1]), SV[2][l + 1]));
597 }
598 }
599
600 const std::size_t kn = regime_of(q, n, K + 1);
601 const std::size_t km = regime_of(q, m, K + 1);
602
603 const std::size_t NNkn = q.Lv1(kn).rows();
604 const CMat IIkn = eye<Complex>(NNkn);
605
606 CMat Vu, Vl;
607 long Lu = 0, Ll = 0;
608
609 if (q.T1(kn) == n) {
610 if (q.T1(km) == m) return SV[kn][km];
611 Vu = SV[kn][km + 1];
612 Vl = SV[kn][km];
613 Lu = q.T1(km + 1);
614 Ll = q.T1(km);
615 } else {
616 const HBlocks hT = h_blocks(Gs[kn], Ghs[kn], q.T1(kn + 1) - n, NNkn);
617 const HBlocks hn = h_blocks(Gs[kn], Ghs[kn], n - q.T1(kn), NNkn);
618
619 CMat Yn;
620 if (kn == K) {
621 const CMat M = msub(msub(sI(s, NNK), q.Lv1(K + 1)), matmul(q.B1(K), hT.hnn));
622 Yn = madd(hT.n0, matmul(hT.nn, mldivide(M, matmul(q.B1(K), hT.hn0))));
623 } else {
624 const std::size_t NNkn1 = q.Lv1(kn + 1).rows();
625 const CMat M = msub(msub(msub(sI(s, NNkn1), q.Lv1(kn + 1)), matmul(q.F1(kn + 1), SY[kn + 1])),
626 matmul(q.B1(kn), hT.hnn));
627 Yn = madd(hT.n0, matmul(hT.nn, mldivide(M, matmul(q.B1(kn), hT.hn0))));
628 }
629
630 CMat Yhn;
631 if (kn == 1) {
632 const CMat M = msub(msub(sI(s, NN1), q.Lv1(1)), matmul(q.F1(1), hn.n0));
633 Yhn = madd(hn.hnn, matmul(hn.hn0, mldivide(M, matmul(q.F1(1), hn.nn))));
634 } else {
635 const std::size_t NNk = q.Lv1(kn).rows();
636 const CMat M = msub(msub(msub(sI(s, NNk), q.Lv1(kn)), matmul(q.B1(kn - 1), SYh[kn - 1])),
637 matmul(q.F1(kn), hn.n0));
638 Yhn = madd(hn.hnn, matmul(hn.hn0, mldivide(M, matmul(q.F1(kn), hn.nn))));
639 }
640
641 const CMat Mkn = msub(sI(s, q.L1(kn).rows()), q.L1(kn));
642 CMat Vnl;
643 if (q.T1(km) < n) {
644 const CMat M = msub(msub(Mkn, matmul(q.B1(kn), hn.hnn)), matmul(q.F1(kn), Yn));
645 Vnl = mldivide(M, matmul(matmul(q.B1(kn), hn.hn0), SV[kn][km]));
646 } else {
647 const CMat M = msub(msub(Mkn, matmul(q.F1(kn), hT.n0)), matmul(q.B1(kn), Yhn));
648 Vnl = mldivide(M, matmul(matmul(q.F1(kn), hT.nn), SV[kn + 1][km]));
649 }
650 if (m == q.T1(km)) return Vnl;
651
652 CMat Vnu;
653 if (q.T1(km + 1) < n) {
654 const CMat M = msub(msub(Mkn, matmul(q.B1(kn), hn.hnn)), matmul(q.F1(kn), Yn));
655 Vnu = mldivide(M, matmul(matmul(q.B1(kn), hn.hn0), SV[kn][km + 1]));
656 } else {
657 const CMat M = msub(msub(Mkn, matmul(q.F1(kn), hT.n0)), matmul(q.B1(kn), Yhn));
658 Vnu = mldivide(M, matmul(matmul(q.F1(kn), hT.nn), SV[kn + 1][km + 1]));
659 }
660 const CMat Vnn = mldivide(
661 msub(msub(msub(sI(s, NNkn), q.L1(kn)), matmul(q.B1(kn), Yhn)), matmul(q.F1(kn), Yn)),
662 IIkn);
663 if (n == m) return Vnn;
664 if (kn != km) {
665 Vu = Vnu; Vl = Vnl; Lu = q.T1(km + 1); Ll = q.T1(km);
666 } else if (n <= m) {
667 Vu = Vnu; Vl = Vnn; Lu = q.T1(km + 1); Ll = n;
668 } else {
669 Vu = Vnn; Vl = Vnl; Lu = n; Ll = q.T1(km);
670 }
671 }
672
673 return interpolate(Rs[km], Rhs[km], Vl, Vu, m, Ll, Lu);
674}
675
676} // namespace mam
677} // namespace line
678
679#endif // LINE_API_MAM_MAM_TRANSIENT2_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
Matrix transpose() const
Definition matrix.h:110
NumericError(const std::string &what)
Definition error.h:45
std::complex<double> as a number type for the generic linear algebra.
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.
CMat mam_transient2_open(const TransientQbd &q, long n, long m, const Complex &s)
V(s,n,m) for an OPEN piecewise QBD, the port of mam_transient2_open.m.
CMat mam_transient2(const TransientQbd &q, long n, long m, const Complex &s)
V(s,n,m) for a FINITE piecewise QBD, the port of mam_transient2.m.
Matrix< Complex > CMat
CQbdFundMat qbd_fundmat_laplace(const CMat &B, const CMat &L, const CMat &F, double precision=1e-14, unsigned maxNumIt=50)
Port of qbd_fundmat.m at complex argument: cyclic reduction (Bini-Meini logarithmic reduction) on the...
TransientQbd make_transient_qbd(const std::vector< CMat > &B, const std::vector< CMat > &L, const std::vector< CMat > &F, const std::vector< CMat > &Lv, const std::vector< long > &T)
Build the 1-based padded form from plain 0-based vectors.
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
Definition lu.h:94
std::complex< double > Complex
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition lu.h:48
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
Matrix< T > matpow(const Matrix< T > &A, unsigned k)
Integer matrix power, by repeated squaring.
Definition linalg.h:89
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
G and R of a QBD whose local block is complex.
The piecewise QBD blocks, 1-based exactly as the reference's cell arrays.
std::vector< long > T
1-based: entry 0 is unused padding
std::vector< CMat > B
std::vector< CMat > Lv
1-based: entry 0 is unused padding
const CMat & Lv1(std::size_t k) const
const CMat & B1(std::size_t k) const
std::size_t nT() const
long T1(std::size_t k) const
std::vector< CMat > F
const CMat & F1(std::size_t k) const
const CMat & L1(std::size_t k) const
std::vector< CMat > L