From ed79ce1b57c0b55a6d3b14d69d044d559b89f258 Mon Sep 17 00:00:00 2001
From: Martin Bauer <bauer_martin@gmx.de>
Date: Tue, 31 Jan 2017 14:47:25 +0100
Subject: [PATCH] new lbm: moment based methods - equivalence to waLBerla
 checked

---
 boundaries/boundaryconditions.py        |  26 ++-
 creationfunctions.py                    |   5 -
 methods/abstractlbmethod.py             |   4 +
 methods/conservedquantitycomputation.py |   9 +-
 methods/cumulantbased.py                |  95 +++++++++++
 methods/momentbased.py                  | 210 ++++++++++++------------
 6 files changed, 226 insertions(+), 123 deletions(-)

diff --git a/boundaries/boundaryconditions.py b/boundaries/boundaryconditions.py
index 9650abdc..d65ad424 100644
--- a/boundaries/boundaryconditions.py
+++ b/boundaries/boundaryconditions.py
@@ -21,13 +21,25 @@ def ubb(pdfField, direction, lbMethod, velocity):
     inverseDir = invDir(direction)
 
     # TODO adapt velocity to force
-    # TODO compute density
-
-    densitySymbol = lbMethod.conservedQuantityComputation.definedSymbols()['density']
-
-    velTerm = 6 * sum([d_i * v_i for d_i, v_i in zip(neighbor, velocity)]) * weightOfDirection(direction)
-    return [sp.Eq(pdfField[neighbor](inverseDir),
-                  pdfField(direction) - velTerm)]
+    c_s_sq = sp.Rational(1, 3)
+    velTerm = 2 / c_s_sq * sum([d_i * v_i for d_i, v_i in zip(neighbor, velocity)]) * weightOfDirection(direction)
+
+    # in conserved value computation
+    # rename what is currently called density to "virtualDensity"
+    # provide a new quantity density, which is constant in case of incompressible models
+    if lbMethod.conservedQuantityComputation._compressible:  # TODO
+        cqc = lbMethod.conservedQuantityComputation
+        densitySymbol = sp.Symbol("rho")
+        pdfFieldAccesses = [pdfField(i) for i in range(len(lbMethod.stencil))]
+        densityEquations = cqc.outputEquationsFromPdfs(pdfFieldAccesses, {'density': densitySymbol})
+        densitySymbol = lbMethod.conservedQuantityComputation.definedSymbols()['density']
+        result = densityEquations.allEquations
+        result += [sp.Eq(pdfField[neighbor](inverseDir),
+                         pdfField(direction) - velTerm * densitySymbol)]
+        return result
+    else:
+        return [sp.Eq(pdfField[neighbor](inverseDir),
+                      pdfField(direction) - velTerm)]
 
 
 def fixedDensity(pdfField, direction, lbMethod, density):
diff --git a/creationfunctions.py b/creationfunctions.py
index 89e59848..245d9f28 100644
--- a/creationfunctions.py
+++ b/creationfunctions.py
@@ -92,11 +92,6 @@ def createLatticeBoltzmannAst(updateRule=None, optimizationParams={}, **kwargs):
         return ValueError("'target' has to be either 'cpu' or 'gpu'")
 
     res.method = updateRule.method
-    #TODO debug begin
-    from pystencils.cpu import generateC
-    from pystencils.display_utils import highlightCpp
-    print(generateC(res))
-    #TODO debug end
     return res
 
 
diff --git a/methods/abstractlbmethod.py b/methods/abstractlbmethod.py
index 201ab054..de64dac5 100644
--- a/methods/abstractlbmethod.py
+++ b/methods/abstractlbmethod.py
@@ -1,8 +1,12 @@
 import abc
 import sympy as sp
+from collections import namedtuple
 from pystencils.equationcollection import EquationCollection
 
 
