ert 16.0.9__py3-none-any.whl → 19.0.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. _ert/events.py +19 -2
  2. _ert/forward_model_runner/client.py +6 -2
  3. _ert/forward_model_runner/fm_dispatch.py +9 -6
  4. _ert/forward_model_runner/reporting/event.py +1 -0
  5. _ert/forward_model_runner/runner.py +1 -2
  6. _ert/utils.py +12 -0
  7. ert/__main__.py +58 -38
  8. ert/analysis/_enif_update.py +8 -4
  9. ert/analysis/_es_update.py +19 -6
  10. ert/analysis/_update_commons.py +16 -6
  11. ert/base_model_context.py +1 -1
  12. ert/cli/main.py +17 -12
  13. ert/cli/monitor.py +7 -0
  14. ert/config/__init__.py +17 -6
  15. ert/config/_create_observation_dataframes.py +118 -21
  16. ert/config/_get_num_cpu.py +1 -1
  17. ert/config/_observations.py +91 -2
  18. ert/config/_read_summary.py +74 -328
  19. ert/config/design_matrix.py +62 -23
  20. ert/config/distribution.py +1 -1
  21. ert/config/ensemble_config.py +9 -17
  22. ert/config/ert_config.py +155 -58
  23. ert/config/everest_control.py +234 -0
  24. ert/config/{everest_constraints_config.py → everest_response.py} +27 -15
  25. ert/config/field.py +99 -90
  26. ert/config/forward_model_step.py +122 -17
  27. ert/config/gen_data_config.py +5 -10
  28. ert/config/gen_kw_config.py +11 -41
  29. ert/config/known_response_types.py +14 -0
  30. ert/config/parameter_config.py +1 -33
  31. ert/config/parsing/_option_dict.py +10 -2
  32. ert/config/parsing/config_errors.py +1 -1
  33. ert/config/parsing/config_keywords.py +2 -1
  34. ert/config/parsing/config_schema.py +23 -11
  35. ert/config/parsing/config_schema_deprecations.py +3 -3
  36. ert/config/parsing/config_schema_item.py +26 -11
  37. ert/config/parsing/context_values.py +3 -3
  38. ert/config/parsing/file_context_token.py +1 -1
  39. ert/config/parsing/observations_parser.py +6 -2
  40. ert/config/parsing/queue_system.py +9 -0
  41. ert/config/parsing/schema_item_type.py +1 -0
  42. ert/config/queue_config.py +42 -50
  43. ert/config/response_config.py +0 -8
  44. ert/config/rft_config.py +275 -0
  45. ert/config/summary_config.py +3 -8
  46. ert/config/surface_config.py +73 -26
  47. ert/config/workflow_fixtures.py +2 -1
  48. ert/config/workflow_job.py +135 -54
  49. ert/dark_storage/client/__init__.py +2 -2
  50. ert/dark_storage/client/_session.py +4 -4
  51. ert/dark_storage/client/client.py +2 -2
  52. ert/dark_storage/common.py +12 -3
  53. ert/dark_storage/compute/misfits.py +11 -7
  54. ert/dark_storage/endpoints/compute/misfits.py +6 -4
  55. ert/dark_storage/endpoints/ensembles.py +4 -0
  56. ert/dark_storage/endpoints/experiment_server.py +30 -24
  57. ert/dark_storage/endpoints/experiments.py +2 -2
  58. ert/dark_storage/endpoints/observations.py +8 -6
  59. ert/dark_storage/endpoints/parameters.py +4 -12
  60. ert/dark_storage/endpoints/responses.py +24 -5
  61. ert/dark_storage/json_schema/ensemble.py +3 -0
  62. ert/dark_storage/json_schema/experiment.py +1 -1
  63. ert/data/_measured_data.py +6 -5
  64. ert/ensemble_evaluator/__init__.py +8 -1
  65. ert/ensemble_evaluator/config.py +2 -1
  66. ert/ensemble_evaluator/evaluator.py +81 -29
  67. ert/ensemble_evaluator/event.py +6 -0
  68. ert/ensemble_evaluator/snapshot.py +3 -1
  69. ert/ensemble_evaluator/state.py +1 -0
  70. ert/field_utils/__init__.py +8 -0
  71. ert/field_utils/field_utils.py +228 -15
  72. ert/field_utils/grdecl_io.py +1 -1
  73. ert/field_utils/roff_io.py +1 -1
  74. ert/gui/__init__.py +5 -2
  75. ert/gui/ertnotifier.py +1 -1
  76. ert/gui/ertwidgets/__init__.py +23 -16
  77. ert/gui/ertwidgets/analysismoduleedit.py +2 -2
  78. ert/gui/ertwidgets/checklist.py +1 -1
  79. ert/gui/ertwidgets/closabledialog.py +2 -0
  80. ert/gui/ertwidgets/copyablelabel.py +2 -0
  81. ert/gui/ertwidgets/create_experiment_dialog.py +3 -1
  82. ert/gui/ertwidgets/ensembleselector.py +2 -2
  83. ert/gui/ertwidgets/listeditbox.py +2 -0
  84. ert/gui/ertwidgets/models/__init__.py +2 -0
  85. ert/gui/ertwidgets/models/activerealizationsmodel.py +5 -1
  86. ert/gui/ertwidgets/models/path_model.py +1 -1
  87. ert/gui/ertwidgets/models/targetensemblemodel.py +5 -1
  88. ert/gui/ertwidgets/models/text_model.py +4 -1
  89. ert/gui/ertwidgets/pathchooser.py +0 -3
  90. ert/gui/ertwidgets/searchbox.py +17 -4
  91. ert/gui/ertwidgets/stringbox.py +2 -0
  92. ert/gui/{suggestor → ertwidgets/suggestor}/_suggestor_message.py +13 -4
  93. ert/gui/{suggestor → ertwidgets/suggestor}/suggestor.py +63 -30
  94. ert/gui/main.py +41 -13
  95. ert/gui/main_window.py +3 -7
  96. ert/gui/model/fm_step_list.py +3 -0
  97. ert/gui/model/real_list.py +1 -0
  98. ert/gui/model/snapshot.py +1 -0
  99. ert/gui/simulation/combobox_with_description.py +3 -0
  100. ert/gui/simulation/ensemble_experiment_panel.py +8 -2
  101. ert/gui/simulation/ensemble_information_filter_panel.py +7 -2
  102. ert/gui/simulation/ensemble_smoother_panel.py +8 -2
  103. ert/gui/simulation/evaluate_ensemble_panel.py +17 -7
  104. ert/gui/simulation/experiment_panel.py +18 -6
  105. ert/gui/simulation/manual_update_panel.py +35 -10
  106. ert/gui/simulation/multiple_data_assimilation_panel.py +13 -9
  107. ert/gui/simulation/run_dialog.py +47 -20
  108. ert/gui/simulation/single_test_run_panel.py +6 -3
  109. ert/gui/simulation/view/progress_widget.py +2 -0
  110. ert/gui/simulation/view/realization.py +5 -1
  111. ert/gui/simulation/view/update.py +2 -0
  112. ert/gui/summarypanel.py +20 -1
  113. ert/gui/tools/event_viewer/panel.py +3 -4
  114. ert/gui/tools/event_viewer/tool.py +2 -0
  115. ert/gui/tools/load_results/load_results_panel.py +1 -1
  116. ert/gui/tools/load_results/load_results_tool.py +2 -0
  117. ert/gui/tools/manage_experiments/export_dialog.py +136 -0
  118. ert/gui/tools/manage_experiments/manage_experiments_panel.py +2 -0
  119. ert/gui/tools/manage_experiments/storage_info_widget.py +121 -16
  120. ert/gui/tools/manage_experiments/storage_widget.py +4 -3
  121. ert/gui/tools/plot/customize/color_chooser.py +5 -2
  122. ert/gui/tools/plot/customize/customize_plot_dialog.py +2 -0
  123. ert/gui/tools/plot/customize/default_customization_view.py +4 -0
  124. ert/gui/tools/plot/customize/limits_customization_view.py +3 -0
  125. ert/gui/tools/plot/customize/statistics_customization_view.py +3 -0
  126. ert/gui/tools/plot/customize/style_chooser.py +2 -0
  127. ert/gui/tools/plot/customize/style_customization_view.py +3 -0
  128. ert/gui/tools/plot/data_type_keys_widget.py +2 -0
  129. ert/gui/tools/plot/data_type_proxy_model.py +3 -0
  130. ert/gui/tools/plot/plot_api.py +50 -28
  131. ert/gui/tools/plot/plot_ensemble_selection_widget.py +17 -10
  132. ert/gui/tools/plot/plot_widget.py +15 -2
  133. ert/gui/tools/plot/plot_window.py +41 -19
  134. ert/gui/tools/plot/plottery/plot_config.py +2 -0
  135. ert/gui/tools/plot/plottery/plot_context.py +14 -0
  136. ert/gui/tools/plot/plottery/plots/__init__.py +2 -0
  137. ert/gui/tools/plot/plottery/plots/cesp.py +3 -1
  138. ert/gui/tools/plot/plottery/plots/distribution.py +6 -1
  139. ert/gui/tools/plot/plottery/plots/ensemble.py +13 -5
  140. ert/gui/tools/plot/plottery/plots/gaussian_kde.py +12 -2
  141. ert/gui/tools/plot/plottery/plots/histogram.py +3 -1
  142. ert/gui/tools/plot/plottery/plots/misfits.py +436 -0
  143. ert/gui/tools/plot/plottery/plots/observations.py +18 -4
  144. ert/gui/tools/plot/plottery/plots/statistics.py +62 -20
  145. ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
  146. ert/gui/tools/plot/widgets/clearable_line_edit.py +9 -0
  147. ert/gui/tools/plot/widgets/filter_popup.py +2 -0
  148. ert/gui/tools/plot/widgets/filterable_kw_list_model.py +3 -0
  149. ert/gui/tools/plugins/plugin.py +1 -1
  150. ert/gui/tools/plugins/plugins_tool.py +2 -0
  151. ert/gui/tools/plugins/process_job_dialog.py +3 -0
  152. ert/gui/tools/workflows/workflow_dialog.py +2 -0
  153. ert/gui/tools/workflows/workflows_tool.py +2 -0
  154. ert/libres_facade.py +5 -7
  155. ert/logging/__init__.py +4 -1
  156. ert/mode_definitions.py +2 -0
  157. ert/plugins/__init__.py +4 -6
  158. ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
  159. ert/plugins/hook_implementations/workflows/gen_data_rft_export.py +10 -2
  160. ert/plugins/hook_specifications/__init__.py +0 -10
  161. ert/plugins/hook_specifications/jobs.py +0 -9
  162. ert/plugins/plugin_manager.py +53 -124
  163. ert/resources/forward_models/run_reservoirsimulator.py +8 -4
  164. ert/resources/forward_models/template_render.py +10 -10
  165. ert/resources/shell_scripts/delete_directory.py +2 -2
  166. ert/run_models/__init__.py +24 -6
  167. ert/run_models/_create_run_path.py +133 -38
  168. ert/run_models/ensemble_experiment.py +10 -4
  169. ert/run_models/ensemble_information_filter.py +8 -1
  170. ert/run_models/ensemble_smoother.py +9 -3
  171. ert/run_models/evaluate_ensemble.py +8 -6
  172. ert/run_models/event.py +7 -3
  173. ert/run_models/everest_run_model.py +337 -113
  174. ert/run_models/initial_ensemble_run_model.py +25 -24
  175. ert/run_models/manual_update.py +6 -3
  176. ert/run_models/manual_update_enif.py +37 -0
  177. ert/run_models/model_factory.py +78 -18
  178. ert/run_models/multiple_data_assimilation.py +22 -11
  179. ert/run_models/run_model.py +72 -73
  180. ert/run_models/single_test_run.py +7 -4
  181. ert/run_models/update_run_model.py +4 -2
  182. ert/runpaths.py +5 -6
  183. ert/sample_prior.py +9 -4
  184. ert/scheduler/__init__.py +10 -5
  185. ert/scheduler/driver.py +40 -0
  186. ert/scheduler/event.py +3 -1
  187. ert/scheduler/job.py +23 -13
  188. ert/scheduler/lsf_driver.py +15 -5
  189. ert/scheduler/openpbs_driver.py +10 -4
  190. ert/scheduler/scheduler.py +5 -0
  191. ert/scheduler/slurm_driver.py +20 -5
  192. ert/services/__init__.py +2 -2
  193. ert/services/_base_service.py +37 -20
  194. ert/services/_storage_main.py +20 -18
  195. ert/services/ert_server.py +317 -0
  196. ert/shared/_doc_utils/__init__.py +4 -2
  197. ert/shared/_doc_utils/ert_jobs.py +1 -4
  198. ert/shared/net_utils.py +43 -18
  199. ert/shared/storage/connection.py +3 -3
  200. ert/shared/version.py +3 -3
  201. ert/storage/__init__.py +14 -1
  202. ert/storage/local_ensemble.py +44 -13
  203. ert/storage/local_experiment.py +54 -34
  204. ert/storage/local_storage.py +90 -58
  205. ert/storage/migration/to10.py +3 -2
  206. ert/storage/migration/to11.py +9 -10
  207. ert/storage/migration/to12.py +19 -20
  208. ert/storage/migration/to13.py +28 -27
  209. ert/storage/migration/to14.py +3 -3
  210. ert/storage/migration/to15.py +25 -0
  211. ert/storage/migration/to16.py +38 -0
  212. ert/storage/migration/to17.py +42 -0
  213. ert/storage/migration/to18.py +11 -0
  214. ert/storage/migration/to19.py +34 -0
  215. ert/storage/migration/to20.py +23 -0
  216. ert/storage/migration/to21.py +25 -0
  217. ert/storage/migration/to6.py +3 -2
  218. ert/storage/migration/to7.py +12 -13
  219. ert/storage/migration/to8.py +9 -11
  220. ert/storage/migration/to9.py +5 -4
  221. ert/storage/realization_storage_state.py +7 -7
  222. ert/substitutions.py +12 -28
  223. ert/validation/active_range.py +7 -7
  224. ert/validation/ensemble_realizations_argument.py +4 -2
  225. ert/validation/rangestring.py +16 -16
  226. ert/workflow_runner.py +6 -3
  227. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/METADATA +21 -15
  228. ert-19.0.0rc2.dist-info/RECORD +524 -0
  229. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/WHEEL +1 -1
  230. everest/api/everest_data_api.py +14 -1
  231. everest/assets/everest_logo.svg +406 -0
  232. everest/bin/config_branch_script.py +30 -14
  233. everest/bin/everconfigdump_script.py +2 -10
  234. everest/bin/everest_script.py +53 -33
  235. everest/bin/everlint_script.py +3 -5
  236. everest/bin/kill_script.py +7 -5
  237. everest/bin/main.py +11 -24
  238. everest/bin/monitor_script.py +64 -35
  239. everest/bin/utils.py +58 -43
  240. everest/bin/visualization_script.py +23 -13
  241. everest/config/__init__.py +4 -1
  242. everest/config/control_config.py +81 -6
  243. everest/config/control_variable_config.py +4 -3
  244. everest/config/everest_config.py +102 -79
  245. everest/config/forward_model_config.py +5 -3
  246. everest/config/install_data_config.py +7 -5
  247. everest/config/install_job_config.py +45 -3
  248. everest/config/install_template_config.py +3 -3
  249. everest/config/optimization_config.py +19 -6
  250. everest/config/output_constraint_config.py +8 -2
  251. everest/config/server_config.py +6 -55
  252. everest/config/simulator_config.py +62 -17
  253. everest/config/utils.py +25 -105
  254. everest/config/validation_utils.py +34 -15
  255. everest/config_file_loader.py +30 -21
  256. everest/detached/__init__.py +0 -6
  257. everest/detached/client.py +7 -52
  258. everest/detached/everserver.py +19 -45
  259. everest/everest_storage.py +24 -40
  260. everest/gui/everest_client.py +2 -3
  261. everest/gui/main_window.py +2 -2
  262. everest/optimizer/everest2ropt.py +68 -42
  263. everest/optimizer/opt_model_transforms.py +15 -20
  264. everest/optimizer/utils.py +0 -29
  265. everest/plugins/hook_specs.py +0 -24
  266. everest/strings.py +1 -6
  267. everest/util/__init__.py +3 -1
  268. ert/config/everest_objective_config.py +0 -95
  269. ert/config/ext_param_config.py +0 -107
  270. ert/gui/tools/export/__init__.py +0 -3
  271. ert/gui/tools/export/export_panel.py +0 -83
  272. ert/gui/tools/export/export_tool.py +0 -67
  273. ert/gui/tools/export/exporter.py +0 -36
  274. ert/plugins/hook_specifications/ecl_config.py +0 -29
  275. ert/services/storage_service.py +0 -127
  276. ert/summary_key_type.py +0 -234
  277. ert-16.0.9.dist-info/RECORD +0 -521
  278. everest/bin/everexport_script.py +0 -53
  279. everest/config/sampler_config.py +0 -103
  280. everest/simulator/__init__.py +0 -88
  281. everest/simulator/everest_to_ert.py +0 -252
  282. /ert/gui/{suggestor → ertwidgets/suggestor}/__init__.py +0 -0
  283. /ert/gui/{suggestor → ertwidgets/suggestor}/_colors.py +0 -0
  284. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/entry_points.txt +0 -0
  285. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/licenses/COPYING +0 -0
  286. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ from PyQt6.QtCore import pyqtSignal as Signal
