From 985f611f4eb7fe10a10b42b1844ca13f2afa7b66 Mon Sep 17 00:00:00 2001 From: shudson Date: Fri, 19 Apr 2024 12:45:22 -0500 Subject: [PATCH 01/36] Point at libEnsemble asktell branch --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8a6af25c..9feb0d75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ 'Programming Language :: Python :: 3.11', ] dependencies = [ - 'libensemble >= 1.2', + 'libensemble @ git+https://github.com/Libensemble/libensemble@feature/asktell_gens', 'jinja2', 'pandas', 'mpi4py', From af48c3ca24baed7c335843eaf6410b48b2f0cf6d Mon Sep 17 00:00:00 2001 From: shudson Date: Fri, 19 Apr 2024 14:13:46 -0500 Subject: [PATCH 02/36] Add ability to call a libE ask/tell generator --- optimas/gen_functions.py | 9 +++- optimas/generators/__init__.py | 2 + optimas/generators/base.py | 10 +++- optimas/generators/libE_wrapper.py | 78 ++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 optimas/generators/libE_wrapper.py diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index a90d9b41..1d7ca8dd 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -48,6 +48,10 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): # Get generator, objectives, and parameters to analyze. generator = gen_specs["user"]["generator"] + + if hasattr(generator, 'libe_gen_class'): + generator.init_libe_gen(H, persis_info, gen_specs, libE_info) + objectives = generator.objectives analyzed_parameters = generator.analyzed_parameters @@ -110,7 +114,10 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): ev = Evaluation(parameter=par, value=y) trial.complete_evaluation(ev) # Register trial with unknown SEM - generator.tell([trial]) + if hasattr(generator, 'libe_gen_class'): + generator.tell([trial], libE_calc_in=calc_in) + else: + generator.tell([trial]) # Set the number of points to generate to that number: number_of_gen_points = min(n + n_failed_gens, max_evals - n_gens) n_failed_gens = 0 diff --git a/optimas/generators/__init__.py b/optimas/generators/__init__.py index 237fb921..2715b87d 100644 --- a/optimas/generators/__init__.py +++ b/optimas/generators/__init__.py @@ -22,6 +22,7 @@ from .grid_sampling import GridSamplingGenerator from .line_sampling import LineSamplingGenerator from .random_sampling import RandomSamplingGenerator +from .libE_wrapper import libEWrapper __all__ = [ @@ -32,4 +33,5 @@ "GridSamplingGenerator", "LineSamplingGenerator", "RandomSamplingGenerator", + "libEWrapper", ] diff --git a/optimas/generators/base.py b/optimas/generators/base.py index fe62f2f6..9c21c0cf 100644 --- a/optimas/generators/base.py +++ b/optimas/generators/base.py @@ -237,7 +237,10 @@ def ask(self, n_trials: int) -> List[Trial]: return trials def tell( - self, trials: List[Trial], allow_saving_model: Optional[bool] = True + self, + trials: List[Trial], + allow_saving_model: Optional[bool] = True, + libE_calc_in: Optional[np.typing.NDArray] = None, ) -> None: """Give trials back to generator once they have been evaluated. @@ -253,7 +256,10 @@ def tell( for trial in trials: if trial not in self._given_trials: self._add_external_evaluated_trial(trial) - self._tell(trials) + if libE_calc_in is not None: + self._tell(trials, libE_calc_in) + else: + self._tell(trials) for trial in trials: if not trial.failed: log_msg = "Completed trial {} with objective(s) {}".format( diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py new file mode 100644 index 00000000..0673b92c --- /dev/null +++ b/optimas/generators/libE_wrapper.py @@ -0,0 +1,78 @@ +from copy import deepcopy +import numpy as np +from typing import List, Optional + +from optimas.core import ( + Objective, + Trial, + VaryingParameter, + Parameter, + TrialParameter, +) +from .base import Generator + + +class libEWrapper(Generator): + def __init__( + self, + varying_parameters: List[VaryingParameter], + objectives: List[Objective], + constraints: Optional[List[Parameter]] = None, + analyzed_parameters: Optional[List[Parameter]] = None, + use_cuda: Optional[bool] = False, + gpu_id: Optional[int] = 0, + dedicated_resources: Optional[bool] = False, + save_model: Optional[bool] = False, + model_save_period: Optional[int] = 5, + model_history_dir: Optional[str] = "model_history", + custom_trial_parameters: Optional[List[TrialParameter]] = None, + allow_fixed_parameters: Optional[bool] = False, + allow_updating_parameters: Optional[bool] = False, + libe_gen_class=None, # Type hint - add base class for ask/tell gens + ) -> None: + super().__init__( + varying_parameters=varying_parameters, + objectives=objectives, + constraints=constraints, + 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, + allow_fixed_parameters=allow_fixed_parameters, + allow_updating_parameters=allow_updating_parameters, + ) + self.libe_gen_class = libe_gen_class + + def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): + n = len(self.varying_parameters) + gen_specs = deepcopy(gen_specs_in) + gen_specs["out"] = [("x", float, (n,))] + gen_specs["user"]["lb"] = np.zeros(n) + gen_specs["user"]["ub"] = np.zeros(n) + for i, vp in enumerate(self.varying_parameters): + gen_specs["user"]["lb"][i] = vp.lower_bound + gen_specs["user"]["ub"][i] = vp.upper_bound + self.libe_gen = self.libe_gen_class( + H, persis_info, gen_specs, libE_info + ) + + def _ask(self, trials: List[Trial]) -> List[Trial]: + """Fill in the parameter values of the requested trials.""" + n_trials = len(trials) + gen_out = self.libe_gen.ask(n_trials) + + for i, trial in enumerate(trials): + # Extract the 'x' field from gen_out[i] directly + x_values = gen_out[i]["x"] + trial.parameter_values = x_values + + return trials + + def _tell( + self, trials: List[Trial], libE_calc_in: np.typing.NDArray + ) -> None: + self.libe_gen.tell(libE_calc_in) From 707b7cb4b2ff779e0597744969f581fcd88d28f5 Mon Sep 17 00:00:00 2001 From: shudson Date: Fri, 19 Apr 2024 14:15:19 -0500 Subject: [PATCH 03/36] Add example that calls libE RandSample generator --- examples/dummy_random_libEgen/run_example.py | 74 +++++++++++++++++++ .../template_simulation_script.py | 13 ++++ 2 files changed, 87 insertions(+) create mode 100644 examples/dummy_random_libEgen/run_example.py create mode 100644 examples/dummy_random_libEgen/template_simulation_script.py diff --git a/examples/dummy_random_libEgen/run_example.py b/examples/dummy_random_libEgen/run_example.py new file mode 100644 index 00000000..306bf129 --- /dev/null +++ b/examples/dummy_random_libEgen/run_example.py @@ -0,0 +1,74 @@ +"""Basic example of parallel random sampling with simulations.""" + +from libensemble.gen_funcs.persistent_sampling import RandSample +from optimas.core import VaryingParameter, Objective +# from optimas.generators import RandomSamplingGenerator +from optimas.generators import libEWrapper +from optimas.evaluators import TemplateEvaluator +from optimas.explorations import Exploration + + +def analyze_simulation(simulation_directory, output_params): + """Analyze the simulation output. + + This method analyzes the output generated by the simulation to + obtain the value of the optimization objective and other analyzed + parameters, if specified. The value of these parameters has to be + given to the `output_params` dictionary. + + Parameters + ---------- + simulation_directory : str + Path to the simulation folder where the output was generated. + output_params : dict + Dictionary where the value of the objectives and analyzed parameters + will be stored. There is one entry per parameter, where the key + is the name of the parameter given by the user. + + Returns + ------- + dict + The `output_params` dictionary with the results from the analysis. + + """ + # Read back result from file + with open("result.txt") as f: + result = float(f.read()) + # Fill in output parameters. + output_params["f"] = result + return output_params + + +# Create varying parameters and objectives. +var_1 = VaryingParameter("x0", 0.0, 15.0) +var_2 = VaryingParameter("x1", 0.0, 15.0) +obj = Objective("f") + + +# Create generator. +# gen = RandomSamplingGenerator( +# varying_parameters=[var_1, var_2], objectives=[obj], distribution="normal" +# ) + +gen = libEWrapper( + varying_parameters=[var_1, var_2], objectives=[obj], + libe_gen_class=RandSample, +) + +# Create evaluator. +ev = TemplateEvaluator( + sim_template="template_simulation_script.py", + analysis_func=analyze_simulation, +) + + +# Create exploration. +exp = Exploration( + generator=gen, evaluator=ev, max_evals=10, sim_workers=4, run_async=True +) + + +# To safely perform exploration, run it in the block below (this is needed +# for some flavours of multiprocessing, namely spawn and forkserver) +if __name__ == "__main__": + exp.run() diff --git a/examples/dummy_random_libEgen/template_simulation_script.py b/examples/dummy_random_libEgen/template_simulation_script.py new file mode 100644 index 00000000..760286cc --- /dev/null +++ b/examples/dummy_random_libEgen/template_simulation_script.py @@ -0,0 +1,13 @@ +"""Simple template script used for demonstration. + +The script evaluates an analytical expression and stores the results in a +`result.txt` file that is later read by the analysis function. +""" + +import numpy as np + +# 2D function with multiple minima +result = -({{x0}} + 10 * np.cos({{x0}})) * ({{x1}} + 5 * np.cos({{x1}})) + +with open("result.txt", "w") as f: + f.write("%f" % result) From dd62c0688cddc18afcc390961690f74d60b991b0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 19:18:16 +0000 Subject: [PATCH 04/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/dummy_random_libEgen/run_example.py | 4 +++- optimas/gen_functions.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/dummy_random_libEgen/run_example.py b/examples/dummy_random_libEgen/run_example.py index 306bf129..8c54ecf2 100644 --- a/examples/dummy_random_libEgen/run_example.py +++ b/examples/dummy_random_libEgen/run_example.py @@ -2,6 +2,7 @@ from libensemble.gen_funcs.persistent_sampling import RandSample from optimas.core import VaryingParameter, Objective + # from optimas.generators import RandomSamplingGenerator from optimas.generators import libEWrapper from optimas.evaluators import TemplateEvaluator @@ -51,7 +52,8 @@ def analyze_simulation(simulation_directory, output_params): # ) gen = libEWrapper( - varying_parameters=[var_1, var_2], objectives=[obj], + varying_parameters=[var_1, var_2], + objectives=[obj], libe_gen_class=RandSample, ) diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index 1d7ca8dd..ac03daae 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -49,7 +49,7 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): # Get generator, objectives, and parameters to analyze. generator = gen_specs["user"]["generator"] - if hasattr(generator, 'libe_gen_class'): + if hasattr(generator, "libe_gen_class"): generator.init_libe_gen(H, persis_info, gen_specs, libE_info) objectives = generator.objectives @@ -114,7 +114,7 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): ev = Evaluation(parameter=par, value=y) trial.complete_evaluation(ev) # Register trial with unknown SEM - if hasattr(generator, 'libe_gen_class'): + if hasattr(generator, "libe_gen_class"): generator.tell([trial], libE_calc_in=calc_in) else: generator.tell([trial]) From d5a1d4b6e76d15ec12237c197c1395fbfa401ecb Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 9 May 2024 13:20:46 -0500 Subject: [PATCH 05/36] adjustments to combine aposmm-settings and user-settings, plus call setup to start the thread --- optimas/gen_functions.py | 2 ++ optimas/generators/libE_wrapper.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index 5050877b..50f7f9ec 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -124,4 +124,6 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): else: number_of_gen_points = 0 + if hasattr(generator, "libe_gen_class"): + return generator.final_tell(libE_calc_in=calc_in) return H_o, persis_info, FINISHED_PERSISTENT_GEN_TAG diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 0673b92c..474eed3f 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -2,6 +2,8 @@ import numpy as np from typing import List, Optional +from libensemble.generators import LibEnsembleGenInterfacer + from optimas.core import ( Objective, Trial, @@ -53,12 +55,17 @@ def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): gen_specs["out"] = [("x", float, (n,))] gen_specs["user"]["lb"] = np.zeros(n) gen_specs["user"]["ub"] = np.zeros(n) + import ipdb; ipdb.set_trace() for i, vp in enumerate(self.varying_parameters): gen_specs["user"]["lb"][i] = vp.lower_bound gen_specs["user"]["ub"][i] = vp.upper_bound + if hasattr(self, "user_settings"): + gen_specs["user"] = {**gen_specs["user"], **self.user_settings} self.libe_gen = self.libe_gen_class( - H, persis_info, gen_specs, libE_info + gen_specs, H, persis_info, libE_info ) + if issubclass(self.libe_gen_class, LibEnsembleGenInterfacer): + self.libe_gen.setup() # start background thread def _ask(self, trials: List[Trial]) -> List[Trial]: """Fill in the parameter values of the requested trials.""" @@ -76,3 +83,6 @@ def _tell( self, trials: List[Trial], libE_calc_in: np.typing.NDArray ) -> None: self.libe_gen.tell(libE_calc_in) + + def final_tell(self, libE_calc_in: np.typing.NDArray): + return self.libe_gen.final_tell(libE_calc_in) \ No newline at end of file From 18c1bada5f7bff02cbb482f92d17761f459e8772 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 10 May 2024 15:09:20 -0500 Subject: [PATCH 06/36] pass in a parameterized APOSMM instance --- optimas/generators/libE_wrapper.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 474eed3f..5188e215 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -31,6 +31,7 @@ def __init__( allow_fixed_parameters: Optional[bool] = False, allow_updating_parameters: Optional[bool] = False, libe_gen_class=None, # Type hint - add base class for ask/tell gens + libe_gen_instance=None, ) -> None: super().__init__( varying_parameters=varying_parameters, @@ -48,6 +49,7 @@ def __init__( allow_updating_parameters=allow_updating_parameters, ) self.libe_gen_class = libe_gen_class + self.libe_gen_instance = libe_gen_instance def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): n = len(self.varying_parameters) @@ -55,17 +57,19 @@ def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): gen_specs["out"] = [("x", float, (n,))] gen_specs["user"]["lb"] = np.zeros(n) gen_specs["user"]["ub"] = np.zeros(n) - import ipdb; ipdb.set_trace() for i, vp in enumerate(self.varying_parameters): gen_specs["user"]["lb"][i] = vp.lower_bound gen_specs["user"]["ub"][i] = vp.upper_bound - if hasattr(self, "user_settings"): - gen_specs["user"] = {**gen_specs["user"], **self.user_settings} - self.libe_gen = self.libe_gen_class( - gen_specs, H, persis_info, libE_info - ) - if issubclass(self.libe_gen_class, LibEnsembleGenInterfacer): - self.libe_gen.setup() # start background thread + if self.libe_gen_class is not None: + self.libe_gen = self.libe_gen_class( + gen_specs, H, persis_info, libE_info + ) + elif self.libe_gen_instance is not None: + if isinstance(self.libe_gen_instance, LibEnsembleGenInterfacer): + self.libe_gen_instance.setup() # start background thread + self.libe_gen = self.libe_gen_instance + else: + raise ValueError("libe_gen_class or libe_gen_instance must be set") def _ask(self, trials: List[Trial]) -> List[Trial]: """Fill in the parameter values of the requested trials.""" From 1d10b02958dc5bc76e6236618f7079afe075561c Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 10 May 2024 16:45:21 -0500 Subject: [PATCH 07/36] only pass in necessary fields to libE_gen (??) --- optimas/generators/libE_wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 5188e215..86fd9862 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -86,7 +86,7 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: def _tell( self, trials: List[Trial], libE_calc_in: np.typing.NDArray ) -> None: - self.libe_gen.tell(libE_calc_in) + self.libe_gen.tell(libE_calc_in[["sim_id", "f"]]) def final_tell(self, libE_calc_in: np.typing.NDArray): - return self.libe_gen.final_tell(libE_calc_in) \ No newline at end of file + return self.libe_gen.final_tell(libE_calc_in[["sim_id", "f"]]) \ No newline at end of file From e887f06b8c6557e5cf3e5b06632d3c40da300de1 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 10 May 2024 16:50:10 -0500 Subject: [PATCH 08/36] add dummy_aposmm_libE_gen example --- examples/dummy_aposmm_libE_gen/run_example.py | 87 +++++++++++++++++++ .../template_simulation_script.py | 13 +++ 2 files changed, 100 insertions(+) create mode 100644 examples/dummy_aposmm_libE_gen/run_example.py create mode 100644 examples/dummy_aposmm_libE_gen/template_simulation_script.py diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py new file mode 100644 index 00000000..7acf8706 --- /dev/null +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -0,0 +1,87 @@ +"""Basic example of parallel random sampling with simulations.""" + +from math import gamma, pi, sqrt +import numpy as np +from libensemble.generators import APOSMM +from optimas.core import VaryingParameter, Objective + +# from optimas.generators import RandomSamplingGenerator +from optimas.generators import libEWrapper +from optimas.evaluators import TemplateEvaluator +from optimas.explorations import Exploration + +from multiprocessing import set_start_method +set_start_method("fork", force=True) + +def analyze_simulation(simulation_directory, output_params): + """Analyze the simulation output. + + This method analyzes the output generated by the simulation to + obtain the value of the optimization objective and other analyzed + parameters, if specified. The value of these parameters has to be + given to the `output_params` dictionary. + + Parameters + ---------- + simulation_directory : str + Path to the simulation folder where the output was generated. + output_params : dict + Dictionary where the value of the objectives and analyzed parameters + will be stored. There is one entry per parameter, where the key + is the name of the parameter given by the user. + + Returns + ------- + dict + The `output_params` dictionary with the results from the analysis. + + """ + # Read back result from file + with open("result.txt") as f: + result = float(f.read()) + # Fill in output parameters. + output_params["f"] = result + return output_params + + +# Create varying parameters and objectives. +var_1 = VaryingParameter("x0", -3.0, -2.0) +var_2 = VaryingParameter("x1", 3, 2.0) +obj = Objective("f") + +aposmm = APOSMM( + initial_sample_size = 100, + localopt_method = "LN_BOBYQA", + rk_const = 0.5 * ((gamma(2) * 5) ** 0.5) / sqrt(pi), + xtol_abs = 1e-6, + ftol_abs = 1e-6, + dist_to_bound_multiple = 0.5, + max_active_runs = 4, # refers to APOSMM's simul local optimization runs + lb = np.array([-3, -2]), # potentially matches the VaryingParameters + ub = np.array([3, 2]), +) + +gen = libEWrapper( + varying_parameters=[var_1, var_2], + objectives=[obj], + libe_gen_instance=aposmm, +) + +# Create evaluator. +ev = TemplateEvaluator( + sim_template="template_simulation_script.py", + analysis_func=analyze_simulation, +) + + +# Create exploration. +exp = Exploration( + generator=gen, evaluator=ev, max_evals=500, sim_workers=4, run_async=True +) + + +# To safely perform exploration, run it in the block below (this is needed +# for some flavours of multiprocessing, namely spawn and forkserver) +if __name__ == "__main__": + exp.run() + diff --git a/examples/dummy_aposmm_libE_gen/template_simulation_script.py b/examples/dummy_aposmm_libE_gen/template_simulation_script.py new file mode 100644 index 00000000..760286cc --- /dev/null +++ b/examples/dummy_aposmm_libE_gen/template_simulation_script.py @@ -0,0 +1,13 @@ +"""Simple template script used for demonstration. + +The script evaluates an analytical expression and stores the results in a +`result.txt` file that is later read by the analysis function. +""" + +import numpy as np + +# 2D function with multiple minima +result = -({{x0}} + 10 * np.cos({{x0}})) * ({{x1}} + 5 * np.cos({{x1}})) + +with open("result.txt", "w") as f: + f.write("%f" % result) From 1b9b966e98eaa3dc6161ebd0b2bd2e6376a2f9b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 10 May 2024 21:51:35 +0000 Subject: [PATCH 09/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/dummy_aposmm_libE_gen/run_example.py | 21 ++++++++++--------- optimas/generators/libE_wrapper.py | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 7acf8706..c648a3bd 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -11,8 +11,10 @@ from optimas.explorations import Exploration from multiprocessing import set_start_method + set_start_method("fork", force=True) + def analyze_simulation(simulation_directory, output_params): """Analyze the simulation output. @@ -50,15 +52,15 @@ def analyze_simulation(simulation_directory, output_params): obj = Objective("f") aposmm = APOSMM( - initial_sample_size = 100, - localopt_method = "LN_BOBYQA", - rk_const = 0.5 * ((gamma(2) * 5) ** 0.5) / sqrt(pi), - xtol_abs = 1e-6, - ftol_abs = 1e-6, - dist_to_bound_multiple = 0.5, - max_active_runs = 4, # refers to APOSMM's simul local optimization runs - lb = np.array([-3, -2]), # potentially matches the VaryingParameters - ub = np.array([3, 2]), + initial_sample_size=100, + localopt_method="LN_BOBYQA", + rk_const=0.5 * ((gamma(2) * 5) ** 0.5) / sqrt(pi), + xtol_abs=1e-6, + ftol_abs=1e-6, + dist_to_bound_multiple=0.5, + max_active_runs=4, # refers to APOSMM's simul local optimization runs + lb=np.array([-3, -2]), # potentially matches the VaryingParameters + ub=np.array([3, 2]), ) gen = libEWrapper( @@ -84,4 +86,3 @@ def analyze_simulation(simulation_directory, output_params): # for some flavours of multiprocessing, namely spawn and forkserver) if __name__ == "__main__": exp.run() - diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 86fd9862..7766652c 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -89,4 +89,4 @@ def _tell( self.libe_gen.tell(libE_calc_in[["sim_id", "f"]]) def final_tell(self, libE_calc_in: np.typing.NDArray): - return self.libe_gen.final_tell(libE_calc_in[["sim_id", "f"]]) \ No newline at end of file + return self.libe_gen.final_tell(libE_calc_in[["sim_id", "f"]]) From 838d0d1a0cc4f857c2ea3d8eb92eb6869be5cee8 Mon Sep 17 00:00:00 2001 From: jlnav Date: Mon, 13 May 2024 16:50:31 -0500 Subject: [PATCH 10/36] debugging, replacing objective with 6hc, noticing that optimas tries overwriting x with new f values? probably a bug on my end --- examples/dummy_aposmm_libE_gen/run_example.py | 4 ++-- .../template_simulation_script.py | 11 ++++++++++- optimas/gen_functions.py | 2 +- optimas/generators/libE_wrapper.py | 11 ++++++++++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 7acf8706..1b775bac 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -46,7 +46,7 @@ def analyze_simulation(simulation_directory, output_params): # Create varying parameters and objectives. var_1 = VaryingParameter("x0", -3.0, -2.0) -var_2 = VaryingParameter("x1", 3, 2.0) +var_2 = VaryingParameter("x1", 3.0, 2.0) obj = Objective("f") aposmm = APOSMM( @@ -56,7 +56,7 @@ def analyze_simulation(simulation_directory, output_params): xtol_abs = 1e-6, ftol_abs = 1e-6, dist_to_bound_multiple = 0.5, - max_active_runs = 4, # refers to APOSMM's simul local optimization runs + max_active_runs = 6, # refers to APOSMM's simul local optimization runs lb = np.array([-3, -2]), # potentially matches the VaryingParameters ub = np.array([3, 2]), ) diff --git a/examples/dummy_aposmm_libE_gen/template_simulation_script.py b/examples/dummy_aposmm_libE_gen/template_simulation_script.py index 760286cc..816ee617 100644 --- a/examples/dummy_aposmm_libE_gen/template_simulation_script.py +++ b/examples/dummy_aposmm_libE_gen/template_simulation_script.py @@ -7,7 +7,16 @@ import numpy as np # 2D function with multiple minima -result = -({{x0}} + 10 * np.cos({{x0}})) * ({{x1}} + 5 * np.cos({{x1}})) +# result = -({{x0}} + 10 * np.cos({{x0}})) * ({{x1}} + 5 * np.cos({{x1}})) + +x1 = {{x0}} +x2 = {{x1}} + +term1 = (4 - 2.1 * x1**2 + (x1**4) / 3) * x1**2 +term2 = x1 * x2 +term3 = (-4 + 4 * x2**2) * x2**2 + +result = term1 + term2 + term3 with open("result.txt", "w") as f: f.write("%f" % result) diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index 50f7f9ec..08e65bb3 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -115,7 +115,7 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): trial.complete_evaluation(ev) # Register trial with unknown SEM if hasattr(generator, "libe_gen_class"): - generator.tell([trial], libE_calc_in=calc_in) + generator.tell([trial], libE_calc_in=calc_in[i]) else: generator.tell([trial]) # Set the number of points to generate to that number: diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 86fd9862..ae99bd14 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -50,6 +50,7 @@ def __init__( ) self.libe_gen_class = libe_gen_class self.libe_gen_instance = libe_gen_instance + self.given_back_idxs = [] def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): n = len(self.varying_parameters) @@ -86,7 +87,15 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: def _tell( self, trials: List[Trial], libE_calc_in: np.typing.NDArray ) -> None: - self.libe_gen.tell(libE_calc_in[["sim_id", "f"]]) + if hasattr(self.libe_gen, "create_results_array"): + new_array = self.libe_gen.create_results_array() + new_array["f"] = libE_calc_in["f"] + libE_calc_in = new_array + if libE_calc_in["sim_id"] not in self.given_back_idxs: + self.libe_gen.tell(libE_calc_in) + self.given_back_idxs.append(libE_calc_in["sim_id"]) + else: + print("Got back another array with the same ID as a previous?") def final_tell(self, libE_calc_in: np.typing.NDArray): return self.libe_gen.final_tell(libE_calc_in[["sim_id", "f"]]) \ No newline at end of file From 09bf72d48be006aa70ad0a970389d5ab24194efd Mon Sep 17 00:00:00 2001 From: jlnav Date: Tue, 14 May 2024 11:05:26 -0500 Subject: [PATCH 11/36] insert sample points, fix incorrect data being passed back by allowing results_array to be empty --- examples/dummy_aposmm_libE_gen/run_example.py | 12 +++++++++--- optimas/generators/libE_wrapper.py | 8 +++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 1b775bac..e2c8960b 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -3,7 +3,10 @@ from math import gamma, pi, sqrt import numpy as np from libensemble.generators import APOSMM +import libensemble.gen_funcs +libensemble.gen_funcs.rc.aposmm_optimizers = "nlopt" from optimas.core import VaryingParameter, Objective +from libensemble.tests.regression_tests.support import six_hump_camel_minima as minima # from optimas.generators import RandomSamplingGenerator from optimas.generators import libEWrapper @@ -49,10 +52,13 @@ def analyze_simulation(simulation_directory, output_params): var_2 = VaryingParameter("x1", 3.0, 2.0) obj = Objective("f") +n = 2 + aposmm = APOSMM( initial_sample_size = 100, localopt_method = "LN_BOBYQA", - rk_const = 0.5 * ((gamma(2) * 5) ** 0.5) / sqrt(pi), + sample_points = np.round(minima, 1), + rk_const = 0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), xtol_abs = 1e-6, ftol_abs = 1e-6, dist_to_bound_multiple = 0.5, @@ -76,7 +82,7 @@ def analyze_simulation(simulation_directory, output_params): # Create exploration. exp = Exploration( - generator=gen, evaluator=ev, max_evals=500, sim_workers=4, run_async=True + generator=gen, evaluator=ev, max_evals=300, sim_workers=4, run_async=True ) @@ -84,4 +90,4 @@ def analyze_simulation(simulation_directory, output_params): # for some flavours of multiprocessing, namely spawn and forkserver) if __name__ == "__main__": exp.run() - + assert len(gen.libe_gen.all_local_minima) diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index ae99bd14..4d2db9bc 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -81,6 +81,8 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: # Extract the 'x' field from gen_out[i] directly x_values = gen_out[i]["x"] trial.parameter_values = x_values + if "x_on_cube" in gen_out.dtype.names: + trial._x_metadata = gen_out[i]["x_on_cube"] return trials @@ -88,8 +90,12 @@ def _tell( self, trials: List[Trial], libE_calc_in: np.typing.NDArray ) -> None: if hasattr(self.libe_gen, "create_results_array"): - new_array = self.libe_gen.create_results_array() + new_array = self.libe_gen.create_results_array(empty=True) new_array["f"] = libE_calc_in["f"] + new_array["x"] = trials[0].parameter_values + new_array["sim_id"] = libE_calc_in["sim_id"] + if hasattr(trials[0], "_x_metadata"): + new_array["x_on_cube"] = trials[0]._x_metadata libE_calc_in = new_array if libE_calc_in["sim_id"] not in self.given_back_idxs: self.libe_gen.tell(libE_calc_in) From d77f36d9fb26ea4100fd62282e8f5e2407edbe40 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 16:07:32 +0000 Subject: [PATCH 12/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/dummy_aposmm_libE_gen/run_example.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 2da140e0..8181bf80 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -4,9 +4,12 @@ import numpy as np from libensemble.generators import APOSMM import libensemble.gen_funcs + libensemble.gen_funcs.rc.aposmm_optimizers = "nlopt" from optimas.core import VaryingParameter, Objective -from libensemble.tests.regression_tests.support import six_hump_camel_minima as minima +from libensemble.tests.regression_tests.support import ( + six_hump_camel_minima as minima, +) # from optimas.generators import RandomSamplingGenerator from optimas.generators import libEWrapper @@ -57,16 +60,16 @@ def analyze_simulation(simulation_directory, output_params): n = 2 aposmm = APOSMM( - initial_sample_size = 100, - localopt_method = "LN_BOBYQA", - sample_points = np.round(minima, 1), - rk_const = 0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), - xtol_abs = 1e-6, - ftol_abs = 1e-6, - dist_to_bound_multiple = 0.5, - max_active_runs = 6, # refers to APOSMM's simul local optimization runs - lb = np.array([-3, -2]), # potentially matches the VaryingParameters - ub = np.array([3, 2]), + initial_sample_size=100, + localopt_method="LN_BOBYQA", + sample_points=np.round(minima, 1), + rk_const=0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), + xtol_abs=1e-6, + ftol_abs=1e-6, + dist_to_bound_multiple=0.5, + max_active_runs=6, # refers to APOSMM's simul local optimization runs + lb=np.array([-3, -2]), # potentially matches the VaryingParameters + ub=np.array([3, 2]), ) gen = libEWrapper( From 3aea9898048c63ff1bdd192459587e51cbed79e5 Mon Sep 17 00:00:00 2001 From: jlnav Date: Tue, 14 May 2024 14:50:31 -0500 Subject: [PATCH 13/36] trying to pass more data back to aposmm... --- examples/dummy_aposmm_libE_gen/run_example.py | 3 +- optimas/generators/libE_wrapper.py | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 2da140e0..0415413f 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -64,7 +64,7 @@ def analyze_simulation(simulation_directory, output_params): xtol_abs = 1e-6, ftol_abs = 1e-6, dist_to_bound_multiple = 0.5, - max_active_runs = 6, # refers to APOSMM's simul local optimization runs + max_active_runs = 4, # refers to APOSMM's simul local optimization runs lb = np.array([-3, -2]), # potentially matches the VaryingParameters ub = np.array([3, 2]), ) @@ -92,4 +92,5 @@ def analyze_simulation(simulation_directory, output_params): # for some flavours of multiprocessing, namely spawn and forkserver) if __name__ == "__main__": exp.run() + import ipdb; ipdb.set_trace() assert len(gen.libe_gen.all_local_minima) diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index c6a9d1b0..6d323611 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -50,7 +50,7 @@ def __init__( ) self.libe_gen_class = libe_gen_class self.libe_gen_instance = libe_gen_instance - self.given_back_idxs = [] + self.temp_idx = 0 def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): n = len(self.varying_parameters) @@ -76,13 +76,14 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: """Fill in the parameter values of the requested trials.""" n_trials = len(trials) gen_out = self.libe_gen.ask(n_trials) - + for i, trial in enumerate(trials): # Extract the 'x' field from gen_out[i] directly x_values = gen_out[i]["x"] trial.parameter_values = x_values if "x_on_cube" in gen_out.dtype.names: trial._x_metadata = gen_out[i]["x_on_cube"] + trial._local_pt = gen_out[i]["local_pt"] return trials @@ -90,18 +91,21 @@ def _tell( self, trials: List[Trial], libE_calc_in: np.typing.NDArray ) -> None: if hasattr(self.libe_gen, "create_results_array"): - new_array = self.libe_gen.create_results_array(empty=True) - new_array["f"] = libE_calc_in["f"] - new_array["x"] = trials[0].parameter_values - new_array["sim_id"] = libE_calc_in["sim_id"] + if self.temp_idx == 0: + self.new_array = self.libe_gen.create_results_array(self.libe_gen.gen_specs["user"]["max_active_runs"], empty=True) + self.new_array["f"][self.temp_idx] = libE_calc_in["f"] + self.new_array["x"][self.temp_idx] = trials[0].parameter_values + self.new_array["sim_id"][self.temp_idx] = libE_calc_in["sim_id"] if hasattr(trials[0], "_x_metadata"): - new_array["x_on_cube"] = trials[0]._x_metadata - libE_calc_in = new_array - if libE_calc_in["sim_id"] not in self.given_back_idxs: - self.libe_gen.tell(libE_calc_in) - self.given_back_idxs.append(libE_calc_in["sim_id"]) + self.new_array["x_on_cube"][self.temp_idx] = trials[0]._x_metadata + self.new_array["local_pt"][self.temp_idx] = trials[0]._local_pt + print(trials[0]._local_pt) + self.temp_idx += 1 + if self.temp_idx == self.libe_gen.gen_specs["user"]["max_active_runs"]: + self.libe_gen.tell(self.new_array) + self.temp_idx = 0 # reset, create a new array next time around else: - print("Got back another array with the same ID as a previous?") + self.libe_gen.tell(libE_calc_in) def final_tell(self, libE_calc_in: np.typing.NDArray): return self.libe_gen.final_tell(libE_calc_in[["sim_id", "f"]]) From 258c0153a41848f3284bffaf5b84a1112821a960 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 19:53:22 +0000 Subject: [PATCH 14/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/dummy_aposmm_libE_gen/run_example.py | 20 +++++++++---------- optimas/generators/libE_wrapper.py | 16 +++++++++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index f28514f4..c5a665bb 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -60,16 +60,16 @@ def analyze_simulation(simulation_directory, output_params): n = 2 aposmm = APOSMM( - initial_sample_size = 100, - localopt_method = "LN_BOBYQA", - sample_points = np.round(minima, 1), - rk_const = 0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), - xtol_abs = 1e-6, - ftol_abs = 1e-6, - dist_to_bound_multiple = 0.5, - max_active_runs = 4, # refers to APOSMM's simul local optimization runs - lb = np.array([-3, -2]), # potentially matches the VaryingParameters - ub = np.array([3, 2]), + initial_sample_size=100, + localopt_method="LN_BOBYQA", + sample_points=np.round(minima, 1), + rk_const=0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), + xtol_abs=1e-6, + ftol_abs=1e-6, + dist_to_bound_multiple=0.5, + max_active_runs=4, # refers to APOSMM's simul local optimization runs + lb=np.array([-3, -2]), # potentially matches the VaryingParameters + ub=np.array([3, 2]), ) gen = libEWrapper( diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 6d323611..71ca46e6 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -76,7 +76,7 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: """Fill in the parameter values of the requested trials.""" n_trials = len(trials) gen_out = self.libe_gen.ask(n_trials) - + for i, trial in enumerate(trials): # Extract the 'x' field from gen_out[i] directly x_values = gen_out[i]["x"] @@ -92,16 +92,24 @@ def _tell( ) -> None: if hasattr(self.libe_gen, "create_results_array"): if self.temp_idx == 0: - self.new_array = self.libe_gen.create_results_array(self.libe_gen.gen_specs["user"]["max_active_runs"], empty=True) + self.new_array = self.libe_gen.create_results_array( + self.libe_gen.gen_specs["user"]["max_active_runs"], + empty=True, + ) self.new_array["f"][self.temp_idx] = libE_calc_in["f"] self.new_array["x"][self.temp_idx] = trials[0].parameter_values self.new_array["sim_id"][self.temp_idx] = libE_calc_in["sim_id"] if hasattr(trials[0], "_x_metadata"): - self.new_array["x_on_cube"][self.temp_idx] = trials[0]._x_metadata + self.new_array["x_on_cube"][self.temp_idx] = trials[ + 0 + ]._x_metadata self.new_array["local_pt"][self.temp_idx] = trials[0]._local_pt print(trials[0]._local_pt) self.temp_idx += 1 - if self.temp_idx == self.libe_gen.gen_specs["user"]["max_active_runs"]: + if ( + self.temp_idx + == self.libe_gen.gen_specs["user"]["max_active_runs"] + ): self.libe_gen.tell(self.new_array) self.temp_idx = 0 # reset, create a new array next time around else: From b4b970cde80cd9a6efb19b1531c0a23e2596693e Mon Sep 17 00:00:00 2001 From: jlnav Date: Tue, 14 May 2024 17:27:42 -0500 Subject: [PATCH 15/36] remove a debug print --- optimas/generators/libE_wrapper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 71ca46e6..bfb9e9fd 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -104,7 +104,6 @@ def _tell( 0 ]._x_metadata self.new_array["local_pt"][self.temp_idx] = trials[0]._local_pt - print(trials[0]._local_pt) self.temp_idx += 1 if ( self.temp_idx From 715473a0589c6e70f480ac88425c2d22416095c5 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 16 May 2024 14:46:35 -0500 Subject: [PATCH 16/36] adjust run_example constants and bounds, cleanup wrapper's _tell to slot in points to variably-sized results-array (based on either initial sample, or subsequent points) --- examples/dummy_aposmm_libE_gen/run_example.py | 9 ++-- optimas/generators/libE_wrapper.py | 52 ++++++++++++------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index c5a665bb..523b5bf7 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -53,8 +53,8 @@ def analyze_simulation(simulation_directory, output_params): # Create varying parameters and objectives. -var_1 = VaryingParameter("x0", -3.0, -2.0) -var_2 = VaryingParameter("x1", 3.0, 2.0) +var_1 = VaryingParameter("x0", -3.0, 3.0) +var_2 = VaryingParameter("x1", -2.0, 2.0) obj = Objective("f") n = 2 @@ -64,8 +64,8 @@ def analyze_simulation(simulation_directory, output_params): localopt_method="LN_BOBYQA", sample_points=np.round(minima, 1), rk_const=0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), - xtol_abs=1e-6, - ftol_abs=1e-6, + xtol_abs=1e-2, + ftol_abs=1e-2, dist_to_bound_multiple=0.5, max_active_runs=4, # refers to APOSMM's simul local optimization runs lb=np.array([-3, -2]), # potentially matches the VaryingParameters @@ -96,3 +96,4 @@ def analyze_simulation(simulation_directory, output_params): if __name__ == "__main__": exp.run() assert len(gen.libe_gen.all_local_minima) + print(f"Found {len(gen.libe_gen.all_local_minima)} minima!") diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index bfb9e9fd..45c3aced 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -50,7 +50,8 @@ def __init__( ) self.libe_gen_class = libe_gen_class self.libe_gen_instance = libe_gen_instance - self.temp_idx = 0 + self.num_evals = 0 + self.told_initial_sample = False def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): n = len(self.varying_parameters) @@ -87,30 +88,41 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: return trials + def _slot_in_data(self, libE_calc_in, trial): + self.new_array["f"][self.num_evals] = libE_calc_in["f"] + self.new_array["x"][self.num_evals] = trial.parameter_values + self.new_array["sim_id"][self.num_evals] = libE_calc_in["sim_id"] + if hasattr(trial, "_x_metadata"): + self.new_array["x_on_cube"][self.num_evals] = trial._x_metadata + self.new_array["local_pt"][self.num_evals] = trial._local_pt + + def _get_array_size(self): + user = self.libe_gen.gen_specs["user"] + return user["initial_sample_size"] if not self.told_initial_sample else user["max_active_runs"] + + def _got_enough_initial_sample(self): + return self.num_evals > int(0.9*self.libe_gen.gen_specs["user"]["initial_sample_size"]) + + def _got_enough_subsequent_points(self): + return self.num_evals >= self.libe_gen.gen_specs["user"]["max_active_runs"] + def _tell( self, trials: List[Trial], libE_calc_in: np.typing.NDArray ) -> None: if hasattr(self.libe_gen, "create_results_array"): - if self.temp_idx == 0: - self.new_array = self.libe_gen.create_results_array( - self.libe_gen.gen_specs["user"]["max_active_runs"], - empty=True, - ) - self.new_array["f"][self.temp_idx] = libE_calc_in["f"] - self.new_array["x"][self.temp_idx] = trials[0].parameter_values - self.new_array["sim_id"][self.temp_idx] = libE_calc_in["sim_id"] - if hasattr(trials[0], "_x_metadata"): - self.new_array["x_on_cube"][self.temp_idx] = trials[ - 0 - ]._x_metadata - self.new_array["local_pt"][self.temp_idx] = trials[0]._local_pt - self.temp_idx += 1 - if ( - self.temp_idx - == self.libe_gen.gen_specs["user"]["max_active_runs"] - ): + if self.num_evals == 0: + self.new_array = self.libe_gen.create_results_array(self._get_array_size(), empty=True) + self._slot_in_data(libE_calc_in, trials[0]) + self.num_evals += 1 + if not self.told_initial_sample: + # Optimas seems to have trouble completing exactly the initial sample before trying to ask. We're probably okay with 90% :) + if self._got_enough_initial_sample(): + self.libe_gen.tell(self.new_array) + self.told_initial_sample = True + self.num_evals = 0 + elif self._got_enough_subsequent_points(): self.libe_gen.tell(self.new_array) - self.temp_idx = 0 # reset, create a new array next time around + self.num_evals = 0 # reset, create a new array next time around else: self.libe_gen.tell(libE_calc_in) From dd3e3c8cc9a4ab5b16f929746b422c6df31d1f50 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 16 May 2024 19:47:44 +0000 Subject: [PATCH 17/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- optimas/generators/libE_wrapper.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 45c3aced..eecb55de 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -98,20 +98,30 @@ def _slot_in_data(self, libE_calc_in, trial): def _get_array_size(self): user = self.libe_gen.gen_specs["user"] - return user["initial_sample_size"] if not self.told_initial_sample else user["max_active_runs"] - + return ( + user["initial_sample_size"] + if not self.told_initial_sample + else user["max_active_runs"] + ) + def _got_enough_initial_sample(self): - return self.num_evals > int(0.9*self.libe_gen.gen_specs["user"]["initial_sample_size"]) - + return self.num_evals > int( + 0.9 * self.libe_gen.gen_specs["user"]["initial_sample_size"] + ) + def _got_enough_subsequent_points(self): - return self.num_evals >= self.libe_gen.gen_specs["user"]["max_active_runs"] + return ( + self.num_evals >= self.libe_gen.gen_specs["user"]["max_active_runs"] + ) def _tell( self, trials: List[Trial], libE_calc_in: np.typing.NDArray ) -> None: if hasattr(self.libe_gen, "create_results_array"): if self.num_evals == 0: - self.new_array = self.libe_gen.create_results_array(self._get_array_size(), empty=True) + self.new_array = self.libe_gen.create_results_array( + self._get_array_size(), empty=True + ) self._slot_in_data(libE_calc_in, trials[0]) self.num_evals += 1 if not self.told_initial_sample: From 2674dbeafdcf7e64d01ab1819e35f064023b0064 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 22 May 2024 14:24:35 -0500 Subject: [PATCH 18/36] combine libe_gen_instance and libe_gen_class into one parameter, some cleanup, better handling of non-existance of final_tell --- examples/dummy_aposmm_libE_gen/run_example.py | 2 +- examples/dummy_random_libEgen/run_example.py | 2 +- optimas/gen_functions.py | 11 +++++--- optimas/generators/libE_wrapper.py | 25 +++++++++---------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 523b5bf7..184c69a9 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -75,7 +75,7 @@ def analyze_simulation(simulation_directory, output_params): gen = libEWrapper( varying_parameters=[var_1, var_2], objectives=[obj], - libe_gen_instance=aposmm, + libe_gen=aposmm, ) # Create evaluator. diff --git a/examples/dummy_random_libEgen/run_example.py b/examples/dummy_random_libEgen/run_example.py index 8c54ecf2..156738a2 100644 --- a/examples/dummy_random_libEgen/run_example.py +++ b/examples/dummy_random_libEgen/run_example.py @@ -54,7 +54,7 @@ def analyze_simulation(simulation_directory, output_params): gen = libEWrapper( varying_parameters=[var_1, var_2], objectives=[obj], - libe_gen_class=RandSample, + libe_gen=RandSample, ) # Create evaluator. diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index 08e65bb3..1b7f895a 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -49,7 +49,7 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): # Get generator, objectives, and parameters to analyze. generator = gen_specs["user"]["generator"] - if hasattr(generator, "libe_gen_class"): + if hasattr(generator, "libe_gen"): generator.init_libe_gen(H, persis_info, gen_specs, libE_info) objectives = generator.objectives @@ -114,7 +114,7 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): ev = Evaluation(parameter=par, value=y) trial.complete_evaluation(ev) # Register trial with unknown SEM - if hasattr(generator, "libe_gen_class"): + if hasattr(generator, "libe_gen"): generator.tell([trial], libE_calc_in=calc_in[i]) else: generator.tell([trial]) @@ -124,6 +124,9 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): else: number_of_gen_points = 0 - if hasattr(generator, "libe_gen_class"): - return generator.final_tell(libE_calc_in=calc_in) + if hasattr(generator, "libe_gen"): + if hasattr(generator.libe_gen, "final_tell"): + out = generator.final_tell(libE_calc_in=calc_in) + if out is not None: + return out return H_o, persis_info, FINISHED_PERSISTENT_GEN_TAG diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index eecb55de..8ef818fb 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -1,6 +1,7 @@ from copy import deepcopy import numpy as np from typing import List, Optional +import inspect from libensemble.generators import LibEnsembleGenInterfacer @@ -30,8 +31,7 @@ def __init__( custom_trial_parameters: Optional[List[TrialParameter]] = None, allow_fixed_parameters: Optional[bool] = False, allow_updating_parameters: Optional[bool] = False, - libe_gen_class=None, # Type hint - add base class for ask/tell gens - libe_gen_instance=None, + libe_gen = None, ) -> None: super().__init__( varying_parameters=varying_parameters, @@ -48,8 +48,7 @@ def __init__( allow_fixed_parameters=allow_fixed_parameters, allow_updating_parameters=allow_updating_parameters, ) - self.libe_gen_class = libe_gen_class - self.libe_gen_instance = libe_gen_instance + self.libe_gen = libe_gen self.num_evals = 0 self.told_initial_sample = False @@ -62,16 +61,16 @@ def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): for i, vp in enumerate(self.varying_parameters): gen_specs["user"]["lb"][i] = vp.lower_bound gen_specs["user"]["ub"][i] = vp.upper_bound - if self.libe_gen_class is not None: - self.libe_gen = self.libe_gen_class( - gen_specs, H, persis_info, libE_info - ) - elif self.libe_gen_instance is not None: - if isinstance(self.libe_gen_instance, LibEnsembleGenInterfacer): - self.libe_gen_instance.setup() # start background thread - self.libe_gen = self.libe_gen_instance + if self.libe_gen is not None: + if inspect.isclass(self.libe_gen): + self.libe_gen = self.libe_gen( # replace self.libe_gen with initialized instance + gen_specs, H, persis_info, libE_info + ) + else: + if isinstance(self.libe_gen, LibEnsembleGenInterfacer): # no initialization needed except setup() + self.libe_gen.setup() # start background thread else: - raise ValueError("libe_gen_class or libe_gen_instance must be set") + raise ValueError("libe_gen must be set") def _ask(self, trials: List[Trial]) -> List[Trial]: """Fill in the parameter values of the requested trials.""" From 1ed3c311dc6a3163f30423615470524b95ad522e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 19:32:15 +0000 Subject: [PATCH 19/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- optimas/generators/libE_wrapper.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 8ef818fb..26162e1f 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -31,7 +31,7 @@ def __init__( custom_trial_parameters: Optional[List[TrialParameter]] = None, allow_fixed_parameters: Optional[bool] = False, allow_updating_parameters: Optional[bool] = False, - libe_gen = None, + libe_gen=None, ) -> None: super().__init__( varying_parameters=varying_parameters, @@ -67,7 +67,9 @@ def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): gen_specs, H, persis_info, libE_info ) else: - if isinstance(self.libe_gen, LibEnsembleGenInterfacer): # no initialization needed except setup() + if isinstance( + self.libe_gen, LibEnsembleGenInterfacer + ): # no initialization needed except setup() self.libe_gen.setup() # start background thread else: raise ValueError("libe_gen must be set") From 81a3b071d61ebbab003bea3049284808e82df08b Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 22 May 2024 15:01:50 -0500 Subject: [PATCH 20/36] pull aposmm's ub and lb from VaryingParameters --- examples/dummy_aposmm_libE_gen/run_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 184c69a9..63ea8863 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -68,8 +68,8 @@ def analyze_simulation(simulation_directory, output_params): ftol_abs=1e-2, dist_to_bound_multiple=0.5, max_active_runs=4, # refers to APOSMM's simul local optimization runs - lb=np.array([-3, -2]), # potentially matches the VaryingParameters - ub=np.array([3, 2]), + lb=np.array([var_1.lower_bound, var_2.lower_bound]), + ub=np.array([var_1.upper_bound, var_2.upper_bound]), ) gen = libEWrapper( From ab20e984b4cf0f0d590d61e23f8dda62adfc08be Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 23 May 2024 14:27:05 -0500 Subject: [PATCH 21/36] tentative docstrings --- optimas/generators/libE_wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 26162e1f..a6151f5e 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -16,6 +16,7 @@ class libEWrapper(Generator): + """ Generator class that wraps libEnsemble ask/tell generators. """ def __init__( self, varying_parameters: List[VaryingParameter], @@ -90,6 +91,7 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: return trials def _slot_in_data(self, libE_calc_in, trial): + """Slot in libE_calc_in and trial data into corresponding array fields.""" self.new_array["f"][self.num_evals] = libE_calc_in["f"] self.new_array["x"][self.num_evals] = trial.parameter_values self.new_array["sim_id"][self.num_evals] = libE_calc_in["sim_id"] @@ -98,6 +100,7 @@ def _slot_in_data(self, libE_calc_in, trial): self.new_array["local_pt"][self.num_evals] = trial._local_pt def _get_array_size(self): + """ Output array size must match either initial sample or N points to evaluate in parallel.""" user = self.libe_gen.gen_specs["user"] return ( user["initial_sample_size"] From c797c9c6a55e9de8e97c48fec4bbbb8885895e98 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 23 May 2024 15:07:09 -0500 Subject: [PATCH 22/36] attach libE_calc_in to trial, remove final_tell call in persistent_generator --- optimas/gen_functions.py | 11 ++--------- optimas/generators/libE_wrapper.py | 17 +++++++---------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index 1b7f895a..74981268 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -113,20 +113,13 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): y = calc_in[par.name][i] ev = Evaluation(parameter=par, value=y) trial.complete_evaluation(ev) + trial.libE_calc_in = calc_in[i] # Register trial with unknown SEM - if hasattr(generator, "libe_gen"): - generator.tell([trial], libE_calc_in=calc_in[i]) - else: - generator.tell([trial]) + generator.tell([trial]) # Set the number of points to generate to that number: number_of_gen_points = min(n + n_failed_gens, max_evals - n_gens) n_failed_gens = 0 else: number_of_gen_points = 0 - if hasattr(generator, "libe_gen"): - if hasattr(generator.libe_gen, "final_tell"): - out = generator.final_tell(libE_calc_in=calc_in) - if out is not None: - return out return H_o, persis_info, FINISHED_PERSISTENT_GEN_TAG diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index a6151f5e..dd41baed 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -90,11 +90,11 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: return trials - def _slot_in_data(self, libE_calc_in, trial): + def _slot_in_data(self, trial): """Slot in libE_calc_in and trial data into corresponding array fields.""" - self.new_array["f"][self.num_evals] = libE_calc_in["f"] + self.new_array["f"][self.num_evals] = trial.libE_calc_in["f"] self.new_array["x"][self.num_evals] = trial.parameter_values - self.new_array["sim_id"][self.num_evals] = libE_calc_in["sim_id"] + self.new_array["sim_id"][self.num_evals] = trial.libE_calc_in["sim_id"] if hasattr(trial, "_x_metadata"): self.new_array["x_on_cube"][self.num_evals] = trial._x_metadata self.new_array["local_pt"][self.num_evals] = trial._local_pt @@ -118,15 +118,14 @@ def _got_enough_subsequent_points(self): self.num_evals >= self.libe_gen.gen_specs["user"]["max_active_runs"] ) - def _tell( - self, trials: List[Trial], libE_calc_in: np.typing.NDArray - ) -> None: + def _tell(self, trials: List[Trial]) -> None: + trial = trials[0] if hasattr(self.libe_gen, "create_results_array"): if self.num_evals == 0: self.new_array = self.libe_gen.create_results_array( self._get_array_size(), empty=True ) - self._slot_in_data(libE_calc_in, trials[0]) + self._slot_in_data(trial) self.num_evals += 1 if not self.told_initial_sample: # Optimas seems to have trouble completing exactly the initial sample before trying to ask. We're probably okay with 90% :) @@ -138,7 +137,5 @@ def _tell( self.libe_gen.tell(self.new_array) self.num_evals = 0 # reset, create a new array next time around else: - self.libe_gen.tell(libE_calc_in) + self.libe_gen.tell(trial.libE_calc_in) - def final_tell(self, libE_calc_in: np.typing.NDArray): - return self.libe_gen.final_tell(libE_calc_in[["sim_id", "f"]]) From 859669db71c64d6d45edf1d97cc65644416b329c Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 23 May 2024 15:44:53 -0500 Subject: [PATCH 23/36] return final_tell to persistent_generator (for now?) --- optimas/gen_functions.py | 2 ++ optimas/generators/base.py | 2 ++ optimas/generators/libE_wrapper.py | 1 + 3 files changed, 5 insertions(+) diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index 74981268..39d07290 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -121,5 +121,7 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): n_failed_gens = 0 else: number_of_gen_points = 0 + if generator.libe_gen is not None and hasattr(generator.libe_gen, "final_tell"): + generator.libe_gen.final_tell(calc_in[["sim_id", "f"]]) return H_o, persis_info, FINISHED_PERSISTENT_GEN_TAG diff --git a/optimas/generators/base.py b/optimas/generators/base.py index 823d6dfd..f3c7d88e 100644 --- a/optimas/generators/base.py +++ b/optimas/generators/base.py @@ -86,6 +86,7 @@ def __init__( custom_trial_parameters: Optional[List[TrialParameter]] = None, allow_fixed_parameters: Optional[bool] = False, allow_updating_parameters: Optional[bool] = False, + _libe_gen: Optional[object] = None, ) -> None: if objectives is None: objectives = [Objective()] @@ -109,6 +110,7 @@ def __init__( ) self._allow_fixed_parameters = allow_fixed_parameters self._allow_updating_parameters = allow_updating_parameters + self._libe_gen = _libe_gen self._gen_function = persistent_generator self._given_trials = [] # Trials given for evaluation. self._queued_trials = [] # Trials queued to be given for evaluation. diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index dd41baed..c556c4a1 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -48,6 +48,7 @@ def __init__( custom_trial_parameters=custom_trial_parameters, allow_fixed_parameters=allow_fixed_parameters, allow_updating_parameters=allow_updating_parameters, + _libe_gen=libe_gen, ) self.libe_gen = libe_gen self.num_evals = 0 From 751563f3116c9c8ef840e212cee57b6a1c5d1a29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 20:45:55 +0000 Subject: [PATCH 24/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- optimas/gen_functions.py | 4 +++- optimas/generators/libE_wrapper.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index 39d07290..653af9cb 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -121,7 +121,9 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): n_failed_gens = 0 else: number_of_gen_points = 0 - if generator.libe_gen is not None and hasattr(generator.libe_gen, "final_tell"): + if generator.libe_gen is not None and hasattr( + generator.libe_gen, "final_tell" + ): generator.libe_gen.final_tell(calc_in[["sim_id", "f"]]) return H_o, persis_info, FINISHED_PERSISTENT_GEN_TAG diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index c556c4a1..ad89b73e 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -16,7 +16,8 @@ class libEWrapper(Generator): - """ Generator class that wraps libEnsemble ask/tell generators. """ + """Generator class that wraps libEnsemble ask/tell generators.""" + def __init__( self, varying_parameters: List[VaryingParameter], @@ -101,7 +102,7 @@ def _slot_in_data(self, trial): self.new_array["local_pt"][self.num_evals] = trial._local_pt def _get_array_size(self): - """ Output array size must match either initial sample or N points to evaluate in parallel.""" + """Output array size must match either initial sample or N points to evaluate in parallel.""" user = self.libe_gen.gen_specs["user"] return ( user["initial_sample_size"] @@ -139,4 +140,3 @@ def _tell(self, trials: List[Trial]) -> None: self.num_evals = 0 # reset, create a new array next time around else: self.libe_gen.tell(trial.libE_calc_in) - From f8c3fc2a408149256283b012d780e737a84dd239 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 24 May 2024 15:43:30 -0500 Subject: [PATCH 25/36] introduces Exploration.finalize(). Moves gen.final_tell into that method. Remove now-unneeded gen reference from wrapper, since serialization issues for live-gen. *But*, the reference stays alive in libE --- examples/dummy_aposmm_libE_gen/run_example.py | 4 +++- optimas/explorations/base.py | 8 +++++++- optimas/gen_functions.py | 2 -- optimas/generators/libE_wrapper.py | 3 ++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 63ea8863..933a4a0a 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -94,6 +94,8 @@ def analyze_simulation(simulation_directory, output_params): # To safely perform exploration, run it in the block below (this is needed # for some flavours of multiprocessing, namely spawn and forkserver) if __name__ == "__main__": - exp.run() + exp.run(100) + exp.run(200) + exp.finalize() assert len(gen.libe_gen.all_local_minima) print(f"Found {len(gen.libe_gen.all_local_minima)} minima!") diff --git a/optimas/explorations/base.py b/optimas/explorations/base.py index 99061e74..8be0c5fd 100644 --- a/optimas/explorations/base.py +++ b/optimas/explorations/base.py @@ -16,6 +16,7 @@ from libensemble.executors.mpi_executor import MPIExecutor from optimas.core.trial import TrialStatus +from optimas.generators.libE_wrapper import libEWrapper from optimas.generators.base import Generator from optimas.evaluators.base import Evaluator from optimas.evaluators.function_evaluator import FunctionEvaluator @@ -209,7 +210,7 @@ def run(self, n_evals: Optional[int] = None) -> None: self.libE_specs, H0=self._libe_history.H, ) - + # Update history. self._libe_history.H = history @@ -220,6 +221,11 @@ def run(self, n_evals: Optional[int] = None) -> None: # Reset `cwd` to initial value before `libE` was called. os.chdir(cwd) + def finalize(self) -> None: + """Finalize the exploration, cleanup generator.""" + if isinstance(self.generator, libEWrapper): + self.generator.libe_gen.final_tell(self._libe_history.H[["sim_id", "f"]]) + def attach_trials( self, trial_data: Union[Dict, List[Dict], np.ndarray, pd.DataFrame], diff --git a/optimas/gen_functions.py b/optimas/gen_functions.py index 39d07290..74981268 100644 --- a/optimas/gen_functions.py +++ b/optimas/gen_functions.py @@ -121,7 +121,5 @@ def persistent_generator(H, persis_info, gen_specs, libE_info): n_failed_gens = 0 else: number_of_gen_points = 0 - if generator.libe_gen is not None and hasattr(generator.libe_gen, "final_tell"): - generator.libe_gen.final_tell(calc_in[["sim_id", "f"]]) return H_o, persis_info, FINISHED_PERSISTENT_GEN_TAG diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index c556c4a1..4c331eeb 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -56,6 +56,7 @@ def __init__( def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): n = len(self.varying_parameters) + gen_specs_in["user"]["generator"] = None gen_specs = deepcopy(gen_specs_in) gen_specs["out"] = [("x", float, (n,))] gen_specs["user"]["lb"] = np.zeros(n) @@ -71,7 +72,7 @@ def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): else: if isinstance( self.libe_gen, LibEnsembleGenInterfacer - ): # no initialization needed except setup() + ) and self.libe_gen.thread is None: # no initialization needed except setup() self.libe_gen.setup() # start background thread else: raise ValueError("libe_gen must be set") From ce47066fb6ee1c45b62c688a72ed340d22068b61 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 May 2024 20:47:42 +0000 Subject: [PATCH 26/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- optimas/explorations/base.py | 6 ++++-- optimas/generators/libE_wrapper.py | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/optimas/explorations/base.py b/optimas/explorations/base.py index 8be0c5fd..f321e632 100644 --- a/optimas/explorations/base.py +++ b/optimas/explorations/base.py @@ -210,7 +210,7 @@ def run(self, n_evals: Optional[int] = None) -> None: self.libE_specs, H0=self._libe_history.H, ) - + # Update history. self._libe_history.H = history @@ -224,7 +224,9 @@ def run(self, n_evals: Optional[int] = None) -> None: def finalize(self) -> None: """Finalize the exploration, cleanup generator.""" if isinstance(self.generator, libEWrapper): - self.generator.libe_gen.final_tell(self._libe_history.H[["sim_id", "f"]]) + self.generator.libe_gen.final_tell( + self._libe_history.H[["sim_id", "f"]] + ) def attach_trials( self, diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 420d26e6..350a29ce 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -71,9 +71,10 @@ def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): gen_specs, H, persis_info, libE_info ) else: - if isinstance( - self.libe_gen, LibEnsembleGenInterfacer - ) and self.libe_gen.thread is None: # no initialization needed except setup() + if ( + isinstance(self.libe_gen, LibEnsembleGenInterfacer) + and self.libe_gen.thread is None + ): # no initialization needed except setup() self.libe_gen.setup() # start background thread else: raise ValueError("libe_gen must be set") From dc294e59511e914e61cd9108debdd08b8c95f835 Mon Sep 17 00:00:00 2001 From: jlnav Date: Tue, 28 May 2024 15:08:08 -0500 Subject: [PATCH 27/36] make additional APOSMMWrapper subclass of libEWrapper, with additional TrialParameters, plus all aposmm-specific array quirks. --- examples/dummy_aposmm_libE_gen/run_example.py | 4 +- optimas/generators/__init__.py | 2 + optimas/generators/aposmm.py | 101 ++++++++++++++++++ optimas/generators/libE_wrapper.py | 54 +--------- 4 files changed, 108 insertions(+), 53 deletions(-) create mode 100644 optimas/generators/aposmm.py diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 933a4a0a..c09759cd 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -12,7 +12,7 @@ ) # from optimas.generators import RandomSamplingGenerator -from optimas.generators import libEWrapper +from optimas.generators import APOSMMWrapper from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration @@ -72,7 +72,7 @@ def analyze_simulation(simulation_directory, output_params): ub=np.array([var_1.upper_bound, var_2.upper_bound]), ) -gen = libEWrapper( +gen = APOSMMWrapper( varying_parameters=[var_1, var_2], objectives=[obj], libe_gen=aposmm, diff --git a/optimas/generators/__init__.py b/optimas/generators/__init__.py index 2715b87d..c9176a91 100644 --- a/optimas/generators/__init__.py +++ b/optimas/generators/__init__.py @@ -23,6 +23,7 @@ from .line_sampling import LineSamplingGenerator from .random_sampling import RandomSamplingGenerator from .libE_wrapper import libEWrapper +from .aposmm import APOSMMWrapper __all__ = [ @@ -34,4 +35,5 @@ "LineSamplingGenerator", "RandomSamplingGenerator", "libEWrapper", + "APOSMMWrapper", ] diff --git a/optimas/generators/aposmm.py b/optimas/generators/aposmm.py new file mode 100644 index 00000000..93b31b26 --- /dev/null +++ b/optimas/generators/aposmm.py @@ -0,0 +1,101 @@ +from copy import deepcopy +import numpy as np +from typing import List, Optional +import inspect + +from libensemble.generators import LibEnsembleGenInterfacer + +from optimas.core import ( + Objective, + Trial, + VaryingParameter, + Parameter, + TrialParameter, +) +from .libE_wrapper import libEWrapper + + +class APOSMMWrapper(libEWrapper): + """ + Wraps a live, parameterized APOSMM generator instance. Note that .tell() parameters + are internally cached until either the initial sample or N points (for N local-optimization processes) + are evaluated. + """ + + def __init__( + self, + varying_parameters: List[VaryingParameter], + objectives: List[Objective], + libe_gen=None, + ) -> None: + custom_trial_parameters = [ + TrialParameter("x_on_cube", dtype=(float, (len(varying_parameters),))), + TrialParameter("local_pt", dtype=bool), + ] + super().__init__( + varying_parameters=varying_parameters, + objectives=objectives, + custom_trial_parameters=custom_trial_parameters, + libe_gen=libe_gen, + ) + self.libe_gen = libe_gen + self.num_evals = 0 + self._told_initial_sample = False + + def _slot_in_data(self, trial): + """Slot in libE_calc_in and trial data into corresponding array fields.""" + self.new_array["f"][self.num_evals] = trial.libE_calc_in["f"] + self.new_array["x"][self.num_evals] = trial.parameter_values + self.new_array["sim_id"][self.num_evals] = trial.libE_calc_in["sim_id"] + self.new_array["x_on_cube"][self.num_evals] = trial.x_on_cube + self.new_array["local_pt"][self.num_evals] = trial.local_pt + + @property + def _array_size(self): + """Output array size must match either initial sample or N points to evaluate in parallel.""" + user = self.libe_gen.gen_specs["user"] + return ( + user["initial_sample_size"] + if not self._told_initial_sample + else user["max_active_runs"] + ) + + @property + def _enough_initial_sample(self): + return self.num_evals > int( + 0.9 * self.libe_gen.gen_specs["user"]["initial_sample_size"] + ) + + @property + def _enough_subsequent_points(self): + return ( + self.num_evals >= self.libe_gen.gen_specs["user"]["max_active_runs"] + ) + + def _ask(self, trials: List[Trial]) -> List[Trial]: + """Fill in the parameter values of the requested trials.""" + n_trials = len(trials) + gen_out = self.libe_gen.ask(n_trials) + + for i, trial in enumerate(trials): + trial.parameter_values = gen_out[i]["x"] + trial.x_on_cube = gen_out[i]["x_on_cube"] + trial.local_pt = gen_out[i]["local_pt"] + + return trials + + def _tell(self, trials: List[Trial]) -> None: + trial = trials[0] + if self.num_evals == 0: + self.new_array = self.libe_gen.create_results_array( + self._array_size, empty=True + ) + self._slot_in_data(trial) + self.num_evals += 1 + if not self._told_initial_sample and self._enough_initial_sample: + self.libe_gen.tell(self.new_array) + self._told_initial_sample = True + self.num_evals = 0 + elif self._told_initial_sample and self._enough_subsequent_points: + self.libe_gen.tell(self.new_array) + self.num_evals = 0 # reset, create a new array next time around diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 350a29ce..0d9006aa 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -52,10 +52,9 @@ def __init__( _libe_gen=libe_gen, ) self.libe_gen = libe_gen - self.num_evals = 0 - self.told_initial_sample = False def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): + """ Initialize the libEnsemble generator based on gen_f local data, or starts a background thread.""" n = len(self.varying_parameters) gen_specs_in["user"]["generator"] = None gen_specs = deepcopy(gen_specs_in) @@ -88,57 +87,10 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: # Extract the 'x' field from gen_out[i] directly x_values = gen_out[i]["x"] trial.parameter_values = x_values - if "x_on_cube" in gen_out.dtype.names: - trial._x_metadata = gen_out[i]["x_on_cube"] - trial._local_pt = gen_out[i]["local_pt"] return trials - def _slot_in_data(self, trial): - """Slot in libE_calc_in and trial data into corresponding array fields.""" - self.new_array["f"][self.num_evals] = trial.libE_calc_in["f"] - self.new_array["x"][self.num_evals] = trial.parameter_values - self.new_array["sim_id"][self.num_evals] = trial.libE_calc_in["sim_id"] - if hasattr(trial, "_x_metadata"): - self.new_array["x_on_cube"][self.num_evals] = trial._x_metadata - self.new_array["local_pt"][self.num_evals] = trial._local_pt - - def _get_array_size(self): - """Output array size must match either initial sample or N points to evaluate in parallel.""" - user = self.libe_gen.gen_specs["user"] - return ( - user["initial_sample_size"] - if not self.told_initial_sample - else user["max_active_runs"] - ) - - def _got_enough_initial_sample(self): - return self.num_evals > int( - 0.9 * self.libe_gen.gen_specs["user"]["initial_sample_size"] - ) - - def _got_enough_subsequent_points(self): - return ( - self.num_evals >= self.libe_gen.gen_specs["user"]["max_active_runs"] - ) - def _tell(self, trials: List[Trial]) -> None: + """ Pass the raw objective values to generator.""" trial = trials[0] - if hasattr(self.libe_gen, "create_results_array"): - if self.num_evals == 0: - self.new_array = self.libe_gen.create_results_array( - self._get_array_size(), empty=True - ) - self._slot_in_data(trial) - self.num_evals += 1 - if not self.told_initial_sample: - # Optimas seems to have trouble completing exactly the initial sample before trying to ask. We're probably okay with 90% :) - if self._got_enough_initial_sample(): - self.libe_gen.tell(self.new_array) - self.told_initial_sample = True - self.num_evals = 0 - elif self._got_enough_subsequent_points(): - self.libe_gen.tell(self.new_array) - self.num_evals = 0 # reset, create a new array next time around - else: - self.libe_gen.tell(trial.libE_calc_in) + self.libe_gen.tell(trial.libE_calc_in) From 720bf8f50e68338e4b380b77067abfd33a0e30be Mon Sep 17 00:00:00 2001 From: jlnav Date: Tue, 28 May 2024 15:12:34 -0500 Subject: [PATCH 28/36] docstrings --- optimas/generators/aposmm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/optimas/generators/aposmm.py b/optimas/generators/aposmm.py index 93b31b26..6793d594 100644 --- a/optimas/generators/aposmm.py +++ b/optimas/generators/aposmm.py @@ -62,12 +62,14 @@ def _array_size(self): @property def _enough_initial_sample(self): + """ We're typically happy with at least 90% of the initial sample. """ return self.num_evals > int( 0.9 * self.libe_gen.gen_specs["user"]["initial_sample_size"] ) @property def _enough_subsequent_points(self): + """ But we need to evaluate at least N points, for the N local-optimization processes. """ return ( self.num_evals >= self.libe_gen.gen_specs["user"]["max_active_runs"] ) @@ -85,6 +87,7 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: return trials def _tell(self, trials: List[Trial]) -> None: + """ Pass objective values to generator, slotting/caching into APOSMM's expected results array.""" trial = trials[0] if self.num_evals == 0: self.new_array = self.libe_gen.create_results_array( From 696e00e3db600f2a7a821f493963c1a440cd6219 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 20:13:00 +0000 Subject: [PATCH 29/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- optimas/generators/aposmm.py | 14 +++++++------- optimas/generators/libE_wrapper.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/optimas/generators/aposmm.py b/optimas/generators/aposmm.py index 6793d594..26361898 100644 --- a/optimas/generators/aposmm.py +++ b/optimas/generators/aposmm.py @@ -1,7 +1,5 @@ -from copy import deepcopy import numpy as np -from typing import List, Optional -import inspect +from typing import List from libensemble.generators import LibEnsembleGenInterfacer @@ -29,7 +27,9 @@ def __init__( libe_gen=None, ) -> None: custom_trial_parameters = [ - TrialParameter("x_on_cube", dtype=(float, (len(varying_parameters),))), + TrialParameter( + "x_on_cube", dtype=(float, (len(varying_parameters),)) + ), TrialParameter("local_pt", dtype=bool), ] super().__init__( @@ -62,14 +62,14 @@ def _array_size(self): @property def _enough_initial_sample(self): - """ We're typically happy with at least 90% of the initial sample. """ + """We're typically happy with at least 90% of the initial sample.""" return self.num_evals > int( 0.9 * self.libe_gen.gen_specs["user"]["initial_sample_size"] ) @property def _enough_subsequent_points(self): - """ But we need to evaluate at least N points, for the N local-optimization processes. """ + """But we need to evaluate at least N points, for the N local-optimization processes.""" return ( self.num_evals >= self.libe_gen.gen_specs["user"]["max_active_runs"] ) @@ -87,7 +87,7 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: return trials def _tell(self, trials: List[Trial]) -> None: - """ Pass objective values to generator, slotting/caching into APOSMM's expected results array.""" + """Pass objective values to generator, slotting/caching into APOSMM's expected results array.""" trial = trials[0] if self.num_evals == 0: self.new_array = self.libe_gen.create_results_array( diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 0d9006aa..80897595 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -54,7 +54,7 @@ def __init__( self.libe_gen = libe_gen def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): - """ Initialize the libEnsemble generator based on gen_f local data, or starts a background thread.""" + """Initialize the libEnsemble generator based on gen_f local data, or starts a background thread.""" n = len(self.varying_parameters) gen_specs_in["user"]["generator"] = None gen_specs = deepcopy(gen_specs_in) @@ -91,6 +91,6 @@ def _ask(self, trials: List[Trial]) -> List[Trial]: return trials def _tell(self, trials: List[Trial]) -> None: - """ Pass the raw objective values to generator.""" + """Pass the raw objective values to generator.""" trial = trials[0] self.libe_gen.tell(trial.libE_calc_in) From ce8beb89a9aa54fe0c4cff4fdbe3e3c007163d71 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 31 May 2024 13:33:26 -0500 Subject: [PATCH 30/36] rough-comparison between aposmm-detected minima and known minima, additional docs for Exploration.finalize() --- examples/dummy_aposmm_libE_gen/run_example.py | 17 +++++++++++++---- optimas/explorations/base.py | 11 ++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index c09759cd..7d37b913 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -8,7 +8,7 @@ libensemble.gen_funcs.rc.aposmm_optimizers = "nlopt" from optimas.core import VaryingParameter, Objective from libensemble.tests.regression_tests.support import ( - six_hump_camel_minima as minima, + six_hump_camel_minima as known_minima, ) # from optimas.generators import RandomSamplingGenerator @@ -62,10 +62,10 @@ def analyze_simulation(simulation_directory, output_params): aposmm = APOSMM( initial_sample_size=100, localopt_method="LN_BOBYQA", - sample_points=np.round(minima, 1), + sample_points=np.round(known_minima, 1), rk_const=0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), - xtol_abs=1e-2, - ftol_abs=1e-2, + xtol_abs=1e-5, + ftol_abs=1e-5, dist_to_bound_multiple=0.5, max_active_runs=4, # refers to APOSMM's simul local optimization runs lb=np.array([var_1.lower_bound, var_2.lower_bound]), @@ -97,5 +97,14 @@ def analyze_simulation(simulation_directory, output_params): exp.run(100) exp.run(200) exp.finalize() + assert len(gen.libe_gen.all_local_minima) print(f"Found {len(gen.libe_gen.all_local_minima)} minima!") + found_minima = [i["x"] for i in gen.libe_gen.all_local_minima] + found_minima_combined = np.zeros(len(gen.libe_gen.all_local_minima), dtype=(float, 2)) + for i in range(len(found_minima)): + found_minima_combined[i] = found_minima[i] + found_minima = found_minima_combined + known_minima = np.round(known_minima, 3) # much precision lost? + found_minima = np.round(found_minima, 3) + assert any([i in known_minima for i in found_minima]) diff --git a/optimas/explorations/base.py b/optimas/explorations/base.py index f321e632..f729538a 100644 --- a/optimas/explorations/base.py +++ b/optimas/explorations/base.py @@ -222,7 +222,16 @@ def run(self, n_evals: Optional[int] = None) -> None: os.chdir(cwd) def finalize(self) -> None: - """Finalize the exploration, cleanup generator.""" + """Finalize the exploration, cleanup the generator and loggers. + + When using generators known to live in memory between `Exploration.run()` + invocations (oftentimes generators from libEnsemble), call this method to + indicate to the generator and other background processes to close down + their background threads. + + There is no guarantee that subsequent `.run()` invocations will operate + after calling `.finalize()`. + """ if isinstance(self.generator, libEWrapper): self.generator.libe_gen.final_tell( self._libe_history.H[["sim_id", "f"]] From bb26486e9e6debee432abba4c72876d99e8da8a9 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 31 May 2024 13:43:54 -0500 Subject: [PATCH 31/36] additional documentation --- optimas/generators/aposmm.py | 52 ++++++++++++++++++++++++++++-- optimas/generators/libE_wrapper.py | 3 ++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/optimas/generators/aposmm.py b/optimas/generators/aposmm.py index 26361898..ddee1480 100644 --- a/optimas/generators/aposmm.py +++ b/optimas/generators/aposmm.py @@ -1,3 +1,5 @@ +"""Contains definition for APOSMMWrapper class for translating APOSMM to Optimas-compatible format.""" + import numpy as np from typing import List @@ -15,9 +17,53 @@ class APOSMMWrapper(libEWrapper): """ - Wraps a live, parameterized APOSMM generator instance. Note that .tell() parameters - are internally cached until either the initial sample or N points (for N local-optimization processes) - are evaluated. + Wraps a live, parameterized APOSMM generator instance. + + .. code-block:: python + + from math import gamma, pi, sqrt + import numpy as np + + from optimas.generators import APOSMMWrapper + from optimas.core import Objective, Trial, VaryingParameter + from libensemble.generators import APOSMM + import libensemble.gen_funcs + + ... + + # Create varying parameters and objectives. + var_1 = VaryingParameter("x0", -3.0, 3.0) + var_2 = VaryingParameter("x1", -2.0, 2.0) + obj = Objective("f") + + n = 2 + + aposmm = APOSMM( + initial_sample_size=100, + localopt_method="LN_BOBYQA", + rk_const=0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), + xtol_abs=1e-5, + ftol_abs=1e-5, + dist_to_bound_multiple=0.5, + max_active_runs=4, # refers to APOSMM's simul local optimization runs + lb=np.array([var_1.lower_bound, var_2.lower_bound]), + ub=np.array([var_1.upper_bound, var_2.upper_bound]), + ) + + gen = APOSMMWrapper( + varying_parameters=[var_1, var_2], + objectives=[obj], + libe_gen=aposmm, + ) + + Parameters + ---------- + varying_parameters : list of VaryingParameter + List of input parameters to vary. + objectives : list of Objective + List of optimization objectives. + libe_gen : object + A live, parameterized APOSMM generator instance. Must import and provide from libEnsemble. """ def __init__( diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 80897595..8cd9a22c 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -1,3 +1,6 @@ +"""Contains definition for libEWrapper class for translating various libEnsemble ask/tell generators to Optimas-compatible format.""" + + from copy import deepcopy import numpy as np from typing import List, Optional From 9f2a059244802adf96416f6ed1dda63bc42e5bc8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 18:45:08 +0000 Subject: [PATCH 32/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/dummy_aposmm_libE_gen/run_example.py | 4 +++- optimas/explorations/base.py | 8 ++++---- optimas/generators/libE_wrapper.py | 1 - 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 7d37b913..2487b8df 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -101,7 +101,9 @@ def analyze_simulation(simulation_directory, output_params): assert len(gen.libe_gen.all_local_minima) print(f"Found {len(gen.libe_gen.all_local_minima)} minima!") found_minima = [i["x"] for i in gen.libe_gen.all_local_minima] - found_minima_combined = np.zeros(len(gen.libe_gen.all_local_minima), dtype=(float, 2)) + found_minima_combined = np.zeros( + len(gen.libe_gen.all_local_minima), dtype=(float, 2) + ) for i in range(len(found_minima)): found_minima_combined[i] = found_minima[i] found_minima = found_minima_combined diff --git a/optimas/explorations/base.py b/optimas/explorations/base.py index f729538a..90671dc8 100644 --- a/optimas/explorations/base.py +++ b/optimas/explorations/base.py @@ -223,12 +223,12 @@ def run(self, n_evals: Optional[int] = None) -> None: def finalize(self) -> None: """Finalize the exploration, cleanup the generator and loggers. - - When using generators known to live in memory between `Exploration.run()` + + When using generators known to live in memory between `Exploration.run()` invocations (oftentimes generators from libEnsemble), call this method to - indicate to the generator and other background processes to close down + indicate to the generator and other background processes to close down their background threads. - + There is no guarantee that subsequent `.run()` invocations will operate after calling `.finalize()`. """ diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index 8cd9a22c..bf05fcf9 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -1,6 +1,5 @@ """Contains definition for libEWrapper class for translating various libEnsemble ask/tell generators to Optimas-compatible format.""" - from copy import deepcopy import numpy as np from typing import List, Optional From 52dbd2e134c6bc84082adba563b588e1b5f8440e Mon Sep 17 00:00:00 2001 From: jlnav Date: Mon, 3 Jun 2024 11:46:34 -0500 Subject: [PATCH 33/36] indexing fix --- examples/dummy_aposmm_libE_gen/run_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 2487b8df..5dbc781b 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -105,7 +105,7 @@ def analyze_simulation(simulation_directory, output_params): len(gen.libe_gen.all_local_minima), dtype=(float, 2) ) for i in range(len(found_minima)): - found_minima_combined[i] = found_minima[i] + found_minima_combined[i] = found_minima[i][0] found_minima = found_minima_combined known_minima = np.round(known_minima, 3) # much precision lost? found_minima = np.round(found_minima, 3) From 08a835bb2d4efa30b76b9d86acf071c7d544310d Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 5 Jun 2024 12:30:38 -0500 Subject: [PATCH 34/36] fixes precision, but more runs are needed --- examples/dummy_aposmm_libE_gen/run_example.py | 12 ++++++------ .../template_simulation_script.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/dummy_aposmm_libE_gen/run_example.py b/examples/dummy_aposmm_libE_gen/run_example.py index 5dbc781b..c447fdac 100644 --- a/examples/dummy_aposmm_libE_gen/run_example.py +++ b/examples/dummy_aposmm_libE_gen/run_example.py @@ -64,8 +64,8 @@ def analyze_simulation(simulation_directory, output_params): localopt_method="LN_BOBYQA", sample_points=np.round(known_minima, 1), rk_const=0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), - xtol_abs=1e-5, - ftol_abs=1e-5, + xtol_abs=1e-6, + ftol_abs=1e-6, dist_to_bound_multiple=0.5, max_active_runs=4, # refers to APOSMM's simul local optimization runs lb=np.array([var_1.lower_bound, var_2.lower_bound]), @@ -87,7 +87,7 @@ def analyze_simulation(simulation_directory, output_params): # Create exploration. exp = Exploration( - generator=gen, evaluator=ev, max_evals=300, sim_workers=4, run_async=True + generator=gen, evaluator=ev, max_evals=1000, sim_workers=4, run_async=True ) @@ -95,7 +95,7 @@ def analyze_simulation(simulation_directory, output_params): # for some flavours of multiprocessing, namely spawn and forkserver) if __name__ == "__main__": exp.run(100) - exp.run(200) + exp.run() exp.finalize() assert len(gen.libe_gen.all_local_minima) @@ -107,6 +107,6 @@ def analyze_simulation(simulation_directory, output_params): for i in range(len(found_minima)): found_minima_combined[i] = found_minima[i][0] found_minima = found_minima_combined - known_minima = np.round(known_minima, 3) # much precision lost? - found_minima = np.round(found_minima, 3) + known_minima = np.round(known_minima, 6) + found_minima = np.round(found_minima, 6) assert any([i in known_minima for i in found_minima]) diff --git a/examples/dummy_aposmm_libE_gen/template_simulation_script.py b/examples/dummy_aposmm_libE_gen/template_simulation_script.py index 816ee617..285c4497 100644 --- a/examples/dummy_aposmm_libE_gen/template_simulation_script.py +++ b/examples/dummy_aposmm_libE_gen/template_simulation_script.py @@ -19,4 +19,4 @@ result = term1 + term2 + term3 with open("result.txt", "w") as f: - f.write("%f" % result) + f.write("{}".format(result)) From 6232b4192be54a19bb17f68e4cfd1384b758863b Mon Sep 17 00:00:00 2001 From: jlnav Date: Mon, 29 Jul 2024 15:42:25 -0500 Subject: [PATCH 35/36] adjust wrappers to use upstream class's ask_np and tell_np --- optimas/generators/aposmm.py | 12 ++++-------- optimas/generators/libE_wrapper.py | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/optimas/generators/aposmm.py b/optimas/generators/aposmm.py index ddee1480..39a125c5 100644 --- a/optimas/generators/aposmm.py +++ b/optimas/generators/aposmm.py @@ -3,8 +3,6 @@ import numpy as np from typing import List -from libensemble.generators import LibEnsembleGenInterfacer - from optimas.core import ( Objective, Trial, @@ -123,7 +121,7 @@ def _enough_subsequent_points(self): def _ask(self, trials: List[Trial]) -> List[Trial]: """Fill in the parameter values of the requested trials.""" n_trials = len(trials) - gen_out = self.libe_gen.ask(n_trials) + gen_out = self.libe_gen.ask_np(n_trials) for i, trial in enumerate(trials): trial.parameter_values = gen_out[i]["x"] @@ -136,15 +134,13 @@ def _tell(self, trials: List[Trial]) -> None: """Pass objective values to generator, slotting/caching into APOSMM's expected results array.""" trial = trials[0] if self.num_evals == 0: - self.new_array = self.libe_gen.create_results_array( - self._array_size, empty=True - ) + self.new_array = np.zeros(self._array_size, dtype=self.libe_gen.gen_specs["out"] + [("f", float)]) self._slot_in_data(trial) self.num_evals += 1 if not self._told_initial_sample and self._enough_initial_sample: - self.libe_gen.tell(self.new_array) + self.libe_gen.tell_np(self.new_array) self._told_initial_sample = True self.num_evals = 0 elif self._told_initial_sample and self._enough_subsequent_points: - self.libe_gen.tell(self.new_array) + self.libe_gen.tell_np(self.new_array) self.num_evals = 0 # reset, create a new array next time around diff --git a/optimas/generators/libE_wrapper.py b/optimas/generators/libE_wrapper.py index bf05fcf9..c8dd212e 100644 --- a/optimas/generators/libE_wrapper.py +++ b/optimas/generators/libE_wrapper.py @@ -5,7 +5,7 @@ from typing import List, Optional import inspect -from libensemble.generators import LibEnsembleGenInterfacer +from libensemble.generators import LibensembleGenThreadInterfacer from optimas.core import ( Objective, @@ -73,7 +73,7 @@ def init_libe_gen(self, H, persis_info, gen_specs_in, libE_info): ) else: if ( - isinstance(self.libe_gen, LibEnsembleGenInterfacer) + isinstance(self.libe_gen, LibensembleGenThreadInterfacer) and self.libe_gen.thread is None ): # no initialization needed except setup() self.libe_gen.setup() # start background thread From 595fd86e042a5fb29f823c93a4c6f8907fb8ee5e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:42:58 +0000 Subject: [PATCH 36/36] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- optimas/generators/aposmm.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/optimas/generators/aposmm.py b/optimas/generators/aposmm.py index 39a125c5..5897567a 100644 --- a/optimas/generators/aposmm.py +++ b/optimas/generators/aposmm.py @@ -134,7 +134,10 @@ def _tell(self, trials: List[Trial]) -> None: """Pass objective values to generator, slotting/caching into APOSMM's expected results array.""" trial = trials[0] if self.num_evals == 0: - self.new_array = np.zeros(self._array_size, dtype=self.libe_gen.gen_specs["out"] + [("f", float)]) + self.new_array = np.zeros( + self._array_size, + dtype=self.libe_gen.gen_specs["out"] + [("f", float)], + ) self._slot_in_data(trial) self.num_evals += 1 if not self._told_initial_sample and self._enough_initial_sample: