ert 18.0.9__py3-none-any.whl → 19.0.0rc0__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/forward_model_runner/client.py +6 -2
- ert/__main__.py +20 -6
- ert/analysis/_es_update.py +6 -19
- ert/cli/main.py +7 -3
- ert/config/__init__.py +3 -4
- ert/config/_create_observation_dataframes.py +57 -8
- ert/config/_get_num_cpu.py +1 -1
- ert/config/_observations.py +77 -1
- ert/config/distribution.py +1 -1
- ert/config/ensemble_config.py +3 -3
- ert/config/ert_config.py +50 -8
- ert/config/{ext_param_config.py → everest_control.py} +8 -12
- ert/config/everest_response.py +3 -5
- ert/config/field.py +76 -14
- ert/config/forward_model_step.py +12 -9
- ert/config/gen_data_config.py +3 -4
- ert/config/gen_kw_config.py +2 -12
- ert/config/parameter_config.py +1 -16
- ert/config/parsing/_option_dict.py +10 -2
- ert/config/parsing/config_keywords.py +1 -0
- ert/config/parsing/config_schema.py +8 -0
- ert/config/parsing/config_schema_deprecations.py +14 -3
- ert/config/parsing/config_schema_item.py +12 -3
- 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/queue_config.py +0 -1
- ert/config/response_config.py +0 -1
- ert/config/rft_config.py +78 -33
- ert/config/summary_config.py +1 -2
- ert/config/surface_config.py +59 -16
- ert/dark_storage/common.py +1 -1
- ert/dark_storage/compute/misfits.py +4 -1
- ert/dark_storage/endpoints/compute/misfits.py +4 -2
- ert/dark_storage/endpoints/experiment_server.py +12 -9
- ert/dark_storage/endpoints/experiments.py +2 -2
- ert/dark_storage/endpoints/observations.py +4 -2
- ert/dark_storage/endpoints/parameters.py +2 -18
- ert/dark_storage/endpoints/responses.py +10 -5
- ert/dark_storage/json_schema/experiment.py +1 -1
- ert/data/_measured_data.py +6 -5
- ert/ensemble_evaluator/config.py +2 -1
- ert/field_utils/field_utils.py +1 -1
- ert/field_utils/grdecl_io.py +9 -26
- ert/field_utils/roff_io.py +1 -1
- ert/gui/__init__.py +5 -2
- ert/gui/ertnotifier.py +1 -1
- ert/gui/ertwidgets/pathchooser.py +0 -3
- ert/gui/ertwidgets/suggestor/suggestor.py +63 -30
- ert/gui/main.py +27 -5
- ert/gui/main_window.py +0 -5
- ert/gui/simulation/experiment_panel.py +12 -7
- ert/gui/simulation/run_dialog.py +2 -16
- ert/gui/summarypanel.py +0 -19
- ert/gui/tools/manage_experiments/export_dialog.py +136 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +110 -9
- ert/gui/tools/plot/plot_api.py +24 -15
- ert/gui/tools/plot/plot_widget.py +10 -2
- ert/gui/tools/plot/plot_window.py +26 -18
- 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 +3 -1
- 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 +3 -1
- ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
- ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
- ert/plugins/plugin_manager.py +4 -0
- ert/resources/forward_models/run_reservoirsimulator.py +8 -3
- ert/run_models/_create_run_path.py +3 -3
- ert/run_models/everest_run_model.py +13 -11
- ert/run_models/initial_ensemble_run_model.py +2 -2
- ert/run_models/run_model.py +30 -1
- ert/services/_base_service.py +6 -5
- ert/services/ert_server.py +4 -4
- ert/shared/_doc_utils/__init__.py +4 -2
- ert/shared/net_utils.py +43 -18
- ert/shared/version.py +3 -3
- ert/storage/__init__.py +2 -0
- ert/storage/local_ensemble.py +13 -7
- ert/storage/local_experiment.py +2 -2
- ert/storage/local_storage.py +41 -25
- ert/storage/migration/to11.py +1 -1
- ert/storage/migration/to18.py +0 -1
- ert/storage/migration/to19.py +34 -0
- ert/storage/migration/to20.py +23 -0
- ert/storage/migration/to21.py +25 -0
- ert/workflow_runner.py +2 -1
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/METADATA +1 -1
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/RECORD +112 -112
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/WHEEL +1 -1
- everest/bin/everlint_script.py +0 -2
- everest/bin/utils.py +2 -1
- everest/bin/visualization_script.py +4 -11
- everest/config/control_config.py +4 -4
- everest/config/control_variable_config.py +2 -2
- everest/config/everest_config.py +9 -0
- everest/config/utils.py +2 -2
- everest/config/validation_utils.py +7 -1
- everest/config_file_loader.py +0 -2
- everest/detached/client.py +3 -3
- everest/everest_storage.py +0 -2
- everest/gui/everest_client.py +2 -2
- everest/optimizer/everest2ropt.py +4 -4
- everest/optimizer/opt_model_transforms.py +2 -2
- ert/config/violations.py +0 -0
- 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-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/entry_points.txt +0 -0
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/licenses/COPYING +0 -0
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/top_level.txt +0 -0
ert/gui/__init__.py
CHANGED
|
@@ -20,12 +20,15 @@ else:
|
|
|
20
20
|
|
|
21
21
|
def is_high_contrast_mode() -> bool:
|
|
22
22
|
app = cast(QWidget, QApplication.instance())
|
|
23
|
-
return
|
|
23
|
+
return (
|
|
24
|
+
app is not None
|
|
25
|
+
and app.palette().color(QPalette.ColorRole.Window).lightness() > 245
|
|
26
|
+
)
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
def is_dark_mode() -> bool:
|
|
27
30
|
app = cast(QWidget, QApplication.instance())
|
|
28
|
-
return app.palette().base().color().value() < 70
|
|
31
|
+
return app is not None and app.palette().base().color().value() < 70
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
__version__ = ert.shared.__version__
|
ert/gui/ertnotifier.py
CHANGED
|
@@ -83,7 +83,6 @@ class PathChooser(QWidget):
|
|
|
83
83
|
if self._model.pathMustExist():
|
|
84
84
|
valid = False
|
|
85
85
|
message = PathChooser.PATH_DOES_NOT_EXIST_MSG
|
|
86
|
-
# todo: check if new (non-existing) file has directory or file format?
|
|
87
86
|
elif path_exists:
|
|
88
87
|
if self._model.pathMustBeExecutable() and is_file and not is_executable:
|
|
89
88
|
valid = False
|
|
@@ -122,8 +121,6 @@ class PathChooser(QWidget):
|
|
|
122
121
|
|
|
123
122
|
def selectPath(self) -> None:
|
|
124
123
|
"""Pops up the 'select a file/directory' dialog"""
|
|
125
|
-
# todo: This probably needs some reworking to work properly with
|
|
126
|
-
# different scenarios... (file + dir)
|
|
127
124
|
self._editing = True
|
|
128
125
|
current_directory = self.getPath()
|
|
129
126
|
|
|
@@ -19,9 +19,11 @@ from PyQt6.QtWidgets import (
|
|
|
19
19
|
QVBoxLayout,
|
|
20
20
|
QWidget,
|
|
21
21
|
)
|
|
22
|
+
from typing_extensions import override
|
|
22
23
|
|
|
23
24
|
from ert.gui import is_dark_mode
|
|
24
25
|
|
|
26
|
+
from .. import CopyButton
|
|
25
27
|
from ._colors import BLUE_TEXT
|
|
26
28
|
from ._suggestor_message import SuggestorMessage
|
|
27
29
|
|
|
@@ -105,6 +107,32 @@ QPushButton:hover {{
|
|
|
105
107
|
"""
|
|
106
108
|
|
|
107
109
|
|
|
110
|
+
class _CopyAllButton(CopyButton):
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
errors: list[ErrorInfo],
|
|
114
|
+
warnings: list[WarningInfo],
|
|
115
|
+
deprecations: list[WarningInfo],
|
|
116
|
+
) -> None:
|
|
117
|
+
super().__init__()
|
|
118
|
+
self.setText(" Copy all messages")
|
|
119
|
+
self.all_messages = "\n\n".join(
|
|
120
|
+
[
|
|
121
|
+
f"{info.message}" + (f"\n{info.location()}" if info.location() else "")
|
|
122
|
+
for info in (errors + warnings + deprecations)
|
|
123
|
+
]
|
|
124
|
+
)
|
|
125
|
+
self.setStyleSheet(SECONDARY_BUTTON_STYLE)
|
|
126
|
+
|
|
127
|
+
@override
|
|
128
|
+
def copy(self) -> None:
|
|
129
|
+
logger.info(
|
|
130
|
+
"Copy all button in Suggestor used. "
|
|
131
|
+
f"Copied {len(self.all_messages)} characters"
|
|
132
|
+
)
|
|
133
|
+
self.copy_text(self.all_messages)
|
|
134
|
+
|
|
135
|
+
|
|
108
136
|
class Suggestor(QWidget):
|
|
109
137
|
def __init__(
|
|
110
138
|
self,
|
|
@@ -145,7 +173,24 @@ class Suggestor(QWidget):
|
|
|
145
173
|
data_layout.addWidget(self._help_panel(help_links))
|
|
146
174
|
self.__layout.addWidget(data_widget)
|
|
147
175
|
|
|
148
|
-
|
|
176
|
+
action_buttons = QWidget(parent=self)
|
|
177
|
+
action_buttons_layout = QHBoxLayout()
|
|
178
|
+
action_buttons_layout.setContentsMargins(0, 24, 0, 0)
|
|
179
|
+
|
|
180
|
+
if any([errors, warnings, deprecations]):
|
|
181
|
+
action_buttons_layout.addWidget(
|
|
182
|
+
_CopyAllButton(errors, warnings, deprecations)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
action_buttons_layout.addStretch()
|
|
186
|
+
|
|
187
|
+
if continue_action:
|
|
188
|
+
action_buttons_layout.addWidget(self._continue_button())
|
|
189
|
+
|
|
190
|
+
action_buttons_layout.addWidget(self._close_button())
|
|
191
|
+
|
|
192
|
+
action_buttons.setLayout(action_buttons_layout)
|
|
193
|
+
self.__layout.addWidget(action_buttons)
|
|
149
194
|
|
|
150
195
|
def _help_panel(self, help_links: dict[str, str]) -> QFrame:
|
|
151
196
|
help_button_frame = QFrame(parent=self)
|
|
@@ -204,35 +249,23 @@ class Suggestor(QWidget):
|
|
|
204
249
|
area_layout.addWidget(self._messages(errors, warnings, deprecations))
|
|
205
250
|
return problem_area
|
|
206
251
|
|
|
207
|
-
def
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
run = QPushButton("Open ERT")
|
|
225
|
-
run.setStyleSheet(BUTTON_STYLE)
|
|
226
|
-
run.setObjectName("run_ert_button")
|
|
227
|
-
run.pressed.connect(run_pressed)
|
|
228
|
-
buttons_layout.addWidget(run)
|
|
229
|
-
|
|
230
|
-
give_up.setStyleSheet(SECONDARY_BUTTON_STYLE)
|
|
231
|
-
|
|
232
|
-
buttons_layout.addWidget(give_up)
|
|
233
|
-
buttons.setLayout(buttons_layout)
|
|
234
|
-
|
|
235
|
-
return buttons
|
|
252
|
+
def _continue_button(self) -> QWidget:
|
|
253
|
+
assert self._continue_action
|
|
254
|
+
continue_button = QPushButton("Open ERT")
|
|
255
|
+
continue_button.setStyleSheet(BUTTON_STYLE)
|
|
256
|
+
continue_button.setObjectName("run_ert_button")
|
|
257
|
+
continue_button.pressed.connect(self._continue_action)
|
|
258
|
+
continue_button.pressed.connect(self.close)
|
|
259
|
+
return continue_button
|
|
260
|
+
|
|
261
|
+
def _close_button(self) -> QPushButton:
|
|
262
|
+
close_button = QPushButton("Close")
|
|
263
|
+
close_button.setObjectName("close_button")
|
|
264
|
+
close_button.pressed.connect(self.close)
|
|
265
|
+
close_button.setStyleSheet(
|
|
266
|
+
SECONDARY_BUTTON_STYLE if self._continue_action else BUTTON_STYLE
|
|
267
|
+
)
|
|
268
|
+
return close_button
|
|
236
269
|
|
|
237
270
|
def _messages(
|
|
238
271
|
self,
|
ert/gui/main.py
CHANGED
|
@@ -14,7 +14,7 @@ from signal import SIG_DFL, SIGINT, signal
|
|
|
14
14
|
from opentelemetry.trace import Status, StatusCode
|
|
15
15
|
from PyQt6.QtCore import QDir
|
|
16
16
|
from PyQt6.QtGui import QIcon
|
|
17
|
-
from PyQt6.QtWidgets import QApplication, QWidget
|
|
17
|
+
from PyQt6.QtWidgets import QApplication, QMessageBox, QWidget
|
|
18
18
|
|
|
19
19
|
from ert.config import (
|
|
20
20
|
ErrorInfo,
|
|
@@ -29,12 +29,13 @@ from ert.gui.tools.event_viewer import (
|
|
|
29
29
|
from ert.namespace import Namespace
|
|
30
30
|
from ert.plugins import ErtRuntimePlugins, get_site_plugins
|
|
31
31
|
from ert.services import ErtServer
|
|
32
|
+
from ert.shared import __version__
|
|
32
33
|
from ert.storage import (
|
|
33
34
|
ErtStorageException,
|
|
34
35
|
LocalStorage,
|
|
35
36
|
local_storage_set_ert_config,
|
|
36
|
-
open_storage,
|
|
37
37
|
)
|
|
38
|
+
from ert.storage.local_storage import _LOCAL_STORAGE_VERSION, _storage_version
|
|
38
39
|
from ert.trace import trace, tracer
|
|
39
40
|
|
|
40
41
|
from .ertwidgets import Suggestor
|
|
@@ -164,9 +165,30 @@ def _start_initial_gui_window(
|
|
|
164
165
|
if ert_config is not None:
|
|
165
166
|
try:
|
|
166
167
|
if LocalStorage.check_migration_needed(Path(ert_config.ens_path)):
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
storage_version = _storage_version(Path(ert_config.ens_path))
|
|
169
|
+
current_version = _LOCAL_STORAGE_VERSION
|
|
170
|
+
|
|
171
|
+
migrate_dialog = QMessageBox.warning(
|
|
172
|
+
None,
|
|
173
|
+
f"Migrate storage to version {current_version}?",
|
|
174
|
+
f"Ert storage is version {storage_version} and needs to be migrated"
|
|
175
|
+
f" to version {current_version} to be able to continue\n\n"
|
|
176
|
+
f"After migration, this storage can only be opened with Ert"
|
|
177
|
+
f" version {__version__} or newer\n\n"
|
|
178
|
+
"Do you wish to continue migrating storage?\n",
|
|
179
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if migrate_dialog == QMessageBox.StandardButton.Yes:
|
|
183
|
+
logger.info(
|
|
184
|
+
f"Migrating from version {storage_version} to"
|
|
185
|
+
f" version {current_version}"
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
logger.info("Storage migration cancelled by user")
|
|
189
|
+
os._exit(0)
|
|
190
|
+
|
|
191
|
+
LocalStorage.perform_migration(Path(ert_config.ens_path))
|
|
170
192
|
storage_path = ert_config.ens_path
|
|
171
193
|
except ErtStorageException as err:
|
|
172
194
|
validation_messages.errors.append(
|
ert/gui/main_window.py
CHANGED
|
@@ -31,7 +31,6 @@ from ert.gui.find_ert_info import find_ert_info
|
|
|
31
31
|
from ert.gui.simulation import ExperimentPanel
|
|
32
32
|
from ert.gui.simulation.run_dialog import RunDialog
|
|
33
33
|
from ert.gui.tools.event_viewer import EventViewerTool, GUILogHandler
|
|
34
|
-
from ert.gui.tools.export import ExportTool
|
|
35
34
|
from ert.gui.tools.load_results import LoadResultsTool
|
|
36
35
|
from ert.gui.tools.manage_experiments import ManageExperimentsPanel
|
|
37
36
|
from ert.gui.tools.plot.plot_window import PlotWindow
|
|
@@ -373,10 +372,6 @@ class ErtMainWindow(QMainWindow):
|
|
|
373
372
|
tools_menu.addAction(self._event_viewer_tool.getAction())
|
|
374
373
|
self.close_signal.connect(self._event_viewer_tool.close_wnd)
|
|
375
374
|
|
|
376
|
-
self.export_tool = ExportTool(self.ert_config, self.notifier)
|
|
377
|
-
self.export_tool.setParent(self)
|
|
378
|
-
tools_menu.addAction(self.export_tool.getAction())
|
|
379
|
-
|
|
380
375
|
self.workflows_tool = WorkflowsTool(self.ert_config, self.notifier)
|
|
381
376
|
self.workflows_tool.setParent(self)
|
|
382
377
|
tools_menu.addAction(self.workflows_tool.getAction())
|
|
@@ -55,9 +55,15 @@ def create_md_table(kv: dict[str, str], output: str) -> str:
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def get_simulation_thread(
|
|
58
|
-
model: Any,
|
|
58
|
+
model: Any,
|
|
59
|
+
rerun_failed_realizations: bool = False,
|
|
60
|
+
use_ipc_protocol: bool = False,
|
|
61
|
+
prioritize_private_ip_address: bool = False,
|
|
59
62
|
) -> ErtThread:
|
|
60
|
-
evaluator_server_config = EvaluatorServerConfig(
|
|
63
|
+
evaluator_server_config = EvaluatorServerConfig(
|
|
64
|
+
use_ipc_protocol=use_ipc_protocol,
|
|
65
|
+
prioritize_private_ip_address=prioritize_private_ip_address,
|
|
66
|
+
)
|
|
61
67
|
|
|
62
68
|
def run() -> None:
|
|
63
69
|
model.api.start_simulations_thread(
|
|
@@ -354,10 +360,6 @@ class ExperimentPanel(QWidget):
|
|
|
354
360
|
return
|
|
355
361
|
QApplication.restoreOverrideCursor()
|
|
356
362
|
|
|
357
|
-
self.configuration_summary.log_summary(
|
|
358
|
-
args.mode, model.get_number_of_active_realizations()
|
|
359
|
-
)
|
|
360
|
-
|
|
361
363
|
self._dialog = RunDialog(
|
|
362
364
|
f"Experiment - {self._config_file} {find_ert_info()}",
|
|
363
365
|
model.api,
|
|
@@ -368,7 +370,9 @@ class ExperimentPanel(QWidget):
|
|
|
368
370
|
run_path=Path(self.config.runpath_config.runpath_format_string),
|
|
369
371
|
storage_path=self._notifier.storage.path,
|
|
370
372
|
)
|
|
371
|
-
self._dialog.
|
|
373
|
+
self._dialog.queue_system.setText(
|
|
374
|
+
f"Queue system:\n{model.queue_config.queue_system.formatted_name}"
|
|
375
|
+
)
|
|
372
376
|
self.experiment_started.emit(self._dialog)
|
|
373
377
|
self._simulation_done = False
|
|
374
378
|
self.run_button.setEnabled(self._simulation_done)
|
|
@@ -379,6 +383,7 @@ class ExperimentPanel(QWidget):
|
|
|
379
383
|
rerun_failed_realizations,
|
|
380
384
|
use_ipc_protocol=self.config.queue_config.queue_system
|
|
381
385
|
== QueueSystem.LOCAL,
|
|
386
|
+
prioritize_private_ip_address=self.config.prioritize_private_ip_address,
|
|
382
387
|
)
|
|
383
388
|
self._dialog.setup_event_monitoring(rerun_failed_realizations)
|
|
384
389
|
simulation_thread.start()
|
ert/gui/simulation/run_dialog.py
CHANGED
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from queue import SimpleQueue
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import cast
|
|
8
8
|
|
|
9
9
|
import humanize
|
|
10
10
|
from PyQt6.QtCore import QModelIndex, QSize, Qt, QThread, QTimer
|
|
@@ -32,7 +32,7 @@ from PyQt6.QtWidgets import (
|
|
|
32
32
|
from typing_extensions import override
|
|
33
33
|
|
|
34
34
|
from _ert.events import EnsembleEvaluationWarning
|
|
35
|
-
from ert.config import ErrorInfo,
|
|
35
|
+
from ert.config import ErrorInfo, WarningInfo
|
|
36
36
|
from ert.ensemble_evaluator import (
|
|
37
37
|
EndEvent,
|
|
38
38
|
FullSnapshotEvent,
|
|
@@ -666,20 +666,6 @@ class RunDialog(QFrame):
|
|
|
666
666
|
self.rerun_failed_realizations_experiment.emit()
|
|
667
667
|
self.set_show_warning_button_to_initial_state()
|
|
668
668
|
|
|
669
|
-
def set_queue_system_name(self, queue_system: QueueSystem) -> None:
|
|
670
|
-
match queue_system:
|
|
671
|
-
case QueueSystem.LSF:
|
|
672
|
-
formatted_queue_system = "LSF"
|
|
673
|
-
case QueueSystem.LOCAL:
|
|
674
|
-
formatted_queue_system = "Local"
|
|
675
|
-
case QueueSystem.TORQUE:
|
|
676
|
-
formatted_queue_system = "Torque/OpenPBS"
|
|
677
|
-
case QueueSystem.SLURM:
|
|
678
|
-
formatted_queue_system = "Slurm"
|
|
679
|
-
case default:
|
|
680
|
-
assert_never(default)
|
|
681
|
-
self.queue_system.setText(f"Queue system:\n{formatted_queue_system}")
|
|
682
|
-
|
|
683
669
|
@override
|
|
684
670
|
def hideEvent(self, event: QHideEvent | None) -> None:
|
|
685
671
|
for file_dialog in self.findChildren(FileDialog):
|
ert/gui/summarypanel.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
3
|
from typing import TYPE_CHECKING, Any
|
|
5
4
|
|
|
6
5
|
from PyQt6.QtCore import Qt
|
|
@@ -19,8 +18,6 @@ from ert.gui.ertwidgets import ErtSummary
|
|
|
19
18
|
if TYPE_CHECKING:
|
|
20
19
|
from ert.config import ErtConfig
|
|
21
20
|
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
21
|
|
|
25
22
|
class SummaryTemplate:
|
|
26
23
|
def __init__(self, title: str) -> None:
|
|
@@ -126,22 +123,6 @@ class SummaryPanel(QFrame):
|
|
|
126
123
|
|
|
127
124
|
self._layout.addLayout(layout)
|
|
128
125
|
|
|
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
|
-
|
|
145
126
|
@staticmethod
|
|
146
127
|
def _runlength_encode_list(strings: list[str]) -> list[tuple[str, int]]:
|
|
147
128
|
"""Runlength encode a list of strings.
|
|
@@ -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)
|