From b029feea5ef5892b24c35a9f70535df8d9c199be 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 | 49 +++++++++++++++++++ 4 files changed, 66 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..a81b40ad15c 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 EnsembleRealizationsArgument @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 = EnsembleRealizationsArgument( + 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..4a7d2a93be5 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 EnsembleRealizationsArgument, ProperNameFormatArgument @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 = EnsembleRealizationsArgument( + 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..c1da09fbfac 100644 --- a/src/ert/validation/__init__.py +++ b/src/ert/validation/__init__.py @@ -6,12 +6,14 @@ 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 EnsembleRealizationsArgument from .runpath_argument import RunPathArgument from .validation_status import ValidationStatus __all__ = [ "ActiveRange", "ArgumentDefinition", + "EnsembleRealizationsArgument", "IntegerArgument", "NumberListStringArgument", "ProperNameArgument", 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..4220b00b65f --- /dev/null +++ b/src/ert/validation/realizations_in_ensemble_argument.py @@ -0,0 +1,49 @@ +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 EnsembleRealizationsArgument(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 = [] + initialized_realization_ids = self.__ensemble.is_initalized() + for realization in attempted_realizations: + if not realization in initialized_realization_ids: + invalid_realizations.append(realization) + + if invalid_realizations: + validation_status.setFailed() + validation_status.addToMessage( + EnsembleRealizationsArgument.UNINITIALIZED_REALIZATIONS_SPECIFIED + % str(invalid_realizations) + ) + return validation_status + + validation_status.setValue(token) + return validation_status