LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_maxima_twomoment.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_QSYS_MAXIMA_TWOMOMENT_H
6#define LINE_API_QSYS_MAXIMA_TWOMOMENT_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Two-moment approximation for the maximum of n iid non-negative variables.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_maxima_twomoment.m, cross-checked
14 * against jar/src/main/java/jline/api/qsys/Qsys_maxima_twomoment.java.
15 *
16 * THE SHAPE OF THE ANSWER. For a law with an exponential-like tail the maximum
17 * of n samples grows like c~^2 (log n + ...): doubling n ADDS a constant, it
18 * does not scale the answer. The two moments buy the SLOPE and an offset:
19 *
20 * x_n(q) = c~^2 [log(n eta) - log log(1/q)] (1.9)
21 * E[M_n] = c~^2 [log(n eta) + gamma]
22 * cs2 >= 1: c~^2 = cs2, eta = (cs2+1)/(2 cs2^2) (1.11)-(1.12)
23 * cs2 < 1: c~^2 = sqrt(cs2), eta = exp((1-sqrt(cs2))/sqrt(cs2))
24 *
25 * WHEN NOT TO USE IT: n must pass n* ~ cs2/q (4.22), because with a highly
26 * variable law only about n p of the samples can contend for the maximum.
27 * Measured against exact maxima the closed form is within a few percent for
28 * n >= 100 at cs2 = 4 and 16, and useless at n = 10 for cs2 = 16 -- exactly what
29 * n* predicts.
30 *
31 * AND WHEN TWO MOMENTS ARE NOT ENOUGH: below cs2 = 1 the maximum is genuinely
32 * family-dependent. An Erlang and a shifted exponential with the same two
33 * moments have maxima differing by tens of percent, diverging as n grows,
34 * because their tails decay at different rates.
35 *
36 * ARITHMETIC. Logarithms throughout: transcendental only.
37 *
38 * Reference: C. Crow, D. Goldberg, W. Whitt (2007). Two-moment approximations
39 * for maxima. Operations Research 55(3), 532-548.
40 */
41
42#include <cmath>
43#include <cstddef>
44#include <string>
45
46#include "line/num/number.h"
47#include "line/util/error.h"
48
49namespace line {
50namespace qsys {
51
52/** Two-moment description of a maximum. */
53template <class T>
55 T value; ///< the closed-form mean or quantile
56 T slope; ///< mean * c~^2, the coefficient of log n
57 T eta; ///< the offset inside the logarithm
58 T threshold; ///< n*, below which the form should not be used
59 bool reliable; ///< whether n >= n*
60 std::string family; ///< the representative law used
61 T exactFittedValue; ///< the maximum computed exactly from that representative
62 bool hasFitted = false;
63};
64
65/**
66 * @brief Two-moment approximation for the maximum of n iid non-negative
67 * variables.
68 *
69 * @param n the number of samples
70 * @param mean the mean of the underlying law
71 * @param cs2 its squared coefficient of variation
72 * @param q a quantile level in (0,1); non-positive returns the mean
73 * @param exactFitted also compute the maximum exactly from the fitted law
74 */
75template <class T>
76MaximaResult<T> qsys_maxima_twomoment(std::size_t n, const T& mean, const T& cs2,
77 const T& q = num_traits<T>::from_int(0),
78 bool exactFitted = true) {
79 static_assert(num_traits<T>::has_transcendental, "qsys_maxima_twomoment needs logarithms");
80 using std::exp;
81 using std::log;
82 using std::pow;
83 using std::sqrt;
84 const T zero = num_traits<T>::from_int(0);
85 const T one = num_traits<T>::from_int(1);
86 const T two = num_traits<T>::from_int(2);
87 if (n < 1) throw InputError("qsys_maxima_twomoment: at least one sample is required");
88 if (mean <= zero) throw InputError("qsys_maxima_twomoment: the mean must be positive");
89 if (cs2 <= zero) throw InputError("qsys_maxima_twomoment: the SCV must be positive");
90 if (q < zero || q >= one)
91 throw InputError("qsys_maxima_twomoment: the quantile level must lie in [0,1)");
92
93 const T EULER = num_traits<T>::from_double(0.5772156649015329);
95 T ct, eta;
96 if (cs2 >= one) {
97 ct = cs2;
98 eta = (cs2 + one) / (two * cs2 * cs2);
99 r.family = "H2";
100 } else {
101 ct = sqrt(cs2);
102 eta = exp((one - sqrt(cs2)) / sqrt(cs2));
103 r.family = "shifted exponential";
104 }
105 const T nT = num_traits<T>::from_int(static_cast<long>(n));
106 const bool wantMean = (q <= zero);
107 const T inner = wantMean ? T(log(nT * eta) + EULER) : T(log(nT * eta) - log(log(one / q)));
108 r.value = mean * ct * inner;
109 r.slope = mean * ct;
110 r.eta = eta;
111 r.threshold = cs2 / (wantMean ? num_traits<T>::from_rational(1, 2) : q); // eq. (4.22)
112 r.reliable = nT >= r.threshold;
113
114 if (exactFitted) {
115 // Fit the representative law and compute the maximum exactly from F^n.
116 T d = zero, m = mean, p1 = zero, l1 = zero, l2 = zero, hi;
117 if (cs2 >= one) {
118 p1 = (one + sqrt((cs2 - one) / (cs2 + one))) / two;
119 l1 = two * p1 / mean;
120 l2 = two * (one - p1) / mean;
121 hi = num_traits<T>::from_int(40) * mean * (cs2 > one ? cs2 : one);
122 } else {
123 d = mean * (one - sqrt(cs2));
124 m = mean * sqrt(cs2);
125 hi = d + num_traits<T>::from_int(40) * m;
126 }
127 auto ccdf = [&](const T& t) {
128 if (cs2 >= one) return T(p1 * exp(-l1 * t) + (one - p1) * exp(-l2 * t));
129 return t <= d ? one : T(exp(-(t - d) / m));
130 };
131 const std::size_t gn = 200000;
132 const T h = hi / num_traits<T>::from_int(static_cast<long>(gn));
133 r.hasFitted = true;
134 if (wantMean) {
135 T acc = zero;
136 for (std::size_t i = 0; i <= gn; ++i) {
137 const T t = num_traits<T>::from_int(static_cast<long>(i)) * h;
138 const T v = one - pow(one - ccdf(t), num_traits<T>::from_int(static_cast<long>(n)));
139 acc += (i == 0 || i == gn) ? T(v / two) : v;
140 }
141 r.exactFittedValue = acc * h;
142 } else {
143 r.exactFittedValue = hi;
144 for (std::size_t i = 0; i <= gn; ++i) {
145 const T t = num_traits<T>::from_int(static_cast<long>(i)) * h;
146 if (pow(one - ccdf(t), num_traits<T>::from_int(static_cast<long>(n))) >= q) {
147 r.exactFittedValue = t;
148 break;
149 }
150 }
151 }
152 }
153 return r;
154}
155
156} // namespace qsys
157} // namespace line
158
159#endif // LINE_API_QSYS_MAXIMA_TWOMOMENT_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
MaximaResult< T > qsys_maxima_twomoment(std::size_t n, const T &mean, const T &cs2, const T &q=num_traits< T >::from_int(0), bool exactFitted=true)
Two-moment approximation for the maximum of n iid non-negative variables.
Number-type abstraction for the templated API port.
Two-moment description of a maximum.
T slope
mean * c~^2, the coefficient of log n
T value
the closed-form mean or quantile
T eta
the offset inside the logarithm
T threshold
n*, below which the form should not be used
std::string family
the representative law used
T exactFittedValue
the maximum computed exactly from that representative