From f5b4f809a2d9325f57ba8984ab1a4317d8d14136 Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Mon, 3 Feb 2025 15:14:43 +0100 Subject: [PATCH] irregular free-slip implementation --- CMakeLists.txt | 5 +- include/sfg/IrregularFreeSlip.hpp | 170 ++++++++++++++++++ src/sfg_walberla/api.py | 60 ++++++- src/sfg_walberla/boundaries/__init__.py | 3 +- src/sfg_walberla/boundaries/boundary_utils.py | 90 ++++++++++ src/sfg_walberla/boundaries/freeslip.py | 125 +++++++++++++ src/sfg_walberla/boundaries/hbb.py | 14 +- src/sfg_walberla/reflection.py | 29 +++ src/sfg_walberla/sweep.py | 51 +++++- tests/CMakeLists.txt | 6 + tests/boundary_sweeps/CMakeLists.txt | 5 + tests/boundary_sweeps/FreeSlip.py | 16 ++ tests/boundary_sweeps/TestBoundarySweeps.cpp | 6 + 13 files changed, 554 insertions(+), 26 deletions(-) create mode 100644 include/sfg/IrregularFreeSlip.hpp create mode 100644 src/sfg_walberla/boundaries/boundary_utils.py create mode 100644 src/sfg_walberla/boundaries/freeslip.py create mode 100644 src/sfg_walberla/reflection.py create mode 100644 tests/boundary_sweeps/CMakeLists.txt create mode 100644 tests/boundary_sweeps/FreeSlip.py create mode 100644 tests/boundary_sweeps/TestBoundarySweeps.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 16854db..f9e7057 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,10 @@ include( PrepareSFG ) add_library( sfg_walberla INTERFACE ) target_sources( sfg_walberla - INTERFACE include/sfg/SparseIteration.hpp include/sfg/GenericHbbBoundary.hpp + INTERFACE + include/sfg/SparseIteration.hpp + include/sfg/GenericHbbBoundary.hpp + include/sfg/IrregularFreeSlip.hpp ) target_include_directories( sfg_walberla INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include ) diff --git a/include/sfg/IrregularFreeSlip.hpp b/include/sfg/IrregularFreeSlip.hpp new file mode 100644 index 0000000..54fecce --- /dev/null +++ b/include/sfg/IrregularFreeSlip.hpp @@ -0,0 +1,170 @@ +#pragma once + +#include "core/DataTypes.h" +#include "core/cell/Cell.h" +#include "domain_decomposition/BlockDataID.h" +#include "domain_decomposition/IBlock.h" +#include "field/GhostLayerField.h" +#include "field/FlagField.h" +#include "sfg/SparseIteration.hpp" +#include "stencil/Directions.h" +#include <tuple> + +#include "SparseIteration.hpp" + +namespace walberla::sfg +{ + struct IrregularFreeSlipLinkInfo + { + int32_t x; + int32_t y; + int32_t z; + int32_t dir; + int32_t source_x; + int32_t source_y; + int32_t source_z; + int32_t source_dir; + IrregularFreeSlipLinkInfo( + walberla::Cell fluidCell, walberla::stencil::Direction linkDir, + walberla::Cell sourceCell, walberla::stencil::Direction sourceDir) + : x{fluidCell.x()}, y{fluidCell.y()}, z{fluidCell.z()}, + dir{int32_t(linkDir)}, source_x{sourceCell.x()}, + source_y{sourceCell.y()}, source_z{sourceCell.z()}, + source_dir{int32_t(sourceDir)} {} + + bool operator==(const IrregularFreeSlipLinkInfo &other) + { + return std::tie(x, y, z, dir, source_x, source_y, source_z, source_dir) == + std::tie(other.x, other.y, other.z, other.dir, other.source_x, + other.source_y, other.source_z, other.source_dir); + } + }; + + namespace detail + { + template <typename Stencil, typename FlagField_T> + class FreeSlipLinksFromFlagField + { + public: + using IndexList = SparseIndexList<IrregularFreeSlipLinkInfo>; + + FreeSlipLinksFromFlagField( + StructuredBlockForest &sbfs, + ConstBlockDataID flagFieldID, + field::FlagUID boundaryFlagUID, + field::FlagUID fluidFlagUID) : sbfs_{sbfs}, flagFieldID_(flagFieldID), boundaryFlagUID_(boundaryFlagUID), fluidFlagUID_(fluidFlagUID) {} + + IndexList collectLinks() + { + IndexList indexList{sbfs_}; + + for (auto &block : sbfs_) + { + FlagField_T *flagField = block.getData<FlagField_T>(flagFieldID_); + auto &idxVector = indexList.get(block); + + for (auto it = flagField->beginXYZ(); it != flagField->end(); ++it) + { + Cell c{it.cell()}; + if (!isFlagSet(it, fluidFlagUID_)) + { + continue; + } + + for (auto dIt = Stencil::beginNoCenter(); dIt != Stencil::end(); ++dIt) + { + stencil::Direction dir{*dIt}; + Cell neighbor{c + dir}; + if (flagField->isFlagSet(neighbor, boundaryFlagUID_)) + { + idxVector.emplace_back(createLink(flagField, c, dir)); + } + } + } + } + } + + private: + IrregularFreeSlipLinkInfo createLink(FlagField_T *flagField, const Cell &fluidCell, const stencil::Direction dir) + { + const Cell wallCell{fluidCell + dir}; + + // inverse direction of 'dir' as lattice vector + + const cell_idx_t ix = stencil::cx[stencil::inverseDir[dir]]; + const cell_idx_t iy = stencil::cy[stencil::inverseDir[dir]]; + const cell_idx_t iz = stencil::cz[stencil::inverseDir[dir]]; + + stencil::Direction sourceDir = stencil::inverseDir[dir]; // compute reflected (mirrored) of inverse direction of 'dir' + + cell_idx_t wnx = 0; // compute "normal" vector of free slip wall + cell_idx_t wny = 0; + cell_idx_t wnz = 0; + + if (flagField->isFlagSet(wallCell.x() + ix, wallCell.y(), wallCell.z(), fluidFlagUID_)) + { + wnx = ix; + sourceDir = stencil::mirrorX[sourceDir]; + } + if (flagField->isFlagSet(wallCell.x(), wallCell.y() + iy, wallCell.z(), fluidFlagUID_)) + { + wny = iy; + sourceDir = stencil::mirrorY[sourceDir]; + } + if (flagField->isFlagSet(wallCell.x(), wallCell.y(), wallCell.z() + iz, fluidFlagUID_)) + { + wnz = iz; + sourceDir = stencil::mirrorZ[sourceDir]; + } + + // concave corner (neighbors are non-fluid) + if (wnx == 0 && wny == 0 && wnz == 0) + { + wnx = ix; + wny = iy; + wnz = iz; + sourceDir = dir; + } + + const Cell sourceCell{ + wallCell.x() + wnx, + wallCell.y() + wny, + wallCell.z() + wnz}; + + return { + fluidCell, + dir, + sourceCell, + sourceDir}; + } + + StructuredBlockForest &sbfs_; + const ConstBlockDataID flagFieldID_; + const field::FlagUID boundaryFlagUID_; + const field::FlagUID fluidFlagUID_; + }; + } + + template <typename Impl> + class IrregularFreeSlipFactory + { + using Stencil = typename Impl::Stencil; + using Sweep = typename Impl::Sweep; + + public: + IrregularFreeSlipFactory(const shared_ptr<StructuredBlockForest> blocks, BlockDataID pdfFieldID) + : blocks_{blocks}, pdfFieldID_{pdfFieldID} {} + + template <typename FlagField_T> + Sweep fromFlagField(BlockDataID flagFieldID, field::FlagUID boundaryFlagUID, field::FlagUID fluidFlagUID) + { + detail::FreeSlipLinksFromFlagField< Stencil, FlagField_T > linksFromFlagField{ *blocks_, flagFieldID, boundaryFlagUID, fluidFlagUID}; + auto indexVector = linksFromFlagField.collectLinks(); + return Impl::irregularFromIndexVector(indexVector); + } + + protected: + shared_ptr<StructuredBlockForest> blocks_; + BlockDataID pdfFieldID_; + }; +} diff --git a/src/sfg_walberla/api.py b/src/sfg_walberla/api.py index 0be30b8..207c433 100644 --- a/src/sfg_walberla/api.py +++ b/src/sfg_walberla/api.py @@ -19,9 +19,10 @@ from pystencilssfg.lang import ( Ref, ExprLike, cpptype, - CppClass + CppClass, ) from pystencilssfg.lang.types import CppTypeFactory, CppType +from pystencilssfg.lang.cpp import std real_t = PsCustomType("walberla::real_t") @@ -84,14 +85,41 @@ class AABB(_PlainCppClass): return Vector3(real_t).bind("{}.max()", self) +class Cell(_PlainCppClass): + _type = cpptype("walberla::Cell", "core/cell/Cell.h") + + def x(self) -> AugExpr: + return AugExpr.format("{}.x()", self) + + def y(self) -> AugExpr: + return AugExpr.format("{}.y()", self) + + def z(self) -> AugExpr: + return AugExpr.format("{}.z()", self) + + class CellInterval(_PlainCppClass): _type = cpptype("walberla::CellInterval", "core/cell/CellInterval.h") +class Direction(_PlainCppClass): + _type = cpptype("walberla::stencil::Direction", "stencil/Directions.h") + + class BlockDataID(_PlainCppClass): _type = cpptype("walberla::BlockDataID", "domain_decomposition/BlockDataID.h") +class IBlock(_PlainCppClass): + _type = cpptype("walberla::IBlock", "domain_decomposition/IBlock.h") + + def getData(self, dtype: str | PsType, id: BlockDataID) -> AugExpr: + return AugExpr.format("{}.template getData< {} >({})", self, dtype, id) + + def getAABB(self) -> AABB: + return AABB().bind("{}.getAABB()", self) + + class IBlockPtr(_PlainCppClass): _type = cpptype("walberla::IBlock *", "domain_decomposition/IBlock.h") @@ -110,7 +138,9 @@ class SharedPtr(CppClass): class StructuredBlockForest(AugExpr): - typename = cpptype("walberla::StructuredBlockForest", "blockforest/StructuredBlockForest.h") + typename = cpptype( + "walberla::StructuredBlockForest", "blockforest/StructuredBlockForest.h" + ) def __init__(self, ref: bool = True, const: bool = False): dtype = self.typename(const=const, ref=ref) @@ -303,9 +333,29 @@ def glfield(field: Field, ci: str | AugExpr | None = None): return GhostLayerFieldExtraction(field_ptr, ci) +class SparseIndexList(AugExpr): + _template = cpptype( + "walberla::sfg::SparseIndexList< {IndexStruct} >", "sfg/SparseIteration.hpp" + ) + + def __init__( + self, idx_struct: PsStructType, const: bool = False, ref: bool = False + ): + self._idx_struct = idx_struct + dtype = self._template(IndexStruct=idx_struct, const=const, ref=ref) + super().__init__(dtype) + + def get(self, block: IBlock) -> std.vector: + return std.vector(self._idx_struct, ref=True).bind("{}.get({})", self, block) + + def bufferId(self) -> BlockDataID: + return BlockDataID().bind("{}.bufferId()", self) + + class IndexListBufferPtr(SrcField): _template = cpptype( - "walberla::sfg::internal::IndexListBuffer< {IndexStruct} >", "sfg/SparseIteration.hpp" + "walberla::sfg::internal::IndexListBuffer< {IndexStruct} >", + "sfg/SparseIteration.hpp", ) def __init__(self, idx_struct: PsStructType): @@ -350,7 +400,7 @@ CellIdx = PsStructType( ( ("x", create_type("int64_t")), ("y", create_type("int64_t")), - ("z", create_type("int64_t")) + ("z", create_type("int64_t")), ), - "walberla::sfg::CellIdx" + "walberla::sfg::CellIdx", ) diff --git a/src/sfg_walberla/boundaries/__init__.py b/src/sfg_walberla/boundaries/__init__.py index 9a732bf..716bcb0 100644 --- a/src/sfg_walberla/boundaries/__init__.py +++ b/src/sfg_walberla/boundaries/__init__.py @@ -1,3 +1,4 @@ from .hbb import SimpleHbbBoundary +from .freeslip import FreeSlip, IRREGULAR -__all__ = ["SimpleHbbBoundary"] +__all__ = ["SimpleHbbBoundary", "FreeSlip", "IRREGULAR"] diff --git a/src/sfg_walberla/boundaries/boundary_utils.py b/src/sfg_walberla/boundaries/boundary_utils.py new file mode 100644 index 0000000..6618ddf --- /dev/null +++ b/src/sfg_walberla/boundaries/boundary_utils.py @@ -0,0 +1,90 @@ +from functools import cache + +from pystencils import Field, FieldType, TypedSymbol, Assignment +from pystencils.types import PsStructType, create_type + +from lbmpy import LBStencil +from lbmpy.methods import AbstractLbMethod +from lbmpy.boundaries.boundaryconditions import LbBoundary +from lbmpy.advanced_streaming import Timestep + +from pystencilssfg import SfgComposer +from pystencilssfg.composer.custom import CustomGenerator + +BoundaryIndexType = create_type("int32") + +HbbLinkType = PsStructType( + [ + ("x", BoundaryIndexType), + ("y", BoundaryIndexType), + ("z", BoundaryIndexType), + ("dir", BoundaryIndexType), + ], + "walberla::sfg::HbbLink", +) + + +class WalberlaLbmBoundary: + idx_struct_type: PsStructType + + @classmethod + @cache + def get_index_field(cls): + return Field( + "indexVector", + FieldType.INDEXED, + cls.idx_struct_type, + (0,), + (TypedSymbol("indexVectorLength", BoundaryIndexType), 1), + (1, 1), + ) + + def get_assignments( + self, + lb_method: AbstractLbMethod, + pdf_field: Field, + prev_timestep: Timestep = Timestep.BOTH, + streaming_pattern="pull", + ) -> list[Assignment]: + assert isinstance(self, LbBoundary) + + index_field = self.get_index_field() + + from lbmpy.advanced_streaming.indexing import BetweenTimestepsIndexing + + indexing = BetweenTimestepsIndexing( + pdf_field, + lb_method.stencil, + prev_timestep, + streaming_pattern, + BoundaryIndexType, + BoundaryIndexType, + ) + + f_out, f_in = indexing.proxy_fields + dir_symbol = indexing.dir_symbol + inv_dir = indexing.inverse_dir_symbol + + boundary_assignments = self( + f_out, + f_in, + dir_symbol, + inv_dir, + lb_method, + index_field, + None, # TODO: Fix force vector + ) + boundary_assignments = indexing.substitute_proxies(boundary_assignments) + + elements: list[Assignment] = [] + + index_arrs_node = indexing.create_code_node() + elements += index_arrs_node.get_array_declarations() + + for node in self.get_additional_code_nodes(lb_method)[::-1]: + elements += node.get_array_declarations() + + elements += [Assignment(dir_symbol, index_field[0]("dir"))] + elements += boundary_assignments.all_assignments + + return elements diff --git a/src/sfg_walberla/boundaries/freeslip.py b/src/sfg_walberla/boundaries/freeslip.py new file mode 100644 index 0000000..0f0b1b8 --- /dev/null +++ b/src/sfg_walberla/boundaries/freeslip.py @@ -0,0 +1,125 @@ +from pystencils import Field, Assignment, CreateKernelConfig +from pystencils.types import PsStructType + +from lbmpy.methods import AbstractLbMethod +from lbmpy.boundaries.boundaryconditions import LbBoundary + +from pystencilssfg import SfgComposer +from pystencilssfg.composer.custom import CustomGenerator + +from .hbb import BoundaryIndexType +from .boundary_utils import WalberlaLbmBoundary +from ..sweep import Sweep +from ..api import SparseIndexList + +__all__ = ["IRREGULAR", "FreeSlip"] + + +class _IrregularSentinel: + def __repr__(self) -> str: + return "IRREGULAR" + + +IRREGULAR = _IrregularSentinel() + + +class FreeSlip(CustomGenerator): + """Free-Slip boundary condition""" + + def __init__( + self, + name: str, + lb_method: AbstractLbMethod, + pdf_field: Field, + wall_normal: tuple[int, int, int] | _IrregularSentinel, + ): + self._name = name + self._method = lb_method + self._pdf_field = pdf_field + self._wall_normal = wall_normal + + def generate(self, sfg: SfgComposer) -> None: + if self._wall_normal == IRREGULAR: + self._generate_irregular(sfg) + else: + self._generate_regular(sfg) + + def _generate_irregular(self, sfg: SfgComposer): + sfg.include("sfg/IrregularFreeSlip.hpp") + + # Get waLBerla build config + bc_obj = WalberlaIrregularFreeSlip() + + # Get assignments for bc + bc_asm = bc_obj.get_assignments(self._method, self._pdf_field) + + # Build generator config + bc_cfg = CreateKernelConfig() + bc_cfg.index_dtype = BoundaryIndexType + index_field = bc_obj.get_index_field() + bc_cfg.index_field = index_field + + # Prepare sweep + bc_sweep = Sweep(self._name, bc_asm, bc_cfg) + + # Emit code + sfg.generate(bc_sweep) + + # Build factory + factory_name = f"{self._name}Factory" + factory_crtp_base = f"walberla::sfg::IrregularFreeSlipFactory< {factory_name} >" + index_vector = SparseIndexList( + WalberlaIrregularFreeSlip.idx_struct_type, ref=True + ).var("indexVector") + + sweep_type = bc_sweep.generated_class() + sweep_ctor_args = { + f"{self._pdf_field.name}Id": "this->pdfFieldID_", + f"{index_field.name}Id": index_vector.bufferId(), + } + + stencil_name = self._method.stencil.name + sfg.include(f"stencil/{stencil_name}.h") + + sfg.klass(factory_name, bases=[factory_crtp_base])( + sfg.private( + f"using Base = {factory_crtp_base};", + "friend class Base;", + f"using Stencil = walberla::stencil::{stencil_name};", + f"using Sweep = {sweep_type.get_dtype().c_string()};", + sfg.method("irregularFromIndexVector", returns=sweep_type.get_dtype(), inline=True)( + sfg.expr("return {};", sweep_type.ctor(**sweep_ctor_args)) + ), + ) + ) + + def _generate_regular(self, sfg: SfgComposer): + raise NotImplementedError("Regular-geometry free slip is not implemented yet") + + +class WalberlaIrregularFreeSlip(LbBoundary, WalberlaLbmBoundary): + idx_struct_type = PsStructType( + ( + ("x", BoundaryIndexType), + ("y", BoundaryIndexType), + ("z", BoundaryIndexType), + ("dir", BoundaryIndexType), + ("source_x", BoundaryIndexType), + ("source_y", BoundaryIndexType), + ("source_z", BoundaryIndexType), + ("source_dir", BoundaryIndexType), + ), + "walberla::sfg::IrregularFreeSlipLinkInfo", + ) + + def __call__( + self, f_out, f_in, dir_symbol, inv_dir, lb_method, index_field, force_vector + ): + source_cell = ( + index_field("source_x"), + index_field("source_y"), + index_field("source_z"), + ) + source_dir = index_field("source_dir") + + return Assignment(f_in(inv_dir[dir_symbol]), f_out[source_cell](source_dir)) diff --git a/src/sfg_walberla/boundaries/hbb.py b/src/sfg_walberla/boundaries/hbb.py index fdc1683..d041f1b 100644 --- a/src/sfg_walberla/boundaries/hbb.py +++ b/src/sfg_walberla/boundaries/hbb.py @@ -9,6 +9,7 @@ from pystencilssfg.composer.class_composer import SfgClassComposer from pystencilssfg.composer.custom import CustomGenerator from pystencilssfg.lang import AugExpr +from lbmpy import LBStencil from lbmpy.methods import AbstractLbMethod from lbmpy.boundaries.boundaryconditions import LbBoundary from lbmpy.advanced_streaming.indexing import Timestep @@ -20,18 +21,7 @@ from ..sweep import ( combine_vectors, ) from ..api import GhostLayerFieldPtr, BlockDataID, IBlockPtr, StructuredBlockForest, IndexListBufferPtr - -BoundaryIndexType = create_type("int32") - -HbbLinkType = PsStructType( - [ - ("x", BoundaryIndexType), - ("y", BoundaryIndexType), - ("z", BoundaryIndexType), - ("dir", BoundaryIndexType), - ], - "walberla::sfg::HbbLink", -) +from .boundary_utils import HbbLinkType, BoundaryIndexType class HbbBoundaryProperties(SweepClassProperties): diff --git a/src/sfg_walberla/reflection.py b/src/sfg_walberla/reflection.py new file mode 100644 index 0000000..13e422d --- /dev/null +++ b/src/sfg_walberla/reflection.py @@ -0,0 +1,29 @@ +from pystencilssfg.lang import CppClass, AugExpr +from pystencilssfg.lang.types import cpptype +from pystencilssfg.ir import SfgClass + + +class GeneratedClassWrapperBase(CppClass): + _class: SfgClass + _namespace: str | None + + def __init_subclass__(cls) -> None: + typename = ( + f"{cls._namespace}::{cls._class.class_name}" + if cls._namespace is not None + else cls._class.class_name + ) + cls.template = cpptype(typename) + + def ctor(self, **kwargs) -> AugExpr: + for candidate_ctor in self._class.constructors(): + ctor_argnames = [param.name for param in candidate_ctor.parameters] + if set(ctor_argnames) == set(kwargs.keys()): + break + else: + raise Exception( + f"No constructor of class {self._class.class_name} matches the argument names {kwargs.keys()}" + ) + + ctor_args = [kwargs[name] for name in ctor_argnames] + return self.ctor_bind(*ctor_args) diff --git a/src/sfg_walberla/sweep.py b/src/sfg_walberla/sweep.py index 5c16a44..a79d133 100644 --- a/src/sfg_walberla/sweep.py +++ b/src/sfg_walberla/sweep.py @@ -31,6 +31,8 @@ from pystencilssfg.lang import ( SrcVector, strip_ptr_ref, ) +from pystencilssfg.lang.types import CppTypeFactory, cpptype +from .reflection import GeneratedClassWrapperBase from .api import ( StructuredBlockForest, GenericWalberlaField, @@ -422,12 +424,18 @@ class Sweep(CustomGenerator): config: CreateKernelConfig | None = None, ): if config is not None: - if config.ghost_layers is not None: + config = config.copy() + + if config.get_option("ghost_layers") is not None: raise ValueError( "Specifying `ghost_layers` in your codegen config is invalid when generating a waLBerla sweep." ) - elif config.iteration_slice is None: - config = replace(config, ghost_layers=0) + + if ( + config.get_option("iteration_slice") is None + and config.get_option("index_field") is None + ): + config.ghost_layers = 0 else: config = CreateKernelConfig(ghost_layers=0) @@ -436,7 +444,7 @@ class Sweep(CustomGenerator): if isinstance(assignments, AssignmentCollection): self._assignments = assignments else: - self._assignments = AssignmentCollection(assignments) + self._assignments = AssignmentCollection(assignments) # type: ignore self._gen_config = config if self._gen_config.get_target() == Target.CUDA: @@ -451,13 +459,21 @@ class Sweep(CustomGenerator): # Map from shadow field to shadowed field self._shadow_fields: dict[Field, Field] = dict() + # RESULTS - unset at this point + self._generated_class: type[GeneratedClassWrapperBase] | None = None + + # CONFIGURATION + @property def sparse(self) -> bool: - return self._gen_config.index_field is not None + return self._gen_config.get_option("index_field") is not None @sparse.setter def sparse(self, sparse_iteration: bool): if sparse_iteration: + if self._gen_config.get_option("index_field") is not None: + return + if self._gen_config.get_option("index_dtype") != create_type("int64"): raise ValueError( "Sparse sweeps require `int64_t` as their index data type. Check your code generator config." @@ -471,8 +487,6 @@ class Sweep(CustomGenerator): self._gen_config.index_field = None self._gen_config.ghost_layers = 0 - # CONFIGURATION - def swap_fields(self, field: Field, shadow_field: Field): if field in self._shadow_fields: raise ValueError(f"Field swap for {field} was already registered.") @@ -503,6 +517,8 @@ class Sweep(CustomGenerator): knamespace = sfg.kernel_namespace(f"{self._name}_kernels") assignments = BlockforestParameters.process(self._assignments) + # TODO: Get default config from waLBerla build system and override its entries + # from the user-provided config khandle = knamespace.create(assignments, self._name, self._gen_config) all_fields: dict[str, FieldInfo] = { @@ -601,3 +617,24 @@ class Sweep(CustomGenerator): sfg.public(*methods), *shadows_cache.render(sfg), ) + + gen_class = sfg.context.get_class(self._name) + assert gen_class is not None + + class GenClassWrapper(GeneratedClassWrapperBase): + _class = gen_class + _namespace = sfg.context.fully_qualified_namespace + + self._generated_class = GenClassWrapper + + # CODE-GENERATION RESULTS + # These properties are only available after the sweep was generated + + @property + def generated_class(self) -> type[GeneratedClassWrapperBase]: + if self._generated_class is None: + raise AttributeError( + "Generated class is unavailable - code generation was not run yet." + ) + + return self._generated_class diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 59a075a..22ae8c4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,10 @@ cmake_minimum_required( VERSION 3.24 ) project( sfg-walberla-testsuite ) +set(WALBERLA_BUILD_TESTS OFF CACHE BOOL "") +set(WALBERLA_BUILD_BENCHMARKS OFF CACHE BOOL "") +set(WALBERLA_BUILD_TUTORIALS OFF CACHE BOOL "") + include(FetchContent) FetchContent_Declare( @@ -15,3 +19,5 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/.. ${CMAKE_BINARY_DIR}/sfg-walberla) # Test Directories include(CTest) + +add_subdirectory( boundary_sweeps ) diff --git a/tests/boundary_sweeps/CMakeLists.txt b/tests/boundary_sweeps/CMakeLists.txt new file mode 100644 index 0000000..91aa645 --- /dev/null +++ b/tests/boundary_sweeps/CMakeLists.txt @@ -0,0 +1,5 @@ + +add_executable( TestBoundarySweeps TestBoundarySweeps.cpp ) + +walberla_generate_sources( TestBoundarySweeps SCRIPTS FreeSlip.py ) +target_link_libraries( TestBoundarySweeps core blockforest field sfg_walberla ) diff --git a/tests/boundary_sweeps/FreeSlip.py b/tests/boundary_sweeps/FreeSlip.py new file mode 100644 index 0000000..abf2f03 --- /dev/null +++ b/tests/boundary_sweeps/FreeSlip.py @@ -0,0 +1,16 @@ +from pystencilssfg import SourceFileGenerator + +from pystencils import fields +from lbmpy import create_lb_method, Stencil, LBMConfig + +from sfg_walberla.boundaries import FreeSlip, IRREGULAR + +with SourceFileGenerator() as sfg: + sfg.namespace("gen") + + lb_config = LBMConfig(stencil=Stencil.D3Q19) + lb_method = create_lb_method(lbm_config=lb_config) + pdf_field = fields("f(19): double[3D]") + + freeslip = FreeSlip("FreeSlip", lb_method, pdf_field, wall_normal=IRREGULAR) + sfg.generate(freeslip) diff --git a/tests/boundary_sweeps/TestBoundarySweeps.cpp b/tests/boundary_sweeps/TestBoundarySweeps.cpp new file mode 100644 index 0000000..734d702 --- /dev/null +++ b/tests/boundary_sweeps/TestBoundarySweeps.cpp @@ -0,0 +1,6 @@ + +#include "gen/FreeSlip.hpp" + +int main(void) { + +} -- GitLab