From 363112f3e0e27b7dcde62509d08601bbd24ba649 Mon Sep 17 00:00:00 2001
From: Martin Bauer <martin.bauer@fau.de>
Date: Thu, 18 Jan 2018 09:11:41 +0100
Subject: [PATCH] New Boundary  step system / fixes in lbmpy phasefield

- scaling interface width eta instead of surface tensions tau to correct
  interface profile & surface tensions
---
 boundaries/__init__.py               |   2 +-
 boundaries/boundary_kernel.py        |  24 ++-
 boundaries/boundaryconditions.py     |  61 ++++----
 boundaries/createindexlist.py        |  16 ++
 boundaries/createindexlistcython.pyx |   3 -
 boundaries/handlinginterface.py      |   2 +-
 boundary_handling.py                 | 221 +++++++++++++++++++++++++++
 creationfunctions.py                 |   5 +-
 lbstep.py                            | 146 ++++++++++++++++++
 phasefield/analytical.py             |   9 +-
 phasefield/postprocessing.py         |   5 +-
 11 files changed, 437 insertions(+), 57 deletions(-)
 create mode 100644 boundary_handling.py
 create mode 100644 lbstep.py

diff --git a/boundaries/__init__.py b/boundaries/__init__.py
index a07c43c5..7588d247 100644
--- a/boundaries/__init__.py
+++ b/boundaries/__init__.py
@@ -1,2 +1,2 @@
-from lbmpy.boundaries.boundaryconditions import NoSlip, NoSlipFullWay, UBB, FixedDensity, NeumannByCopy
+from lbmpy.boundaries.boundaryconditions import NoSlip, UBB, FixedDensity, NeumannByCopy
 from lbmpy.boundaries.boundaryhandling import BoundaryHandling
\ No newline at end of file
diff --git a/boundaries/boundary_kernel.py b/boundaries/boundary_kernel.py
index ed6f4007..d9e0f89c 100644
--- a/boundaries/boundary_kernel.py
+++ b/boundaries/boundary_kernel.py
@@ -2,6 +2,7 @@ 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")
@@ -51,30 +52,25 @@ class LbmMethodInfo(CustomCppCode):
         super(LbmMethodInfo, self).__init__(code, symbolsRead=set(), symbolsDefined=symbolsDefined)
 
 
-def generateIndexBoundaryKernel(pdfField, indexArr, lbMethod, boundaryFunctor, target='cpu',
-                                createInitializationKernel=False):
+def generateIndexBoundaryKernel(pdfField, indexArr, lbMethod, boundaryFunctor, target='cpu', openMP=True):
     indexField = Field.createFromNumpyArray("indexField", indexArr)
-    return generateIndexBoundaryKernelGeneric(pdfField, indexField, indexArr.dtype, lbMethod, boundaryFunctor, target,
-                                              createInitializationKernel)
+    return generateIndexBoundaryKernelGeneric(pdfField, indexField, lbMethod, boundaryFunctor, target, openMP)
 
 
-def generateIndexBoundaryKernelGeneric(pdfField, indexField, indexArrDtype, lbMethod, boundaryFunctor, target='cpu',
-                                       createInitializationKernel=False):
-
+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'))]
-    if createInitializationKernel:
-        boundaryEqList += boundaryFunctor.additionalDataInitKernelEquations(pdfField=pdfField, directionSymbol=dirSymbol,
-                                                                            lbMethod=lbMethod, indexField=indexField)
-    else:
-        boundaryEqList += boundaryFunctor(pdfField=pdfField, directionSymbol=dirSymbol, lbMethod=lbMethod,
-                                          indexField=indexField)
+    boundaryEqList += boundaryFunctor(pdfField=pdfField, directionSymbol=dirSymbol, lbMethod=lbMethod,
+                                      indexField=indexField)
     elements += boundaryEqList
 
     if target == 'cpu':
         from pystencils.cpu import createIndexedKernel
