Source code for gemseo.algos.opt.scipy_local.scipy_local

# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
# Contributors:
#    INITIAL AUTHORS - initial API and implementation and/or initial
#                           documentation
#        :author: Damien Guenot
#    OTHER AUTHORS   - MACROSCOPIC CHANGES
#         Francois Gallard : refactoring for v1, May 2016
"""The library of SciPy local optimization algorithms."""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from typing import Final

from numpy import isfinite
from numpy import real
from scipy.optimize import minimize

from gemseo.algos.design_space_utils import get_value_and_bounds
from gemseo.algos.opt.base_optimization_library import BaseOptimizationLibrary
from gemseo.algos.opt.base_optimization_library import OptimizationAlgorithmDescription
from gemseo.algos.opt.base_optimizer_settings import BaseOptimizerSettings
from gemseo.algos.opt.scipy_local.settings.base_scipy_local_settings import (
    BaseScipyLocalSettings,
)
from gemseo.algos.opt.scipy_local.settings.cobyqa import COBYQA_Settings
from gemseo.algos.opt.scipy_local.settings.lbfgsb import L_BFGS_B_Settings
from gemseo.algos.opt.scipy_local.settings.nelder_mead import NELDER_MEAD_Settings
from gemseo.algos.opt.scipy_local.settings.slsqp import SLSQP_Settings
from gemseo.algos.opt.scipy_local.settings.tnc import TNC_Settings
from gemseo.utils.compatibility.scipy import SCIPY_GREATER_THAN_1_14
from gemseo.utils.constants import C_LONG_MAX

if TYPE_CHECKING:
    from gemseo.algos.optimization_problem import OptimizationProblem


[docs] @dataclass class SciPyAlgorithmDescription(OptimizationAlgorithmDescription): """The description of the SciPy local optimization library.""" library_name: str = "SciPy Local" """The library name.""" Settings: type[BaseScipyLocalSettings] = BaseScipyLocalSettings """The option validation model for SciPy local optimization library."""
[docs] class ScipyOpt(BaseOptimizationLibrary): """The library of SciPy optimization algorithms.""" __DOC: Final[str] = "https://docs.scipy.org/doc/scipy/reference/" ALGORITHM_INFOS: ClassVar[dict[str, SciPyAlgorithmDescription]] = { "SLSQP": SciPyAlgorithmDescription( algorithm_name="SLSQP", description=( "Sequential Least-Squares Quadratic Programming (SLSQP) " "implemented in the SciPy library" ), handle_equality_constraints=True, handle_inequality_constraints=True, internal_algorithm_name="SLSQP", require_gradient=True, positive_constraints=True, website=f"{__DOC}optimize.minimize-slsqp.html", Settings=SLSQP_Settings, ), "L-BFGS-B": SciPyAlgorithmDescription( algorithm_name="L-BFGS-B", description=( "Limited-memory BFGS algorithm implemented in the SciPy library" ), internal_algorithm_name="L-BFGS-B", require_gradient=True, website=f"{__DOC}optimize.minimize-lbfgsb.html", Settings=L_BFGS_B_Settings, ), "TNC": SciPyAlgorithmDescription( algorithm_name="TNC", description=( "Truncated Newton (TNC) algorithm implemented in SciPy library" ), internal_algorithm_name="TNC", require_gradient=True, website=f"{__DOC}optimize.minimize-tnc.html", Settings=TNC_Settings, ), "NELDER-MEAD": SciPyAlgorithmDescription( algorithm_name="NELDER-MEAD", description="Nelder-Mead algorithm implemented in the SciPy library", internal_algorithm_name="Nelder-Mead", website=f"{__DOC}optimize.minimize-neldermead.html", Settings=NELDER_MEAD_Settings, ), } if SCIPY_GREATER_THAN_1_14: ALGORITHM_INFOS["COBYQA"] = SciPyAlgorithmDescription( algorithm_name="COBYQA", description=( "Derivative-free trust-region SQP method " "based on quadratic models for constrained optimization." ), internal_algorithm_name="COBYQA", handle_equality_constraints=True, handle_inequality_constraints=True, positive_constraints=True, website=f"{__DOC}optimize.minimize-cobyqa.html", Settings=COBYQA_Settings, ) def _run(self, problem: OptimizationProblem, **settings: Any) -> tuple[str, Any]: # Get the normalized bounds: x_0, l_b, u_b = get_value_and_bounds(problem.design_space, self._normalize_ds) # Replace infinite values with None: l_b = [val if isfinite(val) else None for val in l_b] u_b = [val if isfinite(val) else None for val in u_b] bounds = list(zip(l_b, u_b)) # Get constraint in SciPy format scipy_constraints = [ { "type": constraint.f_type, "fun": constraint.evaluate, "jac": constraint.jac, } for constraint in self._get_right_sign_constraints(problem) ] # Filter settings to get only the scipy.optimize.minimize ones options_ = self._filter_settings(settings, BaseOptimizerSettings) # Deactivate stopping criteria which are handled by GEMSEO tolerance = 0.0 if self._algo_name != "TNC": options_["maxiter"] = C_LONG_MAX opt_result = minimize( fun=lambda x: real(problem.objective.evaluate(x)), x0=x_0, method=self.ALGORITHM_INFOS[self._algo_name].internal_algorithm_name, jac=problem.objective.jac, bounds=bounds, constraints=scipy_constraints, options=options_, tol=tolerance, ) return opt_result.message, opt_result.status