EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
ChiSquared.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <limits>
13#include <type_traits>
14
17
18namespace Emrys
19{
20
21namespace internal
22{
23
38template <typename Scalar>
39[[nodiscard]] Scalar
40regularizedGammaP(std::type_identity_t<Scalar> const & a, std::type_identity_t<Scalar> const & x)
41{
42 if (x <= Scalar(0))
43 {
44 return Scalar(0);
45 }
46 // Both expansions take the logarithm of x, which an infinite x makes NaN.
47 if (!(x < Eigen::NumTraits<Scalar>::infinity()))
48 {
49 return isNaN(x) ? x : Scalar(1);
50 }
51 using Literal = typename Eigen::NumTraits<Scalar>::Literal;
52 Scalar const epsilon = Scalar(Eigen::NumTraits<Scalar>::epsilon());
53 // Where x is near a, the series' terms fall as about exp(-n^2 / 2a), so it takes some
54 // sqrt(2a ln(1/epsilon)) of them, past any fixed limit once a is in the tens of thousands, as
55 // a cumulative consistency monitor's is; both loops are allowed twice that.
56 Scalar const maxIterations =
57 Scalar(1000) + Scalar(2) * Eigen::numext::sqrt(Scalar(Scalar(-2) * a * Eigen::numext::log(epsilon)));
58 Scalar const logPrefactor = Scalar(-x + a * Eigen::numext::log(x)) - logGamma<Scalar>(a);
59 if (x < a + Scalar(1))
60 {
61 Scalar term = Scalar(1) / a;
62 Scalar sum = term;
63 for (int n = 1;
64 scalarFromCount<Scalar>(n) < maxIterations && Eigen::numext::abs(term) > Eigen::numext::abs(sum) * epsilon;
65 ++n)
66 {
67 term *= x / (a + Scalar(n));
68 sum += term;
69 }
70 Scalar const p = sum * Eigen::numext::exp(logPrefactor);
71 return p < Scalar(1) ? p : Scalar(1);
72 }
73 // Modified Lentz's method.
74 Scalar const tiny = Scalar((std::numeric_limits<Literal>::min)()) / epsilon;
75 Scalar b = x + Scalar(1) - a;
76 Scalar c = Scalar(1) / tiny;
77 Scalar d = Scalar(1) / b;
78 Scalar h = d;
79 for (int i = 1; scalarFromCount<Scalar>(i) < maxIterations; ++i)
80 {
81 Scalar const an = -Scalar(i) * (Scalar(i) - a);
82 b += Scalar(2);
83 d = an * d + b;
84 if (Eigen::numext::abs(d) < tiny)
85 {
86 d = tiny;
87 }
88 c = b + an / c;
89 if (Eigen::numext::abs(c) < tiny)
90 {
91 c = tiny;
92 }
93 d = Scalar(1) / d;
94 Scalar const delta = d * c;
95 h *= delta;
96 if (Eigen::numext::abs(Scalar(delta - Scalar(1))) <= epsilon)
97 {
98 break;
99 }
100 }
101 Scalar const p = Scalar(1) - Eigen::numext::exp(logPrefactor) * h;
102 return Scalar(0) < p ? p : Scalar(0);
103}
104
105} // namespace internal
106
121template <typename Scalar>
122[[nodiscard]] Scalar
123chiSquaredCdf(std::type_identity_t<Scalar> const & x, std::type_identity_t<Scalar> const & dof)
124{
125 EMRYS_FILTER_ASSERT(dof > Scalar(0) && "the degrees of freedom must be positive");
126 return internal::regularizedGammaP<Scalar>(dof / Scalar(2), x / Scalar(2));
127}
128
146template <typename Scalar>
147[[nodiscard]] Scalar
148chiSquaredQuantile(std::type_identity_t<Scalar> const & probability, std::type_identity_t<Scalar> const & dof)
149{
150 EMRYS_FILTER_ASSERT(probability > Scalar(0) && probability < Scalar(1) && "the probability must be in (0, 1)");
151 EMRYS_FILTER_ASSERT(dof > Scalar(0) && "the degrees of freedom must be positive");
152 if (!(dof > Scalar(0)) || isNaN(probability))
153 {
154 return Eigen::NumTraits<Scalar>::quiet_NaN();
155 }
156 if (probability <= Scalar(0))
157 {
158 return Scalar(0);
159 }
160 if (probability >= Scalar(1))
161 {
162 return Eigen::NumTraits<Scalar>::infinity();
163 }
164 Scalar const epsilon = Scalar(Eigen::NumTraits<Scalar>::epsilon());
165 Scalar const a = dof / Scalar(2);
166 Scalar const logNormalizer = Scalar(a * Eigen::numext::log(Scalar(2))) + logGamma<Scalar>(a);
167 Scalar lower = Scalar(0);
168 Scalar upper = dof < Scalar(1) ? Scalar(1) : dof;
169 while (chiSquaredCdf<Scalar>(upper, dof) < probability)
170 {
171 lower = upper;
172 upper *= Scalar(2);
173 // A probability that rounds to one below the cdf's last representable step has no finite
174 // bracket.
175 if (!isFinite(upper))
176 {
177 return upper;
178 }
179 }
180 // A small probability with few degrees of freedom puts the quantile many orders of magnitude
181 // below one, so the bracket closes from below by a ratio squared at each step, and bisects
182 // geometrically while its ends differ by more than a factor of two.
183 if (lower == Scalar(0))
184 {
185 Scalar ratio = Scalar(0.5);
186 Scalar next = upper * ratio;
187 while (Scalar(0) < next && !(chiSquaredCdf<Scalar>(next, dof) < probability))
188 {
189 upper = next;
190 ratio *= ratio;
191 next = upper * ratio;
192 }
193 lower = next;
194 }
195 auto const middle = [&] {
196 return Scalar(2) * lower < upper && Scalar(0) < lower
197 ? Scalar(Eigen::numext::sqrt(lower) * Eigen::numext::sqrt(upper))
198 : Scalar((lower + upper) / Scalar(2));
199 };
200 Scalar x = middle();
201 Scalar step = upper - lower;
202 Scalar stepBefore = step;
203 for (int i = 0; i < 200; ++i)
204 {
205 Scalar const error = chiSquaredCdf<Scalar>(x, dof) - probability;
206 if (error < Scalar(0))
207 {
208 lower = x;
209 }
210 else
211 {
212 upper = x;
213 }
214 Scalar const exponent = (a - Scalar(1)) * Eigen::numext::log(x) - x / Scalar(2) - logNormalizer;
215 Scalar const density = Eigen::numext::exp(exponent);
216 // Newton's step, unless it leaves the bracket or is more than half the step before last,
217 // as it is far down a tail, where it shrinks the bracket less than bisection would.
218 Scalar next = x - error / density;
219 if (!(next > lower && next < upper)
220 || Eigen::numext::abs(Scalar(Scalar(2) * error)) > Eigen::numext::abs(Scalar(stepBefore * density)))
221 {
222 next = middle();
223 }
224 stepBefore = step;
225 step = next - x;
226 bool const converged = Eigen::numext::abs(step) <= Scalar(4) * epsilon * x;
227 x = next;
228 if (converged)
229 {
230 break;
231 }
232 }
233 return x;
234}
235
236} // namespace Emrys
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
Scalar chiSquaredQuantile(std::type_identity_t< Scalar > const &probability, std::type_identity_t< Scalar > const &dof)
Definition ChiSquared.h:148
Scalar chiSquaredCdf(std::type_identity_t< Scalar > const &x, std::type_identity_t< Scalar > const &dof)
Definition ChiSquared.h:123
Scalar logGamma(std::type_identity_t< Scalar > const &x)
Definition Math.h:216
bool isNaN(T const &x)
Definition Math.h:329
bool isFinite(T const &x)
Definition Math.h:305