7
7
  from PyQt6.QtCore import pyqtSlot as Slot
8
8
  from PyQt6.QtGui import QCloseEvent
9
9
  from PyQt6.QtWidgets import QDialog, QPlainTextEdit, QVBoxLayout
10
+ from typing_extensions import override
10
11
 
11
12
  from ert.gui.tools.search_bar import SearchBar
12
13
 
@@ -32,10 +33,7 @@ class _GUILogHandler(logging.Handler):
32
33
 
33
34
 
34
35
  class GUILogHandler(_Signaler):
35
- """
36
- Log handler which will emit a qt signal every time a
37
- log is emitted
38
- """
36
+ """Log handler which will emit a qt signal every time a log is emitted."""
39
37
 
40
38
  def __init__(self) -> None:
41
39
  super().__init__()
@@ -78,6 +76,7 @@ class EventViewerPanel(QDialog):
78
76
  def val_changed(self, value: str) -> None:
79
77
  self.text_box.appendPlainText(value)
80
78
 
79
+ @override
81
80
  def closeEvent(self, event: QCloseEvent | None) -> None:
82
81
  logger.info("Gui utility: EventViewer tool was used")
83
82
  super().closeEvent(event)
@@ -1,6 +1,7 @@
1
1
  from PyQt6.QtCore import QObject
