Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NLOPT as a solver #1340

Merged
merged 19 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[mypy-scipy.*,requests,pandas,compiler_gym,compiler_gym.*,gym,gym.*,gym_anm,matplotlib.*,pytest,cma,bayes_opt.*,torchvision.models,torch.*,mpl_toolkits.*,fcmaes.*,tqdm,pillow,PIL,PIL.Image,sklearn.*,pyomo.*,pyproj,IOHexperimenter.*,tensorflow,koncept.models,cv2,imquality,imquality.brisque,lpips,mixsimulator.*,networkx.*,cdt.*,pymoo,pymoo.*,bayes_optim.*,olympus.*]
ignore_missing_imports = True

[mypy-nevergrad.functions.rl.agents,torchvision,torchvision.*,nevergrad.functions.games.*,nevergrad.functions.multiobjective.pyhv,nevergrad.optimization.test_doc,,pymoo,pymoo.*,pybullet,pybullet_envs,pybulletgym,pyvirtualdisplay,aquacrop.*]
[mypy-nevergrad.functions.rl.agents,torchvision,torchvision.*,nevergrad.functions.games.*,nevergrad.functions.multiobjective.pyhv,nevergrad.optimization.test_doc,,pymoo,pymoo.*,pybullet,pybullet_envs,pybulletgym,pyvirtualdisplay,nlopt,aquacrop.*]
ignore_missing_imports = True
ignore_errors = True

Expand Down
43 changes: 41 additions & 2 deletions nevergrad/optimization/recastlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ def __init__(
"Nelder-Mead",
"COBYLA",
"SLSQP",
"NLOPT",
"Powell",
], f"Unknown method '{method}'"
self.method = method
self.random_restart = random_restart
# The following line rescales to [0, 1] if fully bounded.

