Skip to main content

datui_lib/analysis/
distribution_fit.rs

1//! Fitting a column to each candidate distribution and testing each fit. One parameter
2//! set per family, estimated once, serves the test, Q-Q plot and histogram overlay. The
3//! test is Kolmogorov-Smirnov calibrated by parametric bootstrap (the statistic ranked
4//! among those of samples drawn from the fit and refitted), so it is a valid p-value
5//! with estimated parameters and for discrete families. A p-value is not the
6//! probability of the model, and the largest is not the best: among unrejected
7//! families, the lowest AIC wins.
8
9use crate::analysis::statistics::DistributionType;
10
11/// Every family tested, in the order a tie is listed.
12pub const FAMILIES: [DistributionType; 14] = [
13    DistributionType::Normal,
14    DistributionType::LogNormal,
15    DistributionType::Uniform,
16    DistributionType::Exponential,
17    DistributionType::Gamma,
18    DistributionType::ChiSquared,
19    DistributionType::Beta,
20    DistributionType::StudentsT,
21    DistributionType::Weibull,
22    DistributionType::PowerLaw,
23    DistributionType::Poisson,
24    DistributionType::Bernoulli,
25    DistributionType::Binomial,
26    DistributionType::Geometric,
27];
28
29/// Simulated samples per test. The smallest p-value it can give is 1/200.
30pub const REPLICATES: usize = 199;
31
32/// The most values a test is run on. The bootstrap costs this times [`REPLICATES`]
33/// fits per family; past a few hundred values, a KS test rejects any real-world data
34/// anyway, which says more about the sample size than about the fit.
35pub const TEST_VALUES: usize = 500;
36
37/// A distribution with its parameters, as fitted to a column.
38#[derive(Debug, Clone, Copy, PartialEq)]
39pub enum Fitted {
40    Normal {
41        mean: f64,
42        sd: f64,
43    },
44    LogNormal {
45        mu: f64,
46        sigma: f64,
47    },
48    Uniform {
49        low: f64,
50        high: f64,
51    },
52    Exponential {
53        rate: f64,
54    },
55    Gamma {
56        shape: f64,
57        scale: f64,
58    },
59    ChiSquared {
60        df: f64,
61    },
62    Beta {
63        alpha: f64,
64        beta: f64,
65    },
66    StudentsT {
67        df: f64,
68        location: f64,
69        scale: f64,
70    },
71    Weibull {
72        shape: f64,
73        scale: f64,
74    },
75    PowerLaw {
76        xmin: f64,
77        alpha: f64,
78    },
79    Poisson {
80        rate: f64,
81    },
82    Bernoulli {
83        p: f64,
84    },
85    Binomial {
86        trials: u64,
87        p: f64,
88    },
89    /// Failures before the first success when `start` is 0; trials up to and including
90    /// it when `start` is 1.
91    Geometric {
92        p: f64,
93        start: u64,
94    },
95}
96
97/// How a family fared against a column.
98#[derive(Debug, Clone, PartialEq)]
99pub enum FitOutcome {
100    Tested(FitTest),
101    /// The family cannot describe these values, and why: `needs positive values`.
102    NotApplicable(&'static str),
103}
104
105impl FitOutcome {
106    pub fn p_value(&self) -> Option<f64> {
107        match self {
108            Self::Tested(test) => Some(test.p_value),
109            Self::NotApplicable(_) => None,
110        }
111    }
112
113    pub fn test(&self) -> Option<&FitTest> {
114        match self {
115            Self::Tested(test) => Some(test),
116            Self::NotApplicable(_) => None,
117        }
118    }
119}
120
121/// A fit and its test.
122#[derive(Debug, Clone, PartialEq)]
123pub struct FitTest {
124    /// Fitted to every value read, and the parameters every view of this family uses.
125    pub fitted: Fitted,
126    /// The bootstrap p-value: `(1 + beyond) / (1 + replicates)`.
127    pub p_value: f64,
128    /// Simulated samples whose statistic was at least the column's.
129    pub beyond: usize,
130    pub replicates: usize,
131    /// Values the test was run on.
132    pub tested_on: usize,
133    /// On every value read; lower is better, and only comparable within one kind
134    /// (densities or probabilities).
135    pub aic: f64,
136}
137
138impl FitTest {
139    /// Whether no simulated sample reached the column's statistic, so the p-value is
140    /// only a bound: `< 0.005`, not `0.005`.
141    pub fn at_bound(&self) -> bool {
142        self.beyond == 0
143    }
144}
145
146impl Fitted {
147    pub fn family(&self) -> DistributionType {
148        match self {
149            Self::Normal { .. } => DistributionType::Normal,
150            Self::LogNormal { .. } => DistributionType::LogNormal,
151            Self::Uniform { .. } => DistributionType::Uniform,
152            Self::Exponential { .. } => DistributionType::Exponential,
153            Self::Gamma { .. } => DistributionType::Gamma,
154            Self::ChiSquared { .. } => DistributionType::ChiSquared,
155            Self::Beta { .. } => DistributionType::Beta,
156            Self::StudentsT { .. } => DistributionType::StudentsT,
157            Self::Weibull { .. } => DistributionType::Weibull,
158            Self::PowerLaw { .. } => DistributionType::PowerLaw,
159            Self::Poisson { .. } => DistributionType::Poisson,
160            Self::Bernoulli { .. } => DistributionType::Bernoulli,
161            Self::Binomial { .. } => DistributionType::Binomial,
162            Self::Geometric { .. } => DistributionType::Geometric,
163        }
164    }
165
166    /// Whether this is a distribution over whole numbers.
167    pub fn discrete(&self) -> bool {
168        matches!(
169            self,
170            Self::Poisson { .. }
171                | Self::Bernoulli { .. }
172                | Self::Binomial { .. }
173                | Self::Geometric { .. }
174        )
175    }
176
177    fn parameters(&self) -> usize {
178        match self {
179            Self::Exponential { .. }
180            | Self::ChiSquared { .. }
181            | Self::Poisson { .. }
182            | Self::Bernoulli { .. } => 1,
183            // The start is read off the values, as the minimum is for a power law.
184            Self::Geometric { .. } => 2,
185            Self::StudentsT { .. } => 3,
186            _ => 2,
187        }
188    }
189
190    /// `P(X <= x)`.
191    pub fn cdf(&self, x: f64) -> f64 {
192        match *self {
193            Self::Normal { mean, sd } => normal_cdf((x - mean) / sd),
194            Self::LogNormal { mu, sigma } => {
195                if x <= 0.0 {
196                    0.0
197                } else {
198                    normal_cdf((x.ln() - mu) / sigma)
199                }
200            }
201            Self::Uniform { low, high } => ((x - low) / (high - low)).clamp(0.0, 1.0),
202            Self::Exponential { rate } => {
203                if x <= 0.0 {
204                    0.0
205                } else {
206                    -(-rate * x).exp_m1()
207                }
208            }
209            Self::Gamma { shape, scale } => {
210                if x <= 0.0 {
211                    0.0
212                } else {
213                    gamma_p(shape, x / scale)
214                }
215            }
216            Self::ChiSquared { df } => {
217                if x <= 0.0 {
218                    0.0
219                } else {
220                    gamma_p(df / 2.0, x / 2.0)
221                }
222            }
223            Self::Beta { alpha, beta } => {
224                if x <= 0.0 {
225                    0.0
226                } else if x >= 1.0 {
227                    1.0
228                } else {
229                    beta_inc(alpha, beta, x)
230                }
231            }
232            Self::StudentsT {
233                df,
234                location,
235                scale,
236            } => t_cdf((x - location) / scale, df),
237            Self::Weibull { shape, scale } => {
238                if x <= 0.0 {
239                    0.0
240                } else {
241                    -(-(x / scale).powf(shape)).exp_m1()
242                }
243            }
244            Self::PowerLaw { xmin, alpha } => {
245                if x < xmin {
246                    0.0
247                } else {
248                    1.0 - (x / xmin).powf(1.0 - alpha)
249                }
250            }
251            Self::Poisson { rate } => {
252                if x < 0.0 {
253                    0.0
254                } else {
255                    // P(X <= k) = Q(k + 1, rate), the upper regularized gamma.
256                    gamma_q(x.floor() + 1.0, rate)
257                }
258            }
259            Self::Bernoulli { p } => {
260                if x < 0.0 {
261                    0.0
262                } else if x < 1.0 {
263                    1.0 - p
264                } else {
265                    1.0
266                }
267            }
268            Self::Binomial { trials, p } => {
269                let k = x.floor();
270                if k < 0.0 {
271                    0.0
272                } else if k >= trials as f64 {
273                    1.0
274                } else {
275                    // P(X <= k) = I_{1-p}(n - k, k + 1).
276                    beta_inc(trials as f64 - k, k + 1.0, 1.0 - p)
277                }
278            }
279            Self::Geometric { p, start } => {
280                let k = x.floor() - start as f64;
281                if k < 0.0 {
282                    0.0
283                } else {
284                    -((k + 1.0) * (-p).ln_1p()).exp_m1()
285                }
286            }
287        }
288    }
289
290    /// `P(X < x)`: the same as [`Self::cdf`] where no single value carries weight.
291    pub fn cdf_below(&self, x: f64) -> f64 {
292        if !self.discrete() {
293            return self.cdf(x);
294        }
295        if x == x.floor() {
296            self.cdf(x - 1.0)
297        } else {
298            self.cdf(x)
299        }
300    }
301
302    /// The smallest `x` with `P(X <= x) >= p`.
303    pub fn quantile(&self, p: f64) -> f64 {
304        if p.is_nan() {
305            return f64::NAN;
306        }
307        match *self {
308            Self::Normal { mean, sd } => mean + sd * normal_quantile(p),
309            Self::LogNormal { mu, sigma } => (mu + sigma * normal_quantile(p)).exp(),
310            Self::Uniform { low, high } => low + (high - low) * p.clamp(0.0, 1.0),
311            Self::Exponential { rate } => -(-p).ln_1p() / rate,
312            Self::Weibull { shape, scale } => scale * (-(-p).ln_1p()).powf(1.0 / shape),
313            Self::PowerLaw { xmin, alpha } => xmin * (1.0 - p).powf(-1.0 / (alpha - 1.0)),
314            Self::StudentsT {
315                df,
316                location,
317                scale,
318            } => location + scale * t_quantile(p, df),
319            Self::Gamma { .. } | Self::ChiSquared { .. } | Self::Beta { .. } => {
320                self.continuous_quantile_by_search(p)
321            }
322            Self::Poisson { .. }
323            | Self::Bernoulli { .. }
324            | Self::Binomial { .. }
325            | Self::Geometric { .. } => self.discrete_quantile(p),
326        }
327    }
328
329    /// Bisection on the CDF, for the families with no closed-form inverse.
330    fn continuous_quantile_by_search(&self, p: f64) -> f64 {
331        if p <= 0.0 {
332            return match self {
333                Self::Beta { .. } => 0.0,
334                _ => 0.0,
335            };
336        }
337        if p >= 1.0 {
338            return match self {
339                Self::Beta { .. } => 1.0,
340                _ => f64::INFINITY,
341            };
342        }
343        let (mut low, mut high) = match self {
344            Self::Beta { .. } => (0.0, 1.0),
345            _ => {
346                let mut high = 1.0;
347                while self.cdf(high) < p && high < 1e300 {
348                    high *= 2.0;
349                }
350                (0.0, high)
351            }
352        };
353        for _ in 0..200 {
354            let mid = 0.5 * (low + high);
355            if self.cdf(mid) < p {
356                low = mid;
357            } else {
358                high = mid;
359            }
360            if high - low <= 1e-12 * high.abs().max(1e-300) {
361                break;
362            }
363        }
364        0.5 * (low + high)
365    }
366
367    /// The smallest whole number whose CDF reaches `p`, by bisection from a guess (log of
368    /// the spread in evaluations; stepping one at a time was a million steps per draw for
369    /// large geometric fits).
370    fn discrete_quantile(&self, p: f64) -> f64 {
371        let start = match *self {
372            Self::Geometric { start, .. } => start as f64,
373            _ => 0.0,
374        };
375        if p <= 0.0 {
376            return start;
377        }
378        if p >= 1.0 {
379            return match *self {
380                Self::Bernoulli { .. } => 1.0,
381                Self::Binomial { trials, .. } => trials as f64,
382                _ => f64::INFINITY,
383            };
384        }
385        let (guess, spread) = match *self {
386            // The geometric's own inverse, exact up to rounding.
387            Self::Geometric { p: q, .. } => {
388                let k = ((-p).ln_1p() / (-q).ln_1p()).ceil() - 1.0;
389                (start + k.max(0.0), 1.0)
390            }
391            Self::Poisson { rate } => (rate + rate.sqrt() * normal_quantile(p), rate.sqrt()),
392            Self::Binomial { trials, p: q } => {
393                let n = trials as f64;
394                let sd = (n * q * (1.0 - q)).sqrt();
395                (n * q + sd * normal_quantile(p), sd)
396            }
397            _ => (start, 1.0),
398        };
399        let top = match *self {
400            Self::Bernoulli { .. } => 1.0,
401            Self::Binomial { trials, .. } => trials as f64,
402            _ => 1e15,
403        };
404        let guess = guess.floor().clamp(start, top);
405        let step = spread.max(1.0).ceil();
406        // The answer lies in (low, high]: cdf(low) < p <= cdf(high), with `low` below
407        // the support when nothing in it falls short.
408        let (mut low, mut high);
409        if self.cdf(guess) >= p {
410            high = guess;
411            low = start - 1.0;
412            let mut step = step;
413            while high > start {
414                let next = (high - step).max(start);
415                if self.cdf(next) < p {
416                    low = next;
417                    break;
418                }
419                high = next;
420                step *= 2.0;
421            }
422        } else {
423            low = guess;
424            high = top;
425            let mut step = step;
426            while low < top {
427                let next = (low + step).min(top);
428                if self.cdf(next) >= p {
429                    high = next;
430                    break;
431                }
432                low = next;
433                step *= 2.0;
434            }
435        }
436        while high - low > 1.0 {
437            let mid = (low + (high - low) / 2.0).floor();
438            if self.cdf(mid) >= p {
439                high = mid;
440            } else {
441                low = mid;
442            }
443        }
444        high
445    }
446
447    /// `ln` of the density, or of the probability at a whole number for a discrete
448    /// family; `-inf` outside the support.
449    fn ln_density(&self, x: f64) -> f64 {
450        match *self {
451            Self::Normal { mean, sd } => {
452                let z = (x - mean) / sd;
453                -0.5 * z * z - sd.ln() - 0.5 * LN_2PI
454            }
455            Self::LogNormal { mu, sigma } => {
456                if x <= 0.0 {
457                    return f64::NEG_INFINITY;
458                }
459                let z = (x.ln() - mu) / sigma;
460                -0.5 * z * z - sigma.ln() - x.ln() - 0.5 * LN_2PI
461            }
462            Self::Uniform { low, high } => {
463                if x < low || x > high {
464                    f64::NEG_INFINITY
465                } else {
466                    -(high - low).ln()
467                }
468            }
469            Self::Exponential { rate } => {
470                if x < 0.0 {
471                    f64::NEG_INFINITY
472                } else {
473                    rate.ln() - rate * x
474                }
475            }
476            Self::Gamma { shape, scale } => gamma_ln_pdf(x, shape, scale),
477            Self::ChiSquared { df } => gamma_ln_pdf(x, df / 2.0, 2.0),
478            Self::Beta { alpha, beta } => {
479                if x <= 0.0 || x >= 1.0 {
480                    return f64::NEG_INFINITY;
481                }
482                (alpha - 1.0) * x.ln() + (beta - 1.0) * (-x).ln_1p() - ln_beta(alpha, beta)
483            }
484            Self::StudentsT {
485                df,
486                location,
487                scale,
488            } => {
489                let t = (x - location) / scale;
490                ln_gamma((df + 1.0) / 2.0)
491                    - ln_gamma(df / 2.0)
492                    - 0.5 * (df * std::f64::consts::PI).ln()
493                    - scale.ln()
494                    - (df + 1.0) / 2.0 * (t * t / df).ln_1p()
495            }
496            Self::Weibull { shape, scale } => {
497                if x < 0.0 {
498                    return f64::NEG_INFINITY;
499                }
500                let z = x / scale;
501                shape.ln() - scale.ln() + (shape - 1.0) * z.ln() - z.powf(shape)
502            }
503            Self::PowerLaw { xmin, alpha } => {
504                if x < xmin {
505                    return f64::NEG_INFINITY;
506                }
507                (alpha - 1.0).ln() - xmin.ln() - alpha * (x / xmin).ln()
508            }
509            Self::Poisson { rate } => {
510                if x < 0.0 || x != x.floor() {
511                    return f64::NEG_INFINITY;
512                }
513                x * rate.ln() - rate - ln_gamma(x + 1.0)
514            }
515            Self::Bernoulli { p } => match x {
516                0.0 => (-p).ln_1p(),
517                1.0 => p.ln(),
518                _ => f64::NEG_INFINITY,
519            },
520            Self::Binomial { trials, p } => {
521                let n = trials as f64;
522                if x < 0.0 || x > n || x != x.floor() {
523                    return f64::NEG_INFINITY;
524                }
525                ln_gamma(n + 1.0) - ln_gamma(x + 1.0) - ln_gamma(n - x + 1.0)
526                    + x * p.ln()
527                    + (n - x) * (-p).ln_1p()
528            }
529            Self::Geometric { p, start } => {
530                let k = x - start as f64;
531                if k < 0.0 || x != x.floor() {
532                    return f64::NEG_INFINITY;
533                }
534                p.ln() + k * (-p).ln_1p()
535            }
536        }
537    }
538
539    /// The density, or the probability at a whole number for a discrete family.
540    pub fn density(&self, x: f64) -> f64 {
541        self.ln_density(x).exp()
542    }
543
544    fn sample(&self, rng: &mut Rng) -> f64 {
545        match *self {
546            Self::Gamma { shape, scale } => rng.gamma(shape) * scale,
547            Self::ChiSquared { df } => rng.gamma(df / 2.0) * 2.0,
548            Self::Beta { alpha, beta } => {
549                let a = rng.gamma(alpha);
550                let b = rng.gamma(beta);
551                a / (a + b)
552            }
553            Self::StudentsT {
554                df,
555                location,
556                scale,
557            } => {
558                let z = rng.normal();
559                let v = rng.gamma(df / 2.0) * 2.0;
560                location + scale * z / (v / df).sqrt()
561            }
562            // Inverting the CDF costs a few dozen incomplete gammas or betas per draw,
563            // each longer as the counts grow: seconds to minutes per test at counts in
564            // the millions.
565            Self::Poisson { rate } if rate >= 10.0 => rng.poisson(rate),
566            Self::Binomial { trials, p } if trials as f64 * p.min(1.0 - p) >= 10.0 => {
567                rng.binomial(trials, p)
568            }
569            _ => self.quantile(rng.uniform()),
570        }
571    }
572
573    /// Estimate a family's parameters from `values`, or say why the family cannot
574    /// describe them.
575    pub fn fit(family: DistributionType, values: &[f64]) -> Result<Fitted, &'static str> {
576        let n = values.len();
577        if n < 5 {
578            return Err("needs 5 or more values");
579        }
580        let nf = n as f64;
581        let mean = values.iter().sum::<f64>() / nf;
582        let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / (nf - 1.0);
583        let min = values.iter().copied().fold(f64::INFINITY, f64::min);
584        let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
585        let counts = values.iter().all(|v| *v >= 0.0 && *v == v.floor());
586        if variance <= 0.0 || !variance.is_finite() {
587            return Err("needs values that vary");
588        }
589        let positive = || {
590            if min > 0.0 {
591                Ok(())
592            } else {
593                Err("needs positive values")
594            }
595        };
596        let fitted = match family {
597            DistributionType::Normal => Fitted::Normal {
598                mean,
599                sd: variance.sqrt(),
600            },
601            DistributionType::LogNormal => {
602                positive()?;
603                let logs: Vec<f64> = values.iter().map(|v| v.ln()).collect();
604                let mu = logs.iter().sum::<f64>() / nf;
605                let var = logs.iter().map(|v| (v - mu).powi(2)).sum::<f64>() / (nf - 1.0);
606                Fitted::LogNormal {
607                    mu,
608                    sigma: var.sqrt(),
609                }
610            }
611            DistributionType::Uniform => Fitted::Uniform {
612                low: min,
613                high: max,
614            },
615            DistributionType::Exponential => {
616                if min < 0.0 {
617                    return Err("needs values of zero or more");
618                }
619                Fitted::Exponential { rate: 1.0 / mean }
620            }
621            DistributionType::Gamma => {
622                positive()?;
623                // Minka's closed-form approximation to the maximum-likelihood shape.
624                let mean_log = values.iter().map(|v| v.ln()).sum::<f64>() / nf;
625                let s = mean.ln() - mean_log;
626                if s <= 0.0 {
627                    return Err("needs values that vary");
628                }
629                let shape = (3.0 - s + ((s - 3.0).powi(2) + 24.0 * s).sqrt()) / (12.0 * s);
630                Fitted::Gamma {
631                    shape,
632                    scale: mean / shape,
633                }
634            }
635            DistributionType::ChiSquared => {
636                positive()?;
637                Fitted::ChiSquared { df: mean }
638            }
639            DistributionType::Beta => {
640                if min <= 0.0 || max >= 1.0 {
641                    return Err("needs values strictly between 0 and 1");
642                }
643                let common = mean * (1.0 - mean) / variance - 1.0;
644                if common <= 0.0 {
645                    return Err("spread too wide for a beta");
646                }
647                // Moments to start, then maximum likelihood, as every family here is
648                // fitted where it can be: AIC compares likelihoods, and a family
649                // fitted by moments competes with a handicap.
650                beta_mle(values, mean * common, (1.0 - mean) * common)
651            }
652            DistributionType::StudentsT => {
653                let fourth = values.iter().map(|v| (v - mean).powi(4)).sum::<f64>() / nf;
654                let excess = fourth / (variance * variance) - 3.0;
655                if excess <= 0.0 {
656                    return Err("tails no heavier than a normal's");
657                }
658                // Kurtosis 6 / (df - 4) above a normal's, for df above 4.
659                let df = (4.0 + 6.0 / excess).clamp(2.5, 1_000.0);
660                Fitted::StudentsT {
661                    df,
662                    location: mean,
663                    scale: (variance * (df - 2.0) / df).sqrt(),
664                }
665            }
666            DistributionType::Weibull => {
667                positive()?;
668                weibull_mle(values).ok_or("no weibull fits these values")?
669            }
670            DistributionType::PowerLaw => {
671                positive()?;
672                let sum_log = values.iter().map(|v| (v / min).ln()).sum::<f64>();
673                if sum_log <= 0.0 {
674                    return Err("needs values that vary");
675                }
676                Fitted::PowerLaw {
677                    xmin: min,
678                    alpha: 1.0 + nf / sum_log,
679                }
680            }
681            DistributionType::Poisson => {
682                if !counts {
683                    return Err("needs counts: whole numbers, zero or more");
684                }
685                Fitted::Poisson { rate: mean }
686            }
687            DistributionType::Bernoulli => {
688                if !values.iter().all(|v| *v == 0.0 || *v == 1.0) {
689                    return Err("needs only 0 and 1");
690                }
691                Fitted::Bernoulli { p: mean }
692            }
693            DistributionType::Binomial => {
694                if !counts {
695                    return Err("needs counts: whole numbers, zero or more");
696                }
697                if variance >= mean {
698                    return Err("spread too wide for a binomial");
699                }
700                // Moments, then at least as many trials as the largest count.
701                let p = 1.0 - variance / mean;
702                let trials = ((mean / p).round() as u64).max(max as u64).max(1);
703                Fitted::Binomial {
704                    trials,
705                    p: (mean / trials as f64).clamp(1e-12, 1.0 - 1e-12),
706                }
707            }
708            DistributionType::Geometric => {
709                if !counts {
710                    return Err("needs counts: whole numbers, zero or more");
711                }
712                // Counted from 1 (trials) when no zero appears, from 0 (failures) when
713                // one does.
714                let start = if min >= 1.0 { 1 } else { 0 };
715                Fitted::Geometric {
716                    p: 1.0 / (1.0 + mean - start as f64),
717                    start,
718                }
719            }
720            DistributionType::Constant | DistributionType::Unknown => {
721                return Err("not a distribution");
722            }
723        };
724        if fitted.valid() {
725            Ok(fitted)
726        } else {
727            Err("no fit for these values")
728        }
729    }
730
731    fn valid(&self) -> bool {
732        let finite_positive = |x: f64| x.is_finite() && x > 0.0;
733        match *self {
734            Self::Normal { mean, sd } => mean.is_finite() && finite_positive(sd),
735            Self::LogNormal { mu, sigma } => mu.is_finite() && finite_positive(sigma),
736            Self::Uniform { low, high } => low.is_finite() && high.is_finite() && high > low,
737            Self::Exponential { rate } => finite_positive(rate),
738            Self::Gamma { shape, scale } => finite_positive(shape) && finite_positive(scale),
739            Self::ChiSquared { df } => finite_positive(df),
740            Self::Beta { alpha, beta } => finite_positive(alpha) && finite_positive(beta),
741            Self::StudentsT {
742                df,
743                location,
744                scale,
745            } => finite_positive(df) && location.is_finite() && finite_positive(scale),
746            Self::Weibull { shape, scale } => finite_positive(shape) && finite_positive(scale),
747            Self::PowerLaw { xmin, alpha } => {
748                finite_positive(xmin) && alpha.is_finite() && alpha > 1.0
749            }
750            Self::Poisson { rate } => finite_positive(rate),
751            Self::Bernoulli { p } | Self::Geometric { p, .. } => p > 0.0 && p < 1.0,
752            Self::Binomial { trials, p } => trials >= 1 && p > 0.0 && p < 1.0,
753        }
754    }
755}
756
757/// Maximum-likelihood Beta by Newton's method on both shapes, from `alpha` and `beta`:
758/// `ψ(a) - ψ(a + b)` and `ψ(b) - ψ(a + b)` equal the mean logs of `x` and `1 - x`.
759fn beta_mle(values: &[f64], alpha: f64, beta: f64) -> Fitted {
760    let n = values.len() as f64;
761    let mean_log = values.iter().map(|v| v.ln()).sum::<f64>() / n;
762    let mean_log_rest = values.iter().map(|v| (-v).ln_1p()).sum::<f64>() / n;
763    let (mut a, mut b) = (alpha, beta);
764    for _ in 0..100 {
765        let both = digamma(a + b);
766        let g1 = digamma(a) - both - mean_log;
767        let g2 = digamma(b) - both - mean_log_rest;
768        let t = trigamma(a + b);
769        let (j11, j22, j12) = (trigamma(a) - t, trigamma(b) - t, -t);
770        let det = j11 * j22 - j12 * j12;
771        if det.abs() < 1e-300 {
772            break;
773        }
774        let da = (g1 * j22 - g2 * j12) / det;
775        let db = (g2 * j11 - g1 * j12) / det;
776        // Halved until both stay positive.
777        let mut step = 1.0;
778        while (a - step * da <= 0.0 || b - step * db <= 0.0) && step > 1e-6 {
779            step /= 2.0;
780        }
781        let (next_a, next_b) = (a - step * da, b - step * db);
782        let done = (next_a - a).abs() < 1e-10 * a && (next_b - b).abs() < 1e-10 * b;
783        a = next_a;
784        b = next_b;
785        if done {
786            break;
787        }
788    }
789    Fitted::Beta { alpha: a, beta: b }
790}
791
792/// `ψ(x)`, the digamma function, for `x > 0`: shifted up past 6, then its asymptotic
793/// series.
794fn digamma(mut x: f64) -> f64 {
795    let mut result = 0.0;
796    while x < 6.0 {
797        result -= 1.0 / x;
798        x += 1.0;
799    }
800    let inv = 1.0 / x;
801    let inv2 = inv * inv;
802    result + x.ln()
803        - 0.5 * inv
804        - inv2 * (1.0 / 12.0 - inv2 * (1.0 / 120.0 - inv2 * (1.0 / 252.0 - inv2 / 240.0)))
805}
806
807/// `ψ'(x)`, the trigamma function, for `x > 0`.
808fn trigamma(mut x: f64) -> f64 {
809    let mut result = 0.0;
810    while x < 6.0 {
811        result += 1.0 / (x * x);
812        x += 1.0;
813    }
814    let inv = 1.0 / x;
815    let inv2 = inv * inv;
816    result
817        + inv
818        + 0.5 * inv2
819        + inv * inv2 * (1.0 / 6.0 - inv2 * (1.0 / 30.0 - inv2 * (1.0 / 42.0 - inv2 / 30.0)))
820}
821
822/// Maximum-likelihood Weibull by Newton's method on the shape.
823fn weibull_mle(values: &[f64]) -> Option<Fitted> {
824    let n = values.len() as f64;
825    let logs: Vec<f64> = values.iter().map(|v| v.ln()).collect();
826    let mean_log = logs.iter().sum::<f64>() / n;
827    // Scaled so the powers do not overflow for large values.
828    let top = values.iter().copied().fold(0.0, f64::max);
829    let scaled: Vec<f64> = values.iter().map(|v| v / top).collect();
830    let scaled_logs: Vec<f64> = scaled.iter().map(|v| v.ln()).collect();
831    let mean_scaled_log = scaled_logs.iter().sum::<f64>() / n;
832    let sd_log = (logs.iter().map(|l| (l - mean_log).powi(2)).sum::<f64>() / n).sqrt();
833    // The shape a Gumbel on the logs suggests, as a start.
834    let mut k = if sd_log > 0.0 { 1.2825 / sd_log } else { 1.0 };
835    for _ in 0..100 {
836        let (mut s0, mut s1, mut s2) = (0.0, 0.0, 0.0);
837        for (x, l) in scaled.iter().zip(&scaled_logs) {
838            let xk = x.powf(k);
839            s0 += xk;
840            s1 += xk * l;
841            s2 += xk * l * l;
842        }
843        if s0 <= 0.0 {
844            return None;
845        }
846        let f = s1 / s0 - 1.0 / k - mean_scaled_log;
847        let df = (s2 * s0 - s1 * s1) / (s0 * s0) + 1.0 / (k * k);
848        let step = f / df;
849        let next = (k - step).clamp(k / 2.0, k * 2.0);
850        let done = (next - k).abs() < 1e-10 * k;
851        k = next;
852        if done {
853            break;
854        }
855    }
856    let s0 = scaled.iter().map(|x| x.powf(k)).sum::<f64>();
857    let scale = top * (s0 / n).powf(1.0 / k);
858    Some(Fitted::Weibull { shape: k, scale })
859}
860
861/// The two-sided KS statistic of sorted `values` against `fitted`: the largest CDF gap
862/// on either side of each step. Ties are one step, compared at and just below the value,
863/// so discrete fits are measured at their jumps.
864pub fn ks_statistic(sorted: &[f64], fitted: &Fitted) -> f64 {
865    let n = sorted.len() as f64;
866    let mut d: f64 = 0.0;
867    let mut i = 0;
868    while i < sorted.len() {
869        let x = sorted[i];
870        let mut j = i;
871        while j < sorted.len() && sorted[j] == x {
872            j += 1;
873        }
874        let below = i as f64 / n;
875        let at = j as f64 / n;
876        d = d
877            .max((at - fitted.cdf(x)).abs())
878            .max((fitted.cdf_below(x) - below).abs());
879        i = j;
880    }
881    d
882}
883
884/// Test one family against `values`, all of them as read.
885pub fn test_family(family: DistributionType, values: &[f64], seed: u64) -> FitOutcome {
886    let fitted = match Fitted::fit(family, values) {
887        Ok(fitted) => fitted,
888        Err(reason) => return FitOutcome::NotApplicable(reason),
889    };
890    let ln_likelihood: f64 = values.iter().map(|v| fitted.ln_density(*v)).sum();
891    let aic = 2.0 * fitted.parameters() as f64 - 2.0 * ln_likelihood;
892
893    // The test runs on a seeded random subset, refitted: the bootstrap below refits
894    // each simulated sample, and the column has to be treated the same way.
895    let mut rng = Rng::new(seed ^ family as u64);
896    let mut subset = values.to_vec();
897    if subset.len() > TEST_VALUES {
898        for i in 0..TEST_VALUES {
899            let j = i + (rng.next_u64() % (subset.len() - i) as u64) as usize;
900            subset.swap(i, j);
901        }
902        subset.truncate(TEST_VALUES);
903    }
904    subset.sort_by(f64::total_cmp);
905    let Ok(sub_fit) = Fitted::fit(family, &subset) else {
906        return FitOutcome::NotApplicable("no fit for these values");
907    };
908    let observed = ks_statistic(&subset, &sub_fit);
909
910    let mut beyond = 0;
911    let mut simulated = vec![0.0; subset.len()];
912    for _ in 0..REPLICATES {
913        for value in simulated.iter_mut() {
914            *value = sub_fit.sample(&mut rng);
915        }
916        simulated.sort_by(f64::total_cmp);
917        // A simulated sample the family cannot refit counts against the fit: the test
918        // leans towards not rejecting rather than towards a p-value it did not earn.
919        let statistic = match Fitted::fit(family, &simulated) {
920            Ok(refit) => ks_statistic(&simulated, &refit),
921            Err(_) => f64::INFINITY,
922        };
923        if statistic >= observed {
924            beyond += 1;
925        }
926    }
927    FitOutcome::Tested(FitTest {
928        fitted,
929        p_value: (1 + beyond) as f64 / (1 + REPLICATES) as f64,
930        beyond,
931        replicates: REPLICATES,
932        tested_on: subset.len(),
933        aic,
934    })
935}
936
937/// Test every family, in parallel: each is independent, and each is a few hundred
938/// thousand CDF evaluations.
939pub fn test_all(values: &[f64], seed: u64) -> Vec<(DistributionType, FitOutcome)> {
940    std::thread::scope(|scope| {
941        let handles: Vec<_> = FAMILIES
942            .iter()
943            .map(|family| scope.spawn(move || (*family, test_family(*family, values, seed))))
944            .collect();
945        handles
946            .into_iter()
947            .filter_map(|handle| handle.join().ok())
948            .collect()
949    })
950}
951
952/// Below this p-value a family is rejected. At 0.05 the true family of a column is
953/// rejected one time in twenty by design, and a verdict that flips between samples of
954/// the same data says more about the sample.
955pub const REJECT_P: f64 = 0.01;
956
957/// A family that contains a simpler one fits whatever the simpler one does: uniform
958/// values are a Beta(1, 1). When the simpler one is not rejected, it is the answer.
959fn nested_in(family: DistributionType) -> &'static [DistributionType] {
960    match family {
961        DistributionType::Beta => &[DistributionType::Uniform],
962        DistributionType::Gamma => &[DistributionType::Exponential, DistributionType::ChiSquared],
963        DistributionType::Weibull => &[DistributionType::Exponential],
964        DistributionType::StudentsT => &[DistributionType::Normal],
965        // A binomial of one trial is a Bernoulli; of many trials and a small p, a
966        // Poisson.
967        DistributionType::Binomial => &[DistributionType::Bernoulli, DistributionType::Poisson],
968        _ => &[],
969    }
970}
971
972/// The family the values are consistent with, or `Unknown` when all are rejected: the
973/// lowest AIC among the unrejected, preferring a count distribution for counts (density
974/// and probability are not on one scale).
975pub fn select(results: &[(DistributionType, FitOutcome)], counts: bool) -> DistributionType {
976    let held: Vec<&FitTest> = results
977        .iter()
978        .filter_map(|(_, outcome)| outcome.test())
979        .filter(|test| test.p_value >= REJECT_P)
980        .collect();
981    let discrete_held = counts && held.iter().any(|test| test.fitted.discrete());
982    let best = held
983        .iter()
984        .filter(|test| test.fitted.discrete() == discrete_held)
985        .min_by(|a, b| a.aic.total_cmp(&b.aic));
986    let Some(best) = best else {
987        return DistributionType::Unknown;
988    };
989    let family = best.fitted.family();
990    // The simpler of two nested families when it holds and the richer one is not
991    // decisively better: an AIC gap of 10 or more is the usual line for "no support".
992    // A test on a few hundred values can miss a t's tails that every value shows.
993    nested_in(family)
994        .iter()
995        .find(|simpler| {
996            held.iter()
997                .any(|test| test.fitted.family() == **simpler && test.aic - best.aic < DECISIVE_AIC)
998        })
999        .copied()
1000        .unwrap_or(family)
1001}
1002
1003/// An AIC gap at which the family behind has essentially no support.
1004const DECISIVE_AIC: f64 = 10.0;
1005
1006/// The families as a list is read: tested ones by p-value, highest first, then the
1007/// ones that do not apply, in [`FAMILIES`] order.
1008pub fn listing_order(results: &[(DistributionType, FitOutcome)]) -> Vec<DistributionType> {
1009    let mut tested: Vec<(DistributionType, f64)> = results
1010        .iter()
1011        .filter_map(|(family, outcome)| outcome.p_value().map(|p| (*family, p)))
1012        .collect();
1013    tested.sort_by(|a, b| b.1.total_cmp(&a.1));
1014    let mut order: Vec<DistributionType> = tested.into_iter().map(|(family, _)| family).collect();
1015    let missing: Vec<DistributionType> = FAMILIES
1016        .iter()
1017        .filter(|family| !order.contains(family))
1018        .copied()
1019        .collect();
1020    order.extend(missing);
1021    order
1022}
1023
1024/// Theoretical quantiles for a Q-Q plot of `n` sorted values: the fitted quantile at
1025/// each plotting position `i / (n + 1)`.
1026pub fn qq_quantiles(fitted: &Fitted, n: usize) -> Vec<f64> {
1027    (1..=n)
1028        .map(|i| fitted.quantile(i as f64 / (n as f64 + 1.0)))
1029        .collect()
1030}
1031
1032const LN_2PI: f64 = 1.837_877_066_409_345_5;
1033
1034/// `ln Γ(x)` for `x > 0`, by the Lanczos approximation (g = 7, nine terms), accurate
1035/// to about 1e-15.
1036pub fn ln_gamma(x: f64) -> f64 {
1037    const COEFFICIENTS: [f64; 9] = [
1038        0.999_999_999_999_809_9,
1039        676.520_368_121_885_1,
1040        -1_259.139_216_722_402_8,
1041        771.323_428_777_653_1,
1042        -176.615_029_162_140_6,
1043        12.507_343_278_686_905,
1044        -0.138_571_095_265_720_12,
1045        9.984_369_578_019_572e-6,
1046        1.505_632_735_149_311_6e-7,
1047    ];
1048    if x < 0.5 {
1049        // Reflection, for the half-integers the t density needs near zero.
1050        let pi = std::f64::consts::PI;
1051        return (pi / (pi * x).sin()).ln() - ln_gamma(1.0 - x);
1052    }
1053    let x = x - 1.0;
1054    let mut sum = COEFFICIENTS[0];
1055    for (i, c) in COEFFICIENTS.iter().enumerate().skip(1) {
1056        sum += c / (x + i as f64);
1057    }
1058    let t = x + 7.5;
1059    0.5 * LN_2PI + (x + 0.5) * t.ln() - t + sum.ln()
1060}
1061
1062fn ln_beta(a: f64, b: f64) -> f64 {
1063    ln_gamma(a) + ln_gamma(b) - ln_gamma(a + b)
1064}
1065
1066fn gamma_ln_pdf(x: f64, shape: f64, scale: f64) -> f64 {
1067    if x <= 0.0 {
1068        return f64::NEG_INFINITY;
1069    }
1070    (shape - 1.0) * x.ln() - x / scale - shape * scale.ln() - ln_gamma(shape)
1071}
1072
1073/// The regularized lower incomplete gamma `P(a, x)`.
1074pub fn gamma_p(a: f64, x: f64) -> f64 {
1075    let (p, _) = incomplete_gamma(a, x);
1076    p
1077}
1078
1079/// Its complement `Q(a, x) = 1 - P(a, x)`, computed directly: in the upper tail `P` is
1080/// within 1e-16 of 1, and `1 - P` would be noise.
1081pub fn gamma_q(a: f64, x: f64) -> f64 {
1082    let (_, q) = incomplete_gamma(a, x);
1083    q
1084}
1085
1086/// `(P(a, x), Q(a, x))`: by the series below `a + 1`, by the continued fraction for
1087/// `Q` above (Numerical Recipes, `gser` and `gcf`), each side taken from whichever is
1088/// computed without cancellation.
1089fn incomplete_gamma(a: f64, x: f64) -> (f64, f64) {
1090    if x <= 0.0 {
1091        return (0.0, 1.0);
1092    }
1093    if !x.is_finite() {
1094        return (1.0, 0.0);
1095    }
1096    let ln_prefix = a * x.ln() - x - ln_gamma(a);
1097    if x < a + 1.0 {
1098        let mut term = 1.0 / a;
1099        let mut sum = term;
1100        let mut ap = a;
1101        for _ in 0..10_000 {
1102            ap += 1.0;
1103            term *= x / ap;
1104            sum += term;
1105            if term.abs() < sum.abs() * 1e-16 {
1106                break;
1107            }
1108        }
1109        let p = (sum.ln() + ln_prefix).exp().clamp(0.0, 1.0);
1110        (p, 1.0 - p)
1111    } else {
1112        // Lentz's method for the continued fraction of Q(a, x).
1113        let tiny = 1e-300;
1114        let mut b = x + 1.0 - a;
1115        let mut c = 1.0 / tiny;
1116        let mut d = 1.0 / b;
1117        let mut h = d;
1118        for i in 1..10_000 {
1119            let an = -(i as f64) * (i as f64 - a);
1120            b += 2.0;
1121            d = an * d + b;
1122            if d.abs() < tiny {
1123                d = tiny;
1124            }
1125            c = b + an / c;
1126            if c.abs() < tiny {
1127                c = tiny;
1128            }
1129            d = 1.0 / d;
1130            let delta = d * c;
1131            h *= delta;
1132            if (delta - 1.0).abs() < 1e-16 {
1133                break;
1134            }
1135        }
1136        let q = (ln_prefix + h.ln()).exp().clamp(0.0, 1.0);
1137        (1.0 - q, q)
1138    }
1139}
1140
1141/// The regularized incomplete beta `I_x(a, b)` (Numerical Recipes, `betai`).
1142pub fn beta_inc(a: f64, b: f64, x: f64) -> f64 {
1143    if x <= 0.0 {
1144        return 0.0;
1145    }
1146    if x >= 1.0 {
1147        return 1.0;
1148    }
1149    let ln_front = a * x.ln() + b * (-x).ln_1p() - ln_beta(a, b);
1150    if x < (a + 1.0) / (a + b + 2.0) {
1151        (ln_front.exp() * beta_fraction(a, b, x) / a).clamp(0.0, 1.0)
1152    } else {
1153        (1.0 - ln_front.exp() * beta_fraction(b, a, 1.0 - x) / b).clamp(0.0, 1.0)
1154    }
1155}
1156
1157fn beta_fraction(a: f64, b: f64, x: f64) -> f64 {
1158    let tiny = 1e-300;
1159    let (qab, qap, qam) = (a + b, a + 1.0, a - 1.0);
1160    let mut c = 1.0;
1161    let mut d = 1.0 - qab * x / qap;
1162    if d.abs() < tiny {
1163        d = tiny;
1164    }
1165    d = 1.0 / d;
1166    let mut h = d;
1167    for m in 1..10_000 {
1168        let m = m as f64;
1169        let m2 = 2.0 * m;
1170        let aa = m * (b - m) * x / ((qam + m2) * (a + m2));
1171        d = 1.0 + aa * d;
1172        if d.abs() < tiny {
1173            d = tiny;
1174        }
1175        c = 1.0 + aa / c;
1176        if c.abs() < tiny {
1177            c = tiny;
1178        }
1179        d = 1.0 / d;
1180        h *= d * c;
1181        let aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
1182        d = 1.0 + aa * d;
1183        if d.abs() < tiny {
1184            d = tiny;
1185        }
1186        c = 1.0 + aa / c;
1187        if c.abs() < tiny {
1188            c = tiny;
1189        }
1190        d = 1.0 / d;
1191        let delta = d * c;
1192        h *= delta;
1193        if (delta - 1.0).abs() < 1e-16 {
1194            break;
1195        }
1196    }
1197    h
1198}
1199
1200/// The standard normal CDF, through `erfc(x) = Q(1/2, x²)`: accurate in the tails,
1201/// where `1 - erf` loses everything.
1202pub fn normal_cdf(z: f64) -> f64 {
1203    if z.is_nan() {
1204        return f64::NAN;
1205    }
1206    let tail = 0.5 * gamma_q(0.5, 0.5 * z * z);
1207    if z < 0.0 { tail } else { 1.0 - tail }
1208}
1209
1210/// The standard normal quantile. Acklam's rational approximation, then one Halley step
1211/// on the exact CDF: good to about 1e-15. Exactly 0 at the median, `-inf` and `inf` at
1212/// 0 and 1, NaN outside them.
1213pub fn normal_quantile(p: f64) -> f64 {
1214    if !(0.0..=1.0).contains(&p) {
1215        return f64::NAN;
1216    }
1217    if p == 0.0 {
1218        return f64::NEG_INFINITY;
1219    }
1220    if p == 1.0 {
1221        return f64::INFINITY;
1222    }
1223    if p == 0.5 {
1224        return 0.0;
1225    }
1226    const A: [f64; 6] = [
1227        -3.969_683_028_665_376e1,
1228        2.209_460_984_245_205e2,
1229        -2.759_285_104_469_687e2,
1230        1.383_577_518_672_69e2,
1231        -3.066_479_806_614_716e1,
1232        2.506_628_277_459_239,
1233    ];
1234    const B: [f64; 5] = [
1235        -5.447_609_879_822_406e1,
1236        1.615_858_368_580_409e2,
1237        -1.556_989_798_598_866e2,
1238        6.680_131_188_771_972e1,
1239        -1.328_068_155_288_572e1,
1240    ];
1241    const C: [f64; 6] = [
1242        -7.784_894_002_430_293e-3,
1243        -3.223_964_580_411_365e-1,
1244        -2.400_758_277_161_838,
1245        -2.549_732_539_343_734,
1246        4.374_664_141_464_968,
1247        2.938_163_982_698_783,
1248    ];
1249    const D: [f64; 4] = [
1250        7.784_695_709_041_462e-3,
1251        3.224_671_290_700_398e-1,
1252        2.445_134_137_142_996,
1253        3.754_408_661_907_416,
1254    ];
1255    let low = 0.02425;
1256    let x = if p < low {
1257        let q = (-2.0 * p.ln()).sqrt();
1258        (((((C[0] * q + C[1]) * q + C[2]) * q + C[3]) * q + C[4]) * q + C[5])
1259            / ((((D[0] * q + D[1]) * q + D[2]) * q + D[3]) * q + 1.0)
1260    } else if p <= 1.0 - low {
1261        let q = p - 0.5;
1262        let r = q * q;
1263        (((((A[0] * r + A[1]) * r + A[2]) * r + A[3]) * r + A[4]) * r + A[5]) * q
1264            / (((((B[0] * r + B[1]) * r + B[2]) * r + B[3]) * r + B[4]) * r + 1.0)
1265    } else {
1266        let q = (-2.0 * (-p).ln_1p()).sqrt();
1267        -(((((C[0] * q + C[1]) * q + C[2]) * q + C[3]) * q + C[4]) * q + C[5])
1268            / ((((D[0] * q + D[1]) * q + D[2]) * q + D[3]) * q + 1.0)
1269    };
1270    // One Halley step against the exact CDF.
1271    let e = normal_cdf(x) - p;
1272    let u = e * (2.0 * std::f64::consts::PI).sqrt() * (0.5 * x * x).exp();
1273    x - u / (1.0 + 0.5 * x * u)
1274}
1275
1276/// Student's t CDF with `df` degrees of freedom.
1277pub fn t_cdf(t: f64, df: f64) -> f64 {
1278    if t.is_nan() {
1279        return f64::NAN;
1280    }
1281    let tail = 0.5 * beta_inc(0.5 * df, 0.5, df / (df + t * t));
1282    if t > 0.0 { 1.0 - tail } else { tail }
1283}
1284
1285fn t_quantile(p: f64, df: f64) -> f64 {
1286    if p <= 0.0 {
1287        return f64::NEG_INFINITY;
1288    }
1289    if p >= 1.0 {
1290        return f64::INFINITY;
1291    }
1292    if p == 0.5 {
1293        return 0.0;
1294    }
1295    let mut high = 1.0;
1296    let target = p.max(1.0 - p);
1297    while t_cdf(high, df) < target && high < 1e300 {
1298        high *= 2.0;
1299    }
1300    let (mut low, mut hi) = (0.0, high);
1301    for _ in 0..200 {
1302        let mid = 0.5 * (low + hi);
1303        if t_cdf(mid, df) < target {
1304            low = mid;
1305        } else {
1306            hi = mid;
1307        }
1308        if hi - low <= 1e-13 * hi {
1309            break;
1310        }
1311    }
1312    let t = 0.5 * (low + hi);
1313    if p < 0.5 { -t } else { t }
1314}
1315
1316/// A small seeded generator (SplitMix64): the same seed draws the same samples on
1317/// every machine, which a test of a test needs.
1318pub struct Rng(u64);
1319
1320impl Rng {
1321    pub fn new(seed: u64) -> Self {
1322        Self(seed)
1323    }
1324
1325    pub fn next_u64(&mut self) -> u64 {
1326        self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
1327        let mut z = self.0;
1328        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1329        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1330        z ^ (z >> 31)
1331    }
1332
1333    /// Uniform on (0, 1), never either end.
1334    pub fn uniform(&mut self) -> f64 {
1335        ((self.next_u64() >> 11) as f64 + 0.5) / (1u64 << 53) as f64
1336    }
1337
1338    pub fn normal(&mut self) -> f64 {
1339        normal_quantile(self.uniform())
1340    }
1341
1342    /// A Gamma(shape, 1) draw, by Marsaglia and Tsang's method.
1343    pub fn gamma(&mut self, shape: f64) -> f64 {
1344        if shape < 1.0 {
1345            let boost = self.uniform().powf(1.0 / shape);
1346            return self.gamma(shape + 1.0) * boost;
1347        }
1348        let d = shape - 1.0 / 3.0;
1349        let c = 1.0 / (9.0 * d).sqrt();
1350        loop {
1351            let x = self.normal();
1352            let v = 1.0 + c * x;
1353            if v <= 0.0 {
1354                continue;
1355            }
1356            let v = v * v * v;
1357            let u = self.uniform();
1358            if u.ln() < 0.5 * x * x + d - d * v + d * v.ln() {
1359                return d * v;
1360            }
1361        }
1362    }
1363
1364    /// A Poisson(rate) draw for a rate of 10 or more, by Hörmann's transformed
1365    /// rejection (PTRS): exact, and a constant cost whatever the rate.
1366    pub fn poisson(&mut self, rate: f64) -> f64 {
1367        let ln_rate = rate.ln();
1368        let b = 0.931 + 2.53 * rate.sqrt();
1369        let a = -0.059 + 0.02483 * b;
1370        let ln_inv_alpha = (1.1239 + 1.1328 / (b - 3.4)).ln();
1371        let v_r = 0.9277 - 3.6224 / (b - 2.0);
1372        loop {
1373            let u = self.uniform() - 0.5;
1374            let v = self.uniform();
1375            let us = 0.5 - u.abs();
1376            let k = ((2.0 * a / us + b) * u + rate + 0.43).floor();
1377            if us >= 0.07 && v <= v_r {
1378                return k;
1379            }
1380            if k < 0.0 || (us < 0.013 && v > us) {
1381                continue;
1382            }
1383            if v.ln() + ln_inv_alpha - (a / (us * us) + b).ln()
1384                <= -rate + k * ln_rate - ln_gamma(k + 1.0)
1385            {
1386                return k;
1387            }
1388        }
1389    }
1390
1391    /// A Binomial(trials, p) draw for `trials * min(p, 1 - p)` of 10 or more, by
1392    /// Hörmann's transformed rejection (BTRS): exact, and a constant cost whatever
1393    /// the trials.
1394    pub fn binomial(&mut self, trials: u64, p: f64) -> f64 {
1395        if p > 0.5 {
1396            return trials as f64 - self.binomial(trials, 1.0 - p);
1397        }
1398        let n = trials as f64;
1399        let q = 1.0 - p;
1400        let spq = (n * p * q).sqrt();
1401        let b = 1.15 + 2.53 * spq;
1402        let a = -0.0873 + 0.0248 * b + 0.01 * p;
1403        let c = n * p + 0.5;
1404        let v_r = 0.92 - 4.2 / b;
1405        let alpha = (2.83 + 5.1 / b) * spq;
1406        let ln_pq = (p / q).ln();
1407        let m = ((n + 1.0) * p).floor();
1408        let h = ln_gamma(m + 1.0) + ln_gamma(n - m + 1.0);
1409        loop {
1410            let u = self.uniform() - 0.5;
1411            let v = self.uniform();
1412            let us = 0.5 - u.abs();
1413            let k = ((2.0 * a / us + b) * u + c).floor();
1414            if k < 0.0 || k > n {
1415                continue;
1416            }
1417            if us >= 0.07 && v <= v_r {
1418                return k;
1419            }
1420            let v = (v * alpha / (a / (us * us) + b)).ln();
1421            if v <= h - ln_gamma(k + 1.0) - ln_gamma(n - k + 1.0) + (k - m) * ln_pq {
1422                return k;
1423            }
1424        }
1425    }
1426}
1427
1428#[cfg(test)]
1429mod tests {
1430    use super::*;
1431
1432    fn close(a: f64, b: f64, tolerance: f64) -> bool {
1433        (a - b).abs() <= tolerance * b.abs().max(1.0)
1434    }
1435
1436    /// Reference values from Wichura's AS241 (Python's `NormalDist().inv_cdf`).
1437    #[test]
1438    fn normal_quantiles_match_reference_values() {
1439        for (p, z) in [
1440            (0.975, 1.959_963_984_540_054),
1441            (0.841_344_746_068_542_9, 1.0),
1442            (0.001, -3.090_232_306_167_813),
1443            (1e-10, -6.361_340_902_404_056),
1444            (0.3, -0.524_400_512_708_041),
1445        ] {
1446            assert!(
1447                close(normal_quantile(p), z, 1e-12),
1448                "{p}: {}",
1449                normal_quantile(p)
1450            );
1451        }
1452        assert_eq!(normal_quantile(0.5), 0.0);
1453        assert_eq!(normal_quantile(0.0), f64::NEG_INFINITY);
1454        assert_eq!(normal_quantile(1.0), f64::INFINITY);
1455        assert!(normal_quantile(1.5).is_nan());
1456        let grid: Vec<f64> = (1..1000)
1457            .map(|i| normal_quantile(i as f64 / 1000.0))
1458            .collect();
1459        assert!(grid.windows(2).all(|pair| pair[0] < pair[1]));
1460        for i in 1..500 {
1461            let p = i as f64 / 1000.0;
1462            assert!(close(normal_quantile(p), -normal_quantile(1.0 - p), 1e-12));
1463        }
1464    }
1465
1466    #[test]
1467    fn cdfs_match_reference_values() {
1468        assert!(close(normal_cdf(1.959_963_984_540_054), 0.975, 1e-14));
1469        assert!(close(normal_cdf(-8.0), 6.220_960_574_271_784e-16, 1e-9));
1470        // Student's t with 5 df at 2.015048373: 0.95.
1471        assert!(close(t_cdf(2.015_048_372_669_157, 5.0), 0.95, 1e-10));
1472        // Chi-squared, 3 df, at 7.814727903: 0.95.
1473        let chi = Fitted::ChiSquared { df: 3.0 };
1474        assert!(close(chi.cdf(7.814_727_903_251_178), 0.95, 1e-10));
1475        // Beta(2, 5) at 0.5: 57/64.
1476        let beta = Fitted::Beta {
1477            alpha: 2.0,
1478            beta: 5.0,
1479        };
1480        assert!(close(beta.cdf(0.5), 57.0 / 64.0, 1e-12));
1481        // I_0.5(2, 3) = 11/16; Beta(1, 1) is the uniform.
1482        assert!(close(beta_inc(2.0, 3.0, 0.5), 0.6875, 1e-12));
1483        assert!(close(beta_inc(1.0, 1.0, 0.3), 0.3, 1e-12));
1484        // The 97.5th percentile of t with 5 degrees of freedom is 2.5706.
1485        assert!(close(t_cdf(2.5706, 5.0), 0.975, 1e-4));
1486        assert!(close(t_cdf(-2.5706, 5.0), 0.025, 1e-4));
1487        assert!(close(t_cdf(0.0, 5.0), 0.5, 1e-12));
1488        // Poisson(3): P(X <= 2) = 8.5 e^-3.
1489        let poisson = Fitted::Poisson { rate: 3.0 };
1490        assert!(close(poisson.cdf(2.0), 8.5 * (-3.0f64).exp(), 1e-12));
1491        // Binomial(10, 0.3): P(X <= 3) = 0.6496107184.
1492        let binomial = Fitted::Binomial { trials: 10, p: 0.3 };
1493        assert!(close(binomial.cdf(3.0), 0.649_610_718_4, 1e-9));
1494        assert!(close(ln_gamma(0.5), 0.572_364_942_924_700_1, 1e-14));
1495        assert!(close(ln_gamma(10.0), 12.801_827_480_081_469, 1e-14));
1496    }
1497
1498    /// Every family's quantile inverts its CDF and rises with the probability, and
1499    /// the log-normal's stay positive.
1500    #[test]
1501    fn quantiles_invert_their_cdfs() {
1502        let fits = [
1503            Fitted::Normal { mean: 3.0, sd: 2.0 },
1504            Fitted::LogNormal {
1505                mu: 0.5,
1506                sigma: 0.8,
1507            },
1508            Fitted::Uniform {
1509                low: -1.0,
1510                high: 4.0,
1511            },
1512            Fitted::Exponential { rate: 2.0 },
1513            Fitted::Gamma {
1514                shape: 2.5,
1515                scale: 0.7,
1516            },
1517            Fitted::ChiSquared { df: 5.0 },
1518            Fitted::Beta {
1519                alpha: 2.0,
1520                beta: 5.0,
1521            },
1522            Fitted::StudentsT {
1523                df: 5.0,
1524                location: 1.0,
1525                scale: 2.0,
1526            },
1527            Fitted::Weibull {
1528                shape: 1.7,
1529                scale: 3.0,
1530            },
1531            Fitted::PowerLaw {
1532                xmin: 1.0,
1533                alpha: 2.5,
1534            },
1535        ];
1536        for fit in fits {
1537            let qs: Vec<f64> = (1..100).map(|i| fit.quantile(i as f64 / 100.0)).collect();
1538            assert!(qs.windows(2).all(|pair| pair[0] < pair[1]), "{fit:?}");
1539            for (i, q) in qs.iter().enumerate() {
1540                let p = (i + 1) as f64 / 100.0;
1541                assert!(
1542                    close(fit.cdf(*q), p, 1e-8),
1543                    "{fit:?} at {p}: {}",
1544                    fit.cdf(*q)
1545                );
1546            }
1547        }
1548        let lognormal = Fitted::LogNormal {
1549            mu: 0.0,
1550            sigma: 1.0,
1551        };
1552        assert!(lognormal.quantile(1e-9) > 0.0);
1553        let discrete = [
1554            Fitted::Poisson { rate: 4.0 },
1555            Fitted::Binomial { trials: 20, p: 0.4 },
1556            Fitted::Geometric { p: 0.3, start: 1 },
1557            Fitted::Bernoulli { p: 0.3 },
1558            // Fitted to counts in the millions, where walking to the answer is a
1559            // million steps.
1560            Fitted::Poisson { rate: 1e6 },
1561            Fitted::Binomial {
1562                trials: 2_000_000,
1563                p: 0.4,
1564            },
1565            Fitted::Geometric { p: 1e-6, start: 0 },
1566        ];
1567        for fit in discrete {
1568            for i in 1..100 {
1569                let p = i as f64 / 100.0;
1570                let k = fit.quantile(p);
1571                assert!(fit.cdf(k) >= p && fit.cdf(k - 1.0) < p, "{fit:?} at {p}");
1572            }
1573        }
1574    }
1575
1576    /// Both sides of each step count: values bunched high against a uniform are far
1577    /// from it below the first one, which a check at the top of each step misses.
1578    #[test]
1579    fn the_ks_statistic_is_two_sided() {
1580        let uniform = Fitted::Uniform {
1581            low: 0.0,
1582            high: 1.0,
1583        };
1584        assert!(close(ks_statistic(&[0.5, 0.6, 0.9], &uniform), 0.5, 1e-12));
1585        assert!(close(ks_statistic(&[0.1, 0.4, 0.7], &uniform), 0.3, 1e-12));
1586        // Ties are one step: three 1s against a fair coin are 0.5 away at 1.
1587        let coin = Fitted::Bernoulli { p: 0.5 };
1588        assert!(close(ks_statistic(&[1.0, 1.0, 1.0], &coin), 0.5, 1e-12));
1589    }
1590
1591    fn draw(fit: Fitted, n: usize, seed: u64) -> Vec<f64> {
1592        let mut rng = Rng::new(seed);
1593        (0..n).map(|_| fit.sample(&mut rng)).collect()
1594    }
1595
1596    /// The Poisson and binomial samplers for large counts draw the distribution, not
1597    /// an approximation: whole numbers whose CDF matches the family's.
1598    #[test]
1599    fn large_count_draws_follow_their_family() {
1600        let fits = [
1601            Fitted::Poisson { rate: 10.0 },
1602            Fitted::Poisson { rate: 37.5 },
1603            Fitted::Poisson { rate: 1e6 },
1604            Fitted::Binomial { trials: 40, p: 0.7 },
1605            Fitted::Binomial {
1606                trials: 2_000_000,
1607                p: 0.4,
1608            },
1609        ];
1610        for fit in fits {
1611            let mut values = draw(fit, 5_000, 9);
1612            assert!(values.iter().all(|v| *v >= 0.0 && *v == v.floor()));
1613            values.sort_by(f64::total_cmp);
1614            // 1.63 / sqrt(n) is the 1% critical value of the KS statistic.
1615            let d = ks_statistic(&values, &fit);
1616            assert!(d < 1.63 / (5_000f64).sqrt(), "{fit:?}: D = {d}");
1617        }
1618    }
1619
1620    /// The bootstrap p-value is calibrated: values drawn from a family are rejected at
1621    /// 5% about 5% of the time, and values from another are rejected.
1622    #[test]
1623    fn the_bootstrap_p_value_is_calibrated() {
1624        let normal = Fitted::Normal {
1625            mean: 10.0,
1626            sd: 3.0,
1627        };
1628        let rejected = (0..40)
1629            .filter(|seed| {
1630                let values = draw(normal, 120, 1_000 + seed);
1631                test_family(DistributionType::Normal, &values, *seed)
1632                    .p_value()
1633                    .unwrap()
1634                    < 0.05
1635            })
1636            .count();
1637        assert!(
1638            rejected <= 6,
1639            "{rejected} of 40 normal samples rejected as normal"
1640        );
1641
1642        let skewed = draw(
1643            Fitted::LogNormal {
1644                mu: 0.0,
1645                sigma: 1.0,
1646            },
1647            400,
1648            7,
1649        );
1650        let outcome = test_family(DistributionType::Normal, &skewed, 7);
1651        let test = outcome.test().unwrap();
1652        assert!(test.at_bound(), "{test:?}");
1653        assert!(test.p_value < 0.01);
1654    }
1655
1656    /// A family that does not apply says why and has no p-value to rank.
1657    #[test]
1658    fn a_family_that_does_not_apply_has_no_p_value() {
1659        let values = draw(Fitted::Normal { mean: 0.0, sd: 1.0 }, 50, 3);
1660        let outcome = test_family(DistributionType::LogNormal, &values, 3);
1661        assert_eq!(outcome, FitOutcome::NotApplicable("needs positive values"));
1662        let results = vec![
1663            (DistributionType::LogNormal, outcome),
1664            (
1665                DistributionType::Normal,
1666                test_family(DistributionType::Normal, &values, 3),
1667            ),
1668        ];
1669        assert_eq!(
1670            listing_order(&results)[..2],
1671            [DistributionType::Normal, DistributionType::LogNormal]
1672        );
1673    }
1674
1675    /// Selection is among the families not rejected, by AIC, the simpler of two
1676    /// nested ones when both hold: exponential values are a Gamma of shape 1 too.
1677    #[test]
1678    fn selection_prefers_the_simpler_family_that_holds() {
1679        let values = draw(Fitted::Exponential { rate: 2.0 }, 2_000, 11);
1680        let results = test_all(&values, 11);
1681        assert_eq!(select(&results, false), DistributionType::Exponential);
1682
1683        let counts = draw(Fitted::Poisson { rate: 5.0 }, 2_000, 12);
1684        let results = test_all(&counts, 12);
1685        assert_eq!(select(&results, true), DistributionType::Poisson);
1686    }
1687}