diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 8e1c8124f36..17bd022233f 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -105,6 +105,18 @@ class Hull_Reformulation(GDP_to_MIP_Transformation): 'LeeGrossmann', or 'GrossmannLee' EPS : float The value to use for epsilon [default: 1e-4] + eigenvalue_tolerance : float + Numerical tolerance for eigenvalue-based positive/negative + semi-definite checks when using the exact hull reformulation for + quadratic constraints (``exact_hull_quadratic=True``). An + eigenvalue :math:`\lambda` is treated as non-negative if + :math:`\lambda >= -\text{eigenvalue_tolerance}` and as + non-positive if :math:`\lambda <= \text{eigenvalue_tolerance}` + (i.e., eigenvalues in + ``[-eigenvalue_tolerance, eigenvalue_tolerance]`` are treated as + zero). Increasing this value makes the convexity check more + permissive; decreasing it makes it more conservative. + [default: 1e-10] targets : block, disjunction, or list of those types The targets to transform. This can be a block, disjunction, or a list of blocks and Disjunctions [default: the instance] @@ -184,6 +196,30 @@ class Hull_Reformulation(GDP_to_MIP_Transformation): description="Epsilon value to use in perspective function", ), ) + CONFIG.declare( + 'eigenvalue_tolerance', + cfg.ConfigValue( + default=1e-10, + domain=cfg.NonNegativeFloat, + description="Numerical tolerance for eigenvalue-based PSD/NSD checks " + "in exact hull quadratic reformulations", + doc=""" + Numerical tolerance used when determining positive semi-definiteness + (PSD) or negative semi-definiteness (NSD) of the Hessian matrix Q in + the exact hull reformulation for quadratic constraints + (``exact_hull_quadratic=True``). + + An eigenvalue ``lam`` is treated as non-negative if + ``lam >= -eigenvalue_tolerance``, and non-positive if + ``lam <= eigenvalue_tolerance``. Increasing this value makes the + convexity classification more permissive (i.e., a wider band around + zero is treated as numerically zero, so more eigenvalues are accepted + as PSD/NSD); decreasing it makes the check more conservative (i.e., + eigenvalues must be further from zero). For ill-conditioned Q matrices + a larger tolerance may be appropriate. + """, + ), + ) CONFIG.declare( 'assume_fixed_vars_permanent', cfg.ConfigValue( @@ -976,7 +1012,7 @@ def _build_exact_quadratic_hull( Q[idx_i, idx_j] += 0.5 * coef Q[idx_j, idx_i] += 0.5 * coef - numerical_tolerance = 1e-10 + numerical_tolerance = self._config.eigenvalue_tolerance eigenvalues, _ = np.linalg.eigh(Q) Q_is_psd = not np.any(eigenvalues < -numerical_tolerance) Q_is_nsd = not np.any(eigenvalues > numerical_tolerance)