+RelaxationInfo = namedtuple('Relaxationinfo', ['equilibriumValue', 'relaxationRate'])
+
+
 class LbmCollisionRule(EquationCollection):
     def __init__(self, lbmMethod, *args, **kwargs):
         super(LbmCollisionRule, self).__init__(*args, **kwargs)
diff --git a/methods/conservedquantitycomputation.py b/methods/conservedquantitycomputation.py
index 2cd989ba..40b00fca 100644
--- a/methods/conservedquantitycomputation.py
+++ b/methods/conservedquantitycomputation.py
@@ -165,10 +165,11 @@ class DensityVelocityComputation(AbstractConservedQuantityComputation):
             if tuple(velOutputSymbols) != tuple(self._symbolsOrder1):
                 mainEquations += [sp.Eq(a, b) for a, b in zip(velOutputSymbols, self._symbolsOrder1)]
             else:
-                # TODO
-                pass
+                for u_i in self._symbolsOrder1:
+                    mainEquations.append(sp.Eq(u_i, eqs[u_i]))
+                    del eqs[u_i]
 
-        eqColl = eqColl.copy(mainEquations, eqColl.allEquations)
+        eqColl = eqColl.copy(mainEquations, [sp.Eq(a, b) for a, b in eqs.items()])
         return eqColl.newWithoutUnusedSubexpressions()
 
     def __repr__(self):
