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

Enable load results manually from any available iteration #8700

Merged
merged 11 commits into from
Nov 11, 2024
60 changes: 34 additions & 26 deletions src/ert/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import logging
import time
from pathlib import Path
from typing import Iterable

from ert.config import InvalidResponseFile, ParameterConfig, ResponseConfig
from ert.run_arg import RunArg
from ert.config import InvalidResponseFile
from ert.storage import Ensemble
from ert.storage.realization_storage_state import RealizationStorageState

from .load_status import LoadResult, LoadStatus
Expand All @@ -16,24 +15,27 @@


async def _read_parameters(
run_arg: RunArg, parameter_configuration: Iterable[ParameterConfig]
run_path: str,
realization: int,
ensemble: Ensemble,
) -> LoadResult:
result = LoadResult(LoadStatus.LOAD_SUCCESSFUL, "")
error_msg = ""
parameter_configuration = ensemble.experiment.parameter_configuration.values()
for config in parameter_configuration:
if not config.forward_init:
continue
try:
start_time = time.perf_counter()
logger.debug(f"Starting to load parameter: {config.name}")
ds = config.read_from_runpath(Path(run_arg.runpath), run_arg.iens)
ds = config.read_from_runpath(Path(run_path), realization)
await asyncio.sleep(0)
logger.debug(
f"Loaded {config.name}",
extra={"Time": f"{(time.perf_counter() - start_time):.4f}s"},
)
start_time = time.perf_counter()
run_arg.ensemble_storage.save_parameters(config.name, run_arg.iens, ds)
ensemble.save_parameters(config.name, realization, ds)
await asyncio.sleep(0)
logger.debug(
f"Saved {config.name} to storage",
Expand All @@ -42,33 +44,34 @@ async def _read_parameters(
except Exception as err:
error_msg += str(err)
result = LoadResult(LoadStatus.LOAD_FAILURE, error_msg)
logger.warning(f"Failed to load: {run_arg.iens}", exc_info=err)
logger.warning(f"Failed to load: {realization}", exc_info=err)
return result


async def _write_responses_to_storage(
run_arg: RunArg, response_configs: Iterable[ResponseConfig]
run_path: str,
realization: int,
ensemble: Ensemble,
) -> LoadResult:
errors = []
response_configs = ensemble.experiment.response_configuration.values()
for config in response_configs:
try:
start_time = time.perf_counter()
logger.debug(f"Starting to load response: {config.response_type}")
try:
ds = config.read_from_file(run_arg.runpath, run_arg.iens)
ds = config.read_from_file(run_path, realization)
except (FileNotFoundError, InvalidResponseFile) as err:
errors.append(str(err))
logger.warning(f"Failed to write: {run_arg.iens}: {err}")
logger.warning(f"Failed to write: {realization}: {err}")
continue
await asyncio.sleep(0)
logger.debug(
f"Loaded {config.response_type}",
extra={"Time": f"{(time.perf_counter() - start_time):.4f}s"},
)
start_time = time.perf_counter()
run_arg.ensemble_storage.save_response(
config.response_type, ds, run_arg.iens
)
ensemble.save_response(config.response_type, ds, realization)
await asyncio.sleep(0)
logger.debug(
f"Saved {config.response_type} to storage",
Expand All @@ -77,7 +80,7 @@ async def _write_responses_to_storage(
except Exception as err:
errors.append(str(err))
logger.exception(
f"Unexpected exception while writing response to storage {run_arg.iens}",
f"Unexpected exception while writing response to storage {realization}",
exc_info=err,
)
continue
Expand All @@ -88,43 +91,48 @@ async def _write_responses_to_storage(


async def forward_model_ok(
run_arg: RunArg,
run_path: str,
realization: int,
iter: int,
ensemble: Ensemble,
) -> LoadResult:
parameters_result = LoadResult(LoadStatus.LOAD_SUCCESSFUL, "")
response_result = LoadResult(LoadStatus.LOAD_SUCCESSFUL, "")
try:
# We only read parameters after the prior, after that, ERT
# handles parameters
if run_arg.itr == 0:
if iter == 0:
parameters_result = await _read_parameters(
run_arg,
run_arg.ensemble_storage.experiment.parameter_configuration.values(),
run_path,
realization,
ensemble,
)

if parameters_result.status == LoadStatus.LOAD_SUCCESSFUL:
response_result = await _write_responses_to_storage(
run_arg,
run_arg.ensemble_storage.experiment.response_configuration.values(),
run_path,
realization,
ensemble,
)

except Exception as err:
logger.exception(
f"Failed to load results for realization {run_arg.iens}",
f"Failed to load results for realization {realization}",
exc_info=err,
)
parameters_result = LoadResult(
LoadStatus.LOAD_FAILURE,
"Failed to load results for realization "
f"{run_arg.iens}, failed with: {err}",
f"{realization}, failed with: {err}",
)

final_result = parameters_result
if response_result.status != LoadStatus.LOAD_SUCCESSFUL:
final_result = response_result
run_arg.ensemble_storage.set_failure(
run_arg.iens, RealizationStorageState.LOAD_FAILURE, final_result.message
ensemble.set_failure(
realization, RealizationStorageState.LOAD_FAILURE, final_result.message
)
elif run_arg.ensemble_storage.has_failure(run_arg.iens):
run_arg.ensemble_storage.unset_failure(run_arg.iens)
elif ensemble.has_failure(realization):
ensemble.unset_failure(realization)

return final_result
4 changes: 2 additions & 2 deletions src/ert/gui/ertwidgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def wrapper(*arg: Any) -> Any:
from .ensembleselector import EnsembleSelector
from .checklist import CheckList
from .stringbox import StringBox
from .multilinestringbox import MultiLineStringBox
from .textbox import TextBox
from .listeditbox import ListEditBox
from .customdialog import CustomDialog
from .pathchooser import PathChooser
Expand All @@ -52,13 +52,13 @@ def wrapper(*arg: Any) -> Any:
"EnsembleSelector",
"ErtMessageBox",
"ListEditBox",
"MultiLineStringBox",
"PathChooser",
"PathModel",
"SearchBox",
"SelectableListModel",
"StringBox",
"TargetEnsembleModel",
"TextBox",
"TextModel",
"ValueModel",
"showWaitCursorWhileWaiting",
Expand Down
33 changes: 27 additions & 6 deletions src/ert/gui/ertwidgets/create_experiment_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
)

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


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

def __init__(
self,
Expand Down Expand Up @@ -54,6 +55,15 @@ def __init__(
)
self._ensemble_edit.setValidator(ProperNameArgument())

iteration_label = QLabel("Ensemble iteration:")
self._iterations_model = ValueModel(0) # type: ignore
self._iterations_field = StringBox(
self._iterations_model, # type: ignore
"0",
minimum_width=200,
)
self._iterations_field.setValidator(IntegerArgument(from_value=0))
self._iterations_field.setObjectName("iterations_field_ced")
buttons = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
Qt.Orientation.Horizontal,
Expand All @@ -67,7 +77,7 @@ def __init__(

self._ok_button.clicked.connect(
lambda: self.onDone.emit(
self._experiment_edit.get_text, self._ensemble_edit.get_text
self.experiment_name, self.ensemble_name, self.iteration
)
)

Expand All @@ -76,12 +86,15 @@ def enableOkButton() -> None:

self._experiment_edit.textChanged.connect(enableOkButton)
self._ensemble_edit.textChanged.connect(enableOkButton)
self._iterations_field.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)
layout.addWidget(iteration_label, 2, 0)
layout.addWidget(self._iterations_field, 2, 1)
layout.addWidget(buttons, 3, 1)

self.setLayout(layout)

Expand All @@ -103,5 +116,13 @@ def experiment_name(self) -> str:
def ensemble_name(self) -> str:
return self._ensemble_edit.get_text

@property
def iteration(self) -> int:
return int(self._iterations_field.get_text)

def isConfigurationValid(self) -> bool:
return self._experiment_edit.isValid() and self._ensemble_edit.isValid()
return (
self._experiment_edit.isValid()
and self._ensemble_edit.isValid()
and self._iterations_field.isValid()
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,78 +8,74 @@
from .validationsupport import ValidationSupport

if TYPE_CHECKING:
from ert.validation import ArgumentDefinition
from ert.validation import StringDefinition

from .models import TextModel


class MultiLineStringBox(QTextEdit):
"""MultiLineStringBox shows a multiline string. The data structure expected and sent to the
getter and setter is a multiline string."""
class TextBox(QTextEdit):
"""TextBox shows a multi line string. The data structure expected and sent to the
getter and setter is a multi line string."""

def __init__(
self,
model: TextModel,
default_string: str = "",
placeholder_text: str = "",
minimum_width: int = 250,
readonly: bool = False,
):
QTextEdit.__init__(self)
self.setMinimumWidth(minimum_width)
self._validation = ValidationSupport(self)
self._validator: Optional[ArgumentDefinition] = None
self._validator: Optional[StringDefinition] = None
self._model = model
self._enable_validation = True

if placeholder_text:
self.setPlaceholderText(placeholder_text)
self.textChanged.connect(self.stringBoxChanged)

self.textChanged.connect(self.textBoxChanged)
self.textChanged.connect(self.validateString)

self._valid_color = self.palette().color(self.backgroundRole())
self.setText(default_string)

self._model.valueChanged.connect(self.modelChanged)
self.modelChanged()
self.setReadOnly(readonly)

def validateString(self) -> None:
if not self._enable_validation or self._validator is None:
return

string_to_validate = self.toPlainText()
if not string_to_validate and self.placeholderText():
string_to_validate = self.placeholderText()

validation_success = self._validator.validate(string_to_validate)

palette = self.palette()
if not validation_success:
palette.setColor(QPalette.ColorRole.Base, ValidationSupport.ERROR_COLOR)
self.setPalette(palette)
self._validation.setValidationMessage(
str(validation_success), ValidationSupport.EXCLAMATION
)
else:
palette.setColor(QPalette.ColorRole.Base, self._valid_color)
self.setPalette(palette)
self._validation.setValidationMessage("")
if self._enable_validation:
string_to_validate = self.get_text
if self._validator is not None:
status = self._validator.validate(string_to_validate)

palette = QPalette()
if not status:
palette.setColor(
self.backgroundRole(), ValidationSupport.ERROR_COLOR
)
self.setPalette(palette)
self._validation.setValidationMessage(
str(status), ValidationSupport.EXCLAMATION
)
else:
palette.setColor(self.backgroundRole(), self._valid_color)
self.setPalette(palette)
self._validation.setValidationMessage("")

def emitChange(self, q_string: Any) -> None:
self.textChanged.emit(str(q_string))

def stringBoxChanged(self) -> None:
"""Called whenever the contents of the textedit changes."""
text: Optional[str] = self.get_text
def textBoxChanged(self) -> None:
"""Called whenever the contents of the textbox changes."""
text: Optional[str] = self.toPlainText()
if not text:
text = None

self._model.setValue(text)

def modelChanged(self) -> None:
"""Retrieves data from the model and inserts it into the textedit"""
"""Retrieves data from the model and inserts it into the textbox"""
text = self._model.getValue()
if text is None:
text = ""
Expand All @@ -92,7 +88,7 @@ def modelChanged(self) -> None:
def model(self) -> TextModel:
return self._model

def setValidator(self, validator: ArgumentDefinition) -> None:
def setValidator(self, validator: StringDefinition) -> None:
self._validator = validator

def getValidationSupport(self) -> ValidationSupport:
Expand Down
Loading