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

finish documentation section

parent 0b493f17
No related branches found
No related tags found
1 merge request!23Introduce field and vector extraction protocols
Pipeline #74892 passed
......@@ -7,6 +7,21 @@ kernelspec:
(how_to_cpp_api_modelling)=
# How To Reflect C++ APIs
```{code-cell} ipython3
:tags: [remove-cell]
from __future__ import annotations
import sys
from pathlib import Path
mockup_path = Path("../_util").resolve()
sys.path.append(str(mockup_path))
from sfg_monkeypatch import DocsPatchedGenerator # monkeypatch SFG for docs
from pystencilssfg import SourceFileGenerator
```
Pystencils-SFG is designed to help you generate C++ code that interfaces with pystencils on the one side,
and with your handwritten code on the other side.
This requires that the C++ classes and APIs of your framework or application be represented within the SFG system.
......@@ -253,7 +268,11 @@ 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.
at all-zero index and offsets (see [](#note-on-ghost-layers)).
The other two, when called with a coordinate $c \ge 0$, shall return
the size and linearization stride of the field in that direction.
If the coordinate is equal or larger than the field's dimensionality,
return `None` instead.
### Sample Field API Reflection
......@@ -265,9 +284,9 @@ and exposes its data pointer, shape, and strides through public methods:
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();
size_t get_shape(size_t coord);
size_t get_stride(size_t coord);
ElemType * data_ptr();
}
```
......@@ -297,40 +316,89 @@ class MyField(lang.CppClass, SupportsFieldExtraction):
) -> None:
self._elem_type = elem_type
self._dim = dim
super().__init__(ElemType=dtype, DIM=dim, **kwargs)
super().__init__(ElemType=elem_type, DIM=dim, **kwargs)
# Reflection of Public Methods
def shape(self, coord: int | lang.AugExpr) -> lang.AugExpr:
return lang.AugExpr.format("{}.shape({})", self, coord)
def get_shape(self, coord: int | lang.AugExpr) -> lang.AugExpr:
return lang.AugExpr.format("{}.get_shape({})", self, coord)
def stride(self, coord: int | lang.AugExpr) -> lang.AugExpr:
return lang.AugExpr.format("{}.stride({})", self, coord)
def get_stride(self, coord: int | lang.AugExpr) -> lang.AugExpr:
return lang.AugExpr.format("{}.get_stride({})", self, coord)
def data(self) -> lang.AugExpr:
return lang.AugExpr.format("{}.data()", self, coord)
def data_ptr(self) -> lang.AugExpr:
return lang.AugExpr.format("{}.data_ptr()", self)
# Field Extraction Protocol that uses the above interface
def _extract_ptr(self) -> lang.AugExpr:
return self.data()
return self.data_ptr()
def _extract_size(self, coordinate: int) -> lang.AugExpr | None:
if coordinate > self._dim:
return None
else:
return self.shape(coordinate)
return self.get_shape(coordinate)
def _extract_stride(self, coordinate: int) -> lang.AugExpr | None:
if coordinate > self._dim:
return None
else:
return self.stride(coordinate)
return self.get_stride(coordinate)
```
Our custom field reflection is now ready to be used.
The following generator script demonstrates what code is generated when an instance of `MyField`
is passed to `sfg.map_field`:
```{code-cell} ipython3
import pystencils as ps
from pystencilssfg.lang.cpp import std
with SourceFileGenerator() as sfg:
# Create symbolic fields
f = ps.fields("f: double[3D]")
f_myfield = MyField(f.dtype, f.ndim, ref=True).var(f.name)
# Create the kernel
asm = ps.Assignment(f(0), 2 * f(0))
khandle = sfg.kernels.create(asm)
# Create the wrapper function
sfg.function("invoke")(
sfg.map_field(f, f_myfield),
sfg.call(khandle)
)
```
### Add a Factory Function
In the above example, an instance of `MyField` representing the field `f` is created by the
slightly verbose expression `MyField(f.dtype, f.ndim, ref=True).var(f.name)`.
Having to write this sequence every time, for every field, introduces unnecessary
cognitive load and lots of potential sources of error.
Whenever it is possible to create a field reflection using just information contained in a
pystencils {any}`Field <pystencils.field.Field>` object,
the API reflection should therefore implement a factory method `from_field`:
```{code-cell} ipython3
class MyField(lang.CppClass, SupportsFieldExtraction):
...
@classmethod
def from_field(cls, field: ps.Field, const: bool = False, ref: bool = False) -> MyField:
return cls(f.dtype, f.ndim, const=const, ref=ref).var(f.name)
```
:::{admonition} To Do
The above signature is idiomatic for `from_field`, and you should stick to it as far as possible.
We can now use it inside the generator script:
Demonstrate in a generator script
:::
```{code-block} python
f = ps.fields("f: double[3D]")
f_myfield = MyField.from_field(f)
```
(note-on-ghost-layers)=
### A Note on Ghost Layers
Some care has to be taken when reflecting data structures that model the notion
......
......@@ -352,8 +352,8 @@ computing landscape, including [Kokkos Views][kokkos_view], [C++ std::mdspan][md
[SYCL buffers][sycl_buffer], and many framework-specific custom-built classes.
Using the protocols behind {any}`sfg.map_field <SfgBasicComposer.map_field>`,
it is possible to automatically emit code
that extracts the indexing information required by a kernel from any of these classes
- provided a suitable API reflection is available.
that extracts the indexing information required by a kernel from any of these classes,
as long as a suitable API reflection is available.
:::{seealso}
[](#field_data_structure_reflection) for instructions on how to set up field API
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment