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
ert/config/__init__.py CHANGED
@@ -11,9 +11,8 @@ from .ensemble_config import EnsembleConfig
11
11
  from .ert_config import ErtConfig, forward_model_step_from_config_contents
12
12
  from .ert_plugin import ErtPlugin
13
13
  from .ert_script import ErtScript
14
- from .everest_constraints_config import EverestConstraintsConfig
15
- from .everest_objective_config import EverestObjectivesConfig
16
- from .ext_param_config import ExtParamConfig
14
+ from .everest_control import EverestControl, SamplerConfig
15
+ from .everest_response import EverestConstraintsConfig, EverestObjectivesConfig
17
16
  from .external_ert_script import ExternalErtScript
18
17
  from .field import Field, field_transform
19
18
  from .forward_model_step import (
@@ -23,12 +22,16 @@ from .forward_model_step import (
23
22
  ForwardModelStepPlugin,
24
23
  ForwardModelStepValidationError,
25
24
  ForwardModelStepWarning,
25
+ SiteInstalledForwardModelStep,
26
+ SiteOrUserForwardModelStep,
27
+ UserInstalledForwardModelStep,
26
28
  )
27
29
  from .gen_data_config import GenDataConfig
28
30
  from .gen_kw_config import DataSource, GenKwConfig, PriorDict
31
+ from .known_response_types import KnownResponseTypes
29
32
  from .lint_file import lint_file
30
33
  from .model_config import ModelConfig
31
- from .parameter_config import ParameterCardinality, ParameterConfig, ParameterMetadata
34
+ from .parameter_config import ParameterCardinality, ParameterConfig
32
35
  from .parsing import (
33
36
  ConfigValidationError,
34
37
  ConfigWarning,
@@ -45,6 +48,7 @@ from .queue_config import (
45
48
  QueueConfig,
46
49
  )
47
50
  from .response_config import InvalidResponseFile, ResponseConfig, ResponseMetadata
51
+ from .rft_config import RFTConfig
48
52
  from .summary_config import SummaryConfig
49
53
  from .surface_config import SurfaceConfig
50
54
  from .workflow import Workflow
@@ -63,6 +67,7 @@ from .workflow_fixtures import (
63
67
  fixtures_per_hook,
64
68
  )
65
69
  from .workflow_job import (
70
+ BaseErtScriptWorkflow,
66
71
  ErtScriptWorkflow,
67
72
  ExecutableWorkflow,
68
73
  WorkflowJob,
@@ -72,6 +77,7 @@ from .workflow_job import (
72
77
  __all__ = [
73
78
  "AnalysisConfig",
74
79
  "AnalysisModule",
80
+ "BaseErtScriptWorkflow",
75
81
  "ConfigValidationError",
76
82
  "ConfigWarning",
77
83
  "DataSource",
@@ -84,9 +90,9 @@ __all__ = [
84
90
  "ErtScript",
85
91
  "ErtScriptWorkflow",
86
92
  "EverestConstraintsConfig",
93
+ "EverestControl",
87
94
  "EverestObjectivesConfig",
88
95
  "ExecutableWorkflow",
89
- "ExtParamConfig",
90
96
  "ExternalErtScript",
91
97
  "Field",
92
98
  "ForwardModelStep",
@@ -103,6 +109,7 @@ __all__ = [
103
109
  "InversionTypeES",
104
110
  "KnownQueueOptions",
105
111
  "KnownQueueOptionsAdapter",
112
+ "KnownResponseTypes",
106
113
  "LegacyWorkflowConfigs",
107
114
  "LocalQueueOptions",
108
115
  "ModelConfig",
@@ -112,7 +119,6 @@ __all__ = [
112
119
  "OutlierSettings",
113
120
  "ParameterCardinality",
114
121
  "ParameterConfig",
115
- "ParameterMetadata",
116
122
  "PostExperimentFixtures",
117
123
  "PostSimulationFixtures",
118
124
  "PostUpdateFixtures",
@@ -123,10 +129,15 @@ __all__ = [
123
129
  "PriorDict",
124
130
  "QueueConfig",
125
131
  "QueueSystem",
132
+ "RFTConfig",
126
133
  "ResponseConfig",
127
134
  "ResponseMetadata",
135
+ "SamplerConfig",
136
+ "SiteInstalledForwardModelStep",
137
+ "SiteOrUserForwardModelStep",
128
138
  "SummaryConfig",
129
139
  "SurfaceConfig",
140
+ "UserInstalledForwardModelStep",
130
141
  "WarningInfo",
131
142
  "Workflow",
132
143
  "WorkflowConfigs",
@@ -7,8 +7,8 @@ from typing import TYPE_CHECKING, Any, assert_never
7
7
 
8
8
  import numpy as np
9
9
  import polars as pl
10
+ from resfo_utilities import history_key
10
11
 
11
- from ert.summary_key_type import history_key
12
12
  from ert.validation import rangestring_to_list
13
13
 
14
14
  from ._observations import (
@@ -18,6 +18,7 @@ from ._observations import (
18
18
  Observation,
19
19
  ObservationDate,
20
20
  ObservationError,
21
+ RFTObservation,
21
22
  SummaryObservation,
22
23
  )
23
24
  from .gen_data_config import GenDataConfig
@@ -28,18 +29,21 @@ from .parsing import (
28
29
  ObservationConfigError,
29
30
  )
30
31
  from .refcase import Refcase
32
+ from .rft_config import RFTConfig
31
33
 
32
34
  if TYPE_CHECKING:
33
35
  import numpy.typing as npt
34
36
 
35
37
 
36
38
  DEFAULT_TIME_DELTA = timedelta(seconds=30)
39
+ DEFAULT_LOCATION_RANGE_M = 3000
37
40
 
38
41
 
39
42
  def create_observation_dataframes(
40
43
  observations: Sequence[Observation],
41
44
  refcase: Refcase | None,
42
45
  gen_data_config: GenDataConfig | None,
46
+ rft_config: RFTConfig | None,
43
47
  time_map: list[datetime] | None,
44
48
  history: HistorySource,
45
49
  ) -> dict[str, pl.DataFrame]:
@@ -55,7 +59,6 @@ def create_observation_dataframes(
55
59
  config_errors: list[ErrorInfo] = []
56
60
  grouped: dict[str, list[pl.DataFrame]] = defaultdict(list)
57
61
  for obs in observations:
58
- obs_name = obs.name
59
62
  try:
60
63
  match obs:
61
64
  case HistoryObservation():
@@ -63,7 +66,7 @@ def create_observation_dataframes(
63
66
  _handle_history_observation(
64
67
  refcase,
65
68
  obs,
66
- obs_name,
69
+ obs.name,
67
70
  history,
68
71
  time_len,
69
72
  )
@@ -72,7 +75,7 @@ def create_observation_dataframes(
72
75
  grouped["summary"].append(
73
76
  _handle_summary_observation(
74
77
  obs,
75
- obs_name,
78
+ obs.name,
76
79
  obs_time_list,
77
80
  bool(refcase),
78
81
  )
@@ -82,11 +85,18 @@ def create_observation_dataframes(
82
85
  _handle_general_observation(
83
86
  gen_data_config,
84
87
  obs,
85
- obs_name,
88
+ obs.name,
86
89
  obs_time_list,
87
90
  bool(refcase),
88
91
  )
89
92
  )
93
+ case RFTObservation():
94
+ if rft_config is None:
95
+ raise TypeError(
96
+ "create_observation_dataframes requires "
97
+ "rft_config is not None when using RFTObservation"
98
+ )
99
+ grouped["rft"].append(_handle_rft_observation(rft_config, obs))
90
100
  case default:
91
101
  assert_never(default)
92
102
  except ObservationConfigError as err:
@@ -286,6 +296,43 @@ def _get_restart(
286
296
  ) from err
287
297
 
288
298
 
299
+ def _has_localization(summary_dict: SummaryObservation) -> bool:
300
+ return any(
301
+ [
302
+ summary_dict.location_x is not None,
303
+ summary_dict.location_y is not None,
304
+ summary_dict.location_range is not None,
305
+ ]
306
+ )
307
+
308
+
309
+ def _validate_localization_values(summary_dict: SummaryObservation) -> None:
310
+ """The user must provide LOCATION_X and LOCATION_Y to use localization, while
311
+ unprovided LOCATION_RANGE should default to some value.
312
+
313
+ This method assumes the summary dict contains at least one LOCATION key.
314
+ """
315
+ if summary_dict.location_x is None or summary_dict.location_y is None:
316
+ loc_values = {
317
+ "LOCATION_X": summary_dict.location_x,
318
+ "LOCATION_Y": summary_dict.location_y,
319
+ "LOCATION_RANGE": summary_dict.location_range,
320
+ }
321
+ provided_loc_values = {k: v for k, v in loc_values.items() if v is not None}
322
+
323
+ provided_loc_values_string = ", ".join(
324
+ key.upper() for key in provided_loc_values
325
+ )
326
+ raise ObservationConfigError.with_context(
327
+ f"Localization for observation {summary_dict.name} is misconfigured.\n"
328
+ f"Only {provided_loc_values_string} were provided. To enable "
329
+ f"localization for an observation, ensure that both LOCATION_X and "
330
+ f"LOCATION_Y are defined - or remove LOCATION keywords to disable "
331
+ f"localization.",
332
+ summary_dict,
333
+ )
334
+
335
+
289
336
  def _handle_summary_observation(
290
337
  summary_dict: SummaryObservation,
291
338
  obs_key: str,
@@ -323,15 +370,23 @@ def _handle_summary_observation(
323
370
  "Observation uncertainty must be strictly > 0", summary_key
324
371
  ) from None
325
372
 
326
- return pl.DataFrame(
327
- {
328
- "response_key": [summary_key],
329
- "observation_key": [obs_key],
330
- "time": pl.Series([date]).dt.cast_time_unit("ms"),
331
- "observations": pl.Series([value], dtype=pl.Float32),
332
- "std": pl.Series([std_dev], dtype=pl.Float32),
333
- }
334
- )
373
+ data_dict = {
374
+ "response_key": [summary_key],
375
+ "observation_key": [obs_key],
376
+ "time": pl.Series([date]).dt.cast_time_unit("ms"),
377
+ "observations": pl.Series([value], dtype=pl.Float32),
378
+ "std": pl.Series([std_dev], dtype=pl.Float32),
379
+ }
380
+
381
+ if _has_localization(summary_dict):
382
+ _validate_localization_values(summary_dict)
383
+ data_dict["location_x"] = summary_dict.location_x
384
+ data_dict["location_y"] = summary_dict.location_y
385
+ data_dict["location_range"] = (
386
+ summary_dict.location_range or DEFAULT_LOCATION_RANGE_M
387
+ )
388
+
389
+ return pl.DataFrame(data_dict)
335
390
 
336
391
 
337
392
  def _handle_general_observation(
@@ -414,10 +469,8 @@ def _handle_general_observation(
414
469
  stds = file_values[1::2]
415
470
 
416
471
  else:
417
- assert (
418
- general_observation.value is not None
419
- and general_observation.error is not None
420
- )
472
+ assert general_observation.value is not None
473
+ assert general_observation.error is not None
421
474
  values = np.array([general_observation.value])
422
475
  stds = np.array([general_observation.error])
423
476
 
@@ -439,9 +492,11 @@ def _handle_general_observation(
439
492
  raise ObservationConfigError.with_context(
440
493
  f"Values ({values}), error ({stds}) and "
441
494
  f"index list ({indices}) must be of equal length",
442
- general_observation.obs_file
443
- if general_observation.obs_file is not None
444
- else "",
495
+ (
496
+ general_observation.obs_file
497
+ if general_observation.obs_file is not None
498
+ else ""
499
+ ),
445
500
  )
446
501
 
447
502
  if np.any(stds <= 0):
@@ -461,3 +516,45 @@ def _handle_general_observation(
461
516
  "std": pl.Series(stds, dtype=pl.Float32),
462
517
  }
463
518
  )
519
+
520
+
521
+ def _handle_rft_observation(
522
+ rft_config: RFTConfig,
523
+ rft_observation: RFTObservation,
524
+ ) -> pl.DataFrame:
525
+ location = (rft_observation.east, rft_observation.north, rft_observation.tvd)
526
+ if location not in rft_config.locations:
527
+ rft_config.locations.append(location)
528
+
529
+ data_to_read = rft_config.data_to_read
530
+ if rft_observation.well not in data_to_read:
531
+ rft_config.data_to_read[rft_observation.well] = {}
532
+
533
+ well_dict = data_to_read[rft_observation.well]
534
+ if rft_observation.date not in well_dict:
535
+ well_dict[rft_observation.date] = []
536
+
537
+ property_list = well_dict[rft_observation.date]
538
+ if rft_observation.property not in property_list:
539
+ property_list.append(rft_observation.property)
540
+
541
+ if rft_observation.error <= 0.0:
542
+ raise ObservationConfigError.with_context(
543
+ "Observation uncertainty must be strictly > 0", rft_observation.well
544
+ )
545
+
546
+ return pl.DataFrame(
547
+ {
548
+ "response_key": (
549
+ f"{rft_observation.well}:"
550
+ f"{rft_observation.date}:"
551
+ f"{rft_observation.property}"
552
+ ),
553
+ "observation_key": rft_observation.name,
554
+ "east": pl.Series([location[0]], dtype=pl.Float32),
555
+ "north": pl.Series([location[1]], dtype=pl.Float32),
556
+ "tvd": pl.Series([location[2]], dtype=pl.Float32),
557
+ "observations": pl.Series([rft_observation.value], dtype=pl.Float32),
558
+ "std": pl.Series([rft_observation.error], dtype=pl.Float32),
559
+ }
560
+ )
@@ -156,7 +156,7 @@ class _Parser:
156
156
  def next_line(self) -> Iterator[str]: ...
157
157
 
158
158
  @overload
159
- def next_line(self, __default: T) -> Iterator[str] | T: ...
159
+ def next_line(self, __default: T, /) -> Iterator[str] | T: ...
160
160
 
161
161
  def next_line(self, *args: T) -> Iterator[str] | T:
162
162
  self.line_number += 1
@@ -90,10 +90,13 @@ class _SummaryValues:
90
90
  name: str
91
91
  value: float
92
92
  key: str #: The :term:`summary key` in the summary response
93
+ location_x: float | None = None
94
+ location_y: float | None = None
95
+ location_range: float | None = None
93
96
 
94
97
 
95
98
  @dataclass
96
- class SummaryObservation(ObservationDate, ObservationError, _SummaryValues):
99
+ class SummaryObservation(ObservationDate, _SummaryValues, ObservationError):
97
100
  @classmethod
98
101
  def from_obs_dict(cls, directory: str, observation_dict: ObservationDict) -> Self:
99
102
  error_mode = ErrorModes.ABS
@@ -101,6 +104,7 @@ class SummaryObservation(ObservationDate, ObservationError, _SummaryValues):
101
104
 
102
105
  date_dict: ObservationDate = ObservationDate()
103
106
  float_values: dict[str, float] = {"ERROR_MIN": 0.1}
107
+ localization_values: dict[str, float] = {}
104
108
  for key, value in observation_dict.items():
105
109
  match key:
106
110
  case "type" | "name":
@@ -121,6 +125,12 @@ class SummaryObservation(ObservationDate, ObservationError, _SummaryValues):
121
125
  summary_key = value
122
126
  case "DATE":
123
127
  date_dict.date = value
128
+ case "LOCATION_X":
129
+ localization_values["x"] = validate_float(value, key)
130
+ case "LOCATION_Y":
131
+ localization_values["y"] = validate_float(value, key)
132
+ case "LOCATION_RANGE":
133
+ localization_values["range"] = validate_float(value, key)
124
134
  case _:
125
135
  raise _unknown_key_error(str(key), observation_dict["name"])
126
136
  if "VALUE" not in float_values:
@@ -137,6 +147,9 @@ class SummaryObservation(ObservationDate, ObservationError, _SummaryValues):
137
147
  error_min=float_values["ERROR_MIN"],
138
148
  key=summary_key,
139
149
  value=float_values["VALUE"],
150
+ location_x=localization_values.get("x"),
151
+ location_y=localization_values.get("y"),
152
+ location_range=localization_values.get("range"),
140
153
  **date_dict.__dict__,
141
154
  )
142
155
 
@@ -201,12 +214,88 @@ class GeneralObservation(ObservationDate, _GeneralObservation):
201
214
  return output
202
215
 
203
216
 
204
- Observation = HistoryObservation | SummaryObservation | GeneralObservation
217
+ @dataclass
218
+ class RFTObservation:
219
+ name: str
220
+ well: str
221
+ date: str
222
+ property: str
223
+ value: float
224
+ error: float
225
+ north: float
226
+ east: float
227
+ tvd: float
228
+
229
+ @classmethod
230
+ def from_obs_dict(cls, directory: str, observation_dict: ObservationDict) -> Self:
231
+ well = None
232
+ observed_property = None
233
+ observed_value = None
234
+ error = None
235
+ date = None
236
+ north = None
237
+ east = None
238
+ tvd = None
239
+ for key, value in observation_dict.items():
240
+ match key:
241
+ case "type" | "name":
242
+ pass
243
+ case "WELL":
244
+ well = value
245
+ case "PROPERTY":
246
+ observed_property = value
247
+ case "VALUE":
248
+ observed_value = validate_float(value, key)
249
+ case "ERROR":
250
+ error = validate_float(value, key)
251
+ case "DATE":
252
+ date = value
253
+ case "NORTH":
254
+ north = validate_float(value, key)
255
+ case "EAST":
256
+ east = validate_float(value, key)
257
+ case "TVD":
258
+ tvd = validate_float(value, key)
259
+ case _:
260
+ raise _unknown_key_error(str(key), observation_dict["name"])
261
+ if well is None:
262
+ raise _missing_value_error(observation_dict["name"], "WELL")
263
+ if observed_value is None:
264
+ raise _missing_value_error(observation_dict["name"], "VALUE")
265
+ if observed_property is None:
266
+ raise _missing_value_error(observation_dict["name"], "PROPERTY")
267
+ if error is None:
268
+ raise _missing_value_error(observation_dict["name"], "ERROR")
269
+ if date is None:
270
+ raise _missing_value_error(observation_dict["name"], "DATE")
271
+ if north is None:
272
+ raise _missing_value_error(observation_dict["name"], "NORTH")
273
+ if east is None:
274
+ raise _missing_value_error(observation_dict["name"], "EAST")
275
+ if tvd is None:
276
+ raise _missing_value_error(observation_dict["name"], "TVD")
277
+ return cls(
278
+ observation_dict["name"],
279
+ well,
280
+ date,
281
+ observed_property,
282
+ observed_value,
283
+ error,
284
+ north,
285
+ east,
286
+ tvd,
287
+ )
288
+
289
+
290
+ Observation = (
291
+ HistoryObservation | SummaryObservation | GeneralObservation | RFTObservation
292
+ )
205
293
 
206
294
  _TYPE_TO_CLASS: dict[ObservationType, type[Observation]] = {
207
295
  ObservationType.HISTORY: HistoryObservation,
208
296
  ObservationType.SUMMARY: SummaryObservation,
209
297
  ObservationType.GENERAL: GeneralObservation,
298
+ ObservationType.RFT: RFTObservation,
210
299
  }
211
300
 
212
301