Language Reference

Abstract

This document contains a draft of the DSL syntax for GTScript on meshes.

Design Goals

  1. Concise, compact and readable syntax

  2. General enough to allow generation of efficient code for meshes with unstructured, partially structured, structured meshes

  3. Easy to translate into GTScriptAST

Motivation and Scope

For global weather models (e.g. IFS-FVM, ICON) the ability to handle non-regular grids is required.

Concepts

This section is WIP and will be moved to the concepts repo when it matured.

Location

A bounded set with nonempty interior. We differentiate between horizontal locations, i.e. Cell, Edge, Vertex and vertical locations, i.e. a layer. Each location can be attributed to a domain dimesion.

Mesh

A subdivision of the computational domain into locations.

Field

A mapping between locations and quantities at this location.

\[f: (c_1, ..., c_n) \mapsto a\]

Local field

A field defined in the neighbourhood of a cell.

Neighbourhood chain

Used to select direct or indirect neighbours of a cell. See here https://docs.google.com/document/d/1nQZzKGi7Go9R3fme78ViB_PrQDVPxy4xTzSuGh_yJAI/edit for a description.

Todo: properly define concept and add illustrations.

Primary location

The location(s) a stencil is applied to.

Secondary location

The location on which operations on neighbours act on.

Stencil

The entry point into GTScript code is a stencil, giving the user a local perspective on the mesh. The syntax to define a stencil follows the regular python syntax to define functions with an additional @gtscript.stencil decorator, where the first function argument is the Mesh followed by a set of Field s.

@gtscript.stencil
def stencil_name(mesh: Mesh, my_field : Field[[Vertex], dtype]):
  # ...

Computations

The stencils body is composed of one or more computations specifying the iteration domain. The iteration domain is split into horizontal and vertical iteration domains. The vertical iteration domain, which is 1D, can have different policies: FORWARD, BACKWARD and PARALLEL, indicating the order constraints of the iteration. The horizontal iteration domain is of 2-dimensional nature with one unstructured dimension representing the geometrically 2-dimensional horizontal plane. There is only one horizontal iteration policy and it is always PARALLEL, so it is not specified. Statements are executed as specified in the GTScript parallel model

