Skip to content

Vine Copulas

Overview

Vine copulas decompose a \(d\)-dimensional copula into \(d(d-1)/2\) bivariate copulas arranged in a tree structure. VineCopula is the primary runtime and supports three structural modes:

  • auto R-vine: data-driven structure selected by Dissmann's algorithm;
  • fixed C-vine: successive-root star trees;
  • fixed D-vine: path trees.

Any other valid regular vine can be supplied as an RVineMatrix. C/D/R are structure modes of one model, not separate generic runtime classes.

Each edge copula is selected automatically from the configured candidate families via AIC, and can use constant (MLE) or time-varying (SCAR, GAS) parameters.

The shared transform_type option is passed to candidate constructors. Archimedean edge families support softplus, xtanh, exp, and logistic. For exp or logistic, use an Archimedean-only candidate pool as shown in Parameter Transforms. BivariateGaussianCopula accepts the same argument for constructor uniformity, but Gaussian edges always use their bounded GaussianTanh correlation mapping.

Use candidates= to configure the automatic family-selection pool. For Gaussian pair-copula edges, use BivariateGaussianCopula; the multivariate GaussianCopula is not a valid vine edge family. The lower-level copulas= fit argument has a different purpose: it fixes every edge family and rotation as (CopulaClass, rotation) specs in a list-of-lists matching the tree layout.

C-vine

A C-vine uses a star structure where the first variable is the root of tree 0, the second variable is the root of tree 1, and so on.

from pyscarcopula import VineCopula

vine = VineCopula.cvine(d=u.shape[1])
vine.fit(u, method='scar-tm-ou',
         truncation_level=2,
         min_edge_logL=10,
         to_pobs=True)
vine.summary()

Static MLE models fitted to the same C-vine structure and fixed edge families have equivalent edge semantics and likelihood. Internal edge ordering and seeded sampling trajectories are not an API parity guarantee.

Conditional sampling uses matrix-based suffix conditioning and a DAG+MCMC fallback for arbitrary conditioning sets; prediction supports return_diagnostics, MCMC controls, and dynamic_conditioning.

Auto-selected R-vine

An R-vine selects the tree structure from data using Dissmann's algorithm: at each tree level, a maximum spanning tree is built on \(|\text{Kendall's tau}|\), subject to the proximity condition.

from pyscarcopula import VineCopula

vine = VineCopula()
vine.fit(u, method='scar-tm-ou',
         truncation_level=2,
         min_edge_logL=10,
         to_pobs=True)
vine.summary()

An auto-selected R-vine can place the strongest available pairwise dependencies at each tree level instead of being constrained to a C-vine star structure. Compare fitted structures with the same likelihood and information criterion.

If the conditioning set is known in advance, you can bias structure selection toward an R-vine that supports the fast exact conditional sampler for that set, with the fixed variables placed at the end of the R-vine variable order:

vine = VineCopula()
vine.fit(
    u,
    method='scar-tm-ou',
    truncation_level=2,
    min_edge_logL=10,
    to_pobs=True,
    given_vars=[0, 2],
)

given_vars is a fit-time structure-selection target. With the default conditional_strict=True, fit raises ValueError if no suffix-compatible exact structure is constructed. With conditional_strict=False, prediction can still use other conditioning sets, but the unsupported fit-time target remains rejected by predict. To use the approximate fallback for that set, fit without given_vars and supply the set only to predict(given=...).

Fixed D-vine

Use a D-vine factory when the first-tree path order is part of the model:

from pyscarcopula import VineCopula

vine = VineCopula.dvine(
    d=u.shape[1],
    order=[0, 2, 1, 3],
).fit(u, method="mle")

VineCopula.cvine(...) and .dvine(...) both configure a fixed RVineMatrix; fitting their pair copulas never runs structure selection.

Arbitrary fixed regular vine

Prefer decoded tree edges over manually writing either matrix convention:

from pyscarcopula import VineCopula
from pyscarcopula.vine import RVineMatrix

structure = RVineMatrix.from_trees(
    d=4,
    trees=[
        [
            (frozenset({0, 1}), frozenset()),
            (frozenset({1, 2}), frozenset()),
            (frozenset({1, 3}), frozenset()),
        ],
        [
            (frozenset({0, 2}), frozenset({1})),
            (frozenset({0, 3}), frozenset({1})),
        ],
        [
            (frozenset({2, 3}), frozenset({0, 1})),
        ],
    ],
)
vine = VineCopula(structure=structure).fit(u, method="mle")

RVineMatrix.from_trees validates tree sizes, indices, and the proximity condition before any optimizer is called.

Candidate families versus fixed families

Constructor candidates= defines the pool searched independently for every edge:

from pyscarcopula import BivariateGaussianCopula, FrankCopula

vine = VineCopula.dvine(
    d=u.shape[1],
    candidates=[BivariateGaussianCopula, FrankCopula],
).fit(u, method="mle")

Fit argument copulas= instead fixes every (family, rotation) in the structure's decoded tree order:

trees = vine.structure.to_trees()
fixed_specs = [
    [(BivariateGaussianCopula, 0) for _ in level]
    for level in trees
]
fixed = VineCopula(structure=vine.structure).fit(
    u,
    method="mle",
    copulas=fixed_specs,
)

