From 9d95a9593763ed40783b751d9ce5a9da86da7f66 Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Fri, 19 Jan 2024 23:40:50 +0100 Subject: [PATCH] extend iteration space; create it.space from ghost layers --- .../nbackend/kernelcreation/context.py | 62 +------- .../nbackend/kernelcreation/defaults.py | 38 +++++ .../kernelcreation/iteration_space.py | 145 ++++++++++++++++++ 3 files changed, 185 insertions(+), 60 deletions(-) create mode 100644 src/pystencils/nbackend/kernelcreation/iteration_space.py diff --git a/src/pystencils/nbackend/kernelcreation/context.py b/src/pystencils/nbackend/kernelcreation/context.py index 28417d564..8d14a5d4f 100644 --- a/src/pystencils/nbackend/kernelcreation/context.py +++ b/src/pystencils/nbackend/kernelcreation/context.py @@ -2,7 +2,6 @@ from __future__ import annotations from typing import cast from dataclasses import dataclass -from abc import ABC from ...field import Field from ...typing import TypedSymbol, BasicType @@ -10,10 +9,11 @@ from ...typing import TypedSymbol, BasicType from ..arrays import PsLinearizedArray from ..types import PsIntegerType from ..types.quick import make_type -from ..typed_expressions import PsTypedVariable, VarOrConstant from ..constraints import PsKernelConstraint from ..exceptions import PsInternalCompilerError +from .iteration_space import IterationSpace, FullIterationSpace, SparseIterationSpace + @dataclass class PsArrayDescriptor: @@ -21,64 +21,6 @@ class PsArrayDescriptor: array: PsLinearizedArray -class IterationSpace(ABC): - """Represents the n-dimensonal iteration space of a pystencils kernel. - - Instances of this class represent the kernel's iteration region during translation from - SymPy, before any indexing sources are generated. It provides the counter symbols which - should be used to translate field accesses to array accesses. - - There are two types of iteration spaces, modelled by subclasses: - - The full iteration space translates to an n-dimensional loop nest or the corresponding device - indexing scheme. - - The sparse iteration space translates to a single loop over an index list which in turn provides the - spatial indices. - """ - - def __init__(self, spatial_indices: tuple[PsTypedVariable, ...]): - if len(spatial_indices) == 0: - raise ValueError("Iteration space must be at least one-dimensional.") - - self._spatial_indices = spatial_indices - - @property - def spatial_indices(self) -> tuple[PsTypedVariable, ...]: - return self._spatial_indices - - -class FullIterationSpace(IterationSpace): - def __init__( - self, - lower: tuple[VarOrConstant, ...], - upper: tuple[VarOrConstant, ...], - counters: tuple[PsTypedVariable, ...], - ): - if not (len(lower) == len(upper) == len(counters)): - raise ValueError( - "Lower and upper iteration limits and counters must have the same shape." - ) - - super().__init__(counters) - - self._lower = lower - self._upper = upper - self._counters = counters - - @property - def lower(self): - return self._lower - - @property - def upper(self): - return self._upper - - -class SparseIterationSpace(IterationSpace): - def __init__(self, spatial_index_variables: tuple[PsTypedVariable, ...]): - super().__init__(spatial_index_variables) - # todo - - class KernelCreationContext: """Manages the translation process from the SymPy frontend to the backend AST, and collects all necessary information for the translation. diff --git a/src/pystencils/nbackend/kernelcreation/defaults.py b/src/pystencils/nbackend/kernelcreation/defaults.py index 2ef68fe89..c928ef4e4 100644 --- a/src/pystencils/nbackend/kernelcreation/defaults.py +++ b/src/pystencils/nbackend/kernelcreation/defaults.py @@ -15,3 +15,41 @@ A possibly incomplete list of symbols and types that need to be defined: - The sparse iteration counter (doesn't even exist yet) - ... """ + +from typing import TypeVar, Generic, Callable +from ..types import PsAbstractType, PsSignedIntegerType +from ..typed_expressions import PsTypedVariable + +from ...typing import TypedSymbol + +SymbolT = TypeVar("SymbolT") + + +class PsDefaults(Generic[SymbolT]): + def __init__(self, symcreate: Callable[[str, PsAbstractType], SymbolT]): + self.index_dtype = PsSignedIntegerType(64) + """Default data type for indices.""" + + self.spatial_counter_names = ("ctr_0", "ctr_1", "ctr_2") + """Names of the default spatial counters""" + + self.spatial_counters = ( + symcreate("ctr_0", self.index_dtype), + symcreate("ctr_1", self.index_dtype), + symcreate("ctr_2", self.index_dtype), + ) + """Default spatial counters""" + + self.index_struct_coordinates = ( + symcreate("x", self.index_dtype), + symcreate("y", self.index_dtype), + symcreate("z", self.index_dtype), + ) + """Default symbols for spatial coordinates in index list structures""" + + self.sparse_iteration_counter = symcreate("list_idx", self.index_dtype) + """Default sparse iteration counter.""" + + +Sympy = PsDefaults[TypedSymbol](TypedSymbol) +Pymbolic = PsDefaults[PsTypedVariable](PsTypedVariable) diff --git a/src/pystencils/nbackend/kernelcreation/iteration_space.py b/src/pystencils/nbackend/kernelcreation/iteration_space.py new file mode 100644 index 000000000..ade56a04c --- /dev/null +++ b/src/pystencils/nbackend/kernelcreation/iteration_space.py @@ -0,0 +1,145 @@ +from __future__ import annotations +from typing import Sequence, TYPE_CHECKING +from abc import ABC +from dataclasses import dataclass +from functools import reduce +from operator import mul + +from ...field import Field +from ...transformations import get_common_field + +from ..typed_expressions import ( + PsTypedVariable, + VarOrConstant, + ExprOrConstant, + PsTypedConstant, +) +from ..arrays import PsLinearizedArray +from .defaults import Pymbolic as Defaults + +if TYPE_CHECKING: + from .context import KernelCreationContext + + +class IterationSpace(ABC): + """Represents the n-dimensonal iteration space of a pystencils kernel. + + Instances of this class represent the kernel's iteration region during translation from + SymPy, before any indexing sources are generated. It provides the counter symbols which + should be used to translate field accesses to array accesses. + + There are two types of iteration spaces, modelled by subclasses: + - The full iteration space translates to an n-dimensional loop nest or the corresponding device + indexing scheme. + - The sparse iteration space translates to a single loop over an index list which in turn provides the + spatial indices. + """ + + def __init__(self, spatial_indices: tuple[PsTypedVariable, ...]): + if len(spatial_indices) == 0: + raise ValueError("Iteration space must be at least one-dimensional.") + + self._spatial_indices = spatial_indices + + @property + def spatial_indices(self) -> tuple[PsTypedVariable, ...]: + return self._spatial_indices + + +class FullIterationSpace(IterationSpace): + @dataclass + class Dimension: + start: VarOrConstant + stop: VarOrConstant + step: VarOrConstant + counter: PsTypedVariable + + @staticmethod + def create_with_ghost_layers( + ctx: KernelCreationContext, + fields: set[Field], + ghost_layers: int | Sequence[int | tuple[int, int]], + ) -> FullIterationSpace: + """Create an iteration space for a collection of fields with ghost layers.""" + + repr_field: Field = get_common_field(fields) + repr_arr = ctx.get_array(repr_field) + dim = repr_field.spatial_dimensions + counters = [ + PsTypedVariable(name, ctx.index_dtype) + for name in Defaults.spatial_counter_names + ] + + if isinstance(ghost_layers, int): + ghost_layers_spec = [(ghost_layers, ghost_layers) for _ in range(dim)] + else: + if len(ghost_layers) != dim: + raise ValueError("Too few entries in ghost layer spec") + ghost_layers_spec = [ + ((gl, gl) if isinstance(gl, int) else gl) for gl in ghost_layers + ] + + one = PsTypedConstant(1, ctx.index_dtype) + + ghost_layer_exprs = [ + ( + PsTypedConstant(gl_left, ctx.index_dtype), + PsTypedConstant(gl_right, ctx.index_dtype), + ) + for (gl_left, gl_right) in ghost_layers_spec + ] + + dimensions = [ + FullIterationSpace.Dimension(gl_left, shape - gl_right, one, ctr) + for (gl_left, gl_right), shape, ctr in zip( + ghost_layer_exprs, repr_arr.shape, counters, strict=True + ) + ] + + return FullIterationSpace(ctx, dimensions) + + def __init__(self, ctx: KernelCreationContext, dimensions: Sequence[Dimension]): + super().__init__(tuple(dim.counter for dim in dimensions)) + + self._ctx = ctx + self._dimensions = dimensions + + @property + def dimensions(self): + return self._dimensions + + @property + def lower(self): + return (dim.start for dim in self._dimensions) + + @property + def upper(self): + return (dim.stop for dim in self._dimensions) + + @property + def steps(self): + return (dim.step for dim in self._dimensions) + + def num_iteration_items(self, dimension: int | None = None) -> ExprOrConstant: + if dimension is None: + return reduce( + mul, (self.num_iteration_items(d) for d in range(len(self.dimensions))) + ) + else: + dim = self.dimensions[dimension] + one = PsTypedConstant(1, self._ctx.index_dtype) + return one + (dim.stop - dim.start - one) / dim.step + + +class SparseIterationSpace(IterationSpace): + def __init__( + self, + spatial_indices: tuple[PsTypedVariable, ...], + index_list: PsLinearizedArray, + ): + super().__init__(spatial_indices) + self._index_list = index_list + + @property + def index_list(self) -> PsLinearizedArray: + return self._index_list -- GitLab