diff --git a/.github/workflows/unix-noax.yml b/.github/workflows/unix-noax.yml index dd2b7309..04a643fe 100644 --- a/.github/workflows/unix-noax.yml +++ b/.github/workflows/unix-noax.yml @@ -31,6 +31,7 @@ jobs: conda install numpy pandas pytorch cpuonly -c pytorch conda install -c conda-forge mpi4py mpich pip install .[test] + pip install git+https://github.com/campa-consortium/generator_standard.git@obs_type pip uninstall --yes ax-platform # Run without Ax - shell: bash -l {0} name: Run unit tests without Ax diff --git a/.github/workflows/unix-openmpi.yml b/.github/workflows/unix-openmpi.yml index 782c6023..86af3eb1 100644 --- a/.github/workflows/unix-openmpi.yml +++ b/.github/workflows/unix-openmpi.yml @@ -31,6 +31,7 @@ jobs: conda install numpy pandas pytorch cpuonly -c pytorch conda install -c conda-forge mpi4py openmpi=5.* pip install .[test] + pip install git+https://github.com/campa-consortium/generator_standard.git@obs_type - shell: bash -l {0} name: Run unit tests with openMPI run: | diff --git a/.github/workflows/unix.yml b/.github/workflows/unix.yml index 6f9cf08a..34749727 100644 --- a/.github/workflows/unix.yml +++ b/.github/workflows/unix.yml @@ -31,6 +31,7 @@ jobs: conda install numpy pandas pytorch cpuonly -c pytorch conda install -c conda-forge mpi4py mpich pip install .[test] + pip install git+https://github.com/campa-consortium/generator_standard.git@obs_type - shell: bash -l {0} name: Run unit tests with MPICH run: | diff --git a/examples/astra/run_optimization_serial_ASTRA.py b/examples/astra/run_optimization_serial_ASTRA.py index 434ca5a5..64f46635 100644 --- a/examples/astra/run_optimization_serial_ASTRA.py +++ b/examples/astra/run_optimization_serial_ASTRA.py @@ -9,34 +9,32 @@ https://optimas.readthedocs.io/en/latest/index.html """ -from optimas.core import VaryingParameter, Objective, Parameter from optimas.generators import AxSingleFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration +from generator_standard.vocs import VOCS from analysis_script import analyze_simulation -# Create varying parameters and objectives. +# Create VOCS object. # name of parameter, lower bound of values to be explored, # upper bound of values to be explored -var_1 = VaryingParameter("RF_phase", -2.5, 2.5) -var_2 = VaryingParameter("B_sol", 0.12, 0.38) -# Objectives that will be minimized: -obj_1 = Objective("bunch_length", minimize=True) -obj_2 = Objective("emittance", minimize=True) -# Additional example parameters that will be analyzed but are not used for the -# optimization: -em_x = Parameter("emittance_x") -em_y = Parameter("emittance_y") +vocs = VOCS( + variables={ + "RF_phase": [-2.5, 2.5], + "B_sol": [0.12, 0.38], + }, + objectives={ + "bunch_length": "MINIMIZE", + "emittance": "MINIMIZE", + }, + observables=["emittance_x", "emittance_y"], +) # Create generator. # Pick the generator to be used, here Single-fidelity Bayesian optimization. -# The analyzed_parameters are parameters that are calculated for each -# simulation but not used for the optimization. gen = AxSingleFidelityGenerator( - varying_parameters=[var_1, var_2], - objectives=[obj_1, obj_2], + vocs=vocs, n_init=8, - analyzed_parameters=[em_x, em_y], ) diff --git a/examples/dummy/run_example.py b/examples/dummy/run_example.py index a586967f..929adfee 100644 --- a/examples/dummy/run_example.py +++ b/examples/dummy/run_example.py @@ -1,6 +1,6 @@ """Basic example of parallel Bayesian optimization with Ax.""" -from optimas.core import VaryingParameter, Objective +from generator_standard.vocs import VOCS from optimas.generators import AxSingleFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration @@ -10,16 +10,16 @@ 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. + obtain the value of the optimization objective and other observables. + 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 + Dictionary where the value of the objectives and observables will be stored. There is one entry per parameter, where the key is the name of the parameter given by the user. @@ -37,16 +37,18 @@ def analyze_simulation(simulation_directory, output_params): 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", minimize=True) +# Create VOCS object defining variables, objectives. +vocs = VOCS( + variables={ + "x0": [0.0, 15.0], + "x1": [0.0, 15.0], + }, + objectives={"f": "MINIMIZE"}, +) # Create generator. -gen = AxSingleFidelityGenerator( - varying_parameters=[var_1, var_2], objectives=[obj], n_init=2 -) +gen = AxSingleFidelityGenerator(vocs=vocs, n_init=2) # Create evaluator. diff --git a/examples/dummy_grid_sampling/run_example.py b/examples/dummy_grid_sampling/run_example.py index 89365677..63c88ccf 100644 --- a/examples/dummy_grid_sampling/run_example.py +++ b/examples/dummy_grid_sampling/run_example.py @@ -1,6 +1,6 @@ """Basic example of parallel grid sampling with simulations.""" -from optimas.core import VaryingParameter, Objective +from generator_standard.vocs import VOCS from optimas.generators import GridSamplingGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration @@ -10,16 +10,16 @@ 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. + obtain the value of the optimization objective and other observables. + 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 + Dictionary where the value of the objectives and observables will be stored. There is one entry per parameter, where the key is the name of the parameter given by the user. @@ -37,16 +37,18 @@ def analyze_simulation(simulation_directory, output_params): 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 VOCS object defining variables, objectives. +vocs = VOCS( + variables={ + "x0": [0.0, 15.0], + "x1": [0.0, 15.0], + }, + objectives={"f": "MAXIMIZE"}, +) # Create generator. -gen = GridSamplingGenerator( - varying_parameters=[var_1, var_2], objectives=[obj], n_steps=[5, 7] -) +gen = GridSamplingGenerator(vocs=vocs, n_steps=[5, 7]) # Create evaluator. diff --git a/examples/dummy_line_sampling/run_example.py b/examples/dummy_line_sampling/run_example.py index d0084c02..56953671 100644 --- a/examples/dummy_line_sampling/run_example.py +++ b/examples/dummy_line_sampling/run_example.py @@ -1,6 +1,6 @@ """Basic example of parallel line sampling with simulations.""" -from optimas.core import VaryingParameter, Objective +from generator_standard.vocs import VOCS, ContinuousVariable from optimas.generators import LineSamplingGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration @@ -10,16 +10,16 @@ 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. + obtain the value of the optimization objective and other observables. + 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 + Dictionary where the value of the objectives and observables will be stored. There is one entry per parameter, where the key is the name of the parameter given by the user. @@ -37,16 +37,18 @@ def analyze_simulation(simulation_directory, output_params): return output_params -# Create varying parameters and objectives. -var_1 = VaryingParameter("x0", 0.0, 15.0, default_value=5.0) -var_2 = VaryingParameter("x1", 0.0, 15.0, default_value=6.0) -obj = Objective("f") +# Create VOCS object defining variables, objectives. +vocs = VOCS( + variables={ + "x0": ContinuousVariable(domain=[0.0, 15.0], default_value=5.0), + "x1": ContinuousVariable(domain=[0.0, 15.0], default_value=6.0), + }, + objectives={"f": "MAXIMIZE"}, +) # Create generator. -gen = LineSamplingGenerator( - varying_parameters=[var_1, var_2], objectives=[obj], n_steps=[5, 7] -) +gen = LineSamplingGenerator(vocs=vocs, n_steps=[5, 7]) # Create evaluator. diff --git a/examples/dummy_mf/run_example.py b/examples/dummy_mf/run_example.py index 402cd471..b8cef63e 100644 --- a/examples/dummy_mf/run_example.py +++ b/examples/dummy_mf/run_example.py @@ -1,6 +1,6 @@ """Basic example of parallel multi-fidelity Bayesian optimization with Ax.""" -from optimas.core import VaryingParameter, Objective +from generator_standard.vocs import VOCS from optimas.generators import AxMultiFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration @@ -10,16 +10,16 @@ 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. + obtain the value of the optimization objective and other observables. + 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 + Dictionary where the value of the objectives and observables will be stored. There is one entry per parameter, where the key is the name of the parameter given by the user. @@ -37,22 +37,24 @@ def analyze_simulation(simulation_directory, output_params): return output_params -# Create varying parameters (including fidelity) and objectives. -var_1 = VaryingParameter("x0", 0.0, 15.0) -var_2 = VaryingParameter("x1", 0.0, 15.0) -res = VaryingParameter( - "resolution", 1.0, 8.0, is_fidelity=True, fidelity_target_value=8.0 +# Create VOCS object defining variables, objectives. +vocs = VOCS( + variables={ + "x0": [0.0, 15.0], + "x1": [0.0, 15.0], + "resolution": [1.0, 8.0], + }, + objectives={"f": "MINIMIZE"}, ) -obj = Objective("f", minimize=True) # Create generator. gen = AxMultiFidelityGenerator( - varying_parameters=[var_1, var_2, res], - objectives=[obj], + vocs=vocs, n_init=4, fidel_cost_intercept=2.0, ) +gen.set_fidelity_param("resolution", fidelity_target_value=8.0) # Create evaluator. diff --git a/examples/dummy_mt/run_example.py b/examples/dummy_mt/run_example.py index bce75f3a..6a51c546 100644 --- a/examples/dummy_mt/run_example.py +++ b/examples/dummy_mt/run_example.py @@ -1,6 +1,7 @@ """Basic example of parallel multitask Bayesian optimization with Ax.""" -from optimas.core import VaryingParameter, Objective, Task +from generator_standard.vocs import VOCS +from optimas.core import Task from optimas.generators import AxMultitaskGenerator from optimas.evaluators import TemplateEvaluator, MultitaskEvaluator from optimas.explorations import Exploration @@ -10,16 +11,16 @@ 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. + obtain the value of the optimization objective and other observables. + 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 + Dictionary where the value of the objectives and observables will be stored. There is one entry per parameter, where the key is the name of the parameter given by the user. @@ -37,10 +38,15 @@ def analyze_simulation(simulation_directory, output_params): 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", minimize=True) +# Create VOCS object defining variables, objectives. +vocs = VOCS( + variables={ + "x0": [0.0, 15.0], + "x1": [0.0, 15.0], + "trial_type": {"cheap_model", "expensive_model"}, + }, + objectives={"f": "MINIMIZE"}, +) # Create tasks. @@ -50,8 +56,7 @@ def analyze_simulation(simulation_directory, output_params): # Create generator. gen = AxMultitaskGenerator( - varying_parameters=[var_1, var_2], - objectives=[obj], + vocs=vocs, lofi_task=lofi_task, hifi_task=hifi_task, ) diff --git a/examples/dummy_random/run_example.py b/examples/dummy_random/run_example.py index 2978323a..260ed4ed 100644 --- a/examples/dummy_random/run_example.py +++ b/examples/dummy_random/run_example.py @@ -1,6 +1,6 @@ """Basic example of parallel random sampling with simulations.""" -from optimas.core import VaryingParameter, Objective +from generator_standard.vocs import VOCS from optimas.generators import RandomSamplingGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration @@ -10,16 +10,16 @@ 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. + obtain the value of the optimization objective and other observables. + 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 + Dictionary where the value of the objectives and observables will be stored. There is one entry per parameter, where the key is the name of the parameter given by the user. @@ -37,16 +37,18 @@ def analyze_simulation(simulation_directory, output_params): 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 VOCS object defining variables, objectives. +vocs = VOCS( + variables={ + "x0": [0.0, 15.0], + "x1": [0.0, 15.0], + }, + objectives={"f": "MAXIMIZE"}, +) # Create generator. -gen = RandomSamplingGenerator( - varying_parameters=[var_1, var_2], objectives=[obj], distribution="normal" -) +gen = RandomSamplingGenerator(vocs=vocs, distribution="normal") # Create evaluator. diff --git a/examples/hipace/run_example.py b/examples/hipace/run_example.py index fa1789ad..b07e4af5 100644 --- a/examples/hipace/run_example.py +++ b/examples/hipace/run_example.py @@ -10,30 +10,27 @@ the `analysis_script.py` file. """ -from optimas.core import Parameter, VaryingParameter, Objective from optimas.generators import AxSingleFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration +from generator_standard.vocs import VOCS from analysis_script import analyze_simulation -# Create varying parameters and objectives. -var_1 = VaryingParameter("witness_charge", 0.05, 1.0) -obj = Objective("f", minimize=False) - - -# Define additional parameters to analyze. -energy_med = Parameter("energy_med") -energy_mad = Parameter("energy_mad") -charge = Parameter("charge") +# Create VOCS object. +vocs = VOCS( + variables={ + "witness_charge": [0.05, 1.0], + }, + objectives={"f": "MAXIMIZE"}, + observables=["energy_med", "energy_mad", "charge"], +) # Create generator. gen = AxSingleFidelityGenerator( - varying_parameters=[var_1], - objectives=[obj], - analyzed_parameters=[energy_med, energy_mad, charge], + vocs=vocs, n_init=4, ) diff --git a/examples/ionization_injection/run_example.py b/examples/ionization_injection/run_example.py index 100f9d64..33c5db5d 100644 --- a/examples/ionization_injection/run_example.py +++ b/examples/ionization_injection/run_example.py @@ -13,33 +13,30 @@ the `analysis_script.py` file. """ -from optimas.core import Parameter, VaryingParameter, Objective from optimas.generators import AxSingleFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration +from generator_standard.vocs import VOCS from analysis_script import analyze_simulation -# Create varying parameters and objectives. -var_1 = VaryingParameter("laser_scale", 0.7, 1.05) -var_2 = VaryingParameter("z_foc", 3.0, 7.5) -var_3 = VaryingParameter("mult", 0.1, 1.5) -var_4 = VaryingParameter("plasma_scale", 0.6, 0.8) -obj = Objective("f", minimize=False) - - -# Define additional parameters to analyze. -energy_med = Parameter("energy_med") -energy_mad = Parameter("energy_mad") -charge = Parameter("charge") +# Create VOCS object. +vocs = VOCS( + variables={ + "laser_scale": [0.7, 1.05], + "z_foc": [3.0, 7.5], + "mult": [0.1, 1.5], + "plasma_scale": [0.6, 0.8], + }, + objectives={"f": "MAXIMIZE"}, + observables=["energy_med", "energy_mad", "charge"], +) # Create generator. gen = AxSingleFidelityGenerator( - varying_parameters=[var_1, var_2, var_3, var_4], - objectives=[obj], - analyzed_parameters=[energy_med, energy_mad, charge], + vocs=vocs, n_init=4, ) diff --git a/examples/ionization_injection_mf/run_example.py b/examples/ionization_injection_mf/run_example.py index c153deab..e30a58ea 100644 --- a/examples/ionization_injection_mf/run_example.py +++ b/examples/ionization_injection_mf/run_example.py @@ -13,36 +13,33 @@ the `analysis_script.py` file. """ -from optimas.core import Parameter, VaryingParameter, Objective from optimas.generators import AxMultiFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration +from generator_standard.vocs import VOCS from analysis_script import analyze_simulation -# Create varying parameters and objectives. -var_1 = VaryingParameter("laser_scale", 0.7, 1.05) -var_2 = VaryingParameter("z_foc", 3.0, 7.5) -var_3 = VaryingParameter("mult", 0.1, 1.5) -var_4 = VaryingParameter("plasma_scale", 0.6, 0.8) -res = VaryingParameter( - "resolution", 2.0, 4.0, is_fidelity=True, fidelity_target_value=4.0 +# Create VOCS object. +vocs = VOCS( + variables={ + "laser_scale": [0.7, 1.05], + "z_foc": [3.0, 7.5], + "mult": [0.1, 1.5], + "plasma_scale": [0.6, 0.8], + "resolution": [2.0, 4.0], + }, + objectives={"f": "MINIMIZE"}, + observables=["energy_med", "energy_mad", "charge"], + fidelity_parameter="resolution", + fidelity_target_value=4.0, ) -obj = Objective("f", minimize=True) - - -# Define additional parameters to analyze. -energy_med = Parameter("energy_med") -energy_mad = Parameter("energy_mad") -charge = Parameter("charge") # Create generator. gen = AxMultiFidelityGenerator( - varying_parameters=[var_1, var_2, var_3, var_4, res], - objectives=[obj], - analyzed_parameters=[energy_med, energy_mad, charge], + vocs=vocs, n_init=4, ) diff --git a/examples/multi_stage/run_example.py b/examples/multi_stage/run_example.py index cea0aeb6..612e5493 100644 --- a/examples/multi_stage/run_example.py +++ b/examples/multi_stage/run_example.py @@ -10,32 +10,28 @@ the `analysis_script.py` file. """ -from optimas.core import Parameter, VaryingParameter, Objective from optimas.generators import AxSingleFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration +from generator_standard.vocs import VOCS from analysis_script import analyze_simulation -# Create varying parameters and objectives. -var_1 = VaryingParameter("adjust_factor", 0.7, 1.05) -var_2 = VaryingParameter("lens_start", 0.32, 0.347) -obj = Objective("f", minimize=True) - - -# Define additional parameters to analyze. -energy_std = Parameter("energy_std") -energy_avg = Parameter("energy_avg") -charge = Parameter("charge") -emittance = Parameter("emittance") +# Create VOCS object. +vocs = VOCS( + variables={ + "adjust_factor": [0.7, 1.05], + "lens_start": [0.32, 0.347], + }, + objectives={"f": "MINIMIZE"}, + observables=["energy_std", "energy_avg", "charge", "emittance"], +) # Create generator. gen = AxSingleFidelityGenerator( - varying_parameters=[var_1, var_2], - objectives=[obj], - analyzed_parameters=[energy_std, energy_avg, charge, emittance], + vocs=vocs, n_init=4, ) diff --git a/examples/multitask_lpa_fbpic_waket/run_opt.py b/examples/multitask_lpa_fbpic_waket/run_opt.py index f7b3ca73..c2aab41d 100644 --- a/examples/multitask_lpa_fbpic_waket/run_opt.py +++ b/examples/multitask_lpa_fbpic_waket/run_opt.py @@ -2,26 +2,27 @@ from multiprocessing import set_start_method -from optimas.core import VaryingParameter, Objective, Parameter, Task +from optimas.core import Task from optimas.generators import AxMultitaskGenerator from optimas.evaluators import TemplateEvaluator, MultitaskEvaluator from optimas.explorations import Exploration +from generator_standard.vocs import VOCS from analysis_script import analyze_simulation -# Create varying parameters and objectives. -var_1 = VaryingParameter("beam_i_1", 1.0, 10.0) # kA -var_2 = VaryingParameter("beam_i_2", 1.0, 10.0) # kA -var_3 = VaryingParameter("beam_z_i_2", -10.0, 10.0) # µm -var_4 = VaryingParameter("beam_length", 1.0, 20.0) # µm -obj = Objective("f", minimize=True) - - -# Define other quantities to analyze (which are not the optimization objective) -par_1 = Parameter("energy_med") -par_2 = Parameter("energy_mad") -par_3 = Parameter("charge") +# Create VOCS object. +vocs = VOCS( + variables={ + "beam_i_1": [1.0, 10.0], # kA + "beam_i_2": [1.0, 10.0], # kA + "beam_z_i_2": [-10.0, 10.0], # µm + "beam_length": [1.0, 20.0], # µm + "trial_type": {"wake-t", "fbpic"}, + }, + objectives={"f": "MINIMIZE"}, + observables=["energy_med", "energy_mad", "charge"], +) # Create tasks. @@ -31,9 +32,7 @@ # Create generator. gen = AxMultitaskGenerator( - varying_parameters=[var_1, var_2, var_3, var_4], - objectives=[obj], - analyzed_parameters=[par_1, par_2, par_3], + vocs=vocs, use_cuda=True, dedicated_resources=True, hifi_task=hifi_task, diff --git a/examples/wake_t/run_example.py b/examples/wake_t/run_example.py index 64367684..36df8660 100644 --- a/examples/wake_t/run_example.py +++ b/examples/wake_t/run_example.py @@ -11,23 +11,25 @@ file. """ -from optimas.core import VaryingParameter, Objective from optimas.generators import AxSingleFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration +from generator_standard.vocs import VOCS from analysis_script import analyze_simulation -# Create varying parameters and objectives. -var_1 = VaryingParameter("g_lens", 100.0, 1000.0) -obj = Objective("f", minimize=True) +# Create VOCS object. +vocs = VOCS( + variables={ + "g_lens": [100.0, 1000.0], + }, + objectives={"f": "MINIMIZE"}, +) # Create generator. -gen = AxSingleFidelityGenerator( - varying_parameters=[var_1], objectives=[obj], n_init=12 -) +gen = AxSingleFidelityGenerator(vocs=vocs, n_init=12) # Create evaluator. diff --git a/examples/wake_t_fbpic_mt/run_example.py b/examples/wake_t_fbpic_mt/run_example.py index 62954902..e75ce9ab 100644 --- a/examples/wake_t_fbpic_mt/run_example.py +++ b/examples/wake_t_fbpic_mt/run_example.py @@ -12,17 +12,23 @@ file. """ -from optimas.core import VaryingParameter, Objective, Task +from optimas.core import Task from optimas.generators import AxMultitaskGenerator from optimas.evaluators import TemplateEvaluator, MultitaskEvaluator from optimas.explorations import Exploration +from generator_standard.vocs import VOCS from analysis_script import analyze_simulation -# Create varying parameters and objectives. -var_1 = VaryingParameter("g_lens", 100.0, 1000.0) -obj = Objective("f", minimize=True) +# Create VOCS object. +vocs = VOCS( + variables={ + "g_lens": [100.0, 1000.0], + "trial_type": {"wake-t", "fbpic"}, + }, + objectives={"f": "MINIMIZE"}, +) # Create tasks. lofi_task = Task("wake-t", n_init=12, n_opt=12) @@ -31,8 +37,7 @@ # Create generator. gen = AxMultitaskGenerator( - varying_parameters=[var_1], - objectives=[obj], + vocs=vocs, lofi_task=lofi_task, hifi_task=hifi_task, ) diff --git a/optimas/diagnostics/exploration_diagnostics.py b/optimas/diagnostics/exploration_diagnostics.py index f7746f76..14b5ec4b 100644 --- a/optimas/diagnostics/exploration_diagnostics.py +++ b/optimas/diagnostics/exploration_diagnostics.py @@ -19,6 +19,7 @@ from optimas.explorations import Exploration from optimas.utils.other import get_df_with_selection from optimas.utils.ax import AxModelManager +from generator_standard.vocs import VOCS class ExplorationDiagnostics: @@ -102,12 +103,26 @@ def _create_exploration( analyzed_parameters.append(p) # Create exploration using dummy generator and evaluator. + variables = {} + for vp in varying_parameters: + variables[vp.name] = [vp.lower_bound, vp.upper_bound] + + vocs_objectives = {} + for obj in objectives: + vocs_objectives[obj.name] = ( + "MINIMIZE" if obj.minimize else "MAXIMIZE" + ) + + observables = [param.name for param in analyzed_parameters] + + vocs = VOCS( + variables=variables, + objectives=vocs_objectives, + observables=observables, + ) + return Exploration( - generator=Generator( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, - ), + generator=Generator(vocs=vocs), evaluator=Evaluator(sim_function=None), history=history_path, exploration_dir_path=exploration_dir_path, diff --git a/optimas/generators/ax/base.py b/optimas/generators/ax/base.py index 63f8ac3a..0575986a 100644 --- a/optimas/generators/ax/base.py +++ b/optimas/generators/ax/base.py @@ -1,12 +1,13 @@ """Contains the definition of the base Ax generator.""" -from typing import List, Optional +from typing import Optional import logging import torch from optimas.core import Objective, TrialParameter, VaryingParameter, Parameter from optimas.generators.base import Generator +from generator_standard.vocs import VOCS # Disable Ax loggers to get cleaner output. In principle, setting @@ -22,13 +23,8 @@ class AxGenerator(Generator): Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. - objectives : list of Objective - List of optimization objectives. - analyzed_parameters : list of Parameter, optional - List of parameters to analyze at each trial, but which are not - optimization objectives. By default ``None``. + vocs : VOCS + VOCS object defining variables, objectives, constraints, and observables. use_cuda : bool, optional Whether to allow the generator to run on a CUDA GPU. By default ``False``. @@ -63,9 +59,7 @@ class AxGenerator(Generator): def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], - analyzed_parameters: Optional[List[Parameter]] = None, + vocs: VOCS, use_cuda: Optional[bool] = False, gpu_id: Optional[int] = 0, dedicated_resources: Optional[bool] = False, @@ -77,9 +71,7 @@ def __init__( allow_updating_parameters: Optional[bool] = False, ) -> None: super().__init__( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, + vocs=vocs, use_cuda=use_cuda, gpu_id=gpu_id, dedicated_resources=dedicated_resources, diff --git a/optimas/generators/ax/developer/multitask.py b/optimas/generators/ax/developer/multitask.py index dc64beb6..75a98efd 100644 --- a/optimas/generators/ax/developer/multitask.py +++ b/optimas/generators/ax/developer/multitask.py @@ -67,6 +67,7 @@ TrialStatus, ) from .ax_metric import AxMetric +from generator_standard.vocs import VOCS, DiscreteVariable # Define generator states. NOT_STARTED = "not_started" @@ -152,10 +153,8 @@ class AxMultitaskGenerator(AxGenerator): Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. One them should be a fidelity. - objectives : list of Objective - List of optimization objectives. Only one objective is supported. + vocs : VOCS + VOCS object defining variables, objectives, constraints, and observables. lofi_task, hifi_task : Task The low- and high-fidelity tasks. analyzed_parameters : list of Parameter, optional @@ -184,11 +183,9 @@ class AxMultitaskGenerator(AxGenerator): def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], + vocs: VOCS, lofi_task: Task, hifi_task: Task, - analyzed_parameters: Optional[List[Parameter]] = None, use_cuda: Optional[bool] = False, gpu_id: Optional[int] = 0, dedicated_resources: Optional[bool] = False, @@ -196,19 +193,20 @@ def __init__( model_save_period: Optional[int] = 5, model_history_dir: Optional[str] = "model_history", ) -> None: + # As trial parameters these get written to history array # Ax trial_index and arm toegther locate a point # Multiple points (Optimas trials) can share the same Ax trial_index + # vocs interface note: These are not part of vocs. They are only stored + # to allow keeping track of them from previous runs. custom_trial_parameters = [ TrialParameter("arm_name", "ax_arm_name", dtype="U32"), - TrialParameter("trial_type", "ax_trial_type", dtype="U32"), TrialParameter("ax_trial_id", "ax_trial_index", dtype=int), ] - self._check_inputs(varying_parameters, objectives, lofi_task, hifi_task) + self._check_inputs(vocs, lofi_task, hifi_task) + super().__init__( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, + vocs=vocs, use_cuda=use_cuda, gpu_id=gpu_id, dedicated_resources=dedicated_resources, @@ -242,20 +240,30 @@ def get_gen_specs( gen_specs["out"].append(("task", str, max_length)) return gen_specs + def _validate_vocs(self, vocs: VOCS) -> None: + """Validate VOCS for multitask generator.""" + super()._validate_vocs(vocs) + # Check that only one objective has been given. + n_objectives = len(vocs.objectives) + assert n_objectives == 1, ( + "Multitask generator supports only a single objective. " + "Objectives given: {}.".format(n_objectives) + ) + # Check that there is a discrete variable called 'trial_type' + assert ( + "trial_type" in vocs.variables + ), "Multitask generator requires a discrete variable named 'trial_type'" + assert isinstance( + vocs.variables["trial_type"], DiscreteVariable + ), "Variable 'trial_type' must be a discrete variable" + def _check_inputs( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], + vocs: VOCS, lofi_task: Task, hifi_task: Task, ) -> None: """Check that the generator inputs are valid.""" - # Check that only one objective has been given. - n_objectives = len(objectives) - assert n_objectives == 1, ( - "Multitask generator supports only a single objective. " - "Objectives given: {}.".format(n_objectives) - ) # Check that the number of low-fidelity trials per iteration is larger # than that of high-fidelity trials. assert lofi_task.n_opt >= hifi_task.n_opt, ( @@ -274,11 +282,14 @@ def suggest(self, num_points: Optional[int]) -> List[dict]: var.name: arm.parameters.get(var.name) for var in self._varying_parameters } - # SH for VOCS standard these will need to be 'variables' - # For now much match the trial parameter names. + # SH We can use a discrete var here in vocs (converted for now to trial parameters) + # But unlike varying parameters the name refers to a fixed generator concept. + for trial_param in self._custom_trial_parameters: + if trial_param.name == "trial_type": + point[trial_param.name] = trial_type + point["ax_trial_id"] = trial_index point["arm_name"] = arm.name - point["trial_type"] = trial_type points.append(point) return points @@ -295,6 +306,7 @@ def ingest(self, results: List[dict]) -> None: custom_parameters=self._custom_trial_parameters, ) trials.append(trial) + if self.gen_state == NOT_STARTED: self._incorporate_external_data(trials) else: diff --git a/optimas/generators/ax/service/ax_client.py b/optimas/generators/ax/service/ax_client.py index ce6db0f0..c392a96b 100644 --- a/optimas/generators/ax/service/ax_client.py +++ b/optimas/generators/ax/service/ax_client.py @@ -6,6 +6,7 @@ from ax.core.objective import MultiObjective from optimas.core import Objective, VaryingParameter, Parameter +from generator_standard.vocs import VOCS from .base import AxServiceGenerator @@ -71,18 +72,19 @@ def __init__( model_save_period: Optional[int] = 5, model_history_dir: Optional[str] = "model_history", ): - varying_parameters = self._get_varying_parameters(ax_client) - objectives = self._get_objectives(ax_client) + # Create VOCS object from AxClient data + vocs = self._create_vocs_from_ax_client(ax_client) + + # Add constraints to analyzed parameters analyzed_parameters = self._add_constraints_to_analyzed_parameters( analyzed_parameters, ax_client ) + use_cuda = self._use_cuda(ax_client) self._ax_client = ax_client + super().__init__( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, - enforce_n_init=True, + vocs=vocs, abandon_failed_trials=abandon_failed_trials, use_cuda=use_cuda, gpu_id=gpu_id, @@ -92,6 +94,38 @@ def __init__( model_history_dir=model_history_dir, ) + def _create_vocs_from_ax_client(self, ax_client: AxClient) -> VOCS: + """Create a VOCS object from the AxClient data.""" + # Extract variables from search space + variables = {} + for _, p in ax_client.experiment.search_space.parameters.items(): + variables[p.name] = [p.lower, p.upper] + + # Extract objectives from optimization config + objectives = {} + ax_objective = ax_client.experiment.optimization_config.objective + if isinstance(ax_objective, MultiObjective): + ax_objectives = ax_objective.objectives + else: + ax_objectives = [ax_objective] + + for ax_obj in ax_objectives: + obj_type = "MINIMIZE" if ax_obj.minimize else "MAXIMIZE" + objectives[ax_obj.metric_names[0]] = obj_type + + # Extract observables from outcome constraints (if any) + observables = set() + ax_config = ax_client.experiment.optimization_config + if ax_config.outcome_constraints: + for constraint in ax_config.outcome_constraints: + observables.add(constraint.metric.name) + + return VOCS( + variables=variables, + objectives=objectives, + observables=observables, + ) + def _get_varying_parameters(self, ax_client: AxClient): """Obtain the list of varying parameters from the AxClient.""" varying_parameters = [] diff --git a/optimas/generators/ax/service/base.py b/optimas/generators/ax/service/base.py index d55681d7..858943f7 100644 --- a/optimas/generators/ax/service/base.py +++ b/optimas/generators/ax/service/base.py @@ -30,6 +30,12 @@ convert_optimas_to_ax_parameters, convert_optimas_to_ax_objectives, ) +from generator_standard.vocs import ( + VOCS, + LessThanConstraint, + GreaterThanConstraint, + BoundsConstraint, +) class AxServiceGenerator(AxGenerator): @@ -37,27 +43,19 @@ class AxServiceGenerator(AxGenerator): Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. - objectives : list of Objective - List of optimization objectives. - analyzed_parameters : list of Parameter, optional - List of parameters to analyze at each trial, but which are not - optimization objectives. By default ``None``. + vocs : VOCS + VOCS object defining variables, objectives, constraints, and observables. parameter_constraints : list of str, optional List of string representation of parameter constraints, such as ``"x3 >= x4"`` or ``"-x3 + 2*x4 - 3.5*x5 >= 2"``. For the latter constraints, any number of arguments is accepted, and acceptable operators are ``<=`` and ``>=``. - outcome_constraints : list of str, optional - List of string representation of outcome constraints (i.e., constraints - on any of the ``analyzed_parameters``) of form - ``"metric_name >= bound"``, like ``"m1 <= 3."``. n_init : int, optional Number of evaluations to perform during the initialization phase using Sobol sampling. If external data is attached to the exploration, the number of initialization evaluations will be reduced by the same amount, unless `enforce_n_init=True`. By default, ``4``. + enforce_n_init : bool, optional Whether to enforce the generation of `n_init` Sobol trials, even if external data is supplied. By default, ``False``. @@ -92,11 +90,8 @@ class AxServiceGenerator(AxGenerator): def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], - analyzed_parameters: Optional[List[Parameter]] = None, + vocs: VOCS, parameter_constraints: Optional[List[str]] = None, - outcome_constraints: Optional[List[str]] = None, n_init: Optional[int] = 4, enforce_n_init: Optional[bool] = False, abandon_failed_trials: Optional[bool] = True, @@ -109,9 +104,7 @@ def __init__( model_history_dir: Optional[str] = "model_history", ) -> None: super().__init__( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, + vocs=vocs, use_cuda=use_cuda, gpu_id=gpu_id, dedicated_resources=dedicated_resources, @@ -127,10 +120,46 @@ def __init__( self._fit_out_of_design = fit_out_of_design self._fixed_features = None self._parameter_constraints = parameter_constraints - self._outcome_constraints = outcome_constraints + self._outcome_constraints, constraint_parameters = ( + self._convert_vocs_constraints_to_outcome_constraints() + ) + # Add constraint parameters to analyzed parameters + if constraint_parameters: + if self._analyzed_parameters is None: + self._analyzed_parameters = [] + self._analyzed_parameters.extend(constraint_parameters) self._ax_client = self._create_ax_client() self._model = AxModelManager(self._ax_client) + def _convert_vocs_constraints_to_outcome_constraints( + self, + ) -> tuple[List[str], List[Parameter]]: + """Convert VOCS constraints to AX outcome constraints format and create analyzed parameters.""" + outcome_constraints = [] + constraint_parameters = [] + if hasattr(self._vocs, "constraints") and self._vocs.constraints: + for ( + constraint_name, + constraint_spec, + ) in self._vocs.constraints.items(): + # Create analyzed parameter for this constraint + constraint_parameters.append(Parameter(constraint_name)) + + # Handle different constraint types + if isinstance(constraint_spec, LessThanConstraint): + outcome_constraints.append( + f"{constraint_name} <= {constraint_spec.value}" + ) + elif isinstance(constraint_spec, GreaterThanConstraint): + outcome_constraints.append( + f"{constraint_name} >= {constraint_spec.value}" + ) + elif isinstance(constraint_spec, BoundsConstraint): + lo, hi = constraint_spec.range + outcome_constraints.append(f"{constraint_name} >= {lo}") + outcome_constraints.append(f"{constraint_name} <= {hi}") + return outcome_constraints, constraint_parameters + @property def ax_client(self) -> AxClient: """Get the underlying AxClient.""" @@ -352,3 +381,73 @@ def _mark_trial_as_failed(self, trial: Trial): ax_trial.mark_abandoned(unsafe=True) else: ax_trial.mark_failed(unsafe=True) + + def fix_value(self, var_name: str, value: float) -> None: + """Fix a parameter to a specific value.""" + var = None + for vp in self._varying_parameters: + if vp.name == var_name: + var = vp + break + + if var is None: + raise ValueError( + f"Variable '{var_name}' not found in varying parameters" + ) + + var.fix_value(value) + self._update_parameter(var) + + def free_value(self, var_name: str) -> None: + """Free a previously fixed parameter.""" + var = None + for vp in self._varying_parameters: + if vp.name == var_name: + var = vp + break + + if var is None: + raise ValueError( + f"Variable '{var_name}' not found in varying parameters" + ) + + if not var.is_fixed: + raise ValueError(f"Variable '{var_name}' was not previously fixed") + + var.free_value() + self._update_parameter(var) + + def update_range( + self, var_name: str, lower_bound: float, upper_bound: float + ) -> None: + """Update the range of a parameter. + + Parameters + ---------- + var_name : str + Name of the variable to update. + lower_bound : float + New lower bound for the parameter. + upper_bound : float + New upper bound for the parameter. + """ + var = None + for vp in self._varying_parameters: + if vp.name == var_name: + var = vp + break + + if var is None: + raise ValueError( + f"Variable '{var_name}' not found in varying parameters" + ) + + # Update the VOCS variable domain + if var_name in self._vocs.variables: + self._vocs.variables[var_name].domain = [lower_bound, upper_bound] + + # Update the varying parameter + var.update_range(lower_bound, upper_bound) + + # Update the Ax client + self._update_parameter(var) diff --git a/optimas/generators/ax/service/multi_fidelity.py b/optimas/generators/ax/service/multi_fidelity.py index 2c65e2f0..df8c9190 100644 --- a/optimas/generators/ax/service/multi_fidelity.py +++ b/optimas/generators/ax/service/multi_fidelity.py @@ -11,6 +11,7 @@ from optimas.core import Objective, VaryingParameter, Parameter from .base import AxServiceGenerator +from generator_standard.vocs import VOCS class AxMultiFidelityGenerator(AxServiceGenerator): @@ -18,17 +19,10 @@ class AxMultiFidelityGenerator(AxServiceGenerator): Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. One them should be a fidelity. - objectives : list of Objective - List of optimization objectives. - analyzed_parameters : list of Parameter, optional - List of parameters to analyze at each trial, but which are not - optimization objectives. By default ``None``. - outcome_constraints : list of str, optional - List of string representation of outcome constraints (i.e., constraints - on any of the ``analyzed_parameters``) of form - ``"metric_name >= bound"``, like ``"m1 <= 3."``. + vocs : VOCS + VOCS object defining variables, objectives, constraints, and observables. + One of the variables should be a fidelity parameter. + n_init : int, optional Number of evaluations to perform during the initialization phase using Sobol sampling. If external data is attached to the exploration, the @@ -72,10 +66,7 @@ class AxMultiFidelityGenerator(AxServiceGenerator): def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], - analyzed_parameters: Optional[List[Parameter]] = None, - outcome_constraints: Optional[List[str]] = None, + vocs: VOCS, n_init: Optional[int] = 4, enforce_n_init: Optional[bool] = False, abandon_failed_trials: Optional[bool] = True, @@ -90,10 +81,7 @@ def __init__( ) -> None: self.fidel_cost_intercept = fidel_cost_intercept super().__init__( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, - outcome_constraints=outcome_constraints, + vocs=vocs, n_init=n_init, enforce_n_init=enforce_n_init, abandon_failed_trials=abandon_failed_trials, @@ -142,3 +130,35 @@ def _create_generation_steps( ) return steps + + def set_fidelity_param( + self, + var_name: str, + fidelity_target_value: float = None, + ) -> None: + """Set a parameter as the fidelity parameter for multi-fidelity optimization. + + Parameters + ---------- + var_name : str + Name of the variable to set as fidelity parameter. + fidelity_target_value : float, optional + The target fidelity value for optimization. + """ + var = None + for vp in self._varying_parameters: + if vp.name == var_name: + var = vp + break + + if var is None: + raise ValueError( + f"Variable '{var_name}' not found in varying parameters" + ) + + var.is_fidelity = True + if fidelity_target_value is not None: + var.fidelity_target_value = fidelity_target_value + + # Update the Ax client + self._update_parameter(var) diff --git a/optimas/generators/ax/service/single_fidelity.py b/optimas/generators/ax/service/single_fidelity.py index 6cbdef6c..af94a4e0 100644 --- a/optimas/generators/ax/service/single_fidelity.py +++ b/optimas/generators/ax/service/single_fidelity.py @@ -7,6 +7,7 @@ from optimas.core import Objective, VaryingParameter, Parameter from .base import AxServiceGenerator +from generator_standard.vocs import VOCS class AxSingleFidelityGenerator(AxServiceGenerator): @@ -23,13 +24,8 @@ class AxSingleFidelityGenerator(AxServiceGenerator): Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. - objectives : list of Objective - List of optimization objectives. - analyzed_parameters : list of Parameter, optional - List of parameters to analyze at each trial, but which are not - optimization objectives. By default ``None``. + vocs : VOCS + VOCS object defining variables, objectives, constraints, and observables. parameter_constraints : list of str, optional List of string representation of parameter constraints, such as ``"x3 >= x4"`` or ``"-x3 + 2*x4 - 3.5*x5 >= 2"``. @@ -93,11 +89,8 @@ class AxSingleFidelityGenerator(AxServiceGenerator): def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], - analyzed_parameters: Optional[List[Parameter]] = None, + vocs: VOCS, parameter_constraints: Optional[List[str]] = None, - outcome_constraints: Optional[List[str]] = None, n_init: Optional[int] = 4, enforce_n_init: Optional[bool] = False, abandon_failed_trials: Optional[bool] = True, @@ -112,11 +105,8 @@ def __init__( ) -> None: self._fully_bayesian = fully_bayesian super().__init__( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, + vocs=vocs, parameter_constraints=parameter_constraints, - outcome_constraints=outcome_constraints, n_init=n_init, enforce_n_init=enforce_n_init, abandon_failed_trials=abandon_failed_trials, diff --git a/optimas/generators/base.py b/optimas/generators/base.py index 5a979714..c99037ac 100644 --- a/optimas/generators/base.py +++ b/optimas/generators/base.py @@ -20,25 +20,19 @@ TrialParameter, TrialStatus, ) +from generator_standard.vocs import VOCS, ContinuousVariable, DiscreteVariable +from generator_standard.generator import Generator as StandardGenerator logger = get_logger(__name__) -class Generator: +class Generator(StandardGenerator): """Base class for all generators. Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. - objectives : list of Objective - List of optimization objectives. - constraints : list of Parameter, optional - [Not yet implemented] List of optimization constraints. By default - ``None``. - analyzed_parameters : list of Parameter, optional - List of parameters to analyze at each trial, but which are not - optimization objectives. By default ``None``. + vocs : VOCS + VOCS object specifying variables, objectives, constraints, and observables. use_cuda : bool, optional Whether to allow the generator to run on a CUDA GPU. By default ``False``. @@ -73,10 +67,7 @@ class Generator: def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], - constraints: Optional[List[Parameter]] = None, - analyzed_parameters: Optional[List[Parameter]] = None, + vocs: VOCS, use_cuda: Optional[bool] = False, gpu_id: Optional[int] = 0, dedicated_resources: Optional[bool] = False, @@ -87,16 +78,23 @@ def __init__( allow_fixed_parameters: Optional[bool] = False, allow_updating_parameters: Optional[bool] = False, ) -> None: - if objectives is None: - objectives = [Objective()] + # Initialize the standard generator which calls `_validate_vocs` + super().__init__(vocs) + # Store copies to prevent unexpected behavior if parameters are changed # externally. - self._varying_parameters = deepcopy(varying_parameters) - self._objectives = deepcopy(objectives) - self._constraints = constraints + self._vocs = deepcopy(vocs) + + # Convert VOCS to optimas internal format for backward compatibility + self._varying_parameters = ( + self._convert_vocs_variables_to_varying_parameters() + ) + self._objectives = self._convert_vocs_objectives_to_objectives() + self._constraints = self._convert_vocs_constraints_to_constraints() self._analyzed_parameters = ( - [] if analyzed_parameters is None else analyzed_parameters + self._convert_vocs_observables_to_parameters() ) + self._save_model = save_model self._model_save_period = model_save_period self._model_history_dir = model_history_dir @@ -107,6 +105,12 @@ def __init__( self._custom_trial_parameters = ( [] if custom_trial_parameters is None else custom_trial_parameters ) + + # Automatically add discrete variables as trial parameters + discrete_trial_params = ( + self._convert_vocs_discrete_variables_to_trial_parameters() + ) + self._custom_trial_parameters.extend(discrete_trial_params) self._allow_fixed_parameters = allow_fixed_parameters self._allow_updating_parameters = allow_updating_parameters self._gen_function = persistent_generator @@ -115,6 +119,114 @@ def __init__( self._trial_count = 0 self._check_parameters(self._varying_parameters) + def _validate_vocs(self, vocs: VOCS) -> None: + """Ensure the generator has at least one variable and one objective.""" + if not vocs.variables: + raise ValueError("VOCS must define at least one variable.") + if not vocs.objectives: + raise ValueError("VOCS must define at least one objective.") + + def _convert_vocs_variables_to_varying_parameters( + self, + ) -> List[VaryingParameter]: + """Convert VOCS variables to optimas VaryingParameter objects.""" + varying_parameters = [] + for var_name, var_spec in self._vocs.variables.items(): + # Handle ContinuousVariable + if isinstance(var_spec, ContinuousVariable): + vp = VaryingParameter( + name=var_name, + lower_bound=var_spec.domain[0], + upper_bound=var_spec.domain[1], + default_value=var_spec.default_value, + ) + varying_parameters.append(vp) + # Handle DiscreteVariable that is a range of integers + # TODO: Suggest supporting IntegerVariables in vocs + elif isinstance(var_spec, DiscreteVariable): + values = list(var_spec.values) + if len(values) > 1: + # Check if values form a continuous integer range + sorted_values = sorted(values) + if all( + isinstance(v, int) for v in values + ) and sorted_values == list( + range(sorted_values[0], sorted_values[-1] + 1) + ): + vp = VaryingParameter( + name=var_name, + lower_bound=sorted_values[0], + upper_bound=sorted_values[-1], + dtype=int, + ) + varying_parameters.append(vp) + return varying_parameters + + def _convert_vocs_objectives_to_objectives(self) -> List[Objective]: + """Convert VOCS objectives to optimas Objective objects.""" + objectives = [] + for obj_name, obj_type in self._vocs.objectives.items(): + minimize = obj_type == "MINIMIZE" + obj = Objective(name=obj_name, minimize=minimize) + objectives.append(obj) + return objectives + + def _convert_vocs_constraints_to_constraints( + self, + ) -> Optional[List[Parameter]]: + """Convert VOCS constraints to optimas Parameter objects.""" + if not self._vocs.constraints: + return None + constraints = [] + for const_name, const_spec in self._vocs.constraints.items(): + # For now, create a basic Parameter - constraint handling needs more work + param = Parameter(name=const_name) + constraints.append(param) + return constraints + + def _convert_vocs_observables_to_parameters(self) -> List[Parameter]: + """Convert VOCS observables to optimas Parameter objects.""" + parameters = [] + # Handle both set of strings and dict + for obs_name in self._vocs.observables: + if isinstance(self._vocs.observables, dict): + obs_spec = self._vocs.observables[obs_name] + else: + obs_spec = None + param = Parameter(name=obs_name, dtype=obs_spec) + parameters.append(param) + return parameters + + def _convert_vocs_discrete_variables_to_trial_parameters( + self, + ) -> List[TrialParameter]: + """Convert discrete variables from VOCS to TrialParameter objects. + + Only converts discrete variables that were NOT already converted to + VaryingParameters. + """ + trial_parameters = [] + # Get the names of variables that were already converted to + # VaryingParameters + varying_param_names = {vp.name for vp in self._varying_parameters} + + for var_name, var_spec in self._vocs.variables.items(): + if isinstance(var_spec, DiscreteVariable): + # Only convert if it wasn't already converted to a + # VaryingParameter + if var_name not in varying_param_names: + max_len = max(len(str(val)) for val in var_spec.values) + trial_param = TrialParameter( + var_name, var_name, dtype=f"U{max_len}" + ) + trial_parameters.append(trial_param) + return trial_parameters + + @property + def vocs(self) -> VOCS: + """Get the VOCS object.""" + return self._vocs + @property def varying_parameters(self) -> List[VaryingParameter]: """Get the list of varying parameters.""" diff --git a/optimas/generators/grid_sampling.py b/optimas/generators/grid_sampling.py index 36dd7abc..2a0a1a0a 100644 --- a/optimas/generators/grid_sampling.py +++ b/optimas/generators/grid_sampling.py @@ -5,6 +5,7 @@ import numpy as np from optimas.core import Objective, Trial, VaryingParameter, Parameter +from generator_standard.vocs import VOCS from .base import Generator @@ -19,30 +20,19 @@ class GridSamplingGenerator(Generator): Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. - objectives : list of Objective - List of optimization objectives. + vocs : VOCS + VOCS object specifying variables, objectives, constraints, and observables. n_steps : list of int Number of grid steps along each direction. - analyzed_parameters : list of Parameter, optional - List of parameters to analyze at each trial, but which are not - optimization objectives. By default ``None``. """ def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], + vocs: VOCS, n_steps: List[int], - analyzed_parameters: Optional[List[Parameter]] = None, ) -> None: - super().__init__( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, - ) + super().__init__(vocs=vocs) self._n_steps = n_steps if n_steps is np.ndarray else np.array(n_steps) self._create_configurations() diff --git a/optimas/generators/line_sampling.py b/optimas/generators/line_sampling.py index 4253aa12..c1afc8fa 100644 --- a/optimas/generators/line_sampling.py +++ b/optimas/generators/line_sampling.py @@ -5,6 +5,7 @@ import numpy as np from optimas.core import Objective, Trial, VaryingParameter, Parameter +from generator_standard.vocs import VOCS from .base import Generator @@ -24,54 +25,45 @@ class LineSamplingGenerator(Generator): Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. - objectives : list of Objective - List of optimization objectives. + vocs : VOCS + VOCS object specifying variables, objectives, constraints, and observables. n_steps : ndarray or list of int A 1D array or list with the number of steps along each direction. - analyzed_parameters : list of Parameter, optional - List of parameters to analyze at each trial, but which are not - optimization objectives. By default ``None``. """ def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], + vocs: VOCS, n_steps: Union[np.ndarray, List[int]], - analyzed_parameters: Optional[List[Parameter]] = None, ) -> None: - super().__init__( - varying_parameters=varying_parameters, - objectives=objectives, - analyzed_parameters=analyzed_parameters, - ) - self._check_inputs(varying_parameters, objectives, n_steps) + super().__init__(vocs=vocs) + self._check_inputs(vocs, n_steps) self._n_steps = n_steps if n_steps is np.ndarray else np.array(n_steps) self._create_configurations() + def _validate_vocs(self, vocs: VOCS) -> None: + super()._validate_vocs(vocs) + for var_name, var_spec in vocs.variables.items(): + if var_spec.default_value is None: + raise ValueError( + f"Variable '{var_name}' does not have a default value. " + ) + def _check_inputs( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], + vocs: VOCS, n_steps: int, ) -> None: """Check that the generator inputs are valid.""" # Check as many n_steps as varying_parameters are provided. assert len(n_steps) == len( - varying_parameters + self.varying_parameters ), "Length of `n_steps` ({}) and ".format( len(n_steps) ) + "`varying_parameters` ({}) do not match.".format( - len(varying_parameters) + len(self.varying_parameters) ) - # Check that all varying parameters have a default value. - for var in varying_parameters: - assert ( - var.default_value is not None - ), "Parameter {} does not have a default value.".format(var.name) def _create_configurations(self) -> None: """Create a list will all configurations to be evaluated.""" diff --git a/optimas/generators/random_sampling.py b/optimas/generators/random_sampling.py index 9c88646c..00aeb398 100644 --- a/optimas/generators/random_sampling.py +++ b/optimas/generators/random_sampling.py @@ -5,6 +5,7 @@ import numpy as np from optimas.core import Objective, Trial, VaryingParameter, Parameter +from generator_standard.vocs import VOCS from .base import Generator @@ -16,10 +17,8 @@ class RandomSamplingGenerator(Generator): Parameters ---------- - varying_parameters : list of VaryingParameter - List of input parameters to vary. - objectives : list of Objective - List of optimization objectives. + vocs : VOCS + VOCS object specifying variables, objectives, constraints, and observables. distribution : {'uniform', 'normal'}, optional The random distribution to use. The ``'uniform'`` option draws samples from a uniform distribution within the lower :math:`l_b` and upper @@ -29,30 +28,21 @@ class RandomSamplingGenerator(Generator): :math:`\sigma = u_b - c`. By default, ``'uniform'``. seed : int, optional Seed to initialize the random generator. - analyzed_parameters : list of Parameter, optional - List of parameters to analyze at each trial, but which are not - optimization objectives. By default ``None``. """ def __init__( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], + vocs: VOCS, distribution: Optional[str] = "uniform", seed: Optional[int] = None, - analyzed_parameters: Optional[List[Parameter]] = None, ) -> None: - super().__init__( - varying_parameters, - objectives, - analyzed_parameters=analyzed_parameters, - ) + super().__init__(vocs=vocs) self._generate_sampling = { "uniform": self._generate_uniform_sampling, "normal": self._generate_normal_sampling, } - self._check_inputs(varying_parameters, objectives, distribution) + self._check_inputs(vocs, distribution) self._distribution = distribution self._rng = np.random.default_rng(seed) self._define_generator_parameters() @@ -70,8 +60,7 @@ def suggest(self, num_points: Optional[int]) -> List[dict]: def _check_inputs( self, - varying_parameters: List[VaryingParameter], - objectives: List[Objective], + vocs: VOCS, distribution: str, ) -> None: """Check that the generator inputs are valid.""" diff --git a/tests/test_analyzed_parameters.py b/tests/test_analyzed_parameters.py index ea5945a1..c39fa2da 100644 --- a/tests/test_analyzed_parameters.py +++ b/tests/test_analyzed_parameters.py @@ -1,4 +1,5 @@ import numpy as np +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -21,21 +22,14 @@ def test_analyzed_parameters(): Test that an exploration runs successfully when including not only an objective, but also a set of additional analyzed parameters. """ - # Define varying parameters. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - - # Define objective and other parameters to analyze. - obj = Objective("f", minimize=False) - par1 = Parameter("analyzed_parameter_1") - par2 = Parameter("analyzed_parameter_2") + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + observables=["analyzed_parameter_1", "analyzed_parameter_2"], + ) # Create generator. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[par1, par2], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create function evaluator. ev = FunctionEvaluator(function=eval_func) @@ -77,21 +71,14 @@ def test_analyzed_parameters_from_history(): values of the analyzed parameters in the history file are correctly loaded back into the exploration. """ - # Define varying parameters. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - - # Define objective and other parameters to analyze. - obj = Objective("f", minimize=False) - par1 = Parameter("analyzed_parameter_1") - par2 = Parameter("analyzed_parameter_2") + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + observables=["analyzed_parameter_1", "analyzed_parameter_2"], + ) # Create generator. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[par1, par2], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create function evaluator. ev = FunctionEvaluator(function=eval_func) diff --git a/tests/test_ax_generators.py b/tests/test_ax_generators.py index fe1992a0..5c5b9cb8 100644 --- a/tests/test_ax_generators.py +++ b/tests/test_ax_generators.py @@ -14,7 +14,8 @@ AxClientGenerator, ) from optimas.evaluators import FunctionEvaluator, MultitaskEvaluator -from optimas.core import VaryingParameter, Objective, Task, Parameter +from optimas.core import Task +from generator_standard.vocs import VOCS # Some tests will use threading (instead of multiprocessing) to be able to @@ -151,17 +152,15 @@ def test_ax_single_fidelity(): trial_count = 0 trials_to_fail = [2, 6] - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - p1 = Parameter("p1") + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + constraints={"p1": ["LESS_THAN", 30.0]}, + ) gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[p1], + vocs=vocs, parameter_constraints=["x0 + x1 <= 10"], - outcome_constraints=["p1 <= 30"], ) ev = FunctionEvaluator(function=eval_func_sf) exploration = Exploration( @@ -190,7 +189,7 @@ def test_ax_single_fidelity(): assert all(history["x0"] + history["x1"] <= 10.0 + 1e-3) ocs = gen._ax_client.experiment.optimization_config.outcome_constraints assert len(ocs) == 1 - assert ocs[0].metric.name == p1.name + assert ocs[0].metric.name == "p1" # Save history for later restart test np.save("./tests_output/ax_sf_history", exploration._libe_history.H) @@ -213,17 +212,15 @@ def test_ax_single_fidelity_resume(): fit_out_of_design_vals = [False, True] for fit_out_of_design in fit_out_of_design_vals: - var1 = VaryingParameter("x0", 5.1, 6.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - p1 = Parameter("p1") + vocs = VOCS( + variables={"x0": [5.1, 6.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + constraints={"p1": ["LESS_THAN", 30.0]}, + ) gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[p1], + vocs=vocs, parameter_constraints=["x0 + x1 <= 10"], - outcome_constraints=["p1 <= 30"], fit_out_of_design=fit_out_of_design, ) ev = FunctionEvaluator(function=eval_func_sf) @@ -278,13 +275,13 @@ def test_ax_single_fidelity_int(): trial_count = 0 trials_to_fail = [2, 6] - var1 = VaryingParameter("x0", -50.0, 5.0, dtype=int) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - - gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], objectives=[obj] + # TODO: Suggest supporting IntegerVariables in vocs + vocs = VOCS( + variables={"x0": set(range(-50, 6)), "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, ) + + gen = AxSingleFidelityGenerator(vocs=vocs) ev = FunctionEvaluator(function=eval_func_sf) exploration = Exploration( generator=gen, @@ -319,14 +316,12 @@ def test_ax_single_fidelity_moo(): trial_count = 0 trials_to_fail = [2, 6] - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - obj2 = Objective("f2", minimize=False) - - gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], objectives=[obj, obj2] + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE", "f2": "MAXIMIZE"}, ) + + gen = AxSingleFidelityGenerator(vocs=vocs) ev = FunctionEvaluator(function=eval_func_sf_moo) exploration = Exploration( generator=gen, @@ -360,13 +355,12 @@ def test_ax_single_fidelity_fb(): trial_count = 0 trials_to_fail = [2, 6] - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - - gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], objectives=[obj], fully_bayesian=True + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, ) + + gen = AxSingleFidelityGenerator(vocs=vocs, fully_bayesian=True) ev = FunctionEvaluator(function=eval_func_sf) exploration = Exploration( generator=gen, @@ -398,16 +392,12 @@ def test_ax_single_fidelity_moo_fb(): trial_count = 0 trials_to_fail = [2, 6] - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - obj2 = Objective("f2", minimize=False) - - gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], - objectives=[obj, obj2], - fully_bayesian=True, + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE", "f2": "MAXIMIZE"}, ) + + gen = AxSingleFidelityGenerator(vocs=vocs, fully_bayesian=True) ev = FunctionEvaluator(function=eval_func_sf_moo) exploration = Exploration( generator=gen, @@ -439,18 +429,18 @@ def test_ax_single_fidelity_updated_params(): trial_count = 0 trials_to_fail = [] - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - - # Start with a fixed value of x0. - var1.fix_value(-10.0) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], - objectives=[obj], + vocs=vocs, fit_out_of_design=True, ) + + # Start with a fixed value of x0. + gen.fix_value("x0", -10.0) ev = FunctionEvaluator(function=eval_func_sf) exploration = Exploration( generator=gen, @@ -465,21 +455,18 @@ def test_ax_single_fidelity_updated_params(): assert all(exploration.history["x0"] == -10) # Free value of x0 and run 5 evals. - var1.free_value() - gen.update_parameter(var1) + gen.free_value("x0") exploration.run(n_evals=5) assert not all(exploration.history["x0"][-5:] == -10) # Update range of x0 and run 10 evals. - var1.update_range(-20.0, 0.0) - gen.update_parameter(var1) + gen.update_range("x0", -20.0, 0.0) exploration.run(n_evals=10) assert all(exploration.history["x0"][-10:] >= -20) assert all(exploration.history["x0"][-10:] <= 0.0) # Fix of x0 and run 5 evals. - var1.fix_value(-9) - gen.update_parameter(var1) + gen.fix_value("x0", -9) exploration.run(n_evals=5) assert all(exploration.history["x0"][-5:] == -9) @@ -488,8 +475,7 @@ def test_ax_single_fidelity_updated_params(): assert exploration.history["x0"].to_numpy()[-1] == -7 # Free value and run 3 evals. - var1.free_value() - gen.update_parameter(var1) + gen.free_value("x0") exploration.run(n_evals=3) assert all(exploration.history["x0"][-3:] != -9) @@ -505,20 +491,14 @@ def test_ax_multi_fidelity(): trial_count = 0 trials_to_fail = [2, 5] - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - var3 = VaryingParameter( - "res", 1.0, 8.0, is_fidelity=True, fidelity_target_value=8.0 + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0], "res": [1.0, 8.0]}, + objectives={"f": "MAXIMIZE"}, + constraints={"p1": ["LESS_THAN", 30.0]}, ) - obj = Objective("f", minimize=False) - p1 = Parameter("p1") - gen = AxMultiFidelityGenerator( - varying_parameters=[var1, var2, var3], - objectives=[obj], - analyzed_parameters=[p1], - outcome_constraints=["p1 <= 30"], - ) + gen = AxMultiFidelityGenerator(vocs=vocs) + gen.set_fidelity_param("res", fidelity_target_value=8.0) ev = FunctionEvaluator(function=eval_func_mf) exploration = Exploration( generator=gen, @@ -539,7 +519,7 @@ def test_ax_multi_fidelity(): # Check constraints. ocs = gen._ax_client.experiment.optimization_config.outcome_constraints assert len(ocs) == 1 - assert ocs[0].metric.name == p1.name + assert ocs[0].metric.name == "p1" # Perform checks. check_run_ax_service(ax_client, gen, exploration, len(trials_to_fail)) @@ -554,16 +534,20 @@ def test_ax_multi_fidelity(): def test_ax_multitask(): """Test that an exploration with a multitask generator runs""" - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={ + "x0": [-50.0, 5.0], + "x1": [-5.0, 15.0], + "trial_type": {"task_1", "task_2"}, + }, + objectives={"f": "MAXIMIZE"}, + ) task1 = Task("task_1", n_init=2, n_opt=1) task2 = Task("task_2", n_init=5, n_opt=3) gen = AxMultitaskGenerator( - varying_parameters=[var1, var2], - objectives=[obj], + vocs=vocs, hifi_task=task1, lofi_task=task2, ) @@ -666,16 +650,13 @@ def test_ax_single_fidelity_with_history(): trial_count = 0 trials_to_fail = [] - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - p1 = Parameter("p1") - - gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[p1], + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + constraints={"p1": ["LESS_THAN", 30.0]}, ) + + gen = AxSingleFidelityGenerator(vocs=vocs) ev = FunctionEvaluator(function=eval_func_sf) exploration = Exploration( generator=gen, @@ -709,19 +690,14 @@ def test_ax_multi_fidelity_with_history(): trial_count = 0 trials_to_fail = [] - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - var3 = VaryingParameter( - "res", 1.0, 8.0, is_fidelity=True, fidelity_target_value=8.0 + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0], "res": [1.0, 8.0]}, + objectives={"f": "MAXIMIZE"}, + constraints={"p1": ["LESS_THAN", 30.0]}, ) - obj = Objective("f", minimize=False) - p1 = Parameter("p1") - gen = AxMultiFidelityGenerator( - varying_parameters=[var1, var2, var3], - objectives=[obj], - analyzed_parameters=[p1], - ) + gen = AxMultiFidelityGenerator(vocs=vocs) + gen.set_fidelity_param("res", fidelity_target_value=8.0) ev = FunctionEvaluator(function=eval_func_mf) exploration = Exploration( generator=gen, @@ -749,16 +725,20 @@ def test_ax_multitask_with_history(): restarted from a history file """ - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={ + "x0": [-50.0, 5.0], + "x1": [-5.0, 15.0], + "trial_type": {"task_1", "task_2"}, + }, + objectives={"f": "MAXIMIZE"}, + ) task1 = Task("task_1", n_init=2, n_opt=1) task2 = Task("task_2", n_init=5, n_opt=3) gen = AxMultitaskGenerator( - varying_parameters=[var1, var2], - objectives=[obj], + vocs=vocs, hifi_task=task1, lofi_task=task2, ) @@ -783,17 +763,16 @@ def test_ax_service_init(): or evaluations are given. """ - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) n_init = 2 n_external = 4 for i in range(n_external): - gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], objectives=[obj], n_init=n_init - ) + gen = AxSingleFidelityGenerator(vocs=vocs, n_init=n_init) ev = FunctionEvaluator(function=eval_func_sf) exploration = Exploration( generator=gen, @@ -839,8 +818,7 @@ def test_ax_service_init(): # Test single case with `enforce_n_init=True` gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2], - objectives=[obj], + vocs=vocs, n_init=n_init, enforce_n_init=True, ) diff --git a/tests/test_ax_model_manager.py b/tests/test_ax_model_manager.py index 20fd2e46..89e7b67c 100644 --- a/tests/test_ax_model_manager.py +++ b/tests/test_ax_model_manager.py @@ -4,11 +4,11 @@ from matplotlib.gridspec import GridSpec from optimas.explorations import Exploration -from optimas.core import VaryingParameter, Objective from optimas.generators import AxSingleFidelityGenerator from optimas.evaluators import FunctionEvaluator from optimas.diagnostics import ExplorationDiagnostics from optimas.utils.ax import AxModelManager +from generator_standard.vocs import VOCS def eval_func_sf_moo(input_params, output_params): @@ -26,15 +26,13 @@ def test_ax_model_manager(): runs and that the generator and Ax client are updated after running. """ - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - var3 = VaryingParameter("x2", -5.0, 15.0) - obj = Objective("f", minimize=True) - obj2 = Objective("f2", minimize=False) - - gen = AxSingleFidelityGenerator( - varying_parameters=[var1, var2, var3], objectives=[obj, obj2] + # Create VOCS object + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0], "x2": [-5.0, 15.0]}, + objectives={"f": "MINIMIZE", "f2": "MAXIMIZE"}, ) + + gen = AxSingleFidelityGenerator(vocs=vocs) ev = FunctionEvaluator(function=eval_func_sf_moo) exploration = Exploration( generator=gen, @@ -88,8 +86,12 @@ def test_ax_model_manager(): gs = GridSpec(2, 2, wspace=0.2, hspace=0.3) # center coordinates - x1_c = 0.5 * (var2.lower_bound + var2.upper_bound) - x2_c = 0.5 * (var3.lower_bound + var3.upper_bound) + x1_c = 0.5 * ( + vocs.variables["x1"].domain[0] + vocs.variables["x1"].domain[1] + ) + x2_c = 0.5 * ( + vocs.variables["x2"].domain[0] + vocs.variables["x2"].domain[1] + ) # plot model for `f` with custom slice value fig, ax1 = mm_axcl.plot_contour( diff --git a/tests/test_chain_evaluator.py b/tests/test_chain_evaluator.py index 0e2ce139..9a3d067e 100644 --- a/tests/test_chain_evaluator.py +++ b/tests/test_chain_evaluator.py @@ -1,6 +1,7 @@ import os import numpy as np +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -26,17 +27,14 @@ def analysis_func_2(sim_dir, output_params): def test_chain_evaluator(): # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - par1 = Parameter("result_1") - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + observables=["result_1"], + ) # Define variables and objectives. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[par1], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create template evaluator. ev1 = TemplateEvaluator( @@ -78,14 +76,13 @@ def test_chain_evaluator_only_final_analysis(): """Test a ChainEvaluator where only the final TemplateEvaluator has an analysis function.""" # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) # Define variables and objectives. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], objectives=[obj] - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create template evaluator. ev1 = TemplateEvaluator( diff --git a/tests/test_comms.py b/tests/test_comms.py index bf60f5db..dcae8ac8 100644 --- a/tests/test_comms.py +++ b/tests/test_comms.py @@ -1,4 +1,5 @@ import numpy as np +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -17,17 +18,16 @@ def eval_func(input_params, output_params): def test_libe_comms(): """Test local and local_threading communications.""" # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) max_evals = 10 for comm in ["local", "local_threading"]: # Create generator. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], objectives=[obj] - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create function evaluator. ev = FunctionEvaluator(function=eval_func) diff --git a/tests/test_env_script.py b/tests/test_env_script.py index 4c87eb43..74471e1d 100644 --- a/tests/test_env_script.py +++ b/tests/test_env_script.py @@ -1,6 +1,7 @@ import os import numpy as np +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -21,17 +22,14 @@ def analysis_func(sim_dir, output_params): def test_env_script(): # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - test_var = Parameter("test_var", dtype="U10") + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + observables={"test_var": "U10"}, + ) # Define variables and objectives. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[test_var], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create template evaluator. ev = TemplateEvaluator( diff --git a/tests/test_exploration_diagnostics.py b/tests/test_exploration_diagnostics.py index c8b2edd7..c3f11878 100644 --- a/tests/test_exploration_diagnostics.py +++ b/tests/test_exploration_diagnostics.py @@ -4,6 +4,7 @@ from matplotlib.gridspec import GridSpec import matplotlib.pyplot as plt import pytest +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -29,15 +30,13 @@ def test_exploration_diagnostics(): exploration_dir_path = "./tests_output/test_exploration_diagnostics" # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f1", minimize=False) - obj2 = Objective("f2", minimize=True) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f1": "MAXIMIZE", "f2": "MINIMIZE"}, + ) # Create generator. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], objectives=[obj, obj2], seed=0 - ) + gen = RandomSamplingGenerator(vocs=vocs, seed=0) # Create template evaluator. ev = TemplateEvaluator( @@ -137,12 +136,12 @@ def test_exploration_diagnostics(): diags.print_best_evaluations(top=3, objective="f1") diags.print_evaluation(best_ev_f1["trial_index"].item()) - # Check that all 3 possible objective inputs give the same result. + # Check that all possible objective inputs give the same result. _, trace1 = diags.get_objective_trace() _, trace2 = diags.get_objective_trace("f1") - _, trace3 = diags.get_objective_trace(obj) + # _, trace3 = diags.get_objective_trace(obj) # Can be removed np.testing.assert_array_equal(trace1, trace2) - np.testing.assert_array_equal(trace1, trace3) + # np.testing.assert_array_equal(trace1, trace3) # Test making plot using the diagnostics API. fig, ax = plt.subplots() diff --git a/tests/test_exploration_resume.py b/tests/test_exploration_resume.py index f84c397c..7d42eec5 100644 --- a/tests/test_exploration_resume.py +++ b/tests/test_exploration_resume.py @@ -1,4 +1,5 @@ import os +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -17,14 +18,13 @@ def analysis_func(sim_dir, output_params): def test_exploration_in_steps(): """Test that an exploration runs correctly when doing so in several steps.""" # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) # Define variables and objectives. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], objectives=[obj] - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create template evaluator. ev = TemplateEvaluator( @@ -66,14 +66,13 @@ def test_exploration_in_steps_without_limit(): without a limit on the maximum number of evaluations. """ # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) # Define evaluator. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], objectives=[obj] - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create template evaluator. ev = TemplateEvaluator( @@ -108,14 +107,13 @@ def test_exploration_in_steps_without_limit(): def test_exploration_resume(): """Test that an exploration correctly resumes from a previous run.""" # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) # Define variables and objectives. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], objectives=[obj] - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create template evaluator. ev = TemplateEvaluator( diff --git a/tests/test_exploration_run_exception.py b/tests/test_exploration_run_exception.py index 96a3ebe7..d6c197f8 100644 --- a/tests/test_exploration_run_exception.py +++ b/tests/test_exploration_run_exception.py @@ -1,9 +1,9 @@ import os +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import FunctionEvaluator -from optimas.core import VaryingParameter, Objective def eval_func(input_params, output_params): @@ -18,15 +18,13 @@ def test_exception_during_exploration_run(): even if an exception occurs. """ # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) # Create generator. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create function evaluator. ev = FunctionEvaluator(function=eval_func, create_evaluation_dirs=True) diff --git a/tests/test_function_evaluator.py b/tests/test_function_evaluator.py index 2223aabc..219016c2 100644 --- a/tests/test_function_evaluator.py +++ b/tests/test_function_evaluator.py @@ -4,6 +4,7 @@ import numpy as np import matplotlib.pyplot as plt import pytest +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -44,20 +45,18 @@ def test_function_evaluator(): for create_dirs in create_dirs_options: # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - # Test also more complex analyzed parameters. - p0 = Parameter("p0", dtype=(float, (2, 4))) - p1 = Parameter("p1", dtype="O") - p2 = Parameter("fig", dtype="O") + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + observables={ + "p0": (float, (2, 4)), + "p1": "O", + "fig": "O", + }, + ) # Create generator. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[p0, p1, p2], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create function evaluator. ev = FunctionEvaluator( @@ -109,15 +108,13 @@ def test_function_evaluator_with_logs(): """Test a function evaluator with redirected stdout and stderr.""" # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) # Create generator. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create function evaluator. ev = FunctionEvaluator( diff --git a/tests/test_gpu_resources.py b/tests/test_gpu_resources.py index a7931550..4dff5609 100644 --- a/tests/test_gpu_resources.py +++ b/tests/test_gpu_resources.py @@ -4,7 +4,7 @@ set_start_method("spawn", force=True) import numpy as np -from optimas.core import VaryingParameter, Objective, Parameter +from generator_standard.vocs import VOCS from optimas.generators import AxSingleFidelityGenerator from optimas.evaluators import TemplateEvaluator from optimas.explorations import Exploration @@ -31,17 +31,16 @@ def create_exploration( gpu_id=0, exploration_dir_path="./exploration", ): - # Create varying parameters and objectives. - var_1 = VaryingParameter("x0", 0.0, 15.0) - var_2 = VaryingParameter("x1", 0.0, 15.0) - obj = Objective("f", minimize=True) - p0 = Parameter("cuda_visible_devices", dtype="U10") + # Create VOCS object + vocs = VOCS( + variables={"x0": [0.0, 15.0], "x1": [0.0, 15.0]}, + objectives={"f": "MINIMIZE"}, + observables={"cuda_visible_devices": "U10"}, + ) # Create generator. gen = AxSingleFidelityGenerator( - varying_parameters=[var_1, var_2], - objectives=[obj], - analyzed_parameters=[p0], + vocs=vocs, n_init=4, dedicated_resources=dedicated_resources, use_cuda=use_cuda, diff --git a/tests/test_grid_sampling.py b/tests/test_grid_sampling.py index c6a35850..d804fe3e 100644 --- a/tests/test_grid_sampling.py +++ b/tests/test_grid_sampling.py @@ -1,4 +1,5 @@ import numpy as np +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import GridSamplingGenerator @@ -18,24 +19,23 @@ def test_grid_sampling(): """Test that grid sampling generates the expected configurations.""" # Create varying parameters. - names = ["x0", "x1"] lower_bounds = [-3.0, 2.0] upper_bounds = [1.0, 5.0] - vars = [] n_steps = [7, 15] - for name, lb, ub in zip(names, lower_bounds, upper_bounds): - vars.append(VaryingParameter(name, lb, ub)) # Set number of evaluations. n_evals = np.prod(n_steps) - # Define objective. - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={ + "x0": [lower_bounds[0], upper_bounds[0]], + "x1": [lower_bounds[1], upper_bounds[1]], + }, + objectives={"f": "MAXIMIZE"}, + ) # Create generator and run exploration. - gen = GridSamplingGenerator( - varying_parameters=vars, objectives=[obj], n_steps=n_steps - ) + gen = GridSamplingGenerator(vocs=vocs, n_steps=n_steps) ev = FunctionEvaluator(function=eval_func) exploration = Exploration( generator=gen, diff --git a/tests/test_grid_sampling_mpi.py b/tests/test_grid_sampling_mpi.py index 5d8e1569..12e31200 100644 --- a/tests/test_grid_sampling_mpi.py +++ b/tests/test_grid_sampling_mpi.py @@ -1,5 +1,6 @@ import numpy as np import pytest +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import GridSamplingGenerator @@ -20,24 +21,23 @@ def test_grid_sampling(): """Test that grid sampling generates the expected configurations.""" # Create varying parameters. - names = ["x0", "x1"] lower_bounds = [-3.0, 2.0] upper_bounds = [1.0, 5.0] - vars = [] n_steps = [7, 15] - for name, lb, ub in zip(names, lower_bounds, upper_bounds): - vars.append(VaryingParameter(name, lb, ub)) # Set number of evaluations. n_evals = np.prod(n_steps) - # Define objective. - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={ + "x0": [lower_bounds[0], upper_bounds[0]], + "x1": [lower_bounds[1], upper_bounds[1]], + }, + objectives={"f": "MAXIMIZE"}, + ) # Create generator and run exploration. - gen = GridSamplingGenerator( - varying_parameters=vars, objectives=[obj], n_steps=n_steps - ) + gen = GridSamplingGenerator(vocs=vocs, n_steps=n_steps) ev = FunctionEvaluator(function=eval_func) exploration = Exploration( generator=gen, diff --git a/tests/test_line_sampling.py b/tests/test_line_sampling.py index 1b09bf89..b2ee3978 100644 --- a/tests/test_line_sampling.py +++ b/tests/test_line_sampling.py @@ -2,6 +2,7 @@ import numpy as np import pytest +from generator_standard.vocs import VOCS, ContinuousVariable from optimas.explorations import Exploration from optimas.generators import LineSamplingGenerator @@ -20,26 +21,31 @@ def eval_func(input_params, output_params): def test_line_sampling(): """Test that line sampling generates the expected configurations.""" - # Create varying parameters. - names = ["x0", "x1"] + # Define test parameters lower_bounds = [-3.0, 2.0] upper_bounds = [1.0, 5.0] defaults = [0, 0] n_steps = [7, 15] - vars = [] - for name, lb, ub, dv in zip(names, lower_bounds, upper_bounds, defaults): - vars.append(VaryingParameter(name, lb, ub, default_value=dv)) # Set number of evaluations. n_evals = np.sum(n_steps) - # Define objective. - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={ + "x0": ContinuousVariable( + domain=[lower_bounds[0], upper_bounds[0]], + default_value=defaults[0], + ), + "x1": ContinuousVariable( + domain=[lower_bounds[1], upper_bounds[1]], + default_value=defaults[1], + ), + }, + objectives={"f": "MAXIMIZE"}, + ) # Create generator and run exploration. - gen = LineSamplingGenerator( - varying_parameters=vars, objectives=[obj], n_steps=n_steps - ) + gen = LineSamplingGenerator(vocs=vocs, n_steps=n_steps) ev = FunctionEvaluator(function=eval_func) exploration = Exploration( generator=gen, @@ -74,27 +80,23 @@ def test_line_sampling(): def test_line_sampling_errors(): """Test that the line sampling raises the correct exceptions.""" - # Create varying parameters with missing default value. - var1 = VaryingParameter("x0", -3, 1) - var2 = VaryingParameter("x0", -3, 1) - - # Define objective. - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-3, 1], "x1": [-3, 1]}, objectives={"f": "MAXIMIZE"} + ) # Check that an exception is raised when default values are missing. with pytest.raises( - AssertionError, match="Parameter x0 does not have a default value." + ValueError, match="Variable 'x0' does not have a default value." ): - gen = LineSamplingGenerator( - varying_parameters=[var1, var2], objectives=[obj], n_steps=[3, 5] - ) - - # Create varying parameters. - var1 = VaryingParameter("x0", -3, 1, default_value=0.0) - var2 = VaryingParameter("x0", -3, 1, default_value=0.0) - - # Define objective. - obj = Objective("f", minimize=False) + gen = LineSamplingGenerator(vocs=vocs, n_steps=[3, 5]) + + vocs = VOCS( + variables={ + "x0": ContinuousVariable(domain=[-3, 1], default_value=0.0), + "x1": ContinuousVariable(domain=[-3, 1], default_value=0.0), + }, + objectives={"f": "MAXIMIZE"}, + ) # Check that an exception is raised when n_steps is not correct. with pytest.raises( @@ -104,9 +106,7 @@ def test_line_sampling_errors(): " `varying_parameters` (2) do not match." ), ): - gen = LineSamplingGenerator( - varying_parameters=[var1, var2], objectives=[obj], n_steps=[3] - ) + LineSamplingGenerator(vocs=vocs, n_steps=[3]) if __name__ == "__main__": diff --git a/tests/test_manual_exploration.py b/tests/test_manual_exploration.py index 1eb6de37..997e5121 100644 --- a/tests/test_manual_exploration.py +++ b/tests/test_manual_exploration.py @@ -1,5 +1,6 @@ import numpy as np import pandas as pd +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -20,20 +21,21 @@ def test_manual_exploration(): """Tests methods for manually attaching trials and evaluations.""" # Create varying parameters. - names = ["x0", "x1"] lower_bounds = [-3.0, 2.0] upper_bounds = [1.0, 5.0] - vars = [] - for name, lb, ub in zip(names, lower_bounds, upper_bounds): - vars.append(VaryingParameter(name, lb, ub)) - par1 = Parameter("par1") # Set number of evaluations. n_evals = 10 n_evals_substep = 3 - # Define objective. - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={ + "x0": [lower_bounds[0], upper_bounds[0]], + "x1": [lower_bounds[1], upper_bounds[1]], + }, + objectives={"f": "MAXIMIZE"}, + observables=["par1"], + ) # Trials to attach and evaluate manually. trials_to_attach = {"x0": [-2.3, 1.0, 0.5], "x1": [2.4, 1.0, 3.0]} @@ -55,9 +57,7 @@ def test_manual_exploration(): for i, (trials, evals) in enumerate(zip(test_trials, test_evals)): # Create generator and run exploration. gen = RandomSamplingGenerator( - varying_parameters=vars, - objectives=[obj], - analyzed_parameters=[par1], + vocs=vocs, distribution="uniform", seed=1, ) diff --git a/tests/test_random_sampling.py b/tests/test_random_sampling.py index 69c23be8..b8b0d58f 100644 --- a/tests/test_random_sampling.py +++ b/tests/test_random_sampling.py @@ -1,4 +1,5 @@ import numpy as np +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -22,23 +23,23 @@ def test_uniform_sampling(): seed = 1 # Create varying parameters. - names = ["x0", "x1"] lower_bounds = [-3.0, 2.0] upper_bounds = [1.0, 5.0] - vars = [] - for name, lb, ub in zip(names, lower_bounds, upper_bounds): - vars.append(VaryingParameter(name, lb, ub)) # Set number of evaluations. n_evals = 10 - # Define objective. - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={ + "x0": [lower_bounds[0], upper_bounds[0]], + "x1": [lower_bounds[1], upper_bounds[1]], + }, + objectives={"f": "MAXIMIZE"}, + ) # Create generator and run exploration. gen = RandomSamplingGenerator( - varying_parameters=vars, - objectives=[obj], + vocs=vocs, distribution="uniform", seed=1, ) @@ -60,7 +61,9 @@ def test_uniform_sampling(): # Generate expected points. rng = np.random.default_rng(seed=seed) - configs = rng.uniform(lower_bounds, upper_bounds, (n_evals, len(vars))) + configs = rng.uniform( + lower_bounds, upper_bounds, (n_evals, len(lower_bounds)) + ) x0_test = configs[:, 0] x1_test = configs[:, 1] @@ -77,23 +80,22 @@ def test_normal_sampling(): seed = 1 # Create varying parameters. - names = ["x0", "x1"] center = [0.0, 0.0] sigma = [1.0, 5.0] - vars = [] - for name, c, s in zip(names, center, sigma): - vars.append(VaryingParameter(name, c - s, c + s)) # Set number of evaluations. n_evals = 10 - # Define objective. - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={ + "x0": [center[0] - sigma[0], center[0] + sigma[0]], + "x1": [center[1] - sigma[1], center[1] + sigma[1]], + }, + objectives={"f": "MAXIMIZE"}, + ) # Create generator and run exploration. - gen = RandomSamplingGenerator( - varying_parameters=vars, objectives=[obj], distribution="normal", seed=1 - ) + gen = RandomSamplingGenerator(vocs=vocs, distribution="normal", seed=1) ev = FunctionEvaluator(function=eval_func) exploration = Exploration( generator=gen, @@ -112,7 +114,7 @@ def test_normal_sampling(): # Generate expected points. rng = np.random.default_rng(seed=seed) - configs = rng.normal(center, sigma, (n_evals, len(vars))) + configs = rng.normal(center, sigma, (n_evals, len(center))) x0_test = configs[:, 0] x1_test = configs[:, 1] diff --git a/tests/test_template_evaluator.py b/tests/test_template_evaluator.py index 171ae7ff..ce05cf43 100644 --- a/tests/test_template_evaluator.py +++ b/tests/test_template_evaluator.py @@ -2,6 +2,7 @@ import numpy as np import matplotlib.pyplot as plt +from generator_standard.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator @@ -24,20 +25,18 @@ def analysis_func(sim_dir, output_params): def test_template_evaluator(): # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) - # Test also more complex analyzed parameters. - p0 = Parameter("p0", dtype=(float, (2, 4))) - p1 = Parameter("p1", dtype="O") - p2 = Parameter("fig", dtype="O") + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + observables={ + "p0": (float, (2, 4)), + "p1": "O", + "fig": "O", + }, + ) # Define variables and objectives. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - analyzed_parameters=[p0, p1, p2], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create template evaluator. ev = TemplateEvaluator( @@ -87,15 +86,13 @@ def test_template_evaluator_timeout(): os.environ["OPTIMAS_TEST_SLEEP"] = "20" # Define variables and objectives. - var1 = VaryingParameter("x0", -50.0, 5.0) - var2 = VaryingParameter("x1", -5.0, 15.0) - obj = Objective("f", minimize=False) + vocs = VOCS( + variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0]}, + objectives={"f": "MAXIMIZE"}, + ) # Define variables and objectives. - gen = RandomSamplingGenerator( - varying_parameters=[var1, var2], - objectives=[obj], - ) + gen = RandomSamplingGenerator(vocs=vocs) # Create template evaluator with 1s timeout. ev = TemplateEvaluator(