Skip to content
Snippets Groups Projects
Commit 9dd3ef09 authored by Martin Bauer's avatar Martin Bauer
Browse files

Improved lbmpy boundary setup

- easy and efficient access to fluid, boundary and link positions
  through BoundaryDataSetter object
- more code reuse for serial and parallel setups
parent f4294eca
No related branches found
No related tags found
No related merge requests found
...@@ -9,20 +9,11 @@ class BoundaryHandling(PeriodicityHandling, GenericBoundaryHandling): # importa ...@@ -9,20 +9,11 @@ class BoundaryHandling(PeriodicityHandling, GenericBoundaryHandling): # importa
shapeWithGl = [a + 2 * ghostLayers for a in domainShape] shapeWithGl = [a + 2 * ghostLayers for a in domainShape]
self.domainShape = domainShape self.domainShape = domainShape
self.domainShapeWithGhostLayers = shapeWithGl self.domainShapeWithGhostLayers = shapeWithGl
flagFieldInterface = NumpyFlagFieldInterface(shapeWithGl, flagDtype) flagInterface = NumpyFlagFieldInterface(shapeWithGl, flagDtype)
GenericBoundaryHandling.__init__(self, flagFieldInterface, pdfField, lbMethod, ghostLayers, target, openMP) GenericBoundaryHandling.__init__(self, flagInterface, pdfField, lbMethod, None, ghostLayers, target, openMP)
PeriodicityHandling.__init__(self, list(domainShape) + [len(lbMethod.stencil)]) PeriodicityHandling.__init__(self, list(domainShape) + [len(lbMethod.stencil)])
def setBoundary(self, boundaryObject, indexExpr=None, maskCallback=None, sliceNormalizationGhostLayers=1):
mask = None
if maskCallback is not None:
gridParams = [np.arange(start=-sliceNormalizationGhostLayers, stop=s - sliceNormalizationGhostLayers) + 0.5
for s in self.flagField.shape]
indices = np.meshgrid(*gridParams, indexing='ij')
mask = maskCallback(*indices)
return GenericBoundaryHandling.setBoundary(self, boundaryObject, indexExpr, mask)
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
for cls in BoundaryHandling.__bases__: for cls in BoundaryHandling.__bases__:
cls.__call__(self, *args, **kwargs) cls.__call__(self, *args, **kwargs)
......
...@@ -4,6 +4,7 @@ from pystencils.field import Field ...@@ -4,6 +4,7 @@ from pystencils.field import Field
from pystencils.slicing import normalizeSlice from pystencils.slicing import normalizeSlice
from lbmpy.boundaries.boundary_kernel import generateIndexBoundaryKernel from lbmpy.boundaries.boundary_kernel import generateIndexBoundaryKernel
from lbmpy.boundaries.createindexlist import createBoundaryIndexList from lbmpy.boundaries.createindexlist import createBoundaryIndexList
from pystencils.cache import memorycache
class FlagFieldInterface(object): class FlagFieldInterface(object):
...@@ -37,20 +38,63 @@ class FlagFieldInterface(object): ...@@ -37,20 +38,63 @@ class FlagFieldInterface(object):
return name return name
class BoundaryDataSetter:
def __init__(self, indexArray, offset, stencil, ghostLayers):
self.indexArray = indexArray
self.offset = offset
self.stencil = np.array(stencil)
arrFieldNames = indexArray.dtype.names
self.dim = 3 if 'z' in arrFieldNames else 2
assert 'x' in arrFieldNames and 'y' in arrFieldNames and 'dir' in arrFieldNames, str(arrFieldNames)
self.boundaryDataNames = set(self.indexArray.dtype.names) - set(['x', 'y', 'z', 'dir'])
self.coordMap = {0: 'x', 1: 'y', 2: 'z'}
self.ghostLayers = ghostLayers
def fluidCellPositions(self, coord):
assert coord < self.dim
return self.indexArray[self.coordMap[coord]] + self.offset[coord] - self.ghostLayers
@memorycache()
def linkOffsets(self):
return self.stencil[self.indexArray['dir']]
@memorycache()
def linkPositions(self, coord):
return self.fluidCellPositions(coord) + 0.5 * self.linkOffsets()[:, coord]
@memorycache()
def boundaryCellPositions(self, coord):
return self.fluidCellPositions(coord) + self.linkOffsets()[:, coord]
def __setitem__(self, key, value):
if key not in self.boundaryDataNames:
raise KeyError("Invalid boundary data name %s. Allowed are %s" % (key, self.boundaryDataNames))
self.indexArray[key] = value
def __getitem__(self, item):
if item not in self.boundaryDataNames:
raise KeyError("Invalid boundary data name %s. Allowed are %s" % (item, self.boundaryDataNames))
return self.indexArray[item]
class GenericBoundaryHandling(object): class GenericBoundaryHandling(object):
class BoundaryInfo(object): class BoundaryInfo(object):
def __init__(self, kernel, ast, indexArray, idxArrayForExecution): def __init__(self, kernel, ast, indexArray, idxArrayForExecution, boundaryDataSetter):
self.kernel = kernel self.kernel = kernel
self.ast = ast self.ast = ast
self.indexArray = indexArray self.indexArray = indexArray
self.idxArrayForExecution = idxArrayForExecution # is different for GPU kernels self.idxArrayForExecution = idxArrayForExecution # is different for GPU kernels
self.boundaryDataSetter = boundaryDataSetter
def __init__(self, flagFieldInterface, pdfField, lbMethod, ghostLayers=1, target='cpu', openMP=True): def __init__(self, flagFieldInterface, pdfField, lbMethod, offset=None, ghostLayers=1, target='cpu', openMP=True):
""" """
:param flagFieldInterface: implementation of FlagFieldInterface :param flagFieldInterface: implementation of FlagFieldInterface
:param pdfField: numpy array :param pdfField: numpy array
:param lbMethod: :param lbMethod:
:param offset: offset that is added to all coordinates when calling callback functions to set up geometry
or boundary data. This is used for waLBerla simulations to pass the block offset
:param target: 'cpu' or 'gpu' :param target: 'cpu' or 'gpu'
:param openMP: :param openMP:
""" """
...@@ -61,7 +105,7 @@ class GenericBoundaryHandling(object): ...@@ -61,7 +105,7 @@ class GenericBoundaryHandling(object):
self._openMP = openMP self._openMP = openMP
self.ghostLayers = ghostLayers self.ghostLayers = ghostLayers
self._dirty = False self._dirty = False
self.offset = offset if offset else (0,) * lbMethod.dim
self._boundaryInfos = {} # mapping of boundary object to boundary info self._boundaryInfos = {} # mapping of boundary object to boundary info
self._symbolicPdfField = Field.createFromNumpyArray('pdfs', pdfField, indexDimensions=1) self._symbolicPdfField = Field.createFromNumpyArray('pdfs', pdfField, indexDimensions=1)
...@@ -98,33 +142,17 @@ class GenericBoundaryHandling(object): ...@@ -98,33 +142,17 @@ class GenericBoundaryHandling(object):
self._dirty = False self._dirty = False
self._boundaryInfos = {} self._boundaryInfos = {}
def _invalidateCache(self):
self._dirty = True
def triggerReinitializationOfBoundaryData(self, **kwargs): def triggerReinitializationOfBoundaryData(self, **kwargs):
if self._dirty: if self._dirty:
self.prepare() self.prepare()
return return
else: else:
for boundaryObject, boundaryInfo in self._boundaryInfos.items(): for boundaryObject, boundaryInfo in self._boundaryInfos.items():
self.__boundaryDataInitialization(boundaryInfo.indexArray, boundaryObject, **kwargs) self.__boundaryDataInitialization(boundaryInfo, boundaryObject, **kwargs)
if self._target == 'gpu': if self._target == 'gpu':
import pycuda.gpuarray as gpuarray import pycuda.gpuarray as gpuarray
boundaryInfo.idxArrayForExecution = gpuarray.to_gpu(boundaryInfo.indexArray) boundaryInfo.idxArrayForExecution = gpuarray.to_gpu(boundaryInfo.indexArray)
def __boundaryDataInitialization(self, idxArray, boundaryObject, **kwargs):
if boundaryObject.additionalDataInitCallback:
#TODO x,y,z coordinates should be transformed here
boundaryObject.additionalDataInitCallback(idxArray, **kwargs)
if boundaryObject.additionalDataInitKernelEquations:
initKernelAst = generateIndexBoundaryKernel(self._symbolicPdfField, idxArray, self._lbMethod,
boundaryObject, target='cpu',
createInitializationKernel=True)
from pystencils.cpu import makePythonFunction as makePythonCpuFunction
initKernel = makePythonCpuFunction(initKernelAst, {'indexField': idxArray, 'pdfs': self._pdfField})
initKernel()
def prepare(self): def prepare(self):
"""Compiles boundary kernels according to flag field. When setting boundaries the cache is automatically """Compiles boundary kernels according to flag field. When setting boundaries the cache is automatically
invalidated, so there is no need to call this function manually, as long as the flag field is not manually invalidated, so there is no need to call this function manually, as long as the flag field is not manually
...@@ -152,7 +180,6 @@ class GenericBoundaryHandling(object): ...@@ -152,7 +180,6 @@ class GenericBoundaryHandling(object):
extendedIdxField[prop] = idxArray[prop] extendedIdxField[prop] = idxArray[prop]
idxArray = extendedIdxField idxArray = extendedIdxField
self.__boundaryDataInitialization(idxArray, boundaryObject)
ast = generateIndexBoundaryKernel(self._symbolicPdfField, idxArray, self._lbMethod, boundaryObject, ast = generateIndexBoundaryKernel(self._symbolicPdfField, idxArray, self._lbMethod, boundaryObject,
target=self._target) target=self._target)
...@@ -170,15 +197,30 @@ class GenericBoundaryHandling(object): ...@@ -170,15 +197,30 @@ class GenericBoundaryHandling(object):
else: else:
assert False assert False
boundaryInfo = GenericBoundaryHandling.BoundaryInfo(kernel, ast, idxArray, idxArrayForExecution) boundaryDataSetter = BoundaryDataSetter(idxArray, self.offset, self._lbMethod.stencil, self.ghostLayers)
boundaryInfo = GenericBoundaryHandling.BoundaryInfo(kernel, ast, idxArray,
idxArrayForExecution, boundaryDataSetter)
self._boundaryInfos[boundaryObject] = boundaryInfo self._boundaryInfos[boundaryObject] = boundaryInfo
if boundaryObject.additionalData:
self.__boundaryDataInitialization(boundaryInfo, boundaryObject)
self._dirty = False self._dirty = False
def reserveFlag(self, boundaryObject): def reserveFlag(self, boundaryObject):
self._flagFieldInterface.getFlag(boundaryObject) self._flagFieldInterface.getFlag(boundaryObject)
def setBoundary(self, boundaryObject, indexExpr=None, maskArr=None): def setBoundary(self, boundaryObject, indexExpr=None, maskCallback=None):
mask = None
flagField = self._flagFieldInterface.array
if maskCallback is not None:
gridParams = [offset + np.arange(-self.ghostLayers, s - self.ghostLayers) + 0.5
for s, offset in zip(flagField.shape, self.offset)]
indices = np.meshgrid(*gridParams, indexing='ij')
mask = maskCallback(*indices)
return self.setBoundaryWithMaskArray(boundaryObject, indexExpr, mask)
def setBoundaryWithMaskArray(self, boundaryObject, indexExpr=None, maskArr=None):
""" """
Sets boundary in a rectangular region (slice) Sets boundary in a rectangular region (slice)
...@@ -207,3 +249,19 @@ class GenericBoundaryHandling(object): ...@@ -207,3 +249,19 @@ class GenericBoundaryHandling(object):
@property @property
def flagField(self): def flagField(self):
return self._flagFieldInterface.array return self._flagFieldInterface.array
def _invalidateCache(self):
self._dirty = True
def __boundaryDataInitialization(self, boundaryInfo, boundaryObject, **kwargs):
if boundaryObject.additionalDataInitCallback:
boundaryObject.additionalDataInitCallback(boundaryInfo.boundaryDataSetter, **kwargs)
if boundaryObject.additionalDataInitKernelEquations:
initKernelAst = generateIndexBoundaryKernel(self._symbolicPdfField, boundaryInfo.indexArray, self._lbMethod,
boundaryObject, target='cpu',
createInitializationKernel=True)
from pystencils.cpu import makePythonFunction as makePythonCpuFunction
initKernel = makePythonCpuFunction(initKernelAst, {'indexField': boundaryInfo.indexArray,
'pdfs': self._pdfField})
initKernel()
\ No newline at end of file
...@@ -64,7 +64,7 @@ class SteadyStateChapmanEnskogAnalysis(object): ...@@ -64,7 +64,7 @@ class SteadyStateChapmanEnskogAnalysis(object):
chapmanEnskogHierarchy = [chapmanEnskogHierarchy[i] for i in range(self.order + 1)] chapmanEnskogHierarchy = [chapmanEnskogHierarchy[i] for i in range(self.order + 1)]
insertedHierarchy = [] insertedHierarchy = []
rawHierarchy = []j rawHierarchy = []
substitutionDict = {} substitutionDict = {}
for ceEq, f_i in zip(chapmanEnskogHierarchy, self.fSyms): for ceEq, f_i in zip(chapmanEnskogHierarchy, self.fSyms):
newEq = -1 / self.collisionOpSym * (ceEq - self.collisionOpSym * f_i) newEq = -1 / self.collisionOpSym * (ceEq - self.collisionOpSym * f_i)
......
...@@ -3,14 +3,28 @@ import waLBerla as wlb ...@@ -3,14 +3,28 @@ import waLBerla as wlb
from pystencils.slicing import normalizeSlice from pystencils.slicing import normalizeSlice
def slicedBlockIteration(blocks, indexExpr=None, ghostLayers=1, sliceNormalizationGhostLayers=1): def slicedBlockIteration(blocks, sliceObj=None, ghostLayers=1, sliceNormalizationGhostLayers=1,
if indexExpr is None: withIndexArrays=False):
indexExpr = [slice(None, None, None)] * 3 """
Iterates of all blocks that have an intersection with the given slice object.
For these blocks a slice object in local block coordinates is given
:param blocks: waLBerla block data structure
:param sliceObj: a slice (i.e. rectangular subregion), can be created with makeSlice[]
:param ghostLayers: how many ghost layers are included in the local slice and the optional index arrays
:param sliceNormalizationGhostLayers: slices can have relative coordinates e.g. makeSlice[0.2, :, :]
when computing absolute values, the domain size is needed. This parameter
specifies how many ghost layers are taken into account for this operation.
:param withIndexArrays: if true index arrays [x,y,z] are yielded as last parameters. These arrays contain the
cell midpoints in global coordinates
"""
if sliceObj is None:
sliceObj = [sliceObj(None, None, None)] * 3
domainCellBB = blocks.getDomainCellBB() domainCellBB = blocks.getDomainCellBB()
domainExtent = [s + 2 * sliceNormalizationGhostLayers for s in domainCellBB.size] domainExtent = [s + 2 * sliceNormalizationGhostLayers for s in domainCellBB.size]
indexExpr = normalizeSlice(indexExpr, domainExtent) sliceObj = normalizeSlice(sliceObj, domainExtent)
targetCellBB = wlb.CellInterval.fromSlice(indexExpr) targetCellBB = wlb.CellInterval.fromSlice(sliceObj)
targetCellBB.shift(*[a - sliceNormalizationGhostLayers for a in domainCellBB.min]) targetCellBB.shift(*[a - sliceNormalizationGhostLayers for a in domainCellBB.min])
for block in blocks: for block in blocks:
...@@ -19,12 +33,15 @@ def slicedBlockIteration(blocks, indexExpr=None, ghostLayers=1, sliceNormalizati ...@@ -19,12 +33,15 @@ def slicedBlockIteration(blocks, indexExpr=None, ghostLayers=1, sliceNormalizati
if intersection.empty(): if intersection.empty():
continue continue
meshGridParams = [offset + 0.5 + np.arange(width)
for offset, width in zip(intersection.min, intersection.size)]
indexArrays = np.meshgrid(*meshGridParams, indexing='ij')
localTargetBB = blocks.transformGlobalToLocal(block, intersection) localTargetBB = blocks.transformGlobalToLocal(block, intersection)
localTargetBB.shift(ghostLayers, ghostLayers, ghostLayers) localTargetBB.shift(ghostLayers, ghostLayers, ghostLayers)
localSlice = localTargetBB.toSlice() localSlice = localTargetBB.toSlice()
yield block, indexArrays, localSlice
if withIndexArrays:
meshGridParams = [offset + 0.5 + np.arange(width)
for offset, width in zip(intersection.min, intersection.size)]
indexArrays = np.meshgrid(*meshGridParams, indexing='ij')
yield block, localSlice, indexArrays
else:
yield block, localSlice
...@@ -10,8 +10,9 @@ class BoundaryHandling(object): ...@@ -10,8 +10,9 @@ class BoundaryHandling(object):
flagFieldInterface = WalberlaFlagFieldInterface(block, flagFieldId) flagFieldInterface = WalberlaFlagFieldInterface(block, flagFieldId)
pdfField = wlb.field.toArray(block[pdfFieldId], withGhostLayers=True) pdfField = wlb.field.toArray(block[pdfFieldId], withGhostLayers=True)
ghostLayers = block[pdfFieldId].nrOfGhostLayers ghostLayers = block[pdfFieldId].nrOfGhostLayers
return GenericBoundaryHandling(flagFieldInterface, pdfField, lbMethod, ghostLayers=ghostLayers, offset = blocks.getBlockCellBB(block).min
target=target, openMP=openMP) return GenericBoundaryHandling(flagFieldInterface, pdfField, lbMethod, offset=offset,
ghostLayers=ghostLayers, target=target, openMP=openMP)
self._boundaryId = boundaryId self._boundaryId = boundaryId
self._pdfFieldId = pdfFieldId self._pdfFieldId = pdfFieldId
...@@ -41,13 +42,11 @@ class BoundaryHandling(object): ...@@ -41,13 +42,11 @@ class BoundaryHandling(object):
return return
gl = self._blocks[0][self._boundaryId].ghostLayers gl = self._blocks[0][self._boundaryId].ghostLayers
ngl = sliceNormalizationGhostLayers
for block in self._blocks: for block in self._blocks:
block[self._boundaryId].reserveFlag(boundaryObject) block[self._boundaryId].reserveFlag(boundaryObject)
for block, indexArrs, localSlice in slicedBlockIteration(self._blocks, indexExpr, gl, ngl): for block, localSlice in slicedBlockIteration(self._blocks, indexExpr, gl, sliceNormalizationGhostLayers):
mask = maskCallback(*indexArrs) if maskCallback else None block[self._boundaryId].setBoundary(boundaryObject, indexExpr=localSlice, maskCallback=maskCallback)
block[self._boundaryId].setBoundary(boundaryObject, indexExpr=localSlice, maskArr=mask)
# ---------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment