Skip to content

Commit

Permalink
Nevergrad solver interface changes.
Browse files Browse the repository at this point in the history
Refactored the Nevergrad solver interface to be a class. It now
functions as follows:

```
solver = NevergradGenericSolver(problem)
results = solver.solve("f_1")
```
  • Loading branch information
gialmisi committed Apr 25, 2024
1 parent 68555b4 commit 0e36c8d
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 55 deletions.
8 changes: 4 additions & 4 deletions desdeo/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"BonminOptions",
"IpoptOptions",
"CreateSolverType",
"GurobipySolver",
"NevergradGenericSolver",
"PersistentGurobipySolver",
"SolverOptions",
"SolverResults",
Expand All @@ -22,8 +24,6 @@
"add_stom_sf_nondiff",
"add_weighted_sums",
"available_nevergrad_optimizers",
"GurobipySolver",
"create_ng_generic_solver",
"create_pyomo_bonmin_solver",
"create_pyomo_ipopt_solver",
"create_pyomo_gurobi_solver",
Expand All @@ -36,13 +36,13 @@

from desdeo.tools.generics import CreateSolverType, SolverOptions, SolverResults
from desdeo.tools.gurobipy_solver_interfaces import (
PersistentGurobipySolver,
GurobipySolver,
PersistentGurobipySolver,
)
from desdeo.tools.ng_solver_interfaces import (
NevergradGenericOptions,
NevergradGenericSolver,
available_nevergrad_optimizers,
create_ng_generic_solver,
)
from desdeo.tools.pyomo_solver_interfaces import (
BonminOptions,
Expand Down
85 changes: 48 additions & 37 deletions desdeo/tools/ng_solver_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from desdeo.tools.generics import CreateSolverType, SolverResults

# forward typehints
create_ng_generic_solver: CreateSolverType
NevergradGenericSolver: CreateSolverType

available_nevergrad_optimizers = [
"NGOpt",
Expand Down Expand Up @@ -90,35 +90,46 @@ def parse_ng_results(results: dict, problem: Problem, evaluator: SympyEvaluator)
)


def create_ng_generic_solver(
problem: Problem, options: NevergradGenericOptions = _default_nevergrad_generic_options
) -> Callable[[str], SolverResults]:
"""Creates a solver that utilizes optimizations routines found in the nevergrad library.
class NevergradGenericSolver:
"""Creates a solver that utilizes optimizations routines found in the nevergrad library."""

These solvers are best utilized for black-box, gradient free optimization with
computationally expensive function calls. Utilizing multiple workers is recommended
(see `NevergradGenericOptions`) when function calls are heavily I/O bound.
def __init__(self, problem: Problem, options: NevergradGenericOptions = _default_nevergrad_generic_options):
"""Creates a solver that utilizes optimizations routines found in the nevergrad library.
See https://facebookresearch.github.io/nevergrad/getting_started.html for further information
on nevergrad and its solvers.
These solvers are best utilized for black-box, gradient free optimization with
computationally expensive function calls. Utilizing multiple workers is recommended
(see `NevergradGenericOptions`) when function calls are heavily I/O bound.
References:
Rapin, J., & Teytaud, O. (2018). Nevergrad - A gradient-free
optimization platform. GitHub.
https://GitHub.com/FacebookResearch/Nevergrad
See https://facebookresearch.github.io/nevergrad/getting_started.html for further information
on nevergrad and its solvers.
Args:
problem (Problem): the problem to be solved.
options (NgOptOptions): options to be passes to the solver. Defaults to `_default_ng_ngopt_options`.
References:
Rapin, J., & Teytaud, O. (2018). Nevergrad - A gradient-free
optimization platform. GitHub.
https://GitHub.com/FacebookResearch/Nevergrad
Returns:
Callable[[str], SolverResults]: returns a callable function that takes
as its argument one of the symbols defined for a function expression in
problem.
"""
evaluator = SympyEvaluator(problem)
Args:
problem (Problem): the problem to be solved.
options (NgOptOptions): options to be passes to the solver. Defaults to `_default_ng_ngopt_options`.
Returns:
Callable[[str], SolverResults]: returns a callable function that takes
as its argument one of the symbols defined for a function expression in
problem.
"""
self.problem = problem
self.options = options
self.evaluator = SympyEvaluator(problem)

def solver(target: str) -> SolverResults:
def solve(self, target: str) -> SolverResults:
"""Solve the problem for the given target.
Args:
target (str): the symbol of the objective function to be optimized.
Returns:
SolverResults: the results of the optimization.
"""
parametrization = ng.p.Dict(
**{
var.symbol: ng.p.Scalar(
Expand All @@ -127,23 +138,25 @@ def solver(target: str) -> SolverResults:
# initial value.
init=var.initial_value if var.initial_value is not None else (var.lowerbound + var.upperbound) / 2
).set_bounds(var.lowerbound, var.upperbound)
for var in problem.variables
for var in self.problem.variables
}
)

optimizer = ng.optimizers.registry[options.optimizer](
parametrization=parametrization, **options.model_dump(exclude="optimizer")
optimizer = ng.optimizers.registry[self.options.optimizer](
parametrization=parametrization, **self.options.model_dump(exclude="optimizer")
)

constraint_symbols = None if problem.constraints is None else [con.symbol for con in problem.constraints]
constraint_symbols = (
None if self.problem.constraints is None else [con.symbol for con in self.problem.constraints]
)

try:
if optimizer.num_workers == 1:
# single thread
recommendation = optimizer.minimize(
lambda xs, t=target: evaluator.evaluate_target(xs, t),
lambda xs, t=target: self.evaluator.evaluate_target(xs, t),
constraint_violation=[
lambda xs, t=con_t: evaluator.evaluate_target(xs, t) for con_t in constraint_symbols
lambda xs, t=con_t: self.evaluator.evaluate_target(xs, t) for con_t in constraint_symbols
]
if constraint_symbols is not None
else None,
Expand All @@ -153,25 +166,23 @@ def solver(target: str) -> SolverResults:
# multiple processors
with ThreadPoolExecutor(max_workers=optimizer.num_workers) as executor:
recommendation = optimizer.minimize(
lambda xs, t=target: evaluator.evaluate_target(xs, t),
lambda xs, t=target: self.evaluator.evaluate_target(xs, t),
constraint_violation=[
lambda xs, t=con_t: evaluator.evaluate_target(xs, t) for con_t in constraint_symbols
lambda xs, t=con_t: self.evaluator.evaluate_target(xs, t) for con_t in constraint_symbols
]
if constraint_symbols is not None
else None,
executor=executor,
batch_mode=False,
)

msg = f"Recommendation found by {options.optimizer}."
msg = f"Recommendation found by {self.options.optimizer}."
success = True

except Exception as e:
msg = f"{options.optimizer} failed. Possible reason: {e}"
msg = f"{self.options.optimizer} failed. Possible reason: {e}"
success = False

result = {"recommendation": recommendation, "message": msg, "success": success}

return parse_ng_results(result, problem, evaluator)

return solver
return parse_ng_results(result, self.problem, self.evaluator)
10 changes: 5 additions & 5 deletions tests/test_ng_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from desdeo.problem import zdt1
from desdeo.tools import (
NevergradGenericOptions,
NevergradGenericSolver,
add_asf_nondiff,
add_epsilon_constraints,
available_nevergrad_optimizers,
create_ng_generic_solver,
)


Expand All @@ -26,9 +26,9 @@ def test_ngopt_solver():
# without constraints
problem_w_sf, target = add_asf_nondiff(problem, "target", rp, reference_in_aug=True)

solver = create_ng_generic_solver(problem_w_sf, options=solver_opts)
solver = NevergradGenericSolver(problem_w_sf, options=solver_opts)

res = solver(target)
res = solver.solve(target)

assert res.success

Expand All @@ -37,8 +37,8 @@ def test_ngopt_solver():
problem, "target", {"f_1": "f_1_eps", "f_2": "f_2_eps"}, "f_1", rp
)

solver = create_ng_generic_solver(problem_w_sf, options=solver_opts)
solver = NevergradGenericSolver(problem_w_sf, options=solver_opts)

res = solver(target)
res = solver.solve(target)

assert res.success
18 changes: 9 additions & 9 deletions tests/test_scalarization.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
from desdeo.tools import (
BonminOptions,
NevergradGenericOptions,
NevergradGenericSolver,
create_pyomo_bonmin_solver,
create_scipy_minimize_solver,
create_ng_generic_solver,
)
from desdeo.tools.scalarization import (
ScalarizationError,
Expand Down Expand Up @@ -559,9 +559,9 @@ def test_nimbus_sf_nondiff_solve():

solver_options = NevergradGenericOptions(budget=250, num_workers=1, optimizer="NGOpt")

solver = create_ng_generic_solver(problem_w_sum, solver_options)
solver = NevergradGenericSolver(problem_w_sum, solver_options)

results = solver(t_sum)
results = solver.solve(t_sum)
assert results.success

xs = results.optimal_variables
Expand All @@ -582,9 +582,9 @@ def test_nimbus_sf_nondiff_solve():

solver_options = NevergradGenericOptions(budget=500, num_workers=1, optimizer="NGOpt")

solver = create_ng_generic_solver(problem_w_sf, solver_options)
solver = NevergradGenericSolver(problem_w_sf, solver_options)

results = solver(sf_target)
results = solver.solve(sf_target)

xs = results.optimal_variables
assert results.success
Expand Down Expand Up @@ -616,9 +616,9 @@ def test_stom_sf_nondiff_solve():

solver_options = NevergradGenericOptions(budget=250, num_workers=1, optimizer="NGOpt")

solver = create_ng_generic_solver(problem_w_sf, solver_options)
solver = NevergradGenericSolver(problem_w_sf, solver_options)

result = solver(target)
result = solver.solve(target)

assert result.success

Expand Down Expand Up @@ -648,9 +648,9 @@ def test_guess_sf_nondiff_solve():

solver_options = NevergradGenericOptions(budget=250, num_workers=1, optimizer="NGOpt")

solver = create_ng_generic_solver(problem_w_sf, solver_options)
solver = NevergradGenericSolver(problem_w_sf, solver_options)

result = solver(target)
result = solver.solve(target)

assert result.success

Expand Down

0 comments on commit 0e36c8d

Please sign in to comment.