Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve experiment/ensemble name validation in GUI #8646

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions src/ert/gui/ertwidgets/create_experiment_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
QDialogButtonBox,
QGridLayout,
QLabel,
QLineEdit,
QWidget,
)

from ert.gui.ertnotifier import ErtNotifier
from ert.gui.ertwidgets import StringBox, TextModel
from ert.validation.range_string_argument import NotInStorage
from ert.validation.proper_name_argument import (
ExperimentValidation,
ProperNameArgument,
)


class CreateExperimentDialog(QDialog):
Expand All @@ -36,16 +38,15 @@ def __init__(

experiment_label = QLabel("Experiment name:")
self._experiment_edit = StringBox(
TextModel(""), placeholder_text="My experiment", minimum_width=200
)
self._experiment_edit.setValidator(
NotInStorage(notifier.storage, "experiments")
TextModel(""), placeholder_text="My_experiment", minimum_width=200
)
self._experiment_edit.setValidator(ExperimentValidation(notifier.storage))

ensemble_label = QLabel("Ensemble name:")
self._ensemble_edit = QLineEdit()
self._ensemble_edit.setMinimumWidth(200)
self._ensemble_edit.setPlaceholderText("My ensemble")
self._ensemble_edit = StringBox(
TextModel(""), placeholder_text="My_ensemble", minimum_width=200
)
self._ensemble_edit.setValidator(ProperNameArgument())

buttons = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
Expand All @@ -66,10 +67,7 @@ def __init__(
self._ok_button.setEnabled(False)

def enableOkButton() -> None:
self._ok_button.setEnabled(
len(self._experiment_edit.text()) != 0
and len(self._ensemble_edit.text()) != 0
)
self._ok_button.setEnabled(self.isConfigurationValid())

self._experiment_edit.textChanged.connect(enableOkButton)
self._ensemble_edit.textChanged.connect(enableOkButton)
Expand All @@ -82,6 +80,14 @@ def enableOkButton() -> None:

self.setLayout(layout)

self._experiment_edit.getValidationSupport().validationChanged.connect(
enableOkButton
)

self._ensemble_edit.getValidationSupport().validationChanged.connect(
enableOkButton
)

self._experiment_edit.setFocus()

@property
Expand All @@ -91,3 +97,6 @@ def experiment_name(self) -> str:
@property
def ensemble_name(self) -> str:
return self._ensemble_edit.text()

def isConfigurationValid(self) -> bool:
return self._experiment_edit.isValid() and self._ensemble_edit.isValid()
11 changes: 8 additions & 3 deletions src/ert/gui/simulation/ensemble_experiment_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
from ert.mode_definitions import ENSEMBLE_EXPERIMENT_MODE
from ert.run_models import EnsembleExperiment
from ert.validation import RangeStringArgument
from ert.validation.range_string_argument import NotInStorage
from ert.validation.proper_name_argument import (
ExperimentValidation,
ProperNameArgument,
)

from .experiment_config_panel import ExperimentConfigPanel

Expand Down Expand Up @@ -46,14 +49,16 @@ def __init__(self, ensemble_size: int, run_path: str, notifier: ErtNotifier):
),
)
self._experiment_name_field.setMinimumWidth(250)
layout.addRow("Experiment name:", self._experiment_name_field)
self._experiment_name_field.setValidator(
NotInStorage(self.notifier.storage, "experiments")
ExperimentValidation(self.notifier.storage)
)
self._experiment_name_field.setObjectName("experiment_field")
layout.addRow("Experiment name:", self._experiment_name_field)

self._ensemble_name_field = StringBox(
TextModel(""), placeholder_text="ensemble"
)
self._ensemble_name_field.setValidator(ProperNameArgument())
self._ensemble_name_field.setMinimumWidth(250)

layout.addRow("Ensemble name:", self._ensemble_name_field)
Expand Down
17 changes: 11 additions & 6 deletions src/ert/gui/simulation/ensemble_smoother_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ert.mode_definitions import ENSEMBLE_SMOOTHER_MODE
from ert.run_models import EnsembleSmoother
from ert.validation import ProperNameFormatArgument, RangeStringArgument
from ert.validation.range_string_argument import NotInStorage
from ert.validation.proper_name_argument import ExperimentValidation

from .experiment_config_panel import ExperimentConfigPanel