@@ -277,8 +278,6 @@ def applyForceModelShift(shiftMemberName, dim, equationCollection, forceModel, c
         return equationCollection
 
 
-
-
 if __name__ == '__main__':
     from lbmpy.creationfunctions import createLatticeBoltzmannMethod
     from lbmpy.simplificationfactory import createSimplificationStrategy
diff --git a/methods/cumulantbased.py b/methods/cumulantbased.py
index e69de29b..86a09e4a 100644
--- a/methods/cumulantbased.py
+++ b/methods/cumulantbased.py
@@ -0,0 +1,95 @@
+import sympy as sp
+from collections import OrderedDict
+from lbmpy.methods.abstractlbmethod import AbstractLbMethod, LbmCollisionRule, RelaxationInfo
+from lbmpy.methods.conservedquantitycomputation import AbstractConservedQuantityComputation
+from lbmpy.moments import MOMENT_SYMBOLS
+from lbmpy.cumulants import cumulantsFromPdfs
+
+
+class CumulantBasedLbMethod(AbstractLbMethod):
+
+    def __init__(self, stencil, cumulantToRelaxationInfoDict, conservedQuantityComputation, forceModel=None):
+        assert isinstance(conservedQuantityComputation, AbstractConservedQuantityComputation)
+        super(CumulantBasedLbMethod, self).__init__(stencil)
+
+        self._forceModel = forceModel
+        self._cumulantToRelaxationInfoDict = OrderedDict(cumulantToRelaxationInfoDict.items())
+        self._conservedQuantityComputation = conservedQuantityComputation
+
+    @property
+    def cumulantToRelaxationInfoDict(self):
+        return self._cumulantToRelaxationInfoDict
+
+    def setFirstMomentRelaxationRate(self, relaxationRate):
+        for e in MOMENT_SYMBOLS[:self.dim]:
+            assert e in self._cumulantToRelaxationInfoDict, "First cumulants are not relaxed separately by this method"
+        for e in MOMENT_SYMBOLS[:self.dim]:
+            prevEntry = self._cumulantToRelaxationInfoDict[e]
+            newEntry = RelaxationInfo(prevEntry[0], relaxationRate)
+            self._cumulantToRelaxationInfoDict[e] = newEntry
+
+    @property
+    def moments(self):
+        return tuple(self._cumulantToRelaxationInfoDict.keys())
+
+    @property
+    def momentEquilibriumValues(self):
+        return tuple([e.equilibriumValue for e in self._cumulantToRelaxationInfoDict.values()])
+
+    @property
+    def relaxationRates(self):
+        return tuple([e.relaxationRate for e in self._cumulantToRelaxationInfoDict.values()])
+
+    def _repr_html_(self):
+        table = """
+        <table style="border:none; width: 100%">
+            <tr {nb}>
+                <th {nb} >Cumulant</th>
+                <th {nb} >Eq. Value </th>
+                <th {nb} >Relaxation Time</th>
+            </tr>
+            {content}
+        </table>
+        """
+        content = ""
+        for cumulant, (eqValue, rr) in self._cumulantToRelaxationInfoDict.items():
+            vals = {
+                'rr': sp.latex(rr),
+                'cumulant': sp.latex(cumulant),
+                'eqValue': sp.latex(eqValue),
+                'nb': 'style="border:none"',
+            }
+            content += """<tr {nb}>
+                            <td {nb}>${cumulant}$</td>
+                            <td {nb}>${eqValue}$</td>
+                            <td {nb}>${rr}$</td>
+                         </tr>\n""".format(**vals)
+        return table.format(content=content, nb='style="border:none"')
+
+    def getEquilibrium(self, conservedQuantityEquations=None):
+        D = sp.eye(len(self.relaxationRates))
+        return self._getCollisionRuleWithRelaxationMatrix(D, conservedQuantityEquations=conservedQuantityEquations)
+
+    def getCollisionRule(self, conservedQuantityEquations=None):
+        # Step 1: transform input into cumulant space
+
+        cumulantsFromPdfs(self.stencil, )
+        # Step 2: create linear transformation from basic cumulants to requested cumulants
+
+        # Step 3: relaxation
+
+        # Step 4: transform back into standard cumulants
+
+        # Step 5: transform cumulants to moments
+
+        # Step 6: transform moments to pdfs
+
+        D = sp.diag(*self.relaxationRates)
+        relaxationRateSubExpressions, D = self._generateRelaxationMatrix(D)
+        eqColl = self._getCollisionRuleWithRelaxationMatrix(D, relaxationRateSubExpressions, conservedQuantityEquations)
+        if self._forceModel is not None:
+            forceModelTerms = self._forceModel(self)
+            newEqs = [sp.Eq(eq.lhs, eq.rhs + fmt) for eq, fmt in zip(eqColl.mainEquations, forceModelTerms)]
+            eqColl = eqColl.copy(newEqs)
+        return eqColl
+
diff --git a/methods/momentbased.py b/methods/momentbased.py
index 34b0988e..abd3903f 100644
--- a/methods/momentbased.py
+++ b/methods/momentbased.py
@@ -1,63 +1,16 @@
 import sympy as sp
 import collections
-from collections import namedtuple, OrderedDict, defaultdict
+from collections import OrderedDict, defaultdict
 
 from lbmpy.stencils import stencilsHaveSameEntries, getStencil
 from lbmpy.maxwellian_equilibrium import getMomentsOfDiscreteMaxwellianEquilibrium, \
     getMomentsOfContinuousMaxwellianEquilibrium
-from lbmpy.methods.abstractlbmethod import AbstractLbMethod, LbmCollisionRule
+from lbmpy.methods.abstractlbmethod import AbstractLbMethod, LbmCollisionRule, RelaxationInfo
 from lbmpy.methods.conservedquantitycomputation import AbstractConservedQuantityComputation, DensityVelocityComputation
 from lbmpy.moments import MOMENT_SYMBOLS, momentMatrix, isShearMoment, \
     isEven, gramSchmidt, getOrder, getDefaultMomentSetForStencil
 from pystencils.sympyextensions import commonDenominator, replaceAdditive
 
-RelaxationInfo = namedtuple('Relaxationinfo', ['equilibriumValue', 'relaxationRate'])
-
-
-def compareMomentBasedLbMethods(reference, other):
-    import ipy_table
-    table = []
-    captionRows = [len(table)]
-    table.append(['Shared Moment', 'ref', 'other', 'difference'])
-
-    referenceMoments = set(reference.moments)
-    otherMoments = set(other.moments)
-    for moment in referenceMoments.intersection(otherMoments):
-        referenceValue = reference.momentToRelaxationInfoDict[moment].equilibriumValue
-        otherValue = other.momentToRelaxationInfoDict[moment].equilibriumValue
-        diff = sp.simplify(referenceValue - otherValue)
-        table.append(["$%s$" % (sp.latex(moment), ),
-                      "$%s$" % (sp.latex(referenceValue), ),
-                      "$%s$" % (sp.latex(otherValue), ),
-                      "$%s$" % (sp.latex(diff),)])
-
-    onlyInRef = referenceMoments - otherMoments
-    if onlyInRef:
-        captionRows.append(len(table))
-        table.append(['Only in Ref', 'value', '', ''])
-        for moment in onlyInRef:
-            val = reference.momentToRelaxationInfoDict[moment].equilibriumValue
-            table.append(["$%s$" % (sp.latex(moment),),
-                          "$%s$" % (sp.latex(val),),
-                          " ", " "])
-
-    onlyInOther = otherMoments - referenceMoments
-    if onlyInOther:
-        captionRows.append(len(table))
-        table.append(['Only in Other', '', 'value', ''])
-        for moment in onlyInOther:
-            val = other.momentToRelaxationInfoDict[moment].equilibriumValue
-            table.append(["$%s$" % (sp.latex(moment),),
-                          " ",
-                          "$%s$" % (sp.latex(val),),
-                          " "])
-
-    tableDisplay = ipy_table.make_table(table)
-    for rowIdx in captionRows:
-        for col in range(4):
-            ipy_table.set_cell_style(rowIdx, col, color='#bbbbbb')
-    return tableDisplay
-
 
 class MomentBasedLbMethod(AbstractLbMethod):
     def __init__(self, stencil, momentToRelaxationInfoDict, conservedQuantityComputation, forceModel=None):
@@ -76,13 +29,13 @@ class MomentBasedLbMethod(AbstractLbMethod):
                                              the symbols used in the equilibrium moments like e.g. density and velocity
         :param forceModel: force model instance, or None if no forcing terms are required
         """
-        super(MomentBasedLbMethod, self).__init__(stencil)
-
         assert isinstance(conservedQuantityComputation, AbstractConservedQuantityComputation)
+        super(MomentBasedLbMethod, self).__init__(stencil)
 
         self._forceModel = forceModel
         self._momentToRelaxationInfoDict = OrderedDict(momentToRelaxationInfoDict.items())
         self._conservedQuantityComputation = conservedQuantityComputation
+        self._weights = None
 
         equilibriumMoments = []
         for moment, relaxInfo in momentToRelaxationInfoDict.items():
@@ -99,45 +52,13 @@ class MomentBasedLbMethod(AbstractLbMethod):
         assert len(undefinedEquilibriumSymbols) == 0, "Undefined symbol(s) in equilibrium moment: %s" % \
                                                       (undefinedEquilibriumSymbols,)
 
-        self._weights = None
-
     @property
     def momentToRelaxationInfoDict(self):
         return self._momentToRelaxationInfoDict
 
-    def setFirstMomentRelaxationRate(self, relaxationRate):
-        for e in MOMENT_SYMBOLS[:self.dim]:
-            assert e in self._momentToRelaxationInfoDict, "First moments are not relaxed separately by this method"
-        for e in MOMENT_SYMBOLS[:self.dim]:
-            prevEntry = self._momentToRelaxationInfoDict[e]
-            newEntry = RelaxationInfo(prevEntry[0], relaxationRate)
-            self._momentToRelaxationInfoDict[e] = newEntry
-
-    def _repr_html_(self):
-        table = """
-        <table style="border:none; width: 100%">
-            <tr {nb}>
-                <th {nb} >Moment</th>
-                <th {nb} >Eq. Value </th>
-                <th {nb} >Relaxation Time</th>
-            </tr>
-            {content}
-        </table>
-        """
-        content = ""
-        for moment, (eqValue, rr) in self._momentToRelaxationInfoDict.items():
-            vals = {
-                'rr': sp.latex(rr),
-                'moment': sp.latex(moment),
-                'eqValue': sp.latex(eqValue),
-                'nb': 'style="border:none"',
-            }
-            content += """<tr {nb}>
-                            <td {nb}>${moment}$</td>
-                            <td {nb}>${eqValue}$</td>
-                            <td {nb}>${rr}$</td>
-                         </tr>\n""".format(**vals)
-        return table.format(content=content, nb='style="border:none"')
+    @property
+    def conservedQuantityComputation(self):
+        return self._conservedQuantityComputation
 
     @property
     def moments(self):
@@ -165,21 +86,6 @@ class MomentBasedLbMethod(AbstractLbMethod):
             self._weights = self._computeWeights()
         return self._weights
 
-    def _computeWeights(self):
-        replacements = self._conservedQuantityComputation.defaultValues
-        eqColl = self.getEquilibrium().copyWithSubstitutionsApplied(replacements).insertSubexpressions()
-        newMainEqs = [sp.Eq(e.lhs,
-                            replaceAdditive(e.rhs, 1, sum(self.preCollisionPdfSymbols), requiredMatchReplacement=1.0))
-                      for e in eqColl.mainEquations]
-        eqColl = eqColl.copy(newMainEqs)
-
-        weights = []
-        for eq in eqColl.mainEquations:
-            value = eq.rhs.expand()
-            assert len(value.atoms(sp.Symbol)) == 0, "Failed to compute weights"
-            weights.append(value)
-        return weights
-
     def getShearRelaxationRate(self):
         """
         Assumes that all shear moments are relaxed with same rate - returns this rate
@@ -213,11 +119,56 @@ class MomentBasedLbMethod(AbstractLbMethod):
             eqColl = eqColl.copy(newEqs)
         return eqColl
 
-    @property
-    def conservedQuantityComputation(self):
-        return self._conservedQuantityComputation
+    def setFirstMomentRelaxationRate(self, relaxationRate):
+        for e in MOMENT_SYMBOLS[:self.dim]:
+            assert e in self._momentToRelaxationInfoDict, "First moments are not relaxed separately by this method"
+        for e in MOMENT_SYMBOLS[:self.dim]:
+            prevEntry = self._momentToRelaxationInfoDict[e]
+            newEntry = RelaxationInfo(prevEntry[0], relaxationRate)
+            self._momentToRelaxationInfoDict[e] = newEntry
+
+    def _repr_html_(self):
+        table = """
+        <table style="border:none; width: 100%">
+            <tr {nb}>
+                <th {nb} >Moment</th>
+                <th {nb} >Eq. Value </th>
+                <th {nb} >Relaxation Time</th>
+            </tr>
+            {content}
+        </table>
+        """
+        content = ""
+        for moment, (eqValue, rr) in self._momentToRelaxationInfoDict.items():
+            vals = {
+                'rr': sp.latex(rr),
+                'moment': sp.latex(moment),
+                'eqValue': sp.latex(eqValue),
+                'nb': 'style="border:none"',
+            }
+            content += """<tr {nb}>
+                            <td {nb}>${moment}$</td>
+                            <td {nb}>${eqValue}$</td>
+                            <td {nb}>${rr}$</td>
+                         </tr>\n""".format(**vals)
+        return table.format(content=content, nb='style="border:none"')
+
+    def _computeWeights(self):
+        replacements = self._conservedQuantityComputation.defaultValues
+        eqColl = self.getEquilibrium().copyWithSubstitutionsApplied(replacements).insertSubexpressions()
+        newMainEqs = [sp.Eq(e.lhs,
+                            replaceAdditive(e.rhs, 1, sum(self.preCollisionPdfSymbols), requiredMatchReplacement=1.0))
+                      for e in eqColl.mainEquations]
+        eqColl = eqColl.copy(newMainEqs)
+
+        weights = []
+        for eq in eqColl.mainEquations:
+            value = eq.rhs.expand()
+            assert len(value.atoms(sp.Symbol)) == 0, "Failed to compute weights"
+            weights.append(value)
+        return weights
 
-    def _getCollisionRuleWithRelaxationMatrix(self, D, additionalSubexpressions=[], conservedQuantityEquations=None):
+    def _getCollisionRuleWithRelaxationMatrix(self, D, additionalSubexpressions=(), conservedQuantityEquations=None):
         f = sp.Matrix(self.preCollisionPdfSymbols)
         M = momentMatrix(self.moments, self.stencil)
         m_eq = sp.Matrix(self.momentEquilibriumValues)
@@ -232,7 +183,7 @@ class MomentBasedLbMethod(AbstractLbMethod):
         simplificationHints.update(self._conservedQuantityComputation.definedSymbols())
         simplificationHints['relaxationRates'] = D.atoms(sp.Symbol)
 
-        allSubexpressions = additionalSubexpressions + conservedQuantityEquations.allEquations
+        allSubexpressions = list(additionalSubexpressions) + conservedQuantityEquations.allEquations
         return LbmCollisionRule(self, collisionEqs, allSubexpressions,
                                 simplificationHints)
 
@@ -517,3 +468,50 @@ def createOrthogonalMRT(stencil, relaxationRateGetter=None, compressible=False,
     return createWithDiscreteMaxwellianEqMoments(stencil, momentToRelaxationRateDict, compressible, forceModel,
                                                  equilibriumAccuracyOrder)
 
+
+# ----------------------------------------- Comparison view for notebooks ----------------------------------------------
+
+
+def compareMomentBasedLbMethods(reference, other):
+    import ipy_table
+    table = []
+    captionRows = [len(table)]
+    table.append(['Shared Moment', 'ref', 'other', 'difference'])
+
+    referenceMoments = set(reference.moments)
+    otherMoments = set(other.moments)
+    for moment in referenceMoments.intersection(otherMoments):
+        referenceValue = reference.momentToRelaxationInfoDict[moment].equilibriumValue
+        otherValue = other.momentToRelaxationInfoDict[moment].equilibriumValue
+        diff = sp.simplify(referenceValue - otherValue)
+        table.append(["$%s$" % (sp.latex(moment), ),
+                      "$%s$" % (sp.latex(referenceValue), ),
+                      "$%s$" % (sp.latex(otherValue), ),
+                      "$%s$" % (sp.latex(diff),)])
+
+    onlyInRef = referenceMoments - otherMoments
+    if onlyInRef:
+        captionRows.append(len(table))
+        table.append(['Only in Ref', 'value', '', ''])
+        for moment in onlyInRef:
+            val = reference.momentToRelaxationInfoDict[moment].equilibriumValue
+            table.append(["$%s$" % (sp.latex(moment),),
+                          "$%s$" % (sp.latex(val),),
+                          " ", " "])
+
+    onlyInOther = otherMoments - referenceMoments
+    if onlyInOther:
+        captionRows.append(len(table))
+        table.append(['Only in Other', '', 'value', ''])
+        for moment in onlyInOther:
+            val = other.momentToRelaxationInfoDict[moment].equilibriumValue
+            table.append(["$%s$" % (sp.latex(moment),),
+                          " ",
+                          "$%s$" % (sp.latex(val),),
+                          " "])
+
+    tableDisplay = ipy_table.make_table(table)
+    for rowIdx in captionRows:
+        for col in range(4):
+            ipy_table.set_cell_style(rowIdx, col, color='#bbbbbb')
+    return tableDisplay
-- 
GitLab