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
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from json import JSONEncoder
4
- from typing import Any, TypeVar, no_type_check
4
+ from typing import Any, Self, TypeVar, no_type_check
5
5
 
6
6
  from .file_context_token import FileContextToken
7
7
 
@@ -37,7 +37,7 @@ class ContextBool:
37
37
 
38
38
 
39
39
  class ContextInt(int):
40
- def __new__(cls, val: int, token: FileContextToken) -> ContextInt:
40
+ def __new__(cls, val: int, token: FileContextToken) -> Self:
41
41
  obj = super().__new__(cls, val)
42
42
  obj.token = token
43
43
  return obj
@@ -50,7 +50,7 @@ class ContextInt(int):
50
50
 
51
51
 
52
52
  class ContextFloat(float):
53
- def __new__(cls, val: float, token: FileContextToken) -> ContextFloat:
53
+ def __new__(cls, val: float, token: FileContextToken) -> Self:
54
54
  obj = super().__new__(cls, val)
55
55
  obj.token = token
56
56
  return obj
@@ -12,7 +12,7 @@ class FileContextToken(Token):
12
12
 
13
13
  filename: str
14
14
 
15
- def __new__(cls, token: Token, filename: str) -> FileContextToken:
15
+ def __new__(cls, token: Token, filename: str) -> FileContextToken: # noqa: PYI034
16
16
  inst = super().__new__(
17
17
  cls,
18
18
  token.type,
@@ -18,6 +18,7 @@ class ObservationType(StrEnum):
18
18
  HISTORY = "HISTORY_OBSERVATION"
19
19
  SUMMARY = "SUMMARY_OBSERVATION"
20
20
  GENERAL = "GENERAL_OBSERVATION"
21
+ RFT = "RFT_OBSERVATION"
21
22
 
22
23
 
23
24
  ObservationDict = dict[str, Any]
@@ -92,7 +93,7 @@ def parse_observations(content: str, filename: str) -> list[ObservationDict]:
92
93
  ), ["TYPE"]:
93
94
  message = (
94
95
  f"Unknown observation type '{unexpected_token}', "
95
- f"expected either 'GENERAL_OBSERVATION', "
96
+ f"expected either 'RFT_OBSERVATION', 'GENERAL_OBSERVATION', "
96
97
  f"'SUMMARY_OBSERVATION' or 'HISTORY_OBSERVATION'."
97
98
  )
98
99
  case UnexpectedToken(token=unexpected_char, expected=allowed_chars), _:
