use crate::error::StatsError;
use std::f64::consts::PI;
pub const EULER_MASCHERONI: f64 = 0.577_215_664_901_532_9;
const XI_THRESHOLD: f64 = 1e-10;
#[inline]
fn gev_bracket(x: f64, mu: f64, sigma: f64, xi: f64) -> Option<f64> {
let t = 1.0 + xi * (x - mu) / sigma;
if t <= 0.0 {
None
} else {
Some(t)
}
}
struct Lcg {
state: u64,
}
impl Lcg {
fn new(seed: u64) -> Self {
Self {
state: seed.wrapping_add(1),
}
}
fn next_f64(&mut self) -> f64 {
self.state = self
.state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
let bits = (self.state >> 11) as f64;
(bits + 0.5) / (1u64 << 53) as f64
}
}
#[derive(Debug, Clone)]
pub struct GeneralizedExtremeValue {
pub mu: f64,
pub sigma: f64,
pub xi: f64,
}
impl GeneralizedExtremeValue {
pub fn new(mu: f64, sigma: f64, xi: f64) -> Result<Self, StatsError> {
if sigma <= 0.0 {
return Err(StatsError::InvalidArgument(format!(
"GEV scale parameter sigma must be positive, got {sigma}"
)));
}
Ok(Self { mu, sigma, xi })
}
pub fn pdf(&self, x: f64) -> f64 {
let xi = self.xi;
let sigma = self.sigma;
let mu = self.mu;
if xi.abs() < XI_THRESHOLD {
let z = (x - mu) / sigma;
(1.0 / sigma) * (-z - (-z).exp()).exp()
} else {
match gev_bracket(x, mu, sigma, xi) {
None => 0.0,
Some(t) => {
let log_t = t.ln();
let exponent = -(1.0 + 1.0 / xi) * log_t - t.powf(-1.0 / xi);
(1.0 / sigma) * exponent.exp()
}
}
}
}
pub fn cdf(&self, x: f64) -> f64 {
let xi = self.xi;
let sigma = self.sigma;
let mu = self.mu;
if xi.abs() < XI_THRESHOLD {
let z = (x - mu) / sigma;
(-(-z).exp()).exp()
} else {
match gev_bracket(x, mu, sigma, xi) {
None => {
if xi > 0.0 {
0.0
} else {
1.0
}
}
Some(t) => (-t.powf(-1.0 / xi)).exp(),
}
}
}
pub fn quantile(&self, p: f64) -> Result<f64, StatsError> {
if p <= 0.0 || p >= 1.0 {
return Err(StatsError::InvalidArgument(format!(
"Probability p must be in (0, 1), got {p}"
)));
}
let log_p = -p.ln();
let xi = self.xi;
let mu = self.mu;
let sigma = self.sigma;
let q = if xi.abs() < XI_THRESHOLD {
mu - sigma * log_p.ln()
} else {
mu + sigma * (log_p.powf(-xi) - 1.0) / xi
};
Ok(q)
}
pub fn mean(&self) -> Option<f64> {
let xi = self.xi;
if xi >= 1.0 {
return None;
}
if xi.abs() < XI_THRESHOLD {
Some(self.mu + self.sigma * EULER_MASCHERONI)
} else {
let g = gamma_approx(1.0 - xi)?;
Some(self.mu + self.sigma * (g - 1.0) / xi)
}
}
pub fn variance(&self) -> Option<f64> {
let xi = self.xi;
if xi >= 0.5 {
return None;
}
if xi.abs() < XI_THRESHOLD {
Some(self.sigma.powi(2) * PI * PI / 6.0)
} else {
let g1 = gamma_approx(1.0 - xi)?;
let g2 = gamma_approx(1.0 - 2.0 * xi)?;
Some(self.sigma.powi(2) * (g2 - g1.powi(2)) / xi.powi(2))
}
}
pub fn return_level(&self, return_period: f64) -> Result<f64, StatsError> {
if return_period <= 1.0 {
return Err(StatsError::InvalidArgument(format!(
"Return period must be > 1, got {return_period}"
)));
}
let p = 1.0 - 1.0 / return_period;
self.quantile(p)
}
pub fn sample(&self, n: usize, seed: u64) -> Vec<f64> {
let mut rng = Lcg::new(seed);
(0..n)
.filter_map(|_| {
let u = rng.next_f64();
self.quantile(u).ok()
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct Gumbel {
pub mu: f64,
pub beta: f64,
}
impl Gumbel {
pub fn new(mu: f64, beta: f64) -> Result<Self, StatsError> {
if beta <= 0.0 {
return Err(StatsError::InvalidArgument(format!(
"Gumbel scale parameter beta must be positive, got {beta}"
)));
}
Ok(Self { mu, beta })
}
pub fn pdf(&self, x: f64) -> f64 {
let z = (x - self.mu) / self.beta;
(1.0 / self.beta) * (-z - (-z).exp()).exp()
}
pub fn cdf(&self, x: f64) -> f64 {
let z = (x - self.mu) / self.beta;
(-(-z).exp()).exp()
}
pub fn quantile(&self, p: f64) -> f64 {
self.mu - self.beta * (-(p.ln())).ln()
}
pub fn mean(&self) -> f64 {
self.mu + self.beta * EULER_MASCHERONI
}
pub fn variance(&self) -> f64 {
self.beta.powi(2) * PI * PI / 6.0
}
pub fn sample(&self, n: usize, seed: u64) -> Vec<f64> {
let mut rng = Lcg::new(seed);
(0..n)
.map(|_| {
let u = rng.next_f64();
self.quantile(u)
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct Frechet {
pub alpha: f64,
pub s: f64,
pub m: f64,
}
impl Frechet {
pub fn new(alpha: f64, s: f64, m: f64) -> Result<Self, StatsError> {
if alpha <= 0.0 {
return Err(StatsError::InvalidArgument(format!(
"Fréchet shape alpha must be positive, got {alpha}"
)));
}
if s <= 0.0 {
return Err(StatsError::InvalidArgument(format!(
"Fréchet scale s must be positive, got {s}"
)));
}
Ok(Self { alpha, s, m })
}
pub fn pdf(&self, x: f64) -> f64 {
if x <= self.m {
return 0.0;
}
let z = (x - self.m) / self.s;
(self.alpha / self.s) * z.powf(-self.alpha - 1.0) * (-z.powf(-self.alpha)).exp()
}
pub fn cdf(&self, x: f64) -> f64 {
if x <= self.m {
return 0.0;
}
let z = (x - self.m) / self.s;
(-z.powf(-self.alpha)).exp()
}
pub fn quantile(&self, p: f64) -> f64 {
self.m + self.s * (-(p.ln())).powf(-1.0 / self.alpha)
}
pub fn mean(&self) -> Option<f64> {
if self.alpha <= 1.0 {
return None;
}
let g = gamma_approx(1.0 - 1.0 / self.alpha)?;
Some(self.m + self.s * g)
}
}
#[derive(Debug, Clone)]
pub struct GeneralizedPareto {
pub mu: f64,
pub sigma: f64,
pub xi: f64,
}
impl GeneralizedPareto {
pub fn new(mu: f64, sigma: f64, xi: f64) -> Result<Self, StatsError> {
if sigma <= 0.0 {
return Err(StatsError::InvalidArgument(format!(
"GPD scale parameter sigma must be positive, got {sigma}"
)));
}
Ok(Self { mu, sigma, xi })
}
pub fn pdf(&self, x: f64) -> f64 {
if x < self.mu {
return 0.0;
}
let xi = self.xi;
let sigma = self.sigma;
let mu = self.mu;
if xi.abs() < XI_THRESHOLD {
let z = (x - mu) / sigma;
(1.0 / sigma) * (-z).exp()
} else {
match gev_bracket(x, mu, sigma, xi) {
None => 0.0,
Some(t) => (1.0 / sigma) * t.powf(-1.0 / xi - 1.0),
}
}
}
pub fn cdf(&self, x: f64) -> f64 {
if x < self.mu {
return 0.0;
}
let xi = self.xi;
let sigma = self.sigma;
let mu = self.mu;
if xi.abs() < XI_THRESHOLD {
let z = (x - mu) / sigma;
1.0 - (-z).exp()
} else {
match gev_bracket(x, mu, sigma, xi) {
None => {
if xi < 0.0 {
1.0
} else {
0.0
}
}
Some(t) => 1.0 - t.powf(-1.0 / xi),
}
}
}
pub fn quantile(&self, p: f64) -> Result<f64, StatsError> {
if p <= 0.0 || p >= 1.0 {
return Err(StatsError::InvalidArgument(format!(
"Probability p must be in (0, 1), got {p}"
)));
}
let xi = self.xi;
let sigma = self.sigma;
let mu = self.mu;
let q = if xi.abs() < XI_THRESHOLD {
mu - sigma * (1.0 - p).ln()
} else {
mu + sigma * ((1.0 - p).powf(-xi) - 1.0) / xi
};
Ok(q)
}
pub fn mean(&self) -> Option<f64> {
if self.xi >= 1.0 {
None
} else {
Some(self.mu + self.sigma / (1.0 - self.xi))
}
}
pub fn variance(&self) -> Option<f64> {
if self.xi >= 0.5 {
None
} else {
let denom = (1.0 - self.xi).powi(2) * (1.0 - 2.0 * self.xi);
Some(self.sigma.powi(2) / denom)
}
}
pub fn exceedance_quantile(&self, p: f64) -> Result<f64, StatsError> {
self.quantile(p)
}
pub fn sample(&self, n: usize, seed: u64) -> Vec<f64> {
let mut rng = Lcg::new(seed);
(0..n)
.filter_map(|_| {
let u = rng.next_f64();
self.quantile(u).ok()
})
.collect()
}
}
pub(crate) fn gamma_approx(z: f64) -> Option<f64> {
if z <= 0.0 {
return None;
}
const G: f64 = 7.0;
const C: [f64; 9] = [
0.999_999_999_999_809_93,
676.520_368_121_885_10,
-1_259.139_216_722_402_8,
771.323_428_777_653_10,
-176.615_029_162_140_59,
12.507_343_278_686_905,
-0.138_571_095_265_720_12,
9.984_369_578_019_572e-6,
1.505_632_735_149_311_6e-7,
];
let mut z = z;
if z < 0.5 {
let sin_piz = (PI * z).sin();
let g = gamma_approx(1.0 - z)?;
return Some(PI / (sin_piz * g));
}
z -= 1.0;
let mut x = C[0];
for (i, &c) in C[1..].iter().enumerate() {
x += c / (z + i as f64 + 1.0);
}
let t = z + G + 0.5;
let result = (2.0 * PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * x;
Some(result)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_gev_new_invalid_sigma() {
assert!(GeneralizedExtremeValue::new(0.0, 0.0, 0.0).is_err());
assert!(GeneralizedExtremeValue::new(0.0, -1.0, 0.0).is_err());
}
#[test]
fn test_gev_new_valid() {
assert!(GeneralizedExtremeValue::new(0.0, 1.0, 0.0).is_ok());
assert!(GeneralizedExtremeValue::new(0.0, 1.0, 0.5).is_ok());
assert!(GeneralizedExtremeValue::new(0.0, 1.0, -0.5).is_ok());
}
#[test]
fn test_gev_gumbel_case_cdf() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.0).unwrap();
assert!(approx_eq(gev.cdf(0.0), 0.367_879_441, 1e-8));
}
#[test]
fn test_gev_cdf_pdf_consistency() {
let gev = GeneralizedExtremeValue::new(2.0, 1.5, 0.3).unwrap();
let h = 1e-5;
let x = 3.0_f64;
let numerical_pdf = (gev.cdf(x + h) - gev.cdf(x - h)) / (2.0 * h);
assert!(approx_eq(numerical_pdf, gev.pdf(x), 1e-4));
}
#[test]
fn test_gev_quantile_inverse_cdf() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.2).unwrap();
for &p in &[0.1, 0.25, 0.5, 0.75, 0.9, 0.99] {
let q = gev.quantile(p).unwrap();
let cdf_q = gev.cdf(q);
assert!(
approx_eq(cdf_q, p, 1e-10),
"p={p}, quantile={q}, CDF(quantile)={cdf_q}"
);
}
}
#[test]
fn test_gev_quantile_invalid_p() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.0).unwrap();
assert!(gev.quantile(0.0).is_err());
assert!(gev.quantile(1.0).is_err());
assert!(gev.quantile(-0.1).is_err());
}
#[test]
fn test_gev_mean_gumbel() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.0).unwrap();
let m = gev.mean().unwrap();
assert!(approx_eq(m, EULER_MASCHERONI, 1e-10));
}
#[test]
fn test_gev_variance_gumbel() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.0).unwrap();
let v = gev.variance().unwrap();
let expected = PI * PI / 6.0;
assert!(approx_eq(v, expected, 1e-10));
}
#[test]
fn test_gev_mean_undefined_for_large_xi() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 1.5).unwrap();
assert!(gev.mean().is_none());
}
#[test]
fn test_gev_return_level() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.0).unwrap();
let rl = gev.return_level(100.0).unwrap();
let expected = gev.quantile(0.99).unwrap();
assert!(approx_eq(rl, expected, 1e-12));
}
#[test]
fn test_gev_return_level_invalid() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.0).unwrap();
assert!(gev.return_level(0.5).is_err());
assert!(gev.return_level(1.0).is_err());
}
#[test]
fn test_gev_sample_length() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.0).unwrap();
let s = gev.sample(50, 42);
assert_eq!(s.len(), 50);
}
#[test]
fn test_gev_frechet_support() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, 0.5).unwrap();
let below = gev.cdf(-2.1);
assert_eq!(below, 0.0);
}
#[test]
fn test_gev_weibull_support() {
let gev = GeneralizedExtremeValue::new(0.0, 1.0, -0.5).unwrap();
let above = gev.cdf(2.1);
assert_eq!(above, 1.0);
}
#[test]
fn test_gumbel_new_invalid() {
assert!(Gumbel::new(0.0, 0.0).is_err());
assert!(Gumbel::new(0.0, -1.0).is_err());
}
#[test]
fn test_gumbel_cdf_at_mu() {
let g = Gumbel::new(5.0, 2.0).unwrap();
assert!(approx_eq(g.cdf(5.0), (-1.0_f64).exp(), 1e-10));
}
#[test]
fn test_gumbel_quantile_roundtrip() {
let g = Gumbel::new(1.0, 2.0).unwrap();
for &p in &[0.05, 0.5, 0.95] {
let q = g.quantile(p);
assert!(approx_eq(g.cdf(q), p, 1e-12));
}
}
#[test]
fn test_gumbel_mean_variance() {
let g = Gumbel::new(0.0, 1.0).unwrap();
assert!(approx_eq(g.mean(), EULER_MASCHERONI, 1e-10));
assert!(approx_eq(g.variance(), PI * PI / 6.0, 1e-10));
}
#[test]
fn test_gumbel_sample() {
let g = Gumbel::new(0.0, 1.0).unwrap();
let s = g.sample(100, 7);
assert_eq!(s.len(), 100);
assert!(s.iter().all(|x| x.is_finite()));
}
#[test]
fn test_frechet_new_invalid() {
assert!(Frechet::new(0.0, 1.0, 0.0).is_err()); assert!(Frechet::new(2.0, 0.0, 0.0).is_err()); }
#[test]
fn test_frechet_cdf_below_location() {
let f = Frechet::new(2.0, 1.0, 3.0).unwrap();
assert_eq!(f.cdf(3.0), 0.0);
assert_eq!(f.cdf(2.9), 0.0);
}
#[test]
fn test_frechet_pdf_positive() {
let f = Frechet::new(2.0, 1.0, 0.0).unwrap();
let p = f.pdf(2.0);
assert!(p > 0.0);
}
#[test]
fn test_frechet_mean_undefined_alpha_le_1() {
let f = Frechet::new(0.5, 1.0, 0.0).unwrap();
assert!(f.mean().is_none());
let f2 = Frechet::new(1.0, 1.0, 0.0).unwrap();
assert!(f2.mean().is_none());
}
#[test]
fn test_frechet_mean_defined() {
let f = Frechet::new(2.0, 1.0, 0.0).unwrap();
let m = f.mean();
assert!(m.is_some());
assert!(m.unwrap() > 0.0);
}
#[test]
fn test_gpd_new_invalid_sigma() {
assert!(GeneralizedPareto::new(0.0, 0.0, 0.0).is_err());
assert!(GeneralizedPareto::new(0.0, -1.0, 0.5).is_err());
}
#[test]
fn test_gpd_exponential_case() {
let g = GeneralizedPareto::new(0.0, 2.0, 0.0).unwrap();
let x = 3.0_f64;
let expected_cdf = 1.0 - (-x / 2.0).exp();
assert!(approx_eq(g.cdf(x), expected_cdf, 1e-12));
}
#[test]
fn test_gpd_quantile_inverse() {
let g = GeneralizedPareto::new(0.0, 1.5, 0.2).unwrap();
for &p in &[0.1, 0.5, 0.9] {
let q = g.quantile(p).unwrap();
assert!(approx_eq(g.cdf(q), p, 1e-10), "p={p}");
}
}
#[test]
fn test_gpd_mean() {
let g = GeneralizedPareto::new(0.0, 2.0, 0.3).unwrap();
let m = g.mean().unwrap();
let expected = 0.0 + 2.0 / (1.0 - 0.3);
assert!(approx_eq(m, expected, 1e-12));
}
#[test]
fn test_gpd_mean_undefined() {
let g = GeneralizedPareto::new(0.0, 1.0, 1.0).unwrap();
assert!(g.mean().is_none());
}
#[test]
fn test_gpd_variance_undefined() {
let g = GeneralizedPareto::new(0.0, 1.0, 0.5).unwrap();
assert!(g.variance().is_none());
}
#[test]
fn test_gpd_variance_defined() {
let g = GeneralizedPareto::new(0.0, 2.0, 0.0).unwrap();
let v = g.variance().unwrap();
assert!(approx_eq(v, 4.0, 1e-10));
}
#[test]
fn test_gpd_sample() {
let g = GeneralizedPareto::new(0.0, 1.0, 0.1).unwrap();
let s = g.sample(200, 13);
assert_eq!(s.len(), 200);
assert!(s.iter().all(|&x| x >= 0.0));
}
#[test]
fn test_gpd_bounded_support_negative_xi() {
let g = GeneralizedPareto::new(0.0, 1.0, -0.5).unwrap();
assert_eq!(g.cdf(2.1), 1.0); assert!(approx_eq(g.cdf(2.0), 1.0, 1e-10));
}
#[test]
fn test_gamma_approx_known_values() {
assert!(approx_eq(gamma_approx(1.0).unwrap(), 1.0, 1e-10));
assert!(approx_eq(gamma_approx(2.0).unwrap(), 1.0, 1e-10));
assert!(approx_eq(gamma_approx(3.0).unwrap(), 2.0, 1e-10));
assert!(approx_eq(gamma_approx(0.5).unwrap(), PI.sqrt(), 1e-10));
}
}