ert 19.0.0rc1__py3-none-any.whl → 19.0.0rc3__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.
- ert/__main__.py +63 -94
- ert/analysis/_es_update.py +14 -11
- ert/config/_create_observation_dataframes.py +262 -23
- ert/config/_observations.py +153 -181
- ert/config/_read_summary.py +5 -4
- ert/config/ert_config.py +56 -1
- ert/config/parsing/observations_parser.py +0 -6
- ert/config/rft_config.py +1 -1
- ert/dark_storage/compute/__init__.py +0 -0
- ert/dark_storage/compute/misfits.py +42 -0
- ert/dark_storage/endpoints/__init__.py +2 -0
- ert/dark_storage/endpoints/compute/__init__.py +0 -0
- ert/dark_storage/endpoints/compute/misfits.py +95 -0
- ert/dark_storage/endpoints/experiments.py +3 -0
- ert/dark_storage/json_schema/experiment.py +1 -0
- ert/gui/main_window.py +0 -2
- ert/gui/tools/manage_experiments/export_dialog.py +0 -4
- ert/gui/tools/manage_experiments/storage_info_widget.py +5 -1
- ert/gui/tools/plot/plot_api.py +10 -10
- ert/gui/tools/plot/plot_widget.py +0 -5
- ert/gui/tools/plot/plot_window.py +1 -1
- ert/services/__init__.py +3 -7
- ert/services/_base_service.py +387 -0
- ert/services/_storage_main.py +22 -59
- ert/services/ert_server.py +24 -186
- ert/services/webviz_ert_service.py +20 -0
- ert/shared/storage/command.py +38 -0
- ert/shared/storage/extraction.py +42 -0
- ert/shared/version.py +3 -3
- ert/storage/local_ensemble.py +95 -2
- ert/storage/local_experiment.py +16 -0
- ert/storage/local_storage.py +1 -3
- ert/utils/__init__.py +0 -20
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/METADATA +2 -2
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/RECORD +46 -41
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/WHEEL +1 -1
- everest/bin/everest_script.py +5 -5
- everest/bin/kill_script.py +2 -2
- everest/bin/monitor_script.py +2 -2
- everest/bin/utils.py +4 -4
- everest/detached/everserver.py +6 -6
- everest/gui/main_window.py +2 -2
- everest/util/__init__.py +19 -1
- ert/config/observation_config_migrations.py +0 -793
- ert/storage/migration/to22.py +0 -18
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/entry_points.txt +0 -0
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/licenses/COPYING +0 -0
- {ert-19.0.0rc1.dist-info → ert-19.0.0rc3.dist-info}/top_level.txt +0 -0
ert/config/_observations.py
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
1
|
import os
|
|
5
2
|
from collections.abc import Sequence
|
|
6
3
|
from dataclasses import dataclass
|
|
7
4
|
from enum import StrEnum
|
|
5
|
+
from itertools import starmap
|
|
8
6
|
from typing import Any, Self
|
|
9
7
|
|
|
10
|
-
import pandas as pd
|
|
11
|
-
|
|
12
8
|
from .parsing import (
|
|
13
9
|
ErrorInfo,
|
|
14
10
|
ObservationConfigError,
|
|
@@ -16,8 +12,6 @@ from .parsing import (
|
|
|
16
12
|
ObservationType,
|
|
17
13
|
)
|
|
18
14
|
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
15
|
|
|
22
16
|
class ErrorModes(StrEnum):
|
|
23
17
|
REL = "REL"
|
|
@@ -25,52 +19,102 @@ class ErrorModes(StrEnum):
|
|
|
25
19
|
RELMIN = "RELMIN"
|
|
26
20
|
|
|
27
21
|
|
|
22
|
+
@dataclass
|
|
23
|
+
class ObservationError:
|
|
24
|
+
error_mode: ErrorModes
|
|
25
|
+
error: float
|
|
26
|
+
error_min: float
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Segment(ObservationError):
|
|
31
|
+
name: str
|
|
32
|
+
start: int
|
|
33
|
+
stop: int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class HistoryObservation(ObservationError):
|
|
38
|
+
name: str
|
|
39
|
+
segments: list[Segment]
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def key(self) -> str:
|
|
43
|
+
"""The :term:`summary key` to be fetched from :ref:`refcase`."""
|
|
44
|
+
# For history observations the key is also the name, ie.
|
|
45
|
+
# "HISTORY_OBSERVATION FOPR" means to add the values from
|
|
46
|
+
# the summary vector FOPRH in refcase as observations.
|
|
47
|
+
return self.name
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_obs_dict(cls, directory: str, observation_dict: ObservationDict) -> Self:
|
|
51
|
+
error_mode = ErrorModes.RELMIN
|
|
52
|
+
error = 0.1
|
|
53
|
+
error_min = 0.1
|
|
54
|
+
segments = []
|
|
55
|
+
for key, value in observation_dict.items():
|
|
56
|
+
match key:
|
|
57
|
+
case "type" | "name":
|
|
58
|
+
pass
|
|
59
|
+
case "ERROR":
|
|
60
|
+
error = validate_positive_float(value, key)
|
|
61
|
+
case "ERROR_MIN":
|
|
62
|
+
error_min = validate_positive_float(value, key)
|
|
63
|
+
case "ERROR_MODE":
|
|
64
|
+
error_mode = validate_error_mode(value)
|
|
65
|
+
case "segments":
|
|
66
|
+
segments = list(starmap(_validate_segment_dict, value))
|
|
67
|
+
case _:
|
|
68
|
+
raise _unknown_key_error(str(key), observation_dict["name"])
|
|
69
|
+
|
|
70
|
+
return cls(
|
|
71
|
+
name=observation_dict["name"],
|
|
72
|
+
error_mode=error_mode,
|
|
73
|
+
error=error,
|
|
74
|
+
error_min=error_min,
|
|
75
|
+
segments=segments,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ObservationDate:
|
|
81
|
+
days: float | None = None
|
|
82
|
+
hours: float | None = None
|
|
83
|
+
date: str | None = None
|
|
84
|
+
restart: int | None = None
|
|
85
|
+
|
|
86
|
+
|
|
28
87
|
@dataclass
|
|
29
88
|
class _SummaryValues:
|
|
30
89
|
name: str
|
|
31
90
|
value: float
|
|
32
91
|
key: str #: The :term:`summary key` in the summary response
|
|
33
|
-
date: str
|
|
34
92
|
location_x: float | None = None
|
|
35
93
|
location_y: float | None = None
|
|
36
94
|
location_range: float | None = None
|
|
37
95
|
|
|
38
96
|
|
|
39
97
|
@dataclass
|
|
40
|
-
class ObservationError:
|
|
41
|
-
error_mode: ErrorModes
|
|
42
|
-
error: float
|
|
43
|
-
error_min: float
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@dataclass
|
|
47
|
-
class SummaryObservation(_SummaryValues, ObservationError):
|
|
98
|
+
class SummaryObservation(ObservationDate, _SummaryValues, ObservationError):
|
|
48
99
|
@classmethod
|
|
49
|
-
def from_obs_dict(
|
|
50
|
-
cls, directory: str, observation_dict: ObservationDict
|
|
51
|
-
) -> list[Self]:
|
|
100
|
+
def from_obs_dict(cls, directory: str, observation_dict: ObservationDict) -> Self:
|
|
52
101
|
error_mode = ErrorModes.ABS
|
|
53
102
|
summary_key = None
|
|
54
103
|
|
|
55
|
-
|
|
104
|
+
date_dict: ObservationDate = ObservationDate()
|
|
56
105
|
float_values: dict[str, float] = {"ERROR_MIN": 0.1}
|
|
57
|
-
localization_values: dict[str, float
|
|
106
|
+
localization_values: dict[str, float] = {}
|
|
58
107
|
for key, value in observation_dict.items():
|
|
59
108
|
match key:
|
|
60
109
|
case "type" | "name":
|
|
61
110
|
pass
|
|
111
|
+
case "RESTART":
|
|
112
|
+
date_dict.restart = validate_positive_int(value, key)
|
|
62
113
|
case "ERROR" | "ERROR_MIN":
|
|
63
114
|
float_values[str(key)] = validate_positive_float(value, key)
|
|
64
|
-
case "DAYS" | "HOURS"
|
|
65
|
-
|
|
66
|
-
(
|
|
67
|
-
"SUMMARY_OBSERVATION must use DATE to specify "
|
|
68
|
-
"date, DAYS | HOURS is no longer allowed. "
|
|
69
|
-
"Please run:\n ert convert_observations "
|
|
70
|
-
"<your_ert_config.ert>\nto migrate the observation config "
|
|
71
|
-
"to use the correct format."
|
|
72
|
-
),
|
|
73
|
-
key,
|
|
115
|
+
case "DAYS" | "HOURS":
|
|
116
|
+
setattr(
|
|
117
|
+
date_dict, str(key).lower(), validate_positive_float(value, key)
|
|
74
118
|
)
|
|
75
119
|
case "VALUE":
|
|
76
120
|
float_values[str(key)] = validate_float(value, key)
|
|
@@ -79,16 +123,13 @@ class SummaryObservation(_SummaryValues, ObservationError):
|
|
|
79
123
|
case "KEY":
|
|
80
124
|
summary_key = value
|
|
81
125
|
case "DATE":
|
|
82
|
-
date = value
|
|
83
|
-
case "
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
localization_values["y"] = validate_float(value
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if "RADIUS" in value
|
|
90
|
-
else None
|
|
91
|
-
)
|
|
126
|
+
date_dict.date = value
|
|
127
|
+
case "LOCATION_X":
|
|
128
|
+
localization_values["x"] = validate_float(value, key)
|
|
129
|
+
case "LOCATION_Y":
|
|
130
|
+
localization_values["y"] = validate_float(value, key)
|
|
131
|
+
case "LOCATION_RANGE":
|
|
132
|
+
localization_values["range"] = validate_float(value, key)
|
|
92
133
|
case _:
|
|
93
134
|
raise _unknown_key_error(str(key), observation_dict["name"])
|
|
94
135
|
if "VALUE" not in float_values:
|
|
@@ -98,21 +139,18 @@ class SummaryObservation(_SummaryValues, ObservationError):
|
|
|
98
139
|
if "ERROR" not in float_values:
|
|
99
140
|
raise _missing_value_error(observation_dict["name"], "ERROR")
|
|
100
141
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
date=date,
|
|
114
|
-
)
|
|
115
|
-
]
|
|
142
|
+
return cls(
|
|
143
|
+
name=observation_dict["name"],
|
|
144
|
+
error_mode=error_mode,
|
|
145
|
+
error=float_values["ERROR"],
|
|
146
|
+
error_min=float_values["ERROR_MIN"],
|
|
147
|
+
key=summary_key,
|
|
148
|
+
value=float_values["VALUE"],
|
|
149
|
+
location_x=localization_values.get("x"),
|
|
150
|
+
location_y=localization_values.get("y"),
|
|
151
|
+
location_range=localization_values.get("range"),
|
|
152
|
+
**date_dict.__dict__,
|
|
153
|
+
)
|
|
116
154
|
|
|
117
155
|
|
|
118
156
|
@dataclass
|
|
@@ -124,15 +162,12 @@ class _GeneralObservation:
|
|
|
124
162
|
index_list: str | None = None
|
|
125
163
|
index_file: str | None = None
|
|
126
164
|
obs_file: str | None = None
|
|
127
|
-
restart: int | None = None
|
|
128
165
|
|
|
129
166
|
|
|
130
167
|
@dataclass
|
|
131
|
-
class GeneralObservation(_GeneralObservation):
|
|
168
|
+
class GeneralObservation(ObservationDate, _GeneralObservation):
|
|
132
169
|
@classmethod
|
|
133
|
-
def from_obs_dict(
|
|
134
|
-
cls, directory: str, observation_dict: ObservationDict
|
|
135
|
-
) -> list[Self]:
|
|
170
|
+
def from_obs_dict(cls, directory: str, observation_dict: ObservationDict) -> Self:
|
|
136
171
|
try:
|
|
137
172
|
data = observation_dict["DATA"]
|
|
138
173
|
except KeyError as err:
|
|
@@ -147,22 +182,12 @@ class GeneralObservation(_GeneralObservation):
|
|
|
147
182
|
output.restart = validate_positive_int(value, key)
|
|
148
183
|
case "VALUE":
|
|
149
184
|
output.value = validate_float(value, key)
|
|
150
|
-
case "ERROR":
|
|
185
|
+
case "ERROR" | "DAYS" | "HOURS":
|
|
151
186
|
setattr(
|
|
152
187
|
output, str(key).lower(), validate_positive_float(value, key)
|
|
153
188
|
)
|
|
154
|
-
case "DATE" | "
|
|
155
|
-
|
|
156
|
-
(
|
|
157
|
-
"GENERAL_OBSERVATION must use RESTART to specify "
|
|
158
|
-
"report step. Please run:\n ert convert_observations "
|
|
159
|
-
"<your_ert_config.ert>\nto migrate the observation config "
|
|
160
|
-
"to use the correct format."
|
|
161
|
-
),
|
|
162
|
-
key,
|
|
163
|
-
)
|
|
164
|
-
case "INDEX_LIST":
|
|
165
|
-
output.index_list = value
|
|
189
|
+
case "DATE" | "INDEX_LIST":
|
|
190
|
+
setattr(output, str(key).lower(), value)
|
|
166
191
|
case "OBS_FILE" | "INDEX_FILE":
|
|
167
192
|
assert not isinstance(key, tuple)
|
|
168
193
|
filename = value
|
|
@@ -185,8 +210,7 @@ class GeneralObservation(_GeneralObservation):
|
|
|
185
210
|
f" VALUE = {output.value}, ERROR must also be given.",
|
|
186
211
|
observation_dict["name"],
|
|
187
212
|
)
|
|
188
|
-
|
|
189
|
-
return [output]
|
|
213
|
+
return output
|
|
190
214
|
|
|
191
215
|
|
|
192
216
|
@dataclass
|
|
@@ -202,63 +226,7 @@ class RFTObservation:
|
|
|
202
226
|
tvd: float
|
|
203
227
|
|
|
204
228
|
@classmethod
|
|
205
|
-
def
|
|
206
|
-
cls,
|
|
207
|
-
directory: str,
|
|
208
|
-
observation_dict: ObservationDict,
|
|
209
|
-
filename: str,
|
|
210
|
-
observed_property: str = "PRESSURE",
|
|
211
|
-
) -> list[Self]:
|
|
212
|
-
if not os.path.isabs(filename):
|
|
213
|
-
filename = os.path.join(directory, filename)
|
|
214
|
-
if not os.path.exists(filename):
|
|
215
|
-
raise ObservationConfigError.with_context(
|
|
216
|
-
f"The CSV file ({filename}) does not exist or is not accessible.",
|
|
217
|
-
filename,
|
|
218
|
-
)
|
|
219
|
-
csv_file = pd.read_csv(
|
|
220
|
-
filename,
|
|
221
|
-
encoding="utf-8",
|
|
222
|
-
on_bad_lines="error",
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
required_columns = {
|
|
226
|
-
"WELL_NAME",
|
|
227
|
-
"DATE",
|
|
228
|
-
observed_property,
|
|
229
|
-
"ERROR",
|
|
230
|
-
"NORTH",
|
|
231
|
-
"EAST",
|
|
232
|
-
"TVD",
|
|
233
|
-
}
|
|
234
|
-
missing_required_columns = required_columns - set(csv_file.keys())
|
|
235
|
-
if missing_required_columns:
|
|
236
|
-
raise ObservationConfigError.with_context(
|
|
237
|
-
f"The rft observations file {filename} is missing required column(s) "
|
|
238
|
-
f"{', '.join(sorted(missing_required_columns))}.",
|
|
239
|
-
filename,
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
return [
|
|
243
|
-
cls(
|
|
244
|
-
f"{observation_dict['name']}[{row.Index}]",
|
|
245
|
-
str(row.WELL_NAME),
|
|
246
|
-
str(row.DATE),
|
|
247
|
-
observed_property,
|
|
248
|
-
validate_float(str(getattr(row, observed_property)), observed_property),
|
|
249
|
-
validate_float(str(row.ERROR), "ERROR"),
|
|
250
|
-
validate_float(str(row.NORTH), "NORTH"),
|
|
251
|
-
validate_float(str(row.EAST), "EAST"),
|
|
252
|
-
validate_float(str(row.TVD), "TVD"),
|
|
253
|
-
)
|
|
254
|
-
for row in csv_file.itertuples(index=True)
|
|
255
|
-
]
|
|
256
|
-
|
|
257
|
-
@classmethod
|
|
258
|
-
def from_obs_dict(
|
|
259
|
-
cls, directory: str, observation_dict: ObservationDict
|
|
260
|
-
) -> list[Self]:
|
|
261
|
-
csv_filename = None
|
|
229
|
+
def from_obs_dict(cls, directory: str, observation_dict: ObservationDict) -> Self:
|
|
262
230
|
well = None
|
|
263
231
|
observed_property = None
|
|
264
232
|
observed_value = None
|
|
@@ -287,17 +255,8 @@ class RFTObservation:
|
|
|
287
255
|
east = validate_float(value, key)
|
|
288
256
|
case "TVD":
|
|
289
257
|
tvd = validate_float(value, key)
|
|
290
|
-
case "CSV":
|
|
291
|
-
csv_filename = value
|
|
292
258
|
case _:
|
|
293
259
|
raise _unknown_key_error(str(key), observation_dict["name"])
|
|
294
|
-
if csv_filename is not None:
|
|
295
|
-
return cls.from_csv(
|
|
296
|
-
directory,
|
|
297
|
-
observation_dict,
|
|
298
|
-
csv_filename,
|
|
299
|
-
observed_property or "PRESSURE",
|
|
300
|
-
)
|
|
301
260
|
if well is None:
|
|
302
261
|
raise _missing_value_error(observation_dict["name"], "WELL")
|
|
303
262
|
if observed_value is None:
|
|
@@ -314,24 +273,25 @@ class RFTObservation:
|
|
|
314
273
|
raise _missing_value_error(observation_dict["name"], "EAST")
|
|
315
274
|
if tvd is None:
|
|
316
275
|
raise _missing_value_error(observation_dict["name"], "TVD")
|
|
317
|
-
return
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
329
|
-
]
|
|
276
|
+
return cls(
|
|
277
|
+
observation_dict["name"],
|
|
278
|
+
well,
|
|
279
|
+
date,
|
|
280
|
+
observed_property,
|
|
281
|
+
observed_value,
|
|
282
|
+
error,
|
|
283
|
+
north,
|
|
284
|
+
east,
|
|
285
|
+
tvd,
|
|
286
|
+
)
|
|
330
287
|
|
|
331
288
|
|
|
332
|
-
Observation =
|
|
289
|
+
Observation = (
|
|
290
|
+
HistoryObservation | SummaryObservation | GeneralObservation | RFTObservation
|
|
291
|
+
)
|
|
333
292
|
|
|
334
293
|
_TYPE_TO_CLASS: dict[ObservationType, type[Observation]] = {
|
|
294
|
+
ObservationType.HISTORY: HistoryObservation,
|
|
335
295
|
ObservationType.SUMMARY: SummaryObservation,
|
|
336
296
|
ObservationType.GENERAL: GeneralObservation,
|
|
337
297
|
ObservationType.RFT: RFTObservation,
|
|
@@ -349,20 +309,10 @@ def make_observations(
|
|
|
349
309
|
inp: The collection of statements to validate.
|
|
350
310
|
"""
|
|
351
311
|
result: list[Observation] = []
|
|
352
|
-
error_list: list[ErrorInfo
|
|
312
|
+
error_list: list[ErrorInfo] = []
|
|
353
313
|
for obs_dict in observation_dicts:
|
|
354
|
-
if obs_dict["type"] == ObservationType.HISTORY:
|
|
355
|
-
msg = (
|
|
356
|
-
"HISTORY_OBSERVATION is deprecated, and must be specified "
|
|
357
|
-
"as SUMMARY_OBSERVATION. Run"
|
|
358
|
-
" ert convert_observations <ert_config.ert> to convert your "
|
|
359
|
-
"observations automatically"
|
|
360
|
-
)
|
|
361
|
-
logger.error(msg)
|
|
362
|
-
error_list.append(ObservationConfigError(msg))
|
|
363
|
-
continue
|
|
364
314
|
try:
|
|
365
|
-
result.
|
|
315
|
+
result.append(
|
|
366
316
|
_TYPE_TO_CLASS[obs_dict["type"]].from_obs_dict(directory, obs_dict)
|
|
367
317
|
)
|
|
368
318
|
except KeyError as err:
|
|
@@ -376,6 +326,41 @@ def make_observations(
|
|
|
376
326
|
return result
|
|
377
327
|
|
|
378
328
|
|
|
329
|
+
def _validate_segment_dict(name_token: str, inp: dict[str, Any]) -> Segment:
|
|
330
|
+
start = None
|
|
331
|
+
stop = None
|
|
332
|
+
error_mode = ErrorModes.RELMIN
|
|
333
|
+
error = 0.1
|
|
334
|
+
error_min = 0.1
|
|
335
|
+
for key, value in inp.items():
|
|
336
|
+
match key:
|
|
337
|
+
case "START":
|
|
338
|
+
start = validate_int(value, key)
|
|
339
|
+
case "STOP":
|
|
340
|
+
stop = validate_int(value, key)
|
|
341
|
+
case "ERROR":
|
|
342
|
+
error = validate_positive_float(value, key)
|
|
343
|
+
case "ERROR_MIN":
|
|
344
|
+
error_min = validate_positive_float(value, key)
|
|
345
|
+
case "ERROR_MODE":
|
|
346
|
+
error_mode = validate_error_mode(value)
|
|
347
|
+
case _:
|
|
348
|
+
raise _unknown_key_error(key, name_token)
|
|
349
|
+
|
|
350
|
+
if start is None:
|
|
351
|
+
raise _missing_value_error(name_token, "START")
|
|
352
|
+
if stop is None:
|
|
353
|
+
raise _missing_value_error(name_token, "STOP")
|
|
354
|
+
return Segment(
|
|
355
|
+
name=name_token,
|
|
356
|
+
start=start,
|
|
357
|
+
stop=stop,
|
|
358
|
+
error_mode=error_mode,
|
|
359
|
+
error=error,
|
|
360
|
+
error_min=error_min,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
379
364
|
def validate_error_mode(inp: str) -> ErrorModes:
|
|
380
365
|
if inp == "REL":
|
|
381
366
|
return ErrorModes.REL
|
|
@@ -413,19 +398,6 @@ def validate_positive_float(val: str, key: str) -> float:
|
|
|
413
398
|
return v
|
|
414
399
|
|
|
415
400
|
|
|
416
|
-
def validate_localization(val: dict[str, Any], obs_name: str) -> None:
|
|
417
|
-
errors = []
|
|
418
|
-
if "EAST" not in val:
|
|
419
|
-
errors.append(_missing_value_error(f"LOCALIZATION for {obs_name}", "EAST"))
|
|
420
|
-
if "NORTH" not in val:
|
|
421
|
-
errors.append(_missing_value_error(f"LOCALIZATION for {obs_name}", "NORTH"))
|
|
422
|
-
for key in val:
|
|
423
|
-
if key not in {"EAST", "NORTH", "RADIUS"}:
|
|
424
|
-
errors.append(_unknown_key_error(key, f"LOCALIZATION for {obs_name}"))
|
|
425
|
-
if errors:
|
|
426
|
-
raise ObservationConfigError.from_collected(errors)
|
|
427
|
-
|
|
428
|
-
|
|
429
401
|
def validate_positive_int(val: str, key: str) -> int:
|
|
430
402
|
try:
|
|
431
403
|
v = int(val)
|
ert/config/_read_summary.py
CHANGED
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import fnmatch
|
|
10
10
|
import re
|
|
11
|
+
import warnings
|
|
11
12
|
from collections.abc import Callable, Sequence
|
|
12
13
|
from datetime import datetime, timedelta
|
|
13
14
|
from enum import Enum, auto
|
|
@@ -157,10 +158,10 @@ def _read_spec(
|
|
|
157
158
|
if kw.summary_variable == "TIME":
|
|
158
159
|
date_index = i
|
|
159
160
|
date_unit_str = kw.unit
|
|
160
|
-
except InvalidSummaryKeyError:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
except InvalidSummaryKeyError as err:
|
|
162
|
+
warnings.warn(
|
|
163
|
+
f"Found {err} in summary specification, key not loaded", stacklevel=2
|
|
164
|
+
)
|
|
164
165
|
continue
|
|
165
166
|
|
|
166
167
|
if should_load_key(key):
|
ert/config/ert_config.py
CHANGED
|
@@ -7,6 +7,7 @@ import pprint
|
|
|
7
7
|
import re
|
|
8
8
|
from collections import Counter, defaultdict
|
|
9
9
|
from collections.abc import Mapping
|
|
10
|
+
from datetime import datetime
|
|
10
11
|
from functools import cached_property
|
|
11
12
|
from os import path
|
|
12
13
|
from pathlib import Path
|
|
@@ -22,6 +23,7 @@ from ert.substitutions import Substitutions
|
|
|
22
23
|
from ._create_observation_dataframes import create_observation_dataframes
|
|
23
24
|
from ._design_matrix_validator import DesignMatrixValidator
|
|
24
25
|
from ._observations import (
|
|
26
|
+
HistoryObservation,
|
|
25
27
|
Observation,
|
|
26
28
|
RFTObservation,
|
|
27
29
|
SummaryObservation,
|
|
@@ -50,6 +52,7 @@ from .parsing import (
|
|
|
50
52
|
ConfigWarning,
|
|
51
53
|
ErrorInfo,
|
|
52
54
|
ForwardModelStepKeys,
|
|
55
|
+
HistorySource,
|
|
53
56
|
HookRuntime,
|
|
54
57
|
ObservationConfigError,
|
|
55
58
|
init_forward_model_schema,
|
|
@@ -59,6 +62,7 @@ from .parsing import (
|
|
|
59
62
|
)
|
|
60
63
|
from .parsing.observations_parser import ObservationDict
|
|
61
64
|
from .queue_config import KnownQueueOptions, QueueConfig
|
|
65
|
+
from .refcase import Refcase
|
|
62
66
|
from .rft_config import RFTConfig
|
|
63
67
|
from .workflow import Workflow
|
|
64
68
|
from .workflow_fixtures import fixtures_per_hook
|
|
@@ -100,6 +104,23 @@ def _seed_sequence(seed: int | None) -> int:
|
|
|
100
104
|
return int_seed
|
|
101
105
|
|
|
102
106
|
|
|
107
|
+
def _read_time_map(file_contents: str) -> list[datetime]:
|
|
108
|
+
def str_to_datetime(date_str: str) -> datetime:
|
|
109
|
+
try:
|
|
110
|
+
return datetime.fromisoformat(date_str)
|
|
111
|
+
except ValueError:
|
|
112
|
+
logger.warning(
|
|
113
|
+
"DD/MM/YYYY date format is deprecated"
|
|
114
|
+
", please use ISO date format YYYY-MM-DD."
|
|
115
|
+
)
|
|
116
|
+
return datetime.strptime(date_str, "%d/%m/%Y")
|
|
117
|
+
|
|
118
|
+
dates = []
|
|
119
|
+
for line in file_contents.splitlines():
|
|
120
|
+
dates.append(str_to_datetime(line.strip()))
|
|
121
|
+
return dates
|
|
122
|
+
|
|
123
|
+
|
|
103
124
|
def create_forward_model_json(
|
|
104
125
|
context: dict[str, str],
|
|
105
126
|
forward_model_steps: list[ForwardModelStep],
|
|
@@ -667,6 +688,14 @@ def log_observation_keys(
|
|
|
667
688
|
if key not in {"name", "type"}
|
|
668
689
|
)
|
|
669
690
|
|
|
691
|
+
if "HISTORY_OBSERVATION" in observation_type_counts:
|
|
692
|
+
msg = (
|
|
693
|
+
"HISTORY_OBSERVATION is deprecated and will be removed. "
|
|
694
|
+
"Please use SUMMARY_OBSERVATION instead."
|
|
695
|
+
)
|
|
696
|
+
ConfigWarning.warn(msg)
|
|
697
|
+
logger.warning(msg)
|
|
698
|
+
|
|
670
699
|
logger.info(
|
|
671
700
|
f"Count of observation types:\n\t{dict(observation_type_counts)}\n"
|
|
672
701
|
f"Count of observation keywords:\n\t{dict(observation_keyword_counts)}"
|
|
@@ -712,6 +741,9 @@ class ErtConfig(BaseModel):
|
|
|
712
741
|
user_config_file: str = "no_config"
|
|
713
742
|
config_path: str = Field(init=False, default="")
|
|
714
743
|
observation_declarations: list[Observation] = Field(default_factory=list)
|
|
744
|
+
time_map: list[datetime] | None = None
|
|
745
|
+
history_source: HistorySource = HistorySource.REFCASE_HISTORY
|
|
746
|
+
refcase: Refcase | None = None
|
|
715
747
|
_observations: dict[str, pl.DataFrame] | None = PrivateAttr(None)
|
|
716
748
|
|
|
717
749
|
@property
|
|
@@ -731,6 +763,7 @@ class ErtConfig(BaseModel):
|
|
|
731
763
|
)
|
|
732
764
|
computed = create_observation_dataframes(
|
|
733
765
|
self.observation_declarations,
|
|
766
|
+
self.refcase,
|
|
734
767
|
cast(
|
|
735
768
|
GenDataConfig | None,
|
|
736
769
|
self.ensemble_config.response_configs.get("gen_data", None),
|
|
@@ -739,6 +772,8 @@ class ErtConfig(BaseModel):
|
|
|
739
772
|
RFTConfig | None,
|
|
740
773
|
self.ensemble_config.response_configs.get("rft", None),
|
|
741
774
|
),
|
|
775
|
+
self.time_map,
|
|
776
|
+
self.history_source,
|
|
742
777
|
)
|
|
743
778
|
self._observations = computed
|
|
744
779
|
return computed
|
|
@@ -994,7 +1029,7 @@ class ErtConfig(BaseModel):
|
|
|
994
1029
|
summary_obs = {
|
|
995
1030
|
obs.key
|
|
996
1031
|
for obs in obs_configs
|
|
997
|
-
if isinstance(obs, SummaryObservation)
|
|
1032
|
+
if isinstance(obs, HistoryObservation | SummaryObservation)
|
|
998
1033
|
}
|
|
999
1034
|
if summary_obs:
|
|
1000
1035
|
summary_keys = ErtConfig._read_summary_keys(config_dict)
|
|
@@ -1002,6 +1037,16 @@ class ErtConfig(BaseModel):
|
|
|
1002
1037
|
[key] for key in summary_obs if key not in summary_keys
|
|
1003
1038
|
]
|
|
1004
1039
|
ensemble_config = EnsembleConfig.from_dict(config_dict=config_dict)
|
|
1040
|
+
time_map = None
|
|
1041
|
+
if time_map_args := config_dict.get(ConfigKeys.TIME_MAP):
|
|
1042
|
+
time_map_file, time_map_contents = time_map_args
|
|
1043
|
+
try:
|
|
1044
|
+
time_map = _read_time_map(time_map_contents)
|
|
1045
|
+
except ValueError as err:
|
|
1046
|
+
raise ConfigValidationError.with_context(
|
|
1047
|
+
f"Could not read timemap file {time_map_file}: {err}",
|
|
1048
|
+
time_map_file,
|
|
1049
|
+
) from err
|
|
1005
1050
|
except ConfigValidationError as err:
|
|
1006
1051
|
errors.append(err)
|
|
1007
1052
|
except PydanticValidationError as err:
|
|
@@ -1054,6 +1099,9 @@ class ErtConfig(BaseModel):
|
|
|
1054
1099
|
|
|
1055
1100
|
env_vars = {}
|
|
1056
1101
|
substituter = Substitutions(substitutions)
|
|
1102
|
+
history_source = config_dict.get(
|
|
1103
|
+
ConfigKeys.HISTORY_SOURCE, HistorySource.REFCASE_HISTORY
|
|
1104
|
+
)
|
|
1057
1105
|
|
|
1058
1106
|
# Insert env vars from plugins/site config
|
|
1059
1107
|
for key, val in cls.ENV_VARS.items():
|
|
@@ -1089,6 +1137,7 @@ class ErtConfig(BaseModel):
|
|
|
1089
1137
|
prioritize_private_ip_address = user_prioritize_private_ip_address
|
|
1090
1138
|
|
|
1091
1139
|
try:
|
|
1140
|
+
refcase = Refcase.from_config_dict(config_dict)
|
|
1092
1141
|
cls_config = cls(
|
|
1093
1142
|
substitutions=substitutions,
|
|
1094
1143
|
ensemble_config=ensemble_config,
|
|
@@ -1110,6 +1159,9 @@ class ErtConfig(BaseModel):
|
|
|
1110
1159
|
runpath_config=model_config,
|
|
1111
1160
|
user_config_file=config_file_path,
|
|
1112
1161
|
observation_declarations=list(obs_configs),
|
|
1162
|
+
time_map=time_map,
|
|
1163
|
+
history_source=history_source,
|
|
1164
|
+
refcase=refcase,
|
|
1113
1165
|
prioritize_private_ip_address=prioritize_private_ip_address,
|
|
1114
1166
|
)
|
|
1115
1167
|
|
|
@@ -1127,6 +1179,7 @@ class ErtConfig(BaseModel):
|
|
|
1127
1179
|
)
|
|
1128
1180
|
cls_config._observations = create_observation_dataframes(
|
|
1129
1181
|
obs_configs,
|
|
1182
|
+
refcase,
|
|
1130
1183
|
cast(
|
|
1131
1184
|
GenDataConfig | None,
|
|
1132
1185
|
ensemble_config.response_configs.get("gen_data", None),
|
|
@@ -1135,6 +1188,8 @@ class ErtConfig(BaseModel):
|
|
|
1135
1188
|
RFTConfig | None,
|
|
1136
1189
|
ensemble_config.response_configs.get("rft", None),
|
|
1137
1190
|
),
|
|
1191
|
+
time_map,
|
|
1192
|
+
history_source,
|
|
1138
1193
|
)
|
|
1139
1194
|
except PydanticValidationError as err:
|
|
1140
1195
|
raise ConfigValidationError.from_pydantic(err) from err
|
|
@@ -138,7 +138,6 @@ observations_parser = Lark(
|
|
|
138
138
|
PARAMETER_NAME : CHAR+
|
|
139
139
|
object : "{" [(declaration";")*] "}"
|
|
140
140
|
?declaration: "SEGMENT" STRING object -> segment
|
|
141
|
-
| "LOCALIZATION" object -> localization
|
|
142
141
|
| pair
|
|
143
142
|
pair : PARAMETER_NAME "=" value
|
|
144
143
|
|
|
@@ -194,11 +193,6 @@ class TreeToObservations(Transformer[FileContextToken, list[ObservationDict]]):
|
|
|
194
193
|
def segment(tree):
|
|
195
194
|
return (("SEGMENT", tree[0]), tree[1])
|
|
196
195
|
|
|
197
|
-
@staticmethod
|
|
198
|
-
@no_type_check
|
|
199
|
-
def localization(tree):
|
|
200
|
-
return ("LOCALIZATION", tree[0])
|
|
201
|
-
|
|
202
196
|
@staticmethod
|
|
203
197
|
@no_type_check
|
|
204
198
|
def object(tree):
|
ert/config/rft_config.py
CHANGED
|
@@ -194,7 +194,7 @@ class RFTConfig(ResponseConfig):
|
|
|
194
194
|
.explode("location")
|
|
195
195
|
for (well, time), inner_dict in fetched.items()
|
|
196
196
|
for prop, vals in inner_dict.items()
|
|
197
|
-
if prop != "DEPTH"
|
|
197
|
+
if prop != "DEPTH"
|
|
198
198
|
]
|
|
199
199
|
)
|
|
200
200
|
except KeyError as err:
|
|
File without changes
|