From 32d2911858664c3694507823a57d9874b5538e6e Mon Sep 17 00:00:00 2001 From: Peter Verveer Date: Thu, 6 Feb 2025 14:21:41 +0000 Subject: [PATCH 1/4] Update transform object usage --- pyproject.toml | 2 +- src/ert/run_models/everest_run_model.py | 2 +- src/everest/everest_storage.py | 13 ++++--------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 715e487ee67..4db2f46052f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,7 +138,7 @@ everest = [ "decorator", "resdata", "colorama", - "ropt[pandas]>=0.12,<0.13", + "ropt[pandas]>=0.13,<0.14", "ropt-dakota>=0.11,<0.12", ] diff --git a/src/ert/run_models/everest_run_model.py b/src/ert/run_models/everest_run_model.py index 810912b407b..aaf1e0f607a 100644 --- a/src/ert/run_models/everest_run_model.py +++ b/src/ert/run_models/everest_run_model.py @@ -195,7 +195,7 @@ def run_experiment( output_dir=Path(self._everest_config.optimization_output_dir), ) self.ever_storage.init(self._everest_config) - self.ever_storage.observe_optimizer(optimizer, self._opt_model_transforms) + self.ever_storage.observe_optimizer(optimizer) # Run the optimization: optimizer_exit_code = optimizer.run().exit_code diff --git a/src/everest/everest_storage.py b/src/everest/everest_storage.py index 078dc681197..cc57ae2545c 100644 --- a/src/everest/everest_storage.py +++ b/src/everest/everest_storage.py @@ -387,12 +387,10 @@ def read_from_output_dir(self) -> None: exp = _OptimizerOnlyExperiment(self._output_dir) self.data.read_from_experiment(exp) - def observe_optimizer( - self, optimizer: BasicOptimizer, transforms: OptModelTransforms - ) -> None: + def observe_optimizer(self, optimizer: BasicOptimizer) -> None: optimizer.add_observer( EventType.FINISHED_EVALUATION, - partial(self._on_batch_evaluation_finished, transforms=transforms), + partial(self._on_batch_evaluation_finished), ) optimizer.add_observer( EventType.FINISHED_OPTIMIZER_STEP, self._on_optimization_finished @@ -661,14 +659,11 @@ def _store_gradient_results(self, results: GradientResults) -> _GradientResults: "perturbation_constraints": perturbation_constraints, } - def _on_batch_evaluation_finished( - self, event: Event, transforms: OptModelTransforms - ) -> None: + def _on_batch_evaluation_finished(self, event: Event) -> None: logger.debug("Storing batch results dataframes") converted_results = tuple( - convert_to_maximize(result).transform_back(transforms) - for result in event.data.get("results", []) + convert_to_maximize(result) for result in event.data.get("results", []) ) results: list[FunctionResults | GradientResults] = [] From 29b83190b906abb47b290d29c9531675fddf5132 Mon Sep 17 00:00:00 2001 From: Peter Verveer Date: Thu, 6 Feb 2025 14:21:41 +0000 Subject: [PATCH 2/4] Refactor _forward_model_evaluator --- src/ert/run_models/everest_run_model.py | 121 ++++++++++++++---------- 1 file changed, 73 insertions(+), 48 deletions(-) diff --git a/src/ert/run_models/everest_run_model.py b/src/ert/run_models/everest_run_model.py index aaf1e0f607a..2569a363738 100644 --- a/src/ert/run_models/everest_run_model.py +++ b/src/ert/run_models/everest_run_model.py @@ -250,19 +250,28 @@ def _on_before_forward_model_evaluation( logging.getLogger(EVEREST).info("User abort requested.") optimizer.abort_optimization() - def _forward_model_evaluator( - self, control_values: NDArray[np.float64], evaluator_context: EvaluatorContext - ) -> EvaluatorResult: + def _run_forward_model( + self, + control_values: NDArray[np.float64], + realizations: list[int], + active_control_vectors: list[bool] | None = None, + ) -> tuple[NDArray[np.float64], NDArray[np.float64] | None, list[int]]: # Reset the current run status: self._status = None # Get cached_results: - cached_results = self._get_cached_results(control_values, evaluator_context) + cached_results = self._get_cached_results(control_values, realizations) + + # Collect the indices of the controls that must be evaluated in the batch: + evaluated_control_indices = [ + idx + for idx in range(control_values.shape[0]) + if idx not in cached_results + and (active_control_vectors is None or active_control_vectors[idx]) + ] # Create the batch to run: - batch_data = self._init_batch_data( - control_values, evaluator_context, cached_results - ) + batch_data = self._init_batch_data(control_values, evaluated_control_indices) # Initialize a new ensemble in storage: assert self._experiment is not None @@ -274,7 +283,7 @@ def _forward_model_evaluator( self._setup_sim(sim_id, controls, ensemble) # Evaluate the batch: - run_args = self._get_run_args(ensemble, evaluator_context, batch_data) + run_args = self._get_run_args(ensemble, realizations, batch_data) self._context_env.update( { "_ERT_EXPERIMENT_ID": str(ensemble.experiment_id), @@ -290,33 +299,69 @@ def _forward_model_evaluator( # Gather the results and create the result for ropt: results = self._gather_simulation_results(ensemble) - evaluator_result = self._make_evaluator_result( + objectives, constraints = self._get_objectives_and_constraints( control_values, batch_data, results, cached_results ) # Add the results from the evaluations to the cache: self._add_results_to_cache( - control_values, - evaluator_context, - batch_data, - evaluator_result.objectives, - evaluator_result.constraints, + control_values, realizations, batch_data, objectives, constraints ) # Increase the batch ID for the next evaluation: self._batch_id += 1 - return evaluator_result + # Return the results, together with the indices of the evaluated controls: + return objectives, constraints, evaluated_control_indices - def _get_cached_results( + def _forward_model_evaluator( self, control_values: NDArray[np.float64], evaluator_context: EvaluatorContext + ) -> EvaluatorResult: + control_indices = list(range(control_values.shape[0])) + realizations = [ + self._everest_config.model.realizations[evaluator_context.realizations[idx]] + for idx in control_indices + ] + active_control_vectors = [ + evaluator_context.active is None + or bool(evaluator_context.active[evaluator_context.realizations[idx]]) + for idx in control_indices + ] + batch_id = self._batch_id # Save the batch ID, it will be modified. + objectives, constraints, evaluated_control_indices = self._run_forward_model( + control_values, realizations, active_control_vectors + ) + + # The simulation id's are a simple enumeration over the evaluated + # forward models. For the evaluated controls they are therefore + # implicitly given by there position in the evaluated_control_indices + # list. We store for each control vector that id, or -1 if it was not + # evaluated: + sim_ids = np.fromiter( + ( + evaluated_control_indices.index(idx) + if idx in evaluated_control_indices + else -1 + for idx in control_indices + ), + dtype=np.intc, + ) + + return EvaluatorResult( + objectives=objectives, + constraints=constraints, + batch_id=batch_id, + evaluation_ids=sim_ids, + ) + + def _get_cached_results( + self, control_values: NDArray[np.float64], realizations: list[int] ) -> dict[int, Any]: cached_results: dict[int, Any] = {} if self._simulator_cache is not None: - for control_idx, real_idx in enumerate(evaluator_context.realizations): + for control_idx, realization in enumerate(realizations): cached_data = self._simulator_cache.get( - self._everest_config.model.realizations[real_idx], - control_values[control_idx, :], + realization, control_values[control_idx, :] ) if cached_data is not None: cached_results[control_idx] = cached_data @@ -325,8 +370,7 @@ def _get_cached_results( def _init_batch_data( self, control_values: NDArray[np.float64], - evaluator_context: EvaluatorContext, - cached_results: dict[int, Any], + controls_to_evaluate: list[int], ) -> dict[int, dict[str, Any]]: def _add_controls( controls_config: list[ControlConfig], values: NDArray[np.float64] @@ -348,15 +392,9 @@ def _add_controls( batch_data_item[control.name] = control_dict return batch_data_item - active = evaluator_context.active - realizations = evaluator_context.realizations return { idx: _add_controls(self._everest_config.controls, control_values[idx, :]) - for idx in range(control_values.shape[0]) - if ( - idx not in cached_results - and (active is None or active[realizations[idx]]) - ) + for idx in controls_to_evaluate } def _setup_sim( @@ -416,18 +454,14 @@ def _check_suffix( def _get_run_args( self, ensemble: Ensemble, - evaluator_context: EvaluatorContext, + realizations: list[int], batch_data: dict[int, Any], ) -> list[RunArg]: substitutions = self._substitutions substitutions[""] = ensemble.name self.active_realizations = [True] * len(batch_data) for sim_id, control_idx in enumerate(batch_data.keys()): - substitutions[f""] = str( - self._everest_config.model.realizations[ - evaluator_context.realizations[control_idx] - ] - ) + substitutions[f""] = str(realizations[control_idx]) run_paths = Runpaths( jobname_format=self._model_config.jobname_format_string, runpath_format=self._model_config.runpath_format_string, @@ -483,13 +517,13 @@ def _gather_simulation_results( result[fnc_name] = result[alias] return results - def _make_evaluator_result( + def _get_objectives_and_constraints( self, control_values: NDArray[np.float64], batch_data: dict[int, Any], results: list[dict[str, NDArray[np.float64]]], cached_results: dict[int, Any], - ) -> EvaluatorResult: + ) -> tuple[NDArray[np.float64], NDArray[np.float64] | None]: # We minimize the negative of the objectives: objectives = -self._get_simulation_results( results, self._everest_config.objective_names, control_values, batch_data @@ -514,14 +548,7 @@ def _make_evaluator_result( assert cached_constraints is not None constraints[control_idx, ...] = cached_constraints - sim_ids = np.full(control_values.shape[0], -1, dtype=np.intc) - sim_ids[list(batch_data.keys())] = np.arange(len(batch_data), dtype=np.intc) - return EvaluatorResult( - objectives=objectives, - constraints=constraints, - batch_id=self._batch_id, - evaluation_ids=sim_ids, - ) + return objectives, constraints @staticmethod def _get_simulation_results( @@ -542,7 +569,7 @@ def _get_simulation_results( def _add_results_to_cache( self, control_values: NDArray[np.float64], - evaluator_context: EvaluatorContext, + realizations: list[int], batch_data: dict[int, Any], objectives: NDArray[np.float64], constraints: NDArray[np.float64] | None, @@ -550,9 +577,7 @@ def _add_results_to_cache( if self._simulator_cache is not None: for control_idx in batch_data: self._simulator_cache.add( - self._everest_config.model.realizations[ - evaluator_context.realizations[control_idx] - ], + realizations[control_idx], control_values[control_idx, ...], objectives[control_idx, ...], None if constraints is None else constraints[control_idx, ...], From 00b8f853a4ae986f1be50e065c022f4a1a46b210 Mon Sep 17 00:00:00 2001 From: Peter Verveer Date: Fri, 7 Feb 2025 09:36:04 +0000 Subject: [PATCH 3/4] Use a scaler object to implement objective scaling --- src/ert/run_models/everest_run_model.py | 43 ++++++++++++++--- src/everest/everest_storage.py | 1 - src/everest/optimizer/everest2ropt.py | 6 --- src/everest/optimizer/opt_model_transforms.py | 48 +++++++++++++++++-- .../config_multiobj.yml/ropt_config.json | 2 +- tests/everest/test_math_func.py | 27 ++++++++++- tests/everest/test_ropt_initialization.py | 32 +++++++++++-- 7 files changed, 134 insertions(+), 25 deletions(-) diff --git a/src/ert/run_models/everest_run_model.py b/src/ert/run_models/everest_run_model.py index 2569a363738..cd325478e8b 100644 --- a/src/ert/run_models/everest_run_model.py +++ b/src/ert/run_models/everest_run_model.py @@ -21,6 +21,7 @@ from ropt.evaluator import EvaluatorContext, EvaluatorResult from ropt.plan import BasicOptimizer from ropt.plan import Event as OptimizerEvent +from ropt.transforms import OptModelTransforms from typing_extensions import TypedDict from _ert.events import EESnapshot, EESnapshotUpdate, Event @@ -29,9 +30,13 @@ from ert.runpaths import Runpaths from ert.storage import open_storage from everest.config import ControlConfig, ControlVariableGuessListConfig, EverestConfig +from everest.config.utils import FlattenedControls from everest.everest_storage import EverestStorage, OptimalResult from everest.optimizer.everest2ropt import everest2ropt -from everest.optimizer.opt_model_transforms import get_opt_model_transforms +from everest.optimizer.opt_model_transforms import ( + ObjectiveScaler, + get_optimization_domain_transforms, +) from everest.simulator.everest_to_ert import everest_to_ert_config from everest.strings import EVEREST @@ -97,11 +102,6 @@ def __init__( ) self._everest_config = everest_config - self._opt_model_transforms = get_opt_model_transforms(everest_config.controls) - self._ropt_config = everest2ropt( - everest_config, transforms=self._opt_model_transforms - ) - self._sim_callback = simulation_callback self._opt_callback = optimization_callback self._fm_errors: dict[int, dict[str, Any]] = {} @@ -214,9 +214,38 @@ def run_experiment( case _: self._exit_code = EverestExitCode.COMPLETED + def _init_transforms(self, variables: NDArray[np.float64]) -> OptModelTransforms: + realizations = self._everest_config.model.realizations + nreal = len(realizations) + realization_weights = self._everest_config.model.realizations_weights + if realization_weights is None: + realization_weights = [1.0 / nreal] * nreal + transforms = get_optimization_domain_transforms( + self._everest_config.controls, + self._everest_config.objective_functions, + realization_weights, + ) + # If required, initialize auto-scaling: + assert isinstance(transforms.objectives, ObjectiveScaler) + if transforms.objectives.has_auto_scale: + objectives, _, _ = self._run_forward_model( + np.repeat(np.expand_dims(variables, axis=0), nreal, axis=0), + realizations, + ) + transforms.objectives.calculate_auto_scales(objectives) + return transforms + def _create_optimizer(self) -> BasicOptimizer: + # Initialize the optimization model transforms: + transforms = self._init_transforms( + np.asarray( + FlattenedControls(self._everest_config.controls).initial_guesses, + dtype=np.float64, + ) + ) optimizer = BasicOptimizer( - enopt_config=self._ropt_config, evaluator=self._forward_model_evaluator + enopt_config=everest2ropt(self._everest_config, transforms=transforms), + evaluator=self._forward_model_evaluator, ) # Before each batch evaluation we check if we should abort: diff --git a/src/everest/everest_storage.py b/src/everest/everest_storage.py index cc57ae2545c..4b8692d5693 100644 --- a/src/everest/everest_storage.py +++ b/src/everest/everest_storage.py @@ -14,7 +14,6 @@ from ropt.enums import EventType from ropt.plan import BasicOptimizer, Event from ropt.results import FunctionResults, GradientResults, convert_to_maximize -from ropt.transforms import OptModelTransforms from everest.config import EverestConfig from everest.strings import EVEREST diff --git a/src/everest/optimizer/everest2ropt.py b/src/everest/optimizer/everest2ropt.py index 0cb3540a019..ccd3b0238b7 100644 --- a/src/everest/optimizer/everest2ropt.py +++ b/src/everest/optimizer/everest2ropt.py @@ -59,8 +59,6 @@ def _parse_controls(controls: FlattenedControls, ropt_config): def _parse_objectives(objective_functions: list[ObjectiveFunctionConfig], ropt_config): - scales: list[float] = [] - auto_scale: list[bool] = [] weights: list[float] = [] function_estimator_indices: list[int] = [] function_estimators: list = [] @@ -68,8 +66,6 @@ def _parse_objectives(objective_functions: list[ObjectiveFunctionConfig], ropt_c for objective in objective_functions: assert isinstance(objective.name, str) weights.append(objective.weight or 1.0) - scales.append(objective.scale or 1.0) - auto_scale.append(objective.auto_scale or False) # If any objective specifies an objective type, we have to specify # function estimators in ropt to implement these types. This is done by @@ -95,8 +91,6 @@ def _parse_objectives(objective_functions: list[ObjectiveFunctionConfig], ropt_c ropt_config["objectives"] = { "weights": weights, - "scales": scales, - "auto_scale": auto_scale, } if function_estimators: # Only needed if we specified at least one objective type: diff --git a/src/everest/optimizer/opt_model_transforms.py b/src/everest/optimizer/opt_model_transforms.py index c96cff0332f..5171a9681bb 100644 --- a/src/everest/optimizer/opt_model_transforms.py +++ b/src/everest/optimizer/opt_model_transforms.py @@ -3,9 +3,9 @@ import numpy as np from numpy.typing import NDArray from ropt.transforms import OptModelTransforms -from ropt.transforms.base import VariableTransform +from ropt.transforms.base import ObjectiveTransform, VariableTransform -from everest.config import ControlConfig +from everest.config import ControlConfig, ObjectiveFunctionConfig from everest.config.utils import FlattenedControls @@ -48,7 +48,36 @@ def transform_linear_constraints( ) -def get_opt_model_transforms(controls: list[ControlConfig]) -> OptModelTransforms: +class ObjectiveScaler(ObjectiveTransform): + def __init__( + self, scales: list[float], auto_scales: list[bool], weights: list[float] + ) -> None: + self._scales = np.asarray(scales, dtype=np.float64) + self._auto_scales = np.asarray(auto_scales, dtype=np.bool_) + self._weights = np.asarray(weights, dtype=np.float64) + + def forward(self, objectives: NDArray[np.float64]) -> NDArray[np.float64]: + return objectives / self._scales + + def backward(self, objectives: NDArray[np.float64]) -> NDArray[np.float64]: + return objectives * self._scales + + def calculate_auto_scales(self, objectives: NDArray[np.float64]) -> None: + auto_scales = np.abs( + np.nansum(objectives * self._weights[:, np.newaxis], axis=0) + ) + self._scales[self._auto_scales] *= auto_scales[self._auto_scales] + + @property + def has_auto_scale(self) -> bool: + return bool(np.any(self._auto_scales)) + + +def get_optimization_domain_transforms( + controls: list[ControlConfig], + objectives: list[ObjectiveFunctionConfig], + weights: list[float], +) -> OptModelTransforms: flattened_controls = FlattenedControls(controls) return OptModelTransforms( variables=( @@ -60,5 +89,16 @@ def get_opt_model_transforms(controls: list[ControlConfig]) -> OptModelTransform ) if any(flattened_controls.auto_scales) else None - ) + ), + objectives=ObjectiveScaler( + [ + 1.0 if objective.scale is None else objective.scale + for objective in objectives + ], + [ + False if objective.auto_scale is None else objective.auto_scale + for objective in objectives + ], + weights, + ), ) diff --git a/tests/everest/snapshots/test_ropt_initialization/test_everest2ropt_snapshot/config_multiobj.yml/ropt_config.json b/tests/everest/snapshots/test_ropt_initialization/test_everest2ropt_snapshot/config_multiobj.yml/ropt_config.json index ed52242f8a0..1782492f2cf 100644 --- a/tests/everest/snapshots/test_ropt_initialization/test_everest2ropt_snapshot/config_multiobj.yml/ropt_config.json +++ b/tests/everest/snapshots/test_ropt_initialization/test_everest2ropt_snapshot/config_multiobj.yml/ropt_config.json @@ -42,7 +42,7 @@ ], "realization_filters": null, "scales": [ - 0.6666666667, + 1.0, 1.0 ], "weights": [ diff --git a/tests/everest/test_math_func.py b/tests/everest/test_math_func.py index 5695dcfc632..5b9bf6af95e 100644 --- a/tests/everest/test_math_func.py +++ b/tests/everest/test_math_func.py @@ -155,4 +155,29 @@ def test_math_func_auto_scaled_controls(copy_math_func_test_data_to_tmp): optim = -run_model.result.total_objective # distance is provided as -distance expected_dist = 0.25**2 + 0.25**2 assert expected_dist == pytest.approx(optim, abs=0.05) - assert expected_dist == pytest.approx(optim, abs=0.05) + + +@pytest.mark.integration_test +def test_math_func_auto_scaled_objectives(copy_math_func_test_data_to_tmp): + config = EverestConfig.load_file("config_multiobj.yml") + config_dict = config.model_dump(exclude_none=True) + + # Normalize only distance_p: + config_dict["objective_functions"][0]["auto_scale"] = True + config_dict["objective_functions"][0]["scale"] = 1.0 + + # We two batches, the first to do the auto-scaling, + # the second is the initial function evaluation: + config_dict["optimization"]["max_batch_num"] = 2 + + config = EverestConfig.model_validate(config_dict) + run_model = EverestRunModel.create(config) + evaluator_server_config = EvaluatorServerConfig() + run_model.run_experiment(evaluator_server_config) + optim = run_model.result.total_objective + + expected_p = 1.0 # normalized + expected_q = 4.75 # not normalized + total = -(expected_p * 0.5 + expected_q * 0.25) / (0.5 + 0.25) + + assert total == optim diff --git a/tests/everest/test_ropt_initialization.py b/tests/everest/test_ropt_initialization.py index bccca61922c..a19c7f74083 100644 --- a/tests/everest/test_ropt_initialization.py +++ b/tests/everest/test_ropt_initialization.py @@ -9,7 +9,7 @@ from everest.config import EverestConfig from everest.config_file_loader import yaml_file_to_substituted_config_dict from everest.optimizer.everest2ropt import everest2ropt -from everest.optimizer.opt_model_transforms import get_opt_model_transforms +from everest.optimizer.opt_model_transforms import get_optimization_domain_transforms from tests.everest.utils import relpath _CONFIG_DIR = relpath("test_data/mocked_test_case") @@ -44,7 +44,12 @@ def test_everest2ropt_controls_auto_scale(): controls[0].auto_scale = True controls[0].scaled_range = [0.3, 0.7] ropt_config = everest2ropt( - config, transforms=get_opt_model_transforms(config.controls) + config, + transforms=get_optimization_domain_transforms( + config.controls, + config.objective_functions, + config.model.realizations_weights, + ), ) assert np.allclose(ropt_config.variables.lower_bounds, 0.3) assert np.allclose(ropt_config.variables.upper_bounds, 0.7) @@ -56,7 +61,12 @@ def test_everest2ropt_variables_auto_scale(): controls[0].variables[1].auto_scale = True controls[0].variables[1].scaled_range = [0.3, 0.7] ropt_config = everest2ropt( - config, transforms=get_opt_model_transforms(config.controls) + config, + transforms=get_optimization_domain_transforms( + config.controls, + config.objective_functions, + config.model.realizations_weights, + ), ) assert ropt_config.variables.lower_bounds[0] == 0.0 assert ropt_config.variables.upper_bounds[0] == 0.1 @@ -122,7 +132,12 @@ def test_everest2ropt_controls_input_constraint_auto_scale(): scaled_coefficients[:2, 1] = coefficients[:2, 1] * 2.0 / 0.4 ropt_config = everest2ropt( - config, transforms=get_opt_model_transforms(config.controls) + config, + transforms=get_optimization_domain_transforms( + config.controls, + config.objective_functions, + config.model.realizations_weights, + ), ) assert np.allclose( ropt_config.linear_constraints.coefficients, @@ -260,7 +275,14 @@ def test_everest2ropt_snapshot(case, snapshot): config = EverestConfig.load_file( relpath(f"../../test-data/everest/math_func/{case}") ) - ropt_config = everest2ropt(config).model_dump() + ropt_config = everest2ropt( + config, + transforms=get_optimization_domain_transforms( + config.controls, + config.objective_functions, + config.model.realizations_weights, + ), + ).model_dump() def safe_default(obj): if isinstance(obj, np.ndarray): From 7f42b79632e8227696a7a64e7c5d46b73663f158 Mon Sep 17 00:00:00 2001 From: Peter Verveer Date: Fri, 7 Feb 2025 09:38:25 +0000 Subject: [PATCH 4/4] Handle minimization/maximization conversion with the scaler --- src/ert/run_models/everest_run_model.py | 3 +-- src/everest/everest_storage.py | 7 ++----- src/everest/optimizer/opt_model_transforms.py | 10 ++++++++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/ert/run_models/everest_run_model.py b/src/ert/run_models/everest_run_model.py index cd325478e8b..abecadeec88 100644 --- a/src/ert/run_models/everest_run_model.py +++ b/src/ert/run_models/everest_run_model.py @@ -553,8 +553,7 @@ def _get_objectives_and_constraints( results: list[dict[str, NDArray[np.float64]]], cached_results: dict[int, Any], ) -> tuple[NDArray[np.float64], NDArray[np.float64] | None]: - # We minimize the negative of the objectives: - objectives = -self._get_simulation_results( + objectives = self._get_simulation_results( results, self._everest_config.objective_names, control_values, batch_data ) diff --git a/src/everest/everest_storage.py b/src/everest/everest_storage.py index 4b8692d5693..524c358021d 100644 --- a/src/everest/everest_storage.py +++ b/src/everest/everest_storage.py @@ -13,7 +13,7 @@ import polars as pl from ropt.enums import EventType from ropt.plan import BasicOptimizer, Event -from ropt.results import FunctionResults, GradientResults, convert_to_maximize +from ropt.results import FunctionResults, GradientResults from everest.config import EverestConfig from everest.strings import EVEREST @@ -661,14 +661,11 @@ def _store_gradient_results(self, results: GradientResults) -> _GradientResults: def _on_batch_evaluation_finished(self, event: Event) -> None: logger.debug("Storing batch results dataframes") - converted_results = tuple( - convert_to_maximize(result) for result in event.data.get("results", []) - ) results: list[FunctionResults | GradientResults] = [] best_value = -np.inf best_results = None - for item in converted_results: + for item in event.data.get("results", []): if isinstance(item, GradientResults): results.append(item) if ( diff --git a/src/everest/optimizer/opt_model_transforms.py b/src/everest/optimizer/opt_model_transforms.py index 5171a9681bb..6073cd22463 100644 --- a/src/everest/optimizer/opt_model_transforms.py +++ b/src/everest/optimizer/opt_model_transforms.py @@ -56,11 +56,17 @@ def __init__( self._auto_scales = np.asarray(auto_scales, dtype=np.bool_) self._weights = np.asarray(weights, dtype=np.float64) + # The transform methods below all return the negative of the objectives. + # This is because Everest maximizes the objectives, while ropt is a minimizer. + def forward(self, objectives: NDArray[np.float64]) -> NDArray[np.float64]: - return objectives / self._scales + return -objectives / self._scales def backward(self, objectives: NDArray[np.float64]) -> NDArray[np.float64]: - return objectives * self._scales + return -objectives * self._scales + + def transform_weighted_objective(self, weighted_objective): + return -weighted_objective def calculate_auto_scales(self, objectives: NDArray[np.float64]) -> None: auto_scales = np.abs(