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.
Files changed (117) hide show
  1. _ert/forward_model_runner/client.py +6 -2
  2. ert/__main__.py +20 -6
  3. ert/analysis/_es_update.py +6 -19
  4. ert/cli/main.py +7 -3
  5. ert/config/__init__.py +3 -4
  6. ert/config/_create_observation_dataframes.py +57 -8
  7. ert/config/_get_num_cpu.py +1 -1
  8. ert/config/_observations.py +77 -1
  9. ert/config/distribution.py +1 -1
  10. ert/config/ensemble_config.py +3 -3
  11. ert/config/ert_config.py +50 -8
  12. ert/config/{ext_param_config.py → everest_control.py} +8 -12
  13. ert/config/everest_response.py +3 -5
  14. ert/config/field.py +76 -14
  15. ert/config/forward_model_step.py +12 -9
  16. ert/config/gen_data_config.py +3 -4
  17. ert/config/gen_kw_config.py +2 -12
  18. ert/config/parameter_config.py +1 -16
  19. ert/config/parsing/_option_dict.py +10 -2
  20. ert/config/parsing/config_keywords.py +1 -0
  21. ert/config/parsing/config_schema.py +8 -0
  22. ert/config/parsing/config_schema_deprecations.py +14 -3
  23. ert/config/parsing/config_schema_item.py +12 -3
  24. ert/config/parsing/context_values.py +3 -3
  25. ert/config/parsing/file_context_token.py +1 -1
  26. ert/config/parsing/observations_parser.py +6 -2
  27. ert/config/parsing/queue_system.py +9 -0
  28. ert/config/queue_config.py +0 -1
  29. ert/config/response_config.py +0 -1
  30. ert/config/rft_config.py +78 -33
  31. ert/config/summary_config.py +1 -2
  32. ert/config/surface_config.py +59 -16
  33. ert/dark_storage/common.py +1 -1
  34. ert/dark_storage/compute/misfits.py +4 -1
  35. ert/dark_storage/endpoints/compute/misfits.py +4 -2
  36. ert/dark_storage/endpoints/experiment_server.py +12 -9
  37. ert/dark_storage/endpoints/experiments.py +2 -2
  38. ert/dark_storage/endpoints/observations.py +4 -2
  39. ert/dark_storage/endpoints/parameters.py +2 -18
  40. ert/dark_storage/endpoints/responses.py +10 -5
  41. ert/dark_storage/json_schema/experiment.py +1 -1
  42. ert/data/_measured_data.py +6 -5
  43. ert/ensemble_evaluator/config.py +2 -1
  44. ert/field_utils/field_utils.py +1 -1
  45. ert/field_utils/grdecl_io.py +9 -26
  46. ert/field_utils/roff_io.py +1 -1
  47. ert/gui/__init__.py +5 -2
  48. ert/gui/ertnotifier.py +1 -1
  49. ert/gui/ertwidgets/pathchooser.py +0 -3
  50. ert/gui/ertwidgets/suggestor/suggestor.py +63 -30
  51. ert/gui/main.py +27 -5
  52. ert/gui/main_window.py +0 -5
  53. ert/gui/simulation/experiment_panel.py +12 -7
  54. ert/gui/simulation/run_dialog.py +2 -16
  55. ert/gui/summarypanel.py +0 -19
  56. ert/gui/tools/manage_experiments/export_dialog.py +136 -0
  57. ert/gui/tools/manage_experiments/storage_info_widget.py +110 -9
  58. ert/gui/tools/plot/plot_api.py +24 -15
  59. ert/gui/tools/plot/plot_widget.py +10 -2
  60. ert/gui/tools/plot/plot_window.py +26 -18
  61. ert/gui/tools/plot/plottery/plots/__init__.py +2 -0
  62. ert/gui/tools/plot/plottery/plots/cesp.py +3 -1
  63. ert/gui/tools/plot/plottery/plots/distribution.py +6 -1
  64. ert/gui/tools/plot/plottery/plots/ensemble.py +3 -1
  65. ert/gui/tools/plot/plottery/plots/gaussian_kde.py +12 -2
  66. ert/gui/tools/plot/plottery/plots/histogram.py +3 -1
  67. ert/gui/tools/plot/plottery/plots/misfits.py +436 -0
  68. ert/gui/tools/plot/plottery/plots/observations.py +18 -4
  69. ert/gui/tools/plot/plottery/plots/statistics.py +3 -1
  70. ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
  71. ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
  72. ert/plugins/plugin_manager.py +4 -0
  73. ert/resources/forward_models/run_reservoirsimulator.py +8 -3
  74. ert/run_models/_create_run_path.py +3 -3
  75. ert/run_models/everest_run_model.py +13 -11
  76. ert/run_models/initial_ensemble_run_model.py +2 -2
  77. ert/run_models/run_model.py +30 -1
  78. ert/services/_base_service.py +6 -5
  79. ert/services/ert_server.py +4 -4
  80. ert/shared/_doc_utils/__init__.py +4 -2
  81. ert/shared/net_utils.py +43 -18
  82. ert/shared/version.py +3 -3
  83. ert/storage/__init__.py +2 -0
  84. ert/storage/local_ensemble.py +13 -7
  85. ert/storage/local_experiment.py +2 -2
  86. ert/storage/local_storage.py +41 -25
  87. ert/storage/migration/to11.py +1 -1
  88. ert/storage/migration/to18.py +0 -1
  89. ert/storage/migration/to19.py +34 -0
  90. ert/storage/migration/to20.py +23 -0
  91. ert/storage/migration/to21.py +25 -0
  92. ert/workflow_runner.py +2 -1
  93. {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/METADATA +1 -1
  94. {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/RECORD +112 -112
  95. {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/WHEEL +1 -1
  96. everest/bin/everlint_script.py +0 -2
  97. everest/bin/utils.py +2 -1
  98. everest/bin/visualization_script.py +4 -11
  99. everest/config/control_config.py +4 -4
  100. everest/config/control_variable_config.py +2 -2
  101. everest/config/everest_config.py +9 -0
  102. everest/config/utils.py +2 -2
  103. everest/config/validation_utils.py +7 -1
  104. everest/config_file_loader.py +0 -2
  105. everest/detached/client.py +3 -3
  106. everest/everest_storage.py +0 -2
  107. everest/gui/everest_client.py +2 -2
  108. everest/optimizer/everest2ropt.py +4 -4
  109. everest/optimizer/opt_model_transforms.py +2 -2
  110. ert/config/violations.py +0 -0
  111. ert/gui/tools/export/__init__.py +0 -3
  112. ert/gui/tools/export/export_panel.py +0 -83
  113. ert/gui/tools/export/export_tool.py +0 -69
  114. ert/gui/tools/export/exporter.py +0 -36
  115. {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/entry_points.txt +0 -0
  116. {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/licenses/COPYING +0 -0
  117. {ert-18.0.9.dist-info → ert-19.0.0rc0.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,17 @@ 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 (
12
14
  QFrame,
13
15
  QHBoxLayout,
14
16
  QLabel,
17
+ QPushButton,
15
18
  QStackedLayout,
19
+ QTableWidget,
20
+ QTableWidgetItem,
16
21
  QTabWidget,
17
22
  QTextEdit,
18
23
  QTreeWidget,
@@ -21,8 +26,11 @@ from PyQt6.QtWidgets import (
21
26
  QWidget,
22
27
  )
23
28
 
29
+ from ert import LibresFacade
24
30
  from ert.storage import Ensemble, Experiment, RealizationStorageState
25
31
 
32
+ from .export_dialog import ExportDialog
33
+
26
34
 
27
35
  class _WidgetType(IntEnum):
28
36
  EMPTY_WIDGET = 0
@@ -42,6 +50,8 @@ class _EnsembleWidgetTabs(IntEnum):
42
50
  ENSEMBLE_TAB = 0
43
51
  STATE_TAB = 1
44
52
  OBSERVATIONS_TAB = 2
53
+ PARAMETERS_TAB = 3
54
+ MISFIT_TAB = 4
45
55
 
46
56
 
47
57
  class _ExperimentWidget(QWidget):
@@ -126,12 +136,15 @@ class _EnsembleWidget(QWidget):
126
136
 
127
137
  info_frame.setLayout(info_layout)
128
138
 
139
+ state_frame = QFrame()
140
+ state_layout = QHBoxLayout()
129
141
  self._state_text_edit = QTextEdit()
130
142
  self._state_text_edit.setReadOnly(True)
131
143
  self._state_text_edit.setObjectName("ensemble_state_text")
144
+ state_layout.addWidget(self._state_text_edit)
145
+ state_frame.setLayout(state_layout)
132
146
 
133
147
  observations_frame = QFrame()
134
-
135
148
  self._observations_tree_widget = QTreeWidget(self)
136
149
  self._observations_tree_widget.currentItemChanged.connect(
137
150
  self._currentItemChanged
@@ -151,15 +164,35 @@ class _EnsembleWidget(QWidget):
151
164
  observations_layout.addWidget(self._canvas)
152
165
  observations_frame.setLayout(observations_layout)
153
166
 
167
+ self._parameters_table = QTableWidget()
168
+ self._export_params_button = QPushButton("Export...")
169
+ self._export_params_button.clicked.connect(self.onClickExportParameters)
170
+
171
+ parameters_frame = self.create_export_frame(
172
+ self._parameters_table, self._export_params_button
173
+ )
174
+
175
+ self._misfit_table = QTableWidget()
176
+ self._export_misfit_button = QPushButton("Export...")
177
+ self._export_misfit_button.clicked.connect(self.onClickExportMisfit)
178
+
179
+ misfit_frame = self.create_export_frame(
180
+ self._misfit_table, self._export_misfit_button
181
+ )
182
+
154
183
  self._tab_widget = QTabWidget()
155
184
  self._tab_widget.insertTab(
156
185
  _EnsembleWidgetTabs.ENSEMBLE_TAB, info_frame, "Ensemble"
157
186
  )
187
+ self._tab_widget.insertTab(_EnsembleWidgetTabs.STATE_TAB, state_frame, "State")
158
188
  self._tab_widget.insertTab(
159
- _EnsembleWidgetTabs.STATE_TAB, self._state_text_edit, "State"
189
+ _EnsembleWidgetTabs.OBSERVATIONS_TAB, observations_frame, "Observations"
160
190
  )
161
191
  self._tab_widget.insertTab(
162
- _EnsembleWidgetTabs.OBSERVATIONS_TAB, observations_frame, "Observations"
192
+ _EnsembleWidgetTabs.PARAMETERS_TAB, parameters_frame, "Parameters"
193
+ )
194
+ self._tab_widget.insertTab(
195
+ _EnsembleWidgetTabs.MISFIT_TAB, misfit_frame, "Misfit"
163
196
  )
164
197
  self._tab_widget.currentChanged.connect(self._currentTabChanged)
165
198
 
@@ -168,6 +201,17 @@ class _EnsembleWidget(QWidget):
168
201
 
169
202
  self.setLayout(layout)
170
203
 
204
+ def create_export_frame(self, table: QTableWidget, button: QPushButton) -> QFrame:
205
+ export_frame = QFrame()
206
+ export_layout = QVBoxLayout()
207
+ vertical_header = table.verticalHeader()
208
+ assert vertical_header is not None
209
+ vertical_header.setVisible(False)
210
+ export_layout.addWidget(table)
211
+ export_layout.addWidget(button)
212
+ export_frame.setLayout(export_layout)
213
+ return export_frame
214
+
171
215
  def _currentItemChanged(
172
216
  self, selected: QTreeWidgetItem, _: QTreeWidgetItem
173
217
  ) -> None:
@@ -356,12 +400,53 @@ class _EnsembleWidget(QWidget):
356
400
  0, Qt.SortOrder.AscendingOrder
357
401
  )
358
402
 
359
- for i in range(self._observations_tree_widget.topLevelItemCount()):
360
- item = self._observations_tree_widget.topLevelItem(i)
361
- assert item is not None
362
- if item.childCount() > 0:
363
- self._observations_tree_widget.setCurrentItem(item.child(0))
364
- break
403
+ for i in range(self._observations_tree_widget.topLevelItemCount()):
404
+ item = self._observations_tree_widget.topLevelItem(i)
405
+ assert item is not None
406
+ if item.childCount() > 0:
407
+ self._observations_tree_widget.setCurrentItem(item.child(0))
408
+ break
409
+
410
+ elif index in {
411
+ _EnsembleWidgetTabs.PARAMETERS_TAB,
412
+ _EnsembleWidgetTabs.MISFIT_TAB,
413
+ }:
414
+ assert self._ensemble is not None
415
+
416
+ df: pl.DataFrame = pl.DataFrame()
417
+ with contextlib.suppress(Exception):
418
+ if index == _EnsembleWidgetTabs.PARAMETERS_TAB:
419
+ df = self._ensemble.load_scalar_keys(transformed=True)
420
+ else:
421
+ df = self.get_misfit_df()
422
+
423
+ table = (
424
+ self._parameters_table
425
+ if index == _EnsembleWidgetTabs.PARAMETERS_TAB
426
+ else self._misfit_table
427
+ )
428
+
429
+ table.setUpdatesEnabled(False)
430
+ table.setSortingEnabled(False)
431
+ table.setRowCount(df.height)
432
+ table.setColumnCount(df.width)
433
+ table.setHorizontalHeaderLabels(df.columns)
434
+
435
+ rows = df.rows()
436
+ for r, row in enumerate(rows):
437
+ for c, v in enumerate(row):
438
+ table.setItem(r, c, QTableWidgetItem("" if v is None else str(v)))
439
+
440
+ table.resizeColumnsToContents()
441
+ table.setUpdatesEnabled(True)
442
+
443
+ def get_misfit_df(self) -> DataFrame:
444
+ assert self._ensemble is not None
445
+ df = LibresFacade.load_all_misfit_data(self._ensemble)
446
+ realization_column = pl.Series(df.index)
447
+ df = pl.from_pandas(df)
448
+ df.insert_column(0, realization_column)
449
+ return df
365
450
 
366
451
  @Slot(Ensemble)
367
452
  def setEnsemble(self, ensemble: Ensemble) -> None:
@@ -372,6 +457,22 @@ class _EnsembleWidget(QWidget):
372
457
 
373
458
  self._tab_widget.setCurrentIndex(0)
374
459
 
460
+ @Slot()
461
+ def onClickExportMisfit(self) -> None:
462
+ assert self._ensemble is not None
463
+ misfit_df = self.get_misfit_df()
464
+ export_dialog = ExportDialog(misfit_df, "Export misfit", parent=self)
465
+ export_dialog.show()
466
+
467
+ @Slot()
468
+ def onClickExportParameters(self) -> None:
469
+ assert self._ensemble is not None
470
+ parameters_df = self._ensemble.load_scalar_keys(transformed=True)
471
+ export_dialog = ExportDialog(
472
+ parameters_df, window_title="Export parameters", parent=self
473
+ )
474
+ export_dialog.show()
475
+
375
476
 
376
477
  class _RealizationWidget(QWidget):
377
478
  def __init__(self) -> None:
@@ -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 ParameterMetadata, ResponseMetadata
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
- parameter_metadata: ParameterMetadata | None = None
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 param_metadatas in experiment["parameters"].values():
147
- for metadata in param_metadatas:
148
- param_key = metadata["key"]
149
- all_keys[param_key] = PlotApiKeyDefinition(
150
- key=param_key,
151
- index_type=None,
152
- observations=False,
153
- dimensionality=metadata["dimensionality"],
154
- metadata=metadata["userdata"],
155
- parameter_metadata=ParameterMetadata(**metadata),
156
- )
157
- all_params[param_key] = all_keys[param_key]
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
- key_index = [pd.Timestamp(v) for v in obs["x_axis"]]
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:
@@ -173,7 +175,11 @@ class PlotWidget(QWidget):
173
175
  self._figure.clear()
174
176
 
175
177
  def _sync_log_checkbox(self) -> None:
176
- if type(self._plotter).__name__ == "HistogramPlot":
178
+ if type(self._plotter).__name__ in {
179
+ "HistogramPlot",
180
+ "DistributionPlot",
181
+ "GaussianKDEPlot",
182
+ }:
177
183
  self._log_checkbox.setVisible(True)
178
184
  else:
179
185
  self._log_checkbox.setVisible(False)
@@ -188,6 +194,7 @@ class PlotWidget(QWidget):
188
194
  ensemble_to_data_map: dict[EnsembleObject, pd.DataFrame],
189
195
  observations: pd.DataFrame,
190
196
  std_dev_images: dict[str, npt.NDArray[np.float32]],
197
+ key_def: PlotApiKeyDefinition | None = None,
191
198
  ) -> None:
192
199
  self.resetPlot()
193
200
  try:
@@ -200,6 +207,7 @@ class PlotWidget(QWidget):
200
207
  ensemble_to_data_map,
201
208
  observations,
202
209
  std_dev_images,
210
+ key_def,
203
211
  )
204
212
  self._canvas.draw()
205
213
  self._sync_log_checkbox()
@@ -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 = 2
56
- STD_DEV_DEFAULT = 6
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,12 +274,12 @@ class PlotWindow(QMainWindow):
270
274
  filter_on=key_def.filter_on,
271
275
  )
272
276
  elif (
273
- key_def.parameter_metadata is not None
274
- and "GEN_KW" in key_def.metadata["data_origin"]
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.parameter_metadata.key,
282
+ parameter_key=key_def.parameter.name,
279
283
  )
280
284
  except BaseException as e:
281
285
  handle_exception(e)
@@ -290,10 +294,10 @@ class PlotWindow(QMainWindow):
290
294
  handle_exception(e)
291
295
 
292
296
  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
297
 
296
- layers = key_def.metadata["ertbox_params"]["nz"]
298
+ if isinstance(key_def.parameter, Field):
299
+ plot_widget.showLayerWidget.emit(True)
300
+ layers = key_def.parameter.ertbox_params.nz
297
301
  plot_widget.updateLayerWidget.emit(layers)
298
302
 
299
303
  if layer is None:
@@ -357,7 +361,11 @@ class PlotWindow(QMainWindow):
357
361
  self._updateCustomizer(plot_widget, self._preferred_ensemble_x_axis_format)
358
362
 
359
363
  plot_widget.updatePlot(
360
- plot_context, ensemble_to_data_map, observations, std_dev_images
364
+ plot_context,
365
+ ensemble_to_data_map,
366
+ observations,
367
+ std_dev_images,
368
+ key_def,
361
369
  )
362
370
 
363
371
  def _updateCustomizer(
@@ -386,15 +394,14 @@ class PlotWindow(QMainWindow):
386
394
  def addPlotWidget(
387
395
  self,
388
396
  name: str,
389
- plotter: (
390
- EnsemblePlot
391
- | StatisticsPlot
392
- | HistogramPlot
393
- | GaussianKDEPlot
394
- | DistributionPlot
395
- | CrossEnsembleStatisticsPlot
396
- | StdDevPlot
397
- ),
397
+ plotter: EnsemblePlot
398
+ | StatisticsPlot
399
+ | HistogramPlot
400
+ | GaussianKDEPlot
401
+ | DistributionPlot
402
+ | CrossEnsembleStatisticsPlot
403
+ | StdDevPlot
404
+ | MisfitsPlot,
398
405
  enabled: bool = True,
399
406
  ) -> None:
400
407
  plot_widget = PlotWidget(name, plotter)
@@ -433,6 +440,7 @@ class PlotWindow(QMainWindow):
433
440
  widget
434
441
  for widget in self._plot_widgets
435
442
  if widget._plotter.dimensionality == key_def.dimensionality
443
+ and (key_def.observations or not widget._plotter.requires_observations)
436
444
  ]
437
445
 
438
446
  # 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.nunique() > 1:
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
  )
@@ -8,7 +8,7 @@ import numpy as np
8
8
  import pandas as pd
9
9
  from matplotlib.patches import Rectangle
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
  from ert.shared.status.utils import convert_to_numeric
13
13
 
14
14
  from .plot_tools import ConditionalAxisFormatter, PlotTools
@@ -24,6 +24,7 @@ if TYPE_CHECKING:
24
24
  class HistogramPlot:
25
25
  def __init__(self) -> None:
26
26
  self.dimensionality = 1
27
+ self.requires_observations = False
27
28
 
28
29
  @staticmethod
29
30
  def plot(
@@ -32,6 +33,7 @@ class HistogramPlot:
32
33
  ensemble_to_data_map: dict[EnsembleObject, pd.DataFrame],
33
34
  observation_data: pd.DataFrame,
34
35
  std_dev_images: dict[str, npt.NDArray[np.float32]],
36
+ key_def: PlotApiKeyDefinition | None = None,
35
37
  ) -> None:
36
38
  plotHistogram(figure, plot_context, ensemble_to_data_map)
37
39