use rand::rngs::StdRng;
use rand_distr::{Distribution, Normal};
pub(crate) fn ar1_phi(step_m: f64, corr_length_m: f64) -> f64 {
if !(corr_length_m.is_finite() && corr_length_m > 0.0) || step_m <= 0.0 {
return 0.0;
}
(-step_m / corr_length_m).exp()
}
pub(crate) fn correlated_gaussian<F: Fn(usize) -> f64>(
n: usize,
phi: F,
rng: &mut StdRng,
) -> Vec<f64> {
let sn = Normal::new(0.0, 1.0).expect("standard normal");
let mut out = Vec::with_capacity(n);
if n == 0 {
return out;
}
let mut prev = sn.sample(rng);
out.push(prev);
for k in 1..n {
let p = phi(k).clamp(0.0, 1.0 - 1e-9);
let eps = sn.sample(rng);
let z = p * prev + (1.0 - p * p).sqrt() * eps;
out.push(z);
prev = z;
}
out
}
#[cfg(test)]
pub(crate) fn lag1_autocorr(series: &[f64]) -> f64 {
let n = series.len();
if n < 2 {
return 0.0;
}
let m = series.iter().sum::<f64>() / n as f64;
let mut num = 0.0;
let mut den = 0.0;
for i in 0..n {
den += (series[i] - m).powi(2);
if i + 1 < n {
num += (series[i] - m) * (series[i + 1] - m);
}
}
if den == 0.0 {
0.0
} else {
num / den
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sampling::seeded_rng;
#[test]
fn phi_maps_length_to_correlation() {
let phi = ar1_phi(1.0, 10.0);
assert!((phi - (-0.1_f64).exp()).abs() < 1e-12);
assert_eq!(ar1_phi(1.0, 0.0), 0.0);
assert_eq!(ar1_phi(1.0, -5.0), 0.0);
}
#[test]
fn series_is_reproducible_and_unit_variance() {
let phi = ar1_phi(0.5, 8.0);
let mut r1 = seeded_rng(7);
let mut r2 = seeded_rng(7);
let a = correlated_gaussian(5000, |_| phi, &mut r1);
let b = correlated_gaussian(5000, |_| phi, &mut r2);
assert_eq!(a, b);
let m = a.iter().sum::<f64>() / a.len() as f64;
let v = a.iter().map(|x| (x - m).powi(2)).sum::<f64>() / a.len() as f64;
assert!(m.abs() < 0.1, "mean {m}");
assert!((v - 1.0).abs() < 0.15, "var {v}");
}
#[test]
fn recovers_the_correlation_length() {
let phi = ar1_phi(1.0, 20.0); let mut rng = seeded_rng(3);
let s = correlated_gaussian(20000, |_| phi, &mut rng);
let r = lag1_autocorr(&s);
assert!((r - phi).abs() < 0.03, "recovered {r} vs {phi}");
}
#[test]
fn zero_phi_is_white() {
let mut rng = seeded_rng(1);
let s = correlated_gaussian(20000, |_| 0.0, &mut rng);
assert!(lag1_autocorr(&s).abs() < 0.03);
}
}