LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
infradius_h.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_PFQN_INFRADIUS_H_H
6#define LINE_API_PFQN_INFRADIUS_H_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Integrands of the Norlund-Rice inversion of the normalizing constant, in
12 * their two changes of variable.
13 *
14 * Templated port of matlab/src/api/pfqn/infradius_h.m (logistic substitution,
15 * used by pfqn_nrl) and matlab/src/api/pfqn/infradius_hnorm.m (normal-CDF
16 * substitution, used by pfqn_nrp). Both map the Cauchy contour integral for G
17 * onto R^R and evaluate
18 *
19 * h(x) = Re[ gld( sum_r L(:,r) e^{2 pi i (t_r - tbar)}, Ntot, alpha ) ] * jac(x)
20 *
21 * with t the substituted variable, tbar = sum_r beta_r t_r, beta = N/Ntot, and
22 * the Jacobian prod_r e^{x_r}/(1+e^{x_r})^2 for the logistic map or
23 * prod_r phi(x_r) for the normal one. gld is the single-class load-dependent
24 * normalizing constant evaluated at COMPLEX demands.
25 *
26 * COMPLEX gld. MATLAB gets complex arithmetic from the language: pfqn_gld
27 * detects a non-real demand vector and takes its linear-scale recursion, since
28 * the logarithms it otherwise uses are not defined there. There is no
29 * num_traits for a complex type here, so the recursion is carried out
30 * explicitly on the Cx<T> pairs of pfqn_asympt_common.h; it is the same
31 * recursion g(m,n,tm) = g(m-1,n,1) + L_m g(m,n-1,tm+1)/mu(m,tm), and it
32 * remains exact in the field, only complex.
33 *
34 * PRECISION CEILING of the normal substitution. normcdf and normpdf need the
35 * error function, which is not available uniformly across the arithmetic
36 * backends, so infradius_hnorm evaluates the standard normal CDF by
37 * converting to double, calling std::erfc, and converting back. That caps
38 * infradius_hnorm -- and therefore pfqn_nrp -- near 1e-15 relative whatever T
39 * is. infradius_h has no such ceiling: the logistic map needs only exp.
40 *
41 * ARITHMETIC. Both integrands are gated on num_traits<T>::has_transcendental:
42 * exp, cos and sin are unavoidable, and so is erfc for the normal one.
43 */
44
45#include <cmath>
46#include <cstddef>
47#include <vector>
48
50#include "line/num/number.h"
51#include "line/util/error.h"
52#include "line/util/matrix.h"
53
54namespace line {
55namespace pfqn {
56
57namespace detail {
58
59/**
60 * Single-class load-dependent normalizing constant at complex demands, the
61 * linear-scale branch of pfqn_gldsingle.
62 *
63 * @param L (M) complex demands, @param N total population
64 * @param mu (M x >= N) load-dependent rates, real
65 */
66template <class T>
67Cx<T> gldsingle_complex(const std::vector<Cx<T>>& L, int N, const Matrix<T>& mu) {
68 const std::size_t M = L.size();
69 if (M == 0) throw InputError("gldsingle_complex: empty demand vector");
70 if (N < 0) throw InputError("gldsingle_complex: negative population");
71 if (mu.rows() != M) throw InputError("gldsingle_complex: mu has the wrong station count");
72 if (N > 0 && mu.cols() < static_cast<std::size_t>(N))
73 throw InputError("gldsingle_complex: mu has fewer rate columns than the population");
74 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
75 const std::size_t Nn = static_cast<std::size_t>(N);
76 // g[m][n][tm], m = 0..M, n = 0..N, tm = 0..N+1 as in the reference.
77 std::vector<std::vector<std::vector<Cx<T>>>> g(
78 M + 1, std::vector<std::vector<Cx<T>>>(Nn + 1, std::vector<Cx<T>>(Nn + 2, Cx<T>())));
79 for (std::size_t m = 1; m <= M; ++m) {
80 for (std::size_t tm = 0; tm <= Nn; ++tm) g[m][0][tm] = Cx<T>(one);
81 for (std::size_t n = 1; n <= Nn; ++n)
82 for (std::size_t tm = 0; tm + n <= Nn; ++tm) {
83 const T rate = mu(m - 1, tm);
84 if (rate == zero)
85 throw NumericError("gldsingle_complex: a load-dependent rate is zero");
86 const Cx<T> term = cx_scale(cx_mul(L[m - 1], g[m][n - 1][tm + 1]), T(one / rate));
87 g[m][n][tm] = cx_add(g[m - 1][n][0], term);
88 }
89 }
90 return g[M][Nn][0];
91}
92
93} // namespace detail
94
95/**
96 * Logistic-substitution integrand (matlab/src/api/pfqn/infradius_h.m).
97 *
98 * @param x point in R^R
99 * @param L (M x R) demands
100 * @param N (R) population
101 * @param alpha (M x Ntot) load-dependent rates
102 */
103template <class T>
104T infradius_h(const std::vector<T>& x, const Matrix<T>& L, const std::vector<T>& N,
105 const Matrix<T>& alpha) {
107 "infradius_h requires transcendental arithmetic (logistic map and e^{2 pi i t})");
108 using std::exp;
109 const std::size_t M = L.rows(), R = L.cols();
110 if (N.size() != R || x.size() != R)
111 throw InputError("infradius_h: x, L and N disagree on the class count");
112 const T one = num_traits<T>::from_int(1);
113 T Nt = num_traits<T>::from_int(0);
114 for (const T& v : N) Nt += v;
115
116 std::vector<T> t(R);
117 T tb = num_traits<T>::from_int(0), jac = one;
118 for (std::size_t r = 0; r < R; ++r) {
119 const T e = exp(x[r]);
120 t[r] = T(e / T(one + e));
121 tb += T(N[r] / Nt) * t[r];
122 jac *= T(e / T(T(one + e) * T(one + e)));
123 }
124
125 const T twopi = num_traits<T>::from_double(6.283185307179586476925286766559);
126 std::vector<detail::Cx<T>> Lc(M);
127 for (std::size_t m = 0; m < M; ++m) {
128 detail::Cx<T> acc;
129 for (std::size_t r = 0; r < R; ++r)
130 acc = detail::cx_add(acc, detail::cx_scale(detail::cx_expi(T(twopi * T(t[r] - tb))),
131 L(m, r)));
132 Lc[m] = acc;
133 }
134 const detail::Cx<T> G =
135 detail::gldsingle_complex(Lc, static_cast<int>(num_traits<T>::to_double(Nt)), alpha);
136 return T(G.re * jac);
137}
138
139/**
140 * Normal-CDF substitution integrand
141 * (matlab/src/api/pfqn/infradius_hnorm.m). See the precision note above.
142 */
143template <class T>
144T infradius_hnorm(const std::vector<T>& x, const Matrix<T>& L, const std::vector<T>& N,
145 const Matrix<T>& alpha) {
147 "infradius_hnorm requires transcendental arithmetic (normal CDF and density)");
148 using std::exp;
149 const std::size_t M = L.rows(), R = L.cols();
150 if (N.size() != R || x.size() != R)
151 throw InputError("infradius_hnorm: x, L and N disagree on the class count");
152 T Nt = num_traits<T>::from_int(0);
153 for (const T& v : N) Nt += v;
154
155 const double invsqrt2pi = 0.39894228040143267793994605993438;
156 std::vector<T> t(R);
158 for (std::size_t r = 0; r < R; ++r) {
159 const double xd = num_traits<T>::to_double(x[r]);
160 t[r] = num_traits<T>::from_double(0.5 * std::erfc(-xd / std::sqrt(2.0)));
161 tb += T(N[r] / Nt) * t[r];
162 jac *= num_traits<T>::from_double(invsqrt2pi * std::exp(-0.5 * xd * xd));
163 }
164
165 const T twopi = num_traits<T>::from_double(6.283185307179586476925286766559);
166 std::vector<detail::Cx<T>> Lc(M);
167 for (std::size_t m = 0; m < M; ++m) {
168 detail::Cx<T> acc;
169 for (std::size_t r = 0; r < R; ++r)
170 acc = detail::cx_add(acc, detail::cx_scale(detail::cx_expi(T(twopi * T(t[r] - tb))),
171 L(m, r)));
172 Lc[m] = acc;
173 }
174 const detail::Cx<T> G =
175 detail::gldsingle_complex(Lc, static_cast<int>(num_traits<T>::to_double(Nt)), alpha);
176 return T(G.re * jac);
177}
178
179} // namespace pfqn
180} // namespace line
181
182#endif // LINE_API_PFQN_INFRADIUS_H_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
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Dense matrix and non-owning view.
@ Lc
Birman-Kogan Algorithm 2, single chain subproblems by MVA.
Definition pfqn_nc.h:117
T infradius_h(const std::vector< T > &x, const Matrix< T > &L, const std::vector< T > &N, const Matrix< T > &alpha)
Logistic-substitution integrand (matlab/src/api/pfqn/infradius_h.m).
T infradius_hnorm(const std::vector< T > &x, const Matrix< T > &L, const std::vector< T > &N, const Matrix< T > &alpha)
Normal-CDF substitution integrand (matlab/src/api/pfqn/infradius_hnorm.m).
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...