LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_nrl.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_PFQN_NRL_H
6#define LINE_API_PFQN_PFQN_NRL_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Norlund-Rice inversion of the normalizing constant, in its logistic (NRL)
12 * and probit (NRP) substitutions.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_nrl.m and pfqn_nrp.m. Both scale
15 * the demands column-wise into [0,1], apply the Laplace approximation of
16 * laplaceapprox.m to the corresponding infradius integrand at the origin, and
17 * undo the scaling with sum_r N_r log Lmax_r. The two differ only in the
18 * change of variable, so they are ported together.
19 *
20 * THE CURVATURE TERM IS HALF THE LOG-DETERMINANT, and it is the single thing
21 * most worth checking when a ported value disagrees with MATLAB here. Both
22 * routines consume laplaceapprox's logI, which is log(I) and therefore carries
23 * -(1/2) log det(-H); using the full log-determinant instead shifts every
24 * value by (1/2) log det(-H) with no other symptom. On the single-class model
25 * L = [1/2, 1/3, 1/5], N = 5, Z = 0 that shift is 0.347 nats, turning the
26 * correct -2.1159 into -1.7694, both of which look plausible next to the exact
27 * -1.9951.
28 *
29 * DELAY. A non-zero think time is appended as one more station whose rate row
30 * is 1, 2, ..., Ntot, i.e. an infinite server, exactly as in the reference.
31 *
32 * ARITHMETIC. A Laplace approximation of a contour integral, so gated on
33 * num_traits<T>::has_transcendental. pfqn_nrp additionally carries the
34 * double-precision ceiling of the normal CDF; see infradius_h.h.
35 */
36
37#include <cmath>
38#include <cstddef>
39#include <functional>
40#include <vector>
41
45#include "line/num/number.h"
46#include "line/util/error.h"
47#include "line/util/matrix.h"
48
49namespace line {
50namespace pfqn {
51
52namespace detail {
53
54/** Shared body of pfqn_nrl and pfqn_nrp; `probit` selects the substitution. */
55template <class T>
56T pfqn_nr_impl(const Matrix<T>& L0, const std::vector<T>& N, const std::vector<T>& Z,
57 const Matrix<T>& alpha0, bool probit) {
58 using std::log;
59 const std::size_t M = L0.rows(), R = L0.cols();
60 if (N.size() != R) throw InputError("pfqn_nr: L and N disagree on the class count");
61 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
62 T Nt = zero, Zsum = zero;
63 for (const T& v : N) Nt += v;
64 for (const T& v : Z) Zsum += v;
65 if (Nt < zero) throw InputError("pfqn_nr: negative population");
66 if (Nt == zero) return zero;
67 const std::size_t Ntot = static_cast<std::size_t>(num_traits<T>::to_double(Nt));
68
69 // Append the delay as an infinite-server station.
70 const std::size_t Mx = M + (Zsum > zero ? 1 : 0);
71 Matrix<T> L(Mx, R);
72 for (std::size_t i = 0; i < M; ++i)
73 for (std::size_t r = 0; r < R; ++r) L(i, r) = L0(i, r);
74 Matrix<T> alpha(Mx, Ntot);
75 for (std::size_t i = 0; i < M && i < alpha0.rows(); ++i)
76 for (std::size_t k = 0; k < Ntot; ++k)
77 alpha(i, k) = k < alpha0.cols() ? alpha0(i, k) : one;
78 if (Zsum > zero) {
79 if (Z.size() != R) throw InputError("pfqn_nr: Z has the wrong length");
80 for (std::size_t r = 0; r < R; ++r) L(M, r) = Z[r];
81 for (std::size_t k = 0; k < Ntot; ++k)
82 alpha(M, k) = num_traits<T>::from_int(static_cast<long>(k) + 1);
83 }
84
85 // A single queueing station with no delay has a closed form.
86 if (M == 1 && Zsum == zero) {
87 T lG = detail::num_factln<T>(Nt);
88 for (std::size_t r = 0; r < R; ++r) {
89 lG -= detail::num_factln<T>(N[r]);
90 if (L(0, r) > zero) lG += N[r] * log(L(0, r));
91 }
92 for (std::size_t k = 0; k < Ntot; ++k) lG -= log(alpha(0, k));
93 return lG;
94 }
95
96 // Scale each class column by its largest demand.
97 std::vector<T> Lmax(R, zero);
98 for (std::size_t r = 0; r < R; ++r) {
99 T m = L(0, r);
100 for (std::size_t i = 1; i < Mx; ++i)
101 if (L(i, r) > m) m = L(i, r);
102 if (m <= zero) throw InputError("pfqn_nr: a class has no positive demand");
103 Lmax[r] = m;
104 }
105 Matrix<T> Ls(Mx, R);
106 for (std::size_t i = 0; i < Mx; ++i)
107 for (std::size_t r = 0; r < R; ++r) Ls(i, r) = T(L(i, r) / Lmax[r]);
108
109 const std::function<T(const std::vector<T>&)> h = [&](const std::vector<T>& x) {
110 return probit ? infradius_hnorm(x, Ls, N, alpha) : infradius_h(x, Ls, N, alpha);
111 };
112 const std::vector<T> x0(R, zero);
113 const LaplaceResult<T> la = laplaceapprox<T>(h, x0);
114 T lG = la.logI;
115 for (std::size_t r = 0; r < R; ++r) lG += N[r] * log(Lmax[r]);
116 return lG;
117}
118
119} // namespace detail
120
121/**
122 * Norlund-Rice logit approximation of log G.
123 *
124 * @param L (M x R) demands
125 * @param N (R) population
126 * @param Z (R) think times, empty for zero
127 * @param alpha (M x Ntot) load-dependent rates, empty for all ones
128 */
129template <class T>
130T pfqn_nrl(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
131 const Matrix<T>& alpha) {
133 "pfqn_nrl requires transcendental arithmetic (Laplace approximation of a contour "
134 "integral)");
135 return detail::pfqn_nr_impl(L, N, Z, alpha, false);
136}
137
138/** Norlund-Rice probit approximation of log G. */
139template <class T>
140T pfqn_nrp(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
141 const Matrix<T>& alpha) {
143 "pfqn_nrp requires transcendental arithmetic (Laplace approximation of a contour "
144 "integral)");
145 return detail::pfqn_nr_impl(L, N, Z, alpha, true);
146}
147
148} // namespace pfqn
149} // namespace line
150
151#endif // LINE_API_PFQN_PFQN_NRL_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Integrands of the Norlund-Rice inversion of the normalizing constant, in their two changes of variabl...
Laplace approximation of a multidimensional integral around a given point.
Dense matrix and non-owning view.
LaplaceResult< T > laplaceapprox(const std::function< T(const std::vector< T > &)> &h, const std::vector< T > &x0)
Laplace approximation of a multidimensional integral around a given point.
T pfqn_nrp(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const Matrix< T > &alpha)
Norlund-Rice probit approximation of log G.
Definition pfqn_nrl.h:140
T pfqn_nrl(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const Matrix< T > &alpha)
Norlund-Rice logit approximation of log G.
Definition pfqn_nrl.h:130
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,...