Expand Down Expand Up @@ -55,10 +55,11 @@ def __init__(
),
)
self._experiment_name_field.setMinimumWidth(250)
layout.addRow("Experiment name:", self._experiment_name_field)
self._experiment_name_field.setValidator(
NotInStorage(self.notifier.storage, "experiments")
ExperimentValidation(self.notifier.storage)
)
self._experiment_name_field.setObjectName("experiment_field")
layout.addRow("Experiment name:", self._experiment_name_field)

runpath_label = CopyableLabel(text=run_path)
layout.addRow("Runpath:", runpath_label)
Expand Down Expand Up @@ -90,10 +91,13 @@ def __init__(

self.setLayout(layout)

self._ensemble_format_field.getValidationSupport().validationChanged.connect( # noqa
self._experiment_name_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)
self._ensemble_format_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)
self._active_realizations_field.getValidationSupport().validationChanged.connect( # noqa
self._active_realizations_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)

Expand All @@ -111,7 +115,8 @@ def _update_experiment_name_placeholder(self) -> None:

def isConfigurationValid(self) -> bool:
return (
self._ensemble_format_field.isValid()
self._experiment_name_field.isValid()
and self._ensemble_format_field.isValid()
and self._active_realizations_field.isValid()
)

Expand Down
18 changes: 12 additions & 6 deletions src/ert/gui/simulation/iterated_ensemble_smoother_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ert.mode_definitions import ITERATIVE_ENSEMBLE_SMOOTHER_MODE
from ert.run_models import IteratedEnsembleSmoother
from ert.validation import ProperNameFormatArgument, RangeStringArgument
from ert.validation.range_string_argument import NotInStorage
from ert.validation.proper_name_argument import ExperimentValidation

from .experiment_config_panel import ExperimentConfigPanel

Expand Down Expand Up @@ -47,6 +47,7 @@ def __init__(
ExperimentConfigPanel.__init__(self, IteratedEnsembleSmoother)
self.analysis_config = analysis_config
layout = QFormLayout()
self.setObjectName("iterated_ensemble_smoother_panel")

self._experiment_name_field = StringBox(
TextModel(""),
Expand All @@ -55,10 +56,11 @@ def __init__(
),
)
self._experiment_name_field.setMinimumWidth(250)
layout.addRow("Experiment name:", self._experiment_name_field)
self._experiment_name_field.setValidator(
NotInStorage(self.notifier.storage, "experiments")
ExperimentValidation(self.notifier.storage)
)
self._experiment_name_field.setObjectName("experiment_field")
layout.addRow("Experiment name:", self._experiment_name_field)

runpath_label = CopyableLabel(text=run_path)
layout.addRow("Runpath:", runpath_label)
Expand Down Expand Up @@ -103,10 +105,13 @@ def __init__(
self._active_realizations_field.setValidator(RangeStringArgument(ensemble_size))
layout.addRow("Active realizations", self._active_realizations_field)

self._iterated_target_ensemble_format_field.getValidationSupport().validationChanged.connect( # noqa
self._experiment_name_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)
self._iterated_target_ensemble_format_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)
self._active_realizations_field.getValidationSupport().validationChanged.connect( # noqa
self._active_realizations_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)
self.setLayout(layout)
Expand All @@ -127,7 +132,8 @@ def _update_experiment_name_placeholder(self) -> None:

def isConfigurationValid(self) -> bool:
return (
self._iterated_target_ensemble_format_field.isValid()
self._experiment_name_field.isValid()
and self._iterated_target_ensemble_format_field.isValid()
and self._active_realizations_field.isValid()
)

Expand Down
29 changes: 17 additions & 12 deletions src/ert/gui/simulation/multiple_data_assimilation_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
ProperNameFormatArgument,
RangeStringArgument,
)
from ert.validation.range_string_argument import NotInStorage
from ert.validation.proper_name_argument import ExperimentValidation

from .experiment_config_panel import ExperimentConfigPanel

Expand Down Expand Up @@ -66,10 +66,12 @@ def __init__(
),
)
self._experiment_name_field.setMinimumWidth(250)
layout.addRow("Experiment name:", self._experiment_name_field)
self._experiment_name_field.setValidator(
NotInStorage(self.notifier.storage, "experiments")
ExperimentValidation(self.notifier.storage)
)
self._experiment_name_field.setObjectName("experiment_field")
layout.addRow("Experiment name:", self._experiment_name_field)

runpath_label = CopyableLabel(text=run_path)
layout.addRow("Runpath:", runpath_label)

