From 614e34fe6519711253d824fe0c9514fe0e7c4b3b Mon Sep 17 00:00:00 2001 From: Angel Ferran Pousa Date: Tue, 13 Jun 2023 09:57:00 +0200 Subject: [PATCH 1/6] Implement Xopt Nelder-Mead generator --- optimas/generators/xopt/__init__.py | 0 optimas/generators/xopt/nelder_mead.py | 103 +++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 optimas/generators/xopt/__init__.py create mode 100644 optimas/generators/xopt/nelder_mead.py diff --git a/optimas/generators/xopt/__init__.py b/optimas/generators/xopt/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/optimas/generators/xopt/nelder_mead.py b/optimas/generators/xopt/nelder_mead.py new file mode 100644 index 00000000..96df085c --- /dev/null +++ b/optimas/generators/xopt/nelder_mead.py @@ -0,0 +1,103 @@ +from typing import List, Optional + +import numpy as np +import pandas as pd + +from xopt import VOCS +from xopt.generators import NelderMeadGenerator as XoptNelderMeadGenerator +from xopt.generators.scipy.neldermead import NelderMeadOptions + +from optimas.core import (Objective, TrialParameter, VaryingParameter, + Parameter, Evaluation, Trial) +from optimas.generators.base import Generator + + +class NelderMeadGenerator(Generator): + def __init__( + self, + varying_parameters: List[VaryingParameter], + objectives: List[Objective], + analyzed_parameters: Optional[List[Parameter]] = None, + use_cuda: Optional[bool] = False, + gpu_id: Optional[int] = 0, + dedicated_resources: Optional[bool] = True, + save_model: Optional[bool] = False, + model_save_period: Optional[int] = 5, + model_history_dir: Optional[str] = 'model_history', + custom_trial_parameters: Optional[TrialParameter] = None + ) -> None: + super().__init__( + varying_parameters=varying_parameters, + objectives=objectives, + analyzed_parameters=analyzed_parameters, + use_cuda=use_cuda, + gpu_id=gpu_id, + dedicated_resources=dedicated_resources, + save_model=save_model, + model_save_period=model_save_period, + model_history_dir=model_history_dir, + custom_trial_parameters=custom_trial_parameters + ) + self._create_xopt_generator() + + def _ask( + self, + trials: List[Trial] + ) -> List[Trial]: + n_trials = len(trials) + xopt_trials = self.xopt_gen.generate(n_trials) + if xopt_trials: + for trial, xopt_trial in zip(trials, xopt_trials): + trial.parameter_values = [ + xopt_trial[var.name] for var in self.varying_parameters] + return trials + + def _tell( + self, + trials: List[Trial] + ) -> None: + # pd.DataFrame({"x1": [0.5], "x2": [5.0], "y1": [0.5], "c1": [0.5]}) + for trial in trials: + data = {} + for oe in trial.objective_evaluations: + data[oe.parameter.name] = [oe.value] + df = pd.DataFrame(data) + self.xopt_gen.add_data(df) + + def _create_xopt_generator(self): + variables = {} + for var in self.varying_parameters: + variables[var.name] = [var.lower_bound, var.upper_bound] + objectives = {} + for objective in self.objectives: + name = objective.name + objectives[name] = 'MINIMIZE' if objective.minimize else 'MAXIMIZE' + vocs = VOCS(variables=variables, objectives=objectives) + initial_point = {} + for var in self.varying_parameters: + initial_point[var.name] = (var.lower_bound + var.upper_bound) / 2 + options = NelderMeadOptions(initial_point=initial_point) + self.xopt_gen = XoptNelderMeadGenerator(vocs=vocs, options=options) + + +if __name__ == '__main__': + + def f(x, y): + return - (x + 10 * np.cos(x)) * (y + 5 * np.cos(y)) + + var1 = VaryingParameter('x0', -50, 5) + var2 = VaryingParameter('x1', -5, 15) + obj = Objective('f', minimize=False) + + gen = NelderMeadGenerator([var1, var2], objectives=[obj]) + + for i in range(100): + trial = gen.ask(1) + if trial: + trial = trial[0] + trial_params = trial.parameters_as_dict() + y = f(trial_params['x0'], trial_params['x1']) + print(y) + ev = Evaluation(parameter=obj, value=y) + trial.complete_evaluation(ev) + gen.tell([trial]) From 3c4a4e7fc26c198a16780c74e1ab3b8be75c7112 Mon Sep 17 00:00:00 2001 From: Angel Ferran Pousa Date: Tue, 13 Jun 2023 10:00:42 +0200 Subject: [PATCH 2/6] Remove unnecessary parameters --- optimas/generators/xopt/nelder_mead.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/optimas/generators/xopt/nelder_mead.py b/optimas/generators/xopt/nelder_mead.py index 96df085c..f9a40c3b 100644 --- a/optimas/generators/xopt/nelder_mead.py +++ b/optimas/generators/xopt/nelder_mead.py @@ -7,8 +7,8 @@ from xopt.generators import NelderMeadGenerator as XoptNelderMeadGenerator from xopt.generators.scipy.neldermead import NelderMeadOptions -from optimas.core import (Objective, TrialParameter, VaryingParameter, - Parameter, Evaluation, Trial) +from optimas.core import (Objective, VaryingParameter, Parameter, Evaluation, + Trial) from optimas.generators.base import Generator @@ -18,25 +18,17 @@ def __init__( varying_parameters: List[VaryingParameter], objectives: List[Objective], analyzed_parameters: Optional[List[Parameter]] = None, - use_cuda: Optional[bool] = False, - gpu_id: Optional[int] = 0, - dedicated_resources: Optional[bool] = True, save_model: Optional[bool] = False, model_save_period: Optional[int] = 5, model_history_dir: Optional[str] = 'model_history', - custom_trial_parameters: Optional[TrialParameter] = None ) -> None: super().__init__( varying_parameters=varying_parameters, objectives=objectives, analyzed_parameters=analyzed_parameters, - use_cuda=use_cuda, - gpu_id=gpu_id, - dedicated_resources=dedicated_resources, save_model=save_model, model_save_period=model_save_period, - model_history_dir=model_history_dir, - custom_trial_parameters=custom_trial_parameters + model_history_dir=model_history_dir ) self._create_xopt_generator() From b78931f5e35cd58ff0371d2e9f9116042a7f9a82 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 19:04:55 +0000 Subject: [PATCH 3/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- optimas/generators/xopt/nelder_mead.py | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/optimas/generators/xopt/nelder_mead.py b/optimas/generators/xopt/nelder_mead.py index f9a40c3b..d2ff978f 100644 --- a/optimas/generators/xopt/nelder_mead.py +++ b/optimas/generators/xopt/nelder_mead.py @@ -7,8 +7,13 @@ from xopt.generators import NelderMeadGenerator as XoptNelderMeadGenerator from xopt.generators.scipy.neldermead import NelderMeadOptions -from optimas.core import (Objective, VaryingParameter, Parameter, Evaluation, - Trial) +from optimas.core import ( + Objective, + VaryingParameter, + Parameter, + Evaluation, + Trial, +) from optimas.generators.base import Generator @@ -20,7 +25,7 @@ def __init__( analyzed_parameters: Optional[List[Parameter]] = None, save_model: Optional[bool] = False, model_save_period: Optional[int] = 5, - model_history_dir: Optional[str] = 'model_history', + model_history_dir: Optional[str] = "model_history", ) -> None: super().__init__( varying_parameters=varying_parameters, @@ -28,26 +33,21 @@ def __init__( analyzed_parameters=analyzed_parameters, save_model=save_model, model_save_period=model_save_period, - model_history_dir=model_history_dir + model_history_dir=model_history_dir, ) self._create_xopt_generator() - def _ask( - self, - trials: List[Trial] - ) -> List[Trial]: + def _ask(self, trials: List[Trial]) -> List[Trial]: n_trials = len(trials) xopt_trials = self.xopt_gen.generate(n_trials) if xopt_trials: for trial, xopt_trial in zip(trials, xopt_trials): trial.parameter_values = [ - xopt_trial[var.name] for var in self.varying_parameters] + xopt_trial[var.name] for var in self.varying_parameters + ] return trials - def _tell( - self, - trials: List[Trial] - ) -> None: + def _tell(self, trials: List[Trial]) -> None: # pd.DataFrame({"x1": [0.5], "x2": [5.0], "y1": [0.5], "c1": [0.5]}) for trial in trials: data = {} @@ -63,7 +63,7 @@ def _create_xopt_generator(self): objectives = {} for objective in self.objectives: name = objective.name - objectives[name] = 'MINIMIZE' if objective.minimize else 'MAXIMIZE' + objectives[name] = "MINIMIZE" if objective.minimize else "MAXIMIZE" vocs = VOCS(variables=variables, objectives=objectives) initial_point = {} for var in self.varying_parameters: @@ -72,14 +72,14 @@ def _create_xopt_generator(self): self.xopt_gen = XoptNelderMeadGenerator(vocs=vocs, options=options) -if __name__ == '__main__': +if __name__ == "__main__": def f(x, y): - return - (x + 10 * np.cos(x)) * (y + 5 * np.cos(y)) + return -(x + 10 * np.cos(x)) * (y + 5 * np.cos(y)) - var1 = VaryingParameter('x0', -50, 5) - var2 = VaryingParameter('x1', -5, 15) - obj = Objective('f', minimize=False) + var1 = VaryingParameter("x0", -50, 5) + var2 = VaryingParameter("x1", -5, 15) + obj = Objective("f", minimize=False) gen = NelderMeadGenerator([var1, var2], objectives=[obj]) @@ -88,7 +88,7 @@ def f(x, y): if trial: trial = trial[0] trial_params = trial.parameters_as_dict() - y = f(trial_params['x0'], trial_params['x1']) + y = f(trial_params["x0"], trial_params["x1"]) print(y) ev = Evaluation(parameter=obj, value=y) trial.complete_evaluation(ev) From f8cacf819a8d56219547a6220eed7c2763ee0f72 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 14:26:06 +0000 Subject: [PATCH 4/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- optimas/generators/xopt/__init__.py | 2 +- optimas/generators/xopt/nelder_mead.py | 8 ++++++-- tests/test_neldermead.py | 4 +--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/optimas/generators/xopt/__init__.py b/optimas/generators/xopt/__init__.py index c60bdf89..611f86b0 100644 --- a/optimas/generators/xopt/__init__.py +++ b/optimas/generators/xopt/__init__.py @@ -1 +1 @@ -from .nelder_mead import NelderMeadGenerator \ No newline at end of file +from .nelder_mead import NelderMeadGenerator diff --git a/optimas/generators/xopt/nelder_mead.py b/optimas/generators/xopt/nelder_mead.py index fec23162..48d935ee 100644 --- a/optimas/generators/xopt/nelder_mead.py +++ b/optimas/generators/xopt/nelder_mead.py @@ -4,7 +4,9 @@ import pandas as pd from xopt import VOCS -from xopt.generators.scipy.neldermead import NelderMeadGenerator as XoptNelderMeadGenerator +from xopt.generators.scipy.neldermead import ( + NelderMeadGenerator as XoptNelderMeadGenerator, +) from optimas.core import ( Objective, @@ -67,4 +69,6 @@ def _create_xopt_generator(self): initial_point = {} for var in self.varying_parameters: initial_point[var.name] = (var.lower_bound + var.upper_bound) / 2 - self.xopt_gen = XoptNelderMeadGenerator(vocs=vocs, initial_point=initial_point) + self.xopt_gen = XoptNelderMeadGenerator( + vocs=vocs, initial_point=initial_point + ) diff --git a/tests/test_neldermead.py b/tests/test_neldermead.py index e0592215..48c4ac0c 100644 --- a/tests/test_neldermead.py +++ b/tests/test_neldermead.py @@ -33,9 +33,7 @@ def test_neldermead(): obj = Objective("f", minimize=False) # Create generator and run exploration. - gen = NelderMeadGenerator( - varying_parameters=vars, objectives=[obj] - ) + gen = NelderMeadGenerator(varying_parameters=vars, objectives=[obj]) ev = FunctionEvaluator(function=eval_func) exploration = Exploration( generator=gen, From 03e13ea8197254d882f5d2cf0c905a48bfe96dae Mon Sep 17 00:00:00 2001 From: Remi Lehe Date: Wed, 7 Feb 2024 06:26:09 -0800 Subject: [PATCH 5/6] Update for new Xopt interface --- optimas/generators/xopt/__init__.py | 1 + optimas/generators/xopt/nelder_mead.py | 29 +---------- tests/test_neldermead.py | 70 ++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 27 deletions(-) create mode 100644 tests/test_neldermead.py diff --git a/optimas/generators/xopt/__init__.py b/optimas/generators/xopt/__init__.py index e69de29b..c60bdf89 100644 --- a/optimas/generators/xopt/__init__.py +++ b/optimas/generators/xopt/__init__.py @@ -0,0 +1 @@ +from .nelder_mead import NelderMeadGenerator \ No newline at end of file diff --git a/optimas/generators/xopt/nelder_mead.py b/optimas/generators/xopt/nelder_mead.py index d2ff978f..fec23162 100644 --- a/optimas/generators/xopt/nelder_mead.py +++ b/optimas/generators/xopt/nelder_mead.py @@ -4,8 +4,7 @@ import pandas as pd from xopt import VOCS -from xopt.generators import NelderMeadGenerator as XoptNelderMeadGenerator -from xopt.generators.scipy.neldermead import NelderMeadOptions +from xopt.generators.scipy.neldermead import NelderMeadGenerator as XoptNelderMeadGenerator from optimas.core import ( Objective, @@ -68,28 +67,4 @@ def _create_xopt_generator(self): initial_point = {} for var in self.varying_parameters: initial_point[var.name] = (var.lower_bound + var.upper_bound) / 2 - options = NelderMeadOptions(initial_point=initial_point) - self.xopt_gen = XoptNelderMeadGenerator(vocs=vocs, options=options) - - -if __name__ == "__main__": - - def f(x, y): - return -(x + 10 * np.cos(x)) * (y + 5 * np.cos(y)) - - var1 = VaryingParameter("x0", -50, 5) - var2 = VaryingParameter("x1", -5, 15) - obj = Objective("f", minimize=False) - - gen = NelderMeadGenerator([var1, var2], objectives=[obj]) - - for i in range(100): - trial = gen.ask(1) - if trial: - trial = trial[0] - trial_params = trial.parameters_as_dict() - y = f(trial_params["x0"], trial_params["x1"]) - print(y) - ev = Evaluation(parameter=obj, value=y) - trial.complete_evaluation(ev) - gen.tell([trial]) + self.xopt_gen = XoptNelderMeadGenerator(vocs=vocs, initial_point=initial_point) diff --git a/tests/test_neldermead.py b/tests/test_neldermead.py new file mode 100644 index 00000000..e0592215 --- /dev/null +++ b/tests/test_neldermead.py @@ -0,0 +1,70 @@ +import numpy as np + +from optimas.explorations import Exploration +from optimas.generators.xopt import NelderMeadGenerator +from optimas.evaluators import FunctionEvaluator +from optimas.core import VaryingParameter, Objective + + +def eval_func(input_params, output_params): + """Evaluation function for single-fidelity test""" + x0 = input_params["x0"] + x1 = input_params["x1"] + result = -(x0 + 10 * np.cos(x0)) * (x1 + 5 * np.cos(x1)) + output_params["f"] = result + + +def test_neldermead(): + """Test that grid sampling generates the expected configurations.""" + + # Create varying parameters. + names = ["x0", "x1"] + lower_bounds = [-3.0, 2.0] + upper_bounds = [1.0, 5.0] + vars = [] + n_steps = [7, 15] + for name, lb, ub in zip(names, lower_bounds, upper_bounds): + vars.append(VaryingParameter(name, lb, ub)) + + # Set number of evaluations. + n_evals = np.prod(n_steps) + + # Define objective. + obj = Objective("f", minimize=False) + + # Create generator and run exploration. + gen = NelderMeadGenerator( + varying_parameters=vars, objectives=[obj] + ) + ev = FunctionEvaluator(function=eval_func) + exploration = Exploration( + generator=gen, + evaluator=ev, + max_evals=n_evals, + sim_workers=2, + exploration_dir_path="./tests_output/test_grid_sampling", + ) + exploration.run() + + # Get generated points. + h = exploration.history + h = h[h["sim_ended"]] + x0_gen = h["x0"] + x1_gen = h["x1"] + + # Get expected 1D steps along each variable. + x0_steps = np.linspace(lower_bounds[0], upper_bounds[0], n_steps[0]) + x1_steps = np.linspace(lower_bounds[1], upper_bounds[1], n_steps[1]) + + # Check that the scan along each variable is as expected. + np.testing.assert_array_equal(np.unique(x0_gen), x0_steps) + np.testing.assert_array_equal(np.unique(x1_gen), x1_steps) + + # Check that for every x0 step, the expected x1 steps are performed. + for x0_step in x0_steps: + x1_in_x0_step = x1_gen[x0_gen == x0_step] + np.testing.assert_array_equal(x1_in_x0_step, x1_steps) + + +if __name__ == "__main__": + test_neldermead() From bf16f8db0b6c673fef5d04884df3a66c753f81f6 Mon Sep 17 00:00:00 2001 From: Remi Lehe Date: Fri, 16 Feb 2024 07:50:38 -0800 Subject: [PATCH 6/6] Use only one worker --- tests/test_neldermead.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_neldermead.py b/tests/test_neldermead.py index e0592215..d2ee1ab9 100644 --- a/tests/test_neldermead.py +++ b/tests/test_neldermead.py @@ -41,8 +41,8 @@ def test_neldermead(): generator=gen, evaluator=ev, max_evals=n_evals, - sim_workers=2, - exploration_dir_path="./tests_output/test_grid_sampling", + sim_workers=1, + exploration_dir_path="./tests_output/test_neldermead", ) exploration.run()