ert 18.0.9__py3-none-any.whl → 19.0.0__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/cli/main.py +7 -3
- ert/config/__init__.py +3 -4
- ert/config/_create_observation_dataframes.py +85 -59
- ert/config/_get_num_cpu.py +1 -1
- ert/config/_observations.py +106 -31
- ert/config/distribution.py +1 -1
- ert/config/ensemble_config.py +3 -3
- ert/config/ert_config.py +50 -0
- 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 +3 -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 +12 -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 +14 -4
- 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/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 -3
- ert/gui/simulation/run_dialog.py +2 -16
- ert/gui/tools/manage_experiments/export_dialog.py +136 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +133 -28
- ert/gui/tools/plot/plot_api.py +24 -15
- ert/gui/tools/plot/plot_widget.py +19 -4
- ert/gui/tools/plot/plot_window.py +35 -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 +9 -0
- 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 +25 -8
- ert/storage/local_experiment.py +2 -2
- ert/storage/local_storage.py +45 -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/storage/migration/to22.py +18 -0
- ert/storage/migration/to23.py +49 -0
- ert/workflow_runner.py +2 -1
- {ert-18.0.9.dist-info → ert-19.0.0.dist-info}/METADATA +1 -1
- {ert-18.0.9.dist-info → ert-19.0.0.dist-info}/RECORD +111 -109
- {ert-18.0.9.dist-info → ert-19.0.0.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.0.dist-info}/entry_points.txt +0 -0
- {ert-18.0.9.dist-info → ert-19.0.0.dist-info}/licenses/COPYING +0 -0
- {ert-18.0.9.dist-info → ert-19.0.0.dist-info}/top_level.txt +0 -0
ert/data/_measured_data.py
CHANGED
|
@@ -141,13 +141,14 @@ class MeasuredData:
|
|
|
141
141
|
|
|
142
142
|
# Pandas differentiates vs int and str keys.
|
|
143
143
|
# Legacy-wise we use int keys for realizations
|
|
144
|
-
pddf
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
pddf = (
|
|
145
|
+
pddf.rename(
|
|
146
|
+
columns={str(k): int(k) for k in active_realizations},
|
|
147
|
+
)
|
|
148
|
+
.set_index(["observation_key", "key_index"])
|
|
149
|
+
.transpose()
|
|
147
150
|
)
|
|
148
151
|
|
|
149
|
-
pddf = pddf.set_index(["observation_key", "key_index"]).transpose()
|
|
150
|
-
|
|
151
152
|
return pddf
|
|
152
153
|
|
|
153
154
|
|
ert/ensemble_evaluator/config.py
CHANGED
|
@@ -27,6 +27,7 @@ class EvaluatorServerConfig:
|
|
|
27
27
|
use_token: bool = True,
|
|
28
28
|
host: str | None = None,
|
|
29
29
|
use_ipc_protocol: bool = True,
|
|
30
|
+
prioritize_private_ip_address: bool = False,
|
|
30
31
|
) -> None:
|
|
31
32
|
self.host: str | None = host
|
|
32
33
|
self.router_port: int | None = None
|
|
@@ -50,7 +51,7 @@ class EvaluatorServerConfig:
|
|
|
50
51
|
if use_ipc_protocol:
|
|
51
52
|
self.uri = f"ipc:///tmp/socket-{uuid.uuid4().hex[:8]}"
|
|
52
53
|
elif self.host is None:
|
|
53
|
-
self.host = get_ip_address()
|
|
54
|
+
self.host = get_ip_address(prioritize_private_ip_address)
|
|
54
55
|
|
|
55
56
|
if use_token:
|
|
56
57
|
self.server_public_key, self.server_secret_key = zmq.curve_keypair()
|
ert/field_utils/field_utils.py
CHANGED
|
@@ -238,7 +238,7 @@ def read_field(
|
|
|
238
238
|
ext = path.suffix
|
|
239
239
|
raise ValueError(f'Could not read {field_path}. Unrecognized suffix "{ext}"')
|
|
240
240
|
|
|
241
|
-
return np.ma.MaskedArray(data=values, fill_value=np.nan)
|
|
241
|
+
return np.ma.MaskedArray(data=values, fill_value=np.nan)
|
|
242
242
|
|
|
243
243
|
|
|
244
244
|
def save_field(
|
ert/field_utils/roff_io.py
CHANGED
|
@@ -23,7 +23,7 @@ def export_roff(
|
|
|
23
23
|
binary: bool,
|
|
24
24
|
) -> None:
|
|
25
25
|
dimensions = data.shape
|
|
26
|
-
data = np.flip(data, -1).ravel()
|
|
26
|
+
data = np.flip(data, -1).ravel()
|
|
27
27
|
data = data.astype(np.float32).filled(RMS_UNDEFINED_FLOAT) # type: ignore
|
|
28
28
|
if not np.isfinite(data).all():
|
|
29
29
|
raise ValueError(
|
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,
|
|
@@ -33,8 +33,8 @@ from ert.storage import (
|
|
|
33
33
|
ErtStorageException,
|
|
34
34
|
LocalStorage,
|
|
35
35
|
local_storage_set_ert_config,
|
|
36
|
-
open_storage,
|
|
37
36
|
)
|
|
37
|
+
from ert.storage.local_storage import _LOCAL_STORAGE_VERSION, _storage_version
|
|
38
38
|
from ert.trace import trace, tracer
|
|
39
39
|
|
|
40
40
|
from .ertwidgets import Suggestor
|
|
@@ -164,9 +164,31 @@ def _start_initial_gui_window(
|
|
|
164
164
|
if ert_config is not None:
|
|
165
165
|
try:
|
|
166
166
|
if LocalStorage.check_migration_needed(Path(ert_config.ens_path)):
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
storage_version = _storage_version(Path(ert_config.ens_path))
|
|
168
|
+
current_version = _LOCAL_STORAGE_VERSION
|
|
169
|
+
|
|
170
|
+
migrate_dialog = QMessageBox.warning(
|
|
171
|
+
None,
|
|
172
|
+
f"Migrate storage to version {current_version}?",
|
|
173
|
+
f"Ert storage is version {storage_version} and needs to be migrated"
|
|
174
|
+
f" to version {current_version} to be compatible with the current"
|
|
175
|
+
f" version of Ert\n\n"
|
|
176
|
+
f"After migration, this storage can only be opened with current or"
|
|
177
|
+
f" later versions of Ert\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(
|
|
@@ -368,7 +374,9 @@ class ExperimentPanel(QWidget):
|
|
|
368
374
|
run_path=Path(self.config.runpath_config.runpath_format_string),
|
|
369
375
|
storage_path=self._notifier.storage.path,
|
|
370
376
|
)
|
|
371
|
-
self._dialog.
|
|
377
|
+
self._dialog.queue_system.setText(
|
|
378
|
+
f"Queue system:\n{model.queue_config.queue_system.formatted_name}"
|
|
379
|
+
)
|
|
372
380
|
self.experiment_started.emit(self._dialog)
|
|
373
381
|
self._simulation_done = False
|
|
374
382
|
self.run_button.setEnabled(self._simulation_done)
|
|
@@ -379,6 +387,7 @@ class ExperimentPanel(QWidget):
|
|
|
379
387
|
rerun_failed_realizations,
|
|
380
388
|
use_ipc_protocol=self.config.queue_config.queue_system
|
|
381
389
|
== QueueSystem.LOCAL,
|
|
390
|
+
prioritize_private_ip_address=self.config.prioritize_private_ip_address,
|
|
382
391
|
)
|
|
383
392
|
self._dialog.setup_event_monitoring(rerun_failed_realizations)
|
|
384
393
|
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):
|
|
@@ -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)
|