Computations are expressed (syntactically) using with statements and the three iteration specifications computation, interval and location.

  • computation(iteration order)

    Specifies the iteration order in the vertical dimension, where iteration_policy is one of: PARALLEL, FORWARD, BACKWARD

  • location(location_type)

    Restricts the horizontal dimension to locations of type location_type.

    location(Vertex)
    location(Edge)
    location(Cell)
    

    Locations may be labeled to reference them in neighbor reductions later on as follows:

    with location(Vertex) as v:
      # ...
    
  • interval(start, end):

    Restricts the vertical dimension to the interval \([start, end[\), i.e. an interval including start and excluding end.

    interval(0, 2)      # layer 0 and 1
    interval(0, -1)     # all layers except for the last
    interval(-1, None)  # only the last layer
    

The skeleton of a stencil my_stencil with a single field argument my_field defined on vertices, executing concurrently on all vertices, accross all layers then looks as follows:

@gtscript.stencil
def my_stencil(mesh: Mesh):
  with computation(PARALLEL), location(Vertex), interval(0, None):
    # ...

The iteration specifications may also be nested as long as their order is computation, location, interval.

@gtscript.stencil
def my_stencil(mesh: Mesh):
  with computation(PARALLEL):   # vertical iteration policy
    with location(Vertex):   # location specification
      with interval(0, None):       # vertical interval
        # ...

A stencil running different computations on the first, last and the layers in between could then look as follows:

@gtscript.stencil
def my_stencil(mesh: Mesh):
  with computation(PARALLEL):   # vertical iteration policy
    with location(Vertex):   # location specification
      with interval(0, 1):
        # statements executed on the first layer
        # ...
      with interval(1, -1):
        # statements executed on all, but the first and last layer
        # ...
      with interval(-1, None):
        # statements executed on the last layer
        # ...

The specification of the iteration policy and interval may also be skipped in which case the default iteration policy is PARALLEL and all layers are considered.

@gtscript.stencil
def my_stencil(mesh: Mesh):
  with location(Vertex):
    # ...
  with location(Edge):
    # ...

Todo: Incorporate sparse fields syntax

Types & Variables

GTScript only supports the following limited set of types. All variables are required to be of fixed type throughout the stencil.

Type

Description

Field[[*DIMS], V]

A mapping between locations with their attributed dimensions DIMS and quantaties of type V at this location.

Mesh

A discrete representation of the computational domain (passed as first argument to the stencil call).

LocationType

The type of a location, i.e. Cell, Edge, Vertex.

Location[T]

A location, i.e. a specific cell, edge or vertex, where T is a LocationType. Locations can only be constructed in LocationSpecifications or LocationComprehensions.

DataType

A scalar value
  • bool

  • int

  • float

Variable types:

  • Field

  • TemporaryField

  • Location

  • Mesh

Todo: Expand & seperate variable and type documentation.

Statements

The only statements allowed are assignments.

Assignments

The left-hand-side of an assignment is always a Field defined on the current iteration space. If the field is not passed as a stencil argument, a temporary field is automatically introduced and may be referenced throughout the entire stencil. The right-hand-side of an assignment is an expression with type DataType.

field = expression
field[location] = expression

Modified example of the copy stencil emphasizing the behaviour of temporary fields:

@gtscript.stencil
def tmp_field_copy(
  mesh: Mesh,
  field_in : Field[[Vertex], float],
  field_out : Field[[Vertex], float]
):
    with location(Vertex) as v:
      tmp_field[v] = field_in[v]
    with location(Vertex) as v:
      field_out[v] = tmp_field[v]

Example for a sparse field assignment using a generator expression on neighbors:

@gtscript.stencil
def scale_sparse(
  mesh: Mesh,
  original_field : SparseField[[Vertex], float]
  scaled_field : SparseField[[Vertex], float]
):
    with location(Vertex) as v:
      scaled_field[v, :] = 2*original_field[v] for v in edges(v)

Todo: Off-center writes

Expressions

Literals / Constants

Only boolean and numeric literals are allowed. The precision of numeric literals is contrary to python by default machine-independent, but may be overridden by the user by specifying the dtype stencil decorator argument.

# booleans are always of type bool
True
False
# integer are of type dtypes["int"]
3
# float are of type dtypes["float"]
3.

The user may for example use 32 bit float and integer values for all literals of a stencil as follows:

@gtscript.stencil(dtypes={"float": np.float32, "int": np.int32})
def my_stencil(mesh: gtscript.Mesh):
  with location(Vertex) as v:
      my_field = my_field + 1.1 # 32 bit approximation of 1.1 used
      my_field = my_field + 1   # 32 bit integer with value 1 used

The user may further explicitly specify the type of a literal using regular instantiation syntax, e.g. float32("1.1") for a float with 32 bits of precision.

# Integer
uint32("1")
uint64("1")
int32("1")
int64("1")
# Floating point
float32("1.1")
float64("1.1")

This allows for usage of literals with mixed precision.

@gtscript.stencil(dtypes={"float": np.float32})
def my_stencil(mesh: gtscript.Mesh):
  with location(Vertex) as v:
      my_field = my_field + 1.1 + float64("1.1")

Field access

Fields are accessed using the subscript operator [] with the index being the location to be accessed and a vertical offset. If no subscript is provided the value at the current location and layer is retrieved.

field        # value at the current primary location and layer
field[v, 0]  # value at the current layer and location `v`
field[v, -1] # value at location `v` with vertical offset -1

Arithmetic operators / Elementary reductions

Arithmetic operators and reductions on values of type gtc.common.DataType follow the regular python syntax.

a + b
a - b
a * b
a / b
min(a, b)
max(a, b)

Generator expressions on neighbors

Operations on neighbors are expressed using generator expressions, used e.g. in sparse field assignments and neighbor reductions. They follow the regular python syntax:

expression for location in neighbor_selector

where expression is just an expression, location the name of the symbol referencing the neighbors location and neighbor_selector is a neighbor selector. Inside the expression, fields may be referenced using location. Neighbors of the primary location can be selected via calls to the built-in function neighbors or one of the convenience functions vertices and edges.

# signature
neighbors(primary_location : Location, *chain : LocationType)

# select all cells sharing a common vertex with the current `cell`
neighbors(cell, Vertex, Cell)

Pseudo-code for vertices and edges convenience functions:

def vertices(of : Location):
  return neighbors(of, Vertex)

def edges(of : Location):
  return neighbors(of, Edge)

Example of a generator expression on the vertices of a primary location e:

vertex_field[v] for v in vertices(e)

Neighbor reductions

Reductions over neighbors are composed of a reduction function and a generator expression on the neighbors to reduce over. GTScript supports four reduction functions sum, product, min, max, computing the sum, product, mimimum and maximum, respectively, of its arguments.

Example computing the sum of vertex_field over all neighboring vertices of e:

sum(vertex_field[v] for v in vertices(e))
product(vertex_field[v] for v in vertices(e))

Optionally reductions may be supplied with a weights argument, where the i-th weight is multiplied to the value on the i-th neighbor.

sum(gen_exp, weights=None)
product(gen_exp, weights=None)
min(gen_exp, weights=None)
max(gen_exp, weights=None)

Todo: Sparse field example