LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
lossn_rec.h
Go to the documentation of this file.
1#ifndef LINE_API_LOSSN_LOSSN_REC_H
2#define LINE_API_LOSSN_LOSSN_REC_H
3
4/**
5 * @file lossn_rec.h
6 * @ingroup api_lossn
7 * @brief Exact analysis of a loss network by MDD-rec.
8 *
9 * The normalising constant is the sum of a product form over the admissible set
10 * {n >= 0 : A n <= C}, which is what a decision diagram holding that set
11 * computes in one memoised walk.
12 *
13 * A Kelly loss network carries offered load nu_r on route r and admits a call
14 * only while the resource constraint A n <= C still holds after it. The
15 * stationary law is the truncation of independent Poisson counts to that set,
16 *
17 * P(n) = (1/G) prod_r nu_r^{n_r} / n_r!, G = sum_{A n <= C} prod_r ...,
18 *
19 * so g_r(k) = nu_r^k/k! and `mdd_rec` returns G. By PASTA the acceptance
20 * probability of a class-r call is the ratio of two such constants,
21 *
22 * 1 - B_r = G(C - A e_r) / G(C),
23 *
24 * which is one further diagram per class.
25 *
26 * WHY THIS EXISTS ALONGSIDE `lossn_manjunath` AND `lossn_erlangfp`. The
27 * Manjunath-Sikdar transform evaluates G exactly as a multidimensional residue,
28 * and the residue argument counts WHOLE UNITS: it needs an integral A and C.
29 * This port's `lossn_erlangfp` needs integrality too, for its own reason -- it
30 * raises (1-E_i) to an unsigned integer power -- so before MDD-rec a FRACTIONAL
31 * region had no route here at all except the Monte Carlo `lossn_mci`, whose
32 * answer is a random variable. MDD-rec needs only that the admissible set be
33 * finite and bounded coordinate by coordinate, which a fractional constraint
34 * still is, so it is exact there too and is the default the fractional case now
35 * takes.
36 *
37 * ARITHMETIC. Everything here is a sum, a product and one factorial, so the
38 * whole method is rational and available under exact arithmetic; only the
39 * reported `lG` needs a logarithm, and it is a `double` diagnostic rather than a
40 * `T`, exactly as the other analyzers treat it.
41 *
42 * References:
43 * F. P. Kelly, "Loss networks", Annals of Applied Probability 1(3), 1991.
44 * S. Balsamo, A. Marin, I. Stojic, "Computation of the normalising constant
45 * for product-form models of distributed systems with synchronisation",
46 * Future Generation Computer Systems 111 (2020) 475-490.
47 *
48 * @see lossn_manjunath, lossn_erlangfp, lossn_mci, mdd_rec
49 */
50
51#include <cmath>
52#include <cstddef>
53#include <limits>
54#include <string>
55#include <vector>
56
59#include "line/util/error.h"
60#include "line/util/matrix.h"
61#include "line/num/number.h"
62
63namespace line {
64namespace lossn {
65
66/** Carried load, blocking, log normalising constant and walk count. */
67template <class T>
69 /** Mean number of class-r calls in progress, the carried load. */
70 std::vector<T> QLen;
71 /** Blocking probability per class. */
72 std::vector<T> Loss;
73 /** Normalising constant G(C). */
74 T G;
75 /** log G(C), a double diagnostic. */
76 double lG = 0.0;
77 /** Number of diagram walks performed, K + 1. */
78 int iterations = 0;
79};
80
81namespace detail {
82
83/**
84 * The admissible set {n >= 0 : A n <= C}, generated one call at a time from the
85 * empty network. Adding a call is the only move, so the breadth-first closure
86 * visits exactly the admissible vectors.
87 */
88template <class T>
89mdd::MddStruct lossn_rec_diagram(const Matrix<T>& A, const std::vector<T>& C,
90 const std::vector<int>& bound) {
91 const std::size_t K = bound.size();
92 const std::size_t J = C.size();
93 std::vector<int> domain(K);
94 for (std::size_t r = 0; r < K; ++r) domain[r] = bound[r] + 1;
95
96 const mdd::MddNextState nextfun = [&A, &C, &bound, K, J](const std::vector<int>& s) {
97 std::vector<std::vector<int>> out;
98 for (std::size_t r = 0; r < K; ++r) {
99 if (s[r] >= bound[r]) continue;
100 std::vector<int> t = s;
101 ++t[r];
102 bool ok = true;
103 for (std::size_t j = 0; j < J && ok; ++j) {
105 for (std::size_t q = 0; q < K; ++q)
106 sum += T(A(j, q) * num_traits<T>::from_int(t[q]));
107 ok = !(sum > C[j]);
108 }
109 if (ok) out.push_back(t);
110 }
111 return out;
112 };
113 mdd::MDD diagram = mdd::mdd_reachset(domain, std::vector<int>(K, 0), nextfun);
114 return diagram.to_struct();
115}
116
117/**
118 * G over the admissible set at capacity C, keeping the per-class domains of the
119 * FULL problem so that one set of factors g serves every reduced capacity.
120 */
121template <class T>
122T lossn_rec_G(const Matrix<T>& A, const std::vector<T>& C, const std::vector<int>& bound,
123 const std::vector<std::vector<T>>& g) {
124 const T zero = num_traits<T>::from_int(0);
125 for (std::size_t j = 0; j < C.size(); ++j)
126 if (C[j] < zero) return zero;
127 return mdd::mdd_rec<T>(lossn_rec_diagram<T>(A, C, bound), g);
128}
129
130} // namespace detail
131
132/**
133 * Exact loss-network analysis by MDD-rec.
134 *
135 * @param nu offered load per class, length K
136 * @param A J x K non-negative resource requirement matrix
137 * @param C capacity vector, length J
138 * @return the carried load, the blocking probabilities and the normalising constant
139 */
140template <class T>
141LossnRecResult<T> lossn_rec(const std::vector<T>& nu, const Matrix<T>& A,
142 const std::vector<T>& C) {
143 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
144 const std::size_t K = nu.size();
145 const std::size_t J = C.size();
146 if (A.cols() != K)
147 throw InputError("lossn_rec: A has " + std::to_string(A.cols()) +
148 " columns but there are " + std::to_string(K) + " classes");
149 if (A.rows() != J)
150 throw InputError("lossn_rec: A has " + std::to_string(A.rows()) + " rows but C has " +
151 std::to_string(J) + " entries");
152 for (std::size_t j = 0; j < J; ++j)
153 for (std::size_t r = 0; r < K; ++r)
154 if (A(j, r) < zero)
155 throw InputError("lossn_rec: the resource matrix A must be non-negative");
156
157 // ---- per-class bound: the most calls the tightest constraint alone admits
158 std::vector<int> bound(K, 0);
159 for (std::size_t r = 0; r < K; ++r) {
160 double b = std::numeric_limits<double>::infinity();
161 for (std::size_t j = 0; j < J; ++j)
162 if (A(j, r) > zero)
163 b = std::min(b, std::floor(num_traits<T>::to_double(C[j]) /
164 num_traits<T>::to_double(A(j, r))));
165 if (!std::isfinite(b))
166 throw InputError("lossn_rec: class " + std::to_string(r + 1) +
167 " consumes no resource, so the admissible set is unbounded in that "
168 "coordinate and its normalising constant diverges");
169 bound[r] = static_cast<int>(std::max(0.0, b));
170 }
171
172 std::vector<std::vector<T>> g(K);
173 for (std::size_t r = 0; r < K; ++r) {
174 g[r].assign(bound[r] + 1, one);
175 T fact = one, pw = one;
176 for (int k = 0; k <= bound[r]; ++k) {
177 if (k > 0) {
178 fact = T(fact * num_traits<T>::from_int(k));
179 pw = T(pw * nu[r]);
180 }
181 g[r][k] = T(pw / fact);
182 }
183 }
184
185 const T G = detail::lossn_rec_G<T>(A, C, bound, g);
186 if (!(G > zero))
187 throw InputError("lossn_rec: the admissible set is empty: no call of any class fits "
188 "within C");
189
190 // ---- carried load per class, from the marginals of the same diagram
191 const mdd::MddStruct mdds = detail::lossn_rec_diagram<T>(A, C, bound);
193 out.G = G;
194 out.QLen.assign(K, zero);
195 out.Loss.assign(K, zero);
196 for (std::size_t r = 0; r < K; ++r) {
197 const std::vector<T> pk = mdd::mdd_rec_marginal<T>(mdds, g, r);
198 T s = zero;
199 for (std::size_t k = 0; k < pk.size(); ++k)
200 s += T(num_traits<T>::from_int(static_cast<int>(k)) * pk[k] / G);
201 out.QLen[r] = s;
202 }
203
204 // ---- blocking: 1 - B_r = G(C - A e_r)/G(C), Kelly's ratio, by PASTA
205 for (std::size_t r = 0; r < K; ++r) {
206 std::vector<T> Cr(J, zero);
207 bool fits = true;
208 for (std::size_t j = 0; j < J; ++j) {
209 Cr[j] = T(C[j] - A(j, r));
210 if (Cr[j] < zero) fits = false;
211 }
212 if (!fits) {
213 out.Loss[r] = one; // the call never fits
214 continue;
215 }
216 const T Gr = detail::lossn_rec_G<T>(A, Cr, bound, g);
217 out.Loss[r] = T(one - Gr / G);
218 if (out.Loss[r] < zero) out.Loss[r] = zero;
219 if (out.Loss[r] > one) out.Loss[r] = one;
220 }
221
222 if constexpr (num_traits<T>::has_transcendental) {
223 out.lG = std::log(num_traits<T>::to_double(G));
224 } else {
225 out.lG = std::log(num_traits<T>::to_double(G));
226 }
227 out.iterations = static_cast<int>(K) + 1;
228 return out;
229}
230
231} // namespace lossn
232} // namespace line
233
234#endif // LINE_API_LOSSN_LOSSN_REC_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The diagram: insert / member / index / enumerate / cardinality.
Definition mdd.h:97
MddStruct to_struct() const
Export the diagram as plain arrays for downstream algorithms.
Definition mdd.h:181
The exception types the port throws.
Dense matrix and non-owning view.
Reachability set generation into a decision diagram.
MDD-rec: the normalising constant of a product-form model whose reachable set is held in a decision d...
LossnRecResult< T > lossn_rec(const std::vector< T > &nu, const Matrix< T > &A, const std::vector< T > &C)
Exact loss-network analysis by MDD-rec.
Definition lossn_rec.h:141
std::function< std::vector< std::vector< int > >(const std::vector< int > &)> MddNextState
Successor function over local indices, for mdd_reachset.
Definition mdd_types.h:155
T mdd_rec(const MddStruct &mdds, const std::vector< std::vector< T > > &g)
The normalising constant G = sum_{s in S} prod_l g_l(s_l).
Definition mdd_rec.h:139
MDD mdd_reachset(const std::vector< int > &domain, const std::vector< int > &init, const MddNextState &nextfun)
Generate and store the reachability set into a quasi-reduced ordered MDD.
std::vector< T > mdd_rec_marginal(const MddStruct &mdds, const std::vector< std::vector< T > > &g, std::size_t l)
Unnormalised masses of {s in S : s_l = k}, one per local value k of level l.
Definition mdd_rec.h:150
Number-type abstraction for the templated API port.
Carried load, blocking, log normalising constant and walk count.
Definition lossn_rec.h:68
double lG
log G(C), a double diagnostic.
Definition lossn_rec.h:76
T G
Normalising constant G(C).
Definition lossn_rec.h:74
std::vector< T > Loss
Blocking probability per class.
Definition lossn_rec.h:72
std::vector< T > QLen
Mean number of class-r calls in progress, the carried load.
Definition lossn_rec.h:70
int iterations
Number of diagram walks performed, K + 1.
Definition lossn_rec.h:78
Plain-array export of an MDD, the input contract of mdd_mcd.
Definition mdd.h:57