use crate::error::{CopulaError, Result};
use nalgebra::DMatrix;
use rand::Rng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub trait Copula {
fn cdf(&self, u: &[f64]) -> Result<f64>;
fn pdf(&self, u: &[f64]) -> Result<f64>;
fn sample<R: Rng + ?Sized>(&self, n: usize, rng: &mut R) -> Result<DMatrix<f64>>;
fn dimension(&self) -> usize;
fn conditional_cdf(&self, u: &[f64], given: &[usize]) -> Result<f64> {
let _ = (u, given);
Err(CopulaError::not_implemented(format!(
"conditional_cdf for {}",
std::any::type_name::<Self>()
)))
}
fn tail_dependence(&self) -> Result<(f64, f64)> {
Err(CopulaError::not_implemented(format!(
"tail_dependence for {}",
std::any::type_name::<Self>()
)))
}
fn kendall_tau(&self) -> Result<f64> {
Err(CopulaError::not_implemented(format!(
"kendall_tau for {}",
std::any::type_name::<Self>()
)))
}
fn spearman_rho(&self) -> Result<f64> {
Err(CopulaError::not_implemented(format!(
"spearman_rho for {}",
std::any::type_name::<Self>()
)))
}
fn has_closed_form(&self) -> (bool, bool) {
(true, true) }
fn family_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
#[cfg(feature = "estimation")]
#[cfg_attr(docsrs, doc(cfg(feature = "estimation")))]
pub trait FittableCopula: Copula {
type Parameters: Clone + std::fmt::Debug;
fn fit(&mut self, pseudo_obs: &DMatrix<f64>) -> Result<Self::Parameters>;
fn log_likelihood(&self, pseudo_obs: &DMatrix<f64>) -> Result<f64>;
fn fit_moments(&mut self, pseudo_obs: &DMatrix<f64>) -> Result<Self::Parameters> {
self.fit(pseudo_obs)
}
fn parameters(&self) -> Self::Parameters;
fn set_parameters(&mut self, params: Self::Parameters) -> Result<()>;
fn standard_errors(&self, _pseudo_obs: &DMatrix<f64>) -> Result<Self::Parameters> {
Err(CopulaError::not_implemented("standard_errors"))
}
fn confidence_intervals(
&self,
_pseudo_obs: &DMatrix<f64>,
_confidence_level: f64,
) -> Result<(Self::Parameters, Self::Parameters)> {
Err(CopulaError::not_implemented("confidence_intervals"))
}
}
pub trait ArchimedeanCopula: Copula {
fn phi(&self, t: f64) -> Result<f64>;
fn phi_inv(&self, s: f64) -> Result<f64>;
fn phi_inv_deriv(&self, s: f64, k: usize) -> Result<f64>;
fn validate_generator(&self) -> Result<()> {
let phi_1 = self.phi(1.0)?;
if (phi_1).abs() > 1e-10 {
return Err(CopulaError::invalid_parameter(
"Generator function must satisfy φ(1) = 0",
));
}
let phi_0 = self.phi(1e-10)?;
if !phi_0.is_infinite() && phi_0 < 1e6 {
return Err(CopulaError::invalid_parameter(
"Generator function must satisfy φ(0) = ∞",
));
}
Ok(())
}
fn parameter(&self) -> f64 {
f64::NAN }
}
pub trait ExtremeValueCopula: Copula {
fn pickands_function(&self, t: f64) -> Result<f64>;
fn validate_pickands(&self) -> Result<()> {
let a_0 = self.pickands_function(0.0)?;
let a_1 = self.pickands_function(1.0)?;
if (a_0 - 1.0).abs() > 1e-10 || (a_1 - 1.0).abs() > 1e-10 {
return Err(CopulaError::invalid_parameter(
"Pickands function must satisfy A(0) = A(1) = 1",
));
}
Ok(())
}
}
pub trait VineCopula: Copula {
fn h_function(&self, u: f64, v: f64) -> Result<f64>;
fn h_function_inv(&self, p: f64, v: f64) -> Result<f64>;
}
pub trait MetaDistribution {
type CopulaType: Copula;
fn copula(&self) -> &Self::CopulaType;
fn copula_mut(&mut self) -> &mut Self::CopulaType;
}
pub trait SymmetricCopula: Copula {}
pub trait ExchangeableCopula: Copula {}
pub trait BoundedParameters {
fn parameter_bounds() -> Vec<(f64, f64)>;
fn check_bounds(&self) -> Result<()>;
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub trait SerializableCopula: Copula + Serialize + for<'de> Deserialize<'de> {
fn to_json(&self) -> Result<String> {
serde_json::to_string(self).map_err(|e| CopulaError::SerializationError {
message: format!("JSON serialization failed: {}", e),
})
}
fn from_json(json: &str) -> Result<Self>
where
Self: Sized,
{
serde_json::from_str(json).map_err(|e| CopulaError::SerializationError {
message: format!("JSON deserialization failed: {}", e),
})
}
}
pub trait ParameterConversion<T> {
fn from_kendall_tau(tau: f64) -> Result<T>;
fn from_spearman_rho(rho: f64) -> Result<T>;
fn to_kendall_tau(&self) -> Result<f64>;
fn to_spearman_rho(&self) -> Result<f64>;
}
#[cfg(test)]
mod tests {
use super::*;
struct MockCopula {
dimension: usize,
}
impl Copula for MockCopula {
fn cdf(&self, u: &[f64]) -> Result<f64> {
if u.len() != self.dimension {
return Err(CopulaError::dimension_mismatch(self.dimension, u.len()));
}
Ok(u.iter().product()) }
fn pdf(&self, u: &[f64]) -> Result<f64> {
if u.len() != self.dimension {
return Err(CopulaError::dimension_mismatch(self.dimension, u.len()));
}
Ok(1.0) }
fn sample<R: Rng + ?Sized>(&self, n: usize, rng: &mut R) -> Result<DMatrix<f64>> {
use rand::RngExt;
let mut samples = DMatrix::<f64>::zeros(n, self.dimension);
for i in 0..n {
for j in 0..self.dimension {
samples[(i, j)] = rng.random::<f64>();
}
}
Ok(samples)
}
fn dimension(&self) -> usize {
self.dimension
}
fn family_name(&self) -> &'static str {
"Mock"
}
}
#[test]
fn test_mock_copula_basic_operations() {
let copula = MockCopula { dimension: 2 };
let cdf = copula.cdf(&[0.5, 0.5]).unwrap();
assert_eq!(cdf, 0.25);
let pdf = copula.pdf(&[0.5, 0.5]).unwrap();
assert_eq!(pdf, 1.0);
assert_eq!(copula.dimension(), 2);
assert!(copula.cdf(&[0.5]).is_err());
}
#[test]
fn test_trait_default_implementations() {
let copula = MockCopula { dimension: 2 };
assert!(copula.conditional_cdf(&[0.5, 0.5], &[0]).is_err());
assert!(copula.tail_dependence().is_err());
assert!(copula.kendall_tau().is_err());
assert!(copula.spearman_rho().is_err());
}
}