diff --git a/src/sfg_walberla/api.py b/src/sfg_walberla/api.py index 8e087b2530b717f741d1b3fabc85d1bdca18d822..2fec8a000dc80f5755e503a13194de4ba5b1d23c 100644 --- a/src/sfg_walberla/api.py +++ b/src/sfg_walberla/api.py @@ -8,11 +8,20 @@ from pystencils.types import ( PsPointerType, PsType, ) -from pystencilssfg.lang import IFieldExtraction, AugExpr, SrcField, SrcVector, Ref, ExprLike +from pystencilssfg.lang import ( + IFieldExtraction, + AugExpr, + SrcField, + SrcVector, + Ref, + ExprLike, +) from pystencilssfg.ir import SfgHeaderInclude real_t = PsCustomType("walberla::real_t") +cell_idx_t = PsCustomType("walberla::cell_idx_t") +uint_t = PsCustomType("walberla::uint_t") class Vector2(SrcVector): @@ -50,10 +59,10 @@ class AABB(AugExpr): super().__init__(dtype) def min(self) -> Vector3: - return Vector3().bind("{}.min()", self) + return Vector3(real_t).bind("{}.min()", self) def max(self) -> Vector3: - return Vector3().bind("{}.max()", self) + return Vector3(real_t).bind("{}.max()", self) class BlockDataID(AugExpr): @@ -79,10 +88,25 @@ class IBlockPtr(AugExpr): def required_includes(self) -> set[SfgHeaderInclude]: return {SfgHeaderInclude.parse("domain_decomposition/IBlock.h")} + def deref(self) -> AugExpr: + return AugExpr.format("*{}", self) + + +class StructuredBlockForest(AugExpr): + def __init__(self, ref: bool = True, const: bool = False): + dtype = self.ref_type(const) if ref else self.cpp_type(const) + super().__init__(dtype) + + @staticmethod + def shared_ptr(name: str = "blocks", const: bool = False): + const_str = "const " if const else "" + dtype = PsCustomType( + f"std::shared_ptr< {const_str}walberla::StructuredBlockForest >", const=True + ) + return AugExpr(dtype).var(name) -class StructuredBlockForest: @staticmethod - def shared_ptr_ref(name, const: bool = False): + def shared_ptr_ref(name: str = "blocks", const: bool = False): const_str = "const " if const else "" dtype = PsCustomType( f"std::shared_ptr< {const_str}walberla::StructuredBlockForest >", const=True @@ -90,12 +114,36 @@ class StructuredBlockForest: return AugExpr(Ref(dtype)).var(name) @staticmethod - def ref_type(const: bool = False): - return Ref(PsCustomType("walberla::StructuredBlockForest", const=const)) + def cpp_type(const: bool = False) -> PsType: + return PsCustomType("walberla::StructuredBlockForest", const=const) + + @staticmethod + def ref_type(const: bool = False) -> Ref: + return Ref(StructuredBlockForest.cpp_type(const)) @staticmethod - def ref(name, const: bool = False): - return AugExpr(StructuredBlockForest.ref_type(const)).var(name) + def ref(name: str = "blocks", const: bool = False): + return StructuredBlockForest(ref=True).var(name) + + def dx(self, level: int | AugExpr = 0) -> AugExpr: + return AugExpr(real_t).bind("{}.dx({})", self, level) + + def dy(self, level: int | AugExpr = 0) -> AugExpr: + return AugExpr(real_t).bind("{}.dy({})", self, level) + + def dz(self, level: int | AugExpr = 0) -> AugExpr: + return AugExpr(real_t).bind("{}.dz({})", self, level) + + def cell_extents(self, coord: int, level: int | AugExpr = 0) -> AugExpr: + dx_getter = [self.dx, self.dy, self.dz][coord] + return dx_getter(level) + + def cellsPerBlock(self, coord: int, block: AugExpr) -> AugExpr: + x = ["X", "Y", "Z"][coord] + return AugExpr(uint_t).bind("{}.getNumberOf{}Cells({})", self, x, block) + + def getLevel(self, block: AugExpr) -> AugExpr: + return AugExpr(uint_t).bind("{}.getLevel({})", self, block) class GhostLayerFieldPtr(SrcField): diff --git a/src/sfg_walberla/sweep.py b/src/sfg_walberla/sweep.py index c1c2c1f403c677d913578f77922f59fd578077c7..4e73446b52e0fae63271481048ba4f9a906857a6 100644 --- a/src/sfg_walberla/sweep.py +++ b/src/sfg_walberla/sweep.py @@ -11,17 +11,23 @@ from pystencilssfg.composer.class_composer import SfgClassComposer from pystencilssfg.ir import SfgMethod from pystencils import ( - KernelFunction, Assignment, AssignmentCollection, CreateKernelConfig, Field, - TypedSymbol, ) from pystencils.types import PsType, constify, deconstify, PsCustomType from pystencilssfg import SfgComposer -from pystencilssfg.lang import VarLike, asvar, SfgVar, AugExpr, Ref, SrcVector +from pystencilssfg.lang import ( + VarLike, + asvar, + SfgVar, + AugExpr, + Ref, + SrcVector, + strip_ptr_ref, +) from .api import ( StructuredBlockForest, GhostLayerFieldPtr, @@ -29,6 +35,7 @@ from .api import ( BlockDataID, Vector2, Vector3, + real_t, ) @@ -39,7 +46,7 @@ class SweepClassProperties: dtype: PsType getter: bool setter: bool - initializer: AugExpr | None = None + initializer: tuple[AugExpr, ...] | None = None def __post_init__(self): if self.dtype.const and self.setter: @@ -86,7 +93,12 @@ class SweepClassProperties: return self._properties.values() def add_property( - self, prop: VarLike, const=False, setter: bool = True, getter: bool = True + self, + prop: VarLike, + const=False, + setter: bool = True, + getter: bool = True, + initializer: tuple[AugExpr, ...] | None = None, ): prop = asvar(prop) if prop.name in self._properties: @@ -95,7 +107,7 @@ class SweepClassProperties: dtype = constify(prop.dtype) if const else deconstify(prop.dtype) self._properties[prop.name] = SweepClassProperties.Property( - prop.name, dtype, getter, setter + prop.name, dtype, getter, setter, initializer ) def get(self, prop: VarLike) -> AugExpr: @@ -115,16 +127,19 @@ class SweepClassProperties: if p.initializer is None: ctor.add_param(p.var) else: - for var in p.initializer.depends: + for var in chain.from_iterable(e.depends for e in p.initializer): if var not in ctor.parameters: - if deconstify(var.dtype) == StructuredBlockForest.ref_type(): + if ( + deconstify(strip_ptr_ref(var.dtype)) + == StructuredBlockForest.cpp_type() + ): ctor.add_param(var, 0) else: ctor.add_param(var) for p in self._properties.values(): if p.initializer is not None: - ctor.init(p.member_var)(p.initializer) + ctor.init(p.member_var)(*p.initializer) else: ctor.init(p.member_var)(p.var) @@ -148,13 +163,14 @@ class BlockforestParameters: self._block = block self._ci = cell_interval self._extractions: dict[SfgVar, AugExpr] = dict() - self._properties: list[SweepClassProperties.Property] = [] + + self._blockforest: StructuredBlockForest | None = None @staticmethod def process(asms: AssignmentCollection) -> AssignmentCollection: from sympy.core.function import AppliedUndef - expandable_appls: set[AppliedUndef] = filter( + expandable_appls: set[AppliedUndef] = filter( # type: ignore lambda expr: hasattr(expr, "expansion_func"), asms.atoms(AppliedUndef) ) @@ -167,6 +183,22 @@ class BlockforestParameters: return asms.new_with_substitutions(subs) + def blockforest(self) -> StructuredBlockForest: + if self._blockforest is None: + blocks = StructuredBlockForest.shared_ptr() + self._property_cache.add_property( + blocks, + getter=False, + setter=False, + initializer=(StructuredBlockForest.shared_ptr_ref(),), + ) + self._blockforest = StructuredBlockForest().bind( + "*({})", self._property_cache.get(blocks) + ) + return self._blockforest + else: + return self._blockforest + def filter_params(self, params: set[SfgVar]) -> set[SfgVar]: from .symbolic import block, cell @@ -180,12 +212,34 @@ class BlockforestParameters: elif param.name == block.aabb_max[coord].name: self._extractions[param] = self._block.getAABB().max()[coord] break - # TODO: ci + elif param.name == block.ci_min[coord].name: + if self._ci is None: + self._extractions[param] = AugExpr.format("0") + else: + raise NotImplementedError() + break + elif param.name == block.ci_max[coord].name: + if self._ci is None: + self._extractions[param] = AugExpr.format( + "{} - 1", + self.blockforest().cellsPerBlock(coord, self._block), + ) + else: + raise NotImplementedError() + break elif param.name == cell.cell_extents[coord].name: - pass # TODO + self._extractions[param] = self.blockforest().cell_extents( + coord, self.blockforest().getLevel(self._block.deref()) + ) + break else: params_filtered.add(param) + return params_filtered + + def render_extractions(self, sfg: SfgComposer): + return (sfg.set_param(p, expr) for p, expr in self._extractions.items()) + @dataclass class FieldInfo: @@ -329,7 +383,7 @@ class Sweep(CustomGenerator): def __init__( self, name: str, - kernel: Sequence[Assignment] | AssignmentCollection | KernelFunction, + assignments: Sequence[Assignment] | AssignmentCollection, config: CreateKernelConfig | None = None, ): if config is not None: @@ -343,7 +397,10 @@ class Sweep(CustomGenerator): config = CreateKernelConfig(ghost_layers=0) self._name = name - self._kernel = kernel + if isinstance(assignments, AssignmentCollection): + self._assignments = assignments + else: + self._assignments = AssignmentCollection(assignments) self._gen_config = config # Map from shadow field to shadowed field @@ -364,10 +421,8 @@ class Sweep(CustomGenerator): def generate(self, sfg: SfgComposer) -> None: knamespace = sfg.kernel_namespace(f"{self._name}_kernels") - if isinstance(self._kernel, KernelFunction): - khandle = knamespace.add(self._kernel, self._name) - else: - khandle = knamespace.create(self._kernel, self._name, self._gen_config) + assignments = BlockforestParameters.process(self._assignments) + khandle = knamespace.create(assignments, self._name, self._gen_config) all_fields: dict[str, FieldInfo] = { f.name: FieldInfo( @@ -389,11 +444,15 @@ class Sweep(CustomGenerator): key=lambda fi: fi.field.name, ) + props = SweepClassProperties() + parameters = khandle.scalar_parameters - vector_groups = combine_vectors(parameters) + blockforest_params = BlockforestParameters(props, block, None) - props = SweepClassProperties() + parameters = blockforest_params.filter_params(parameters) + + vector_groups = combine_vectors(parameters) for fi in block_fields: props.add_property(fi.data_id, setter=False, getter=True) @@ -436,6 +495,8 @@ class Sweep(CustomGenerator): sfg.map_vector(components, vector) for vector, components in vector_groups.items() ), + # Extract geometry information + *(blockforest_params.render_extractions(sfg)), sfg.call(khandle), # Perform field swaps *( diff --git a/src/sfg_walberla/symbolic.py b/src/sfg_walberla/symbolic.py index 8f6c0763ff4d203429d213250984fa173f772202..5349d00584b295729b4b34da9a8c959ad3f97e8c 100644 --- a/src/sfg_walberla/symbolic.py +++ b/src/sfg_walberla/symbolic.py @@ -28,7 +28,9 @@ block = BlockCoordinates def expandable(name: str): def wrap(expansion_func): nargs = len(inspect.signature(expansion_func).parameters) - return sp.Function(name, nargs=nargs, expansion_func=staticmethod(expansion_func)) + return sp.Function( + name, nargs=nargs, expansion_func=staticmethod(expansion_func) + ) return wrap @@ -56,13 +58,35 @@ class CellCoordinates: 2 ] * CastFunc.as_numeric(block.ci_min[2] + DEFAULTS.spatial_counters[2]) - local_x = sp.Function("cell.local_x", nargs=0, walberla_info="cell.global_center.x") - local_y = sp.Function("cell.local_y", nargs=0, walberla_info="cell.global_center.y") - local_z = sp.Function("cell.local_z", nargs=0, walberla_info="cell.global_center.z") - - dx = sp.Function("cell.dx", nargs=0, walberla_info="cell.dx") - dy = sp.Function("cell.dy", nargs=0, walberla_info="cell.dy") - dz = sp.Function("cell.dz", nargs=0, walberla_info="cell.dz") + @expandable("cell.local_x") + def local_x(): + return CellCoordinates.cell_extents[0] * CastFunc.as_numeric( + block.ci_min[0] + DEFAULTS.spatial_counters[0] + ) + + @expandable("cell.local_y") + def local_y(): + return CellCoordinates.cell_extents[1] * CastFunc.as_numeric( + block.ci_min[1] + DEFAULTS.spatial_counters[1] + ) + + @expandable("cell.local_z") + def local_z(): + return CellCoordinates.cell_extents[2] * CastFunc.as_numeric( + block.ci_min[2] + DEFAULTS.spatial_counters[2] + ) + + @expandable("cell.dx") + def dx(): + return CellCoordinates.cell_extents[0] + + @expandable("cell.dy") + def dy(): + return CellCoordinates.cell_extents[1] + + @expandable("cell.dz") + def dz(): + return CellCoordinates.cell_extents[2] cell = CellCoordinates diff --git a/tests/BlockforestGeometry.py b/tests/BlockforestGeometry.py new file mode 100644 index 0000000000000000000000000000000000000000..09445e9e8be29f59556150ff6f9b0754ff040d21 --- /dev/null +++ b/tests/BlockforestGeometry.py @@ -0,0 +1,20 @@ +from pystencils.session import * +from pystencilssfg import SourceFileGenerator, SfgConfiguration +from sfg_walberla import Sweep +from sfg_walberla.symbolic import cell + +cfg = SfgConfiguration( + output_directory="out" +) + +with SourceFileGenerator(cfg) as sfg: + f = ps.fields("f(3): [3D]") + + asms = [ + ps.Assignment(f.center(0), cell.x()), + ps.Assignment(f.center(1), cell.y()), + ps.Assignment(f.center(2), cell.z()) + ] + + sweep = Sweep("coordinates", asms) + sfg.generate(sweep)