Expand Down Expand Up @@ -119,13 +121,16 @@ def __init__(
self._ensemble_selector.currentIndexChanged.connect(self.update_experiment_name)
layout.addRow("Restart from:", self._ensemble_selector)

self._target_ensemble_format_field.getValidationSupport().validationChanged.connect( # noqa
self._experiment_name_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)
self._target_ensemble_format_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)
self._active_realizations_field.getValidationSupport().validationChanged.connect( # noqa
self._active_realizations_field.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)
self._relative_iteration_weights_box.getValidationSupport().validationChanged.connect( # noqa
self._relative_iteration_weights_box.getValidationSupport().validationChanged.connect(
self.simulationConfigurationChanged
)

Expand Down Expand Up @@ -158,15 +163,14 @@ def update_experiment_name(self) -> None:

@Slot(bool)
def update_experiment_edit(self, checked: bool) -> None:
self._experiment_name_field.clear()
self._experiment_name_field.enable_validation(not checked)
self._experiment_name_field.setEnabled(not checked)
if checked:
self._experiment_name_field.setText(
self._ensemble_selector.selected_ensemble.experiment.name
)
else:
self._experiment_name_field.clear()

self._experiment_name_field.enable_validation(not checked)
self._experiment_name_field.setEnabled(not checked)
self._evaluate_weights_box_enabled()

def _evaluate_weights_box_enabled(self) -> None:
Expand Down Expand Up @@ -219,15 +223,16 @@ def updateVisualizationOfNormalizedWeights() -> None:
else:
normalized_weights_model.setValue("The weights are invalid!")

self._relative_iteration_weights_box.getValidationSupport().validationChanged.connect( # noqa
self._relative_iteration_weights_box.getValidationSupport().validationChanged.connect(
updateVisualizationOfNormalizedWeights
)

updateVisualizationOfNormalizedWeights() # To normalize the default weights

def isConfigurationValid(self) -> bool:
return (
self._target_ensemble_format_field.isValid()
self._experiment_name_field.isValid()
and self._target_ensemble_format_field.isValid()
and self._active_realizations_field.isValid()
and self._relative_iteration_weights_box.isValid()
and self.weights_valid
Expand Down
30 changes: 30 additions & 0 deletions src/ert/validation/proper_name_argument.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
from __future__ import annotations

import re
from typing import TYPE_CHECKING

from .argument_definition import ArgumentDefinition
from .validation_status import ValidationStatus

if TYPE_CHECKING:
from ..storage import Storage


class ProperNameArgument(ArgumentDefinition):
NOT_A_VALID_NAME = (
Expand Down Expand Up @@ -36,3 +42,27 @@ def validate(self, token: str) -> ValidationStatus:
validation_status.setValue(token)

return validation_status


class ExperimentValidation(ProperNameArgument):
def __init__(self, storage: Storage) -> None:
self.storage = storage
super().__init__()

def validate(self, token: str) -> ValidationStatus:
validation_status = super().validate(token)

if not validation_status:
return validation_status

existing = [exp.name for exp in self.storage.experiments]

if token in existing:
validation_status.setFailed()
validation_status.addToMessage(
f"Experiment name must be unique, not one of: {existing}"
)
elif not validation_status.failed():
validation_status.setValue(token)

return validation_status
25 changes: 1 addition & 24 deletions src/ert/validation/range_string_argument.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Optional
from typing import Optional

from .active_range import ActiveRange
from .argument_definition import ArgumentDefinition
from .validation_status import ValidationStatus

if TYPE_CHECKING:
from ..storage import Storage


class RangeStringArgument(ArgumentDefinition):
NOT_A_VALID_RANGE_STRING = (
Expand Down Expand Up @@ -46,23 +43,3 @@ def validate(self, token: str) -> ValidationStatus:
validation_status.setValue(token)

return validation_status


class NotInStorage(ArgumentDefinition):
def __init__(self, storage: Storage, prop: str) -> None:
self.storage = storage
self.prop = prop

def validate(self, token: str) -> ValidationStatus:
validation_status = ValidationStatus()

existing = [exp.name for exp in getattr(self.storage, self.prop)]
if token in existing:
validation_status.setFailed()
validation_status.addToMessage(
f"{self.prop.capitalize()} name must be unique, not one of: {existing}"
)

validation_status.setValue(token)

return validation_status
2 changes: 1 addition & 1 deletion tests/unit_tests/gui/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ def handle_popup_dialog():


def add_experiment_manually(
qtbot, gui, experiment_name="My experiment", ensemble_name="default"
qtbot, gui, experiment_name="My_experiment", ensemble_name="default"
):
manage_tool = gui.tools["Manage experiments"]
manage_tool.trigger()
Expand Down
Loading