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
@@ -0,0 +1,234 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import json
5
+ import logging
6
+ from collections.abc import Iterator, Mapping, MutableMapping
7
+ from dataclasses import field
8
+ from pathlib import Path
9
+ from textwrap import dedent
10
+ from typing import TYPE_CHECKING, Any, Literal, Self
11
+
12
+ import networkx as nx
13
+ import numpy as np
14
+ import xarray as xr
15
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
16
+ from ropt.workflow import find_sampler_plugin
17
+
18
+ from ert.substitutions import substitute_runpath_name
19
+
20
+ from .parameter_config import ParameterConfig
21
+
22
+ if TYPE_CHECKING:
23
+ import numpy.typing as npt
24
+
25
+ from ert.storage import Ensemble
26
+
27
+ Number = int | float
28
+ DataType = Mapping[str, Number | Mapping[str, Number]]
29
+ MutableDataType = MutableMapping[str, Number | MutableMapping[str, Number]]
30
+
31
+
32
+ class SamplerConfig(BaseModel):
33
+ backend: str | None = Field(
34
+ default=None,
35
+ description=dedent(
36
+ """
37
+ [Deprecated]
38
+
39
+ The correct backend will be inferred by the method. If several backends
40
+ have a method named `A`, pick a specific backend `B` by putting `B/A` in
41
+ the `method` field.
42
+ """
43
+ ),
44
+ )
45
+ method: str = Field(
46
+ default="norm",
47
+ description=dedent(
48
+ """
49
+ The sampling method or distribution used by the sampler backend.
50
+
51
+ The set of available methods depends on the sampler backend used. By
52
+ default a plugin based on `scipy.stats` is used, implementing the
53
+ following methods:
54
+
55
+ - From Probability Distributions:
56
+ - `norm`: Samples from a standard normal distribution (mean 0,
57
+ standard deviation 1).
58
+ - `truncnorm`: Samples from a truncated normal distribution
59
+ (mean 0, std. dev. 1), truncated to the range `[-1, 1]`.
60
+ - `uniform`: Samples from a uniform distribution in the range
61
+ `[-1, 1]`.
62
+
63
+ - From Quasi-Monte Carlo Sequences:
64
+ - `sobol`: Uses Sobol' sequences.
65
+ - `halton`: Uses Halton sequences.
66
+ - `lhs`: Uses Latin Hypercube Sampling.
67
+
68
+ Note: QMC samples are generated in the unit hypercube `[0, 1]^d`
69
+ and then scaled to the hypercube `[-1, 1]^d`.
70
+ """
71
+ ),
72
+ )
73
+ options: dict[str, Any] | None = Field(
74
+ default=None,
75
+ description=dedent(
76
+ """
77
+ Specifies a dict of optional parameters for the sampler backend.
78
+
79
+ This dict of values is passed unchanged to the selected method in
80
+ the backend.
81
+ """
82
+ ),
83
+ )
84
+ shared: bool | None = Field(
85
+ default=None,
86
+ description=dedent(
87
+ """
88
+ Whether to share perturbations between realizations.
89
+ """
90
+ ),
91
+ )
92
+ model_config = ConfigDict(extra="forbid")
93
+
94
+ @model_validator(mode="after")
95
+ def validate_backend_and_method(self) -> Self:
96
+ if self.backend is not None:
97
+ message = (
98
+ "sampler.backend is deprecated. "
99
+ "The correct backend will be inferred by the method. "
100
+ "If several backends have a method named A, you need to pick "
101
+ "a specific backend B by putting B/A in sampler.method."
102
+ )
103
+ print(message)
104
+ # Note: Importing EVEREST.everest
105
+ # leads to circular import, but we still wish to log
106
+ # from "everest" here as per old behavior.
107
+ # Can consider logging this as if from ERT,
108
+ # which is valid if we store SamplerConfig as part of
109
+ # EverestControl configs.
110
+ logging.getLogger("everest").warning(message)
111
+
112
+ # Update the default for backends that are not scipy:
113
+ if (
114
+ self.backend not in {None, "scipy"}
115
+ and "method" not in self.model_fields_set
116
+ ):
117
+ self.method = "default"
118
+
119
+ if self.backend is not None:
120
+ self.method = f"{self.backend}/{self.method}"
121
+
122
+ try:
123
+ plugin = find_sampler_plugin(f"{self.method}")
124
+ except ValueError:
125
+ raise
126
+ except Exception as exc:
127
+ ert_version = importlib.metadata.version("ert")
128
+ ropt_version = importlib.metadata.version("ropt")
129
+ msg = (
130
+ f"Error while initializing ropt:\n\n{exc}.\n\n"
131
+ "There may a be version mismatch between "
132
+ f"ERT ({ert_version}) and ropt ({ropt_version})\n"
133
+ "If the installation is correct, please report this as a bug."
134
+ )
135
+ raise RuntimeError(msg) from exc
136
+
137
+ if plugin is None:
138
+ raise ValueError(f"Sampler method '{self.method}' not found")
139
+
140
+ self.backend = None
141
+
142
+ return self
143
+
144
+
145
+ class EverestControl(ParameterConfig):
146
+ """Create an EverestControl for @key with the given @input_keys
147
+
148
+ @input_keys can be either a list of keys as strings or a dict with
149
+ keys as strings and a list of suffixes for each key.
150
+ If a list of strings is given, the order is preserved.
151
+ """
152
+
153
+ type: Literal["everest_parameters"] = "everest_parameters"
154
+ input_keys: list[str] = field(default_factory=list)
155
+ forward_init: bool = False
156
+ output_file: str = ""
157
+ forward_init_file: str = ""
158
+ update: bool = False
159
+ types: list[Literal["well_control", "generic_control"]]
160
+ initial_guesses: list[float]
161
+ control_types: list[Literal["real", "integer"]]
162
+ enabled: list[bool]
163
+ min: list[float]
164
+ max: list[float]
165
+ perturbation_types: list[Literal["absolute", "relative"]]
166
+ perturbation_magnitudes: list[float]
167
+ scaled_ranges: list[tuple[float, float]]
168
+ samplers: list[SamplerConfig | None]
169
+
170
+ # Set up for deprecation, but has to live here until support for the
171
+ # "dotdash" notation is removed for everest controls via everest config.
172
+ input_keys_dotdash: list[str] = field(default_factory=list)
173
+
174
+ @property
175
+ def parameter_keys(self) -> list[str]:
176
+ return self.input_keys
177
+
178
+ def read_from_runpath(
179
+ self, run_path: Path, real_nr: int, iteration: int
180
+ ) -> xr.Dataset:
181
+ raise NotImplementedError
182
+
183
+ def write_to_runpath(
184
+ self, run_path: Path, real_nr: int, ensemble: Ensemble
185
+ ) -> None:
186
+ file_path = run_path / substitute_runpath_name(
187
+ self.output_file, real_nr, ensemble.iteration
188
+ )
189
+ Path.mkdir(file_path.parent, exist_ok=True, parents=True)
190
+
191
+ data: MutableDataType = {}
192
+ for da in ensemble.load_parameters(self.name, real_nr)["values"]:
193
+ assert isinstance(da, xr.DataArray)
194
+ name = str(da.names.values)
195
+ try:
196
+ outer, inner = name.split("\0")
197
+
198
+ if outer not in data:
199
+ data[outer] = {}
200
+ data[outer][inner] = float(da) # type: ignore
201
+ except ValueError:
202
+ data[name] = float(da)
203
+
204
+ file_path.write_text(json.dumps(data), encoding="utf-8")
205
+
206
+ def create_storage_datasets(
207
+ self,
208
+ from_data: npt.NDArray[np.float64],
209
+ iens_active_index: npt.NDArray[np.int_],
210
+ ) -> Iterator[tuple[int, xr.Dataset]]:
211
+ for i, realization in enumerate(iens_active_index):
212
+ yield (
213
+ int(realization),
214
+ xr.Dataset(
215
+ {
216
+ "values": ("names", from_data[:, i]),
217
+ "names": [
218
+ x.split(f"{self.name}.")[1].replace(".", "\0")
219
+ for x in self.parameter_keys
220
+ ],
221
+ }
222
+ ),
223
+ )
224
+
225
+ def load_parameters(
226
+ self, ensemble: Ensemble, realizations: npt.NDArray[np.int_]
227
+ ) -> npt.NDArray[np.float64]:
228
+ raise NotImplementedError
229
+
230
+ def load_parameter_graph(self) -> nx.Graph[int]:
231
+ raise NotImplementedError
232
+
233
+ def __len__(self) -> int:
234
+ return len(self.input_keys)
@@ -11,12 +11,21 @@ from .response_config import InvalidResponseFile, ResponseConfig, ResponseMetada
11
11
  from .responses_index import responses_index
