LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
dqsys_bernoulli1.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_DQSYS_DQSYS_BERNOULLI1_H
6#define LINE_API_DQSYS_DQSYS_BERNOULLI1_H
7
8/**
9 * @file
10 * @ingroup api_dqsys
11 * State dependent Bernoulli server on a discrete time scale.
12 *
13 * Templated port of matlab/src/api/dqsys/dqsys_bernoulli1.m. Time advances in
14 * slots. In the slot starting at t with n jobs present the job in service
15 * departs with probability p(n) and an arrival occurs with probability b(n),
16 * independently; both are recorded at the end of the slot with the departure
17 * resolved first (Daduna's LA rule and D/A rule). The queue length at slot
18 * boundaries is a discrete birth-death chain with
19 *
20 * pi(n) = [prod_{m=0}^{n-1} b(m) / prod_{m=0}^{n} c(m)]
21 * * [prod_{m=1}^{n-1} q(m) / prod_{m=1}^{n} p(m)] / H,
22 *
23 * c = 1-b and q = 1-p, which is theorem 2.3 of Daduna (2001), and corollary 2.8
24 * once b(n) = 0 above the capacity. For constant b and p it collapses to the
25 * Geo/Geo/1 law of `dqsys_geogeo1` under the LAS_DA convention.
26 *
27 * The law seen by an arriving customer, with himself not counted, is theorem
28 * 2.11 and is returned in `arrivalPmf`. It is not the time-stationary law:
29 * discrete time has no PASTA analogue, and the two differ even when the arrival
30 * stream is a state independent Bernoulli process. In that state independent
31 * case pi_1 is exactly the EAS-convention queue length law of `dqsys_geogeo1`,
32 * geometric with ratio r = b(1-p)/(p(1-b)).
33 *
34 * Both laws are built by their exact product recurrences rather than in log
35 * space, so the exact instantiation returns them with no rounding.
36 */
37
38#include <cmath>
39#include <cstddef>
40#include <functional>
41#include <string>
42#include <vector>
43
45#include "line/num/number.h"
46#include "line/util/error.h"
47
48namespace line {
49namespace dqsys {
50
51/** Steady-state quantities of a finite-buffer Bernoulli server. */
52template <class T>
54 std::size_t capacity = 0; ///< buffer capacity in jobs
55 std::vector<T> arrivalProb; ///< offered b(n), n = 0..L
56 std::vector<T> serviceProb; ///< p(n), indexed by n-1
57 std::vector<T> pmf; ///< time-stationary law, theorem 2.3
58 std::vector<T> arrivalPmf; ///< arrival law, theorem 2.11
60 T utilization; ///< 1 - pi(0)
61 T throughput; ///< carried departures per slot
62 T lossProb; ///< fraction of offered arrivals lost
65 T meanSojournTime; ///< in slots, by Little's law
66 T meanWaitingTime; ///< in slots
67 T normConst; ///< H of theorem 2.3
68 std::string analyzer = "dqsys_bernoulli1";
69};
70
71/**
72 * The reference's unbounded-buffer branch (L = Inf), bernoulli1_infinite: the
73 * Geo/Geo/1 closed form under the LAS-DA convention, with the queue-length laws
74 * returned as functions of n as the reference returns handles.
75 */
76template <class T>
80 std::function<T(std::size_t)> pmf; ///< P(N = n), dqsys_geogeo1's pmf
81 std::function<T(std::size_t)> arrivalPmf; ///< (1 - r) r^n, r = b(1-p)/(p(1-b))
85 T lossProb; ///< 0 on an unbounded buffer
90 T normConst; ///< 1 / emptyProb
91 std::string analyzer = "dqsys_bernoulli1";
92};
93
94/**
95 * Unbounded buffer, the reference's default L = Inf: scalar arrival and service
96 * probabilities with b < p.
97 */
98template <class T>
100 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
101 if (b < zero || b > one)
102 throw InputError("dqsys_bernoulli1: arrival probabilities must be real and in [0,1]");
103 if (!(p > zero) || p > one)
104 throw InputError("dqsys_bernoulli1: service probabilities must be real and in (0,1]");
105 if (b >= p)
106 throw InputError("dqsys_bernoulli1: load b/p must be strictly less than 1 on an "
107 "unbounded buffer");
109 const T r = g.ratio;
111 res.arrivalProb = b;
112 res.serviceProb = p;
113 res.pmf = [g](std::size_t n) { return dqsys_geogeo1_pmf(g, static_cast<int>(n)); };
114 res.arrivalPmf = [r, one](std::size_t n) {
115 return T((one - r) * num_pow_int(r, static_cast<unsigned>(n)));
116 };
117 res.emptyProb = g.emptyProb;
118 res.utilization = g.utilization;
119 res.throughput = g.throughput;
120 res.lossProb = zero;
125 res.normConst = one / g.emptyProb;
126 return res;
127}
128
129/** dqsys_bernoulli1(b, p) with the reference's default L = Inf. */
130template <class T>
132 return dqsys_bernoulli1_infinite(b, p);
133}
134
135/**
136 * Finite buffer of L jobs. An arrival in a slot that finds L jobs present is
137 * lost, which is the loss system of corollary 2.8.
138 *
139 * @param b offered arrival probabilities b(n) for n = 0..L, or one entry for a
140 * state independent stream
141 * @param p service probabilities p(n) for n = 1..L, or one entry for a state
142 * independent server
143 * @param L buffer capacity in jobs
144 */
145template <class T>
146Bernoulli1Result<T> dqsys_bernoulli1(const std::vector<T>& b, const std::vector<T>& p,
147 std::size_t L) {
148 if (L < 1) {
149 throw InputError("dqsys_bernoulli1: L must be a positive integer");
150 }
151 const T zero = num_traits<T>::from_int(0);
152 const T one = num_traits<T>::from_int(1);
153
154 std::vector<T> boff(L + 1);
155 if (b.size() == 1) {
156 for (std::size_t n = 0; n <= L; ++n) boff[n] = b[0];
157 } else if (b.size() == L + 1) {
158 boff = b;
159 } else {
160 throw InputError("dqsys_bernoulli1: the arrival probability vector must have one entry "
161 "per state 0..L");
162 }
163 std::vector<T> pv(L);
164 if (p.size() == 1) {
165 for (std::size_t n = 0; n < L; ++n) pv[n] = p[0];
166 } else if (p.size() == L) {
167 pv = p;
168 } else if (p.size() == L + 1) {
169 // A vector of length L+1 is accepted with its first entry, which would
170 // be p(0), ignored.
171 pv.assign(p.begin() + 1, p.end());
172 } else {
173 throw InputError("dqsys_bernoulli1: the service probability vector must have L or L+1 "
174 "entries");
175 }
176 for (std::size_t n = 0; n <= L; ++n) {
177 if (boff[n] < zero || boff[n] > one) {
178 throw InputError("dqsys_bernoulli1: arrival probabilities must lie in [0,1]");
179 }
180 }
181 for (std::size_t n = 0; n < L; ++n) {
182 if (!(pv[n] > zero) || pv[n] > one) {
183 throw InputError("dqsys_bernoulli1: service probabilities must lie in (0,1]");
184 }
185 }
186 std::vector<T> badm = boff;
187 badm[L] = zero; // an arrival finding L jobs is lost
188 for (std::size_t n = 0; n < L; ++n) {
189 if (!(badm[n] < one)) {
190 // c(n)=0 makes the weight of theorem 2.3 diverge at n; p(n)=1 is
191 // fine and truncates the chain instead, which is example 2.9.
192 throw InputError("dqsys_bernoulli1: arrival probabilities below the capacity must be "
193 "strictly less than one");
194 }
195 }
196
197 // Theorem 2.3 by its exact recurrence, u(0) = 1/c(0) and
198 // u(n) = u(n-1) b(n-1) q(n-1) / (c(n) p(n)) with q(0) := 1. The weights are
199 // kept relative to their running maximum, which is the reference's
200 // exp(lw - max(lw)) and keeps the product finite where (b q / (c p))^L
201 // would overflow; the scale is restored in normConst.
202 std::vector<T> u(L + 1, zero);
203 u[0] = one / (one - badm[0]);
204 T scale = one; // the product of every rescaling applied so far
205 const T big = num_traits<T>::from_double(1e150);
206 for (std::size_t n = 1; n <= L; ++n) {
207 const T q = (n >= 2) ? (one - pv[n - 2]) : one;
208 u[n] = u[n - 1] * badm[n - 1] * q / ((one - badm[n]) * pv[n - 1]);
209 if (u[n] > big) {
210 const T f = u[n];
211 for (std::size_t k = 0; k <= n; ++k) u[k] = u[k] / f;
212 scale = scale * f;
213 }
214 }
215 T H = zero;
216 for (std::size_t n = 0; n <= L; ++n) H = H + u[n];
217
219 r.capacity = L;
220 r.arrivalProb = boff;
221 r.serviceProb = pv;
222 r.pmf.resize(L + 1);
223 for (std::size_t n = 0; n <= L; ++n) r.pmf[n] = u[n] / H;
224
225 // Theorem 2.11 by its exact recurrence, v(0) = b(0)/(c(0) c(1)) and
226 // v(n) = v(n-1) b(n) q(n) / (c(n+1) p(n)), again relative to its running
227 // maximum. A vanishing b(n) zeroes every later weight, as the reference's
228 // -Inf log weights do; when every weight vanishes arrivalPmf is EMPTY.
229 std::vector<T> v(L, zero);
230 v[0] = badm[0] / ((one - badm[0]) * (one - badm[1]));
231 for (std::size_t n = 1; n < L; ++n) {
232 v[n] = v[n - 1] * badm[n] * (one - pv[n - 1]) / ((one - badm[n + 1]) * pv[n - 1]);
233 if (v[n] > big) {
234 const T f = v[n];
235 for (std::size_t k = 0; k <= n; ++k) v[k] = v[k] / f;
236 }
237 }
238 T Ha = zero;
239 for (std::size_t n = 0; n < L; ++n) Ha = Ha + v[n];
240 r.arrivalPmf.clear();
241 if (Ha > zero) {
242 r.arrivalPmf.resize(L);
243 for (std::size_t n = 0; n < L; ++n) r.arrivalPmf[n] = v[n] / Ha;
244 }
245
246 r.emptyProb = r.pmf[0];
247 r.utilization = one - r.pmf[0];
248 T q = zero, t = zero, offered = zero;
249 for (std::size_t n = 0; n <= L; ++n) {
250 q = q + r.pmf[n] * num_traits<T>::from_int(static_cast<long>(n));
251 offered = offered + r.pmf[n] * boff[n];
252 if (n >= 1) t = t + r.pmf[n] * pv[n - 1];
253 }
254 r.meanQueueLength = q;
255 r.throughput = t;
256 r.lossProb = (offered > zero) ? (r.pmf[L] * boff[L] / offered) : zero;
258 r.meanSojournTime = (t > zero) ? (q / t) : zero;
259 r.meanWaitingTime = (t > zero) ? (r.meanWaitingQueue / t) : zero;
260 r.normConst = H * scale; // the reference's H * exp(max(lw))
261 return r;
262}
263
264} // namespace dqsys
265} // namespace line
266
267#endif // LINE_API_DQSYS_DQSYS_BERNOULLI1_H
InputError(const std::string &what)
Definition error.h:105
Geo/Geo/1: the discrete-time single-server queue with geometric interarrival and service times.
The exception types the port throws.
T dqsys_geogeo1_pmf(const GeoGeo1Result< T > &r, int n)
Stationary queue-length pmf under the convention of the result.
Bernoulli1InfiniteResult< T > dqsys_bernoulli1(const T &b, const T &p)
dqsys_bernoulli1(b, p) with the reference's default L = Inf.
Bernoulli1InfiniteResult< T > dqsys_bernoulli1_infinite(const T &b, const T &p)
Unbounded buffer, the reference's default L = Inf: scalar arrival and service probabilities with b < ...
GeoGeo1Result< T > dqsys_geogeo1(const T &a, const T &s, GeoConvention convention=GeoConvention::LAS_DA)
Geo/Geo/1: the discrete-time single-server queue with geometric interarrival and service times.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:204
Number-type abstraction for the templated API port.
The reference's unbounded-buffer branch (L = Inf), bernoulli1_infinite: the Geo/Geo/1 closed form und...
std::function< T(std::size_t)> arrivalPmf
(1 - r) r^n, r = b(1-p)/(p(1-b))
std::function< T(std::size_t)> pmf
P(N = n), dqsys_geogeo1's pmf.
Steady-state quantities of a finite-buffer Bernoulli server.
T throughput
carried departures per slot
std::vector< T > serviceProb
p(n), indexed by n-1
std::vector< T > pmf
time-stationary law, theorem 2.3
T meanSojournTime
in slots, by Little's law
std::vector< T > arrivalPmf
arrival law, theorem 2.11
std::size_t capacity
buffer capacity in jobs
std::vector< T > arrivalProb
offered b(n), n = 0..L
T lossProb
fraction of offered arrivals lost
T ratio
r = a(1-s)/(s(1-a))