Skip to content

Commit 84fdd0d

Browse files
committed
Support glob pattern in seismic responses
Changes to other tests are mostly caused by additional check for directory existence that doesn't play nicely with mocked_files.
1 parent d2c7504 commit 84fdd0d

2 files changed

Lines changed: 74 additions & 34 deletions

File tree

src/ert/config/seismic_config.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import polars as pl
99

1010
from ert.config._reservoir_data_utils import SeismicData
11-
from ert.substitutions import substitute_runpath_name
1211

1312
from .parsing import (
1413
ConfigDict,
@@ -64,15 +63,21 @@ def validate_distance_between_responses(df: pl.DataFrame) -> None:
6463
f"{SeismicData.TOLERANCE * 2} m apart."
6564
)
6665

66+
def _collect_response_filepaths(self, run_path: str) -> list[Path]:
67+
filepaths = []
68+
for file in self.expected_input_files:
69+
filepaths.extend(
70+
SeismicData.resolve_pattern_filepaths(
71+
run_path, file, on_error=InvalidResponseFile
72+
)
73+
)
74+
return list(dict.fromkeys(filepaths))
75+
6776
def read_from_file(self, run_path: str, iens: int, iter_: int) -> pl.DataFrame:
6877
responses = pl.DataFrame(schema=self.response_schema())
69-
for key, file in zip(self.keys, self.expected_input_files, strict=True):
70-
filepath_runpath_relative = substitute_runpath_name(file, iens, iter_)
71-
filepath = Path(run_path) / filepath_runpath_relative
72-
if not filepath.exists():
73-
raise InvalidResponseFile(
74-
f"Expected seismic response file {filepath} does not exist."
75-
)
78+
filepaths = self._collect_response_filepaths(run_path)
79+
keys = [f.stem for f in filepaths]
80+
for key, filepath in zip(keys, filepaths, strict=True):
7681
suffix = filepath.suffix.lower()
7782
if suffix == ".parquet":
7883
data = pl.read_parquet(filepath)
@@ -114,4 +119,5 @@ def from_config_dict(cls, config_dict: ConfigDict) -> Self | None:
114119
name="seismic",
115120
input_files=files,
116121
keys=[Path(f).stem for f in files],
122+
has_finalized_keys=False,
117123
)

tests/ert/unit_tests/config/test_seismic_config.py

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from io import BytesIO, StringIO
22
from pathlib import Path
3+
from textwrap import dedent
34
from typing import cast
45

56
import polars as pl
@@ -108,19 +109,18 @@ def test_that_unsupported_seismic_response_file_extension_raises_invalid_respons
108109
):
109110
key = "horizon--amplitude_full_min_depth--20250101_20240101"
110111
name = f"{key}.txt"
111-
runpath = "/runpath"
112-
mocked_files[f"{runpath}/{name}"] = "irrelevant content"
112+
mocked_files[f"{name}"] = "irrelevant content"
113113

114114
seismic_config = SeismicConfig(
115115
input_files=[name],
116116
keys=[key],
117117
)
118118

119119
with pytest.raises(InvalidResponseFile) as err:
120-
seismic_config.read_from_file(runpath, 1, 1)
120+
seismic_config.read_from_file("", 1, 1)
121121

122122
assert (
123-
f"Unsupported seismic response file extension '.txt' for {runpath}/{name}. "
123+
f"Unsupported seismic response file extension '.txt' for {name}. "
124124
"Expected '.csv' or '.parquet'." in str(err.value)
125125
)
126126

@@ -140,7 +140,6 @@ def test_that_seismic_config_reads_from_all_input_files(mocked_files, suffix):
140140
key2 = "horizon--amplitude_full_mean_depth--20260101_20240101"
141141
name1 = f"{key1}{suffix}"
142142
name2 = f"{key2}{suffix}"
143-
runpath = "/runpath"
144143

145144
frame1 = pl.DataFrame(
146145
{
@@ -160,27 +159,67 @@ def test_that_seismic_config_reads_from_all_input_files(mocked_files, suffix):
160159
"REGION": [1.0, 1.0],
161160
}
162161
)
163-
_mock_seismic_response(mocked_files, f"{runpath}/{name1}", frame1, suffix)
164-
_mock_seismic_response(mocked_files, f"{runpath}/{name2}", frame2, suffix)
162+
_mock_seismic_response(mocked_files, f"{name1}", frame1, suffix)
163+
_mock_seismic_response(mocked_files, f"{name2}", frame2, suffix)
165164

166165
seismic_config = SeismicConfig(
167166
input_files=[name1, name2],
168167
keys=[key1, key2],
169168
)
170169

171-
data = seismic_config.read_from_file(runpath, 1, 1)
170+
data = seismic_config.read_from_file("", 1, 1)
172171
assert data.shape == (4, 4)
173172
assert data["response_key"].to_list() == [key1, key1, key2, key2]
174173
assert data["east"].to_list() == [100.0, 105.0, 100.0, 105.0]
175174
assert data["north"].to_list() == [200.0, 205.0, 200.0, 205.0]
176175
assert data["values"].to_list() == [1.0, 2.0, 3.0, 4.0]
177176

