Skip to content

Commit

Permalink
Use a scaler object to re-implement control scaling
Browse files Browse the repository at this point in the history
  • Loading branch information
verveerpj committed Jan 31, 2025
1 parent a45fce0 commit 6e0574b
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 40 deletions.
9 changes: 6 additions & 3 deletions src/ert/run_models/everest_run_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from ert.storage import open_storage
from everest.config import ControlConfig, ControlVariableGuessListConfig, EverestConfig
from everest.everest_storage import EverestStorage, OptimalResult
from everest.optimizer.everest2ropt import everest2ropt
from everest.optimizer.everest2ropt import everest2ropt, get_ropt_transforms
from everest.simulator.everest_to_ert import everest_to_ert_config
from everest.strings import EVEREST

Expand Down Expand Up @@ -96,7 +96,10 @@ def __init__(
)

self._everest_config = everest_config
self._ropt_config = everest2ropt(everest_config)
self._ropt_transforms = get_ropt_transforms(everest_config)
self._ropt_config = everest2ropt(
everest_config, transforms=self._ropt_transforms
)

self._sim_callback = simulation_callback
self._opt_callback = optimization_callback
Expand Down Expand Up @@ -191,7 +194,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.ever_storage.observe_optimizer(optimizer, self._ropt_transforms)

# Run the optimization:
optimizer_exit_code = optimizer.run().exit_code
Expand Down
16 changes: 12 additions & 4 deletions src/everest/everest_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import traceback
from dataclasses import dataclass, field
from functools import partial
from pathlib import Path
from typing import Any, TypedDict, cast

Expand All @@ -13,6 +14,7 @@
from ropt.enums import EventType
from ropt.plan import BasicOptimizer, Event
from ropt.results import FunctionResults, GradientResults, convert_to_maximize
from ropt.transforms import Transforms

from everest.config import EverestConfig
from everest.strings import EVEREST
Expand Down Expand Up @@ -387,9 +389,12 @@ def read_from_output_dir(self) -> None:
exp = _OptimizerOnlyExperiment(self._output_dir)
self.data.read_from_experiment(exp)

def observe_optimizer(self, optimizer: BasicOptimizer) -> None:
def observe_optimizer(
self, optimizer: BasicOptimizer, transforms: Transforms
) -> None:
optimizer.add_observer(
EventType.FINISHED_EVALUATION, self._on_batch_evaluation_finished
EventType.FINISHED_EVALUATION,
partial(self._on_batch_evaluation_finished, transforms=transforms),
)
optimizer.add_observer(
EventType.FINISHED_OPTIMIZER_STEP, self._on_optimization_finished
Expand Down Expand Up @@ -658,11 +663,14 @@ def _store_gradient_results(self, results: GradientResults) -> _GradientResults:
"perturbation_constraints": perturbation_constraints,
}

def _on_batch_evaluation_finished(self, event: Event) -> None:
def _on_batch_evaluation_finished(
self, event: Event, transforms: Transforms
) -> None:
logger.debug("Storing batch results dataframes")

converted_results = tuple(
convert_to_maximize(result) for result in event.data.get("results", [])
convert_to_maximize(result).transform_back(transforms)
for result in event.data.get("results", [])
)
results: list[FunctionResults | GradientResults] = []

Expand Down
54 changes: 25 additions & 29 deletions src/everest/optimizer/everest2ropt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import os
from typing import Any

from ropt.config.enopt import EnOptConfig
from ropt.config.enopt import EnOptConfig, EnOptContext
from ropt.enums import ConstraintType, PerturbationType, VariableType
from ropt.transforms import Transforms

