Skip to content
Draft
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
10 changes: 10 additions & 0 deletions esmvalcore/cmor/_fixes/fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
class Fix:
"""Base class for dataset fixes."""

GROUP_CUBES_BY_DATE = False
"""Flag for grouping cubes for fix_metadata.

Fixes are applied to each group element individually.

If ``False`` (default), group cubes by file. If ``True``, group cubes by
date.

"""

def __init__(
self,
vardef: VariableInfo,
Expand Down
43 changes: 39 additions & 4 deletions esmvalcore/cmor/_fixes/native6/era5.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,44 @@
return cubes


class Rsut(Fix):
"""Fixes for rsut."""

# Enable grouping cubes by date for fix_metadata since multiple variables
# from multiple files are needed
GROUP_CUBES_BY_DATE = True

def fix_metadata(self, cubes):
"""Fix metadata.

Derive rsut as

rsut = rsdt - rsnt

with

rsut = TOA Outgoing Shortwave Radiation
rsdt = TOA Incoming Shortwave Radiation
rsnt = TOA Net Incoming Shortwave Radiation

"""
rsdt_cube = cubes.extract_cube(

Check warning on line 438 in esmvalcore/cmor/_fixes/native6/era5.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/native6/era5.py#L438

Added line #L438 was not covered by tests
iris.NameConstraint(long_name="TOA incident solar radiation"),
)
rsnt_cube = cubes.extract_cube(

Check warning on line 441 in esmvalcore/cmor/_fixes/native6/era5.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/native6/era5.py#L441

Added line #L441 was not covered by tests
iris.NameConstraint(
long_name="Mean top net short-wave radiation flux",
),
)
rsdt_cube = Rsdt(None).fix_metadata([rsdt_cube])[0]

Check notice on line 446 in esmvalcore/cmor/_fixes/native6/era5.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

esmvalcore/cmor/_fixes/native6/era5.py#L446

Call to untyped function "fix_metadata" in typed context. (no-untyped-call)
rsdt_cube.convert_units(self.vardef.units)

Check warning on line 447 in esmvalcore/cmor/_fixes/native6/era5.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/native6/era5.py#L446-L447

Added lines #L446 - L447 were not covered by tests

rsdt_cube.data = rsdt_cube.core_data() - rsnt_cube.core_data()
rsdt_cube.attributes["positive"] = "up"

Check warning on line 450 in esmvalcore/cmor/_fixes/native6/era5.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/native6/era5.py#L449-L450

Added lines #L449 - L450 were not covered by tests

return iris.cube.CubeList([rsdt_cube])

Check warning on line 452 in esmvalcore/cmor/_fixes/native6/era5.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/native6/era5.py#L452

Added line #L452 was not covered by tests


class Rss(Fix):
"""Fixes for Rss."""

Expand Down Expand Up @@ -497,10 +535,7 @@
class AllVars(Fix):
"""Fixes for all variables."""

def _fix_coordinates( # noqa: C901
self,
cube,
):
def _fix_coordinates(self, cube): # noqa: C901

Check notice on line 538 in esmvalcore/cmor/_fixes/native6/era5.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

esmvalcore/cmor/_fixes/native6/era5.py#L538

AllVars._fix_coordinates is too complex (13) (MC0001)

Check notice on line 538 in esmvalcore/cmor/_fixes/native6/era5.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

esmvalcore/cmor/_fixes/native6/era5.py#L538

Function is missing a type annotation. (no-untyped-def)
"""Fix coordinates."""
# Add scalar height coordinates
if "height2m" in self.vardef.dimensions:
Expand Down
42 changes: 32 additions & 10 deletions esmvalcore/cmor/fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@

import logging
from collections import defaultdict
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from iris.cube import Cube, CubeList

from esmvalcore.cmor._fixes.fix import Fix
from esmvalcore.local import _get_start_end_date

if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Iterable, Sequence
from pathlib import Path

from esmvalcore.config import Session
Expand Down Expand Up @@ -102,6 +103,27 @@
return file


def _group_cubes(fixes: Iterable[Fix], cubes: CubeList) -> dict[Any, CubeList]:
"""Group cubes for fix_metadata; each group is processed individually."""
grouped_cubes: dict[Any, CubeList] = defaultdict(CubeList)

# Group by date
if any(fix.GROUP_CUBES_BY_DATE for fix in fixes):
for cube in cubes:
if "source_file" in cube.attributes:
dates = _get_start_end_date(cube.attributes["source_file"])

Check warning on line 114 in esmvalcore/cmor/fix.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/fix.py#L112-L114

Added lines #L112 - L114 were not covered by tests
else:
dates = None
grouped_cubes[dates].append(cube)

Check warning on line 117 in esmvalcore/cmor/fix.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/fix.py#L116-L117

Added lines #L116 - L117 were not covered by tests

# Group by file name
else:
for cube in cubes:
grouped_cubes[cube.attributes.get("source_file", "")].append(cube)

return grouped_cubes


def fix_metadata(
cubes: Sequence[Cube],
short_name: str,
Expand Down Expand Up @@ -166,14 +188,14 @@
)
fixed_cubes = CubeList()

# Group cubes by input file and apply all fixes to each group element
# (i.e., each file) individually
by_file = defaultdict(list)
for cube in cubes:
by_file[cube.attributes.get("source_file", "")].append(cube)

for cube_list in by_file.values():
cube_list = CubeList(cube_list)
# Group cubes and apply all fixes to each group element individually. There
# are two options for grouping:
# (1) By input file name (default).
# (2) By time range (can be enabled by setting the attribute
# GROUP_CUBES_BY_DATE=True for the fix class; see
# _fixes.native6.era5.Rsut for an example).
grouped_cubes = _group_cubes(fixes, cubes)
for cube_list in grouped_cubes.values():
for fix in fixes:
cube_list = fix.fix_metadata(cube_list)
Comment on lines 189 to 200
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be nicer to define the grouping operation on the Fix object, so this code would look like:

    fixed_cubes = CubeList(cubes)
    for fix in fixes:
        fixed_cubes = CubeList(
            cube
            for group in fix.group_input_for_fix_metadata(fixed_cubes)
            for cube in fix.fix_metadata(group)
        )


Expand Down
1 change: 1 addition & 0 deletions tests/unit/cmor/test_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def setUp(self):
self.cube = self._create_mock_cube()
self.fixed_cube = self._create_mock_cube()
self.mock_fix = Mock()
self.mock_fix.GROUP_CUBES_BY_DATE = False
self.mock_fix.fix_metadata.return_value = [self.fixed_cube]
self.expected_get_fixes_call = {
"project": "project",
Expand Down