178177

178+
@pytest.mark.usefixtures("use_tmpdir")
179+
def test_that_seismic_config_supports_glob_pattern():
180+
key1 = "horizon1--amplitude_full_mean_depth--20260101_20240101"
181+
key2 = "horizon1--amplitude_full_min_depth--20250101_20240101"
182+
key3 = "horizon2--amplitude_far_min_depth--20250101_20240101"
183+
key4 = "other_horizon--amplitude_full_mean_depth--20260101_20240101"
184+
185+
runpath = "runpath"
186+
Path(runpath).mkdir(parents=True, exist_ok=True)
187+
188+
for key in [key1, key2, key3, key4]:
189+
name = f"{key}.csv"
190+
simulated_path = runpath + "/" + name
191+
content = dedent(
192+
"""
193+
X_UTME,Y_UTMN,OBS,OBS_ERROR,REGION
194+
100.00,200.00,1.0,0.005,1.0
195+
"""
196+
)
197+
Path(simulated_path).write_text(
198+
content,
199+
encoding="utf8",
200+
)
201+
202+
pattern1 = "horizon1--amplitude_full*"
203+
pattern2 = "horizon2--amplitude_far*"
204+
duplicate_pattern = "horizon*"
205+
config = ErtConfig.from_dict(
206+
{
207+
"SEISMIC": [pattern1, pattern2, duplicate_pattern],
208+
}
209+
)
210+
211+
seismic_config = cast(
212+
SeismicConfig, config.ensemble_config.response_configs["seismic"]
213+
)
214+
215+
data = seismic_config.read_from_file(runpath, 1, 1)
216+
assert sorted(data["response_key"].to_list()) == [key1, key2, key3]
217+
218+
179219
@pytest.mark.parametrize("suffix", [".csv", ".parquet"])
180220
def test_that_empty_seismic_response_file_does_not_raise(mocked_files, suffix):
181221
key = "horizon--amplitude_full_min_depth--20250101_20240101"
182222
name = f"{key}{suffix}"
183-
runpath = "/runpath"
184223

185224
empty = pl.DataFrame(
186225
schema={
@@ -191,14 +230,14 @@ def test_that_empty_seismic_response_file_does_not_raise(mocked_files, suffix):
191230
"REGION": pl.Float64,
192231
}
193232
)
194-
_mock_seismic_response(mocked_files, f"{runpath}/{name}", empty, suffix)
233+
_mock_seismic_response(mocked_files, f"{name}", empty, suffix)
195234

196235
seismic_config = SeismicConfig(
197236
input_files=[name],
198237
keys=[key],
199238
)
200239

201-
data = seismic_config.read_from_file(runpath, 1, 1)
240+
data = seismic_config.read_from_file("", 1, 1)
202241
assert data.is_empty()
203242

204243

@@ -209,32 +248,27 @@ def test_that_empty_seismic_response_file_does_not_raise(mocked_files, suffix):
209248
pytest.param([0.0, 0.0], [0.1953125, 0.0], id="less than double tolerance"),
210249
],
211250
)
212-
@pytest.mark.parametrize("suffix", [".csv", ".parquet"])
213251
def test_that_seismic_response_coordinate_distance_below_tolerance_raises(
214-
mocked_files, east, north, suffix
252+
mocked_files, east, north
215253
):
216254
key = "horizon--amplitude_full_min_depth--20250101_20240101"
217-
name = f"{key}{suffix}"
218-
runpath = "/runpath"
219-
220-
frame = pl.DataFrame(
221-
{
222-
"X_UTME": east,
223-
"Y_UTMN": north,
224-
"OBS": [1.0, 2.0],
225-
"OBS_ERROR": [0.005, 0.005],
226-
"REGION": [1.0, 1.0],
227-
}
255+
name = f"{key}.csv"
256+
257+
mocked_files[name] = dedent(
258+
f"""
259+
X_UTME,Y_UTMN,OBS,OBS_ERROR,REGION
260+
{east[0]},{north[0]},1.0,0.005,1.0
261+
{east[1]},{north[1]},2.0,0.005,1.0
262+
"""
228263
)
229-
_mock_seismic_response(mocked_files, f"{runpath}/{name}", frame, suffix)
230264

231265
seismic_config = SeismicConfig(
232266
input_files=[name],
233267
keys=[key],
234268
)
235269

236270
with pytest.raises(InvalidResponseFile) as err:
237-
seismic_config.read_from_file(runpath, 1, 1)
271+
seismic_config.read_from_file("", 1, 1)
238272

239273
assert (
240274
"Seismic response coordinates with approximate locations "

0 commit comments

Comments
 (0)