diff --git a/boundaries/boundaryconditions.py b/boundaries/boundaryconditions.py new file mode 100644 index 0000000000000000000000000000000000000000..48ab5c894bb10e96c38d5f47d48dd83d79b42359 --- /dev/null +++ b/boundaries/boundaryconditions.py @@ -0,0 +1,48 @@ +import sympy as sp +from lbmpy.boundaries.boundaryhandling import offsetFromDir, weightOfDirection, invDir + + +def noSlip(pdfField, direction, latticeModel): + neighbor = offsetFromDir(direction, latticeModel.dim) + inverseDir = invDir(direction) + return [sp.Eq(pdfField[neighbor](inverseDir), pdfField(direction))] + + +def ubb(pdfField, direction, latticeModel, velocity): + neighbor = offsetFromDir(direction, latticeModel.dim) + inverseDir = invDir(direction) + + 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)] + + +def fixedDensity(pdfField, direction, latticeModel, density): + from lbmpy_old.equilibria import standardDiscreteEquilibrium + neighbor = offsetFromDir(direction, latticeModel.dim) + inverseDir = invDir(direction) + stencil = latticeModel.stencil + + if not latticeModel.compressible: + density -= 1 + + eqParams = {'stencil': stencil, + 'order': 2, + 'c_s_sq': sp.Rational(1, 3), + 'compressible': latticeModel.compressible, + 'rho': density} + + u = sp.Matrix(latticeModel.symbolicVelocity) + symmetricEq = (standardDiscreteEquilibrium(u=u, **eqParams) + standardDiscreteEquilibrium(u=-u, **eqParams)) / 2 + + subExpr1, rhoExpr, subExpr2, uExprs = getDensityVelocityExpressions(stencil, + [pdfField(i) for i in range(len(stencil))], + latticeModel.compressible) + subExprs = subExpr1 + [rhoExpr] + subExpr2 + uExprs + + conditions = [(eq_i, sp.Equality(direction, i)) for i, eq_i in enumerate(symmetricEq)] + [(0, True)] + eq_component = sp.Piecewise(*conditions) + + return subExprs + [sp.Eq(pdfField[neighbor](inverseDir), + 2 * eq_component - pdfField(direction))] + diff --git a/boundaries/boundaryhandling.py b/boundaries/boundaryhandling.py new file mode 100644 index 0000000000000000000000000000000000000000..dba1bd2db4eed5af19674fd04ac7b7bb93d1818e --- /dev/null +++ b/boundaries/boundaryhandling.py @@ -0,0 +1,166 @@ +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.transformations import moveConstantsBeforeLoop, resolveFieldAccesses, typingFromSympyInspection, \ + typeAllEquations +from pystencils.cpu import makePythonFunction + + +INV_DIR_SYMBOL = TypedSymbol("invDir", "int") +WEIGHTS_SYMBOL = TypedSymbol("weights", "double") + + +class BoundaryHandling: + def __init__(self, symbolicPdfField, domainShape, latticeModel, ghostLayers=1): + self._symbolicPdfField = symbolicPdfField + self._shapeWithGhostLayers = [d + 2 * ghostLayers for d in domainShape] + self._fluidFlag = 2 ** 31 + self.flagField = np.full(self._shapeWithGhostLayers, self._fluidFlag, dtype=np.int32) + self._ghostLayers = ghostLayers + self._latticeModel = latticeModel + self._boundaryFunctions = [] + self._nameToIndex = {} + self._boundarySweeps = [] + + def addBoundary(self, boundaryFunction, name=None): + if name is None: + name = boundaryFunction.__name__ + + self._nameToIndex[name] = len(self._boundaryFunctions) + self._boundaryFunctions.append(boundaryFunction) + + def invalidateIndexCache(self): + self._boundarySweeps = [] + + def clear(self): + np.fill(self._fluidFlag) + self.invalidateIndexCache() + + def getFlag(self, name): + return 2 ** self._nameToIndex[name] + + def setBoundary(self, name, indexExpr, clearOtherBoundaries=True): + if not isinstance(name, str): + function = name + if hasattr(function, '__name__'): + name = function.__name__ + else: + name = function.name + + if function not in self._boundaryFunctions: + self.addBoundary(function, name) + + flag = self.getFlag(name) + if clearOtherBoundaries: + self.flagField[indexExpr] = flag + else: + # clear fluid flag + np.bitwise_and(self.flagField[indexExpr], np.invert(self._fluidFlag), self.flagField[indexExpr]) + # add new boundary flag + np.bitwise_or(self.flagField[indexExpr], flag, self.flagField[indexExpr]) + + self.invalidateIndexCache() + + def prepare(self): + self.invalidateIndexCache() + for boundaryIdx, boundaryFunc in enumerate(self._boundaryFunctions): + idxField = createBoundaryIndexList(self.flagField, self._ghostLayers, self._latticeModel.stencil, + 2 ** boundaryIdx, self._fluidFlag) + ast = generateBoundaryHandling(self._symbolicPdfField, idxField, self._latticeModel, boundaryFunc) + self._boundarySweeps.append(makePythonFunction(ast, {'indexField': idxField})) + + def __call__(self, **kwargs): + if len(self._boundarySweeps) == 0: + self.prepare() + for boundarySweep in self._boundarySweeps: + boundarySweep(**kwargs) + + +# -------------------------------------- Helper Functions -------------------------------------------------------------- + + +def offsetSymbols(dim): + return [TypedSymbol("c_%d" % (d,), "int") for d in range(dim)] + + +def offsetFromDir(dirIdx, dim): + return tuple([sp.IndexedBase(symbol, shape=(1,))[dirIdx] for symbol in offsetSymbols(dim)]) + + +def invDir(dirIdx): + return sp.IndexedBase(INV_DIR_SYMBOL, shape=(1,))[dirIdx] + + +def weightOfDirection(dirIdx): + return sp.IndexedBase(WEIGHTS_SYMBOL, shape=(1,))[dirIdx] + + +# ------------------------------------- Kernel Generation -------------------------------------------------------------- + +class LatticeModelInfo(CustomCppCode): + def __init__(self, latticeModel): + stencil = latticeModel.stencil + symbolsDefined = set(offsetSymbols(latticeModel.dim) + [INV_DIR_SYMBOL, WEIGHTS_SYMBOL]) + + offsetSym = offsetSymbols(latticeModel.dim) + code = "\n" + for i in range(latticeModel.dim): + offsetStr = ", ".join([str(d[i]) for d in stencil]) + code += "const int %s [] = { %s };\n" % (offsetSym[i].name, offsetStr) + + invDirs = [] + for direction in stencil: + inverseDir = tuple([-i for i in direction]) + invDirs.append(str(stencil.index(inverseDir))) + + code += "static const int %s [] = { %s };\n" % (INV_DIR_SYMBOL.name, ", ".join(invDirs)) + weights = [str(w.evalf()) for w in latticeModel.weights] + code += "static const double %s [] = { %s };\n" % (WEIGHTS_SYMBOL.name, ",".join(weights)) + super(LatticeModelInfo, self).__init__(code, symbolsRead=set(), symbolsDefined=symbolsDefined) + + +def generateBoundaryHandling(pdfField, indexArr, latticeModel, boundaryFunctor): + dim = latticeModel.dim + + cellLoopBody = Block([]) + cellLoop = LoopOverCoordinate(cellLoopBody, coordinateToLoopOver=0, start=0, stop=indexArr.shape[0]) + + indexField = Field.createFromNumpyArray("indexField", indexArr, indexDimensions=1) + + coordinateSymbols = [TypedSymbol(name, "int") for name in ['x', 'y', 'z']] + for d in range(dim): + cellLoopBody.append(SympyAssignment(coordinateSymbols[d], indexField[0](d))) + dirSymbol = TypedSymbol("dir", "int") + cellLoopBody.append(SympyAssignment(dirSymbol, indexField[0](dim))) + + boundaryEqList = boundaryFunctor(pdfField, dirSymbol, latticeModel) + if type(boundaryEqList) is tuple: + boundaryEqList, additionalNodes = boundaryEqList + else: + additionalNodes = [] + + typeInfos = typingFromSympyInspection(boundaryEqList, pdfField.dtype) + fieldsRead, fieldsWritten, assignments = typeAllEquations(boundaryEqList, typeInfos) + fieldsAccessed = fieldsRead.union(fieldsWritten) - set([indexField]) + + for be in assignments: + cellLoopBody.append(be) + + functionBody = Block([cellLoop]) + ast = KernelFunction(functionBody, [pdfField, indexField]) + + if len(additionalNodes) > 0: + loops = ast.atoms(LoopOverCoordinate) + assert len(loops) == 1 + loop = list(loops)[0] + for node in additionalNodes: + loop.body.append(node) + + functionBody.insertFront(LatticeModelInfo(latticeModel)) + + fixedCoordinateMapping = {f.name: coordinateSymbols[:dim] for f in fieldsAccessed} + resolveFieldAccesses(ast, set(['indexField']), fieldToFixedCoordinates=fixedCoordinateMapping) + moveConstantsBeforeLoop(ast) + return ast diff --git a/methods/conservedquantitycomputation.py b/methods/conservedquantitycomputation.py index 749832c10df2999d76b109270c26599f1a935d80..a65d2a46804c73f6c5e444be135db7935f94172a 100644 --- a/methods/conservedquantitycomputation.py +++ b/methods/conservedquantitycomputation.py @@ -136,12 +136,13 @@ class DensityVelocityComputation(AbstractConservedQuantityComputation): eqs += [sp.Eq(l, r) for l, r in zip(self._symbolsOrder1, firstOrderMoments)] return EquationCollection(eqs, []) - def outputEquationsFromPdfs(self, pdfs, outputQuantityNames): - outputQuantityNames = set(outputQuantityNames) - + def outputEquationsFromPdfs(self, pdfs, outputQuantityNamesToSymbols): dim = len(self._stencil[0]) - eqColl = getEquationsForZerothAndFirstOrderMoment(self._stencil, pdfs, self._symbolOrder0, - self._symbolsOrder1[:dim]) + + symbolsToExtract = set() + + eqColl = getEquationsForZerothAndFirstOrderMoment(self._stencil, pdfs, self._symbolOrder0, self._symbolsOrder1) + if self._compressible: eqColl = divideFirstOrderMomentsByRho(eqColl, dim) else: @@ -149,18 +150,18 @@ class DensityVelocityComputation(AbstractConservedQuantityComputation): eqColl = applyForceModelShift('macroscopicVelocityShift', dim, eqColl, self._forceModel, self._compressible) - nameToSymbol = {'density': self._symbolOrder0, - 'velocity': self._symbolsOrder1} - - symbolsToExtract = set() - for e in outputQuantityNames: - symbol = nameToSymbol[e] - if hasattr(symbol, "__len__"): - symbolsToExtract.update(symbol) - else: - symbolsToExtract.add(symbol) - - return eqColl.extract(symbolsToExtract) + mainEquations = [] + if 'density' in outputQuantityNamesToSymbols: + densityOutputSymbol = outputQuantityNamesToSymbols['density'] + mainEquations.append(sp.Eq(densityOutputSymbol, self._symbolOrder0)) + symbolsToExtract.add(densityOutputSymbol) + if 'velocity' in outputQuantityNamesToSymbols: + velOutputSymbols = outputQuantityNamesToSymbols['velocity'] + mainEquations += [sp.Eq(a, b) for a, b in zip(velOutputSymbols, self._symbolsOrder1)] + symbolsToExtract.update(velOutputSymbols) + + eqColl = eqColl.copy(mainEquations, eqColl.allEquations) + return eqColl.newWithoutUnusedSubexpressions() def __repr__(self): return "ConservedValueComputation for %s" % (", " .join(self.conservedQuantities.keys()),) diff --git a/updatekernels.py b/updatekernels.py new file mode 100644 index 0000000000000000000000000000000000000000..8b8c8b6a413ccb2d3b571e94cde038eb18091542 --- /dev/null +++ b/updatekernels.py @@ -0,0 +1,71 @@ +import numpy as np +from pystencils import Field +from lbmpy.fieldaccess import StreamPullTwoFieldsAccessor + + +# -------------------------------------------- LBM Kernel Creation ----------------------------------------------------- + + +def createLBMKernel(collisionRule, inputField, outputField, accessor): + """ + Replaces the pre- and post collision symbols in the collision rule by field accesses. + + :param collisionRule: instance of LbmCollisionRule, defining the collision step + :param inputField: field used for reading pdf values + :param outputField: field used for writing pdf values (may be the same as input field for certain accessors) + :param accessor: instance of PdfFieldAccessor, defining where to read and write values + to create e.g. a fused stream-collide kernel + :return: LbmCollisionRule where pre- and post collision symbols have been replaced + """ + method = collisionRule.method + preCollisionSymbols = method.preCollisionSymbols + postCollisionSymbols = method.postCollisionPdfSymbols + substitutions = {} + + inputAccesses = accessor.read(inputField, method.stencil) + outputAccesses = accessor.write(outputField, method.stencil) + + for (idx, offset), inputAccess, outputAccess in zip(enumerate(method.stencil), inputAccesses, outputAccesses): + substitutions[preCollisionSymbols[idx]] = inputAccess + substitutions[postCollisionSymbols[idx]] = outputAccess + + return collisionRule.copyWithSubstitutionsApplied(substitutions) + + +def createStreamPullKernel(collisionRule, numpyField=None, srcFieldName="src", dstFieldName="dst", + genericLayout='numpy', genericFieldType=np.float64): + """ + Implements a stream-pull scheme, where values are read from source and written to destination field + :param collisionRule: a collision rule created by lbm method + :param numpyField: optional numpy field for PDFs. Used to create a kernel of fixed loop bounds and strides + if None, a generic kernel is created + :param srcFieldName: name of the pdf source field + :param dstFieldName: name of the pdf destination field + :param genericLayout: if no numpyField is given to determine the layout, a variable sized field with the given + genericLayout is used + :param genericFieldType: if no numpyField is given, this data type is used for the fields + :return: lbm update rule, where generic pdf references are replaced by field accesses + """ + dim = collisionRule.method.dim + if numpyField is not None: + assert len(numpyField.shape) == dim + 1 + + if numpyField is None: + src = Field.createGeneric(srcFieldName, dim, indexDimensions=1, layout=genericLayout, dtype=genericFieldType) + dst = Field.createGeneric(dstFieldName, dim, indexDimensions=1, layout=genericLayout, dtype=genericFieldType) + else: + src = Field.createFromNumpyArray(srcFieldName, numpyField, indexDimensions=1) + dst = Field.createFromNumpyArray(dstFieldName, numpyField, indexDimensions=1) + + return createLBMKernel(collisionRule, src, dst, StreamPullTwoFieldsAccessor) + + +# ------------------------------------------- Add output fields to kernel ---------------------------------------------- + + +def addOutputFieldForConservedQuantities(collisionRule, conservedQuantitiesToOutputFieldDict): + method = collisionRule.method + cqc = method.conservedQuantityComputation.outputEquationsFromPdfs(method.preCollisionPdfSymbols, + conservedQuantitiesToOutputFieldDict) + return collisionRule.merge(cqc) +