From fd57b76555b3ffe1490eb60cda258de23180182f Mon Sep 17 00:00:00 2001
From: Martin Bauer <martin.bauer@fau.de>
Date: Wed, 25 Jan 2017 12:09:23 +0100
Subject: [PATCH] new lbm module: macroscopic values & bugfixes

---
 boundaries/boundaryconditions.py     | 22 +++++-----
 boundaries/boundaryhandling.py       | 22 +++++-----
 boundaries/createindexlist.py        |  5 ++-
 boundaries/createindexlistcython.pyx |  1 +
 creationfunctions.py                 | 37 +++++++++++------
 macroscopicValueKernels.py           | 62 +++++++++++++++++-----------
 methods/momentbased.py               | 55 +++++++++++++-----------
 updatekernels.py                     |  3 +-
 8 files changed, 122 insertions(+), 85 deletions(-)

diff --git a/boundaries/boundaryconditions.py b/boundaries/boundaryconditions.py
index 293f3ad9..35b3709e 100644
--- a/boundaries/boundaryconditions.py
+++ b/boundaries/boundaryconditions.py
@@ -5,19 +5,19 @@ from pystencils.sympyextensions import getSymmetricPart
 from lbmpy.boundaries.boundaryhandling import offsetFromDir, weightOfDirection, invDir
 
 
-def noSlip(pdfField, direction, lbmMethod):
+def noSlip(pdfField, direction, lbMethod):
     """No-Slip, simple bounce back boundary condition, enforcing zero velocity at obstacle"""
-    neighbor = offsetFromDir(direction, lbmMethod.dim)
+    neighbor = offsetFromDir(direction, lbMethod.dim)
     inverseDir = invDir(direction)
     return [sp.Eq(pdfField[neighbor](inverseDir), pdfField(direction))]
 
 
-def ubb(pdfField, direction, lbmMethod, velocity):
+def ubb(pdfField, direction, lbMethod, velocity):
     """Velocity bounce back boundary condition, enforcing specified velocity at obstacle"""
 
-    assert len(velocity) == lbmMethod.dim, \
-        "Dimension of velocity (%d) does not match dimension of LB method (%d)" % (len(velocity, lbmMethod.dim))
-    neighbor = offsetFromDir(direction, lbmMethod.dim)
+    assert len(velocity) == lbMethod.dim, \
+        "Dimension of velocity (%d) does not match dimension of LB method (%d)" % (len(velocity), lbMethod.dim)
+    neighbor = offsetFromDir(direction, lbMethod.dim)
     inverseDir = invDir(direction)
 
     velTerm = 6 * sum([d_i * v_i for d_i, v_i in zip(neighbor, velocity)]) * weightOfDirection(direction)
@@ -25,21 +25,21 @@ def ubb(pdfField, direction, lbmMethod, velocity):
                   pdfField(direction) - velTerm)]
 
 
-def fixedDensity(pdfField, direction, lbmMethod, density):
+def fixedDensity(pdfField, direction, lbMethod, density):
     """Boundary condition that fixes the density/pressure at the obstacle"""
 
     def removeAsymmetricPartOfMainEquations(eqColl, dofs):
         newMainEquations = [sp.Eq(e.lhs, getSymmetricPart(e.rhs, dofs)) for e in eqColl.mainEquations]
         return eqColl.copy(newMainEquations)
 
-    neighbor = offsetFromDir(direction, lbmMethod.dim)
+    neighbor = offsetFromDir(direction, lbMethod.dim)
     inverseDir = invDir(direction)
 
-    symmetricEq = removeAsymmetricPartOfMainEquations(lbmMethod.getEquilibrium())
-    simplification = createSimplificationStrategy(lbmMethod)
+    symmetricEq = removeAsymmetricPartOfMainEquations(lbMethod.getEquilibrium())
+    simplification = createSimplificationStrategy(lbMethod)
     symmetricEq = simplification(symmetricEq)
 
