# Continue: ICON stencils on the Declarative GT4Py
###### tags: `functional cycle 8`
shaped by: Matthias, Hannes
appetite: half cycle
developers: developers who are free in the 2nd half (+ Peter (EXCLAIM) with mentoring)
## Motivation
The previous project was only partially completted with a simple point-wise stencil. After the ICON bindings are completed (or partly in parallel), we propose to finish this project.
## Implementation
### Simple stencil in Python
Implement `mo_nh_diffusion_stencil_01` and `mo_nh_diffusion_stencil_05` first in pure Python, with validation against a NumPy solution, see https://github.com/havogt/gt4py/blob/icon_stencils_iterator_view/tests/iterator_tests/test_nh_diff_05.py.
### Integration into ICON with generated bindings
With the result of the ICON bindings project, we can integrate these stencils into ICON.
### Performance comparison
Simple performance comparison of the dawn generated code vs the code generated in this project. E.g. by running ICON through nvprof.
Take care that
- setup work (e.g. allocation of temporaries) are not measured
- the same neighbor tables are used in both codes (in case there is a layout transformation involved)
### Optional: more stencils
More stencils can be ported with a focus on testing more features:
- field conditionals
- `mo_velocity_advection_stencil_18` or `mo_velocity_advection_stencil_21` (more involved)
- stencils that are fusable (this would allow us to test the inline transformation)
- fusing `mo_nh_diffusion_stencil_16` with `mo_nh_diffusion_stencil_17` leads to a situation two subsequent reductions can be inlined
- maybe mixing of staggered and non-staggered fields in the vertical
- fusing `wrap_setup_mo_velocity_advection_stencil_05_a` and `wrap_setup_mo_velocity_advection_stencil_05_b` leads to such a situation
Note that the `scan` will not be ready and therefore we should not try to implement a vertical solver.
### Appendix
#### Selected stencils
##### mo_nh_diffusion_stencil_01
###### dusk
```python=
from dusk.script import *
@stencil
def icon_laplacian_diamond(
diff_multfac_smag: Field[K],
tangent_orientation: Field[Edge],
inv_primal_edge_length: Field[Edge],
inv_vert_vert_length: Field[Edge],
u_vert: Field[Vertex, K],
v_vert: Field[Vertex, K],
primal_normal_x: Field[Edge > Cell > Vertex],
primal_normal_y: Field[Edge > Cell > Vertex],
dual_normal_x: Field[Edge > Cell > Vertex],
dual_normal_y: Field[Edge > Cell > Vertex],
vn: Field[Edge, K],
kh_smag: Field[Edge, K],
kh_smag_ec: Field[Edge, K],
nabla2: Field[Edge, K],
smag_limit: Field[K],
smag_offset: Global
) -> None:
vn_vert: Field[Edge > Cell > Vertex, K]
dvt_tang: Field[Edge, K]
dvt_norm: Field[Edge, K]
kh_smag_1: Field[Edge, K]
kh_smag_2: Field[Edge, K]
with levels_upward:
# fill sparse dimension vn vert using the loop concept
with sparse[Edge > Cell > Vertex]:
vn_vert = u_vert * primal_normal_x + v_vert * primal_normal_y
# dvt_tang for smagorinsky
dvt_tang = sum_over(
Edge > Cell > Vertex,
(u_vert * dual_normal_x) + (v_vert * dual_normal_y),
weights=[-1.0, 1.0, 0.0, 0.0]
)
dvt_tang = dvt_tang * tangent_orientation
# dvt_norm for smagorinsky
dvt_norm = sum_over(
Edge > Cell > Vertex,
u_vert * dual_normal_x + v_vert * dual_normal_y,
weights=[0.0, 0.0, -1.0, 1.0],
)
# compute smagorinsky
kh_smag_1 = sum_over(
Edge > Cell > Vertex,
vn_vert,
weights=[-1.0, 1.0, 0.0, 0.0]
)
kh_smag_1 = (kh_smag_1 * tangent_orientation * inv_primal_edge_length) + (
dvt_norm * inv_vert_vert_length
)
kh_smag_1 = kh_smag_1 * kh_smag_1
kh_smag_2 = sum_over(
Edge > Cell > Vertex,
vn_vert,
weights=[0.0, 0.0, -1.0, 1.0]
)
kh_smag_2 = (kh_smag_2 * inv_vert_vert_length) - (
dvt_tang * inv_primal_edge_length
)
kh_smag_2 = kh_smag_2 * kh_smag_2
kh_smag = diff_multfac_smag * sqrt(kh_smag_2 + kh_smag_1)
# compute nabla2 using the diamond reduction
nabla2 = (sum_over(
Edge > Cell > Vertex,
vn_vert,
weights=[1.0, 1.0, 0.0, 0.0],
) - 2.0 * vn) * (inv_primal_edge_length * inv_primal_edge_length)
nabla2 = nabla2 + (sum_over(
Edge > Cell > Vertex,
vn_vert,
weights=[0.0, 0.0, 1.0, 1.0],
) - 2.0 * vn) * (inv_vert_vert_length * inv_vert_vert_length)
nabla2 = 4.0 * nabla2
kh_smag_ec = kh_smag
kh_smag = max(0., kh_smag - smag_offset)
kh_smag = min(kh_smag, smag_limit)
```
###### GT4Py deprecated prototype
```python=
def nh_diff_01(
e2v: E2V,
diff_multfac_smag: Field[K, dtype],
tangent_orientation: Field[Edge, dtype],
inv_primal_edge_length: Field[Edge, dtype],
inv_vert_vert_length: Field[Edge, dtype],
u_vert: Field[[Vertex, K], dtype],
v_vert: Field[[Vertex, K], dtype],
primal_normal_x: SparseField[E2V, dtype],
primal_normal_y: SparseField[E2V, dtype],
dual_normal_x: SparseField[E2V, dtype],
dual_normal_y: SparseField[E2V, dtype],
vn: Field[[Edge, K], dtype],
kh_smag: Field[[Edge, K], dtype],
kh_smag_ec: Field[[Edge, K], dtype],
nabla2: Field[[Edge, K], dtype],
smag_limit: Field[K, dtype],
smag_offset: Field[K, dtype]
):
with computation(FORWARD), interval(0, None):
with location(Edge) as e:
weights_tang = LocalField[E2V, dtype]([-1., 1., 0., 0.])
weights_norm = LocalField[E2V, dtype]([0., 0., -1., 1.])
weights_close = LocalField[E2V, dtype]([1., 1., 0., 0.])
weights_far = LocalField[E2V, dtype]([0., 0., 1., 1.])
# fill sparse dimension vn vert using the loop concept
vn_vert = (u_vert[v] * primal_normal_x[e, v] + v_vert[v] * primal_normal_y[e, v] for v in e2v[e])
# dvt_tang for smagorinsky
dvt_tang = sum(weights_tang[e, v]*((u_vert[v] * dual_normal_x[e, v]) + (v_vert[v] * dual_normal_y[e, v])) for v in e2v[e])
dvt_tang = dvt_tang * tangent_orientation
# dvt_norm for smagorinsky
dvt_norm = sum(weights_norm[e, v]*(u_vert[v] * dual_normal_x[e, v] + v_vert[v] * dual_normal_y[e, v]) for v in e2v[e])
# compute smagorinsky
kh_smag_1 = sum(weights_tang[e, v]*vn_vert[e, v] for v in e2v[e])
kh_smag_1 = (kh_smag_1 * tangent_orientation * inv_primal_edge_length) + ( dvt_norm * inv_vert_vert_length)
kh_smag_1 = kh_smag_1 * kh_smag_1
kh_smag_2 = sum(weights_norm[e, v]*vn_vert[e, v] for v in e2v[e])
kh_smag_2 = (kh_smag_2 * inv_vert_vert_length) - ( dvt_tang * inv_primal_edge_length)
kh_smag_2 = kh_smag_2 * kh_smag_2
kh_smag = diff_multfac_smag * sqrt(kh_smag_2 + kh_smag_1)
# compute nabla2 using the diamond reduction
nabla2 = (sum(weights_close[e, v]*vn_vert[e, v] for v in e2v[e]) - 2.0 * vn) * (inv_primal_edge_length * inv_primal_edge_length)
nabla2 = nabla2 + (sum(weights_far[e, v]*vn_vert[e, v] for v in e2v[e]) - 2.0 * vn) * (inv_vert_vert_length * inv_vert_vert_length)
nabla2 = 4.0 * nabla2
kh_smag_ec = kh_smag
kh_smag = max(0., kh_smag - smag_offset)
kh_smag = min(kh_smag, smag_limit)
```
##### mo_nh_diffusion_stencil_05
###### dusk
```python=
mo_nh_diffusion_stencil_05(
z_nabla4_e2: Field[Edge, K],
u_vert: Field[Vertex, K],
v_vert: Field[Vertex, K],
primal_normal_vert_v1: Field[Edge > Cell > Vertex],
primal_normal_vert_v2: Field[Edge > Cell > Vertex],
z_nabla2_e: Field[Edge, K],
inv_vert_vert_length: Field[Edge],
inv_primal_edge_length: Field[Edge],
):
nabv_tang: Field[Edge, K]
nabv_norm: Field[Edge, K]
with domain.upward.across[nudging:halo]:
nabv_tang = sum_over(
Edge > Cell > Vertex,
u_vert * primal_normal_vert_v1 + v_vert * primal_normal_vert_v2,
weights=[1.0, 1.0, 0.0, 0.0],
)
nabv_norm = sum_over(
Edge > Cell > Vertex,
u_vert * primal_normal_vert_v1 + v_vert * primal_normal_vert_v2,
weights=[0.0, 0.0, 1.0, 1.0],
)
z_nabla4_e2 = 4.0 * (
(nabv_norm - 2.0 * z_nabla2_e) * inv_vert_vert_length ** 2
+ (nabv_tang - 2.0 * z_nabla2_e) * inv_primal_edge_length ** 2
)
```
###### GT4Py deprecated prototype
```python=
def nh_diff_05(
e2v: E2V,
z_nabla4_e2: Field[[Edge, K], dtype],
u_vert: Field[[Vertex, K], dtype],
v_vert: Field[[Vertex, K], dtype],
primal_normal_vert_v1: SparseField[E2V, dtype],
primal_normal_vert_v2: SparseField[E2V, dtype],
z_nabla2_e: Field[[Edge, K], dtype],
inv_vert_vert_length: Field[Edge, dtype],
inv_primal_edge_length: Field[Edge, dtype],
):
with computation(FORWARD), interval(0, None):
with location(Edge) as e:
weights_0 = LocalField[E2V, dtype]([1., 1., 0., 0.])
weights_1 = LocalField[E2V, dtype]([0., 0., 1., 1.])
nabv_tang = sum(weights_0[e, v]*(u_vert[v]*primal_normal_vert_v1[e, v] + v_vert[v]*primal_normal_vert_v2[e, v]) for v in e2v[e])
nabv_norm = sum(weights_1[e, v]*(u_vert[v]*primal_normal_vert_v1[e, v] + v_vert[v]*primal_normal_vert_v2[e, v]) for v in e2v[e])
z_nabla4_e2 = 4.0*((nabv_norm[e] - 2.0*z_nabla2_e[e])*inv_vert_vert_length[e]*inv_vert_vert_length[e] + (nabv_tang[e] - 2.0*z_nabla2_e[e])*inv_primal_edge_length[e]*inv_primal_edge_length[e])
```
#### Bindings example from 2021 hackathon
```cpp
namespace icon_bindings_sten_impl {
struct default_tag {};
template <int...> struct field_kind {};
// template<class Tag>
using neigh_tbl_t = gridtools::fortran_array_view<int, 2, default_tag, false>;
auto alloc_sten_impl(int n_edges, int n_k, neigh_tbl_t e2v) {
return sten({-1, n_edges, -1, n_k}, icon::make_connectivity_producer<2>(e2v));
}
BINDGEN_EXPORT_BINDING_WRAPPED(3, alloc_sten, alloc_sten_impl);
// template<class Tag>
using sten_t = decltype(alloc_sten_impl(0, 0, neigh_tbl_t /*<Tag>*/ {{}}));
template <class T>
void sten_impl(sten_t sten,
gridtools::fortran_array_view<T, 1, field_kind<0>> field_in,
gridtools::fortran_array_view<T, 1, field_kind<0>> field_out) {
sten(field_in, field_out);
}
BINDGEN_EXPORT_GENERIC_BINDING_WRAPPED(3, sten, sten_impl, (double));
} // namespace icon_bindings_sten_impl
```