diff --git a/docs/source/.gitignore b/docs/source/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..6577d5875e1b2de85630d5aa8f0af0f5ede45333 --- /dev/null +++ b/docs/source/.gitignore @@ -0,0 +1 @@ +**/_sfg_out \ No newline at end of file diff --git a/docs/source/_util/sfg_mockup.py b/docs/source/_util/sfg_mockup.py new file mode 100644 index 0000000000000000000000000000000000000000..4bcaad19c0a8e059ed8eb6640621c863b6dc623e --- /dev/null +++ b/docs/source/_util/sfg_mockup.py @@ -0,0 +1,19 @@ +from pystencilssfg import SourceFileGenerator +from pystencilssfg.config import SfgConfig + + +class DocsMockupGenerator(SourceFileGenerator): + scriptname: str = "script" + + def _scriptname(self) -> str: + return f"{DocsMockupGenerator.scriptname}.py" + + def __init__( + self, sfg_config: SfgConfig | None = None, keep_unknown_argv: bool = False + ): + if sfg_config is None: + sfg_config = SfgConfig() + + sfg_config.output_directory = "_sfg_out" + + super().__init__(sfg_config, keep_unknown_argv=True) diff --git a/docs/source/conf.py b/docs/source/conf.py index d6aab17bc3f667bbfc923c80c2d1c35b9e08a3d7..db8f7823250c96230ded5b8b99f9fb968a29d2a6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -89,17 +89,3 @@ myst_enable_extensions = [ "dollarmath", "colon_fence", ] - -# Prepare code generation examples - -def build_examples(): - import subprocess - import os - - examples_dir = os.path.join("usage", "examples",) - - subprocess.run(["python", "build.py"], cwd=examples_dir).check_returncode() - - -print("Generating output of example scripts...") -build_examples() diff --git a/docs/source/getting_started.md b/docs/source/getting_started.md new file mode 100644 index 0000000000000000000000000000000000000000..822ea0b5a9f0609546e332de49c8376d66e41ecb --- /dev/null +++ b/docs/source/getting_started.md @@ -0,0 +1,136 @@ +--- +file_format: mystnb +kernelspec: + name: python3 +--- + +# Getting Started + +## Prequesites + +To use pystencils-sfg, you will need at least Python 3.10. +You will also need the appropriate compilers for building the generated code, +such as + - a modern C++ compiler (e.g. GCC, clang) + - `nvcc` for CUDA or `hipcc` for HIP + - Intel OneAPI or AdaptiveCpp for SYCL + +Furthermore, an installation of clang-format for automatic code formatting is strongly recommended. + +## Install the Latest Development Revision + +As pystencils-sfg is still unreleased, it can at this time only be obtained directly +from its Git repository. + +Create a fresh [virtual environment](https://docs.python.org/3/library/venv.html) or activate +an existing one. Install both the pystencils 2.0 and pystencils-sfg development revisions from Git: + +```{code-block} bash +pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev" +pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils-sfg.git" +``` + +````{caution} + +*pystencils-sfg* is not compatible with the *pystencils 1.3.x* releases available from PyPI; +at the moment, you will still have to manually install the latest version of pystencils 2.0. +```` + +## Check your Installation + +To verify that the SFG was successfully installed, execute the following command: + +```{code-block} bash +sfg-cli version +``` + +You should see an output like `0.1a4+...`. + +## Writing a Basic Generator Script + +To start using pystencils-sfg, create a new empty Python file and populate it with the +following minimal skeleton: + +```{code-block} python +from pystencilssfg import SourceFileGenerator + +with SourceFileGenerator() as sfg: + ... +``` + +The above snippet defines the basic structure of a *generator script*. +When executed, the above will produce two (nearly) empty C++ files +in the current folder, both with the same name as your Python script +but with `.hpp` and `.cpp` file extensions instead. + +Generator scripts are the primary mode of using pystencils-sfg; +in them, code generation is orchestrated by the `SourceFileGenerator` context manager. +When entering into the region controlled by the `SourceFileGenerator`, +it supplies us with a *composer object*, customarily called `sfg`. +Through the composer, we can declaratively populate the generated files with code. + +## Adding a pystencils Kernel + +One of the core applications of pystencils-sfg is to generate and wrap pystencils-kernels +for usage within C++ applications. +To register a kernel, pass its assignments to `sfg.kernels.create`. +This gives you a *kernel handle*, through which you can call the kernel from a function: + +```{code-cell} ipython3 +:tags: [remove-cell] + +import sys +from pathlib import Path + +mockup_path = Path("_util").resolve() +sys.path.append(str(mockup_path)) + +from sfg_mockup import DocsMockupGenerator as SourceFileGenerator +``` + +```{code-cell} ipython3 +:tags: [remove-cell] +SourceFileGenerator.scriptname = "demo1" +``` + +```{code-cell} ipython3 +import pystencils as ps +import sympy as sp + +with SourceFileGenerator() as sfg: + # Define a copy kernel + src, dst = ps.fields("src, dst: [1D]") + c = sp.Symbol("c") + + @ps.kernel + def scale(): + dst.center @= c * src.center() + + # Register the kernel for code generation + scale_kernel = sfg.kernels.create(scale, "scale_kernel") + + # Wrap it in a function + sfg.function("scale")( + sfg.call(scale_kernel) + ) + +``` + +::::{tab-set} + +:::{tab-item} Generated Header +```{literalinclude} _sfg_out/demo1.hpp +:language: C++ +``` +::: + +:::{tab-item} Generated Implementation +```{literalinclude} _sfg_out/demo1.cpp +:language: C++ +``` +::: + +:::: + +## Next Steps + diff --git a/docs/source/index.md b/docs/source/index.md index 0cab08335824b9c6247052f243127b8d72287dfa..b11cb53af25eb9408663ef97ef73971c66f00132 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -1,5 +1,12 @@ # The pystencils Source File Generator +```{toctree} +:maxdepth: 1 +:hidden: + +getting_started +``` + ```{toctree} :maxdepth: 1 :hidden: diff --git a/docs/source/usage/generator_scripts.md b/docs/source/usage/generator_scripts.md index 3141feec607c7eff68394288624b67a192d84ad0..343fd74d624714210c817c0be963e2a5e5dae8c3 100644 --- a/docs/source/usage/generator_scripts.md +++ b/docs/source/usage/generator_scripts.md @@ -1,14 +1,32 @@ + (guide:generator_scripts)= -# Generator Scripts +# Generator Script Configuration and Command-Line Interface -Writing generator scripts is the primary usage idiom of *pystencils-sfg*. -A generator script is a Python script, say `kernels.py`, which contains *pystencils-sfg* -code at the top level that, when executed, emits source code to a pair of files `kernels.hpp` -and `kernels.cpp`. This guide describes how to write such a generator script, its structure, and how -it can be used to generate code. +Generator scripts are the primary mode of using pystencils-sfg. +A generator script is a Python script, say `kernels.py`, which uses the pystencils-sfg API +at the top level that, when executed, emits source code to a pair of files `kernels.hpp` +and `kernels.cpp`. +This guide describes the basic structure of generator scripts, their execution from the command line, +the configuration of the code generator both inline and from the shell, +as well as customization of their command-line options. ## Anatomy +At minimum, each generator script must contain the following code: + +```{code-block} python +from pystencilssfg import SourceFileGenerator + +with SourceFileGenerator() as sfg: + ... +``` + +The code generation process begins as the above code enters the region controlled by the +`SourceFileGenerator`. +The object `sfg` in the above snippet is the *composer*, which exposes the primary API +for interacting with the code generator. + + The code generation process in a generator script is controlled by the `SourceFileGenerator` context manager. It configures the code generator by combining configuration options from the environment (e.g. a CMake build system) with options specified in the script, diff --git a/src/pystencilssfg/emission/emitter.py b/src/pystencilssfg/emission/emitter.py index c1b6e9c79e09bf1d5604dbd6fca9304190e85272..7f8870b2250ff42d8e4f31d28f7c303d5b7e2b7d 100644 --- a/src/pystencilssfg/emission/emitter.py +++ b/src/pystencilssfg/emission/emitter.py @@ -13,8 +13,8 @@ class SfgCodeEmitter: def __init__( self, output_directory: Path, - code_style: CodeStyle, - clang_format: ClangFormatOptions, + code_style: CodeStyle = CodeStyle(), + clang_format: ClangFormatOptions = ClangFormatOptions(), ): self._output_dir = output_directory self._code_style = code_style diff --git a/src/pystencilssfg/generator.py b/src/pystencilssfg/generator.py index f3f67a02f4da7a44ae324ddd41f5585645cadb58..379b24d0502a45da46af69bf4ec71ac93c03c110 100644 --- a/src/pystencilssfg/generator.py +++ b/src/pystencilssfg/generator.py @@ -32,14 +32,7 @@ class SourceFileGenerator: `sfg.context.argv`. """ - def __init__( - self, - sfg_config: SfgConfig | None = None, - keep_unknown_argv: bool = False, - ): - if sfg_config and not isinstance(sfg_config, SfgConfig): - raise TypeError("sfg_config is not an SfgConfiguration.") - + def _scriptname(self) -> str: import __main__ if not hasattr(__main__, "__file__"): @@ -50,7 +43,17 @@ class SourceFileGenerator: ) scriptpath = Path(__main__.__file__) - scriptname = scriptpath.name + return scriptpath.name + + def __init__( + self, + sfg_config: SfgConfig | None = None, + keep_unknown_argv: bool = False, + ): + if sfg_config and not isinstance(sfg_config, SfgConfig): + raise TypeError("sfg_config is not an SfgConfiguration.") + + scriptname = self._scriptname() basename = scriptname.rsplit(".")[0] from argparse import ArgumentParser