LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_cbh.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_CBH_H
6#define LINE_API_PFQN_PFQN_CBH_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Convolutional Bound Hierarchy (Dowdy, Eager, Gordon and Saxton 1984) on the
12 * throughput of a single-class closed product-form network.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_cbh.m. Column c = M - level of
15 * Buzen's g array is filled from a Balanced Job Bound estimate of the first c
16 * stations (e_0 = 1, e_i = e_{i-1}/B_i), the remaining `level` stations are
17 * convolved exactly, and the infinite-server delay is convolved exactly as
18 * well. The BJB upper fill yields the upper bound, the lower fill the lower
19 * bound, and both meet the exact solution at level = M.
20 *
21 * ARITHMETIC. Every step is an addition, a multiplication or a division of
22 * field elements -- the balanced fill uses the arithmetic mean and the maximum
23 * of the demands, not a root or a logarithm -- so the bound is EXACT in
24 * rational arithmetic and is deliberately left ungated. The only non-rational
25 * ingredient, Z^j/j!, is a rational for rational Z.
26 */
27
28#include <algorithm>
29#include <cstddef>
30#include <vector>
31
33#include "line/num/number.h"
34#include "line/util/error.h"
35
36namespace line {
37namespace pfqn {
38
39/** Return value of pfqn_cbh, mirroring [Xlo, Xhi]. */
40template <class T>
41struct CbhBounds {
42 T Xlo;
43 T Xhi;
44};
45
46namespace detail {
47
48/** One side of the hierarchy: BJB-filled column c, then exact convolution. */
49template <class T>
50T cbh_hier(const std::vector<T>& L, int N, const T& Z, std::size_t c, bool upper) {
51 const std::size_t M = L.size();
52 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
53 T Rc = zero, Lbc = L[0];
54 for (std::size_t i = 0; i < c; ++i) {
55 Rc += L[i];
56 if (L[i] > Lbc) Lbc = L[i];
57 }
58 const T Lac = T(Rc / num_traits<T>::from_int(static_cast<long>(c)));
59
60 std::vector<T> g(static_cast<std::size_t>(N) + 1, zero);
61 if (c == 1) {
62 // The single-server column is already exact: g(n) = L_1^n.
63 for (int n = 0; n <= N; ++n) g[static_cast<std::size_t>(n)] = num_pow_int(L[0], static_cast<unsigned>(n));
64 } else {
65 g[0] = one;
66 for (int i = 1; i <= N; ++i) {
67 const T iT = num_traits<T>::from_int(i);
68 const T den = upper ? T(Rc + T(iT - one) * Lac) : T(Rc + T(iT - one) * Lbc);
69 const T B = T(iT / den); // BJB fill; upper fill -> upper bound
70 if (B == zero) throw NumericError("pfqn_cbh: zero balanced-job-bound fill");
71 g[static_cast<std::size_t>(i)] = T(g[static_cast<std::size_t>(i) - 1] / B);
72 }
73 }
74 for (std::size_t m = c; m < M; ++m)
75 for (int n = 1; n <= N; ++n)
76 g[static_cast<std::size_t>(n)] += L[m] * g[static_cast<std::size_t>(n) - 1];
77
78 if (Z > zero) {
79 std::vector<T> gd(static_cast<std::size_t>(N) + 1, zero);
80 // Poisson weight Z^j/j!. In double the naive ratio overflows for j >~ 171
81 // and j runs to the POPULATION; an exact T cannot overflow and keeps it.
82 for (int j = 0; j <= N; ++j) {
84 using std::exp;
85 using std::log;
86 const T jT = num_traits<T>::from_int(j);
87 gd[static_cast<std::size_t>(j)] = T(exp(jT * log(Z) - detail::num_factln<T>(jT)));
88 } else {
89 gd[static_cast<std::size_t>(j)] =
90 T(num_pow_int(Z, static_cast<unsigned>(j)) / num_factorial<T>(static_cast<unsigned>(j)));
91 }
92 }
93 std::vector<T> gfull(static_cast<std::size_t>(N) + 1, zero);
94 for (int n = 0; n <= N; ++n) {
95 T acc = zero;
96 for (int j = 0; j <= n; ++j)
97 acc += g[static_cast<std::size_t>(j)] * gd[static_cast<std::size_t>(n - j)];
98 gfull[static_cast<std::size_t>(n)] = acc;
99 }
100 g = gfull;
101 }
102 if (g[static_cast<std::size_t>(N)] == zero) throw NumericError("pfqn_cbh: zero normalizing constant");
103 return T(g[static_cast<std::size_t>(N) - 1] / g[static_cast<std::size_t>(N)]);
104}
105
106} // namespace detail
107
108/**
109 * @brief Convolutional Bound Hierarchy (Dowdy, Eager, Gordon and Saxton 1984)
110 * on the throughput of a single-class closed product-form network.
111 *
112 * @param L (M) per-station demands
113 * @param N population
114 * @param Z think time
115 * @param level number of exactly convolved stations, clamped to [1, M]
116 */
117template <class T>
118CbhBounds<T> pfqn_cbh(const std::vector<T>& L, int N, const T& Z, int level) {
119 const std::size_t M = L.size();
120 if (M == 0) throw InputError("pfqn_cbh: empty demand vector");
121 if (N < 1) throw InputError("pfqn_cbh: population must be at least one");
122 int lv = std::max(1, std::min(level, static_cast<int>(M)));
123 const std::size_t c = static_cast<std::size_t>(std::max(1, static_cast<int>(M) - lv));
124 CbhBounds<T> r;
125 r.Xlo = detail::cbh_hier(L, N, Z, c, false);
126 r.Xhi = detail::cbh_hier(L, N, Z, c, true);
127 return r;
128}
129
130template <class T>
131CbhBounds<T> pfqn_cbh(const std::vector<T>& L, int N, const T& Z) {
132 return pfqn_cbh(L, N, Z, 2);
133}
134
135} // namespace pfqn
136} // namespace line
137
138#endif // LINE_API_PFQN_PFQN_CBH_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.
CbhBounds< T > pfqn_cbh(const std::vector< T > &L, int N, const T &Z, int level)
Convolutional Bound Hierarchy (Dowdy, Eager, Gordon and Saxton 1984) on the throughput of a single-cl...
Definition pfqn_cbh.h:118
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:184
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:192
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
Return value of pfqn_cbh, mirroring [Xlo, Xhi].
Definition pfqn_cbh.h:41