# GTScript: syntax enhancements
###### tags: `betting-archive`
shaped by: Enrique, Mauro
This project consists in the implementation of the syntax additions approved in the GTScript Syntax workshop from last year ([first part](https://github.com/GridTools/concepts/wiki/GTScript-Workshop-2020-08-19), [second part](https://github.com/GridTools/concepts/wiki/GTScript-Workshop-2020-09-29))
**Important:** The documentation (specially the Quick Start Guide) should be updated to cover the new implemented features in this project.
## Appetite
Half a cycle (1, or 2 people for the extra parts)
## 1. Explicit (optional) typing of declared symbols
Support explicit assigment of types to newly defined temporary symbols and literal values using the following approaches:
+ Inline creation of typed literals (so it can be used inside expressions):
`float32(4.5)`
+ Type annotations for symbol definitions (it should also work with string annotations for types provided at compilation time):
`ALPHA: float32 = 4.5`
`ALPHA: "my_float_type" = 4.5`
+ Use `dtypes` argument of `gt.stencil`, with the builtins `int` and `float` as keys, to define the actual implementation data types of builtin `int`/`float` literals (this allows compile-time definition of the literals while avoids polluting stencil codes with unnecessary explicit type casts):
```python
@gtscript.stencil(dtypes={int: np.int32, float: np.float32})
def my_stencil(...):
field_a = field_a + 43.5 # => field_a = field_a + float32(43.5)
```
Full example:
```python=
@gt.stencil(dtypes={"external_type": float64, int=np.int32, float=np.float32})
def stencil(field_in: Field3D[float64], field_out: Field3D[float64]):
with computation(FORWARD), interval(...):
tmp_a: float32 = field_in + field_out # raise error if types do not match?
tmp_b: float32 = float32(field_in + field_out) # explicit casting is always safe
# Next example will be casted to the right precision at
# compile-time (dtypes arg of gt.stencil()) if it matches
# the kind (float/integral), otherwise it should raise an error
other: "external_type" = 2.2
field_out = 2.2 * field_out # this literal should be float32
```
List of supported types: `bool`, `int8`, `int16`, `int32`, `int64`, `float32`, `float64`
**Main goal:** support explicit typing of literal constants allowing an unambiguous behavior when using mixed-precision operations (current implementation relies on Python types, which promotes literals to 64 bit precision by default, which is not always desirable).
**Open questions:** should it be allowed to declare a symbol with a type annotation without a initialization value? Should it be allowed to place this symbol declarations outside of an interval or even a computation?
```python=
@gt.stencil()
def stencil(...):
tmp_a: float32 # Should this be allowed ??
with computation(FORWARD):
tmp_a: float32 # Should this be allowed ??
with interval(0, 1):
tmp_a: float32 # Should this be allowed ??
with interval(1, -1):
tmp_a = 32.1
```
### 1.b) Optional: support explicit typing of function arguments
Additionally, we propose to implement support for type annotations in function signatures. They should be optional for now to keep backward compatibility, but if they are provided, arguments should be strictly checked in all the calls.
**Open questions:** Could also return types be supported and checked both in the definition and in the call site? (Possible _time-sink_)
Full example:
```python
@GTScript.function
def my_function_1(field_a, field_b):
return field_a + 2.0 * field_b
def my_function_2(field_a, field_b: Field[IJK, float32]):
return field_a + 2.0 * field_b
def my_function_3(field_a: Field[float64], field_b: Field[float64]) -> float32:
return float32(field_a + 2.0 * field_b)
def my_function_3_wrong(field_a: Field[float64], field_b: Field[float64]) -> float32:
return field_a + 2.0 * field_b ## Raise error for return type ?
...
@gt.stencil(...)
def stencil(field_in: Field3D[float64], field_out: Field3D[float64]):
with computation(FORWARD), interval(...):
# Untyped functions are OK as they are right now
tmp_1 = my_function_1(field_in, field_out)
# second parameter is wrong type
tmp_2 = my_function_2(field_in, field_out)
# raise error for return type ?
tmp_3: float64 = my_function_3(field_in, field_out)
```
## 2. Enhance syntax for field indexing in cartesian grids
We propose to use the axes names in cartesian grids as keywords to identify field index offsets. They will be indicated with capital `I`, `J` and `K` as these symbold are quite commonly understood by the users and they are already used in the signature for the specification of lower dimensional `Fields`.
When an axis name is used inside a field access expression, all the non-used access will be associated to zero offsets, and the order of the axes names in the expression is not relevant:
```python
field[I+1] #==> field[1, 0 ,0]
field[J-1] #==> field[0, -1, 0]
field[K+1, I-1] #==> field[-1, 0 ,1]
```
An axis name can be assigned to local symbols and passed as arguments to `GTScript.functions` to enable parametric directions and save code duplication. We also propose the `Axis` keyword as optional type annotation for local symbols and function arguments:
```python=
@GTScript.function
def delta_plus(in_f, direction: Axis):
return in_f - in_f[direction + 1]
@GTScript.function
def delta(in_f, offset: Axis):
return in_f - in_f[offset]
@GTScript.stencil
def my_stencil(in_f: Field[dtype],
out_plus: Field[dtype],
out_minus: Field[dtype]):
with computation(PARALLEL), interval(...)
out_plus = delta_plus(in_f, I)
out_minus = delta(in_f, I-1)
axis: Axis = I
tmp_2 = 2.0 * field[axis + 3]
```
## 3. Extra tasks
- Remove `PARALLEL` keyword as iteration order: only `FORWARD` and `BACKWARD` are allowed, and if none of them is specified, it is assumed to be `PARALLEL`
- Verify that the order of specification of vertical intervals matches the order of execution, that is, in increasing order for `FORWARD` intervals, decreasing order for `BACKWARD`, and any other order for `PARALLEL`/`None`
<!-- ## 3. Expose the iteration index (positional computations) ??
Add a new builtin function `index(AXES_NAME)` to get the actual iteration index value.
Supported functionality:
- only as a run time `int` value in an expression
- **NOT** as an offset in field indices
Example:
```python
with computation(), interval(...):
current_i = index(I)
if current_i > 3:
field = field[0] - current_i
else:
field = 0.0
```
Implementation:
- Consider only the new GTC-based backends.
- For the GridTools backends, investigate how to implement the feature using _positional computations_ for optimal performance.
- For the NumPy backend, it could involve a considerable performance penalty, since it probably requires the creation of a full domain ndarray with the coordinate values.
-->