|
30 | 30 | from esmvalcore.cmor.table import CMOR_TABLES
|
31 | 31 | from esmvalcore.exceptions import ESMValCoreDeprecationWarning
|
32 | 32 | from esmvalcore.iris_helpers import has_irregular_grid, has_unstructured_grid
|
| 33 | +from esmvalcore.preprocessor._shared import ( |
| 34 | + get_dims_along_axes, |
| 35 | +) |
33 | 36 | from esmvalcore.preprocessor._shared import (
|
34 | 37 | get_array_module,
|
35 | 38 | preserve_float_dtype,
|
|
39 | 42 | add_cell_measure,
|
40 | 43 | )
|
41 | 44 | from esmvalcore.preprocessor.regrid_schemes import (
|
42 |
| - ESMPyAreaWeighted, |
43 |
| - ESMPyLinear, |
44 |
| - ESMPyNearest, |
45 | 45 | GenericFuncScheme,
|
| 46 | + IrisESMFRegrid, |
46 | 47 | UnstructuredLinear,
|
47 | 48 | UnstructuredNearest,
|
48 | 49 | )
|
|
91 | 92 | # curvilinear grids; i.e., grids that can be described with 2D latitude and 2D
|
92 | 93 | # longitude coordinates with common dimensions)
|
93 | 94 | HORIZONTAL_SCHEMES_IRREGULAR = {
|
94 |
| - 'area_weighted': ESMPyAreaWeighted(), |
95 |
| - 'linear': ESMPyLinear(), |
96 |
| - 'nearest': ESMPyNearest(), |
| 95 | + 'area_weighted': IrisESMFRegrid(method='conservative'), |
| 96 | + 'linear': IrisESMFRegrid(method='bilinear'), |
| 97 | + 'nearest': IrisESMFRegrid(method='nearest'), |
| 98 | +} |
| 99 | + |
| 100 | +# Supported horizontal regridding schemes for meshes |
| 101 | +# https://scitools-iris.readthedocs.io/en/stable/further_topics/ugrid/index.html |
| 102 | +HORIZONTAL_SCHEMES_MESH = { |
| 103 | + 'area_weighted': IrisESMFRegrid(method='conservative'), |
| 104 | + 'linear': IrisESMFRegrid(method='bilinear'), |
| 105 | + 'nearest': IrisESMFRegrid(method='nearest'), |
97 | 106 | }
|
98 | 107 |
|
99 | 108 | # Supported horizontal regridding schemes for unstructured grids (i.e., grids,
|
@@ -533,29 +542,7 @@ def _get_target_grid_cube(
|
533 | 542 | return target_grid_cube
|
534 | 543 |
|
535 | 544 |
|
536 |
| -def _attempt_irregular_regridding(cube: Cube, scheme: str) -> bool: |
537 |
| - """Check if irregular regridding with ESMF should be used.""" |
538 |
| - if not has_irregular_grid(cube): |
539 |
| - return False |
540 |
| - if scheme not in HORIZONTAL_SCHEMES_IRREGULAR: |
541 |
| - raise ValueError( |
542 |
| - f"Regridding scheme '{scheme}' does not support irregular data, " |
543 |
| - f"expected one of {list(HORIZONTAL_SCHEMES_IRREGULAR)}") |
544 |
| - return True |
545 |
| - |
546 |
| - |
547 |
| -def _attempt_unstructured_regridding(cube: Cube, scheme: str) -> bool: |
548 |
| - """Check if unstructured regridding should be used.""" |
549 |
| - if not has_unstructured_grid(cube): |
550 |
| - return False |
551 |
| - if scheme not in HORIZONTAL_SCHEMES_UNSTRUCTURED: |
552 |
| - raise ValueError( |
553 |
| - f"Regridding scheme '{scheme}' does not support unstructured " |
554 |
| - f"data, expected one of {list(HORIZONTAL_SCHEMES_UNSTRUCTURED)}") |
555 |
| - return True |
556 |
| - |
557 |
| - |
558 |
| -def _load_scheme(src_cube: Cube, scheme: str | dict): |
| 545 | +def _load_scheme(src_cube: Cube, tgt_cube: Cube, scheme: str | dict): |
559 | 546 | """Return scheme that can be used in :meth:`iris.cube.Cube.regrid`."""
|
560 | 547 | loaded_scheme: Any = None
|
561 | 548 |
|
@@ -586,23 +573,27 @@ def _load_scheme(src_cube: Cube, scheme: str | dict):
|
586 | 573 | logger.debug("Loaded regridding scheme %s", loaded_scheme)
|
587 | 574 | return loaded_scheme
|
588 | 575 |
|
589 |
| - # Scheme is a dict -> assume this describes a generic regridding scheme |
590 | 576 | if isinstance(scheme, dict):
|
| 577 | + # Scheme is a dict -> assume this describes a generic regridding scheme |
591 | 578 | loaded_scheme = _load_generic_scheme(scheme)
|
592 |
| - |
593 |
| - # Scheme is a str -> load appropriate regridding scheme depending on the |
594 |
| - # type of input data |
595 |
| - elif _attempt_irregular_regridding(src_cube, scheme): |
596 |
| - loaded_scheme = HORIZONTAL_SCHEMES_IRREGULAR[scheme] |
597 |
| - elif _attempt_unstructured_regridding(src_cube, scheme): |
598 |
| - loaded_scheme = HORIZONTAL_SCHEMES_UNSTRUCTURED[scheme] |
599 | 579 | else:
|
600 |
| - loaded_scheme = HORIZONTAL_SCHEMES_REGULAR.get(scheme) |
601 |
| - |
602 |
| - if loaded_scheme is None: |
603 |
| - raise ValueError( |
604 |
| - f"Got invalid regridding scheme string '{scheme}', expected one " |
605 |
| - f"of {list(HORIZONTAL_SCHEMES_REGULAR)}") |
| 580 | + # Scheme is a str -> load appropriate regridding scheme depending on |
| 581 | + # the type of input data |
| 582 | + if has_irregular_grid(src_cube) or has_irregular_grid(tgt_cube): |
| 583 | + grid_type = 'irregular' |
| 584 | + elif src_cube.mesh is not None or tgt_cube.mesh is not None: |
| 585 | + grid_type = 'mesh' |
| 586 | + elif has_unstructured_grid(src_cube): |
| 587 | + grid_type = 'unstructured' |
| 588 | + else: |
| 589 | + grid_type = 'regular' |
| 590 | + |
| 591 | + schemes = globals()[f"HORIZONTAL_SCHEMES_{grid_type.upper()}"] |
| 592 | + if scheme not in schemes: |
| 593 | + raise ValueError( |
| 594 | + f"Regridding scheme '{scheme}' not available for {grid_type} " |
| 595 | + f"data, expected one of: {', '.join(schemes)}") |
| 596 | + loaded_scheme = schemes[scheme] |
606 | 597 |
|
607 | 598 | logger.debug("Loaded regridding scheme %s", loaded_scheme)
|
608 | 599 |
|
@@ -676,14 +667,14 @@ def _get_regridder(
|
676 | 667 | return regridder
|
677 | 668 |
|
678 | 669 | # Regridder is not in cached -> return a new one and cache it
|
679 |
| - loaded_scheme = _load_scheme(src_cube, scheme) |
| 670 | + loaded_scheme = _load_scheme(src_cube, tgt_cube, scheme) |
680 | 671 | regridder = loaded_scheme.regridder(src_cube, tgt_cube)
|
681 | 672 | _CACHED_REGRIDDERS.setdefault(name_shape_key, {})
|
682 | 673 | _CACHED_REGRIDDERS[name_shape_key][coord_key] = regridder
|
683 | 674 |
|
684 | 675 | # (2) Weights caching disabled
|
685 | 676 | else:
|
686 |
| - loaded_scheme = _load_scheme(src_cube, scheme) |
| 677 | + loaded_scheme = _load_scheme(src_cube, tgt_cube, scheme) |
687 | 678 | regridder = loaded_scheme.regridder(src_cube, tgt_cube)
|
688 | 679 |
|
689 | 680 | return regridder
|
@@ -860,36 +851,40 @@ def _cache_clear():
|
860 | 851 |
|
861 | 852 | def _rechunk(cube: Cube, target_grid: Cube) -> Cube:
|
862 | 853 | """Re-chunk cube with optimal chunk sizes for target grid."""
|
863 |
| - if not cube.has_lazy_data() or cube.ndim < 3: |
864 |
| - # Only rechunk lazy multidimensional data |
| 854 | + if not cube.has_lazy_data(): |
| 855 | + # Only rechunk lazy data |
865 | 856 | return cube
|
866 | 857 |
|
867 |
| - lon_coord = target_grid.coord(axis='X') |
868 |
| - lat_coord = target_grid.coord(axis='Y') |
869 |
| - if lon_coord.ndim != 1 or lat_coord.ndim != 1: |
870 |
| - # This function only supports 1D lat/lon coordinates. |
871 |
| - return cube |
| 858 | + # Extract grid dimension information from source cube |
| 859 | + src_grid_indices = get_dims_along_axes(cube, ["X", "Y"]) |
| 860 | + src_grid_shape = tuple(cube.shape[i] for i in src_grid_indices) |
| 861 | + src_grid_ndims = len(src_grid_indices) |
872 | 862 |
|
873 |
| - lon_dim, = target_grid.coord_dims(lon_coord) |
874 |
| - lat_dim, = target_grid.coord_dims(lat_coord) |
875 |
| - grid_indices = sorted((lon_dim, lat_dim)) |
876 |
| - target_grid_shape = tuple(target_grid.shape[i] for i in grid_indices) |
| 863 | + # Extract grid dimension information from target cube. |
| 864 | + tgt_grid_indices = get_dims_along_axes(target_grid, ["X", "Y"]) |
| 865 | + tgt_grid_shape = tuple(target_grid.shape[i] for i in tgt_grid_indices) |
| 866 | + tgt_grid_ndims = len(tgt_grid_indices) |
877 | 867 |
|
878 |
| - if 2 * np.prod(cube.shape[-2:]) > np.prod(target_grid_shape): |
| 868 | + if 2 * np.prod(src_grid_shape) > np.prod(tgt_grid_shape): |
879 | 869 | # Only rechunk if target grid is more than a factor of 2 larger,
|
880 | 870 | # because rechunking will keep the original chunk in memory.
|
881 | 871 | return cube
|
882 | 872 |
|
| 873 | + # Compute a good chunk size for the target array |
| 874 | + # This uses the fact that horizontal dimension(s) are the last dimension(s) |
| 875 | + # of the input cube and also takes into account that iris regridding needs |
| 876 | + # unchunked data along the grid dimensions. |
883 | 877 | data = cube.lazy_data()
|
| 878 | + tgt_shape = data.shape[:-src_grid_ndims] + tgt_grid_shape |
| 879 | + tgt_chunks = data.chunks[:-src_grid_ndims] + tgt_grid_shape |
884 | 880 |
|
885 |
| - # Compute a good chunk size for the target array |
886 |
| - tgt_shape = data.shape[:-2] + target_grid_shape |
887 |
| - tgt_chunks = data.chunks[:-2] + target_grid_shape |
888 |
| - tgt_data = da.empty(tgt_shape, dtype=data.dtype, chunks=tgt_chunks) |
889 |
| - tgt_data = tgt_data.rechunk({i: "auto" for i in range(cube.ndim - 2)}) |
| 881 | + tgt_data = da.empty(tgt_shape, chunks=tgt_chunks, dtype=data.dtype) |
| 882 | + tgt_data = tgt_data.rechunk( |
| 883 | + {i: "auto" |
| 884 | + for i in range(tgt_data.ndim - tgt_grid_ndims)}) |
890 | 885 |
|
891 | 886 | # Adjust chunks to source array and rechunk
|
892 |
| - chunks = tgt_data.chunks[:-2] + data.shape[-2:] |
| 887 | + chunks = tgt_data.chunks[:-tgt_grid_ndims] + data.shape[-src_grid_ndims:] |
893 | 888 | cube.data = data.rechunk(chunks)
|
894 | 889 |
|
895 | 890 | return cube
|
|
0 commit comments