Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[WIP] Enable using Xopt generators in optimas #151

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions optimas/generators/xopt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .nelder_mead import NelderMeadGenerator
74 changes: 74 additions & 0 deletions optimas/generators/xopt/nelder_mead.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from typing import List, Optional

import numpy as np
import pandas as pd

from xopt import VOCS
from xopt.generators.scipy.neldermead import (
NelderMeadGenerator as XoptNelderMeadGenerator,
)

from optimas.core import (
Objective,
VaryingParameter,
Parameter,
Evaluation,
Trial,
)
from optimas.generators.base import Generator


class NelderMeadGenerator(Generator):
def __init__(
self,
varying_parameters: List[VaryingParameter],
objectives: List[Objective],
analyzed_parameters: Optional[List[Parameter]] = None,
save_model: Optional[bool] = False,
model_save_period: Optional[int] = 5,
model_history_dir: Optional[str] = "model_history",
) -> None:
super().__init__(
varying_parameters=varying_parameters,
objectives=objectives,
analyzed_parameters=analyzed_parameters,
save_model=save_model,
model_save_period=model_save_period,
model_history_dir=model_history_dir,
)
self._create_xopt_generator()

def _ask(self, trials: List[Trial]) -> List[Trial]:
n_trials = len(trials)
xopt_trials = self.xopt_gen.generate(n_trials)
if xopt_trials:
for trial, xopt_trial in zip(trials, xopt_trials):
trial.parameter_values = [
xopt_trial[var.name] for var in self.varying_parameters
]
return trials

def _tell(self, trials: List[Trial]) -> None:
# pd.DataFrame({"x1": [0.5], "x2": [5.0], "y1": [0.5], "c1": [0.5]})
for trial in trials:
data = {}
for oe in trial.objective_evaluations:
data[oe.parameter.name] = [oe.value]
df = pd.DataFrame(data)
self.xopt_gen.add_data(df)

def _create_xopt_generator(self):
variables = {}
for var in self.varying_parameters:
variables[var.name] = [var.lower_bound, var.upper_bound]
objectives = {}
for objective in self.objectives:
name = objective.name
objectives[name] = "MINIMIZE" if objective.minimize else "MAXIMIZE"
vocs = VOCS(variables=variables, objectives=objectives)
initial_point = {}
for var in self.varying_parameters:
initial_point[var.name] = (var.lower_bound + var.upper_bound) / 2
self.xopt_gen = XoptNelderMeadGenerator(
vocs=vocs, initial_point=initial_point
)
68 changes: 68 additions & 0 deletions tests/test_neldermead.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import numpy as np

from optimas.explorations import Exploration
from optimas.generators.xopt import NelderMeadGenerator
from optimas.evaluators import FunctionEvaluator
from optimas.core import VaryingParameter, Objective


def eval_func(input_params, output_params):
"""Evaluation function for single-fidelity test"""
x0 = input_params["x0"]
x1 = input_params["x1"]
result = -(x0 + 10 * np.cos(x0)) * (x1 + 5 * np.cos(x1))
output_params["f"] = result


def test_neldermead():
"""Test that grid sampling generates the expected configurations."""

# Create varying parameters.
names = ["x0", "x1"]
lower_bounds = [-3.0, 2.0]
upper_bounds = [1.0, 5.0]
vars = []
n_steps = [7, 15]
for name, lb, ub in zip(names, lower_bounds, upper_bounds):
vars.append(VaryingParameter(name, lb, ub))

# Set number of evaluations.
n_evals = np.prod(n_steps)

# Define objective.
obj = Objective("f", minimize=False)

# Create generator and run exploration.
gen = NelderMeadGenerator(varying_parameters=vars, objectives=[obj])
ev = FunctionEvaluator(function=eval_func)
exploration = Exploration(
generator=gen,
evaluator=ev,
max_evals=n_evals,
sim_workers=1,
exploration_dir_path="./tests_output/test_neldermead",
)
exploration.run()

# Get generated points.
h = exploration.history
h = h[h["sim_ended"]]
x0_gen = h["x0"]
x1_gen = h["x1"]

# Get expected 1D steps along each variable.
x0_steps = np.linspace(lower_bounds[0], upper_bounds[0], n_steps[0])
x1_steps = np.linspace(lower_bounds[1], upper_bounds[1], n_steps[1])

# Check that the scan along each variable is as expected.
np.testing.assert_array_equal(np.unique(x0_gen), x0_steps)
np.testing.assert_array_equal(np.unique(x1_gen), x1_steps)

# Check that for every x0 step, the expected x1 steps are performed.
for x0_step in x0_steps:
x1_in_x0_step = x1_gen[x0_gen == x0_step]
np.testing.assert_array_equal(x1_in_x0_step, x1_steps)


if __name__ == "__main__":
test_neldermead()
Loading