12
12
 
13
13
 
14
- class EverestConstraintsConfig(ResponseConfig):
14
+ class EverestResponse(ResponseConfig):
15
+ """Base class for Everest response configurations."""
16
+
17
+ has_finalized_keys: bool = True
18
+ scales: list[float | None]
19
+
20
+ @property
21
+ def primary_key(self) -> list[str]:
22
+ return []
23
+
15
24
  @property
16
25
  def metadata(self) -> list[ResponseMetadata]:
17
26
  return [
18
27
  ResponseMetadata(
19
- response_type=self.name,
28
+ response_type=self.type,
20
29
  response_key=response_key,
21
30
  finalized=self.has_finalized_keys,
22
31
  filter_on=None,
@@ -24,10 +33,6 @@ class EverestConstraintsConfig(ResponseConfig):
24
33
  for response_key in self.keys
25
34
  ]
26
35
 
27
- type: Literal["everest_constraints"] = "everest_constraints"
28
- name: str = "everest_constraints"
29
- has_finalized_keys: bool = True
30
-
31
36
  @property
32
37
  def expected_input_files(self) -> list[str]:
33
38
  return self.input_files
@@ -49,7 +54,6 @@ class EverestConstraintsConfig(ResponseConfig):
49
54
  )
50
55
 
51
56
  errors = []
