Skip to content
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
caf2565
Add try_attribute member function to GridFile to allow retrieving opt…
msimberg Aug 7, 2025
643829b
Fix typos in a few docstrings and comments
msimberg Aug 7, 2025
4b0327c
Refactor global grid params to handle torus/sphere in a more structur…
msimberg Aug 7, 2025
4113218
Add more info grid type error message
msimberg Aug 7, 2025
2df94eb
Add more tests for global grid parameters
msimberg Aug 8, 2025
32f322e
Shorten GridSubdivisionParams to GridSubdivision
msimberg Aug 8, 2025
3dc0152
Remove compute_torus_num_cells, for now
msimberg Aug 8, 2025
3958306
Remove todos and debug print statements
msimberg Aug 8, 2025
f4bdfc3
Rename GridType helper class to GridParams
msimberg Aug 8, 2025
c05d469
Slightly simplify reading geometry_type from grid
msimberg Aug 8, 2025
21c6785
Remove useless comment
msimberg Aug 8, 2025
409ceeb
Merge remote-tracking branch 'origin/main' into better-torus-support
msimberg Aug 8, 2025
f87f5d9
More consistent error strings for grid params
msimberg Aug 8, 2025
04025bd
Fix check for torus root/level values
msimberg Aug 8, 2025
bd08cbb
Add more tests for grid params
msimberg Aug 8, 2025
9cf6b5d
Fix missing rename
msimberg Aug 8, 2025
6e96aee
Add geometry_type property to GlobalGridParams
msimberg Aug 8, 2025
667c38c
Merge remote-tracking branch 'origin/main' into better-torus-support
msimberg Aug 27, 2025
8c6d92c
Simplify GridParams setup and validation (make stronger assumptions)
msimberg Aug 27, 2025
471ba0a
Remove incorrect test
msimberg Aug 27, 2025
20b0735
Update model/common/src/icon4py/model/common/grid/icon.py
msimberg Aug 28, 2025
66c31e4
Apply suggestion from @msimberg
msimberg Aug 28, 2025
27b7cba
Use more appropriate type for fallback case matching on geometry types
msimberg Aug 28, 2025
1c31e16
Rename GridParams to GridShape
msimberg Sep 1, 2025
35e4a11
Remove custom GlobalGridParams __init__
msimberg Sep 1, 2025
d3e1bd0
Merge remote-tracking branch 'origin/main' into better-torus-support
msimberg Sep 1, 2025
b940c27
Remove from_mean_cell_area constructor
msimberg Aug 27, 2025
5816b44
Merge remote-tracking branch 'origin/main' into better-torus-support-2
msimberg Sep 3, 2025
8edfafc
Add tests for global grid parameters with real grid files
msimberg Aug 27, 2025
f295c3f
Keep separately track of number of cells and global number of cells
msimberg Aug 27, 2025
f6f0ce8
Use grid file mean cell area or value computed from geometry if possible
msimberg Aug 27, 2025
995d822
Simplify GlobalGridParams construction and cached properties
msimberg Sep 4, 2025
ac84e00
num_cells and mean_cell_area can't be None anymore in GlobalGridParams
msimberg Sep 5, 2025
c68c183
Regroup GlobalGridParams attributes more logically
msimberg Sep 5, 2025
b4d73a8
Merge remote-tracking branch 'msimberg/better-torus-support-2' into b…
msimberg Sep 5, 2025
fbc91d6
Merge remote-tracking branch 'origin/main' into better-torus-support-3
msimberg Sep 10, 2025
a8e9044
Fix import in test_icon.py
msimberg Sep 10, 2025
48207b0
Minor globalgridparams fixes
msimberg Sep 10, 2025
7396648
Update globalgridparams tests
msimberg Sep 10, 2025
b05cc3d
Fix minor typos in grid definitions
msimberg Sep 10, 2025
4879d3a
Read more (optional) MPI-M grid properties into GlobalGridParameters
msimberg Sep 10, 2025
628de75
Make GlobalGridParams frozen kw_only data class
msimberg Sep 10, 2025
6d316bb
Add TODO for GlobalGridParams
msimberg Sep 10, 2025
c8eea7b
Add factory for GlobalGridParams that computes meann cell area
msimberg Sep 11, 2025
38680c7
Read EDGE_LENGTH and DUAL_EDGE_LENGTH from grid file
msimberg Sep 11, 2025
5e7eefc
Remove useless comment
msimberg Sep 11, 2025
a7f8dea
Fix and update GlobalGridParams tests
msimberg Sep 11, 2025
19256ce
Expand GlobalGridParams tests with real grids (check all properties)
msimberg Sep 11, 2025
8c484a6
Remove debug prints
msimberg Sep 11, 2025
c9dbdb3
Fix test_grid_manager_grid_level_and_root test
msimberg Sep 11, 2025
e597f64
Merge remote-tracking branch 'origin/main' into better-torus-support-3
msimberg Sep 12, 2025
0891271
Update model/common/src/icon4py/model/common/grid/icon.py
msimberg Sep 12, 2025
f75a552
Update global grid params tests
msimberg Sep 12, 2025
fda6768
Fix type annotation in GlobalGridParams
msimberg Sep 12, 2025
71e6d69
Use approximate comparisons for global grid parameters
msimberg Sep 15, 2025
4852673
Only use precomputed EDGE_LENGTH and DUAL_EDGE_LENGTH fields
msimberg Sep 15, 2025
6dec71b
Remove TODOs
msimberg Sep 15, 2025
b6d6121
Increase common-integration timelimit in ci from 30 to 35 minutes
msimberg Sep 16, 2025
718ed0a
Merge remote-tracking branch 'origin/main' into better-torus-support-3
msimberg Sep 16, 2025
75313b6
Increase common/integration test time limit in CI
msimberg Sep 16, 2025
a886952
Fix bug reading dual cell areas in grid manager
msimberg Sep 18, 2025
c986803
Make sure mean quantities are scalars instead of arrays in global gri…
msimberg Sep 18, 2025
3320813
Add TODO for moving mean quantities computation to GridGeometry
msimberg Sep 18, 2025
a236f1f
Increase dace tests timelimit in CI
msimberg Sep 18, 2025
9c9857a
Slightly increase rbf cell interpolation test tolerance due to change…
msimberg Sep 19, 2025
a21bf4b
Add missing experiment fixture to rbf tests
msimberg Sep 19, 2025
581f465
Fix formatting
msimberg Sep 19, 2025
bdd5bcb
Merge remote-tracking branch 'origin/main' into better-torus-support-3
msimberg Sep 19, 2025
4e8786e
Merge remote-tracking branch 'origin/main' into better-torus-support-3
msimberg Sep 22, 2025
8875e0f
Re-remove edge length provider (read from grid file instead)
msimberg Sep 22, 2025
3c12735
Expand GlobalGridParams construction tests
msimberg Sep 22, 2025
47687a4
Add GlobalGridParams.from_fields tests
msimberg Sep 22, 2025
a75b0a1
Format files
msimberg Sep 22, 2025
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
2 changes: 1 addition & 1 deletion ci/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ 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
Expand Down
40 changes: 2 additions & 38 deletions model/common/src/icon4py/model/common/grid/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,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
Expand Down Expand Up @@ -177,50 +179,12 @@ def __init__(
self._register_computed_fields()

def _register_computed_fields(self):
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)

Expand Down
49 changes: 46 additions & 3 deletions model/common/src/icon4py/model/common/grid/grid_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,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:
Expand Down Expand Up @@ -185,6 +185,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),
Expand Down Expand Up @@ -335,11 +345,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)

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 = mean_dual_cell_area = xp.mean(
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
Expand Down
2 changes: 2 additions & 0 deletions model/common/src/icon4py/model/common/grid/gridfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
135 changes: 89 additions & 46 deletions model/common/src/icon4py/model/common/grid/icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 Mapping
from typing import Final
from typing import Final, TypeVar

import gt4py.next as gtx
from gt4py.next import allocators as gtx_allocators
Expand Down Expand Up @@ -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)
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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,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
)


Expand Down
Loading