From 9dd3ef0915657941f4be6b458f73faf67b250eab Mon Sep 17 00:00:00 2001
From: Martin Bauer <martin.bauer@fau.de>
Date: Fri, 20 Oct 2017 11:00:17 +0200
Subject: [PATCH] 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
---
 boundaries/boundaryhandling.py  |  13 +---
 boundaries/handlinginterface.py | 104 +++++++++++++++++++++++++-------
 chapman_enskog/steady_state.py  |   2 +-
 parallel/blockiteration.py      |  39 ++++++++----
 parallel/boundaryhandling.py    |  11 ++--
 5 files changed, 117 insertions(+), 52 deletions(-)

diff --git a/boundaries/boundaryhandling.py b/boundaries/boundaryhandling.py
index f055f2d7..569a098a 100644
--- a/boundaries/boundaryhandling.py
+++ b/boundaries/boundaryhandling.py
@@ -9,20 +9,11 @@ class BoundaryHandling(PeriodicityHandling, GenericBoundaryHandling):  # importa
         shapeWithGl = [a + 2 * ghostLayers for a in domainShape]
         self.domainShape = domainShape
         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)])
 
-    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):
         for cls in BoundaryHandling.__bases__:
             cls.__call__(self, *args, **kwargs)
diff --git a/boundaries/handlinginterface.py b/boundaries/handlinginterface.py
index 8f85e006..5320b739 100644
--- a/boundaries/handlinginterface.py
+++ b/boundaries/handlinginterface.py
@@ -4,6 +4,7 @@ from pystencils.field import Field
 from pystencils.slicing import normalizeSlice
 from lbmpy.boundaries.boundary_kernel import generateIndexBoundaryKernel
 from lbmpy.boundaries.createindexlist import createBoundaryIndexList
+from pystencils.cache import memorycache
 
 
 class FlagFieldInterface(object):
@@ -37,20 +38,63 @@ class FlagFieldInterface(object):
         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 BoundaryInfo(object):
-        def __init__(self, kernel, ast, indexArray, idxArrayForExecution):
+        def __init__(self, kernel, ast, indexArray, idxArrayForExecution, boundaryDataSetter):
             self.kernel = kernel
             self.ast = ast
             self.indexArray = indexArray
             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 pdfField: numpy array
         :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 openMP:
         """
@@ -61,7 +105,7 @@ class GenericBoundaryHandling(object):
         self._openMP = openMP
         self.ghostLayers = ghostLayers
         self._dirty = False
-
+        self.offset = offset if offset else (0,) * lbMethod.dim
         self._boundaryInfos = {}  # mapping of boundary object to boundary info
         self._symbolicPdfField = Field.createFromNumpyArray('pdfs', pdfField, indexDimensions=1)
 
@@ -98,33 +142,17 @@ class GenericBoundaryHandling(object):
         self._dirty = False
         self._boundaryInfos = {}
 
-    def _invalidateCache(self):
-        self._dirty = True
-
     def triggerReinitializationOfBoundaryData(self, **kwargs):
         if self._dirty:
             self.prepare()
             return
         else:
             for boundaryObject, boundaryInfo in self._boundaryInfos.items():
-                self.__boundaryDataInitialization(boundaryInfo.indexArray, boundaryObject, **kwargs)
+                self.__boundaryDataInitialization(boundaryInfo, boundaryObject, **kwargs)
                 if self._target == 'gpu':
                     import pycuda.gpuarray as gpuarray
                     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):
         """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
@@ -152,7 +180,6 @@ class GenericBoundaryHandling(object):
                     extendedIdxField[prop] = idxArray[prop]
 
                 idxArray = extendedIdxField
-                self.__boundaryDataInitialization(idxArray, boundaryObject)
 
             ast = generateIndexBoundaryKernel(self._symbolicPdfField, idxArray, self._lbMethod, boundaryObject,
                                               target=self._target)
@@ -170,15 +197,30 @@ class GenericBoundaryHandling(object):
             else:
                 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
 
+            if boundaryObject.additionalData:
+                self.__boundaryDataInitialization(boundaryInfo, boundaryObject)
+
         self._dirty = False
 
     def reserveFlag(self, 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)
 
@@ -207,3 +249,19 @@ class GenericBoundaryHandling(object):
     @property
     def flagField(self):
         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
