LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_bkt.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_BKT_H
6#define LINE_API_PFQN_PFQN_BKT_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Knessl-Tier expansion with the Stirling-remainder correction (BKT).
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_bkt.m. pfqn_kt extracts N from the
14 * generating function of G by steepest descent on
15 *
16 * F(xi) = sum_r Z_r xi_r - sum_k log(1 - U_k) - sum_r N_r log xi_r, U = L xi.
17 *
18 * On the demand-free integral the exact coefficient is [xi^N] exp(Z xi) = Z^N/N!,
19 * whereas the expansion returns N log Z - (N log N - N + log(2 pi N)/2), which is
20 * Stirling's approximation of log(N!) in place of log(N!). So KT lies ABOVE the
21 * exact value by the remainder
22 *
23 * s(N) = log(N!) - (N log N - N + log(2 pi N)/2)
24 * = lgamma(N+1) - (N + 1/2) log N + N - log(2 pi)/2
25 *
26 * per Laplaced class direction, and BKT subtracts sum_r s(N_r). s(1) is the
27 * constant pfqn_ble adds per STATION direction, and s(N) = 1/(12 N) + O(N^-2);
28 * the remainder is evaluated exactly, since truncating it at 1/(12 N) loses an
29 * order of magnitude (median |error| on the 1562 models of Cas17 sec5.3.1: 0.083
30 * nats for KT, 1.9e-4 for the truncation, 1.4e-5 for the exact remainder). With
31 * a think time BKT is the SAME estimator as pfqn_ble, to the accuracy of the
32 * two saddle-point solvers; without one they differ by the constant
33 * kappa - r(N+M). See _kb/03-api-layer.md.
34 *
35 * WHICH CLASSES. Only the classes pfqn_kt actually Laplaces are corrected: a class
36 * with no jobs is dropped by its recursion, and a self-looping class (one nonzero
37 * demand and no think time, folded only when more than one class remains) has its
38 * coefficient extracted exactly, so neither carries a remainder. The predicate
39 * below is pfqn_kt's own, re-derived here rather than trusting R.
40 *
41 * ARITHMETIC. Inherited from pfqn_kt: gated on num_traits<T>::has_transcendental.
42 */
43
44#include <cmath>
45#include <cstddef>
46#include <vector>
47
50#include "line/num/number.h"
51#include "line/util/matrix.h"
52
53namespace line {
54namespace pfqn {
55
56/** Return value of pfqn_bkt, mirroring [Gn, lGn] (X and Q are pfqn_kt's seeds). */
57template <class T>
59
60/** s(N) = log(N!) - (N log N - N + log(2 pi N)/2), exactly, for N >= 1. */
61template <class T>
64 "pfqn_stirling_remainder requires transcendental arithmetic");
65 using std::log;
66 const T half = num_traits<T>::from_double(0.5);
67 const T twopi = num_traits<T>::from_double(6.283185307179586476925286766559);
68 return T(detail::num_factln<T>(n) - (n + half) * log(n) + n - log(twopi) / num_traits<T>::from_int(2));
69}
70
71/**
72 * @brief Knessl-Tier expansion with the Stirling-remainder correction (BKT).
73 *
74 * @param L (M x R) demands, @param N (R) population, @param Z (R) think times
75 * (pass an empty vector or all zeros for the Z = 0 branch)
76 */
77template <class T>
78BktResult<T> pfqn_bkt(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z) {
80 "pfqn_bkt requires transcendental arithmetic (steepest-descent expansion of log G)");
81 using std::exp;
82 const T zero = num_traits<T>::from_int(0);
83 const std::size_t M = L.rows(), R = L.cols();
84 std::vector<T> Zc = Z;
85 if (Zc.empty()) Zc.assign(R, zero);
86
87 BktResult<T> res = pfqn_kt(L, N, Zc);
88
89 std::size_t nkeep = 0;
90 for (std::size_t r = 0; r < R; ++r)
91 if (N[r] > zero) ++nkeep;
92 T corr = zero;
93 for (std::size_t r = 0; r < R; ++r) {
94 if (!(N[r] > zero)) continue; // dropped by pfqn_kt's recursion
95 if (nkeep > 1) { // pfqn_kt folds self-loops only with Rorig > 1
96 std::size_t nnz = 0;
97 for (std::size_t i = 0; i < M; ++i)
98 if (L(i, r) != zero) ++nnz;
99 if (nnz == 1 && Zc[r] == zero) continue; // extracted exactly, no remainder
100 }
101 corr += pfqn_stirling_remainder<T>(N[r]);
102 }
103 res.lG -= corr;
104 res.G = exp(res.lG);
105 return res;
106}
107
108template <class T>
109BktResult<T> pfqn_bkt(const Matrix<T>& L, const std::vector<T>& N) {
110 return pfqn_bkt(L, N, std::vector<T>());
111}
112
113} // namespace pfqn
114} // namespace line
115
116#endif // LINE_API_PFQN_PFQN_BKT_H
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
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
T pfqn_stirling_remainder(const T &n)
s(N) = log(N!) - (N log N - N + log(2 pi N)/2), exactly, for N >= 1.
Definition pfqn_bkt.h:62
KtResult< T > pfqn_kt(const Matrix< T > &L0, const std::vector< T > &N0, const std::vector< T > &Z0)
Knessl-Tier asymptotic expansion of the normalizing constant.
Definition pfqn_kt.h:71
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 asymptotic expansion of the normalizing constant.
Return value of pfqn_kt, mirroring [G, lG, X, Q].
Definition pfqn_kt.h:56