Skip to content

Commit 19f3d1e

Browse files
committed
fmuobs Support observation with localization metadata
1 parent 1a14d51 commit 19f3d1e

3 files changed

Lines changed: 219 additions & 5 deletions

File tree

src/subscript/fmuobs/parsers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,15 +358,20 @@ def flatten_observation_unit(
358358

359359
for subunit in subunit_keys:
360360
if len(subunit.split()) < 2:
361-
# It must be two strings, like "OBS P1", or "SEGMENT FIRST_YEAR".
362-
raise ValueError("Wrong observation subunit syntax: " + str(subunit))
361+
# Single-word subunit keys (e.g. LOCALIZATION) have no label and
362+
# are not representable in the internal dataframe format; skip them.
363+
logger.debug("Ignoring unlabeled subunit block: %s", subunit)
364+
continue
363365
obs_subunits.append(
364366
{
365367
subunit.split()[0]: subunit.split()[1],
366368
**keyvalues,
367369
**obsunit[subunit],
368370
}
369371
)
372+
if not obs_subunits:
373+
# All subunits were single-word (unlabeled); treat as a plain observation.
374+
return [keyvalues]
370375
return obs_subunits
371376

372377

tests/test_fmuobs.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def fixture_readonly_testdata_dir(monkeypatch):
3333
("ert-doc.csv", "csv"),
3434
("fmu-ensemble-obs.yml", "yaml"),
3535
("drogon_wbhp_rft_wct_gor_tracer_4d.obs", "ert"),
36+
("drogon_wbhp_rft_wct_gor_tracer_plt_local.obs", "ert"),
3637
],
3738
)
3839
def test_autoparse_file(filename, expected_format, readonly_testdata_dir):
@@ -161,6 +162,7 @@ def test_roundtrip_ertobs(filename, readonly_testdata_dir):
161162
("ert-doc.csv"),
162163
("fmu-ensemble-obs.yml"),
163164
("drogon_wbhp_rft_wct_gor_tracer_4d.obs"),
165+
("drogon_wbhp_rft_wct_gor_tracer_plt_local.obs"),
164166
],
165167
)
166168
def test_roundtrip_yaml(filename, readonly_testdata_dir):
@@ -206,6 +208,7 @@ def test_roundtrip_yaml(filename, readonly_testdata_dir):
206208
("ert-doc.csv"),
207209
("fmu-ensemble-obs.yml"),
208210
("drogon_wbhp_rft_wct_gor_tracer_4d.obs"),
211+
("drogon_wbhp_rft_wct_gor_tracer_plt_local.obs"),
209212
],
210213
)
211214
def test_roundtrip_resinsight(filename, readonly_testdata_dir):
@@ -228,19 +231,90 @@ def test_roundtrip_resinsight(filename, readonly_testdata_dir):
228231

229232
# LABEL is not part of the ResInsight format, and a made-up label
230233
# is obtained through the roundtrip (when importing back). Skip it
231-
# when comparing.
234+
# when comparing. ERROR_MODE is also not preserved in ResInsight format.
232235

233236
pd.testing.assert_frame_equal(
234237
ri_roundtrip_dframe.sort_index(axis="columns").drop(
235-
["LABEL", "COMMENT", "SUBCOMMENT"], axis="columns", errors="ignore"
238+
["LABEL", "COMMENT", "SUBCOMMENT", "ERROR_MODE"],
239+
axis="columns",
240+
errors="ignore",
236241
),
237242
dframe.sort_index(axis="columns").drop(
238-
["LABEL", "COMMENT", "SUBCOMMENT"], axis="columns", errors="ignore"
243+
["LABEL", "COMMENT", "SUBCOMMENT", "ERROR_MODE"],
244+
axis="columns",
245+
errors="ignore",
239246
),
240247
check_like=True,
241248
)
242249

243250

251+
def test_drogon_plt_local_localization_ignored(readonly_testdata_dir):
252+
"""Test that LOCALIZATION sub-blocks inside SUMMARY_OBSERVATION are silently
253+
ignored, and that the rest of the observation file is parsed correctly.
254+
255+
The file contains PLT, RFT, WBHP, WWCT, WGOR, and tracer observations,
256+
with LOCALIZATION {EAST=...; NORTH=...; RADIUS=...;} blocks that are not
257+
representable in the internal dataframe format."""
258+
filename = "drogon_wbhp_rft_wct_gor_tracer_plt_local.obs"
259+
dframe = autoparse_file(filename)[1]
260+
261+
assert not dframe.empty
262+
assert "LOCALIZATION" not in dframe.columns
263+
assert set(dframe["CLASS"].unique()) == {
264+
"SUMMARY_OBSERVATION",
265+
"GENERAL_OBSERVATION",
266+
"RFT_OBSERVATION",
267+
}
268+
269+
# SUMMARY_OBSERVATION rows should have the expected key columns
270+
smry_df = dframe[dframe["CLASS"] == "SUMMARY_OBSERVATION"]
271+
assert not smry_df.empty
272+
for col in ["KEY", "VALUE", "ERROR", "DATE"]:
273+
assert col in smry_df.columns
274+
assert smry_df[col].notna().all()
275+
276+
# Roundtrip through ERT obs format for the classes df2ertobs supports
277+
supported_classes = {"SUMMARY_OBSERVATION", "GENERAL_OBSERVATION"}
278+
dframe_supported = dframe[dframe["CLASS"].isin(supported_classes)]
279+
ertobs_str = df2ertobs(dframe_supported)
280+
roundtrip_dframe = ertobs2df(ertobs_str).set_index("CLASS")
281+
dframe_supported = dframe_supported.set_index("CLASS")
282+
283+
for class_ in dframe_supported.index.unique():
284+
roundtrip_subframe = (
285+
roundtrip_dframe.loc[[class_]].dropna(axis=1, how="all").sort_index(axis=1)
286+
)
287+
subframe = (
288+
dframe_supported.loc[[class_]]
289+
.dropna(axis=1, how="all")
290+
.sort_index(axis=1)
291+
.set_index(
292+
list(
293+
{"LABEL", "OBS", "SEGMENT"}.intersection(
294+
set(dframe_supported.columns)
295+
)
296+
)
297+
)
298+
.sort_index()
299+
# ERROR_MODE and comments are not preserved through the ertobs roundtrip
300+
.drop(
301+
["COMMENT", "SUBCOMMENT", "ERROR_MODE"], axis="columns", errors="ignore"
302+
)
303+
)
304+
roundtrip_subframe = roundtrip_subframe.set_index(
305+
list(
306+
{"LABEL", "OBS", "SEGMENT"}.intersection(
307+
set(roundtrip_subframe.columns)
308+
)
309+
)
310+
).sort_index()
311+
pd.testing.assert_frame_equal(
312+
roundtrip_subframe,
313+
subframe,
314+
check_dtype=False,
315+
)
316+
317+
244318
@pytest.mark.integration
245319
def test_integration():
246320
"""Test that the endpoint is installed"""

0 commit comments

Comments
 (0)