Source code for copul.wrapper.pickands_wrapper

# Create a wrapper class that supports both calling with t and sympy operations
import sympy as sp


[docs] class PickandsWrapper: def __init__(self, expr, t_symbol, delta_val=None): self.expr = expr self.t_symbol = t_symbol self.delta_val = delta_val # For compatibility with sympy operations self.func = expr # Store the copula class name if available self.copula_class = None if hasattr(expr, "_args") and len(expr._args) > 0: self.copula_class = getattr(expr._args[0], "__class__", None) if self.copula_class: self.copula_class = self.copula_class.__name__ def __call__(self, t=None): # Handle boundary cases first (all valid Pickands functions must satisfy A(0)=A(1)=1) if t is not None: # Convert to float if possible for easier comparison try: t_val = float(t) # Boundary cases if t_val == 0 or t_val == 1: return sp.Float(1.0) # For HueslerReiss, handle near-boundary cases if ( self.copula_class == "HueslerReiss" or True ): # Handle all copulas for safety if t_val < 1e-10: return sp.Float(1.0) if t_val > 1 - 1e-10: return sp.Float(1.0) except (TypeError, ValueError): # If we can't convert to float, proceed with symbolic substitution pass if t is not None: # If t is provided, substitute it into the expression try: result = self.expr.subs(self.t_symbol, t) # Check if the result is NaN or infinity if result.is_real and not result.is_finite: if self.copula_class == "HueslerReiss": # For HueslerReiss, we know the boundary values if t < 1e-10 or t > 1 - 1e-10: return sp.Float(1.0) return result except Exception: # If there's an error in substitution, use fallback return self._fallback_evaluation(t) return self.expr def _fallback_evaluation(self, t): """Fallback numerical evaluation for problematic cases""" # All valid Pickands functions must satisfy A(0)=A(1)=1 if float(t) == 0 or float(t) == 1: return sp.Float(1.0) # For nearly-boundary values, return values close to 1 if float(t) < 1e-10: # Close to 0 return sp.Float(1.0) if float(t) > 1 - 1e-10: # Close to 1 return sp.Float(1.0) # For other cases, standard special cases for specific copulas if self.copula_class == "HueslerReiss" and self.delta_val is not None: # HueslerReiss simplifies to independence when delta is large if float(self.delta_val) > 1e10: return sp.Float(1.0) return sp.Float(1.0) # Safest default
[docs] def evalf(self): # Convert to a float if hasattr(self.expr, "evalf"): return self.expr.evalf() return self.expr
# Add sympy compatibility methods
[docs] def subs(self, *args, **kwargs): if args and args[0] == self.t_symbol: t_val = args[1] if t_val == 0 or t_val == 1: return sp.Float(1.0) return self.expr.subs(*args, **kwargs)
def __float__(self): result = self.evalf() if hasattr(result, "is_real") and not result.is_finite: return 1.0 # Default for non-finite results return float(result)