diff --git a/chapman_enskog/steady_state.py b/chapman_enskog/steady_state.py
index 8547846b..8c1521f7 100644
--- a/chapman_enskog/steady_state.py
+++ b/chapman_enskog/steady_state.py
@@ -64,7 +64,7 @@ class SteadyStateChapmanEnskogAnalysis(object):
         chapmanEnskogHierarchy = [chapmanEnskogHierarchy[i] for i in range(self.order + 1)]
 
         insertedHierarchy = []
-        rawHierarchy = []j
+        rawHierarchy = []
         substitutionDict = {}
         for ceEq, f_i in zip(chapmanEnskogHierarchy, self.fSyms):
             newEq = -1 / self.collisionOpSym * (ceEq - self.collisionOpSym * f_i)
diff --git a/parallel/blockiteration.py b/parallel/blockiteration.py
index 18e38855..89107b98 100644
--- a/parallel/blockiteration.py
+++ b/parallel/blockiteration.py
@@ -3,14 +3,28 @@ import waLBerla as wlb
 from pystencils.slicing import normalizeSlice
 
 
-def slicedBlockIteration(blocks, indexExpr=None, ghostLayers=1, sliceNormalizationGhostLayers=1):
-    if indexExpr is None:
-        indexExpr = [slice(None, None, None)] * 3
+def slicedBlockIteration(blocks, sliceObj=None, ghostLayers=1, sliceNormalizationGhostLayers=1,
+                         withIndexArrays=False):
+    """
+    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()
     domainExtent = [s + 2 * sliceNormalizationGhostLayers for s in domainCellBB.size]
-    indexExpr = normalizeSlice(indexExpr, domainExtent)
-    targetCellBB = wlb.CellInterval.fromSlice(indexExpr)
+    sliceObj = normalizeSlice(sliceObj, domainExtent)
+    targetCellBB = wlb.CellInterval.fromSlice(sliceObj)
     targetCellBB.shift(*[a - sliceNormalizationGhostLayers for a in domainCellBB.min])
 
     for block in blocks:
@@ -19,12 +33,15 @@ def slicedBlockIteration(blocks, indexExpr=None, ghostLayers=1, sliceNormalizati
         if intersection.empty():
             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.shift(ghostLayers, ghostLayers, ghostLayers)
         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
+
diff --git a/parallel/boundaryhandling.py b/parallel/boundaryhandling.py
index b33af206..601df669 100644
--- a/parallel/boundaryhandling.py
+++ b/parallel/boundaryhandling.py
@@ -10,8 +10,9 @@ class BoundaryHandling(object):
             flagFieldInterface = WalberlaFlagFieldInterface(block, flagFieldId)
             pdfField = wlb.field.toArray(block[pdfFieldId], withGhostLayers=True)
             ghostLayers = block[pdfFieldId].nrOfGhostLayers
-            return GenericBoundaryHandling(flagFieldInterface, pdfField, lbMethod, ghostLayers=ghostLayers,
-                                           target=target, openMP=openMP)
+            offset = blocks.getBlockCellBB(block).min
+            return GenericBoundaryHandling(flagFieldInterface, pdfField, lbMethod, offset=offset,
+                                           ghostLayers=ghostLayers, target=target, openMP=openMP)
 
         self._boundaryId = boundaryId
         self._pdfFieldId = pdfFieldId
@@ -41,13 +42,11 @@ class BoundaryHandling(object):
             return
 
         gl = self._blocks[0][self._boundaryId].ghostLayers
-        ngl = sliceNormalizationGhostLayers
         for block in self._blocks:
             block[self._boundaryId].reserveFlag(boundaryObject)
 
-        for block, indexArrs, localSlice in slicedBlockIteration(self._blocks, indexExpr, gl, ngl):
-            mask = maskCallback(*indexArrs) if maskCallback else None
-            block[self._boundaryId].setBoundary(boundaryObject, indexExpr=localSlice, maskArr=mask)
+        for block, localSlice in slicedBlockIteration(self._blocks, indexExpr, gl, sliceNormalizationGhostLayers):
+            block[self._boundaryId].setBoundary(boundaryObject, indexExpr=localSlice, maskCallback=maskCallback)
 
 
 # ----------------------------------------------------------------------------------------------------------------------
-- 
GitLab