LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mfq_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_SOLVE_H
6#define LINE_API_MAM_MFQ_SOLVE_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Core of the Markovian fluid queue: the fundamental matrices Psi, K, U and the
12 * matrix-exponential stationary solution of a general fluid model.
13 *
14 * These are the two BUTools routines that every mfq_* entry point of
15 * matlab/src/api/mam/ ultimately calls (FluidFundamentalMatrices and
16 * GeneralFluidSolve). They carry no LINE-level name of their own, so they live
17 * here as shared infrastructure rather than as a ported API function, and
18 * mfq_sojourn.h and mfq_fluflu_sojourn.h are the thin entry points over them.
19 *
20 * THE MODEL. A background chain with generator Q modulates a fluid level whose
21 * drift in state i is R(i,i), of any sign, zero included. Writing the states in
22 * the order (zero drift, positive drift, negative drift), censoring out the
23 * zero-drift states and rescaling time by |1/R| turns the model into a fluid
24 * queue with drifts +-1, whose first passage matrix Psi solves the Riccati
25 * equation
26 *
27 * Fpm + Fpp Psi + Psi Fmm + Psi Fmp Psi = 0.
28 *
29 * Psi(i,j) is the probability that, starting in an up state i at a level, the
30 * process returns to that level in the down state j. From it, K = Fpp + Psi Fmp
31 * generates the stationary density and U = Fmm + Fmp Psi is the generator of
32 * the chain seen at level zero. The stationary law is then a point mass at
33 * level zero plus the density pi(x) = ini exp(K x) clo.
34 *
35 * THE RICCATI SOLVER. ADDA (Wang, Wang and Li 2011), the BUTools default: an
36 * alternating-directional doubling iteration whose per-step cost is four
37 * inverses and whose error SQUARES each step, so the 1e-14 default is reached
38 * in a few tens of iterations rather than the hundreds a linearly convergent
39 * fixed point would need. SDA is the same iteration with a common shift and is
40 * offered because it is the form quoted in most of the literature; the two
41 * differ only in the shift and in the scaling of E and F. The reference's third
42 * option, cyclic reduction, is NOT ported: it is an alternative with the same
43 * convergence order and no accuracy advantage on this problem, and it would
44 * duplicate the whole solver for nothing. Callers asking for it get an
45 * UnsupportedError naming it, rather than a silent substitution.
46 *
47 * WHERE THE PORT IS STRICTER THAN THE REFERENCE. GeneralFluidSolve censors the
48 * zero-drift states through pinv(-Qv00), a PSEUDO-inverse. That matrix is
49 * singular exactly when the zero-drift states form a closed set, i.e. when the
50 * fluid can be trapped at a constant level forever and the model has no
51 * stationary fluid law to report. The reference then returns whatever the
52 * pseudo-inverse yields, without comment. The port uses the ordinary inverse
53 * and raises NumericError naming the condition, because a pseudo-inverse of a
54 * singular censoring operator is not the answer to a different question, it is
55 * an answer to no question. On every non-degenerate model the two agree
56 * exactly, the pseudo-inverse of a non-singular matrix being its inverse.
57 *
58 * The two overdetermined normalizations that the reference solves with MATLAB's
59 * backslash (an (n+1) x n system that is consistent by construction) are solved
60 * here through the normal equations, which return the same vector for a
61 * consistent full-column-rank system and need no least-squares factorization.
62 *
63 * ARITHMETIC. Gated on num_traits<T>::has_transcendental: ADDA terminates on a
64 * tolerance and its scaling step takes a square root. Everything else is finite
65 * exact linear algebra.
66 */
67
68#include <cmath>
69#include <cstddef>
70#include <vector>
71
72#include "line/num/number.h"
73#include "line/util/error.h"
74#include "line/util/linalg.h"
75#include "line/util/lu.h"
76#include "line/util/matrix.h"
77
78namespace line {
79namespace mam {
80
81/** Which doubling iteration to run for Psi. */
82enum class RiccatiMethod { ADDA, SDA };
83
84/** Psi, K and U of a fluid queue with drifts normalized to +-1. */
85template <class T>
87 Matrix<T> Psi; ///< first return matrix, up states to down states
88 Matrix<T> K; ///< Fpp + Psi Fmp, the density generator
89 Matrix<T> U; ///< Fmm + Fmp Psi, the level-zero generator
90 unsigned iterations;
92};
93
94namespace mfq_detail {
95
96/** Sub-block A(r0..r0+nr-1, c0..c0+nc-1). */
97template <class T>
98Matrix<T> block(const Matrix<T>& A, std::size_t r0, std::size_t c0, std::size_t nr,
99 std::size_t nc) {
101 for (std::size_t i = 0; i < nr; ++i)
102 for (std::size_t j = 0; j < nc; ++j) B(i, j) = A(r0 + i, c0 + j);
103 return B;
104}
105
106/** Elementwise A + B. */
107template <class T>
108Matrix<T> add(const Matrix<T>& A, const Matrix<T>& B) {
109 Matrix<T> C = A;
110 for (std::size_t i = 0; i < A.rows(); ++i)
111 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) += B(i, j);
112 return C;
113}
114
115/** Elementwise A - B. */
116template <class T>
117Matrix<T> sub(const Matrix<T>& A, const Matrix<T>& B) {
118 Matrix<T> C = A;
119 for (std::size_t i = 0; i < A.rows(); ++i)
120 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) -= B(i, j);
121 return C;
122}
123
124/** A scaled by s. */
125template <class T>
126Matrix<T> scale(const Matrix<T>& A, const T& s) {
127 Matrix<T> C = A;
128 for (std::size_t i = 0; i < A.rows(); ++i)
129 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) *= s;
130 return C;
131}
132
133/** MATLAB norm(A, 1): the largest absolute column sum. */
134template <class T>
135T norm1(const Matrix<T>& A) {
136 T best = num_traits<T>::from_int(0);
137 for (std::size_t j = 0; j < A.cols(); ++j) {
138 T s = num_traits<T>::from_int(0);
139 for (std::size_t i = 0; i < A.rows(); ++i) s += num_abs(A(i, j));
140 if (s > best) best = s;
141 }
142 return best;
143}
144
145/**
146 * Solution of the CONSISTENT overdetermined system M x = b through the normal
147 * equations M^T M x = M^T b. Used for the two normalizations that the reference
148 * writes as a MATLAB backslash on an (n+1) x n system.
149 *
150 * Deliberately NOT line::lstsq from util/lstsq.h, and deliberately not named
151 * lstsq either. Two reasons, in order of importance. The systems here are
152 * consistent and of full column rank BY CONSTRUCTION -- they are a square
153 * balance system plus one normalization row -- so the normal equations are
154 * exact for them and the rank-revealing SVD path buys nothing; and squaring the
155 * condition number, the usual objection to normal equations, is not a concern
156 * on a system whose extra row is a normalization. Switching solvers would also
157 * perturb values that currently agree with MATLAB digit for digit, for no
158 * demonstrated gain. The distinct name additionally keeps argument-dependent
159 * lookup from finding both this and line::lstsq on a Matrix<T> argument, which
160 * is an ambiguity rather than an overload.
161 */
162template <class T>
163std::vector<T> normal_equations_solve(const Matrix<T>& M, const std::vector<T>& b) {
164 const std::size_t m = M.rows(), n = M.cols();
165 if (b.size() != m)
166 throw InputError("mfq normal_equations_solve: right-hand side length mismatch");
167 Matrix<T> N(n, n, num_traits<T>::from_int(0));
168 std::vector<T> r(n, num_traits<T>::from_int(0));
169 for (std::size_t i = 0; i < n; ++i) {
170 for (std::size_t j = 0; j < n; ++j) {
171 T s = num_traits<T>::from_int(0);
172 for (std::size_t k = 0; k < m; ++k) s += M(k, i) * M(k, j);
173 N(i, j) = s;
174 }
175 T s = num_traits<T>::from_int(0);
176 for (std::size_t k = 0; k < m; ++k) s += M(k, i) * b[k];
177 r[i] = s;
178 }
179 return solve(N, r);
180}
181
182} // namespace mfq_detail
183
184/**
185 * Psi, K and U of a fluid queue whose drifts have been normalized to +-1.
186 *
187 * @param Fpp up-to-up block, Fpm up-to-down, Fmp down-to-up, Fmm down-to-down
188 * @param precision stopping tolerance on the doubling residual
189 * @param maxNumIt iteration cap
190 * @param method ADDA (the BUTools default) or SDA
191 * @param Fpm generator block from the up-phases to the down-phases
192 * @param Fmp generator block from the down-phases to the up-phases
193 * @param Fmm generator block within the down-phases
194 */
195template <class T>
197 const Matrix<T>& Fmp, const Matrix<T>& Fmm,
198 const T& precision, unsigned maxNumIt,
199 RiccatiMethod method) {
201 "mfq_fundamental runs a tolerance-terminated doubling iteration");
202 using namespace mfq_detail;
203 using std::sqrt;
204 const T zero = num_traits<T>::from_int(0);
205 const std::size_t sA = Fpp.rows();
206 const std::size_t sD = Fmm.rows();
207 if (Fpp.cols() != sA || Fmm.cols() != sD || Fpm.rows() != sA || Fpm.cols() != sD ||
208 Fmp.rows() != sD || Fmp.cols() != sA)
209 throw InputError("mfq_fundamental: the four blocks are not conformable");
210
212 out.iterations = 0;
213 out.converged = true;
214 if (sA == 0) {
215 out.Psi = Matrix<T>(0, sD, zero);
216 out.K = Matrix<T>(0, 0, zero);
217 out.U = Fmm;
218 return out;
219 }
220
221 // ADDA / SDA (Wang, Wang, Li 2011).
222 Matrix<T> A = scale(Fpp, T(-num_traits<T>::from_int(1)));
223 const Matrix<T>& B = Fpm;
224 const Matrix<T>& C = Fmp;
225 Matrix<T> D = scale(Fmm, T(-num_traits<T>::from_int(1)));
226 T gamma1 = A(0, 0), gamma2 = (sD > 0) ? D(0, 0) : zero;
227 for (std::size_t i = 0; i < sA; ++i)
228 if (A(i, i) > gamma1) gamma1 = A(i, i);
229 for (std::size_t i = 0; i < sD; ++i)
230 if (D(i, i) > gamma2) gamma2 = D(i, i);
231 if (method == RiccatiMethod::SDA) {
232 if (gamma2 > gamma1) gamma1 = gamma2;
233 gamma2 = gamma1;
234 }
235 const Matrix<T> IA = eye<T>(sA);
236 const Matrix<T> ID = eye<T>(sD);
237 for (std::size_t i = 0; i < sA; ++i) A(i, i) += gamma2;
238 for (std::size_t i = 0; i < sD; ++i) D(i, i) += gamma1;
239 const T g = gamma1 + gamma2;
240
241 const Matrix<T> Dginv0 = inverse(D);
242 Matrix<T> Vginv = inverse(sub(D, matmul(matmul(C, inverse(A)), B)));
243 Matrix<T> Wginv = inverse(sub(A, matmul(matmul(B, Dginv0), C)));
244 Matrix<T> Eg = sub(ID, scale(Vginv, g));
245 Matrix<T> Fg = sub(IA, scale(Wginv, g));
246 Matrix<T> Gg = scale(matmul(matmul(Dginv0, C), Wginv), g);
247 Matrix<T> Hg = scale(matmul(matmul(Wginv, B), Dginv0), g);
248
249 T diff = num_traits<T>::from_int(1);
250 unsigned numit = 0;
251 while (diff > precision && numit < maxNumIt) {
252 Vginv = matmul(Eg, inverse(sub(ID, matmul(Gg, Hg))));
253 Wginv = matmul(Fg, inverse(sub(IA, matmul(Hg, Gg))));
254 Gg = add(Gg, matmul(matmul(Vginv, Gg), Fg));
255 Hg = add(Hg, matmul(matmul(Wginv, Hg), Eg));
256 Eg = matmul(Vginv, Eg);
257 Fg = matmul(Wginv, Fg);
258 const T neg = norm1(Eg);
259 const T nfg = norm1(Fg);
260 if (method == RiccatiMethod::ADDA) {
261 const T eta = sqrt(nfg / neg);
262 Eg = scale(Eg, eta);
263 Fg = scale(Fg, T(num_traits<T>::from_int(1) / eta));
264 diff = neg * nfg;
265 } else {
266 diff = (neg < nfg) ? neg : nfg;
267 }
268 ++numit;
269 }
270 out.iterations = numit;
271 out.converged = (numit < maxNumIt);
272 out.Psi = Hg;
273 out.K = add(Fpp, matmul(out.Psi, Fmp));
274 out.U = add(Fmm, matmul(Fmp, out.Psi));
275 return out;
276}
277
278/** Stationary matrix-exponential solution of a general Markovian fluid model. */
279template <class T>
281 std::vector<T> mass0; ///< P(level 0, state j), length N
282 std::vector<T> ini; ///< initial vector of the density, length Np
283 Matrix<T> K; ///< matrix exponent of the density, Np x Np
284 Matrix<T> clo; ///< closing matrix of the density, Np x N
285};
286
287/**
288 * Stationary law of a general Markovian fluid model, pi(x) = ini exp(K x) clo
289 * above level zero plus the point mass mass0 at zero.
290 *
291 * @param Q generator of the background chain, N x N
292 * @param R diagonal drift matrix, N x N, entries of any sign
293 * @param Q0 boundary generator at level zero; pass an empty matrix for the
294 * regular boundary behaviour Q0 = Q
295 * @param prec tolerance, used both to classify a drift as zero and to stop the
296 * Riccati iteration, exactly as in the reference
297 */
298template <class T>
300 const Matrix<T>& Q0, const T& prec) {
302 "mfq_general_solve calls a tolerance-terminated Riccati solver");
303 using namespace mfq_detail;
304 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
305 const std::size_t N = Q.rows();
306 if (Q.cols() != N || R.rows() != N || R.cols() != N)
307 throw InputError("mfq_general_solve: Q and R must be square and of equal order");
308
309 // Partition the state space by the sign of the drift: zero, positive,
310 // negative, in that order.
311 std::vector<std::size_t> ixz, ixp, ixn;
312 for (std::size_t i = 0; i < N; ++i) {
313 const T d = R(i, i);
314 if (num_abs(d) <= prec)
315 ixz.push_back(i);
316 else if (d > prec)
317 ixp.push_back(i);
318 else
319 ixn.push_back(i);
320 }
321 const std::size_t Nz = ixz.size(), Np = ixp.size(), Nn = ixn.size();
322 // Nn==0 vs Np==0 rationale: see _kb/03-api-layer.md (cpp port notes: mam) and _kb/14-cpp-multiprecision.md
323 if (Nn == 0)
324 throw InputError(
325 "mfq_general_solve: every state has an up drift, so the fluid level grows without "
326 "bound and has no stationary law");
327
328 std::vector<std::size_t> perm;
329 perm.insert(perm.end(), ixz.begin(), ixz.end());
330 perm.insert(perm.end(), ixp.begin(), ixp.end());
331 perm.insert(perm.end(), ixn.begin(), ixn.end());
332 Matrix<T> P(N, N, zero);
333 for (std::size_t i = 0; i < N; ++i) P(i, perm[i]) = one;
334
335 // Qv = P Q P^T, Rv = P R P^T: P is a permutation, so its inverse is P^T.
336 Matrix<T> Qv(N, N, zero), Rv(N, N, zero);
337 for (std::size_t i = 0; i < N; ++i)
338 for (std::size_t j = 0; j < N; ++j) {
339 Qv(i, j) = Q(perm[i], perm[j]);
340 Rv(i, j) = R(perm[i], perm[j]);
341 }
342
343 // Censor the zero-drift states out.
344 Matrix<T> iQv00(Nz, Nz, zero);
345 if (Nz > 0) {
346 Matrix<T> negQ00(Nz, Nz, zero);
347 for (std::size_t i = 0; i < Nz; ++i)
348 for (std::size_t j = 0; j < Nz; ++j) negQ00(i, j) = -Qv(i, j);
349 try {
350 iQv00 = inverse(negQ00);
351 } catch (const NumericError&) {
352 throw NumericError(
353 "mfq_general_solve: the zero-drift states form a closed set, so the fluid can be "
354 "trapped at a constant level and the model has no stationary fluid law; the "
355 "MATLAB reference silently substitutes a pseudo-inverse here");
356 }
357 }
358 const std::size_t Npn = Np + Nn;
359 Matrix<T> Qbar = block(Qv, Nz, Nz, Npn, Npn);
360 if (Nz > 0) {
361 const Matrix<T> L = block(Qv, Nz, 0, Npn, Nz);
362 const Matrix<T> Rt = block(Qv, 0, Nz, Nz, Npn);
363 Qbar = add(Qbar, matmul(matmul(L, iQv00), Rt));
364 }
365 Matrix<T> absRi(Npn, Npn, zero);
366 for (std::size_t i = 0; i < Npn; ++i) absRi(i, i) = one / num_abs(Rv(Nz + i, Nz + i));
367 const Matrix<T> Qz = matmul(absRi, Qbar);
368
369 const FluidFundamental<T> ff =
370 mfq_fundamental(block(Qz, 0, 0, Np, Np), block(Qz, 0, Np, Np, Nn),
371 block(Qz, Np, 0, Nn, Np), block(Qz, Np, Np, Nn, Nn), prec, 150u,
373 const Matrix<T>& Psi = ff.Psi;
374 const Matrix<T>& K = ff.K;
375 const Matrix<T>& U = ff.U;
376
377 // Pm = [I_Np, Psi], iCn and iCp the down and up blocks of absRi.
378 Matrix<T> Pm(Np, Npn, zero);
379 for (std::size_t i = 0; i < Np; ++i) {
380 Pm(i, i) = one;
381 for (std::size_t j = 0; j < Nn; ++j) Pm(i, Np + j) = Psi(i, j);
382 }
383 const Matrix<T> iCp = block(absRi, 0, 0, Np, Np);
384 const Matrix<T> iCn = block(absRi, Np, Np, Nn, Nn);
385
386 // clo = [ (iCp Qv(p,z) + Psi iCn Qv(n,z)) iQv00 , Pm absRi ], Np x N.
387 Matrix<T> clo(Np, N, zero);
388 if (Nz > 0) {
389 const Matrix<T> Qpz = block(Qv, Nz, 0, Np, Nz);
390 const Matrix<T> Qnz = block(Qv, Nz + Np, 0, Nn, Nz);
391 const Matrix<T> lhs =
392 matmul(add(matmul(iCp, Qpz), matmul(matmul(Psi, iCn), Qnz)), iQv00);
393 for (std::size_t i = 0; i < Np; ++i)
394 for (std::size_t j = 0; j < Nz; ++j) clo(i, j) = lhs(i, j);
395 }
396 {
397 const Matrix<T> rhs = matmul(Pm, absRi);
398 for (std::size_t i = 0; i < Np; ++i)
399 for (std::size_t j = 0; j < Npn; ++j) clo(i, Nz + j) = rhs(i, j);
400 }
401
403 out.K = K;
404 const std::vector<T> eN = ones<T>(N);
405 const Matrix<T> negKinv = inverse(scale(K, T(-one)));
406
407 if (Q0.rows() == 0) {
408 // Regular boundary behaviour, Q0 = Q.
409 // clo goes back to the original state ordering: clo * P.
410 Matrix<T> cloP(Np, N, zero);
411 for (std::size_t i = 0; i < Np; ++i)
412 for (std::size_t j = 0; j < N; ++j) cloP(i, perm[j]) = clo(i, j);
413
414 std::vector<T> Ua(Nn, zero);
415 if (Nz > 0) {
416 const Matrix<T> Qnz = block(Qv, Nz + Np, 0, Nn, Nz);
417 const std::vector<T> t = mulvec(matmul(matmul(iCn, Qnz), iQv00), ones<T>(Nz));
418 for (std::size_t i = 0; i < Nn; ++i) Ua[i] += t[i];
419 }
420 {
421 const std::vector<T> t = mulvec(iCn, ones<T>(Nn));
422 for (std::size_t i = 0; i < Nn; ++i) Ua[i] += t[i];
423 }
424 {
425 const Matrix<T> Qnp = block(Qz, Np, 0, Nn, Np);
426 const std::vector<T> t = mulvec(matmul(matmul(Qnp, negKinv), cloP), eN);
427 for (std::size_t i = 0; i < Nn; ++i) Ua[i] += t[i];
428 }
429 // pm solves pm [U, Ua] = [0 ... 0, 1], an (Nn+1)-equation system in Nn
430 // unknowns that is consistent by construction.
431 Matrix<T> Msys(Nn + 1, Nn, zero);
432 for (std::size_t j = 0; j < Nn; ++j)
433 for (std::size_t i = 0; i < Nn; ++i) Msys(j, i) = U(i, j);
434 for (std::size_t i = 0; i < Nn; ++i) Msys(Nn, i) = Ua[i];
435 std::vector<T> rhs(Nn + 1, zero);
436 rhs[Nn] = one;
437 const std::vector<T> pm = normal_equations_solve(Msys, rhs);
438
439 std::vector<T> m0(N, zero);
440 {
441 const std::vector<T> pmiCn = vecmul(pm, iCn);
442 if (Nz > 0) {
443 const Matrix<T> Qnz = block(Qv, Nz + Np, 0, Nn, Nz);
444 const std::vector<T> t = vecmul(vecmul(pmiCn, Qnz), iQv00);
445 for (std::size_t j = 0; j < Nz; ++j) m0[j] = t[j];
446 }
447 for (std::size_t j = 0; j < Nn; ++j) m0[Nz + Np + j] = pmiCn[j];
448 }
449 out.mass0.assign(N, zero);
450 for (std::size_t j = 0; j < N; ++j) out.mass0[perm[j]] = m0[j];
451 out.ini = vecmul(pm, block(Qz, Np, 0, Nn, Np));
452 out.clo = cloP;
453 } else {
454 if (Q0.rows() != N || Q0.cols() != N)
455 throw InputError("mfq_general_solve: Q0 must be square and of the order of Q");
456 Matrix<T> Q0v(N, N, zero);
457 for (std::size_t i = 0; i < N; ++i)
458 for (std::size_t j = 0; j < N; ++j) Q0v(i, j) = Q0(perm[i], perm[j]);
459
460 // M = [-clo Rv ; Q0v(n, :) ; Q0v(z, :)], N x N, and Ma the normalizer.
461 Matrix<T> Msys(N + 1, N, zero);
462 const Matrix<T> cloRv = matmul(clo, Rv);
463 for (std::size_t i = 0; i < Np; ++i)
464 for (std::size_t j = 0; j < N; ++j) Msys(j, i) = -cloRv(i, j);
465 for (std::size_t i = 0; i < Nn; ++i)
466 for (std::size_t j = 0; j < N; ++j) Msys(j, Np + i) = Q0v(Nz + Np + i, j);
467 for (std::size_t i = 0; i < Nz; ++i)
468 for (std::size_t j = 0; j < N; ++j) Msys(j, Np + Nn + i) = Q0v(i, j);
469 {
470 const std::vector<T> s = mulvec(matmul(negKinv, clo), eN);
471 for (std::size_t i = 0; i < Np; ++i) Msys(N, i) = s[i];
472 for (std::size_t i = Np; i < N; ++i) Msys(N, i) = one;
473 }
474 std::vector<T> rhs(N + 1, zero);
475 rhs[N] = one;
476 const std::vector<T> sol = normal_equations_solve(Msys, rhs);
477
478 out.ini.assign(sol.begin(), sol.begin() + Np);
479 Matrix<T> cloP(Np, N, zero);
480 for (std::size_t i = 0; i < Np; ++i)
481 for (std::size_t j = 0; j < N; ++j) cloP(i, perm[j]) = clo(i, j);
482 out.clo = cloP;
483 std::vector<T> m0(N, zero);
484 for (std::size_t j = 0; j < Nz; ++j) m0[j] = sol[Np + Nn + j];
485 for (std::size_t j = 0; j < Nn; ++j) m0[Nz + Np + j] = sol[Np + j];
486 out.mass0.assign(N, zero);
487 for (std::size_t j = 0; j < N; ++j) out.mass0[perm[j]] = m0[j];
488 }
489 return out;
490}
491
492/** mfq_general_solve with the regular boundary and the BUTools default prec = 1e-14. */
493template <class T>
497
498/**
499 * The similarity transformation B with B v = e, for a non-negative column
500 * vector v. Port of BUTools TransformToOnes: sort v decreasing so that a
501 * non-zero entry leads, then take the lower-triangular matrix of reciprocal
502 * partial sums. It works even when v has zero entries, which is why the naive
503 * diag(1/v) is not used.
504 */
505template <class T>
506Matrix<T> mfq_transform_to_ones(const std::vector<T>& v) {
507 const std::size_t m = v.size();
508 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
509 if (m == 0) throw InputError("mfq_transform_to_ones: empty vector");
510 std::vector<std::size_t> ix(m);
511 for (std::size_t i = 0; i < m; ++i) ix[i] = i;
512 // Stable sort on -v, i.e. decreasing v, matching MATLAB's sort(-clovec).
513 for (std::size_t i = 1; i < m; ++i)
514 for (std::size_t j = i; j > 0 && v[ix[j]] > v[ix[j - 1]]; --j) std::swap(ix[j], ix[j - 1]);
515 std::vector<T> cp(m);
516 for (std::size_t i = 0; i < m; ++i) cp[i] = v[ix[i]];
517 Matrix<T> Bt(m, m, zero);
518 T acc = zero;
519 for (std::size_t i = 0; i < m; ++i) {
520 acc += cp[i];
521 if (acc == zero)
522 throw NumericError("mfq_transform_to_ones: the closing vector sums to zero");
523 for (std::size_t j = 0; j <= i; ++j) Bt(i, j) = one / acc;
524 }
525 // B = Bt * P, with P(i, ix(i)) = 1.
526 Matrix<T> B(m, m, zero);
527 for (std::size_t i = 0; i < m; ++i)
528 for (std::size_t j = 0; j < m; ++j) B(i, ix[j]) = Bt(i, j);
529 return B;
530}
531
532} // namespace mam
533} // namespace line
534
535#endif // LINE_API_MAM_MFQ_SOLVE_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
The algorithm cannot proceed on this instance (singular matrix, ...).
Definition error.h:43
NumericError(const std::string &what)
Definition error.h:45
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.
RiccatiMethod
Which doubling iteration to run for Psi.
Definition mfq_solve.h:82
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
GeneralFluidSolution< T > mfq_general_solve(const Matrix< T > &Q, const Matrix< T > &R, const Matrix< T > &Q0, const T &prec)
Stationary law of a general Markovian fluid model, pi(x) = ini exp(K x) clo above level zero plus the...
Definition mfq_solve.h:299
Matrix< T > mfq_transform_to_ones(const std::vector< T > &v)
The similarity transformation B with B v = e, for a non-negative column vector v.
Definition mfq_solve.h:506
T num_abs(const T &v)
Definition number.h:172
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
Definition linalg.h:50
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
std::vector< T > mulvec(const Matrix< T > &A, const std::vector< T > &v)
Matrix times column vector, A v.
Definition linalg.h:62
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
Definition linalg.h:104
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
Psi, K and U of a fluid queue with drifts normalized to +-1.
Definition mfq_solve.h:86
Matrix< T > U
Fmm + Fmp Psi, the level-zero generator.
Definition mfq_solve.h:89
Matrix< T > Psi
first return matrix, up states to down states
Definition mfq_solve.h:87
Matrix< T > K
Fpp + Psi Fmp, the density generator.
Definition mfq_solve.h:88
Stationary matrix-exponential solution of a general Markovian fluid model.
Definition mfq_solve.h:280
Matrix< T > K
matrix exponent of the density, Np x Np
Definition mfq_solve.h:283
std::vector< T > mass0
P(level 0, state j), length N.
Definition mfq_solve.h:281
std::vector< T > ini
initial vector of the density, length Np
Definition mfq_solve.h:282
Matrix< T > clo
closing matrix of the density, Np x N
Definition mfq_solve.h:284