Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 09:55:37

0001 /*
0002  * Copyright Nick Thompson, 2019
0003  * Use, modification and distribution are subject to the
0004  * Boost Software License, Version 1.0. (See accompanying file
0005  * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
0006  */
0007 
0008 #ifndef BOOST_MATH_STATISTICS_ANDERSON_DARLING_HPP
0009 #define BOOST_MATH_STATISTICS_ANDERSON_DARLING_HPP
0010 
0011 #include <cmath>
0012 #include <algorithm>
0013 #include <boost/math/statistics/univariate_statistics.hpp>
0014 #include <boost/math/special_functions/erf.hpp>
0015 
0016 namespace boost { namespace math { namespace statistics {
0017 
0018 template<class RandomAccessContainer>
0019 auto anderson_darling_normality_statistic(RandomAccessContainer const & v,
0020                                           typename RandomAccessContainer::value_type mu = std::numeric_limits<typename RandomAccessContainer::value_type>::quiet_NaN(),
0021                                           typename RandomAccessContainer::value_type sd = std::numeric_limits<typename RandomAccessContainer::value_type>::quiet_NaN())
0022 {
0023     using Real = typename RandomAccessContainer::value_type;
0024     using std::log;
0025     using std::sqrt;
0026     using boost::math::erfc;
0027 
0028     if (std::isnan(mu)) {
0029         mu = boost::math::statistics::mean(v);
0030     }
0031     if (std::isnan(sd)) {
0032         sd = sqrt(boost::math::statistics::sample_variance(v));
0033     }
0034 
0035     typedef boost::math::policies::policy<
0036           boost::math::policies::promote_float<false>,
0037           boost::math::policies::promote_double<false> >
0038           no_promote_policy;
0039 
0040     // This is where Knuth's literate programming could really come in handy!
0041     // I need some LaTeX. The idea is that before any observation, the ecdf is identically zero.
0042     // So we need to compute:
0043     // \int_{-\infty}^{v_0} \frac{F(x)F'(x)}{1- F(x)} \, \mathrm{d}x, where F(x) := \frac{1}{2}[1+\erf(\frac{x-\mu}{\sigma \sqrt{2}})]
0044     // Astonishingly, there is an analytic evaluation to this integral, as you can validate with the following Mathematica command:
0045     // Integrate[(1/2 (1 + Erf[(x - mu)/Sqrt[2*sigma^2]])*Exp[-(x - mu)^2/(2*sigma^2)]*1/Sqrt[2*\[Pi]*sigma^2])/(1 - 1/2 (1 + Erf[(x - mu)/Sqrt[2*sigma^2]])),
0046     // {x, -Infinity, x0}, Assumptions -> {x0 \[Element] Reals && mu \[Element] Reals && sigma > 0}]
0047     // This gives (for s = x-mu/sqrt(2sigma^2))
0048     // -1/2 + erf(s) + log(2/(1+erf(s)))
0049 
0050 
0051     Real inv_var_scale = 1/(sd*sqrt(Real(2)));
0052     Real s0 = (v[0] - mu)*inv_var_scale;
0053     Real erfcs0 = erfc(s0, no_promote_policy());
0054     // Note that if erfcs0 == 0, then left_tail = inf (numerically), and hence the entire integral is numerically infinite:
0055     if (erfcs0 <= 0) {
0056         return std::numeric_limits<Real>::infinity();
0057     }
0058 
0059     // Note that we're going to add erfcs0/2 when we compute the integral over [x_0, x_1], so drop it here:
0060     Real left_tail = -1 + log(Real(2));
0061 
0062 
0063     // For the right tail, the ecdf is identically 1.
0064     // Hence we need the integral:
0065     // \int_{v_{n-1}}^{\infty} \frac{(1-F(x))F'(x)}{F(x)} \, \mathrm{d}x
0066     // This also has an analytic evaluation! It can be found via the following Mathematica command:
0067     // Integrate[(E^(-(z^2/2)) *(1 - 1/2 (1 + Erf[z/Sqrt[2]])))/(Sqrt[2 \[Pi]] (1/2 (1 + Erf[z/Sqrt[2]]))),
0068     // {z, zn, \[Infinity]}, Assumptions -> {zn \[Element] Reals && mu \[Element] Reals}]
0069     // This gives (for sf = xf-mu/sqrt(2sigma^2))
0070     // -1/2 + erf(sf)/2 + 2log(2/(1+erf(sf)))
0071 
0072     Real sf = (v[v.size()-1] - mu)*inv_var_scale;
0073     //Real erfcsf = erfc<Real>(sf, no_promote_policy());
0074     // This is the actual value of the tail integral. However, the -erfcsf/2 cancels from the integral over [v_{n-2}, v_{n-1}]:
0075     //Real right_tail = -erfcsf/2 + log(Real(2)) - log(2-erfcsf);
0076 
0077     // Use erfc(-x) = 2 - erfc(x)
0078     Real erfcmsf = erfc<Real>(-sf, no_promote_policy());
0079     // Again if this is precisely zero then the integral is numerically infinite:
0080     if (erfcmsf == 0) {
0081         return std::numeric_limits<Real>::infinity();
0082     }
0083     Real right_tail = log(2/erfcmsf);
0084 
0085     // Now we need each integral:
0086     // \int_{v_i}^{v_{i+1}} \frac{(i+1/n - F(x))^2F'(x)}{F(x)(1-F(x))}  \, \mathrm{d}x
0087     // Again we get an analytical evaluation via the following Mathematica command:
0088     // Integrate[((E^(-(z^2/2))/Sqrt[2 \[Pi]])*(k1 - F[z])^2)/(F[z]*(1 - F[z])),
0089     // {z, z1, z2}, Assumptions -> {z1 \[Element] Reals && z2 \[Element] Reals &&k1 \[Element] Reals}] // FullSimplify
0090 
0091     Real integrals = 0;
0092     int64_t N = v.size();
0093     for (int64_t i = 0; i < N - 1; ++i) {
0094         if (v[i] > v[i+1]) {
0095             throw std::domain_error("Input data must be sorted in increasing order v[0] <= v[1] <= . . .  <= v[n-1]");
0096         }
0097 
0098         Real k = (i+1)/Real(N);
0099         Real s1 = (v[i+1]-mu)*inv_var_scale;
0100         Real erfcs1 = erfc<Real>(s1, no_promote_policy());
0101         Real term = k*(k*log(erfcs0*(-2 + erfcs1)/(erfcs1*(-2 + erfcs0))) + 2*log(erfcs1/erfcs0));
0102 
0103         integrals += term;
0104         s0 = s1;
0105         erfcs0 = erfcs1;
0106     }
0107     integrals -= log(erfcs0);
0108     return v.size()*(left_tail + right_tail + integrals);
0109 }
0110 
0111 }}}
0112 #endif