From 70f4d7add8f43f658daec67e8279f5acd83352cb Mon Sep 17 00:00:00 2001
From: Martin Bauer <martin.bauer@fau.de>
Date: Mon, 5 Feb 2018 12:42:52 +0100
Subject: [PATCH] Bugfixes in LBM phase field code

- 3 phase model works now with step sytem
---
 boundaries/boundary_handling.py |   2 +-
 lbstep.py                       |  21 ++--
 phasefield/analytical.py        |  29 ++---
 phasefield/scenario.py          | 202 --------------------------------
 4 files changed, 23 insertions(+), 231 deletions(-)
 delete mode 100644 phasefield/scenario.py

diff --git a/boundaries/boundary_handling.py b/boundaries/boundary_handling.py
index 4157bda3..083f863b 100644
--- a/boundaries/boundary_handling.py
+++ b/boundaries/boundary_handling.py
@@ -32,7 +32,7 @@ class BoundaryHandling:
                              "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=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)
diff --git a/lbstep.py b/lbstep.py
index f07d87f6..66aac26e 100644
--- a/lbstep.py
+++ b/lbstep.py
@@ -42,6 +42,7 @@ class LatticeBoltzmannStep:
             Q = len(getStencil(methodParameters['stencil']))
         target = optimizationParams['target']
 
+        self.name = name
         self._dataHandling = dataHandling
         self._pdfArrName = name + "_pdfSrc"
         self._tmpArrName = name + "_pdfTmp"
@@ -67,11 +68,11 @@ class LatticeBoltzmannStep:
         if velocityInputArrayName is not None:
             methodParameters['velocityInput'] = self._dataHandling.fields[velocityInputArrayName]
 
-        self._kernelParams = kernelParams
+        self.kernelParams = kernelParams
 
         # --- Kernel creation ---
         if lbmKernel is None:
-            switchToSymbolicRelaxationRatesForEntropicMethods(methodParameters, self._kernelParams)
+            switchToSymbolicRelaxationRatesForEntropicMethods(methodParameters, self.kernelParams)
             optimizationParams['symbolicField'] = dataHandling.fields[self._pdfArrName]
             methodParameters['fieldName'] = self._pdfArrName
             methodParameters['secondFieldName'] = self._tmpArrName
@@ -82,10 +83,7 @@ class LatticeBoltzmannStep:
             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._sync = dataHandling.synchronizationFunction([self._pdfArrName], methodParameters['stencil'], target)
         self._boundaryHandling = BoundaryHandling(self._lbmKernel.method, self._dataHandling, self._pdfArrName,
                                                   name=name + "_boundaryHandling",
                                                   target=target, openMP=optimizationParams['openMP'])
@@ -98,6 +96,7 @@ class LatticeBoltzmannStep:
         for b in self._dataHandling.iterate():
             b[self.densityDataName].fill(1.0)
             b[self.velocityDataName].fill(0.0)
+        self.setPdfFieldsFromMacroscopicValues()
 
         # -- VTK output
         self.vtkWriter = self.dataHandling.vtkWriter(name + str(LatticeBoltzmannStep.vtkScenarioNrCounter),
@@ -180,7 +179,6 @@ class LatticeBoltzmannStep:
         return SlicedGetter(self.densitySlice)
 
     def preRun(self):
-        self._dataHandling.runKernel(self._setterKernel, **self._kernelParams)
         if self._gpu:
             self._dataHandling.toGpu(self._pdfArrName)
             if self._dataHandling.isOnGpu(self.velocityDataName):
@@ -188,17 +186,20 @@ class LatticeBoltzmannStep:
             if self._dataHandling.isOnGpu(self.densityDataName):
                 self._dataHandling.toGpu(self.densityDataName)
 
+    def setPdfFieldsFromMacroscopicValues(self):
+        self._dataHandling.runKernel(self._setterKernel, **self.kernelParams)
+
     def timeStep(self):
         self._sync()
-        self._boundaryHandling()
-        self._dataHandling.runKernel(self._lbmKernel, **self._kernelParams)
+        self._boundaryHandling(**self.kernelParams)
+        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, **self._kernelParams)
+        self._dataHandling.runKernel(self._getterKernel, **self.kernelParams)
 
     def run(self, timeSteps):
         self.preRun()
