LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_lekt.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_LEKT_H
6#define LINE_API_PFQN_PFQN_LEKT_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * The common corrected asymptotic expansion (LE-KT), computed on the cheaper side.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_lekt.m. The corrected logistic
14 * expansion (pfqn_ble) and the corrected Knessl-Tier expansion (pfqn_bkt) are
15 * ONE estimator, evaluated in M-1 and in R dimensions. With a think time their
16 * stationary points are one point in dual coordinates,
17 *
18 * xi_r = N_r / (Z_r + v u'L_r) (the class throughputs of the LE fixed point)
19 * v u_k = 1 / (1 - U_k) (the M/M/1 factor of the KT saddle)
20 *
21 * and Sylvester's identity det(I_R + C'C) = det(I_M + CC') exchanges the R x R
22 * Hessian determinant for the M x M one, after which every 2 pi cancels on both
23 * sides; the two agree to the accuracy of the saddle-point solvers. Without a
24 * think time the LE branch integrates the radius exactly as Gamma(N+M) while KT
25 * Laplaces it, so they differ by the constant (1 - log(2 pi)/2) - r(N+M), r the
26 * Stirling remainder of a Gamma direction; the common estimator is defined as
27 * the KT value, and the LE side here carries M (1 - log(2 pi)/2) - r(N+M) in
28 * place of pfqn_ble's (M-1)(1 - log(2 pi)/2). See _kb/03-api-layer.md.
29 *
30 * ROUTE. The KT side is an R-dimensional convex solve and an R x R
31 * determinant, the LE side an M-dimensional fixed point and an (M-1) x (M-1)
32 * one, so the KT side is taken when R <= M, and whenever a class self-loops
33 * (one nonzero demand and no think time), which pfqn_kt extracts exactly.
34 *
35 * ARITHMETIC. Inherited from both sides: gated on has_transcendental.
36 */
37
38#include <cmath>
39#include <cstddef>
40#include <string>
41#include <vector>
42
47#include "line/num/number.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace pfqn {
52
53/** Return value of pfqn_lekt, mirroring [Gn, lGn, route]. */
54template <class T>
55struct LektResult {
56 T G;
57 T lG;
58 std::string route; ///< "kt" or "le"
59};
60
61/** "kt" when R <= M or a class self-loops, "le" otherwise. */
62template <class T>
63std::string pfqn_lekt_route(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z) {
64 const T zero = num_traits<T>::from_int(0);
65 const std::size_t M = L.rows(), R = L.cols();
66 bool selfloop = false;
67 if (R > 1) {
68 for (std::size_t r = 0; r < R && !selfloop; ++r) {
69 std::size_t nnz = 0;
70 for (std::size_t i = 0; i < M; ++i)
71 if (L(i, r) != zero) ++nnz;
72 const T z = Z.empty() ? zero : Z[r];
73 if (nnz == 1 && z == zero) selfloop = true;
74 }
75 }
76 return (R <= M || selfloop) ? "kt" : "le";
77}
78
79/**
80 * @brief The common corrected asymptotic expansion (LE-KT), computed on the
81 * cheaper side.
82 *
83 * @param L (M x R) demands, @param N (R) population, @param Z (R) think times
84 * (pass an empty vector or all zeros for the Z = 0 branch)
85 */
86template <class T>
87LektResult<T> pfqn_lekt(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z) {
89 "pfqn_lekt requires transcendental arithmetic (Laplace approximation of an integral)");
90 using std::exp;
91 using std::log;
92 const T zero = num_traits<T>::from_int(0);
93 const std::size_t M = L.rows(), R = L.cols();
94 std::vector<T> Zc = Z;
95 if (Zc.empty()) Zc.assign(R, zero);
96
97 LektResult<T> res;
98 res.route = pfqn_lekt_route(L, N, Zc);
99 if (res.route == "kt") {
100 BktResult<T> k = pfqn_bkt(L, N, Zc);
101 res.G = k.G;
102 res.lG = k.lG;
103 return res;
104 }
105 BleResult<T> b = pfqn_ble(L, N, Zc);
106 res.G = b.G;
107 res.lG = b.lG;
108
109 T Ntot = zero, Lsum = zero, Zsum = zero;
110 for (const T& x : N) Ntot += x;
111 for (const T& x : Zc) Zsum += x;
112 for (std::size_t i = 0; i < M; ++i)
113 for (std::size_t r = 0; r < R; ++r) Lsum += L(i, r);
114 if (M == 0 || N.empty() || Ntot == zero || num_traits<T>::to_double(Lsum) < 1e-4) {
115 return res; // pfqn_ble's degenerate branch: the delay term is exact
116 }
117 const bool no_delay = num_traits<T>::to_double(Zsum) < lang::GlobalConstants::Zero;
118 if (!no_delay) return res;
119 // the Z = 0 branch of pfqn_ble counts M-1 directions; the common estimator
120 // carries M kappa - r(N+M)
121 const T half = num_traits<T>::from_double(0.5);
122 const T twopi = num_traits<T>::from_double(6.283185307179586476925286766559);
123 const T eta = T(Ntot + num_traits<T>::from_int(static_cast<long>(M)));
124 const T kappa = T(num_traits<T>::from_int(1) - log(twopi) / num_traits<T>::from_int(2));
125 const T r = T(detail::num_lgamma<T>(eta) - (eta - half) * log(eta) + eta - half * log(twopi));
126 res.lG += kappa - r;
127 res.G = exp(res.lG);
128 return res;
129}
130
131template <class T>
132LektResult<T> pfqn_lekt(const Matrix<T>& L, const std::vector<T>& N) {
133 return pfqn_lekt(L, N, std::vector<T>());
134}
135
136} // namespace pfqn
137} // namespace line
138
139#endif // LINE_API_PFQN_PFQN_LEKT_H
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Dense matrix and non-owning view.
BktResult< T > pfqn_bkt(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Knessl-Tier expansion with the Stirling-remainder correction (BKT).
Definition pfqn_bkt.h:78
LektResult< T > pfqn_lekt(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
The common corrected asymptotic expansion (LE-KT), computed on the cheaper side.
Definition pfqn_lekt.h:87
std::string pfqn_lekt_route(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
"kt" when R <= M or a class self-loops, "le" otherwise.
Definition pfqn_lekt.h:63
BleResult< T > pfqn_ble(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Logistic expansion estimate of the normalizing constant, bias-corrected.
Definition pfqn_ble.h:56
LeResult< T > BleResult
Return value of pfqn_ble, mirroring [Gn, lGn].
Definition pfqn_ble.h:47
KtResult< T > BktResult
Return value of pfqn_bkt, mirroring [Gn, lGn] (X and Q are pfqn_kt's seeds).
Definition pfqn_bkt.h:58
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
Knessl-Tier expansion with the Stirling-remainder correction (BKT).
Logistic expansion with the eps->0 bias correction (BLE).
static constexpr double Zero
Definition lang_types.h:670
Return value of pfqn_lekt, mirroring [Gn, lGn, route].
Definition pfqn_lekt.h:55
std::string route
"kt" or "le"
Definition pfqn_lekt.h:58