-    densitySymbol = lbmMethod.conservedQuantityComputation.definedSymbols()['density']
+    densitySymbol = lbMethod.conservedQuantityComputation.definedSymbols()['density']
 
     conditions = [(eq_i.rhs, sp.Equality(direction, i))
                   for i, eq_i in enumerate(symmetricEq.mainEquations)] + [(0, True)]
diff --git a/boundaries/boundaryhandling.py b/boundaries/boundaryhandling.py
index cc2dbb9b..2f6ecd27 100644
--- a/boundaries/boundaryhandling.py
+++ b/boundaries/boundaryhandling.py
@@ -12,13 +12,13 @@ WEIGHTS_SYMBOL = TypedSymbol("weights", "double")
 
 
 class BoundaryHandling:
-    def __init__(self, symbolicPdfField, domainShape, latticeModel, ghostLayers=1, target='cpu'):
+    def __init__(self, symbolicPdfField, domainShape, lbMethod, ghostLayers=1, target='cpu'):
         self._symbolicPdfField = symbolicPdfField
         self._shapeWithGhostLayers = [d + 2 * ghostLayers for d in domainShape]
-        self._fluidFlag = 2 ** 31
+        self._fluidFlag = 2 ** 30
         self.flagField = np.full(self._shapeWithGhostLayers, self._fluidFlag, dtype=np.int32)
         self._ghostLayers = ghostLayers
-        self._latticeModel = latticeModel
+        self._lbMethod = lbMethod
         self._boundaryFunctions = []
         self._nameToIndex = {}
         self._boundarySweeps = []
@@ -30,8 +30,10 @@ class BoundaryHandling:
         if name is None:
             name = boundaryFunction.__name__
 
-        self._nameToIndex[name] = len(self._boundaryFunctions)
+        newIdx = len(self._boundaryFunctions)
+        self._nameToIndex[name] = newIdx
         self._boundaryFunctions.append(boundaryFunction)
+        return 2 ** newIdx
 
     def invalidateIndexCache(self):
         self._boundarySweeps = []
@@ -68,9 +70,9 @@ class BoundaryHandling:
     def prepare(self):
         self.invalidateIndexCache()
         for boundaryIdx, boundaryFunc in enumerate(self._boundaryFunctions):
