pystencils.grids.protocols.IField#

class pystencils.grids.protocols.IField(*args, **kwargs)#

Interface for algebraic fields to the pystencils code generator.

Algebraic fields in pystencils are functions

(1)#\[f: I \to \mathcal{D}\]

from an index space \(I \subset \mathbb{Z}^{k}\) to some domain \(\mathcal{D}\). The dimensionality \(k\) is the field’s spatial rank (or just rank). In code generation, fields are backed by \(m\)-dimensional memory buffers (where \(m \ge k\)). The IField protocol defines the interface any algebraic field type must define to communicate its memory properties to the code generator.

Each algebraic field type must provide the following information to the code generator:

  • its name;

  • its memory data type (dtype);

  • its buffer specification defining the required memory properties of the field’s underlying buffers;

  • its iteration limits defining the valid index space for a kernel’s spatial iteration.

Universal Buffer Model and Indexing Rules

At runtime, pystencils fields are backed by contiguous, linearized memory buffers. Between the algebraic front-end, the code generator, and any runtime system, the indexing semantics of these buffers are governed by the following universal buffer model (see also Fig. 1).

An m-dimensional buffer of element type T is a contiguous memory region of size

B = allocShape[0] * ... * allocShape[m-1] * sizeof(T)

bytes.

The buffer’s memory is linearized according to the linearization strides (strides[0], ..., strides[m-  1]).

The buffer is split into an inner region and a padding region. The inner region has the format shape[0], ..., shape[m-  1], and is embedded at an offset (offset[0], ..., offset[m-  1]).

The buffer’s base pointer identifies the first entry of the inner region. The memory address of buffer element (i[0], ..., i[m-1]) is calculated as in Lst. 1:

Listing 1 Linearization of buffer indices#
addr = base_ptr + strides[0] * i[0] + strides[1] * i[1] + ... + strides[m-1] * i[m-1]

This has a few consequences:

  • The first entry of the inner region always has buffer index (0, ..., 0)

  • The inner region is covered exactly by the indices (0 : shape[0] - 1, 0 : shape[1] - 1, ..., 0 : shape[m-  1] - 1)

  • The padding region is accessed by negative indices, and indices with i[k] >= shape[k]

../../../_images/IFieldBufferModel.svg

Fig. 1 Universal buffer model for pystencils fields. Properties modelled by FieldBufferSpec are shown in bold; hidden properties are printed thin.#

The universal buffer model is realized by the IField protocol via get_buffer_spec. The FieldBufferSpec instance returned by get_buffer_spec defines the buffer’s data type T; (the name of) its base pointer; as well as its inner region shape and its strides as constants or symbols. These properties are printed bold in Fig. 1. Base pointer and strides are used by the backend for index calculations (Lst. 1), while the shape may be used to define the iteration space.

Field Access to Buffer Indices

The rules for how algebraic field accesses are translated into buffer indices, and how the buffer is split into inner and padding regions for a given field, depend on the field class. TensorField, for instance, allows the presence of ghost layers which it maps into the padding region.

NumPy Arrays as Buffers

NumPy and its sister libraries (CuPy, DPNP) are the prime runtime environments for pystencils, and their ndarray classes the foremost implementation of field buffers. However, ndarray does not implement the universal buffer protocol, as it does not differentiate between the inner and padding regions.

When creating an ndarray for a given field, that array must have shape (allocShape[0], ..., allocShape[m-1]); but when passing it to a kernel, its base pointer must be extracted from the correct offset. These two aspects of array handling are realized by fields implementing the following protocols:

  • CreateNdArray.create_ndarray: Gets the underlying array module and the required spatial shape; must create and return an ndarray of the given module to hold the data for the field. The spatial shape is the shape of the inner region of the field’s index space \(I\) (see (1)). For instance, TensorField.create_ndarray(xp, spatial_shape) will create an array of shape (spatial_shape[0] + 2 * ghost_layers, ...) + tensor_shape to accomodate its ghost layers, while also setting up the array’s strides to match its memory layout. create_ndarray is primarily used by the PatchData class to allocate its arrays.

  • ViewNdArray.view_ndarray: Gets an ndarray instance backing the field, and must return a view into the inner region of that array. For instance, TensorField.view_ndarray(arr) will return arr[ghost_layers:-ghost_layers, ..., 0, ..., 0], cutting of its ghost layers in the spatial buffer dimensions.

property name: str#

Name of the field

property dtype: PsType | DynamicType#

Data type of the field’s elements

property grid: PatchGrid | None#

The patch grid that defines this field’s index space

get_buffer_spec()#

Return the buffer specification defining the field’s memory properties

Return type:

FieldBufferSpec

get_iteration_limits()#

Return the iteration limits for kernels operating on this field

Return type:

IterationLimits