Skip to content
Snippets Groups Projects
Commit 0b493f17 authored by Frederik Hennig's avatar Frederik Hennig
Browse files

update documentation; start writing user guide on field api reflection

parent e37608fb
No related branches found
No related tags found
1 merge request!23Introduce field and vector extraction protocols
Pipeline #74412 passed
......@@ -21,6 +21,12 @@ Data Types
.. automodule:: pystencilssfg.lang.types
:members:
Extraction Protocols
--------------------
.. automodule:: pystencilssfg.lang.extractions
:members:
C++ Standard Library (``pystencilssfg.lang.cpp``)
-------------------------------------------------
......
......@@ -233,7 +233,120 @@ expr, lang.depends(expr), lang.includes(expr)
(field_data_structure_reflection)=
## Reflecting Field Data Structures
One key feature of pystencils-sfg is its ability to map symbolic fields
onto arbitrary array data structures
using the composer's {any}`map_field <SfgBasicComposer.map_field>` method.
The APIs of a custom field data structure can naturally be injected into pystencils-sfg
using the modelling framework described above.
However, for them to be recognized by `map_field`,
the reflection class also needs to implement the {any}`SupportsFieldExtraction` protocol.
This requires that the following three methods are implemented:
```{code-block} python
def _extract_ptr(self) -> AugExpr: ...
def _extract_size(self, coordinate: int) -> AugExpr | None: ...
def _extract_stride(self, coordinate: int) -> AugExpr | None: ...
```
The first, `_extract_ptr`, must return an expression that evaluates
to the base pointer of the field's memory buffer.
This pointer has to point at the field entry which pystencils accesses
at all-zero index and offsets.
### Sample Field API Reflection
Consider the following class template for a field, which takes its element type
and dimensionality as template parameters
and exposes its data pointer, shape, and strides through public methods:
```{code-block} C++
template< std::floating_point ElemType, size_t DIM >
class MyField {
public:
size_t shape(size_t coord);
size_t stride(size_t coord);
ElemType * data();
}
```
It could be reflected by the following class.
Note that in this case we define a custom `__init__` method in order to
intercept the template arguments `elem_type` and `dim`
and store them as instance members.
Our `__init__` then forwards all its arguments up to `CppClass.__init__`.
We then define reflection methods for `shape`, `stride` and `data` -
the implementation of the field extraction protocol then simply calls these methods.
```{code-cell} ipython3
from pystencilssfg.lang import SupportsFieldExtraction
from pystencils.types import UserTypeSpec
class MyField(lang.CppClass, SupportsFieldExtraction):
template = lang.cpptype(
"MyField< {ElemType}, {DIM} >",
"MyField.hpp"
)
def __init__(
self,
elem_type: UserTypeSpec,
dim: int,
**kwargs,
) -> None:
self._elem_type = elem_type
self._dim = dim
super().__init__(ElemType=dtype, DIM=dim, **kwargs)
# Reflection of Public Methods
def shape(self, coord: int | lang.AugExpr) -> lang.AugExpr:
return lang.AugExpr.format("{}.shape({})", self, coord)
def stride(self, coord: int | lang.AugExpr) -> lang.AugExpr:
return lang.AugExpr.format("{}.stride({})", self, coord)
def data(self) -> lang.AugExpr:
return lang.AugExpr.format("{}.data()", self, coord)
# Field Extraction Protocol that uses the above interface
def _extract_ptr(self) -> lang.AugExpr:
return self.data()
def _extract_size(self, coordinate: int) -> lang.AugExpr | None:
if coordinate > self._dim:
return None
else:
return self.shape(coordinate)
def _extract_stride(self, coordinate: int) -> lang.AugExpr | None:
if coordinate > self._dim:
return None
else:
return self.stride(coordinate)
```
:::{admonition} To Do
Write guide on field data structure reflection
Demonstrate in a generator script
:::
### A Note on Ghost Layers
Some care has to be taken when reflecting data structures that model the notion
of ghost layers.
Consider an array with the index space $[0, N_x) \times [0, N_y)$,
its base pointer identifying the entry $(0, 0)$.
When a pystencils kernel is generated with a shell of $k$ ghost layers
(see {any}`CreateKernelConfig.ghost_layers <pystencils.codegen.config.CreateKernelConfig.ghost_layers>`),
it will process only the subspace $[k, N_x - k) \times [k, N_x - k)$.
If your data structure is implemented such that ghost layer nodes have coordinates
$< 0$ and $\ge N_{x, y}$,
you must hence take care that
- either, `_extract_ptr` returns a pointer identifying the array entry at `(-k, -k)`;
- or, ensure that kernels operating on your data structure are always generated
with `ghost_layers = 0`.
In either case, you must make sure that the number of ghost layers in your data structure
matches the expected number of ghost layers of the kernel.
......@@ -518,7 +518,7 @@ class SfgBasicComposer(SfgIComposer):
Args:
field: The pystencils field to be mapped
index_provider: An expression representing a field, or a field extraction provider instance
index_provider: An object that provides the field indexing information
cast_indexing_symbols: Whether to always introduce explicit casts for indexing symbols
"""
return SfgDeferredFieldMapping(
......@@ -540,7 +540,7 @@ class SfgBasicComposer(SfgIComposer):
Args:
lhs_components: Vector components as a list of symbols.
rhs: A `SrcVector` object representing a vector data structure.
rhs: An object providing access to vector components
"""
components: list[SfgVar | sp.Symbol] = [
(asvar(c) if isinstance(c, _VarLike) else c) for c in lhs_components
......
......@@ -13,14 +13,41 @@ class SupportsFieldExtraction(Protocol):
They can therefore be passed to `sfg.map_field <SfgBasicComposer.map_field>`.
"""
# how-to-guide begin
@abstractmethod
def _extract_ptr(self) -> AugExpr: ...
def _extract_ptr(self) -> AugExpr:
"""Extract the field base pointer.
Return an expression which represents the base pointer
of this field data structure.
:meta public:
"""
@abstractmethod
def _extract_size(self, coordinate: int) -> AugExpr | None: ...
def _extract_size(self, coordinate: int) -> AugExpr | None:
"""Extract field size in a given coordinate.
If ``coordinate`` is valid for this field (i.e. smaller than its dimensionality),
return an expression representing the logical size of this field
in the given dimension.
Otherwise, return `None`.
:meta public:
"""
@abstractmethod
def _extract_stride(self, coordinate: int) -> AugExpr | None: ...
def _extract_stride(self, coordinate: int) -> AugExpr | None:
"""Extract field stride in a given coordinate.
If ``coordinate`` is valid for this field (i.e. smaller than its dimensionality),
return an expression representing the memory linearization stride of this field
in the given dimension.
Otherwise, return `None`.
:meta public:
"""
# how-to-guide end
class SupportsVectorExtraction(Protocol):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment