LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_pbh.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_PBH_H
6#define LINE_API_PFQN_PFQN_PBH_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Performance Bound Hierarchy (Eager and Sevcik 1983, ACM TOCS 1(2):99-115)
12 * for single-class closed product-form networks, and the two iterative
13 * families that are defined in terms of it.
14 *
15 * Templated port of matlab/src/api/pfqn/pfqn_pbh.m, pfqn_pbk.m and
16 * pfqn_bjbk.m. `level` MVA steps from an ABA-initialized residence give nested
17 * optimistic and pessimistic bounds that converge to exact MVA as level -> N.
18 *
19 * MATLAB REDUNDANCY, reproduced rather than hidden: pfqn_pbk.m and
20 * pfqn_bjbk.m are both one-line forwarders to pfqn_pbh with the same
21 * arguments, so PB(k) and BJB(k) return identical numbers for every input.
22 * The two names are kept because the surrounding solver code refers to both,
23 * and collapsing them here would hide the fact in the port.
24 *
25 * ARITHMETIC. The recursion is a finite sequence of field operations -- no
26 * root, no logarithm anywhere -- so these bounds are EXACT in rational
27 * arithmetic and are deliberately left ungated. That is worth having: a bound
28 * violated only by rounding cannot be told apart from a real violation.
29 */
30
31#include <algorithm>
32#include <cstddef>
33#include <vector>
34
35#include "line/num/number.h"
36#include "line/util/error.h"
37
38namespace line {
39namespace pfqn {
40
41/** Return value of pfqn_pbh, mirroring [Xlo, Xhi, Qlo, Qhi]. */
42template <class T>
43struct PbhBounds {
44 T Xlo;
45 T Xhi;
46 std::vector<T> Qlo;
47 std::vector<T> Qhi;
48};
49
50namespace detail {
51
52/** Per-station residence vector of the level-`level` bound, one side. */
53template <class T>
54std::vector<T> pbh_residence(const std::vector<T>& L, int N, const T& Z, int level, bool optimistic) {
55 const std::size_t K = L.size();
56 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
57 std::size_t b = 0;
58 for (std::size_t i = 1; i < K; ++i)
59 if (L[i] > L[b]) b = i;
60 const int lv = std::min(level, N);
61 const int n0 = N - lv;
62 T Lsum = zero;
63 for (const T& x : L) Lsum += x;
64
65 std::vector<T> Rk(K, zero);
66 if (!optimistic) {
67 // Pessimistic start, carried in QUEUE LENGTHS: all n0 customers at the
68 // bottleneck (eq 13), Rk = L.*(1+Q); then the MVA step Rk = L.*(1+Q),
69 // Q = n Rk/(Z + sum Rk) for n = n0+1..N (MATLAB pbh_residence 'pess').
70 std::vector<T> Q(K, zero);
71 Q[b] = num_traits<T>::from_int(n0);
72 for (std::size_t i = 0; i < K; ++i) Rk[i] = T(L[i] * T(one + Q[i]));
73 for (int n = n0 + 1; n <= N; ++n) {
74 for (std::size_t i = 0; i < K; ++i) Rk[i] = T(L[i] * T(one + Q[i]));
75 T Rtot = zero;
76 for (const T& x : Rk) Rtot += x;
77 const T f = T(num_traits<T>::from_int(n) / T(Z + Rtot));
78 for (std::size_t i = 0; i < K; ++i) Q[i] = T(f * Rk[i]);
79 }
80 return Rk;
81 }
82 T asym = T(num_traits<T>::from_int(n0) * L[b] - Z);
83 if (asym < Lsum) asym = Lsum;
84 const T v = T(asym / num_traits<T>::from_int(static_cast<long>(K)));
85 Rk.assign(K, v);
86 if (n0 == 0) Rk.assign(K, zero);
87
88 for (int n = n0 + 1; n <= N; ++n) {
89 T Rtot = zero;
90 for (const T& x : Rk) Rtot += x;
91 if (n == 1 || T(Z + Rtot) == zero) {
92 Rk = L;
93 } else {
94 const T f = T(num_traits<T>::from_int(n - 1) / T(Z + Rtot));
95 for (std::size_t i = 0; i < K; ++i) Rk[i] = T(L[i] * T(one + f * Rk[i]));
96 }
97 }
98 return Rk;
99}
100
101} // namespace detail
102
103/**
104 * @brief Performance Bound Hierarchy (Eager and Sevcik 1983, ACM TOCS
105 * 1(2):99-115) for single-class closed product-form networks, and the
106 * two iterative families that are defined in terms of it.
107 *
108 * @param L (M) per-station demands
109 * @param N population
110 * @param Z think time
111 * @param level hierarchy level >= 0, clamped to N
112 */
113template <class T>
114PbhBounds<T> pfqn_pbh(const std::vector<T>& L, int N, const T& Z, int level) {
115 const std::size_t K = L.size();
116 if (K == 0) throw InputError("pfqn_pbh: empty demand vector");
117 if (N < 0) throw InputError("pfqn_pbh: negative population");
118 if (level < 0) throw InputError("pfqn_pbh: negative hierarchy level");
119 const T zero = num_traits<T>::from_int(0);
120 T Lmax = L[0], Lsum = zero;
121 for (const T& x : L) {
122 Lsum += x;
123 if (x > Lmax) Lmax = x;
124 }
125
126 const std::vector<T> Ro = detail::pbh_residence(L, N, Z, level, true);
127 const std::vector<T> Rp = detail::pbh_residence(L, N, Z, level, false);
128
129 T Rosum = zero, Rpsum = zero;
130 for (const T& x : Ro) Rosum += x;
131 for (const T& x : Rp) Rpsum += x;
132
133 // Joint with the asymptotic residence lower bound R(N) >= max(N Lmax - Z, sum L).
134 T RoC = Rosum;
135 T asym = T(num_traits<T>::from_int(N) * Lmax - Z);
136 if (asym < Lsum) asym = Lsum;
137 if (RoC < asym) RoC = asym;
138
139 PbhBounds<T> r;
140 if (Lmax == zero) throw NumericError(ErrorKind{"Degenerate"}, "pfqn_pbh: all demands are zero");
141 r.Xhi = T(num_traits<T>::from_int(1) / Lmax);
142 const T alt = T(num_traits<T>::from_int(N) / T(Z + RoC));
143 if (alt < r.Xhi) r.Xhi = alt;
144 r.Xlo = T(num_traits<T>::from_int(N) / T(Z + Rpsum));
145
146 r.Qlo.resize(K);
147 r.Qhi.resize(K);
148 for (std::size_t i = 0; i < K; ++i) {
149 r.Qlo[i] = T(r.Xlo * Ro[i]);
150 r.Qhi[i] = T(r.Xhi * Rp[i]);
151 }
152 return r;
153}
154
155template <class T>
156PbhBounds<T> pfqn_pbh(const std::vector<T>& L, int N, const T& Z) {
157 return pfqn_pbh(L, N, Z, 1);
158}
159
160/** PB(k), the iterative Eager-Sevcik proportional bound. Forwards to pfqn_pbh. */
161template <class T>
162PbhBounds<T> pfqn_pbk(const std::vector<T>& L, int N, const T& Z, int k) {
163 return pfqn_pbh(L, N, Z, k);
164}
165
166template <class T>
167PbhBounds<T> pfqn_pbk(const std::vector<T>& L, int N, const T& Z) {
168 return pfqn_pbh(L, N, Z, 1);
169}
170
171/** BJB(k), the iterative Balanced Job Bound. Forwards to pfqn_pbh. */
172template <class T>
173PbhBounds<T> pfqn_bjbk(const std::vector<T>& L, int N, const T& Z, int k) {
174 return pfqn_pbh(L, N, Z, k);
175}
176
177template <class T>
178PbhBounds<T> pfqn_bjbk(const std::vector<T>& L, int N, const T& Z) {
179 return pfqn_pbh(L, N, Z, 1);
180}
181
182} // namespace pfqn
183} // namespace line
184
185#endif // LINE_API_PFQN_PFQN_PBH_H
InputError(const std::string &what)
Definition error.h:105
NumericError(const std::string &what)
Definition error.h:112
The exception types the port throws.
PbhBounds< T > pfqn_pbk(const std::vector< T > &L, int N, const T &Z, int k)
PB(k), the iterative Eager-Sevcik proportional bound.
Definition pfqn_pbh.h:162
PbhBounds< T > pfqn_bjbk(const std::vector< T > &L, int N, const T &Z, int k)
BJB(k), the iterative Balanced Job Bound.
Definition pfqn_pbh.h:173
PbhBounds< T > pfqn_pbh(const std::vector< T > &L, int N, const T &Z, int level)
Performance Bound Hierarchy (Eager and Sevcik 1983, ACM TOCS 1(2):99-115) for single-class closed pro...
Definition pfqn_pbh.h:114
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Return value of pfqn_pbh, mirroring [Xlo, Xhi, Qlo, Qhi].
Definition pfqn_pbh.h:43
std::vector< T > Qlo
Definition pfqn_pbh.h:46
std::vector< T > Qhi
Definition pfqn_pbh.h:47