diff --git a/.github/workflows/unix-noax.yml b/.github/workflows/unix-noax.yml index dd2b7309..5c772541 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/gest-api.git 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 d0cd1702..e807c210 100644 --- a/.github/workflows/unix-openmpi.yml +++ b/.github/workflows/unix-openmpi.yml @@ -32,6 +32,7 @@ jobs: conda install -c pytorch numpy pandas conda install -c conda-forge mpi4py openmpi=5.* pip install .[test] + pip install git+https://github.com/campa-consortium/gest-api.git - shell: bash -l {0} name: Run unit tests with openMPI run: | diff --git a/.github/workflows/unix.yml b/.github/workflows/unix.yml index 3092d169..c464ea9f 100644 --- a/.github/workflows/unix.yml +++ b/.github/workflows/unix.yml @@ -32,6 +32,7 @@ jobs: conda install -c pytorch numpy pandas conda install -c conda-forge mpi4py mpich pip install .[test] + pip install git+https://github.com/campa-consortium/gest-api.git - shell: bash -l {0} name: Run unit tests with MPICH run: | diff --git a/doc/environment.yaml b/doc/environment.yaml index dac040ba..0815571d 100644 --- a/doc/environment.yaml +++ b/doc/environment.yaml @@ -11,6 +11,7 @@ dependencies: - matplotlib - nbsphinx - numpydoc + - git+https://github.com/campa-consortium/gest-api.git - pydata-sphinx-theme - sphinx-copybutton - sphinx-design diff --git a/doc/source/user_guide/advanced_usage/build_gp_surrogates.ipynb b/doc/source/user_guide/advanced_usage/build_gp_surrogates.ipynb index edaa8f4c..b82f9971 100644 --- a/doc/source/user_guide/advanced_usage/build_gp_surrogates.ipynb +++ b/doc/source/user_guide/advanced_usage/build_gp_surrogates.ipynb @@ -11,7 +11,7 @@ "\n", "The :class:`~optimas.diagnostics.ExplorationDiagnostics` class\n", "provides a simple way of fitting a Gaussian process (GP) model to any of the\n", - "objectives or analyzed parameters of an ``optimas``\n", + "objectives or observables of an ``optimas``\n", ":class:`~optimas.explorations.Exploration`, independently of which generator\n", "was used. This is useful to get a better understanding of the underlying\n", "function, make predictions, etc.\n", @@ -29,7 +29,7 @@ "\n", "The following cell sets up and runs an optimization with two input parameters\n", "``x1`` and ``x2``, two objectives ``f1`` and ``f2``, and one additional\n", - "analyzed parameter ``p1``.\n", + "observable ``p1``.\n", "At each evaluation, the ``eval_func_sf_moo`` function is run,\n", "which assigns a value to each outcome parameter according to the analytical\n", "formulas\n", @@ -55,9 +55,9 @@ "source": [ "import numpy as np\n", "from optimas.explorations import Exploration\n", - "from optimas.core import VaryingParameter, Objective, Parameter\n", "from optimas.generators import RandomSamplingGenerator\n", "from optimas.evaluators import FunctionEvaluator\n", + "from gest_api.vocs import VOCS\n", "\n", "\n", "def eval_func_sf_moo(input_params, output_params):\n", @@ -70,17 +70,20 @@ " output_params[\"p1\"] = np.sin(x1) + np.cos(x2)\n", "\n", "\n", - "var1 = VaryingParameter(\"x1\", 0.0, 5.0)\n", - "var2 = VaryingParameter(\"x2\", -5.0, 5.0)\n", - "par1 = Parameter(\"p1\")\n", - "obj1 = Objective(\"f1\", minimize=True)\n", - "obj2 = Objective(\"f2\", minimize=False)\n", - "\n", - "gen = RandomSamplingGenerator(\n", - " varying_parameters=[var1, var2],\n", - " objectives=[obj1, obj2],\n", - " analyzed_parameters=[par1],\n", + "# Create VOCS object defining variables, objectives, and observables.\n", + "vocs = VOCS(\n", + " variables={\n", + " \"x1\": [0.0, 5.0],\n", + " \"x2\": [-5.0, 5.0],\n", + " },\n", + " objectives={\n", + " \"f1\": \"MINIMIZE\",\n", + " \"f2\": \"MAXIMIZE\",\n", + " },\n", + " observables=[\"p1\"],\n", ")\n", + "\n", + "gen = RandomSamplingGenerator(vocs=vocs)\n", "ev = FunctionEvaluator(function=eval_func_sf_moo)\n", "exploration = Exploration(\n", " generator=gen,\n", @@ -125,18 +128,18 @@ "raw_mimetype": "text/restructuredtext" }, "source": [ - "Building a GP model of each objective and analyzed parameter\n", - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", + "Building a GP model of each objective and observable\n", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", "\n", "To build a GP model, simply call\n", ":meth:`~optimas.diagnostics.Exploration.build_gp_model` on the diagnostics,\n", "indicating the name of the variable to which the model should be fitted.\n", - "This variable can be any ``objective`` or ``analyzed_parameter`` of the\n", + "This variable can be any ``objective`` or ``observable`` of the\n", "optimization.\n", "\n", - "Note that when building a surrogate model of an analyzed parameter, it is\n", + "Note that when building a surrogate model of an observable, it is\n", "required to provide a value to the ``minimize`` argument. This parameter\n", - "should therefore be ``True`` if lower values of the analyzed parameter are\n", + "should therefore be ``True`` if lower values of the observable are\n", "better than higher values. This information is necessary, e.g., for determining\n", "the best point in the model." ] @@ -147,7 +150,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Build one model for each objective and analyzed parameter.\n", + "# Build one model for each objective and observable.\n", "f1_model = diags.build_gp_model(\"f1\")\n", "f2_model = diags.build_gp_model(\"f2\")\n", "p1_model = diags.build_gp_model(\"p1\", minimize=False)" diff --git a/doc/source/user_guide/basic_usage/exploration_diagnostics.ipynb b/doc/source/user_guide/basic_usage/exploration_diagnostics.ipynb index de162a2f..2f380dd6 100644 --- a/doc/source/user_guide/basic_usage/exploration_diagnostics.ipynb +++ b/doc/source/user_guide/basic_usage/exploration_diagnostics.ipynb @@ -125,16 +125,17 @@ "import matplotlib.pyplot as plt\n", "\n", "fig, ax = plt.subplots()\n", - "vps = diags.varying_parameters\n", + "vocs = diags._exploration.generator.vocs\n", "df = diags.history\n", - "f1 = diags.objectives[0]\n", - "ax.axvline(vps[0].lower_bound)\n", - "ax.axvline(vps[0].upper_bound)\n", - "ax.set_xlabel(vps[0].name)\n", - "ax.axhline(vps[1].lower_bound)\n", - "ax.axhline(vps[1].upper_bound)\n", - "ax.set_ylabel(vps[1].name)\n", - "ax.scatter(df[vps[0].name], df[vps[1].name], c=df[f1.name])" + "vps = list(vocs.variables.keys())\n", + "f1 = list(vocs.objectives.keys())[0]\n", + "ax.axvline(vocs.variables[vps[0]].domain[0])\n", + "ax.axvline(vocs.variables[vps[0]].domain[1])\n", + "ax.set_xlabel(vps[0])\n", + "ax.axhline(vocs.variables[vps[1]].domain[0])\n", + "ax.axhline(vocs.variables[vps[1]].domain[1])\n", + "ax.set_ylabel(vps[1])\n", + "ax.scatter(df[vps[0]], df[vps[1]], c=df[f1])" ] } ], @@ -144,5 +145,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/examples/astra/run_optimization_serial_ASTRA.py b/examples/astra/run_optimization_serial_ASTRA.py index 434ca5a5..bf83a6b6 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 gest_api.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..29287464 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 gest_api.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..c3838c0e 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 gest_api.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..2de74516 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 gest_api.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..be62133f 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 gest_api.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..50a185aa 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 gest_api.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..31a38923 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 gest_api.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..5c8a1644 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 gest_api.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/hipace/template_simulation_script b/examples/hipace/template_simulation_script index ee6cf6e5..4a3c2efd 100644 --- a/examples/hipace/template_simulation_script +++ b/examples/hipace/template_simulation_script @@ -11,7 +11,9 @@ hipace.depos_order_xy = 2 hipace.dt = adaptive hipace.nt_per_betatron = 30 -geometry.is_periodic = true true false # Is periodic? +boundary.field = Periodic +boundary.particle = Periodic + geometry.prob_lo = -250.e-6 -250.e-6 -250.e-6 # physical domain geometry.prob_hi = 250.e-6 250.e-6 110.e-6 diff --git a/examples/ionization_injection/run_example.py b/examples/ionization_injection/run_example.py index 100f9d64..8e9445b6 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 gest_api.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..d5262b8d 100644 --- a/examples/ionization_injection_mf/run_example.py +++ b/examples/ionization_injection_mf/run_example.py @@ -13,38 +13,34 @@ 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 gest_api.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"], ) -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, ) +gen.set_fidelity_param("resolution", fidelity_target_value=4.0) # Create evaluator. diff --git a/examples/multi_stage/run_example.py b/examples/multi_stage/run_example.py index cea0aeb6..5bc86014 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 gest_api.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..6fb2f743 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 gest_api.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..f3a4175e 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 gest_api.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..36327a51 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 gest_api.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..13f0eb79 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 gest_api.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..e721b147 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.core import TrialParameter from optimas.generators.base import Generator +from gest_api.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..32cb49d6 100644 --- a/optimas/generators/ax/developer/multitask.py +++ b/optimas/generators/ax/developer/multitask.py @@ -59,14 +59,12 @@ from optimas.generators.ax.base import AxGenerator from optimas.core import ( TrialParameter, - VaryingParameter, - Objective, - Parameter, Task, Trial, TrialStatus, ) from .ax_metric import AxMetric +from gest_api.vocs import VOCS, DiscreteVariable # Define generator states. NOT_STARTED = "not_started" @@ -152,10 +150,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 +180,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 +190,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 +237,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 +279,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. + # trial_type is declared as a discrete variable in vocs + # and converted internally to a trial parameter. + 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 +303,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: @@ -303,7 +312,6 @@ def ingest(self, results: List[dict]) -> None: def _incorporate_external_data(self, trials: List[Trial]) -> None: """Incorporate external data (e.g., from history) into experiment.""" # Get trial indices. - # SH should have handling if ax_trial_ids are None... trial_indices = [] for trial in trials: trial_indices.append(trial.ax_trial_id) diff --git a/optimas/generators/ax/service/ax_client.py b/optimas/generators/ax/service/ax_client.py index ce6db0f0..11d2cb0a 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 gest_api.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..3b452632 100644 --- a/optimas/generators/ax/service/base.py +++ b/optimas/generators/ax/service/base.py @@ -19,9 +19,7 @@ from ax import Arm from optimas.core import ( - Objective, Trial, - VaryingParameter, Parameter, ) from optimas.generators.ax.base import AxGenerator @@ -30,6 +28,12 @@ convert_optimas_to_ax_parameters, convert_optimas_to_ax_objectives, ) +from gest_api.vocs import ( + VOCS, + LessThanConstraint, + GreaterThanConstraint, + BoundsConstraint, +) class AxServiceGenerator(AxGenerator): @@ -37,27 +41,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 +88,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 +102,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 +118,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 +379,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..dbbefb01 100644 --- a/optimas/generators/ax/service/multi_fidelity.py +++ b/optimas/generators/ax/service/multi_fidelity.py @@ -9,8 +9,8 @@ from ax.modelbridge.generation_strategy import GenerationStep from ax.modelbridge.registry import Models -from optimas.core import Objective, VaryingParameter, Parameter from .base import AxServiceGenerator +from gest_api.vocs import VOCS class AxMultiFidelityGenerator(AxServiceGenerator): @@ -18,17 +18,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 +65,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 +80,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 +129,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..2728cf89 100644 --- a/optimas/generators/ax/service/single_fidelity.py +++ b/optimas/generators/ax/service/single_fidelity.py @@ -5,8 +5,8 @@ from ax.modelbridge.generation_strategy import GenerationStep from ax.modelbridge.registry import Models -from optimas.core import Objective, VaryingParameter, Parameter from .base import AxServiceGenerator +from gest_api.vocs import VOCS class AxSingleFidelityGenerator(AxServiceGenerator): @@ -23,13 +23,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 +88,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 +104,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..14fe1524 100644 --- a/optimas/generators/base.py +++ b/optimas/generators/base.py @@ -20,25 +20,25 @@ TrialParameter, TrialStatus, ) +from gest_api.vocs import ( + VOCS, + ContinuousVariable, + DiscreteVariable, + MinimizeObjective, + ExploreObjective, +) +from gest_api.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 +73,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 +84,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 +111,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 +125,111 @@ 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 + 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(): + if isinstance(obj_type, ExploreObjective): + raise ValueError("EXPLORE is not supported in Optimas") + minimize = isinstance(obj_type, MinimizeObjective) + 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 = [] + for obs_name, obs_spec in self._vocs.observables.items(): + dtype = obs_spec.dtype + param = Parameter(name=obs_name, dtype=dtype) + 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..ddda75e7 100644 --- a/optimas/generators/grid_sampling.py +++ b/optimas/generators/grid_sampling.py @@ -4,7 +4,8 @@ import numpy as np -from optimas.core import Objective, Trial, VaryingParameter, Parameter +from optimas.core import Trial +from gest_api.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..a73ea997 100644 --- a/optimas/generators/line_sampling.py +++ b/optimas/generators/line_sampling.py @@ -4,7 +4,8 @@ import numpy as np -from optimas.core import Objective, Trial, VaryingParameter, Parameter +from optimas.core import Trial +from gest_api.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..c038200b 100644 --- a/optimas/generators/random_sampling.py +++ b/optimas/generators/random_sampling.py @@ -4,7 +4,8 @@ import numpy as np -from optimas.core import Objective, Trial, VaryingParameter, Parameter +from optimas.core import Trial +from gest_api.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..222781b2 100644 --- a/tests/test_analyzed_parameters.py +++ b/tests/test_analyzed_parameters.py @@ -1,9 +1,9 @@ import numpy as np +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import FunctionEvaluator -from optimas.core import VaryingParameter, Objective, Parameter def eval_func(input_params, output_params): @@ -21,21 +21,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 +70,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..57d566b5 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 gest_api.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] + # Integer variables are not supported in vocs (use discrete variable) + 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..f18800f9 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 gest_api.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..790adea1 100644 --- a/tests/test_chain_evaluator.py +++ b/tests/test_chain_evaluator.py @@ -1,11 +1,11 @@ import os import numpy as np +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import TemplateEvaluator, ChainEvaluator -from optimas.core import VaryingParameter, Objective, Parameter def analysis_func_1(sim_dir, output_params): @@ -26,17 +26,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 +75,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..f39ae418 100644 --- a/tests/test_comms.py +++ b/tests/test_comms.py @@ -1,9 +1,9 @@ import numpy as np +from gest_api.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): @@ -17,17 +17,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..4f1e4dfe 100644 --- a/tests/test_env_script.py +++ b/tests/test_env_script.py @@ -1,11 +1,11 @@ import os import numpy as np +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import TemplateEvaluator -from optimas.core import VaryingParameter, Objective, Parameter def analysis_func(sim_dir, output_params): @@ -21,17 +21,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..c02b5339 100644 --- a/tests/test_exploration_diagnostics.py +++ b/tests/test_exploration_diagnostics.py @@ -4,11 +4,11 @@ from matplotlib.gridspec import GridSpec import matplotlib.pyplot as plt import pytest +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import TemplateEvaluator -from optimas.core import VaryingParameter, Objective from optimas.diagnostics import ExplorationDiagnostics @@ -29,15 +29,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 +135,10 @@ 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) np.testing.assert_array_equal(trace1, trace2) - 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..dfc3d11e 100644 --- a/tests/test_exploration_resume.py +++ b/tests/test_exploration_resume.py @@ -1,9 +1,9 @@ import os +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import TemplateEvaluator -from optimas.core import VaryingParameter, Objective def analysis_func(sim_dir, output_params): @@ -17,14 +17,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 +65,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 +106,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..20a3a4f3 100644 --- a/tests/test_exploration_run_exception.py +++ b/tests/test_exploration_run_exception.py @@ -1,9 +1,9 @@ import os +from gest_api.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..3d331255 100644 --- a/tests/test_function_evaluator.py +++ b/tests/test_function_evaluator.py @@ -4,11 +4,11 @@ import numpy as np import matplotlib.pyplot as plt import pytest +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import FunctionEvaluator -from optimas.core import VaryingParameter, Objective, Parameter from optimas.diagnostics import ExplorationDiagnostics @@ -44,20 +44,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 +107,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..a3052925 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 gest_api.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..e454f346 100644 --- a/tests/test_grid_sampling.py +++ b/tests/test_grid_sampling.py @@ -1,9 +1,9 @@ import numpy as np +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import GridSamplingGenerator from optimas.evaluators import FunctionEvaluator -from optimas.core import VaryingParameter, Objective def eval_func(input_params, output_params): @@ -18,24 +18,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..20f93eed 100644 --- a/tests/test_grid_sampling_mpi.py +++ b/tests/test_grid_sampling_mpi.py @@ -1,10 +1,10 @@ import numpy as np import pytest +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import GridSamplingGenerator from optimas.evaluators import FunctionEvaluator -from optimas.core import VaryingParameter, Objective def eval_func(input_params, output_params): @@ -20,24 +20,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..b8b3c917 100644 --- a/tests/test_line_sampling.py +++ b/tests/test_line_sampling.py @@ -2,11 +2,11 @@ import numpy as np import pytest +from gest_api.vocs import VOCS, ContinuousVariable from optimas.explorations import Exploration from optimas.generators import LineSamplingGenerator from optimas.evaluators import FunctionEvaluator -from optimas.core import VaryingParameter, Objective def eval_func(input_params, output_params): @@ -20,26 +20,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 +79,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 +105,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..adc1f845 100644 --- a/tests/test_manual_exploration.py +++ b/tests/test_manual_exploration.py @@ -1,10 +1,10 @@ import numpy as np import pandas as pd +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import FunctionEvaluator -from optimas.core import VaryingParameter, Objective, Parameter def eval_func(input_params, output_params): @@ -20,20 +20,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 +56,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..ebf3e8a5 100644 --- a/tests/test_random_sampling.py +++ b/tests/test_random_sampling.py @@ -1,9 +1,9 @@ import numpy as np +from gest_api.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): @@ -22,23 +22,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 +60,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 +79,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 +113,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..9fc5301e 100644 --- a/tests/test_template_evaluator.py +++ b/tests/test_template_evaluator.py @@ -2,11 +2,11 @@ import numpy as np import matplotlib.pyplot as plt +from gest_api.vocs import VOCS from optimas.explorations import Exploration from optimas.generators import RandomSamplingGenerator from optimas.evaluators import TemplateEvaluator -from optimas.core import VaryingParameter, Objective, Parameter def analysis_func(sim_dir, output_params): @@ -24,20 +24,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 +85,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(