5#ifndef LINE_API_LOSSN_LOSSN_MANJUNATH_H
6#define LINE_API_LOSSN_LOSSN_MANJUNATH_H
123inline long lossn_gcd(
long a,
long b) {
125 const long t = a % b;
129 return a < 0 ? -a : a;
140struct LossnManjunathRule {
141 std::vector<std::vector<long>> A;
154LossnManjunathRule lossn_manjunath_integralize(
const Matrix<T>& A,
const std::vector<T>& C) {
155 const std::size_t J = C.size(), R = A.cols();
156 LossnManjunathRule rule;
157 for (std::size_t j = 0; j < J; ++j) {
158 const double c = num_traits<T>::to_double(C[j]);
159 if (c < 0.0 || std::fabs(c - std::round(c)) > 1e-9)
161 "lossn_manjunath: C must contain nonnegative integers -- the residue argument counts "
162 "whole units of capacity. Use lossn_mci, which compares in real arithmetic, or "
165 std::vector<long> row(R, 0);
166 for (std::size_t r = 0; r < R; ++r) {
167 const double a = num_traits<T>::to_double(A(j, r));
168 if (a < 0.0 || std::fabs(a - std::round(a)) > 1e-9)
170 "lossn_manjunath: A must contain nonnegative integers -- the residue argument counts "
171 "whole units of capacity. Use lossn_mci, which compares in real arithmetic, or "
173 row[r] = std::lround(a);
174 if (row[r] != 0) any =
true;
180 long g = std::lround(c);
181 for (std::size_t r = 0; r < R; ++r)
182 if (row[r] != 0) g = lossn_gcd(g, row[r]);
184 for (std::size_t r = 0; r < R; ++r) row[r] /= g;
185 rule.C.push_back(std::lround(c) / g);
187 rule.C.push_back(std::lround(c));
189 rule.A.push_back(row);
205T lossn_manjunath_series(
const std::vector<std::vector<T>>& f,
const std::vector<std::vector<long>>& A,
206 const std::vector<long>& C,
const std::vector<long>& nmaxFull,
207 const LossnManjunathOptions& options, std::size_t& peak) {
208 const std::size_t R = f.size(), J = C.size();
209 const T zero = num_traits<T>::from_int(0);
213 std::vector<std::size_t> first(J, 0), last(J, 0);
214 for (std::size_t j = 0; j < J; ++j) {
216 for (std::size_t r = 0; r < R; ++r) {
217 if (A[j][r] == 0)
continue;
225 throw NumericError(
"lossn_manjunath: a constraint row with no nonzero entry reached the "
226 "series; the rule was not reduced");
229 std::vector<std::size_t> curdim(J, 1);
230 std::vector<T> ser(1, num_traits<T>::from_int(1));
232 for (std::size_t r = 0; r < R; ++r) {
234 for (std::size_t j = 0; j < J; ++j) {
235 if (first[j] != r)
continue;
236 const std::size_t newdim =
static_cast<std::size_t
>(C[j]) + 1;
237 std::size_t pre = 1, post = 1;
238 for (std::size_t k = 0; k < j; ++k) pre *= curdim[k];
239 for (std::size_t k = j + 1; k < J; ++k) post *= curdim[k];
240 if (newdim != 0 && pre * post > options.max_live_states / newdim)
242 "lossn_manjunath: the exact transform would hold more than " +
243 std::to_string(options.max_live_states) +
244 " series coefficients at once. Peak memory is the product of (C_j+1) over the "
245 "links live at the same time, so a wide constraint row with a large capacity "
246 "is what costs; raise LossnManjunathOptions::max_live_states deliberately, or use "
247 "lossn_mci, which is unbiased at any size");
248 std::vector<T> grown(pre * newdim * post, zero);
251 for (std::size_t q = 0; q < post; ++q)
252 for (std::size_t p = 0; p < pre; ++p)
253 grown[p + q * pre * newdim] = ser[p + q * pre];
256 if (ser.size() > peak) peak = ser.size();
260 bool constrained =
false;
261 for (std::size_t j = 0; j < J; ++j)
262 if (A[j][r] != 0) constrained =
true;
270 for (
long n = 0; n <= nmaxFull[r] && static_cast<std::size_t>(n) < f[r].size(); ++n)
271 s += f[r][
static_cast<std::size_t
>(n)];
272 for (T& v : ser) v *= s;
279 long nmax = std::min<long>(nmaxFull[r],
static_cast<long>(f[r].size()) - 1);
280 for (std::size_t j = 0; j < J; ++j)
281 if (A[j][r] > 0) nmax = std::min<long>(nmax, C[j] / A[j][r]);
283 std::vector<std::size_t> stride(J, 1);
284 for (std::size_t k = 1; k < J; ++k) stride[k] = stride[k - 1] * curdim[k - 1];
285 const std::size_t P = ser.size();
287 std::vector<T> next(P, zero);
288 std::vector<std::size_t> sub(J, 0);
289 for (
long n = 0; n <= nmax; ++n) {
290 const T c = f[r][
static_cast<std::size_t
>(n)];
291 if (c == zero)
continue;
293 for (std::size_t i = 0; i < P; ++i) next[i] += T(c * ser[i]);
300 std::fill(sub.begin(), sub.end(),
static_cast<std::size_t
>(0));
302 for (std::size_t i = 0; i < P; ++i) {
305 for (std::size_t j = 0; j < J && ok; ++j) {
306 const std::size_t d = sub[j] +
static_cast<std::size_t
>(A[j][r] * n);
307 if (
static_cast<long>(d) > C[j])
310 tgt += d * stride[j];
313 next[tgt] += T(c * ser[i]);
318 for (std::size_t j = 0; j < J; ++j) {
319 if (++sub[j] < curdim[j])
break;
332 for (std::size_t j = 0; j < J; ++j) {
333 if (last[j] != r)
continue;
334 std::size_t pre = 1, post = 1;
335 for (std::size_t k = 0; k < j; ++k) pre *= curdim[k];
336 for (std::size_t k = j + 1; k < J; ++k) post *= curdim[k];
337 const std::size_t dj = curdim[j];
338 std::vector<T> summed(pre * post, zero);
339 for (std::size_t q = 0; q < post; ++q)
340 for (std::size_t d = 0; d < dj; ++d)
341 for (std::size_t p = 0; p < pre; ++p)
342 summed[p + q * pre] += ser[p + d * pre + q * pre * dj];
349 throw NumericError(
"lossn_manjunath: a link was never integrated out; the elimination order is "
350 "inconsistent with the constraint rows");
367 const std::size_t R = nu.size();
368 if (A.
cols() != R || A.
rows() != C.size())
369 throw InputError(
"lossn_manjunath: A must be J x R, matching C and nu");
371 for (std::size_t r = 0; r < R; ++r)
372 if (nu[r] < zero)
throw InputError(
"lossn_manjunath: nu must be nonnegative");
375 out.
QLen.assign(R, zero);
376 out.
Loss.assign(R, zero);
378 const detail::LossnManjunathRule rule = detail::lossn_manjunath_integralize(A, C);
379 const std::size_t J = rule.C.size();
384 std::vector<bool> freeRoute(R,
true);
385 for (std::size_t j = 0; j < J; ++j)
386 for (std::size_t r = 0; r < R; ++r)
387 if (rule.A[j][r] != 0) freeRoute[r] =
false;
391 for (std::size_t r = 0; r < R; ++r) {
399 if (J == 0 || allFree) {
405 std::vector<long> nmax(R, 0);
406 std::vector<std::vector<T>> f(R, std::vector<T>(1, one));
407 double logscale = 0.0;
408 for (std::size_t r = 0; r < R; ++r) {
409 if (freeRoute[r])
continue;
410 long v = std::numeric_limits<long>::max();
411 for (std::size_t j = 0; j < J; ++j)
412 if (rule.A[j][r] > 0) v = std::min<long>(v, rule.C[j] / rule.A[j][r]);
414 const std::size_t len =
static_cast<std::size_t
>(v) + 1;
420 f[r].assign(len, zero);
427 f[r].assign(len, zero);
429 for (std::size_t n = 1; n < len; ++n)
437 const T lnu = log(nu[r]);
438 std::vector<T> lf(len, zero);
439 T lfact = zero, m = zero;
440 for (std::size_t n = 0; n < len; ++n) {
443 if (n == 0 || lf[n] > m) m = lf[n];
445 f[r].assign(len, zero);
446 for (std::size_t n = 0; n < len; ++n) f[r][n] = exp(T(lf[n] - m));
451 const T G = detail::lossn_manjunath_series(f, rule.A, rule.C, nmax, options, out.
peak_states);
454 "lossn_manjunath: the admissible set is empty -- no state satisfies A n <= C, so the loss "
455 "network has no stationary distribution");
458 for (std::size_t r = 0; r < R; ++r) {
459 if (freeRoute[r])
continue;
460 std::vector<long> Cr = rule.C;
461 bool overflows =
false;
462 for (std::size_t j = 0; j < J; ++j) {
463 Cr[j] -= rule.A[j][r];
464 if (Cr[j] < 0) overflows =
true;
473 const T Gr = detail::lossn_manjunath_series(f, rule.A, Cr, nmax, options, out.
peak_states);
475 const T ratio = T(Gr / G);
476 out.
QLen[r] = T(nu[r] * ratio);
477 out.
Loss[r] = T(one - ratio);
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
The exception types the port throws.
Dense matrix and non-owning view.
LossnManjunathResult< T > lossn_manjunath(const std::vector< T > &nu, const Matrix< T > &A, const std::vector< T > &C, const LossnManjunathOptions &options=LossnManjunathOptions())
Exact normalizing constant, carried load and blocking of a loss network.
Number-type abstraction for the templated API port.
Controls of lossn_manjunath.
std::size_t max_live_states
Cap on the product of (C_j+1) over the simultaneously live links, i.e.
Result of lossn_manjunath.
std::size_t peak_states
Peak number of live series coefficients, the realised cost.
double lG
log of the EXACT normalizing constant g(C)
std::vector< T > QLen
mean carried load E[n_r] per route
std::vector< T > Loss
blocking probability per route
std::size_t iterations
Always 1: the transform is direct, and the field exists for the shared analyzer contract that the ite...