diff --git a/boundaries/boundaryhandling.py b/boundaries/boundaryhandling.py index 2f6ecd27dbeacfbc9f1e540a4d081e1cb300091d..14e40146d7509f3ee2b28dda456a57151508ac5c 100644 --- a/boundaries/boundaryhandling.py +++ b/boundaries/boundaryhandling.py @@ -2,7 +2,7 @@ import sympy as sp import numpy as np from pystencils import TypedSymbol, Field from pystencils.backends.cbackend import CustomCppCode -from pystencils.ast import Block, SympyAssignment, LoopOverCoordinate, KernelFunction +from pystencils.astnodes import Block, SympyAssignment, LoopOverCoordinate, KernelFunction from pystencils.transformations import moveConstantsBeforeLoop, resolveFieldAccesses, typingFromSympyInspection, \ typeAllEquations from lbmpy.boundaries.createindexlist import createBoundaryIndexList diff --git a/creationfunctions.py b/creationfunctions.py index 245d9f2804124da8d3be9a6227f25559a8f9bebb..6d2e8121ff52a452c25510d7400e3aa0451beae5 100644 --- a/creationfunctions.py +++ b/creationfunctions.py @@ -4,7 +4,7 @@ Factory functions for standard LBM methods import sympy as sp from copy import copy from lbmpy.stencils import getStencil -from lbmpy.methods.momentbased import createSRT, createTRT, createOrthogonalMRT +from lbmpy.methods import createSRT, createTRT, createOrthogonalMRT import lbmpy.forcemodels as forceModels from lbmpy.simplificationfactory import createSimplificationStrategy from lbmpy.updatekernels import createStreamPullKernel, createPdfArray @@ -19,6 +19,8 @@ def _getParams(params, optParams): 'compressible': False, 'order': 2, + 'useContinuousMaxwellianEquilibrium': False, + 'cumulant': False, 'forceModel': 'none', # can be 'simple', 'luo' or 'guo' 'force': (0, 0, 0), } @@ -148,6 +150,8 @@ def createLatticeBoltzmannMethod(**params): 'compressible': params['compressible'], 'equilibriumAccuracyOrder': params['order'], 'forceModel': forceModel, + 'useContinuousMaxwellianEquilibrium': params['useContinuousMaxwellianEquilibrium'], + 'cumulant': params['cumulant'], } methodName = params['method'] relaxationRates = params['relaxationRates'] diff --git a/methods/__init__.py b/methods/__init__.py index f5231bd7ed67bb704e7b5164b649c5f9243743f0..cf454ae62242ca87ce713d80d8089e760114dfbe 100644 --- a/methods/__init__.py +++ b/methods/__init__.py @@ -1,5 +1,5 @@ from lbmpy.methods.abstractlbmethod import AbstractLbMethod from lbmpy.methods.momentbased import MomentBasedLbMethod, RelaxationInfo -from lbmpy.methods.momentbased import createSRT, createTRT, createTRTWithMagicNumber, createOrthogonalMRT, \ +from lbmpy.methods.creationfunctions import createSRT, createTRT, createTRTWithMagicNumber, createOrthogonalMRT, \ createWithContinuousMaxwellianEqMoments, createWithDiscreteMaxwellianEqMoments from lbmpy.methods.conservedquantitycomputation import AbstractConservedQuantityComputation, DensityVelocityComputation diff --git a/methods/conservedquantitycomputation.py b/methods/conservedquantitycomputation.py index daf6bbf6d662611cbfb8697739e4466085705fb1..340605285f033d86c784a1aea95048a43f577a27 100644 --- a/methods/conservedquantitycomputation.py +++ b/methods/conservedquantitycomputation.py @@ -108,7 +108,7 @@ class DensityVelocityComputation(AbstractConservedQuantityComputation): return self._symbolOrder0 @property - def firstOrderMomentSymbol(self): + def firstOrderMomentSymbols(self): return self._symbolsOrder1 @property diff --git a/methods/creationfunctions.py b/methods/creationfunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..12a84f339ee1e1b1570fe488c18d60edfa9c6ff0 --- /dev/null +++ b/methods/creationfunctions.py @@ -0,0 +1,335 @@ +import sympy as sp +from collections import OrderedDict, defaultdict +from lbmpy.methods.cumulantbased import CumulantBasedLbMethod +from lbmpy.methods.momentbased import MomentBasedLbMethod +from lbmpy.stencils import stencilsHaveSameEntries, getStencil +from lbmpy.moments import isEven, gramSchmidt, getDefaultMomentSetForStencil, MOMENT_SYMBOLS, getOrder, isShearMoment +from pystencils.sympyextensions import commonDenominator +from lbmpy.methods.conservedquantitycomputation import DensityVelocityComputation +from lbmpy.methods.abstractlbmethod import RelaxationInfo +from lbmpy.maxwellian_equilibrium import getMomentsOfDiscreteMaxwellianEquilibrium, \ + getMomentsOfContinuousMaxwellianEquilibrium, getCumulantsOfDiscreteMaxwellianEquilibrium, \ + getCumulantsOfContinuousMaxwellianEquilibrium + + +def createWithDiscreteMaxwellianEqMoments(stencil, momentToRelaxationRateDict, compressible=False, forceModel=None, + equilibriumAccuracyOrder=2, cumulant=False): + r""" + Creates a moment-based LBM by taking a list of moments with corresponding relaxation rate. These moments are + relaxed against the moments of the discrete Maxwellian distribution. + + :param stencil: nested tuple defining the discrete velocity space. See `func:lbmpy.stencils.getStencil` + :param momentToRelaxationRateDict: dict that has as many entries as the stencil. Each moment, which can be + represented by an exponent tuple or in polynomial form + (see `lbmpy.moments`), is mapped to a relaxation rate. + :param compressible: incompressible LBM methods split the density into :math:`\rho = \rho_0 + \Delta \rho` + where :math:`\rho_0` is chosen as one, and the first moment of the pdfs is :math:`\Delta \rho` . + This approximates the incompressible Navier-Stokes equations better than the standard + compressible model. + :param forceModel: force model instance, or None if no external forces + :param equilibriumAccuracyOrder: approximation order of macroscopic velocity :math:`\mathbf{u}` in the equilibrium + :param cumulant: if True relax cumulants instead of moments + :return: :class:`lbmpy.methods.MomentBasedLbmMethod` instance + """ + momToRrDict = OrderedDict(momentToRelaxationRateDict) + assert len(momToRrDict) == len(stencil), \ + "The number of moments has to be the same as the number of stencil entries" + + densityVelocityComputation = DensityVelocityComputation(stencil, compressible, forceModel) + + if cumulant: + eqValues = getCumulantsOfDiscreteMaxwellianEquilibrium(stencil, list(momToRrDict.keys()), + c_s_sq=sp.Rational(1, 3), compressible=compressible, + order=equilibriumAccuracyOrder) + else: + eqValues = getMomentsOfDiscreteMaxwellianEquilibrium(stencil, list(momToRrDict.keys()), + c_s_sq=sp.Rational(1, 3), compressible=compressible, + order=equilibriumAccuracyOrder) + + rrDict = OrderedDict([(mom, RelaxationInfo(eqMom, rr)) + for mom, rr, eqMom in zip(momToRrDict.keys(), momToRrDict.values(), eqValues)]) + if cumulant: + return CumulantBasedLbMethod(stencil, rrDict, densityVelocityComputation, forceModel) + else: + return MomentBasedLbMethod(stencil, rrDict, densityVelocityComputation, forceModel) + + +def createWithContinuousMaxwellianEqMoments(stencil, momentToRelaxationRateDict, compressible=False, forceModel=None, + equilibriumAccuracyOrder=2, cumulant=False): + r""" + Creates a moment-based LBM by taking a list of moments with corresponding relaxation rate. These moments are + relaxed against the moments of the continuous Maxwellian distribution. + For parameter description see :func:`lbmpy.methods.createWithDiscreteMaxwellianEqMoments`. + By using the continuous Maxwellian we automatically get a compressible model. + """ + momToRrDict = OrderedDict(momentToRelaxationRateDict) + assert len(momToRrDict) == len( + stencil), "The number of moments has to be the same as the number of stencil entries" + dim = len(stencil[0]) + densityVelocityComputation = DensityVelocityComputation(stencil, True, forceModel) + + if cumulant: + eqValues = getCumulantsOfContinuousMaxwellianEquilibrium(list(momToRrDict.keys()), dim, + c_s_sq=sp.Rational(1, 3), + order=equilibriumAccuracyOrder) + else: + eqValues = getMomentsOfContinuousMaxwellianEquilibrium(list(momToRrDict.keys()), dim, c_s_sq=sp.Rational(1, 3), + order=equilibriumAccuracyOrder) + + if not compressible: + rho = densityVelocityComputation.definedSymbols(order=0)[1] + u = densityVelocityComputation.definedSymbols(order=1)[1] + eqValues = [compressibleToIncompressibleMomentValue(em, rho, u) for em in eqValues] + + rrDict = OrderedDict([(mom, RelaxationInfo(eqMom, rr)) + for mom, rr, eqMom in zip(momToRrDict.keys(), momToRrDict.values(), eqValues)]) + if cumulant: + return CumulantBasedLbMethod(stencil, rrDict, densityVelocityComputation, forceModel) + else: + return MomentBasedLbMethod(stencil, rrDict, densityVelocityComputation, forceModel) + + +# ------------------------------------ SRT / TRT/ MRT Creators --------------------------------------------------------- + + +def createSRT(stencil, relaxationRate, useContinuousMaxwellianEquilibrium=False, **kwargs): + r""" + Creates a single relaxation time (SRT) lattice Boltzmann model also known as BGK model. + + :param stencil: nested tuple defining the discrete velocity space. See :func:`lbmpy.stencils.getStencil` + :param relaxationRate: relaxation rate (inverse of the relaxation time) + usually called :math:`\omega` in LBM literature + :return: :class:`lbmpy.methods.MomentBasedLbmMethod` instance + """ + moments = getDefaultMomentSetForStencil(stencil) + rrDict = OrderedDict([(m, relaxationRate) for m in moments]) + if useContinuousMaxwellianEquilibrium: + return createWithContinuousMaxwellianEqMoments(stencil, rrDict, **kwargs) + else: + return createWithDiscreteMaxwellianEqMoments(stencil, rrDict, **kwargs) + + +def createTRT(stencil, relaxationRateEvenMoments, relaxationRateOddMoments, + useContinuousMaxwellianEquilibrium=False, **kwargs): + """ + Creates a two relaxation time (TRT) lattice Boltzmann model, where even and odd moments are relaxed differently. + In the SRT model the exact wall position of no-slip boundaries depends on the viscosity, the TRT method does not + have this problem. + + Parameters are similar to :func:`lbmpy.methods.createSRT`, but instead of one relaxation rate there are + two relaxation rates: one for even moments (determines viscosity) and one for odd moments. + If unsure how to choose the odd relaxation rate, use the function :func:`lbmpy.methods.createTRTWithMagicNumber`. + """ + moments = getDefaultMomentSetForStencil(stencil) + rrDict = OrderedDict([(m, relaxationRateEvenMoments if isEven(m) else relaxationRateOddMoments) for m in moments]) + if useContinuousMaxwellianEquilibrium: + return createWithContinuousMaxwellianEqMoments(stencil, rrDict, **kwargs) + else: + return createWithDiscreteMaxwellianEqMoments(stencil, rrDict, **kwargs) + + +def createTRTWithMagicNumber(stencil, relaxationRate, magicNumber=sp.Rational(3, 16), **kwargs): + """ + Creates a two relaxation time (TRT) lattice Boltzmann method, where the relaxation time for odd moments is + determines from the even moment relaxation time and a "magic number". + For possible parameters see :func:`lbmpy.methods.createTRT` + """ + rrOdd = relaxationRateFromMagicNumber(relaxationRate, magicNumber) + return createTRT(stencil, relaxationRateEvenMoments=relaxationRate, relaxationRateOddMoments=rrOdd, **kwargs) + + +def createOrthogonalMRT(stencil, relaxationRateGetter=None, useContinuousMaxwellianEquilibrium=False, **kwargs): + r""" + Returns a orthogonal multi-relaxation time model for the stencils D2Q9, D3Q15, D3Q19 and D3Q27. + These MRT methods are just one specific version - there are many MRT methods possible for all these stencils + which differ by the linear combination of moments and the grouping into equal relaxation times. + To create a generic MRT method use :func:`lbmpy.methods.createWithDiscreteMaxwellianEqMoments` + + :param stencil: nested tuple defining the discrete velocity space. See `func:lbmpy.stencils.getStencil` + :param relaxationRateGetter: function getting a list of moments as argument, returning the associated relaxation + time. The default returns: + + - 0 for moments of order 0 and 1 (conserved) + - :math:`\omega`: from moments of order 2 (rate that determines viscosity) + - numbered :math:`\omega_i` for the rest + """ + if relaxationRateGetter is None: + relaxationRateGetter = defaultRelaxationRateNames() + + x, y, z = MOMENT_SYMBOLS + one = sp.Rational(1, 1) + + momentToRelaxationRateDict = OrderedDict() + if stencilsHaveSameEntries(stencil, getStencil("D2Q9")): + moments = getDefaultMomentSetForStencil(stencil) + orthogonalMoments = gramSchmidt(moments, stencil) + orthogonalMomentsScaled = [e * commonDenominator(e) for e in orthogonalMoments] + nestedMoments = list(sortMomentsIntoGroupsOfSameOrder(orthogonalMomentsScaled).values()) + elif stencilsHaveSameEntries(stencil, getStencil("D3Q15")): + sq = x ** 2 + y ** 2 + z ** 2 + nestedMoments = [ + [one, x, y, z], # [0, 3, 5, 7] + [sq - 1], # [1] + [3 * sq ** 2 - 6 * sq + 1], # [2] + [(3 * sq - 5) * x, (3 * sq - 5) * y, (3 * sq - 5) * z], # [4, 6, 8] + [3 * x ** 2 - sq, y ** 2 - z ** 2, x * y, y * z, x * z], # [9, 10, 11, 12, 13] + [x * y * z] + ] + elif stencilsHaveSameEntries(stencil, getStencil("D3Q19")): + sq = x ** 2 + y ** 2 + z ** 2 + nestedMoments = [ + [one, x, y, z], # [0, 3, 5, 7] + [sq - 1], # [1] + [3 * sq ** 2 - 6 * sq + 1], # [2] + [(3 * sq - 5) * x, (3 * sq - 5) * y, (3 * sq - 5) * z], # [4, 6, 8] + [3 * x ** 2 - sq, y ** 2 - z ** 2, x * y, y * z, x * z], # [9, 11, 13, 14, 15] + [(2 * sq - 3) * (3 * x ** 2 - sq), (2 * sq - 3) * (y ** 2 - z ** 2)], # [10, 12] + [(y ** 2 - z ** 2) * x, (z ** 2 - x ** 2) * y, (x ** 2 - y ** 2) * z] # [16, 17, 18] + ] + elif stencilsHaveSameEntries(stencil, getStencil("D3Q27")): + xsq, ysq, zsq = x ** 2, y ** 2, z ** 2 + allMoments = [ + sp.Rational(1, 1), # 0 + x, y, z, # 1, 2, 3 + x * y, x * z, y * z, # 4, 5, 6 + xsq - ysq, # 7 + (xsq + ysq + zsq) - 3 * zsq, # 8 + (xsq + ysq + zsq) - 2, # 9 + 3 * (x * ysq + x * zsq) - 4 * x, # 10 + 3 * (xsq * y + y * zsq) - 4 * y, # 11 + 3 * (xsq * z + ysq * z) - 4 * z, # 12 + x * ysq - x * zsq, # 13 + xsq * y - y * zsq, # 14 + xsq * z - ysq * z, # 15 + x * y * z, # 16 + 3 * (xsq * ysq + xsq * zsq + ysq * zsq) - 4 * (xsq + ysq + zsq) + 4, # 17 + 3 * (xsq * ysq + xsq * zsq - 2 * ysq * zsq) - 2 * (2 * xsq - ysq - zsq), # 18 + 3 * (xsq * ysq - xsq * zsq) - 2 * (ysq - zsq), # 19 + 3 * (xsq * y * z) - 2 * (y * z), # 20 + 3 * (x * ysq * z) - 2 * (x * z), # 21 + 3 * (x * y * zsq) - 2 * (x * y), # 22 + 9 * (x * ysq * zsq) - 6 * (x * ysq + x * zsq) + 4 * x, # 23 + 9 * (xsq * y * zsq) - 6 * (xsq * y + y * zsq) + 4 * y, # 24 + 9 * (xsq * ysq * z) - 6 * (xsq * z + ysq * z) + 4 * z, # 25 + 27 * (xsq * ysq * zsq) - 18 * (xsq * ysq + xsq * zsq + ysq * zsq) + 12 * (xsq + ysq + zsq) - 8, # 26 + ] + nestedMoments = list(sortMomentsIntoGroupsOfSameOrder(allMoments).values()) + else: + raise NotImplementedError("No MRT model is available (yet) for this stencil. " + "Create a custom MRT using 'createWithDiscreteMaxwellianEqMoments'") + + for momentList in nestedMoments: + rr = relaxationRateGetter(momentList) + for m in momentList: + momentToRelaxationRateDict[m] = rr + + if useContinuousMaxwellianEquilibrium: + return createWithContinuousMaxwellianEqMoments(stencil, momentToRelaxationRateDict, **kwargs) + else: + return createWithDiscreteMaxwellianEqMoments(stencil, momentToRelaxationRateDict, **kwargs) + + +# ----------------------------------------- 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 + + +# ------------------------------------ Helper Functions ---------------------------------------------------------------- + + +def sortMomentsIntoGroupsOfSameOrder(moments): + """Returns a dictionary mapping the order (int) to a list of moments with that order.""" + result = defaultdict(list) + for i, moment in enumerate(moments): + order = getOrder(moment) + result[order].append(moment) + return result + + +def defaultRelaxationRateNames(): + nextIndex = 0 + + def result(momentList): + nonlocal nextIndex + shearMomentInside = False + allConservedMoments = True + for m in momentList: + if isShearMoment(m): + shearMomentInside = True + if not (getOrder(m) == 0 or getOrder(m) == 1): + allConservedMoments = False + + if shearMomentInside: + return sp.Symbol("omega") + elif allConservedMoments: + return 0 + else: + nextIndex += 1 + return sp.Symbol("omega_%d" % (nextIndex,)) + + return result + + +def relaxationRateFromMagicNumber(hydrodynamicRelaxationRate, magicNumber): + omega = hydrodynamicRelaxationRate + return (4 - 2 * omega) / (4 * magicNumber * omega + 2 - omega) + + +def compressibleToIncompressibleMomentValue(term, rho, u): + term = term.expand() + if term.func != sp.Add: + args = [term, ] + else: + args = term.args + + res = 0 + for t in args: + containedSymbols = t.atoms(sp.Symbol) + if rho in containedSymbols and len(containedSymbols.intersection(set(u))) > 0: + res += t / rho + else: + res += t + return res diff --git a/methods/entropic.py b/methods/entropic.py new file mode 100644 index 0000000000000000000000000000000000000000..40600ce88792dc1430a4c958fc80a3f22d4c24ee --- /dev/null +++ b/methods/entropic.py @@ -0,0 +1,262 @@ +from collections import OrderedDict +from functools import reduce +import itertools +import operator +import sympy as sp +from lbmpy.methods.creationfunctions import createWithDiscreteMaxwellianEqMoments +from pystencils.transformations import fastSubs +from lbmpy.stencils import getStencil +from lbmpy.simplificationfactory import createSimplificationStrategy +from lbmpy.moments import momentsUpToComponentOrder, MOMENT_SYMBOLS, momentsOfOrder, \ + exponentsToPolynomialRepresentations + + +def discreteEntropy(function, f_eq): + return -sum([f_i * sp.ln(f_i / w_i) for f_i, w_i in zip(function, f_eq)]) + + +def discreteApproxEntropy(function, f_eq): + return -sum([f_i * ((f_i / w_i)-1) for f_i, w_i in zip(function, f_eq)]) + + +def splitUpdateEquationsInDeltasPlusRest(updateEqsRhs, relaxationRate): + deltas = [ue.expand().collect(relaxationRate).coeff(relaxationRate) + for ue in updateEqsRhs] + rest = [ue.expand().collect(relaxationRate) - relaxationRate * delta for ue, delta in zip(updateEqsRhs, deltas)] + + return rest, deltas + + +def findEntropyMaximizingOmega(omega_s, f_eq, ds, dh): + dsdh = sum([ds_i * dh_i / f_eq_i for ds_i, dh_i, f_eq_i in zip(ds, dh, f_eq)]) + dhdh = sum([dh_i * dh_i / f_eq_i for dh_i, f_eq_i in zip(dh, f_eq)]) + return 1 - ((omega_s - 1) * dsdh / dhdh) + + +def determineRelaxationRateByEntropyCondition(updateRule, omega_s, omega_h): + stencil = updateRule.method.stencil + Q = len(stencil) + fSymbols = updateRule.method.preCollisionPdfSymbols + + updateEqsRhs = [e.rhs for e in updateRule.mainEquations] + _, ds = splitUpdateEquationsInDeltasPlusRest(updateEqsRhs, omega_s) + _, dh = splitUpdateEquationsInDeltasPlusRest(updateEqsRhs, omega_h) + dsSymbols = [sp.Symbol("entropicDs_%d" % (i,)) for i in range(Q)] + dhSymbols = [sp.Symbol("entropicDh_%d" % (i,)) for i in range(Q)] + feqSymbols = [sp.Symbol("entropicFeq_%d" % (i,)) for i in range(Q)] + + subexprs = [sp.Eq(a, b) for a, b in zip(dsSymbols, ds)] + \ + [sp.Eq(a, b) for a, b in zip(dhSymbols, dh)] + \ + [sp.Eq(a, f_i + ds_i + dh_i) for a, f_i, ds_i, dh_i in zip(feqSymbols, fSymbols, dsSymbols, dhSymbols)] + + optimalOmegaH = findEntropyMaximizingOmega(omega_s, feqSymbols, dsSymbols, dhSymbols) + + subexprs += [sp.Eq(omega_h, optimalOmegaH)] + + newUpdateEquations = [] + for updateEq in updateRule.mainEquations: + index = updateRule.method.postCollisionPdfSymbols.index(updateEq.lhs) + newEq = sp.Eq(updateEq.lhs, fSymbols[index] + omega_s * dsSymbols[index] + omega_h * dhSymbols[index]) + newUpdateEquations.append(newEq) + return updateRule.copy(newUpdateEquations, updateRule.subexpressions + subexprs) + + +def decompositionByRelaxationRate(updateRule, relaxationRate): + lm = updateRule.latticeModel + stencil = lm.stencil + + affineTerms = [0] * len(stencil) + linearTerms = [0] * len(stencil) + quadraticTerms = [0] * len(stencil) + + for updateEquation in updateRule.updateEquations: + index = lm.pdfDestinationSymbols.index(updateEquation.lhs) + rhs = updateEquation.rhs + linearTerms[index] = rhs.coeff(relaxationRate) + quadraticTerms[index] = rhs.coeff(relaxationRate**2) + affineTerms[index] = rhs - relaxationRate * linearTerms[index] - relaxationRate**2 * quadraticTerms[index] + + if relaxationRate in affineTerms[index].atoms(sp.Symbol): + raise ValueError("Relaxation Rate decomposition failed (affine part) - run simplification first") + if relaxationRate in linearTerms[index].atoms(sp.Symbol): + raise ValueError("Relaxation Rate decomposition failed (linear part) - run simplification first") + if relaxationRate in quadraticTerms[index].atoms(sp.Symbol): + raise ValueError("Relaxation Rate decomposition failed (quadratic part) - run simplification first") + + return affineTerms, linearTerms, quadraticTerms + + +class RelaxationRatePolynomialDecomposition: + + def __init__(self, collisionRule, freeRelaxationRates, fixedRelaxationRates): + self._collisionRule = collisionRule + self._freeRelaxationRates = freeRelaxationRates + self._fixedRelaxationRates = fixedRelaxationRates + self._allRelaxationRates = fixedRelaxationRates + freeRelaxationRates + for se in collisionRule.subexpressions: + for rr in freeRelaxationRates: + assert rr not in se.rhs.atoms(sp.Symbol), \ + "Decomposition not possible since free relaxation rates are already in subexpressions" + + def symbolicRelaxationRateFactors(self, relaxationRate, power): + Q = len(self._collisionRule.method.stencil) + omegaIdx = self._allRelaxationRates.index(relaxationRate) + return [sp.Symbol("entFacOmega_%d_%d_%d" % (i, omegaIdx, power)) for i in range(Q)] + + def relaxationRateFactors(self, relaxationRate): + updateEquations = self._collisionRule.mainEquations + + result = [] + for updateEquation in updateEquations: + factors = [] + rhs = updateEquation.rhs + power = 0 + while True: + power += 1 + factor = rhs.coeff(relaxationRate ** power) + if factor != 0: + if relaxationRate in factor.atoms(sp.Symbol): + raise ValueError("Relaxation Rate decomposition failed - run simplification first") + factors.append(factor) + else: + break + + result.append(factors) + + return result + + def constantExprs(self): + subsDict = {rr: 0 for rr in self._freeRelaxationRates} + subsDict.update({rr: 0 for rr in self._fixedRelaxationRates}) + updateEquations = self._collisionRule.mainEquations + return [fastSubs(eq.rhs, subsDict) for eq in updateEquations] + + def equilibriumExprs(self): + subsDict = {rr: 1 for rr in self._freeRelaxationRates} + subsDict.update({rr: 1 for rr in self._fixedRelaxationRates}) + updateEquations = self._collisionRule.mainEquations + return [fastSubs(eq.rhs, subsDict) for eq in updateEquations] + + def symbolicEquilibrium(self): + Q = len(self._collisionRule.method.stencil) + return [sp.Symbol("entFeq_%d" % (i,)) for i in range(Q)] + + +def determineRelaxationRateByEntropyConditionIterative(updateRule, omega_s, omega_h, + newtonIterations=2, initialValue=1): + method = updateRule.method + decomp = RelaxationRatePolynomialDecomposition(updateRule, [omega_h], [omega_s]) + + # compute and assign relaxation rate factors + newUpdateEquations = [] + fEqEqs = [] + rrFactorDefinitions = [] + relaxationRates = [omega_s, omega_h] + + for i, constantExpr in enumerate(decomp.constantExprs()): + updateEqRhs = constantExpr + fEqRhs = constantExpr + for rr in relaxationRates: + factors = decomp.relaxationRateFactors(rr) + for idx, f in enumerate(factors[i]): + power = idx + 1 + symbolicFactor = decomp.symbolicRelaxationRateFactors(rr, power)[i] + rrFactorDefinitions.append(sp.Eq(symbolicFactor, f)) + updateEqRhs += rr ** power * symbolicFactor + fEqRhs += symbolicFactor + newUpdateEquations.append(sp.Eq(method.postCollisionPdfSymbols[i], updateEqRhs)) + fEqEqs.append(sp.Eq(decomp.symbolicEquilibrium()[i], fEqRhs)) + + # newton iterations to determine free omega + intermediateOmegas = [sp.Symbol("omega_iter_%i" % (i,)) for i in range(newtonIterations+1)] + intermediateOmegas[0] = initialValue + intermediateOmegas[-1] = omega_h + + newtonIterationEquations = [] + for omega_idx in range(len(intermediateOmegas)-1): + rhsOmega = intermediateOmegas[omega_idx] + lhsOmega = intermediateOmegas[omega_idx+1] + updateEqsRhs = [e.rhs for e in newUpdateEquations] + entropy = discreteApproxEntropy(updateEqsRhs, [e.lhs for e in fEqEqs]) + entropyDiff = sp.diff(entropy, omega_h) + entropySecondDiff = sp.diff(entropyDiff, omega_h) + entropyDiff = entropyDiff.subs(omega_h, rhsOmega) + entropySecondDiff = entropySecondDiff.subs(omega_h, rhsOmega) + + newtonEq = sp.Eq(lhsOmega, rhsOmega - entropyDiff / entropySecondDiff) + newtonIterationEquations.append(newtonEq) + + # final update equations + newSubexpressions = updateRule.subexpressions + rrFactorDefinitions + fEqEqs + newtonIterationEquations + return updateRule.copy(newUpdateEquations, newSubexpressions) + + +def createKbcEntropicCollisionRule(dim, name='KBC-N4', useNewtonIterations=False, velocityRelaxation=None, + shearRelaxationRate=sp.Symbol("omega"), + higherOrderRelaxationRate=sp.Symbol("omega_h"), + fixedOmega=None, **kwargs): + def product(iterable): + return reduce(operator.mul, iterable, 1) + + theMoment = MOMENT_SYMBOLS[:dim] + + rho = [sp.Rational(1, 1)] + velocity = list(theMoment) + + shearTensorOffDiagonal = [product(t) for t in itertools.combinations(theMoment, 2)] + shearTensorDiagonal = [m_i * m_i for m_i in theMoment] + shearTensorTrace = sum(shearTensorDiagonal) + shearTensorTracefreeDiagonal = [dim * d - shearTensorTrace for d in shearTensorDiagonal] + + energyTransportTensor = list(exponentsToPolynomialRepresentations([a for a in momentsOfOrder(3, dim, True) + if 3 not in a])) + + explicitlyDefined = set(rho + velocity + shearTensorOffDiagonal + shearTensorDiagonal + energyTransportTensor) + rest = list(set(exponentsToPolynomialRepresentations(momentsUpToComponentOrder(2, dim))) - explicitlyDefined) + assert len(rest) + len(explicitlyDefined) == 3**dim + + # naming according to paper Karlin 2015: Entropic multirelaxation lattice Boltzmann models for turbulent flows + D = shearTensorOffDiagonal + shearTensorTracefreeDiagonal[:-1] + T = [shearTensorTrace] + Q = energyTransportTensor + if name == 'KBC-N1': + decomposition = [D, T+Q+rest] + elif name == 'KBC-N2': + decomposition = [D+T, Q+rest] + elif name == 'KBC-N3': + decomposition = [D+Q, T+rest] + elif name == 'KBC-N4': + decomposition = [D+T+Q, rest] + else: + raise ValueError("Unknown model. Supported models KBC-Nx") + + omega_s, omega_h = shearRelaxationRate, higherOrderRelaxationRate + shearPart, restPart = decomposition + + velRelaxation = omega_s if velocityRelaxation is None else velocityRelaxation + relaxationRates = [omega_s] + \ + [velRelaxation] * len(velocity) + \ + [omega_s] * len(shearPart) + \ + [omega_h] * len(restPart) + + stencil = getStencil("D2Q9") if dim == 2 else getStencil("D3Q27") + allMoments = rho + velocity + shearPart + restPart + momentToRr = OrderedDict((m, rr) for m, rr in zip(allMoments, relaxationRates)) + method = createWithDiscreteMaxwellianEqMoments(stencil, momentToRr, cumulant=False, **kwargs) + + simplify = createSimplificationStrategy(method) + collisionRule = simplify(method.getCollisionRule()) + + if useNewtonIterations: + collisionRule = determineRelaxationRateByEntropyConditionIterative(collisionRule, omega_s, omega_h, 4) + else: + collisionRule = determineRelaxationRateByEntropyCondition(collisionRule, omega_s, omega_h) + + if fixedOmega: + collisionRule = collisionRule.newWithSubstitutions({omega_s: fixedOmega}) + + return collisionRule + + +if __name__ == "__main__": + ur = createKbcEntropicCollisionRule(2, useNewtonIterations=False) diff --git a/methods/momentbased.py b/methods/momentbased.py index 35626f81bb93dacfda9b74078a30088fc478dc8a..1ad8be9d9993bfdc0804ceb3227e4e822b6bc084 100644 --- a/methods/momentbased.py +++ b/methods/momentbased.py @@ -1,16 +1,10 @@ import sympy as sp -import collections -from collections import OrderedDict, defaultdict +from collections import OrderedDict, Sequence -from lbmpy.stencils import stencilsHaveSameEntries, getStencil -from lbmpy.maxwellian_equilibrium import getMomentsOfDiscreteMaxwellianEquilibrium, \ - getMomentsOfContinuousMaxwellianEquilibrium, getCumulantsOfDiscreteMaxwellianEquilibrium, \ - getCumulantsOfContinuousMaxwellianEquilibrium 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 +from lbmpy.methods.conservedquantitycomputation import AbstractConservedQuantityComputation +from lbmpy.moments import MOMENT_SYMBOLS, momentMatrix, isShearMoment +from pystencils.sympyextensions import replaceAdditive class MomentBasedLbMethod(AbstractLbMethod): @@ -44,7 +38,7 @@ class MomentBasedLbMethod(AbstractLbMethod): symbolsInEquilibriumMoments = sp.Matrix(equilibriumMoments).atoms(sp.Symbol) conservedQuantities = set() for v in self._conservedQuantityComputation.definedSymbols().values(): - if isinstance(v, collections.Sequence): + if isinstance(v, Sequence): conservedQuantities.update(v) else: conservedQuantities.add(v) @@ -79,7 +73,7 @@ class MomentBasedLbMethod(AbstractLbMethod): @property def firstOrderEquilibriumMomentSymbols(self, ): - return self._conservedQuantityComputation.firstOrderMomentsSymbol + return self._conservedQuantityComputation.firstOrderMomentSymbols @property def weights(self): @@ -214,329 +208,5 @@ class MomentBasedLbMethod(AbstractLbMethod): return [], relaxationMatrix -# ------------------------------------ Helper Functions ---------------------------------------------------------------- -def sortMomentsIntoGroupsOfSameOrder(moments): - """Returns a dictionary mapping the order (int) to a list of moments with that order.""" - result = defaultdict(list) - for i, moment in enumerate(moments): - order = getOrder(moment) - result[order].append(moment) - return result - - -def defaultRelaxationRateNames(): - nextIndex = 0 - - def result(momentList): - nonlocal nextIndex - shearMomentInside = False - allConservedMoments = True - for m in momentList: - if isShearMoment(m): - shearMomentInside = True - if not (getOrder(m) == 0 or getOrder(m) == 1): - allConservedMoments = False - - if shearMomentInside: - return sp.Symbol("omega") - elif allConservedMoments: - return 0 - else: - nextIndex += 1 - return sp.Symbol("omega_%d" % (nextIndex,)) - - return result - - -def relaxationRateFromMagicNumber(hydrodynamicRelaxationRate, magicNumber): - omega = hydrodynamicRelaxationRate - return (4 - 2 * omega) / (4 * magicNumber * omega + 2 - omega) - - -# -------------------- Generic Creators by matching equilibrium moments ------------------------------------------------ - -def compressibleToIncompressibleMomentValue(term, rho, u): - term = term.expand() - if term.func != sp.Add: - args = [term, ] - else: - args = term.args - - res = 0 - for t in args: - containedSymbols = t.atoms(sp.Symbol) - if rho in containedSymbols and len(containedSymbols.intersection(set(u))) > 0: - res += t / rho - else: - res += t - return res - - -from lbmpy.methods.cumulantbased import CumulantBasedLbMethod - - -def createWithDiscreteMaxwellianEqMoments(stencil, momentToRelaxationRateDict, compressible=False, forceModel=None, - equilibriumAccuracyOrder=2, cumulant=False): - r""" - Creates a moment-based LBM by taking a list of moments with corresponding relaxation rate. These moments are - relaxed against the moments of the discrete Maxwellian distribution. - - :param stencil: nested tuple defining the discrete velocity space. See `func:lbmpy.stencils.getStencil` - :param momentToRelaxationRateDict: dict that has as many entries as the stencil. Each moment, which can be - represented by an exponent tuple or in polynomial form - (see `lbmpy.moments`), is mapped to a relaxation rate. - :param compressible: incompressible LBM methods split the density into :math:`\rho = \rho_0 + \Delta \rho` - where :math:`\rho_0` is chosen as one, and the first moment of the pdfs is :math:`\Delta \rho` . - This approximates the incompressible Navier-Stokes equations better than the standard - compressible model. - :param forceModel: force model instance, or None if no external forces - :param equilibriumAccuracyOrder: approximation order of macroscopic velocity :math:`\mathbf{u}` in the equilibrium - :param cumulant: if True relax cumulants instead of moments - :return: :class:`lbmpy.methods.MomentBasedLbmMethod` instance - """ - momToRrDict = OrderedDict(momentToRelaxationRateDict) - assert len(momToRrDict) == len(stencil), \ - "The number of moments has to be the same as the number of stencil entries" - - densityVelocityComputation = DensityVelocityComputation(stencil, compressible, forceModel) - - if cumulant: - eqValues = getCumulantsOfDiscreteMaxwellianEquilibrium(stencil, list(momToRrDict.keys()), - c_s_sq=sp.Rational(1, 3), compressible=compressible, - order=equilibriumAccuracyOrder) - else: - eqValues = getMomentsOfDiscreteMaxwellianEquilibrium(stencil, list(momToRrDict.keys()), - c_s_sq=sp.Rational(1, 3), compressible=compressible, - order=equilibriumAccuracyOrder) - - rrDict = OrderedDict([(mom, RelaxationInfo(eqMom, rr)) - for mom, rr, eqMom in zip(momToRrDict.keys(), momToRrDict.values(), eqValues)]) - if cumulant: - return CumulantBasedLbMethod(stencil, rrDict, densityVelocityComputation, forceModel) - else: - return MomentBasedLbMethod(stencil, rrDict, densityVelocityComputation, forceModel) - - -def createWithContinuousMaxwellianEqMoments(stencil, momentToRelaxationRateDict, compressible=False, forceModel=None, - equilibriumAccuracyOrder=2, cumulant=False): - r""" - Creates a moment-based LBM by taking a list of moments with corresponding relaxation rate. These moments are - relaxed against the moments of the continuous Maxwellian distribution. - For parameter description see :func:`lbmpy.methods.createWithDiscreteMaxwellianEqMoments`. - By using the continuous Maxwellian we automatically get a compressible model. - """ - momToRrDict = OrderedDict(momentToRelaxationRateDict) - assert len(momToRrDict) == len( - stencil), "The number of moments has to be the same as the number of stencil entries" - dim = len(stencil[0]) - densityVelocityComputation = DensityVelocityComputation(stencil, True, forceModel) - - if cumulant: - eqValues = getCumulantsOfContinuousMaxwellianEquilibrium(list(momToRrDict.keys()), dim, - c_s_sq=sp.Rational(1, 3), - order=equilibriumAccuracyOrder) - else: - eqValues = getMomentsOfContinuousMaxwellianEquilibrium(list(momToRrDict.keys()), dim, c_s_sq=sp.Rational(1, 3), - order=equilibriumAccuracyOrder) - - if not compressible: - rho = densityVelocityComputation.definedSymbols(order=0)[1] - u = densityVelocityComputation.definedSymbols(order=1)[1] - eqValues = [compressibleToIncompressibleMomentValue(em, rho, u) for em in eqValues] - - rrDict = OrderedDict([(mom, RelaxationInfo(eqMom, rr)) - for mom, rr, eqMom in zip(momToRrDict.keys(), momToRrDict.values(), eqValues)]) - if cumulant: - return CumulantBasedLbMethod(stencil, rrDict, densityVelocityComputation, forceModel) - else: - return MomentBasedLbMethod(stencil, rrDict, densityVelocityComputation, forceModel) - - -# ------------------------------------ SRT / TRT/ MRT Creators --------------------------------------------------------- - - -def createSRT(stencil, relaxationRate, useContinuousMaxwellianEquilibrium=False, **kwargs): - r""" - Creates a single relaxation time (SRT) lattice Boltzmann model also known as BGK model. - - :param stencil: nested tuple defining the discrete velocity space. See :func:`lbmpy.stencils.getStencil` - :param relaxationRate: relaxation rate (inverse of the relaxation time) - usually called :math:`\omega` in LBM literature - :return: :class:`lbmpy.methods.MomentBasedLbmMethod` instance - """ - moments = getDefaultMomentSetForStencil(stencil) - rrDict = OrderedDict([(m, relaxationRate) for m in moments]) - if useContinuousMaxwellianEquilibrium: - return createWithContinuousMaxwellianEqMoments(stencil, rrDict, **kwargs) - else: - return createWithDiscreteMaxwellianEqMoments(stencil, rrDict, **kwargs) - - -def createTRT(stencil, relaxationRateEvenMoments, relaxationRateOddMoments, - useContinuousMaxwellianEquilibrium=False, **kwargs): - """ - Creates a two relaxation time (TRT) lattice Boltzmann model, where even and odd moments are relaxed differently. - In the SRT model the exact wall position of no-slip boundaries depends on the viscosity, the TRT method does not - have this problem. - - Parameters are similar to :func:`lbmpy.methods.createSRT`, but instead of one relaxation rate there are - two relaxation rates: one for even moments (determines viscosity) and one for odd moments. - If unsure how to choose the odd relaxation rate, use the function :func:`lbmpy.methods.createTRTWithMagicNumber`. - """ - moments = getDefaultMomentSetForStencil(stencil) - rrDict = OrderedDict([(m, relaxationRateEvenMoments if isEven(m) else relaxationRateOddMoments) for m in moments]) - if useContinuousMaxwellianEquilibrium: - return createWithContinuousMaxwellianEqMoments(stencil, rrDict, **kwargs) - else: - return createWithDiscreteMaxwellianEqMoments(stencil, rrDict, **kwargs) - - -def createTRTWithMagicNumber(stencil, relaxationRate, magicNumber=sp.Rational(3, 16), **kwargs): - """ - Creates a two relaxation time (TRT) lattice Boltzmann method, where the relaxation time for odd moments is - determines from the even moment relaxation time and a "magic number". - For possible parameters see :func:`lbmpy.methods.createTRT` - """ - rrOdd = relaxationRateFromMagicNumber(relaxationRate, magicNumber) - return createTRT(stencil, relaxationRateEvenMoments=relaxationRate, relaxationRateOddMoments=rrOdd, **kwargs) - - -def createOrthogonalMRT(stencil, relaxationRateGetter=None, useContinuousMaxwellianEquilibrium=False, **kwargs): - r""" - Returns a orthogonal multi-relaxation time model for the stencils D2Q9, D3Q15, D3Q19 and D3Q27. - These MRT methods are just one specific version - there are many MRT methods possible for all these stencils - which differ by the linear combination of moments and the grouping into equal relaxation times. - To create a generic MRT method use :func:`lbmpy.methods.createWithDiscreteMaxwellianEqMoments` - - :param stencil: nested tuple defining the discrete velocity space. See `func:lbmpy.stencils.getStencil` - :param relaxationRateGetter: function getting a list of moments as argument, returning the associated relaxation - time. The default returns: - - - 0 for moments of order 0 and 1 (conserved) - - :math:`\omega`: from moments of order 2 (rate that determines viscosity) - - numbered :math:`\omega_i` for the rest - """ - if relaxationRateGetter is None: - relaxationRateGetter = defaultRelaxationRateNames() - - x, y, z = MOMENT_SYMBOLS - one = sp.Rational(1, 1) - - momentToRelaxationRateDict = OrderedDict() - if stencilsHaveSameEntries(stencil, getStencil("D2Q9")): - moments = getDefaultMomentSetForStencil(stencil) - orthogonalMoments = gramSchmidt(moments, stencil) - orthogonalMomentsScaled = [e * commonDenominator(e) for e in orthogonalMoments] - nestedMoments = list(sortMomentsIntoGroupsOfSameOrder(orthogonalMomentsScaled).values()) - elif stencilsHaveSameEntries(stencil, getStencil("D3Q15")): - sq = x ** 2 + y ** 2 + z ** 2 - nestedMoments = [ - [one, x, y, z], # [0, 3, 5, 7] - [sq - 1], # [1] - [3 * sq ** 2 - 6 * sq + 1], # [2] - [(3 * sq - 5) * x, (3 * sq - 5) * y, (3 * sq - 5) * z], # [4, 6, 8] - [3 * x ** 2 - sq, y ** 2 - z ** 2, x * y, y * z, x * z], # [9, 10, 11, 12, 13] - [x * y * z] - ] - elif stencilsHaveSameEntries(stencil, getStencil("D3Q19")): - sq = x ** 2 + y ** 2 + z ** 2 - nestedMoments = [ - [one, x, y, z], # [0, 3, 5, 7] - [sq - 1], # [1] - [3 * sq ** 2 - 6 * sq + 1], # [2] - [(3 * sq - 5) * x, (3 * sq - 5) * y, (3 * sq - 5) * z], # [4, 6, 8] - [3 * x ** 2 - sq, y ** 2 - z ** 2, x * y, y * z, x * z], # [9, 11, 13, 14, 15] - [(2 * sq - 3) * (3 * x ** 2 - sq), (2 * sq - 3) * (y ** 2 - z ** 2)], # [10, 12] - [(y ** 2 - z ** 2) * x, (z ** 2 - x ** 2) * y, (x ** 2 - y ** 2) * z] # [16, 17, 18] - ] - elif stencilsHaveSameEntries(stencil, getStencil("D3Q27")): - xsq, ysq, zsq = x ** 2, y ** 2, z ** 2 - allMoments = [ - sp.Rational(1, 1), # 0 - x, y, z, # 1, 2, 3 - x * y, x * z, y * z, # 4, 5, 6 - xsq - ysq, # 7 - (xsq + ysq + zsq) - 3 * zsq, # 8 - (xsq + ysq + zsq) - 2, # 9 - 3 * (x * ysq + x * zsq) - 4 * x, # 10 - 3 * (xsq * y + y * zsq) - 4 * y, # 11 - 3 * (xsq * z + ysq * z) - 4 * z, # 12 - x * ysq - x * zsq, # 13 - xsq * y - y * zsq, # 14 - xsq * z - ysq * z, # 15 - x * y * z, # 16 - 3 * (xsq * ysq + xsq * zsq + ysq * zsq) - 4 * (xsq + ysq + zsq) + 4, # 17 - 3 * (xsq * ysq + xsq * zsq - 2 * ysq * zsq) - 2 * (2 * xsq - ysq - zsq), # 18 - 3 * (xsq * ysq - xsq * zsq) - 2 * (ysq - zsq), # 19 - 3 * (xsq * y * z) - 2 * (y * z), # 20 - 3 * (x * ysq * z) - 2 * (x * z), # 21 - 3 * (x * y * zsq) - 2 * (x * y), # 22 - 9 * (x * ysq * zsq) - 6 * (x * ysq + x * zsq) + 4 * x, # 23 - 9 * (xsq * y * zsq) - 6 * (xsq * y + y * zsq) + 4 * y, # 24 - 9 * (xsq * ysq * z) - 6 * (xsq * z + ysq * z) + 4 * z, # 25 - 27 * (xsq * ysq * zsq) - 18 * (xsq * ysq + xsq * zsq + ysq * zsq) + 12 * (xsq + ysq + zsq) - 8, # 26 - ] - nestedMoments = list(sortMomentsIntoGroupsOfSameOrder(allMoments).values()) - else: - raise NotImplementedError("No MRT model is available (yet) for this stencil. " - "Create a custom MRT using 'createWithDiscreteMaxwellianEqMoments'") - - for momentList in nestedMoments: - rr = relaxationRateGetter(momentList) - for m in momentList: - momentToRelaxationRateDict[m] = rr - - if useContinuousMaxwellianEquilibrium: - return createWithContinuousMaxwellianEqMoments(stencil, momentToRelaxationRateDict, **kwargs) - else: - return createWithDiscreteMaxwellianEqMoments(stencil, momentToRelaxationRateDict, **kwargs) - - -# ----------------------------------------- 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 diff --git a/updatekernels.py b/updatekernels.py index cab9c76027f38197e3d8e266231eae4d88219781..d27ed4f6ad0f4bad8632a90c0cff90ad5ee49910 100644 --- a/updatekernels.py +++ b/updatekernels.py @@ -1,4 +1,5 @@ import numpy as np +import sympy as sp from pystencils import Field from pystencils.sympyextensions import fastSubs from lbmpy.fieldaccess import StreamPullTwoFieldsAccessor @@ -109,3 +110,9 @@ def addOutputFieldForConservedQuantities(collisionRule, conservedQuantitiesToOut conservedQuantitiesToOutputFieldDict) return collisionRule.merge(cqc) + +def writeQuantitiesToField(collisionRule, symbols, outputField): + if not hasattr(symbols, "__len__"): + symbols = [symbols] + eqs = [sp.Eq(outputField(i), s) for i, s in enumerate(symbols)] + return collisionRule.copy(collisionRule.mainEquations + eqs)