From 53e73419befedb17eebf851e1b6ee81716c37ec0 Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Sun, 2 Mar 2025 11:16:06 +0100 Subject: [PATCH] finish documentation section --- docs/source/usage/api_modelling.md | 102 ++++++++++++++++++++++----- docs/source/usage/how_to_composer.md | 4 +- 2 files changed, 87 insertions(+), 19 deletions(-) diff --git a/docs/source/usage/api_modelling.md b/docs/source/usage/api_modelling.md index 88dbe5c..bba61ed 100644 --- a/docs/source/usage/api_modelling.md +++ b/docs/source/usage/api_modelling.md @@ -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 diff --git a/docs/source/usage/how_to_composer.md b/docs/source/usage/how_to_composer.md index 7f08829..966a9a6 100644 --- a/docs/source/usage/how_to_composer.md +++ b/docs/source/usage/how_to_composer.md @@ -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 -- GitLab