LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_xmax_erlang.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_FJ_XMAX_ERLANG_H
6#define LINE_API_FJ_XMAX_ERLANG_H
7
8/**
9 * @file
10 * @ingroup api_fj
11 * Expected maximum of K i.i.d. Erlang-k service times.
12 *
13 * Templated port of matlab/src/api/fj/fj_xmax_erlang.m, cross-checked against
14 * FJ_xmax.fj_xmax_erlang in jar/src/main/java/jline/api/fj/FJ_xmax.java
15 * (identical formulas; the JAR replaces MATLAB's adaptive `integral` with a
16 * 10001-point composite Simpson rule, which this port also does).
17 *
18 * MIXED ARITHMETIC. At k = 2 the MATLAB file uses the closed form
19 *
20 * X_K^max = (1/mu) sum_{n=1..K} C(K,n) (-1)^{n-1} sum_{m=1..n} C(n,m) m! / (2 n^{m+1})
21 *
22 * which is rational and therefore exact in any field, cancellation included:
23 * the alternating outer sum is exactly the kind that double cannot carry past
24 * about K = 25. For any other k the mean is a quadrature of 1 - F(t)^K against
25 * the Erlang CDF and needs exp, so it is only available when T carries
26 * transcendental functions; asking for it at exact arithmetic throws
27 * UnsupportedError rather than silently substituting something else.
28 */
29
31#include "line/num/number.h"
32#include "line/util/error.h"
33
34namespace line {
35namespace fj {
36
37namespace detail {
38
39/** Erlang-k CDF, 1 - exp(-mu t) sum_{j<k} (mu t)^j / j!. */
40template <class T>
41T erlang_cdf(const T& t, unsigned k, const T& mu) {
42 static_assert(num_traits<T>::has_transcendental,
43 "erlang_cdf requires transcendental arithmetic");
44 const T zero = num_traits<T>::from_int(0);
45 if (t <= zero) return zero;
46 const T x = mu * t;
47 T S = zero;
48 for (unsigned j = 0; j < k; ++j) S += num_pow_int(x, j) / num_factorial<T>(j);
49 return num_traits<T>::from_int(1) - num_exp(T(-x)) * S;
50}
51
52/** Erlang-k survival function, exp(-mu x) sum_{j<k} (mu x)^j / j!. */
53template <class T>
54T erlang_survival(const T& x, unsigned k, const T& mu) {
55 static_assert(num_traits<T>::has_transcendental,
56 "erlang_survival requires transcendental arithmetic");
57 const T zero = num_traits<T>::from_int(0);
58 if (x <= zero) return num_traits<T>::from_int(1);
59 const T y = mu * x;
60 T S = zero;
61 for (unsigned j = 0; j < k; ++j) S += num_pow_int(y, j) / num_factorial<T>(j);
62 return num_exp(T(-y)) * S;
63}
64
65/** Quadrature branch of fj_xmax_erlang, for k != 2. */
66template <class T>
67T fj_xmax_erlang_quad(unsigned K, unsigned k, const T& mu) {
68 const T kk = num_traits<T>::from_int(static_cast<long>(k));
69 const T upper = kk / mu * num_traits<T>::from_int(10) + num_traits<T>::from_int(10) * num_sqrt(kk) / mu;
70 const T one = num_traits<T>::from_int(1);
71 return simpson<T>([&](const T& t) { return T(one - num_pow_int(erlang_cdf(t, k, mu), K)); },
72 num_traits<T>::from_int(0), upper);
73}
74
75} // namespace detail
76
77/**
78 * @brief Expected maximum of K i.i.d. Erlang-k service times.
79 *
80 * @param K number of branches, K >= 1
81 * @param k Erlang stages, k >= 1
82 * @param mu per-stage rate, > 0 (branch mean is k/mu)
83 * @return expected maximum of K Erlang-k samples
84 */
85template <class T>
86T fj_xmax_erlang(unsigned K, unsigned k, const T& mu) {
87 detail::require_positive_K(K, "fj_xmax_erlang");
88 if (k < 1) throw InputError("fj_xmax_erlang: the stage count k must be a positive integer");
89 if (mu <= num_traits<T>::from_int(0)) throw InputError("fj_xmax_erlang: the rate mu must be positive");
90
91 if (k == 2) {
92 T outer = num_traits<T>::from_int(0);
93 const T two = num_traits<T>::from_int(2);
94 for (unsigned n = 1; n <= K; ++n) {
95 const T nn = num_traits<T>::from_int(static_cast<long>(n));
96 T inner = num_traits<T>::from_int(0);
97 for (unsigned m = 1; m <= n; ++m)
98 inner += detail::fj_binom<T>(n, m) * num_factorial<T>(m) / (two * num_pow_int(nn, m + 1));
99 const T term = detail::fj_binom<T>(K, n) * inner;
100 if ((n - 1) % 2 == 0) outer += term;
101 else outer -= term;
102 }
103 return outer / mu;
104 }
105
106 if constexpr (num_traits<T>::has_transcendental) {
107 return detail::fj_xmax_erlang_quad<T>(K, k, mu);
108 } else {
109 throw UnsupportedError(
110 "fj_xmax_erlang: only k = 2 has a closed form; any other stage count needs a "
111 "quadrature of the Erlang CDF and therefore transcendental arithmetic");
112 }
113}
114
115} // namespace fj
116} // namespace line
117
118#endif // LINE_API_FJ_XMAX_ERLANG_H
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
The exception types the port throws.
Shared return types and arithmetic helpers for the templated fork-join port.
T fj_xmax_erlang(unsigned K, unsigned k, const T &mu)
Expected maximum of K i.i.d.
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:184
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:192
Number-type abstraction for the templated API port.