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

Add tree view for 'case view' #7107

Merged
merged 3 commits into from
Feb 29, 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
79 changes: 79 additions & 0 deletions src/ert/gui/ertwidgets/create_experiment_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from qtpy.QtCore import (
Qt,
Signal,
)
from qtpy.QtWidgets import (
QDialog,
QDialogButtonBox,
QGridLayout,
QLabel,
QLineEdit,
)


class CreateExperimentDialog(QDialog):
onDone = Signal(str, str)

def __init__(
self,
title: str = "Create new experiment",
parent=None,
):
QDialog.__init__(self, parent=parent)
self.setModal(True)
self.setWindowTitle(title)
self.setFixedSize(400, 100)

layout = QGridLayout()

experiment_label = QLabel("Experiment name:")
self._experiment_edit = QLineEdit()
self._experiment_edit.setPlaceholderText("My experiment")

ensemble_label = QLabel("Ensemble name:")
self._ensemble_edit = QLineEdit()
self._ensemble_edit.setPlaceholderText("My ensemble")

buttons = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
Qt.Orientation.Horizontal,
self,
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
self._ok_button = buttons.button(QDialogButtonBox.Ok)
assert self._ok_button

self._ok_button.clicked.connect(
lambda: self.onDone.emit(
self._experiment_edit.text(), self._ensemble_edit.text()
)
)
self._ok_button.setEnabled(False)

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

self._experiment_edit.textChanged.connect(enableOkButton)
self._ensemble_edit.textChanged.connect(enableOkButton)

layout.addWidget(experiment_label, 0, 0)
layout.addWidget(self._experiment_edit, 0, 1)
layout.addWidget(ensemble_label, 1, 0)
layout.addWidget(self._ensemble_edit, 1, 1)
layout.addWidget(buttons, 2, 1)

self.setLayout(layout)

self._experiment_edit.setFocus()

@property
def experiment_name(self) -> str:
return self._experiment_edit.text()

@property
def ensemble_name(self) -> str:
return self._ensemble_edit.text()
130 changes: 130 additions & 0 deletions src/ert/gui/ertwidgets/models/storage_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from typing import Any, List

from qtpy.QtCore import (
QAbstractItemModel,
QModelIndex,
Qt,
Slot,
)

from ert.storage import EnsembleReader, ExperimentReader, StorageReader


class Ensemble:
def __init__(self, ensemble: EnsembleReader, parent: Any):
self._parent = parent
self._name = ensemble.name
self._id = ensemble.id
self._start_time = ensemble.started_at

def row(self) -> int:
if self._parent:
return self._parent._children.index(self)
return 0

def data(self, index: QModelIndex, role=Qt.ItemDataRole.DisplayRole) -> Any:
if not index.isValid():
return None

if role == Qt.ItemDataRole.DisplayRole:
return f"{self._name} - {self._start_time} ({self._id})"

return None


class Experiment:
def __init__(self, experiment: ExperimentReader, parent: Any):
self._parent = parent
self._id = experiment.id
self._name = experiment.name
self._experiment_type = (
experiment.simulation_arguments["analysis_module"]
if "analysis_module" in experiment.simulation_arguments
else ""
)
self._children: List[Ensemble] = []

def add_ensemble(self, ensemble: Ensemble) -> None:
self._children.append(ensemble)

def row(self) -> int:
if self._parent:
return self._parent._children.index(self)
return 0

def data(self, index: QModelIndex, role=Qt.ItemDataRole.DisplayRole) -> Any:
if not index.isValid():
return None

if role == Qt.ItemDataRole.DisplayRole:
return f"{self._name} - {self._experiment_type} ({self._id})"

return None


class StorageModel(QAbstractItemModel):
def __init__(self, storage: StorageReader):
super().__init__(None)
self._children: List[Experiment] = []
self._load_storage(storage)

@Slot(StorageReader)
def reloadStorage(self, storage: StorageReader) -> None:
self.beginResetModel()
self._load_storage(storage)
self.endResetModel()

@Slot()
def add_experiment(self, experiment: Experiment) -> None:
idx = QModelIndex()
self.beginInsertRows(idx, 0, 0)
self._children.append(experiment)
self.endInsertRows()

