LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_hst.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_HST_H
6#define LINE_API_PFQN_HST_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Operational sensitivity of throughput to homogeneous-service-time (HST)
12 * violations, and the constrained worst case (Suri 1983).
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_hst.m.
15 *
16 * A robustness certificate for a single-class closed product-form solution: how
17 * far the predicted throughput can move when the HST assumption fails at one
18 * station. That assumption states that the mean service time at station i does
19 * not depend on the queue length there. Suri (1983) perturbs it to
20 * S_i(n) = S_i (1 + a_n), one relative deviation per queue-length level n, and
21 * shows (eq. 3.11) that to first order
22 *
23 * (1/X0) dX0/da_n = c_n = P(n_i >= n+1)/u_i - P(n_i >= n),
24 *
25 * with u_i = L_i X0 the station utilization and the marginals taken from the
26 * product-form solution, P(n_i >= n) = L_i^n G(N-n)/G(N). The naive certificate
27 * |dX0/X0| <= (sum_n |c_n|) d follows from |a_n| <= d alone, and by Lemma 3.1
28 * that total equals Q_i(N) - Q_i(N-1).
29 *
30 * That bound is loose because the deviations are not free: an operationally
31 * consistent perturbation must leave the OBSERVED mean service time unchanged,
32 * sum_n p_n a_n = 0 with p_n = P(n_i = n). The constrained problem (P1),
33 *
34 * max |sum_n c_n a_n| s.t. |a_n| <= d, sum_n p_n a_n = 0,
35 *
36 * is a one-constraint linear program, solved here exactly: its optimum sets
37 * a_n = +/- d according to whether the ratio c_n / p_n exceeds a threshold, with
38 * at most one fractional coordinate. On the paper's Figure 1 system it collapses
39 * 0.831 d to 0.102 d.
40 *
41 * Reference: R. Suri, "Robustness of Queuing Network Formulas", JACM
42 * 30(3):564-594, 1983 (eq. 3.11, Lemma 3.1, problem (P1)).
43 *
44 * Arithmetic: TRANSCENDENTAL. The marginals come from pfqn_rgf, whose recursion
45 * is carried in the log domain.
46 */
47
48#include <algorithm>
49#include <cstddef>
50#include <limits>
51#include <numeric>
52#include <vector>
53
55#include "line/num/number.h"
56#include "line/util/error.h"
57
58namespace line {
59namespace pfqn {
60
61/** Everything the HST certificate reports about one station. */
62template <class T>
63struct HstResult {
64 std::size_t station; ///< 0-based index of the station analysed
65 T X; ///< product-form throughput
66 T U; ///< utilization of that station
67 T Q; ///< mean queue length there
68 std::vector<T> Pgeq; ///< P(n_i >= k), k = 0..N
69 std::vector<T> p; ///< P(n_i = k), k = 0..N
70 std::vector<T> c; ///< sensitivity coefficients c_k, k = 1..N (eq. 3.11)
71 T total; ///< sum_k |c_k|, the unconstrained certificate per unit d
72 T worst; ///< the (P1) optimum per unit d
73 std::vector<T> astar; ///< the worst-case deviation profile a_k / d, k = 1..N
74};
75
76/**
77 * @brief Operational sensitivity of throughput to homogeneous-service-time
78 * (HST) violations, and the constrained worst case (Suri 1983).
79 *
80 * @param L (M) service demands of the queueing stations
81 * @param N population, an integer of at least one job
82 * @param Z think time
83 * @param ist 0-based station the HST perturbation is applied to
84 */
85template <class T>
86HstResult<T> pfqn_hst(const std::vector<T>& L, int N, const T& Z, std::size_t ist) {
88 "pfqn_hst reads its marginals from pfqn_rgf and needs transcendental arithmetic");
89 using std::exp;
90 using std::log;
91 const T zero = num_traits<T>::from_int(0);
92 const T one = num_traits<T>::from_int(1);
93 const std::size_t M = L.size();
94 if (M == 0) throw InputError("pfqn_hst requires at least one queueing station");
95 if (ist >= M)
96 throw InputError("pfqn_hst: the station index is out of range for the supplied demands");
97 if (N < 1) throw InputError("pfqn_hst requires an integer population of at least one job");
98 if (L[ist] <= zero)
99 throw InputError(
100 "pfqn_hst: the requested station has zero demand, so its queue-length marginals are "
101 "degenerate");
102
103 const RgfResult<T> rgf = pfqn_rgf(L, N, Z); // lg[k] = log G(k), k = 0..N
104 const std::size_t Np = static_cast<std::size_t>(N);
105
106 HstResult<T> res;
107 res.station = ist;
108 res.X = exp(T(rgf.lg[Np - 1] - rgf.lg[Np]));
109 const T y = L[ist];
110 const T logy = log(y);
111
112 // P(n_i >= k) = y^k G(N-k)/G(N)
113 res.Pgeq.assign(Np + 1, zero);
114 for (std::size_t k = 0; k <= Np; ++k)
115 res.Pgeq[k] = exp(T(num_traits<T>::from_int(static_cast<long>(k)) * logy + rgf.lg[Np - k] -
116 rgf.lg[Np]));
117 res.p.assign(Np + 1, zero);
118 for (std::size_t k = 0; k <= Np; ++k)
119 res.p[k] = T(res.Pgeq[k] - (k + 1 <= Np ? res.Pgeq[k + 1] : zero));
120
121 res.U = T(y * res.X);
122 res.Q = zero;
123 for (std::size_t k = 1; k <= Np; ++k) res.Q += res.Pgeq[k];
124
125 // eq. (3.11): c_n = P(>= n+1)/u - P(>= n), n = 1..N
126 res.c.assign(Np, zero);
127 for (std::size_t n = 1; n <= Np; ++n) {
128 const T Pn1 = (n + 1 <= Np) ? res.Pgeq[n + 1] : zero;
129 res.c[n - 1] = T(T(Pn1 / res.U) - res.Pgeq[n]);
130 }
131 res.total = zero;
132 for (std::size_t k = 0; k < Np; ++k) res.total += (res.c[k] < zero ? T(-res.c[k]) : res.c[k]);
133
134 // (P1): one equality constraint plus a box. At the optimum
135 // a_n = sign(c_n - lambda p_n) d, so sorting by the ratio c_n / p_n and
136 // sweeping the split point enumerates every candidate lambda; the constraint
137 // fixes the single fractional coordinate at the split.
138 std::vector<T> pp(Np);
139 for (std::size_t n = 0; n < Np; ++n) pp[n] = res.p[n + 1];
140 std::vector<std::size_t> ord(Np);
141 std::iota(ord.begin(), ord.end(), static_cast<std::size_t>(0));
142 const T tiny = num_traits<T>::from_double(std::numeric_limits<double>::min());
143 std::vector<T> ratio(Np);
144 for (std::size_t n = 0; n < Np; ++n) ratio[n] = T(res.c[n] / (pp[n] > tiny ? pp[n] : tiny));
145 std::stable_sort(ord.begin(), ord.end(),
146 [&ratio](std::size_t a, std::size_t b) { return ratio[b] < ratio[a]; });
147
148 T best = zero;
149 std::vector<T> astar(Np, zero);
150 for (std::size_t k = 0; k <= Np; ++k) {
151 std::vector<T> a(Np, T(-one));
152 for (std::size_t t = 0; t < k; ++t) a[ord[t]] = one;
153 for (std::size_t piv = 0; piv < Np; ++piv) {
154 if (pp[piv] <= zero) continue;
155 std::vector<T> aa = a;
156 T rest = zero;
157 for (std::size_t n = 0; n < Np; ++n) rest += pp[n] * aa[n];
158 rest -= pp[piv] * aa[piv];
159 const T v = T(-rest / pp[piv]);
160 if (v < T(-one) || v > one) continue;
161 aa[piv] = v;
162 T obj = zero;
163 for (std::size_t n = 0; n < Np; ++n) obj += res.c[n] * aa[n];
164 const T mobj = obj < zero ? T(-obj) : obj;
165 const T mbest = best < zero ? T(-best) : best;
166 if (mobj > mbest) {
167 best = obj;
168 astar = aa;
169 }
170 }
171 }
172 if (best < zero) { // the feasible set is symmetric
173 best = T(-best);
174 for (std::size_t n = 0; n < Np; ++n) astar[n] = T(-astar[n]);
175 }
176 res.worst = best;
177 res.astar = astar;
178 return res;
179}
180
181/** MATLAB default: the bottleneck station, argmax L. */
182template <class T>
183HstResult<T> pfqn_hst(const std::vector<T>& L, int N, const T& Z) {
184 if (L.empty()) throw InputError("pfqn_hst requires at least one queueing station");
185 std::size_t ist = 0;
186 for (std::size_t i = 1; i < L.size(); ++i)
187 if (L[i] > L[ist]) ist = i;
188 return pfqn_hst(L, N, Z, ist);
189}
190
191/** MATLAB default: no think time and the bottleneck station. */
192template <class T>
193HstResult<T> pfqn_hst(const std::vector<T>& L, int N) {
194 return pfqn_hst(L, N, num_traits<T>::from_int(0));
195}
196
197} // namespace pfqn
198} // namespace line
199
200#endif // LINE_API_PFQN_HST_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
HstResult< T > pfqn_hst(const std::vector< T > &L, int N, const T &Z, std::size_t ist)
Operational sensitivity of throughput to homogeneous-service-time (HST) violations,...
Definition pfqn_hst.h:86
RgfResult< T > pfqn_rgf(const std::vector< T > &L, int N, const T &Z)
Recursion by Generating Functions (RGF) for the normalizing constant of a SINGLE-CLASS closed product...
Definition pfqn_rgf.h:91
Number-type abstraction for the templated API port.
Recursion by Generating Functions (RGF) for the normalizing constant of a SINGLE-CLASS closed product...
Everything the HST certificate reports about one station.
Definition pfqn_hst.h:63
T worst
the (P1) optimum per unit d
Definition pfqn_hst.h:72
T U
utilization of that station
Definition pfqn_hst.h:66
std::vector< T > c
sensitivity coefficients c_k, k = 1..N (eq. 3.11)
Definition pfqn_hst.h:70
T total
sum_k |c_k|, the unconstrained certificate per unit d
Definition pfqn_hst.h:71
T X
product-form throughput
Definition pfqn_hst.h:65
std::vector< T > p
P(n_i = k), k = 0..N.
Definition pfqn_hst.h:69
T Q
mean queue length there
Definition pfqn_hst.h:67
std::vector< T > astar
the worst-case deviation profile a_k / d, k = 1..N
Definition pfqn_hst.h:73
std::vector< T > Pgeq
P(n_i >= k), k = 0..N.
Definition pfqn_hst.h:68
std::size_t station
0-based index of the station analysed
Definition pfqn_hst.h:64
Return value of pfqn_rgf, mirroring [G, lG, lg].
Definition pfqn_rgf.h:56
std::vector< T > lg
log g(0), log g(1), ..., log g(N)
Definition pfqn_rgf.h:59