diff --git a/src/ert/config/analysis_module.py b/src/ert/config/analysis_module.py index fa09588abc7..5281966a4e0 100644 --- a/src/ert/config/analysis_module.py +++ b/src/ert/config/analysis_module.py @@ -86,7 +86,7 @@ def correlation_threshold(self, ensemble_size: int) -> float: Section 2.3 - Localization in the CHOP problem """ if self.localization_correlation_threshold is None: - return 3 / math.sqrt(ensemble_size) + return min(1.0, 3 / math.sqrt(ensemble_size)) return self.localization_correlation_threshold diff --git a/src/ert/gui/ertwidgets/analysismoduleedit.py b/src/ert/gui/ertwidgets/analysismoduleedit.py index 82e4258a5c3..990eaee917c 100644 --- a/src/ert/gui/ertwidgets/analysismoduleedit.py +++ b/src/ert/gui/ertwidgets/analysismoduleedit.py @@ -1,35 +1,42 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from collections import defaultdict from PyQt6.QtCore import QMargins, Qt -from PyQt6.QtWidgets import QHBoxLayout, QPushButton, QWidget +from PyQt6.QtWidgets import ( + QDialog, + QDialogButtonBox, + QHBoxLayout, + QPushButton, + QVBoxLayout, + QWidget, +) +from ert.config import ESSettings, LocalizationType, ParameterConfig from ert.gui.icon_utils import load_icon from .analysismodulevariablespanel import AnalysisModuleVariablesPanel -from .closabledialog import ClosableDialog - -if TYPE_CHECKING: - from ert.config import AnalysisModule class AnalysisModuleEdit(QWidget): def __init__( self, - analysis_module: AnalysisModule, + es_settings: ESSettings, + parameter_config: list[ParameterConfig], ensemble_size: int, ) -> None: - self.analysis_module = analysis_module - self.ensemble_size = ensemble_size QWidget.__init__(self) + self._es_settings: ESSettings = es_settings + self._parameter_config: list[ParameterConfig] = parameter_config + self._ensemble_size: int = ensemble_size + layout = QHBoxLayout() variables_popup_button = QPushButton("Edit") variables_popup_button.setObjectName("analysis_variables_popup_button") variables_popup_button.setIcon(load_icon("edit.svg")) - variables_popup_button.clicked.connect(self.showVariablesPopup) + variables_popup_button.clicked.connect(self._show_update_settings_dialog) layout.addWidget(variables_popup_button, 0, Qt.AlignmentFlag.AlignLeft) layout.setContentsMargins(QMargins(0, 0, 0, 0)) @@ -37,13 +44,77 @@ def __init__( self.setLayout(layout) - def showVariablesPopup(self) -> None: - variable_dialog = AnalysisModuleVariablesPanel( - self.analysis_module, self.ensemble_size + @property + def parameter_config(self) -> list[ParameterConfig]: + return self._parameter_config + + @parameter_config.setter + def parameter_config(self, value: list[ParameterConfig]) -> None: + self._parameter_config = value + + def _show_update_settings_dialog(self) -> None: + dialog = QDialog(self.parent()) # type: ignore + dialog.setWindowTitle("Update settings") + dialog.setModal(True) + dialog.setWindowFlag(Qt.WindowType.CustomizeWindowHint, True) + dialog.setWindowFlag(Qt.WindowType.WindowContextHelpButtonHint, False) + dialog.setWindowFlag(Qt.WindowType.WindowCloseButtonHint, False) + + layout = QVBoxLayout() + + update_strategies: dict[str, LocalizationType] = defaultdict( + lambda: LocalizationType.GLOBAL + ) + for parameter_config in self._parameter_config: + if parameter_config.update_strategy: + update_strategies[parameter_config.type.upper()] = ( + parameter_config.update_strategy + ) + + correlation_threshold = 1.0 + if self._ensemble_size != 0: + correlation_threshold = self._es_settings.correlation_threshold( + self._ensemble_size + ) + + update_settings_dialog = AnalysisModuleVariablesPanel( + update_strategies=update_strategies, + correlation_threshold=correlation_threshold, + enkf_truncation=self._es_settings.enkf_truncation, ) - dialog = ClosableDialog( - "Edit variables", - variable_dialog, - self.parent(), # type: ignore + + layout.addWidget(update_settings_dialog, stretch=1) + + button_box = QDialogButtonBox( + QDialogButtonBox.StandardButton.Save + | QDialogButtonBox.StandardButton.Cancel ) - dialog.exec() + save_button = button_box.button(QDialogButtonBox.StandardButton.Save) + assert save_button is not None + save_button.setAutoDefault(False) + cancel_button = button_box.button(QDialogButtonBox.StandardButton.Cancel) + assert cancel_button is not None + cancel_button.setAutoDefault(False) + button_box.accepted.connect(dialog.accept) + button_box.rejected.connect(dialog.reject) + + button_layout = QHBoxLayout() + button_layout.addStretch() + button_layout.addWidget(button_box) + layout.addLayout(button_layout) + + dialog.setLayout(layout) + dialog.setFixedSize(450, 300) + + if dialog.exec() == QDialog.DialogCode.Accepted: + self._es_settings.localization_correlation_threshold = ( + update_settings_dialog.correlation_threshold + ) + self._es_settings.enkf_truncation = update_settings_dialog.enkf_truncation + for name, strategy in update_settings_dialog.update_strategies.items(): + for parameter_config in self._parameter_config: + if ( + parameter_config.type.upper() == name + and parameter_config.update_strategy is not None + ): + parameter_config.update_strategy = strategy diff --git a/src/ert/gui/ertwidgets/analysismodulevariablespanel.py b/src/ert/gui/ertwidgets/analysismodulevariablespanel.py index f7d68a402bf..dc05f1c8c30 100644 --- a/src/ert/gui/ertwidgets/analysismodulevariablespanel.py +++ b/src/ert/gui/ertwidgets/analysismodulevariablespanel.py @@ -1,55 +1,157 @@ from __future__ import annotations -from functools import partial from typing import cast from annotated_types import Ge, Gt, Le +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QStandardItem, QStandardItemModel from PyQt6.QtWidgets import ( + QComboBox, QDoubleSpinBox, QFormLayout, + QLabel, QWidget, ) -from ert.config import AnalysisModule +from ert.config import AnalysisModule, LocalizationType + + +class _LocalizationTypeModel(QStandardItemModel): + def __init__(self, exclude: set[LocalizationType] | None = None) -> None: + super().__init__() + + type_set = set(LocalizationType) + if exclude is not None: + type_set -= exclude + + for localization_type in sorted(type_set, key=lambda lt: lt.name): + item = QStandardItem(localization_type.name) + item.setData(localization_type, Qt.ItemDataRole.UserRole) + self.appendRow(item) class AnalysisModuleVariablesPanel(QWidget): - def __init__(self, analysis_module: AnalysisModule, ensemble_size: int) -> None: + def __init__( + self, + update_strategies: dict[str, LocalizationType], + correlation_threshold: float, + enkf_truncation: float, + ) -> None: QWidget.__init__(self) - self.analysis_module = analysis_module - layout = QFormLayout() + self._update_strategies = update_strategies + self._correlation_threshold = correlation_threshold + self._enkf_truncation = enkf_truncation + layout = QFormLayout() self.blockSignals(True) + layout.addRow( + QLabel("Select the localization method for each parameter type") + ) + + gen_kw_combobox = QComboBox(self) + gen_kw_combobox.setModel( + _LocalizationTypeModel(exclude={LocalizationType.DISTANCE}) + ) + gen_kw_combobox.setCurrentIndex( + self._find_correct_index(gen_kw_combobox, "GEN_KW") + ) + gen_kw_combobox.currentIndexChanged.connect( + lambda index: self._update_strategies.__setitem__( + "GEN_KW", + gen_kw_combobox.itemData(index, Qt.ItemDataRole.UserRole), + ) + ) + layout.addRow("GEN_KW", gen_kw_combobox) + + field_combobox = QComboBox(self) + field_combobox.setModel(_LocalizationTypeModel()) + field_combobox.setCurrentIndex( + self._find_correct_index(field_combobox, "FIELD") + ) + field_combobox.currentIndexChanged.connect( + lambda index: self._update_strategies.__setitem__( + "FIELD", + field_combobox.itemData(index, Qt.ItemDataRole.UserRole), + ) + ) + layout.addRow("FIELD", field_combobox) + + surface_combobox = QComboBox(self) + surface_combobox.setModel(_LocalizationTypeModel()) + surface_combobox.setCurrentIndex( + self._find_correct_index(surface_combobox, "SURFACE") + ) + surface_combobox.currentIndexChanged.connect( + lambda index: self._update_strategies.__setitem__( + "SURFACE", + surface_combobox.itemData(index, Qt.ItemDataRole.UserRole), + ) + ) + layout.addRow("SURFACE", surface_combobox) + + layout.addRow(QLabel("General settings")) + var_name = "enkf_truncation" metadata = AnalysisModule.model_fields[var_name] - self.truncation_spinner = self.createDoubleSpinBox( + self.truncation_spinner = self._create_double_spinbox( var_name, - analysis_module.enkf_truncation, + self._enkf_truncation, cast(float, next(v for v in metadata.metadata if isinstance(v, Gt)).gt) + 0.001, cast(float, next(v for v in metadata.metadata if isinstance(v, Le)).le), 0.01, ) + self.truncation_spinner.valueChanged.connect( + lambda value: setattr(self, "_enkf_truncation", value) + ) + layout.addRow("Singular value truncation", self.truncation_spinner) var_name = "localization_correlation_threshold" metadata = AnalysisModule.model_fields[var_name] - self.local_spinner = self.createDoubleSpinBox( + self.threshold_spinner = self._create_double_spinbox( var_name, - analysis_module.correlation_threshold(ensemble_size), + self._correlation_threshold, cast(float, next(v for v in metadata.metadata if isinstance(v, Ge)).ge), cast(float, next(v for v in metadata.metadata if isinstance(v, Le)).le), 0.1, ) - self.local_spinner.setObjectName("localization_correlation_threshold") - layout.addRow("Adaptive localization correlation threshold", self.local_spinner) + self.threshold_spinner.setObjectName("localization_correlation_threshold") + self.threshold_spinner.valueChanged.connect( + lambda value: setattr(self, "_correlation_threshold", value) + ) + + layout.addRow( + "Adaptive localization correlation threshold", self.threshold_spinner + ) self.setLayout(layout) self.blockSignals(False) - def createDoubleSpinBox( + @property + def update_strategies(self) -> dict[str, LocalizationType]: + return self._update_strategies + + @property + def correlation_threshold(self) -> float: + return self._correlation_threshold + + @property + def enkf_truncation(self) -> float: + return self._enkf_truncation + + def _find_correct_index(self, combobox: QComboBox, type_name: str) -> int: + if type_name in self._update_strategies: + localization_type = self._update_strategies[type_name] + if ( + index := combobox.findData(localization_type, Qt.ItemDataRole.UserRole) + ) != -1: + return index + return combobox.findData(LocalizationType.GLOBAL, Qt.ItemDataRole.UserRole) + + def _create_double_spinbox( self, variable_name: str, variable_value: float, @@ -61,16 +163,10 @@ def createDoubleSpinBox( spinner.setDecimals(6) spinner.setFixedWidth(180) spinner.setObjectName(variable_name) - spinner.setRange( min_value, max_value, ) - spinner.setSingleStep(step_length) spinner.setValue(variable_value) - spinner.valueChanged.connect(partial(self.valueChangedSpinner, variable_name)) return spinner - - def valueChangedSpinner(self, name: str, value: float) -> None: - setattr(self.analysis_module, name, value) diff --git a/src/ert/gui/experiments/ensemble_smoother_panel.py b/src/ert/gui/experiments/ensemble_smoother_panel.py index 0d124a22968..72a7c839a33 100644 --- a/src/ert/gui/experiments/ensemble_smoother_panel.py +++ b/src/ert/gui/experiments/ensemble_smoother_panel.py @@ -95,14 +95,15 @@ def __init__( layout.addRow("Ensemble format:", self._ensemble_format_field) self._analysis_module_edit = AnalysisModuleEdit( - analysis_config.es_settings, - sum( + es_settings=analysis_config.es_settings, + parameter_config=parameter_configuration, + ensemble_size=sum( active_realizations ), # only use active realizations for setting threshold ) self._analysis_module_edit.setObjectName("ensemble_smoother_edit") - layout.addRow("Analysis module:", self._analysis_module_edit) + layout.addRow("Update settings:", self._analysis_module_edit) self._active_realizations_field = StringBox( ActiveRealizationsModel(len(active_realizations)), # type: ignore "config/experiment/active_realizations", diff --git a/src/ert/gui/experiments/experiment_panel.py b/src/ert/gui/experiments/experiment_panel.py index 6b727a9ebc7..9c938847b12 100644 --- a/src/ert/gui/experiments/experiment_panel.py +++ b/src/ert/gui/experiments/experiment_panel.py @@ -242,7 +242,12 @@ def __init__( experiment_type_valid, ) self.addExperimentConfigPanel( - ManualUpdatePanel(run_path, notifier, analysis_config), + ManualUpdatePanel( + run_path, + notifier, + analysis_config, + config.ensemble_config.parameter_configuration, + ), experiment_type_valid, ) diff --git a/src/ert/gui/experiments/manual_update_panel.py b/src/ert/gui/experiments/manual_update_panel.py index 56a1b545880..af20c6d4e72 100644 --- a/src/ert/gui/experiments/manual_update_panel.py +++ b/src/ert/gui/experiments/manual_update_panel.py @@ -8,7 +8,7 @@ from PyQt6.QtCore import pyqtSlot as Slot from PyQt6.QtWidgets import QComboBox, QFormLayout, QLabel, QWidget -from ert.config import AnalysisConfig, ErrorInfo +from ert.config import AnalysisConfig, ErrorInfo, ParameterConfig from ert.gui.ertnotifier import ErtNotifier from ert.gui.ertwidgets import ( ActiveRealizationsModel, @@ -37,6 +37,7 @@ class Arguments: target_ensemble: str ensemble_size: int experiment_name: str + parameter_configuration: list[ParameterConfig] class ManualUpdatePanel(ExperimentConfigPanel): @@ -47,9 +48,11 @@ def __init__( run_path: str, notifier: ErtNotifier, analysis_config: AnalysisConfig, + parameter_configuration: list[ParameterConfig], ) -> None: super().__init__(ManualUpdate) self.setObjectName("Manual_update_panel") + self._analysis_config = analysis_config layout = QFormLayout() layout.setFormAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) @@ -93,10 +96,15 @@ def show_only_with_response_data_filter( self._ensemble_format_field.setValidator(ProperNameFormatArgument()) layout.addRow("Ensemble format:", self._ensemble_format_field) - self._analysis_module_edit = AnalysisModuleEdit(analysis_config.es_settings, 0) + self._analysis_module_edit = AnalysisModuleEdit( + es_settings=analysis_config.es_settings, + parameter_config=parameter_configuration, + ensemble_size=0, + ) self._analysis_module_edit.setObjectName("ensemble_smoother_edit") self._analysis_module_edit.setEnabled(False) - layout.addRow("Analysis module:", self._analysis_module_edit) + + layout.addRow("Update settings:", self._analysis_module_edit) self._active_realizations_model = ActiveRealizationsModel(0, show_default=False) self._active_realizations_field = StringBox( self._active_realizations_model, # type: ignore @@ -105,6 +113,9 @@ def show_only_with_response_data_filter( self._active_realizations_field.setObjectName("active_realizations_box") self._realizations_from_fs() layout.addRow("Active realizations", self._active_realizations_field) + self._active_realizations_field.getValidationSupport().validationChanged.connect( + self.experiment_configuration_changed + ) self._experiment_name_field = StringBox( TextModel(""), @@ -123,20 +134,31 @@ def show_only_with_response_data_filter( ) ) - self._active_realizations_field.getValidationSupport().validationChanged.connect( - self.experiment_configuration_changed - ) self._ensemble_selector.ensemble_populated.connect(self._realizations_from_fs) self._ensemble_selector.ensemble_populated.connect( self.experiment_configuration_changed ) + self._ensemble_selector.ensemble_populated.connect( + self._parameter_configuration_changed + ) + self._ensemble_selector.currentIndexChanged.connect(self._realizations_from_fs) + self._ensemble_selector.currentIndexChanged.connect( + self._parameter_configuration_changed + ) + self.setLayout(layout) @property def selected_update_method(self) -> str: return self._update_method_dropdown.currentText() + def _parameter_configuration_changed(self) -> None: + if self._ensemble_selector.selected_ensemble is not None: + self._analysis_module_edit.parameter_config = list( + self._ensemble_selector.selected_ensemble.experiment.parameter_configuration.values() + ) + @Slot(str) def _on_update_method_changed(self, new_method: str) -> None: if new_method == "ES Update": @@ -164,6 +186,7 @@ def get_experiment_arguments(self) -> Arguments: target_ensemble=self._ensemble_format_model.getValue(), # type: ignore ensemble_size=self._ensemble_size, experiment_name=self._experiment_name_field.get_text, + parameter_configuration=self._analysis_module_edit.parameter_config, ) def _realizations_from_fs(self) -> None: @@ -193,7 +216,8 @@ def _realizations_from_fs(self) -> None: active_realizations_size = sum( self._active_realizations_model.getActiveRealizationsMask() ) - self._analysis_module_edit.ensemble_size = active_realizations_size + self._analysis_module_edit._ensemble_size = active_realizations_size + self._analysis_module_edit.setEnabled(bool(active_realizations_size)) except OSError as err: logger.error(str(err)) diff --git a/src/ert/gui/experiments/multiple_data_assimilation_panel.py b/src/ert/gui/experiments/multiple_data_assimilation_panel.py index e4ea9f57acb..537c787bb4f 100644 --- a/src/ert/gui/experiments/multiple_data_assimilation_panel.py +++ b/src/ert/gui/experiments/multiple_data_assimilation_panel.py @@ -53,6 +53,8 @@ from ert.config import AnalysisConfig from ert.storage import Ensemble + + logger = logging.getLogger(__name__) @@ -64,6 +66,7 @@ class Arguments: weights: str prior_ensemble_id: str | None # UUID not serializable in json experiment_name: str + parameter_configuration: list[ParameterConfig] class MultipleDataAssimilationPanel(ExperimentConfigPanel): @@ -77,6 +80,7 @@ def __init__( config_num_realization: int, ) -> None: super().__init__(MultipleDataAssimilation) + self.notifier = notifier self._configured_weights = analysis_config.es_settings.weights self._weights_source = self._configured_weights @@ -128,12 +132,14 @@ def __init__( self._createInputForWeights(layout) self._analysis_module_edit = AnalysisModuleEdit( - analysis_config.es_settings, - sum( + es_settings=analysis_config.es_settings, + parameter_config=parameter_configuration, + ensemble_size=sum( active_realizations ), # only use active realizations for setting threshold ) - layout.addRow("Analysis module:", self._analysis_module_edit) + layout.addRow("Update settings:", self._analysis_module_edit) + self._active_realizations_field = StringBox( ActiveRealizationsModel(len(active_realizations)), # type: ignore "config/experiment/active_realizations", @@ -206,8 +212,15 @@ def get_ensembles_of_ensemble_experiment_type( layout.addRow("Select prior ensemble:", self._select_prior_ensemble_box) self._ensemble_selector.ensemble_populated.connect(self.select_prior_toggled) + self._ensemble_selector.ensemble_populated.connect( + self._parameter_configuration_changed + ) self._ensemble_selector.currentIndexChanged.connect(self._realizations_from_fs) + self._ensemble_selector.currentIndexChanged.connect( + self._parameter_configuration_changed + ) self._ensemble_selector.currentIndexChanged.connect(self.update_experiment_name) + layout.addRow("Run from prior ensemble:", self._ensemble_selector) self._experiment_name_field.getValidationSupport().validationChanged.connect( @@ -256,6 +269,12 @@ def _update_experiment_name_placeholder(self) -> None: self.notifier.storage.get_unique_experiment_name(ES_MDA_MODE) ) + def _parameter_configuration_changed(self) -> None: + if self._ensemble_selector.selected_ensemble is not None: + self._analysis_module_edit.parameter_config = list( + self._ensemble_selector.selected_ensemble.experiment.parameter_configuration.values() + ) + @Slot() def update_experiment_name(self) -> None: if not self._experiment_name_field.isEnabled(): @@ -428,6 +447,7 @@ def get_experiment_arguments(self) -> Arguments: else None ), experiment_name=self._experiment_name_field.get_text, + parameter_configuration=self._analysis_module_edit.parameter_config, ) def setWeights(self, weights: Any) -> None: diff --git a/src/ert/run_models/model_factory.py b/src/ert/run_models/model_factory.py index 203c5999b30..96f09014207 100644 --- a/src/ert/run_models/model_factory.py +++ b/src/ert/run_models/model_factory.py @@ -319,6 +319,11 @@ def _setup_manual_update( ert_templates=config.ert_templates, shape_registry=config.shape_registry, experiment_name=args.experiment_name, + parameter_configuration=getattr( + args, + "parameter_configuration", + config.ensemble_config.parameter_configuration, + ), ) return ManualUpdate(**runmodel_config.model_dump(), status_queue=status_queue) @@ -355,6 +360,7 @@ def _setup_manual_update_enif( ert_templates=config.ert_templates, shape_registry=config.shape_registry, experiment_name=args.experiment_name, + parameter_configuration=args.parameter_configuration, ) return ManualUpdateEnIF(**runmodel_config.model_dump(), status_queue=status_queue) @@ -488,7 +494,11 @@ def _setup_multiple_data_assimilation( parameter_configs, design_matrix = _merge_parameters( design_matrix=None if prior_ensemble else config.analysis_config.design_matrix, - parameter_configs=config.ensemble_config.parameter_configuration, + parameter_configs=getattr( + args, + "parameter_configuration", + config.ensemble_config.parameter_configuration, + ), require_updateable_param=True, ) diff --git a/src/ert/run_models/run_model_configs.py b/src/ert/run_models/run_model_configs.py index e3807c20bef..dfe0583216e 100644 --- a/src/ert/run_models/run_model_configs.py +++ b/src/ert/run_models/run_model_configs.py @@ -304,6 +304,12 @@ class ManualUpdateConfig(UpdateRunModelConfig): ert_templates: list[tuple[str, str]] shape_registry: ShapeRegistry | None = None experiment_name: str + parameter_configuration: list[ + Annotated[ + (GenKwConfig | SurfaceConfig | FieldConfig | EverestControl), + Field(discriminator="type"), + ] + ] def to_experiment_config( self, *, prior_experiment_config: ExperimentConfig @@ -315,9 +321,9 @@ def to_experiment_config( "ensemble_id": self.ensemble_id, "ert_templates": self.ert_templates, **self._update_experiment_config(), - "parameter_configuration": prior_experiment_config.get( - "parameter_configuration", [] - ), + "parameter_configuration": [ + param.model_dump(mode="json") for param in self.parameter_configuration + ], "response_configuration": prior_experiment_config.get( "response_configuration", [] ), diff --git a/tests/ert/ui_tests/cli/test_run_prior_esmda.py b/tests/ert/ui_tests/cli/test_run_prior_esmda.py index 8e6ff70dbcd..1e708042e1b 100644 --- a/tests/ert/ui_tests/cli/test_run_prior_esmda.py +++ b/tests/ert/ui_tests/cli/test_run_prior_esmda.py @@ -227,6 +227,7 @@ def _build_esmda_run_prior_model(prior_ensemble_id: str): weights="1,1", prior_ensemble_id=prior_ensemble_id, experiment_name="restart-experiment", + parameter_configuration=[], ), SimpleQueue(), ) diff --git a/tests/ert/ui_tests/gui/test_main_window.py b/tests/ert/ui_tests/gui/test_main_window.py index e22435f033c..2c013faa178 100644 --- a/tests/ert/ui_tests/gui/test_main_window.py +++ b/tests/ert/ui_tests/gui/test_main_window.py @@ -14,7 +14,6 @@ from PyQt6.QtWidgets import ( QCheckBox, QComboBox, - QDoubleSpinBox, QLabel, QMenuBar, QMessageBox, @@ -34,7 +33,6 @@ StringBox, Suggestor, ) -from ert.gui.ertwidgets.analysismodulevariablespanel import AnalysisModuleVariablesPanel from ert.gui.ertwidgets.suggestor._suggestor_message import SuggestorMessage from ert.gui.experiments import ExperimentPanel, RunDialog from ert.gui.main import ErtMainWindow, GUILogHandler, _setup_main_window @@ -490,29 +488,6 @@ def handle_add_dialog(): qtbot.mouseClick(initialize_button, Qt.MouseButton.LeftButton) -def test_that_truncation_can_be_set_from_gui(qtbot, opened_main_window_poly): - gui = opened_main_window_poly - - sim_mode = get_child(gui, QWidget, name="experiment_type") - qtbot.keyClick(sim_mode, Qt.Key.Key_Down) - es_panel = get_child(gui, QWidget, name="ensemble_smoother_panel") - es_edit = get_child(es_panel, QWidget, name="ensemble_smoother_edit") - - # Testing modal dialogs requires some care. - # https://github.com/pytest-dev/pytest-qt/issues/256 - def handle_analysis_module_panel(): - var_panel = wait_for_child(gui, qtbot, AnalysisModuleVariablesPanel) - spinner = wait_for_child(var_panel, qtbot, QDoubleSpinBox, "enkf_truncation") - assert spinner.isEnabled() - - var_panel.parent().close() - - QTimer.singleShot(500, handle_analysis_module_panel) - qtbot.mouseClick( - get_child(es_edit, QPushButton), Qt.MouseButton.LeftButton, delay=1 - ) - - def test_that_the_manage_experiments_tool_can_be_used_with_clean_storage( opened_main_window_poly, qtbot ): diff --git a/tests/ert/unit_tests/config/test_analysis_module.py b/tests/ert/unit_tests/config/test_analysis_module.py new file mode 100644 index 00000000000..a352ee39b97 --- /dev/null +++ b/tests/ert/unit_tests/config/test_analysis_module.py @@ -0,0 +1,24 @@ +import math + +import pytest + +from ert.config.analysis_module import ESSettings + + +@pytest.mark.parametrize( + ("settings", "ensemble_size", "expected_threshold"), + [ + (ESSettings(), 4, 1.0), + (ESSettings(), 8, 1.0), + (ESSettings(), 9, 1.0), + (ESSettings(), 36, 0.5), + (ESSettings(), 200, 3 / math.sqrt(200)), + (ESSettings(localization_correlation_threshold=0.2), 9, 0.2), + ], +) +def test_that_correlation_threshold_uses_default_or_custom_value( + settings, ensemble_size, expected_threshold +): + assert settings.correlation_threshold(ensemble_size) == pytest.approx( + expected_threshold + ) diff --git a/tests/ert/unit_tests/gui/ertwidgets/test_analysismoduleedit.py b/tests/ert/unit_tests/gui/ertwidgets/test_analysismoduleedit.py new file mode 100644 index 00000000000..7edfb54bb62 --- /dev/null +++ b/tests/ert/unit_tests/gui/ertwidgets/test_analysismoduleedit.py @@ -0,0 +1,161 @@ +import pytest +from PyQt6.QtCore import Qt, QTimer +from PyQt6.QtWidgets import QApplication, QDialog, QPushButton +from pytestqt.qtbot import QtBot + +from ert.config import ESSettings, GenKwConfig, LocalizationType +from ert.gui.ertwidgets.analysismoduleedit import AnalysisModuleEdit +from ert.gui.ertwidgets.analysismodulevariablespanel import AnalysisModuleVariablesPanel + + +def test_that_click_opens_the_correct_dialog(qtbot: QtBot): + widget = AnalysisModuleEdit( + es_settings=ESSettings(), parameter_config=[], ensemble_size=10 + ) + qtbot.addWidget(widget) + + def inspect_and_close_dialog() -> None: + dialog = QApplication.activeModalWidget() + assert dialog is not None + assert isinstance(dialog, QDialog) + + panel = dialog.findChild(AnalysisModuleVariablesPanel) + assert panel is not None + dialog.reject() + + QTimer.singleShot(0, inspect_and_close_dialog) + + button = widget.findChild(QPushButton) + assert button is not None + qtbot.mouseClick(button, Qt.MouseButton.LeftButton) + + +def test_that_settings_are_updated_correctly(qtbot: QtBot): + es_settings = ESSettings() + es_settings.localization_correlation_threshold = 0.5 + es_settings.enkf_truncation = 0.2 + ensemble_size = 10 + parameter = GenKwConfig( + name="name", + distribution={"name": "uniform", "min": 0, "max": 1}, + update_strategy=LocalizationType.GLOBAL, + ) + parameter_config = [parameter] + + widget = AnalysisModuleEdit( + es_settings=es_settings, + parameter_config=parameter_config, + ensemble_size=ensemble_size, + ) + qtbot.addWidget(widget) + + def inspect_and_accept_dialog() -> None: + + dialog = QApplication.activeModalWidget() + assert dialog is not None + assert isinstance(dialog, QDialog) + + panel = dialog.findChild(AnalysisModuleVariablesPanel) + assert panel is not None + + # Update settings in the panel + panel._correlation_threshold = 0.7 + panel._enkf_truncation = 0.3 + panel._update_strategies["GEN_KW"] = LocalizationType.ADAPTIVE + + dialog.accept() + + QTimer.singleShot(0, inspect_and_accept_dialog) + + button = widget.findChild(QPushButton) + assert button is not None + qtbot.mouseClick(button, Qt.MouseButton.LeftButton) + + # After the dialog is accepted, check that the settings are updated + assert pytest.approx(es_settings.localization_correlation_threshold) == 0.7 + assert pytest.approx(es_settings.enkf_truncation) == 0.3 + assert widget._parameter_config[0].update_strategy == LocalizationType.ADAPTIVE + + +def test_that_only_gen_kw_parameters_with_update_strategy_are_updated(qtbot: QtBot): + parameter_without_strategy = GenKwConfig( + name="without_strategy", + distribution={"name": "uniform", "min": 0, "max": 1}, + update_strategy=None, + ) + parameter_with_strategy = GenKwConfig( + name="with_strategy", + distribution={"name": "uniform", "min": 0, "max": 1}, + update_strategy=LocalizationType.GLOBAL, + ) + + widget = AnalysisModuleEdit( + es_settings=ESSettings(), + parameter_config=[parameter_without_strategy, parameter_with_strategy], + ensemble_size=10, + ) + qtbot.addWidget(widget) + + def select_adaptive_strategy_and_accept_dialog() -> None: + dialog = QApplication.activeModalWidget() + assert isinstance(dialog, QDialog) + + panel = dialog.findChild(AnalysisModuleVariablesPanel) + assert panel is not None + panel._update_strategies["GEN_KW"] = LocalizationType.ADAPTIVE + dialog.accept() + + QTimer.singleShot(0, select_adaptive_strategy_and_accept_dialog) + + button = widget.findChild(QPushButton) + assert button is not None + qtbot.mouseClick(button, Qt.MouseButton.LeftButton) + + assert parameter_without_strategy.update_strategy is None + assert parameter_with_strategy.update_strategy == LocalizationType.ADAPTIVE + + +def test_that_settings_are_not_updated_on_cancel(qtbot: QtBot): + es_settings = ESSettings() + es_settings.localization_correlation_threshold = 0.5 + es_settings.enkf_truncation = 0.2 + ensemble_size = 10 + parameter = GenKwConfig( + name="name", + distribution={"name": "uniform", "min": 0, "max": 1}, + update_strategy=LocalizationType.GLOBAL, + ) + parameter_config = [parameter] + + widget = AnalysisModuleEdit( + es_settings=es_settings, + parameter_config=parameter_config, + ensemble_size=ensemble_size, + ) + qtbot.addWidget(widget) + + def inspect_and_reject_dialog() -> None: + dialog = QApplication.activeModalWidget() + assert dialog is not None + assert isinstance(dialog, QDialog) + + panel = dialog.findChild(AnalysisModuleVariablesPanel) + assert panel is not None + + # Update settings in the panel + panel._correlation_threshold = 0.7 + panel._enkf_truncation = 0.3 + panel._update_strategies["GEN_KW"] = LocalizationType.ADAPTIVE + + dialog.reject() + + QTimer.singleShot(0, inspect_and_reject_dialog) + + button = widget.findChild(QPushButton) + assert button is not None + qtbot.mouseClick(button, Qt.MouseButton.LeftButton) + + # After the dialog is rejected, check that the settings are not updated + assert pytest.approx(es_settings.localization_correlation_threshold) == 0.5 + assert pytest.approx(es_settings.enkf_truncation) == 0.2 + assert widget._parameter_config[0].update_strategy == LocalizationType.GLOBAL diff --git a/tests/ert/unit_tests/gui/ertwidgets/test_analysispanel.py b/tests/ert/unit_tests/gui/ertwidgets/test_analysispanel.py new file mode 100644 index 00000000000..db41dfb17be --- /dev/null +++ b/tests/ert/unit_tests/gui/ertwidgets/test_analysispanel.py @@ -0,0 +1,142 @@ +import math + +import pytest +from PyQt6.QtWidgets import QComboBox, QDoubleSpinBox +from pytestqt.qtbot import QtBot + +from ert.config import ESSettings, LocalizationType +from ert.gui.ertwidgets.analysismodulevariablespanel import AnalysisModuleVariablesPanel + + +def test_that_empty_update_strategies_are_set_to_global(qtbot: QtBot): + settings = ESSettings() + settings.localization_correlation_threshold = 0.5 + settings.enkf_truncation = 0.2 + update_strategies = {} + + widget = AnalysisModuleVariablesPanel( + update_strategies=update_strategies, + correlation_threshold=0.5, + enkf_truncation=0.2, + ) + + qtbot.addWidget(widget) + + comboboxes = widget.findChildren(QComboBox) + assert len(comboboxes) == 3 + + for combobox in comboboxes: + if ( + combobox.objectName() == "GEN_KW" + or combobox.objectName() == "SURFACE" + or combobox.objectName() == "FIELD" + ): + assert combobox.currentData() == LocalizationType.GLOBAL + + +def test_that_the_panel_initializes_with_correct_values(qtbot: QtBot): + settings = ESSettings() + settings.localization_correlation_threshold = 0.5 + settings.enkf_truncation = 0.2 + update_strategies = { + "GEN_KW": LocalizationType.GLOBAL, + "SURFACE": LocalizationType.DISTANCE, + "FIELD": LocalizationType.ADAPTIVE, + } + + widget = AnalysisModuleVariablesPanel( + update_strategies=update_strategies, + correlation_threshold=0.5, + enkf_truncation=0.2, + ) + + qtbot.addWidget(widget) + + correlation_spinner = widget.findChild( + QDoubleSpinBox, name="localization_correlation_threshold" + ) + truncation_spinner = widget.findChild(QDoubleSpinBox, name="enkf_truncation") + + assert math.isclose( + correlation_spinner.value(), settings.localization_correlation_threshold + ) + assert math.isclose(truncation_spinner.value(), settings.enkf_truncation) + + comboboxes = widget.findChildren(QComboBox) + assert len(comboboxes) == 3 + + for combobox in comboboxes: + if combobox.objectName() == "GEN_KW": + assert combobox.currentData() == LocalizationType.GLOBAL + elif combobox.objectName() == "SURFACE": + assert combobox.currentData() == LocalizationType.DISTANCE + elif combobox.objectName() == "FIELD": + assert combobox.currentData() == LocalizationType.ADAPTIVE + + +@pytest.mark.parametrize( + ("object_name", "property_name", "changed_value"), + [ + ("localization_correlation_threshold", "correlation_threshold", 0.7), + ("enkf_truncation", "enkf_truncation", 0.4), + ], +) +def test_that_changing_numeric_control_updates_corresponding_property( + qtbot: QtBot, + object_name: str, + property_name: str, + changed_value: float, +) -> None: + widget = AnalysisModuleVariablesPanel( + update_strategies={}, + correlation_threshold=0.5, + enkf_truncation=0.2, + ) + qtbot.addWidget(widget) + + spinner = widget.findChild(QDoubleSpinBox, name=object_name) + + assert spinner is not None + spinner.setValue(changed_value) + assert math.isclose(getattr(widget, property_name), changed_value) + + +@pytest.mark.parametrize( + ("parameter_type", "initial_strategy", "changed_strategy"), + [ + ("GEN_KW", LocalizationType.GLOBAL, LocalizationType.ADAPTIVE), + ("FIELD", LocalizationType.ADAPTIVE, LocalizationType.DISTANCE), + ("SURFACE", LocalizationType.DISTANCE, LocalizationType.GLOBAL), + ], +) +def test_that_changing_localization_control_updates_parameter_strategy_property( + qtbot: QtBot, + parameter_type: str, + initial_strategy: LocalizationType, + changed_strategy: LocalizationType, +) -> None: + update_strategies = { + "GEN_KW": LocalizationType.GLOBAL, + "FIELD": LocalizationType.ADAPTIVE, + "SURFACE": LocalizationType.DISTANCE, + } + widget = AnalysisModuleVariablesPanel( + update_strategies=update_strategies, + correlation_threshold=0.5, + enkf_truncation=0.2, + ) + qtbot.addWidget(widget) + + matching_comboboxes = [ + combobox + for combobox in widget.findChildren(QComboBox) + if combobox.currentData() == initial_strategy + ] + assert len(matching_comboboxes) == 1 + + combobox = matching_comboboxes[0] + changed_index = combobox.findData(changed_strategy) + assert changed_index != -1 + combobox.setCurrentIndex(changed_index) + + assert widget.update_strategies[parameter_type] == changed_strategy diff --git a/tests/ert/unit_tests/gui/experiments/test_manual_update.py b/tests/ert/unit_tests/gui/experiments/test_manual_update.py index d705afdd484..7f6d0d940cd 100644 --- a/tests/ert/unit_tests/gui/experiments/test_manual_update.py +++ b/tests/ert/unit_tests/gui/experiments/test_manual_update.py @@ -53,6 +53,7 @@ def test_that_active_realizations_selector_validates_with_ensemble_size_from_pri analysis_config=AnalysisConfig(minimum_required_realizations=1), run_path="", notifier=notifier, + parameter_configuration=[], ) qtbot.addWidget(panel) @@ -120,6 +121,7 @@ def test_that_manual_update_ensemble_selector_only_shows_ensembles_with_data( analysis_config=AnalysisConfig(minimum_required_realizations=1), run_path="", notifier=notifier, + parameter_configuration=[], ) qtbot.addWidget(panel) ensemble_selector = panel.findChild(EnsembleSelector) @@ -154,6 +156,7 @@ def test_that_panel_does_not_crash_when_no_realization_has_parameters( analysis_config=AnalysisConfig(minimum_required_realizations=1), run_path="", notifier=notifier, + parameter_configuration=[], ) qtbot.addWidget(panel) @@ -188,6 +191,7 @@ def test_that_empty_experiment_name_field_defaults_to_manual_update_of_mock_ense analysis_config=AnalysisConfig(minimum_required_realizations=1), run_path="", notifier=notifier, + parameter_configuration=[], ) qtbot.addWidget(panel) @@ -211,6 +215,7 @@ def test_that_experiment_name_field_is_used_in_experiment_arguments( analysis_config=AnalysisConfig(minimum_required_realizations=1), run_path="", notifier=notifier, + parameter_configuration=[], ) qtbot.addWidget(panel) diff --git a/tests/ert/unit_tests/gui/experiments/test_multiple_data_assimilation_panel.py b/tests/ert/unit_tests/gui/experiments/test_multiple_data_assimilation_panel.py index 00cddc3d215..ab679abf383 100644 --- a/tests/ert/unit_tests/gui/experiments/test_multiple_data_assimilation_panel.py +++ b/tests/ert/unit_tests/gui/experiments/test_multiple_data_assimilation_panel.py @@ -1,4 +1,3 @@ -import math from dataclasses import dataclass from unittest.mock import MagicMock @@ -231,7 +230,7 @@ def _open_and_capture_threshold(panel, qtbot): def inspect_and_close_dialog() -> None: nonlocal captured_value dialog = QApplication.activeModalWidget() - if isinstance(dialog, QDialog) and dialog.windowTitle() == "Edit variables": + if isinstance(dialog, QDialog) and dialog.windowTitle() == "Update settings": spinner = dialog.findChild( QDoubleSpinBox, name="localization_correlation_threshold" ) @@ -248,39 +247,6 @@ def inspect_and_close_dialog() -> None: return captured_value -@pytest.mark.parametrize( - ("active_realizations", "expected_threshold"), - [ - ([True], 1.0), - ([True, False, True, True, True, True, True, True], 1), - ( - [False, True, True] * 5, - 3 / math.sqrt(10), - ), - ([True, False] * 200, 3 / math.sqrt(200)), - ], -) -def test_that_analysis_module_edit_threshold_matches_expected_from_ensemble_size_via_ui( - qtbot: QtBot, active_realizations, expected_threshold -) -> None: - notifier = ErtNotifier() - notifier._storage = MockStorage() - - panel = MultipleDataAssimilationPanel( - analysis_config=AnalysisConfig(minimum_required_realizations=1), - parameter_configuration=EnsembleConfig().parameter_configuration, - run_path="", - notifier=notifier, - active_realizations=active_realizations, - config_num_realization=len(active_realizations), - ) - qtbot.addWidget(panel) - - observed_threshold = _open_and_capture_threshold(panel, qtbot) - - assert observed_threshold == pytest.approx(expected_threshold) - - @dataclass(frozen=True) class EnsInfo: ensemble_id: str diff --git a/tests/ert/unit_tests/gui/run_analysis/test_analysispanel.py b/tests/ert/unit_tests/gui/run_analysis/test_analysispanel.py deleted file mode 100644 index 647362bbe2c..00000000000 --- a/tests/ert/unit_tests/gui/run_analysis/test_analysispanel.py +++ /dev/null @@ -1,45 +0,0 @@ -import math - -import pytest -from PyQt6.QtWidgets import QDoubleSpinBox -from pytestqt.qtbot import QtBot - -from ert.config import ESSettings -from ert.gui.ertwidgets.analysismodulevariablespanel import AnalysisModuleVariablesPanel - - -@pytest.mark.parametrize( - ("ensemble_size", "expected"), - [ - (1, 1), - (8, 1), - (9, 3 / math.sqrt(9)), - (200, 3 / math.sqrt(200)), - ], -) -def test_that_default_localization_threshold_depends_on_ensemble_size( - qtbot: QtBot, ensemble_size, expected -): - settings = ESSettings() - widget = AnalysisModuleVariablesPanel(settings, ensemble_size) - qtbot.addWidget(widget) - - spinner = widget.findChild( - QDoubleSpinBox, name="localization_correlation_threshold" - ) - assert spinner.value() == pytest.approx(expected) - - -@pytest.mark.parametrize("set_value", [0.0, 0.2, 0.5, 1.0]) -def test_that_setting_localization_threshold_updates_analysis_settings( - qtbot: QtBot, set_value -): - settings = ESSettings() - widget = AnalysisModuleVariablesPanel(settings, 123) - qtbot.addWidget(widget) - - spinner = widget.findChild( - QDoubleSpinBox, name="localization_correlation_threshold" - ) - spinner.setValue(set_value) - assert spinner.value() == settings.localization_correlation_threshold == set_value diff --git a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/heat_equationconfig.ert/config.json b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/heat_equationconfig.ert/config.json index c2a6328ff4a..c7ef3ff50ee 100644 --- a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/heat_equationconfig.ert/config.json +++ b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/heat_equationconfig.ert/config.json @@ -200,66 +200,7 @@ }, "experiment_name": "es-mda", "design_matrix": null, - "parameter_configuration": [ - { - "type": "gen_kw", - "name": "t", - "forward_init": false, - "update_strategy": "adaptive", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.5, - "max": 1.5 - }, - "group": "INIT_TEMP_SCALE", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "x", - "forward_init": false, - "update_strategy": "adaptive", - "dimensionality": 1, - "distribution": { - "name": "normal", - "mean": 0.8, - "std": 0.1 - }, - "group": "CORR_LENGTH", - "input_source": "sampled" - }, - { - "type": "field", - "name": "COND", - "forward_init": true, - "update_strategy": "adaptive", - "dimensionality": 3, - "ertbox_params": { - "nx": 50, - "ny": 50, - "nz": 1, - "axis_orientation": 1, - "xlength": 50.0, - "ylength": 50.0, - "xinc": 1.0, - "yinc": 1.0, - "rotation_angle": 0.0, - "origin": [ - 0.0, - 0.0 - ] - }, - "file_format": "bgrdecl", - "output_transformation": "EXP", - "input_transformation": "LN", - "truncation_min": null, - "truncation_max": null, - "forward_init_file": "cond.bgrdecl", - "output_file": "cond.bgrdecl", - "grid_file": "test-data/ert/heat_equation.config.ert/CASE.EGRID" - } - ], + "parameter_configuration": [], "response_configuration": [ { "type": "summary", diff --git a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/poly_examplepoly.ert/poly.json b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/poly_examplepoly.ert/poly.json index 8c3c2753baa..2acbfb9acde 100644 --- a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/poly_examplepoly.ert/poly.json +++ b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/poly_examplepoly.ert/poly.json @@ -190,50 +190,7 @@ }, "experiment_name": "es-mda", "design_matrix": null, - "parameter_configuration": [ - { - "type": "gen_kw", - "name": "a", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.0, - "max": 1.0 - }, - "group": "COEFFS", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "b", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.0, - "max": 2.0 - }, - "group": "COEFFS", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "c", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.0, - "max": 5.0 - }, - "group": "COEFFS", - "input_source": "sampled" - } - ], + "parameter_configuration": [], "response_configuration": [ { "type": "gen_data", diff --git a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/snake_oilsnake_oil.ert/snake_oil.json b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/snake_oilsnake_oil.ert/snake_oil.json index 938f3a48458..633bb30c7c2 100644 --- a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/snake_oilsnake_oil.ert/snake_oil.json +++ b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_esmda_matches_snapshot/snake_oilsnake_oil.ert/snake_oil.json @@ -166,148 +166,7 @@ }, "experiment_name": "es-mda", "design_matrix": null, - "parameter_configuration": [ - { - "type": "gen_kw", - "name": "BPR_555_PERSISTENCE", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.1, - "max": 0.5 - }, - "group": "SNAKE_OIL_PARAM_BPR", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "BPR_138_PERSISTENCE", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.2, - "max": 0.7 - }, - "group": "SNAKE_OIL_PARAM_BPR", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "OP1_PERSISTENCE", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.01, - "max": 0.4 - }, - "group": "SNAKE_OIL_PARAM_OP1", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "OP1_OCTAVES", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 3.0, - "max": 5.0 - }, - "group": "SNAKE_OIL_PARAM_OP1", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "OP1_DIVERGENCE_SCALE", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.25, - "max": 1.25 - }, - "group": "SNAKE_OIL_PARAM_OP1", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "OP1_OFFSET", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": -0.1, - "max": 0.1 - }, - "group": "SNAKE_OIL_PARAM_OP1", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "OP2_PERSISTENCE", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.1, - "max": 0.6 - }, - "group": "SNAKE_OIL_PARAM_OP2", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "OP2_OCTAVES", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 5.0, - "max": 12.0 - }, - "group": "SNAKE_OIL_PARAM_OP2", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "OP2_DIVERGENCE_SCALE", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": 0.5, - "max": 1.5 - }, - "group": "SNAKE_OIL_PARAM_OP2", - "input_source": "sampled" - }, - { - "type": "gen_kw", - "name": "OP2_OFFSET", - "forward_init": false, - "update_strategy": "global", - "dimensionality": 1, - "distribution": { - "name": "uniform", - "min": -0.2, - "max": 0.2 - }, - "group": "SNAKE_OIL_PARAM_OP2", - "input_source": "sampled" - } - ], + "parameter_configuration": [], "response_configuration": [ { "type": "summary", diff --git a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/heat_equationconfig.ert/config.json b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/heat_equationconfig.ert/config.json index 12245d079ad..b24affe7e36 100644 --- a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/heat_equationconfig.ert/config.json +++ b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/heat_equationconfig.ert/config.json @@ -233,5 +233,6 @@ } } }, - "experiment_name": "my_experiment" + "experiment_name": "my_experiment", + "parameter_configuration": [] } diff --git a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/poly_examplepoly.ert/poly.json b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/poly_examplepoly.ert/poly.json index fd83ddb19da..21c7aeea901 100644 --- a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/poly_examplepoly.ert/poly.json +++ b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/poly_examplepoly.ert/poly.json @@ -194,5 +194,6 @@ "shape_registry": { "shapes": {} }, - "experiment_name": "my_experiment" + "experiment_name": "my_experiment", + "parameter_configuration": [] } diff --git a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/snake_oilsnake_oil.ert/snake_oil.json b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/snake_oilsnake_oil.ert/snake_oil.json index 35ba3f01148..93c23be4bef 100644 --- a/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/snake_oilsnake_oil.ert/snake_oil.json +++ b/tests/ert/unit_tests/run_models/snapshots/test_experiment_serialization/test_that_dumped_manual_update_matches_snapshot/snake_oilsnake_oil.ert/snake_oil.json @@ -187,5 +187,6 @@ "shape_registry": { "shapes": {} }, - "experiment_name": "my_experiment" + "experiment_name": "my_experiment", + "parameter_configuration": [] } diff --git a/tests/ert/unit_tests/run_models/test_experiment_serialization.py b/tests/ert/unit_tests/run_models/test_experiment_serialization.py index 84cc4e777a1..be996dfd79e 100644 --- a/tests/ert/unit_tests/run_models/test_experiment_serialization.py +++ b/tests/ert/unit_tests/run_models/test_experiment_serialization.py @@ -748,6 +748,7 @@ def test_that_dumped_manual_update_matches_snapshot( ensemble_id=str(prior.id), target_ensemble="posterior", experiment_name="my_experiment", + parameter_configuration=[], ), case=f"{config_dir}.{config_file}", ) @@ -878,6 +879,7 @@ def test_that_dumped_esmda_matches_snapshot( weights="4, 2, 1", prior_ensemble_id=None, experiment_name="es-mda", + parameter_configuration=[], ), case=f"{config_dir}.{config_file}", ) diff --git a/tests/ert/unit_tests/run_models/test_manual_update.py b/tests/ert/unit_tests/run_models/test_manual_update.py index 54f0cd95bb8..8385c8265ca 100644 --- a/tests/ert/unit_tests/run_models/test_manual_update.py +++ b/tests/ert/unit_tests/run_models/test_manual_update.py @@ -57,6 +57,7 @@ def test_that_manual_update_from_ensemble_experiment_supports_all_update_modes( ensemble_id=ensemble_id_to_update, target_ensemble="updated_ens%d", experiment_name="my manual update", + parameter_configuration=[], ), status_queue=queue.SimpleQueue(), ) diff --git a/tests/ert/unit_tests/run_models/test_model_factory.py b/tests/ert/unit_tests/run_models/test_model_factory.py index 1f774528230..46be412c8c5 100644 --- a/tests/ert/unit_tests/run_models/test_model_factory.py +++ b/tests/ert/unit_tests/run_models/test_model_factory.py @@ -72,6 +72,7 @@ def test_that_the_model_warns_when_active_realizations_less_min_realizations( num_iterations=1, prior_ensemble_id="", weights="2,3", + parameter_configuration=[], ), queue.SimpleQueue(), ) @@ -201,6 +202,7 @@ def test_that_setup_multiple_data_assimilation_uses_the_arguments_from_the_cli( prior_ensemble_id=None, experiment_name="My-experiment", starting_iteration=0, + parameter_configuration=[], ), ObservationSettings(), queue.SimpleQueue(), @@ -235,6 +237,7 @@ def test_that_setup_multiple_data_assimilation_uses_config_weights_when_cli_omit prior_ensemble_id=None, experiment_name="My-experiment", starting_iteration=0, + parameter_configuration=[], ), ObservationSettings(), queue.SimpleQueue(), @@ -279,6 +282,7 @@ def test_multiple_data_assimilation_restart_paths( target_ensemble="restart_case_%d", prior_ensemble_id=str(uuid1()), experiment_name="just_assimilatin", + parameter_configuration=[], ) monkeypatch.setattr(