diff --git a/CMakeLists.txt b/CMakeLists.txt index 15cdc03ad250bf7d9b197a8801a867f204ae4113..8dd89fe357e31548cbb51ee5f66bb84ccff03a99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include( PrepareSFG ) add_library( sfg_walberla INTERFACE ) target_sources( sfg_walberla - INTERFACE include/GenericHbbBoundary.hpp + INTERFACE include/sfg/IndexVectors.hpp include/sfg/GenericHbbBoundary.hpp ) target_include_directories( sfg_walberla INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include ) diff --git a/cmake/PrepareSFG.cmake b/cmake/PrepareSFG.cmake index c7551063b2ed0bb5938a6c2f69dd6b22da8216ae..ca0aa53570796e8f933751a04e55070ecd81cfd9 100644 --- a/cmake/PrepareSFG.cmake +++ b/cmake/PrepareSFG.cmake @@ -1,7 +1,7 @@ # Maybe set up private virtual environment set( - WALBERLA_CODEGEN_PRIVATE_VENV OFF + WALBERLA_CODEGEN_PRIVATE_VENV ON CACHE BOOL "Create a private virtual Python environment inside the build tree for code generation" ) diff --git a/include/GenericHbbBoundary.hpp b/include/sfg/GenericHbbBoundary.hpp similarity index 85% rename from include/GenericHbbBoundary.hpp rename to include/sfg/GenericHbbBoundary.hpp index f68d5e12cbd304db15203ff9acc3ad878d5eeca3..f527606d3fd5f5bdedf276b56f40c33441df9b86 100644 --- a/include/GenericHbbBoundary.hpp +++ b/include/sfg/GenericHbbBoundary.hpp @@ -14,31 +14,13 @@ #include <memory> #include <tuple> +#include "IndexVectors.hpp" + namespace walberla { namespace sfg { -/** - * @brief Index vectors for sparse-iteration boundary handling - */ -template< typename LinkStruct > -class BoundaryIndexVectors -{ -public: - using CpuIndexVector = std::vector< LinkStruct >; - - BoundaryIndexVectors() = default; - bool operator==(const BoundaryIndexVectors& other) const { return other.links_ == links_; } - - CpuIndexVector& vector() { return links_; } - - LinkStruct* pointerCpu() { return links_.data(); } - -private: - CpuIndexVector links_; -}; - struct HbbLink { int32_t x; @@ -67,7 +49,7 @@ class GenericHbbBoundary { public: using Stencil = Stencil_T; - using IndexVectors = BoundaryIndexVectors< HbbLink >; + using IndexVectors = IndexListBuffer< HbbLink >; GenericHbbBoundary(StructuredBlockForest& blocks) { diff --git a/include/sfg/IndexVectors.hpp b/include/sfg/IndexVectors.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9862e7662b63cceb337217392aa92cb55cf06739 --- /dev/null +++ b/include/sfg/IndexVectors.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include <vector> +#include "core/DataTypes.h" +#include "core/cell/Cell.h" + +#include "domain_decomposition/BlockDataID.h" +#include "blockforest/StructuredBlockForest.h" + +namespace walberla::sfg +{ + namespace internal + { + /** + * @brief Index vectors for sparse sweeps + */ + template <typename IndexStruct> + class IndexListBuffer + { + public: + using CpuVector = std::vector<IndexStruct>; + + IndexListBuffer() = default; + bool operator==(const IndexListBuffer &other) const { return other.vec_ == vec_; } + + CpuVector &vector() { return vec_; } + + IndexStruct *pointerCpu() { return vec_.data(); } + + private: + CpuVector vec_; + }; + } + + struct CellIdx + { + int64_t x; + int64_t y; + int64_t z; + + CellIdx(const Cell &c) + : x{int64_c(c.x())}, y{int64_c(c.y())}, z{int64_c(c.z())} + { + } + + bool operator==(const CellIdx &other) const + { + return std::tie(x, y, z) == std::tie(other.x, other.y, other.z); + } + }; + + template <typename IndexStruct = CellIdx> + class SparseIndexList + { + public: + using Buffer_T = internal::IndexListBuffer<IndexStruct>; + + explicit SparseIndexList(StructuredBlockForest &sbfs) : bufferId_{createBuffers(sbfs)} {} + + std::vector<IndexStruct> &get(IBlock &block) + { + Buffer_T *buf = block.template getData<Buffer_T>(bufferId_); + return buf->vector(); + } + + const BlockDataID & bufferId() const { + return bufferId_; + } + + private: + static BlockDataID createBuffers(StructuredBlockForest &sbfs) + { + auto createIdxVector = [](IBlock *const, StructuredBlockStorage *const) + { return new Buffer_T(); }; + return sbfs.addStructuredBlockData<Buffer_T>(createIdxVector); + } + + BlockDataID bufferId_; + }; +} diff --git a/src/sfg_walberla/api.py b/src/sfg_walberla/api.py index 2818b43f0ebe7bec7ef17859bc6857e7c40d8430..b6a1aeb7dcb177cd62adf1572580b74093511033 100644 --- a/src/sfg_walberla/api.py +++ b/src/sfg_walberla/api.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Callable +from typing import cast from pystencils import Field from pystencils.types import ( @@ -9,6 +9,7 @@ from pystencils.types import ( PsCustomType, PsPointerType, PsType, + PsStructType, ) from pystencilssfg.lang import ( IFieldExtraction, @@ -18,8 +19,9 @@ from pystencilssfg.lang import ( Ref, ExprLike, cpptype, + CppClass ) -from pystencilssfg.lang.types import CppType +from pystencilssfg.lang.types import CppTypeFactory, CppType real_t = PsCustomType("walberla::real_t") @@ -27,8 +29,8 @@ cell_idx_t = PsCustomType("walberla::cell_idx_t") uint_t = PsCustomType("walberla::uint_t") -class _CppClass(AugExpr): - _type: Callable[..., CppType] +class _PlainCppClass(AugExpr): + _type: CppTypeFactory def __init__(self, const: bool = False, ref: bool = False): dtype = self._type(const=const, ref=ref) @@ -72,7 +74,7 @@ class Vector3(SrcVector): return AugExpr(self._element_type).bind("{}[{}]", self, idx) -class AABB(_CppClass): +class AABB(_PlainCppClass): _type = cpptype("walberla::AABB", "core/math/AABB.h") def min(self) -> Vector3: @@ -82,15 +84,15 @@ class AABB(_CppClass): return Vector3(real_t).bind("{}.max()", self) -class CellInterval(_CppClass): +class CellInterval(_PlainCppClass): _type = cpptype("walberla::CellInterval", "core/cell/CellInterval.h") -class BlockDataID(_CppClass): +class BlockDataID(_PlainCppClass): _type = cpptype("walberla::BlockDataID", "domain_decomposition/BlockDataID.h") -class IBlockPtr(_CppClass): +class IBlockPtr(_PlainCppClass): _type = cpptype("walberla::IBlock *", "domain_decomposition/IBlock.h") def getData(self, dtype: str | PsType, id: BlockDataID) -> AugExpr: @@ -103,34 +105,26 @@ class IBlockPtr(_CppClass): return AugExpr.format("*{}", self) +class SharedPtr(CppClass): + template = cpptype("std::shared_ptr< {} >", "<memory>") + + class StructuredBlockForest(AugExpr): + typename = cpptype("walberla::StructuredBlockForest", "blockforest/StructuredBlockForest.h") + def __init__(self, ref: bool = True, const: bool = False): - dtype = self.ref_type(const) if ref else self.cpp_type(const) + dtype = self.typename(const=const, ref=ref) 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) + dtype = StructuredBlockForest.typename(const=const) + return SharedPtr(dtype).var(name) @staticmethod 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 - ) - return AugExpr(Ref(dtype)).var(name) - - @staticmethod - 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)) + dtype = StructuredBlockForest.typename(const=const) + return SharedPtr(dtype, const=True, ref=True).var(name) @staticmethod def ref(name: str = "blocks", const: bool = False): @@ -307,3 +301,56 @@ def glfield(field: Field, ci: str | AugExpr | None = None): ci = AugExpr(PsCustomType("walberla::CellInterval", const=True)).var(ci) return GhostLayerFieldExtraction(field_ptr, ci) + + +class IndexListBufferPtr(SrcField): + _template = cpptype( + "walberla::sfg::internal::IndexListBuffer< {IndexStruct} >", "sfg/IndexVectors.hpp" + ) + + def __init__(self, idx_struct: PsStructType): + self._idx_struct = idx_struct + dtype = self._template(IndexStruct=idx_struct) + self._base_type = dtype + super().__init__(PsPointerType(dtype)) + + @property + def field_type(self) -> CppType: + return cast(CppType, self._base_type) + + def pointerCpu(self) -> AugExpr: + return AugExpr().format("{}->pointerCpu()", self) + + def vector(self) -> AugExpr: + return AugExpr().format("{}->vector()", self) + + def get_extraction(self) -> IFieldExtraction: + class IdxVectorExtraction(IFieldExtraction): + def ptr(_) -> AugExpr: + return self.pointerCpu() + + def size(_, coordinate: int) -> AugExpr | None: + if coordinate == 0: + return AugExpr.format("{}.size()", self.vector()) + elif coordinate == 1: + return AugExpr.format("1") + else: + raise ValueError() + + def stride(_, coordinate: int) -> AugExpr | None: + if coordinate <= 1: + return AugExpr.format("1") + else: + raise ValueError() + + return IdxVectorExtraction() + + +CellIdx = PsStructType( + ( + ("x", create_type("int64_t")), + ("y", create_type("int64_t")), + ("z", create_type("int64_t")) + ), + "walberla::sfg::CellIdx" +) diff --git a/src/sfg_walberla/boundaries/hbb.py b/src/sfg_walberla/boundaries/hbb.py index 2da0f1765def3493ae0c806c5a65d47f2c6f5ec2..e93ac79c3244e8683eda324596114a6d5ff2bd5d 100644 --- a/src/sfg_walberla/boundaries/hbb.py +++ b/src/sfg_walberla/boundaries/hbb.py @@ -2,26 +2,24 @@ from pystencils import Field, FieldType, TypedSymbol, Target, AssignmentCollection -from pystencils.types import PsStructType, create_type, PsCustomType, PsPointerType +from pystencils.types import PsStructType, create_type from pystencilssfg import SfgComposer from pystencilssfg.composer.class_composer import SfgClassComposer from pystencilssfg.composer.custom import CustomGenerator -from pystencilssfg.lang import SrcField, AugExpr +from pystencilssfg.lang import AugExpr from lbmpy.methods import AbstractLbMethod from lbmpy.boundaries.boundaryconditions import LbBoundary from lbmpy.advanced_streaming.indexing import Timestep -from pystencilssfg.lang.expressions import IFieldExtraction - from ..sweep import ( FieldInfo, SweepClassProperties, BlockforestParameters, combine_vectors, ) -from ..api import GhostLayerFieldPtr, BlockDataID, IBlockPtr, StructuredBlockForest +from ..api import GhostLayerFieldPtr, BlockDataID, IBlockPtr, StructuredBlockForest, IndexListBufferPtr BoundaryIndexType = create_type("int32") @@ -36,41 +34,6 @@ HbbLinkType = PsStructType( ) -class BoundaryIndexVectorsPtr(SrcField): - def __init__(self, link_struct: PsStructType): - self._link_struct = link_struct - typename = f"walberla::sfg::BoundaryIndexVectors< {link_struct.c_string()} >" - self.vector_type = PsCustomType(typename) - super().__init__(PsPointerType(self.vector_type)) - - def pointerCpu(self) -> AugExpr: - return AugExpr().format("{}->pointerCpu()", self) - - def vector(self) -> AugExpr: - return AugExpr().format("{}->vector()", self) - - def get_extraction(self) -> IFieldExtraction: - class IdxVectorExtraction(IFieldExtraction): - def ptr(_) -> AugExpr: - return self.pointerCpu() - - def size(_, coordinate: int) -> AugExpr | None: - if coordinate == 0: - return AugExpr.format("{}.size()", self.vector()) - elif coordinate == 1: - return AugExpr.format("1") - else: - raise ValueError() - - def stride(_, coordinate: int) -> AugExpr | None: - if coordinate <= 1: - return AugExpr.format("1") - else: - raise ValueError() - - return IdxVectorExtraction() - - class HbbBoundaryProperties(SweepClassProperties): def _constructor(self, sfg: SfgComposer) -> SfgClassComposer.ConstructorBuilder: blocks = StructuredBlockForest.shared_ptr_ref() @@ -132,7 +95,7 @@ class SimpleHbbBoundary(CustomGenerator): for s in sorted(parameters, key=lambda p: p.name): props.add_property(s, setter=True, getter=True) - idx_vector = BoundaryIndexVectorsPtr(HbbLinkType).var("indexVector") + idx_vector = IndexListBufferPtr(HbbLinkType).var("indexVector") idx_vector_id = BlockDataID().bind("this->indexVectorsId_") base_class = f"walberla::sfg::GenericHbbBoundary< walberla::stencil::{self._stencil.name} >" @@ -148,16 +111,16 @@ class SimpleHbbBoundary(CustomGenerator): ), # Get fields from block *( - sfg.init(fi.glfield_ptr)( - block.getData(fi.glfield_ptr.field_type, fi.data_id) + sfg.init(fi.wlb_field_ptr)( + block.getData(fi.wlb_field_ptr.field_type, fi.data_id) ) for fi in domain_fields ), # Map GhostLayerFields to pystencils fields - *(sfg.map_field(fi.field, fi.glfield_ptr) for fi in domain_fields), + *(sfg.map_field(fi.field, fi.wlb_field_ptr) for fi in domain_fields), # Get index vector sfg.init(idx_vector)( - block.getData(idx_vector.vector_type, idx_vector_id) + block.getData(idx_vector.field_type, idx_vector_id) ), # Map index vector to pystencils field sfg.map_field(self._index_field, idx_vector), diff --git a/src/sfg_walberla/sweep.py b/src/sfg_walberla/sweep.py index f5960b8cbf9c7da58bc83728ba67c5fb2724b091..19fcc8d6b5f6f0374963b2c0d83d3b06276ed405 100644 --- a/src/sfg_walberla/sweep.py +++ b/src/sfg_walberla/sweep.py @@ -15,9 +15,11 @@ from pystencils import ( AssignmentCollection, CreateKernelConfig, Field, + FieldType, Target, + create_type, ) -from pystencils.types import PsType, constify, deconstify, PsCustomType +from pystencils.types import PsType, constify, deconstify, PsCustomType, PsStructType from pystencilssfg import SfgComposer from pystencilssfg.lang import ( @@ -34,11 +36,13 @@ from .api import ( GenericWalberlaField, GhostLayerFieldPtr, GpuFieldPtr, + IndexListBufferPtr, IBlockPtr, BlockDataID, Vector2, Vector3, CellInterval, + CellIdx, ) @@ -134,7 +138,7 @@ class SweepClassProperties: if var not in ctor.parameters: if ( deconstify(strip_ptr_ref(var.dtype)) - == StructuredBlockForest.cpp_type() + == StructuredBlockForest.typename() ): ctor.add_param(var, 0) else: @@ -257,9 +261,27 @@ class BlockforestParameters: @dataclass class FieldInfo: field: Field - glfield_ptr: GenericWalberlaField + wlb_field_ptr: GenericWalberlaField | IndexListBufferPtr data_id: BlockDataID + def as_glfield(self) -> GenericWalberlaField: + if not isinstance(self.wlb_field_ptr, GenericWalberlaField): + raise AttributeError( + "This FieldInfo does not encapsulate a ghost layer field" + ) + return self.wlb_field_ptr + + def is_glfield(self) -> bool: + return isinstance(self.wlb_field_ptr, GenericWalberlaField) + + def as_index_list(self) -> IndexListBufferPtr: + if not isinstance(self.wlb_field_ptr, IndexListBufferPtr): + raise AttributeError("This FieldInfo does not encapsulate an index list") + return self.wlb_field_ptr + + def is_index_list(self) -> bool: + return isinstance(self.wlb_field_ptr, IndexListBufferPtr) + class ShadowFieldCache: """ @@ -277,12 +299,12 @@ class ShadowFieldCache: def get_shadow(self, original_name: str) -> AugExpr: original = self._originals[original_name] return AugExpr.format( - "this->{}({})", self._getter(original.field.name), original.glfield_ptr + "this->{}({})", self._getter(original.field.name), original.wlb_field_ptr ) def perform_swap(self, original_name: str, shadow_field: FieldInfo) -> AugExpr: original = self._originals[original_name] - return original.glfield_ptr.swapDataPointers(shadow_field.glfield_ptr) + return original.as_glfield().swapDataPointers(shadow_field.wlb_field_ptr) def render(self, sfg: SfgComposer): if not self._originals: @@ -295,22 +317,22 @@ class ShadowFieldCache: for orig_name, orig in self._originals.items(): unique_ptr_type = PsCustomType( - f"std::unique_ptr< {orig.glfield_ptr.field_type} >" + f"std::unique_ptr< {orig.as_glfield().field_type} >" ) - cache_ptr_name = self._cache_ptr(orig.glfield_ptr) + cache_ptr_name = self._cache_ptr(orig.as_glfield()) cache_ptrs.append(sfg.var(cache_ptr_name, unique_ptr_type)) getters.append( sfg.method( self._getter(orig_name), - returns=orig.glfield_ptr.get_dtype(), + returns=orig.wlb_field_ptr.get_dtype(), inline=True, )( sfg.branch(f"{cache_ptr_name} == nullptr")( AugExpr.format( "{}.reset({});", cache_ptr_name, - orig.glfield_ptr.cloneUninitialized(), + orig.as_glfield().cloneUninitialized(), ) ), f"return {cache_ptr_name}.get();", @@ -410,15 +432,16 @@ class Sweep(CustomGenerator): config = CreateKernelConfig(ghost_layers=0) self._name = name + if isinstance(assignments, AssignmentCollection): self._assignments = assignments else: self._assignments = AssignmentCollection(assignments) self._gen_config = config - if self._gen_config.target == Target.CUDA: + if self._gen_config.get_target() == Target.CUDA: self._glfield_type = GpuFieldPtr - elif self._gen_config.target.is_cpu(): + elif self._gen_config.get_target().is_cpu(): self._glfield_type = GhostLayerFieldPtr else: raise ValueError( @@ -428,6 +451,28 @@ class Sweep(CustomGenerator): # Map from shadow field to shadowed field self._shadow_fields: dict[Field, Field] = dict() + @property + def sparse(self) -> bool: + return self._gen_config.index_field is not None + + @sparse.setter + def sparse(self, sparse_iteration: bool): + if sparse_iteration: + if self._gen_config.index_dtype != create_type("int64"): + raise ValueError( + "Sparse sweeps require `int64_t` as their index data type. Check your code generator config." + ) + + self._gen_config.index_field = Field.create_generic( + "indexList", 1, CellIdx, field_type=FieldType.INDEXED + ) + self._gen_config.ghost_layers = None + else: + 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.") @@ -440,6 +485,20 @@ class Sweep(CustomGenerator): self._shadow_fields[shadow_field] = field + # CODE GENERATION + + def _walberla_field(self, f: Field) -> GenericWalberlaField | IndexListBufferPtr: + match f.field_type: + case FieldType.GENERIC | FieldType.CUSTOM: + return self._glfield_type.create(f) + case FieldType.INDEXED: + assert isinstance(f.dtype, PsStructType) + return IndexListBufferPtr(f.dtype).var(f.name) + case _: + raise ValueError( + f"Unable to map field {f} of type {f.field_type} to a waLBerla field." + ) + def generate(self, sfg: SfgComposer) -> None: knamespace = sfg.kernel_namespace(f"{self._name}_kernels") @@ -448,7 +507,7 @@ class Sweep(CustomGenerator): all_fields: dict[str, FieldInfo] = { f.name: FieldInfo( - f, self._glfield_type.create(f), BlockDataID().var(f"{f.name}Id") + f, self._walberla_field(f), BlockDataID().var(f"{f.name}Id") ) for f in khandle.fields } @@ -487,22 +546,29 @@ class Sweep(CustomGenerator): *(sfg.init(fi.data_id)(props.get(fi.data_id)) for fi in block_fields), # Extract regular fields from block *( - sfg.init(fi.glfield_ptr)( - block.getData(fi.glfield_ptr.field_type, fi.data_id) + sfg.init(fi.wlb_field_ptr)( + block.getData(fi.wlb_field_ptr.field_type, fi.data_id) ) for fi in block_fields ), # Get shadow fields from cache *( - sfg.init(shadow_info.glfield_ptr)( + sfg.init(shadow_info.wlb_field_ptr)( shadows_cache.get_shadow(orig_name) ) for orig_name, shadow_info in swaps.items() ), # Map GhostLayerFields to pystencils fields *( - sfg.map_field(fi.field, fi.glfield_ptr.with_cell_interval(ci)) + sfg.map_field(fi.field, fi.as_glfield().with_cell_interval(ci)) + for fi in all_fields.values() + if fi.is_glfield() + ), + # Map index lists to pystencils fields + *( + sfg.map_field(fi.field, fi.as_index_list()) for fi in all_fields.values() + if fi.is_index_list() ), # Get parameters from class *(sfg.init(param)(props.get(param)) for param in parameters), @@ -521,13 +587,17 @@ class Sweep(CustomGenerator): ), ] - sfg.klass(self._name)( - *props.render(sfg), - sfg.public( - sfg.method("operator()")(*render_runmethod()), + methods = [sfg.method("operator()")(*render_runmethod())] + + if not self.sparse: + methods.append( sfg.method("runOnCellInterval")( *render_runmethod(CellInterval(const=True, ref=True).var("ci")) - ), - ), + ) + ) + + sfg.klass(self._name)( + *props.render(sfg), + sfg.public(*methods), *shadows_cache.render(sfg), ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..b75a729e6afd2fc0c610b87bdc10d746850297df --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required( VERSION 3.24 ) +project( sfg-walberla-testsuite ) + +include(FetchContent) + +FetchContent_Declare( + walberla + GIT_REPOSITORY https://i10git.cs.fau.de/walberla/walberla.git +) + +message( STATUS "Fetching waLBerla sources (this might take a while)..." ) +FetchContent_MakeAvailable(walberla) + +add_subdirectory(${CMAKE_SOURCE_DIR}/.. ${CMAKE_BINARY_DIR}/sfg-walberla) + +# Test Directories +include(CTest) + +add_subdirectory( SparseIteration ) diff --git a/tests/CMakePresets.json b/tests/CMakePresets.json new file mode 100644 index 0000000000000000000000000000000000000000..11b6c5797a4a848db2ef82c3e601e403848fb15a --- /dev/null +++ b/tests/CMakePresets.json @@ -0,0 +1,29 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 23, + "patch": 0 + }, + "configurePresets": [ + { + "name": "testsuite-dbg", + "binaryDir": "${sourceDir}/build/${presetName}", + "generator": "Ninja", + "cacheVariables": { + "WALBERLA_BUILD_TESTS": false + } + } + ], + "testPresets": [ + { + "name": "SFG Testsuite", + "configurePreset": "testsuite-dbg", + "filter": { + "include": { + "name": "TestSparseSweeps" + } + } + } + ] +} diff --git a/tests/SparseIteration/CMakeLists.txt b/tests/SparseIteration/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..23666fabf4d41684ad3489000ac15993ab841fac --- /dev/null +++ b/tests/SparseIteration/CMakeLists.txt @@ -0,0 +1,6 @@ + +add_executable( TestSparseSweeps TestSparseSweeps.cpp ) +walberla_generate_sources( TestSparseSweeps SCRIPTS SparseSweep.py ) +target_link_libraries( TestSparseSweeps PRIVATE core blockforest field sfg_walberla ) + +add_test( NAME TestSparseSweeps COMMAND TestSparseSweeps ) diff --git a/tests/SparseIteration/SparseSweep.py b/tests/SparseIteration/SparseSweep.py new file mode 100644 index 0000000000000000000000000000000000000000..4489d3beee63edae16f324fb82c50a568134b22b --- /dev/null +++ b/tests/SparseIteration/SparseSweep.py @@ -0,0 +1,20 @@ +from pystencilssfg import SourceFileGenerator +from sfg_walberla import Sweep +from sfg_walberla.symbolic import cell +from pystencils import fields, Assignment + +with SourceFileGenerator() as sfg: + sfg.namespace("TestSparseSweeps::gen") + f = fields("f(3): [3D]", layout="fzyx") + + asms = [ + Assignment(f.center(0), cell.x()), + Assignment(f.center(1), cell.y()), + Assignment(f.center(2), cell.z()), + ] + + sweep = Sweep("SparseSetCoordinates", asms) + sweep.sparse = True + + sfg.generate(sweep) + diff --git a/tests/SparseIteration/TestSparseSweeps.cpp b/tests/SparseIteration/TestSparseSweeps.cpp new file mode 100644 index 0000000000000000000000000000000000000000..272930b78f835fe387a86806662b1275e706ac15 --- /dev/null +++ b/tests/SparseIteration/TestSparseSweeps.cpp @@ -0,0 +1,94 @@ +#include "core/all.h" +#include "blockforest/all.h" +#include "field/GhostLayerField.h" +#include "field/AddToStorage.h" +#include "sfg/IndexVectors.hpp" + +#include <array> + +#include "gen/TestSparseSweeps/SparseSweep.hpp" + +namespace TestSparseSweeps +{ + using namespace walberla; + + using std::array; + using std::vector; + + using PointField = field::GhostLayerField<real_t, 3>; + + Vector3<real_t> pointOnSpiral(real_t z) + { + real_t two_pi_z{2.0 * math::pi * z}; + return Vector3<real_t>{cos(two_pi_z), sin(two_pi_z), z}; + } + + void main(int argc, char **argv) + { + + AABB domainAabb{0., 0., 0., 40., 40., 120.}; + array<uint_t, 3> gridSize{4, 4, 12}; + array<uint_t, 3> cpb{10, 10, 10}; + + auto blocks = blockforest::createUniformBlockGrid( + domainAabb, + gridSize[0], gridSize[1], gridSize[2], + cpb[0], cpb[1], cpb[2]); + + BlockDataID fID = field::addToStorage<PointField>(blocks, "f", real_c(0.0), field::fzyx); + sfg::SparseIndexList indexList{*blocks}; + + auto isOnSpiral = [&](Cell globalCell) -> bool + { + Vector3<real_t> globalPoint{blocks->getCellCenter(globalCell)}; + Vector3<real_t> spiralPoint{pointOnSpiral(globalPoint[2])}; + return (globalPoint - spiralPoint).sqrLength() < 2.0; + }; + + CellInterval allCells{{0, 0, 0}, {cell_idx_c(cpb[0]), cell_idx_c(cpb[1]), cell_idx_c(cpb[2])}}; + + for (auto &b : *blocks) + { + vector<sfg::CellIdx> idxVec{indexList.get(b)}; + for (Cell c : allCells) + { + Cell globalCell{c}; + blocks->transformBlockLocalToGlobalCell(c, b); + if (isOnSpiral(globalCell)) + { + idxVec.emplace_back(c); + } + } + } + + gen::SparseSetCoordinates sweep{blocks, fID, indexList.bufferId()}; + + for (auto &b : *blocks) + { + sweep(&b); + } + + for (auto &b : *blocks) + { + PointField & f = *b.getData<PointField>(fID); + for (Cell c : allCells) + { + Cell globalCell{c}; + blocks->transformBlockLocalToGlobalCell(c, b); + if (isOnSpiral(c)) + { + Vector3< real_t > cellCenter { blocks->getCellCenter(globalCell) }; + WALBERLA_CHECK_FLOAT_EQUAL(f.get(c, 0), cellCenter[0]); + WALBERLA_CHECK_FLOAT_EQUAL(f.get(c, 1), cellCenter[1]); + WALBERLA_CHECK_FLOAT_EQUAL(f.get(c, 2), cellCenter[2]); + } + } + } + } +} + +int main(int argc, char **argv) +{ + TestSparseSweeps::main(argc, argv); + return 0; +} \ No newline at end of file