LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
aoi_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_AOI_TYPES_H
6#define LINE_API_AOI_TYPES_H
7
8/**
9 * @file
10 * @ingroup api_aoi
11 * Shared return types and arithmetic helpers for the templated Age of
12 * Information port.
13 *
14 * The MATLAB AoI family in matlab/src/api/aoi/ returns one of two triples:
15 * [meanAoI, varAoI, peakAoI] for the fully parameterized queues (M/M/1,
16 * M/D/1, D/M/1) and [meanAoI, lstAoI, peakAoI] for the ones taking a
17 * Laplace-Stieltjes transform as input (M/GI/1, GI/M/1). Both live here,
18 * mirroring jline.api.aoi.AoiResult and jline.api.aoi.AoiLstResult; each
19 * ported function lives in its own header named after the MATLAB file, as
20 * required by the port convention.
21 *
22 * An LST is represented as a std::function<T(const T&)> rather than a
23 * distribution object, exactly as MATLAB represents it as a function handle
24 * and the JAR as a LstFunction. Nothing in the family inverts an LST: the
25 * transforms are evaluated at real arguments only, and where MATLAB needs a
26 * derivative it takes a central difference with a fixed step.
27 */
28
29#include <cmath>
30#include <functional>
31#include <string>
32
33#include "line/num/number.h"
34#include "line/util/error.h"
35
36namespace line {
37namespace aoi {
38
39/** A Laplace-Stieltjes transform evaluated at real arguments. */
40template <class T>
41using Lst = std::function<T(const T&)>;
42
43/** [meanAoI, varAoI, peakAoI], mirroring jline.api.aoi.AoiResult. */
44template <class T>
50
51/**
52 * [meanAoI, lstAoI, peakAoI], mirroring jline.api.aoi.AoiLstResult.
53 *
54 * has_lst is true whenever MATLAB returns a transform handle, which every
55 * LST-input function in the family now does (the LCFS-D and LCFS-S transforms
56 * are Theorem 35 of Inoue et al. 2019).
57 */
58template <class T>
65
66namespace detail {
67
68/** exp(v), resolved by ADL so double, cpp_bin_float and mpfr all work. */
69template <class T>
70inline T num_exp(const T& v) {
71 using std::exp;
72 return exp(v);
73}
74
75/** Guard on positivity of a rate or a time. */
76template <class T>
77inline void require_positive(const T& v, const char* fn, const char* what) {
78 if (v <= num_traits<T>::from_int(0))
79 throw InputError(std::string(fn) + ": " + what + " must be positive");
80}
81
82/** Guard on stability; every function in the family requires rho < 1. */
83template <class T>
84inline void require_stable(const T& rho, const char* fn) {
85 if (rho >= num_traits<T>::from_int(1))
86 throw NumericError(std::string(fn) + ": unstable system, rho >= 1");
87}
88
89/**
90 * Central difference of an LST at s with MATLAB's step, h = 1e-6 max(1,|s|).
91 *
92 * Every MATLAB file in the family that needs d/ds H*(s) uses exactly this
93 * step; reproducing it, rather than improving on it, is what makes the port
94 * agree with MATLAB to the twelfth digit. The approximation is the reason
95 * these functions require transcendental arithmetic even when the transform
96 * itself is rational: the answer is step-dependent, so an exact field would
97 * return an exact value of the wrong quantity.
98 */
99template <class T>
100inline T lst_derivative(const Lst<T>& f, const T& s) {
101 const T one = num_traits<T>::from_int(1);
102 const T mag = s < num_traits<T>::from_int(0) ? T(-s) : s;
103 const T scale = mag > one ? mag : one;
104 const T h = num_traits<T>::from_double(1e-6) * scale;
105 return (f(T(s + h)) - f(T(s - h))) / (num_traits<T>::from_int(2) * h);
106}
107
108/**
109 * Second derivative of an LST at s by the five-point central stencil,
110 * f'' ~ (-f(s+2h) + 16 f(s+h) - 30 f(s) + 16 f(s-h) - f(s-2h)) / (12 h^2),
111 * with h = 5e-3 max(1,|s|). The stencil is O(h^4), so a step this wide keeps
112 * both the truncation and the eps/h^2 roundoff near 1e-10, where a three-point
113 * difference cannot get below 1e-8. MATLAB, the JAR and Python use the same
114 * stencil and step (aoi_lcfsd_gim1).
115 */
116template <class T>
117inline T lst_second_derivative(const Lst<T>& f, const T& s) {
118 const T one = num_traits<T>::from_int(1);
119 const T mag = s < num_traits<T>::from_int(0) ? T(-s) : s;
120 const T scale = mag > one ? mag : one;
121 const T h = num_traits<T>::from_double(5e-3) * scale;
122 const T two = num_traits<T>::from_int(2);
123 const T num = T(-f(T(s + two * h)) + num_traits<T>::from_int(16) * f(T(s + h)) -
124 num_traits<T>::from_int(30) * f(s) + num_traits<T>::from_int(16) * f(T(s - h)) -
125 f(T(s - two * h)));
126 return num / (num_traits<T>::from_int(12) * h * h);
127}
128
129/**
130 * Bisection on a bracketed sign change, the replacement for MATLAB's fzero.
131 *
132 * MATLAB brackets every sigma root in this family on [0.001, 0.999] and calls
133 * fzero (Brent); the JAR replaces it with bisection (Aoi_fcfs_dm1). This port
134 * follows the JAR. With max_iter = 200 the bracket collapses below the
135 * representable resolution of double and of the 50-digit real type alike.
136 */
137template <class T, class F>
138inline T bisect(const F& f, T lo, T hi, const char* fn, unsigned max_iter = 200) {
139 const T zero = num_traits<T>::from_int(0);
140 T flo = f(lo);
141 const T fhi = f(hi);
142 if ((flo > zero && fhi > zero) || (flo < zero && fhi < zero))
143 throw NumericError(std::string(fn) + ": the root is not bracketed by [0.001, 0.999]");
144 for (unsigned it = 0; it < max_iter; ++it) {
145 const T mid = (lo + hi) / num_traits<T>::from_int(2);
146 if (mid == lo || mid == hi) break;
147 const T fm = f(mid);
148 if (fm == zero) return mid;
149 if ((fm > zero) == (flo > zero)) { lo = mid; flo = fm; }
150 else { hi = mid; }
151 }
152 return (lo + hi) / num_traits<T>::from_int(2);
153}
154
155/**
156 * The sigma of a GI/M/1 queue: the unique root in (0,1) of Y*(mu - mu s) = s,
157 * the probability an arriving job finds the server busy.
158 */
159template <class T>
160inline T gim1_sigma(const Lst<T>& Y_lst, const T& mu, const char* fn) {
161 return bisect<T>([&](const T& s) { return T(Y_lst(T(mu - mu * s)) - s); },
162 num_traits<T>::from_double(0.001), num_traits<T>::from_double(0.999), fn);
163}
164
165} // namespace detail
166
167} // namespace aoi
168} // namespace line
169
170#endif // LINE_API_AOI_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.
std::function< T(const T &)> Lst
A Laplace-Stieltjes transform evaluated at real arguments.
Definition aoi_types.h:41
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
[meanAoI, lstAoI, peakAoI], mirroring jline.api.aoi.AoiLstResult.
Definition aoi_types.h:59
[meanAoI, varAoI, peakAoI], mirroring jline.api.aoi.AoiResult.
Definition aoi_types.h:45