def _load_storage(self, storage: StorageReader) -> None:
self._children = []
for experiment in storage.experiments:
ex = Experiment(experiment, self)
for ensemble in experiment.ensembles:
ens = Ensemble(ensemble, ex)
ex.add_ensemble(ens)
self._children.append(ex)

def columnCount(self, parent: QModelIndex) -> int:
return 1

def rowCount(self, parent: QModelIndex) -> int:
if parent.isValid():
if isinstance(parent.internalPointer(), Ensemble):
return 0
return len(parent.internalPointer()._children)
else:
return len(self._children)

def parent(self, index: QModelIndex) -> QModelIndex:
if not index.isValid():
return QModelIndex()

child_item = index.internalPointer()
parentItem = child_item._parent

if parentItem == self:
return QModelIndex()

return self.createIndex(parentItem.row(), 0, parentItem)

def data(self, index: QModelIndex, role=Qt.ItemDataRole.DisplayRole) -> Any:
if not index.isValid():
return None

return index.internalPointer().data(index, role)

def index(self, row: int, column: int, parent: QModelIndex) -> QModelIndex:
parentItem = parent.internalPointer() if parent.isValid() else self
try:
childItem = parentItem._children[row]
except KeyError:
childItem = None
if childItem:
return self.createIndex(row, column, childItem)
return QModelIndex()
70 changes: 70 additions & 0 deletions src/ert/gui/ertwidgets/storage_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from qtpy.QtCore import QSortFilterProxyModel, Qt
from qtpy.QtWidgets import (
QHeaderView,
QLineEdit,
QTreeView,
QVBoxLayout,
QWidget,
)

from ert.config import ErtConfig
from ert.gui.ertnotifier import ErtNotifier
from ert.gui.ertwidgets.caselist import AddWidget
from ert.gui.ertwidgets.create_experiment_dialog import CreateExperimentDialog
from ert.gui.ertwidgets.models.storage_model import StorageModel


class StorageWidget(QWidget):
def __init__(
self, notifier: ErtNotifier, ert_config: ErtConfig, ensemble_size: int
):
QWidget.__init__(self)

self._notifier = notifier
self._ert_config = ert_config
self._ensemble_size = ensemble_size

tree_view = QTreeView(self)
storage_model = StorageModel(self._notifier.storage)
notifier.storage_changed.connect(storage_model.reloadStorage)
notifier.ertChanged.connect(
lambda: storage_model.reloadStorage(self._notifier.storage)
)

if isinstance(tree_view.header(), QHeaderView):
tree_view.header().hide()

search_bar = QLineEdit(self)
search_bar.setPlaceholderText("Filter")
proxy_model = QSortFilterProxyModel()
proxy_model.setFilterKeyColumn(-1) # Search all columns.
proxy_model.setSourceModel(storage_model)
proxy_model.sort(0, Qt.SortOrder.AscendingOrder)

tree_view.setModel(proxy_model)
search_bar.textChanged.connect(proxy_model.setFilterFixedString)

self._create_experiment_button = AddWidget(self.add_item)

layout = QVBoxLayout()
layout.addWidget(search_bar)
layout.addWidget(tree_view)
layout.addWidget(self._create_experiment_button)

self.setLayout(layout)

def add_item(self) -> None:
create_experiment_dialog = CreateExperimentDialog(parent=self)
create_experiment_dialog.show()
if create_experiment_dialog.exec_():
ensemble = self._notifier.storage.create_experiment(
parameters=self._ert_config.ensemble_config.parameter_configuration,
responses=self._ert_config.ensemble_config.response_configuration,
observations=self._ert_config.observations,
name=create_experiment_dialog.experiment_name,
).create_ensemble(
name=create_experiment_dialog.ensemble_name,
ensemble_size=self._ensemble_size,
)
self._notifier.set_current_case(ensemble)
self._notifier.ertChanged.emit()
20 changes: 10 additions & 10 deletions src/ert/gui/tools/manage_cases/case_init_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
from ert.config import ErtConfig
from ert.enkf_main import sample_prior
from ert.gui.ertwidgets import showWaitCursorWhileWaiting
from ert.gui.ertwidgets.caselist import CaseList
from ert.gui.ertwidgets.caseselector import CaseSelector
from ert.gui.ertwidgets.checklist import CheckList
from ert.gui.ertwidgets.models.selectable_list_model import SelectableListModel
from ert.gui.ertwidgets.storage_widget import StorageWidget

