Newer
Older
from __future__ import annotations
from typing import TYPE_CHECKING, Sequence
from abc import ABC, abstractmethod
from pystencils import Field
from pystencils.astnodes import KernelFunction
from .tree import (
SfgCallTreeNode,
SfgKernelCallNode,
SfgStatements,
SfgFunctionParams,
SfgSequence,
SfgBlock,
)
from .tree.deferred_nodes import SfgDeferredFieldMapping
from .tree.conditional import SfgCondition, SfgCustomCondition, SfgBranch
from .source_components import (
SfgFunction,
SfgHeaderInclude,
SfgKernelNamespace,
SfgKernelHandle,
)
from .source_concepts import SrcField, TypedSymbolOrObject, SrcVector
if TYPE_CHECKING:
from .context import SfgContext
class SfgComposer:
"""Primary interface for constructing source files in pystencils-sfg."""
def __init__(self, ctx: SfgContext):
self._ctx = ctx
@property
def context(self):
return self._ctx
def prelude(self, content: str):
"""Add a string to the code file's prelude.
Do not wrap the given string in comment syntax."""
self._ctx.append_to_prelude(content)
def define(self, definition: str):
"""Add a custom definition to the generated header file."""
self._ctx.add_definition(definition)
def namespace(self, namespace: str):
"""Set the inner code namespace. Throws an exception if a namespace was already set."""
self._ctx.set_namespace(namespace)
@property
def kernels(self) -> SfgKernelNamespace:
"""The default kernel namespace. Add kernels like:
```Python
sfg.kernels.add(ast, "kernel_name")
sfg.kernels.create(assignments, "kernel_name", config)
```"""
return self._ctx._default_kernel_namespace
def kernel_namespace(self, name: str) -> SfgKernelNamespace:
"""Returns the kernel namespace of the given name, creating it if it does not exist yet."""
kns = self._ctx.get_kernel_namespace(name)
if kns is None:
kns = SfgKernelNamespace(self, name)
self._ctx.add_kernel_namespace(kns)
return kns
def include(self, header_file: str):
system_header = False
if header_file.startswith("<") and header_file.endswith(">"):
header_file = header_file[1:-1]
system_header = True
self._ctx.add_include(
SfgHeaderInclude(header_file, system_header=system_header)
)
def kernel_function(
self, name: str, ast_or_kernel_handle: KernelFunction | SfgKernelHandle
):
"""Creates a function comprising just a single kernel call.
Args:
ast_or_kernel_handle: Either a pystencils AST, or a kernel handle for an already registered AST.
"""
if self._ctx.get_function(name) is not None:
raise ValueError(f"Function {name} already exists.")
if isinstance(ast_or_kernel_handle, KernelFunction):
khandle = self._ctx.default_kernel_namespace.add(ast_or_kernel_handle)
tree = SfgKernelCallNode(khandle)
elif isinstance(ast_or_kernel_handle, SfgKernelCallNode):
tree = ast_or_kernel_handle
else:
raise TypeError("Invalid type of argument `ast_or_kernel_handle`!")
self._ctx.add_function(func)
def function(self, name: str):
"""Add a function.
The syntax of this function adder uses a chain of two calls to mimic C++ syntax:
```Python
sfg.function("FunctionName")(
# Function Body
)
```
The function body is constructed via sequencing;
refer to [make_sequence][pystencilssfg.composer.make_sequence].
if self._ctx.get_function(name) is not None:
raise ValueError(f"Function {name} already exists.")
def sequencer(*args: str | tuple | SfgCallTreeNode | SfgNodeBuilder):
tree = make_sequence(*args)
self._ctx.add_function(func)
return sequencer
def call(self, kernel_handle: SfgKernelHandle) -> SfgKernelCallNode:
"""Use inside a function body to generate a kernel call.
Args:
kernel_handle: Handle to a kernel previously added to some kernel namespace.
"""
return SfgKernelCallNode(kernel_handle)
def seq(self, *args: SfgCallTreeNode) -> SfgSequence:
"""Syntax sequencing. For details, refer to [make_sequence][pystencilssfg.composer.make_sequence]"""
return make_sequence(*args)
def params(self, *args: TypedSymbolOrObject) -> SfgFunctionParams:
"""Use inside a function body to add parameters to the function."""
return SfgFunctionParams(args)
@property
def branch(self) -> SfgBranchBuilder:
"""Use inside a function body to create an if/else conditonal branch.
The syntax is:
```Python
sfg.branch("condition")(
# then-body
)(
# else-body (may be omitted)
)
```
"""
return SfgBranchBuilder()
def map_field(self, field: Field, src_object: SrcField) -> SfgDeferredFieldMapping:
"""Map a pystencils field to a field data structure, from which pointers, sizes
and strides should be extracted.
Args:
field: The pystencils field to be mapped
src_object: A `SrcField` object representing a field data structure.
"""
return SfgDeferredFieldMapping(field, src_object)
def map_param(
self, lhs: TypedSymbolOrObject, rhs: TypedSymbolOrObject, mapping: str
):
"""Arbitrary parameter mapping: Add a single line of code to define a left-hand
side object from a right-hand side."""
return SfgStatements(mapping, (lhs,), (rhs,))
def map_vector(self, lhs_components: Sequence[TypedSymbolOrObject], rhs: SrcVector):
"""Extracts scalar numerical values from a vector data type."""
return make_sequence(
*(
rhs.extract_component(dest, coord)
for coord, dest in enumerate(lhs_components)
)
)
class SfgNodeBuilder(ABC):
@abstractmethod
def resolve(self) -> SfgCallTreeNode:
pass
def make_sequence(*args: tuple | str | SfgCallTreeNode | SfgNodeBuilder) -> SfgSequence:
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
"""Construct a sequence of C++ code from various kinds of arguments.
`make_sequence` is ubiquitous throughout the function building front-end;
among others, it powers the syntax of
[SfgComposer.function][pystencilssfg.SfgComposer.function] and
[SfgComposer.branch][pystencilssfg.SfgComposer.branch].
`make_sequence` constructs an abstract syntax tree for code within a function body, accepting various
types of arguments which then get turned into C++ code. These are:
- Strings (`str`) are printed as-is
- Tuples (`tuple`) signify *blocks*, i.e. C++ code regions enclosed in `{ }`
- Sub-ASTs and AST builders, which are often produced by the syntactic sugar and
factory methods of [SfgComposer][pystencilssfg.SfgComposer].
Its usage is best shown by example:
```Python
tree = make_sequence(
"int a = 0;",
"int b = 1;",
(
"int tmp = b;",
"b = a;",
"a = tmp;"
),
SfgKernelCall(kernel_handle)
)
sfg.context.add_function("myFunction", tree)
```
will translate to
```C++
void myFunction() {
int a = 0;
int b = 0;
{
int tmp = b;
b = a;
a = tmp;
}
kernels::kernel( ... );
}
```
"""
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
children = []
for i, arg in enumerate(args):
if isinstance(arg, SfgNodeBuilder):
children.append(arg.resolve())
elif isinstance(arg, SfgCallTreeNode):
children.append(arg)
elif isinstance(arg, str):
children.append(SfgStatements(arg, (), ()))
elif isinstance(arg, tuple):
# Tuples are treated as blocks
subseq = make_sequence(*arg)
children.append(SfgBlock(subseq))
else:
raise TypeError(f"Sequence argument {i} has invalid type.")
return SfgSequence(children)
class SfgBranchBuilder(SfgNodeBuilder):
def __init__(self):
self._phase = 0
self._cond = None
self._branch_true = SfgSequence(())
self._branch_false = None
def __call__(self, *args) -> SfgBranchBuilder:
match self._phase:
case 0: # Condition
if len(args) != 1:
raise ValueError(
"Must specify exactly one argument as branch condition!"
)
cond = args[0]
if isinstance(cond, str):
cond = SfgCustomCondition(cond)
elif not isinstance(cond, SfgCondition):
raise ValueError(
"Invalid type for branch condition. Must be either `str` or a subclass of `SfgCondition`."
)
self._cond = cond
case 1: # Then-branch
self._branch_true = make_sequence(*args)
case 2: # Else-branch
self._branch_false = make_sequence(*args)
case _: # There's no third branch!
raise TypeError("Branch construct already complete.")
self._phase += 1
return self
def resolve(self) -> SfgCallTreeNode:
assert self._cond is not None
return SfgBranch(self._cond, self._branch_true, self._branch_false)