-            idxField = createBoundaryIndexList(self.flagField, self._latticeModel.stencil,
+            idxField = createBoundaryIndexList(self.flagField, self._lbMethod.stencil,
                                                2 ** boundaryIdx, self._fluidFlag, self._ghostLayers)
-            ast = generateBoundaryHandling(self._symbolicPdfField, idxField, self._latticeModel, boundaryFunc)
+            ast = generateBoundaryHandling(self._symbolicPdfField, idxField, self._lbMethod, boundaryFunc)
 
             if self._target == 'cpu':
                 from pystencils.cpu import makePythonFunction as makePythonCpuFunction
@@ -131,8 +133,8 @@ class LbmMethodInfo(CustomCppCode):
         super(LbmMethodInfo, self).__init__(code, symbolsRead=set(), symbolsDefined=symbolsDefined)
 
 
-def generateBoundaryHandling(pdfField, indexArr, latticeModel, boundaryFunctor):
-    dim = latticeModel.dim
+def generateBoundaryHandling(pdfField, indexArr, lbMethod, boundaryFunctor):
+    dim = lbMethod.dim
 
     cellLoopBody = Block([])
     cellLoop = LoopOverCoordinate(cellLoopBody, coordinateToLoopOver=0, start=0, stop=indexArr.shape[0])
@@ -145,7 +147,7 @@ def generateBoundaryHandling(pdfField, indexArr, latticeModel, boundaryFunctor):
     dirSymbol = TypedSymbol("dir", "int")
     cellLoopBody.append(SympyAssignment(dirSymbol, indexField[0](dim)))
 
-    boundaryEqList = boundaryFunctor(pdfField, dirSymbol, latticeModel)
+    boundaryEqList = boundaryFunctor(pdfField, dirSymbol, lbMethod)
     if type(boundaryEqList) is tuple:
         boundaryEqList, additionalNodes = boundaryEqList
     else:
@@ -168,7 +170,7 @@ def generateBoundaryHandling(pdfField, indexArr, latticeModel, boundaryFunctor):
         for node in additionalNodes:
             loop.body.append(node)
 
-    functionBody.insertFront(LbmMethodInfo(latticeModel))
+    functionBody.insertFront(LbmMethodInfo(lbMethod))
 
     fixedCoordinateMapping = {f.name: coordinateSymbols[:dim] for f in fieldsAccessed}
     resolveFieldAccesses(ast, set(['indexField']), fieldToFixedCoordinates=fixedCoordinateMapping)
diff --git a/boundaries/createindexlist.py b/boundaries/createindexlist.py
index 52b52662..83ce8575 100644
--- a/boundaries/createindexlist.py
+++ b/boundaries/createindexlist.py
@@ -30,10 +30,11 @@ def createBoundaryIndexList(flagField, stencil, boundaryMask, fluidMask, nrOfGho
     if cythonFuncsAvailable:
         stencil = np.array(stencil, dtype=np.int32)
         if len(flagField.shape) == 2:
-            return np.array(createBoundaryIndexList2D(flagField, nrOfGhostLayers, boundaryMask, fluidMask, stencil))
+            idxList = createBoundaryIndexList2D(flagField, nrOfGhostLayers, boundaryMask, fluidMask, stencil)
         elif len(flagField.shape) == 3:
-            return np.array(createBoundaryIndexList3D(flagField, nrOfGhostLayers, boundaryMask, fluidMask, stencil))
+            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=np.int32)
     else:
         return _createBoundaryIndexListPython(flagField, nrOfGhostLayers, boundaryMask, fluidMask, stencil)
diff --git a/boundaries/createindexlistcython.pyx b/boundaries/createindexlistcython.pyx
index 02034844..94acf552 100644
--- a/boundaries/createindexlistcython.pyx
+++ b/boundaries/createindexlistcython.pyx
@@ -4,6 +4,7 @@ ctypedef fused IntegerType:
     short
     int
     long
+    long long
     unsigned short
     unsigned int
     unsigned long
diff --git a/creationfunctions.py b/creationfunctions.py
index 2ed6ec43..1409f05b 100644
--- a/creationfunctions.py
+++ b/creationfunctions.py
@@ -47,10 +47,12 @@ def _getParams(params, optParams):
     return paramsResult, optParamsResult
 
 
-def createLatticeBoltzmannFunction(optimizationParams={}, **kwargs):
+def createLatticeBoltzmannFunction(ast=None, optimizationParams={}, **kwargs):
     params, optParams = _getParams(kwargs, optimizationParams)
 
-    ast = createLatticeBoltzmannKernel(**params, optimizationParams=optParams)
+    if ast is None:
+        ast = createLatticeBoltzmannAst(**params, optimizationParams=optParams)
+
     if params['target'] == 'cpu':
         from pystencils.cpu import makePythonFunction as makePythonCpuFunction, addOpenMP
         if 'openMP' in optParams:
@@ -58,18 +60,22 @@ def createLatticeBoltzmannFunction(optimizationParams={}, **kwargs):
                 addOpenMP(ast)
             elif isinstance(optParams['openMP'], int):
                 addOpenMP(ast, numThreads=optParams['openMP'])
-        return makePythonCpuFunction(ast)
+        res = makePythonCpuFunction(ast)
     elif params['target'] == 'gpu':
         from pystencils.gpucuda import makePythonFunction as makePythonGpuFunction
-        return makePythonGpuFunction(ast)
+        res = makePythonGpuFunction(ast)
     else:
         return ValueError("'target' has to be either 'cpu' or 'gpu'")
 
+    res.method = ast.method
+    return res
+
 
-def createLatticeBoltzmannKernel(optimizationParams={}, **kwargs):
+def createLatticeBoltzmannAst(updateRule=None, optimizationParams={}, **kwargs):
     params, optParams = _getParams(kwargs, optimizationParams)
 
-    updateRule = createLatticeBoltzmannUpdateRule(**params, optimizationParams=optimizationParams)
+    if updateRule is None:
+        updateRule = createLatticeBoltzmannUpdateRule(**params, optimizationParams=optimizationParams)
 
     if params['target'] == 'cpu':
         from pystencils.cpu import createKernel
@@ -77,26 +83,31 @@ def createLatticeBoltzmannKernel(optimizationParams={}, **kwargs):
             splitGroups = updateRule.simplificationHints['splitGroups']
         else:
             splitGroups = ()
-        return createKernel(updateRule.allEquations, splitGroups=splitGroups)
+        res = createKernel(updateRule.allEquations, splitGroups=splitGroups)
     elif params['target'] == 'gpu':
         from pystencils.gpucuda import createCUDAKernel
-        return createCUDAKernel(updateRule.allEquations)
+        res = createCUDAKernel(updateRule.allEquations)
     else:
         return ValueError("'target' has to be either 'cpu' or 'gpu'")
 
+    res.method = updateRule.method
+    return res
 
-def createLatticeBoltzmannUpdateRule(optimizationParams={}, **kwargs):
+
+def createLatticeBoltzmannUpdateRule(lbMethod=None, optimizationParams={}, **kwargs):
     params, optParams = _getParams(kwargs, optimizationParams)
     stencil = getStencil(params['stencil'])
-    method = createLatticeBoltzmannCollisionRule(**params)
+
+    if lbMethod is None:
+        lbMethod = createLatticeBoltzmannMethod(**params)
 
     splitInnerLoop = 'split' in optParams and optParams['split']
 
     dirCSE = 'doCseInOpposingDirections'
     doCseInOpposingDirections = False if dirCSE not in optParams else optParams[dirCSE]
     doOverallCse = False if 'doOverallCse' not in optParams else optParams['doOverallCse']
-    simplification = createSimplificationStrategy(method, doCseInOpposingDirections, doOverallCse, splitInnerLoop)
-    collisionRule = simplification(method.getCollisionRule())
+    simplification = createSimplificationStrategy(lbMethod, doCseInOpposingDirections, doOverallCse, splitInnerLoop)
+    collisionRule = simplification(lbMethod.getCollisionRule())
 
     if 'fieldSize' in optParams and optParams['fieldSize']:
         npField = createPdfArray(optParams['fieldSize'], len(stencil), layout=optParams['fieldLayout'])
@@ -111,7 +122,7 @@ def createLatticeBoltzmannUpdateRule(optimizationParams={}, **kwargs):
     return updateRule
 
 
-def createLatticeBoltzmannCollisionRule(**params):
+def createLatticeBoltzmannMethod(**params):
     params, _ = _getParams(params, {})
 
     stencil = getStencil(params['stencil'])
diff --git a/macroscopicValueKernels.py b/macroscopicValueKernels.py
index 2e911a4e..ea03843b 100644
--- a/macroscopicValueKernels.py
+++ b/macroscopicValueKernels.py
@@ -1,7 +1,7 @@
 import sympy as sp
-
-from lbmpy.simplificationfactory import createSimplificationStrategy
+from copy import deepcopy
 from pystencils.field import Field
+from lbmpy.simplificationfactory import createSimplificationStrategy
 
 
 def compileMacroscopicValuesGetter(lbMethod, outputQuantities, pdfArr=None, fieldLayout='numpy', target='cpu'):
@@ -75,6 +75,9 @@ def compileMacroscopicValuesSetter(lbMethod, quantitiesToSet, pdfArr=None, field
     :param lbMethod: instance of :class:`lbmpy.methods.AbstractLbMethod`
     :param quantitiesToSet: map from conserved quantity name to fixed value or numpy array
     :param pdfArr: optional numpy array for pdf field - used to get optimal loop structure for kernel
+    :param fieldLayout: layout of the pdf field if pdfArr was not given
+    :param target: 'cpu' or 'gpu'
+    :return: function taking pdf array as single argument and which sets the field to the given values
     """
     if pdfArr is not None:
         pdfField = Field.createFromNumpyArray('pdfs', pdfArr, indexDimensions=1)
@@ -82,22 +85,28 @@ def compileMacroscopicValuesSetter(lbMethod, quantitiesToSet, pdfArr=None, field
         pdfField = Field.createGeneric('pdfs', lbMethod.dim, indexDimensions=1, layout=fieldLayout)
 
     fixedKernelParameters = {}
+    cqc = lbMethod.conservedQuantityComputation
 
     valueMap = {}
     atLeastOneFieldInput = False
     for quantityName, value in quantitiesToSet.items():
         if hasattr(value, 'shape'):
             fixedKernelParameters[quantityName] = value
-            value = Field.createFromNumpyArray(quantityName, value)
             atLeastOneFieldInput = True
+            numComponents = cqc.conservedQuantities[quantityName]
+            field = Field.createFromNumpyArray(quantityName, value, indexDimensions=0 if numComponents <= 1 else 1)
+            if numComponents == 1:
+                value = field(0)
+            else:
+                value = [field(i) for i in range(numComponents)]
+
         valueMap[quantityName] = value
 
-    cqc = lbMethod.conservedQuantityComputation
     cqEquations = cqc.equilibriumInputEquationsFromInitValues(**valueMap)
 
     eq = lbMethod.getEquilibrium(conservedQuantityEquations=cqEquations)
     if atLeastOneFieldInput:
-        simplification = createSimplificationStrategy(eq)
+        simplification = createSimplificationStrategy(lbMethod)
         eq = simplification(eq)
     else:
         eq = eq.insertSubexpressions()
@@ -123,32 +132,37 @@ def compileMacroscopicValuesSetter(lbMethod, quantitiesToSet, pdfArr=None, field
     return setter
 
 
-def compileAdvancedVelocitySetter(collisionRule, velocityArray, pdfArr=None):
+def compileAdvancedVelocitySetter(lbMethod, velocityArray, velocityRelaxationRate=1.3, pdfArr=None):
     """
     Advanced initialization of velocity field through iteration procedure according to
     Mei, Luo, Lallemand and Humieres: Consistent initial conditions for LBM simulations, 2005
 
-    Important: this procedure only works if a non-zero relaxation rate was used for the velocity moments!
-
-    :param collisionRule: unsimplified collision rule
+    :param lbMethod:
     :param velocityArray: array with velocity field
-    :param pdfArr: optional array, to compile kernel with fixed layout and shape
-    :return: function, that has to be called multiple times, with a pdf field (src/dst) until convergence
-             similar to the normal streamCollide step, also with boundary handling
+    :param velocityRelaxationRate: relaxation rate for the velocity moments - determines convergence behaviour
+                                   of the initialization scheme
+    :return: collision rule
     """
-    velocityField = Field.createFromNumpyArray('vel', velocityArray, indexDimensions=1)
+    velocityField = Field.createFromNumpyArray('velInput', velocityArray, indexDimensions=1)
+
+    cqc = lbMethod.conservedQuantityComputation
+    densitySymbol = cqc.definedSymbols(order=0)[1]
+    velocitySymbols = cqc.definedSymbols(order=1)[1]
+
+    # density is computed from pdfs
+    eqInputFromPdfs = cqc.equilibriumInputEquationsFromPdfs(lbMethod.preCollisionPdfSymbols)
+    eqInputFromPdfs = eqInputFromPdfs.extract([densitySymbol])
+    # velocity is read from input field
+    velSymbols = [velocityField(i) for i in range(lbMethod.dim)]
+    eqInputFromField = cqc.equilibriumInputEquationsFromInitValues(velocity=velSymbols)
+    eqInputFromField = eqInputFromField.extract(velocitySymbols)
+    # then both are merged together
+    eqInput = eqInputFromPdfs.merge(eqInputFromField)
 
-    # create normal LBM kernel and replace velocity by expressions of velocity field
-    from lbmpy_old.simplifications import sympyCSE
-    latticeModel = collisionRule.latticeModel
-    collisionRule = sympyCSE(collisionRule)
-    collisionRule = streamPullWithSourceAndDestinationFields(collisionRule, pdfArr)
+    # set first order relaxation rate
+    lbMethod = deepcopy(lbMethod)
+    lbMethod.setFirstMomentRelaxationRate(velocityRelaxationRate)
 
-    replacements = {u_i: sp.Eq(u_i, velocityField(i)) for i, u_i in enumerate(latticeModel.symbolicVelocity)}
+    return lbMethod.getCollisionRule(eqInput)
 
-    newSubExpressions = [replacements[eq.lhs] if eq.lhs in replacements else eq for eq in collisionRule.subexpressions]
-    newCollisionRule = LbmCollisionRule(collisionRule.updateEquations, newSubExpressions,
-                                        latticeModel, collisionRule.updateEquationDirections)
-    kernelAst = createKernel(newCollisionRule.equations)
-    return makePythonFunction(kernelAst, {'vel': velocityArray})
 
diff --git a/methods/momentbased.py b/methods/momentbased.py
index 05075f76..0495980d 100644
--- a/methods/momentbased.py
+++ b/methods/momentbased.py
@@ -35,23 +35,14 @@ class MomentBasedLbMethod(AbstractLbMethod):
 
         assert isinstance(conservedQuantityComputation, AbstractConservedQuantityComputation)
 
-        moments = []
-        relaxationRates = []
-        equilibriumMoments = []
-        for moment, relaxInfo in momentToRelaxationInfoDict.items():
-            moments.append(moment)
-            relaxationRates.append(relaxInfo.relaxationRate)
-            equilibriumMoments.append(relaxInfo.equilibriumValue)
-
         self._forceModel = forceModel
-        self._moments = moments
-        self._momentToRelaxationInfoDict = momentToRelaxationInfoDict
-        self._momentMatrix = momentMatrix(moments, self.stencil)
-        self._relaxationRates = sp.Matrix(relaxationRates)
-        self._equilibriumMoments = sp.Matrix(equilibriumMoments)
+        self._momentToRelaxationInfoDict = OrderedDict(momentToRelaxationInfoDict.items())
         self._conservedQuantityComputation = conservedQuantityComputation
 
-        symbolsInEquilibriumMoments = self._equilibriumMoments.atoms(sp.Symbol)
+        equilibriumMoments = []
+        for moment, relaxInfo in momentToRelaxationInfoDict.items():
+            equilibriumMoments.append(relaxInfo.equilibriumValue)
+        symbolsInEquilibriumMoments = sp.Matrix(equilibriumMoments).atoms(sp.Symbol)
         conservedQuantities = set()
         for v in self._conservedQuantityComputation.definedSymbols().values():
             if isinstance(v, collections.Sequence):
@@ -65,6 +56,14 @@ class MomentBasedLbMethod(AbstractLbMethod):
 
         self._weights = None
 
+    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%">
@@ -77,7 +76,7 @@ class MomentBasedLbMethod(AbstractLbMethod):
         </table>
         """
         content = ""
-        for rr, moment, eqValue in zip(self._relaxationRates, self._moments, self._equilibriumMoments):
+        for moment, (eqValue, rr) in self._momentToRelaxationInfoDict.items():
             vals = {
                 'rr': sp.latex(rr),
                 'moment': sp.latex(moment),
@@ -93,7 +92,15 @@ class MomentBasedLbMethod(AbstractLbMethod):
 
     @property
     def moments(self):
-        return self._moments
+        return tuple(self._momentToRelaxationInfoDict.keys())
+
+    @property
+    def momentEquilibriumValues(self):
+        return tuple([e.equilibriumValue for e in self._momentToRelaxationInfoDict.values()])
+
+    @property
+    def relaxationRates(self):
+        return tuple([e.relaxationRate for e in self._momentToRelaxationInfoDict.values()])
 
     @property
     def zerothOrderEquilibriumMomentSymbol(self, ):
@@ -144,13 +151,13 @@ class MomentBasedLbMethod(AbstractLbMethod):
                                           "Can not determine their relaxation rate automatically")
 
     def getEquilibrium(self, conservedQuantityEquations=None):
-        D = sp.eye(len(self._relaxationRates))
+        D = sp.eye(len(self.relaxationRates))
         return self._getCollisionRuleWithRelaxationMatrix(D, conservedQuantityEquations=conservedQuantityEquations)
 
-    def getCollisionRule(self):
-        D = sp.diag(*self._relaxationRates)
+    def getCollisionRule(self, conservedQuantityEquations=None):
+        D = sp.diag(*self.relaxationRates)
         relaxationRateSubExpressions, D = self._generateRelaxationMatrix(D)
-        eqColl = self._getCollisionRuleWithRelaxationMatrix(D, relaxationRateSubExpressions)
+        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)]
@@ -163,8 +170,8 @@ class MomentBasedLbMethod(AbstractLbMethod):
 
     def _getCollisionRuleWithRelaxationMatrix(self, D, additionalSubexpressions=[], conservedQuantityEquations=None):
         f = sp.Matrix(self.preCollisionPdfSymbols)
