diff --git a/boundaries/__init__.py b/boundaries/__init__.py index 6d1d37c07b7eef741c663b488ac61b2b6ea8e799..4315452897b7a53ba34818a409e2f78bb8e70274 100644 --- a/boundaries/__init__.py +++ b/boundaries/__init__.py @@ -1,2 +1,2 @@ from lbmpy.boundaries.boundaryconditions import NoSlip, UBB, FixedDensity, NeumannByCopy -from lbmpy.boundaries.boundary_handling import BoundaryHandling +from lbmpy.boundaries.boundaryhandling import LatticeBoltzmannBoundaryHandling diff --git a/boundaries/boundary_handling.py b/boundaries/boundary_handling.py deleted file mode 100644 index 083f863b842573e0018c83809c0900ae5d9aa8e0..0000000000000000000000000000000000000000 --- a/boundaries/boundary_handling.py +++ /dev/null @@ -1,337 +0,0 @@ -import numpy as np - -from lbmpy.stencils import inverseDirection -from pystencils.cache import memorycache -from pystencils import Field -from lbmpy.boundaries import NoSlip -from lbmpy.boundaries.boundary_kernel import generateIndexBoundaryKernelGeneric -from lbmpy.boundaries.createindexlist import createBoundaryIndexArray, numpyDataTypeForBoundaryObject - - -class BoundaryHandling: - - def __init__(self, lbMethod, dataHandling, pdfFieldName, name="boundaryHandling", target='cpu', openMP=True): - assert dataHandling.hasData(pdfFieldName) - - self._lbMethod = lbMethod - self._dataHandling = dataHandling - self._pdfArrayName = pdfFieldName - self._flagFieldName = name + "Flags" - self._indexArrayName = name + "IndexArrays" - self._target = target - self._openMP = openMP - self._boundaryObjectToBoundaryInfo = {} - self._fluidFlag = 1 << 0 - self._nextFreeFlag = 1 - - self._dirty = True - - # Add flag field to data handling if it does not yet exist - if dataHandling.hasData(self._flagFieldName) or dataHandling.hasData(self._indexArrayName): - raise ValueError("There is already a boundary handling registered at the data handling." - "If you want to add multiple handlings, choose a different name.") - - gpu = self._target == 'gpu' - dataHandling.addArray(self._flagFieldName, dtype=np.uint32, cpu=True, gpu=False) - dataHandling.addCustomClass(self._indexArrayName, self.IndexFieldBlockData, cpu=True, gpu=gpu) - - ffGhostLayers = self._dataHandling.ghostLayersOfField(self._flagFieldName) - for b in self._dataHandling.iterate(ghostLayers=ffGhostLayers): - b[self._flagFieldName].fill(self._fluidFlag) - - @property - def shape(self): - return self._dataHandling.shape - - @property - def dim(self): - return self._lbMethod.dim - - @property - def lbMethod(self): - return self._lbMethod - - @property - def dataHandling(self): - return self._dataHandling - - @property - def boundaryObjects(self): - return tuple(self._boundaryObjectToName.keys()) - - @property - def flagArrayName(self): - return self._flagFieldName - - def getBoundaryNameToFlagDict(self): - result = {bObj.name: bInfo.flag for bObj, bInfo in self._boundaryObjectToBoundaryInfo.items()} - result['fluid'] = self._fluidFlag - return result - - def getMask(self, sliceObj, boundaryObj, inverse=False): - if isinstance(boundaryObj, str) and boundaryObj.lower() == 'fluid': - flag = self._fluidFlag - else: - flag = self._boundaryObjectToBoundaryInfo[boundaryObj].flag - - arr = self.dataHandling.gatherArray(self.flagArrayName, sliceObj) - if arr is None: - return None - else: - result = np.bitwise_and(arr, flag) - if inverse: - result = np.logical_not(result) - return result - - def setBoundary(self, boundaryObject, sliceObj=None, maskCallback=None, ghostLayers=True, innerGhostLayers=True): - """ - Sets boundary using either a rectangular slice, a boolean mask or a combination of both - - :param boundaryObject: instance of a boundary object that should be set - :param sliceObj: a slice object (can be created with makeSlice[]) that selects a part of the domain where - the boundary should be set. If none, the complete domain is selected which makes only sense - if a maskCallback is passed. The slice can have ':' placeholders, which are interpreted - depending on the 'includeGhostLayers' parameter i.e. if it is True, the slice extends - into the ghost layers - :param maskCallback: callback function getting x,y (z) parameters of the cell midpoints and returning a - boolean mask with True entries where boundary cells should be set. - The x, y, z arrays have 2D/3D shape such that they can be used directly - to create the boolean return array. i.e return x < 10 sets boundaries in cells with - midpoint x coordinate smaller than 10. - :param ghostLayers see DataHandling.iterate() - """ - if isinstance(boundaryObject, str) and boundaryObject.lower() == 'fluid': - flag = self._fluidFlag - else: - flag = self._getFlagForBoundary(boundaryObject) - - for b in self._dataHandling.iterate(sliceObj, ghostLayers=ghostLayers, innerGhostLayers=innerGhostLayers): - flagArr = b[self._flagFieldName] - if maskCallback is not None: - mask = maskCallback(*b.midpointArrays) - flagArr[mask] = flag - else: - flagArr.fill(flag) - - self._dirty = True - - def forceOnBoundary(self, boundaryObj): - if isinstance(boundaryObj, NoSlip): - return self._forceOnNoSlip(boundaryObj) - else: - self.__call__() - return self._forceOnBoundary(boundaryObj) - - def prepare(self): - if not self._dirty: - return - self._createIndexFields() - self._dirty = False - - def triggerReinitializationOfBoundaryData(self, **kwargs): - if self._dirty: - self.prepare() - else: - ffGhostLayers = self._dataHandling.ghostLayersOfField(self._flagFieldName) - for b in self._dataHandling.iterate(ghostLayers=ffGhostLayers): - for bObj, setter in b[self._indexArrayName].boundaryObjectToDataSetter.items(): - self._boundaryDataInitialization(bObj, setter, **kwargs) - - def __call__(self, **kwargs): - if self._dirty: - self.prepare() - - for b in self._dataHandling.iterate(gpu=self._target == 'gpu'): - for bObj, idxArr in b[self._indexArrayName].boundaryObjectToIndexList.items(): - kwargs[self._pdfArrayName] = b[self._pdfArrayName] - self._boundaryObjectToBoundaryInfo[bObj].kernel(indexField=idxArr, **kwargs) - - def geometryToVTK(self, fileName='geometry', boundaries='all', ghostLayers=False): - """ - Writes a VTK field where each cell with the given boundary is marked with 1, other cells are 0 - This can be used to display the simulation geometry in Paraview - :param fileName: vtk filename - :param boundaries: boundary object, or special string 'fluid' for fluid cells or special string 'all' for all - boundary conditions. - can also be a sequence, to write multiple boundaries to VTK file - :param ghostLayers: number of ghost layers to write, or True for all, False for none - """ - if boundaries == 'all': - boundaries = list(self._boundaryObjectToBoundaryInfo.keys()) + ['fluid'] - elif not hasattr(boundaries, "__len__"): - boundaries = [boundaries] - - masksToName = {} - for b in boundaries: - if b == 'fluid': - masksToName[self._fluidFlag] = 'fluid' - else: - masksToName[self._boundaryObjectToBoundaryInfo[b].flag] = b.name - - writer = self.dataHandling.vtkWriterFlags(fileName, self._flagFieldName, masksToName, ghostLayers=ghostLayers) - writer(1) - - # ------------------------------ Implementation Details ------------------------------------------------------------ - - def _forceOnNoSlip(self, boundaryObj): - dh = self._dataHandling - ffGhostLayers = dh.ghostLayersOfField(self._flagFieldName) - method = self._lbMethod - stencil = np.array(method.stencil) - - result = np.zeros(self.dim) - - for b in dh.iterate(ghostLayers=ffGhostLayers): - objToIndList = b[self._indexArrayName].boundaryObjectToIndexList - pdfArray = b[self._pdfArrayName] - if boundaryObj in objToIndList: - indArr = objToIndList[boundaryObj] - indices = [indArr[name] for name in ('x', 'y', 'z')[:method.dim]] - indices.append(indArr['dir']) - values = 2 * pdfArray[tuple(indices)] - forces = stencil[indArr['dir']] * values[:, np.newaxis] - result += forces.sum(axis=0) - return dh.reduceFloatSequence(list(result), 'sum') - - def _forceOnBoundary(self, boundaryObj): - dh = self._dataHandling - ffGhostLayers = dh.ghostLayersOfField(self._flagFieldName) - method = self._lbMethod - stencil = np.array(method.stencil) - invDirection = np.array([method.stencil.index(inverseDirection(d)) - for d in method.stencil]) - - result = np.zeros(self.dim) - - for b in dh.iterate(ghostLayers=ffGhostLayers): - objToIndList = b[self._indexArrayName].boundaryObjectToIndexList - pdfArray = b[self._pdfArrayName] - if boundaryObj in objToIndList: - indArr = objToIndList[boundaryObj] - indices = [indArr[name] for name in ('x', 'y', 'z')[:method.dim]] - offsets = stencil[indArr['dir']] - invDir = invDirection[indArr['dir']] - fluidValues = pdfArray[tuple(indices) + (indArr['dir'],)] - boundaryValues = pdfArray[tuple(ind + offsets[:, i] for i, ind in enumerate(indices)) + (invDir,)] - values = fluidValues + boundaryValues - forces = stencil[indArr['dir']] * values[:, np.newaxis] - result += forces.sum(axis=0) - - return dh.reduceFloatSequence(list(result), 'sum') - - def _getFlagForBoundary(self, boundaryObject): - if boundaryObject not in self._boundaryObjectToBoundaryInfo: - symbolicIndexField = Field.createGeneric('indexField', spatialDimensions=1, - dtype=numpyDataTypeForBoundaryObject(boundaryObject, self.dim)) - - spPdfField = self._dataHandling.fields[self._pdfArrayName] - ast = generateIndexBoundaryKernelGeneric(spPdfField, symbolicIndexField, self._lbMethod, - boundaryObject, target=self._target, openMP=self._openMP) - - boundaryInfo = self.BoundaryInfo(boundaryObject, flag=1 << self._nextFreeFlag, kernel=ast.compile()) - - self._nextFreeFlag += 1 - self._boundaryObjectToBoundaryInfo[boundaryObject] = boundaryInfo - return self._boundaryObjectToBoundaryInfo[boundaryObject].flag - - def _createIndexFields(self): - dh = self._dataHandling - ffGhostLayers = dh.ghostLayersOfField(self._flagFieldName) - for b in dh.iterate(ghostLayers=ffGhostLayers): - flagArr = b[self._flagFieldName] - pdfArr = b[self._pdfArrayName] - indexArrayBD = b[self._indexArrayName] - indexArrayBD.clear() - for bInfo in self._boundaryObjectToBoundaryInfo.values(): - idxArr = createBoundaryIndexArray(flagArr, self.lbMethod.stencil, bInfo.flag, self._fluidFlag, - bInfo.boundaryObject, dh.ghostLayersOfField(self._flagFieldName)) - if idxArr.size == 0: - continue - - # TODO test boundary data setter (especially offset) - boundaryDataSetter = BoundaryDataSetter(idxArr, b.offset, self.lbMethod.stencil, ffGhostLayers, pdfArr) - indexArrayBD.boundaryObjectToIndexList[bInfo.boundaryObject] = idxArr - indexArrayBD.boundaryObjectToDataSetter[bInfo.boundaryObject] = boundaryDataSetter - self._boundaryDataInitialization(bInfo.boundaryObject, boundaryDataSetter) - - def _boundaryDataInitialization(self, boundaryObject, boundaryDataSetter, **kwargs): - if boundaryObject.additionalDataInitCallback: - boundaryObject.additionalDataInitCallback(boundaryDataSetter, **kwargs) - if self._target == 'gpu': - self._dataHandling.toGpu(self._indexArrayName) - - class BoundaryInfo(object): - def __init__(self, boundaryObject, flag, kernel): - self.boundaryObject = boundaryObject - self.flag = flag - self.kernel = kernel - - class IndexFieldBlockData: - def __init__(self, *args, **kwargs): - self.boundaryObjectToIndexList = {} - self.boundaryObjectToDataSetter = {} - - def clear(self): - self.boundaryObjectToIndexList.clear() - self.boundaryObjectToDataSetter.clear() - - @staticmethod - def toCpu(gpuVersion, cpuVersion): - gpuVersion = gpuVersion.boundaryObjectToIndexList - cpuVersion = cpuVersion.boundaryObjectToIndexList - for obj, cpuArr in cpuVersion.values(): - gpuVersion[obj].get(cpuArr) - - @staticmethod - def toGpu(gpuVersion, cpuVersion): - from pycuda import gpuarray - gpuVersion = gpuVersion.boundaryObjectToIndexList - cpuVersion = cpuVersion.boundaryObjectToIndexList - for obj, cpuArr in cpuVersion.items(): - if obj not in gpuVersion: - gpuVersion[obj] = gpuarray.to_gpu(cpuArr) - else: - gpuVersion[obj].set(cpuArr) - - -class BoundaryDataSetter: - - def __init__(self, indexArray, offset, stencil, ghostLayers, pdfArray): - self.indexArray = indexArray - self.offset = offset - self.stencil = np.array(stencil) - self.pdfArray = pdfArray.view() - self.pdfArray.flags.writeable = False - - 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 + 0.5 - - @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] \ No newline at end of file diff --git a/boundaries/boundary_kernel.py b/boundaries/boundary_kernel.py deleted file mode 100644 index d9e0f89c623856ef6dc03b73a39c93e2f470949b..0000000000000000000000000000000000000000 --- a/boundaries/boundary_kernel.py +++ /dev/null @@ -1,76 +0,0 @@ -import sympy as sp -import numpy as np -from pystencils.backends.cbackend import CustomCppCode -from pystencils import TypedSymbol, Field -from pystencils.cpu import addOpenMP -from pystencils.data_types import castFunc, createType - -INV_DIR_SYMBOL = TypedSymbol("invDir", "int") -WEIGHTS_SYMBOL = TypedSymbol("weights", "double") - - -# -------------------------------------- Helper Functions -------------------------------------------------------------- - - -def offsetSymbols(dim): - return [TypedSymbol("c_%d" % (d,), createType(np.int64)) for d in range(dim)] - - -def offsetFromDir(dirIdx, dim): - return tuple([sp.IndexedBase(symbol, shape=(1,))[dirIdx] for symbol in offsetSymbols(dim)]) - - -def invDir(dirIdx): - return sp.IndexedBase(INV_DIR_SYMBOL, shape=(1,))[dirIdx] - - -def weightOfDirection(dirIdx): - return sp.IndexedBase(WEIGHTS_SYMBOL, shape=(1,))[dirIdx] - - -# ------------------------------------- Kernel Generation -------------------------------------------------------------- - -class LbmMethodInfo(CustomCppCode): - def __init__(self, lbMethod): - stencil = lbMethod.stencil - symbolsDefined = set(offsetSymbols(lbMethod.dim) + [INV_DIR_SYMBOL, WEIGHTS_SYMBOL]) - - offsetSym = offsetSymbols(lbMethod.dim) - code = "\n" - for i in range(lbMethod.dim): - offsetStr = ", ".join([str(d[i]) for d in stencil]) - code += "const int64_t %s [] = { %s };\n" % (offsetSym[i].name, offsetStr) - - invDirs = [] - for direction in stencil: - inverseDir = tuple([-i for i in direction]) - invDirs.append(str(stencil.index(inverseDir))) - - code += "const int %s [] = { %s };\n" % (INV_DIR_SYMBOL.name, ", ".join(invDirs)) - weights = [str(w.evalf()) for w in lbMethod.weights] - code += "const double %s [] = { %s };\n" % (WEIGHTS_SYMBOL.name, ",".join(weights)) - super(LbmMethodInfo, self).__init__(code, symbolsRead=set(), symbolsDefined=symbolsDefined) - - -def generateIndexBoundaryKernel(pdfField, indexArr, lbMethod, boundaryFunctor, target='cpu', openMP=True): - indexField = Field.createFromNumpyArray("indexField", indexArr) - return generateIndexBoundaryKernelGeneric(pdfField, indexField, lbMethod, boundaryFunctor, target, openMP) - - -def generateIndexBoundaryKernelGeneric(pdfField, indexField, lbMethod, boundaryFunctor, target='cpu', openMP=True): - elements = [LbmMethodInfo(lbMethod)] - indexArrDtype = indexField.dtype.numpyDtype - dirSymbol = TypedSymbol("dir", indexArrDtype.fields['dir'][0]) - boundaryEqList = [sp.Eq(dirSymbol, indexField[0]('dir'))] - boundaryEqList += boundaryFunctor(pdfField=pdfField, directionSymbol=dirSymbol, lbMethod=lbMethod, - indexField=indexField) - elements += boundaryEqList - - if target == 'cpu': - from pystencils.cpu import createIndexedKernel - ast = createIndexedKernel(elements, [indexField]) - addOpenMP(ast, numThreads=openMP) - return ast - elif target == 'gpu': - from pystencils.gpucuda import createdIndexedCUDAKernel - return createdIndexedCUDAKernel(elements, [indexField]) \ No newline at end of file diff --git a/boundaries/boundaryconditions.py b/boundaries/boundaryconditions.py index ab8caf25270836d276b871a462d5556b202a0c4f..3b84aeede7dcea506818f9f13e3c2ec5c2adaf27 100644 --- a/boundaries/boundaryconditions.py +++ b/boundaries/boundaryconditions.py @@ -1,10 +1,9 @@ import sympy as sp -import numpy as np from lbmpy.simplificationfactory import createSimplificationStrategy from pystencils.astnodes import SympyAssignment from pystencils.sympyextensions import getSymmetricPart from pystencils import Field -from lbmpy.boundaries.boundary_kernel import offsetFromDir, weightOfDirection, invDir +from lbmpy.boundaries.boundaryhandling import BoundaryOffsetInfo, LbmWeightInfo from pystencils.data_types import createType @@ -63,8 +62,8 @@ class NoSlip(Boundary): """No-Slip, (half-way) simple bounce back boundary condition, enforcing zero velocity at obstacle""" def __call__(self, pdfField, directionSymbol, lbMethod, **kwargs): - neighbor = offsetFromDir(directionSymbol, lbMethod.dim) - inverseDir = invDir(directionSymbol) + neighbor = BoundaryOffsetInfo.offsetFromDir(directionSymbol, lbMethod.dim) + inverseDir = BoundaryOffsetInfo.invDir(directionSymbol) return [sp.Eq(pdfField[neighbor](inverseDir), pdfField(directionSymbol))] def __hash__(self): @@ -118,8 +117,8 @@ class UBB(Boundary): assert self.dim == lbMethod.dim, "Dimension of UBB (%d) does not match dimension of method (%d)" \ % (self.dim, lbMethod.dim) - neighbor = offsetFromDir(direction, lbMethod.dim) - inverseDir = invDir(direction) + neighbor = BoundaryOffsetInfo.offsetFromDir(direction, lbMethod.dim) + inverseDir = BoundaryOffsetInfo.invDir(direction) velocity = tuple(v_i.getShifted(*neighbor) if isinstance(v_i, Field.Access) and not velFromIdxField else v_i for v_i in vel) @@ -130,6 +129,7 @@ class UBB(Boundary): velocity = [eq.rhs for eq in shiftedVelEqs.extract(cqc.firstOrderMomentSymbols).mainEquations] c_s_sq = sp.Rational(1, 3) + weightOfDirection = LbmWeightInfo.weightOfDirection velTerm = 2 / c_s_sq * sum([d_i * v_i for d_i, v_i in zip(neighbor, velocity)]) * weightOfDirection(direction) # Better alternative: in conserved value computation @@ -163,8 +163,8 @@ class FixedDensity(Boundary): newMainEquations = [sp.Eq(e.lhs, getSymmetricPart(e.rhs, dofs)) for e in eqColl.mainEquations] return eqColl.copy(newMainEquations) - neighbor = offsetFromDir(directionSymbol, lbMethod.dim) - inverseDir = invDir(directionSymbol) + neighbor = BoundaryOffsetInfo.offsetFromDir(directionSymbol, lbMethod.dim) + inverseDir = BoundaryOffsetInfo.invDir(directionSymbol) cqc = lbMethod.conservedQuantityComputation velocity = cqc.definedSymbols()['velocity'] @@ -195,7 +195,7 @@ class FixedDensity(Boundary): class NeumannByCopy(Boundary): def __call__(self, pdfField, directionSymbol, lbMethod, **kwargs): - neighbor = offsetFromDir(directionSymbol, lbMethod.dim) + neighbor = BoundaryOffsetInfo.offsetFromDir(directionSymbol, lbMethod.dim) return [sp.Eq(pdfField[neighbor](directionSymbol), pdfField(directionSymbol))] def __hash__(self): diff --git a/boundaries/boundaryhandling.py b/boundaries/boundaryhandling.py new file mode 100644 index 0000000000000000000000000000000000000000..1ab372a541d866b763316268ea92b48043d2b26a --- /dev/null +++ b/boundaries/boundaryhandling.py @@ -0,0 +1,103 @@ +import numpy as np +import sympy as sp +from lbmpy.stencils import inverseDirection +from pystencils import TypedSymbol, createIndexedKernel +from pystencils.backends.cbackend import CustomCppCode +from pystencils.boundaries import BoundaryHandling +from pystencils.boundaries.boundaryhandling import BoundaryOffsetInfo + + +class LatticeBoltzmannBoundaryHandling(BoundaryHandling): + + def __init__(self, lbMethod, dataHandling, pdfFieldName, name="boundaryHandling", target='cpu', openMP=True): + self.lbMethod = lbMethod + super(LatticeBoltzmannBoundaryHandling, self).__init__(dataHandling, pdfFieldName, lbMethod.stencil, + name, target, openMP) + + def forceOnBoundary(self, boundaryObj): + from lbmpy.boundaries import NoSlip + if isinstance(boundaryObj, NoSlip): + return self._forceOnNoSlip(boundaryObj) + else: + self.__call__() + return self._forceOnBoundary(boundaryObj) + + # ------------------------------ Implementation Details ------------------------------------------------------------ + + def _forceOnNoSlip(self, boundaryObj): + dh = self._dataHandling + ffGhostLayers = dh.ghostLayersOfField(self._flagFieldName) + method = self.lbMethod + stencil = np.array(method.stencil) + + result = np.zeros(self.dim) + + for b in dh.iterate(ghostLayers=ffGhostLayers): + objToIndList = b[self._indexArrayName].boundaryObjectToIndexList + pdfArray = b[self._fieldName] + if boundaryObj in objToIndList: + indArr = objToIndList[boundaryObj] + indices = [indArr[name] for name in ('x', 'y', 'z')[:method.dim]] + indices.append(indArr['dir']) + values = 2 * pdfArray[tuple(indices)] + forces = stencil[indArr['dir']] * values[:, np.newaxis] + result += forces.sum(axis=0) + return dh.reduceFloatSequence(list(result), 'sum') + + def _forceOnBoundary(self, boundaryObj): + dh = self._dataHandling + ffGhostLayers = dh.ghostLayersOfField(self._flagFieldName) + method = self.lbMethod + stencil = np.array(method.stencil) + invDirection = np.array([method.stencil.index(inverseDirection(d)) + for d in method.stencil]) + + result = np.zeros(self.dim) + + for b in dh.iterate(ghostLayers=ffGhostLayers): + objToIndList = b[self._indexArrayName].boundaryObjectToIndexList + pdfArray = b[self._fieldName] + if boundaryObj in objToIndList: + indArr = objToIndList[boundaryObj] + indices = [indArr[name] for name in ('x', 'y', 'z')[:method.dim]] + offsets = stencil[indArr['dir']] + invDir = invDirection[indArr['dir']] + fluidValues = pdfArray[tuple(indices) + (indArr['dir'],)] + boundaryValues = pdfArray[tuple(ind + offsets[:, i] for i, ind in enumerate(indices)) + (invDir,)] + values = fluidValues + boundaryValues + forces = stencil[indArr['dir']] * values[:, np.newaxis] + result += forces.sum(axis=0) + + return dh.reduceFloatSequence(list(result), 'sum') + + def _createBoundaryKernel(self, symbolicField, symbolicIndexField, boundaryObject): + return createLatticeBoltzmannBoundaryKernel(symbolicField, symbolicIndexField, self.lbMethod, + boundaryObject, target=self._target, openMP=self._openMP) + + +class LbmWeightInfo(CustomCppCode): + + # --------------------------- Functions to be used by boundaries -------------------------- + + @staticmethod + def weightOfDirection(dirIdx): + return sp.IndexedBase(LbmWeightInfo.WEIGHTS_SYMBOL, shape=(1,))[dirIdx] + + # ---------------------------------- Internal --------------------------------------------- + + WEIGHTS_SYMBOL = TypedSymbol("weights", "double") + + def __init__(self, lbMethod): + weights = [str(w.evalf()) for w in lbMethod.weights] + code = "const double %s [] = { %s };\n" % (LbmWeightInfo.WEIGHTS_SYMBOL.name, ",".join(weights)) + super(LbmWeightInfo, self).__init__(code, symbolsRead=set(), + symbolsDefined=set([LbmWeightInfo.WEIGHTS_SYMBOL])) + + +def createLatticeBoltzmannBoundaryKernel(pdfField, indexField, lbMethod, boundaryFunctor, target='cpu', openMP=True): + elements = [BoundaryOffsetInfo(lbMethod.stencil), LbmWeightInfo(lbMethod)] + indexArrDtype = indexField.dtype.numpyDtype + dirSymbol = TypedSymbol("dir", indexArrDtype.fields['dir'][0]) + elements += [sp.Eq(dirSymbol, indexField[0]('dir'))] + elements += boundaryFunctor(pdfField=pdfField, directionSymbol=dirSymbol, lbMethod=lbMethod, indexField=indexField) + return createIndexedKernel(elements, [indexField], target=target, cpuOpenMP=openMP) diff --git a/boundaries/createindexlist.py b/boundaries/createindexlist.py deleted file mode 100644 index e3a971e261aeea4e106bc00bf59172556cc09ed4..0000000000000000000000000000000000000000 --- a/boundaries/createindexlist.py +++ /dev/null @@ -1,79 +0,0 @@ -import numpy as np -import itertools -import warnings - -try: - import pyximport; - - pyximport.install() - from lbmpy.boundaries.createindexlistcython import createBoundaryIndexList2D, createBoundaryIndexList3D - - cythonFuncsAvailable = True -except Exception: - cythonFuncsAvailable = False - createBoundaryIndexList2D = None - createBoundaryIndexList3D = None - -boundaryIndexArrayCoordinateNames = ["x", "y", "z"] -directionMemberName = "dir" - - -def numpyDataTypeForBoundaryObject(boundaryObject, dim): - coordinateNames = boundaryIndexArrayCoordinateNames[:dim] - return np.dtype([(name, np.int32) for name in coordinateNames] + - [(directionMemberName, np.int32)] + - [(i[0], i[1].numpyDtype) for i in boundaryObject.additionalData], align=True) - - -def _createBoundaryIndexListPython(flagFieldArr, nrOfGhostLayers, boundaryMask, fluidMask, stencil): - coordinateNames = boundaryIndexArrayCoordinateNames[:len(flagFieldArr.shape)] - indexArrDtype = np.dtype([(name, np.int32) for name in coordinateNames] + [(directionMemberName, np.int32)]) - - result = [] - gl = nrOfGhostLayers - for cell in itertools.product(*reversed([range(gl, i-gl) for i in flagFieldArr.shape])): - cell = cell[::-1] - if not flagFieldArr[cell] & fluidMask: - continue - for dirIdx, direction in enumerate(stencil): - neighborCell = tuple([cell_i + dir_i for cell_i, dir_i in zip(cell, direction)]) - if flagFieldArr[neighborCell] & boundaryMask: - result.append(cell + (dirIdx,)) - - return np.array(result, dtype=indexArrDtype) - - -def createBoundaryIndexList(flagField, stencil, boundaryMask, fluidMask, nrOfGhostLayers=1): - dim = len(flagField.shape) - coordinateNames = boundaryIndexArrayCoordinateNames[:dim] - indexArrDtype = np.dtype([(name, np.int32) for name in coordinateNames] + [(directionMemberName, np.int32)]) - - if cythonFuncsAvailable: - stencil = np.array(stencil, dtype=np.int32) - if dim == 2: - idxList = createBoundaryIndexList2D(flagField, nrOfGhostLayers, boundaryMask, fluidMask, stencil) - elif dim == 3: - idxList = createBoundaryIndexList3D(flagField, nrOfGhostLayers, boundaryMask, fluidMask, stencil) - else: - raise ValueError("Flag field has to be a 2 or 3 dimensional numpy array") - return np.array(idxList, dtype=indexArrDtype) - else: - if flagField.size > 1e6: - warnings.warn("Boundary setup may take very long! Consider installing cython to speed it up") - return _createBoundaryIndexListPython(flagField, nrOfGhostLayers, boundaryMask, fluidMask, stencil) - - -def createBoundaryIndexArray(flagField, stencil, boundaryMask, fluidMask, boundaryObject, nrOfGhostLayers=1): - idxArray = createBoundaryIndexList(flagField, stencil, boundaryMask, fluidMask, nrOfGhostLayers) - dim = len(flagField.shape) - - if boundaryObject.additionalData: - coordinateNames = boundaryIndexArrayCoordinateNames[:dim] - indexArrDtype = numpyDataTypeForBoundaryObject(boundaryObject, dim) - extendedIdxField = np.empty(len(idxArray), dtype=indexArrDtype) - for prop in coordinateNames + ['dir']: - extendedIdxField[prop] = idxArray[prop] - - idxArray = extendedIdxField - - return idxArray diff --git a/lbstep.py b/lbstep.py index 1cf28aae7d1d054247821002b2edd39cbb350d80..21da0682985ada3e394788ff08dc58285fab1c00 100644 --- a/lbstep.py +++ b/lbstep.py @@ -1,5 +1,5 @@ import numpy as np -from lbmpy.boundaries.boundary_handling import BoundaryHandling +from lbmpy.boundaries.boundaryhandling import LatticeBoltzmannBoundaryHandling from lbmpy.creationfunctions import switchToSymbolicRelaxationRatesForEntropicMethods, createLatticeBoltzmannFunction, \ updateWithDefaultParameters from lbmpy.simplificationfactory import createSimplificationStrategy @@ -101,9 +101,9 @@ class LatticeBoltzmannStep: # -- Boundary Handling & Synchronization --- self._sync = dataHandling.synchronizationFunction([self._pdfArrName], methodParameters['stencil'], target) - self._boundaryHandling = BoundaryHandling(self.method, self._dataHandling, self._pdfArrName, - name=name + "_boundaryHandling", - target=target, openMP=optimizationParams['openMP']) + self._boundaryHandling = LatticeBoltzmannBoundaryHandling(self.method, self._dataHandling, self._pdfArrName, + name=name + "_boundaryHandling", + target=target, openMP=optimizationParams['openMP']) # -- Macroscopic Value Kernels self._getterKernel, self._setterKernel = self._compilerMacroscopicSetterAndGetter() diff --git a/scenarios.py b/scenarios.py index 0ead321806614a33ae337e7d0a15d5b3096f0a57..4fe2ad872a1bdd7905e0d9fb96e0857f161d88b7 100644 --- a/scenarios.py +++ b/scenarios.py @@ -58,6 +58,7 @@ def createFullyPeriodicFlow(initialVelocity, periodicityInKernel=False, lbmKerne step = LatticeBoltzmannStep(dataHandling=dataHandling, name="periodicScenario", lbmKernel=lbmKernel, **kwargs) for b in step.dataHandling.iterate(ghostLayers=False): np.copyto(b[step.velocityDataName], initialVelocity[b.globalSlice]) + step.setPdfFieldsFromMacroscopicValues() return step