52
-
53
57
  run_path_ = Path(run_path)
54
58
  datasets_per_name = []
55
59
 
@@ -72,24 +76,32 @@ class EverestConstraintsConfig(ResponseConfig):
72
76
  if all(isinstance(err, FileNotFoundError) for err in errors):
73
77
  raise FileNotFoundError(
74
78
  "Could not find one or more files/directories while reading "
75
- f"{self.name}: {','.join([str(err) for err in errors])}"
79
+ f"{self.type}: {','.join([str(err) for err in errors])}"
76
80
  )
77
81
  else:
78
82
  raise InvalidResponseFile(
79
83
  "Error reading "
80
- f"{self.name}, errors: {','.join([str(err) for err in errors])}"
84
+ f"{self.type}, errors: {','.join([str(err) for err in errors])}"
81
85
  )
82
86
 
83
87
  combined = pl.concat(datasets_per_name)
84
88
  return combined
85
89
 
86
- @property
87
- def response_type(self) -> str:
88
- return "everest_constraints"
89
90
 
90
- @property
91
- def primary_key(self) -> list[str]:
92
- return []
91
+ class EverestConstraintsConfig(EverestResponse):
92
+ type: Literal["everest_constraints"] = "everest_constraints"
93
+ targets: list[float | None]
94
+ upper_bounds: list[float]
95
+ lower_bounds: list[float]
93
96
 
94
97
 
95
98
  responses_index.add_response_type(EverestConstraintsConfig)
99
+
100
+
101
+ class EverestObjectivesConfig(EverestResponse):
102
+ type: Literal["everest_objectives"] = "everest_objectives"
103
+ weights: list[float | None]
104
+ objective_types: list[Literal["mean", "stddev"]]
105
+
106
+
107
+ responses_index.add_response_type(EverestObjectivesConfig)
ert/config/field.py CHANGED
@@ -3,32 +3,33 @@ from __future__ import annotations
3
3
  import itertools
4
4
  import logging
5
5
  import os
6
- from collections.abc import Iterator
7
- from functools import cached_property
6
+ from collections.abc import Callable, Iterator
8
7
  from pathlib import Path
9
- from typing import TYPE_CHECKING, Any, Literal, Self, cast, overload
8
+ from typing import TYPE_CHECKING, Any, Final, Literal, Self, cast, overload
10
9
 
11
10
  import networkx as nx
12
11
  import numpy as np
13
12
  import xarray as xr
14
- import xtgeo # type: ignore
13
+ import xtgeo
15
14
  from pydantic import field_serializer
16
15
 