2
2
  from PyQt6.QtCore import pyqtSlot as Slot
3
3
  from PyQt6.QtGui import QIcon
4
+ from typing_extensions import override
4
5
 
5
6
  from ert.gui.tools import Tool
6
7
 
@@ -21,6 +22,7 @@ class EventViewerTool(Tool, QObject):
21
22
  self.logging_window.setWindowTitle(f"Event viewer: {config_filename}")
22
23
  self.setEnabled(True)
23
24
 
25
+ @override
24
26
  def trigger(self) -> None:
25
27
  self.logging_window.show()
26
28
 
@@ -13,10 +13,10 @@ from ert.gui.ertwidgets import (
13
13
  EnsembleSelector,
14
14
  QApplication,
15
15
  StringBox,
16
+ Suggestor,
16
17
  TextBox,
17
18
  TextModel,
18
19
  )
19
- from ert.gui.suggestor import Suggestor
20
20
  from ert.run_models.run_model import captured_logs
21
21
  from ert.storage.local_ensemble import load_parameters_and_responses_from_runpath
22
22
  from ert.validation import RangeStringArgument, StringDefinition
@@ -2,6 +2,7 @@ import logging
2
2
  from typing import Any
3
3
 
4
4
  from PyQt6.QtGui import QIcon
5
+ from typing_extensions import override
5
6
 
