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
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The changes listed in this file are categorised as follows:

### Fixed

- ``Cmip6MeteorDataGetter.validate_pattern_scaling_cache`` now accepts an optional ``variable`` argument matching the per-variable suffix ``MeteorPatternScaling`` saves under (``cmip6-{model}-aer-{variable}``). Without it, a cache that ``MeteorPatternScaling.__init__`` loads successfully by filename was reported as "invalid" by the outer validator, causing ``MeteorInterface`` to run ``prepare_pattern_scaling_training_data`` (which fetches and assembles CMIP6 data over the network) on every generation call and immediately discard the result. Threading ``variable=`` through ``_train_pattern_scaling`` fixes the mismatch and eliminates the redundant work (~13–18 s per generation call in the NorESM2-MM profiling workloads).
- Variable length timeseries now works also when don't have "year" as time dimension.
- Fixes to generate annual and monthly gridded ensembles with unified noise and preserving more of the variance.

Expand Down
18 changes: 16 additions & 2 deletions src/meteor/cmip6_meteor_data_getter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,9 @@ def prepare_pattern_scaling_training_data(
)
return training_data

def validate_pattern_scaling_cache(self, cache_file, model_name, scenario="aer"):
def validate_pattern_scaling_cache(
self, cache_file, model_name, scenario="aer", variable=None
):
"""
Validate a cached pattern scaling model file.

Expand All @@ -1098,6 +1100,11 @@ def validate_pattern_scaling_cache(self, cache_file, model_name, scenario="aer")
Expected model name
scenario : str, optional
Scenario suffix for expected model name. Default is "aer".
variable : str, optional
Variable suffix appended to the expected model name (e.g. "tas").
Must match the name ``MeteorPatternScaling`` uses when saving the
per-variable model. When omitted, the legacy (no-suffix) form is
used, but this will mismatch caches saved with per-variable names.

Returns
-------
Expand All @@ -1121,7 +1128,14 @@ def validate_pattern_scaling_cache(self, cache_file, model_name, scenario="aer")
... print(f"✅ {info['message']}")
"""
expected_name = f"cmip6-{model_name}-{scenario}"
expected_vars = set(self.flds)
if variable is not None:
expected_name = f"{expected_name}-{variable}"
# Per-variable caches only carry that one variable; asking for all
# of self.flds here would spuriously reject a valid single-variable
# cache and force a re-fit.
expected_vars = {variable}
else:
expected_vars = set(self.flds)

info = {
"expected_name": expected_name,
Expand Down
9 changes: 7 additions & 2 deletions src/meteor/meteor_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,14 @@ def _train_pattern_scaling(self, variable, config, verbose=True):
self.model, variable=variable
)

# Check cache
# Check cache. The variable suffix must match the name
# MeteorPatternScaling saves under (``cmip6-{model}-aer-{variable}``);
# without it the validator returns "invalid" for a cache that then
# loads successfully by filename, causing wasted training-data prep.
is_valid, cached_model, info = ( # pylint: disable=unused-variable
self.data_getter.validate_pattern_scaling_cache(cache_file, self.model)
self.data_getter.validate_pattern_scaling_cache(
cache_file, self.model, variable=variable
)
)

if is_valid:
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/test_cmip6_meteor_data_getter.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,35 @@ def test_caching(tmp_path):
assert cached_model is None
assert "Error reading cached file" in info["message"]

# variable= must be honored so that per-variable caches (which
# MeteorPatternScaling saves under `cmip6-{model}-aer-{variable}` and which
# only carry that one variable in patternflds) round-trip.
# Without this, MeteorInterface prepares training data on every call because
# this check returns "invalid" even when the pkl loads fine by filename.
per_var_file = tmp_path / "per_variable.pkl"
per_var_name = "cmip6-ModelA-aer-tas"
with open(per_var_file, "wb") as handle:
pickle.dump(
{"name": per_var_name, "patternflds": {"tas": None}},
handle,
)

# No variable= arg: legacy validator, mismatches per-variable name.
is_valid, _, info = data_getter.validate_pattern_scaling_cache(
str(per_var_file), "ModelA"
)
assert is_valid is False
assert "Model name mismatch" in info["message"]

# variable="tas": matches name AND expected_vars is narrowed to just
# {"tas"} instead of self.flds, so the single-variable pkl is accepted.
is_valid, cached_model, info = data_getter.validate_pattern_scaling_cache(
str(per_var_file), "ModelA", variable="tas"
)
assert is_valid is True
assert cached_model is not None
assert "Cache valid" in info["message"]


# def test_error_handling_for_invalid_experiments_and_fields():
# """Test error handling for invalid experiments and fields to hit lines 585-593."""
Expand Down
Loading