LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mfq_ld_solve.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_MFQ_LD_SOLVE_H
6#define LINE_API_MAM_MFQ_LD_SOLVE_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * First- and second-order level-dependent (multi-regime) Markovian fluid
12 * queues: the matrix-exponential building blocks of the stationary law.
13 *
14 * Port of matlab/src/api/mam/mfq_ld_solve.m and the BUTools
15 * SecondOrderLevelDependentFluidSolve it wraps. The generator, the drift and
16 * optionally the VARIANCE change at threshold levels, giving a piecewise
17 * homogeneous first- or second-order (Brownian) fluid queue. Setting every
18 * variance cell to zero reduces it to first order. The blocks it returns are
19 * exactly what mfq_ld_mean and mfq_ld_distr consume, so this closes that
20 * family rather than extending it.
21 *
22 * METHOD. Per regime, the states with neither drift nor variance are censored
23 * out, and the remainder is split into three classes: positive drift with no
24 * variance, negative drift with no variance, and any state with variance. The
25 * density is anchored at both ends of the regime, forward from its lower
26 * threshold and backward from its upper one, and each direction's exponent
27 * comes from a QBD. The second-order terms are what force the QBD form: with a
28 * variance the balance equation is second order in the level, and multiplying
29 * through by a constant c chosen from the spectrum turns it into the matrix
30 * QUADRATIC of a discrete QBD, from whose R the exponent is recovered as
31 * K = (R - I) c. c is the smallest value that keeps the transformed triple
32 * substochastic, taken as the maximum over the drift states of -Q_ii/R_ii and
33 * over the variance states of the larger root of the discriminant, floored at 1.
34 *
35 * WHICH QBD SOLVER, AND WHY NO NEW ONE WAS NEEDED. The reference calls
36 * QBD_CR(Bm, Lm, Fm), and the R it returns satisfies
37 *
38 * Fm + R Lm + R^2 Bm = 0,
39 *
40 * which is EXACTLY the contract of the port's existing qbd_R,
41 * F + R L + R^2 B = 0, under the direct mapping B = Bm, L = Lm, F = Fm. So the
42 * cyclic-reduction routine did not have to be transcribed.
43 *
44 * That equation was established by MEASUREMENT: do not read QBD_CR's
45 * "(sub)stochastic" error message as asking for the discrete-time form
46 * R = A0 + R A1 + R^2 A2. The triples this caller builds sum to a GENERATOR,
47 * and the shift that reading implies (L = Lm - I) gives a wrong K. The test
48 * asserts the residual above on the port's own R.
49 *
50 * The boundary system then couples the K+1 point masses to the 2K density
51 * initial vectors through flux conservation at every threshold, the boundary
52 * conditions that reflective and absorbing states impose, and continuity of the
53 * second-order density across a threshold. One normalization row replaces the
54 * first flux equation.
55 *
56 * The boundary flags boundaryL/boundaryU are ONE FLAG PER BACKGROUND STATE, a
57 * length-N vector shared by every regime, defaulting to reflective. A K x N
58 * default makes the logical mask larger than the array it indexes and crashes
59 * every model with K >= 2.
60 *
61 * ARITHMETIC. Templated on T and gated on num_traits<T>::has_transcendental:
62 * the QBD iteration is tolerance-terminated and expm is a Pade approximation.
63 * As in mfq_ld_distr, the ONLY eigenvalue computation is the branch test inside
64 * the normalization's integral of a matrix exponential, which selects between
65 * two algebraically equivalent formulas and never supplies a value that reaches
66 * the result, so Real instantiation is honest here. Contrast mfq_multiregime,
67 * where the Schur factors ARE the basis of the answer and double is the ceiling.
68 */
69
70#include <cmath>
71#include <cstddef>
72#include <vector>
73
77#include "line/api/mam/qbd_r.h"
78#include "line/num/number.h"
79#include "line/util/error.h"
80#include "line/util/expm.h"
81#include "line/util/linalg.h"
82#include "line/util/lu.h"
83#include "line/util/matrix.h"
84
85namespace line {
86namespace mam {
87
88/** Boundary behaviour of one background state at a reflecting level. */
89enum class FluidBoundary { Reflective = 0, Absorbing = 1 };
90
91namespace ld_solve_detail {
92
93/** Rows ri, columns ci of A. */
94template <class T>
95Matrix<T> pick(const Matrix<T>& A, const std::vector<std::size_t>& ri,
96 const std::vector<std::size_t>& ci) {
97 Matrix<T> B(ri.size(), ci.size(), num_traits<T>::from_int(0));
98 for (std::size_t i = 0; i < ri.size(); ++i)
99 for (std::size_t j = 0; j < ci.size(); ++j) B(i, j) = A(ri[i], ci[j]);
100 return B;
101}
102
103/** Whether v contains x. */
104inline bool has(const std::vector<std::size_t>& v, std::size_t x) {
105 for (std::size_t y : v)
106 if (y == x) return true;
107 return false;
108}
109
110/**
111 * The R of the reference's QBD_CR(Bm, Lm, Fm), which satisfies
112 * Fm + R Lm + R^2 Bm = 0 and is therefore the port's qbd_R under the direct
113 * mapping B = Bm, L = Lm, F = Fm. See the file header for how that was
114 * established and for the reading it displaced.
115 */
116template <class T>
117Matrix<T> qbd_cr_R(const Matrix<T>& Bm, const Matrix<T>& Lm, const Matrix<T>& Fm, const T& tol) {
118 return qbd_R(Bm, Lm, Fm, 200000u, tol);
119}
120
121} // namespace ld_solve_detail
122
123/**
124 * Solve a first- or second-order level-dependent fluid queue.
125 *
126 * @param Q per-regime generators, K of them
127 * @param R per-regime DIAGONAL drift matrices, K of them
128 * @param S per-regime DIAGONAL variance matrices; all zero = first order
129 * @param Thr the K thresholds
130 * @param boundaryL per background state, the behaviour at the lower boundary;
131 * empty means every state reflective
132 * @param boundaryU likewise at the upper boundary; empty means the same as
133 * boundaryL, as in the reference
134 * @param Qt boundary generators, K+1 of them; empty means
135 * {Q[0], ..., Q[K-1], Q[K-1]}, a single entry is replicated
136 * @param prec tolerance for the state classification and the QBD solves
137 */
138template <class T>
140 const std::vector<Matrix<T>>& R,
141 const std::vector<Matrix<T>>& S,
142 const std::vector<T>& Thr,
143 const std::vector<FluidBoundary>& boundaryL,
144 const std::vector<FluidBoundary>& boundaryU,
145 const std::vector<Matrix<T>>& Qt, const T& prec) {
147 "mfq_ld_solve runs a tolerance-terminated QBD iteration and evaluates expm");
148 using namespace ld_solve_detail;
149 using std::sqrt;
150 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
151 const T two = num_traits<T>::from_int(2);
152 const std::size_t K = Thr.size();
153 if (K == 0) throw InputError("mfq_ld_solve: at least one regime is required");
154 if (Q.size() != K || R.size() != K || S.size() != K)
155 throw InputError("mfq_ld_solve: Q, R and S must have one entry per regime");
156 const std::size_t N = Q[0].rows();
157 if (N == 0) throw InputError("mfq_ld_solve: the background chain is empty");
158
159 std::vector<FluidBoundary> bL = boundaryL, bU = boundaryU;
160 if (bL.empty()) bL.assign(N, FluidBoundary::Reflective);
161 if (bU.empty()) bU = bL;
162 if (bL.size() != N || bU.size() != N)
163 throw InputError(
164 "mfq_ld_solve: the boundary flags are one per BACKGROUND STATE, a vector of length N");
165 std::vector<Matrix<T>> Qtk = Qt;
166 if (Qtk.empty()) {
167 for (std::size_t k = 0; k < K; ++k) Qtk.push_back(Q[k]);
168 Qtk.push_back(Q[K - 1]);
169 } else if (Qtk.size() == 1) {
170 while (Qtk.size() < K + 1) Qtk.push_back(Qtk[0]);
171 }
172 if (Qtk.size() != K + 1) throw InputError("mfq_ld_solve: expected K+1 boundary generators");
173
174 std::vector<T> Tv(K + 1, zero);
175 for (std::size_t k = 0; k < K; ++k) Tv[k + 1] = Thr[k];
176
177 // ---- per-regime forward and backward exponents ----
178 std::vector<Matrix<T>> Sh(K); // S/2, the form every later formula uses
179 std::vector<Matrix<T>> KF(K), KB(K), cloF(K), cloB(K);
180 std::vector<std::size_t> Np(K, 0), Nn(K, 0), Ns(K, 0), NbF(K, 0), NbB(K, 0);
181 std::vector<std::vector<std::size_t>> vixp(K), vixn(K), vix0(K), vixs(K);
182 for (std::size_t k = 0; k < K; ++k) {
183 if (Q[k].rows() != N || R[k].rows() != N || S[k].rows() != N)
184 throw InputError("mfq_ld_solve: a regime has the wrong order");
185 Sh[k] = Matrix<T>(N, N, zero);
186 for (std::size_t i = 0; i < N; ++i)
187 for (std::size_t j = 0; j < N; ++j) Sh[k](i, j) = S[k](i, j) / two;
188
189 std::vector<std::size_t> ix0, ixn0;
190 for (std::size_t i = 0; i < N; ++i) {
191 if (num_abs(R[k](i, i)) <= prec && Sh[k](i, i) <= prec) ix0.push_back(i);
192 else ixn0.push_back(i);
193 }
194 const std::size_t Nzero = ix0.size(), Nnz = ixn0.size();
195 if (Nnz == 0)
196 throw InputError("mfq_ld_solve: a regime has neither drift nor variance in any state");
197
198 Matrix<T> Qv = pick(Q[k], ixn0, ixn0);
199 Matrix<T> Wz(Nnz, Nzero, zero); // Qn0 inv(-Q00), reused for the closing matrices
200 if (Nzero > 0) {
201 Matrix<T> negQ00 = pick(Q[k], ix0, ix0);
202 for (std::size_t i = 0; i < Nzero; ++i)
203 for (std::size_t j = 0; j < Nzero; ++j) negQ00(i, j) = -negQ00(i, j);
204 Wz = matmul(pick(Q[k], ixn0, ix0), inverse(negQ00));
205 Qv = mfq_detail::add(Qv, matmul(Wz, pick(Q[k], ix0, ixn0)));
206 }
207 const Matrix<T> Rv = pick(R[k], ixn0, ixn0);
208 const Matrix<T> Sv = pick(Sh[k], ixn0, ixn0);
209
210 std::vector<std::size_t> ixp, ixn, ixs;
211 for (std::size_t i = 0; i < Nnz; ++i) {
212 if (Sv(i, i) > prec) ixs.push_back(i);
213 else if (Rv(i, i) > prec) ixp.push_back(i);
214 else if (Rv(i, i) < -prec) ixn.push_back(i);
215 }
216 Np[k] = ixp.size();
217 Nn[k] = ixn.size();
218 Ns[k] = ixs.size();
219
220 // The discriminant of the second-order balance equation, shared by both
221 // directions: Rv^2 - 2 (2 Sv) Qv on the variance states.
222 std::vector<T> discr(ixs.size(), zero);
223 for (std::size_t i = 0; i < ixs.size(); ++i) {
224 const std::size_t q = ixs[i];
225 discr[i] = Rv(q, q) * Rv(q, q) -
226 two * (two * Sv(q, q)) * Qv(q, q);
227 }
228
229 // ---- FORWARD ----
230 {
231 T c = one;
232 for (std::size_t q : ixp) {
233 const T v = -Qv(q, q) / Rv(q, q);
234 if (v > c) c = v;
235 }
236 for (std::size_t i = 0; i < ixs.size(); ++i)
237 if (discr[i] > zero) {
238 const std::size_t q = ixs[i];
239 const T v = (-Rv(q, q) + sqrt(discr[i])) / (two * Sv(q, q));
240 if (v > c) c = v;
241 }
242 std::vector<std::size_t> ixbF = ixs;
243 ixbF.insert(ixbF.end(), ixp.begin(), ixp.end());
244 NbF[k] = ixbF.size();
245 const std::size_t nb = NbF[k], nn = Nn[k];
246 Matrix<T> Bm(nb + nn, nb + nn, zero), Lm(nb + nn, nb + nn, zero),
247 Fm(nb + nn, nb + nn, zero);
248 const Matrix<T> Sbb = pick(Sv, ixbF, ixbF), Rbb = pick(Rv, ixbF, ixbF);
249 const Matrix<T> Qbb = pick(Qv, ixbF, ixbF), Qbn = pick(Qv, ixbF, ixn);
250 const Matrix<T> Qnb = pick(Qv, ixn, ixbF), Qnn = pick(Qv, ixn, ixn);
251 const Matrix<T> Rnn = pick(Rv, ixn, ixn);
252 for (std::size_t i = 0; i < nb; ++i)
253 for (std::size_t j = 0; j < nb; ++j) {
254 Bm(i, j) = c * Sbb(i, j);
255 Lm(i, j) = -Rbb(i, j) - two * c * Sbb(i, j);
256 Fm(i, j) = Qbb(i, j) / c + c * Sbb(i, j) + Rbb(i, j);
257 }
258 for (std::size_t i = 0; i < nb; ++i)
259 for (std::size_t j = 0; j < nn; ++j) Fm(i, nb + j) = Qbn(i, j) / c;
260 for (std::size_t i = 0; i < nn; ++i) {
261 for (std::size_t j = 0; j < nb; ++j) Lm(nb + i, j) = Qnb(i, j) / c;
262 for (std::size_t j = 0; j < nn; ++j) {
263 Bm(nb + i, nb + j) = -Rnn(i, j);
264 Lm(nb + i, nb + j) = Qnn(i, j) / c + Rnn(i, j);
265 }
266 }
267 const Matrix<T> QR = qbd_cr_R(Bm, Lm, Fm, prec);
268 KF[k] = Matrix<T>(nb, nb, zero);
269 for (std::size_t i = 0; i < nb; ++i)
270 for (std::size_t j = 0; j < nb; ++j)
271 KF[k](i, j) = (QR(i, j) - (i == j ? one : zero)) * c;
272 // clovF has a column per non-zero-drift state: identity on ixbF,
273 // Psi on ixn.
274 Matrix<T> clov(nb, Nnz, zero);
275 for (std::size_t i = 0; i < nb; ++i) clov(i, ixbF[i]) = one;
276 for (std::size_t i = 0; i < nb; ++i)
277 for (std::size_t j = 0; j < nn; ++j) clov(i, ixn[j]) = QR(i, nb + j);
278 cloF[k] = Matrix<T>(nb, N, zero);
279 for (std::size_t i = 0; i < nb; ++i)
280 for (std::size_t j = 0; j < Nnz; ++j) cloF[k](i, ixn0[j]) = clov(i, j);
281 if (Nzero > 0) {
282 const Matrix<T> z = matmul(clov, Wz);
283 for (std::size_t i = 0; i < nb; ++i)
284 for (std::size_t j = 0; j < Nzero; ++j) cloF[k](i, ix0[j]) = z(i, j);
285 }
286 }
287
288 // ---- BACKWARD ----
289 {
290 T c = one;
291 for (std::size_t q : ixn) {
292 const T v = -Qv(q, q) / (-Rv(q, q));
293 if (v > c) c = v;
294 }
295 for (std::size_t i = 0; i < ixs.size(); ++i)
296 if (discr[i] > zero) {
297 const std::size_t q = ixs[i];
298 const T v = (Rv(q, q) + sqrt(discr[i])) / (two * Sv(q, q));
299 if (v > c) c = v;
300 }
301 std::vector<std::size_t> ixbB = ixs;
302 ixbB.insert(ixbB.end(), ixn.begin(), ixn.end());
303 NbB[k] = ixbB.size();
304 const std::size_t nb = NbB[k], np = Np[k];
305 Matrix<T> Bm(nb + np, nb + np, zero), Lm(nb + np, nb + np, zero),
306 Fm(nb + np, nb + np, zero);
307 const Matrix<T> Sbb = pick(Sv, ixbB, ixbB), Rbb = pick(Rv, ixbB, ixbB);
308 const Matrix<T> Qbb = pick(Qv, ixbB, ixbB), Qbp = pick(Qv, ixbB, ixp);
309 const Matrix<T> Qpb = pick(Qv, ixp, ixbB), Qpp = pick(Qv, ixp, ixp);
310 const Matrix<T> Rpp = pick(Rv, ixp, ixp);
311 for (std::size_t i = 0; i < nb; ++i)
312 for (std::size_t j = 0; j < nb; ++j) {
313 Bm(i, j) = c * Sbb(i, j);
314 Lm(i, j) = Rbb(i, j) - two * c * Sbb(i, j);
315 Fm(i, j) = Qbb(i, j) / c + c * Sbb(i, j) - Rbb(i, j);
316 }
317 for (std::size_t i = 0; i < nb; ++i)
318 for (std::size_t j = 0; j < np; ++j) Fm(i, nb + j) = Qbp(i, j) / c;
319 for (std::size_t i = 0; i < np; ++i) {
320 for (std::size_t j = 0; j < nb; ++j) Lm(nb + i, j) = Qpb(i, j) / c;
321 for (std::size_t j = 0; j < np; ++j) {
322 Bm(nb + i, nb + j) = Rpp(i, j);
323 Lm(nb + i, nb + j) = Qpp(i, j) / c - Rpp(i, j);
324 }
325 }
326 const Matrix<T> QR = qbd_cr_R(Bm, Lm, Fm, prec);
327 KB[k] = Matrix<T>(nb, nb, zero);
328 for (std::size_t i = 0; i < nb; ++i)
329 for (std::size_t j = 0; j < nb; ++j)
330 KB[k](i, j) = (QR(i, j) - (i == j ? one : zero)) * c;
331 Matrix<T> clov(nb, Nnz, zero);
332 for (std::size_t i = 0; i < nb; ++i) clov(i, ixbB[i]) = one;
333 for (std::size_t i = 0; i < nb; ++i)
334 for (std::size_t j = 0; j < np; ++j) clov(i, ixp[j]) = QR(i, nb + j);
335 cloB[k] = Matrix<T>(nb, N, zero);
336 for (std::size_t i = 0; i < nb; ++i)
337 for (std::size_t j = 0; j < Nnz; ++j) cloB[k](i, ixn0[j]) = clov(i, j);
338 if (Nzero > 0) {
339 const Matrix<T> z = matmul(clov, Wz);
340 for (std::size_t i = 0; i < nb; ++i)
341 for (std::size_t j = 0; j < Nzero; ++j) cloB[k](i, ix0[j]) = z(i, j);
342 }
343 }
344
345 // Index sets lifted back to the original state numbering.
346 for (std::size_t q : ixp) vixp[k].push_back(ixn0[q]);
347 for (std::size_t q : ixn) vixn[k].push_back(ixn0[q]);
348 for (std::size_t q : ixs) vixs[k].push_back(ixn0[q]);
349 vix0[k] = ix0;
350 }
351
352 // ---- boundary system ----
353 // Unknown layout: masses[0], then per regime iniF, iniB, masses[k+1].
354 std::vector<std::size_t> pp;
355 pp.push_back(0);
356 pp.push_back(N);
357 for (std::size_t k = 0; k < K; ++k) {
358 pp.push_back(pp.back() + NbF[k]);
359 pp.push_back(pp.back() + NbB[k]);
360 pp.push_back(pp.back() + N);
361 }
362 // pp[0] = masses[0], pp[1] = iniF[0], pp[2] = iniB[0], pp[3] = masses[1], ...
363 const std::size_t Neq = pp.back();
364 Matrix<T> M(Neq, Neq, zero);
365
366 auto rowF = [&](std::size_t k) { return pp[1 + 3 * k]; };
367 auto rowB = [&](std::size_t k) { return pp[2 + 3 * k]; };
368 auto rowM = [&](std::size_t k) { return k == 0 ? std::size_t(0) : pp[3 * k]; };
369
370 // Flux conservation at every threshold.
371 for (std::size_t k = 0; k <= K; ++k) {
372 const std::size_t col = k * N;
373 for (std::size_t i = 0; i < N; ++i)
374 for (std::size_t j = 0; j < N; ++j) M(rowM(k) + i, col + j) = -Qtk[k](i, j);
375 if (k > 0) {
376 const std::size_t g = k - 1;
377 const Matrix<T> E = expm(KF[g], T(Tv[k] - Tv[k - 1]));
378 // expm(KF Tk) (-cloF R + KF cloF S)
379 const Matrix<T> aa = mfq_detail::sub(
380 matmul(matmul(E, matmul(KF[g], cloF[g])), Sh[g]),
381 matmul(matmul(E, cloF[g]), R[g]));
382 for (std::size_t i = 0; i < NbF[g]; ++i)
383 for (std::size_t j = 0; j < N; ++j) M(rowF(g) + i, col + j) = aa(i, j);
384 const Matrix<T> bb = mfq_detail::sub(
385 mfq_detail::scale(matmul(cloB[g], R[g]), T(-one)),
386 matmul(matmul(KB[g], cloB[g]), Sh[g]));
387 for (std::size_t i = 0; i < NbB[g]; ++i)
388 for (std::size_t j = 0; j < N; ++j) M(rowB(g) + i, col + j) = bb(i, j);
389 }
390 if (k < K) {
391 const Matrix<T> cc = mfq_detail::sub(matmul(cloF[k], R[k]),
392 matmul(matmul(KF[k], cloF[k]), Sh[k]));
393 for (std::size_t i = 0; i < NbF[k]; ++i)
394 for (std::size_t j = 0; j < N; ++j) M(rowF(k) + i, col + j) = cc(i, j);
395 const Matrix<T> E = expm(KB[k], T(Tv[k + 1] - Tv[k]));
396 const Matrix<T> dd = matmul(
397 E, mfq_detail::add(matmul(cloB[k], R[k]), matmul(matmul(KB[k], cloB[k]), Sh[k])));
398 for (std::size_t i = 0; i < NbB[k]; ++i)
399 for (std::size_t j = 0; j < N; ++j) M(rowB(k) + i, col + j) = dd(i, j);
400 }
401 }
402
403 // Boundary and continuity conditions.
404 std::size_t col = (K + 1) * N;
405 for (std::size_t k = 0; k <= K; ++k) {
406 if (k == 0) {
407 // No mass in an up-drift state, nor in a reflective variance state.
408 std::vector<std::size_t> ixr0;
409 for (std::size_t q : vixs[0])
410 if (bL[q] == FluidBoundary::Reflective) ixr0.push_back(q);
411 std::vector<std::size_t> sel = vixp[0];
412 sel.insert(sel.end(), ixr0.begin(), ixr0.end());
413 for (std::size_t i = 0; i < sel.size(); ++i) M(rowM(0) + sel[i], col + i) = one;
414 col += sel.size();
415 // Zero density in an absorbing variance state.
416 std::vector<std::size_t> ixa0;
417 for (std::size_t q : vixs[0])
418 if (bL[q] == FluidBoundary::Absorbing) ixa0.push_back(q);
419 const Matrix<T> pdfB = matmul(expm(KB[0], T(Tv[1] - Tv[0])), cloB[0]);
420 for (std::size_t i = 0; i < ixa0.size(); ++i) {
421 for (std::size_t r = 0; r < NbF[0]; ++r)
422 M(rowF(0) + r, col + i) = cloF[0](r, ixa0[i]);
423 for (std::size_t r = 0; r < NbB[0]; ++r)
424 M(rowB(0) + r, col + i) = pdfB(r, ixa0[i]);
425 }
426 col += ixa0.size();
427 } else if (k == K) {
428 std::vector<std::size_t> ixrB;
429 for (std::size_t q : vixs[K - 1])
430 if (bU[q] == FluidBoundary::Reflective) ixrB.push_back(q);
431 std::vector<std::size_t> sel = vixn[K - 1];
432 sel.insert(sel.end(), ixrB.begin(), ixrB.end());
433 for (std::size_t i = 0; i < sel.size(); ++i) M(rowM(K) + sel[i], col + i) = one;
434 col += sel.size();
435 std::vector<std::size_t> ixaB;
436 for (std::size_t q : vixs[K - 1])
437 if (bU[q] == FluidBoundary::Absorbing) ixaB.push_back(q);
438 const Matrix<T> pdfF =
439 matmul(expm(KF[K - 1], T(Tv[K] - Tv[K - 1])), cloF[K - 1]);
440 for (std::size_t i = 0; i < ixaB.size(); ++i) {
441 for (std::size_t r = 0; r < NbF[K - 1]; ++r)
442 M(rowF(K - 1) + r, col + i) = pdfF(r, ixaB[i]);
443 for (std::size_t r = 0; r < NbB[K - 1]; ++r)
444 M(rowB(K - 1) + r, col + i) = cloB[K - 1](r, ixaB[i]);
445 }
446 col += ixaB.size();
447 } else {
448 const std::size_t g = k - 1; // regime below, 0-based
449 // No mass except where the drift reverses across the threshold.
450 std::vector<std::size_t> st0;
451 for (std::size_t q = 0; q < N; ++q) {
452 const bool keep = (has(vixp[g], q) && has(vixn[k], q)) || has(vix0[g], q) ||
453 has(vix0[k], q);
454 if (!keep) st0.push_back(q);
455 }
456 for (std::size_t i = 0; i < st0.size(); ++i) M(rowM(k) + st0[i], col + i) = one;
457 col += st0.size();
458 // Continuity of the second-order density across the threshold.
459 std::vector<std::size_t> sts;
460 for (std::size_t q = 0; q < N; ++q) {
461 const bool isS = has(vixs[g], q) || has(vixs[k], q);
462 const bool excl = has(vixn[k], q) || has(vixp[g], q);
463 if (isS && !excl) sts.push_back(q);
464 }
465 Matrix<T> sqSg(N, N, zero), sqSk(N, N, zero);
466 for (std::size_t q = 0; q < N; ++q) {
467 sqSg(q, q) = sqrt(Sh[g](q, q));
468 sqSk(q, q) = sqrt(Sh[k](q, q));
469 }
470 const Matrix<T> BelowF = matmul(
471 matmul(expm(KF[g], T(Tv[k] - Tv[k - 1])),
472 mfq_detail::scale(cloF[g], T(-one))),
473 sqSg);
474 const Matrix<T> BelowB =
475 matmul(mfq_detail::scale(cloB[g], T(-one)), sqSg);
476 const Matrix<T> AboveF = matmul(cloF[k], sqSk);
477 const Matrix<T> AboveB =
478 matmul(matmul(expm(KB[k], T(Tv[k + 1] - Tv[k])), cloB[k]), sqSk);
479 for (std::size_t i = 0; i < sts.size(); ++i) {
480 for (std::size_t r = 0; r < NbF[g]; ++r)
481 M(rowF(g) + r, col + i) = BelowF(r, sts[i]);
482 for (std::size_t r = 0; r < NbB[g]; ++r)
483 M(rowB(g) + r, col + i) = BelowB(r, sts[i]);
484 for (std::size_t r = 0; r < NbF[k]; ++r)
485 M(rowF(k) + r, col + i) = AboveF(r, sts[i]);
486 for (std::size_t r = 0; r < NbB[k]; ++r)
487 M(rowB(k) + r, col + i) = AboveB(r, sts[i]);
488 }
489 col += sts.size();
490 }
491 }
492 if (col != Neq)
493 throw NumericError(ErrorKind{"Singular"},
494 "mfq_ld_solve: the boundary conditions do not close the system; check the drift and "
495 "variance pattern across the thresholds");
496
497 // Normalization replaces the first flux equation: total mass plus the
498 // integral of every regime's density is one.
499 {
500 std::vector<T> h(Neq, zero);
501 for (std::size_t i = 0; i < N; ++i) h[i] = one;
502 for (std::size_t k = 0; k < K; ++k) {
503 Matrix<T> sF, sB;
504 mfq_ld_detail::integ_exp_pair(KF[k], KB[k], T(Tv[k + 1] - Tv[k]), sF, sB);
505 const Matrix<T> gF = matmul(sF, cloF[k]);
506 const Matrix<T> gB = matmul(sB, cloB[k]);
507 for (std::size_t i = 0; i < NbF[k]; ++i) {
508 T s = zero;
509 for (std::size_t j = 0; j < N; ++j) s += gF(i, j);
510 h[rowF(k) + i] = s;
511 }
512 for (std::size_t i = 0; i < NbB[k]; ++i) {
513 T s = zero;
514 for (std::size_t j = 0; j < N; ++j) s += gB(i, j);
515 h[rowB(k) + i] = s;
516 }
517 for (std::size_t i = 0; i < N; ++i) h[rowM(k + 1) + i] = one;
518 }
519 for (std::size_t i = 0; i < Neq; ++i) M(i, 0) = h[i];
520 }
521
522 // b M = rhs, i.e. M^T b^T = rhs^T.
523 Matrix<T> Mt(Neq, Neq, zero);
524 for (std::size_t i = 0; i < Neq; ++i)
525 for (std::size_t j = 0; j < Neq; ++j) Mt(i, j) = M(j, i);
526 std::vector<T> rhs(Neq, zero);
527 rhs[0] = one;
528 const std::vector<T> b = solve(Mt, rhs);
529
531 out.Thr = Thr;
532 out.masses.assign(K + 1, std::vector<T>(N, zero));
533 for (std::size_t k = 0; k <= K; ++k)
534 for (std::size_t j = 0; j < N; ++j) out.masses[k][j] = b[rowM(k) + j];
535 for (std::size_t k = 0; k < K; ++k) {
536 out.iniF.push_back(std::vector<T>(b.begin() + static_cast<long>(rowF(k)),
537 b.begin() + static_cast<long>(rowF(k) + NbF[k])));
538 out.iniB.push_back(std::vector<T>(b.begin() + static_cast<long>(rowB(k)),
539 b.begin() + static_cast<long>(rowB(k) + NbB[k])));
540 out.KF.push_back(KF[k]);
541 out.KB.push_back(KB[k]);
542 out.cloF.push_back(cloF[k]);
543 out.cloB.push_back(cloB[k]);
544 }
545 return out;
546}
547
548/** mfq_ld_solve with reflective boundaries, Qt = Q and the default prec = 1e-14. */
549template <class T>
551 const std::vector<Matrix<T>>& R,
552 const std::vector<Matrix<T>>& S,
553 const std::vector<T>& Thr) {
554 return mfq_ld_solve(Q, R, S, Thr, std::vector<FluidBoundary>(), std::vector<FluidBoundary>(),
555 std::vector<Matrix<T>>(), T(num_traits<T>::from_double(1e-14)));
556}
557
558} // namespace mam
559} // namespace line
560
561#endif // LINE_API_MAM_MFQ_LD_SOLVE_H
InputError(const std::string &what)
Definition error.h:105
NumericError(const std::string &what)
Definition error.h:112
The exception types the port throws.
Matrix exponential by scaling and squaring with a diagonal Pade approximant.
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.
Stationary density and distribution of a first- or second-order level-dependent (multi-regime) Markov...
Stationary mean fluid level E[X] of a first- or second-order level-dependent (multi-regime) Markovian...
Core of the Markovian fluid queue: the fundamental matrices Psi, K, U and the matrix-exponential stat...
FluidBoundary
Boundary behaviour of one background state at a reflecting level.
Matrix< T > qbd_R(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
R by successive substitutions (qbd_R.m): iterate R <- -(F + R^2 B) L^-1.
Definition qbd_r.h:184
LevelDependentFluidBlocks< T > mfq_ld_solve(const std::vector< Matrix< T > > &Q, const std::vector< Matrix< T > > &R, const std::vector< Matrix< T > > &S, const std::vector< T > &Thr, const std::vector< FluidBoundary > &boundaryL, const std::vector< FluidBoundary > &boundaryU, const std::vector< Matrix< T > > &Qt, const T &prec)
Solve a first- or second-order level-dependent fluid queue.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_abs(const T &v)
Definition number.h:184
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 > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Matrix< T > expm(const Matrix< T > &A)
Matrix exponential exp(A).
Definition expm.h:141
Number-type abstraction for the templated API port.
Quasi-birth-death processes: the rate matrix R, the fundamental matrix G, the caudal characteristic,...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
The matrix-exponential building blocks of a multi-regime fluid queue, the output of mfq_ld_solve.
Definition mfq_ld_mean.h:68
std::vector< std::vector< T > > masses
K+1 point-mass vectors of length N.
Definition mfq_ld_mean.h:69
std::vector< T > Thr
K regime thresholds T(1)..T(K).
Definition mfq_ld_mean.h:76
std::vector< Matrix< T > > KF
K forward matrix exponents.
Definition mfq_ld_mean.h:71
std::vector< std::vector< T > > iniB
K backward initial vectors.
Definition mfq_ld_mean.h:73
std::vector< Matrix< T > > KB
K backward matrix exponents.
Definition mfq_ld_mean.h:74
std::vector< std::vector< T > > iniF
K forward initial vectors.
Definition mfq_ld_mean.h:70
std::vector< Matrix< T > > cloF
K forward closing matrices.
Definition mfq_ld_mean.h:72
std::vector< Matrix< T > > cloB
K backward closing matrices.
Definition mfq_ld_mean.h:75