Skip to content

Commit 0eb002e

Browse files
authored
feat: add static target handling in resampling validation (#494)
1 parent 24d23cc commit 0eb002e

2 files changed

Lines changed: 72 additions & 5 deletions

File tree

src/access_moppy/utilities.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,8 @@ def parse_cmip6_table_frequency(compound_name: str) -> pd.Timedelta:
687687
"Eday": pd.Timedelta(days=1),
688688
"Eyr": pd.Timedelta(days=365),
689689
"Efx": pd.Timedelta(days=0),
690+
"fx": pd.Timedelta(days=0),
691+
"Ofx": pd.Timedelta(days=0),
690692
# Sea ice tables
691693
"SImon": pd.Timedelta(days=30),
692694
"SIday": pd.Timedelta(days=1),
@@ -2164,6 +2166,36 @@ def validate_and_resample_if_needed(
21642166
Returns:
21652167
tuple of (dataset, was_resampled)
21662168
"""
2169+
# Get target frequency
2170+
target_freq = parse_cmip6_table_frequency(compound_name)
2171+
target_seconds = target_freq.total_seconds()
2172+
2173+
# Time-invariant (fx/*fx) targets do not require temporal validation/resampling.
2174+
# Accept datasets with no time axis or a singleton time axis; reject true
2175+
# time-varying inputs for fixed-output tables.
2176+
if target_seconds == 0:
2177+
if time_coord not in ds.coords:
2178+
logger.debug(
2179+
"Static target table for %s with no '%s' coordinate - no resampling needed",
2180+
compound_name,
2181+
time_coord,
2182+
)
2183+
return ds, False
2184+
2185+
time_size = ds[time_coord].size
2186+
if time_size <= 1:
2187+
logger.debug(
2188+
"Static target table for %s with singleton '%s' coordinate - no resampling needed",
2189+
compound_name,
2190+
time_coord,
2191+
)
2192+
return ds, False
2193+
2194+
raise IncompatibleFrequencyError(
2195+
f"Cannot process '{compound_name}' as a fixed (fx) variable: "
2196+
f"dataset has {time_size} time steps on '{time_coord}'."
2197+
)
2198+
21672199
# Detect current frequency
21682200
detected_freq = detect_time_frequency_lazy(ds, time_coord)
21692201
if detected_freq is None:
@@ -2172,13 +2204,8 @@ def validate_and_resample_if_needed(
21722204
f"Check that the '{time_coord}' coordinate has valid units and sufficient time points."
21732205
)
21742206

2175-
# Get target frequency
2176-
target_freq = parse_cmip6_table_frequency(compound_name)
2177-
21782207
# Check if resampling is needed
21792208
input_seconds = detected_freq.total_seconds()
2180-
target_seconds = target_freq.total_seconds()
2181-
21822209
# Check compatibility first
21832210
is_compatible, reason = is_frequency_compatible(detected_freq, target_freq)
21842211
if not is_compatible:

tests/unit/test_temporal_resampling.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,46 @@ def test_invalid_compound_name_raises_error(self):
355355
with pytest.raises(ValueError):
356356
validate_and_resample_if_needed(ds, "Unknown.tas", "tas")
357357

358+
def test_static_target_without_time_coord_no_resampling(self):
359+
"""Static targets (fx) should skip time validation when no time coord exists."""
360+
ds = xr.Dataset(
361+
{"mrsofc": (["lat", "lon"], np.ones((2, 3), dtype="f4"))},
362+
coords={"lat": [0.0, 1.0], "lon": [10.0, 20.0, 30.0]},
363+
)
364+
365+
ds_result, was_resampled = validate_and_resample_if_needed(
366+
ds, "Efx.mrsofc", "mrsofc"
367+
)
368+
369+
assert not was_resampled
370+
assert ds_result is ds
371+
372+
def test_static_target_singleton_time_coord_no_resampling(self):
373+
"""Static targets tolerate a singleton source time axis (common UM fx inputs)."""
374+
time = pd.date_range("2000-01-01", periods=1, freq="D")
375+
ds = xr.Dataset(
376+
{"mrsofc": (["time", "lat", "lon"], np.ones((1, 2, 3), dtype="f4"))},
377+
coords={"time": time, "lat": [0.0, 1.0], "lon": [10.0, 20.0, 30.0]},
378+
)
379+
380+
ds_result, was_resampled = validate_and_resample_if_needed(
381+
ds, "Efx.mrsofc", "mrsofc"
382+
)
383+
384+
assert not was_resampled
385+
assert ds_result is ds
386+
387+
def test_static_target_rejects_time_varying_input(self):
388+
"""Static targets should reject inputs with multiple time steps."""
389+
time = pd.date_range("2000-01-01", periods=2, freq="D")
390+
ds = xr.Dataset(
391+
{"mrsofc": (["time", "lat", "lon"], np.ones((2, 2, 3), dtype="f4"))},
392+
coords={"time": time, "lat": [0.0, 1.0], "lon": [10.0, 20.0, 30.0]},
393+
)
394+
395+
with pytest.raises(IncompatibleFrequencyError, match=r"fixed \(fx\)"):
396+
validate_and_resample_if_needed(ds, "Efx.mrsofc", "mrsofc")
397+
358398

359399
class TestErrorHandling:
360400
"""Tests for error handling in resampling operations."""

0 commit comments

Comments
 (0)