use crate::archimedean::{ClaytonCopula, FrankCopula, GumbelCopula, JoeCopula, AMHCopula};
use crate::elliptical::{GaussianCopula, StudentTCopula};
use crate::{Copula, CopulaError, Result};
use nalgebra::DMatrix;
use rand::Rng;
#[derive(Clone)]
pub enum CopulaType {
Clayton(ClaytonCopula),
Gumbel(GumbelCopula),
Frank(FrankCopula),
Joe(JoeCopula),
AMH(AMHCopula),
Gaussian(GaussianCopula),
StudentT(StudentTCopula),
}
impl CopulaType {
fn cdf(&self, u: &[f64]) -> Result<f64> {
match self {
CopulaType::Clayton(c) => c.cdf(u),
CopulaType::Gumbel(c) => c.cdf(u),
CopulaType::Frank(c) => c.cdf(u),
CopulaType::Joe(c) => c.cdf(u),
CopulaType::AMH(c) => c.cdf(u),
CopulaType::Gaussian(c) => c.cdf(u),
CopulaType::StudentT(c) => c.cdf(u),
}
}
fn pdf(&self, u: &[f64]) -> Result<f64> {
match self {
CopulaType::Clayton(c) => c.pdf(u),
CopulaType::Gumbel(c) => c.pdf(u),
CopulaType::Frank(c) => c.pdf(u),
CopulaType::Joe(c) => c.pdf(u),
CopulaType::AMH(c) => c.pdf(u),
CopulaType::Gaussian(c) => c.pdf(u),
CopulaType::StudentT(c) => c.pdf(u),
}
}
}
#[derive(Clone)]
pub struct PairCopula {
copula: CopulaType,
var1: usize,
var2: usize,
conditioning_set: Vec<usize>,
}
impl PairCopula {
pub fn new(
copula: CopulaType,
var1: usize,
var2: usize,
conditioning_set: Vec<usize>,
) -> Self {
Self {
copula,
var1,
var2,
conditioning_set,
}
}
fn h_function(&self, u: f64, v: f64) -> Result<f64> {
let h = 1e-8;
let c1 = self.copula.cdf(&[u, v + h])?;
let c2 = self.copula.cdf(&[u, v])?;
Ok(((c1 - c2) / h).clamp(0.0, 1.0))
}
fn h_inv(&self, u: f64, v: f64) -> Result<f64> {
let mut u2_low = 1e-10;
let mut u2_high = 1.0 - 1e-10;
for _ in 0..50 {
let u2 = (u2_low + u2_high) / 2.0;
let h_val = self.h_function(u2, v)?;
if (h_val - u).abs() < 1e-10 {
return Ok(u2);
}
if h_val < u {
u2_low = u2;
} else {
u2_high = u2;
}
}
Ok((u2_low + u2_high) / 2.0)
}
}
#[derive(Clone)]
pub struct CVineCopula {
dimension: usize,
trees: Vec<Vec<PairCopula>>,
}
impl CVineCopula {
pub fn new(dimension: usize, trees: Vec<Vec<PairCopula>>) -> Result<Self> {
if dimension < 2 {
return Err(CopulaError::invalid_parameter(
"dimension must be >= 2 for vine copulas",
));
}
if trees.len() != dimension - 1 {
return Err(CopulaError::invalid_parameter(&format!(
"C-vine with dimension {} should have {} trees, got {}",
dimension,
dimension - 1,
trees.len()
)));
}
for (level, tree) in trees.iter().enumerate() {
let expected_pairs = dimension - level - 1;
if tree.len() != expected_pairs {
return Err(CopulaError::invalid_parameter(&format!(
"Tree {} should have {} pair-copulas, got {}",
level + 1,
expected_pairs,
tree.len()
)));
}
}
Ok(Self { dimension, trees })
}
fn compute_conditionals(&self, u: &[f64]) -> Result<Vec<Vec<f64>>> {
let d = self.dimension;
let mut v = vec![vec![0.0; d]; d];
for j in 0..d {
v[0][j] = u[j];
}
for level in 0..self.trees.len() {
for (j, pair_cop) in self.trees[level].iter().enumerate() {
let idx = level + j + 1;
if idx < d {
v[level + 1][idx] = pair_cop.h_function(v[level][idx], v[level][level])?;
}
}
}
Ok(v)
}
}
impl Copula for CVineCopula {
fn cdf(&self, u: &[f64]) -> Result<f64> {
if u.len() != self.dimension {
return Err(CopulaError::dimension_mismatch(self.dimension, u.len()));
}
crate::error::validate_unit_range(u)?;
Err(CopulaError::not_implemented(
"C-vine CDF requires specialized numerical methods",
))
}
fn pdf(&self, u: &[f64]) -> Result<f64> {
if u.len() != self.dimension {
return Err(CopulaError::dimension_mismatch(self.dimension, u.len()));
}
crate::error::validate_unit_range(u)?;
Err(CopulaError::not_implemented(
"C-vine PDF computation not yet implemented",
))
}
fn sample<R: Rng + ?Sized>(&self, n: usize, rng: &mut R) -> Result<DMatrix<f64>> {
use rand_distr::{Distribution, Uniform};
let uniform = Uniform::new(0.0, 1.0);
let d = self.dimension;
let mut samples = DMatrix::<f64>::zeros(n, d);
for i in 0..n {
let mut w: Vec<f64> = (0..d).map(|_| uniform.sample(rng)).collect();
let mut v = vec![vec![0.0; d]; d];
v[0][0] = w[0];
for j in 1..d {
v[0][j] = self.trees[0][j - 1].h_inv(w[j], v[0][0])?;
}
for level in 1..self.trees.len() {
for (j, pair_cop) in self.trees[level].iter().enumerate() {
let idx = level + j + 1;
if idx < d {
let cond_val = pair_cop.h_function(v[level - 1][idx], v[level - 1][level])?;
v[level][idx] = pair_cop.h_inv(cond_val, v[level - 1][level])?;
}
}
}
for j in 0..d {
samples[(i, j)] = v[0][j];
}
}
Ok(samples)
}
fn dimension(&self) -> usize {
self.dimension
}
}
#[derive(Clone)]
pub struct DVineCopula {
dimension: usize,
trees: Vec<Vec<PairCopula>>,
}
impl DVineCopula {
pub fn new(dimension: usize, trees: Vec<Vec<PairCopula>>) -> Result<Self> {
if dimension < 2 {
return Err(CopulaError::invalid_parameter(
"dimension must be >= 2 for vine copulas",
));
}
if trees.len() != dimension - 1 {
return Err(CopulaError::invalid_parameter(&format!(
"D-vine with dimension {} should have {} trees, got {}",
dimension,
dimension - 1,
trees.len()
)));
}
for (level, tree) in trees.iter().enumerate() {
let expected_pairs = dimension - level - 1;
if tree.len() != expected_pairs {
return Err(CopulaError::invalid_parameter(&format!(
"Tree {} should have {} pair-copulas, got {}",
level + 1,
expected_pairs,
tree.len()
)));
}
}
Ok(Self { dimension, trees })
}
}
impl Copula for DVineCopula {
fn cdf(&self, u: &[f64]) -> Result<f64> {
if u.len() != self.dimension {
return Err(CopulaError::dimension_mismatch(self.dimension, u.len()));
}
crate::error::validate_unit_range(u)?;
Err(CopulaError::not_implemented(
"D-vine CDF requires specialized numerical methods",
))
}
fn pdf(&self, u: &[f64]) -> Result<f64> {
if u.len() != self.dimension {
return Err(CopulaError::dimension_mismatch(self.dimension, u.len()));
}
crate::error::validate_unit_range(u)?;
Err(CopulaError::not_implemented(
"D-vine PDF computation not yet implemented",
))
}
fn sample<R: Rng + ?Sized>(&self, n: usize, rng: &mut R) -> Result<DMatrix<f64>> {
use rand_distr::{Distribution, Uniform};
let uniform = Uniform::new(0.0, 1.0);
let d = self.dimension;
let mut samples = DMatrix::<f64>::zeros(n, d);
for i in 0..n {
let mut w: Vec<f64> = (0..d).map(|_| uniform.sample(rng)).collect();
let mut v = vec![vec![0.0; d]; d];
v[0][0] = w[0];
v[0][1] = self.trees[0][0].h_inv(w[1], v[0][0])?;
for j in 2..d {
v[0][j] = w[j];
for level in 0..(j.min(self.trees.len())) {
if level < self.trees.len() && j - level - 1 < self.trees[level].len() {
let pair_cop = &self.trees[level][j - level - 1];
let cond_var = v[level][j - level - 1];
v[level + 1][j] = pair_cop.h_inv(v[level][j], cond_var)?;
}
}
}
for j in 0..d {
samples[(i, j)] = v[0][j];
}
}
Ok(samples)
}
fn dimension(&self) -> usize {
self.dimension
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pair_copula_creation() {
let clayton = CopulaType::Clayton(ClaytonCopula::new(2.0).unwrap());
let pair = PairCopula::new(clayton, 0, 1, vec![]);
assert_eq!(pair.var1, 0);
assert_eq!(pair.var2, 1);
}
#[test]
fn test_cvine_creation() {
let c12 = CopulaType::Clayton(ClaytonCopula::new(2.0).unwrap());
let c13 = CopulaType::Clayton(ClaytonCopula::new(1.5).unwrap());
let c23_1 = CopulaType::Clayton(ClaytonCopula::new(1.0).unwrap());
let tree1 = vec![
PairCopula::new(c12, 0, 1, vec![]),
PairCopula::new(c13, 0, 2, vec![]),
];
let tree2 = vec![PairCopula::new(c23_1, 1, 2, vec![0])];
let cvine = CVineCopula::new(3, vec![tree1, tree2]).unwrap();
assert_eq!(cvine.dimension(), 3);
}
#[test]
fn test_cvine_wrong_num_trees() {
let c12 = CopulaType::Clayton(ClaytonCopula::new(2.0).unwrap());
let tree1 = vec![PairCopula::new(c12, 0, 1, vec![])];
let result = CVineCopula::new(3, vec![tree1]);
assert!(result.is_err());
}
#[test]
fn test_dvine_creation() {
let c12 = CopulaType::Clayton(ClaytonCopula::new(2.0).unwrap());
let c23 = CopulaType::Clayton(ClaytonCopula::new(1.5).unwrap());
let c13_2 = CopulaType::Clayton(ClaytonCopula::new(1.0).unwrap());
let tree1 = vec![
PairCopula::new(c12, 0, 1, vec![]),
PairCopula::new(c23, 1, 2, vec![]),
];
let tree2 = vec![PairCopula::new(c13_2, 0, 2, vec![1])];
let dvine = DVineCopula::new(3, vec![tree1, tree2]).unwrap();
assert_eq!(dvine.dimension(), 3);
}
#[test]
fn test_cvine_sample() {
use rand::thread_rng;
let mut rng = thread_rng();
let c12 = CopulaType::Clayton(ClaytonCopula::new(2.0).unwrap());
let tree1 = vec![PairCopula::new(c12, 0, 1, vec![])];
let cvine = CVineCopula::new(2, vec![tree1]).unwrap();
let samples = cvine.sample(10, &mut rng).unwrap();
assert_eq!(samples.nrows(), 10);
assert_eq!(samples.ncols(), 2);
for i in 0..10 {
for j in 0..2 {
assert!(samples[(i, j)] >= 0.0 && samples[(i, j)] <= 1.0);
}
}
}
}