Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding fix for ORAS5 #2422

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
Empty file.
190 changes: 190 additions & 0 deletions esmvalcore/cmor/_fixes/oras5/_base_fixes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
"""Fix base classes for ORAS5 on-the-fly CMORizer."""

import logging
from pathlib import Path

Check warning on line 4 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L3-L4

Added lines #L3 - L4 were not covered by tests

import dask.array as da
import iris
import numpy as np

Check warning on line 8 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L6-L8

Added lines #L6 - L8 were not covered by tests

# import xarray as xr
# from iris import Constraint
from iris.mesh import Connectivity, MeshXY

Check warning on line 12 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L12

Added line #L12 was not covered by tests

from ..icon.icon import IconFix

Check warning on line 14 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L14

Added line #L14 was not covered by tests

logger = logging.getLogger(__name__)

Check warning on line 16 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L16

Added line #L16 was not covered by tests


class Oras5Fix(IconFix):

Check warning on line 19 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L19

Added line #L19 was not covered by tests
"""Base class for all ORAS5 fixes."""

CACHE_DIR = Path.home() / ".esmvaltool" / "cache"
CACHE_VALIDITY = 7 * 24 * 60 * 60 # [s]; = 1 week
TIMEOUT = 5 * 60 # [s]; = 5 min
GRID_FILE_ATTR = "grid_file_uri"

Check warning on line 25 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L22-L25

Added lines #L22 - L25 were not covered by tests

def __init__(self, *args, **kwargs):

Check warning on line 27 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L27

Added line #L27 was not covered by tests
"""Initialize ORAS5 fix."""
super().__init__(*args, **kwargs)
self._horizontal_grids = {}
self._meshes = {}

Check warning on line 31 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L29-L31

Added lines #L29 - L31 were not covered by tests

def _create_mesh(self, cube):

Check warning on line 33 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L33

Added line #L33 was not covered by tests
"""Create mesh from horizontal grid file."""
# Get coordinates
face_lon = cube.coord("longitude")
face_lat = cube.coord("latitude")
node_lon = cube.coord("longitude").bounds.T.flatten()
node_lat = cube.coord("latitude").bounds.T.flatten()

Check warning on line 39 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L36-L39

Added lines #L36 - L39 were not covered by tests

# Make the node locations a 2D array
nodes_flat = np.stack([node_lon, node_lat], axis=1)

Check warning on line 42 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L42

Added line #L42 was not covered by tests

# Find the unique nodes to be able to associate them with the faces
# Unfortunately, dask does not support the axis parameter...
nodes_unique, indices = np.unique(

Check warning on line 46 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L46

Added line #L46 was not covered by tests
nodes_flat, return_inverse=True, axis=0
)

# Get the unique nodes as dask arrays
node_lon = da.from_array(nodes_unique[:, 0])
node_lat = da.from_array(nodes_unique[:, 1])

Check warning on line 52 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L51-L52

Added lines #L51 - L52 were not covered by tests

# Get dimensions (N_faces x M_nodes)
n_faces = len(face_lat.core_points())
n_nodes = int(len(indices) / n_faces)

Check warning on line 56 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L55-L56

Added lines #L55 - L56 were not covered by tests

# Reshape indices to N_faces x M_nodes dask array
indices = da.reshape(da.from_array(indices), (n_nodes, n_faces)).T

Check warning on line 59 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L59

Added line #L59 was not covered by tests

# Create the necessary mask
mask = da.full(da.shape(indices), False)

Check warning on line 62 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L62

Added line #L62 was not covered by tests

# Define the connectivity
connectivity = Connectivity(

Check warning on line 65 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L65

Added line #L65 was not covered by tests
indices=da.ma.masked_array(indices, mask=mask),
cf_role="face_node_connectivity",
start_index=0,
location_axis=0,
)

# Put everything together to get a U-Grid style mesh
node_lat = iris.coords.AuxCoord(

Check warning on line 73 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L73

Added line #L73 was not covered by tests
node_lat,
standard_name="latitude",
var_name="lat",
long_name="latitude",
units="degrees",
)
node_lon = iris.coords.AuxCoord(

Check warning on line 80 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L80

Added line #L80 was not covered by tests
node_lon,
standard_name="longitude",
var_name="lon",
long_name="longitude",
units="degrees",
)

