Skip to content

Commit

Permalink
Remove src/everest/suite.py
Browse files Browse the repository at this point in the history
Move all suite.py logic to runmodel
  • Loading branch information
yngve-sk authored Oct 24, 2024
1 parent 64bbc7e commit e4445c2
Show file tree
Hide file tree
Showing 19 changed files with 379 additions and 264 deletions.
73 changes: 73 additions & 0 deletions src/ert/run_models/everest_run_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import copy
import datetime
import functools
import json
import logging
import os
import queue
import random
import re
import shutil
import threading
Expand Down Expand Up @@ -37,6 +39,7 @@
from everest.config import EverestConfig
from everest.optimizer.everest2ropt import everest2ropt
from everest.simulator import Simulator
from everest.simulator.everest_to_ert import everest_to_ert_config
from everest.strings import EVEREST, SIMULATOR_END, SIMULATOR_START, SIMULATOR_UPDATE

from ..resources import all_shell_script_fm_steps
Expand Down Expand Up @@ -298,6 +301,11 @@ def __init__(
optimization_callback: OptimizerCallback,
display_all_jobs: bool = True,
):
everest_config = self._add_defaults(everest_config)

Path(everest_config.log_dir).mkdir(parents=True, exist_ok=True)
Path(everest_config.optimization_output_dir).mkdir(parents=True, exist_ok=True)

self.ropt_config = everest2ropt(everest_config)
self.everest_config = everest_config
self.support_restart = False
Expand Down Expand Up @@ -333,6 +341,57 @@ def __init__(

self.num_retries_per_iter = 0 # OK?

@staticmethod
def _add_defaults(config: EverestConfig) -> EverestConfig:
"""This function exists as a temporary mechanism to default configurations that
needs to be global in the sense that they should carry over both to ropt and ERT.
When the proper mechanism for this is implemented this code
should die.
"""
defaulted_config = config.copy()
assert defaulted_config.environment is not None

random_seed = defaulted_config.environment.random_seed
if random_seed is None:
random_seed = random.randint(1, 2**30)

defaulted_config.environment.random_seed = random_seed

logging.getLogger(EVEREST).info("Using random seed: %d", random_seed)
logging.getLogger(EVEREST).info(
"To deterministically reproduce this experiment, "
"add the above random seed to your configuration file."
)

return defaulted_config

@classmethod
def create(
cls,
ever_config: EverestConfig,
simulation_callback: Optional[SimulationCallback] = None,
optimization_callback: Optional[OptimizerCallback] = None,
random_seed: Optional[int] = None,
) -> EverestRunModel:
def default_simulation_callback(
simulation_status: SimulationStatus | None, event: str
) -> str | None:
return None

def default_optimization_callback() -> str | None:
return None

ert_config = everest_to_ert_config(cls._add_defaults(ever_config))
return cls(
random_seed=random_seed,
config=ert_config,
everest_config=ever_config,
simulation_callback=simulation_callback or default_simulation_callback,
optimization_callback=optimization_callback
or default_optimization_callback,
)

def run_experiment(
self, evaluator_server_config: EvaluatorServerConfig, restart: bool = False
) -> None:
Expand Down Expand Up @@ -500,3 +559,17 @@ def name(cls) -> str:
@classmethod
def description(cls) -> str:
return "Run batches "

@property
def exit_code(
self,
) -> Optional[Literal["max_batch_num_reached"] | OptimizerExitCode]:
return self._exit_code

@property
def result(self) -> Optional[seba_sqlite.sqlite_storage.OptimalResult]:
return self._result

def __repr__(self) -> str:
config_json = json.dumps(self.everest_config, sort_keys=True, indent=2)
return f"EverestRunModel(config={config_json})"
18 changes: 15 additions & 3 deletions src/everest/detached/jobs/everserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
from flask import Flask, Response, jsonify, request
from ropt.enums import OptimizerExitCode

from ert.config import QueueSystem
from ert.ensemble_evaluator import EvaluatorServerConfig
from ert.run_models.everest_run_model import EverestRunModel
from everest import export_to_csv, validate_export
from everest.config import EverestConfig
from everest.detached import ServerStatus, get_opt_status, update_everserver_status
Expand All @@ -30,7 +33,6 @@
SIM_PROGRESS_ENDPOINT,
STOP_ENDPOINT,
)
from everest.suite import start_optimization
from everest.util import configure_logger, makedirs_if_needed, version_info


