From ca70d8f8d7f0c428c6212f2b51d3011530346861 Mon Sep 17 00:00:00 2001 From: Jonathan Karlsen Date: Wed, 16 Oct 2024 16:12:25 +0200 Subject: [PATCH] Add validation for uninitialized ensembles in manual update and evaluate experiment This commit adds validation for the realizations specified by the user when trying to run `manual_update` or `evaluate_experiment`. The validator checks the selected ensemble if the specified realization(s) exists. If not, the field becomes red and a warning is displayed. The commit also disables the realization field until an ensemble is selected. --- .../gui/simulation/evaluate_ensemble_panel.py | 11 ++-- src/ert/gui/simulation/manual_update_panel.py | 14 ++--- src/ert/validation/__init__.py | 2 + .../realizations_in_ensemble_argument.py | 51 +++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 src/ert/validation/realizations_in_ensemble_argument.py diff --git a/src/ert/gui/simulation/evaluate_ensemble_panel.py b/src/ert/gui/simulation/evaluate_ensemble_panel.py index 44d7400c4ef..546e99a365e 100644 --- a/src/ert/gui/simulation/evaluate_ensemble_panel.py +++ b/src/ert/gui/simulation/evaluate_ensemble_panel.py @@ -14,7 +14,7 @@ from ert.gui.simulation.experiment_config_panel import ExperimentConfigPanel from ert.mode_definitions import EVALUATE_ENSEMBLE_MODE from ert.run_models.evaluate_ensemble import EvaluateEnsemble -from ert.validation import RangeStringArgument +from ert.validation import RealizationsInEnsembleArgument @dataclass @@ -47,9 +47,10 @@ def __init__(self, ensemble_size: int, run_path: str, notifier: ErtNotifier): ActiveRealizationsModel(ensemble_size, show_default=False), # type: ignore "config/simulation/active_realizations", ) - self._active_realizations_field.setValidator( - RangeStringArgument(ensemble_size), + self._realizations_validator = RealizationsInEnsembleArgument( + self._ensemble_selector.selected_ensemble, max_value=ensemble_size ) + self._active_realizations_field.setValidator(self._realizations_validator) self._realizations_from_fs() layout.addRow("Active realizations", self._active_realizations_field) @@ -68,7 +69,7 @@ def isConfigurationValid(self) -> bool: return ( self._active_realizations_field.isValid() and self._ensemble_selector.currentIndex() != -1 - and bool(self._active_realizations_field.text()) + and self._active_realizations_field.isValid() ) def get_experiment_arguments(self) -> Arguments: @@ -80,7 +81,9 @@ def get_experiment_arguments(self) -> Arguments: def _realizations_from_fs(self) -> None: ensemble = self._ensemble_selector.selected_ensemble + self._active_realizations_field.setEnabled(ensemble is not None) if ensemble: + self._realizations_validator.set_ensemble(ensemble) parameters = ensemble.get_realization_mask_with_parameters() missing_responses = ~ensemble.get_realization_mask_with_responses() failures = ~ensemble.get_realization_mask_without_failure() diff --git a/src/ert/gui/simulation/manual_update_panel.py b/src/ert/gui/simulation/manual_update_panel.py index b96f75db15f..a685fc9c2fa 100644 --- a/src/ert/gui/simulation/manual_update_panel.py +++ b/src/ert/gui/simulation/manual_update_panel.py @@ -17,7 +17,7 @@ from ert.gui.simulation.experiment_config_panel import ExperimentConfigPanel from ert.mode_definitions import MANUAL_UPDATE_MODE from ert.run_models.manual_update import ManualUpdate -from ert.validation import ProperNameFormatArgument, RangeStringArgument +from ert.validation import ProperNameFormatArgument, RealizationsInEnsembleArgument @dataclass @@ -72,14 +72,13 @@ def __init__( ActiveRealizationsModel(ensemble_size, show_default=False), # type: ignore "config/simulation/active_realizations", ) - self._active_realizations_field.setValidator( - RangeStringArgument(ensemble_size), + self._realizations_validator = RealizationsInEnsembleArgument( + self._ensemble_selector.selected_ensemble, max_value=ensemble_size ) + self._active_realizations_field.setValidator(self._realizations_validator) self._realizations_from_fs() layout.addRow("Active realizations", self._active_realizations_field) - self.setLayout(layout) - self._active_realizations_field.getValidationSupport().validationChanged.connect( self.simulationConfigurationChanged ) @@ -88,12 +87,13 @@ def __init__( self.simulationConfigurationChanged ) self._ensemble_selector.currentIndexChanged.connect(self._realizations_from_fs) + self.setLayout(layout) def isConfigurationValid(self) -> bool: return ( self._active_realizations_field.isValid() and self._ensemble_selector.currentIndex() != -1 - and bool(self._active_realizations_field.text()) + and self._active_realizations_field.isValid() ) def get_experiment_arguments(self) -> Arguments: @@ -106,7 +106,9 @@ def get_experiment_arguments(self) -> Arguments: def _realizations_from_fs(self) -> None: ensemble = self._ensemble_selector.selected_ensemble + self._active_realizations_field.setEnabled(ensemble is not None) if ensemble: + self._realizations_validator.set_ensemble(ensemble) parameters = ensemble.get_realization_mask_with_parameters() responses = ensemble.get_realization_mask_with_responses() mask = np.logical_and(parameters, responses) diff --git a/src/ert/validation/__init__.py b/src/ert/validation/__init__.py index b503bea56cc..53433d46b14 100644 --- a/src/ert/validation/__init__.py +++ b/src/ert/validation/__init__.py @@ -6,6 +6,7 @@ from .proper_name_format_argument import ProperNameFormatArgument from .range_string_argument import RangeStringArgument from .rangestring import mask_to_rangestring, rangestring_to_list, rangestring_to_mask +from .realizations_in_ensemble_argument import RealizationsInEnsembleArgument from .runpath_argument import RunPathArgument from .validation_status import ValidationStatus @@ -17,6 +18,7 @@ "ProperNameArgument", "ProperNameFormatArgument", "RangeStringArgument", + "RealizationsInEnsembleArgument", "RunPathArgument", "ValidationStatus", "mask_to_rangestring", diff --git a/src/ert/validation/realizations_in_ensemble_argument.py b/src/ert/validation/realizations_in_ensemble_argument.py new file mode 100644 index 00000000000..035ec29f2a7 --- /dev/null +++ b/src/ert/validation/realizations_in_ensemble_argument.py @@ -0,0 +1,51 @@ +from typing import TYPE_CHECKING, Optional + +from .range_string_argument import RangeStringArgument +from .rangestring import rangestring_to_list +from .validation_status import ValidationStatus + +if TYPE_CHECKING: + from ert.storage import Ensemble + + +class RealizationsInEnsembleArgument(RangeStringArgument): + UNINITIALIZED_REALIZATIONS_SPECIFIED = ( + "The specified realization(s) %s are not found in selected ensemble." + ) + + def __init__( + self, ensemble: "Ensemble", max_value: Optional[int], **kwargs: bool + ) -> None: + super().__init__(max_value, **kwargs) + self.__ensemble = ensemble + + def set_ensemble(self, ensemble: "Ensemble") -> None: + self.__ensemble = ensemble + + def validate(self, token: str) -> ValidationStatus: + if not token: + return ValidationStatus() + + validation_status = super().validate(token) + if not validation_status: + return validation_status + attempted_realizations = rangestring_to_list(token) + + invalid_realizations = [] + for realization in attempted_realizations: + if not self._validate_selected_realization_exist(realization): + invalid_realizations.append(realization) + + if invalid_realizations: + validation_status.setFailed() + validation_status.addToMessage( + RealizationsInEnsembleArgument.UNINITIALIZED_REALIZATIONS_SPECIFIED + % str(invalid_realizations) + ) + return validation_status + + validation_status.setValue(token) + return validation_status + + def _validate_selected_realization_exist(self, realization: int): + return self.__ensemble._responses_exist_for_realization(realization)