Source code for copul.schur_order.plod_verifier

import logging
import numpy as np

log = logging.getLogger(__name__)


# --------------------------------------------------------------------------- #
#  Positive-Lower-Orthant-Dependence (PLOD) verifier
# --------------------------------------------------------------------------- #
[docs] class PLODVerifier: r""" Verifier for the Positive–Lower–Orthant–Dependence (PLOD) property. A copula :math:`C` is PLOD iff :math:`C(u,v) \ge u\,v` for all :math:`0<u,v<1`. """ def __init__(self): # nothing to configure pass # ------------------------------------------------------------------ # # Public driver # ------------------------------------------------------------------ #
[docs] def is_plod(self, copul, range_min=None, range_max=None): r""" Check whether *all* members of a one-parameter copula family (or a single fixed copula) satisfy the PLOD property. Parameters ---------- copul : BivCopula (class or instance) The copula family or a concrete copula to test. range_min, range_max : float, optional Parameter range to scan if ``copul`` is a family. Returns ------- bool ``True`` – PLOD holds for every parameter tested. ``False`` – at least one parameter violates PLOD. """ range_min = -10 if range_min is None else range_min range_max = 10 if range_max is None else range_max n_interpolate = 20 # grid on parameter axis grid = np.linspace(0.001, 0.999, 40) # grid on (u,v) # ---------- 1) No parameter → check directly ------------------ # try: param_name = str(copul.params[0]) except (AttributeError, IndexError): return self._copula_is_plod(copul, grid) # ---------- 2) Otherwise scan the parameter range ------------- # interval = copul.intervals[param_name] p_min = float(max(interval.inf, range_min)) p_max = float(min(interval.sup, range_max)) if interval.left_open: p_min += 0.01 if interval.right_open: p_max -= 0.01 params = np.linspace(p_min, p_max, n_interpolate) is_plod_final = True for p in params: C = copul(**{param_name: p}) is_plod = self._copula_is_plod(C, grid) log.debug("param %s = %.4g → PLOD %s", param_name, p, is_plod) is_plod_final &= is_plod if not is_plod_final: # stop once a counter-example is found break return is_plod_final
# ------------------------------------------------------------------ # # Core routine for a *concrete* copula instance # ------------------------------------------------------------------ # def _copula_is_plod(self, C, grid): r""" Check whether a **concrete copula instance** satisfies PLOD on the given evaluation grid. Parameters ---------- C : BivCopula Copula instance to test. grid : array-like Grid of values in (0,1) for both coordinates. Returns ------- bool ``True`` iff :math:`C(u,v)\ge u v` holds on the grid. """ tol = 1e-10 try: C_expr = C.cdf.func # SymPy expression u_sym, v_sym = C.u, C.v diff = C_expr - u_sym * v_sym # should be ≥ 0 for u in grid: for v in grid: if diff.subs({u_sym: u, v_sym: v}) < -tol: return False # ---------- numeric fallback ----------------------------------- # except Exception: cdf = C.cdf # SymPyFuncWrapper → callable for u in grid: for v in grid: if cdf(u, v) < u * v - tol: return False return True