LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
trace_gamma.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_TRACE_TRACE_GAMMA_H
6#define LINE_API_TRACE_TRACE_GAMMA_H
7
8/**
9 * @file
10 * @ingroup api_trace
11 * Autocorrelation decay rate of a trace: the gamma of the geometric model
12 * rho(k) = rho0 * gamma^k, with rho0 = (1 - 1/scv)/2 fixed by the second
13 * moment and gamma fitted by least squares on the empirical acf.
14 *
15 * Templated port of `jar/src/main/java/jline/api/trace/Trace_var.java#trace_gamma`,
16 * cross-checked against matlab/lib/kpctoolbox/trace/trace_gamma.m.
17 *
18 * DIVERGENCE, MATLAB vs JAR: MATLAB fits gamma by nonlinear regression
19 * (nlinfit with a fair robust weight, falling back to lsqcurvefit), the JAR
20 * by an exhaustive search over the ten grid points 0.990, 0.991, ..., 0.999.
21 * The JAR can therefore never report a decay rate outside that window and its
22 * resolution is 1e-3, so the two agree only when the true rate happens to sit
23 * on the grid. The grid search is what is ported, because it is the only one
24 * of the two that is deterministic and free of an optimizer dependency; the
25 * grid is exposed as a parameter so a caller can refine it.
26 *
27 * REFERENCE DEFECT (JAR): trace_gamma builds the lags 1..min(limit, n-1) and
28 * then indexes the trace_acf result by those lags, but trace_acf silently
29 * DROPS every lag above n-2. For any trace with n <= limit+1 -- with the
30 * default limit of 1000, any trace of at most 1001 samples -- the returned
31 * array is one element shorter than the loop bound and the method throws
32 * ArrayIndexOutOfBoundsException. This port builds the lags as
33 * 1..min(limit, n-2) so that the residual is computed on the acf that
34 * actually exists.
35 *
36 * ARITHMETIC: rho0 and the residual sum of squares are rational in the
37 * samples, and the grid points are raised to INTEGER lag powers, so the whole
38 * fit is a field computation; instantiated for double, Rational and Real50.
39 */
40
41#include <cstddef>
42#include <vector>
43
48#include "line/num/number.h"
49#include "line/util/error.h"
50
51namespace line {
52namespace trace {
53
54/** Return value of trace_gamma, mirroring [GAMMA, RHO0, RESIDUALS]. */
55template <class T>
57 T gamma; ///< best decay rate on the grid
58 T rho0; ///< (1 - 1/scv)/2, the GE-type lag-0 amplitude
59 T residuals; ///< sum of squared deviations from the fitted model
60};
61
62/**
63 * @brief Autocorrelation decay rate of a trace: the gamma of the geometric
64 * model rho(k) = rho0 * gamma^k, with rho0 = (1 - 1/scv)/2 fixed by
65 * the second moment and gamma fitted by least squares on the empirical
66 * acf.
67 *
68 * @param S the trace
69 * @param limit largest lag considered (the JAR default is 1000)
70 * @param grid candidate decay rates; the JAR grid 0.990..0.999 by default
71 */
72template <class T>
73TraceGammaResult<T> trace_gamma(const std::vector<T>& S, long limit = 1000,
74 const std::vector<T>& grid = std::vector<T>()) {
75 detail::require_nonempty(S, "trace_gamma");
76 const long n = static_cast<long>(S.size());
77 if (n < 4) throw InputError("trace_gamma: the fit needs at least four samples");
78
79 const long maxlag = std::min<long>(limit, n - 2);
80 std::vector<int> lags;
81 for (long l = 1; l <= maxlag; ++l) lags.push_back(static_cast<int>(l));
82 const std::vector<T> rho = trace_acf(S, lags);
83
84 // rho0 uses the POPULATION scv, as both references do (they form
85 // M2 - M1^2 explicitly rather than calling var).
86 const T scv = trace_scv(S, false);
87 if (scv == num_traits<T>::from_int(0))
88 throw NumericError("trace_gamma: the trace has zero scv, rho0 is undefined");
92
93 std::vector<T> g = grid;
94 if (g.empty())
95 for (long k = 990; k <= 999; ++k) g.push_back(num_traits<T>::from_rational(k, 1000));
96
97 bool first = true;
98 for (std::size_t a = 0; a < g.size(); ++a) {
99 T res = num_traits<T>::from_int(0);
100 for (std::size_t i = 0; i < rho.size(); ++i) {
101 const T expected = out.rho0 * num_pow_int(g[a], static_cast<unsigned>(lags[i]));
102 const T d = rho[i] - expected;
103 res += d * d;
104 }
105 if (first || res < out.residuals) {
106 out.residuals = res;
107 out.gamma = g[a];
108 first = false;
109 }
110 }
111 if (first) throw InputError("trace_gamma: the candidate grid is empty");
112 return out;
113}
114
115} // namespace trace
116} // namespace line
117
118#endif // LINE_API_TRACE_TRACE_GAMMA_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.
TraceGammaResult< T > trace_gamma(const std::vector< T > &S, long limit=1000, const std::vector< T > &grid=std::vector< T >())
Autocorrelation decay rate of a trace: the gamma of the geometric model rho(k) = rho0 * gamma^k,...
Definition trace_gamma.h:73
T trace_scv(const std::vector< T > &S, bool unbiased=true)
Squared coefficient of variation of a trace, var/mean^2.
Definition trace_scv.h:39
std::vector< T > trace_acf(const std::vector< T > &S, const std::vector< int > &lags)
Autocorrelation coefficients of a trace at the requested lags.
Definition trace_acf.h:57
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.
Return value of trace_gamma, mirroring [GAMMA, RHO0, RESIDUALS].
Definition trace_gamma.h:56
T rho0
(1 - 1/scv)/2, the GE-type lag-0 amplitude
Definition trace_gamma.h:58
T gamma
best decay rate on the grid
Definition trace_gamma.h:57
T residuals
sum of squared deviations from the fitted model
Definition trace_gamma.h:59
Autocorrelation coefficients of a trace at the requested lags.
Sample mean of a trace.
Squared coefficient of variation of a trace, var/mean^2.
Shared declarations for the empirical trace statistics domain.