6
7
  from ert.config import ErtConfig
7
8
  from ert.gui.ertnotifier import ErtNotifier
@@ -23,6 +24,7 @@ class LoadResultsTool(Tool):
23
24
  self._notifier = notifier
24
25
  self._config = config
25
26
 
27
+ @override
26
28
  def trigger(self) -> None:
27
29
  if self._import_widget is None:
28
30
  self._import_widget = LoadResultsPanel(self._config, self._notifier)
@@ -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)
@@ -12,6 +12,7 @@ from PyQt6.QtWidgets import (
12
12
  QVBoxLayout,
13
13
  QWidget,
14
14
  )
15
+ from typing_extensions import override
15
16
 
16
17
  from ert.gui.ertwidgets import (
17
18
  CheckList,
@@ -153,6 +154,7 @@ class ManageExperimentsPanel(QTabWidget):
153
154
 
154
155
  self.addTab(panel, "Initialize from scratch")
155
156
 
157
+ @override
156
158
  def eventFilter(self, a0: QObject | None, a1: QEvent | None) -> bool:
157
159
  if a1 is not None and a1.type() == QEvent.Type.Close:
158
160
  self.notifier.emitErtChange()
@@ -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.STATE_TAB, self._state_text_edit, "State"
193
+ _EnsembleWidgetTabs.OBSERVATIONS_TAB, observations_frame, "Observations"
160
194
  )