copulas= contains classes and rotations, not fitted copula instances.

Inspecting structure

print(vine.structure)               # canonical RVineMatrix
print(vine.structure.to_trees())    # decoded semantic edges
print(vine.natural_order_matrix)    # numerical/integration convention

vine.matrix returns the same natural-order representation for compatibility with older code. Prefer structure for modelling and natural_order_matrix when an external integration explicitly needs that convention.

RVineCopula is a compatibility name for the same VineCopula type.

Truncation

For large d, not all edges benefit from dynamic parameters:

# Trees 0-1: SCAR, trees 2+: MLE
vine.fit(u, method='scar-tm-ou', truncation_level=2,
         truncation_fill='mle')

# Edges below min_edge_logL become independent
vine.fit(u, method='scar-tm-ou', min_edge_logL=10)

# Default truncation_fill='independent': truncate and prune to independence
vine.fit(u, method='scar-tm-ou',
         truncation_level=2, min_edge_logL=10)

Edges where no parametric copula beats independence by AIC are set to IndependentCopula automatically.

MLE optimizer options and NumericalConfig also apply to the refinement stage of automatic family selection. Screening uses the configured native thread count. An explicit natural-parameter alpha0 requires exactly one non-independent candidate family, or fixed edge families via copulas=; with several candidate families, omit it to use family-specific itau starts. Internally generated itau starts are projected onto each family's MLE bounds; explicit alpha0 values outside those bounds are rejected. Exact Kendall dependence (tau = ±1) is retained for selection: Gaussian starts at its admissible correlation bound, and bounded Archimedean families start at their upper parameter bound. An unbounded Archimedean itau limit is reported as a candidate numerical failure, rather than being replaced with an arbitrary finite parameter. The default pool can still select Gaussian; a pool with no evaluable candidate raises and preserves the previous fit. Interior itau mappings and public tau_to_param domains are unchanged. A fitted MLE point must pass a final native likelihood and gradient check: an optimizer's finite numerical-failure penalty is not accepted as a likelihood. Unexpected native, allocation, or configuration errors abort fitting and preserve any previous fitted model. A candidate's numerical failure is reported with a warning; if no candidate can be evaluated, fitting raises instead of reporting a successful independent model.

dynamic_failure_policy='fallback' replaces an unsuccessful dynamic edge fit with its MLE selection result. Use 'keep' to retain the unsuccessful dynamic result and its diagnostics, or 'raise' to abort fitting while preserving any previous fitted model. This policy applies to fixed structures and every candidate in auto, beam, and multi-start structure selection. Falling back does not turn an unsuccessful MLE result into a successful fit.

Goodness of fit

from pyscarcopula.stattests import gof_test

gof = gof_test(vine, u, to_pobs=False)

The gof_test function dispatches to the correct Rosenblatt transform for both C-vine and R-vine models, including mixed SCAR/MLE edges.

Sampling and prediction

import numpy as np

# Predict: next-step conditional sampling (for VaR/CVaR)
predictions = vine.predict(
    n=10000,
    u=u,
    horizon='next',
    rng=np.random.default_rng(2025),
)

# Sample: reproduce fitted model (for parameter recovery)
samples = vine.sample(n=10000, rng=np.random.default_rng(2024))

For a vine with only built-in static MLE or independent edges, unconditional predict uses the same bounded row batches as sample. Static prediction does not compute or cache historical pseudo-observations, and static edge parameters remain scalar during conditional prediction.

Conditional generation is supported via given={var_index: u_value} in pseudo-observation space:

# Fit with given_vars=[0] when this exact conditioning target is known.
pred_cond = vine.predict(
    n=5000,
    u=u,
    given={0: 0.6},
    horizon='current',
    rng=np.random.default_rng(2026),
)

Use a fresh np.random.default_rng(seed) for each call when exact reproducibility is required. Reusing the same generator object advances its random stream.

VineCopula automatically selects exact suffix conditioning when the fitted structure supports it and otherwise uses an approximate DAG+MCMC fallback. VineCopula.predict does not accept conditional_method; that name is reported only in diagnostics. See Prediction Semantics for the shared meaning of predict, sample, and horizon, and R-vine Conditioning for exact-path requirements, MCMC controls, and diagnostic fields.

For SCAR-TM edges, predict(..., horizon='current') uses the posterior latent state after the fitted history and predict(..., horizon='next') uses the one-step-ahead latent state. For SCAR-TM-OU edges, sample simulates independent OU trajectories.

SCAR vines use edge-wise pseudo likelihood for fitting and likelihood evaluation. Each edge is optimized separately. Pseudo-observations for higher trees are constructed from the fitted lower-tree h-functions, using mixture h-functions for latent edges. sample reproduces the latent-trajectory vine, while predict uses edge-wise posterior or one-step predictive states from the observed history. The edge-wise criterion approximates the full joint marginal likelihood; joint latent-state optimization and filtering are outside this contract.

For SCAR-TM predictive parameter sampling, predictive_r_mode may be None, "grid", or "histogram".