diff --git a/phasefield/analytical.py b/phasefield/analytical.py
index 1b996d9e..19faba63 100644
--- a/phasefield/analytical.py
+++ b/phasefield/analytical.py
@@ -73,17 +73,8 @@ def symmetricSymbolicSurfaceTension(i, j):
     return sp.Symbol("%s_%d_%d" % ((surfaceTensionSymbolName, ) + index))
 
 
-def symbolicOrderParameters(numPhases, symbolicLastOrderParameter=False):
-    """
-    Returns a tuple with numPhases entries, where the all but the last are numbered symbols and the last entry
-    is 1 - others
-    """
-    if symbolicLastOrderParameter:
-        return sp.symbols("%s_:%i" % (orderParameterSymbolName, numPhases))
-    else:
-        phi = sp.symbols("%s_:%i" % (orderParameterSymbolName, numPhases - 1))
-        phi = phi + (1 - sum(phi),)  # choose last order parameter as 1 - sum(others)
-        return phi
+def symbolicOrderParameters(numSymbols):
+    return sp.symbols("%s_:%i" % (orderParameterSymbolName, numSymbols))
 
 
 def freeEnergyFunction3Phases(orderParameters=None, interfaceWidth=interfaceWidthSymbol, transformed=True,
@@ -155,10 +146,12 @@ def freeEnergyFunctionalNPhases(numPhases=None, surfaceTensions=symmetricSymboli
     """
     assert not (numPhases is None and orderParameters is None)
     if orderParameters is None:
-        phi = symbolicOrderParameters(numPhases)
+        phi = symbolicOrderParameters(numPhases-1)
     else:
         phi = orderParameters
-        numPhases = len(phi)
+        numPhases = len(phi) + 1
+
+    phi = tuple(phi) + (1 - sum(phi),)
 
     # 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
@@ -195,10 +188,10 @@ def analyticInterfaceProfile(x, interfaceWidth=interfaceWidthSymbol):
     get from the condition :math:`\mu_0 = 0` (thermodynamic equilibrium) for a situation with only a single order
     parameter, i.e. at a transition between two phases.
     >>> numPhases = 4
-    >>> x, phi = sp.Symbol("x"), symbolicOrderParameters(numPhases)
-    >>> F = freeEnergyFunctionalNPhases(numPhases)
+    >>> x, phi = sp.Symbol("x"), symbolicOrderParameters(numPhases-1)
+    >>> F = freeEnergyFunctionalNPhases(orderParameters=phi)
     >>> mu = chemicalPotentialsFromFreeEnergy(F)
-    >>> mu0 = mu[0].subs({p: 0 for p in phi[1:-1]})  # mu[0] as function of one order parameter only
+    >>> mu0 = mu[0].subs({p: 0 for p in phi[1:]})  # mu[0] as function of one order parameter only
     >>> solution = analyticInterfaceProfile(x)
     >>> solutionSubstitution = {phi[0]: solution, Diff(Diff(phi[0])): sp.diff(solution, x, x) }
     >>> sp.expand(mu0.subs(solutionSubstitution))  # inserting solution should solve the mu_0=0 equation
@@ -220,7 +213,7 @@ def chemicalPotentialsFromFreeEnergy(freeEnergy, orderParameters=None):
     return sp.Matrix([functionalDerivative(freeEnergy, op, constants) for op in orderParameters])
 
 
-def createChemicalPotentialEvolutionEquations(freeEnergy, orderParameters, phiField, muField, dx=1):
+def createChemicalPotentialEvolutionEquations(freeEnergy, orderParameters, phiField, muField, dx=1, cse=True):
     """Reads from order parameter (phi) field and updates chemical potentials"""
     chemicalPotential = chemicalPotentialsFromFreeEnergy(freeEnergy, orderParameters)
     laplaceDiscretization = {Diff(Diff(op)): discreteLaplace(phiField, i, dx)
@@ -229,7 +222,7 @@ def createChemicalPotentialEvolutionEquations(freeEnergy, orderParameters, phiFi
     chemicalPotential = chemicalPotential.subs({op: phiField(i) for i, op in enumerate(orderParameters)})
 
     muSweepEqs = [sp.Eq(muField(i), cp) for i, cp in enumerate(chemicalPotential)]
-    return muSweepEqs #sympyCseOnEquationList(muSweepEqs)
+    return muSweepEqs if not cse else sympyCseOnEquationList(muSweepEqs)
 
 
 def createForceUpdateEquations(forceField, phiField, muField, dx=1):
diff --git a/phasefield/scenario.py b/phasefield/scenario.py
deleted file mode 100644
index 8aed3e17..00000000
--- a/phasefield/scenario.py
+++ /dev/null
@@ -1,202 +0,0 @@
-import numpy as np
-
-from lbmpy.boundaries.periodicityhandling import PeriodicityHandling
-from lbmpy.creationfunctions import updateWithDefaultParameters, createLatticeBoltzmannFunction
-from lbmpy.macroscopic_value_kernels import compileMacroscopicValuesSetter
-from lbmpy.phasefield.cahn_hilliard_lbm import createCahnHilliardLbFunction
-from lbmpy.stencils import getStencil
-from lbmpy.updatekernels import createPdfArray
-from pystencils.field import Field, getLayoutOfArray
-from pystencils.slicing import addGhostLayers, removeGhostLayers
-from lbmpy.phasefield.analytical import symbolicOrderParameters, freeEnergyFunctionalNPhases, \
-    createChemicalPotentialEvolutionEquations, createForceUpdateEquations
-
-
-class PhasefieldScenario(object):
-    def __init__(self, domainSize, numPhases, mobilityRelaxationRates=1.1,
-                 surfaceTensionCallback=lambda i, j: 1e-3 if i !=j else 0, interfaceWidth=3, dx=1, gamma=1,
-                 optimizationParams={}, initialVelocity=None, kernelParams={}, **kwargs):
-
-        self.numPhases = numPhases
-        self.timeStepsRun = 0
-        self.domainSize = domainSize
-
-        # ---- Parameter normalization
-        if not hasattr(mobilityRelaxationRates, '__len__'):
-            mobilityRelaxationRates = [mobilityRelaxationRates] * numPhases
-
-        D = len(domainSize)
-
-        ghostLayers = 1
-        domainSizeWithGhostLayer = tuple([s + 2 * ghostLayers for s in domainSize])
-
-        if 'stencil' not in kwargs:
-            kwargs['stencil'] = 'D2Q9' if D == 2 else 'D3Q27'
-
-        methodParameters, optimizationParams = updateWithDefaultParameters(kwargs, optimizationParams)
-
-        stencil = getStencil(methodParameters['stencil'])
-        fieldLayout = optimizationParams['fieldLayout']
-        Q = len(stencil)
-
-        if isinstance(initialVelocity, np.ndarray):
-            assert initialVelocity.shape[-1] == D
-            initialVelocity = addGhostLayers(initialVelocity, indexDimensions=1, ghostLayers=1,
-                                             layout=getLayoutOfArray(self._pdfArrays[0]))
-        elif initialVelocity is None:
-            initialVelocity = [0] * D
-
-        self.kernelParams = kernelParams
-
-        # ---- Arrays
-        self.velArr = np.zeros(domainSizeWithGhostLayer + (D,), order=fieldLayout)
-        self.muArr = np.zeros(domainSizeWithGhostLayer + (numPhases - 1,), order=fieldLayout)
-        self.phiArr = np.zeros(domainSizeWithGhostLayer + (numPhases - 1,), order=fieldLayout)
-        self.forceArr = np.zeros(domainSizeWithGhostLayer + (D,), order=fieldLayout)
-
-        self._pdfArrays = [[createPdfArray(domainSize, Q, layout=optimizationParams['fieldLayout'])
-                            for i in range(numPhases)],
-                           [createPdfArray(domainSize, Q, layout=optimizationParams['fieldLayout'])
-                            for i in range(numPhases)]]
-
-        # ---- Fields
-        velField = Field.createFromNumpyArray('vel', self.velArr, indexDimensions=1)
-        muField = Field.createFromNumpyArray('mu', self.muArr, indexDimensions=1)
-        phiField = Field.createFromNumpyArray('phi', self.phiArr, indexDimensions=1)
-        forceField = Field.createFromNumpyArray('F', self.forceArr, indexDimensions=1)
-
-        orderParameters = symbolicOrderParameters(numPhases)
-        freeEnergy = freeEnergyFunctionalNPhases(numPhases, surfaceTensionCallback, interfaceWidth, orderParameters)
-
-        # ---- Sweeps
-        muSweepEquations = createChemicalPotentialEvolutionEquations(freeEnergy, orderParameters, phiField, muField, dx)
-        forceSweepEquations = createForceUpdateEquations(numPhases, forceField, phiField, muField, dx)
-        if optimizationParams['target'] == 'cpu':
-            from pystencils.cpu import createKernel, makePythonFunction
-            self.muSweep = makePythonFunction(createKernel(muSweepEquations))
-            self.forceSweep = makePythonFunction(createKernel(forceSweepEquations))
-        else:
-            from pystencils.gpucuda import createCUDAKernel, makePythonFunction
-            self.muSweep = makePythonFunction(createCUDAKernel(muSweepEquations))
-            self.forceSweep = makePythonFunction(createCUDAKernel(forceSweepEquations))
-
-        optimizationParams['pdfArr'] = self._pdfArrays[0][0]
-
-        self.lbSweepHydro = createLatticeBoltzmannFunction(force=[forceField(i) for i in range(D)],
-                                                           output={'velocity': velField},
-                                                           optimizationParams=optimizationParams, **kwargs)
-
-        useFdForCahnHilliard = False
-        if useFdForCahnHilliard:
-            dt = 0.01
-            mobility = 1
-            from lbmpy.phasefield.analytical import cahnHilliardFdKernel
-            self.sweepsCH = [cahnHilliardFdKernel(i, phiField, muField, velField, mobility,
-                                                  dx, dt, optimizationParams['target'])
-                             for i in range(numPhases-1)]
-        else:
-            self.sweepsCH = [createCahnHilliardLbFunction(stencil, mobilityRelaxationRates[i],
-                                                          velField, muField(i), phiField(i), optimizationParams, gamma)
-                             for i in range(numPhases-1)]
-
-        self.lbSweeps = [self.lbSweepHydro] + self.sweepsCH
-
-        self._pdfPeriodicityHandler = PeriodicityHandling(self._pdfArrays[0][0].shape, (True, True, True),
-                                                          optimizationParams['target'])
-
-        assert self.muArr.shape == self.phiArr.shape
-        self._muPhiPeriodicityHandler = PeriodicityHandling(self.muArr.shape, (True, True, True),
-                                                            optimizationParams['target'])
-
-        # Pdf array initialization
-        hydroLbmInit = compileMacroscopicValuesSetter(self.lbSweepHydro.method,
-                                                      {'density': 1.0, 'velocity': initialVelocity},
-                                                      pdfArr=self._pdfArrays[0][0], target='cpu')
-        hydroLbmInit(pdfs=self._pdfArrays[0][0], F=self.forceArr, **self.kernelParams)
-        self.initializeCahnHilliardPdfsAccordingToPhi()
-
-        self._nonPdfArrays = {
-            'phiArr': self.phiArr,
-            'muArr': self.muArr,
-            'velArr': self.velArr,
-            'forceArr': self.forceArr,
-        }
-        self._nonPdfGpuArrays = None
-        self._pdfGpuArrays = None
-        self.target = optimizationParams['target']
-
-        self.hydroVelocitySetter = None
-
-    def updateHydroPdfsAccordingToVelocity(self):
-        if self.hydroVelocitySetter is None:
-            self.hydroVelocitySetter = compileMacroscopicValuesSetter(self.lbSweepHydro.method,
-                                                                      {'density': 1.0, 'velocity': self.velArr},
-                                                                      pdfArr=self._pdfArrays[0][0], target='cpu')
-        self.hydroVelocitySetter(pdfs=self._pdfArrays[0][0], F=self.forceArr, **self.kernelParams)
-
-    def _arraysFromCpuToGpu(self):
-        import pycuda.gpuarray as gpuarray
-        if self._nonPdfGpuArrays is None:
-            self._nonPdfGpuArrays = {name: gpuarray.to_gpu(arr) for name, arr in self._nonPdfArrays.items()}
-            self._pdfGpuArrays = [[gpuarray.to_gpu(arr) for arr in self._pdfArrays[0]],
-                                  [gpuarray.to_gpu(arr) for arr in self._pdfArrays[1]]]
-        else:
-            for name, arr in self._nonPdfArrays.items():
-                self._nonPdfGpuArrays[name].set(arr)
-            for i in range(2):
-                for cpuArr, gpuArr in zip(self._pdfArrays[i], self._pdfGpuArrays[i]):
-                    gpuArr.set(cpuArr)
-
-    def _arraysFromGpuToCpu(self):
-        for name, arr in self._nonPdfArrays.items():
-            self._nonPdfGpuArrays[name].get(arr)
-        for cpuArr, gpuArr in zip(self._pdfArrays[0], self._pdfGpuArrays[0]):
-            gpuArr.get(cpuArr)
-
-    def initializeCahnHilliardPdfsAccordingToPhi(self):
-        for i in range(1, self.numPhases):
-            self._pdfArrays[0][i].fill(0)
-            self._pdfArrays[0][i][..., 0] = self.phiArr[..., i-1]
-
-    def gaussianSmoothPhiFields(self, sigma):
-        from scipy.ndimage.filters import gaussian_filter
-        for i in range(self.phiArr.shape[-1]):
-            gaussian_filter(self.phi[..., i], sigma, output=self.phi[..., i], mode='wrap')
-
-    @property
-    def phi(self):
-        return removeGhostLayers(self.phiArr, indexDimensions=1)
-
-    @property
-    def mu(self):
-        return removeGhostLayers(self.muArr, indexDimensions=1)
-
-    @property
-    def velocity(self):
-        return removeGhostLayers(self.velArr, indexDimensions=1)
-
-    def run(self, timeSteps=1):
-        """Run the scenario for the given amount of time steps"""
-        if self.target == 'gpu':
-            self._arraysFromCpuToGpu()
-            self._timeLoop(self._pdfGpuArrays, timeSteps=timeSteps, **self._nonPdfGpuArrays)
-            self._arraysFromGpuToCpu()
-        else:
-            self._timeLoop(self._pdfArrays, timeSteps=timeSteps, **self._nonPdfArrays)
-        self.timeStepsRun += timeSteps
-
-    def _timeLoop(self, pdfArrays, phiArr, muArr, velArr, forceArr, timeSteps):
-        for t in range(timeSteps):
-            self._muPhiPeriodicityHandler(pdfs=phiArr)
-            self.muSweep(phi=phiArr, mu=muArr)
-
-            self._muPhiPeriodicityHandler(pdfs=muArr)
-            self.forceSweep(mu=muArr, phi=phiArr, F=forceArr)
-
-            for src in pdfArrays[0]:
-                self._pdfPeriodicityHandler(pdfs=src)
-
-            for sweep, src, dst in zip(self.lbSweeps, *pdfArrays):
-                sweep(src=src, dst=dst, F=forceArr, phi=phiArr, vel=velArr, mu=muArr)
-
-            pdfArrays[0], pdfArrays[1] = pdfArrays[1], pdfArrays[0]
-- 
GitLab