@@ -122,7 +123,10 @@ observations_parser = Lark(
122
123
  r"""
123
124
  start: observation*
124
125
  ?observation: type OBSERVATION_NAME object? ";"
125
- TYPE: "HISTORY_OBSERVATION" | "SUMMARY_OBSERVATION" | "GENERAL_OBSERVATION"
126
+ TYPE: "HISTORY_OBSERVATION"
127
+ | "SUMMARY_OBSERVATION"
128
+ | "GENERAL_OBSERVATION"
129
+ | "RFT_OBSERVATION"
126
130
  type: TYPE
127
131
  ?value: object
128
132
  | STRING
@@ -23,3 +23,12 @@ class QueueSystem(StrEnum):
23
23
  @staticmethod
24
24
  def ert_config_case() -> str:
25
25
  return "upper"
26
+
27
+ @property
28
+ def formatted_name(self) -> str:
29
+ return {
30
+ self.LSF: "LSF",
31
+ self.LOCAL: "Local",
32
+ self.TORQUE: "Torque/OpenPBS",
33
+ self.SLURM: "Slurm",
34
+ }[self]
@@ -9,6 +9,7 @@ class SchemaItemType(StrEnum):
9
9
  POSITIVE_FLOAT = "POSITIVE_FLOAT"
10
10
  PATH = "PATH"
11
11
  EXISTING_PATH = "EXISTING_PATH"
12
+ EXISTING_FILE = "EXISTING_FILE"
12
13
  # EXISTING_PATH_INLINE is a directive to the
13
14
  # schema validation to inline the contents of
14
15
  # the file.
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
  import logging
4
4
  import os
5
5
  import re
6
- import shutil
7
6
  from abc import abstractmethod
7
+ from textwrap import dedent
8
8
  from typing import (
9
9
  TYPE_CHECKING,
10
10
  Annotated,
@@ -51,7 +51,6 @@ class QueueOptions(
51
51
  BaseModelWithContextSupport,
52
52
  validate_assignment=True,
53
53
  extra="forbid",
54
- use_enum_values=True,
55
54
  validate_default=True,
56
55
  ):
57
56
  name: QueueSystem
@@ -60,11 +59,26 @@ class QueueOptions(
60
59
  num_cpu: pydantic.NonNegativeInt = 1
61
60
  realization_memory: pydantic.NonNegativeInt = Field(
62
61
  default=0,
63
- description="""When set, a job is only allowed to use realization_memory
64
- of memory.
65
-
66
- realization_memory may be an integer value, indicating the number of bytes, or a
67
- string consisting of a number followed by a unit. The unit indicates the
62
+ description=dedent("""
63
+ Amount of memory to set aside for a forward model.
64
+
65
+ This information is propagated to the queue system as the amount of memory
66
+ to reserve/book for a realization to complete. It is up to the configuration
67
+ of the queuing system how to treat this information, but usually it will
68
+ stop more realizations being assigned to a compute node if the compute nodes
69
+ memory is already fully booked.
70
+
71
+ Setting this number lower than the peak memory consumption of each
72
+ realization puts the realization at risk of being killed in an out-of-memory
73
+ situation. Setting this number higher than needed will give longer wait
74
+ times in the queue.
75
+
76
+ For the local queue system, this keyword has no effect. In that scenario,
77
+ you can use `max_running` to choke the memory consumption.
78
+ scheduling of compute jobs.
79
+
80
+ `realization_memory` may be an integer value, indicating the number of bytes, or
81
+ a string consisting of a number followed by a unit. The unit indicates the
68
82
  multiplier that is applied, and must start with one of these characters:
69
83
 
70
84
  * b, B: bytes
@@ -77,11 +91,8 @@ class QueueOptions(
77
91
  Spaces between the number and the unit are ignored, and so are any
78
92
  characters after the first. For example: 2g, 2G, and 2 GB all resolve
79
93
  to the same value: 2 gigabytes, equaling 2 * 1024**3 bytes.
80
-
81
- If not set, or set to zero, the allowed amount of memory is unlimited.
82
- """,
94
+ """),
83
95
  )
84
- job_script: str = shutil.which("fm_dispatch.py") or "fm_dispatch.py"
85
96
  project_code: str | None = None
86
97
  activate_script: str | None = Field(default=None, validate_default=True)
87
98
 
@@ -184,7 +195,6 @@ class LsfQueueOptions(QueueOptions):
184
195
  "max_running",
185
196
  "num_cpu",
186
197
  "realization_memory",
187
- "job_script",
188
198
  }
189
199
  )
190
200
  driver_dict["exclude_hosts"] = driver_dict.pop("exclude_host")
@@ -212,7 +222,6 @@ class TorqueQueueOptions(QueueOptions):
212
222
  "submit_sleep",
213
223
  "num_cpu",
214
224
  "realization_memory",
215
- "job_script",
216
225
  }
217
226
  )
218
227
  driver_dict["queue_name"] = driver_dict.pop("queue")
@@ -241,7 +250,6 @@ class SlurmQueueOptions(QueueOptions):
241
250
  "submit_sleep",
242
251
  "num_cpu",
243
252
  "realization_memory",
244
- "job_script",
245
253
  }
246
254
  )
247
255
  driver_dict["sbatch_cmd"] = driver_dict.pop("sbatch")
@@ -390,6 +398,11 @@ class QueueConfig(BaseModelWithContextSupport):
390
398
  config_dict: ConfigDict,
391
399
  site_queue_options: QueueOptions | None = None,
392
400
  ) -> QueueConfig:
401
+ """Merge ConfigDict with QueueOptions.
402
+
403
+ A ConfigDict is parsed from an Ert configuration.
404
+
405
+ site_queue_options is overridden by config_dict"""
393
406
  site_queue_options_dict = (
394
407
  site_queue_options.model_dump(exclude_unset=True)
395
408
  if site_queue_options
@@ -406,48 +419,27 @@ class QueueConfig(BaseModelWithContextSupport):
406
419
  usr_queue_options_dict = cls._user_queue_options_from_dict(
407
420
  selected_queue_system, config_dict
408
421
  )
409
- default_queue_options_dict = {
410
- name: generic_option.default
411
- for name, generic_option in QueueOptions.model_fields.items()
412
- }
413
422
 
414
- usr_job_script = usr_queue_options_dict.get("job_script")
415
- site_job_script = site_queue_options_dict.get("job_script")
416
-
417
- config_dict["JOB_SCRIPT"] = (
418
- usr_job_script
419
- or site_job_script
420
- or shutil.which("fm_dispatch.py")
421
- or "fm_dispatch.py"
422
- )
423
+ merged_queue_options_dict = (
424
+ site_queue_options_dict
425
+ if str(selected_queue_system) == site_queue_system
426
+ else {}
427
+ ) | usr_queue_options_dict
423
428
 
424
429
  max_submit: int = config_dict.get(ConfigKeys.MAX_SUBMIT, 1)
425
430
  stop_long_running = config_dict.get(ConfigKeys.STOP_LONG_RUNNING, False)
426
431
 
427
- usr_num_cpu = usr_queue_options_dict.get("num_cpu")
428
- site_num_cpu = site_queue_options_dict.get("num_cpu")
429
-
430
- if usr_num_cpu is not None:
431
- config_dict["NUM_CPU"] = usr_num_cpu
432
- elif (
433
- site_num_cpu is None
434
- and (data_file := config_dict.get(ConfigKeys.DATA_FILE))
435
- and (num_cpu := get_num_cpu_from_data_file(data_file))
432
+ if "num_cpu" in merged_queue_options_dict:
433
+ config_dict["NUM_CPU"] = merged_queue_options_dict.get("num_cpu")
434
+ elif (data_file := config_dict.get(ConfigKeys.DATA_FILE)) and (
435
+ num_cpu := get_num_cpu_from_data_file(data_file)
436
436
  ):
437
437
  logger.info(f"Parsed NUM_CPU={num_cpu} from {data_file}")
438
- usr_queue_options_dict[ConfigKeys.NUM_CPU] = num_cpu
438
+ merged_queue_options_dict[ConfigKeys.NUM_CPU] = num_cpu
439
439
 
440
440
  selected_queue_options = QueueOptions.create_queue_options(
441
441
  selected_queue_system,
442
- (
443
- default_queue_options_dict
444
- | (
445
- site_queue_options_dict
446
- if str(selected_queue_system) == site_queue_system
447
- else {}
448
- )
449
- | usr_queue_options_dict
450
- ),
442
+ merged_queue_options_dict,
451
443
  True,
452
444
  )
453
445
 
@@ -485,11 +477,11 @@ class QueueConfig(BaseModelWithContextSupport):
485
477
 
486
478
  # validate all queue options for the unselected queues
487
479
  # and show a warning
488
- for _queue_system in QueueSystem:
489
- if _queue_system != selected_queue_system:
480
+ for queue_system in QueueSystem:
481
+ if queue_system != selected_queue_system:
490
482
  _ = QueueOptions.create_queue_options(
491
- _queue_system,
492
- grouped_queue_options[_queue_system],
483
+ queue_system,
484
+ grouped_queue_options[queue_system],
493
485
  False,
494
486
  )
495
487
 
@@ -31,7 +31,6 @@ class ResponseMetadata(BaseModel):
31
31
 
32
32
  class ResponseConfig(BaseModel):
33
33
  type: str
34
- name: str
35
34
  input_files: list[str] = Field(default_factory=list)
36
35
  keys: list[str] = Field(default_factory=list)
37
36
  has_finalized_keys: bool = False
@@ -60,13 +59,6 @@ class ResponseConfig(BaseModel):
60
59
  def expected_input_files(self) -> list[str]:
61
60
  """Returns a list of filenames expected to be produced by the forward model"""
62
61
 
63
- @property
64
- @abstractmethod
65
- def response_type(self) -> str:
66
- """Label to identify what kind of response it is.
67
- Must not overlap with that of other response configs."""
68
- ...
69
-
70
62
  @property
71
63
  @abstractmethod
72
64
  def primary_key(self) -> list[str]:
@@ -0,0 +1,275 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import fnmatch
5
+ import logging
6
+ import os
7
+ import re
8
+ from collections import defaultdict
9
+ from typing import IO, Any, Literal
10
+
11
+ import numpy as np
12
+ import numpy.typing as npt
13
+ import polars as pl
14
+ from pydantic import Field
15
+ from resfo_utilities import CornerpointGrid, InvalidRFTError, RFTReader
16
+
17
+ from ert.substitutions import substitute_runpath_name
18
+
19
+ from .parsing import ConfigDict, ConfigKeys, ConfigValidationError, ConfigWarning
20
+ from .response_config import InvalidResponseFile, ResponseConfig, ResponseMetadata
21
+ from .responses_index import responses_index
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class RFTConfig(ResponseConfig):
27
+ type: Literal["rft"] = "rft"
28
+ name: str = "rft"
29
+ has_finalized_keys: bool = False
30
+ data_to_read: dict[str, dict[str, list[str]]] = Field(default_factory=dict)
31
+ locations: list[tuple[float, float, float]] = Field(default_factory=list)
32
+
33
+ @property
34
+ def metadata(self) -> list[ResponseMetadata]:
35
+ return [
36
+ ResponseMetadata(
37
+ response_type=self.name,
38
+ response_key=response_key,
39
+ filter_on=None,
40
+ finalized=self.has_finalized_keys,
41
+ )
42
+ for response_key in self.keys
43
+ ]
44
+
45
+ @property
46
+ def expected_input_files(self) -> list[str]:
47
+ base = self.input_files[0]
48
+ if base.upper().endswith(".DATA"):
49
+ # For backwards compatibility, it is
50
+ # allowed to give REFCASE and ECLBASE both
51
+ # with and without .DATA extensions
52
+ base = base[:-5]
53
+
54
+ return [f"{base}.RFT"]
55
+
56
+ def _find_indices(
57
+ self, egrid_file: str | os.PathLike[str] | IO[Any]
58
+ ) -> dict[tuple[int, int, int] | None, set[tuple[float, float, float]]]:
59
+ indices = defaultdict(set)
60
+ for a, b in zip(
61
+ CornerpointGrid.read_egrid(egrid_file).find_cell_containing_point(
62
+ self.locations
63
+ ),
64
+ self.locations,
65
+ strict=True,
66
+ ):
67
+ indices[a].add(b)
68
+ return indices
69
+
70
+ def read_from_file(self, run_path: str, iens: int, iter_: int) -> pl.DataFrame:
71
+ filename = substitute_runpath_name(self.input_files[0], iens, iter_)
72
+ if filename.upper().endswith(".DATA"):
73
+ # For backwards compatibility, it is
74
+ # allowed to give REFCASE and ECLBASE both
75
+ # with and without .DATA extensions
76
+ filename = filename[:-5]
77
+ grid_filename = f"{run_path}/{filename}"
78
+ if grid_filename.upper().endswith(".RFT"):
79
+ grid_filename = grid_filename[:-4]
80
+ grid_filename += ".EGRID"
81
+ fetched: dict[tuple[str, datetime.date], dict[str, npt.NDArray[np.float32]]] = (
82
+ defaultdict(dict)
83
+ )
84
+ indices = {}
85
+ if self.locations:
86
+ indices = self._find_indices(grid_filename)
87
+ if None in indices:
88
+ raise InvalidResponseFile(
89
+ f"Did not find grid coordinate for location(s) {indices[None]}"
90
+ )
91
+ # This is a somewhat complicated optimization in order to
92
+ # support wildcards in well names, dates and properties
93
+ # A python for loop is too slow so we use a compiled regex
94
+ # instead
95
+ if not self.data_to_read:
96
+ return pl.DataFrame(
97
+ {
98
+ "response_key": [],
99
+ "time": [],
100
+ "depth": [],
101
+ "values": [],
102
+ "location": [],
103
+ }
104
+ )
105
+
106
+ sep = "\x31"
107
+
108
+ def _translate(pat: str) -> str:
109
+ """Translates fnmatch pattern to match anywhere"""
110
+ return fnmatch.translate(pat).replace("\\z", "").replace("\\Z", "")
111
+
112
+ def _props_matcher(props: list[str]) -> str:
113
+ """Regex for matching given props _and_ DEPTH"""
114
+ pattern = f"({'|'.join(_translate(p) for p in props)})"
115
+ if re.fullmatch(pattern, "DEPTH") is None:
116
+ return f"({'|'.join(_translate(p) for p in [*props, 'DEPTH'])})"
117
+ else:
118
+ return pattern
119
+
120
+ matcher = re.compile(
121
+ "|".join(
122
+ "("
123
+ + re.escape(sep).join(
124
+ (
125
+ _translate(well),
126
+ _translate(time),
127
+ _props_matcher(props),
128
+ )
129
+ )
130
+ + ")"
131
+ for well, inner_dict in self.data_to_read.items()
132
+ for time, props in inner_dict.items()
133
+ )
134
+ )
135
+ locations = {}
136
+ try:
137
+ with RFTReader.open(f"{run_path}/{filename}") as rft:
138
+ for entry in rft:
139
+ date = entry.date
140
+ well = entry.well
141
+ for rft_property in entry:
142
+ key = f"{well}{sep}{date}{sep}{rft_property}"
143
+ if matcher.fullmatch(key) is not None:
144
+ values = entry[rft_property]
145
+ locations[well, date] = [
146
+ list(
147
+ indices.get(
148
+ (c[0] - 1, c[1] - 1, c[2] - 1),
149
+ [(None, None, None)],
150
+ )
151
+ )
152
+ for c in entry.connections
153
+ ]
154
+ if np.isdtype(values.dtype, np.float32):
155
+ fetched[well, date][rft_property] = values
156
+ except (FileNotFoundError, InvalidRFTError) as err:
157
+ raise InvalidResponseFile(
158
+ f"Could not read RFT from {run_path}/{filename}: {err}"
159
+ ) from err
160
+
161
+ if not fetched:
162
+ return pl.DataFrame(
163
+ {
164
+ "response_key": [],
165
+ "time": [],
166
+ "depth": [],
167
+ "values": [],
168
+ "location": [],
169
+ }
170
+ )
171
+
172
+ try:
173
+ df = pl.concat(
174
+ [
175
+ pl.DataFrame(
176
+ {
177
+ "response_key": [f"{well}:{time.isoformat()}:{prop}"],
178
+ "time": [time],
179
+ "depth": [fetched[well, time]["DEPTH"]],
180
+ "values": [vals],
181
+ "location": pl.Series(
182
+ [
183
+ locations.get(
184
+ (well, time), [(None, None, None)] * len(vals)
185
+ )
186
+ ],
187
+ dtype=pl.Array(
188
+ pl.List(pl.Array(pl.Float32, 3)), len(vals)
189
+ ),
190
+ ),
191
+ }
192
+ )
193
+ .explode("depth", "values", "location")
194
+ .explode("location")
195
+ for (well, time), inner_dict in fetched.items()
196
+ for prop, vals in inner_dict.items()
197
+ if prop != "DEPTH"
198
+ ]
199
+ )
200
+ except KeyError as err:
201
+ raise InvalidResponseFile(
202
+ f"Could not find {err.args[0]} in RFTFile {filename}"
203
+ ) from err
204
+
205
+ return df.with_columns(
206
+ east=pl.col("location").arr.get(0),
207
+ north=pl.col("location").arr.get(1),
208
+ tvd=pl.col("location").arr.get(2),
209
+ ).drop("location")
210
+
211
+ @property
212
+ def response_type(self) -> str:
213
+ return "rft"
214
+
215
+ @property
216
+ def primary_key(self) -> list[str]:
217
+ return ["east", "north", "tvd"]
218
+
219
+ @classmethod
220
+ def from_config_dict(cls, config_dict: ConfigDict) -> RFTConfig | None:
221
+ if rfts := config_dict.get(ConfigKeys.RFT, []):
222
+ eclbase: str | None = config_dict.get("ECLBASE")
223
+ if eclbase is None:
224
+ raise ConfigValidationError(
225
+ "In order to use rft responses, ECLBASE has to be set."
226
+ )
227
+ fm_steps = config_dict.get(ConfigKeys.FORWARD_MODEL, [])
228
+ names = [fm_step[0] for fm_step in fm_steps]
229
+ simulation_step_exists = any(
230
+ any(sim in name.lower() for sim in ["eclipse", "flow"])
231
+ for name in names
232
+ )
233
+ if not simulation_step_exists:
234
+ ConfigWarning.warn(
235
+ "Config contains a RFT key but no forward model "
236
+ "step known to generate rft files"
237
+ )
238
+
239
+ declared_data: dict[str, dict[datetime.date, list[str]]] = defaultdict(
240
+ lambda: defaultdict(list)
241
+ )
242
+ for rft in rfts:
243
+ for expected in ["WELL", "DATE", "PROPERTIES"]:
244
+ if expected not in rft:
245
+ raise ConfigValidationError.with_context(
246
+ f"For RFT keyword {expected} must be specified.", rft
247
+ )
248
+ well = rft["WELL"]
249
+ props = [p.strip() for p in rft["PROPERTIES"].split(",")]
250
+ time = rft["DATE"]
251
+ declared_data[well][time] += props
252
+ data_to_read = {
253
+ well: {time: sorted(set(p)) for time, p in inner_dict.items()}
254
+ for well, inner_dict in declared_data.items()
255
+ }
256
+ keys = sorted(
257
+ {
258
+ f"{well}:{time}:{p}"
259
+ for well, inner_dict in declared_data.items()
260
+ for time, props in inner_dict.items()
261
+ for p in props
262
+ }
263
+ )
264
+
265
+ return cls(
266
+ name="rft",
267
+ input_files=[eclbase.replace("%d", "<IENS>")],
268
+ keys=keys,
269
+ data_to_read=data_to_read,
270
+ )
271
+
272
+ return None
273
+
274
+
275
+ responses_index.add_response_type(RFTConfig)
@@ -19,14 +19,13 @@ logger = logging.getLogger(__name__)
19
19
 
20
20
  class SummaryConfig(ResponseConfig):
21
21
  type: Literal["summary"] = "summary"
22
- name: str = "summary"
23
22
  has_finalized_keys: bool = False
24
23
 
25
24
  @property
26
25
  def metadata(self) -> list[ResponseMetadata]:
27
26
  return [
28
27
  ResponseMetadata(
29
- response_type=self.name,
28
+ response_type=self.type,
30
29
  response_key=response_key,
31
30
  filter_on=None,
32
31
  finalized=self.has_finalized_keys,
@@ -72,10 +71,6 @@ class SummaryConfig(ResponseConfig):
72
71
  df = df.sort(by=["time"])
73
72
  return df
74
73
 
75
- @property
76
- def response_type(self) -> str:
77
- return "summary"
78
-
79
74
  @property
80
75
  def primary_key(self) -> list[str]:
81
76
  return ["time"]
@@ -91,8 +86,8 @@ class SummaryConfig(ResponseConfig):
91
86
  fm_steps = config_dict.get(ConfigKeys.FORWARD_MODEL, [])
92
87
  names = [fm_step[0] for fm_step in fm_steps]
93
88
  simulation_step_exists = any(
94
- any(sim in _name.lower() for sim in ["eclipse", "flow"])
95
- for _name in names
89
+ any(sim in name.lower() for sim in ["eclipse", "flow"])
90
+ for name in names
96
91
  )
97
92
  if not simulation_step_exists:
98
93
  ConfigWarning.warn(