LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_xia.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_XIA_H
6#define LINE_API_PFQN_XIA_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Xia's asymptotic approximation of the normalizing constant of a
12 * load-dependent (multiserver) closed network.
13 *
14 * Templated port of jar/src/main/java/jline/api/pfqn/ld/Pfqn_xia.java. MATLAB
15 * has no counterpart.
16 *
17 * The demands are first rescaled so that the largest per-server utilization
18 * rho_i = L_i / s_i is one. The stations that attain it are the BOTTLENECK SET
19 * B; they saturate and contribute the M/M/s saturated term, while every other
20 * station contributes its finite-capacity Erlang-like partial sum
21 *
22 * F(u, k) = sum_{j < k} u^j / j! + (u^k / k!) / (1 - u/k),
23 *
24 * the closed form of the geometric tail beyond the k-th server. The result is
25 *
26 * log G ~ -log((B-1)!) - N log(c) + sum_{b in B} [ s_b log L_b - log(s_b!) ]
27 * + sum_{k not in B} log F(L_k, s_k),
28 *
29 * c being the rescaling factor. Note the leading behaviour in N enters ONLY
30 * through -N log c: this is the large-population limit, so the approximation
31 * does not resolve the O(1) corrections that a finite population carries.
32 *
33 * A NON-BOTTLENECK STATION WITH u > k GIVES A NEGATIVE F, whose logarithm is
34 * NaN and poisons the whole constant. The reference guards only against an
35 * INFINITE F (u == k exactly), not a negative one, so it propagates the NaN;
36 * that is reproduced rather than patched, because suppressing the term would
37 * quietly return a plausible number for a model the expansion does not cover.
38 * The condition cannot arise when every station has one server, but it can once
39 * s varies. Note that at Real precision Boost raises on log of a negative where
40 * double returns NaN, so the same input surfaces as an exception rather than a
41 * NaN; both are refusals and neither is a silent wrong number.
42 *
43 * Arithmetic: TRANSCENDENTAL. Logs and factorials throughout.
44 */
45
46#include <cmath>
47#include <cstddef>
48#include <vector>
49
51#include "line/num/number.h"
52#include "line/util/error.h"
53#include "line/util/matrix.h"
54
55namespace line {
56namespace pfqn {
57
58namespace detail {
59
60/** F(u,k) = sum_{j<k} u^j/j! + (u^k/k!)/(1 - u/k). */
61template <class T>
62T pfqn_xia_F(const T& u, const T& k) {
63 const T one = num_traits<T>::from_int(1);
64 const long kk = static_cast<long>(num_traits<T>::to_double(k));
65 T ret = num_traits<T>::from_int(0);
66 T upow = one;
67 T fact = one;
68 for (long j = 0; j < kk; ++j) {
69 ret += T(upow / fact);
70 upow *= u;
71 fact *= num_traits<T>::from_int(j + 1);
72 }
73 // the tail term: u^k / k! / (1 - u/k)
74 const T denom = T(one - T(u / k));
75 return T(ret + T(T(upow / fact) / denom));
76}
77
78} // namespace detail
79
80/**
81 * @brief Xia's asymptotic approximation of the normalizing constant of a
82 * load-dependent (multiserver) closed network.
83 *
84 * @param L (M) service demands
85 * @param N population
86 * @param s (M) server counts
87 * @return log of the approximate normalizing constant
88 */
89template <class T>
90T pfqn_xia(const std::vector<T>& L, int N, const std::vector<T>& s) {
92 "pfqn_xia is an asymptotic expansion in logs and needs transcendental "
93 "arithmetic");
94 using std::log;
95 const std::size_t M = L.size();
96 if (M == 0) throw InputError("pfqn_xia requires at least one station");
97 if (s.size() != M) throw InputError("pfqn_xia: L and s disagree on the station count");
98 const T zero = num_traits<T>::from_int(0);
99 for (std::size_t i = 0; i < M; ++i) {
100 if (L[i] <= zero) throw InputError("pfqn_xia requires positive demands");
101 if (s[i] <= zero) throw InputError("pfqn_xia requires positive server counts");
102 }
103
104 std::vector<T> rho(M);
105 for (std::size_t i = 0; i < M; ++i) rho[i] = T(L[i] / s[i]);
106 T rmax = rho[0];
107 for (std::size_t i = 1; i < M; ++i)
108 if (rho[i] > rmax) rmax = rho[i];
109 const T scalefactor = T(num_traits<T>::from_int(1) / rmax);
110 std::vector<T> Ls(M), rs(M);
111 for (std::size_t i = 0; i < M; ++i) {
112 Ls[i] = T(L[i] * scalefactor);
113 rs[i] = T(rho[i] * scalefactor);
114 }
115 T rsmax = rs[0];
116 for (std::size_t i = 1; i < M; ++i)
117 if (rs[i] > rsmax) rsmax = rs[i];
118
119 std::vector<std::size_t> bnk, nbnk;
120 for (std::size_t i = 0; i < M; ++i) {
121 if (rs[i] == rsmax)
122 bnk.push_back(i);
123 else
124 nbnk.push_back(i);
125 }
126 const std::size_t B = bnk.size();
127
128 T logGasy = T(-detail::num_factln<T>(num_traits<T>::from_int(static_cast<long>(B) - 1)) -
129 num_traits<T>::from_int(N) * log(scalefactor));
130 for (std::size_t b = 0; b < B; ++b) {
131 const std::size_t i = bnk[b];
132 logGasy += s[i] * log(Ls[i]) - detail::num_factln<T>(s[i]);
133 }
134 for (std::size_t k = 0; k < nbnk.size(); ++k) {
135 const std::size_t i = nbnk[k];
136 const T f = detail::pfqn_xia_F(Ls[i], s[i]);
137 const double fd = num_traits<T>::to_double(f);
138 if (std::isfinite(fd)) logGasy += log(f);
139 }
140 return logGasy;
141}
142
143} // namespace pfqn
144} // namespace line
145
146#endif // LINE_API_PFQN_XIA_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Dense matrix and non-owning view.
T pfqn_xia(const std::vector< T > &L, int N, const std::vector< T > &s)
Xia's asymptotic approximation of the normalizing constant of a load-dependent (multiserver) closed n...
Definition pfqn_xia.h:90
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...