diff --git a/src/ert/gui/simulation/ensemble_experiment_panel.py b/src/ert/gui/simulation/ensemble_experiment_panel.py index 622b7aaaccf..dd2cf2e513e 100644 --- a/src/ert/gui/simulation/ensemble_experiment_panel.py +++ b/src/ert/gui/simulation/ensemble_experiment_panel.py @@ -2,8 +2,9 @@ from qtpy import QtCore from qtpy.QtCore import Slot -from qtpy.QtWidgets import QFormLayout, QLabel +from qtpy.QtWidgets import QFormLayout, QHBoxLayout, QLabel, QPushButton +from ert.config import AnalysisConfig, DesignMatrix from ert.gui.ertnotifier import ErtNotifier from ert.gui.ertwidgets import ( ActiveRealizationsModel, @@ -11,13 +12,11 @@ StringBox, TextModel, ) +from ert.gui.tools.design_matrix.design_matrix_panel import DesignMatrixPanel from ert.mode_definitions import ENSEMBLE_EXPERIMENT_MODE from ert.run_models import EnsembleExperiment from ert.validation import RangeStringArgument -from ert.validation.proper_name_argument import ( - ExperimentValidation, - ProperNameArgument, -) +from ert.validation.proper_name_argument import ExperimentValidation, ProperNameArgument from .experiment_config_panel import ExperimentConfigPanel @@ -31,7 +30,13 @@ class Arguments: class EnsembleExperimentPanel(ExperimentConfigPanel): - def __init__(self, ensemble_size: int, run_path: str, notifier: ErtNotifier): + def __init__( + self, + analysis_config: AnalysisConfig, + ensemble_size: int, + run_path: str, + notifier: ErtNotifier, + ): self.notifier = notifier super().__init__(EnsembleExperiment) self.setObjectName("Ensemble_experiment_panel") @@ -78,6 +83,21 @@ def __init__(self, ensemble_size: int, run_path: str, notifier: ErtNotifier): ) layout.addRow("Active realizations", self._active_realizations_field) + design_matrix = analysis_config.design_matrix + if design_matrix is not None: + show_dm_param_button = QPushButton("Show parameters") + show_dm_param_button.setObjectName("show-dm-parameters") + show_dm_param_button.setMinimumWidth(50) + + button_layout = QHBoxLayout() + button_layout.addWidget(show_dm_param_button) + button_layout.addStretch() # Add stretch to push the button to the left + + layout.addRow("Design Matrix", button_layout) + show_dm_param_button.clicked.connect( + lambda: self.on_show_dm_params_clicked(design_matrix) + ) + self.setLayout(layout) self._active_realizations_field.getValidationSupport().validationChanged.connect( @@ -92,6 +112,25 @@ def __init__(self, ensemble_size: int, run_path: str, notifier: ErtNotifier): self.notifier.ertChanged.connect(self._update_experiment_name_placeholder) + def on_show_dm_params_clicked(self, design_matrix: DesignMatrix) -> None: + assert design_matrix is not None + + if design_matrix.design_matrix_df is None: + design_matrix.read_design_matrix() + + if ( + design_matrix.design_matrix_df is not None + and not design_matrix.design_matrix_df.empty + ): + viewer = DesignMatrixPanel( + design_matrix.design_matrix_df, + design_matrix.xls_filename.name, + ) + viewer.setMinimumHeight(500) + viewer.setMinimumWidth(1000) + viewer.adjustSize() + viewer.exec_() + @Slot(ExperimentConfigPanel) def experimentTypeChanged(self, w: ExperimentConfigPanel) -> None: if isinstance(w, EnsembleExperimentPanel): diff --git a/src/ert/gui/simulation/experiment_panel.py b/src/ert/gui/simulation/experiment_panel.py index 3ad875eb821..53c59ea5f29 100644 --- a/src/ert/gui/simulation/experiment_panel.py +++ b/src/ert/gui/simulation/experiment_panel.py @@ -142,8 +142,9 @@ def __init__( SingleTestRunPanel(run_path, notifier), True, ) + analysis_config = config.analysis_config self.addExperimentConfigPanel( - EnsembleExperimentPanel(ensemble_size, run_path, notifier), + EnsembleExperimentPanel(analysis_config, ensemble_size, run_path, notifier), True, ) self.addExperimentConfigPanel( @@ -154,7 +155,7 @@ def __init__( experiment_type_valid = bool( config.ensemble_config.parameter_configs and config.observations ) - analysis_config = config.analysis_config + self.addExperimentConfigPanel( MultipleDataAssimilationPanel( analysis_config, run_path, notifier, ensemble_size diff --git a/src/ert/gui/tools/design_matrix/design_matrix_panel.py b/src/ert/gui/tools/design_matrix/design_matrix_panel.py new file mode 100644 index 00000000000..2644c8d2ddc --- /dev/null +++ b/src/ert/gui/tools/design_matrix/design_matrix_panel.py @@ -0,0 +1,50 @@ +from typing import Optional + +import pandas as pd +from qtpy.QtGui import QStandardItem, QStandardItemModel +from qtpy.QtWidgets import QDialog, QTableView, QVBoxLayout, QWidget + + +class DesignMatrixPanel(QDialog): + def __init__( + self, + design_matrix_df: pd.DataFrame, + filename: str, + parent: Optional[QWidget] = None, + ) -> None: + super().__init__(parent) + + self.setWindowTitle(f"Design matrix parameters from {filename}") + + table_view = QTableView(self) + table_view.setEditTriggers(QTableView.NoEditTriggers) + + self.model = self.create_model(design_matrix_df) + table_view.setModel(self.model) + + table_view.resizeColumnsToContents() + table_view.resizeRowsToContents() + + layout = QVBoxLayout() + layout.addWidget(table_view) + self.setLayout(layout) + self.adjustSize() + + @staticmethod + def create_model(design_matrix_df: pd.DataFrame) -> QStandardItemModel: + model = QStandardItemModel() + + if isinstance(design_matrix_df.columns, pd.MultiIndex): + header_labels = [str(col[-1]) for col in design_matrix_df.columns] + else: + header_labels = design_matrix_df.columns.astype(str).tolist() + + model.setHorizontalHeaderLabels(header_labels) + + for index, _ in design_matrix_df.iterrows(): + items = [ + QStandardItem(str(design_matrix_df.at[index, col])) + for col in design_matrix_df.columns + ] + model.appendRow(items) + return model diff --git a/tests/ert/unit_tests/gui/simulation/test_run_dialog.py b/tests/ert/unit_tests/gui/simulation/test_run_dialog.py index 0098f32e891..e8e520e8a83 100644 --- a/tests/ert/unit_tests/gui/simulation/test_run_dialog.py +++ b/tests/ert/unit_tests/gui/simulation/test_run_dialog.py @@ -708,3 +708,48 @@ def test_that_stdout_and_stderr_buttons_react_to_file_content( with qtbot.waitSignal(run_dialog.accepted, timeout=30000): run_dialog.close() + + +@pytest.mark.integration_test +@pytest.mark.usefixtures("use_tmpdir") +@pytest.mark.parametrize( + "design_matrix_entry", + (True, False), +) +def test_that_design_matrix_show_parameters_button_is_visible( + design_matrix_entry, qtbot: QtBot, storage +): + xls_filename = "design_matrix.xls" + with open(f"{xls_filename}", "w", encoding="utf-8"): + pass + config_file = "minimal_config.ert" + with open(config_file, "w", encoding="utf-8") as f: + f.write("NUM_REALIZATIONS 1") + if design_matrix_entry: + f.write( + f"\nDESIGN_MATRIX {xls_filename} DESIGN_SHEET:DesignSheet01 DEFAULT_SHEET:DefaultValues" + ) + + args_mock = Mock() + args_mock.config = config_file + + ert_config = ErtConfig.from_file(config_file) + with StorageService.init_service( + project=os.path.abspath(ert_config.ens_path), + ): + gui = _setup_main_window(ert_config, args_mock, GUILogHandler(), storage) + experiment_panel = gui.findChild(ExperimentPanel) + assert isinstance(experiment_panel, ExperimentPanel) + + simulation_mode_combo = experiment_panel.findChild(QComboBox) + assert isinstance(simulation_mode_combo, QComboBox) + + simulation_mode_combo.setCurrentText(EnsembleExperiment.name()) + simulation_settings = gui.findChild(EnsembleExperimentPanel) + show_dm_parameters = simulation_settings.findChild( + QPushButton, "show-dm-parameters" + ) + if design_matrix_entry: + assert isinstance(show_dm_parameters, QPushButton) + else: + assert show_dm_parameters is None