-        return createIndexedKernel(elements, [indexField])
+        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 f67d31cd..9557acc5 100644
--- a/boundaries/boundaryconditions.py
+++ b/boundaries/boundaryconditions.py
@@ -39,13 +39,8 @@ class Boundary(object):
 
     @property
     def additionalDataInitCallback(self):
-        """Return a callback function called with (x, y, [z]), and returning a dict of data-name to data for each
-         element that should be initialized"""
-        return None
-
-    @property
-    def additionalDataInitKernelEquations(self):
-        """Should return callback that returns kernel equations for the boundary data initialization kernel"""
+        """Return a callback function called with a boundary data setter object and returning a dict of
+        data-name to data for each element that should be initialized"""
         return None
 
     @property
@@ -82,31 +77,33 @@ class NoSlip(Boundary):
         return self.name == other.name
 
 
-class NoSlipFullWay(Boundary):
-    """Full-way bounce back"""
-
-    @property
-    def additionalData(self):
-        return [('lastValue', createType("double"))]
-
-    @property
-    def additionalDataInitKernelEquations(self):
-        def kernelEqGetter(pdfField, directionSymbol, indexField, **kwargs):
-            return [sp.Eq(indexField('lastValue'), pdfField(directionSymbol))]
-        return kernelEqGetter
-
-    def __call__(self, pdfField, directionSymbol, lbMethod, indexField, **kwargs):
-        neighbor = offsetFromDir(directionSymbol, lbMethod.dim)
-        inverseDir = invDir(directionSymbol)
-        return [sp.Eq(pdfField[neighbor](inverseDir), indexField('lastValue')),
-                sp.Eq(indexField('lastValue'), pdfField(directionSymbol))]
-
-    def __hash__(self):
-        # All boundaries of these class behave equal -> should also be equal
-        return hash("NoSlipFullWay")
-
-    def __eq__(self, other):
-        return type(other) == NoSlipFullWay
+#class NoSlipFullWay(Boundary):
+#    """Full-way bounce back"""
+#
+#    @property
+#    def additionalData(self):
+#        return [('lastValue', createType("double"))]
+#
+#    @property
+#    def additionalDataInitKernelEquations(self):
+#        # TODO this function is not longer availabel
+#        # formulate as additionalDataInitCallback
+#        def kernelEqGetter(pdfField, directionSymbol, indexField, **kwargs):
+#            return [sp.Eq(indexField('lastValue'), pdfField(directionSymbol))]
+#        return kernelEqGetter
+#
+#    def __call__(self, pdfField, directionSymbol, lbMethod, indexField, **kwargs):
+#        neighbor = offsetFromDir(directionSymbol, lbMethod.dim)
+#        inverseDir = invDir(directionSymbol)
+#        return [sp.Eq(pdfField[neighbor](inverseDir), indexField('lastValue')),
+#                sp.Eq(indexField('lastValue'), pdfField(directionSymbol))]
+#
+#    def __hash__(self):
+#        # All boundaries of these class behave equal -> should also be equal
+#        return hash("NoSlipFullWay")
+#
+#    def __eq__(self, other):
+#        return type(other) == NoSlipFullWay
 
 
 class UBB(Boundary):