from everest.config import (
EverestConfig,
Expand All @@ -16,44 +17,20 @@
from everest.config.utils import FlattenedControls
from everest.strings import EVEREST

from .transforms import ControlScaler


def _parse_controls(controls: FlattenedControls, ropt_config):
control_types = [
None if type_ is None else VariableType[type_.upper()]
for type_ in controls.types
]
if all(item is None for item in controls.auto_scales):
offsets = None
scales = None
else:
scales = [
(ub - lb) / (sr[1] - sr[0]) if au else 1.0
for au, lb, ub, sr in zip(
controls.auto_scales,
controls.lower_bounds,
controls.upper_bounds,
controls.scaled_ranges,
strict=True,
)
]
offsets = [
lb - sr[0] * sc if au else 0.0
for au, lb, sc, sr in zip(
controls.auto_scales,
controls.lower_bounds,
scales,
controls.scaled_ranges,
strict=True,
)
]
indices = [idx for idx, is_enabled in enumerate(controls.enabled) if is_enabled]
ropt_config["variables"] = {
"types": None if all(item is None for item in control_types) else control_types,
"initial_values": controls.initial_guesses,
"lower_bounds": controls.lower_bounds,
"upper_bounds": controls.upper_bounds,
"offsets": offsets,
"scales": scales,
"indices": indices if indices else None,
}

Expand Down Expand Up @@ -367,7 +344,9 @@ def _parse_environment(
ropt_config["gradient"]["seed"] = random_seed


def everest2ropt(ever_config: EverestConfig) -> EnOptConfig:
def everest2ropt(
ever_config: EverestConfig, transforms: Transforms | None = None
) -> EnOptConfig:
"""Generate a ropt configuration from an Everest one
NOTE: This method is a work in progress. So far only the some of
Expand Down Expand Up @@ -402,4 +381,21 @@ def everest2ropt(ever_config: EverestConfig) -> EnOptConfig:
ropt_config=ropt_config,
)

return EnOptConfig.model_validate(ropt_config)
return EnOptConfig.model_validate(
ropt_config,
context=None if transforms is None else EnOptContext(transforms=transforms),
)


def get_ropt_transforms(ever_config: EverestConfig) -> Transforms:
controls = FlattenedControls(ever_config.controls)
if any(item is not None for item in controls.auto_scales):
variable_scaler = ControlScaler(
controls.lower_bounds,
controls.upper_bounds,
controls.scaled_ranges,
controls.auto_scales,
)
else:
variable_scaler = None
return Transforms(variables=variable_scaler)
27 changes: 27 additions & 0 deletions src/everest/optimizer/transforms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from collections.abc import Sequence

import numpy as np
from ropt.transforms import VariableScaler


class ControlScaler(VariableScaler):
def __init__(
self,
lower_bounds: Sequence[float],
upper_bounds: Sequence[float],
scaled_ranges: Sequence[tuple[float, float]],
auto_scales: Sequence[bool],
) -> None:
scales = [
(ub - lb) / (sr[1] - sr[0]) if au else 1.0
for au, lb, ub, sr in zip(
auto_scales, lower_bounds, upper_bounds, scaled_ranges, strict=True
)
]
offsets = [
lb - sr[0] * sc if au else 0.0
for au, lb, sc, sr in zip(
auto_scales, lower_bounds, scales, scaled_ranges, strict=True
)
]
super().__init__(np.array(scales), np.array(offsets))
8 changes: 4 additions & 4 deletions tests/everest/test_ropt_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,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.everest2ropt import everest2ropt, get_ropt_transforms
from tests.everest.utils import relpath

_CONFIG_DIR = relpath("test_data/mocked_test_case")
Expand Down Expand Up @@ -41,7 +41,7 @@ def test_everest2ropt_controls_auto_scale():
controls = config.controls
controls[0].auto_scale = True
controls[0].scaled_range = [0.3, 0.7]
ropt_config = everest2ropt(config)
ropt_config = everest2ropt(config, transforms=get_ropt_transforms(config))
assert numpy.allclose(ropt_config.variables.lower_bounds, 0.3)
assert numpy.allclose(ropt_config.variables.upper_bounds, 0.7)

Expand All @@ -51,7 +51,7 @@ def test_everest2ropt_variables_auto_scale():
controls = config.controls
controls[0].variables[1].auto_scale = True
controls[0].variables[1].scaled_range = [0.3, 0.7]
ropt_config = everest2ropt(config)
ropt_config = everest2ropt(config, transforms=get_ropt_transforms(config))
assert ropt_config.variables.lower_bounds[0] == 0.0
assert ropt_config.variables.upper_bounds[0] == 0.1
assert ropt_config.variables.lower_bounds[1] == 0.3
Expand Down Expand Up @@ -115,7 +115,7 @@ def test_everest2ropt_controls_input_constraint_auto_scale():
scaled_coefficients = coefficients * (max_values - min_values) / 0.4
scaled_coefficients[:2, 1] = coefficients[:2, 1] * 2.0 / 0.4

ropt_config = everest2ropt(config)
ropt_config = everest2ropt(config, transforms=get_ropt_transforms(config))
assert numpy.allclose(
ropt_config.linear_constraints.coefficients,
scaled_coefficients,
Expand Down

0 comments on commit 6e0574b

Please sign in to comment.