LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fes_map_moments.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_FES_MAP_MOMENTS_H
6#define LINE_API_FES_MAP_MOMENTS_H
7
8/**
9 * @file
10 * @ingroup api_fes
11 * Moments and index of dispersion of an inter-departure MAP.
12 *
13 * Templated port of matlab/src/api/fes/fes_map_moments.m and fes_map_euler.m,
14 * mirrored by the JAR and native Python. Evaluates equations (4), (5) and (7) of
15 * Casale, Mi, Cherkasova and Smirni, IEEE Trans. Soft. Eng. 37(5), 2011.
16 *
17 * The inverse (-T0)^-1 is dense even when T0 is sparse, so it is never formed:
18 * the moments follow from the vector recursion v_{k+1} = v_k (-T0)^-1, each step
19 * being one linear solve. Method "euler" replaces the solve by the quadrature
20 * v (-T0)^-1 = v int_0^inf exp(T0 t) dt of Section 5.2.2, integrated by the
21 * trapezoid rule with the Euler propagator exp(T0 dt) ~ I + T0 dt and a step
22 * below the inverse of the largest diagonal element in absolute value, as in the
23 * uniformization method. Method "ssolve" is the default because it is exact and
24 * faster; "euler" reproduces the reference implementation of the paper and is
25 * first order in the step.
26 *
27 * ARITHMETIC: "ssolve" uses field operations only and is exact at T = Rational;
28 * "euler" is an approximation at every arithmetic.
29 */
30
31#include <cmath>
32#include <cstddef>
33#include <string>
34#include <vector>
35
38#include "line/num/number.h"
39#include "line/util/error.h"
40#include "line/util/linalg.h"
41#include "line/util/lu.h"
42#include "line/util/matrix.h"
43
44namespace line {
45namespace fes {
46
47/** Descriptors a MAP(2) is fitted against. */
48template <class T>
50 T e1;
51 T e2;
52 T e3;
53 T e11;
54 T idc;
55};
56
57namespace detail {
58
59/** Solve the row system x A = b through the transpose. */
60template <class T>
61std::vector<T> solve_row(const Matrix<T>& A, const std::vector<T>& b) {
62 Matrix<T> At(A.cols(), A.rows());
63 for (std::size_t i = 0; i < A.rows(); ++i)
64 for (std::size_t j = 0; j < A.cols(); ++j) At(j, i) = A(i, j);
65 return solve(At, b);
66}
67
68} // namespace detail
69
70/**
71 * Approximate v (-T0)^-1 by the trapezoid rule with the Euler propagator.
72 *
73 * @param v row vector to be multiplied by (-T0)^-1
74 * @param T0 hidden transitions of the MAP, a stable matrix
75 * @param dt integration step, below 1/max(abs(diag(T0)))
76 * @param tol relative mass left when the integration stops
77 * @param iter_max maximum number of integration steps
78 */
79template <class T>
80std::vector<T> fes_map_euler(const std::vector<T>& v, const Matrix<T>& T0, const T& dt, double tol,
81 std::size_t iter_max) {
82 const T zero = num_traits<T>::from_int(0);
83 const T half = num_traits<T>::from_rational(1, 2);
84 std::vector<T> y(v.size(), zero);
85 std::vector<T> z = v;
86 double nrm0 = 0;
87 for (const T& x : v) nrm0 += std::abs(num_traits<T>::to_double(x));
88
89 for (std::size_t it = 0; it < iter_max; ++it) {
90 const std::vector<T> zT0 = vecmul(z, T0);
91 std::vector<T> znext(z.size());
92 for (std::size_t i = 0; i < z.size(); ++i) znext[i] = z[i] + dt * zT0[i];
93 for (std::size_t i = 0; i < y.size(); ++i) y[i] += dt * half * (z[i] + znext[i]);
94 z = znext;
95 double nrm = 0;
96 for (const T& x : z) nrm += std::abs(num_traits<T>::to_double(x));
97 if (nrm <= tol * nrm0) break;
98 }
99 return y;
100}
101
102/**
103 * @brief Moments and index of dispersion of an inter-departure MAP.
104 *
105 * @param map the pair (T0, T1) of the inter-departure MAP
106 * @param method "ssolve" for the linear solve, "euler" for the quadrature
107 * @param step_safety fraction of the uniformization bound used as Euler step
108 * @param tol relative mass left when the Euler quadrature stops
109 * @param iter_max maximum number of Euler integration steps
110 */
111template <class T>
112FesMapMoments<T> fes_map_moments(const mam::Map<T>& map, const std::string& method = "ssolve",
113 double step_safety = 0.1, double tol = 1e-12,
114 std::size_t iter_max = 1000000) {
115 const Matrix<T>& T0 = map.D0;
116 const Matrix<T>& T1 = map.D1;
117 const std::size_t dim = T0.rows();
118 const T zero = num_traits<T>::from_int(0);
119 const T one = num_traits<T>::from_int(1);
120
121 const Matrix<T> Q = mam::map_infgen(map);
122 const std::vector<T> phi = mc::ctmc_solve(Q);
123 std::vector<T> pie = vecmul(phi, T1);
124 T lambda = zero;
125 for (const T& x : pie) lambda += x;
126 for (std::size_t i = 0; i < pie.size(); ++i) pie[i] = pie[i] / lambda;
127
128 Matrix<T> negT0(dim, dim, zero);
129 for (std::size_t i = 0; i < dim; ++i)
130 for (std::size_t j = 0; j < dim; ++j) negT0(i, j) = -T0(i, j);
131
132 const bool euler = (method == "euler");
133 if (!euler && method != "ssolve")
134 throw InputError("fes_map_moments: unknown method, use ssolve or euler");
135
136 T dt = zero;
137 if (euler) {
138 double dmax = 0;
139 for (std::size_t i = 0; i < dim; ++i) {
140 const double d = std::abs(num_traits<T>::to_double(T0(i, i)));
141 if (d > dmax) dmax = d;
142 }
143 dt = num_traits<T>::from_double(step_safety / dmax);
144 }
145
146 const std::vector<T> v1 = euler ? fes_map_euler(pie, T0, dt, tol, iter_max)
147 : detail::solve_row(negT0, pie);
148 const std::vector<T> v2 = euler ? fes_map_euler(v1, T0, dt, tol, iter_max)
149 : detail::solve_row(negT0, v1);
150 const std::vector<T> v3 = euler ? fes_map_euler(v2, T0, dt, tol, iter_max)
151 : detail::solve_row(negT0, v2);
152 const std::vector<T> v2T1 = vecmul(v2, T1);
153 const std::vector<T> v4 = euler ? fes_map_euler(v2T1, T0, dt, tol, iter_max)
154 : detail::solve_row(negT0, v2T1);
155
157 out.e1 = zero;
158 out.e2 = zero;
159 out.e3 = zero;
160 out.e11 = zero;
161 for (std::size_t i = 0; i < dim; ++i) {
162 out.e1 += v1[i];
163 out.e2 += v2[i];
164 out.e3 += v3[i];
165 out.e11 += v4[i];
166 }
167 out.e2 = num_traits<T>::from_int(2) * out.e2;
168 out.e3 = num_traits<T>::from_int(6) * out.e3;
169
170 // equation (7), with pie inv(Q + e phi) obtained from the rank-one update
171 // y Q = pie - phi under the normalization y e = 1
172 Matrix<T> A = Q;
173 for (std::size_t i = 0; i < dim; ++i) A(i, dim - 1) = one;
174 std::vector<T> rhs(dim);
175 for (std::size_t i = 0; i < dim; ++i) rhs[i] = pie[i] - phi[i];
176 rhs[dim - 1] = one;
177 const std::vector<T> y = detail::solve_row(A, rhs);
178 const std::vector<T> yT1 = vecmul(y, T1);
179 T s = zero;
180 for (const T& x : yT1) s += x;
181 out.idc = one + num_traits<T>::from_int(2) * (lambda - s);
182 return out;
183}
184
185} // namespace fes
186} // namespace line
187
188#endif // LINE_API_FES_MAP_MOMENTS_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
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.
LU factorization with partial pivoting, templated on the number type.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
FesMapMoments< T > fes_map_moments(const mam::Map< T > &map, const std::string &method="ssolve", double step_safety=0.1, double tol=1e-12, std::size_t iter_max=1000000)
Moments and index of dispersion of an inter-departure MAP.
std::vector< T > fes_map_euler(const std::vector< T > &v, const Matrix< T > &T0, const T &dt, double tol, std::size_t iter_max)
Approximate v (-T0)^-1 by the trapezoid rule with the Euler propagator.
Matrix< T > map_infgen(const Map< T > &m)
Generator of the underlying phase process, D0 + D1.
Definition map_moment.h:62
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
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
Number-type abstraction for the templated API port.
Descriptors a MAP(2) is fitted against.
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54