Skip to content

Commit e3eb9ff

Browse files
authored
Allow using multi model statistics preprocessor on datasets without timerange (#2644)
1 parent 11c8eea commit e3eb9ff

File tree

3 files changed

+136
-51
lines changed

3 files changed

+136
-51
lines changed

esmvalcore/_recipe/recipe.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,14 @@ def _get_common_attributes(products, settings):
367367
if all(p.attributes.get(key, object()) == value for p in products):
368368
attributes[key] = value
369369

370-
# Ensure that attribute timerange is always available. This depends on the
371-
# "span" setting: if "span=overlap", the intersection of all periods is
372-
# used; if "span=full", the union is used. The default value for "span" is
373-
# "overlap".
370+
# Ensure that attribute timerange is always available if at least one of
371+
# the input datasets defines it. This depends on the "span" setting: if
372+
# "span=overlap", the intersection of all periods is used; if "span=full",
373+
# the union is used. The default value for "span" is "overlap".
374374
span = settings.get("span", "overlap")
375375
for product in products:
376+
if "timerange" not in product.attributes:
377+
continue
376378
timerange = product.attributes["timerange"]
377379
start, end = _parse_period(timerange)
378380
if "timerange" not in attributes:
@@ -397,10 +399,12 @@ def _get_common_attributes(products, settings):
397399

398400
attributes["timerange"] = _dates_to_timerange(start_date, end_date)
399401

400-
# Ensure that attributes start_year and end_year are always available
401-
start_year, end_year = _parse_period(attributes["timerange"])
402-
attributes["start_year"] = int(str(start_year[0:4]))
403-
attributes["end_year"] = int(str(end_year[0:4]))
402+
# Ensure that attributes start_year and end_year are always available if at
403+
# least one of the input datasets defines it
404+
if "timerange" in attributes:
405+
start_year, end_year = _parse_period(attributes["timerange"])
406+
attributes["start_year"] = int(str(start_year[0:4]))
407+
attributes["end_year"] = int(str(end_year[0:4]))
404408

405409
return attributes
406410

esmvalcore/local.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -539,14 +539,18 @@ def _get_multiproduct_filename(attributes: dict, preproc_dir: Path) -> Path:
539539
# Remove duplicate segments:
540540
filename_segments = list(dict.fromkeys(filename_segments))
541541

542-
# Add period and extension
543-
filename_segments.append(f"{attributes['timerange'].replace('/', '-')}.nc")
542+
# Add time period if possible
543+
if "timerange" in attributes:
544+
filename_segments.append(
545+
f"{attributes['timerange'].replace('/', '-')}"
546+
)
544547

548+
filename = f"{'_'.join(filename_segments)}.nc"
545549
outfile = Path(
546550
preproc_dir,
547551
attributes["diagnostic"],
548552
attributes["variable_group"],
549-
"_".join(filename_segments),
553+
filename,
550554
)
551555

552556
return outfile

tests/unit/recipe/test_recipe.py

Lines changed: 117 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,19 @@ def test_multi_model_filename_full():
241241
assert attributes["end_year"] == 1992
242242

243243

244+
@pytest.mark.parametrize("span", ["full", "overlap"])
245+
def test_multi_model_filename_no_timerange(span):
246+
"""Test timerange in multi-model filename is correct."""
247+
cube = iris.cube.Cube(np.array([1]))
248+
products = [
249+
PreprocessorFile(cube, "A", {}),
250+
PreprocessorFile(cube, "B", {}),
251+
]
252+
settings = {"span": span}
253+
attributes = _recipe._get_common_attributes(products, settings)
254+
assert "timerange" not in attributes
255+
256+
244257
def test_update_multiproduct_multi_model_statistics():
245258
"""Test ``_update_multiproduct``."""
246259
settings = {
@@ -317,15 +330,15 @@ def test_update_multiproduct_multi_model_statistics():
317330
for attr in common_attributes:
318331
assert attr in product.attributes
319332
assert product.attributes[attr] == common_attributes[attr]
320-
assert "alias" in product.attributes
321-
assert "dataset" in product.attributes
322-
assert "multi_model_statistics" in product.attributes
323-
assert "timerange" in product.attributes
324-
assert product.attributes["timerange"] == "2002/2004"
325-
assert "start_year" in product.attributes
326-
assert product.attributes["start_year"] == 2002
327-
assert "end_year" in product.attributes
328-
assert product.attributes["end_year"] == 2004
333+
assert "alias" in product.attributes
334+
assert "dataset" in product.attributes
335+
assert "multi_model_statistics" in product.attributes
336+
assert "timerange" in product.attributes
337+
assert product.attributes["timerange"] == "2002/2004"
338+
assert "start_year" in product.attributes
339+
assert product.attributes["start_year"] == 2002
340+
assert "end_year" in product.attributes
341+
assert product.attributes["end_year"] == 2004
329342
if "MultiModelStd_Dev" in str(product.filename):
330343
assert product.attributes["alias"] == "MultiModelStd_Dev"
331344
assert product.attributes["dataset"] == "MultiModelStd_Dev"
@@ -352,6 +365,72 @@ def test_update_multiproduct_multi_model_statistics():
352365
assert "MultiModelStd_Dev" in str(stats["std_dev"].filename)
353366

354367

368+
def test_update_multiproduct_no_timerange():
369+
"""Test ``_update_multiproduct``."""
370+
settings = {
371+
"multi_model_statistics": {"statistics": ["mean"]},
372+
"save": {"compute": False},
373+
}
374+
common_attributes = {
375+
"project": "CMIP6",
376+
"diagnostic": "d",
377+
"variable_group": "var",
378+
}
379+
cube = iris.cube.Cube(np.array([1]))
380+
products = [
381+
PreprocessorFile(
382+
cube,
383+
"A",
384+
attributes={
385+
"dataset": "a",
386+
**common_attributes,
387+
},
388+
settings=settings,
389+
),
390+
PreprocessorFile(
391+
cube,
392+
"B",
393+
attributes={
394+
"dataset": "b",
395+
**common_attributes,
396+
},
397+
settings=settings,
398+
),
399+
]
400+
order = ("load", "multi_model_statistics", "save")
401+
preproc_dir = "/preproc"
402+
step = "multi_model_statistics"
403+
output, settings = _recipe._update_multiproduct(
404+
products, order, preproc_dir, step
405+
)
406+
407+
assert len(output) == 1
408+
product = list(output)[0]
409+
410+
assert product.filename == Path("/preproc/d/var/CMIP6_MultiModelMean.nc")
411+
412+
for attr in common_attributes:
413+
assert attr in product.attributes
414+
assert product.attributes[attr] == common_attributes[attr]
415+
assert "alias" in product.attributes
416+
assert "dataset" in product.attributes
417+
assert "multi_model_statistics" in product.attributes
418+
assert "timerange" not in product.attributes
419+
assert "start_year" not in product.attributes
420+
assert "end_year" not in product.attributes
421+
assert product.attributes["alias"] == "MultiModelMean"
422+
assert product.attributes["dataset"] == "MultiModelMean"
423+
assert product.attributes["multi_model_statistics"] == "MultiModelMean"
424+
425+
assert len(settings) == 1
426+
output_products = settings["output_products"]
427+
assert len(output_products) == 1
428+
stats = output_products[""]
429+
assert len(stats) == 1
430+
assert "mean" in stats
431+
assert "MultiModelMean" in str(stats["mean"].filename)
432+
433+
355434
def test_update_multiproduct_multi_model_statistics_percentile():
356435
"""Test ``_update_multiproduct``."""
357436
settings = {
@@ -434,15 +513,15 @@ def test_update_multiproduct_multi_model_statistics_percentile():
434513
for attr in common_attributes:
435514
assert attr in product.attributes
436515
assert product.attributes[attr] == common_attributes[attr]
437-
assert "alias" in product.attributes
438-
assert "dataset" in product.attributes
439-
assert "multi_model_statistics" in product.attributes
440-
assert "timerange" in product.attributes
441-
assert product.attributes["timerange"] == "2002/2004"
442-
assert "start_year" in product.attributes
443-
assert product.attributes["start_year"] == 2002
444-
assert "end_year" in product.attributes
445-
assert product.attributes["end_year"] == 2004
516+
assert "alias" in product.attributes
517+
assert "dataset" in product.attributes
518+
assert "multi_model_statistics" in product.attributes
519+
assert "timerange" in product.attributes
520+
assert product.attributes["timerange"] == "2002/2004"
521+
assert "start_year" in product.attributes
522+
assert product.attributes["start_year"] == 2002
523+
assert "end_year" in product.attributes
524+
assert product.attributes["end_year"] == 2004
446525
if "MultiModelPercentile5-0" in str(product.filename):
447526
assert product.attributes["alias"] == "MultiModelPercentile5-0"
448527
assert product.attributes["dataset"] == "MultiModelPercentile5-0"
@@ -513,16 +592,16 @@ def test_update_multiproduct_ensemble_statistics():
513592
for attr in common_attributes:
514593
assert attr in product.attributes
515594
assert product.attributes[attr] == common_attributes[attr]
516-
assert "alias" in product.attributes
517-
assert product.attributes["alias"] == "EnsembleMedian"
518-
assert "dataset" in product.attributes
519-
assert product.attributes["dataset"] == "CanESM2"
520-
assert "ensemble_statistics" in product.attributes
521-
assert product.attributes["ensemble_statistics"] == "EnsembleMedian"
522-
assert "start_year" in product.attributes
523-
assert product.attributes["start_year"] == 2000
524-
assert "end_year" in product.attributes
525-
assert product.attributes["end_year"] == 2000
595+
assert "alias" in product.attributes
596+
assert product.attributes["alias"] == "EnsembleMedian"
597+
assert "dataset" in product.attributes
598+
assert product.attributes["dataset"] == "CanESM2"
599+
assert "ensemble_statistics" in product.attributes
600+
assert product.attributes["ensemble_statistics"] == "EnsembleMedian"
601+
assert "start_year" in product.attributes
602+
assert product.attributes["start_year"] == 2000
603+
assert "end_year" in product.attributes
604+
assert product.attributes["end_year"] == 2000
526605

527606
assert len(settings) == 1
528607
output_products = settings["output_products"]
@@ -585,18 +664,16 @@ def test_update_multiproduct_ensemble_statistics_percentile():
585664
for attr in common_attributes:
586665
assert attr in product.attributes
587666
assert product.attributes[attr] == common_attributes[attr]
588-
assert "alias" in product.attributes
589-
assert product.attributes["alias"] == "EnsemblePercentile5"
590-
assert "dataset" in product.attributes
591-
assert product.attributes["dataset"] == "CanESM2"
592-
assert "ensemble_statistics" in product.attributes
593-
assert product.attributes["ensemble_statistics"] == (
594-
"EnsemblePercentile5"
595-
)
596-
assert "start_year" in product.attributes
597-
assert product.attributes["start_year"] == 2000
598-
assert "end_year" in product.attributes
599-
assert product.attributes["end_year"] == 2000
667+
assert "alias" in product.attributes
668+
assert product.attributes["alias"] == "EnsemblePercentile5"
669+
assert "dataset" in product.attributes
670+
assert product.attributes["dataset"] == "CanESM2"
671+
assert "ensemble_statistics" in product.attributes
672+
assert product.attributes["ensemble_statistics"] == ("EnsemblePercentile5")
673+
assert "start_year" in product.attributes
674+
assert product.attributes["start_year"] == 2000
675+
assert "end_year" in product.attributes
676+
assert product.attributes["end_year"] == 2000
600677

601678
assert len(settings) == 1
602679
output_products = settings["output_products"]

0 commit comments

Comments
 (0)