Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions src/access_moppy/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,8 @@ def parse_cmip6_table_frequency(compound_name: str) -> pd.Timedelta:
"Eday": pd.Timedelta(days=1),
"Eyr": pd.Timedelta(days=365),
"Efx": pd.Timedelta(days=0),
"fx": pd.Timedelta(days=0),
"Ofx": pd.Timedelta(days=0),
# Sea ice tables
"SImon": pd.Timedelta(days=30),
"SIday": pd.Timedelta(days=1),
Expand Down Expand Up @@ -2164,6 +2166,36 @@ def validate_and_resample_if_needed(
Returns:
tuple of (dataset, was_resampled)
"""
# Get target frequency
target_freq = parse_cmip6_table_frequency(compound_name)
target_seconds = target_freq.total_seconds()

# Time-invariant (fx/*fx) targets do not require temporal validation/resampling.
# Accept datasets with no time axis or a singleton time axis; reject true
# time-varying inputs for fixed-output tables.
if target_seconds == 0:
if time_coord not in ds.coords:
logger.debug(
"Static target table for %s with no '%s' coordinate - no resampling needed",
compound_name,
time_coord,
)
return ds, False

time_size = ds[time_coord].size
if time_size <= 1:
logger.debug(
"Static target table for %s with singleton '%s' coordinate - no resampling needed",
compound_name,
time_coord,
)
return ds, False

raise IncompatibleFrequencyError(
f"Cannot process '{compound_name}' as a fixed (fx) variable: "
f"dataset has {time_size} time steps on '{time_coord}'."
)

# Detect current frequency
detected_freq = detect_time_frequency_lazy(ds, time_coord)
if detected_freq is None:
Expand All @@ -2172,13 +2204,8 @@ def validate_and_resample_if_needed(
f"Check that the '{time_coord}' coordinate has valid units and sufficient time points."
)

# Get target frequency
target_freq = parse_cmip6_table_frequency(compound_name)

# Check if resampling is needed
input_seconds = detected_freq.total_seconds()
target_seconds = target_freq.total_seconds()

# Check compatibility first
is_compatible, reason = is_frequency_compatible(detected_freq, target_freq)
if not is_compatible:
Expand Down
40 changes: 40 additions & 0 deletions tests/unit/test_temporal_resampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,46 @@ def test_invalid_compound_name_raises_error(self):
with pytest.raises(ValueError):
validate_and_resample_if_needed(ds, "Unknown.tas", "tas")

def test_static_target_without_time_coord_no_resampling(self):
"""Static targets (fx) should skip time validation when no time coord exists."""
ds = xr.Dataset(
{"mrsofc": (["lat", "lon"], np.ones((2, 3), dtype="f4"))},
coords={"lat": [0.0, 1.0], "lon": [10.0, 20.0, 30.0]},
)

ds_result, was_resampled = validate_and_resample_if_needed(
ds, "Efx.mrsofc", "mrsofc"
)

assert not was_resampled
assert ds_result is ds

def test_static_target_singleton_time_coord_no_resampling(self):
"""Static targets tolerate a singleton source time axis (common UM fx inputs)."""
time = pd.date_range("2000-01-01", periods=1, freq="D")
ds = xr.Dataset(
{"mrsofc": (["time", "lat", "lon"], np.ones((1, 2, 3), dtype="f4"))},
coords={"time": time, "lat": [0.0, 1.0], "lon": [10.0, 20.0, 30.0]},
)

ds_result, was_resampled = validate_and_resample_if_needed(
ds, "Efx.mrsofc", "mrsofc"
)

assert not was_resampled
assert ds_result is ds

def test_static_target_rejects_time_varying_input(self):
"""Static targets should reject inputs with multiple time steps."""
time = pd.date_range("2000-01-01", periods=2, freq="D")
ds = xr.Dataset(
{"mrsofc": (["time", "lat", "lon"], np.ones((2, 2, 3), dtype="f4"))},
coords={"time": time, "lat": [0.0, 1.0], "lon": [10.0, 20.0, 30.0]},
)

with pytest.raises(IncompatibleFrequencyError, match=r"fixed \(fx\)"):
validate_and_resample_if_needed(ds, "Efx.mrsofc", "mrsofc")


class TestErrorHandling:
"""Tests for error handling in resampling operations."""
Expand Down