From 734b370a9f968a8234303cbb404c432c5c2069eb Mon Sep 17 00:00:00 2001
From: Frederik Hennig <frederik.hennig@fau.de>
Date: Wed, 22 Nov 2023 14:52:25 +0900
Subject: [PATCH] small mdspan refactor. Added install instructions and usage
 primer.

---
 docs/index.md                                 | 58 ++++++++++++++++++-
 mkdocs.yml                                    |  8 ++-
 .../source_concepts/cpp/__init__.py           |  5 +-
 .../source_concepts/cpp/std_mdspan.py         | 27 ++++++++-
 tests/cmake_integration/kernels.py            |  2 -
 tests/mdspan/Makefile                         |  2 +-
 tests/mdspan/kernels.py                       | 27 +++------
 tests/mdspan/main.cpp                         | 16 +++--
 8 files changed, 113 insertions(+), 32 deletions(-)

diff --git a/docs/index.md b/docs/index.md
index 68eb439..e830190 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -3,5 +3,61 @@
 A bridge over the semantic gap between code emitted by [pystencils](https://pypi.org/project/pystencils/)
 and your C/C++/Cuda/HIP framework.
 
-The initial version of this software is still under development.
+## Installation
 
+### From Git
+
+Clone the [repository](https://i10git.cs.fau.de/da15siwa/pystencils-sfg) and install the package into your current Python environment
+(usage of virtual environments is strongly encouraged!):
+
+```shell
+$ git clone https://i10git.cs.fau.de/da15siwa/pystencils-sfg.git
+$ cd pystencils-sfg
+$ pip install .
+```
+
+### From PyPI
+
+Not yet available.
+
+## Primer
+
+With *pystencils-sfg*, including your *pystencils*-generated kernels with handwritten code becomes straightforward
+and intuitive. To illustrate, generating a Jacobi smoother for the two-dimensional Poisson equation,
+using the awesome C++23 `std::mdspan`, takes just a few lines of code:
+
+```python
+import sympy as sp
+
+from pystencils import fields, kernel
+
+from pystencilssfg import SourceFileGenerator
+from pystencilssfg.source_concepts.cpp import mdspan_ref
+
+with SourceFileGenerator() as sfg:
+    u_src, u_dst, f = fields("u_src, u_dst, f(1) : double[2D]", layout="fzyx")
+    h = sp.Symbol("h")
+
+    @kernel
+    def poisson_jacobi():
+        u_dst[0,0] @= (h**2 * f[0, 0] * u_src[1, 0] + u_src[-1, 0] + u_src[0, 1] + u_src[0, -1]) / 4
+
+    poisson_kernel = sfg.kernels.create(poisson_jacobi)
+
+    sfg.function("jacobi_smooth")(
+        sfg.map_field(u_src, mdspan_ref(u_src)),
+        sfg.map_field(u_dst, mdspan_ref(u_dst)),
+        sfg.map_field(f, mdspan_ref(f)),
+        sfg.call(poisson_kernel)
+    )
+```
+
+Take this code, store it into a file `poisson_smoother.py`, and enter the magic words into a terminal:
+
+```shell
+$ python poisson_smoother.py
+```
+
+This command will execute the code generator through the `SourceFileGenerator` context manager.
+The code generator takes the name of your Python script, replaces `.py` with `.cpp` and `.h`, and writes
+`poisson_smoother.cpp` and `poisson_smoother.h` into the current directory, ready to be `#include`d.
diff --git a/mkdocs.yml b/mkdocs.yml
index 96fb45b..5125538 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,7 +1,9 @@
 site_name: pystencils Source File Generator Documentation
 theme: 
   name: material
-  features: navigation.tabs
+  features: 
+    - navigation.tabs
+    - content.code.copy
   palette:
     scheme: slate
     primary: deep purple
@@ -27,6 +29,10 @@ plugins:
             show_signature_annotations: True
             signature_crossrefs: True
 
+markdown_extensions:
+  - pymdownx.highlight:
+      anchor_linenums: true
+  - pymdownx.superfences 
 
 nav:
   - Home: index.md
diff --git a/src/pystencilssfg/source_concepts/cpp/__init__.py b/src/pystencilssfg/source_concepts/cpp/__init__.py
index 206e2e3..ed0b13e 100644
--- a/src/pystencilssfg/source_concepts/cpp/__init__.py
+++ b/src/pystencilssfg/source_concepts/cpp/__init__.py
@@ -1,6 +1,7 @@
-from .std_mdspan import std_mdspan
+from .std_mdspan import StdMdspan, mdspan_ref
 from .std_vector import std_vector, std_vector_ref
 
 __all__ = [
-    "std_mdspan", "std_vector", "std_vector_ref"
+    "StdMdspan", "std_vector", "std_vector_ref",
+    "mdspan_ref"
 ]
diff --git a/src/pystencilssfg/source_concepts/cpp/std_mdspan.py b/src/pystencilssfg/source_concepts/cpp/std_mdspan.py
index bd5c0ea..d600387 100644
--- a/src/pystencilssfg/source_concepts/cpp/std_mdspan.py
+++ b/src/pystencilssfg/source_concepts/cpp/std_mdspan.py
@@ -1,5 +1,8 @@
 from typing import Union
 
+import numpy as np
+
+from pystencils import Field
 from pystencils.typing import FieldPointerSymbol, FieldStrideSymbol, FieldShapeSymbol
 
 from ...tree import SfgStatements
@@ -9,7 +12,7 @@ from ...types import PsType, cpp_typename, SrcType
 from ...exceptions import SfgException
 
 
-class std_mdspan(SrcField):
+class StdMdspan(SrcField):
     dynamic_extent = "std::dynamic_extent"
 
     def __init__(self, identifer: str,
@@ -77,3 +80,25 @@ class std_mdspan(SrcField):
                 f"assert( {self._identifier}.stride({coordinate}) == {stride} );",
                 (), (self, )
             )
+
+
+def mdspan_ref(field: Field, extents_type: type = np.uint32):
+    """Creates a `std::mdspan &` for a given pystencils field."""
+    from pystencils.field import layout_string_to_tuple
+
+    if field.layout != layout_string_to_tuple("soa", field.spatial_dimensions):
+        raise NotImplementedError("mdspan mapping is currently only available for structure-of-arrays fields")
+
+    extents = []
+
+    for s in field.spatial_shape:
+        extents += StdMdspan.dynamic_extent if isinstance(s, FieldShapeSymbol) else s
+
+    if field.index_shape != (1,):
+        for s in field.index_shape:
+            extents += StdMdspan.dynamic_extent if isinstance(s, FieldShapeSymbol) else s
+
+    return StdMdspan(field.name, field.dtype,
+                     (StdMdspan.dynamic_extent, StdMdspan.dynamic_extent),
+                     extents_type=extents_type,
+                     reference=True)
diff --git a/tests/cmake_integration/kernels.py b/tests/cmake_integration/kernels.py
index 901bdb0..5fcac7c 100644
--- a/tests/cmake_integration/kernels.py
+++ b/tests/cmake_integration/kernels.py
@@ -10,8 +10,6 @@ from pystencilssfg import SourceFileGenerator
 with SourceFileGenerator() as sfg:
     src, dst = fields("src, dst(1) : double[2D]")
 
-    h = sp.Symbol('h')
-
     @kernel
     def poisson_jacobi():
         dst[0, 0] @= (src[1, 0] + src[-1, 0] + src[0, 1] + src[0, -1]) / 4
diff --git a/tests/mdspan/Makefile b/tests/mdspan/Makefile
index 1761528..5a2fb0e 100644
--- a/tests/mdspan/Makefile
+++ b/tests/mdspan/Makefile
@@ -20,7 +20,7 @@ clean:
 
 $(GEN_SRC)/kernels.cpp $(GEN_SRC)/kernels.h &: kernels.py
 	@$(dir_guard)
-	$(PYTHON) $< -d $(@D)
+	$(PYTHON) $< --sfg-output-dir $(@D)
 
 $(OBJ)/kernels.o: $(GEN_SRC)/kernels.cpp $(GEN_SRC)/kernels.h
 	@$(dir_guard)
diff --git a/tests/mdspan/kernels.py b/tests/mdspan/kernels.py
index 3f045ba..dc790d2 100644
--- a/tests/mdspan/kernels.py
+++ b/tests/mdspan/kernels.py
@@ -1,32 +1,23 @@
 import sympy as sp
-import numpy as np
 
-from pystencils.session import *
+from pystencils import fields, kernel
 
 from pystencilssfg import SourceFileGenerator
-from pystencilssfg.source_concepts.cpp import std_mdspan
-
-def field_t(field: ps.Field):
-    return std_mdspan(field.name,
-                      field.dtype,
-                      (std_mdspan.dynamic_extent, std_mdspan.dynamic_extent),
-                      extents_type=np.uint32,
-                      reference=True)
-
+from pystencilssfg.source_concepts.cpp import mdspan_ref
 
 with SourceFileGenerator() as sfg:
-    src, dst = ps.fields("src, dst(1) : double[2D]")
-
-    h = sp.Symbol('h')
+    u_src, u_dst, f = fields("u_src, u_dst, f(1) : double[2D]", layout="fzyx")
+    h = sp.Symbol("h")
 
-    @ps.kernel
+    @kernel
     def poisson_jacobi():
-        dst[0,0] @= (src[1, 0] + src[-1, 0] + src[0, 1] + src[0, -1]) / 4
+        u_dst[0,0] @= (h**2 * f[0, 0] * u_src[1, 0] + u_src[-1, 0] + u_src[0, 1] + u_src[0, -1]) / 4
 
     poisson_kernel = sfg.kernels.create(poisson_jacobi)
 
     sfg.function("jacobi_smooth")(
-        sfg.map_field(src, field_t(src)),
-        sfg.map_field(dst, field_t(dst)),
+        sfg.map_field(u_src, mdspan_ref(u_src)),
+        sfg.map_field(u_dst, mdspan_ref(u_dst)),
+        sfg.map_field(f, mdspan_ref(f)),
         sfg.call(poisson_kernel)
     )
diff --git a/tests/mdspan/main.cpp b/tests/mdspan/main.cpp
index d8247a5..21c8015 100644
--- a/tests/mdspan/main.cpp
+++ b/tests/mdspan/main.cpp
@@ -25,34 +25,38 @@ int main(int argc, char ** argv){
     std::vector< double > data_dst(N*N);
     field_t dst(data_dst.data(), N, N);
 
+    std::vector< double > data_f(N*N);
+    field_t f(data_f.data(), N, N);
+
     for(uint32_t i = 0; i < N; ++i){
         for(uint32_t j = 0; j < N; ++j){
             if(i == 0 || j == 0 || i == N-1 || j == N-1){
                 src[i, j] = boundary(double(i) * h, double(j) * h);
                 dst[i, j] = boundary(double(i) * h, double(j) * h);
+                f[i, j] = 0.0;
             }
         }
     }
     
     for(uint32_t i = 0; i < n_iters; ++i){
-        poisson::jacobi_smooth(dst, src);
+        jacobi_smooth(f, h, dst, src);
         std::swap(src, dst);
     }
 
-    std::ofstream f("data.out", std::ios::trunc | std::ios::out);
+    std::ofstream file("data.out", std::ios::trunc | std::ios::out);
 
-    if(!f.is_open()){
+    if(!file.is_open()){
         std::cerr << "Could not open output file.\n";
     } else {
         for(uint32_t i = 0; i < N; ++i){
             for(uint32_t j = 0; j < N; ++j){
-                f << src[i, j] << " ";
+                file << src[i, j] << " ";
             }
-            f << '\n';
+            file << '\n';
         }
     }
 
-    f.close();
+    file.close();
 
     return 0;
 }
-- 
GitLab