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
|
@@ -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:
|
|
@@ -175,9 +223,12 @@ class _EnsembleWidget(QWidget):
|
|
|
175
223
|
return
|
|
176
224
|
|
|
177
225
|
observation_key = selected.data(1, Qt.ItemDataRole.DisplayRole)
|
|
178
|
-
|
|
226
|
+
parent = selected.parent()
|
|
227
|
+
|
|
228
|
+
if not observation_key or not parent:
|
|
179
229
|
return
|
|
180
230
|
|
|
231
|
+
response_type = parent.data(0, Qt.ItemDataRole.UserRole)
|
|
181
232
|
observation_label = selected.data(0, Qt.ItemDataRole.DisplayRole)
|
|
182
233
|
assert self._ensemble is not None
|
|
183
234
|
observations_dict = self._ensemble.experiment.observations
|
|
@@ -187,17 +238,7 @@ class _EnsembleWidget(QWidget):
|
|
|
187
238
|
ax.set_title(observation_key)
|
|
188
239
|
ax.grid(True)
|
|
189
240
|
|
|
190
|
-
|
|
191
|
-
(
|
|
192
|
-
(response_type, df)
|
|
193
|
-
for response_type, df in observations_dict.items()
|
|
194
|
-
if observation_key in df["observation_key"]
|
|
195
|
-
),
|
|
196
|
-
(None, None),
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
assert response_type is not None
|
|
200
|
-
assert obs_for_type is not None
|
|
241
|
+
obs_for_type = observations_dict[response_type]
|
|
201
242
|
|
|
202
243
|
response_config = self._ensemble.experiment.response_configuration[
|
|
203
244
|
response_type
|
|
@@ -329,14 +370,23 @@ class _EnsembleWidget(QWidget):
|
|
|
329
370
|
.to_numpy()
|
|
330
371
|
):
|
|
331
372
|
match_list = self._observations_tree_widget.findItems(
|
|
332
|
-
response_key, Qt.MatchFlag.MatchExactly
|
|
373
|
+
response_key, Qt.MatchFlag.MatchExactly, 0
|
|
333
374
|
)
|
|
334
|
-
|
|
375
|
+
|
|
376
|
+
root = next(
|
|
377
|
+
(
|
|
378
|
+
item
|
|
379
|
+
for item in match_list
|
|
380
|
+
if item.data(0, Qt.ItemDataRole.UserRole) == response_type
|
|
381
|
+
),
|
|
382
|
+
None,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
if root is None:
|
|
335
386
|
root = QTreeWidgetItem(
|
|
336
387
|
self._observations_tree_widget, [response_key]
|
|
337
388
|
)
|
|
338
|
-
|
|
339
|
-
root = match_list[0]
|
|
389
|
+
root.setData(0, Qt.ItemDataRole.UserRole, response_type)
|
|
340
390
|
|
|
341
391
|
obs_ds = obs_ds_for_type.filter(
|
|
342
392
|
pl.col("observation_key").eq(obs_key)
|
|
@@ -352,16 +402,55 @@ class _EnsembleWidget(QWidget):
|
|
|
352
402
|
],
|
|
353
403
|
)
|
|
354
404
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
405
|
+
self._observations_tree_widget.sortItems(0, Qt.SortOrder.AscendingOrder)
|
|
406
|
+
|
|
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)
|
|
358
438
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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:
|
ert/gui/tools/plot/plot_api.py
CHANGED
|
@@ -18,8 +18,9 @@ from pandas.api.types import is_numeric_dtype
|
|
|
18
18
|
from pandas.errors import ParserError
|
|
19
19
|
from resfo_utilities import history_key
|
|
20
20
|
|
|
21
|
-
from ert.config import
|
|
21
|
+
from ert.config import ParameterConfig, ResponseMetadata
|
|
22
22
|
from ert.services import ErtServer
|
|
23
|
+
from ert.storage.local_experiment import _parameters_adapter as parameter_config_adapter
|
|
23
24
|
from ert.storage.realization_storage_state import RealizationStorageState
|
|
24
25
|
|
|
25
26
|
logger = logging.getLogger(__name__)
|
|
@@ -44,7 +45,7 @@ class PlotApiKeyDefinition(NamedTuple):
|
|
|
44
45
|
dimensionality: int
|
|
45
46
|
metadata: dict[Any, Any]
|
|
46
47
|
filter_on: dict[Any, Any] | None = None
|
|
47
|
-
|
|
48
|
+
parameter: ParameterConfig | None = None
|
|
48
49
|
response_metadata: ResponseMetadata | None = None
|
|
49
50
|
|
|
50
51
|
|
|
@@ -143,18 +144,21 @@ class PlotApi:
|
|
|
143
144
|
self._check_response(response)
|
|
144
145
|
|
|
145
146
|
for experiment in response.json():
|
|
146
|
-
for
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
147
|
+
for metadata in experiment["parameters"].values():
|
|
148
|
+
param_cfg = parameter_config_adapter.validate_python(metadata)
|
|
149
|
+
if group := metadata.get("group"):
|
|
150
|
+
param_key = f"{group}:{metadata['name']}"
|
|
151
|
+
else:
|
|
152
|
+
param_key = metadata["name"]
|
|
153
|
+
all_keys[param_key] = PlotApiKeyDefinition(
|
|
154
|
+
key=param_key,
|
|
155
|
+
index_type=None,
|
|
156
|
+
observations=False,
|
|
157
|
+
dimensionality=metadata["dimensionality"],
|
|
158
|
+
metadata={"data_origin": metadata["type"]},
|
|
159
|
+
parameter=param_cfg,
|
|
160
|
+
)
|
|
161
|
+
all_params[param_key] = all_keys[param_key]
|
|
158
162
|
|
|
159
163
|
return list(all_keys.values())
|
|
160
164
|
|
|
@@ -318,12 +322,17 @@ class PlotApi:
|
|
|
318
322
|
f"ensemble_name={ensemble.name}, e={e}"
|
|
319
323
|
) from e
|
|
320
324
|
|
|
325
|
+
key_index: list[int | float | pd.Timestamp]
|
|
321
326
|
for obs in observations:
|
|
322
327
|
try:
|
|
323
328
|
int(obs["x_axis"][0])
|
|
324
329
|
key_index = [int(v) for v in obs["x_axis"]]
|
|
325
330
|
except ValueError:
|
|
326
|
-
|
|
331
|
+
try:
|
|
332
|
+
float(obs["x_axis"][0])
|
|
333
|
+
key_index = [float(v) for v in obs["x_axis"]]
|
|
334
|
+
except ValueError:
|
|
335
|
+
key_index = [pd.Timestamp(v) for v in obs["x_axis"]]
|
|
327
336
|
|
|
328
337
|
observations_dfs.append(
|
|
329
338
|
pd.DataFrame(
|
|
@@ -25,7 +25,7 @@ from PyQt6.QtWidgets import (
|
|
|
25
25
|
)
|
|
26
26
|
from typing_extensions import override
|
|
27
27
|
|
|
28
|
-
from .plot_api import EnsembleObject
|
|
28
|
+
from .plot_api import EnsembleObject, PlotApiKeyDefinition
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
31
31
|
from .plottery import PlotContext
|
|
@@ -34,6 +34,7 @@ if TYPE_CHECKING:
|
|
|
34
34
|
from .plottery.plots.ensemble import EnsemblePlot
|
|
35
35
|
from .plottery.plots.gaussian_kde import GaussianKDEPlot
|
|
36
36
|
from .plottery.plots.histogram import HistogramPlot
|
|
37
|
+
from .plottery.plots.misfits import MisfitsPlot
|
|
37
38
|
from .plottery.plots.statistics import StatisticsPlot
|
|
38
39
|
from .plottery.plots.std_dev import StdDevPlot
|
|
39
40
|
|
|
@@ -122,6 +123,7 @@ class PlotWidget(QWidget):
|
|
|
122
123
|
"DistributionPlot",
|
|
123
124
|
"CrossEnsembleStatisticsPlot",
|
|
124
125
|
"StdDevPlot",
|
|
126
|
+
"MisfitsPlot",
|
|
125
127
|
],
|
|
126
128
|
parent: QWidget | None = None,
|
|
127
129
|
) -> None:
|
|
@@ -161,6 +163,7 @@ class PlotWidget(QWidget):
|
|
|
161
163
|
vbox.addSpacing(8)
|
|
162
164
|
self.setLayout(vbox)
|
|
163
165
|
|
|
166
|
+
self._negative_values_in_data = False
|
|
164
167
|
self._dirty = True
|
|
165
168
|
self._active = False
|
|
166
169
|
self.resetPlot()
|
|
@@ -173,7 +176,15 @@ class PlotWidget(QWidget):
|
|
|
173
176
|
self._figure.clear()
|
|
174
177
|
|
|
175
178
|
def _sync_log_checkbox(self) -> None:
|
|
176
|
-
if
|
|
179
|
+
if (
|
|
180
|
+
type(self._plotter).__name__
|
|
181
|
+
in {
|
|
182
|
+
"HistogramPlot",
|
|
183
|
+
"DistributionPlot",
|
|
184
|
+
"GaussianKDEPlot",
|
|
185
|
+
}
|
|
186
|
+
and self._negative_values_in_data is False
|
|
187
|
+
):
|
|
177
188
|
self._log_checkbox.setVisible(True)
|
|
178
189
|
else:
|
|
179
190
|
self._log_checkbox.setVisible(False)
|
|
@@ -188,11 +199,15 @@ class PlotWidget(QWidget):
|
|
|
188
199
|
ensemble_to_data_map: dict[EnsembleObject, pd.DataFrame],
|
|
189
200
|
observations: pd.DataFrame,
|
|
190
201
|
std_dev_images: dict[str, npt.NDArray[np.float32]],
|
|
202
|
+
key_def: PlotApiKeyDefinition | None = None,
|
|
191
203
|
) -> None:
|
|
192
204
|
self.resetPlot()
|
|
193
205
|
try:
|
|
206
|
+
self._sync_log_checkbox()
|
|
194
207
|
plot_context.log_scale = (
|
|
195
|
-
self._log_checkbox.isVisible()
|
|
208
|
+
self._log_checkbox.isVisible()
|
|
209
|
+
and self._log_checkbox.isChecked()
|
|
210
|
+
and self._negative_values_in_data is False
|
|
196
211
|
)
|
|
197
212
|
self._plotter.plot(
|
|
198
213
|
self._figure,
|
|
@@ -200,9 +215,9 @@ class PlotWidget(QWidget):
|
|
|
200
215
|
ensemble_to_data_map,
|
|
201
216
|
observations,
|
|
202
217
|
std_dev_images,
|
|
218
|
+
key_def,
|
|
203
219
|
)
|
|
204
220
|
self._canvas.draw()
|
|
205
|
-
self._sync_log_checkbox()
|
|
206
221
|
except Exception as e:
|
|
207
222
|
exc_type, _, exc_tb = sys.exc_info()
|
|
208
223
|
sys.stderr.write("-" * 80 + "\n")
|
|
@@ -22,6 +22,7 @@ from PyQt6.QtWidgets import (
|
|
|
22
22
|
QWidget,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
+
from ert.config.field import Field
|
|
25
26
|
from ert.dark_storage.common import get_storage_api_version
|
|
26
27
|
from ert.gui.ertwidgets import CopyButton, showWaitCursorWhileWaiting
|
|
27
28
|
from ert.services._base_service import ServerBootFail
|
|
@@ -39,6 +40,7 @@ from .plottery.plots import (
|
|
|
39
40
|
EnsemblePlot,
|
|
40
41
|
GaussianKDEPlot,
|
|
41
42
|
HistogramPlot,
|
|
43
|
+
MisfitsPlot,
|
|
42
44
|
StatisticsPlot,
|
|
43
45
|
StdDevPlot,
|
|
44
46
|
)
|
|
@@ -50,10 +52,11 @@ ENSEMBLE = "Ensemble"
|
|
|
50
52
|
HISTOGRAM = "Histogram"
|
|
51
53
|
STATISTICS = "Statistics"
|
|
52
54
|
STD_DEV = "Std Dev"
|
|
55
|
+
MISFITS = "Misfits"
|
|
53
56
|
|
|
54
57
|
RESPONSE_DEFAULT = 0
|
|
55
|
-
GEN_KW_DEFAULT =
|
|
56
|
-
STD_DEV_DEFAULT =
|
|
58
|
+
GEN_KW_DEFAULT = 3
|
|
59
|
+
STD_DEV_DEFAULT = 7
|
|
57
60
|
|
|
58
61
|
|
|
59
62
|
logger = logging.getLogger(__name__)
|
|
@@ -188,6 +191,7 @@ class PlotWindow(QMainWindow):
|
|
|
188
191
|
|
|
189
192
|
self.addPlotWidget(ENSEMBLE, EnsemblePlot())
|
|
190
193
|
self.addPlotWidget(STATISTICS, StatisticsPlot())
|
|
194
|
+
self.addPlotWidget(MISFITS, MisfitsPlot())
|
|
191
195
|
self.addPlotWidget(HISTOGRAM, HistogramPlot())
|
|
192
196
|
self.addPlotWidget(GAUSSIAN_KDE, GaussianKDEPlot())
|
|
193
197
|
self.addPlotWidget(DISTRIBUTION, DistributionPlot())
|
|
@@ -270,16 +274,25 @@ class PlotWindow(QMainWindow):
|
|
|
270
274
|
filter_on=key_def.filter_on,
|
|
271
275
|
)
|
|
272
276
|
elif (
|
|
273
|
-
key_def.
|
|
274
|
-
and
|
|
277
|
+
key_def.parameter is not None
|
|
278
|
+
and key_def.parameter.type == "gen_kw"
|
|
275
279
|
):
|
|
276
280
|
ensemble_to_data_map[ensemble] = self._api.data_for_parameter(
|
|
277
281
|
ensemble_id=ensemble.id,
|
|
278
|
-
parameter_key=key_def.
|
|
282
|
+
parameter_key=key_def.parameter.name,
|
|
279
283
|
)
|
|
280
284
|
except BaseException as e:
|
|
281
285
|
handle_exception(e)
|
|
282
286
|
|
|
287
|
+
negative_values_in_data = False
|
|
288
|
+
if key_def.parameter is not None and key_def.parameter.type == "gen_kw":
|
|
289
|
+
for data in ensemble_to_data_map.values():
|
|
290
|
+
data = data.T
|
|
291
|
+
if data.le(0).any().any():
|
|
292
|
+
negative_values_in_data = True
|
|
293
|
+
break
|
|
294
|
+
|
|
295
|
+
plot_widget._negative_values_in_data = negative_values_in_data
|
|
283
296
|
observations = None
|
|
284
297
|
if key_def.observations and selected_ensembles:
|
|
285
298
|
try:
|
|
@@ -290,10 +303,10 @@ class PlotWindow(QMainWindow):
|
|
|
290
303
|
handle_exception(e)
|
|
291
304
|
|
|
292
305
|
std_dev_images: dict[str, npt.NDArray[np.float32]] = {}
|
|
293
|
-
if "FIELD" in key_def.metadata["data_origin"]:
|
|
294
|
-
plot_widget.showLayerWidget.emit(True)
|
|
295
306
|
|
|
296
|
-
|
|
307
|
+
if isinstance(key_def.parameter, Field):
|
|
308
|
+
plot_widget.showLayerWidget.emit(True)
|
|
309
|
+
layers = key_def.parameter.ertbox_params.nz
|
|
297
310
|
plot_widget.updateLayerWidget.emit(layers)
|
|
298
311
|
|
|
299
312
|
if layer is None:
|
|
@@ -357,7 +370,11 @@ class PlotWindow(QMainWindow):
|
|
|
357
370
|
self._updateCustomizer(plot_widget, self._preferred_ensemble_x_axis_format)
|
|
358
371
|
|
|
359
372
|
plot_widget.updatePlot(
|
|
360
|
-
plot_context,
|
|
373
|
+
plot_context,
|
|
374
|
+
ensemble_to_data_map,
|
|
375
|
+
observations,
|
|
376
|
+
std_dev_images,
|
|
377
|
+
key_def,
|
|
361
378
|
)
|
|
362
379
|
|
|
363
380
|
def _updateCustomizer(
|
|
@@ -386,15 +403,14 @@ class PlotWindow(QMainWindow):
|
|
|
386
403
|
def addPlotWidget(
|
|
387
404
|
self,
|
|
388
405
|
name: str,
|
|
389
|
-
plotter:
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
),
|
|
406
|
+
plotter: EnsemblePlot
|
|
407
|
+
| StatisticsPlot
|
|
408
|
+
| HistogramPlot
|
|
409
|
+
| GaussianKDEPlot
|
|
410
|
+
| DistributionPlot
|
|
411
|
+
| CrossEnsembleStatisticsPlot
|
|
412
|
+
| StdDevPlot
|
|
413
|
+
| MisfitsPlot,
|
|
398
414
|
enabled: bool = True,
|
|
399
415
|
) -> None:
|
|
400
416
|
plot_widget = PlotWidget(name, plotter)
|
|
@@ -433,6 +449,7 @@ class PlotWindow(QMainWindow):
|
|
|
433
449
|
widget
|
|
434
450
|
for widget in self._plot_widgets
|
|
435
451
|
if widget._plotter.dimensionality == key_def.dimensionality
|
|
452
|
+
and (key_def.observations or not widget._plotter.requires_observations)
|
|
436
453
|
]
|
|
437
454
|
|
|
438
455
|
# Enabling/disabling tab triggers the
|
|
@@ -3,6 +3,7 @@ from .distribution import DistributionPlot
|
|
|
3
3
|
from .ensemble import EnsemblePlot
|
|
4
4
|
from .gaussian_kde import GaussianKDEPlot
|
|
5
5
|
from .histogram import HistogramPlot
|
|
6
|
+
from .misfits import MisfitsPlot
|
|
6
7
|
from .statistics import StatisticsPlot
|
|
7
8
|
from .std_dev import StdDevPlot
|
|
8
9
|
|
|
@@ -12,6 +13,7 @@ __all__ = [
|
|
|
12
13
|
"EnsemblePlot",
|
|
13
14
|
"GaussianKDEPlot",
|
|
14
15
|
"HistogramPlot",
|
|
16
|
+
"MisfitsPlot",
|
|
15
17
|
"StatisticsPlot",
|
|
16
18
|
"StdDevPlot",
|
|
17
19
|
]
|
|
@@ -8,7 +8,7 @@ from matplotlib.lines import Line2D
|
|
|
8
8
|
from matplotlib.patches import Rectangle
|
|
9
9
|
from typing_extensions import TypedDict
|
|
10
10
|
|
|
11
|
-
from ert.gui.tools.plot.plot_api import EnsembleObject
|
|
11
|
+
from ert.gui.tools.plot.plot_api import EnsembleObject, PlotApiKeyDefinition
|
|
12
12
|
|
|
13
13
|
from .plot_tools import ConditionalAxisFormatter, PlotTools
|
|
14
14
|
|
|
@@ -37,6 +37,7 @@ class CcsData(TypedDict):
|
|
|
37
37
|
class CrossEnsembleStatisticsPlot:
|
|
38
38
|
def __init__(self) -> None:
|
|
39
39
|
self.dimensionality = 1
|
|
40
|
+
self.requires_observations = False
|
|
40
41
|
|
|
41
42
|
@staticmethod
|
|
42
43
|
def plot(
|
|
@@ -45,6 +46,7 @@ class CrossEnsembleStatisticsPlot:
|
|
|
45
46
|
ensemble_to_data_map: dict[EnsembleObject, pd.DataFrame],
|
|
46
47
|
observation_data: pd.DataFrame,
|
|
47
48
|
std_dev_images: dict[str, npt.NDArray[np.float32]],
|
|
49
|
+
key_def: PlotApiKeyDefinition | None = None,
|
|
48
50
|
) -> None:
|
|
49
51
|
plotCrossEnsembleStatistics(
|
|
50
52
|
figure, plot_context, ensemble_to_data_map, observation_data
|
|
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
|
-
from ert.gui.tools.plot.plot_api import EnsembleObject
|
|
8
|
+
from ert.gui.tools.plot.plot_api import EnsembleObject, PlotApiKeyDefinition
|
|
9
9
|
|
|
10
10
|
from .plot_tools import ConditionalAxisFormatter, PlotTools
|
|
11
11
|
|
|
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
class DistributionPlot:
|
|
21
21
|
def __init__(self) -> None:
|
|
22
22
|
self.dimensionality = 1
|
|
23
|
+
self.requires_observations = False
|
|
23
24
|
|
|
24
25
|
@staticmethod
|
|
25
26
|
def plot(
|
|
@@ -28,6 +29,7 @@ class DistributionPlot:
|
|
|
28
29
|
ensemble_to_data_map: dict[EnsembleObject, pd.DataFrame],
|
|
29
30
|
observation_data: pd.DataFrame,
|
|
30
31
|
std_dev_images: dict[str, npt.NDArray[np.float32]],
|
|
32
|
+
key_def: PlotApiKeyDefinition | None = None,
|
|
31
33
|
) -> None:
|
|
32
34
|
plotDistribution(figure, plot_context, ensemble_to_data_map, observation_data)
|
|
33
35
|
|
|
@@ -80,6 +82,9 @@ def plotDistribution(
|
|
|
80
82
|
)
|
|
81
83
|
config.setLegendEnabled(False)
|
|
82
84
|
|
|
85
|
+
if plot_context.log_scale:
|
|
86
|
+
axes.set_yscale("log")
|
|
87
|
+
|
|
83
88
|
PlotTools.finalizePlot(
|
|
84
89
|
plot_context, figure, axes, default_x_label="Ensemble", default_y_label="Value"
|
|
85
90
|
)
|
|
@@ -15,13 +15,14 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from matplotlib.axes import Axes
|
|
16
16
|
from matplotlib.figure import Figure
|
|
17
17
|
|
|
18
|
-
from ert.gui.tools.plot.plot_api import EnsembleObject
|
|
18
|
+
from ert.gui.tools.plot.plot_api import EnsembleObject, PlotApiKeyDefinition
|
|
19
19
|
from ert.gui.tools.plot.plottery import PlotConfig, PlotContext
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class EnsemblePlot:
|
|
23
23
|
def __init__(self) -> None:
|
|
24
24
|
self.dimensionality = 2
|
|
25
|
+
self.requires_observations = False
|
|
25
26
|
|
|
26
27
|
def plot(
|
|
27
28
|
self,
|
|
@@ -30,6 +31,7 @@ class EnsemblePlot:
|
|
|
30
31
|
ensemble_to_data_map: dict[EnsembleObject, pd.DataFrame],
|
|
31
32
|
observation_data: pd.DataFrame,
|
|
32
33
|
std_dev_images: dict[str, npt.NDArray[np.float32]],
|
|
34
|
+
key_def: PlotApiKeyDefinition | None = None,
|
|
33
35
|
) -> None:
|
|
34
36
|
config = plot_context.plotConfig()
|
|
35
37
|
axes = figure.add_subplot(111)
|
|
@@ -6,7 +6,7 @@ import numpy as np
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from scipy.stats import gaussian_kde
|
|
8
8
|
|
|
9
|
-
from ert.gui.tools.plot.plot_api import EnsembleObject
|
|
9
|
+
from ert.gui.tools.plot.plot_api import EnsembleObject, PlotApiKeyDefinition
|
|
10
10
|
|
|
11
11
|
from .plot_tools import ConditionalAxisFormatter, PlotTools
|
|
12
12
|
|
|
@@ -21,6 +21,7 @@ if TYPE_CHECKING:
|
|
|
21
21
|
class GaussianKDEPlot:
|
|
22
22
|
def __init__(self) -> None:
|
|
23
23
|
self.dimensionality = 1
|
|
24
|
+
self.requires_observations = False
|
|
24
25
|
|
|
25
26
|
@staticmethod
|
|
26
27
|
def plot(
|
|
@@ -29,10 +30,16 @@ class GaussianKDEPlot:
|
|
|
29
30
|
ensemble_to_data_map: dict[EnsembleObject, pd.DataFrame],
|
|
30
31
|
observation_data: pd.DataFrame,
|
|
31
32
|
std_dev_images: dict[str, npt.NDArray[np.float32]],
|
|
33
|
+
key_def: PlotApiKeyDefinition | None = None,
|
|
32
34
|
) -> None:
|
|
33
35
|
plotGaussianKDE(figure, plot_context, ensemble_to_data_map, observation_data)
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
def _array_is_constant(data: pd.Series | pd.DataFrame) -> bool:
|
|
39
|
+
array = data.to_numpy()
|
|
40
|
+
return array.shape[0] == 0 or (array[0] == array).all()
|
|
41
|
+
|
|
42
|
+
|
|
36
43
|
def plotGaussianKDE(
|
|
37
44
|
figure: Figure,
|
|
38
45
|
plot_context: PlotContext,
|
|
@@ -55,11 +62,14 @@ def plotGaussianKDE(
|
|
|
55
62
|
if data.empty:
|
|
56
63
|
continue
|
|
57
64
|
data = data[0]
|
|
58
|
-
if data
|
|
65
|
+
if not _array_is_constant(data):
|
|
59
66
|
_plotGaussianKDE(
|
|
60
67
|
axes, config, data, f"{ensemble.experiment_name} : {ensemble.name}"
|
|
61
68
|
)
|
|
62
69
|
|
|
70
|
+
if plot_context.log_scale:
|
|
71
|
+
axes.set_xscale("log")
|
|
72
|
+
|
|
63
73
|
PlotTools.finalizePlot(
|
|
64
74
|
plot_context, figure, axes, default_x_label="Value", default_y_label="Density"
|
|
65
75
|
)
|