mesh = MeshXY(

Check warning on line 88 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L88

Added line #L88 was not covered by tests
topology_dimension=2,
node_coords_and_axes=[(node_lat, "y"), (node_lon, "x")],
connectivities=[connectivity],
face_coords_and_axes=[(face_lat, "y"), (face_lon, "x")],
)

return mesh

Check warning on line 95 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L95

Added line #L95 was not covered by tests

Check notice on line 96 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L96

blank line contains whitespace (W293)
def get_horizontal_grid(self, cube):

Check warning on line 97 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L97

Added line #L97 was not covered by tests
"""Get copy of ORAS5 horizontal grid.

If given, retrieve grid from `horizontal_grid` facet specified by the
user.

Parameters
----------
cube: iris.cube.Cube
Cube for which the ORS5 horizontal grid is retrieved. If the facet
`horizontal_grid` is not specified by the user, it raises a

Check notice on line 107 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L107

trailing whitespace (W291)
NotImplementedError.

Returns
-------
iris.cube.CubeList
Copy of ORAS5 horizontal grid.

Raises
------
FileNotFoundError
Path specified by `horizontal_grid` facet (absolute or relative to
`auxiliary_data_dir`) does not exist.
NotImplementedError
No `horizontal_grid` facet is defined.

"""
if self.extra_facets.get("horizontal_grid") is not None:
grid = self._get_grid_from_facet()

Check warning on line 125 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L124-L125

Added lines #L124 - L125 were not covered by tests
else:
raise NotImplementedError(

Check warning on line 127 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L127

Added line #L127 was not covered by tests
"Full path to suitable ORAS5 grid must be specified in facet "
"'horizontal_grid'"
)

return grid.copy()

Check warning on line 132 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L132

Added line #L132 was not covered by tests

def _get_grid_from_facet(self):

Check warning on line 134 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L134

Added line #L134 was not covered by tests
"""Get horizontal grid from user-defined facet `horizontal_grid`."""
grid_path = self._get_path_from_facet(

Check warning on line 136 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L136

Added line #L136 was not covered by tests
"horizontal_grid", "Horizontal grid file"
)
grid_name = grid_path.name

Check warning on line 139 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L139

Added line #L139 was not covered by tests

# If already loaded, return the horizontal grid
if grid_name in self._horizontal_grids:
return self._horizontal_grids[grid_name]

Check warning on line 143 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L142-L143

Added lines #L142 - L143 were not covered by tests

# Load file
self._horizontal_grids[grid_name] = self._load_cubes(grid_path)
logger.debug("Loaded ORAS5 grid file from %s", grid_path)
return self._horizontal_grids[grid_name]

Check warning on line 148 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L146-L148

Added lines #L146 - L148 were not covered by tests

def get_mesh(self, cube):

Check warning on line 150 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L150

Added line #L150 was not covered by tests
"""Get mesh.

Note
----
If possible, this function uses a cached version of the mesh to save
time.

Parameters
----------
cube: iris.cube.Cube
Cube for which the mesh is retrieved.

Returns
-------
iris.mesh.MeshXY
Mesh of the cube.

Raises
------
FileNotFoundError
Path specified by `horizontal_grid` facet (absolute or relative to
`auxiliary_data_dir`) does not exist.
NotImplementedError
No `horizontal_grid` facet is defined.

"""
# Use `horizontal_grid` facet to determine grid name
grid_path = self._get_path_from_facet(

Check warning on line 178 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L178

Added line #L178 was not covered by tests
"horizontal_grid", "Horizontal grid file"

Check notice on line 179 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L179

continuation line over-indented for hanging indent (E126)
)

Check notice on line 180 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L180

continuation line under-indented for hanging indent (E121)
grid_name = grid_path.name

Check warning on line 181 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L181

Added line #L181 was not covered by tests

# Reuse mesh if possible
if grid_name in self._meshes:
logger.debug("Reusing ORAS5 mesh for grid %s", grid_name)

Check warning on line 185 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L184-L185

Added lines #L184 - L185 were not covered by tests
else:
logger.debug("Creating ORAS5 mesh for grid %s", grid_name)
self._meshes[grid_name] = self._create_mesh(cube)

Check warning on line 188 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L187-L188

Added lines #L187 - L188 were not covered by tests

return self._meshes[grid_name]

Check warning on line 190 in esmvalcore/cmor/_fixes/oras5/_base_fixes.py

View check run for this annotation

Codecov / codecov/patch

esmvalcore/cmor/_fixes/oras5/_base_fixes.py#L190

Added line #L190 was not covered by tests
Loading