Skip to content

Commit

Permalink
Commitin
Browse files Browse the repository at this point in the history
  • Loading branch information
dafeda committed Aug 20, 2024
1 parent 221fea2 commit 3ef56e8
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 277 deletions.
6 changes: 6 additions & 0 deletions src/ert/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ def get_ert_parser(parser: Optional[ArgumentParser] = None) -> ArgumentParser:
help="Name of the ensemble where the results for the "
"updated parameters will be stored.",
)
ensemble_smoother_parser.add_argument(
"--experiment-name",
type=str,
default="ensemble-experiment",
help="Name of the experiment",
)
ensemble_smoother_parser.add_argument(
"--realizations",
type=valid_realizations,
Expand Down
45 changes: 35 additions & 10 deletions src/ert/gui/ertnotifier.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
from typing import Optional
from typing import List, Optional

from qtpy.QtCore import QObject, Signal, Slot

from ert.storage import Ensemble, Storage
from ert.storage import LocalEnsemble, LocalExperiment, LocalStorage


class ErtNotifier(QObject):
ertChanged = Signal()
storage_changed = Signal(object, name="storageChanged")
current_experiment_changed = Signal(object, name="currentExperimentChanged")
current_ensemble_changed = Signal(object, name="currentEnsembleChanged")

def __init__(self, config_file: str):
QObject.__init__(self)
self._config_file = config_file
self._storage: Optional[Storage] = None
self._current_ensemble: Optional[Ensemble] = None
self._storage: Optional[LocalStorage] = None
self._current_experiment: Optional[LocalExperiment] = None
self._current_ensemble: Optional[LocalEnsemble] = None
self._is_simulation_running = False

@property
def is_storage_available(self) -> bool:
return self._storage is not None

@property
def storage(self) -> Storage:
def storage(self) -> LocalStorage:
assert self.is_storage_available
return self._storage # type: ignore

Expand All @@ -31,9 +33,17 @@ def config_file(self) -> str:
return self._config_file

@property
def current_ensemble(self) -> Optional[Ensemble]:
if self._current_ensemble is None and self._storage is not None:
ensembles = list(self._storage.ensembles)
def current_experiment(self) -> Optional[LocalExperiment]:
if self._current_experiment is None and self._storage is not None:
experiments = list(self._storage.experiments)
if experiments:
self._current_experiment = experiments[0]
return self._current_experiment

@property
def current_ensemble(self) -> Optional[LocalEnsemble]:
if self._current_ensemble is None and self._current_experiment is not None:
ensembles = list(self._current_experiment.ensembles)
if ensembles:
self._current_ensemble = ensembles[0]
return self._current_ensemble
Expand All @@ -53,15 +63,30 @@ def emitErtChange(self) -> None:
self.ertChanged.emit()

@Slot(object)
def set_storage(self, storage: Storage) -> None:
def set_storage(self, storage: LocalStorage) -> None:
self._storage = storage
self._current_experiment = None
self._current_ensemble = None
self.storage_changed.emit(storage)

@Slot(object)
def set_current_ensemble(self, ensemble: Optional[Ensemble] = None) -> None:
def set_current_experiment(
self, experiment: Optional[LocalExperiment] = None
) -> None:
self._current_experiment = experiment
self._current_ensemble = None
self.current_experiment_changed.emit(experiment)

@Slot(object)
def set_current_ensemble(self, ensemble: Optional[LocalEnsemble] = None) -> None:
self._current_ensemble = ensemble
self.current_ensemble_changed.emit(ensemble)

@Slot(bool)
def set_is_simulation_running(self, is_running: bool) -> None:
self._is_simulation_running = is_running

def get_current_experiment_ensembles(self) -> List[LocalEnsemble]:
if self._current_experiment is None:
return []
return list(self._current_experiment.ensembles)
46 changes: 17 additions & 29 deletions src/ert/gui/ertwidgets/ensembleselector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ert.storage.realization_storage_state import RealizationStorageState

if TYPE_CHECKING:
from ert.storage import Ensemble
from ert.storage import LocalEnsemble


class EnsembleSelector(QComboBox):
Expand All @@ -24,83 +24,71 @@ def __init__(
):
super().__init__()
self.notifier = notifier

# If true current ensemble of ert will be change
self._update_ert = update_ert
# only show initialized ensembles
self._show_only_undefined = show_only_undefined
# If True, we filter out any ensembles which have children
# One use case is if a user wants to rerun because of failures
# not related to parameterization. We can allow that, but only
# if the ensemble has not been used in an update, as that would
# invalidate the result
self._show_only_no_children = show_only_no_children
self.setSizeAdjustPolicy(QComboBox.AdjustToContents)

self.setEnabled(False)

if update_ert:
# Update ERT when this combo box is changed
self.currentIndexChanged[int].connect(self._on_current_index_changed)

# Update this combo box when ERT is changed
notifier.current_ensemble_changed.connect(
self._on_global_current_ensemble_changed
)

notifier.ertChanged.connect(self.populate)
notifier.storage_changed.connect(self.populate)
notifier.current_experiment_changed.connect(self.populate)

if notifier.is_storage_available:
self.populate()

@property
def selected_ensemble(self) -> Ensemble:
def selected_ensemble(self) -> LocalEnsemble:
return self.itemData(self.currentIndex())

def populate(self) -> None:
block = self.blockSignals(True)

self.clear()

if self._ensemble_list():
self.setEnabled(True)

for ensemble in self._ensemble_list():
self.addItem(ensemble.name, userData=ensemble)

current_index = self.findData(
self.notifier.current_ensemble, Qt.ItemDataRole.UserRole
)

self.setCurrentIndex(max(current_index, 0))

self.blockSignals(block)

self.ensemble_populated.emit()

def _ensemble_list(self) -> Iterable[Ensemble]:
def _ensemble_list(self) -> Iterable[LocalEnsemble]:
if self.notifier.current_experiment is None:
return []

ensembles = self.notifier.current_experiment.ensembles

if self._show_only_undefined:
ensembles = (
ensemble
for ensemble in self.notifier.storage.ensembles
for ensemble in ensembles
if all(
e == RealizationStorageState.UNDEFINED
for e in ensemble.get_ensemble_state()
)
)
else:
ensembles = self.notifier.storage.ensembles

ensemble_list = list(ensembles)

if self._show_only_no_children:
parents = [
ens.parent for ens in self.notifier.storage.ensembles if ens.parent
]
parents = [ens.parent for ens in ensemble_list if ens.parent]
ensemble_list = [val for val in ensemble_list if val.id not in parents]

return sorted(ensemble_list, key=lambda x: x.started_at, reverse=True)

def _on_current_index_changed(self, index: int) -> None:
self.notifier.set_current_ensemble(self.itemData(index))

def _on_global_current_ensemble_changed(self, data: Optional[Ensemble]) -> None:
def _on_global_current_ensemble_changed(
self, data: Optional[LocalEnsemble]
) -> None:
self.setCurrentIndex(max(self.findData(data, Qt.ItemDataRole.UserRole), 0))
5 changes: 5 additions & 0 deletions src/ert/storage/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""
Hierarchical Structure:
The code follows a clear hierarchical structure: Storage -> Experiment -> Ensemble.
"""

from __future__ import annotations

import os
Expand Down
107 changes: 35 additions & 72 deletions src/ert/storage/local_experiment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import json
from datetime import datetime
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional
Expand All @@ -20,7 +19,6 @@
SummaryConfig,
SurfaceConfig,
)
from ert.config.parsing.context_values import ContextBoolEncoder
from ert.config.response_config import ResponseConfig
from ert.storage.mode import BaseMode, Mode, require_write

Expand Down Expand Up @@ -86,76 +84,6 @@ def __init__(
(path / "index.json").read_text(encoding="utf-8")
)

@classmethod
def create(
cls,
storage: LocalStorage,
uuid: UUID,
path: Path,
*,
parameters: Optional[List[ParameterConfig]] = None,
responses: Optional[List[ResponseConfig]] = None,
observations: Optional[Dict[str, xr.Dataset]] = None,
simulation_arguments: Optional[Dict[Any, Any]] = None,
name: Optional[str] = None,
) -> LocalExperiment:
"""
Create a new LocalExperiment and store its configuration data.
Parameters
----------
storage : LocalStorage
Storage instance for experiment creation.
uuid : UUID
Unique identifier for the new experiment.
path : Path
File system path for storing experiment data.
parameters : list of ParameterConfig, optional
List of parameter configurations.
responses : list of ResponseConfig, optional
List of response configurations.
observations : dict of str: xr.Dataset, optional
Observations dictionary.
simulation_arguments : SimulationArguments, optional
Simulation arguments for the experiment.
name : str, optional
Experiment name. Defaults to current date if None.
Returns
-------
local_experiment : LocalExperiment
Instance of the newly created experiment.
"""
if name is None:
name = datetime.today().strftime("%Y-%m-%d")

(path / "index.json").write_text(_Index(id=uuid, name=name).model_dump_json())

parameter_data = {}
for parameter in parameters or []:
parameter.save_experiment_data(path)
parameter_data.update({parameter.name: parameter.to_dict()})
with open(path / cls._parameter_file, "w", encoding="utf-8") as f:
json.dump(parameter_data, f, indent=2)

response_data = {}
for response in responses or []:
response_data.update({response.name: response.to_dict()})
with open(path / cls._responses_file, "w", encoding="utf-8") as f:
json.dump(response_data, f, default=str, indent=2)

if observations:
output_path = path / "observations"
output_path.mkdir()
for obs_name, dataset in observations.items():
dataset.to_netcdf(output_path / f"{obs_name}", engine="scipy")

with open(path / cls._metadata_file, "w", encoding="utf-8") as f:
simulation_data = simulation_arguments if simulation_arguments else {}
json.dump(simulation_data, f, cls=ContextBoolEncoder)

return cls(storage, path, Mode.WRITE)

@require_write
def create_ensemble(
self,
Expand Down Expand Up @@ -194,6 +122,41 @@ def create_ensemble(
prior_ensemble=prior_ensemble,
)

def get_ensemble(self, uuid: UUID) -> LocalEnsemble:
"""
Retrieves an ensemble by UUID.
Parameters
----------
uuid : UUID
The UUID of the ensemble to retrieve.
Returns
local_ensemble : LocalEnsemble
The ensemble associated with the given UUID.
"""
return self.ensembles[uuid]

def get_ensemble_by_name(self, name: str) -> LocalEnsemble:
"""
Retrieves an ensemble by name.
Parameters
----------
name : str
The name of the ensemble to retrieve.
Returns
-------
local_ensemble : LocalEnsemble
The ensemble associated with the given name.
"""

for ens in self.ensembles:
if ens.name == name:
return ens
raise KeyError(f"Ensemble with name '{name}' not found")

@property
def ensembles(self) -> Generator[LocalEnsemble, None, None]:
yield from (
Expand Down
Loading

0 comments on commit 3ef56e8

Please sign in to comment.