---
# System prepended metadata

title: 'Continue: ICON stencils on the Declarative GT4Py'
tags: [functional cycle 8]

---

# 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
```