161
195
  self._tab_widget.insertTab(
162
- _EnsembleWidgetTabs.OBSERVATIONS_TAB, observations_frame, "Observations"
196
+ _EnsembleWidgetTabs.PARAMETERS_TAB, parameters_frame, "Parameters"
197
+ )
198
+ self._tab_widget.insertTab(
199
+ _EnsembleWidgetTabs.MISFIT_TAB, misfit_frame, "Misfit"
163
200
  )
164
201
  self._tab_widget.currentChanged.connect(self._currentTabChanged)
165
202
 
@@ -168,6 +205,17 @@ class _EnsembleWidget(QWidget):
168
205
 
169
206
  self.setLayout(layout)
170
207
 
208
+ def create_export_frame(self, table: QTableWidget, button: QPushButton) -> QFrame:
209
+ export_frame = QFrame()
210
+ export_layout = QVBoxLayout()
211
+ vertical_header = table.verticalHeader()
212
+ assert vertical_header is not None
213
+ vertical_header.setVisible(False)
214
+ export_layout.addWidget(table)
215
+ export_layout.addWidget(button)
216
+ export_frame.setLayout(export_layout)
217
+ return export_frame
218
+
171
219
  def _currentItemChanged(
172
220
  self, selected: QTreeWidgetItem, _: QTreeWidgetItem
173
221
  ) -> None:
@@ -189,9 +237,9 @@ class _EnsembleWidget(QWidget):
189
237
 
190
238
  response_type, obs_for_type = next(
191
239
  (
192
- (response_type, _df)
193
- for response_type, _df in observations_dict.items()
194
- if observation_key in _df["observation_key"]
240
+ (response_type, df)
241
+ for response_type, df in observations_dict.items()
242
+ if observation_key in df["observation_key"]
195
243
  ),
196
244
  (None, None),
197
245
  )
@@ -356,12 +404,53 @@ class _EnsembleWidget(QWidget):
356
404
  0, Qt.SortOrder.AscendingOrder
357
405
  )
358
406
 
