Source code for copul.family.copula_builder

import numpy as np
import sympy

from copul.family.core.copula import Copula
from copul.family.core.biv_copula import BivCopula


[docs] class CopulaBuilder:
[docs] @classmethod def from_cdf(cls, cdf): sp_cdf = sympy.sympify(cdf) free_symbols = [str(symbol) for symbol in sp_cdf.free_symbols] # get greek letters from free symbols params = [symbol for symbol in free_symbols if cls._is_greek(symbol)] func_vars = [symbol for symbol in free_symbols if symbol not in params] n = len(func_vars) obj = cls._from_string(n, params) func_vars = sorted(func_vars) if n == 2: sp_cdf = sp_cdf.subs(func_vars[0], obj.u).subs(func_vars[1], obj.v) else: for i, symbol in enumerate(func_vars): sp_cdf = sp_cdf.subs(symbol, obj.u_symbols[i]) for symbol in params: sp_cdf = sp_cdf.subs(symbol, obj._free_symbols[symbol]) obj._cdf_expr = sp_cdf return obj
[docs] @classmethod def from_pdf(cls, pdf): """ Build a copula object from a PDF expression. Parameters ---------- pdf : str | sympy.Expr A symbolic expression for c(u, v) in the bivariate case or c(u1, ..., ud) in the d-variate case. Greek-letter symbols are treated as parameters; all other symbols are taken as the copula's function variables. """ # Bind greek names (except 'pi') to Symbols so they aren't parsed as special functions greek_names = [ "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", ] local_dict = {name: sympy.symbols(name, real=True) for name in greek_names} # Keep zero terms so u/v/x/y/... remain in free_symbols even if multiplied by 0 sp_pdf = sympy.sympify(pdf, locals=local_dict, evaluate=False) # Identify symbols: greek -> params, the rest -> function variables free_symbols = [str(s) for s in sp_pdf.free_symbols] params = [name for name in free_symbols if cls._is_greek(name) and name != "pi"] func_vars = [name for name in free_symbols if name not in params] n = len(func_vars) if n < 2: raise ValueError("PDF must depend on at least two variables (u, v).") # Create a copula shell and map variables obj = cls._from_string(n, params) func_vars = sorted(func_vars) # Replace function variables with the copula's variables if n == 2: sp_pdf = sp_pdf.subs(func_vars[0], obj.u).subs(func_vars[1], obj.v) vars_on_obj = [obj.u, obj.v] else: for i, name in enumerate(func_vars): sp_pdf = sp_pdf.subs(name, obj.u_symbols[i]) vars_on_obj = list(obj.u_symbols) # Replace parameter names with the object's parameter symbols for name in params: sp_pdf = sp_pdf.subs(name, obj._free_symbols[name]) # Store the PDF obj._pdf_expr = sp_pdf # Also construct and store a CDF by integrating the PDF from 0 to each variable # Use dummy symbols for integration bounds, then substitute upper limits. cdf_expr = sp_pdf for var in vars_on_obj: s = sympy.symbols(f"__int_{str(var)}", real=True, nonnegative=True) cdf_expr = sympy.integrate(cdf_expr.subs(var, s), (s, 0, var)) obj._cdf_expr = cdf_expr return obj
@classmethod def _from_string(cls, n, params): if n == 2: obj = BivCopula() elif n > 2: obj = Copula(n) else: raise ValueError("n must be greater than 1") for key in params: setattr(obj, key, sympy.symbols(key, real=True)) value = getattr(obj, key) obj.params.append(value) obj.intervals[str(value)] = sympy.Interval(-np.inf, np.inf) obj._free_symbols = {symbol: getattr(obj, symbol) for symbol in params} return obj @staticmethod def _is_greek(character: str) -> bool: greek_letters = [ "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", ] return character.lower() in greek_letters
[docs] @classmethod def from_cond_distr_1(cls, cond): """ Build a bivariate copula from the conditional CDF F_{V|U=u}(v) = ∂C/∂u (u, v). """ greek_names = [ "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", ] local_dict = {name: sympy.symbols(name, real=True) for name in greek_names} sp_cond = sympy.sympify(cond, locals=local_dict, evaluate=False) # split symbols into params vs. function vars free_syms = list(sp_cond.free_symbols) param_syms = [s for s in free_syms if cls._is_greek(str(s)) and str(s) != "pi"] func_syms = [s for s in free_syms if s not in param_syms] obj = cls._from_string(2, [str(s) for s in param_syms]) # map variables robustly to (u, v) sub_map = {} for s in func_syms: if str(s) == "u": sub_map[s] = obj.u elif str(s) == "v": sub_map[s] = obj.v remaining = [s for s in func_syms if s not in sub_map] if obj.u not in sub_map.values() and remaining: sub_map[remaining.pop(0)] = obj.u if obj.v not in sub_map.values() and remaining: sub_map[remaining.pop(0)] = obj.v sp_cond = sp_cond.xreplace(sub_map) for ps in param_syms: sp_cond = sp_cond.subs(ps, obj._free_symbols[str(ps)]) # keep u and v visible to wrappers expecting both symbols if obj.u not in sp_cond.free_symbols: sp_cond = sympy.Add(sp_cond, obj.u, -obj.u, evaluate=False) if obj.v not in sp_cond.free_symbols: sp_cond = sympy.Add(sp_cond, obj.v, -obj.v, evaluate=False) # C(u,v) = ∫_0^u F_{V|U=s}(v) ds s = sympy.symbols("__int_u", real=True, nonnegative=True) cdf_expr = sympy.integrate(sp_cond.subs(obj.u, s), (s, 0, obj.u)) cdf_expr = sympy.Add(cdf_expr, obj.u**2 / 2, -(obj.u**2) / 2, evaluate=False) obj._cdf_expr = cdf_expr obj._pdf_expr = sympy.diff(cdf_expr, obj.u, obj.v) return obj
[docs] @classmethod def from_cond_distr_2(cls, cond): """ Build a bivariate copula from the conditional CDF F_{U|V=v}(u) = ∂C/∂v (u, v). """ greek_names = [ "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", ] local_dict = {name: sympy.symbols(name, real=True) for name in greek_names} sp_cond = sympy.sympify(cond, locals=local_dict, evaluate=False) free_syms = list(sp_cond.free_symbols) param_syms = [s for s in free_syms if cls._is_greek(str(s)) and str(s) != "pi"] func_syms = [s for s in free_syms if s not in param_syms] obj = cls._from_string(2, [str(s) for s in param_syms]) # map variables robustly to (u, v) sub_map = {} for s in func_syms: if str(s) == "u": sub_map[s] = obj.u elif str(s) == "v": sub_map[s] = obj.v remaining = [s for s in func_syms if s not in sub_map] if obj.u not in sub_map.values() and remaining: sub_map[remaining.pop(0)] = obj.u if obj.v not in sub_map.values() and remaining: sub_map[remaining.pop(0)] = obj.v sp_cond = sp_cond.xreplace(sub_map) for ps in param_syms: sp_cond = sp_cond.subs(ps, obj._free_symbols[str(ps)]) # keep u and v visible to wrappers expecting both symbols if obj.u not in sp_cond.free_symbols: sp_cond = sympy.Add(sp_cond, obj.u, -obj.u, evaluate=False) if obj.v not in sp_cond.free_symbols: sp_cond = sympy.Add(sp_cond, obj.v, -obj.v, evaluate=False) # C(u,v) = ∫_0^v F_{U|V=t}(u) dt t = sympy.symbols("__int_v", real=True, nonnegative=True) cdf_expr = sympy.integrate(sp_cond.subs(obj.v, t), (t, 0, obj.v)) cdf_expr = sympy.Add(cdf_expr, obj.v**2 / 2, -(obj.v**2) / 2, evaluate=False) obj._cdf_expr = cdf_expr obj._pdf_expr = sympy.diff(cdf_expr, obj.u, obj.v) return obj
[docs] def from_cdf(cdf): return CopulaBuilder.from_cdf(cdf)
[docs] def from_pdf(pdf): return CopulaBuilder.from_pdf(pdf)
[docs] def from_cond_distr_1(cond): """Build from F_{V|U=u}(v) = ∂C/∂u (u, v).""" return CopulaBuilder.from_cond_distr_1(cond)
[docs] def from_cond_distr_2(cond): """Build from F_{U|V=v}(u) = ∂C/∂v (u, v).""" return CopulaBuilder.from_cond_distr_2(cond)
if __name__ == "__main__": cond1_str = "Piecewise((v, v > 1/2), (1/2, (v <= 1/2) & (u < 2*v)), (0, True))" copula = from_cond_distr_1(cond1_str) copula.plot_cond_distr_1()