# Field View Additions
###### tags: `functional cycle 11`
Appetite: full cycle
Developer: Nikki, Ben
Support: Till/Rico?
## Bugfixes to `where` (Nikki) - PR merged
### Tuple return
Cases like
```python=
def foo(cond: Field, inp0: Field, inp1: Field):
return where(cond, (inp0,inp1), (0.,0.))
```
should be allowed.
## Absolute domain (Nikki) - PR merged
Currently, the domain where the a field_operator is applied is deduced from the out-field(s). Some use-cases would benefit from the possibility to specify the domain absolute, not relative to the out-field(s).
### Example: ICON
The domain is already expressed in absolute ranges on the Fortran side. Expressing it relative to some fields is not appealing.
### Syntax
(to be approved by Till, Rico)
```python=
@program
def foo(out_field: Field[[I,J], float], i_size, j_start, j_end) -> ...:
field_op(..., out=out_field, domain={I: (0,i_size), J:(j_start,j_end)})
```
### Conditions
- compile-time check: out_field needs to have the same dimensions as domain
- run-time check: out shape needs to be superset of domain
### Rabbit holes
- interaction between field origin (relative positioning) and domain
### Steps
- Add parsing and type-checking of the domain kwarg.
- Currently we inject size arguments into the interface at iterator IR level to pass the sizes of all fields. If the domain argument is given, we should not inject these extra arguments.
- Lowering: {Dim: (start, end)} -> cartesian_domain/unstructured_domain(named_range(Dim, start, end))
## `If` statement (Ben)
With the same semantics as `where` with scalar condition.
### Examples
```python=
@field_operator
def foo(cond: bool, cond2: bool, inp: Field[[Dim], float])
if(cond):
if(cond2):
res = inp
else:
res = 1.0
else:
res = 0.0
return res
```
as syntax sugar for
```python=
def foo(cond: bool, cond2: bool, inp: Field[[Dim], float])
return where(cond, where(cond2, inp, 1.0), 0.0)
```
---
The following case is forbidden:
```python=
@field_operator
def foo(cond: Field[[Dim], bool], inp: Field[[Dim], float])
if(cond): # ERROR: cond is not a scalar
res = inp
else:
res = 0
return res
```
Pending discussion regarding 0-dim field vs scalar, should `cond: Field[[], bool]` be allowed?
---
```python=
@field_operator
def foo(cond: bool, inp: Field[[Dim], float])
if(cond):
inp = 1.0
return inp
```
as syntax sugar for
```python=
@field_operator
def foo(cond: bool, inp: Field[[Dim], float])
return where(cond, 1.0, inp)
```
Requires definite assignment analysis.
The following is an error (even if cond is always true at runtime)
```python=
@field_operator
def foo(cond: bool)
if(cond):
res = 1.0
return res # res maybe undefined
```
## Calling functions from scan operator (Developer to be assigned)
Allow calling functions on scalars and composites thereof, i.e. tuples, from a scan operator. More explicitly this functions are required to only take scalars arguments (or composites thereof) and return scalars (and composites thereof). This allows the domain scientist to better structure his code.
```python=
@to_be_chosen_name
def scalar_func(mask: bool, v: tuple[float, float]) -> float:
return v[0]+1 if mask else v[1]
@scan_operator(axis=KDim, forward=forward, init=init, backend=fieldview_backend)
def my_scan_op(carry: float, mask: bool, x: float) -> float:
return scalar_func(mask, (carry, x))
my_scan_op(mask, x)
```
Steps:
- Decide on a name for the decorator `@to_be_chosen_name`. One proposal is `@scalar_operator`, alternatively we could already use the `@local_operator` name (planed to be used in local view) for this as the resulting operator is indeed also a local operator.
- Implement a new decorator leveraging the `ffront.decorator.FieldOperator` class (analogous to how the scan operator works). This involves adding a new `foast.ToBeChosenNameOperator` type to `common_types.py` and extensions of `accepts_args` in `type_info.py`. The usual field operator lowering is used (as for the scan operator). No changes are needed here actually.
Good beginners task.
## Asterisks `*` tuple unpacking (Sam)
```python
def my_field_op(inp: tuple[]):
*_, something =
```
## Quickstart guide - scan operator (Till)
Add a section to the quick start guide explaining how scan operators work.
Steps:
- Explain a simple scan operator that showcases the sequential nature of the scan. The example could be the following:
```python
@scan_operator(axis=KDim, forward=forward, init=init, backend=fieldview_backend)
def simple_scan_operator(carry: float, x: float) -> float:
return carry + 1.0
simple_scan_operator(x)
```
One idea to pick up the reader was to define the corresponding scan operator in mathematical terms using recursion alla:
$$
y_i = \begin{cases}
\text{simple_scan_operator}(init, x_0) & \text{ if } i = 0 \\
\text{simple_scan_operator}(y_{i-1}, x_i) & \text{else}
\end{cases}
$$
and then proceed with the actual example.
- Explain how to write a more complicated, ideally meaningful scan operator. The prototypical example is the Thomas-Algorithm that every domain scientist knows up-and-down. Starting from a mathematical description like given [here](https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm) the following implementation could be developed together with the reader: https://github.com/GridTools/gt4py/blob/0e20cfd64eb2e14399f749a48cfd86afb525fd7e/tests/functional_tests/ffront_tests/test_execution.py#L826. The mathematical details don't need to be explained the important bit is that the reader understands the semantics, in particular the scan operators & passes signature, how they relate and the combination of forward and backward pass via a field operator.
## Prototype: `index` builtin
Add an `index(Dim)` builtin which returns the index of the current iteration point.
### Syntax proposal
```python=
@field_operator
def foo():
return index(I)
```
returns a field of `I` indices form `start` to (including) `end-1` if the domain is `I:{start,end}`.
Requires discussion with frontend team (Till/Rico)
We proposed to let the user define index fields on the field operator level such that it is not possible to accidentally shift an otherwise in the signature represented index level.
### Potential problems
TODO:
how to represent at Iterator IR level
```python=
@fundef
def foo(index_field):
return deref(index_field)
@fendef
def fencil():
foo(index(IDim)) # `index_` is builtin that creates an index_field
```
---
Alternatively allow the `index` builtin in the fundef and raise it to fendef in itir to gtfn_ir lowering.
scratchpad:
```python=
@field_operator
def foo() -> Field[[V], dtype]:
return index(V)(V2V[0])
@field_operator
def foo() -> Field[[E], dtype]:
return index(V)(E2V[0])
```
$$index: VertexIndex \to VertexIndex,\quad indx \mapsto indx$$