diff --git a/ci/default.yml b/ci/default.yml index 5fe2b10db8..cba7314809 100644 --- a/ci/default.yml +++ b/ci/default.yml @@ -42,11 +42,11 @@ test_tools_datatests_aarch64: - if: $COMPONENT == 'common' && $LEVEL == 'integration' variables: NUM_PROCESSES: 1 - SLURM_TIMELIMIT: '00:30:00' + SLURM_TIMELIMIT: '00:45:00' - if: $BACKEND == 'dace_gpu' variables: NUM_PROCESSES: 8 - SLURM_TIMELIMIT: '00:45:00' + SLURM_TIMELIMIT: '01:00:00' - if: $BACKEND == 'embedded' variables: SLURM_TIMELIMIT: '00:15:00' diff --git a/model/common/src/icon4py/model/common/grid/geometry.py b/model/common/src/icon4py/model/common/grid/geometry.py index b43bfb7184..b489192dae 100644 --- a/model/common/src/icon4py/model/common/grid/geometry.py +++ b/model/common/src/icon4py/model/common/grid/geometry.py @@ -138,6 +138,8 @@ def __init__( input_fields_provider = factory.PrecomputedFieldProvider( { # TODO(halungge): rescaled by grid_length_rescale_factor (mo_grid_tools.f90) + attrs.EDGE_LENGTH: extra_fields[gridfile.GeometryName.EDGE_LENGTH], + attrs.DUAL_EDGE_LENGTH: extra_fields[gridfile.GeometryName.DUAL_EDGE_LENGTH], attrs.EDGE_CELL_DISTANCE: extra_fields[gridfile.GeometryName.EDGE_CELL_DISTANCE], attrs.EDGE_VERTEX_DISTANCE: extra_fields[ gridfile.GeometryName.EDGE_VERTEX_DISTANCE @@ -175,50 +177,12 @@ def __init__( self._register_computed_fields() def _register_computed_fields(self) -> None: - edge_length_provider = factory.ProgramFieldProvider( - func=stencils.compute_edge_length, - domain={ - dims.EdgeDim: ( - self._edge_domain(h_grid.Zone.LOCAL), - self._edge_domain(h_grid.Zone.LOCAL), - ) - }, - fields={ - "length": attrs.EDGE_LENGTH, - }, - deps={ - "vertex_lat": attrs.VERTEX_LAT, - "vertex_lon": attrs.VERTEX_LON, - }, - params={"radius": self._grid.global_properties.radius}, - ) - self.register_provider(edge_length_provider) meta = attrs.metadata_for_inverse(attrs.attrs[attrs.EDGE_LENGTH]) name = meta["standard_name"] self._attrs.update({name: meta}) inverse_edge_length = self._inverse_field_provider(attrs.EDGE_LENGTH) self.register_provider(inverse_edge_length) - dual_length_provider = factory.ProgramFieldProvider( - func=stencils.compute_cell_center_arc_distance, - domain={ - dims.EdgeDim: ( - self._edge_domain(h_grid.Zone.LOCAL), - self._edge_domain(h_grid.Zone.LOCAL), - ) - }, - fields={ - "dual_edge_length": attrs.DUAL_EDGE_LENGTH, - }, - deps={ - "edge_neighbor_0_lat": "latitude_of_edge_cell_neighbor_0", - "edge_neighbor_0_lon": "longitude_of_edge_cell_neighbor_0", - "edge_neighbor_1_lat": "latitude_of_edge_cell_neighbor_1", - "edge_neighbor_1_lon": "longitude_of_edge_cell_neighbor_1", - }, - params={"radius": self._grid.global_properties.radius}, - ) - self.register_provider(dual_length_provider) inverse_dual_edge_length = self._inverse_field_provider(attrs.DUAL_EDGE_LENGTH) self.register_provider(inverse_dual_edge_length) diff --git a/model/common/src/icon4py/model/common/grid/grid_manager.py b/model/common/src/icon4py/model/common/grid/grid_manager.py index a5730a65d4..a1f976eae8 100644 --- a/model/common/src/icon4py/model/common/grid/grid_manager.py +++ b/model/common/src/icon4py/model/common/grid/grid_manager.py @@ -120,9 +120,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): def __call__(self, backend: gtx_typing.Backend | None, keep_skip_values: bool): if not self._reader: self.open() + self._geometry = self._read_geometry_fields(backend) self._grid = self._construct_grid(backend=backend, with_skip_values=keep_skip_values) self._coordinates = self._read_coordinates(backend) - self._geometry = self._read_geometry_fields(backend) self.close() def _read_coordinates(self, backend: gtx_typing.Backend | None) -> CoordinateDict: @@ -186,6 +186,16 @@ def _read_geometry_fields(self, backend: gtx_typing.Backend | None): self._reader.variable(gridfile.GeometryName.DUAL_AREA), allocator=backend, ), + gridfile.GeometryName.EDGE_LENGTH.value: gtx.as_field( + (dims.EdgeDim,), + self._reader.variable(gridfile.GeometryName.EDGE_LENGTH), + allocator=backend, + ), + gridfile.GeometryName.DUAL_EDGE_LENGTH.value: gtx.as_field( + (dims.EdgeDim,), + self._reader.variable(gridfile.GeometryName.DUAL_EDGE_LENGTH), + allocator=backend, + ), gridfile.GeometryName.EDGE_CELL_DISTANCE.value: gtx.as_field( (dims.EdgeDim, dims.E2CDim), self._reader.variable(gridfile.GeometryName.EDGE_CELL_DISTANCE, transpose=True), @@ -285,11 +295,44 @@ def _construct_grid( grid_level = self._reader.attribute(gridfile.MandatoryPropertyName.LEVEL) if geometry_type := self._reader.try_attribute(gridfile.MPIMPropertyName.GEOMETRY): geometry_type = base.GeometryType(geometry_type) - global_params = icon.GlobalGridParams( + sphere_radius = self._reader.try_attribute(gridfile.MPIMPropertyName.SPHERE_RADIUS) + domain_length = self._reader.try_attribute(gridfile.MPIMPropertyName.DOMAIN_LENGTH) + domain_height = self._reader.try_attribute(gridfile.MPIMPropertyName.DOMAIN_HEIGHT) + + # TODO(msimberg): Compute these in GridGeometry once FieldProviders can produce scalars. + # This will also allow easier handling once grids are distributed. + mean_edge_length = self._reader.try_attribute(gridfile.MPIMPropertyName.MEAN_EDGE_LENGTH) + mean_dual_edge_length = self._reader.try_attribute( + gridfile.MPIMPropertyName.MEAN_DUAL_EDGE_LENGTH + ) + mean_cell_area = self._reader.try_attribute(gridfile.MPIMPropertyName.MEAN_CELL_AREA) + mean_dual_cell_area = self._reader.try_attribute( + gridfile.MPIMPropertyName.MEAN_DUAL_CELL_AREA + ) + + edge_lengths = self.geometry[gridfile.GeometryName.EDGE_LENGTH.value].ndarray + dual_edge_lengths = self.geometry[gridfile.GeometryName.DUAL_EDGE_LENGTH.value].ndarray + cell_areas = self.geometry[gridfile.GeometryName.CELL_AREA.value].ndarray + dual_cell_areas = self.geometry[gridfile.GeometryName.DUAL_AREA.value].ndarray + + global_params = icon.GlobalGridParams.from_fields( + backend=backend, grid_shape=icon.GridShape( geometry_type=geometry_type, subdivision=icon.GridSubdivision(root=grid_root, level=grid_level), - ) + ), + radius=sphere_radius, + domain_length=domain_length, + domain_height=domain_height, + num_cells=num_cells, + mean_edge_length=mean_edge_length, + mean_dual_edge_length=mean_dual_edge_length, + mean_cell_area=mean_cell_area, + mean_dual_cell_area=mean_dual_cell_area, + edge_lengths=edge_lengths, + dual_edge_lengths=dual_edge_lengths, + cell_areas=cell_areas, + dual_cell_areas=dual_cell_areas, ) grid_size = base.HorizontalGridSize( num_vertices=num_vertices, num_edges=num_edges, num_cells=num_cells diff --git a/model/common/src/icon4py/model/common/grid/gridfile.py b/model/common/src/icon4py/model/common/grid/gridfile.py index 31f227bbee..0eaeee8c2b 100644 --- a/model/common/src/icon4py/model/common/grid/gridfile.py +++ b/model/common/src/icon4py/model/common/grid/gridfile.py @@ -179,6 +179,8 @@ class GeometryName(FieldName): CELL_AREA = "cell_area" # TODO(halungge): compute from coordinates DUAL_AREA = "dual_area" + EDGE_LENGTH = "edge_length" + DUAL_EDGE_LENGTH = "dual_edge_length" CELL_NORMAL_ORIENTATION = "orientation_of_normal" TANGENT_ORIENTATION = "edge_system_orientation" EDGE_ORIENTATION_ON_VERTEX = "edge_orientation" diff --git a/model/common/src/icon4py/model/common/grid/icon.py b/model/common/src/icon4py/model/common/grid/icon.py index 405897d9ea..61f23788d2 100644 --- a/model/common/src/icon4py/model/common/grid/icon.py +++ b/model/common/src/icon4py/model/common/grid/icon.py @@ -6,11 +6,10 @@ # Please, refer to the LICENSE file in the root directory. # SPDX-License-Identifier: BSD-3-Clause import dataclasses -import functools import logging import math from collections.abc import Callable -from typing import Final +from typing import Final, TypeVar import gt4py.next as gtx from gt4py.next import allocators as gtx_allocators @@ -76,60 +75,104 @@ def __init__( self.subdivision = subdivision -@dataclasses.dataclass +_T = TypeVar("_T") + + +@dataclasses.dataclass(kw_only=True, frozen=True) class GlobalGridParams: grid_shape: Final[GridShape | None] = None radius: float = constants.EARTH_RADIUS - num_cells: int - mean_cell_area: float - - def __init__( - self, - *, - grid_shape: GridShape | None = None, - radius: float = constants.EARTH_RADIUS, - num_cells: int | None = None, + domain_length: float | None = None + domain_height: float | None = None + global_num_cells: int | None = None + num_cells: int | None = None + mean_edge_length: float | None = None + mean_dual_edge_length: float | None = None + mean_cell_area: float | None = None + mean_dual_cell_area: float | None = None + characteristic_length: float | None = None + + @classmethod + def from_fields( + cls: type[_T], + backend: gtx.typing.Backend | None, + mean_edge_length: float | None = None, + edge_lengths: data_alloc.NDArray | None = None, + mean_dual_edge_length: float | None = None, + dual_edge_lengths: data_alloc.NDArray | None = None, mean_cell_area: float | None = None, - ) -> None: - self.grid_shape = grid_shape - self.radius = radius - - if num_cells is not None: - self.num_cells = num_cells + cell_areas: data_alloc.NDArray | None = None, + mean_dual_cell_area: float | None = None, + dual_cell_areas: data_alloc.NDArray | None = None, + **kwargs, + ) -> _T: + xp = data_alloc.import_array_ns(backend) + + def init_mean(value: float | None, data: data_alloc.NDArray | None) -> float | None: + if value is not None: + return value + if data is not None: + return xp.mean(data).item() + return None + + mean_edge_length = init_mean(mean_edge_length, edge_lengths) + mean_dual_edge_length = init_mean(mean_dual_edge_length, dual_edge_lengths) + mean_cell_area = init_mean(mean_cell_area, cell_areas) + mean_dual_cell_area = init_mean(mean_dual_cell_area, dual_cell_areas) + + return cls( + mean_edge_length=mean_edge_length, + mean_dual_edge_length=mean_dual_edge_length, + mean_cell_area=mean_cell_area, + mean_dual_cell_area=mean_dual_cell_area, + **kwargs, + ) - if mean_cell_area is not None: - self.mean_cell_area = mean_cell_area + def __post_init__(self) -> None: + if self.geometry_type is not None: + match self.geometry_type: + case base.GeometryType.ICOSAHEDRON: + object.__setattr__(self, "domain_length", None) + object.__setattr__(self, "domain_height", None) + if self.radius is None: + object.__setattr__(self, "radius", constants.EARTH_RADIUS) + case base.GeometryType.TORUS: + object.__setattr__(self, "radius", None) + case _: + ... + + if self.global_num_cells is None and self.geometry_type is base.GeometryType.ICOSAHEDRON: + object.__setattr__( + self, + "global_num_cells", + compute_icosahedron_num_cells(self.grid_shape.subdivision), + ) + + if self.num_cells is None and self.global_num_cells is not None: + object.__setattr__(self, "num_cells", self.global_num_cells) + + if ( + self.mean_cell_area is None + and self.radius is not None + and self.global_num_cells is not None + and self.geometry_type is base.GeometryType.ICOSAHEDRON + ): + object.__setattr__( + self, + "mean_cell_area", + compute_mean_cell_area_for_sphere(self.radius, self.global_num_cells), + ) + + if self.characteristic_length is None and self.mean_cell_area is not None: + object.__setattr__(self, "characteristic_length", math.sqrt(self.mean_cell_area)) @property def geometry_type(self) -> base.GeometryType | None: return self.grid_shape.geometry_type if self.grid_shape else None - @functools.cached_property - def num_cells(self) -> int: - match self.geometry_type: - case base.GeometryType.ICOSAHEDRON: - assert self.grid_shape.subdivision is not None - return compute_icosahedron_num_cells(self.grid_shape.subdivision) - case base.GeometryType.TORUS: - raise NotImplementedError("TODO : lookup torus cell number computation") - case _: - raise ValueError(f"Unknown geometry type {self.geometry_type}") - - @functools.cached_property - def characteristic_length(self) -> float: - return math.sqrt(self.mean_cell_area) - - @functools.cached_property - def mean_cell_area(self) -> float: - match self.geometry_type: - case base.GeometryType.ICOSAHEDRON: - return compute_mean_cell_area_for_sphere(self.radius, self.num_cells) - case base.GeometryType.TORUS: - raise NotImplementedError( - f"mean_cell_area not implemented for {self.geometry_type}" - ) - case _: - raise NotImplementedError(f"Unknown geometry type {self.geometry_type}") + @property + def subdivision(self) -> GridSubdivision | None: + return self.grid_shape.subdivision if self.grid_shape else None def compute_icosahedron_num_cells(subdivision: GridSubdivision) -> int: diff --git a/model/common/tests/common/grid/unit_tests/test_grid_manager.py b/model/common/tests/common/grid/unit_tests/test_grid_manager.py index a44a976808..187c5b017b 100644 --- a/model/common/tests/common/grid/unit_tests/test_grid_manager.py +++ b/model/common/tests/common/grid/unit_tests/test_grid_manager.py @@ -360,7 +360,7 @@ def test_grid_manager_grid_level_and_root( global_num_cells == utils.run_grid_manager( grid_descriptor, keep_skip_values=True, backend=backend - ).grid.global_properties.num_cells + ).grid.global_properties.global_num_cells ) diff --git a/model/common/tests/common/grid/unit_tests/test_icon.py b/model/common/tests/common/grid/unit_tests/test_icon.py index 3af1188cfd..2cfef6f978 100644 --- a/model/common/tests/common/grid/unit_tests/test_icon.py +++ b/model/common/tests/common/grid/unit_tests/test_icon.py @@ -18,6 +18,7 @@ from icon4py.model.common import constants, dimension as dims from icon4py.model.common.grid import base, gridfile, horizontal as h_grid, icon +from icon4py.model.common.utils import data_allocation as data_alloc from icon4py.model.testing import definitions, grid_utils as gridtest_utils from icon4py.model.testing.fixtures import ( backend, @@ -173,7 +174,8 @@ def test_grid_size(icon_grid: base_grid.Grid) -> None: @pytest.mark.parametrize( - "grid_descriptor", (definitions.Grids.MCH_CH_R04B09_DSL, definitions.Grids.R02B04_GLOBAL) + "grid_descriptor", + (definitions.Grids.MCH_CH_R04B09_DSL, definitions.Grids.R02B04_GLOBAL), ) @pytest.mark.parametrize("offset", (utils.horizontal_offsets()), ids=lambda x: x.value) def test_when_keep_skip_value_then_neighbor_table_matches_config( @@ -194,11 +196,14 @@ def test_when_keep_skip_value_then_neighbor_table_matches_config( @pytest.mark.parametrize( - "grid_descriptor", (definitions.Grids.MCH_CH_R04B09_DSL, definitions.Grids.R02B04_GLOBAL) + "grid_descriptor", + (definitions.Grids.MCH_CH_R04B09_DSL, definitions.Grids.R02B04_GLOBAL), ) @pytest.mark.parametrize("dim", (utils.local_dims())) def test_when_replace_skip_values_then_only_pentagon_points_remain( - grid_descriptor: definitions.GridDescription, dim: gtx.Dimension, backend: gtx_typing.Backend + grid_descriptor: definitions.GridDescription, + dim: gtx.Dimension, + backend: gtx_typing.Backend, ) -> None: if dim == dims.V2E2VDim: pytest.skip("V2E2VDim is not supported in the current grid configuration.") @@ -221,7 +226,7 @@ def _sphere_area(radius: float) -> float: @pytest.mark.parametrize( - "geometry_type,grid_root,grid_level,num_cells,mean_cell_area,expected_num_cells,expected_mean_cell_area", + "geometry_type,grid_root,grid_level,global_num_cells,num_cells,mean_cell_area,expected_global_num_cells,expected_num_cells,expected_mean_cell_area", [ ( base.GeometryType.ICOSAHEDRON, @@ -229,6 +234,8 @@ def _sphere_area(radius: float) -> float: 0, None, None, + None, + 20, 20, _sphere_area(constants.EARTH_RADIUS) / 20, ), @@ -238,6 +245,8 @@ def _sphere_area(radius: float) -> float: 1, None, None, + None, + 20 * 4, 20 * 4, _sphere_area(constants.EARTH_RADIUS) / (20 * 4), ), @@ -247,24 +256,28 @@ def _sphere_area(radius: float) -> float: 2, None, None, + None, + 20 * 16, 20 * 16, _sphere_area(constants.EARTH_RADIUS) / (20 * 16), ), - (base.GeometryType.ICOSAHEDRON, 2, 4, None, None, 20480, 24907282236.708576), - (base.GeometryType.ICOSAHEDRON, 4, 9, None, None, 83886080, 6080879.45232143), - (base.GeometryType.ICOSAHEDRON, 2, 4, 42, 123.456, 42, 123.456), - (base.GeometryType.ICOSAHEDRON, 4, 9, None, 123.456, 83886080, 123.456), - (base.GeometryType.ICOSAHEDRON, 4, 9, 42, None, 42, 12145265243042.658), - (base.GeometryType.TORUS, 2, 0, 42, None, 42, None), - (base.GeometryType.TORUS, None, None, 42, None, 42, None), + (base.GeometryType.ICOSAHEDRON, 2, 4, None, None, None, 20480, 20480, 24907282236.708576), + (base.GeometryType.ICOSAHEDRON, 4, 9, 765, None, None, 765, 765, 666798876088.6165), + (base.GeometryType.ICOSAHEDRON, 2, 4, None, 42, 123.456, 20480, 42, 123.456), + (base.GeometryType.ICOSAHEDRON, 4, 9, None, None, 123.456, 83886080, 83886080, 123.456), + (base.GeometryType.ICOSAHEDRON, 4, 9, None, 42, None, 83886080, 42, 6080879.45232143), + (base.GeometryType.TORUS, 2, 0, None, 42, 123.456, None, 42, 123.456), + (base.GeometryType.TORUS, None, None, None, 42, None, None, 42, None), ], ) def test_global_grid_params( geometry_type: base.GeometryType, grid_root: int | None, grid_level: int | None, + global_num_cells: int | None, num_cells: int | None, mean_cell_area: float | None, + expected_global_num_cells: int | None, expected_num_cells: int | None, expected_mean_cell_area: float | None, ) -> None: @@ -273,12 +286,20 @@ def test_global_grid_params( params = icon.GlobalGridParams( grid_shape=icon.GridShape( geometry_type=geometry_type, - subdivision=icon.GridSubdivision(root=grid_root, level=grid_level) # type: ignore[arg-type] - if grid_root is not None - else None, + subdivision=( + icon.GridSubdivision(root=grid_root, level=grid_level) # type: ignore[arg-type] + if grid_root is not None + else None + ), ), + domain_length=42.0, + domain_height=100.5, + global_num_cells=global_num_cells, num_cells=num_cells, + mean_edge_length=13.0, + mean_dual_edge_length=None, mean_cell_area=mean_cell_area, + mean_dual_cell_area=None, ) assert geometry_type == params.geometry_type if geometry_type == base.GeometryType.TORUS: @@ -288,13 +309,69 @@ def test_global_grid_params( assert ( icon.GridSubdivision(root=grid_root, level=grid_level) == params.grid_shape.subdivision # type: ignore[arg-type, union-attr] ) - assert expected_num_cells == params.num_cells if geometry_type == base.GeometryType.TORUS: - with pytest.raises(NotImplementedError) as e: - assert expected_mean_cell_area == params.mean_cell_area - e.match("mean_cell_area is not implemented for GeometryType.TORUS") + assert params.radius is None + assert params.domain_length == 42.0 + assert params.domain_height == 100.5 else: - assert expected_mean_cell_area == params.mean_cell_area + assert pytest.approx(params.radius) == constants.EARTH_RADIUS + assert params.domain_length is None + assert params.domain_height is None + assert params.global_num_cells == expected_global_num_cells + assert params.num_cells == expected_num_cells + assert pytest.approx(params.mean_edge_length) == 13.0 + assert params.mean_dual_edge_length is None + assert pytest.approx(params.mean_cell_area) == expected_mean_cell_area + assert params.mean_dual_cell_area is None + if expected_mean_cell_area is not None: + assert pytest.approx(params.characteristic_length) == math.sqrt(expected_mean_cell_area) + + +@pytest.mark.parametrize( + "geometry_type", + [base.GeometryType.ICOSAHEDRON, base.GeometryType.TORUS], +) +def test_global_grid_params_from_fields( + geometry_type: base.GeometryType, + backend: gtx_typing.Backend, +) -> None: + xp = data_alloc.import_array_ns(backend) + + # Means provided directly (higher priority than calculating from fields) + params = icon.GlobalGridParams.from_fields( + grid_shape=icon.GridShape( + geometry_type=geometry_type, subdivision=icon.GridSubdivision(root=2, level=2) + ), + mean_edge_length=13.0, + mean_dual_edge_length=14.0, + mean_cell_area=15.0, + mean_dual_cell_area=16.0, + edge_lengths=xp.asarray([1.0, 2.0]), + dual_edge_lengths=xp.asarray([2.0, 3.0]), + cell_areas=xp.asarray([3.0, 4.0]), + dual_cell_areas=xp.asarray([4.0, 5.0]), + backend=backend, + ) + assert pytest.approx(params.mean_edge_length) == 13.0 + assert pytest.approx(params.mean_dual_edge_length) == 14.0 + assert pytest.approx(params.mean_cell_area) == 15.0 + assert pytest.approx(params.mean_dual_cell_area) == 16.0 + + # Means computed from fields + params = icon.GlobalGridParams.from_fields( + grid_shape=icon.GridShape( + geometry_type=geometry_type, subdivision=icon.GridSubdivision(root=2, level=2) + ), + edge_lengths=xp.asarray([1.0, 2.0]), + dual_edge_lengths=xp.asarray([2.0, 3.0]), + cell_areas=xp.asarray([3.0, 4.0]), + dual_cell_areas=xp.asarray([4.0, 5.0]), + backend=backend, + ) + assert pytest.approx(params.mean_edge_length) == 1.5 + assert pytest.approx(params.mean_dual_edge_length) == 2.5 + assert pytest.approx(params.mean_cell_area) == 3.5 + assert pytest.approx(params.mean_dual_cell_area) == 4.5 @pytest.mark.parametrize( @@ -305,17 +382,172 @@ def test_global_grid_params( (None, None, None), ], ) -def test_global_grid_params_fail( - geometry_type: base.GeometryType, - grid_root: int, - grid_level: int, -) -> None: +def test_grid_shape_fail(geometry_type: base.GeometryType, grid_root: int, grid_level: int) -> None: with pytest.raises(ValueError): - _ = icon.GlobalGridParams( - grid_shape=icon.GridShape( - geometry_type=geometry_type, - subdivision=icon.GridSubdivision(root=grid_root, level=grid_level) + _ = icon.GridShape( + geometry_type=geometry_type, + subdivision=( + icon.GridSubdivision(root=grid_root, level=grid_level) if grid_root is not None - else None, - ) + else None + ), ) + + +@pytest.mark.datatest +@pytest.mark.parametrize( + "grid_descriptor, geometry_type, subdivision, radius, domain_length, domain_height, global_num_cells, num_cells, mean_edge_length, mean_dual_edge_length, mean_cell_area, mean_dual_cell_area, characteristic_length", + [ + ( + definitions.Grids.R02B04_GLOBAL, + base.GeometryType.ICOSAHEDRON, + icon.GridSubdivision(root=2, level=4), + constants.EARTH_RADIUS, + None, + None, + 20480, + 20480, + 240221.1036647776, + 138710.63736114913, + 24906292887.251026, + 49802858653.68937, + 157817.27689721118, + ), + ( + definitions.Grids.R02B07_GLOBAL, + base.GeometryType.ICOSAHEDRON, + icon.GridSubdivision(root=2, level=7), + constants.EARTH_RADIUS, + None, + None, + 1310720, + 1310720, + 30050.07607616417, + 17349.90054929857, + 389176284.94852674, + 778350194.5608561, + 19727.55141796687, + ), + ( + definitions.Grids.R19_B07_MCH_LOCAL, + base.GeometryType.ICOSAHEDRON, + icon.GridSubdivision(root=19, level=7), + constants.EARTH_RADIUS, + None, + None, + 118292480, + 283876, + 3092.8086192896153, + 1782.4707626479924, + 4119096.374920686, + 8192823.87559748, + 2029.555708750239, + ), + ( + definitions.Grids.MCH_OPR_R04B07_DOMAIN01, + base.GeometryType.ICOSAHEDRON, + icon.GridSubdivision(root=4, level=7), + constants.EARTH_RADIUS, + None, + None, + 5242880, + 10700, + 14295.416301386269, + 8173.498324820434, + 87967127.69851978, + 170825432.57740065, + 9379.079256436624, + ), + ( + definitions.Grids.MCH_OPR_R19B08_DOMAIN01, + base.GeometryType.ICOSAHEDRON, + icon.GridSubdivision(root=19, level=8), + constants.EARTH_RADIUS, + None, + None, + 473169920, + 44528, + 1546.76182117618, + 889.1206039451661, + 1029968.5064089653, + 2032098.7893505183, + 1014.8736406119558, + ), + ( + definitions.Grids.MCH_CH_R04B09_DSL, + base.GeometryType.ICOSAHEDRON, + icon.GridSubdivision(root=4, level=9), + constants.EARTH_RADIUS, + None, + None, + 83886080, + 20896, + 3803.019140934253, + 2180.911493355989, + 6256048.940145881, + 12259814.063180268, + 2501.209495453326, + ), + ( + definitions.Grids.TORUS_100X116_1000M, + base.GeometryType.TORUS, + None, + None, + 100000.0, + 100458.94683899487, + None, + 23200, + 1000.0, + 577.3502691896258, + 433012.7018922193, + 866025.4037844389, + 658.0370064762462, + ), + ( + definitions.Grids.TORUS_50000x5000, + base.GeometryType.TORUS, + None, + None, + 50000.0, + 5248.638810814779, + None, + 1056, + 757.5757575757576, + 437.3865675678984, + 248515.09520903317, + 497030.1904180664, + 498.51288369412595, + ), + ], +) +def test_global_grid_params_from_grid_manager( + grid_descriptor: definitions.GridDescription, + backend: gtx_typing.Backend, + geometry_type: base.GeometryType, + subdivision: base.GridSubdivision, + radius: float, + domain_length: float, + domain_height: float, + global_num_cells: int, + num_cells: int, + mean_edge_length: float, + mean_dual_edge_length: float, + mean_cell_area: float, + mean_dual_cell_area: float, + characteristic_length: float, +) -> None: + grid = utils.run_grid_manager(grid_descriptor, keep_skip_values=True, backend=backend).grid + params = grid.global_properties + assert params is not None + assert pytest.approx(params.geometry_type) == geometry_type + assert pytest.approx(params.subdivision) == subdivision + assert pytest.approx(params.radius) == radius + assert pytest.approx(params.domain_length) == domain_length + assert pytest.approx(params.domain_height) == domain_height + assert pytest.approx(params.global_num_cells) == global_num_cells + assert pytest.approx(params.num_cells) == num_cells + assert pytest.approx(params.mean_edge_length) == mean_edge_length + assert pytest.approx(params.mean_dual_edge_length) == mean_dual_edge_length + assert pytest.approx(params.mean_cell_area) == mean_cell_area + assert pytest.approx(params.mean_dual_cell_area) == mean_dual_cell_area + assert pytest.approx(params.characteristic_length) == characteristic_length diff --git a/model/common/tests/common/interpolation/unit_tests/test_interpolation_factory.py b/model/common/tests/common/interpolation/unit_tests/test_interpolation_factory.py index 36d33884da..2e2aa1fe7a 100644 --- a/model/common/tests/common/interpolation/unit_tests/test_interpolation_factory.py +++ b/model/common/tests/common/interpolation/unit_tests/test_interpolation_factory.py @@ -406,7 +406,7 @@ def test_nudgecoeffs( "experiment, atol", [ (definitions.Experiments.EXCLAIM_APE, 3e-9), - (definitions.Experiments.MCH_CH_R04B09, 3e-2), + (definitions.Experiments.MCH_CH_R04B09, 4e-2), ], ) @pytest.mark.datatest diff --git a/model/testing/src/icon4py/model/testing/definitions.py b/model/testing/src/icon4py/model/testing/definitions.py index 33bca1b698..29159b1a60 100644 --- a/model/testing/src/icon4py/model/testing/definitions.py +++ b/model/testing/src/icon4py/model/testing/definitions.py @@ -72,7 +72,7 @@ class Grids: R02B07_GLOBAL: Final = GridDescription( name="r02b07_global", - description="R02b07, large global grid, generated by MPI-M GridGenerator", + description="R02B07, large global grid, generated by MPI-M GridGenerator", sizes={"cell": 1310720, "vertex": 655362, "edge": 1966080}, kind=GridKind.GLOBAL, file_name="icon_grid_0023_R02B07_G.nc", @@ -115,7 +115,7 @@ class Grids: description="Torus grid with a domain (100x116) vertices and a resolution (edge length) of 1000m, generated by MPI-M GridGenerator ", sizes={"cell": 23200, "vertex": 11600, "edge": 34800}, kind=GridKind.TORUS, - file_name=" Torus_Triangles_100x116_1000m.nc", + file_name="Torus_Triangles_100x116_1000m.nc", uri="https://polybox.ethz.ch/index.php/s/yqvotFss9i1OKzs/download", ) TORUS_50000x5000: Final = GridDescription(