LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_scb.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_SCB_H
6#define LINE_API_PFQN_PFQN_SCB_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Dowdy-Carlson-Krantz-Tripathi (1992) single-class bounds of multi-class
12 * queueing networks, J. ACM 39(1):188-213.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_scb.m, pfqn_scbgap.m,
15 * pfqn_usumbound.m and pfqn_minclasses.m.
16 *
17 * SEMANTICS DIFFER FROM EVERY OTHER pfqn_* BOUND IN THIS TREE. aba/bjb/gb/...
18 * bracket the exact solution OF THE GIVEN MODEL; pfqn_scb brackets the
19 * multiclass system that the given single-class model aggregates. Its lower
20 * side is therefore the EXACT single-class solution, not an approximation of
21 * it, and mixing the family into an auto composite would compare two different
22 * quantities.
23 *
24 * ARITHMETIC. pfqn_scb runs the exact single-class MVA recursion (additions,
25 * multiplications and one division per population step) and then scales by a
26 * rational factor, so it stays in the field and is left ungated. The three
27 * combinatorial bounds are pure rational expressions in N, K and r.
28 */
29
30#include <algorithm>
31#include <cstddef>
32#include <vector>
33
34#include "line/num/number.h"
35#include "line/util/error.h"
36
37namespace line {
38namespace pfqn {
39
40/** Return value of pfqn_scb, mirroring [Xlo, Xhi, Ulo, Uhi]. */
41template <class T>
42struct ScbBounds {
43 T Xlo; ///< lower bound on multiclass throughput X_R (= exact X_1)
44 T Xhi; ///< upper bound on X_R
45 std::vector<T> Ulo; ///< (K) lower bounds on the multiclass utilizations U_k,R
46 std::vector<T> Uhi; ///< (K) upper bounds on U_k,R
47};
48
49/**
50 * Bracket on the throughput and the per-device utilizations of the UNKNOWN
51 * multiclass system whose single-class counterpart has demands L at population N.
52 *
53 * Theorem 2 / Corollary 2: aggregating an R-class model into its single-class
54 * counterpart can only understate performance, U_k,1 <= U_k,R and X_1 <= X_R,
55 * and Corollary 1 makes the utilization ratio uniform, U_k,R/U_k,1 = X_R/X_1
56 * for every k. Theorem 3 (their Expression 3) caps the relative throughput
57 * error at (m-1)/(N+m-1), m = min(N,K), independently of the demands. The
58 * single-server capacity U_k,R <= 1 caps the same ratio at 1/(X_1*max(L)),
59 * tight on the paper's own worst case, so both are applied.
60 *
61 * @param L (K) demands of the queueing stations only; a delay station is not
62 * admitted, Theorem 3 resting on the delay-free balanced-network
63 * throughput N/((N+m-1)D)
64 * @param N population (N >= 1)
65 */
66template <class T>
67ScbBounds<T> pfqn_scb(const std::vector<T>& L, long N) {
68 const std::size_t K = L.size();
69 if (K == 0) throw InputError("pfqn_scb: requires at least one queueing station");
70 if (N < 1) throw InputError("pfqn_scb: requires N >= 1");
71 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
72
73 // Exact single-class MVA at Z=0. This IS the lower bound (Theorem 2), so it
74 // is computed exactly rather than bounded: a bounded X1 would not bracket X_R.
75 std::vector<T> Q(K, zero), Rk(K, zero);
76 T X1 = zero;
77 for (long n = 1; n <= N; ++n) {
78 T sumR = zero;
79 for (std::size_t k = 0; k < K; ++k) {
80 Rk[k] = T(L[k] * T(one + Q[k]));
81 sumR += Rk[k];
82 }
83 X1 = T(num_traits<T>::from_int(n) / sumR);
84 for (std::size_t k = 0; k < K; ++k) Q[k] = T(X1 * Rk[k]);
85 }
86
87 const long m = std::min<long>(N, static_cast<long>(K));
88 T ratio = T(num_traits<T>::from_int(N + m - 1) / num_traits<T>::from_int(N));
89 T Dmax = L[0];
90 for (const T& x : L)
91 if (x > Dmax) Dmax = x;
92 if (T(X1 * Dmax) > zero) {
93 // U_k,R <= 1 with the uniform ratio of Corollary 1. Tight at the worst case.
94 const T cap = T(one / T(X1 * Dmax));
95 if (cap < ratio) ratio = cap;
96 }
97
99 b.Xlo = X1;
100 b.Xhi = T(X1 * ratio);
101 b.Ulo.resize(K);
102 b.Uhi.resize(K);
103 for (std::size_t k = 0; k < K; ++k) {
104 b.Ulo[k] = T(X1 * L[k]);
105 b.Uhi[k] = T(b.Ulo[k] * ratio);
106 }
107 return b;
108}
109
110/**
111 * Demand-free bound on the relative throughput error incurred when r of the N
112 * single-customer classes are merged into one class. With r = N this is the
113 * full single-class aggregation error of their Theorem 3, at most 50%; with
114 * r < N it is the partial-aggregation error of their Theorem 4. The bound never
115 * reads the demands, so it can be attached as a certified error bar to any
116 * result computed on merged chains.
117 *
118 * General case, dominating classes allowed (Expression 4, and with r = N
119 * Expression 3): e = (min(r,K)-1)/(r+min(r,K)-1). Undominated case, every
120 * customer placing the same total demand (Theorem 5 and its comment (3), which
121 * lifts the N = R restriction): e = r(r-1)/(min(N,K)(2r-1)), valid for r <= K
122 * only, smaller than the general case by the factor r/min(N,K) and equal to it
123 * at r = K. THE DOMAIN IS NOT COSMETIC: Theorem 5 gives each of its R classes a
124 * dedicated device, so r never exceeds K there, and comment (3) states the
125 * generalization for r < K. Evaluated at r > K the expression climbs past the
126 * general bound and past the 50% cap of Theorem 3, i.e. it stops being a bound,
127 * so r > K is refused rather than returned.
128 *
129 * @param N total customers, one per class @param K devices
130 * @param r classes merged into one (1 <= r <= N)
131 * @param undominated true for the tighter Theorem-5 form, valid only when every
132 * customer's total device demand is equal, and only for r <= K
133 */
134template <class T>
135T pfqn_scbgap(long N, long K, long r, bool undominated) {
136 if (N < 1 || K < 1) throw InputError("pfqn_scbgap: requires N >= 1 and K >= 1");
137 if (r < 1 || r > N) throw InputError("pfqn_scbgap: requires 1 <= r <= N");
138 if (r == 1) return num_traits<T>::from_int(0); // merging one class changes nothing
139 if (undominated) {
140 if (r > K)
141 throw InputError(
142 "pfqn_scbgap: the undominated (Theorem 5) form is defined for r <= K only; "
143 "beyond it the expression exceeds the general bound and the 50% cap");
144 // Theorem 5, comment (3)
145 return T(num_traits<T>::from_int(r * (r - 1)) /
146 num_traits<T>::from_int(std::min<long>(N, K) * (2 * r - 1)));
147 }
148 const long m = std::min<long>(r, K);
149 // Expression (4); r=N gives (3)
150 return T(num_traits<T>::from_int(m - 1) / num_traits<T>::from_int(r + m - 1));
151}
152
153/** Full single-class aggregation: r = N, dominating classes allowed. */
154template <class T>
155T pfqn_scbgap(long N, long K) {
156 return pfqn_scbgap<T>(N, K, N, false);
157}
158
159/**
160 * Largest value the sum of device utilizations can take in any closed
161 * product-form network with R classes, K devices and N customers (Theorem 6):
162 * sum_k U_k,R <= (H-1) + (K-H+1)(N-H+1)/(K+N-2H+1), H = min(R,K). Demand-free
163 * and nondecreasing in R, which is what makes it invertible into a lower bound
164 * on the number of necessary classes; see pfqn_minclasses. The paper's worked
165 * case is K = 2, N = 3, R = 1, giving 2N/(N+1) = 1.5.
166 */
167template <class T>
168T pfqn_usumbound(long R, long K, long N) {
169 if (N < 1 || K < 1) throw InputError("pfqn_usumbound: requires N >= 1 and K >= 1");
170 if (R < 1 || R > N) throw InputError("pfqn_usumbound: requires 1 <= R <= N");
171 const long H = std::min<long>(R, K);
172 return T(num_traits<T>::from_int(H - 1) +
173 T(num_traits<T>::from_int((K - H + 1) * (N - H + 1)) /
174 num_traits<T>::from_int(K + N - 2 * H + 1)));
175}
176
177/**
178 * Smallest number of customer classes R consistent with an observed sum of
179 * device utilizations, by inverting the nondecreasing pfqn_usumbound. Only
180 * measured quantities are needed -- the utilizations, the device count and the
181 * population -- so the answer is available BEFORE any class-specific demand has
182 * been characterized. An upper bound on R is meaningless and none is returned.
183 *
184 * The paper's example: K = 2, N = 3, Usum = 1.6 -> 2, a single class admitting
185 * at most 2N/(N+1) = 1.5.
186 *
187 * @return least R in 1..N with pfqn_usumbound(R,K,N) >= Usum, or -1 where Usum
188 * exceeds min(N,K) and so is unattainable by ANY class structure. -1 is
189 * this port's integral encoding of the NaN MATLAB and Python return.
190 */
191template <class T>
192long pfqn_minclasses(const T& Usum, long K, long N) {
193 if (N < 1 || K < 1) throw InputError("pfqn_minclasses: requires N >= 1 and K >= 1");
194 if (Usum < num_traits<T>::from_int(0))
195 throw InputError("pfqn_minclasses: requires a nonnegative utilization sum");
196 // The reference's relative slack, reproduced so a boundary case decides the
197 // same way in both codebases; in exact arithmetic it only widens by 1e-12.
198 const T one = num_traits<T>::from_int(1);
199 const T scale = Usum > one ? Usum : one;
200 const T tol = T(num_traits<T>::from_double(1e-12) * scale);
201 for (long R = 1; R <= N; ++R)
202 if (!(pfqn_usumbound<T>(R, K, N) < T(Usum - tol))) return R;
203 return -1;
204}
205
206} // namespace pfqn
207} // namespace line
208
209#endif // LINE_API_PFQN_PFQN_SCB_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
ScbBounds< T > pfqn_scb(const std::vector< T > &L, long N)
Bracket on the throughput and the per-device utilizations of the UNKNOWN multiclass system whose sing...
Definition pfqn_scb.h:67
long pfqn_minclasses(const T &Usum, long K, long N)
Smallest number of customer classes R consistent with an observed sum of device utilizations,...
Definition pfqn_scb.h:192
T pfqn_usumbound(long R, long K, long N)
Largest value the sum of device utilizations can take in any closed product-form network with R class...
Definition pfqn_scb.h:168
T pfqn_scbgap(long N, long K, long r, bool undominated)
Demand-free bound on the relative throughput error incurred when r of the N single-customer classes a...
Definition pfqn_scb.h:135
Number-type abstraction for the templated API port.
Return value of pfqn_scb, mirroring [Xlo, Xhi, Ulo, Uhi].
Definition pfqn_scb.h:42
std::vector< T > Ulo
(K) lower bounds on the multiclass utilizations U_k,R
Definition pfqn_scb.h:45
T Xlo
lower bound on multiclass throughput X_R (= exact X_1)
Definition pfqn_scb.h:43
T Xhi
upper bound on X_R
Definition pfqn_scb.h:44
std::vector< T > Uhi
(K) upper bounds on U_k,R
Definition pfqn_scb.h:46