if TYPE_CHECKING:
from typing import List
Expand Down Expand Up @@ -77,13 +77,13 @@ def addCreateNewCaseTab(self):
panel = QWidget()
panel.setObjectName("create_new_case_tab")
layout = QVBoxLayout()
case_list = CaseList(self.ert_config, self.notifier, self.ensemble_size)

layout.addWidget(case_list, stretch=1)

storage_widget = StorageWidget(
self.notifier, self.ert_config, self.ensemble_size
)
layout.addWidget(storage_widget, stretch=1)
panel.setLayout(layout)

self.addTab(panel, "Create new case")
self.addTab(panel, "Create new experiment")

def addInitializeFromScratchTab(self):
panel = QWidget()
Expand Down Expand Up @@ -139,15 +139,15 @@ def addShowCaseInfo(self):
self.notifier,
update_ert=False,
)
row1 = createRow(QLabel("Select case:"), case_selector)
row1 = createRow(QLabel("Select ensemble:"), case_selector)

layout.addLayout(row1)

self._case_info_area = QTextEdit(objectName="html_text")
self._case_info_area.setReadOnly(True)
self._case_info_area.setMinimumHeight(300)

row2 = createRow(QLabel("Case info:"), self._case_info_area)
row2 = createRow(QLabel("Ensemble info:"), self._case_info_area)

layout.addLayout(row2)

Expand All @@ -157,7 +157,7 @@ def addShowCaseInfo(self):
case_selector.currentIndexChanged[int].connect(self._showInfoForCase)
self.notifier.ertChanged.connect(self._showInfoForCase)

self.addTab(case_widget, "Case info")
self.addTab(case_widget, "Ensemble info")

def _showInfoForCase(self, index=None):
if index is None:
Expand All @@ -179,5 +179,5 @@ def _showInfoForCase(self, index=None):

@showWaitCursorWhileWaiting
def on_tab_changed(self, p_int):
if self.tabText(p_int) == "Case info":
if self.tabText(p_int) == "Ensemble info":
self._showInfoForCase()
1 change: 1 addition & 0 deletions src/ert/run_models/ensemble_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def run_experiment(
name=self.simulation_arguments.experiment_name,
parameters=self.ert_config.ensemble_config.parameter_configuration,
observations=self.ert_config.observations,
simulation_arguments=self._simulation_arguments,
responses=self.ert_config.ensemble_config.response_configuration,
)
ensemble = self._storage.create_ensemble(
Expand Down
2 changes: 2 additions & 0 deletions src/ert/run_models/ensemble_smoother.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ def run_experiment(
parameters=self.ert_config.ensemble_config.parameter_configuration,
observations=self.ert_config.observations,
responses=self.ert_config.ensemble_config.response_configuration,
simulation_arguments=self._simulation_arguments,
name=self._simulation_arguments.experiment_name,
)

self.set_env_key("_ERT_EXPERIMENT_ID", str(experiment.id))
prior_name = self._simulation_arguments.current_case
prior = self._storage.create_ensemble(
Expand Down
1 change: 1 addition & 0 deletions src/ert/run_models/iterated_ensemble_smoother.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def run_experiment(
parameters=self.ert_config.ensemble_config.parameter_configuration,
observations=self.ert_config.observations,
responses=self.ert_config.ensemble_config.response_configuration,
simulation_arguments=self._simulation_arguments,
name=self._simulation_arguments.experiment_name,
)
prior = self._storage.create_ensemble(
Expand Down
1 change: 1 addition & 0 deletions src/ert/run_models/multiple_data_assimilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def run_experiment(
parameters=self.ert_config.ensemble_config.parameter_configuration,
observations=self.ert_config.observations,
responses=self.ert_config.ensemble_config.response_configuration,
simulation_arguments=self._simulation_arguments,
name=self._simulation_arguments.experiment_name,
)

Expand Down
Loading
Loading