Skip to content

Commit

Permalink
Group items in Experiment Panel's drop-down
Browse files Browse the repository at this point in the history
  • Loading branch information
AugustoMagalhaes authored Sep 30, 2024
1 parent 0e88cbd commit e607feb
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 45 deletions.
44 changes: 36 additions & 8 deletions src/ert/gui/simulation/combobox_with_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

LABEL_ROLE = -3994
DESCRIPTION_ROLE = -4893
GROUP_TITLE_ROLE = -4894

COLOR_HIGHLIGHT_LIGHT = QColor(230, 230, 230, 255)
COLOR_HIGHLIGHT_DARK = QColor(60, 60, 60, 255)
Expand All @@ -26,19 +27,34 @@ def __init__(
description: str,
enabled: bool = True,
parent: Optional[QWidget] = None,
group: Optional[str] = None,
) -> None:
super().__init__(parent)
layout = QVBoxLayout()
layout.setSpacing(2)
layout.setSpacing(5)
self.setStyleSheet("background: rgba(0,0,0,1);")
self.label = QLabel(label)
color = "color: rgba(192,192,192,80);" if not enabled else ";"
pd_top = "0px" if group else "5px"
if group:
self.group = QLabel(group)
self.group.setStyleSheet(
f"""
{color}
padding-top: 5px;
padding-left: 2px;
background: rgba(0,0,0,0);
font-style: italic;
font-size: 14px;
"""
)
layout.addWidget(self.group)

