LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_types.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_TYPES_H
6#define LINE_API_FJ_TYPES_H
7
8/**
9 * @file
10 * @ingroup api_fj
11 * Shared return types and arithmetic helpers for the templated fork-join port.
12 *
13 * The MATLAB fork-join family in matlab/src/api/fj/ returns a scalar in most
14 * cases but a pair in a few (fj_bounds, fj_char_max, fj_xmax_normal,
15 * fj_xmax_pareto, fj_xmax_approx, fj_order_stat, fj_quorum_moments) and a
16 * struct in one (fj_gk_bound). Those aggregates live here, together with the
17 * handful of ADL wrappers and the binomial coefficient the family needs; each
18 * ported function lives in its own header named after the MATLAB file, as
19 * required by the port convention.
20 *
21 * The binomial coefficient is built multiplicatively so that every partial
22 * product is an integer: in exact arithmetic C(n,k) is therefore exact for any
23 * n, with no overflow and no rounding, which is what makes the alternating
24 * sums in fj_respt_vm and fj_xmax_erlang trustworthy. Those sums cancel
25 * catastrophically in double past K ~ 20; the exact instantiation is the only
26 * way to see how much of the double answer is left.
27 */
28
29#include <cmath>
30#include <cstddef>
31#include <functional>
32#include <string>
33
34#include "line/num/number.h"
35#include "line/util/error.h"
36
37namespace line {
38namespace fj {
39
40/** Distribution families for which a G(K) standardized-maximum factor exists. */
41enum class FJDistType { Exp, Uniform, Evd, Bound };
42
43/** Bracketing methods for the normal-maximum approximation. */
45
46/** [Rmax, Rmin] of fj_bounds: pessimistic and optimistic response-time bounds. */
47template <class T>
51};
52
53/** [MK, mK] of fj_char_max: characteristic maximum and its threshold. */
54template <class T>
56 T MK;
57 T mK;
58};
59
60/** All four G(K) factors of fj_gk_bound in its 'all' mode. */
61template <class T>
69
70/** [Xmax, GK] of fj_xmax_approx. */
71template <class T>
74 T GK;
75};
76
77/** [Xmax, Vmax] of fj_xmax_normal. */
78template <class T>
83
84/** [Xmax, MK] of fj_xmax_pareto. */
85template <class T>
88 T MK;
89};
90
91/**
92 * [F_Yk, E_Yk] of fj_order_stat.
93 *
94 * The CDF is a polynomial in the base CDF value and so is available in every
95 * arithmetic mode; the expected value is a quadrature and is only produced
96 * when T carries transcendental functions. mean_available says which.
97 */
98template <class T>
104
105/** [m, v] of fj_quorum_moments: mean and variance of the k-of-n join time. */
106template <class T>
108 T m;
109 T v;
110};
111
112namespace detail {
113
114/** exp(v), resolved by ADL so double, cpp_bin_float and mpfr all work. */
115template <class T>
116inline T num_exp(const T& v) {
117 using std::exp;
118 return exp(v);
119}
120
121/** log(v), resolved by ADL. */
122template <class T>
123inline T num_log(const T& v) {
124 using std::log;
125 return log(v);
126}
127
128/** sqrt(v), resolved by ADL. */
129template <class T>
130inline T num_sqrt(const T& v) {
131 using std::sqrt;
132 return sqrt(v);
133}
134
135/** base^exponent for a real-valued exponent, resolved by ADL. */
136template <class T>
137inline T num_pow(const T& base, const T& exponent) {
138 using std::pow;
139 return pow(base, exponent);
140}
141
142/** pi to the working precision of T, from the double literal MATLAB uses. */
143template <class T>
144inline T num_pi() {
145 return num_traits<T>::from_double(3.14159265358979323846264338327950288);
146}
147
148/**
149 * Binomial coefficient C(n,k) as a value of T, built multiplicatively.
150 *
151 * Every partial product r * (n-k+i) is divisible by i, so the running value is
152 * an integer throughout and the result is exact in any field.
153 */
154template <class T>
155inline T fj_binom(unsigned n, unsigned k) {
156 if (k > n) return num_traits<T>::from_int(0);
157 const unsigned kk = (k > n - k) ? n - k : k;
158 T r = num_traits<T>::from_int(1);
159 for (unsigned i = 1; i <= kk; ++i) {
160 r *= num_traits<T>::from_int(static_cast<long>(n - kk + i));
161 r /= num_traits<T>::from_int(static_cast<long>(i));
162 }
163 return r;
164}
165
166/**
167 * Composite Simpson rule on [a,b] with n_intervals panels (n_intervals even).
168 *
169 * MATLAB uses the adaptive quad in `integral`; the JAR replaces it with a fixed
170 * 10001-point composite Simpson everywhere it ports one of these functions
171 * (FJ_xmax.fj_xmax_erlang, fj_xmax_pareto, FJ_rmax.fj_rmax_erlang). This port
172 * follows the JAR convention so that the three non-MATLAB implementations agree
173 * with each other, and keeps the same default panel count.
174 */
175template <class T, class F>
176inline T simpson(const F& f, const T& a, const T& b, unsigned n_intervals = 10000) {
177 if (n_intervals % 2 != 0 || n_intervals == 0)
178 throw InputError("simpson: the panel count must be a positive even number");
179 const T h = (b - a) / num_traits<T>::from_int(static_cast<long>(n_intervals));
180 T acc = f(a) + f(b);
181 const T two = num_traits<T>::from_int(2), four = num_traits<T>::from_int(4);
182 for (unsigned i = 1; i < n_intervals; ++i) {
183 const T x = a + h * num_traits<T>::from_int(static_cast<long>(i));
184 acc += (i % 2 == 1 ? four : two) * f(x);
185 }
186 return acc * h / num_traits<T>::from_int(3);
187}
188
189/**
190 * Bisection on a bracketed sign change, the replacement for MATLAB's fzero.
191 *
192 * MATLAB's fzero is Brent's method; the JAR replaces it with bisection
193 * wherever it ports one of these functions (FJ_char_max, Aoi_fcfs_dm1), and
194 * this port follows suit. With max_iter = 200 the bracket is narrowed below
195 * any representable tolerance for double and for the 50-digit real type
196 * alike, so the two agree with MATLAB to full precision.
197 */
198template <class T, class F>
199inline T bisect(const F& f, T lo, T hi, const char* fn, unsigned max_iter = 200) {
200 const T zero = num_traits<T>::from_int(0);
201 T flo = f(lo), fhi = f(hi);
202 if ((flo > zero && fhi > zero) || (flo < zero && fhi < zero))
203 throw NumericError(std::string(fn) + ": the root is not bracketed by the initial interval");
204 for (unsigned it = 0; it < max_iter; ++it) {
205 const T mid = (lo + hi) / num_traits<T>::from_int(2);
206 if (mid == lo || mid == hi) break;
207 const T fm = f(mid);
208 if (fm == zero) return mid;
209 if ((fm > zero) == (flo > zero)) { lo = mid; flo = fm; }
210 else { hi = mid; fhi = fm; }
211 }
212 return (lo + hi) / num_traits<T>::from_int(2);
213}
214
215/**
216 * The standard normal quantile, MATLAB's sqrt(2)*erfinv(2u-1).
217 *
218 * C++ has no erfinv, so the inverse is taken by bisection on the complementary
219 * error function, which std does provide. This is called once per query in the
220 * fork-join family, so the ~60 iterations are free and the result is tight to
221 * the last representable bit of double.
222 */
223inline double normal_quantile(double u) {
224 if (!(u > 0.0) || !(u < 1.0))
225 throw InputError("normal_quantile: the probability must lie in (0,1)");
226 double lo = -40.0, hi = 40.0;
227 for (int it = 0; it < 200; ++it) {
228 const double mid = 0.5 * (lo + hi);
229 if (mid == lo || mid == hi) break;
230 // Phi(mid) = erfc(-mid/sqrt(2))/2
231 const double phi = 0.5 * std::erfc(-mid / std::sqrt(2.0));
232 if (phi < u) lo = mid; else hi = mid;
233 }
234 return 0.5 * (lo + hi);
235}
236
237/** Guard shared by every function that forms a harmonic number. */
238inline void require_positive_K(unsigned K, const char* fn) {
239 if (K < 1) throw InputError(std::string(fn) + ": K must be a positive integer");
240}
241
242} // namespace detail
243
244} // namespace fj
245} // namespace line
246
247#endif // LINE_API_FJ_TYPES_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
FJNormalMethod
Bracketing methods for the normal-maximum approximation.
Definition fj_types.h:44
FJDistType
Distribution families for which a G(K) standardized-maximum factor exists.
Definition fj_types.h:41
Number-type abstraction for the templated API port.
Box constraint on one variable.
Definition neldermead.h:110
[Rmax, Rmin] of fj_bounds: pessimistic and optimistic response-time bounds.
Definition fj_types.h:48
[MK, mK] of fj_char_max: characteristic maximum and its threshold.
Definition fj_types.h:55
All four G(K) factors of fj_gk_bound in its 'all' mode.
Definition fj_types.h:62
[F_Yk, E_Yk] of fj_order_stat.
Definition fj_types.h:99
[m, v] of fj_quorum_moments: mean and variance of the k-of-n join time.
Definition fj_types.h:107
[Xmax, GK] of fj_xmax_approx.
Definition fj_types.h:72
[Xmax, Vmax] of fj_xmax_normal.
Definition fj_types.h:79
[Xmax, MK] of fj_xmax_pareto.
Definition fj_types.h:86