LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_char_max.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_CHAR_MAX_H
6#define LINE_API_FJ_CHAR_MAX_H
7
8/**
9 * @file
10 * @ingroup api_fj
11 * Gravey's characteristic maximum M_K, an upper bound on the expected maximum
12 * of K i.i.d. random variables.
13 *
14 * Templated port of matlab/src/api/fj/fj_char_max.m, cross-checked against
15 * FJ_char_max.fj_char_max_exp and fj_char_max_erlang in
16 * jar/src/main/java/jline/api/fj/FJ_char_max.java (identical; the JAR splits
17 * the MATLAB dist_type switch into two entry points, which this port mirrors
18 * as two overloads).
19 *
20 * m_K is the greatest lower bound with P(X > m_K) <= 1/K, and
21 * M_K = m_K + K int_{m_K}^inf P(X > x) dx.
22 *
23 * exponential: m_K = ln(K)/mu, M_K = H_K/mu (the bound is tight here)
24 * Erlang-k: m_K solves exp(-mu m) sum_{i<k} (mu m)^i/i! = 1/K,
25 * M_K = (k/mu)[1 + K exp(-mu m_K) (mu m_K)^k / k!]
26 *
27 * static_assert(num_traits<T>::has_transcendental) -- a log in the
28 * exponential case and a bracketed root of a transcendental equation in the
29 * Erlang one.
30 *
31 * The MATLAB 'general' mode, which takes an arbitrary survival function
32 * handle, locates mK by a doubling bracket and a root search, and integrates
33 * the tail by quadrature after a doubling search for the truncation point, is
34 * ported as fj_char_max_general(K, S_X) below (a distinct name, since a callable
35 * argument would be ambiguous with the exponential overload). The reference's
36 * doubling searches have no termination guarantee on a heavy tail; the port
37 * caps the bracket search and refuses, where the reference would loop.
38 */
39
43#include "line/num/number.h"
44#include "line/util/error.h"
45
46namespace line {
47namespace fj {
48
49/**
50 * Exponential branch.
51 *
52 * @param K number of samples, K >= 1
53 * @param mu rate, > 0
54 * @return [MK, mK]
55 */
56template <class T>
57FJCharMaxResult<T> fj_char_max(unsigned K, const T& mu) {
59 "fj_char_max requires transcendental arithmetic");
60 detail::require_positive_K(K, "fj_char_max");
61 if (mu <= num_traits<T>::from_int(0)) throw InputError("fj_char_max: the rate mu must be positive");
62 const T Kt = num_traits<T>::from_int(static_cast<long>(K));
63 return {T(fj_harmonic<T>(K) / mu), T(detail::num_log(Kt) / mu)};
64}
65
66/**
67 * Erlang-k branch.
68 *
69 * @param K number of samples, K >= 1
70 * @param k Erlang stages, k >= 1
71 * @param mu per-stage rate, > 0
72 * @return [MK, mK]
73 */
74template <class T>
75FJCharMaxResult<T> fj_char_max(unsigned K, unsigned k, const T& mu) {
77 "fj_char_max requires transcendental arithmetic");
78 detail::require_positive_K(K, "fj_char_max");
79 if (k < 1) throw InputError("fj_char_max: the Erlang stage count k must be positive");
80 if (mu <= num_traits<T>::from_int(0)) throw InputError("fj_char_max: the rate mu must be positive");
81
82 const T one = num_traits<T>::from_int(1);
83 const T Kt = num_traits<T>::from_int(static_cast<long>(K));
84 const T kt = num_traits<T>::from_int(static_cast<long>(k));
85 const T target = one / Kt;
86
87 T hi = kt / mu + detail::num_log(Kt) / mu;
88 if (hi <= num_traits<T>::from_int(0)) hi = kt / mu;
89 while (detail::erlang_survival(hi, k, mu) > target) hi *= num_traits<T>::from_int(2);
90 const T mK = detail::bisect<T>(
91 [&](const T& x) { return T(detail::erlang_survival(x, k, mu) - target); },
92 num_traits<T>::from_int(0), hi, "fj_char_max");
93
94 const T MK = (kt / mu) * (one + Kt * detail::num_exp(T(-mu * mK)) * num_pow_int(T(mu * mK), k) /
96 return {MK, mK};
97}
98
99/**
100 * General branch, the reference's dist_type 'general': the survival function
101 * S_X(x) = P(X > x) of the parallel tasks, as a callable.
102 *
103 * mK solves S_X(mK) = 1/K on [0, u] with u doubled from 1 until S_X(u) <= 1/K
104 * (fzero, here bisection as in the other branches), and
105 * MK = mK + K * int_{mK}^{upper} S_X(x) dx with upper doubled from 1 until
106 * S_X(upper) <= 1e-10 (at most 100 doublings, as in the reference), the
107 * integral by the tree's composite Simpson rule.
108 *
109 * @param K number of samples, K >= 1
110 * @param S_X survival function, S_X(0) = 1, nonincreasing
111 * @return [MK, mK]
112 */
113template <class T, class F>
114FJCharMaxResult<T> fj_char_max_general(unsigned K, const F& S_X) {
116 "fj_char_max requires transcendental arithmetic");
117 detail::require_positive_K(K, "fj_char_max");
118 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
119 const T two = num_traits<T>::from_int(2);
120 const T Kt = num_traits<T>::from_int(static_cast<long>(K));
121 const T target = one / Kt;
122
123 // find_mK_general: upper doubled until S_X(upper) <= 1/K
124 T upper = one;
125 unsigned guard = 0;
126 while (S_X(upper) > target) {
127 upper *= two;
128 if (++guard > 1000)
129 throw NumericError(ErrorKind{"NonConvergence"}, "fj_char_max: the survival function does not fall below 1/K");
130 }
131 const T mK = detail::bisect<T>([&](const T& x) { return T(S_X(x) - target); }, zero, upper,
132 "fj_char_max");
133
134 // find_upper_limit(S_X, 1e-10): at most 100 doublings from 1
135 T ulim = one;
136 const T tail_target = num_traits<T>::from_double(1e-10);
137 for (unsigned it = 0; it < 100; ++it) {
138 if (S_X(ulim) <= tail_target) break;
139 ulim *= two;
140 }
141 T tail_integral = zero;
142 if (ulim > mK) tail_integral = detail::simpson<T>([&](const T& x) { return T(S_X(x)); }, mK, ulim);
143 const T MK = T(mK + Kt * tail_integral);
144 return {MK, mK};
145}
146
147} // namespace fj
148} // namespace line
149
150#endif // LINE_API_FJ_CHAR_MAX_H
InputError(const std::string &what)
Definition error.h:105
NumericError(const std::string &what)
Definition error.h:112
The exception types the port throws.
Harmonic number H_K = sum_{k=1..K} 1/k.
Shared return types and arithmetic helpers for the templated fork-join port.
Expected maximum of K i.i.d.
FJCharMaxResult< T > fj_char_max(unsigned K, const T &mu)
Exponential branch.
Definition fj_char_max.h:57
FJCharMaxResult< T > fj_char_max_general(unsigned K, const F &S_X)
General branch, the reference's dist_type 'general': the survival function S_X(x) = P(X > x) of the p...
T fj_harmonic(unsigned K)
Harmonic number H_K = sum_{k=1..K} 1/k.
Definition fj_harmonic.h:37
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:196
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 <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
[MK, mK] of fj_char_max: characteristic maximum and its threshold.
Definition fj_types.h:55