VineCopula — C-vine, D-vine, and R-vine in Rust
Construct and fit C-vine, D-vine, and R-vine copulas in Rust. Covers VineCopula constructors, VineFitOptions, family selection, and result accessors.
VineCopula represents a pair-copula construction over d variables organized as a sequence of trees. Rscopulas supports three vine types — C-vine, D-vine, and R-vine — each available as a direct Gaussian construction and as a data-driven fit with mixed pair-copula families. VineCopula implements CopulaModel, so you can call log_pdf and sample on any fitted vine after importing the trait.
Constructors
Gaussian constructions
Use these when you already have a correlation matrix and want to skip fitting:
use ndarray::array;
use rscopulas::VineCopula;
// C-vine: star structure, root has highest total dependence
let correlation = array![
[1.0_f64, 0.60, 0.35],
[0.60, 1.0, 0.25],
[0.35, 0.25, 1.0 ],
];
let model = VineCopula::gaussian_c_vine(vec![0, 1, 2], correlation.clone())?;
println!("order = {:?}", model.order());
// D-vine: path structure
let model = VineCopula::gaussian_d_vine(vec![0, 1, 2], correlation)?;
Both functions accept an explicit order: Vec<usize> and a valid correlation matrix. They return Err if the matrix is not a valid correlation matrix or the order is inconsistent with its dimension.
Constructing from explicit trees
If you have hand-specified pair-copula edges, assemble them into VineTree values and call from_trees:
use rscopulas::{VineCopula, VineStructureKind};
let model = VineCopula::from_trees(
VineStructureKind::R,
trees, // Vec<VineTree>
Some(2), // optional truncation level
)?;
Fitting
All three fit methods accept a &PseudoObs and a &VineFitOptions and return FitResult<VineCopula>.
| Method | Structure | Tree selection |
|---|---|---|
VineCopula::fit_c_vine | C-vine | Root chosen by maximum total Kendall tau weight |
VineCopula::fit_d_vine | D-vine | Path order chosen by greedy Kendall tau |
VineCopula::fit_r_vine | R-vine | Dissmann-style maximum spanning tree per level |
Fitting with an explicit order
For exact conditional sampling you often want to pin the variable order so a specific column lands at the Rosenblatt anchor. Use:
| Method | Signature |
|---|---|
VineCopula::fit_c_vine_with_order | fn(data: &PseudoObs, order: &[usize], options: &VineFitOptions) -> Result<FitResult<Self>> |
VineCopula::fit_d_vine_with_order | fn(data: &PseudoObs, order: &[usize], options: &VineFitOptions) -> Result<FitResult<Self>> |
Place the conditioning column at order.last() — it becomes variable_order()[0], the Rosenblatt anchor. See Conditional sampling for the full workflow.
use rscopulas::{PseudoObs, VineCopula, VineFitOptions};
use ndarray::array;
let data = PseudoObs::new(array![
[0.12_f64, 0.18, 0.21],
[0.48, 0.51, 0.46],
[0.82, 0.79, 0.76],
])?;
let fit = VineCopula::fit_r_vine(&data, &VineFitOptions::default())?;
println!("AIC: {}", fit.diagnostics.aic);
VineFitOptions
VineFitOptions controls which pair-copula families are considered, how rotations are handled, which information criterion selects the best edge model, and whether the vine is truncated.
pub struct VineFitOptions {
pub base: FitOptions, // exec_policy, clip_eps, max_iter
pub family_set: Vec<PairCopulaFamily>, // families considered per edge
pub include_rotations: bool, // allow rotated Archimedean families
pub criterion: SelectionCriterion, // Aic or Bic
pub truncation_level: Option<usize>, // stop fitting after this tree level
pub independence_threshold: Option<f64>, // select independence if |tau| <= threshold
}
PairCopulaFamily variants:
| Variant | Notes |
|---|---|
Independence | No parameters; always available. |
Gaussian | Single parameter rho. No rotation needed. |
StudentT | Two parameters (rho, nu). No rotation. |
Clayton | Single theta > 0. Rotations available. |
Frank | Single theta > 0. No rotation. |
Gumbel | Single theta >= 1. Rotations available. |
Khoudraji | Asymmetric composition. Fitted via bounded CPU search. |
SelectionCriterion variants: Aic, Bic.
The default VineFitOptions includes all families, enables rotations, uses AIC, and applies no truncation.
R-vine fit example
use ndarray::array;
use rscopulas::{
PairCopulaFamily, PseudoObs, SelectionCriterion, VineCopula, VineFitOptions,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let data = PseudoObs::new(array![
[0.12, 0.18, 0.21],
[0.21, 0.25, 0.29],
[0.27, 0.22, 0.31],
[0.35, 0.42, 0.39],
[0.48, 0.51, 0.46],
[0.56, 0.49, 0.58],
[0.68, 0.73, 0.69],
[0.82, 0.79, 0.76],
])?;
let options = VineFitOptions {
family_set: vec![
PairCopulaFamily::Independence,
PairCopulaFamily::Gaussian,
PairCopulaFamily::Clayton,
PairCopulaFamily::Frank,
PairCopulaFamily::Gumbel,
PairCopulaFamily::Khoudraji,
],
include_rotations: true,
criterion: SelectionCriterion::Aic,
truncation_level: Some(1),
..VineFitOptions::default()
};
let fit = VineCopula::fit_r_vine(&data, &options)?;
println!("structure = {:?}", fit.model.structure());
println!("order = {:?}", fit.model.order());
println!("pair parameters = {:?}", fit.model.pair_parameters());
Ok(())
}
Accessors
Once you have a VineCopula (from a fit or a constructor), the following methods expose its structure and parameters:
| Method | Return type | Description |
|---|---|---|
.structure() | VineStructureKind | C, D, or R. |
.structure_info() | &VineStructure | R-vine matrix, kind, and truncation level. |
.order() | Vec<usize> | Top-level variable order implied by the structure. |
.variable_order() | &[usize] | Diagonal ordering used by the Rosenblatt transform. Position 0 is the anchor. |
.trees() | &[VineTree] | All fitted tree levels in evaluation order. |
.pair_parameters() | Vec<f64> | Flattened first parameter from each edge copula. |
.truncation_level() | Option<usize> | Configured truncation level, if any. |
Use .truncation_level(Some(1)) in VineFitOptions to fit only the first tree and assign independence copulas to deeper levels. This is the fastest configuration and still captures primary pairwise dependence.
Evaluating and sampling
VineCopula implements CopulaModel, so the standard log_pdf and sample calls work identically to the single-family types:
use rscopulas::{CopulaModel, EvalOptions, SampleOptions};
use rand::{SeedableRng, rngs::StdRng};
let log_densities = fit.model.log_pdf(&data, &EvalOptions::default())?;
let mut rng = StdRng::seed_from_u64(42);
let samples = fit.model.sample(100, &mut rng, &SampleOptions::default())?;
Rosenblatt transforms and conditional sampling
VineCopula exposes the Rosenblatt transforms directly; both accept and return (n, dim) matrices indexed by the original variable label and round-trip up to ~1e-8.
| Method | Signature | Description |
|---|---|---|
.rosenblatt(v, options) | fn(ArrayView2<f64>, &SampleOptions) -> Result<Array2<f64>> | Forward transform U = F(V). |
.inverse_rosenblatt(u, options) | fn(ArrayView2<f64>, &SampleOptions) -> Result<Array2<f64>> | Inverse transform V = F^{-1}(U). sample is a thin wrapper over this. |
.rosenblatt_prefix(v, col_limit, options) | fn(ArrayView2<f64>, usize, &SampleOptions) -> Result<Array2<f64>> | Partial forward transform returning only the first col_limit columns, indexed by diagonal position. Backs the k >= 2 path of the Python sample_conditional convenience. |
use rscopulas::{VineCopula, VineFitOptions, SampleOptions};
let target = 2usize;
let order = vec![0, 1, target];
let vine = VineCopula::fit_c_vine_with_order(
&data, &order, &VineFitOptions::default(),
)?.model;
assert_eq!(vine.variable_order()[0], target);
// Pin the known column at the anchor, fill the rest with fresh uniforms,
// and call inverse_rosenblatt to get conditional samples.
let v = vine.inverse_rosenblatt(u.view(), &SampleOptions::default())?;
See Conditional sampling for the end-to-end workflow and the variable_order / order convention.