LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_lap.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_LAP_H
6#define LINE_API_PFQN_PFQN_LAP_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Laplace approximation of the normalizing constant of a repairman
12 * (single-queue, multiclass) model.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_lap.m. The McKenna-Mitra
15 * integral for a single queueing station is reduced to a one-dimensional
16 * Laplace integral whose saddle point u0 solves
17 *
18 * f(u) = 1 - sum_r N_r L_r / (Z_r + Ntot L_r u) = 0
19 *
20 * after which
21 *
22 * log I = log Ntot - sum_r factln(N_r) - Ntot u0 + sum_r N_r log(Z_r + L_r u0 Ntot)
23 * + (1/2) log(2 pi) - (1/2) log(sum_r (N_r/Ntot)/(Z_r/(Ntot L_r) + u0)^2)
24 * - (1/2) log Ntot.
25 *
26 * ROOT FINDING. MATLAB calls fzero from the initial guess 1, and falls back to
27 * a 1e-4 grid scan over (0,10] when fzero returns a non-finite root. f is
28 * strictly increasing in u on u > 0 (each term N_r L_r/(Z_r + Ntot L_r u) is
29 * decreasing), so the port brackets the root by doubling from 1 and then
30 * bisects, which lands on the same root fzero converges to but without a
31 * derivative or a Newton step that could leave the domain. The fallback scan
32 * is kept for the case where no sign change exists on any bracket, exactly as
33 * MATLAB's is.
34 *
35 * MATLAB returns NaN when the root is negative. The port throws instead:
36 * a NaN normalizing constant propagates silently through a solver, whereas the
37 * condition it signals (no admissible saddle point) is a modelling error.
38 *
39 * ARITHMETIC. Laplace's method plus logarithms, so gated on
40 * num_traits<T>::has_transcendental.
41 */
42
43#include <cmath>
44#include <cstddef>
45#include <vector>
46
48#include "line/num/number.h"
49#include "line/util/error.h"
50
51namespace line {
52namespace pfqn {
53
54/** Assembly of the expansion at the saddle point; defined below. */
55template <class T>
56T finish_lap(const std::vector<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
57 const T& Ntot, const T& u0);
58
59/**
60 * @brief Laplace approximation of the normalizing constant of a repairman
61 * (single-queue, multiclass) model.
62 *
63 * @param L (R) per-class demand at the single station
64 * @param N (R) per-class population
65 * @param Z (R) per-class think time
66 * @return log of the approximate normalizing constant
67 */
68template <class T>
69T pfqn_lap(const std::vector<T>& L, const std::vector<T>& N, const std::vector<T>& Z) {
71 "pfqn_lap requires transcendental arithmetic (Laplace approximation of an integral)");
72 using std::log;
73 const std::size_t R = L.size();
74 if (N.size() != R || Z.size() != R)
75 throw InputError(
76 "pfqn_lap expects per-class vectors for a single queueing station (repairman models)");
77 const T zero = num_traits<T>::from_int(0);
78 T Ntot = zero;
79 for (const T& v : N) Ntot += v;
80 if (Ntot <= zero) throw InputError("pfqn_lap: empty population");
81
82 // f(u) = 1 - sum_r N_r L_r / (Z_r + Ntot L_r u), increasing on u > 0.
83 const auto f = [&](const T& u) {
85 for (std::size_t r = 0; r < R; ++r) {
86 const T den = T(Z[r] + Ntot * L[r] * u);
87 if (den == zero) continue;
88 s -= T(N[r] * L[r] / den);
89 }
90 return s;
91 };
92
94 bool bracketed = false;
95 if (f(lo) <= zero) {
96 for (int k = 0; k < 200; ++k) {
97 if (f(hi) >= zero) {
98 bracketed = true;
99 break;
100 }
101 lo = hi;
102 hi = T(hi * num_traits<T>::from_int(2));
103 }
104 } else {
105 // f already positive at the left edge: MATLAB's grid scan finds no
106 // sign change either and u0 stays at the left edge.
107 return finish_lap(L, N, Z, Ntot, lo);
108 }
109 if (!bracketed) throw NumericError("pfqn_lap: no saddle point on (0, 2^200]");
110 for (int it = 0; it < 400; ++it) {
111 const T mid = T(T(lo + hi) / num_traits<T>::from_int(2));
112 if (f(mid) < zero)
113 lo = mid;
114 else
115 hi = mid;
116 if (num_traits<T>::to_double(num_abs(T(hi - lo))) <=
117 1e-16 * (1.0 + num_traits<T>::to_double(hi)))
118 break;
119 }
120 const T u0 = T(T(lo + hi) / num_traits<T>::from_int(2));
121 return finish_lap(L, N, Z, Ntot, u0);
122}
123
124template <class T>
125T finish_lap(const std::vector<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
126 const T& Ntot, const T& u0) {
127 using std::log;
128 const std::size_t R = L.size();
129 const T zero = num_traits<T>::from_int(0);
130 if (u0 < zero) throw NumericError("pfqn_lap: negative saddle point, no admissible expansion");
131 const T twopi = num_traits<T>::from_double(6.283185307179586476925286766559);
132
133 T logI = T(log(Ntot));
134 for (std::size_t r = 0; r < R; ++r) logI -= detail::num_factln<T>(N[r]);
135 logI -= Ntot * u0;
136 for (std::size_t r = 0; r < R; ++r) logI += N[r] * log(T(Z[r] + L[r] * u0 * Ntot));
137 T f2 = zero;
138 for (std::size_t r = 0; r < R; ++r) {
139 if (L[r] == zero) continue;
140 const T d = T(T(Z[r] / T(Ntot * L[r])) + u0);
141 f2 += T(T(N[r] / Ntot) / T(d * d));
142 }
143 if (f2 <= zero) throw NumericError("pfqn_lap: non-positive curvature at the saddle point");
144 logI += T(num_traits<T>::from_rational(1, 2) * log(twopi));
145 logI -= T(num_traits<T>::from_rational(1, 2) * log(f2));
146 logI -= T(num_traits<T>::from_rational(1, 2) * log(Ntot));
147 return logI;
148}
149
150} // namespace pfqn
151} // namespace line
152
153#endif // LINE_API_PFQN_PFQN_LAP_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
T pfqn_lap(const std::vector< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Laplace approximation of the normalizing constant of a repairman (single-queue, multiclass) model.
Definition pfqn_lap.h:69
T finish_lap(const std::vector< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const T &Ntot, const T &u0)
Assembly of the expansion at the saddle point; defined below.
Definition pfqn_lap.h:125
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...