ert 19.0.1__py3-none-any.whl → 20.0.0b1__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 +94 -63
- ert/analysis/_es_update.py +11 -14
- ert/cli/main.py +1 -1
- ert/config/__init__.py +3 -2
- ert/config/_create_observation_dataframes.py +52 -375
- ert/config/_observations.py +527 -200
- ert/config/_read_summary.py +4 -5
- ert/config/ert_config.py +52 -117
- ert/config/everest_control.py +40 -39
- ert/config/everest_response.py +3 -15
- ert/config/field.py +4 -76
- ert/config/forward_model_step.py +17 -1
- ert/config/gen_data_config.py +14 -17
- ert/config/observation_config_migrations.py +821 -0
- ert/config/parameter_config.py +18 -28
- ert/config/parsing/__init__.py +0 -1
- ert/config/parsing/_parse_zonemap.py +45 -0
- ert/config/parsing/config_keywords.py +1 -0
- ert/config/parsing/config_schema.py +2 -0
- ert/config/parsing/observations_parser.py +2 -0
- ert/config/response_config.py +5 -23
- ert/config/rft_config.py +129 -31
- ert/config/summary_config.py +1 -13
- ert/config/surface_config.py +0 -57
- ert/dark_storage/compute/misfits.py +0 -42
- ert/dark_storage/endpoints/__init__.py +0 -2
- ert/dark_storage/endpoints/experiments.py +2 -5
- ert/dark_storage/json_schema/experiment.py +1 -2
- ert/field_utils/__init__.py +0 -2
- ert/field_utils/field_utils.py +1 -117
- ert/gui/ertwidgets/listeditbox.py +9 -1
- ert/gui/ertwidgets/models/ertsummary.py +20 -6
- ert/gui/ertwidgets/pathchooser.py +9 -1
- ert/gui/ertwidgets/stringbox.py +11 -3
- ert/gui/ertwidgets/textbox.py +10 -3
- ert/gui/ertwidgets/validationsupport.py +19 -1
- ert/gui/main_window.py +11 -6
- ert/gui/simulation/experiment_panel.py +1 -1
- ert/gui/simulation/run_dialog.py +11 -1
- ert/gui/tools/manage_experiments/export_dialog.py +4 -0
- ert/gui/tools/manage_experiments/manage_experiments_panel.py +1 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +1 -1
- ert/gui/tools/manage_experiments/storage_widget.py +21 -4
- ert/gui/tools/plot/data_type_proxy_model.py +1 -1
- ert/gui/tools/plot/plot_api.py +35 -27
- ert/gui/tools/plot/plot_widget.py +5 -0
- ert/gui/tools/plot/plot_window.py +4 -7
- ert/run_models/ensemble_experiment.py +2 -9
- ert/run_models/ensemble_smoother.py +1 -9
- ert/run_models/everest_run_model.py +31 -23
- ert/run_models/initial_ensemble_run_model.py +19 -22
- ert/run_models/manual_update.py +11 -5
- ert/run_models/model_factory.py +7 -7
- ert/run_models/multiple_data_assimilation.py +3 -16
- ert/sample_prior.py +12 -14
- ert/scheduler/job.py +24 -4
- ert/services/__init__.py +7 -3
- ert/services/_storage_main.py +59 -22
- ert/services/ert_server.py +186 -24
- ert/shared/version.py +3 -3
- ert/storage/local_ensemble.py +50 -116
- ert/storage/local_experiment.py +94 -109
- ert/storage/local_storage.py +10 -12
- ert/storage/migration/to24.py +26 -0
- ert/storage/migration/to25.py +91 -0
- ert/utils/__init__.py +20 -0
- {ert-19.0.1.dist-info → ert-20.0.0b1.dist-info}/METADATA +4 -51
- {ert-19.0.1.dist-info → ert-20.0.0b1.dist-info}/RECORD +80 -83
- 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/everest_client.py +0 -6
- everest/gui/main_window.py +2 -2
- everest/util/__init__.py +1 -19
- ert/dark_storage/compute/__init__.py +0 -0
- ert/dark_storage/endpoints/compute/__init__.py +0 -0
- ert/dark_storage/endpoints/compute/misfits.py +0 -95
- ert/services/_base_service.py +0 -387
- ert/services/webviz_ert_service.py +0 -20
- ert/shared/storage/command.py +0 -38
- ert/shared/storage/extraction.py +0 -42
- {ert-19.0.1.dist-info → ert-20.0.0b1.dist-info}/WHEEL +0 -0
- {ert-19.0.1.dist-info → ert-20.0.0b1.dist-info}/entry_points.txt +0 -0
- {ert-19.0.1.dist-info → ert-20.0.0b1.dist-info}/licenses/COPYING +0 -0
- {ert-19.0.1.dist-info → ert-20.0.0b1.dist-info}/top_level.txt +0 -0
|
@@ -1,93 +1,52 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import datetime
|
|
3
4
|
from collections import defaultdict
|
|
4
5
|
from collections.abc import Sequence
|
|
5
|
-
from
|
|
6
|
-
from typing import TYPE_CHECKING, Any, assert_never
|
|
6
|
+
from typing import assert_never
|
|
7
7
|
|
|
8
|
-
import numpy as np
|
|
9
8
|
import polars as pl
|
|
10
|
-
from resfo_utilities import history_key
|
|
11
|
-
|
|
12
|
-
from ert.validation import rangestring_to_list
|
|
13
9
|
|
|
14
10
|
from ._observations import (
|
|
15
|
-
|
|
11
|
+
BreakthroughObservation,
|
|
16
12
|
GeneralObservation,
|
|
17
|
-
HistoryObservation,
|
|
18
13
|
Observation,
|
|
19
|
-
ObservationDate,
|
|
20
|
-
ObservationError,
|
|
21
14
|
RFTObservation,
|
|
22
15
|
SummaryObservation,
|
|
23
16
|
)
|
|
24
|
-
from .gen_data_config import GenDataConfig
|
|
25
17
|
from .parsing import (
|
|
26
|
-
ConfigWarning,
|
|
27
18
|
ErrorInfo,
|
|
28
|
-
HistorySource,
|
|
29
19
|
ObservationConfigError,
|
|
30
20
|
)
|
|
31
|
-
from .refcase import Refcase
|
|
32
21
|
from .rft_config import RFTConfig
|
|
33
22
|
|
|
34
|
-
if TYPE_CHECKING:
|
|
35
|
-
import numpy.typing as npt
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
DEFAULT_TIME_DELTA = timedelta(seconds=30)
|
|
39
23
|
DEFAULT_LOCALIZATION_RADIUS = 3000
|
|
40
24
|
|
|
41
25
|
|
|
42
26
|
def create_observation_dataframes(
|
|
43
27
|
observations: Sequence[Observation],
|
|
44
|
-
refcase: Refcase | None,
|
|
45
|
-
gen_data_config: GenDataConfig | None,
|
|
46
28
|
rft_config: RFTConfig | None,
|
|
47
|
-
time_map: list[datetime] | None,
|
|
48
|
-
history: HistorySource,
|
|
49
29
|
) -> dict[str, pl.DataFrame]:
|
|
50
30
|
if not observations:
|
|
51
31
|
return {}
|
|
52
|
-
obs_time_list: list[datetime] = []
|
|
53
|
-
if refcase is not None:
|
|
54
|
-
obs_time_list = refcase.all_dates
|
|
55
|
-
elif time_map is not None:
|
|
56
|
-
obs_time_list = time_map
|
|
57
32
|
|
|
58
|
-
time_len = len(obs_time_list)
|
|
59
33
|
config_errors: list[ErrorInfo] = []
|
|
60
34
|
grouped: dict[str, list[pl.DataFrame]] = defaultdict(list)
|
|
61
35
|
for obs in observations:
|
|
62
36
|
try:
|
|
63
37
|
match obs:
|
|
64
|
-
case HistoryObservation():
|
|
65
|
-
grouped["summary"].append(
|
|
66
|
-
_handle_history_observation(
|
|
67
|
-
refcase,
|
|
68
|
-
obs,
|
|
69
|
-
obs.name,
|
|
70
|
-
history,
|
|
71
|
-
time_len,
|
|
72
|
-
)
|
|
73
|
-
)
|
|
74
38
|
case SummaryObservation():
|
|
75
39
|
grouped["summary"].append(
|
|
76
40
|
_handle_summary_observation(
|
|
77
41
|
obs,
|
|
78
42
|
obs.name,
|
|
79
|
-
obs_time_list,
|
|
80
|
-
bool(refcase),
|
|
81
43
|
)
|
|
82
44
|
)
|
|
83
45
|
case GeneralObservation():
|
|
84
46
|
grouped["gen_data"].append(
|
|
85
47
|
_handle_general_observation(
|
|
86
|
-
gen_data_config,
|
|
87
48
|
obs,
|
|
88
49
|
obs.name,
|
|
89
|
-
obs_time_list,
|
|
90
|
-
bool(refcase),
|
|
91
50
|
)
|
|
92
51
|
)
|
|
93
52
|
case RFTObservation():
|
|
@@ -97,6 +56,12 @@ def create_observation_dataframes(
|
|
|
97
56
|
"rft_config is not None when using RFTObservation"
|
|
98
57
|
)
|
|
99
58
|
grouped["rft"].append(_handle_rft_observation(rft_config, obs))
|
|
59
|
+
case BreakthroughObservation():
|
|
60
|
+
grouped["breakthrough"].append(
|
|
61
|
+
_handle_breakthrough_observation(
|
|
62
|
+
obs,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
100
65
|
case default:
|
|
101
66
|
assert_never(default)
|
|
102
67
|
except ObservationConfigError as err:
|
|
@@ -118,187 +83,6 @@ def create_observation_dataframes(
|
|
|
118
83
|
return datasets
|
|
119
84
|
|
|
120
85
|
|
|
121
|
-
def _handle_error_mode(
|
|
122
|
-
values: npt.ArrayLike,
|
|
123
|
-
error_dict: ObservationError,
|
|
124
|
-
) -> npt.NDArray[np.double]:
|
|
125
|
-
values = np.asarray(values)
|
|
126
|
-
error_mode = error_dict.error_mode
|
|
127
|
-
error_min = error_dict.error_min
|
|
128
|
-
error = error_dict.error
|
|
129
|
-
match error_mode:
|
|
130
|
-
case ErrorModes.ABS:
|
|
131
|
-
return np.full(values.shape, error)
|
|
132
|
-
case ErrorModes.REL:
|
|
133
|
-
return np.abs(values) * error
|
|
134
|
-
case ErrorModes.RELMIN:
|
|
135
|
-
return np.maximum(np.abs(values) * error, np.full(values.shape, error_min))
|
|
136
|
-
case default:
|
|
137
|
-
assert_never(default)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _handle_history_observation(
|
|
141
|
-
refcase: Refcase | None,
|
|
142
|
-
history_observation: HistoryObservation,
|
|
143
|
-
summary_key: str,
|
|
144
|
-
history_type: HistorySource,
|
|
145
|
-
time_len: int,
|
|
146
|
-
) -> pl.DataFrame:
|
|
147
|
-
if refcase is None:
|
|
148
|
-
raise ObservationConfigError.with_context(
|
|
149
|
-
"REFCASE is required for HISTORY_OBSERVATION", summary_key
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
if history_type == HistorySource.REFCASE_HISTORY:
|
|
153
|
-
local_key = history_key(summary_key)
|
|
154
|
-
else:
|
|
155
|
-
local_key = summary_key
|
|
156
|
-
if local_key not in refcase.keys:
|
|
157
|
-
raise ObservationConfigError.with_context(
|
|
158
|
-
f"Key {local_key!r} is not present in refcase", summary_key
|
|
159
|
-
)
|
|
160
|
-
values = refcase.values[refcase.keys.index(local_key)]
|
|
161
|
-
std_dev = _handle_error_mode(values, history_observation)
|
|
162
|
-
for segment in history_observation.segments:
|
|
163
|
-
start = segment.start
|
|
164
|
-
stop = segment.stop
|
|
165
|
-
if start < 0:
|
|
166
|
-
ConfigWarning.warn(
|
|
167
|
-
f"Segment {segment.name} out of bounds."
|
|
168
|
-
" Truncating start of segment to 0.",
|
|
169
|
-
segment.name,
|
|
170
|
-
)
|
|
171
|
-
start = 0
|
|
172
|
-
if stop >= time_len:
|
|
173
|
-
ConfigWarning.warn(
|
|
174
|
-
f"Segment {segment.name} out of bounds. Truncating"
|
|
175
|
-
f" end of segment to {time_len - 1}.",
|
|
176
|
-
segment.name,
|
|
177
|
-
)
|
|
178
|
-
stop = time_len - 1
|
|
179
|
-
if start > stop:
|
|
180
|
-
ConfigWarning.warn(
|
|
181
|
-
f"Segment {segment.name} start after stop. Truncating"
|
|
182
|
-
f" end of segment to {start}.",
|
|
183
|
-
segment.name,
|
|
184
|
-
)
|
|
185
|
-
stop = start
|
|
186
|
-
if np.size(std_dev[start:stop]) == 0:
|
|
187
|
-
ConfigWarning.warn(
|
|
188
|
-
f"Segment {segment.name} does not"
|
|
189
|
-
" contain any time steps. The interval "
|
|
190
|
-
f"[{start}, {stop}) does not intersect with steps in the"
|
|
191
|
-
"time map.",
|
|
192
|
-
segment.name,
|
|
193
|
-
)
|
|
194
|
-
std_dev[start:stop] = _handle_error_mode(values[start:stop], segment)
|
|
195
|
-
dates_series = pl.Series(refcase.dates).dt.cast_time_unit("ms")
|
|
196
|
-
if (std_dev <= 0).any():
|
|
197
|
-
raise ObservationConfigError.with_context(
|
|
198
|
-
"Observation uncertainty must be strictly > 0", summary_key
|
|
199
|
-
) from None
|
|
200
|
-
|
|
201
|
-
return pl.DataFrame(
|
|
202
|
-
{
|
|
203
|
-
"response_key": summary_key,
|
|
204
|
-
"observation_key": summary_key,
|
|
205
|
-
"time": dates_series,
|
|
206
|
-
"observations": pl.Series(values, dtype=pl.Float32),
|
|
207
|
-
"std": pl.Series(std_dev, dtype=pl.Float32),
|
|
208
|
-
"east": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
209
|
-
"north": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
210
|
-
"radius": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
211
|
-
}
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def _get_time(
|
|
216
|
-
date_dict: ObservationDate, start_time: datetime, context: Any = None
|
|
217
|
-
) -> tuple[datetime, str]:
|
|
218
|
-
if date_dict.date is not None:
|
|
219
|
-
return _parse_date(date_dict.date), f"DATE={date_dict.date}"
|
|
220
|
-
if date_dict.days is not None:
|
|
221
|
-
days = date_dict.days
|
|
222
|
-
return start_time + timedelta(days=days), f"DAYS={days}"
|
|
223
|
-
if date_dict.hours is not None:
|
|
224
|
-
hours = date_dict.hours
|
|
225
|
-
return start_time + timedelta(hours=hours), f"HOURS={hours}"
|
|
226
|
-
raise ObservationConfigError.with_context("Missing time specifier", context=context)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def _parse_date(date_str: str) -> datetime:
|
|
230
|
-
try:
|
|
231
|
-
return datetime.fromisoformat(date_str)
|
|
232
|
-
except ValueError:
|
|
233
|
-
try:
|
|
234
|
-
date = datetime.strptime(date_str, "%d/%m/%Y")
|
|
235
|
-
except ValueError as err:
|
|
236
|
-
raise ObservationConfigError.with_context(
|
|
237
|
-
f"Unsupported date format {date_str}. Please use ISO date format",
|
|
238
|
-
date_str,
|
|
239
|
-
) from err
|
|
240
|
-
else:
|
|
241
|
-
ConfigWarning.warn(
|
|
242
|
-
f"Deprecated time format {date_str}."
|
|
243
|
-
" Please use ISO date format YYYY-MM-DD",
|
|
244
|
-
date_str,
|
|
245
|
-
)
|
|
246
|
-
return date
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def _find_nearest(
|
|
250
|
-
time_map: list[datetime],
|
|
251
|
-
time: datetime,
|
|
252
|
-
threshold: timedelta = DEFAULT_TIME_DELTA,
|
|
253
|
-
) -> int:
|
|
254
|
-
nearest_index = -1
|
|
255
|
-
nearest_diff = None
|
|
256
|
-
for i, t in enumerate(time_map):
|
|
257
|
-
diff = abs(time - t)
|
|
258
|
-
if diff < threshold and (nearest_diff is None or nearest_diff > diff):
|
|
259
|
-
nearest_diff = diff
|
|
260
|
-
nearest_index = i
|
|
261
|
-
if nearest_diff is None:
|
|
262
|
-
raise IndexError(f"{time} is not in the time map")
|
|
263
|
-
return nearest_index
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def _get_restart(
|
|
267
|
-
date_dict: ObservationDate,
|
|
268
|
-
obs_name: str,
|
|
269
|
-
time_map: list[datetime],
|
|
270
|
-
has_refcase: bool,
|
|
271
|
-
) -> int:
|
|
272
|
-
if date_dict.restart is not None:
|
|
273
|
-
return date_dict.restart
|
|
274
|
-
if not time_map:
|
|
275
|
-
raise ObservationConfigError.with_context(
|
|
276
|
-
f"Missing REFCASE or TIME_MAP for observations: {obs_name}",
|
|
277
|
-
obs_name,
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
time, date_str = _get_time(date_dict, time_map[0], context=obs_name)
|
|
281
|
-
|
|
282
|
-
try:
|
|
283
|
-
return _find_nearest(time_map, time)
|
|
284
|
-
except IndexError as err:
|
|
285
|
-
raise ObservationConfigError.with_context(
|
|
286
|
-
f"Could not find {time} ({date_str}) in "
|
|
287
|
-
f"the time map for observations {obs_name}. "
|
|
288
|
-
+ (
|
|
289
|
-
"The time map is set from the REFCASE keyword. Either "
|
|
290
|
-
"the REFCASE has an incorrect/missing date, or the observation "
|
|
291
|
-
"is given an incorrect date.)"
|
|
292
|
-
if has_refcase
|
|
293
|
-
else "(The time map is set from the TIME_MAP "
|
|
294
|
-
"keyword. Either the time map file has an "
|
|
295
|
-
"incorrect/missing date, or the observation is given an "
|
|
296
|
-
"incorrect date."
|
|
297
|
-
),
|
|
298
|
-
obs_name,
|
|
299
|
-
) from err
|
|
300
|
-
|
|
301
|
-
|
|
302
86
|
def _has_localization(summary_dict: SummaryObservation) -> bool:
|
|
303
87
|
return summary_dict.east is not None and summary_dict.north is not None
|
|
304
88
|
|
|
@@ -306,39 +90,11 @@ def _has_localization(summary_dict: SummaryObservation) -> bool:
|
|
|
306
90
|
def _handle_summary_observation(
|
|
307
91
|
summary_dict: SummaryObservation,
|
|
308
92
|
obs_key: str,
|
|
309
|
-
time_map: list[datetime],
|
|
310
|
-
has_refcase: bool,
|
|
311
93
|
) -> pl.DataFrame:
|
|
312
94
|
summary_key = summary_dict.key
|
|
313
95
|
value = summary_dict.value
|
|
314
|
-
std_dev =
|
|
315
|
-
|
|
316
|
-
if summary_dict.restart and not (time_map or has_refcase):
|
|
317
|
-
raise ObservationConfigError.with_context(
|
|
318
|
-
"Keyword 'RESTART' requires either TIME_MAP or REFCASE", context=obs_key
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
if summary_dict.date is not None and not time_map:
|
|
322
|
-
# We special case when the user has provided date in SUMMARY_OBS
|
|
323
|
-
# and not REFCASE or time_map so that we don't change current behavior.
|
|
324
|
-
date = _parse_date(summary_dict.date)
|
|
325
|
-
restart = None
|
|
326
|
-
else:
|
|
327
|
-
restart = _get_restart(summary_dict, obs_key, time_map, has_refcase)
|
|
328
|
-
date = time_map[restart]
|
|
329
|
-
|
|
330
|
-
if restart == 0:
|
|
331
|
-
raise ObservationConfigError.with_context(
|
|
332
|
-
"It is unfortunately not possible to use summary "
|
|
333
|
-
"observations from the start of the simulation. "
|
|
334
|
-
f"Problem with observation {obs_key}"
|
|
335
|
-
f"{' at ' + str(_get_time(summary_dict, time_map[0], obs_key)) if summary_dict.restart is None else ''}", # noqa: E501
|
|
336
|
-
obs_key,
|
|
337
|
-
)
|
|
338
|
-
if std_dev <= 0:
|
|
339
|
-
raise ObservationConfigError.with_context(
|
|
340
|
-
"Observation uncertainty must be strictly > 0", summary_key
|
|
341
|
-
) from None
|
|
96
|
+
std_dev = summary_dict.error
|
|
97
|
+
date = datetime.datetime.fromisoformat(summary_dict.date)
|
|
342
98
|
|
|
343
99
|
localization_radius = (
|
|
344
100
|
summary_dict.radius or DEFAULT_LOCALIZATION_RADIUS
|
|
@@ -361,135 +117,28 @@ def _handle_summary_observation(
|
|
|
361
117
|
|
|
362
118
|
|
|
363
119
|
def _handle_general_observation(
|
|
364
|
-
gen_data_config: GenDataConfig | None,
|
|
365
120
|
general_observation: GeneralObservation,
|
|
366
121
|
obs_key: str,
|
|
367
|
-
time_map: list[datetime],
|
|
368
|
-
has_refcase: bool,
|
|
369
122
|
) -> pl.DataFrame:
|
|
370
123
|
response_key = general_observation.data
|
|
124
|
+
restart = general_observation.restart
|
|
371
125
|
|
|
372
|
-
if
|
|
373
|
-
getattr(general_observation, key) is None
|
|
374
|
-
for key in ["restart", "date", "days", "hours"]
|
|
375
|
-
):
|
|
376
|
-
# The user has not provided RESTART or DATE, this is legal
|
|
377
|
-
# for GEN_DATA, so we default it to None
|
|
378
|
-
restart = None
|
|
379
|
-
else:
|
|
380
|
-
restart = _get_restart(general_observation, obs_key, time_map, has_refcase)
|
|
381
|
-
|
|
382
|
-
if gen_data_config is None or response_key not in gen_data_config.keys:
|
|
383
|
-
raise ObservationConfigError.with_context(
|
|
384
|
-
f"Problem with GENERAL_OBSERVATION {obs_key}:"
|
|
385
|
-
f" No GEN_DATA with name {response_key!r} found",
|
|
386
|
-
response_key,
|
|
387
|
-
)
|
|
388
|
-
assert isinstance(gen_data_config, GenDataConfig)
|
|
389
|
-
|
|
390
|
-
_, report_steps = gen_data_config.get_args_for_key(response_key)
|
|
391
|
-
|
|
392
|
-
response_report_steps = [] if report_steps is None else report_steps
|
|
393
|
-
if (restart is None and response_report_steps) or (
|
|
394
|
-
restart is not None and restart not in response_report_steps
|
|
395
|
-
):
|
|
396
|
-
raise ObservationConfigError.with_context(
|
|
397
|
-
f"The GEN_DATA node:{response_key} is not configured to load from"
|
|
398
|
-
f" report step:{restart} for the observation:{obs_key}",
|
|
399
|
-
response_key,
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
restart = 0 if restart is None else restart
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
general_observation.value is None
|
|
406
|
-
and general_observation.error is None
|
|
407
|
-
and general_observation.obs_file is None
|
|
408
|
-
):
|
|
409
|
-
raise ObservationConfigError.with_context(
|
|
410
|
-
"GENERAL_OBSERVATION must contain either VALUE and ERROR or OBS_FILE",
|
|
411
|
-
context=obs_key,
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
if (
|
|
415
|
-
general_observation.value is not None
|
|
416
|
-
and general_observation.error is not None
|
|
417
|
-
and general_observation.obs_file is not None
|
|
418
|
-
):
|
|
419
|
-
raise ObservationConfigError.with_context(
|
|
420
|
-
"GENERAL_OBSERVATION cannot contain both VALUE/ERROR and OBS_FILE",
|
|
421
|
-
context=general_observation.obs_file,
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
if general_observation.obs_file is not None:
|
|
425
|
-
try:
|
|
426
|
-
file_values = np.loadtxt(
|
|
427
|
-
general_observation.obs_file, delimiter=None
|
|
428
|
-
).ravel()
|
|
429
|
-
except ValueError as err:
|
|
430
|
-
raise ObservationConfigError.with_context(
|
|
431
|
-
f"Failed to read OBS_FILE {general_observation.obs_file}: {err}",
|
|
432
|
-
general_observation.obs_file,
|
|
433
|
-
) from err
|
|
434
|
-
if len(file_values) % 2 != 0:
|
|
435
|
-
raise ObservationConfigError.with_context(
|
|
436
|
-
"Expected even number of values in GENERAL_OBSERVATION",
|
|
437
|
-
general_observation.obs_file,
|
|
438
|
-
)
|
|
439
|
-
values = file_values[::2]
|
|
440
|
-
stds = file_values[1::2]
|
|
441
|
-
|
|
442
|
-
else:
|
|
443
|
-
assert general_observation.value is not None
|
|
444
|
-
assert general_observation.error is not None
|
|
445
|
-
values = np.array([general_observation.value])
|
|
446
|
-
stds = np.array([general_observation.error])
|
|
447
|
-
|
|
448
|
-
index_list = general_observation.index_list
|
|
449
|
-
index_file = general_observation.index_file
|
|
450
|
-
if index_list is not None and index_file is not None:
|
|
451
|
-
raise ObservationConfigError.with_context(
|
|
452
|
-
f"GENERAL_OBSERVATION {obs_key} has both INDEX_FILE and INDEX_LIST.",
|
|
453
|
-
obs_key,
|
|
454
|
-
)
|
|
455
|
-
if index_file is not None:
|
|
456
|
-
indices = np.loadtxt(index_file, delimiter=None, dtype=np.int32).ravel()
|
|
457
|
-
elif index_list is not None:
|
|
458
|
-
indices = np.array(sorted(rangestring_to_list(index_list)), dtype=np.int32)
|
|
459
|
-
else:
|
|
460
|
-
indices = np.arange(len(values), dtype=np.int32)
|
|
461
|
-
|
|
462
|
-
if len({len(stds), len(values), len(indices)}) != 1:
|
|
463
|
-
raise ObservationConfigError.with_context(
|
|
464
|
-
f"Values ({values}), error ({stds}) and "
|
|
465
|
-
f"index list ({indices}) must be of equal length",
|
|
466
|
-
(
|
|
467
|
-
general_observation.obs_file
|
|
468
|
-
if general_observation.obs_file is not None
|
|
469
|
-
else ""
|
|
470
|
-
),
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
if np.any(stds <= 0):
|
|
126
|
+
if general_observation.error <= 0:
|
|
474
127
|
raise ObservationConfigError.with_context(
|
|
475
128
|
"Observation uncertainty must be strictly > 0", obs_key
|
|
476
129
|
)
|
|
130
|
+
|
|
477
131
|
return pl.DataFrame(
|
|
478
132
|
{
|
|
479
|
-
"response_key": response_key,
|
|
480
|
-
"observation_key":
|
|
481
|
-
"report_step": pl.Series(
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
),
|
|
485
|
-
"
|
|
486
|
-
"
|
|
487
|
-
"
|
|
488
|
-
# Location attributes will always be None for general observations, but are
|
|
489
|
-
# necessary to concatenate with other observation dataframes.
|
|
490
|
-
"east": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
491
|
-
"north": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
492
|
-
"radius": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
133
|
+
"response_key": [response_key],
|
|
134
|
+
"observation_key": [general_observation.name],
|
|
135
|
+
"report_step": pl.Series([restart], dtype=pl.UInt16),
|
|
136
|
+
"index": pl.Series([general_observation.index], dtype=pl.UInt16),
|
|
137
|
+
"observations": pl.Series([general_observation.value], dtype=pl.Float32),
|
|
138
|
+
"std": pl.Series([general_observation.error], dtype=pl.Float32),
|
|
139
|
+
"east": pl.Series([general_observation.east], dtype=pl.Float32),
|
|
140
|
+
"north": pl.Series([general_observation.north], dtype=pl.Float32),
|
|
141
|
+
"radius": pl.Series([general_observation.radius], dtype=pl.Float32),
|
|
493
142
|
}
|
|
494
143
|
)
|
|
495
144
|
|
|
@@ -499,8 +148,17 @@ def _handle_rft_observation(
|
|
|
499
148
|
rft_observation: RFTObservation,
|
|
500
149
|
) -> pl.DataFrame:
|
|
501
150
|
location = (rft_observation.east, rft_observation.north, rft_observation.tvd)
|
|
151
|
+
zones = {zone for zones in rft_config.zonemap.values() for zone in zones}
|
|
502
152
|
if location not in rft_config.locations:
|
|
503
|
-
|
|
153
|
+
if (zone := rft_observation.zone) is not None:
|
|
154
|
+
if zone not in zones:
|
|
155
|
+
raise ObservationConfigError(
|
|
156
|
+
f"The RFT_OBSERVATION {rft_observation.name} was given "
|
|
157
|
+
f"zone {zone} but no such zone exists in the ZONEMAP."
|
|
158
|
+
)
|
|
159
|
+
rft_config.locations.append((location, zone))
|
|
160
|
+
else:
|
|
161
|
+
rft_config.locations.append(location)
|
|
504
162
|
|
|
505
163
|
data_to_read = rft_config.data_to_read
|
|
506
164
|
if rft_observation.well not in data_to_read:
|
|
@@ -530,8 +188,27 @@ def _handle_rft_observation(
|
|
|
530
188
|
"east": pl.Series([location[0]], dtype=pl.Float32),
|
|
531
189
|
"north": pl.Series([location[1]], dtype=pl.Float32),
|
|
532
190
|
"tvd": pl.Series([location[2]], dtype=pl.Float32),
|
|
191
|
+
"zone": pl.Series([rft_observation.zone], dtype=pl.String),
|
|
533
192
|
"observations": pl.Series([rft_observation.value], dtype=pl.Float32),
|
|
534
193
|
"std": pl.Series([rft_observation.error], dtype=pl.Float32),
|
|
535
194
|
"radius": pl.Series([None], dtype=pl.Float32),
|
|
536
195
|
}
|
|
537
196
|
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _handle_breakthrough_observation(
|
|
200
|
+
obs_config: BreakthroughObservation,
|
|
201
|
+
) -> pl.DataFrame:
|
|
202
|
+
return pl.DataFrame(
|
|
203
|
+
{
|
|
204
|
+
"observation_key": obs_config.name,
|
|
205
|
+
"response_key": (
|
|
206
|
+
f"BREAKTHROUGH:{obs_config.response_key}:{obs_config.threshold}"
|
|
207
|
+
),
|
|
208
|
+
"observations": obs_config.date.isoformat(),
|
|
209
|
+
"std": pl.Series([obs_config.error], dtype=pl.Float32),
|
|
210
|
+
"east": pl.Series([obs_config.east], dtype=pl.Float32),
|
|
211
|
+
"north": pl.Series([obs_config.north], dtype=pl.Float32),
|
|
212
|
+
"radius": pl.Series([obs_config.radius], dtype=pl.Float32),
|
|
213
|
+
}
|
|
214
|
+
)
|