-        M = self._momentMatrix
-        m_eq = self._equilibriumMoments
+        M = momentMatrix(self.moments, self.stencil)
+        m_eq = sp.Matrix(self.momentEquilibriumValues)
 
         collisionRule = f + M.inv() * D * (m_eq - M * f)
         collisionEqs = [sp.Eq(lhs, rhs) for lhs, rhs in zip(self.postCollisionPdfSymbols, collisionRule)]
@@ -316,7 +323,7 @@ def createSRT(stencil, relaxationRate, compressible=False, forceModel=None, equi
     :return: :class:`lbmpy.methods.MomentBasedLbmMethod` instance
     """
     moments = getDefaultMomentSetForStencil(stencil)
-    rrDict = {m: relaxationRate for m in moments}
+    rrDict = OrderedDict([(m, relaxationRate) for m in moments])
     return createWithDiscreteMaxwellianEqMoments(stencil, rrDict, compressible, forceModel, equilibriumAccuracyOrder)
 
 
@@ -332,7 +339,7 @@ def createTRT(stencil, relaxationRateEvenMoments, relaxationRateOddMoments, comp
     If unsure how to choose the odd relaxation rate, use the function :func:`lbmpy.methods.createTRTWithMagicNumber`.
     """
     moments = getDefaultMomentSetForStencil(stencil)
-    rrDict = {m: relaxationRateEvenMoments if isEven(m) else relaxationRateOddMoments for m in moments}
+    rrDict = OrderedDict([(m, relaxationRateEvenMoments if isEven(m) else relaxationRateOddMoments) for m in moments])
     return createWithDiscreteMaxwellianEqMoments(stencil, rrDict, compressible, forceModel, equilibriumAccuracyOrder)
 
 
diff --git a/updatekernels.py b/updatekernels.py
index b3607e00..cab9c760 100644
--- a/updatekernels.py
+++ b/updatekernels.py
@@ -57,7 +57,8 @@ def createStreamPullKernel(collisionRule, numpyField=None, srcFieldName="src", d
     """
     dim = collisionRule.method.dim
     if numpyField is not None:
-        assert len(numpyField.shape) == dim + 1
+        assert len(numpyField.shape) == dim + 1, "Field dimension mismatch: dimension is %s, should be %d" % \
+                                                 (len(numpyField.shape), dim + 1)
 
     if numpyField is None:
         src = Field.createGeneric(srcFieldName, dim, indexDimensions=1, layout=genericLayout, dtype=genericFieldType)
-- 
GitLab