diff --git a/docs/everest/config_generated.rst b/docs/everest/config_generated.rst index 5516db250b3..3b492e6cd07 100644 --- a/docs/everest/config_generated.rst +++ b/docs/everest/config_generated.rst @@ -577,47 +577,6 @@ Optimizer options The default is to use parallel evaluation if supported. -**restart (optional)** - Type: *Optional[RestartConfig]* - - Optional restarting configuration. - - Restarting the optimization from scratch from a new initial point can be - beneficial to the optimization process for some optimization algorithms. This - option can be used to direct Everest to restart the optimization once or - multiple times. - - **max_restarts (optional)** - Type: *int* - - The maximum number of restarts. - - Sets the maximum number of times that the optimization process will be - restarted. - - The default is equal to a single restart. - - - **restart_from (required)** - Type: *Literal['initial', 'last', 'optimal', 'last_optimal']* - - Restart from the initial, optimal or the last controls. - - When restarting, the initial values for the new run are set according to this field: - - initial: Use the initial controls from the configuration - - last: Use the last controls used by the previous run - - optimal: Use the controls from the optimal solution found so far - - last_optimal: Use the controls from the optimal solution found in previous run - - When restarting from optimal values, the best result obtained so far (either - overall, or in the last restart run) is used, which is defined as the result - with the maximal weighted total objective value. If the `constraint_tolerance` - option is set in the `optimization` section, this tolerance will be used to - exclude results that violate a constraint. - - - - objective_functions (required) ------------------------------ diff --git a/src/everest/config/__init__.py b/src/everest/config/__init__.py index 51db0b74a5d..51c85ba9883 100644 --- a/src/everest/config/__init__.py +++ b/src/everest/config/__init__.py @@ -15,7 +15,6 @@ from .objective_function_config import ObjectiveFunctionConfig from .optimization_config import OptimizationConfig from .output_constraint_config import OutputConstraintConfig -from .restart_config import RestartConfig from .sampler_config import SamplerConfig from .server_config import ServerConfig from .simulator_config import SimulatorConfig @@ -38,7 +37,6 @@ "ObjectiveFunctionConfig", "OptimizationConfig", "OutputConstraintConfig", - "RestartConfig", "SamplerConfig", "ServerConfig", "SimulatorConfig", diff --git a/src/everest/config/optimization_config.py b/src/everest/config/optimization_config.py index 22c90ca9d19..ed39835101a 100644 --- a/src/everest/config/optimization_config.py +++ b/src/everest/config/optimization_config.py @@ -3,7 +3,6 @@ from pydantic import BaseModel, Field, model_validator from everest.config.cvar_config import CVaRConfig -from everest.config.restart_config import RestartConfig from everest.optimizer.utils import get_ropt_plugin_manager @@ -194,16 +193,6 @@ class OptimizationConfig(BaseModel, extra="forbid"): # type: ignore parallel, if supported by the optimization algorithm. The default is to use parallel evaluation if supported. -""", - ) - restart: Optional[RestartConfig] = Field( - default=None, - description="""Optional restarting configuration. - -Restarting the optimization from scratch from a new initial point can be -beneficial to the optimization process for some optimization algorithms. This -option can be used to direct Everest to restart the optimization once or -multiple times. """, ) diff --git a/src/everest/config/restart_config.py b/src/everest/config/restart_config.py deleted file mode 100644 index 97100aefa85..00000000000 --- a/src/everest/config/restart_config.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Literal - -from pydantic import BaseModel, ConfigDict, Field - - -class RestartConfig(BaseModel): # type: ignore - max_restarts: int = Field( - default=1, - gt=0, - description="""The maximum number of restarts. - -Sets the maximum number of times that the optimization process will be -restarted. - -The default is equal to a single restart. -""", - ) - - restart_from: Literal["initial", "last", "optimal", "last_optimal"] = Field( - description="""Restart from the initial, optimal or the last controls. - -When restarting, the initial values for the new run are set according to this field: -- initial: Use the initial controls from the configuration -- last: Use the last controls used by the previous run -- optimal: Use the controls from the optimal solution found so far -- last_optimal: Use the controls from the optimal solution found in previous run - -When restarting from optimal values, the best result obtained so far (either -overall, or in the last restart run) is used, which is defined as the result -with the maximal weighted total objective value. If the `constraint_tolerance` -option is set in the `optimization` section, this tolerance will be used to -exclude results that violate a constraint. - -""", - ) - - model_config = ConfigDict( - extra="forbid", - ) diff --git a/src/everest/docs/__main__.py b/src/everest/docs/__main__.py index d25347ffaee..8891bea198f 100644 --- a/src/everest/docs/__main__.py +++ b/src/everest/docs/__main__.py @@ -1,12 +1,11 @@ -import os - -from tests.everest.utils import relpath +from pathlib import Path from everest.docs.generate_docs_from_config_spec import generate_docs_pydantic_to_rst -committed_file = os.path.abspath( - relpath("..", "..", "docs", "everest", "config_generated.rst") -) +committed_file = ( + Path(__file__).parents[3] / "docs" / "everest" / "config_generated.rst" +).resolve() + print(f"Writing new docs contents to {committed_file}") docs_content = generate_docs_pydantic_to_rst() diff --git a/src/everest/suite.py b/src/everest/suite.py index 59f6d72f741..b7253b2dc10 100644 --- a/src/everest/suite.py +++ b/src/everest/suite.py @@ -443,14 +443,6 @@ def _configure_optimizer(self, simulator: Simulator) -> OptimizationPlanRunner: seed=self._config.environment.random_seed, ) - # Configure restarting: - if self.config.optimization.restart is not None: - optimizer.repeat( - iterations=self.config.optimization.restart.max_restarts + 1, - restart_from=self.config.optimization.restart.restart_from, - metadata_var="restart", - ) - # Initialize output tables. `min_header_len` is set to ensure that all # tables have the same number of header lines, simplifying code that # reads them as fixed width tables. `maximize` is set because ropt diff --git a/test-data/everest/math_func/config_restart.yml b/test-data/everest/math_func/config_restart.yml deleted file mode 100644 index 055026cae63..00000000000 --- a/test-data/everest/math_func/config_restart.yml +++ /dev/null @@ -1,95 +0,0 @@ -# This config file is the same as config_advanced.yml, except that it uses the -# SciPy backend. - -wells: [] - -controls: -- initial_guess: 0.25 - max: 1.0 - min: -1.0 - name: point - perturbation_magnitude: 0.005 - type: generic_control - variables: - - name: x - index: 0 - - name: x - index: 1 - - name: x - index: 2 - -objective_functions: - - - name: distance - -input_constraints: - - - weights: - point.x-0: 0 - point.x-1: 0 - point.x-2: 1 - upper_bound: 0.4 - -output_constraints: - - name: x-0_coord - lower_bound: 0.1 - scale: 0.1 - - -# Optimal value expected at x=0.1, y=0, z=0.4, with distance 3.72 - - -install_jobs: - - - name: adv_distance3 - source: jobs/ADV_DISTANCE3 - - - name: adv_dump_controls - source: jobs/ADV_DUMP_CONTROLS - - -forward_model: - # Compute distance (squared and negated) between 2 points - - adv_distance3 --point-file point.json - --target-file data/r{{ realization}}/target.json - --out distance - # Write the value of each control to a separate file - - adv_dump_controls --controls-file point.json - --out-suffix _coord - - - -model: - realizations: [0, 2] - realizations_weights: [ 0.25, 0.75] - - -install_data: - - - link: false - source: r{{ configpath }}/adv_target_r{{ realization }}.json - target: data/r{{ realization}}/target.json - - -install_templates: [] - -optimization: - backend: scipy - algorithm: SLSQP - convergence_tolerance: 0.001 - constraint_tolerance: 0.001 - perturbation_num: 4 - speculative: True - max_function_evaluations: 3 - restart: - max_restarts: 1 - restart_from: "last" - -environment: - log_level: debug - random_seed: 123 - simulation_folder: scratch/advanced/ - output_folder: everest_output/ - -simulator: - enable_cache: false diff --git a/tests/everest/test_cache.py b/tests/everest/test_cache.py deleted file mode 100644 index 668016b713d..00000000000 --- a/tests/everest/test_cache.py +++ /dev/null @@ -1,56 +0,0 @@ -import numpy as np - -from everest.config import EverestConfig -from everest.simulator import Simulator -from everest.suite import _EverestWorkflow -from tests.everest.utils import relpath, tmp - -CONFIG_PATH = relpath("..", "..", "test-data", "everest", "math_func") -CONFIG_FILE_RESTART = "config_restart.yml" - - -def test_cache_optimizer(monkeypatch): - cached = False - original_call = Simulator.__call__ - - def new_call(*args): - nonlocal cached - result = original_call(*args) - # Without caching there should be 10 evaluations: - if (result.evaluation_ids >= 0).sum() < 10: - cached = True - return result - - monkeypatch.setattr(Simulator, "__call__", new_call) - - with tmp(CONFIG_PATH): - config = EverestConfig.load_file(CONFIG_FILE_RESTART) - config.simulator.enable_cache = False - - workflow = _EverestWorkflow(config) - assert workflow is not None - workflow.start_optimization() - - x1 = np.fromiter( - (workflow.result.controls["point_" + p] for p in ["x-0", "x-1", "x-2"]), - dtype=np.float64, - ) - - assert not cached - assert np.allclose(x1, [0.1, 0.0, 0.4], atol=0.02) - - with tmp(CONFIG_PATH): - config = EverestConfig.load_file(CONFIG_FILE_RESTART) - config.simulator.enable_cache = True - - workflow = _EverestWorkflow(config) - assert workflow is not None - workflow.start_optimization() - - x2 = np.fromiter( - (workflow.result.controls["point_" + p] for p in ["x-0", "x-1", "x-2"]), - dtype=np.float64, - ) - - assert cached - assert np.allclose(x1, x2, atol=np.finfo(np.float64).eps) diff --git a/tests/everest/test_restart.py b/tests/everest/test_restart.py deleted file mode 100644 index 6c7ed1cdef6..00000000000 --- a/tests/everest/test_restart.py +++ /dev/null @@ -1,28 +0,0 @@ -import pytest - -from everest.config import EverestConfig -from everest.suite import _EverestWorkflow -from tests.everest.utils import relpath, tmpdir - -CONFIG_PATH = relpath("..", "..", "test-data", "everest", "math_func") -CONFIG_FILE_RESTART = "config_restart.yml" - - -@tmpdir(CONFIG_PATH) -def test_restart_optimizer(): - config = EverestConfig.load_file(CONFIG_FILE_RESTART) - - workflow = _EverestWorkflow(config) - assert workflow is not None - workflow.start_optimization() - - point_names = ["x-0", "x-1", "x-2"] - # Check resulting points - x0, x1, x2 = (workflow.result.controls["point_" + p] for p in point_names) - assert x0 == pytest.approx(0.1, abs=0.025) - assert x1 == pytest.approx(0.0, abs=0.025) - assert x2 == pytest.approx(0.4, abs=0.025) - - # Since we restarted once, we have twice the number of - # max_function_evaluations: - assert workflow.result.batch == 5 diff --git a/tests/everest/test_simulator_cache.py b/tests/everest/test_simulator_cache.py new file mode 100644 index 00000000000..abd724b9c0c --- /dev/null +++ b/tests/everest/test_simulator_cache.py @@ -0,0 +1,60 @@ +import numpy as np +from ropt.plan import OptimizationPlanRunner + +from everest.config import EverestConfig, SimulatorConfig +from everest.optimizer.everest2ropt import everest2ropt +from everest.simulator import Simulator +from tests.everest.utils import relpath, tmp + +CONFIG_PATH = relpath("..", "..", "test-data", "everest", "math_func") +CONFIG_FILE = "config_advanced_scipy.yml" + + +def test_simulator_cache(monkeypatch): + n_evals = 0 + original_call = Simulator.__call__ + + def new_call(*args): + nonlocal n_evals + result = original_call(*args) + n_evals += (result.evaluation_ids >= 0).sum() + return result + + monkeypatch.setattr(Simulator, "__call__", new_call) + + with tmp(CONFIG_PATH): + config = EverestConfig.load_file(CONFIG_FILE) + config.simulator = SimulatorConfig(enable_cache=True) + + ropt_config = everest2ropt(config) + simulator = Simulator(config) + + # Run once, populating the cache of the simulator: + variables1 = ( + OptimizationPlanRunner( + enopt_config=ropt_config, + evaluator=simulator, + seed=config.environment.random_seed, + ) + .run() + .variables + ) + assert variables1 is not None + assert np.allclose(variables1, [0.1, 0, 0.4], atol=0.02) + assert n_evals > 0 + + # Run again with the same simulator: + n_evals = 0 + variables2 = ( + OptimizationPlanRunner( + enopt_config=ropt_config, + evaluator=simulator, + seed=config.environment.random_seed, + ) + .run() + .variables + ) + assert variables2 is not None + assert n_evals == 0 + + assert np.array_equal(variables1, variables2)