diff --git a/src/optimini/algorithms.py b/src/optimini/algorithms.py new file mode 100644 index 0000000..cc8df19 --- /dev/null +++ b/src/optimini/algorithms.py @@ -0,0 +1,78 @@ +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray +from scipy.optimize import minimize as scipy_minimize + +from optimini.internal_problem import InternalProblem +from optimini.utils import Algorithm, InternalResult + + +@dataclass(frozen=True) +class SciPyLBFGSB(Algorithm): + convergence_ftol: float = 1e-8 + stopping_maxiter: int = 10_000 + limited_memory_length: int = 12 + # more options here ... + + def _solve_internal_problem( + self, problem: InternalProblem, x0: NDArray[np.float64] + ) -> InternalResult: + options = { + "maxcor": self.limited_memory_length, + "ftol": self.convergence_ftol, + "maxiter": self.stopping_maxiter, + } + res = scipy_minimize( + fun=problem.fun, + x0=x0, + method="L-BFGS-B", + options=options, + ) + return InternalResult(x=res.x, fun=res.fun) + + +@dataclass(frozen=True) +class SciPyCG(Algorithm): + convergence_gtol: float = 1e-8 + stopping_maxiter: int = 10_000 + # more options here ... + + def _solve_internal_problem( + self, problem: InternalProblem, x0: NDArray[np.float64] + ) -> InternalResult: + options = { + "gtol": self.convergence_gtol, + "maxiter": self.stopping_maxiter, + } + res = scipy_minimize(fun=problem.fun, x0=x0, method="CG", options=options) + return InternalResult(x=res.x, fun=res.fun) + + +@dataclass(frozen=True) +class SciPyNelderMead(Algorithm): + stopping_maxiter: int = 10_000 + convergence_ftol: float = 1e-8 + adaptive: bool = True + # more options here ... + + def _solve_internal_problem( + self, problem: InternalProblem, x0: NDArray[np.float64] + ) -> InternalResult: + options = { + "maxiter": self.stopping_maxiter, + "fatol": self.convergence_ftol, + "adaptive": self.adaptive, + } + + res = scipy_minimize( + fun=problem.fun, x0=x0, method="Nelder-Mead", options=options + ) + return InternalResult(x=res.x, fun=res.fun) + + +OPTIMIZER_REGISTRY = { + "L-BFGS-B": SciPyLBFGSB, + "CG": SciPyCG, + "Nelder-Mead": SciPyNelderMead, +} diff --git a/src/optimini/minimize.py b/src/optimini/minimize.py index 6f5d7d1..4272d93 100644 --- a/src/optimini/minimize.py +++ b/src/optimini/minimize.py @@ -1,5 +1,4 @@ -from scipy.optimize import minimize as scipy_minimize - +from optimini.algorithms import OPTIMIZER_REGISTRY from optimini.converter import Converter from optimini.history import History from optimini.internal_problem import InternalProblem @@ -13,12 +12,8 @@ def minimize(fun, params, method, options=None): history = History() problem = InternalProblem(fun, converter, history) x0 = converter.flatten(params) - raw_res = scipy_minimize( - fun=problem.fun, - x0=x0, - method=method, - options=options, - ) + algo = OPTIMIZER_REGISTRY[method](**options) + raw_res = algo._solve_internal_problem(problem, x0) res = OptimizeResult( x=converter.unflatten(raw_res.x), history=history, diff --git a/src/optimini/utils.py b/src/optimini/utils.py index f08a8d4..210d407 100644 --- a/src/optimini/utils.py +++ b/src/optimini/utils.py @@ -1,9 +1,11 @@ +from abc import ABC, abstractmethod from dataclasses import dataclass import numpy as np from numpy.typing import NDArray from optimini.history import History +from optimini.internal_problem import InternalProblem @dataclass @@ -13,3 +15,17 @@ class OptimizeResult: x: dict | NDArray[np.float64] history: History fun: float + + +@dataclass(frozen=True) +class InternalResult: + x: NDArray[np.float64] + fun: float + + +class Algorithm(ABC): + @abstractmethod + def _solve_internal_problem( + self, problem: InternalProblem, x0: NDArray[np.float64] + ) -> InternalResult: + pass