359
- 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
407
+ for i in range(self._observations_tree_widget.topLevelItemCount()):
408
+ item = self._observations_tree_widget.topLevelItem(i)
409
+ assert item is not None
410
+ if item.childCount() > 0:
411
+ self._observations_tree_widget.setCurrentItem(item.child(0))
412
+ break
413
+
414
+ elif index in {
415
+ _EnsembleWidgetTabs.PARAMETERS_TAB,
416
+ _EnsembleWidgetTabs.MISFIT_TAB,
417
+ }:
418
+ assert self._ensemble is not None
419
+
420
+ df: pl.DataFrame = pl.DataFrame()
421
+ with contextlib.suppress(Exception):
422
+ if index == _EnsembleWidgetTabs.PARAMETERS_TAB:
423
+ df = self._ensemble.load_scalar_keys(transformed=True)
424
+ else:
425
+ df = self.get_misfit_df()
426
+
427
+ table = (
428
+ self._parameters_table
429
+ if index == _EnsembleWidgetTabs.PARAMETERS_TAB
430
+ else self._misfit_table
431
+ )
432
+
433
+ table.setUpdatesEnabled(False)
434
+ table.setSortingEnabled(False)
435
+ table.setRowCount(df.height)
436
+ table.setColumnCount(df.width)
437
+ table.setHorizontalHeaderLabels(df.columns)
438
+
439
+ rows = df.rows()
440
+ for r, row in enumerate(rows):
441
+ for c, v in enumerate(row):
442
+ table.setItem(r, c, QTableWidgetItem("" if v is None else str(v)))
443
+
444
+ table.resizeColumnsToContents()
445
+ table.setUpdatesEnabled(True)
446
+
447
+ def get_misfit_df(self) -> DataFrame:
448
+ assert self._ensemble is not None
449
+ df = LibresFacade.load_all_misfit_data(self._ensemble)
450
+ realization_column = pl.Series(df.index)
451
+ df = pl.from_pandas(df)
452
+ df.insert_column(0, realization_column)
453
+ return df
365
454
 
366
455
  @Slot(Ensemble)
367
456
  def setEnsemble(self, ensemble: Ensemble) -> None:
@@ -372,6 +461,22 @@ class _EnsembleWidget(QWidget):
372
461
 
373
462
  self._tab_widget.setCurrentIndex(0)
374
463
 
464
+ @Slot()
465
+ def onClickExportMisfit(self) -> None:
466
+ assert self._ensemble is not None
467
+ misfit_df = self.get_misfit_df()
468
+ export_dialog = ExportDialog(misfit_df, "Export misfit", parent=self)
469
+ export_dialog.show()
470
+
471
+ @Slot()
472
+ def onClickExportParameters(self) -> None:
473
+ assert self._ensemble is not None
474
+ parameters_df = self._ensemble.load_scalar_keys(transformed=True)
475
+ export_dialog = ExportDialog(
476
+ parameters_df, window_title="Export parameters", parent=self
477
+ )
478
+ export_dialog.show()
479
+
375
480
 
376
481
  class _RealizationWidget(QWidget):
377
482
  def __init__(self) -> None:
@@ -418,14 +523,14 @@ class _RealizationWidget(QWidget):
418
523
  )
419
524
 
420
525
  html = "<table>"
421
- for name, _response_state in ensemble.get_response_state(realization).items():
422
- html += f"<tr><td>{name} - {_response_state.name}</td></tr>"
526
+ for name, response_state in ensemble.get_response_state(realization).items():
527
+ html += f"<tr><td>{name} - {response_state.name}</td></tr>"
423
528
  html += "</table>"
424
529
  self._response_text_edit.setHtml(html)
425
530
 
426
531
  html = "<table>"
427
- for name, _param_state in ensemble.get_parameter_state(realization).items():
428
- html += f"<tr><td>{name} - {_param_state.name}</td></tr>"
532
+ for name, param_state in ensemble.get_parameter_state(realization).items():
533
+ html += f"<tr><td>{name} - {param_state.name}</td></tr>"
429
534
  html += "</table>"
430
535
  self._parameter_text_edit.setHtml(html)
431
536
 
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from collections.abc import Callable
3
+ from typing import cast
3
4
 