17
16
  from ert.field_utils import (
18
17
  ErtboxParameters,
19
18
  FieldFileFormat,
20
19
  Shape,
20
+ calc_rho_for_2d_grid_layer,
21
21
  calculate_ertbox_parameters,
22
22
  get_shape,
23
23
  read_field,
24
- read_mask,
25
24
  save_field,
25
+ transform_local_ellipse_angle_to_local_coords,
26
+ transform_positions_to_local_field_coordinates,
26
27
  )
27
28
  from ert.substitutions import substitute_runpath_name
28
29
  from ert.utils import log_duration
29
30
 
30
31
  from ._str_to_bool import str_to_bool
31
- from .parameter_config import ParameterConfig, ParameterMetadata
32
+ from .parameter_config import ParameterConfig
32
33
  from .parsing import ConfigValidationError, ConfigWarning
33
34
 
34
35
  if TYPE_CHECKING:
@@ -67,32 +68,9 @@ def create_flattened_cube_graph(px: int, py: int, pz: int) -> nx.Graph[int]:
67
68
  return G
68
69
 
69
70
 
70
- def adjust_graph_for_masking(
71
- G: nx.Graph[int], mask: npt.NDArray[np.bool_]
72
- ) -> nx.Graph[int]:
73
- """
74
- Adjust the graph G according to the masking indices.
75
- Removes nodes specified by the mask and relabels the remaining nodes
76
- to have consecutive labels from 0 to G.number_of_nodes - 1.
77
- Parameters:
78
- - G: The graph to adjust
79
- - mask: Boolean mask flattened array
80
- Returns:
81
- - The adjusted graph
82
- """
83
- # Step 1: Remove nodes specified by mask_indices
84
- mask_indices = np.where(mask)[0]
85
- G.remove_nodes_from(mask_indices)
86
-
87
- # Step 2: Relabel remaining nodes to 0, 1, 2, ..., G.number_of_nodes - 1
88
- new_labels = {old_label: new_label for new_label, old_label in enumerate(G.nodes())}
89
- G = nx.relabel_nodes(G, new_labels, copy=True)
90
-
91
- return G
92
-
93
-
94
71
  class Field(ParameterConfig):
95
72
  type: Literal["field"] = "field"
73
+ dimensionality: Literal[3] = 3
96
74
  ertbox_params: ErtboxParameters
97
75
  file_format: FieldFileFormat
98
76
  output_transformation: str | None
@@ -102,31 +80,15 @@ class Field(ParameterConfig):
102
80
  forward_init_file: str
103
81
  output_file: Path
104
82
  grid_file: str
105
- mask_file: Path | None = None
106
83
 
107
84
  @field_serializer("output_file")
108
85
  def serialize_output_file(self, path: Path) -> str:
109
86
  return str(path)
110
87
 
111
- @field_serializer("mask_file")
112
- def serialize_mask_file(self, path: Path | None) -> str | None:
113
- return str(path) if path is not None else None
114
-
115
88
  @property
116
89
  def parameter_keys(self) -> list[str]:
117
90
  return []
118
91
 
119
- @property
120
- def metadata(self) -> list[ParameterMetadata]:
121
- return [
122
- ParameterMetadata(
123
- key=self.name,
124
- transformation=self.output_transformation,
125
- dimensionality=3,
126
- userdata={"data_origin": "FIELD", "ertbox_params": self.ertbox_params},
127
- )
128
- ]
129
-
130
92
  @classmethod
131
93
  def from_config_list(
132
94
  cls,
@@ -239,11 +201,7 @@ class Field(ParameterConfig):
239
201
  )
240
202
 
241
203
  def __len__(self) -> int:
242
- if self.mask_file is None:
243
- return self.ertbox_params.nx * self.ertbox_params.ny * self.ertbox_params.nz
244
-
245
- # Uses int() to convert to standard python int for mypy
246
- return int(np.size(self.mask) - np.count_nonzero(self.mask))
204
+ return self.ertbox_params.nx * self.ertbox_params.ny * self.ertbox_params.nz
247
205
 
248
206
  @log_duration(_logger, custom_name="load_field")