self.label.setStyleSheet(
f"""
{color}
padding-top:5px;
padding-left: 5px;
padding-top:{pd_top};
padding-left: 10px;
background: rgba(0,0,0,0);
font-weight: bold;
font-size: 13px;
Expand All @@ -49,7 +65,7 @@ def __init__(
f"""
{color}
padding-bottom: 10px;
padding-left: 10px;
padding-left: 15px;
background: rgba(0,0,0,0);
font-style: italic;
font-size: 12px;
Expand All @@ -67,6 +83,7 @@ def paint(self, painter: Any, option: Any, index: Any) -> None:

label = index.data(LABEL_ROLE)
description = index.data(DESCRIPTION_ROLE)
group = index.data(GROUP_TITLE_ROLE)

is_enabled = option.state & QStyle.State_Enabled # type: ignore

Expand All @@ -79,7 +96,7 @@ def paint(self, painter: Any, option: Any, index: Any) -> None:
color = COLOR_HIGHLIGHT_DARK
painter.fillRect(option.rect, color)

widget = _ComboBoxItemWidget(label, description, is_enabled)
widget = _ComboBoxItemWidget(label, description, is_enabled, group=group)
widget.setStyle(option.widget.style())
widget.resize(option.rect.size())

Expand All @@ -90,20 +107,31 @@ def paint(self, painter: Any, option: Any, index: Any) -> None:
def sizeHint(self, option: QStyleOptionViewItem, index: QModelIndex) -> QSize:
label = index.data(LABEL_ROLE)
description = index.data(DESCRIPTION_ROLE)
group = index.data(GROUP_TITLE_ROLE)
adjustment = QSize(0, 20) if group else QSize(0, 0)

widget = _ComboBoxItemWidget(label, description)
return widget.sizeHint()
widget = _ComboBoxItemWidget(label, description, group)
return widget.sizeHint() + adjustment


class QComboBoxWithDescription(QComboBox):
def __init__(self, parent: Optional[QWidget] = None) -> None:
super().__init__(parent)
self.setItemDelegate(_ComboBoxWithDescriptionDelegate(self))

def addDescriptionItem(self, label: Optional[str], description: Any) -> None:
def addDescriptionItem(
self, label: Optional[str], description: Any, group: Optional[str] = None
) -> None:
super().addItem(label)
model = self.model()
assert model is not None
index = model.index(self.count() - 1, 0)
model.setData(index, label, LABEL_ROLE)
model.setData(index, description, DESCRIPTION_ROLE)
model.setData(index, group, GROUP_TITLE_ROLE)

def sizeHint(self) -> QSize:
original_size_hint = super().sizeHint()
new_width = int(original_size_hint.width() + 220)
new_height = int(super().sizeHint().height() * 1.5)
return QSize(new_width, new_height)
62 changes: 42 additions & 20 deletions src/ert/gui/simulation/experiment_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
QCheckBox,
QFrame,
QHBoxLayout,
QLabel,
QMessageBox,
QStackedWidget,
QStyle,
Expand Down Expand Up @@ -49,8 +48,7 @@
if TYPE_CHECKING:
from ert.config import ErtConfig

EXPERIMENT_READY_TO_RUN_BUTTON_MESSAGE = "Run Experiment"
EXPERIMENT_IS_RUNNING_BUTTON_MESSAGE = "Experiment running..."
EXPERIMENT_IS_MANUAL_UPDATE_MESSAGE = "Execute Selected"


def create_md_table(kv: Dict[str, str], output: str) -> str:
Expand Down Expand Up @@ -89,9 +87,6 @@ def __init__(

experiment_type_layout = QHBoxLayout()
experiment_type_layout.addSpacing(10)
experiment_type_layout.addWidget(
QLabel("Experiment type:"), 0, Qt.AlignmentFlag.AlignVCenter
)
experiment_type_layout.addWidget(
self._experiment_type_combo, 0, Qt.AlignmentFlag.AlignVCenter
)
Expand All @@ -100,11 +95,34 @@ def __init__(

self.run_button = QToolButton()
self.run_button.setObjectName("run_experiment")
self.run_button.setText(EXPERIMENT_READY_TO_RUN_BUTTON_MESSAGE)
self.run_button.setIcon(QIcon("img:play_circle.svg"))
self.run_button.setToolTip(EXPERIMENT_IS_MANUAL_UPDATE_MESSAGE)
self.run_button.setIconSize(QSize(32, 32))
self.run_button.clicked.connect(self.run_experiment)
self.run_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
self.run_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.run_button.setMinimumWidth(60)
self.run_button.setMinimumHeight(40)
self.run_button.setStyleSheet(
"""
QToolButton {
border-radius: 10px;
background-color: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #f0f0f0,
stop:1 #d9d9d9
);
border: 1px solid #bfbfbf;
padding: 5px;
}
QToolButton:hover {
background-color: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #d8d8d8,
stop:1 #c3c3c3
);
}
"""
)

experiment_type_layout.addWidget(self.run_button)
experiment_type_layout.addStretch(1)
Expand Down Expand Up @@ -138,26 +156,25 @@ def __init__(
)
analysis_config = config.analysis_config
self.addExperimentConfigPanel(
EnsembleSmootherPanel(analysis_config, run_path, notifier, ensemble_size),
MultipleDataAssimilationPanel(
analysis_config, run_path, notifier, ensemble_size
),
experiment_type_valid,
)
self.addExperimentConfigPanel(
ManualUpdatePanel(ensemble_size, run_path, notifier, analysis_config),
EnsembleSmootherPanel(analysis_config, run_path, notifier, ensemble_size),
experiment_type_valid,
)
self.addExperimentConfigPanel(
MultipleDataAssimilationPanel(
IteratedEnsembleSmootherPanel(
analysis_config, run_path, notifier, ensemble_size
),
experiment_type_valid,
)
self.addExperimentConfigPanel(
IteratedEnsembleSmootherPanel(
analysis_config, run_path, notifier, ensemble_size
),
ManualUpdatePanel(ensemble_size, run_path, notifier, analysis_config),
experiment_type_valid,
)

self.setLayout(layout)

def addExperimentConfigPanel(
Expand All @@ -168,7 +185,9 @@ def addExperimentConfigPanel(
experiment_type = panel.get_experiment_type()
self._experiment_widgets[experiment_type] = panel
self._experiment_type_combo.addDescriptionItem(
experiment_type.name(), experiment_type.description()
experiment_type.name(),
experiment_type.description(),
experiment_type.group(),
)

if not mode_enabled:
Expand Down Expand Up @@ -294,12 +313,10 @@ def run_experiment(self) -> None:
dialog.produce_clipboard_debug_info.connect(self.populate_clipboard_debug_info)

self.run_button.setEnabled(False)
self.run_button.setText(EXPERIMENT_IS_RUNNING_BUTTON_MESSAGE)
dialog.run_experiment()
dialog.show()

def exit_handler() -> None:
self.run_button.setText(EXPERIMENT_READY_TO_RUN_BUTTON_MESSAGE)
self.run_button.setEnabled(True)
self.toggleExperimentType()
self._notifier.emitErtChange()
Expand All @@ -316,9 +333,14 @@ def toggleExperimentType(self) -> None:

def validationStatusChanged(self) -> None:
widget = self._experiment_widgets[self.get_current_experiment_type()]
widgets = QApplication.topLevelWidgets()
is_run_dialog_open = False
for w in widgets:
if isinstance(w, RunDialog) and w.isVisible():
is_run_dialog_open = True
break
self.run_button.setEnabled(
self.run_button.text() == EXPERIMENT_READY_TO_RUN_BUTTON_MESSAGE
and widget.isConfigurationValid()
not is_run_dialog_open and widget.isConfigurationValid()
)

def populate_clipboard_debug_info(self) -> None:
Expand Down
8 changes: 8 additions & 0 deletions src/ert/run_models/base_run_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ def name(cls) -> str: ...
@abstractmethod
def description(cls) -> str: ...

@classmethod
def group(cls) -> Optional[str]:
"""Default value to prevent errors in children classes
since only EnsembleExperiment and EnsembleSmoother should
override it
"""
return None

def send_event(self, event: StatusEvents) -> None:
self._status_queue.put(event)

Expand Down
6 changes: 5 additions & 1 deletion src/ert/run_models/ensemble_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,8 @@ def name(cls) -> str:

@classmethod
def description(cls) -> str:
return "Sample parameters → evaluate (N realizations)"
return "Sample parameters → evaluate all realizations"

@classmethod
def group(cls) -> Optional[str]:
return None
2 changes: 1 addition & 1 deletion src/ert/run_models/evaluate_ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ def name(cls) -> str:

@classmethod
def description(cls) -> str:
return "Take existing ensemble parameters → evaluate"
return "Use existing parameters → evaluate"
2 changes: 1 addition & 1 deletion src/ert/run_models/iterated_ensemble_smoother.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,4 @@ def name(cls) -> str:

@classmethod
def description(cls) -> str:
return "Sample parameters → [Evaluate → update] for N iterations"
return "Sample parameters → [evaluate → update] several iterations"
10 changes: 8 additions & 2 deletions src/ert/run_models/multiple_data_assimilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

logger = logging.getLogger(__name__)

MULTIPLE_DATA_ASSIMILATION_GROUP = "Parameter update"


class MultipleDataAssimilation(UpdateRunModel):
"""
Expand Down Expand Up @@ -184,8 +186,12 @@ def parse_weights(weights: str) -> List[float]:

@classmethod
def name(cls) -> str:
return "Multiple Data Assimilation (ES MDA) - Recommended"
return "Multiple data assimilation"

@classmethod
def description(cls) -> str:
return "[Sample|restart] → [Evaluate → update] for each weight"
return "[Sample|restart] → [evaluate → update] for each weight"

@classmethod
def group(cls) -> Optional[str]:
return MULTIPLE_DATA_ASSIMILATION_GROUP
8 changes: 7 additions & 1 deletion src/ert/run_models/single_test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

from .base_run_model import StatusEvents

SINGLE_TEST_RUN_GROUP = "Forward model evaluation"


class SingleTestRun(EnsembleExperiment):
"""
Expand Down Expand Up @@ -49,4 +51,8 @@ def name(cls) -> str:

@classmethod
def description(cls) -> str:
return "Sample parameters → evaluate (one realization)"
return "Sample parameters → evaluate single realization"

@classmethod
def group(cls) -> Optional[str]:
return SINGLE_TEST_RUN_GROUP
14 changes: 4 additions & 10 deletions tests/ert/ui_tests/gui/test_main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,9 @@ def test_that_es_mda_is_disabled_when_weights_are_invalid(qtbot):
assert gui.windowTitle().startswith("ERT - poly.ert")

combo_box = get_child(gui, QComboBox, name="experiment_type")
combo_box.setCurrentIndex(5)
combo_box.setCurrentIndex(3)

assert (
combo_box.currentText()
== "Multiple Data Assimilation (ES MDA) - Recommended"
)
assert combo_box.currentText() == "Multiple data assimilation"

es_mda_panel = get_child(gui, QWidget, name="ES_MDA_panel")
assert es_mda_panel
Expand Down Expand Up @@ -754,12 +751,9 @@ def test_that_es_mda_restart_run_box_is_disabled_when_there_are_no_cases(qtbot):
combo_box = get_child(gui, QComboBox, name="experiment_type")
qtbot.mouseClick(combo_box, Qt.MouseButton.LeftButton)
assert combo_box.count() == 7
combo_box.setCurrentIndex(5)
combo_box.setCurrentIndex(3)

assert (
combo_box.currentText()
== "Multiple Data Assimilation (ES MDA) - Recommended"
)
assert combo_box.currentText() == "Multiple data assimilation"

es_mda_panel = get_child(gui, QWidget, name="ES_MDA_panel")
assert es_mda_panel
Expand Down
3 changes: 3 additions & 0 deletions tests/ert/ui_tests/gui/test_single_test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def test_single_test_run_after_ensemble_experiment(
with contextlib.suppress(FileNotFoundError):
shutil.rmtree("poly_out")

simulation_mode_combo = experiment_panel.findChild(QComboBox)
simulation_mode_combo.setCurrentText("Single realization test-run")

run_experiment = get_child(experiment_panel, QWidget, name="run_experiment")
qtbot.mouseClick(run_experiment, Qt.LeftButton)
# The Run dialog opens, wait until done appears, then click done
Expand Down
4 changes: 3 additions & 1 deletion tests/ert/unit_tests/gui/simulation/test_run_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,12 +489,14 @@ def handle_error_dialog(run_dialog):
assert "I failed :(" in text
qtbot.mouseClick(error_dialog.box.buttons()[0], Qt.LeftButton)

simulation_mode_combo = gui.findChild(QComboBox)
simulation_mode_combo.setCurrentText("Single realization test-run")
qtbot.mouseClick(run_experiment, Qt.LeftButton)

run_dialog = wait_for_child(gui, qtbot, RunDialog)

QTimer.singleShot(100, lambda: handle_error_dialog(run_dialog))
qtbot.waitUntil(run_dialog.done_button.isVisible, timeout=200000)
run_dialog.close()


@pytest.mark.integration_test
Expand Down

0 comments on commit e607feb

Please sign in to comment.