ert 17.0.0__py3-none-any.whl → 19.0.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _ert/events.py +19 -2
- _ert/forward_model_runner/client.py +6 -2
- ert/__main__.py +28 -13
- ert/analysis/_enif_update.py +8 -4
- ert/analysis/_es_update.py +19 -6
- ert/analysis/_update_commons.py +16 -6
- ert/cli/main.py +13 -6
- ert/cli/monitor.py +7 -0
- ert/config/__init__.py +15 -6
- ert/config/_create_observation_dataframes.py +117 -20
- ert/config/_get_num_cpu.py +1 -1
- ert/config/_observations.py +91 -2
- ert/config/_read_summary.py +8 -6
- ert/config/design_matrix.py +51 -24
- ert/config/distribution.py +1 -1
- ert/config/ensemble_config.py +9 -17
- ert/config/ert_config.py +103 -19
- ert/config/everest_control.py +234 -0
- ert/config/{everest_objective_config.py → everest_response.py} +24 -15
- ert/config/field.py +96 -84
- ert/config/forward_model_step.py +122 -17
- ert/config/gen_data_config.py +5 -10
- ert/config/gen_kw_config.py +5 -35
- ert/config/known_response_types.py +14 -0
- ert/config/parameter_config.py +1 -33
- ert/config/parsing/_option_dict.py +10 -2
- ert/config/parsing/config_keywords.py +2 -0
- ert/config/parsing/config_schema.py +23 -3
- ert/config/parsing/config_schema_deprecations.py +3 -14
- ert/config/parsing/config_schema_item.py +26 -11
- ert/config/parsing/context_values.py +3 -3
- ert/config/parsing/file_context_token.py +1 -1
- ert/config/parsing/observations_parser.py +6 -2
- ert/config/parsing/queue_system.py +9 -0
- ert/config/parsing/schema_item_type.py +1 -0
- ert/config/queue_config.py +4 -5
- ert/config/response_config.py +0 -8
- ert/config/rft_config.py +275 -0
- ert/config/summary_config.py +3 -8
- ert/config/surface_config.py +59 -16
- ert/config/workflow_fixtures.py +2 -1
- ert/dark_storage/client/__init__.py +2 -2
- ert/dark_storage/client/_session.py +4 -4
- ert/dark_storage/client/client.py +2 -2
- ert/dark_storage/common.py +1 -1
- ert/dark_storage/compute/misfits.py +11 -7
- ert/dark_storage/endpoints/compute/misfits.py +6 -4
- ert/dark_storage/endpoints/experiment_server.py +12 -9
- ert/dark_storage/endpoints/experiments.py +2 -2
- ert/dark_storage/endpoints/observations.py +8 -6
- ert/dark_storage/endpoints/parameters.py +2 -18
- ert/dark_storage/endpoints/responses.py +24 -5
- ert/dark_storage/json_schema/experiment.py +1 -1
- ert/data/_measured_data.py +6 -5
- ert/ensemble_evaluator/__init__.py +8 -1
- ert/ensemble_evaluator/config.py +2 -1
- ert/ensemble_evaluator/evaluator.py +81 -29
- ert/ensemble_evaluator/event.py +6 -0
- ert/ensemble_evaluator/snapshot.py +3 -1
- ert/ensemble_evaluator/state.py +1 -0
- ert/field_utils/__init__.py +8 -0
- ert/field_utils/field_utils.py +212 -3
- ert/field_utils/roff_io.py +1 -1
- ert/gui/__init__.py +5 -2
- ert/gui/ertnotifier.py +1 -1
- ert/gui/ertwidgets/__init__.py +23 -16
- ert/gui/ertwidgets/analysismoduleedit.py +2 -2
- ert/gui/ertwidgets/checklist.py +1 -1
- ert/gui/ertwidgets/create_experiment_dialog.py +3 -1
- ert/gui/ertwidgets/ensembleselector.py +2 -2
- ert/gui/ertwidgets/models/__init__.py +2 -0
- ert/gui/ertwidgets/models/activerealizationsmodel.py +2 -1
- ert/gui/ertwidgets/models/path_model.py +1 -1
- ert/gui/ertwidgets/models/targetensemblemodel.py +2 -1
- ert/gui/ertwidgets/models/text_model.py +1 -1
- ert/gui/ertwidgets/pathchooser.py +0 -3
- ert/gui/ertwidgets/searchbox.py +13 -4
- ert/gui/{suggestor → ertwidgets/suggestor}/_suggestor_message.py +13 -4
- ert/gui/{suggestor → ertwidgets/suggestor}/suggestor.py +63 -30
- ert/gui/main.py +37 -8
- ert/gui/main_window.py +1 -7
- ert/gui/simulation/ensemble_experiment_panel.py +1 -1
- ert/gui/simulation/ensemble_information_filter_panel.py +1 -1
- ert/gui/simulation/ensemble_smoother_panel.py +1 -1
- ert/gui/simulation/evaluate_ensemble_panel.py +1 -1
- ert/gui/simulation/experiment_panel.py +16 -3
- ert/gui/simulation/manual_update_panel.py +31 -8
- ert/gui/simulation/multiple_data_assimilation_panel.py +12 -8
- ert/gui/simulation/run_dialog.py +27 -20
- ert/gui/simulation/single_test_run_panel.py +2 -2
- ert/gui/summarypanel.py +20 -1
- ert/gui/tools/load_results/load_results_panel.py +1 -1
- ert/gui/tools/manage_experiments/export_dialog.py +136 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +121 -16
- ert/gui/tools/manage_experiments/storage_widget.py +1 -2
- ert/gui/tools/plot/plot_api.py +37 -25
- ert/gui/tools/plot/plot_widget.py +10 -2
- ert/gui/tools/plot/plot_window.py +38 -18
- ert/gui/tools/plot/plottery/plot_config.py +2 -0
- ert/gui/tools/plot/plottery/plot_context.py +14 -0
- ert/gui/tools/plot/plottery/plots/__init__.py +2 -0
- ert/gui/tools/plot/plottery/plots/cesp.py +3 -1
- ert/gui/tools/plot/plottery/plots/distribution.py +6 -1
- ert/gui/tools/plot/plottery/plots/ensemble.py +12 -3
- ert/gui/tools/plot/plottery/plots/gaussian_kde.py +12 -2
- ert/gui/tools/plot/plottery/plots/histogram.py +3 -1
- ert/gui/tools/plot/plottery/plots/misfits.py +436 -0
- ert/gui/tools/plot/plottery/plots/observations.py +18 -4
- ert/gui/tools/plot/plottery/plots/statistics.py +62 -20
- ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
- ert/mode_definitions.py +2 -0
- ert/plugins/__init__.py +0 -1
- ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
- ert/plugins/hook_implementations/workflows/gen_data_rft_export.py +10 -2
- ert/plugins/hook_specifications/__init__.py +0 -2
- ert/plugins/hook_specifications/jobs.py +0 -9
- ert/plugins/plugin_manager.py +6 -33
- ert/resources/forward_models/run_reservoirsimulator.py +8 -3
- ert/resources/shell_scripts/delete_directory.py +2 -2
- ert/run_models/__init__.py +18 -5
- ert/run_models/_create_run_path.py +131 -37
- ert/run_models/ensemble_experiment.py +10 -4
- ert/run_models/ensemble_information_filter.py +8 -1
- ert/run_models/ensemble_smoother.py +9 -3
- ert/run_models/evaluate_ensemble.py +8 -6
- ert/run_models/event.py +7 -3
- ert/run_models/everest_run_model.py +159 -46
- ert/run_models/initial_ensemble_run_model.py +25 -24
- ert/run_models/manual_update.py +6 -3
- ert/run_models/manual_update_enif.py +37 -0
- ert/run_models/model_factory.py +81 -21
- ert/run_models/multiple_data_assimilation.py +22 -11
- ert/run_models/run_model.py +64 -55
- ert/run_models/single_test_run.py +7 -4
- ert/run_models/update_run_model.py +4 -2
- ert/runpaths.py +5 -6
- ert/sample_prior.py +9 -4
- ert/scheduler/driver.py +37 -0
- ert/scheduler/event.py +3 -1
- ert/scheduler/job.py +23 -13
- ert/scheduler/lsf_driver.py +6 -2
- ert/scheduler/openpbs_driver.py +7 -1
- ert/scheduler/scheduler.py +5 -0
- ert/scheduler/slurm_driver.py +6 -2
- ert/services/__init__.py +2 -2
- ert/services/_base_service.py +37 -20
- ert/services/ert_server.py +317 -0
- ert/shared/_doc_utils/__init__.py +4 -2
- ert/shared/_doc_utils/ert_jobs.py +1 -4
- ert/shared/net_utils.py +43 -18
- ert/shared/storage/connection.py +3 -3
- ert/shared/version.py +3 -3
- ert/storage/__init__.py +2 -0
- ert/storage/local_ensemble.py +38 -12
- ert/storage/local_experiment.py +8 -16
- ert/storage/local_storage.py +68 -42
- ert/storage/migration/to11.py +1 -1
- ert/storage/migration/to16.py +38 -0
- ert/storage/migration/to17.py +42 -0
- ert/storage/migration/to18.py +11 -0
- ert/storage/migration/to19.py +34 -0
- ert/storage/migration/to20.py +23 -0
- ert/storage/migration/to21.py +25 -0
- ert/storage/migration/to8.py +4 -4
- ert/substitutions.py +12 -28
- ert/validation/active_range.py +7 -7
- ert/validation/rangestring.py +16 -16
- ert/workflow_runner.py +2 -1
- {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/METADATA +9 -8
- {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/RECORD +208 -205
- {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/WHEEL +1 -1
- everest/api/everest_data_api.py +14 -1
- everest/bin/config_branch_script.py +3 -6
- everest/bin/everconfigdump_script.py +1 -9
- everest/bin/everest_script.py +21 -11
- everest/bin/everlint_script.py +0 -2
- everest/bin/kill_script.py +2 -2
- everest/bin/monitor_script.py +2 -2
- everest/bin/utils.py +8 -4
- everest/bin/visualization_script.py +6 -14
- everest/config/__init__.py +4 -1
- everest/config/control_config.py +81 -6
- everest/config/control_variable_config.py +4 -3
- everest/config/everest_config.py +75 -42
- everest/config/forward_model_config.py +5 -3
- everest/config/install_data_config.py +7 -5
- everest/config/install_job_config.py +7 -3
- everest/config/install_template_config.py +3 -3
- everest/config/optimization_config.py +19 -6
- everest/config/output_constraint_config.py +8 -2
- everest/config/server_config.py +6 -49
- everest/config/utils.py +25 -105
- everest/config/validation_utils.py +17 -11
- everest/config_file_loader.py +13 -4
- everest/detached/client.py +3 -3
- everest/detached/everserver.py +7 -8
- everest/everest_storage.py +6 -12
- everest/gui/everest_client.py +2 -3
- everest/gui/main_window.py +2 -2
- everest/optimizer/everest2ropt.py +59 -32
- everest/optimizer/opt_model_transforms.py +12 -13
- everest/optimizer/utils.py +0 -29
- everest/strings.py +0 -5
- ert/config/everest_constraints_config.py +0 -95
- ert/config/ext_param_config.py +0 -106
- ert/gui/tools/export/__init__.py +0 -3
- ert/gui/tools/export/export_panel.py +0 -83
- ert/gui/tools/export/export_tool.py +0 -69
- ert/gui/tools/export/exporter.py +0 -36
- ert/services/storage_service.py +0 -127
- everest/config/sampler_config.py +0 -103
- everest/simulator/__init__.py +0 -88
- everest/simulator/everest_to_ert.py +0 -51
- /ert/gui/{suggestor → ertwidgets/suggestor}/__init__.py +0 -0
- /ert/gui/{suggestor → ertwidgets/suggestor}/_colors.py +0 -0
- {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/entry_points.txt +0 -0
- {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/licenses/COPYING +0 -0
- {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/top_level.txt +0 -0
ert/gui/simulation/run_dialog.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from queue import SimpleQueue
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import cast
|
|
7
8
|
|
|
8
9
|
import humanize
|
|
9
10
|
from PyQt6.QtCore import QModelIndex, QSize, Qt, QThread, QTimer
|
|
@@ -30,15 +31,18 @@ from PyQt6.QtWidgets import (
|
|
|
30
31
|
)
|
|
31
32
|
from typing_extensions import override
|
|
32
33
|
|
|
33
|
-
from
|
|
34
|
+
from _ert.events import EnsembleEvaluationWarning
|
|
35
|
+
from ert.config import ErrorInfo, WarningInfo
|
|
34
36
|
from ert.ensemble_evaluator import (
|
|
35
37
|
EndEvent,
|
|
36
38
|
FullSnapshotEvent,
|
|
37
39
|
SnapshotUpdateEvent,
|
|
40
|
+
StartEvent,
|
|
38
41
|
WarningEvent,
|
|
39
42
|
)
|
|
40
43
|
from ert.ensemble_evaluator import identifiers as ids
|
|
41
44
|
from ert.gui.ertnotifier import ErtNotifier
|
|
45
|
+
from ert.gui.ertwidgets import Suggestor
|
|
42
46
|
from ert.gui.model.fm_step_list import FMStepListProxyModel
|
|
43
47
|
from ert.gui.model.node import IterNode
|
|
44
48
|
from ert.gui.model.real_list import RealListModel
|
|
@@ -49,7 +53,6 @@ from ert.gui.model.snapshot import (
|
|
|
49
53
|
RealIens,
|
|
50
54
|
SnapshotModel,
|
|
51
55
|
)
|
|
52
|
-
from ert.gui.suggestor import Suggestor
|
|
53
56
|
from ert.gui.tools.file import FileDialog
|
|
54
57
|
from ert.run_models import (
|
|
55
58
|
RunModelAPI,
|
|
@@ -258,6 +261,7 @@ class RunDialog(QFrame):
|
|
|
258
261
|
self._fm_step_label.setObjectName("fm_step_label")
|
|
259
262
|
self._fm_step_overview = FMStepOverview(self._snapshot_model, self)
|
|
260
263
|
|
|
264
|
+
self._start_time: datetime | None = None
|
|
261
265
|
self.running_time = QLabel("Running time:\n -")
|
|
262
266
|
self.running_time.setMinimumWidth(150)
|
|
263
267
|
self.queue_system = QLabel("")
|
|
@@ -524,9 +528,11 @@ class RunDialog(QFrame):
|
|
|
524
528
|
|
|
525
529
|
@Slot()
|
|
526
530
|
def _on_ticker(self) -> None:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
531
|
+
if self._start_time:
|
|
532
|
+
humanized_runtime = humanize.precisedelta(
|
|
533
|
+
datetime.now() - self._start_time, minimum_unit="seconds", format="%d"
|
|
534
|
+
)
|
|
535
|
+
self.running_time.setText(f"Running time:\n{humanized_runtime}")
|
|
530
536
|
|
|
531
537
|
maximum_memory_usage = self._snapshot_model.root.max_memory_usage
|
|
532
538
|
|
|
@@ -543,11 +549,16 @@ class RunDialog(QFrame):
|
|
|
543
549
|
def _on_event(self, event: object) -> None:
|
|
544
550
|
model = self._snapshot_model
|
|
545
551
|
match event:
|
|
552
|
+
case StartEvent():
|
|
553
|
+
self._start_time = event.timestamp
|
|
546
554
|
case EndEvent(failed=failed, msg=msg):
|
|
547
555
|
self.simulation_done.emit(failed, msg)
|
|
548
556
|
self._ticker.stop()
|
|
549
557
|
case WarningEvent(msg=msg):
|
|
550
558
|
self.post_simulation_warnings.append(msg)
|
|
559
|
+
case EnsembleEvaluationWarning(warning_message=msg):
|
|
560
|
+
self._show_warning(msg)
|
|
561
|
+
|
|
551
562
|
case FullSnapshotEvent(
|
|
552
563
|
status_count=status_count, realization_count=realization_count
|
|
553
564
|
):
|
|
@@ -650,29 +661,25 @@ class RunDialog(QFrame):
|
|
|
650
661
|
if result == QMessageBox.StandardButton.Ok:
|
|
651
662
|
self.rerun_button.setEnabled(False)
|
|
652
663
|
self.kill_button.setEnabled(True)
|
|
664
|
+
self.post_simulation_warnings.clear()
|
|
653
665
|
self._is_rerunning_failed_realizations = True
|
|
654
666
|
self.rerun_failed_realizations_experiment.emit()
|
|
655
667
|
self.set_show_warning_button_to_initial_state()
|
|
656
668
|
|
|
657
|
-
def set_queue_system_name(self, queue_system: QueueSystem) -> None:
|
|
658
|
-
match queue_system:
|
|
659
|
-
case QueueSystem.LSF:
|
|
660
|
-
formatted_queue_system = "LSF"
|
|
661
|
-
case QueueSystem.LOCAL:
|
|
662
|
-
formatted_queue_system = "Local"
|
|
663
|
-
case QueueSystem.TORQUE:
|
|
664
|
-
formatted_queue_system = "Torque/OpenPBS"
|
|
665
|
-
case QueueSystem.SLURM:
|
|
666
|
-
formatted_queue_system = "Slurm"
|
|
667
|
-
case default:
|
|
668
|
-
assert_never(default)
|
|
669
|
-
self.queue_system.setText(f"Queue system:\n{formatted_queue_system}")
|
|
670
|
-
|
|
671
669
|
@override
|
|
672
670
|
def hideEvent(self, event: QHideEvent | None) -> None:
|
|
673
671
|
for file_dialog in self.findChildren(FileDialog):
|
|
674
672
|
file_dialog.close()
|
|
675
673
|
|
|
674
|
+
def _show_warning(self, msg: str) -> None:
|
|
675
|
+
msg_box = QMessageBox(self)
|
|
676
|
+
msg_box.setObjectName("EnsembleEvaluationWarningBox")
|
|
677
|
+
msg_box.setIcon(QMessageBox.Icon.Warning)
|
|
678
|
+
msg_box.setWindowTitle("Ensemble Evaluation Warning")
|
|
679
|
+
msg_box.setText(msg)
|
|
680
|
+
msg_box.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|
681
|
+
msg_box.show()
|
|
682
|
+
|
|
676
683
|
|
|
677
684
|
# Cannot use a non-static method here as
|
|
678
685
|
# it is called when the object is destroyed
|
|
@@ -4,13 +4,13 @@ from PyQt6.QtCore import Qt
|
|
|
4
4
|
from PyQt6.QtWidgets import QFormLayout, QLabel
|
|
5
5
|
from typing_extensions import override
|
|
6
6
|
|
|
7
|
+
from ert.config import AnalysisConfig, ParameterConfig
|
|
7
8
|
from ert.gui.ertnotifier import ErtNotifier
|
|
8
9
|
from ert.gui.ertwidgets import CopyableLabel
|
|
9
10
|
from ert.mode_definitions import TEST_RUN_MODE
|
|
10
11
|
from ert.run_models import SingleTestRun
|
|
11
12
|
|
|
12
|
-
from
|
|
13
|
-
from ..ertwidgets.parameterviewer import get_parameters_button
|
|
13
|
+
from ..ertwidgets import get_parameters_button
|
|
14
14
|
from ._design_matrix_panel import DesignMatrixPanel
|
|
15
15
|
from .experiment_config_panel import ExperimentConfigPanel
|
|
16
16
|
|
ert/gui/summarypanel.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
6
|
from PyQt6.QtCore import Qt
|
|
@@ -13,11 +14,13 @@ from PyQt6.QtWidgets import (
|
|
|
13
14
|
QWidget,
|
|
14
15
|
)
|
|
15
16
|
|
|
16
|
-
from ert.gui.ertwidgets
|
|
17
|
+
from ert.gui.ertwidgets import ErtSummary
|
|
17
18
|
|
|
18
19
|
if TYPE_CHECKING:
|
|
19
20
|
from ert.config import ErtConfig
|
|
20
21
|
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
class SummaryTemplate:
|
|
23
26
|
def __init__(self, title: str) -> None:
|
|
@@ -123,6 +126,22 @@ class SummaryPanel(QFrame):
|
|
|
123
126
|
|
|
124
127
|
self._layout.addLayout(layout)
|
|
125
128
|
|
|
129
|
+
def log_summary(self, run_model: str, num_realizations: int) -> None:
|
|
130
|
+
summary = ErtSummary(self.config)
|
|
131
|
+
|
|
132
|
+
observations = summary.getObservations()
|
|
133
|
+
observations_count = sum(e["count"] for e in observations)
|
|
134
|
+
|
|
135
|
+
_, parameter_count = summary.get_parameters()
|
|
136
|
+
|
|
137
|
+
logger.info(
|
|
138
|
+
f"Experiment summary:\n"
|
|
139
|
+
f"Runmodel: {run_model}\n"
|
|
140
|
+
f"Realizations: {num_realizations}\n"
|
|
141
|
+
f"Parameters: {parameter_count}\n"
|
|
142
|
+
f"Observations: {observations_count}"
|
|
143
|
+
)
|
|
144
|
+
|
|
126
145
|
@staticmethod
|
|
127
146
|
def _runlength_encode_list(strings: list[str]) -> list[tuple[str, int]]:
|
|
128
147
|
"""Runlength encode a list of strings.
|
|
@@ -13,10 +13,10 @@ from ert.gui.ertwidgets import (
|
|
|
13
13
|
EnsembleSelector,
|
|
14
14
|
QApplication,
|
|
15
15
|
StringBox,
|
|
16
|
+
Suggestor,
|
|
16
17
|
TextBox,
|
|
17
18
|
TextModel,
|
|
18
19
|
)
|
|
19
|
-
from ert.gui.suggestor import Suggestor
|
|
20
20
|
from ert.run_models.run_model import captured_logs
|
|
21
21
|
from ert.storage.local_ensemble import load_parameters_and_responses_from_runpath
|
|
22
22
|
from ert.validation import RangeStringArgument, StringDefinition
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
import polars as pl
|
|
6
|
+
from PyQt6.QtCore import (
|
|
7
|
+
Qt,
|
|
8
|
+
)
|
|
9
|
+
from PyQt6.QtCore import (
|
|
10
|
+
pyqtSlot as Slot,
|
|
11
|
+
)
|
|
12
|
+
from PyQt6.QtGui import QColor, QPalette
|
|
13
|
+
from PyQt6.QtWidgets import (
|
|
14
|
+
QDialog,
|
|
15
|
+
QDialogButtonBox,
|
|
16
|
+
QFileDialog,
|
|
17
|
+
QHBoxLayout,
|
|
18
|
+
QLineEdit,
|
|
19
|
+
QPushButton,
|
|
20
|
+
QTextEdit,
|
|
21
|
+
QVBoxLayout,
|
|
22
|
+
QWidget,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ExportDialog(QDialog):
|
|
27
|
+
"""Base dialog for exporting ensemble-related data to files."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
export_data: pl.DataFrame,
|
|
32
|
+
window_title: str = "Export data",
|
|
33
|
+
parent: QWidget | None = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
QDialog.__init__(self, parent)
|
|
36
|
+
self._export_data = export_data
|
|
37
|
+
self.setWindowTitle(window_title)
|
|
38
|
+
|
|
39
|
+
self.setWindowFlags(Qt.WindowType.Dialog)
|
|
40
|
+
self.setModal(True)
|
|
41
|
+
self.setMinimumWidth(450)
|
|
42
|
+
self.setMinimumHeight(200)
|
|
43
|
+
|
|
44
|
+
main_layout = QVBoxLayout()
|
|
45
|
+
|
|
46
|
+
file_layout = QHBoxLayout()
|
|
47
|
+
self._file_path_edit = QLineEdit(self)
|
|
48
|
+
self._file_path_edit.setPlaceholderText("Select output file...")
|
|
49
|
+
self._file_path_edit.textChanged.connect(self.validate_file)
|
|
50
|
+
browse_button = QPushButton("Browse...", self)
|
|
51
|
+
browse_button.clicked.connect(self.browse_file)
|
|
52
|
+
file_layout.addWidget(self._file_path_edit)
|
|
53
|
+
file_layout.addWidget(browse_button)
|
|
54
|
+
main_layout.addLayout(file_layout)
|
|
55
|
+
|
|
56
|
+
self._export_text_area = QTextEdit(self)
|
|
57
|
+
self._export_text_area.setReadOnly(True)
|
|
58
|
+
self._export_text_area.setFixedHeight(100)
|
|
59
|
+
main_layout.addWidget(self._export_text_area)
|
|
60
|
+
|
|
61
|
+
button_box = QDialogButtonBox(self)
|
|
62
|
+
button_box.setStandardButtons(QDialogButtonBox.StandardButton.Cancel)
|
|
63
|
+
button_box.rejected.connect(self.cancel)
|
|
64
|
+
|
|
65
|
+
self._export_button = cast(
|
|
66
|
+
QPushButton,
|
|
67
|
+
button_box.addButton("Export", QDialogButtonBox.ButtonRole.AcceptRole),
|
|
68
|
+
)
|
|
69
|
+
self._export_button.clicked.connect(self.export)
|
|
70
|
+
self._export_button.setEnabled(False) # Initially disabled
|
|
71
|
+
main_layout.addWidget(button_box)
|
|
72
|
+
|
|
73
|
+
self.setLayout(main_layout)
|
|
74
|
+
|
|
75
|
+
@Slot()
|
|
76
|
+
def export(self) -> None:
|
|
77
|
+
self._export_text_area.insertPlainText("Exporting...\n")
|
|
78
|
+
try:
|
|
79
|
+
output_file: str = self._file_path_edit.text().strip()
|
|
80
|
+
self._export_button.setEnabled(False)
|
|
81
|
+
self._export_data.write_csv(output_file, float_precision=6)
|
|
82
|
+
self._export_text_area.insertPlainText(f"Data exported to: {output_file}\n")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
self._export_text_area.insertHtml(
|
|
85
|
+
f"<span style='color: red;'>Could not export data: {e!s}</span><br>"
|
|
86
|
+
)
|
|
87
|
+
finally:
|
|
88
|
+
self._export_button.setEnabled(True)
|
|
89
|
+
|
|
90
|
+
@Slot()
|
|
91
|
+
def cancel(self) -> None:
|
|
92
|
+
self.reject()
|
|
93
|
+
|
|
94
|
+
@Slot()
|
|
95
|
+
def validate_file(self) -> None:
|
|
96
|
+
"""Validation to check if the file path is not empty or invalid."""
|
|
97
|
+
|
|
98
|
+
def _set_invalid(tooltip_text: str = "Invalid file path") -> None:
|
|
99
|
+
palette = self._file_path_edit.palette()
|
|
100
|
+
palette.setColor(QPalette.ColorRole.Text, QColor("red"))
|
|
101
|
+
self._file_path_edit.setPalette(palette)
|
|
102
|
+
self._file_path_edit.setToolTip(tooltip_text)
|
|
103
|
+
self._export_button.setToolTip(tooltip_text)
|
|
104
|
+
self._export_button.setEnabled(False)
|
|
105
|
+
|
|
106
|
+
def _set_valid() -> None:
|
|
107
|
+
palette = self._file_path_edit.palette()
|
|
108
|
+
palette.setColor(QPalette.ColorRole.Text, QColor("black"))
|
|
109
|
+
self._file_path_edit.setPalette(palette)
|
|
110
|
+
self._file_path_edit.setToolTip("")
|
|
111
|
+
self._export_button.setToolTip("")
|
|
112
|
+
self._export_button.setEnabled(True)
|
|
113
|
+
|
|
114
|
+
path = Path(self._file_path_edit.text().strip())
|
|
115
|
+
if str(path) in {"", "."}:
|
|
116
|
+
_set_invalid(tooltip_text="No filename provided")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
if path.is_dir():
|
|
120
|
+
_set_invalid(tooltip_text=f"'{path!s}' is an existing directory.")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
with contextlib.suppress(Exception):
|
|
124
|
+
if path.parent.is_dir():
|
|
125
|
+
_set_valid()
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
_set_invalid()
|
|
129
|
+
|
|
130
|
+
@Slot()
|
|
131
|
+
def browse_file(self) -> None:
|
|
132
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
133
|
+
self, "Select Output File", "", "All Files (*)"
|
|
134
|
+
)
|
|
135
|
+
if file_path:
|
|
136
|
+
self._file_path_edit.setText(file_path)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import json
|
|
2
3
|
from enum import IntEnum
|
|
3
4
|
|
|
@@ -6,13 +7,18 @@ import seaborn as sns
|
|
|
6
7
|
import yaml
|
|
7
8
|
from matplotlib.backends.backend_qt5agg import FigureCanvas # type: ignore
|
|
8
9
|
from matplotlib.figure import Figure
|
|
10
|
+
from polars import DataFrame
|
|
9
11
|
from PyQt6.QtCore import Qt
|
|
10
12
|
from PyQt6.QtCore import pyqtSlot as Slot
|
|
11
13
|
from PyQt6.QtWidgets import (
|
|
14
|
+
QAbstractItemView,
|
|
12
15
|
QFrame,
|
|
13
16
|
QHBoxLayout,
|
|
14
17
|
QLabel,
|
|
18
|
+
QPushButton,
|
|
15
19
|
QStackedLayout,
|
|
20
|
+
QTableWidget,
|
|
21
|
+
QTableWidgetItem,
|
|
16
22
|
QTabWidget,
|
|
17
23
|
QTextEdit,
|
|
18
24
|
QTreeWidget,
|
|
@@ -21,8 +27,11 @@ from PyQt6.QtWidgets import (
|
|
|
21
27
|
QWidget,
|
|
22
28
|
)
|
|
23
29
|
|
|
30
|
+
from ert import LibresFacade
|
|
24
31
|
from ert.storage import Ensemble, Experiment, RealizationStorageState
|
|
25
32
|
|
|
33
|
+
from .export_dialog import ExportDialog
|
|
34
|
+
|
|
26
35
|
|
|
27
36
|
class _WidgetType(IntEnum):
|
|
28
37
|
EMPTY_WIDGET = 0
|
|
@@ -42,6 +51,8 @@ class _EnsembleWidgetTabs(IntEnum):
|
|
|
42
51
|
ENSEMBLE_TAB = 0
|
|
43
52
|
STATE_TAB = 1
|
|
44
53
|
OBSERVATIONS_TAB = 2
|
|
54
|
+
PARAMETERS_TAB = 3
|
|
55
|
+
MISFIT_TAB = 4
|
|
45
56
|
|
|
46
57
|
|
|
47
58
|
class _ExperimentWidget(QWidget):
|
|
@@ -126,12 +137,15 @@ class _EnsembleWidget(QWidget):
|
|
|
126
137
|
|
|
127
138
|
info_frame.setLayout(info_layout)
|
|
128
139
|
|
|
140
|
+
state_frame = QFrame()
|
|
141
|
+
state_layout = QHBoxLayout()
|
|
129
142
|
self._state_text_edit = QTextEdit()
|
|
130
143
|
self._state_text_edit.setReadOnly(True)
|
|
131
144
|
self._state_text_edit.setObjectName("ensemble_state_text")
|
|
145
|
+
state_layout.addWidget(self._state_text_edit)
|
|
146
|
+
state_frame.setLayout(state_layout)
|
|
132
147
|
|
|
133
148
|
observations_frame = QFrame()
|
|
134
|
-
|
|
135
149
|
self._observations_tree_widget = QTreeWidget(self)
|
|
136
150
|
self._observations_tree_widget.currentItemChanged.connect(
|
|
137
151
|
self._currentItemChanged
|
|
@@ -151,15 +165,38 @@ class _EnsembleWidget(QWidget):
|
|
|
151
165
|
observations_layout.addWidget(self._canvas)
|
|
152
166
|
observations_frame.setLayout(observations_layout)
|
|
153
167
|
|
|
168
|
+
self._parameters_table = QTableWidget()
|
|
169
|
+
self._parameters_table.setEditTriggers(
|
|
170
|
+
QAbstractItemView.EditTrigger.NoEditTriggers
|
|
171
|
+
)
|
|
172
|
+
self._export_params_button = QPushButton("Export...")
|
|
173
|
+
self._export_params_button.clicked.connect(self.onClickExportParameters)
|
|
174
|
+
|
|
175
|
+
parameters_frame = self.create_export_frame(
|
|
176
|
+
self._parameters_table, self._export_params_button
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
self._misfit_table = QTableWidget()
|
|
180
|
+
self._export_misfit_button = QPushButton("Export...")
|
|
181
|
+
self._export_misfit_button.clicked.connect(self.onClickExportMisfit)
|
|
182
|
+
|
|
183
|
+
misfit_frame = self.create_export_frame(
|
|
184
|
+
self._misfit_table, self._export_misfit_button
|
|
185
|
+
)
|
|
186
|
+
|
|
154
187
|
self._tab_widget = QTabWidget()
|
|
155
188
|
self._tab_widget.insertTab(
|
|
156
189
|
_EnsembleWidgetTabs.ENSEMBLE_TAB, info_frame, "Ensemble"
|
|
157
190
|
)
|
|
191
|
+
self._tab_widget.insertTab(_EnsembleWidgetTabs.STATE_TAB, state_frame, "State")
|
|
158
192
|
self._tab_widget.insertTab(
|
|
159
|
-
_EnsembleWidgetTabs.
|
|
193
|
+
_EnsembleWidgetTabs.OBSERVATIONS_TAB, observations_frame, "Observations"
|
|
160
194
|
)
|
|
161
195
|
self._tab_widget.insertTab(
|
|
162
|
-
_EnsembleWidgetTabs.
|
|
196
|
+
_EnsembleWidgetTabs.PARAMETERS_TAB, parameters_frame, "Parameters"
|
|
197
|
+
)
|
|
198
|
+
self._tab_widget.insertTab(
|
|
199
|
+
_EnsembleWidgetTabs.MISFIT_TAB, misfit_frame, "Misfit"
|
|
163
200
|
)
|
|
164
201
|
self._tab_widget.currentChanged.connect(self._currentTabChanged)
|
|
165
202
|
|
|
@@ -168,6 +205,17 @@ class _EnsembleWidget(QWidget):
|
|
|
168
205
|
|
|
169
206
|
self.setLayout(layout)
|
|
170
207
|
|
|
208
|
+
def create_export_frame(self, table: QTableWidget, button: QPushButton) -> QFrame:
|
|
209
|
+
export_frame = QFrame()
|
|
210
|
+
export_layout = QVBoxLayout()
|
|
211
|
+
vertical_header = table.verticalHeader()
|
|
212
|
+
assert vertical_header is not None
|
|
213
|
+
vertical_header.setVisible(False)
|
|
214
|
+
export_layout.addWidget(table)
|
|
215
|
+
export_layout.addWidget(button)
|
|
216
|
+
export_frame.setLayout(export_layout)
|
|
217
|
+
return export_frame
|
|
218
|
+
|
|
171
219
|
def _currentItemChanged(
|
|
172
220
|
self, selected: QTreeWidgetItem, _: QTreeWidgetItem
|
|
173
221
|
) -> None:
|
|
@@ -189,9 +237,9 @@ class _EnsembleWidget(QWidget):
|
|
|
189
237
|
|
|
190
238
|
response_type, obs_for_type = next(
|
|
191
239
|
(
|
|
192
|
-
(response_type,
|
|
193
|
-
for response_type,
|
|
194
|
-
if observation_key in
|
|
240
|
+
(response_type, df)
|
|
241
|
+
for response_type, df in observations_dict.items()
|
|
242
|
+
if observation_key in df["observation_key"]
|
|
195
243
|
),
|
|
196
244
|
(None, None),
|
|
197
245
|
)
|
|
@@ -356,12 +404,53 @@ class _EnsembleWidget(QWidget):
|
|
|
356
404
|
0, Qt.SortOrder.AscendingOrder
|
|
357
405
|
)
|
|
358
406
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
407
|
+
for i in range(self._observations_tree_widget.topLevelItemCount()):
|
|
408
|
+
item = self._observations_tree_widget.topLevelItem(i)
|
|
409
|
+
assert item is not None
|
|
410
|
+
if item.childCount() > 0:
|
|
411
|
+
self._observations_tree_widget.setCurrentItem(item.child(0))
|
|
412
|
+
break
|
|
413
|
+
|
|
414
|
+
elif index in {
|
|
415
|
+
_EnsembleWidgetTabs.PARAMETERS_TAB,
|
|
416
|
+
_EnsembleWidgetTabs.MISFIT_TAB,
|
|
417
|
+
}:
|
|
418
|
+
assert self._ensemble is not None
|
|
419
|
+
|
|
420
|
+
df: pl.DataFrame = pl.DataFrame()
|
|
421
|
+
with contextlib.suppress(Exception):
|
|
422
|
+
if index == _EnsembleWidgetTabs.PARAMETERS_TAB:
|
|
423
|
+
df = self._ensemble.load_scalar_keys(transformed=True)
|
|
424
|
+
else:
|
|
425
|
+
df = self.get_misfit_df()
|
|
426
|
+
|
|
427
|
+
table = (
|
|
428
|
+
self._parameters_table
|
|
429
|
+
if index == _EnsembleWidgetTabs.PARAMETERS_TAB
|
|
430
|
+
else self._misfit_table
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
table.setUpdatesEnabled(False)
|
|
434
|
+
table.setSortingEnabled(False)
|
|
435
|
+
table.setRowCount(df.height)
|
|
436
|
+
table.setColumnCount(df.width)
|
|
437
|
+
table.setHorizontalHeaderLabels(df.columns)
|
|
438
|
+
|
|
439
|
+
rows = df.rows()
|
|
440
|
+
for r, row in enumerate(rows):
|
|
441
|
+
for c, v in enumerate(row):
|
|
442
|
+
table.setItem(r, c, QTableWidgetItem("" if v is None else str(v)))
|
|
443
|
+
|
|
444
|
+
table.resizeColumnsToContents()
|
|
445
|
+
table.setUpdatesEnabled(True)
|
|
446
|
+
|
|
447
|
+
def get_misfit_df(self) -> DataFrame:
|
|
448
|
+
assert self._ensemble is not None
|
|
449
|
+
df = LibresFacade.load_all_misfit_data(self._ensemble)
|
|
450
|
+
realization_column = pl.Series(df.index)
|
|
451
|
+
df = pl.from_pandas(df)
|
|
452
|
+
df.insert_column(0, realization_column)
|
|
453
|
+
return df
|
|
365
454
|
|
|
366
455
|
@Slot(Ensemble)
|
|
367
456
|
def setEnsemble(self, ensemble: Ensemble) -> None:
|
|
@@ -372,6 +461,22 @@ class _EnsembleWidget(QWidget):
|
|
|
372
461
|
|
|
373
462
|
self._tab_widget.setCurrentIndex(0)
|
|
374
463
|
|
|
464
|
+
@Slot()
|
|
465
|
+
def onClickExportMisfit(self) -> None:
|
|
466
|
+
assert self._ensemble is not None
|
|
467
|
+
misfit_df = self.get_misfit_df()
|
|
468
|
+
export_dialog = ExportDialog(misfit_df, "Export misfit", parent=self)
|
|
469
|
+
export_dialog.show()
|
|
470
|
+
|
|
471
|
+
@Slot()
|
|
472
|
+
def onClickExportParameters(self) -> None:
|
|
473
|
+
assert self._ensemble is not None
|
|
474
|
+
parameters_df = self._ensemble.load_scalar_keys(transformed=True)
|
|
475
|
+
export_dialog = ExportDialog(
|
|
476
|
+
parameters_df, window_title="Export parameters", parent=self
|
|
477
|
+
)
|
|
478
|
+
export_dialog.show()
|
|
479
|
+
|
|
375
480
|
|
|
376
481
|
class _RealizationWidget(QWidget):
|
|
377
482
|
def __init__(self) -> None:
|
|
@@ -418,14 +523,14 @@ class _RealizationWidget(QWidget):
|
|
|
418
523
|
)
|
|
419
524
|
|
|
420
525
|
html = "<table>"
|
|
421
|
-
for name,
|
|
422
|
-
html += f"<tr><td>{name} - {
|
|
526
|
+
for name, response_state in ensemble.get_response_state(realization).items():
|
|
527
|
+
html += f"<tr><td>{name} - {response_state.name}</td></tr>"
|
|
423
528
|
html += "</table>"
|
|
424
529
|
self._response_text_edit.setHtml(html)
|
|
425
530
|
|
|
426
531
|
html = "<table>"
|
|
427
|
-
for name,
|
|
428
|
-
html += f"<tr><td>{name} - {
|
|
532
|
+
for name, param_state in ensemble.get_parameter_state(realization).items():
|
|
533
|
+
html += f"<tr><td>{name} - {param_state.name}</td></tr>"
|
|
429
534
|
html += "</table>"
|
|
430
535
|
self._parameter_text_edit.setHtml(html)
|
|
431
536
|
|
|
@@ -24,8 +24,7 @@ from PyQt6.QtWidgets import (
|
|
|
24
24
|
|
|
25
25
|
from ert.config import ErrorInfo, ErtConfig
|
|
26
26
|
from ert.gui.ertnotifier import ErtNotifier
|
|
27
|
-
from ert.gui.ertwidgets
|
|
28
|
-
from ert.gui.suggestor import Suggestor
|
|
27
|
+
from ert.gui.ertwidgets import CreateExperimentDialog, Suggestor
|
|
29
28
|
from ert.storage import Ensemble, Experiment
|
|
30
29
|
|
|
31
30
|
from .storage_model import (
|