LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
map_bernstein.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_MAP_BERNSTEIN_H
6#define LINE_API_MAM_MAP_BERNSTEIN_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Acyclic phase-type approximation of an arbitrary density by Bernstein
12 * exponentials.
13 *
14 * Templated port of matlab/lib/kpctoolbox/map/map_bernstein.m and
15 * jar/src/main/java/jline/api/mam/Aph_bernstein.java, after
16 * Horvath and Vicario, "Construction of Phase Type Distributions by Bernstein
17 * Exponentials", EPEW 2023, 201-215.
18 *
19 * The construction evaluates the target density at the n Bernstein nodes
20 * x_i = -log(i/n) and uses the values as the entry law of the Erlang cascade
21 * T = diag(-1..-n) + superdiag(1..n-1),
22 * whose i-th phase has an Erlang(i,i) residual time. The result is a renewal
23 * process, D1 = -T 1 alpha, so the fit is a PH and not a general MAP; the caller
24 * rescales it to the target mean with map_scale, since the construction pins the
25 * time unit at one.
26 *
27 * WHICH REFERENCE. The MATLAB and JAR versions differ and MATLAB is the ground
28 * truth here: it skips a node where the density is not finite and positive,
29 * renormalizes alpha (which the bare 1/(i c) weights only satisfy up to the
30 * skipped nodes), and falls back to an Erlang-n of unit mean when the
31 * normalization constant is not usable at all. The JAR divides by c
32 * unconditionally, so a density that underflows at some node -- a Pareto below
33 * its scale, a Uniform outside its support, any bounded law -- gives it a NaN
34 * generator instead of a fit. That path is exactly the one `sn_nonmarkov_toph`
35 * takes for Uniform and Pareto service.
36 *
37 * ARITHMETIC: transcendental, since the nodes are logarithms of i/n.
38 */
39
40#include <cmath>
41#include <cstddef>
42#include <functional>
43
46#include "line/num/number.h"
47#include "line/util/error.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace mam {
52
53/**
54 * @brief Acyclic phase-type approximation of an arbitrary density by
55 * Bernstein exponentials.
56 *
57 * @param f the density to approximate, evaluated at positive abscissae
58 * @param order number of phases, the reference default being 20
59 * @return the fitted renewal MAP, of unit time scale; rescale with map_scale
60 */
61template <class T>
62Map<T> map_bernstein(const std::function<double(double)>& f, unsigned order = 20) {
64 "map_bernstein evaluates the density at logarithmic nodes");
65 if (order == 0) throw InputError("map_bernstein: the order must be positive");
66 const std::size_t n = order;
67 const T zero = num_traits<T>::from_int(0);
68
69 std::vector<double> fv(n, 0.0);
70 double c = 0.0;
71 for (std::size_t i = 1; i <= n; ++i) {
72 const double xi = -std::log(static_cast<double>(i) / static_cast<double>(n));
73 const double fi = f(xi);
74 if (std::isfinite(fi) && fi > 0.0) {
75 fv[i - 1] = fi;
76 c += fi / static_cast<double>(i);
77 }
78 }
79 // No usable mass at any node: the reference returns a unit-mean Erlang-n and
80 // leaves the rescaling to the caller, rather than emitting a NaN generator.
81 if (!(c > 0.0) || !std::isfinite(c))
82 return map_erlang(num_traits<T>::from_int(1), static_cast<unsigned>(n));
83
84 std::vector<double> alpha(n, 0.0);
85 double asum = 0.0;
86 for (std::size_t i = 1; i <= n; ++i) {
87 if (fv[i - 1] > 0.0) {
88 alpha[i - 1] = fv[i - 1] / (static_cast<double>(i) * c);
89 asum += alpha[i - 1];
90 }
91 }
92 if (asum > 0.0) {
93 for (std::size_t i = 0; i < n; ++i) alpha[i] /= asum;
94 } else {
95 alpha[0] = 1.0; // degenerate: start in the first phase
96 }
97
98 Map<T> m;
99 m.D0 = Matrix<T>(n, n, zero);
100 m.D1 = Matrix<T>(n, n, zero);
101 for (std::size_t i = 0; i < n; ++i) {
102 m.D0(i, i) = num_traits<T>::from_int(-static_cast<int>(i + 1));
103 if (i + 1 < n) m.D0(i, i + 1) = num_traits<T>::from_int(static_cast<int>(i + 1));
104 }
105 // D1 = -T P with P = 1 alpha: every phase restarts from the entry law.
106 for (std::size_t i = 0; i < n; ++i) {
107 T rowsum = zero;
108 for (std::size_t j = 0; j < n; ++j) rowsum += m.D0(i, j);
109 for (std::size_t j = 0; j < n; ++j)
110 m.D1(i, j) = T(-rowsum * num_traits<T>::from_double(alpha[j]));
111 }
112 return m;
113}
114
115/**
116 * The reference's (D0, D1) spelling of the same fit, kept because the JAR
117 * exposes it under this name (Aph_bernstein) and callers ported from it expect
118 * the pair rather than a Map.
119 */
120template <class T>
121Map<T> aph_bernstein(const std::function<double(double)>& f, unsigned order = 20) {
122 return map_bernstein<T>(f, order);
123}
124
125} // namespace mam
126} // namespace line
127
128#endif // LINE_API_MAM_MAP_BERNSTEIN_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
MAP constructors and structural transformations.
Dense matrix and non-owning view.
Map< T > map_bernstein(const std::function< double(double)> &f, unsigned order=20)
Acyclic phase-type approximation of an arbitrary density by Bernstein exponentials.
Map< T > map_erlang(const T &mean, unsigned k)
Erlang-k renewal MAP with the given mean (map_erlang.m).
Map< T > aph_bernstein(const std::function< double(double)> &f, unsigned order=20)
The reference's (D0, D1) spelling of the same fit, kept because the JAR exposes it under this name (A...
Number-type abstraction for the templated API port.
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