use crate::{utils::empirical_copula_cdf, Copula, CopulaError, Result};
use nalgebra::DMatrix;
pub fn cramer_von_mises<C: Copula>(copula: &C, pseudo_obs: &DMatrix<f64>) -> Result<f64> {
crate::utils::validate_pseudo_observations(pseudo_obs)?;
if pseudo_obs.ncols() != copula.dimension() {
return Err(CopulaError::dimension_mismatch(
copula.dimension(),
pseudo_obs.ncols(),
));
}
let n = pseudo_obs.nrows();
let mut sum = 0.0;
for i in 0..n {
let row = pseudo_obs.row(i);
let u: Vec<f64> = row.iter().copied().collect();
let c_n = empirical_copula_cdf(pseudo_obs, &u)?;
let c = copula.cdf(&u)?;
let diff = c - c_n;
sum += diff * diff;
}
Ok(n as f64 * sum)
}
pub fn kolmogorov_smirnov<C: Copula>(copula: &C, pseudo_obs: &DMatrix<f64>) -> Result<f64> {
crate::utils::validate_pseudo_observations(pseudo_obs)?;
if pseudo_obs.ncols() != copula.dimension() {
return Err(CopulaError::dimension_mismatch(
copula.dimension(),
pseudo_obs.ncols(),
));
}
let n = pseudo_obs.nrows();
let mut max_diff = 0.0_f64;
for i in 0..n {
let row = pseudo_obs.row(i);
let u: Vec<f64> = row.iter().copied().collect();
let c_n = empirical_copula_cdf(pseudo_obs, &u)?;
let c = copula.cdf(&u)?;
let diff = (c - c_n).abs();
if diff > max_diff {
max_diff = diff;
}
}
Ok((n as f64).sqrt() * max_diff)
}
pub fn anderson_darling<C: Copula>(copula: &C, pseudo_obs: &DMatrix<f64>) -> Result<f64> {
crate::utils::validate_pseudo_observations(pseudo_obs)?;
if pseudo_obs.ncols() != copula.dimension() {
return Err(CopulaError::dimension_mismatch(
copula.dimension(),
pseudo_obs.ncols(),
));
}
let n = pseudo_obs.nrows();
let mut cdf_vals = Vec::with_capacity(n);
for i in 0..n {
let row = pseudo_obs.row(i);
let u: Vec<f64> = row.iter().copied().collect();
let c = copula.cdf(&u)?;
let c = c.clamp(f64::MIN_POSITIVE, 1.0 - f64::EPSILON);
cdf_vals.push(c);
}
cdf_vals.sort_by(|a, b| a.total_cmp(b));
let mut sum = 0.0;
for (i, c) in cdf_vals.iter().enumerate() {
let j = i + 1;
let term1 = c.ln();
let term2 = (1.0 - cdf_vals[n - j]).ln();
sum += (2 * j - 1) as f64 * (term1 + term2);
}
Ok(-(n as f64) - sum / (n as f64))
}
pub fn cvm_multiplier_bootstrap<C, R>(
copula: &C,
pseudo_obs: &DMatrix<f64>,
n_rep: usize,
rng: &mut R,
) -> Result<Vec<f64>>
where
C: Copula,
R: rand::Rng + ?Sized,
{
if n_rep == 0 {
return Err(CopulaError::invalid_parameter("n_rep must be at least 1"));
}
crate::utils::validate_pseudo_observations(pseudo_obs)?;
if pseudo_obs.ncols() != copula.dimension() {
return Err(CopulaError::dimension_mismatch(
copula.dimension(),
pseudo_obs.ncols(),
));
}
let n = pseudo_obs.nrows();
let mut results = Vec::with_capacity(n_rep);
use rand_distr::{Distribution, StandardNormal};
for _ in 0..n_rep {
let mut weighted_sum = 0.0_f64;
for i in 0..n {
let row = pseudo_obs.row(i);
let u: Vec<f64> = row.iter().copied().collect();
let c_n = empirical_copula_cdf(pseudo_obs, &u)?;
let c = copula.cdf(&u)?;
let diff = c - c_n;
let w: f64 = StandardNormal.sample(rng);
weighted_sum += w * diff;
}
results.push(n as f64 * weighted_sum.powi(2));
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::archimedean::ClaytonCopula;
#[test]
fn cvm_small_for_true_model() {
let mut rng = rand::rng();
let cop = ClaytonCopula::new(2.0).unwrap();
let data = cop.sample(100, &mut rng).unwrap();
let stat = cramer_von_mises(&cop, &data).unwrap();
assert!(stat.is_finite() && stat > 0.0);
}
#[test]
fn ks_statistic_finite() {
let mut rng = rand::rng();
let cop = ClaytonCopula::new(2.0).unwrap();
let data = cop.sample(50, &mut rng).unwrap();
let stat = kolmogorov_smirnov(&cop, &data).unwrap();
assert!(stat.is_finite() && stat > 0.0);
}
#[test]
fn ad_statistic_finite() {
let mut rng = rand::rng();
let cop = ClaytonCopula::new(2.0).unwrap();
let data = cop.sample(50, &mut rng).unwrap();
let stat = anderson_darling(&cop, &data).unwrap();
assert!(stat.is_finite() && stat > 0.0);
}
#[test]
fn multiplier_bootstrap_produces_samples() {
let mut rng = rand::rng();
let cop = ClaytonCopula::new(2.0).unwrap();
let data = cop.sample(40, &mut rng).unwrap();
let reps = cvm_multiplier_bootstrap(&cop, &data, 10, &mut rng).unwrap();
assert_eq!(reps.len(), 10);
assert!(reps.iter().all(|&x| x.is_finite()));
}
}