icon4py-bindings 0.2.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ # ICON4Py - ICON inspired code in Python and GT4Py
2
+ #
3
+ # Copyright (c) 2022-2024, ETH Zurich and MeteoSwiss
4
+ # All rights reserved.
5
+ #
6
+ # Please, refer to the LICENSE file in the root directory.
7
+ # SPDX-License-Identifier: BSD-3-Clause
8
+
9
+ """Package metadata: version, authors, license and copyright."""
10
+
11
+ from typing import Final
12
+
13
+ from packaging import version as pkg_version
14
+
15
+
16
+ __all__ = [
17
+ "__author__",
18
+ "__copyright__",
19
+ "__license__",
20
+ "__version__",
21
+ "__version_info__",
22
+ ]
23
+
24
+ __author__: Final = "ETH Zurich, MeteoSwiss and individual contributors"
25
+ __copyright__: Final = "Copyright (c) 2022-2024 ETH Zurich and MeteoSwiss"
26
+ __license__: Final = "BSD-3-Clause"
27
+
28
+ __version__: Final = "0.2.0rc2"
29
+ __version_info__: Final = pkg_version.parse(__version__)
@@ -0,0 +1,68 @@
1
+ # ICON4Py - ICON inspired code in Python and GT4Py
2
+ #
3
+ # Copyright (c) 2022-2024, ETH Zurich and MeteoSwiss
4
+ # All rights reserved.
5
+ #
6
+ # Please, refer to the LICENSE file in the root directory.
7
+ # SPDX-License-Identifier: BSD-3-Clause
8
+
9
+ """CLI entrypoint that generates the combined icon4py Fortran/C bindings.
10
+
11
+ Run as ``python -m icon4py.bindings.all_bindings --output-f90 X --output-c Y``.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import pathlib
17
+
18
+ import click
19
+
20
+ from icon4py.bindings import diffusion_wrapper, dycore_wrapper, grid_wrapper
21
+ from icon4py.tools import py2fgen
22
+
23
+
24
+ FUNCTIONS = [
25
+ diffusion_wrapper.diffusion_init,
26
+ diffusion_wrapper.diffusion_run,
27
+ grid_wrapper.grid_init,
28
+ dycore_wrapper.solve_nh_init,
29
+ dycore_wrapper.solve_nh_run,
30
+ ]
31
+ LIBRARY_NAME = "icon4py_bindings"
32
+
33
+ _FILE_PATH = click.Path(dir_okay=False, resolve_path=True, path_type=pathlib.Path)
34
+
35
+
36
+ @click.command(name="all_bindings")
37
+ @click.option(
38
+ "--output-f90",
39
+ type=_FILE_PATH,
40
+ default=None,
41
+ help="Path for the generated Fortran interface (.f90).",
42
+ )
43
+ @click.option(
44
+ "--output-c",
45
+ type=_FILE_PATH,
46
+ default=None,
47
+ help="Path for the generated CFFI C source (.c). The .h header is written alongside.",
48
+ )
49
+ def main(output_f90: pathlib.Path | None, output_c: pathlib.Path | None) -> None:
50
+ """Generate the icon4py Fortran/C binding sources at user-chosen paths."""
51
+ if output_f90 is None and output_c is None:
52
+ raise click.UsageError("specify --output-f90 and/or --output-c")
53
+
54
+ plugin = py2fgen.get_cffi_description(FUNCTIONS, LIBRARY_NAME)
55
+ h_basename = output_c.with_suffix(".h").name if output_c is not None else f"{LIBRARY_NAME}.h"
56
+ sources = py2fgen.render(plugin, h_basename=h_basename)
57
+
58
+ # write_if_changed avoids touching mtimes (and triggering downstream
59
+ # rebuilds) when the rendered content matches what is already on disk.
60
+ if output_f90 is not None:
61
+ py2fgen.write_if_changed(sources.f90, output_f90)
62
+ if output_c is not None:
63
+ py2fgen.write_if_changed(sources.c, output_c)
64
+ py2fgen.write_if_changed(sources.h, output_c.with_suffix(".h"))
65
+
66
+
67
+ if __name__ == "__main__":
68
+ main()
@@ -0,0 +1,325 @@
1
+ # ICON4Py - ICON inspired code in Python and GT4Py
2
+ #
3
+ # Copyright (c) 2022-2024, ETH Zurich and MeteoSwiss
4
+ # All rights reserved.
5
+ #
6
+ # Please, refer to the LICENSE file in the root directory.
7
+ # SPDX-License-Identifier: BSD-3-Clause
8
+
9
+
10
+ import functools
11
+ import logging
12
+ from collections.abc import Callable, Iterable
13
+ from types import ModuleType
14
+ from typing import TYPE_CHECKING, Annotated, Final, TypeAlias
15
+
16
+ import gt4py.next as gtx
17
+ import gt4py.next.typing as gtx_typing
18
+ import numpy as np
19
+ from gt4py import eve
20
+ from gt4py._core import definitions as gt4py_definitions
21
+ from gt4py.next.type_system import type_specifications as ts
22
+
23
+ from icon4py.model.common import dimension as dims, model_backends
24
+ from icon4py.model.common.decomposition import definitions, mpi_decomposition
25
+ from icon4py.model.common.grid import base, horizontal as h_grid, icon
26
+ from icon4py.model.common.utils import data_allocation as data_alloc
27
+ from icon4py.model.common.utils.data_allocation import adjust_fortran_indices
28
+ from icon4py.tools import py2fgen
29
+
30
+
31
+ if TYPE_CHECKING:
32
+ xp: Final = np
33
+ else:
34
+ try:
35
+ import cupy as cp
36
+
37
+ xp = cp
38
+ except ImportError:
39
+ cp = None
40
+ xp = np
41
+
42
+
43
+ NDArray: TypeAlias = np.ndarray | xp.ndarray
44
+
45
+ # TODO(havogt): import needed to register MultNodeRun in get_process_properties, does the pattern make sense?
46
+ assert hasattr(mpi_decomposition, "get_multinode_properties")
47
+
48
+ log = logging.getLogger(__name__)
49
+
50
+ Int32Array1D: TypeAlias = Annotated[
51
+ data_alloc.NDArray,
52
+ py2fgen.ArrayParamDescriptor(
53
+ rank=1,
54
+ dtype=ts.ScalarKind.INT32,
55
+ memory_space=py2fgen.MemorySpace.MAYBE_DEVICE,
56
+ is_optional=False,
57
+ ),
58
+ ]
59
+
60
+ OptionalInt32Array1D: TypeAlias = Annotated[
61
+ data_alloc.NDArray | None,
62
+ py2fgen.ArrayParamDescriptor(
63
+ rank=1,
64
+ dtype=ts.ScalarKind.INT32,
65
+ memory_space=py2fgen.MemorySpace.MAYBE_DEVICE,
66
+ is_optional=True,
67
+ ),
68
+ ]
69
+
70
+ OptionalInt32Array2D: TypeAlias = Annotated[
71
+ data_alloc.NDArray | None,
72
+ py2fgen.ArrayParamDescriptor(
73
+ rank=2,
74
+ dtype=ts.ScalarKind.INT32,
75
+ memory_space=py2fgen.MemorySpace.MAYBE_DEVICE,
76
+ is_optional=True,
77
+ ),
78
+ ]
79
+
80
+ Float64Array1D: TypeAlias = Annotated[
81
+ data_alloc.NDArray,
82
+ py2fgen.ArrayParamDescriptor(
83
+ rank=1,
84
+ dtype=ts.ScalarKind.FLOAT64,
85
+ memory_space=py2fgen.MemorySpace.MAYBE_DEVICE,
86
+ is_optional=False,
87
+ ),
88
+ ]
89
+
90
+ Float64Array2D: TypeAlias = Annotated[
91
+ data_alloc.NDArray,
92
+ py2fgen.ArrayParamDescriptor(
93
+ rank=2,
94
+ dtype=ts.ScalarKind.FLOAT64,
95
+ memory_space=py2fgen.MemorySpace.MAYBE_DEVICE,
96
+ is_optional=False,
97
+ ),
98
+ ]
99
+
100
+ Float64Array3D: TypeAlias = Annotated[
101
+ data_alloc.NDArray,
102
+ py2fgen.ArrayParamDescriptor(
103
+ rank=3,
104
+ dtype=ts.ScalarKind.FLOAT64,
105
+ memory_space=py2fgen.MemorySpace.MAYBE_DEVICE,
106
+ is_optional=False,
107
+ ),
108
+ ]
109
+
110
+ OptionalFloat64Array1D: TypeAlias = Annotated[
111
+ data_alloc.NDArray | None,
112
+ py2fgen.ArrayParamDescriptor(
113
+ rank=1,
114
+ dtype=ts.ScalarKind.FLOAT64,
115
+ memory_space=py2fgen.MemorySpace.MAYBE_DEVICE,
116
+ is_optional=True,
117
+ ),
118
+ ]
119
+
120
+ OptionalFloat64Array2D: TypeAlias = Annotated[
121
+ data_alloc.NDArray,
122
+ py2fgen.ArrayParamDescriptor(
123
+ rank=2,
124
+ dtype=ts.ScalarKind.FLOAT64,
125
+ memory_space=py2fgen.MemorySpace.MAYBE_DEVICE,
126
+ is_optional=True,
127
+ ),
128
+ ]
129
+
130
+
131
+ class BackendIntEnum(eve.IntEnum):
132
+ DEFAULT = 0
133
+ DACE = 1
134
+ GTFN = 2
135
+
136
+
137
+ def select_backend(
138
+ selector: BackendIntEnum, on_gpu: bool
139
+ ) -> gtx_typing.Backend | model_backends.BackendDescriptor:
140
+ backend_descriptor: model_backends.BackendDescriptor = {}
141
+ backend_descriptor["device"] = model_backends.GPU if on_gpu else model_backends.CPU
142
+ if selector == BackendIntEnum.DEFAULT:
143
+ return backend_descriptor
144
+ if selector == BackendIntEnum.DACE:
145
+ backend_descriptor["backend_factory"] = model_backends.make_custom_dace_backend
146
+ return backend_descriptor
147
+ if selector == BackendIntEnum.GTFN:
148
+ backend_descriptor["backend_factory"] = model_backends.make_custom_gtfn_backend
149
+ return backend_descriptor
150
+
151
+ raise ValueError(f"Invalid backend selector: {selector}")
152
+
153
+
154
+ def cached_dummy_field_factory(
155
+ allocator: gtx_typing.Allocator | None,
156
+ ) -> Callable[[str, gtx.Domain, gt4py_definitions.DType], gtx.Field]:
157
+ # curried to exclude non-hashable backend from cache
158
+ @functools.lru_cache(maxsize=20)
159
+ def impl(_name: str, domain: gtx.Domain, dtype: gt4py_definitions.DType) -> gtx.Field:
160
+ # _name is used to differentiate between different dummy fields
161
+ return gtx.zeros(domain, dtype=dtype, allocator=allocator)
162
+
163
+ return impl
164
+
165
+
166
+ def shrink_to_dimension(
167
+ sizes: dict[gtx.Dimension, int], tables: dict[gtx.FieldOffset, NDArray]
168
+ ) -> dict[gtx.FieldOffset, NDArray]:
169
+ """Shrink the neighbor tables from nproma size to the actual size of the grid."""
170
+ return {k: v[: sizes[k.target[0]]] for k, v in tables.items()}
171
+
172
+
173
+ def add_origin(xp: ModuleType, table: NDArray) -> NDArray:
174
+ return xp.column_stack((xp.arange(table.shape[0], dtype=xp.int32), table))
175
+
176
+
177
+ def get_nproma(tables: Iterable[NDArray]) -> int:
178
+ tables = list(tables)
179
+ nproma = tables[0].shape[0]
180
+ if not all(table.shape[0] == nproma for table in tables):
181
+ raise ValueError("All connectivity tables must have the same number of rows (nproma).")
182
+ return nproma
183
+
184
+
185
+ def construct_icon_grid(
186
+ cell_starts: np.ndarray,
187
+ cell_ends: np.ndarray,
188
+ vertex_starts: np.ndarray,
189
+ vertex_ends: np.ndarray,
190
+ edge_starts: np.ndarray,
191
+ edge_ends: np.ndarray,
192
+ c2e: NDArray,
193
+ e2c: NDArray,
194
+ c2e2c: NDArray,
195
+ e2c2e: NDArray,
196
+ e2v: NDArray,
197
+ v2e: NDArray,
198
+ v2c: NDArray,
199
+ e2c2v: NDArray,
200
+ c2v: NDArray,
201
+ grid_id: str,
202
+ num_vertices: int,
203
+ num_cells: int,
204
+ num_edges: int,
205
+ vertical_size: int,
206
+ limited_area: bool,
207
+ distributed: bool,
208
+ allocator: gtx_typing.Allocator | None,
209
+ ) -> icon.IconGrid:
210
+ log.debug("Constructing ICON Grid in Python...")
211
+ log.debug("num_cells:%s", num_cells)
212
+ log.debug("num_edges:%s", num_edges)
213
+ log.debug("num_vertices:%s", num_vertices)
214
+ log.debug("num_levels:%s", vertical_size)
215
+
216
+ log.debug("Offsetting Fortran connectivitity arrays by 1")
217
+
218
+ xp = data_alloc.import_array_ns(allocator)
219
+ start_indices = {
220
+ # TODO(halungge): ICON Fortran has 0 values in these arrays in some places possibly where they don't use them.
221
+ # We should investigate where we access these values.
222
+ dims.CellDim: np.maximum(0, adjust_fortran_indices(cell_starts)),
223
+ dims.EdgeDim: np.maximum(0, adjust_fortran_indices(edge_starts)),
224
+ dims.VertexDim: np.maximum(0, adjust_fortran_indices(vertex_starts)),
225
+ }
226
+ end_indices = {dims.CellDim: cell_ends, dims.EdgeDim: edge_ends, dims.VertexDim: vertex_ends}
227
+
228
+ c2e = adjust_fortran_indices(c2e)
229
+ c2v = adjust_fortran_indices(c2v)
230
+ v2c = adjust_fortran_indices(v2c)
231
+ e2v = adjust_fortran_indices(e2v)[
232
+ :, 0:2
233
+ ] # slicing required for e2v as input data is actually e2c2v
234
+ c2e2c = adjust_fortran_indices(c2e2c)
235
+ v2e = adjust_fortran_indices(v2e)
236
+ e2c2v = adjust_fortran_indices(e2c2v)
237
+ e2c = adjust_fortran_indices(e2c)
238
+ e2c2e = adjust_fortran_indices(e2c2e)
239
+
240
+ # stacked arrays
241
+ c2e2c0 = add_origin(xp, c2e2c)
242
+ e2c2e0 = add_origin(xp, e2c2e)
243
+
244
+ config = base.GridConfig(
245
+ horizontal_config=base.HorizontalGridSize(
246
+ num_vertices=num_vertices,
247
+ num_cells=num_cells,
248
+ num_edges=num_edges,
249
+ ),
250
+ vertical_size=vertical_size,
251
+ limited_area=limited_area,
252
+ distributed=distributed,
253
+ keep_skip_values=False,
254
+ )
255
+
256
+ neighbor_tables = {
257
+ dims.C2E: c2e,
258
+ dims.C2V: c2v,
259
+ dims.E2C: e2c,
260
+ dims.E2C2E: e2c2e,
261
+ dims.C2E2C: c2e2c,
262
+ dims.C2E2CO: c2e2c0,
263
+ dims.E2C2EO: e2c2e0,
264
+ dims.V2E: v2e,
265
+ dims.E2V: e2v,
266
+ dims.E2C2V: e2c2v,
267
+ dims.V2C: v2c,
268
+ }
269
+
270
+ neighbor_tables = shrink_to_dimension(
271
+ sizes={dims.EdgeDim: num_edges, dims.VertexDim: num_vertices, dims.CellDim: num_cells},
272
+ tables=neighbor_tables,
273
+ )
274
+ domain_bounds_constructor = functools.partial(
275
+ h_grid.get_start_end_idx_from_icon_arrays,
276
+ start_indices=start_indices,
277
+ end_indices=end_indices,
278
+ )
279
+ start_index, end_index = icon.get_start_and_end_index(domain_bounds_constructor)
280
+
281
+ return icon.icon_grid(
282
+ id_=grid_id,
283
+ allocator=allocator,
284
+ config=config,
285
+ neighbor_tables=neighbor_tables,
286
+ start_index=start_index,
287
+ end_index=end_index,
288
+ grid_params=icon.GridParams(),
289
+ )
290
+
291
+
292
+ def construct_decomposition(
293
+ c_glb_index: np.ndarray,
294
+ e_glb_index: np.ndarray,
295
+ v_glb_index: np.ndarray,
296
+ c_owner_mask: np.ndarray,
297
+ e_owner_mask: np.ndarray,
298
+ v_owner_mask: np.ndarray,
299
+ num_cells: int,
300
+ num_edges: int,
301
+ num_vertices: int,
302
+ comm_id: int,
303
+ ) -> tuple[
304
+ definitions.ProcessProperties, definitions.DecompositionInfo, definitions.ExchangeRuntime
305
+ ]:
306
+ log.debug("Offsetting Fortran connectivitity arrays by 1")
307
+ c_glb_index = adjust_fortran_indices(c_glb_index)
308
+ e_glb_index = adjust_fortran_indices(e_glb_index)
309
+ v_glb_index = adjust_fortran_indices(v_glb_index)
310
+
311
+ c_owner_mask = c_owner_mask[:num_cells]
312
+ e_owner_mask = e_owner_mask[:num_edges]
313
+ v_owner_mask = v_owner_mask[:num_vertices]
314
+
315
+ decomposition_info = (
316
+ definitions.DecompositionInfo()
317
+ # TODO (halungge): last argument is called `decomp_domain` in icon, it is not needed in the granules should we pass it nevertheless?
318
+ .set_dimension(dims.CellDim, c_glb_index, c_owner_mask, None)
319
+ .set_dimension(dims.EdgeDim, e_glb_index, e_owner_mask, None)
320
+ .set_dimension(dims.VertexDim, v_glb_index, v_owner_mask, None)
321
+ )
322
+ process_props = definitions.get_process_properties(definitions.MultiNodeRun(), comm_id)
323
+ exchange = definitions.create_exchange(process_props, decomposition_info)
324
+
325
+ return process_props, decomposition_info, exchange
@@ -0,0 +1,13 @@
1
+ # ICON4Py - ICON inspired code in Python and GT4Py
2
+ #
3
+ # Copyright (c) 2022-2024, ETH Zurich and MeteoSwiss
4
+ # All rights reserved.
5
+ #
6
+ # Please, refer to the LICENSE file in the root directory.
7
+ # SPDX-License-Identifier: BSD-3-Clause
8
+
9
+ from icon4py.model.common.utils import env
10
+
11
+
12
+ WAIT_FOR_COMPILATION: bool = env.flag_to_bool("ICON4PY_WAIT_FOR_COMPILATION", False)
13
+ """Wait in granule initialization until jit compilation is complete."""
@@ -0,0 +1,129 @@
1
+ # ICON4Py - ICON inspired code in Python and GT4Py
2
+ #
3
+ # Copyright (c) 2022-2024, ETH Zurich and MeteoSwiss
4
+ # All rights reserved.
5
+ #
6
+ # Please, refer to the LICENSE file in the root directory.
7
+ # SPDX-License-Identifier: BSD-3-Clause
8
+
9
+ import logging
10
+
11
+ from gt4py.next import common as gtx_common
12
+
13
+ from icon4py.model.common import dimension as dims
14
+ from icon4py.model.common.decomposition import definitions
15
+ from icon4py.model.common.grid import horizontal as h_grid, icon
16
+
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def print_grid_decomp_info(
22
+ icon_grid: icon.IconGrid,
23
+ process_props: definitions.ProcessProperties,
24
+ decomposition_info: definitions.DecompositionInfo,
25
+ num_cells: int,
26
+ num_edges: int,
27
+ num_verts: int,
28
+ ) -> None:
29
+ logger.info(
30
+ "icon_grid:cell_start for rank %s is.... %s",
31
+ process_props.rank,
32
+ "\n".join(
33
+ [
34
+ f"{k!s} - {icon_grid.start_index(k)}"
35
+ for k in h_grid.get_domains_for_dim(dims.CellDim)
36
+ ]
37
+ ),
38
+ )
39
+ logger.info(
40
+ "icon_grid:cell_end for rank %s is.... %s",
41
+ process_props.rank,
42
+ "\n".join(
43
+ [f"{k!s} - {icon_grid.end_index(k)}" for k in h_grid.get_domains_for_dim(dims.CellDim)]
44
+ ),
45
+ )
46
+ logger.info(
47
+ "icon_grid:vert_start for rank %s is.... %s",
48
+ process_props.rank,
49
+ "\n".join(
50
+ [
51
+ f"{k!s} - {icon_grid.start_index(k)}"
52
+ for k in h_grid.get_domains_for_dim(dims.VertexDim)
53
+ ]
54
+ ),
55
+ )
56
+ logger.info(
57
+ "icon_grid:vert_end for rank %s is.... %s",
58
+ process_props.rank,
59
+ "\n".join(
60
+ [
61
+ f"{k!s} - {icon_grid.end_index(k)}"
62
+ for k in h_grid.get_domains_for_dim(dims.VertexDim)
63
+ ]
64
+ ),
65
+ )
66
+ logger.info(
67
+ "icon_grid:edge_start for rank %s is.... %s",
68
+ process_props.rank,
69
+ "\n".join(
70
+ [
71
+ f"{k!s} - {icon_grid.start_index(k)}"
72
+ for k in h_grid.get_domains_for_dim(dims.EdgeDim)
73
+ ]
74
+ ),
75
+ )
76
+ logger.info(
77
+ "icon_grid:edge_end for rank %s is.... %s",
78
+ process_props.rank,
79
+ "\n".join(
80
+ [f"{k!s} - {icon_grid.end_index(k)}" for k in h_grid.get_domains_for_dim(dims.EdgeDim)]
81
+ ),
82
+ )
83
+
84
+ for offset, connectivity in icon_grid.connectivities.items():
85
+ if gtx_common.is_neighbor_table(connectivity):
86
+ logger.debug(
87
+ f"icon_grid:{offset} for rank {process_props.rank} is.... {connectivity.asnumpy()}"
88
+ )
89
+
90
+ logger.info(
91
+ "c_glb_index for rank %s is.... %s",
92
+ process_props.rank,
93
+ decomposition_info.global_index(dims.CellDim)[0:num_cells],
94
+ )
95
+ logger.info(
96
+ "e_glb_index for rank %s is.... %s",
97
+ process_props.rank,
98
+ decomposition_info.global_index(dims.EdgeDim)[0:num_edges],
99
+ )
100
+ logger.info(
101
+ "v_glb_index for rank %s is.... %s",
102
+ process_props.rank,
103
+ decomposition_info.global_index(dims.VertexDim)[0:num_verts],
104
+ )
105
+
106
+ logger.info(
107
+ "c_owner_mask for rank %s is.... %s",
108
+ process_props.rank,
109
+ decomposition_info.owner_mask(dims.CellDim)[0:num_cells],
110
+ )
111
+ logger.info(
112
+ "e_owner_mask for rank %s is.... %s",
113
+ process_props.rank,
114
+ decomposition_info.owner_mask(dims.EdgeDim)[0:num_edges],
115
+ )
116
+ logger.info(
117
+ "v_owner_mask for rank %s is.... %s",
118
+ process_props.rank,
119
+ decomposition_info.owner_mask(dims.VertexDim)[0:num_verts],
120
+ )
121
+
122
+ logger.info(
123
+ f"local cells = {decomposition_info.global_index(dims.CellDim, definitions.DecompositionInfo.EntryType.ALL).shape} "
124
+ f"local edges = {decomposition_info.global_index(dims.EdgeDim, definitions.DecompositionInfo.EntryType.ALL).shape} "
125
+ f"local vertices = {decomposition_info.global_index(dims.VertexDim, definitions.DecompositionInfo.EntryType.ALL).shape}"
126
+ )
127
+ logger.info(
128
+ f"rank={process_props.rank}/{process_props.comm_size}: GHEX context setup: from {process_props.comm_name} with {process_props.comm_size} nodes"
129
+ )