use std::error::Error;
use ndarray::Array1;
use ndarray::Array2;
use super::CopulaType;
use super::cvine::CVine;
use super::dvine::DVine;
use crate::traits::MultivariateExt;
#[derive(Debug, Clone)]
pub enum RVine {
D(DVine),
C(CVine),
}
impl RVine {
pub fn from_dvine(dvine: DVine) -> Self {
RVine::D(dvine)
}
pub fn from_cvine(cvine: CVine) -> Self {
RVine::C(cvine)
}
pub fn independence(dim: usize) -> Result<Self, Box<dyn Error>> {
DVine::independence(dim).map(RVine::D)
}
pub fn dim(&self) -> usize {
match self {
RVine::D(d) => d.dim(),
RVine::C(c) => c.dim(),
}
}
pub fn kind(&self) -> &'static str {
match self {
RVine::D(_) => "DVine",
RVine::C(_) => "CVine",
}
}
}
impl MultivariateExt for RVine {
fn r#type(&self) -> CopulaType {
CopulaType::RVine
}
fn sample(&self, n: usize) -> Result<Array2<f64>, Box<dyn Error>> {
match self {
RVine::D(d) => d.sample(n),
RVine::C(c) => c.sample(n),
}
}
fn fit(&mut self, x: Array2<f64>) -> Result<(), Box<dyn Error>> {
match self {
RVine::D(d) => d.fit(x),
RVine::C(c) => c.fit(x),
}
}
fn check_fit(&self, x: &Array2<f64>) -> Result<(), Box<dyn Error>> {
match self {
RVine::D(d) => d.check_fit(x),
RVine::C(c) => c.check_fit(x),
}
}
fn pdf(&self, x: Array2<f64>) -> Result<Array1<f64>, Box<dyn Error>> {
match self {
RVine::D(d) => d.pdf(x),
RVine::C(c) => c.pdf(x),
}
}
fn log_pdf(&self, x: Array2<f64>) -> Result<Array1<f64>, Box<dyn Error>> {
match self {
RVine::D(d) => d.log_pdf(x),
RVine::C(c) => c.log_pdf(x),
}
}
fn cdf(&self, x: Array2<f64>) -> Result<Array1<f64>, Box<dyn Error>> {
match self {
RVine::D(d) => d.cdf(x),
RVine::C(c) => c.cdf(x),
}
}
}
#[cfg(test)]
mod tests {
use ndarray::array;
use super::*;
use crate::multivariate::dvine::PairCopula;
#[test]
fn rvine_dvine_delegates_density() {
let tree = vec![
vec![
PairCopula::Gaussian { rho: 0.4 },
PairCopula::Gaussian { rho: 0.3 },
],
vec![PairCopula::Clayton { theta: 1.5 }],
];
let dv = DVine::new(3, tree).unwrap();
let rv = RVine::from_dvine(dv.clone());
let q = array![[0.3, 0.5, 0.7], [0.1, 0.5, 0.9]];
let lp_dv = dv.log_pdf(q.clone()).unwrap();
let lp_rv = rv.log_pdf(q).unwrap();
for i in 0..lp_dv.len() {
assert!(
(lp_dv[i] - lp_rv[i]).abs() < 1e-12,
"DVine wrapper diverges from underlying DVine at row {i}: {} vs {}",
lp_dv[i],
lp_rv[i]
);
}
assert_eq!(rv.kind(), "DVine");
assert_eq!(rv.dim(), 3);
}
#[test]
fn rvine_cvine_delegates_density() {
let tree = vec![
vec![
PairCopula::Gaussian { rho: 0.5 },
PairCopula::Gaussian { rho: 0.5 },
],
vec![PairCopula::Independence],
];
let cv = CVine::new(3, tree).unwrap();
let rv = RVine::from_cvine(cv.clone());
let q = array![[0.2, 0.4, 0.6], [0.4, 0.6, 0.8]];
let lp_cv = cv.log_pdf(q.clone()).unwrap();
let lp_rv = rv.log_pdf(q).unwrap();
for i in 0..lp_cv.len() {
assert!(
(lp_cv[i] - lp_rv[i]).abs() < 1e-12,
"CVine wrapper diverges from underlying CVine at row {i}: {} vs {}",
lp_cv[i],
lp_rv[i]
);
}
assert_eq!(rv.kind(), "CVine");
}
#[test]
fn rvine_independence_sampling_uniform_marginals() {
let rv = RVine::independence(3).unwrap();
let s = rv.sample(10_000).unwrap();
for j in 0..3 {
let col = s.column(j);
let m: f64 = col.iter().sum::<f64>() / col.len() as f64;
assert!(
(m - 0.5).abs() < 0.02,
"marginal {j} mean = {m}, expected ~0.5"
);
}
assert_eq!(rv.kind(), "DVine"); }
}