Expand Down Expand Up @@ -269,12 +271,22 @@ def main():

try:
update_everserver_status(config, ServerStatus.running)
exit_code = start_optimization(

run_model = EverestRunModel.create(
config,
simulation_callback=partial(_sim_monitor, shared_data=shared_data),
optimization_callback=partial(_opt_monitor, shared_data=shared_data),
)
status, message = _get_optimization_status(exit_code, shared_data)

evaluator_server_config = EvaluatorServerConfig(
custom_port_range=range(49152, 51819)
if run_model.ert_config.queue_config.queue_system == QueueSystem.LOCAL
else None
)

run_model.run_experiment(evaluator_server_config)

status, message = _get_optimization_status(run_model.exit_code, shared_data)
if status != ServerStatus.completed:
update_everserver_status(config, status, message)
return
Expand Down
130 changes: 0 additions & 130 deletions src/everest/suite.py
Original file line number Diff line number Diff line change
@@ -1,131 +1 @@
from __future__ import annotations

import json
import logging
import random

from ert.config import QueueSystem
from ert.ensemble_evaluator import EvaluatorServerConfig
from ert.run_models.everest_run_model import EverestRunModel
from everest.config import EverestConfig
from everest.plugins.site_config_env import PluginSiteConfigEnv
from everest.simulator.everest_to_ert import everest_to_ert_config
from everest.strings import EVEREST
from everest.util import makedirs_if_needed


def start_optimization(
config, simulation_callback=None, optimization_callback=None, display_all_jobs=True
):
workflow = _EverestWorkflow(
config, simulation_callback, optimization_callback, display_all_jobs
)
with PluginSiteConfigEnv():
res = workflow.start_optimization()
return res


def _add_defaults(config: EverestConfig):
"""This function exists as a temporary mechanism to default configurations that
needs to be global in the sense that they should carry over both to ropt and ERT.
When the proper mechanism for this is implemented this code
should die.
"""
defaulted_config = config.copy()
assert defaulted_config.environment is not None

random_seed = defaulted_config.environment.random_seed
if random_seed is None:
random_seed = random.randint(1, 2**30)

defaulted_config.environment.random_seed = random_seed

logging.getLogger(EVEREST).info("Using random seed: %d", random_seed)
logging.getLogger(EVEREST).info(
"To deterministically reproduce this experiment, "
"add the above random seed to your configuration file."
)

return defaulted_config


class _EverestWorkflow(object):
"""
An instance of this class is the main object in everest.
Through this object an optimization experiment is instantiated and executed/run.
This object will provide access to the entire optimization configuration.
"""

def __init__(
self,
config: EverestConfig,
simulation_callback=None,
optimization_callback=None,
display_all_jobs=True,
):
"""Will initialize an Everest instance either from a configuration file or
a loaded config.
@config a dictionary containing the configuration. See everest --doc
for documentation on the config
@callback a function that will be called whenever changes in the
simulation or optimization routine occur, e.g., when one
realization's simulation completes, the status vector will be
sent, with the event SIMULATOR_UPDATE.
"""

# Callbacks
self._sim_callback = simulation_callback
self._opt_callback = optimization_callback

self._config = _add_defaults(config)

makedirs_if_needed(self.config.log_dir)
makedirs_if_needed(self.config.optimization_output_dir)

def start_optimization(self):
"""Run an optimization with the current settings.
This method must be called from the same thread where this
object has been created (probably because of the use of sqlite3
deeper down).
This method is not thread safe. Multiple overlapping executions
of this method will probably lead to a crash
"""
ert_config = everest_to_ert_config(self.config)
run_model = EverestRunModel(
random_seed=ert_config.random_seed,
config=ert_config,
everest_config=self.config,
simulation_callback=self._sim_callback,
optimization_callback=self._opt_callback,
)

evaluator_server_config = EvaluatorServerConfig(
custom_port_range=range(49152, 51819)
if ert_config.queue_config.queue_system == QueueSystem.LOCAL
else None
)

run_model.run_experiment(evaluator_server_config)

# Extract the best result from the storage.
self._result = run_model._result

return run_model._exit_code

@property
def result(self):
return self._result

@property
def config(self) -> EverestConfig:
return self._config

def __repr__(self):
return "EverestWorkflow(config=%s)" % json.dumps(
self.config, sort_keys=True, indent=2
)
14 changes: 14 additions & 0 deletions tests/everest/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import pytest

from ert.config import QueueSystem
from ert.ensemble_evaluator import EvaluatorServerConfig
from everest.config.control_config import ControlConfig
from tests.everest.utils import relpath

Expand Down Expand Up @@ -123,3 +125,15 @@ def copy_egg_test_data_to_tmp(tmp_path, monkeypatch):
@pytest.fixture
def change_to_tmpdir(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)


@pytest.fixture
def evaluator_server_config_generator():
def create_evaluator_server_config(run_model):
return EvaluatorServerConfig(
custom_port_range=range(49152, 51819)
if run_model.ert_config.queue_config.queue_system == QueueSystem.LOCAL
else None
)

return create_evaluator_server_config
16 changes: 9 additions & 7 deletions tests/everest/test_cvar.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import pytest

from ert.run_models.everest_run_model import EverestRunModel
from everest.config import EverestConfig
from everest.suite import _EverestWorkflow

CONFIG_FILE_CVAR = "config_cvar.yml"


def test_mathfunc_cvar(copy_math_func_test_data_to_tmp):
def test_mathfunc_cvar(
copy_math_func_test_data_to_tmp, evaluator_server_config_generator
):
config = EverestConfig.load_file(CONFIG_FILE_CVAR)

workflow = _EverestWorkflow(config)
assert workflow is not None
workflow.start_optimization()
run_model = EverestRunModel.create(config)
evaluator_server_config = evaluator_server_config_generator(run_model)
run_model.run_experiment(evaluator_server_config)

# Check resulting points
x0, x1, x2 = (workflow.result.controls["point_" + p] for p in ["x", "y", "z"])
x0, x1, x2 = (run_model.result.controls["point_" + p] for p in ["x", "y", "z"])

assert x0 == pytest.approx(0.5, 0.05)
assert x1 == pytest.approx(0.5, 0.05)
assert x2 == pytest.approx(0.5, 0.05)

total_objective = workflow.result.total_objective
total_objective = run_model.result.total_objective
assert total_objective <= 0.001
assert total_objective >= -0.001
16 changes: 9 additions & 7 deletions tests/everest/test_discrete.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from ert.run_models.everest_run_model import EverestRunModel
from everest.config import EverestConfig
from everest.suite import _EverestWorkflow

CONFIG_DISCRETE = "config_discrete.yml"


def test_discrete_optimizer(copy_math_func_test_data_to_tmp):
def test_discrete_optimizer(
copy_math_func_test_data_to_tmp, evaluator_server_config_generator
):
config = EverestConfig.load_file(CONFIG_DISCRETE)

workflow = _EverestWorkflow(config)
assert workflow is not None
workflow.start_optimization()
run_model = EverestRunModel.create(config)
evaluator_server_config = evaluator_server_config_generator(run_model)
run_model.run_experiment(evaluator_server_config)

assert workflow.result.controls["point_x"] == 3
assert workflow.result.controls["point_y"] == 7
assert run_model.result.controls["point_x"] == 3
assert run_model.result.controls["point_y"] == 7
11 changes: 5 additions & 6 deletions tests/everest/test_environment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

import everest
from ert.run_models.everest_run_model import EverestRunModel
from everest.config import EverestConfig
from everest.simulator.everest_to_ert import _everest_to_ert_config_dict

Expand All @@ -13,9 +13,8 @@ def test_seed(copy_math_func_test_data_to_tmp):
config = EverestConfig.load_file(CONFIG_FILE)
config.environment.random_seed = random_seed

ever_workflow = everest.suite._EverestWorkflow(config)

assert random_seed == ever_workflow.config.environment.random_seed
run_model = EverestRunModel.create(config)
assert random_seed == run_model.everest_config.environment.random_seed

# Res
ert_config = _everest_to_ert_config_dict(config)
Expand All @@ -26,6 +25,6 @@ def test_seed(copy_math_func_test_data_to_tmp):
def test_loglevel(copy_math_func_test_data_to_tmp):
config = EverestConfig.load_file(CONFIG_FILE)
config.environment.log_level = "info"
ever_workflow = everest.suite._EverestWorkflow(config)
config = ever_workflow.config
run_model = EverestRunModel.create(config)
config = run_model.everest_config
assert len(EverestConfig.lint_config_dict(config.to_dict())) == 0
Loading

0 comments on commit e4445c2

Please sign in to comment.