if method == "CmaFmin2":
if method in ("CmaFmin2", "NLOPT"):
normalizer = p.helpers.Normalizer(self.parametrization)
if normalizer.fully_bounded:
self._normalizer = normalizer
Expand All @@ -71,7 +72,43 @@ def _optimization_function(
while remaining > 0: # try to restart if budget is not elapsed
options: tp.Dict[str, tp.Any] = {} if weakself.budget is None else {"maxiter": remaining}
# options: tp.Dict[str, tp.Any] = {} if self.budget is None else {"maxiter": remaining}
if weakself.method == "CmaFmin2":
if weakself.method == "NLOPT":
# This is NLOPT, used as in the PCSE simulator notebook.
# ( https://github.com/ajwdewit/pcse_notebooks ).
import nlopt

def nlopt_objective_function(*args):
data = np.asarray([arg for arg in args])[0]
assert len(data) == weakself.dimension, (
str(data) + " does not have length " + str(weakself.dimension)
)
if weakself._normalizer is not None:
data = weakself._normalizer.backward(np.asarray(data, dtype=np.float32))
return objective_function(data)

# Sbplx (based on Subplex) is used by default.
opt = nlopt.opt(nlopt.LN_SBPLX, weakself.dimension)
# Assign the objective function calculator
opt.set_min_objective(nlopt_objective_function)
# Set the bounds.
opt.set_lower_bounds(np.zeros(weakself.dimension))
opt.set_upper_bounds(np.ones(weakself.dimension))
# opt.set_initial_step([0.05, 0.05])
opt.set_maxeval(budget)
# Relative tolerance for convergence
opt.set_ftol_rel(1.0e-10)

# Start the optimization with the first guess
firstguess = 0.5 * np.ones(weakself.dimension)
best_x = opt.optimize(firstguess)
# print("\noptimum at TDWI: %s, SPAN: %s" % (x[0], x[1]))
# print("minimum value = ", opt.last_optimum_value())
# print("result code = ", opt.last_optimize_result())
# print("With %i function calls" % objfunc_calculator.n_calls)
if weakself._normalizer is not None:
best_x = weakself._normalizer.backward(np.asarray(best_x, dtype=np.float32))

elif weakself.method == "CmaFmin2":
import cma # import inline in order to avoid matplotlib initialization warning

def cma_objective_function(data):
Expand Down Expand Up @@ -135,6 +172,7 @@ class NonObjectOptimizer(base.ConfiguredOptimizer):
- SQP (or SLSQP): very powerful e.g. in continuous noisy optimization. It is based on
approximating the objective function by quadratic models.
- Powell
- NLOPT (https://nlopt.readthedocs.io/en/latest/; uses Sbplx, based on Subplex)
random_restart: bool
whether to restart at a random point if the optimizer converged but the budget is not entirely
spent yet (otherwise, restarts from best point)
Expand All @@ -154,6 +192,7 @@ def __init__(self, *, method: str = "Nelder-Mead", random_restart: bool = False)

NelderMead = NonObjectOptimizer(method="Nelder-Mead").set_name("NelderMead", register=True)
CmaFmin2 = NonObjectOptimizer(method="CmaFmin2").set_name("CmaFmin2", register=True)
NLOPT = NonObjectOptimizer(method="NLOPT").set_name("NLOPT", register=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

experimental variant? There are already way too many optimizer classes in optimizerlib

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NLOpt is well known and significantly different... maybe we should split optimizerlib, but we should stop moving essential things in experimental variants.
A split + cleaning of optimizerlib.py is a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not for moving essential things in experimental variants, but avoiding considering everything as essential, from a user point of view there can't be 120 essential things. And NonObjectOptimizer(method="NLOPT") already + is better documented, so way more essential.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it should be more documented, so please add a docstring

Powell = NonObjectOptimizer(method="Powell").set_name("Powell", register=True)
RPowell = NonObjectOptimizer(method="Powell", random_restart=True).set_name("RPowell", register=True)
Cobyla = NonObjectOptimizer(method="COBYLA").set_name("Cobyla", register=True)
Expand Down
1 change: 1 addition & 0 deletions nevergrad/optimization/recorded_recommendations.csv
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ NGOptBase,0.0,-0.3451057176,-0.1327329683,1.9291307781,,,,,,,,,,,,
NGOptSingle16,0.0,0.0,0.0,0.0,,,,,,,,,,,,
NGOptSingle25,0.0,0.0,0.0,0.0,,,,,,,,,,,,
NGOptSingle9,0.0,0.0,0.0,0.0,,,,,,,,,,,,
NLOPT,0.625,0.0,0.5,0.5,,,,,,,,,,,,
NaiveAnisoEMNA,1.012515477,-0.9138691467,-1.0295302074,1.2097964496,,,,,,,,,,,,
NaiveAnisoEMNATBPSA,0.002380178,-0.0558141,-0.3746306258,1.3332040355,,,,,,,,,,,,
NaiveIsoEMNA,1.012515477,-0.9138691467,-1.0295302074,1.2097964496,,,,,,,,,,,,
Expand Down
3 changes: 2 additions & 1 deletion nevergrad/optimization/test_optimizerlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def test_optimizers(name: str) -> None:
optimizer_cls.__class__(**optimizer_cls._config) == optimizer_cls
), "Similar configuration are not equal"
# some classes of optimizer are eigher slow or not good with small budgets:
nameparts = ["Many", "Chain", "BO", "Discrete"] + ["chain"] # TODO remove chain when possible
nameparts = ["Many", "Chain", "BO", "Discrete", "NLOPT"] + ["chain"] # TODO remove chain when possible
is_ngopt = inspect.isclass(optimizer_cls) and issubclass(optimizer_cls, NGOptBase) # type: ignore
verify = (
not optimizer_cls.one_shot
Expand Down Expand Up @@ -435,6 +435,7 @@ def test_bo_parametrization_and_parameters() -> None:
with pytest.warns(None) as record: # type: ignore
opt = optlib.ParametrizedBO(gp_parameters={"alpha": 1})(parametrization, budget=10)
assert not record, record.list # no warning

# parameters
# make sure underlying BO optimizer gets instantiated correctly
new_candidate = opt.parametrization.spawn_child(new_value=((True,), {}))
Expand Down
1 change: 1 addition & 0 deletions requirements/bench.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ olymp==0.0.1b0 ; sys_platform == "linux"
silence_tensorflow # for olymp
tensorflow_probability # for olymp
bayes-optim==0.2.5.5
nlopt