249
207
  def read_from_runpath(
@@ -258,7 +216,6 @@ class Field(ParameterConfig):
258
216
  read_field(
259
217
  run_path / file_name,
260
218
  self.name,
261
- self.mask,
262
219
  Shape(
263
220
  self.ertbox_params.nx,
264
221
  self.ertbox_params.ny,
@@ -294,16 +251,15 @@ class Field(ParameterConfig):
294
251
  from_data: npt.NDArray[np.float64],
295
252
  iens_active_index: npt.NDArray[np.int_],
296
253
  ) -> Iterator[tuple[int, xr.Dataset]]:
254
+ dim_nx, dim_ny, dim_nz = (
255
+ self.ertbox_params.nx,
256
+ self.ertbox_params.ny,
257
+ self.ertbox_params.nz,
258
+ )
259
+
297
260
  for i, realization in enumerate(iens_active_index):
298
- ma = np.ma.MaskedArray( # type: ignore
299
- data=np.zeros(self.mask.size),
300
- mask=self.mask,
301
- fill_value=np.nan,
302
- dtype=from_data.dtype,
303
- )
304
- ma[~ma.mask] = from_data[:, i]
305
- ma = ma.reshape(self.mask.shape) # type: ignore
306
- ds = xr.Dataset({"values": (["x", "y", "z"], ma.filled())})
261
+ values = from_data[:, i].reshape((dim_nx, dim_ny, dim_nz))
262
+ ds = xr.Dataset({"values": (["x", "y", "z"], values)})
307
263
  yield int(realization), ds
308
264
 
309
265
  def load_parameters(
@@ -314,7 +270,7 @@ class Field(ParameterConfig):
314
270
  ensemble_size = len(ds.realizations)
315
271
  da = xr.DataArray(
316
272
  [
317
- np.ma.MaskedArray(data=d, mask=self.mask).compressed() # type: ignore
273
+ np.ma.MaskedArray(data=d).compressed()
318
274
  for d in ds["values"].values.reshape(ensemble_size, -1)
319
275
  ]
320
276
  )
@@ -328,7 +284,7 @@ class Field(ParameterConfig):
328
284
  def _transform_data(
329
285
  self, data_array: xr.DataArray
330
286
  ) -> np.ma.MaskedArray[Any, np.dtype[np.float32]]:
331
- return np.ma.MaskedArray( # type: ignore
287
+ return np.ma.MaskedArray(
332
288
  _field_truncate(
333
289
  field_transform(
334
290
  data_array,
@@ -337,31 +293,18 @@ class Field(ParameterConfig):
337
293
  self.truncation_min,
338
294
  self.truncation_max,
339
295
  ),
340
- self.mask,
341
296
  fill_value=np.nan,
342
297
  )
343
298
 
344
- def save_experiment_data(self, experiment_path: Path) -> None:
345
- mask_path = experiment_path / "grid_mask.npy"
346
- if not mask_path.exists():
347
- mask, _ = read_mask(self.grid_file)
348
- np.save(mask_path, mask)
349
- self.mask_file = mask_path
350
-
351
- @cached_property
352
- def mask(self) -> Any:
353
- if self.mask_file is None:
354
- raise ValueError(
355
- "In order to get Field.mask, Field.save_experiment_data has"
356
- " to be called first"
357
- )
358
- return np.load(self.mask_file)
359
-
360
- def load_parameter_graph(self) -> nx.Graph: # type: ignore
299
+ def load_parameter_graph(self) -> nx.Graph[int]:
361
300
  parameter_graph = create_flattened_cube_graph(
362
301
  px=self.ertbox_params.nx, py=self.ertbox_params.ny, pz=self.ertbox_params.nz
363
302
  )
364
- return adjust_graph_for_masking(G=parameter_graph, mask=self.mask.flatten())
303
+ new_labels = {
304
+ old_label: new_label
305
+ for new_label, old_label in enumerate(parameter_graph.nodes())
306
+ }
307
+ return nx.relabel_nodes(parameter_graph, new_labels, copy=True)
365
308
 
366
309
  @property
367
310
  def nx(self) -> int:
@@ -375,8 +318,77 @@ class Field(ParameterConfig):
375
318
  def nz(self) -> int:
376
319
  return self.ertbox_params.nz
377
320
 
321
+ def calc_rho_for_2d_grid_layer(
322
+ self,
323
+ obs_xpos: npt.NDArray[np.float64],
324
+ obs_ypos: npt.NDArray[np.float64],
325
+ obs_main_range: npt.NDArray[np.float64],
326
+ obs_perp_range: npt.NDArray[np.float64],
327
+ obs_anisotropy_angle: npt.NDArray[np.float64],
328
+ right_handed_grid_indexing: bool = True,
329
+ ) -> npt.NDArray[np.float64]:
330
+ """Function to calculate scaling values to be used in the RHO matrix
331
+ for distance-based localization.
332
+
333
+ Args:
334
+ obs_xpos: x-coordinates in global coordinates of observations
335
+ obs_ypos: y-coordinates in global coordinates of observations
336
+ obs_main_range: Size of influence ellipse main principal direction.
337
+ obs_perp_range: Size of influence ellipse second principal direction.
338
+ obs_anisotropy_angle: Rotation angle anticlock wise of main principal
339
+ direction of influence ellipse relative to global coordinate
340
+ system's x-axis.
341
+ right_handed_grid_indexing: When this is True the field parameters
342
+ grid index order counts J-index down from ny-1 to 0.
343
+ If the value is False, the grid index order is to count J index
344
+ from 0 to ny-1. As standard for 3D field parameters,
345
+ the grid index order follows the right_handed grid indexing.
346
+
347
+ Returns:
348
+ Scaling values (elements of the RHO matrix) as a numpy array
349
+ of shape=(nx,ny,nobservations)
350
+
351
+ """
352
+ # Can only be used if ertbox coordinate system is defined
353
+ assert self.ertbox_params.xinc is not None, (
354
+ "Parameter for grid resolution must be defined"
355
+ )
356
+ assert self.ertbox_params.yinc is not None, (
357
+ "Parameter for grid resolution must be defined"
358
+ )
359
+ assert self.ertbox_params.origin is not None, (
360
+ "Parameter for grid origin must be defined"
361
+ )
362
+ assert self.ertbox_params.rotation_angle is not None, (
363
+ "Parameter for grid rotation must be defined"
364
+ )
365
+ # Transform positions of observations into local coordinates
366
+ xpos, ypos = transform_positions_to_local_field_coordinates(
367
+ self.ertbox_params.origin,
368
+ self.ertbox_params.rotation_angle,
369
+ obs_xpos,
370
+ obs_ypos,
371
+ )
372
+ # Transform localization ellipse orientation to local coordinates
373
+ ellipse_rotation = transform_local_ellipse_angle_to_local_coords(
374
+ self.ertbox_params.rotation_angle, obs_anisotropy_angle
375
+ )
376
+
377
+ return calc_rho_for_2d_grid_layer(
378
+ self.ertbox_params.nx,
379
+ self.ertbox_params.ny,
380
+ self.ertbox_params.xinc,
381
+ self.ertbox_params.yinc,
382
+ xpos,
383
+ ypos,
384
+ obs_main_range,
385
+ obs_perp_range,
386
+ ellipse_rotation,
387
+ right_handed_grid_indexing=right_handed_grid_indexing,
388
+ )
389
+
378
390
 
379
- TRANSFORM_FUNCTIONS = {
391
+ TRANSFORM_FUNCTIONS: Final[dict[str, Callable[[Any], Any]]] = {
380
392
  "LN": np.log,
381
393
  "LOG": np.log,
382
394
  "LN0": lambda v: np.log(v + 0.000001),
@@ -407,17 +419,14 @@ def field_transform(
407
419
  ) -> npt.NDArray[np.float32] | xr.DataArray:
408
420
  if transform_name is None:
409
421
  return data
410
- return TRANSFORM_FUNCTIONS[transform_name](data) # type: ignore
422
+ return TRANSFORM_FUNCTIONS[transform_name](data)
411
423
 
412
424
 
413
425
  def _field_truncate(data: npt.ArrayLike, min_: float | None, max_: float | None) -> Any:
414
426
  if min_ is not None and max_ is not None:
415
- vfunc = np.vectorize(lambda x: max(min(x, max_), min_))
416
- return vfunc(data)
427
+ return np.clip(data, min_, max_)
417
428
  elif min_ is not None:
418
- vfunc = np.vectorize(lambda x: max(x, min_))
419
- return vfunc(data)
429
+ return np.maximum(data, min_)
420
430
  elif max_ is not None:
421
- vfunc = np.vectorize(lambda x: min(x, max_))
422
- return vfunc(data)
431
+ return np.minimum(data, max_)
423
432
  return data