Source code for pyomo.contrib.trustregion.funnel

# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and Engineering
# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this
# software.  This software is distributed under the 3-clause BSD License.
# ____________________________________________________________________________________

# funnel.py – scalar funnel helper (no Filter list)
# --------------------------------------------------------
# Implements Hameed et al. (https://doi.org/10.1002/aic.70258) funnel logic
# Funnel is an alternative to filter globalization mechanism
# This addition lets the users to choose Funnel or Filter
# Public API (mirrors simplicity of filterMethod):
#   funnel = Funnel(phi_init, f_best_init,
#                   phi_min, kappa_f, alpha, beta, mu_s, eta)
#
#   status = funnel.classify_step(theta_k, theta_plus,
#                                 f_k, f_plus, trust_radius)
#           # returns 'f', 'theta', or 'reject'
#
#   if status == 'f':
#       funnel.accept_f(theta_plus, f_plus)
#   elif status == 'theta':
#       funnel.accept_theta(theta_plus)
#
# The class stores only two scalars (phi, f_best) and imposes the
# three Kiessling tests:
#   • switching   f_k - f⁺ ≥ μ_s (θ_k - θ⁺)
#   • Armijo      f_k - f⁺ ≥ η₁ Δ⁽ᵏ⁾          (Δ supplied by caller)
#   • θ‑shrink    θ⁺ ≤ β φᵏ
# --------------------------------------------------------

from __future__ import annotations


[docs] class Funnel: """Scalar funnel tracker for Trust‑Region Funnel (grey‑box). Parameters ---------- phi_init : initial funnel width φ⁰ (≥ θ⁰) f_best_init : first feasible objective (usually f⁰) phi_min : hard floor on φ (>0) kappa_f : shrink factor after theta‑step (0<κ_f<1) kappa_r : relax factor for theta (>1) alpha : curvature exponent (0<α<1) beta : θ‑type shrink factor (0<β<1) mu_s : switching parameter δ (small, e.g.1e‑2) eta : Armijo parameter (0<η<1) """ # -----------------------------------------------------
[docs] def __init__( self, phi_init: float, f_best_init: float, phi_min: float, kappa_f: float, kappa_r: float, alpha: float, beta: float, mu_s: float, eta: float, ): self.phi = max(phi_min, phi_init) self.f_best = f_best_init # store parameters self.phi_min = phi_min self.kappa_f = kappa_f self.kappa_r = kappa_r self.alpha = alpha self.beta = beta self.mu_s = mu_s self.eta = eta
# ----------------------------------------------------- # Helper tests (all scalar, no surrogates required) # ----------------------------------------------------- def _inside_funnel(self, theta_new: float) -> bool: return theta_new <= self.phi def _switching(self, f_old: float, f_new: float, theta_old: float) -> bool: return (f_old - f_new) >= self.mu_s * ((theta_old) ** 2) def _armijo(self, f_old: float, f_new: float, delta: float) -> bool: # actual reduction ≥ η₁ Δ (trust‑region radius used as scale) return (f_old - f_new) >= self.eta * delta def _theta_shrink(self, theta_new: float) -> bool: return theta_new <= self.beta * self.phi # ----------------------------------------------------- # Public classifier # -----------------------------------------------------
[docs] def classify_step( self, theta_old: float, theta_new: float, f_old: float, f_new: float, delta: float, ) -> str: """Return 'f', 'theta', or 'reject' for the trial point.""" # theta, f and reject steps if self._inside_funnel(theta_new): # candidate f‑step → need Armijo if self._switching(f_old, f_new, theta_old): return 'f' if self._armijo(f_old, f_new, delta) else 'reject' # else candidate θ‑step → need θ‑shrink return 'theta' if self._theta_shrink(theta_new) else 'reject' # Outside funnel: allow relaxed theta step if ( self._switching(f_old, f_new, theta_old) and theta_new <= self.kappa_r * self.phi ): return 'theta-relax' return 'reject'
# ----------------------------------------------------- # Updates after acceptance # -----------------------------------------------------
[docs] def accept_f(self, theta_new: float, f_new: float): """Call after accepting an f‑type step.""" if f_new < self.f_best: self.f_best = f_new
[docs] def accept_theta(self, theta_new: float): """Call after accepting a θ‑type step.""" kf = self.kappa_f # gentle convex combo shrink self.phi = max(self.phi_min, (1 - kf) * theta_new + kf * self.phi)