Skip to content

Commit

Permalink
finish wrapping fides
Browse files Browse the repository at this point in the history
  • Loading branch information
mpetrosian committed Aug 21, 2024
1 parent 0f34d32 commit cce012c
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 51 deletions.
169 changes: 140 additions & 29 deletions src/optimagic/optimizers/fides.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""Implement the fides optimizer."""

import logging
from dataclasses import dataclass
from typing import Callable, Literal, cast

import numpy as np
from numpy.typing import NDArray

from optimagic import mark
from optimagic.config import IS_FIDES_INSTALLED
from optimagic.decorators import mark_minimizer
from optimagic.exceptions import NotInstalledError
from optimagic.optimization.algo_options import (
CONVERGENCE_FTOL_ABS,
Expand All @@ -15,40 +18,138 @@
CONVERGENCE_XTOL_ABS,
STOPPING_MAXITER,
)
from optimagic.optimization.algorithm import Algorithm, InternalOptimizeResult
from optimagic.optimization.internal_optimization_problem import (
InternalOptimizationProblem,
)
from optimagic.typing import (
AggregationLevel,
NonNegativeFloat,
PositiveFloat,
PositiveInt,
PyTree,
)

if IS_FIDES_INSTALLED:
from fides import Optimizer, hessian_approximation


@mark_minimizer(
@mark.minimizer(
name="fides",
primary_criterion_entry="value",
needs_scaling=False,
solver_type=AggregationLevel.SCALAR,
is_available=IS_FIDES_INSTALLED,
is_global=False,
needs_jac=True,
needs_hess=False,
supports_parallelism=False,
supports_bounds=True,
supports_linear_constraints=False,
supports_nonlinear_constraints=False,
disable_history=False,
)
def fides(
criterion_and_derivative,
x,
lower_bounds,
upper_bounds,
*,
hessian_update_strategy="bfgs",
convergence_ftol_abs=CONVERGENCE_FTOL_ABS,
convergence_ftol_rel=CONVERGENCE_FTOL_REL,
convergence_xtol_abs=CONVERGENCE_XTOL_ABS,
convergence_gtol_abs=CONVERGENCE_GTOL_ABS,
convergence_gtol_rel=CONVERGENCE_GTOL_REL,
stopping_maxiter=STOPPING_MAXITER,
stopping_max_seconds=np.inf,
trustregion_initial_radius=1.0,
trustregion_stepback_strategy="truncate",
trustregion_subspace_dimension="full",
trustregion_max_stepback_fraction=0.95,
trustregion_decrease_threshold=0.25,
trustregion_increase_threshold=0.75,
trustregion_decrease_factor=0.25,
trustregion_increase_factor=2.0,
):
@dataclass(frozen=True)
class Fides(Algorithm):
hessian_update_strategy: Literal[
"bfgs",
"bb",
"bg",
"dfp",
"sr1",
] = "bfgs"
convergence_ftol_abs: NonNegativeFloat = CONVERGENCE_FTOL_ABS
convergence_ftol_rel: NonNegativeFloat = CONVERGENCE_FTOL_REL
convergence_xtol_abs: NonNegativeFloat = CONVERGENCE_XTOL_ABS
convergence_gtol_abs: NonNegativeFloat = CONVERGENCE_GTOL_ABS
convergence_gtol_rel: NonNegativeFloat = CONVERGENCE_GTOL_REL
stopping_maxiter: PositiveInt = STOPPING_MAXITER
stopping_max_seconds: float = np.inf
trustregion_initial_radius: PositiveFloat = 1.0
trustregion_stepback_strategy: Literal[
"truncate",
"reflect",
"reflect_single",
"mixed",
] = "truncate"
trustregion_subspace_dimension: Literal[
"full",
"2D",
"scg",
] = "full"
trustregion_max_stepback_fraction: float = 0.95
trustregion_decrease_threshold: float = 0.25
trustregion_increase_threshold: float = 0.75
trustregion_decrease_factor: float = 0.25
trustregion_increase_factor: float = 2.0

def _solve_internal_problem(
self, problem: InternalOptimizationProblem, x0: NDArray[np.float64]
) -> InternalOptimizeResult:
res = fides_internal(
fun_and_jac=cast(
Callable[[NDArray[np.float64]], NDArray[np.float64]],
problem.fun_and_jac,
),
x=x0,
lower_bounds=problem.bounds.lower,
upper_bounds=problem.bounds.upper,
hessian_update_strategy=self.hessian_update_strategy,
convergence_ftol_abs=self.convergence_ftol_abs,
convergence_ftol_rel=self.convergence_ftol_rel,
convergence_xtol_abs=self.convergence_xtol_abs,
convergence_gtol_abs=self.convergence_gtol_abs,
convergence_gtol_rel=self.convergence_gtol_rel,
stopping_maxiter=self.stopping_maxiter,
stopping_max_seconds=self.stopping_max_seconds,
trustregion_initial_radius=self.trustregion_initial_radius,
trustregion_stepback_strategy=self.trustregion_stepback_strategy,
trustregion_subspace_dimension=self.trustregion_subspace_dimension,
trustregion_max_stepback_fraction=self.trustregion_max_stepback_fraction,
trustregion_decrease_threshold=self.trustregion_decrease_threshold,
trustregion_increase_threshold=self.trustregion_increase_threshold,
trustregion_decrease_factor=self.trustregion_decrease_factor,
trustregion_increase_factor=self.trustregion_increase_factor,
)

return res


def fides_internal(
fun_and_jac: Callable[[NDArray[np.float64]], NDArray[np.float64]],
x: NDArray[np.float64],
lower_bounds: PyTree | None,
upper_bounds: PyTree | None,
hessian_update_strategy: Literal[
"bfgs",
"bb",
"bg",
"dfp",
"sr1",
],
convergence_ftol_abs: NonNegativeFloat,
convergence_ftol_rel: NonNegativeFloat,
convergence_xtol_abs: NonNegativeFloat,
convergence_gtol_abs: NonNegativeFloat,
convergence_gtol_rel: NonNegativeFloat,
stopping_maxiter: PositiveInt,
stopping_max_seconds: float,
trustregion_initial_radius: PositiveFloat,
trustregion_stepback_strategy: Literal[
"truncate",
"reflect",
"reflect_single",
"mixed",
],
trustregion_subspace_dimension: Literal[
"full",
"2D",
"scg",
],
trustregion_max_stepback_fraction: float,
trustregion_decrease_threshold: float,
trustregion_increase_threshold: float,
trustregion_decrease_factor: float,
trustregion_increase_factor: float,
) -> InternalOptimizeResult:
"""Minimize a scalar function using the Fides Optimizer.
For details see
Expand Down Expand Up @@ -82,7 +183,7 @@ def fides(
hessian_instance = _create_hessian_updater_from_user_input(hessian_update_strategy)

opt = Optimizer(
fun=criterion_and_derivative,
fun=fun_and_jac,
lb=lower_bounds,
ub=upper_bounds,
verbose=logging.ERROR,
Expand All @@ -93,7 +194,17 @@ def fides(
)
raw_res = opt.minimize(x)
res = _process_fides_res(raw_res, opt)
return res
out = InternalOptimizeResult(
x=res["solution_x"],
fun=res["solution_criterion"],
jac=res["solution_derivative"],
hess=res["solution_hessian"],
success=res["success"],
message=res["message"],
n_iterations=res["n_iterations"],
)

return out


def _process_fides_res(raw_res, opt):
Expand Down
59 changes: 37 additions & 22 deletions tests/optimagic/optimizers/test_fides_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import pytest
from numpy.testing import assert_array_almost_equal as aaae
from optimagic.config import IS_FIDES_INSTALLED
from optimagic.optimization.optimize import minimize
from optimagic.parameters.bounds import Bounds

if IS_FIDES_INSTALLED:
from fides.hessian_approximation import FX, SR1, Broyden
from optimagic.optimizers.fides import fides
from optimagic.optimizers.fides import Fides
else:
FX = lambda: None
SR1 = lambda: None
Expand Down Expand Up @@ -40,17 +42,24 @@ def criterion_and_derivative(x):
return (x**2).sum(), 2 * x


def criterion(x):
return (x**2).sum()


@pytest.mark.skipif(not IS_FIDES_INSTALLED, reason="fides not installed.")
@pytest.mark.parametrize("algo_options", test_cases_no_contribs_needed)
def test_fides_correct_algo_options(algo_options):
res = fides(
criterion_and_derivative=criterion_and_derivative,
x=np.array([1, -5, 3]),
lower_bounds=np.array([-10, -10, -10]),
upper_bounds=np.array([10, 10, 10]),
**algo_options,
res = minimize(
fun_and_jac=criterion_and_derivative,
fun=criterion,
x0=np.array([1, -5, 3]),
bounds=Bounds(
lower=np.array([-10, -10, -10]),
upper=np.array([10, 10, 10]),
),
algorithm=Fides(**algo_options),
)
aaae(res["solution_x"], np.zeros(3), decimal=4)
aaae(res.params, np.zeros(3), decimal=4)


test_cases_needing_contribs = [
Expand All @@ -65,23 +74,29 @@ def test_fides_correct_algo_options(algo_options):
@pytest.mark.parametrize("algo_options", test_cases_needing_contribs)
def test_fides_unimplemented_algo_options(algo_options):
with pytest.raises(NotImplementedError):
fides(
criterion_and_derivative=criterion_and_derivative,
x=np.array([1, -5, 3]),
lower_bounds=np.array([-10, -10, -10]),
upper_bounds=np.array([10, 10, 10]),
**algo_options,
minimize(
fun_and_jac=criterion_and_derivative,
fun=criterion,
x0=np.array([1, -5, 3]),
bounds=Bounds(
lower=np.array([-10, -10, -10]),
upper=np.array([10, 10, 10]),
),
algorithm=Fides(**algo_options),
)


@pytest.mark.skipif(not IS_FIDES_INSTALLED, reason="fides not installed.")
def test_fides_stop_after_one_iteration():
res = fides(
criterion_and_derivative=criterion_and_derivative,
x=np.array([1, -5, 3]),
lower_bounds=np.array([-10, -10, -10]),
upper_bounds=np.array([10, 10, 10]),
stopping_maxiter=1,
res = minimize(
fun_and_jac=criterion_and_derivative,
fun=criterion,
x0=np.array([1, -5, 3]),
bounds=Bounds(
lower=np.array([-10, -10, -10]),
upper=np.array([10, 10, 10]),
),
algorithm=Fides(stopping_maxiter=1),
)
assert not res["success"]
assert res["n_iterations"] == 1
assert not res.success
assert res.n_iterations == 1

0 comments on commit cce012c

Please sign in to comment.