4
5
  from PyQt6.QtCore import (
5
6
  QAbstractItemModel,
@@ -23,8 +24,7 @@ from PyQt6.QtWidgets import (
23
24
 
24
25
  from ert.config import ErrorInfo, ErtConfig
25
26
  from ert.gui.ertnotifier import ErtNotifier
26
- from ert.gui.ertwidgets.create_experiment_dialog import CreateExperimentDialog
27
- from ert.gui.suggestor import Suggestor
27
+ from ert.gui.ertwidgets import CreateExperimentDialog, Suggestor
28
28
  from ert.storage import Ensemble, Experiment
29
29
 
30
30
  from .storage_model import (
@@ -149,7 +149,8 @@ class StorageWidget(QWidget):
149
149
  self.setLayout(layout)
150
150
 
151
151
  def _currentChanged(self, selected: QModelIndex, previous: QModelIndex) -> None:
152
- idx = self._tree_view.model().mapToSource(selected) # type: ignore
152
+ model = cast(_SortingProxyModel, self._tree_view.model())
153
+ idx = model.mapToSource(selected)
153
154
  cls = idx.internalPointer()
154
155
 
155
156
  if isinstance(cls, EnsembleModel):
@@ -3,14 +3,15 @@ from PyQt6.QtCore import pyqtSignal as Signal
3
3
  from PyQt6.QtCore import pyqtSlot as Slot
4
4
  from PyQt6.QtGui import QColor, QMouseEvent, QPainter, QPaintEvent
5
5
  from PyQt6.QtWidgets import QColorDialog, QFrame
6
+ from typing_extensions import override
6
7
 
7
8
 
8
9
  class ColorBox(QFrame):
10
+ """A widget that shows a colored box"""
11
+
9
12
  colorChanged = Signal(QColor)
10
13
  mouseRelease = Signal()
11
14
 
12
- """A widget that shows a colored box"""
13
-
14
15
  def __init__(self, size: int = 15) -> None:
15
16
  QFrame.__init__(self)
16
17
  self.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken)
@@ -49,6 +50,7 @@ class ColorBox(QFrame):
49
50
  self._color = new_color
50
51
  self.update()
51
52
 
53
+ @override
52
54
  def paintEvent(self, a0: QPaintEvent | None) -> None:
53
55
  """Paints the box"""
54
56
  painter = QPainter(self)
@@ -69,6 +71,7 @@ class ColorBox(QFrame):
69
71
 
70
72
  QFrame.paintEvent(self, a0)
71
73
 
74
+ @override
72
75
  def mouseReleaseEvent(self, a0: QMouseEvent | None) -> None:
73
76
  if a0:
74
77
  self.mouseRelease.emit()
@@ -20,6 +20,7 @@ from PyQt6.QtWidgets import (
20
20
  QWidget,
21
21
  QWidgetAction,
22
22
  )
23
+ from typing_extensions import override
23
24
 
24
25
  from ert.gui.tools.plot.plot_api import PlotApiKeyDefinition
25
26
  from ert.gui.tools.plot.plottery import PlotConfig, PlotConfigFactory, PlotConfigHistory
@@ -296,6 +297,7 @@ class CustomizePlotDialog(QDialog):
296
297
  def currentPlotKeyChanged(self, new_key: str | None) -> None:
297
298
  self.current_key = new_key
298
299
 
300
+ @override
299
301
  def keyPressEvent(self, a0: QKeyEvent | None) -> None:
300
302
  # Hide when pressing Escape instead of QDialog.keyPressEvent(KeyEscape)
301
303
  # which closes the dialog
@@ -1,5 +1,7 @@
1
1
  from typing import TYPE_CHECKING
2
2
 
3
+ from typing_extensions import override
4
+
3
5
  from .customization_view import CustomizationView, WidgetProperty
4
6
 
5
7
  if TYPE_CHECKING:
@@ -57,6 +59,7 @@ class DefaultCustomizationView(CustomizationView):
57
59
  "observations", "Observations", "Toggle observations visibility."
58
60
  )
59
61
 
62
+ @override
60
63
  def applyCustomization(self, plot_config: "PlotConfig") -> None:
61
64
  plot_config.setTitle(self.title)
62
65
 
@@ -68,6 +71,7 @@ class DefaultCustomizationView(CustomizationView):
68
71
  plot_config.setHistoryEnabled(self.history)
69
72
  plot_config.setObservationsEnabled(self.observations)
70
73
 
74
+ @override
71
75
  def revertCustomization(self, plot_config: "PlotConfig") -> None:
72
76
  if not plot_config.isUnnamed():
73
77
  self.title = plot_config.title()
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, cast
6
6
 
7
7
  from PyQt6.QtGui import QDoubleValidator, QIntValidator
8
8
  from PyQt6.QtWidgets import QLabel, QLineEdit, QStackedWidget
9
+ from typing_extensions import override
9
10
 
10
11
  from ert.gui.tools.plot.plottery import PlotContext, PlotLimits
11
12
  from ert.gui.tools.plot.widgets import ClearableLineEdit, CustomDateEdit
@@ -249,8 +250,10 @@ class LimitsCustomizationView(CustomizationView):
249
250
  self._limits_widget.switchInputOnX(x_axis_type)
250
251
  self._limits_widget.switchInputOnY(y_axis_type)
251
252
 
253
+ @override
252
254
  def revertCustomization(self, plot_config: PlotConfig) -> None:
253
255
  self._limits_widget.limits = plot_config.limits
254
256
 
257
+ @override
255
258
  def applyCustomization(self, plot_config: PlotConfig) -> None:
256
259
  plot_config.limits = self._limits_widget.limits
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, cast
4
4
 
5
5
  from PyQt6.QtWidgets import QComboBox, QHBoxLayout
6
+ from typing_extensions import override
6
7
 
7
8
  from .customization_view import CustomizationView, WidgetProperty
8
9
  from .style_chooser import STYLESET_AREA, StyleChooser
@@ -132,6 +133,7 @@ class StatisticsCustomizationView(CustomizationView):
132
133
  style.marker = marker_style
133
134
  setattr(self, attribute_name, style)
134
135
 
136
+ @override
135
137
  def applyCustomization(self, plot_config: PlotConfig) -> None:
136
138
  plot_config.setStatisticsStyle("mean", self.mean_style)
137
139
  plot_config.setStatisticsStyle("p50", self.p50_style)
@@ -143,6 +145,7 @@ class StatisticsCustomizationView(CustomizationView):
143
145
  plot_config.setStandardDeviationFactor(self.std_dev_factor)
144
146
  plot_config.setDistributionLineEnabled(self.distribution_lines)
145
147
 
148
+ @override
146
149
  def revertCustomization(self, plot_config: PlotConfig) -> None:
147
150
  self.mean_style = plot_config.getStatisticsStyle("mean")
148
151
  self.p50_style = plot_config.getStatisticsStyle("p50")
@@ -8,6 +8,7 @@ from PyQt6.QtWidgets import (
8
8
  QLayout,
9
9
  QWidget,
10
10
  )
11
+ from typing_extensions import override
11
12
 
12
13
  from ert.gui.tools.plot.plottery import PlotStyle
13
14
 
@@ -182,6 +183,7 @@ class StyleChooser(QWidget):
182
183
  self._style.width = thickness
183
184
  self._style.size = size
184
185
 
186
+ @override
185
187
  def setStyle(self, style: PlotStyle) -> None: # type: ignore
186
188
  self._style.copyStyleFrom(style)
187
189
  self._updateLineStyleAndMarker(
@@ -1,6 +1,7 @@
1
1
  from typing import TYPE_CHECKING, cast
2
2
 
3
3
  from PyQt6.QtWidgets import QHBoxLayout
4
+ from typing_extensions import override
4
5
 
5
6
  from .color_chooser import ColorBox
6
7
  from .customization_view import CustomizationView, WidgetProperty
@@ -91,6 +92,7 @@ class StyleCustomizationView(CustomizationView):
91
92
  color_box = self._color_boxes[index]
92
93
  color_box.color = color_tuple
93
94
 
95
+ @override
94
96
  def applyCustomization(self, plot_config: "PlotConfig") -> None:
95
97
  plot_config.setDefaultStyle(self.default_style)
96
98
  plot_config.setHistoryStyle(self.history_style)
@@ -98,6 +100,7 @@ class StyleCustomizationView(CustomizationView):
98
100
  plot_config.setObservationsColor(self.observs_color)
99
101
  plot_config.setLineColorCycle(self.color_cycle)
100
102
 
103
+ @override
101
104
  def revertCustomization(self, plot_config: "PlotConfig") -> None:
102
105
  self.default_style = plot_config.defaultStyle()
103
106
  self.history_style = plot_config.historyStyle()
@@ -9,6 +9,7 @@ from PyQt6.QtWidgets import (
9
9
  QVBoxLayout,
10
10
  QWidget,
11
11
  )
12
+ from typing_extensions import override
12
13
 
13
14
  from ert.gui.ertwidgets import SearchBox
14
15
 
@@ -29,6 +30,7 @@ class _LegendMarker(QWidget):
29
30
 
30
31
  self.color = color
31
32
 
33
+ @override
32
34
  def paintEvent(self, a0: QPaintEvent | None) -> None:
33
35
  painter = QPainter(self)
34
36
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from PyQt6.QtCore import QModelIndex, QObject, QSortFilterProxyModel, Qt
6
+ from typing_extensions import override
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from .data_type_keys_list_model import DataTypeKeysListModel
@@ -21,6 +22,7 @@ class DataTypeProxyModel(QSortFilterProxyModel):
21
22
  self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
22
23
  self.setSourceModel(model)
23
24
 
25
+ @override
24
26
  def filterAcceptsRow(self, source_row: int, source_parent: QModelIndex) -> bool:
25
27
  show = QSortFilterProxyModel.filterAcceptsRow(self, source_row, source_parent)
26
28
 
@@ -45,6 +47,7 @@ class DataTypeProxyModel(QSortFilterProxyModel):
45
47
 
46
48
  return show
47
49
 
50
+ @override
48
51
  def sourceModel(self) -> DataTypeKeysListModel:
49
52
  return QSortFilterProxyModel.sourceModel(self) # type: ignore
50
53