diff --git a/boundaries/createindexlist.py b/boundaries/createindexlist.py
index 69c5cb4d..aa217305 100644
--- a/boundaries/createindexlist.py
+++ b/boundaries/createindexlist.py
@@ -60,3 +60,19 @@ def createBoundaryIndexList(flagField, stencil, boundaryMask, fluidMask, nrOfGho
         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/boundaries/createindexlistcython.pyx b/boundaries/createindexlistcython.pyx
index b6126fd1..dfa84069 100644
--- a/boundaries/createindexlistcython.pyx
+++ b/boundaries/createindexlistcython.pyx
@@ -48,9 +48,6 @@ def createBoundaryIndexList3D(object[IntegerType, ndim=3] flagField,
     boundaryIndexList = []
     numDirections = stencil.shape[0]
 
-    #for x in range(nrOfGhostLayers,xs-nrOfGhostLayers):
-    #    for y in range(nrOfGhostLayers,ys-nrOfGhostLayers):
-    #        for z in range(nrOfGhostLayers, zs-nrOfGhostLayers):
     for z in range(nrOfGhostLayers, zs-nrOfGhostLayers):
         for y in range(nrOfGhostLayers,ys-nrOfGhostLayers):
             for x in range(nrOfGhostLayers,xs-nrOfGhostLayers):
diff --git a/boundaries/handlinginterface.py b/boundaries/handlinginterface.py
index 0100cd98..4653feda 100644
--- a/boundaries/handlinginterface.py
+++ b/boundaries/handlinginterface.py
@@ -223,7 +223,7 @@ class GenericBoundaryHandling(object):
         self._flagFieldInterface.getFlag(boundaryObject)
 
     def setBoundary(self, boundaryObject, indexExpr=None, maskCallback=None, includeGhostLayers=True):
-        """c
+        """
         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 
diff --git a/boundary_handling.py b/boundary_handling.py
new file mode 100644
index 00000000..f981dbee
--- /dev/null
+++ b/boundary_handling.py
@@ -0,0 +1,221 @@
+import numpy as np
+
+from lbmpy.boundaries.boundary_kernel import generateIndexBoundaryKernelGeneric
+from pystencils import Field
+from lbmpy.boundaries.createindexlist import createBoundaryIndexArray, numpyDataTypeForBoundaryObject
+from pystencils.cache import memorycache
+
+
+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]
+
+
+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._pdfFieldName = 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.int32, cpu=True, gpu=gpu)
+        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 dim(self):
+        return self._lbMethod.dim
+
+    @property
+    def lbMethod(self):
+        return self._lbMethod
+
+    @property
+    def boundaryObjects(self):
+        return tuple(self._boundaryObjectToName.keys())
+
+    def setBoundary(self, boundaryObject, sliceObj=None, maskCallback=None, ghostLayers=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()
+        """
+        flag = self._getFlagForBoundary(boundaryObject)
+
+        for b in self._dataHandling.iterate(sliceObj=sliceObj, ghostLayers=ghostLayers):
+            flagArr = b[self._flagFieldName]
+            if maskCallback is not None:
+                mask = maskCallback(*b.midpointArrays)
+                flagArr[mask] = flag
+            else:
+                flagArr.fill(flag)
+
+        self._dirty = True
+
+    def getBoundaryNameToFlagDict(self):
+        result = {bObj.name: bInfo.flag for bObj, bInfo in self._boundaryObjectToBoundaryInfo.values()}
+        result['fluid'] = self._fluidFlag
+        return result
+
+    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():
+            for bInfo in self._boundaryObjectToBoundaryInfo.values():
+                idxArr = b[self._indexArrayName].boundaryObjectToIndexList[bInfo.boundaryObject]
+                kwargs[self._pdfFieldName] = b[self._pdfFieldName]
+                bInfo.kernel(indexField=idxArr, **kwargs)
+
+    # ------------------------------ Implementation Details ------------------------------------------------------------
+
+    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._pdfFieldName]
+            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]
+            for bInfo in self._boundaryObjectToBoundaryInfo.values():
+                idxArr = createBoundaryIndexArray(flagArr, self.lbMethod.stencil, bInfo.flag, self._fluidFlag,
+                                                  bInfo.boundaryObject, dh.ghostLayersOfField(self._flagFieldName))
+
+                pdfArr = b[self._pdfFieldName]
+                # TODO test that offset is used correctly here
+                boundaryDataSetter = BoundaryDataSetter(idxArr, b.offset, self.lbMethod.stencil, ffGhostLayers, pdfArr)
+                indexArrayBD = b[self._indexArrayName]
+                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):
+            self.boundaryObjectToIndexList = {}
+            self.boundaryObjectToDataSetter = {}
+
+        @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.values():
+                if obj not in gpuVersion:
+                    gpuVersion[obj] = gpuarray.to_gpu(cpuArr)
+                else:
+                    gpuVersion[obj].set(cpuArr)
\ No newline at end of file
diff --git a/creationfunctions.py b/creationfunctions.py
index 4e01a01d..d83f0f89 100644
--- a/creationfunctions.py
+++ b/creationfunctions.py
@@ -197,6 +197,7 @@ def updateWithDefaultParameters(params, optParams, failOnUnknownParameter=True):
 
         'fieldSize': None,
         'fieldLayout': 'fzyx',  # can be 'numpy' (='c'), 'reverseNumpy' (='f'), 'fzyx', 'zyxf'
+        'symbolicField': None,
 
         'target': 'cpu',
         'openMP': True,
@@ -348,7 +349,9 @@ def createLatticeBoltzmannUpdateRule(lbMethod=None, optimizationParams={}, **kwa
     else:
         fieldDtype = 'float64'
 
-    if optParams['fieldSize']:
+    if optParams['symbolicField'] is not None:
+        srcField = optParams['symbolicField']
+    elif optParams['fieldSize']:
         fieldSize = [s + 2 for s in optParams['fieldSize']] + [len(stencil)]
         srcField = Field.createFixedSize(params['fieldName'], fieldSize, indexDimensions=1,
                                          layout=optParams['fieldLayout'], dtype=fieldDtype)
diff --git a/lbstep.py b/lbstep.py
new file mode 100644
index 00000000..496dc111
--- /dev/null
+++ b/lbstep.py
@@ -0,0 +1,146 @@
+from lbmpy.boundary_handling import BoundaryHandling
+from lbmpy.creationfunctions import switchToSymbolicRelaxationRatesForEntropicMethods, createLatticeBoltzmannFunction, \
+    updateWithDefaultParameters
+from lbmpy.simplificationfactory import createSimplificationStrategy
+from lbmpy.stencils import getStencil
+from pystencils.datahandling import SerialDataHandling
+from pystencils import createKernel
+
+
+class LatticeBoltzmannStep:
+
+    def __init__(self, domainSize=None, lbmKernel=None, periodicity=False,
+                 kernelParams={}, dataHandling=None, dataPrefix="", optimizationParams={}, **methodParameters):
+
+        # --- Parameter normalization  ---
+        if dataHandling is not None:
+            if domainSize is not None:
+                raise ValueError("When passing a dataHandling, the domainSize parameter can not be specified")
+
+        if dataHandling is None:
+            if domainSize is None:
+                raise ValueError("Specify either domainSize or dataHandling")
+            dataHandling = SerialDataHandling(domainSize, defaultGhostLayers=1, periodicity=periodicity)
+
+        methodParameters, optimizationParams = updateWithDefaultParameters(methodParameters, optimizationParams)
+        target = optimizationParams['target']
+
+        # --- Kernel creation ---
+        if lbmKernel:
+            Q = len(lbmKernel.method.stencil)
+        else:
+            Q = len(getStencil(methodParameters['stencil']))
+
+        self._dataHandling = dataHandling
+        self._pdfArrName = dataPrefix + "pdfSrc"
+        self._tmpArrName = dataPrefix + "pdfTmp"
+        self._velocityArrName = dataPrefix + "velocity"
+        self._densityArrName = dataPrefix + "density"
+
+        self._gpu = target == 'gpu'
+        layout = optimizationParams['fieldLayout']
+        self._dataHandling.addArray(self._pdfArrName, fSize=Q, gpu=self._gpu, layout=layout)
+        self._dataHandling.addArray(self._tmpArrName, fSize=Q, gpu=self._gpu, cpu=not self._gpu, layout=layout)
+        self._dataHandling.addArray(self._velocityArrName, fSize=self._dataHandling.dim, gpu=False, layout=layout)
+        self._dataHandling.addArray(self._densityArrName, fSize=1, gpu=False, layout=layout)
+
+        self._kernelParams = kernelParams
+
+        if lbmKernel is None:
+            switchToSymbolicRelaxationRatesForEntropicMethods(methodParameters, self._kernelParams)
+            optimizationParams['symbolicField'] = dataHandling.fields[self._pdfArrName]
+            methodParameters['fieldName'] = self._pdfArrName
+            methodParameters['secondFieldName'] = self._tmpArrName
+            self._lbmKernel = createLatticeBoltzmannFunction(optimizationParams=optimizationParams, **methodParameters)
+        else:
+            assert self._dataHandling.dim == self._lbmKernel.method.dim, \
+                "Error: %dD Kernel for %D domain" % (self._lbmKernel.method.dim, self._dataHandling.dim)
+            self._lbmKernel = lbmKernel
+
+        # -- Boundary Handling  & Synchronization ---
+        if self._gpu:
+            self._sync = dataHandling.synchronizationFunctionGPU([self._pdfArrName], methodParameters['stencil'])
+        else:
+            self._sync = dataHandling.synchronizationFunctionCPU([self._pdfArrName], methodParameters['stencil'])
+        self._boundaryHandling = BoundaryHandling(self._lbmKernel.method, self._dataHandling, self._pdfArrName,
+                                                  name=dataPrefix + "_boundaryHandling",
+                                                  target=target, openMP=optimizationParams['openMP'])
+
+        # -- Macroscopic Value Kernels
+        self._getterKernel, self._setterKernel = self._compilerMacroscopicSetterAndGetter()
+
+        self.timeStepsRun = 0
+
+        for b in self._dataHandling.iterate():
+            b[self._densityArrName].fill(1.0)
+            b[self._velocityArrName].fill(0.0)
+
+    @property
+    def boundaryHandling(self):
+        """Boundary handling instance of the scenario. Use this to change the boundary setup"""
+        return self._boundaryHandling
+
+    def preRun(self):
+        self._dataHandling.runKernel(self._setterKernel)
+        if self._gpu:
+            self._dataHandling.toGpu(self._pdfArrName)
+
+    def timeStep(self):
+        self._sync()
+        self._boundaryHandling()
+        self._dataHandling.runKernel(self._lbmKernel, **self._kernelParams)
+        self._dataHandling.swap(self._pdfArrName, self._tmpArrName, self._gpu)
+        self.timeStepsRun += 1
+
+    def postRun(self):
+        if self._gpu:
+            self._dataHandling.toCpu(self._pdfArrName)
+        self._dataHandling.runKernel(self._getterKernel)
+
+    def run(self, timeSteps):
+        self.preRun()
+        for i in range(timeSteps):
+            self.timeStep()
+        self.postRun()
+
+    def _compilerMacroscopicSetterAndGetter(self):
+        lbMethod = self._lbmKernel.method
+        D = lbMethod.dim
+        Q = len(lbMethod.stencil)
+        cqc = lbMethod.conservedQuantityComputation
+        pdfField = self._dataHandling.fields[self._pdfArrName]
+        rhoField = self._dataHandling.fields[self._densityArrName]
+        velField = self._dataHandling.fields[self._velocityArrName]
+        pdfSymbols = [pdfField(i) for i in range(Q)]
+
+        getterEqs = cqc.outputEquationsFromPdfs(pdfSymbols, {'density': rhoField, 'velocity': velField})
+        getterKernel = createKernel(getterEqs, target='cpu').compile()
+
+        inpEqs = cqc.equilibriumInputEquationsFromInitValues(rhoField.center, [velField(i) for i in range(D)])
+        setterEqs = lbMethod.getEquilibrium(conservedQuantityEquations=inpEqs)
+        setterEqs = setterEqs.copyWithSubstitutionsApplied({sym: pdfField(i)
+                                                            for i, sym in enumerate(lbMethod.postCollisionPdfSymbols)})
+
+        setterEqs = createSimplificationStrategy(lbMethod)(setterEqs)
+        setterKernel = createKernel(setterEqs, target='cpu').compile()
+        return getterKernel, setterKernel
+
+
+if __name__ == '__main__':
+    from pycuda import autoinit
+    from lbmpy.boundaries import NoSlip, UBB
+    from pystencils import makeSlice
+    step = LatticeBoltzmannStep((30, 30), relaxationRate=1.8, periodicity=True,
+                                optimizationParams={'target': 'cpu', 'openMP': False})
+
+    wall = NoSlip()
+    movingWall = UBB((0.001, 0))
+
+    bh = step.boundaryHandling
+    bh.setBoundary(wall, makeSlice[0, :])
+    bh.setBoundary(wall, makeSlice[-1, :])
+    bh.setBoundary(wall, makeSlice[:, 0])
+    bh.setBoundary(movingWall, makeSlice[:, -1])
+    bh.prepare()
+
+    step.run(5000)
diff --git a/phasefield/analytical.py b/phasefield/analytical.py
index 36c98083..6daad194 100644
--- a/phasefield/analytical.py
+++ b/phasefield/analytical.py
@@ -104,7 +104,10 @@ def freeEnergyFunctionalNPhases(numPhases, surfaceTensions=symmetricSymbolicSurf
     else:
         phi = orderParameters
 
-    tauFactor = sp.Rational(1, 2)  # originally this was 1 / sp.sqrt(2)
+    # Compared to handwritten notes we scale the interface width parameter here to obtain the correct
+    # equations for the interface profile and the surface tensions i.e. to pass tests
+    # test_analyticInterfaceSolution and test_surfaceTensionDerivation
+    interfaceWidth *= sp.sqrt(2)
 
     def f(c):
         return c ** 2 * (1 - c) ** 2
@@ -113,12 +116,12 @@ def freeEnergyFunctionalNPhases(numPhases, surfaceTensions=symmetricSymbolicSurf
         N = numPhases - 1
         if k == l:
             assert surfaceTensions(l, l) == 0
-        return 6 * tauFactor * interfaceWidth * (surfaceTensions(k, N) + surfaceTensions(l, N) - surfaceTensions(k, l))
+        return 3 / sp.sqrt(2) * interfaceWidth * (surfaceTensions(k, N) + surfaceTensions(l, N) - surfaceTensions(k, l))
 
     def bulkTerm(i, j):
         return surfaceTensions(i, j) / 2 * (f(phi[i]) + f(phi[j]) - f(phi[i] + phi[j]))
 
-    F_bulk = 3 * tauFactor / interfaceWidth * sum(bulkTerm(i, j) for i, j in multiSum(2, numPhases) if i != j)
+    F_bulk = 3 / sp.sqrt(2) / interfaceWidth * sum(bulkTerm(i, j) for i, j in multiSum(2, numPhases) if i != j)
     F_interface = sum(lambdaCoeff(i, j) / 2 * Diff(phi[i]) * Diff(phi[j]) for i, j in multiSum(2, numPhases))
 
     return F_bulk + F_interface
diff --git a/phasefield/postprocessing.py b/phasefield/postprocessing.py
index a6c63bea..269fee3e 100644
--- a/phasefield/postprocessing.py
+++ b/phasefield/postprocessing.py
@@ -2,10 +2,11 @@ import numpy as np
 from matplotlib.path import Path
 import itertools
 import scipy
+import scipy.spatial
 import warnings
 
 
-def getIsolines(dataset, level=0.5, refinementFactor=1):
+def getIsolines(dataset, level=0.5, refinementFactor=4):
     from matplotlib._contour import QuadContourGenerator
     indexArrays = np.meshgrid(*[np.arange(s) for s in dataset.shape])
     gen = QuadContourGenerator(*indexArrays, dataset, None, True, 0)
@@ -43,7 +44,7 @@ def findBranchingPoint(pathVertices1, pathVertices2, maxDistance=0.5, branchingP
 
 def findAllBranchingPoints(phaseField1, phaseField2, maxDistance=0.1, branchingPointFilter=3):
     result = []
-    isoLines = [getIsolines(p, level=0.5, refinementFactor=4) for p in (phaseField1, phaseField2)]
+    isoLines = [getIsolines(p, level=0.5, refinementFactor=16) for p in (phaseField1, phaseField2)]
     for path1, path2 in itertools.product(*isoLines):
         bbs = findBranchingPoint(path1, path2, maxDistance, branchingPointFilter)
         result += list(bbs)
-- 
GitLab