59 return std::clamp(std::exp(-std::exp(
exponent)), 0.0, 1.0);
69 for (
int k = 0; k < 100; ++k) {
70 const double y = 4 * k + 1;
72 const double gammaRatio = std::exp(std::lgamma(k + 0.5) - std::lgamma(k + 1.0));
74 (std::pow(std::numbers::pi, 1.5) * std::sqrt(
statistic));
76 if (std::fabs(
term) < 1e-12)
79 return std::clamp(
sum, 0.0, 1.0);
84 const double z = value * value / 4;
86 std::sqrt(std::numbers::pi);
91 const double z = value * value / 4;
93 return std::exp(-
z) * std::pow(value / 2, 2.5) *
bessel / std::sqrt(std::numbers::pi);
98 const double m = 2 * k + 1;
102 const double gamma1 = std::tgamma(k + 0.5);
103 const double gamma2 = std::tgamma(k + 1.5);
122 for (
int k = 0; k < 100; ++k) {
125 if (std::fabs(
term) < 1e-10)
139 while (bins.size() > 2) {
141 if (
sparse->expected >= 5)
143 const std::size_t index =
static_cast<std::size_t
>(
sparse - bins.begin());
144 const std::size_t target = index == 0 ? 1 : index - 1;
145 bins[target].observed +=
sparse->observed;
146 bins[target].expected +=
sparse->expected;
153template <
class Distribution>
154 requires std::floating_point<typename Distribution::value_type>
156 std::span<const typename Distribution::value_type> sample)
159 throw std::invalid_argument(
"Goodness-of-fit testing requires a non-empty sample");
160 std::vector<typename Distribution::value_type> sorted(sample.begin(), sample.end());
161 std::ranges::sort(sorted);
162 const double n =
static_cast<double>(sorted.size());
163 double kolmogorovSmirnov = 0;
164 double andersonDarlingSum = 0;
165 double cramerVonMises = 1 / (12 * n);
166 const double minimumProbability = std::numeric_limits<double>::min();
167 for (std::size_t i = 0; i < sorted.size(); ++i) {
168 const double rawProbability = distribution.F(sorted[i]);
169 const double rawReverseProbability = distribution.F(sorted[sorted.size() - i - 1]);
170 if (!std::isfinite(rawProbability) || !std::isfinite(rawReverseProbability) || rawProbability < 0 ||
171 rawProbability > 1 || rawReverseProbability < 0 || rawReverseProbability > 1)
172 throw std::runtime_error(
"The fitted distribution returned an invalid probability");
173 const double probability = std::clamp(rawProbability, minimumProbability, 1.0);
174 const double reverseProbability =
175 std::clamp(rawReverseProbability, 0.0, 1 - std::numeric_limits<double>::epsilon());
176 kolmogorovSmirnov = std::max(kolmogorovSmirnov, (i + 1) / n - probability);
177 kolmogorovSmirnov = std::max(kolmogorovSmirnov, probability - i / n);
178 andersonDarlingSum += (2 * i + 1) * (std::log(probability) + std::log1p(-reverseProbability));
179 const double expected = (2 * i + 1) / (2 * n);
180 cramerVonMises += (probability - expected) * (probability - expected);
193template <
class Distribution>
194 requires std::integral<typename Distribution::value_type>
196 std::span<const typename Distribution::value_type> sample,
197 int estimatedParameters = 0)
200 throw std::invalid_argument(
"Goodness-of-fit testing requires a non-empty sample");
201 if (estimatedParameters < 0)
202 throw std::invalid_argument(
"The number of estimated parameters should be non-negative");
203 std::map<typename Distribution::value_type, std::size_t> counts;
204 for (
auto value : sample)
206 std::vector<detail::ChiSquaredBin> bins;
207 bins.reserve(counts.size() + 1);
208 double totalExpected = 0;
209 for (
const auto &[value, observed] : counts) {
210 const double probability = distribution.P(value);
211 if (!std::isfinite(probability) || probability < 0 || probability > 1)
212 throw std::runtime_error(
"The fitted distribution returned an invalid probability");
213 const double expected = sample.size() * probability;
214 bins.push_back({
static_cast<double>(observed), expected});
215 totalExpected += expected;
217 if (totalExpected > sample.size() * (1 + 16 * std::numeric_limits<double>::epsilon()))
218 throw std::runtime_error(
"The fitted distribution returned probabilities summing above one");
219 bins.push_back({0, std::max(0.0, sample.size() - totalExpected)});
221 if (std::ranges::any_of(bins, [](
const auto &bin) {
return bin.expected < 5; }))
222 throw std::invalid_argument(
"Chi-squared testing requires expected counts of at least five");
223 const int degreesOfFreedom =
static_cast<int>(bins.size()) - 1 - estimatedParameters;
224 if (degreesOfFreedom <= 0)
225 throw std::invalid_argument(
"Too few chi-squared bins for the estimated parameter count");
226 double statistic = 0;
227 for (
const auto &bin : bins) {
228 if (!(bin.expected > 0))
229 throw std::invalid_argument(
"The fitted distribution assigns zero probability to an observed bin");
230 const double difference = bin.observed - bin.expected;
231 statistic += difference * difference / bin.expected;
236template <
class Distribution, std::ranges::contiguous_range Range>
237 requires std::same_as<std::remove_cv_t<std::ranges::range_value_t<Range>>,
typename Distribution::value_type>
240 return goodness_of_fit(distribution, std::span<const typename Distribution::value_type>(sample));
243template <
class Distribution, std::ranges::contiguous_range Range>
244 requires std::integral<typename Distribution::value_type> &&
245 std::same_as<std::remove_cv_t<std::ranges::range_value_t<Range>>,
typename Distribution::value_type>
248 return goodness_of_fit(distribution, std::span<const typename Distribution::value_type>(sample),
249 estimatedParameters);
The ChiSquaredRand class Chi-squared distribution.
Definition continuous/gamma.hpp:355
double S(const RealType &x) const override
Definition continuous/gamma.hpp:485
The KolmogorovSmirnovRand class Kolmogorov-Smirnov distribution.
Definition kolmogorov_smirnov.hpp:19
double S(const RealType &x) const override
Definition kolmogorov_smirnov.hpp:161
double cvmCorrectionTerm(int k, double statistic)
Definition gof.hpp:96
double cvmCdf(double statistic, std::size_t sampleSize)
Definition gof.hpp:112
double cvmEd3(double value)
Definition gof.hpp:89
double andersonDarlingCdf(double statistic)
Definition gof.hpp:43
void mergeSparseBins(std::vector< ChiSquaredBin > &bins)
Definition gof.hpp:137
double cvmEd2(double value)
Definition gof.hpp:82
double cvmCdfInfinite(double statistic)
Definition gof.hpp:62
Real standardNormal(G &g)
Ziggurat algorithm of Marsaglia and Tsang with 256 layers and an explicit sign bit.
Definition variates.hpp:45
Real besselK(std::type_identity_t< Real > nu, Real x)
Definition special_functions.hpp:330
Definition bivariate_normal.hpp:10
ContinuousGoodnessOfFit goodness_of_fit(const Distribution &distribution, std::span< const typename Distribution::value_type > sample)
Definition gof.hpp:155
double kolmogorov_smirnov_p_value
Definition gof.hpp:25
double anderson_darling
Definition gof.hpp:26
double cramer_von_mises_p_value
Definition gof.hpp:29
double anderson_darling_p_value
Definition gof.hpp:27
double cramer_von_mises
Definition gof.hpp:28
double kolmogorov_smirnov
Definition gof.hpp:24
double p_value
Definition gof.hpp:35
std::size_t bins
Definition gof.hpp:37
int degrees_of_freedom
Definition gof.hpp:36
double chi_squared
Definition gof.hpp:34
double observed
Definition gof.hpp:133
double expected
Definition gof.hpp:134