copula_core/testing/
mod.rs1use crate::{utils::empirical_copula_cdf, Copula, CopulaError, Result};
9use nalgebra::DMatrix;
10
11pub fn cramer_von_mises<C: Copula>(copula: &C, pseudo_obs: &DMatrix<f64>) -> Result<f64> {
29 crate::utils::validate_pseudo_observations(pseudo_obs)?;
30 if pseudo_obs.ncols() != copula.dimension() {
31 return Err(CopulaError::dimension_mismatch(
32 copula.dimension(),
33 pseudo_obs.ncols(),
34 ));
35 }
36
37 let n = pseudo_obs.nrows();
38 let mut sum = 0.0;
39 for i in 0..n {
40 let row = pseudo_obs.row(i);
41 let u: Vec<f64> = row.iter().copied().collect();
42 let c_n = empirical_copula_cdf(pseudo_obs, &u)?;
43 let c = copula.cdf(&u)?;
44 let diff = c - c_n;
45 sum += diff * diff;
46 }
47
48 Ok(n as f64 * sum)
49}
50
51pub fn kolmogorov_smirnov<C: Copula>(copula: &C, pseudo_obs: &DMatrix<f64>) -> Result<f64> {
60 crate::utils::validate_pseudo_observations(pseudo_obs)?;
61 if pseudo_obs.ncols() != copula.dimension() {
62 return Err(CopulaError::dimension_mismatch(
63 copula.dimension(),
64 pseudo_obs.ncols(),
65 ));
66 }
67
68 let n = pseudo_obs.nrows();
69 let mut max_diff = 0.0_f64;
70 for i in 0..n {
71 let row = pseudo_obs.row(i);
72 let u: Vec<f64> = row.iter().copied().collect();
73 let c_n = empirical_copula_cdf(pseudo_obs, &u)?;
74 let c = copula.cdf(&u)?;
75 let diff = (c - c_n).abs();
76 if diff > max_diff {
77 max_diff = diff;
78 }
79 }
80
81 Ok((n as f64).sqrt() * max_diff)
82}
83
84pub fn anderson_darling<C: Copula>(copula: &C, pseudo_obs: &DMatrix<f64>) -> Result<f64> {
92 crate::utils::validate_pseudo_observations(pseudo_obs)?;
93 if pseudo_obs.ncols() != copula.dimension() {
94 return Err(CopulaError::dimension_mismatch(
95 copula.dimension(),
96 pseudo_obs.ncols(),
97 ));
98 }
99
100 let n = pseudo_obs.nrows();
101 let mut cdf_vals = Vec::with_capacity(n);
102 for i in 0..n {
103 let row = pseudo_obs.row(i);
104 let u: Vec<f64> = row.iter().copied().collect();
105 let c = copula.cdf(&u)?;
106 let c = c.clamp(f64::MIN_POSITIVE, 1.0 - f64::EPSILON);
108 cdf_vals.push(c);
109 }
110 cdf_vals.sort_by(|a, b| a.total_cmp(b));
111
112 let mut sum = 0.0;
113 for (i, c) in cdf_vals.iter().enumerate() {
114 let j = i + 1;
115 let term1 = c.ln();
116 let term2 = (1.0 - cdf_vals[n - j]).ln();
117 sum += (2 * j - 1) as f64 * (term1 + term2);
118 }
119
120 Ok(-(n as f64) - sum / (n as f64))
121}
122
123pub fn cvm_multiplier_bootstrap<C, R>(
131 copula: &C,
132 pseudo_obs: &DMatrix<f64>,
133 n_rep: usize,
134 rng: &mut R,
135) -> Result<Vec<f64>>
136where
137 C: Copula,
138 R: rand::Rng + ?Sized,
139{
140 if n_rep == 0 {
141 return Err(CopulaError::invalid_parameter(
142 "n_rep must be at least 1",
143 ));
144 }
145 crate::utils::validate_pseudo_observations(pseudo_obs)?;
146 if pseudo_obs.ncols() != copula.dimension() {
147 return Err(CopulaError::dimension_mismatch(
148 copula.dimension(),
149 pseudo_obs.ncols(),
150 ));
151 }
152
153 let n = pseudo_obs.nrows();
154 let mut results = Vec::with_capacity(n_rep);
155 use rand_distr::{Distribution, StandardNormal};
156
157 for _ in 0..n_rep {
158 let mut weighted_sum = 0.0_f64;
159 for i in 0..n {
160 let row = pseudo_obs.row(i);
161 let u: Vec<f64> = row.iter().copied().collect();
162 let c_n = empirical_copula_cdf(pseudo_obs, &u)?;
163 let c = copula.cdf(&u)?;
164 let diff = c - c_n;
165 let w: f64 = StandardNormal.sample(rng);
166 weighted_sum += w * diff;
167 }
168 results.push(n as f64 * weighted_sum.powi(2));
169 }
170
171 Ok(results)
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::archimedean::ClaytonCopula;
178 use rand::thread_rng;
179
180 #[test]
181 fn cvm_small_for_true_model() {
182 let mut rng = thread_rng();
183 let cop = ClaytonCopula::new(2.0).unwrap();
184 let data = cop.sample(100, &mut rng).unwrap();
185 let stat = cramer_von_mises(&cop, &data).unwrap();
186 assert!(stat.is_finite() && stat > 0.0);
187 }
188
189 #[test]
190 fn ks_statistic_finite() {
191 let mut rng = thread_rng();
192 let cop = ClaytonCopula::new(2.0).unwrap();
193 let data = cop.sample(50, &mut rng).unwrap();
194 let stat = kolmogorov_smirnov(&cop, &data).unwrap();
195 assert!(stat.is_finite() && stat > 0.0);
196 }
197
198 #[test]
199 fn ad_statistic_finite() {
200 let mut rng = thread_rng();
201 let cop = ClaytonCopula::new(2.0).unwrap();
202 let data = cop.sample(50, &mut rng).unwrap();
203 let stat = anderson_darling(&cop, &data).unwrap();
204 assert!(stat.is_finite() && stat > 0.0);
205 }
206
207 #[test]
208 fn multiplier_bootstrap_produces_samples() {
209 let mut rng = thread_rng();
210 let cop = ClaytonCopula::new(2.0).unwrap();
211 let data = cop.sample(40, &mut rng).unwrap();
212 let reps = cvm_multiplier_bootstrap(&cop, &data, 10, &mut rng).unwrap();
213 assert_eq!(reps.len(), 10);
214 assert!(reps.iter().all(|&x| x.is_finite()));
215 }
216}