From 39e46c8bc8fec1c9202bfc3136438b9da985ccb8 Mon Sep 17 00:00:00 2001 From: Janos Gabler Date: Sun, 17 Aug 2025 15:57:08 +0200 Subject: [PATCH 1/2] Add algorithm classes. --- src/optimini/algorithms.py | 78 ++++++++++++++++++++++++++++++++++++++ src/optimini/minimize.py | 14 ++----- src/optimini/utils.py | 16 ++++++++ 3 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 src/optimini/algorithms.py diff --git a/src/optimini/algorithms.py b/src/optimini/algorithms.py new file mode 100644 index 0000000..9f84562 --- /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, + "ftol": 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 8e19cd6..4607995 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 @@ -7,18 +6,13 @@ def minimize(fun, params, method, options=None): - """Minimize a function using a given method""" options = {} if options is None else options converter = Converter(params) history = History() - internal_fun = InternalProblem(fun, converter, history) + problem = InternalProblem(fun, converter, history) x0 = converter.flatten(params) - raw_res = scipy_minimize( - fun=internal_fun.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 From a54c39904d8f01a690af83d213e10066c4f84a75 Mon Sep 17 00:00:00 2001 From: Janos Gabler Date: Sun, 17 Aug 2025 16:52:27 +0200 Subject: [PATCH 2/2] Fix Nelder Mead --- src/optimini/algorithms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optimini/algorithms.py b/src/optimini/algorithms.py index 9f84562..cc8df19 100644 --- a/src/optimini/algorithms.py +++ b/src/optimini/algorithms.py @@ -61,7 +61,7 @@ def _solve_internal_problem( ) -> InternalResult: options = { "maxiter": self.stopping_maxiter, - "ftol": self.convergence_ftol, + "fatol": self.convergence_ftol, "adaptive": self.adaptive, }