Source code for boundlab.poly.exp

"""Exp linearizer for polytope abstract interpretation."""

import torch

from . import PolyBounds, _register_linearizer


[docs] @_register_linearizer("Exp") def exp_linearizer(ub: torch.Tensor, lb: torch.Tensor) -> PolyBounds: r"""CROWN relaxation of :math:`\exp`. Uses the tight secant line as the upper envelope and a tangent at the bound midpoint as the lower envelope, both convex-guaranteed on :math:`[\ell, u]`. Degenerate intervals (:math:`u \approx \ell`) collapse to the exact tangent at :math:`\ell`. """ degen = torch.abs(ub - lb) < 1e-12 exp_lb = torch.exp(lb) exp_ub = torch.exp(ub) # Upper envelope: secant (tight for convex exp) denom = (ub - lb).clamp(min=1e-30) upper_lam = (exp_ub - exp_lb) / denom upper_bias = exp_lb - upper_lam * lb # Lower envelope: tangent at the midpoint mid = 0.5 * (ub + lb) exp_mid = torch.exp(mid) lower_lam = exp_mid lower_bias = exp_mid * (1.0 - mid) # Degenerate case: the exact tangent at lb. exp_tangent = torch.exp(lb) upper_lam = torch.where(degen, exp_tangent, upper_lam) upper_bias = torch.where(degen, exp_tangent * (1.0 - lb), upper_bias) lower_lam = torch.where(degen, exp_tangent, lower_lam) lower_bias = torch.where(degen, exp_tangent * (1.0 - lb), lower_bias) return PolyBounds( upper_lam=upper_lam, upper_bias=upper_bias, lower_lam=lower_lam, lower_bias=lower_bias, )