diff --git a/boundaries/__init__.py b/boundaries/__init__.py index 882a2a77a45db4124562ff582c5b218c60881091..327e1bb2ce0cb7690f8e51b1a8ed811a1f3c3e39 100644 --- a/boundaries/__init__.py +++ b/boundaries/__init__.py @@ -1,2 +1,2 @@ -from lbmpy.boundaries.boundaryconditions import noSlip, ubb, fixedDensity +from lbmpy.boundaries.boundaryconditions import NoSlip, NoSlipFullWay, UBB, FixedDensity from lbmpy.boundaries.boundaryhandling import BoundaryHandling diff --git a/boundaries/boundaryconditions.py b/boundaries/boundaryconditions.py index 3076b41671c28d67e8c6ea402c9ccab2a743e541..1fd31309a40af2581153e71cc1f15023d89ccfbc 100644 --- a/boundaries/boundaryconditions.py +++ b/boundaries/boundaryconditions.py @@ -4,81 +4,160 @@ from lbmpy.simplificationfactory import createSimplificationStrategy from pystencils.sympyextensions import getSymmetricPart from pystencils import Field from lbmpy.boundaries.boundaryhandling import offsetFromDir, weightOfDirection, invDir +from pystencils.types import createTypeFromString -def noSlip(pdfField, direction, lbMethod): - """No-Slip, simple bounce back boundary condition, enforcing zero velocity at obstacle""" - neighbor = offsetFromDir(direction, lbMethod.dim) - inverseDir = invDir(direction) - return [sp.Eq(pdfField[neighbor](inverseDir), pdfField(direction))] +class Boundary(object): + """Base class all boundaries should derive from""" + def __call__(self, pdfField, directionSymbol, lbMethod, indexField): + """ + This function defines the boundary behavior and must therefore be implemented by all boundaries. + Here the boundary is defined as a list of sympy equations, from which a boundary kernel is generated. + :param pdfField: pystencils field describing the pdf. The current cell is cell next to the boundary, + which is influenced by the boundary cell i.e. has a link from the boundary cell to + itself. + :param directionSymbol: a sympy symbol that can be used as index to the pdfField. It describes + the direction pointing from the fluid to the boundary cell + :param lbMethod: an instance of the LB method used. Use this to adapt the boundary to the method + (e.g. compressiblity) + :param indexField: the boundary index field that can be used to retrieve and update boundary data + :return: list of sympy equations + """ + raise NotImplementedError("Boundary class has to overwrite __call__") + + @property + def additionalData(self): + return [] + + @property + def name(self): + return type(self).__name__ + + def additionalDataInit(self, idxArray): + return [] + + +class NoSlip(Boundary): + """No-Slip, (half-way) simple bounce back boundary condition, enforcing zero velocity at obstacle""" + def __call__(self, pdfField, directionSymbol, lbMethod, **kwargs): + neighbor = offsetFromDir(directionSymbol, lbMethod.dim) + inverseDir = invDir(directionSymbol) + return [sp.Eq(pdfField[neighbor](inverseDir), pdfField(directionSymbol))] + + def __hash__(self): + # All boundaries of these class behave equal -> should also be equal + return hash("NoSlip") + + def __eq__(self, other): + return type(other) == NoSlip + + +class NoSlipFullWay(Boundary): + """Full-way bounce back""" + + @property + def additionalData(self): + return [('lastValue', createTypeFromString("double"))] + + def additionalDataInit(self, pdfField, directionSymbol, indexField, **kwargs): + return [sp.Eq(indexField('lastValue'), pdfField(directionSymbol))] + + def __call__(self, pdfField, directionSymbol, lbMethod, indexField, **kwargs): + neighbor = offsetFromDir(directionSymbol, lbMethod.dim) + inverseDir = invDir(directionSymbol) + return [sp.Eq(pdfField[neighbor](inverseDir), indexField('lastValue')), + sp.Eq(indexField('lastValue'), pdfField(directionSymbol))] + + def __hash__(self): + # All boundaries of these class behave equal -> should also be equal + return hash("NoSlipFullWay") + + def __eq__(self, other): + return type(other) == NoSlipFullWay + + +class UBB(Boundary): -def ubb(pdfField, direction, lbMethod, velocity, adaptVelocityToForce=False): """Velocity bounce back boundary condition, enforcing specified velocity at obstacle""" - 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) + def __init__(self, velocity, adaptVelocityToForce=False): + self._velocity = velocity + self._adaptVelocityToForce = adaptVelocityToForce - velocity = tuple(v_i.getShifted(*neighbor) if isinstance(v_i, Field.Access) else v_i for v_i in velocity) + def __call__(self, pdfField, directionSymbol, lbMethod, **kwargs): + vel = self._velocity + direction = directionSymbol - if adaptVelocityToForce: - cqc = lbMethod.conservedQuantityComputation - shiftedVelEqs = cqc.equilibriumInputEquationsFromInitValues(velocity=velocity) - velocity = [eq.rhs for eq in shiftedVelEqs.extract(cqc.firstOrderMomentSymbols).mainEquations] + assert len(vel) == lbMethod.dim, \ + "Dimension of velocity (%d) does not match dimension of LB method (%d)" % (len(vel), lbMethod.dim) + neighbor = offsetFromDir(direction, lbMethod.dim) + inverseDir = invDir(direction) + + velocity = tuple(v_i.getShifted(*neighbor) if isinstance(v_i, Field.Access) else v_i for v_i in vel) + + if self._adaptVelocityToForce: + cqc = lbMethod.conservedQuantityComputation + shiftedVelEqs = cqc.equilibriumInputEquationsFromInitValues(velocity=velocity) + velocity = [eq.rhs for eq in shiftedVelEqs.extract(cqc.firstOrderMomentSymbols).mainEquations] + + 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) + + # Better alternative: 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 not lbMethod.conservedQuantityComputation.zeroCenteredPdfs: + 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)] + + +class FixedDensity(Boundary): + + def __init__(self, density): + self._density = density - 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) + def __call__(self, pdfField, directionSymbol, lbMethod, **kwargs): + """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(directionSymbol, lbMethod.dim) + inverseDir = invDir(directionSymbol) - # Better alternative: 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 not lbMethod.conservedQuantityComputation.zeroCenteredPdfs: 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): - """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, lbMethod.dim) - inverseDir = invDir(direction) - - cqc = lbMethod.conservedQuantityComputation - velocity = cqc.definedSymbols()['velocity'] - symmetricEq = removeAsymmetricPartOfMainEquations(lbMethod.getEquilibrium(), dofs=velocity) - substitutions = {sym: pdfField(i) for i, sym in enumerate(lbMethod.preCollisionPdfSymbols)} - symmetricEq = symmetricEq.copyWithSubstitutionsApplied(substitutions) - - simplification = createSimplificationStrategy(lbMethod) - symmetricEq = simplification(symmetricEq) - - densitySymbol = cqc.definedSymbols()['density'] - - densityEq = cqc.equilibriumInputEquationsFromInitValues(density=density).insertSubexpressions().mainEquations[0] - assert densityEq.lhs == densitySymbol - transformedDensity = densityEq.rhs - - conditions = [(eq_i.rhs, sp.Equality(direction, i)) - for i, eq_i in enumerate(symmetricEq.mainEquations)] + [(0, True)] - eq_component = sp.Piecewise(*conditions) - - subExprs = [sp.Eq(eq.lhs, transformedDensity if eq.lhs == densitySymbol else eq.rhs) - for eq in symmetricEq.subexpressions] - return subExprs + [sp.Eq(pdfField[neighbor](inverseDir), 2 * eq_component - pdfField(direction))] + velocity = cqc.definedSymbols()['velocity'] + symmetricEq = removeAsymmetricPartOfMainEquations(lbMethod.getEquilibrium(), dofs=velocity) + substitutions = {sym: pdfField(i) for i, sym in enumerate(lbMethod.preCollisionPdfSymbols)} + symmetricEq = symmetricEq.copyWithSubstitutionsApplied(substitutions) + + simplification = createSimplificationStrategy(lbMethod) + symmetricEq = simplification(symmetricEq) + + densitySymbol = cqc.definedSymbols()['density'] + + density = self._density + densityEq = cqc.equilibriumInputEquationsFromInitValues(density=density).insertSubexpressions().mainEquations[0] + assert densityEq.lhs == densitySymbol + transformedDensity = densityEq.rhs + + conditions = [(eq_i.rhs, sp.Equality(directionSymbol, i)) + for i, eq_i in enumerate(symmetricEq.mainEquations)] + [(0, True)] + eq_component = sp.Piecewise(*conditions) + + subExprs = [sp.Eq(eq.lhs, transformedDensity if eq.lhs == densitySymbol else eq.rhs) + for eq in symmetricEq.subexpressions] + return subExprs + [sp.Eq(pdfField[neighbor](inverseDir), 2 * eq_component - pdfField(directionSymbol))] diff --git a/boundaries/boundaryhandling.py b/boundaries/boundaryhandling.py index 148120668ab4d0699a906efcb884da25950bd591..5863b4b39aa1451608c7bb08629dc1807d5812cd 100644 --- a/boundaries/boundaryhandling.py +++ b/boundaries/boundaryhandling.py @@ -13,10 +13,9 @@ WEIGHTS_SYMBOL = TypedSymbol("weights", "double") class BoundaryHandling(object): class BoundaryInfo(object): - def __init__(self, name, flag, function, kernel, ast): - self.name = name + def __init__(self, flag, object, kernel, ast): self.flag = flag - self.function = function + self.object = object self.kernel = kernel self.ast = ast @@ -24,29 +23,24 @@ class BoundaryHandling(object): """ Class for managing boundary kernels - :param pdfField: either pdf numpy array (including ghost layers), or pystencils.Field + :param pdfField: pdf numpy array including ghost layers :param domainShape: domain size without ghost layers :param lbMethod: lattice Boltzmann method :param ghostLayers: number of ghost layers :param target: either 'cpu' or 'gpu' """ - if isinstance(pdfField, np.ndarray): - symbolicPdfField = Field.createFromNumpyArray('pdfs', pdfField, indexDimensions=1) - assert pdfField.shape[:-1] == tuple(d + 2*ghostLayers for d in domainShape) - elif isinstance(pdfField, Field): - symbolicPdfField = pdfField - else: - raise ValueError("pdfField has to be either a numpy array or a pystencils.Field") + symbolicPdfField = Field.createFromNumpyArray('pdfs', pdfField, indexDimensions=1) + assert pdfField.shape[:-1] == tuple(d + 2*ghostLayers for d in domainShape) self._symbolicPdfField = symbolicPdfField + self._pdfField = pdfField self._shapeWithGhostLayers = [d + 2 * ghostLayers for d in domainShape] self._fluidFlag = 2 ** 0 self.flagField = np.full(self._shapeWithGhostLayers, self._fluidFlag, dtype=np.int32) self._ghostLayers = ghostLayers self._lbMethod = lbMethod - self._boundaryInfos = [] - self._nameToBoundary = {} + self._boundaryInfos = {} self._periodicityKernels = [] self._dirty = False @@ -66,13 +60,12 @@ class BoundaryHandling(object): """Flag that is set where the lattice Boltzmann update should happen""" return self._fluidFlag - def getFlag(self, name): - """Flag that represents the boundary with given name. Raises KeyError if no such boundary exists.""" - return self._nameToBoundary[name].flag + def getFlag(self, boundaryObject): + """Flag that represents the given boundary.""" + return self._boundaryInfos[boundaryObject].flag - def getBoundaryNames(self): - """List of names of all registered boundary conditions""" - return [b.name for b in self._boundaryInfos] + def getBoundaries(self): + return [b.object for b in self._boundaryInfos.values()] def setPeriodicity(self, x=False, y=False, z=False): """Enable periodic boundary conditions at the border of the domain""" @@ -82,37 +75,24 @@ class BoundaryHandling(object): self._periodicity = [x, y, z] self._compilePeriodicityKernels() - def hasBoundary(self, name): + def hasBoundary(self, boundaryObject): """Returns boolean indicating if a boundary with that name exists""" - return name in self._nameToBoundary + return boundaryObject in self._boundaryInfos - def setBoundary(self, function, indexExpr=None, maskArr=None, name=None): + def setBoundary(self, boundaryObject, indexExpr=None, maskArr=None): """ Sets boundary in a rectangular region (slice) - :param function: boundary function or the string 'fluid' to remove boundary conditions + :param boundaryObject: boundary condition object or the string 'fluid' to remove boundary conditions :param indexExpr: slice expression, where boundary should be set, see :mod:`pystencils.slicing` :param maskArr: optional boolean (masked) array specifying where the boundary should be set - :param name: name of the boundary """ if indexExpr is None: indexExpr = [slice(None, None, None)] * len(self.flagField.shape) - if function == 'fluid': + if boundaryObject == 'fluid': flag = self._fluidFlag else: - if name is None: - if hasattr(function, '__name__'): - name = function.__name__ - elif hasattr(function, 'name'): - name = function.name - else: - raise ValueError("Boundary function has to have a '__name__' or 'name' attribute " - "if name is not specified") - - if not self.hasBoundary(name): - self.addBoundary(function, name) - - flag = self.getFlag(name) + flag = self.addBoundary(boundaryObject) assert flag != self._fluidFlag indexExpr = normalizeSlice(indexExpr, self._shapeWithGhostLayers) @@ -123,26 +103,20 @@ class BoundaryHandling(object): flagFieldView[maskArr] = flag self._dirty = True - def addBoundary(self, boundaryFunction, name=None): + def addBoundary(self, boundaryObject): """ Adds a boundary condition, i.e. reserves a flog in the flag field and returns that flag - If a boundary with that name already exists, the existing flag is returned. + If the boundary already exists, the existing flag is returned. This flag can be logicalled or'ed to the boundaryHandling.flagField - :param boundaryFunction: boundary condition function, see :mod:`lbmpy.boundaries.boundaryconditions` - :param name: boundaries with different name are considered different. If not given - ```boundaryFunction.__name__`` is used + :param boundaryObject: boundary condition object, see :mod:`lbmpy.boundaries.boundaryconditions` """ - if name is None: - name = boundaryFunction.__name__ - - if self.hasBoundary(name): - return self._boundaryInfos[name].flag + if boundaryObject in self._boundaryInfos: + return self._boundaryInfos[boundaryObject].flag newIdx = len(self._boundaryInfos) + 1 # +1 because 2**0 is reserved for fluid flag - boundaryInfo = self.BoundaryInfo(name, 2 ** newIdx, boundaryFunction, None, None) - self._boundaryInfos.append(boundaryInfo) - self._nameToBoundary[name] = boundaryInfo + boundaryInfo = self.BoundaryInfo(2 ** newIdx, boundaryObject, None, None) + self._boundaryInfos[boundaryObject] = boundaryInfo self._dirty = True return boundaryInfo.flag @@ -157,7 +131,7 @@ class BoundaryHandling(object): """Run the boundary handling, all keyword args are passed through to the boundary sweeps""" if self._dirty: self.prepare() - for boundary in self._boundaryInfos: + for boundary in self._boundaryInfos.values(): boundary.kernel(**kwargs) for k in self._periodicityKernels: k(**kwargs) @@ -166,27 +140,47 @@ class BoundaryHandling(object): """Removes all boundaries and fills the domain with fluid""" self.flagField.fill(self._fluidFlag) self._dirty = False - self._boundaryInfos = [] - self._nameToBoundary = {} + self._boundaryInfos = {} def prepare(self): """Fills data structures to speed up the boundary handling and compiles all boundary kernels. This is automatically done when first called. With this function this can be triggered before running.""" - for boundary in self._boundaryInfos: + for boundary in self._boundaryInfos.values(): assert boundary.flag != self._fluidFlag - idxField = createBoundaryIndexList(self.flagField, self._lbMethod.stencil, + idxArray = createBoundaryIndexList(self.flagField, self._lbMethod.stencil, boundary.flag, self._fluidFlag, self._ghostLayers) - ast = generateBoundaryHandling(self._symbolicPdfField, idxField, self._lbMethod, boundary.function, - target=self._target) + + dim = self._lbMethod.dim + + if boundary.object.additionalData: + coordinateNames = ["x", "y", "z"][:dim] + indexArrDtype = np.dtype([(name, np.int32) for name in coordinateNames] + + [('dir', np.int32)] + + [(i[0], i[1].numpyDtype) for i in boundary.object.additionalData]) + extendedIdxField = np.empty(len(idxArray), dtype=indexArrDtype) + for prop in coordinateNames + ['dir']: + extendedIdxField[prop] = idxArray[prop] + + idxArray = extendedIdxField + if boundary.object.additionalDataInit: + initKernelAst = generateIndexBoundaryKernel(self._symbolicPdfField, idxArray, self._lbMethod, + boundary.object, target='cpu', + createInitializationKernel=True) + from pystencils.cpu import makePythonFunction as makePythonCpuFunction + initKernel = makePythonCpuFunction(initKernelAst, {'indexField': idxArray, 'pdfs': self._pdfField}) + initKernel() + + ast = generateIndexBoundaryKernel(self._symbolicPdfField, idxArray, self._lbMethod, boundary.object, + target=self._target) boundary.ast = ast if self._target == 'cpu': from pystencils.cpu import makePythonFunction as makePythonCpuFunction, addOpenMP addOpenMP(ast, numThreads=self.openMP) - boundary.kernel = makePythonCpuFunction(ast, {'indexField': idxField}) + boundary.kernel = makePythonCpuFunction(ast, {'indexField': idxArray}) elif self._target == 'gpu': from pystencils.gpucuda import makePythonFunction as makePythonGpuFunction import pycuda.gpuarray as gpuarray - idxGpuField = gpuarray.to_gpu(idxField) + idxGpuField = gpuarray.to_gpu(idxArray) boundary.kernel = makePythonGpuFunction(ast, {'indexField': idxGpuField}) else: assert False @@ -276,19 +270,20 @@ class LbmMethodInfo(CustomCppCode): super(LbmMethodInfo, self).__init__(code, symbolsRead=set(), symbolsDefined=symbolsDefined) -def generateBoundaryHandling(pdfField, indexArr, lbMethod, boundaryFunctor, target='cpu'): +def generateIndexBoundaryKernel(pdfField, indexArr, lbMethod, boundaryFunctor, target='cpu', + createInitializationKernel=False): indexField = Field.createFromNumpyArray("indexField", indexArr) elements = [LbmMethodInfo(lbMethod)] dirSymbol = TypedSymbol("dir", indexArr.dtype.fields['dir'][0]) boundaryEqList = [sp.Eq(dirSymbol, indexField[0]('dir'))] - boundaryEqList += boundaryFunctor(pdfField, dirSymbol, lbMethod) - if type(boundaryEqList) is tuple: - boundaryEqList, additionalNodes = boundaryEqList - elements += boundaryEqList - elements += additionalNodes + if createInitializationKernel: + boundaryEqList += boundaryFunctor.additionalDataInit(pdfField=pdfField, directionSymbol=dirSymbol, + lbMethod=lbMethod, indexField=indexField) else: - elements += boundaryEqList + boundaryEqList += boundaryFunctor(pdfField=pdfField, directionSymbol=dirSymbol, lbMethod=lbMethod, + indexField=indexField) + elements += boundaryEqList if target == 'cpu': from pystencils.cpu import createIndexedKernel diff --git a/plot2d.py b/plot2d.py index f49f7fe46c6ebf5cc369486fc884a11cb1016db8..320c891343042ac21096f6db582b483de371fec0 100644 --- a/plot2d.py +++ b/plot2d.py @@ -65,9 +65,9 @@ def plotBoundaryHandling(boundaryHandling, boundaryNameToColor=None): 'noSlip': '#000000' } - boundaryNames = ['fluid'] + boundaryHandling.getBoundaryNames() - flagValues = [boundaryHandling.fluidFlag] + [boundaryHandling.getFlag(n) - for n in boundaryHandling.getBoundaryNames()] + boundaryNames = ['fluid'] + [b.name for b in boundaryHandling.getBoundaries()] + flagValues = [boundaryHandling.fluidFlag] + [boundaryHandling.getFlag(b) + for b in boundaryHandling.getBoundaries()] defaultCycler = matplotlib.rcParams['axes.prop_cycle'] colorValues = [fixedColors[name] if name in fixedColors else cycle['color'] diff --git a/scenarios.py b/scenarios.py index d13483753f945ec6c2e5f0321537ebc9ccecba58..9f6ed78545be27d4e92556d37cebb402e2300e0d 100644 --- a/scenarios.py +++ b/scenarios.py @@ -11,9 +11,9 @@ It is a good starting point if you are new to lbmpy. All scenarios can be modified, for example you can create a simple channel first, then place an object in it: ->>> from lbmpy.boundaries import noSlip +>>> from lbmpy.boundaries import NoSlip >>> from pystencils.slicing import makeSlice ->>> scenario.boundaryHandling.setBoundary(noSlip, makeSlice[0.3:0.4, 0.0:0.3]) +>>> scenario.boundaryHandling.setBoundary(NoSlip(), makeSlice[0.3:0.4, 0.0:0.3]) Functions for scenario setup: ----------------------------- @@ -25,12 +25,11 @@ that defines the viscosity of the fluid (valid values being between 0 and 2). """ import numpy as np import sympy as sp -from functools import partial from pystencils.field import getLayoutOfArray, createNumpyArrayWithLayout from pystencils.slicing import sliceFromDirection, addGhostLayers, removeGhostLayers, normalizeSlice, makeSlice from lbmpy.creationfunctions import createLatticeBoltzmannFunction, updateWithDefaultParameters from lbmpy.macroscopic_value_kernels import compileMacroscopicValuesGetter, compileMacroscopicValuesSetter -from lbmpy.boundaries import BoundaryHandling, noSlip, ubb, fixedDensity +from lbmpy.boundaries import BoundaryHandling, NoSlip, NoSlipFullWay, UBB, FixedDensity from lbmpy.stencils import getStencil from lbmpy.updatekernels import createPdfArray @@ -78,18 +77,17 @@ def createLidDrivenCavity(domainSize, lidVelocity=0.005, optimizationParams={}, """ scenario = Scenario(domainSize, kwargs, optimizationParams, lbmKernel=lbmKernel, kernelParams=kernelParams) - myUbb = partial(ubb, velocity=[lidVelocity, 0, 0][:scenario.method.dim]) - myUbb.name = 'ubb' + myUbb = UBB(velocity=[lidVelocity, 0, 0][:scenario.method.dim]) dim = scenario.method.dim scenario.boundaryHandling.setBoundary(myUbb, sliceFromDirection('N', dim)) for direction in ('W', 'E', 'S') if scenario.method.dim == 2 else ('W', 'E', 'S', 'T', 'B'): - scenario.boundaryHandling.setBoundary(noSlip, sliceFromDirection(direction, dim)) + scenario.boundaryHandling.setBoundary(NoSlip(), sliceFromDirection(direction, dim)) return scenario def createForceDrivenChannel(force=1e-6, domainSize=None, dim=2, radius=None, length=None, initialVelocity=None, - optimizationParams={}, lbmKernel=None, kernelParams={}, **kwargs): + optimizationParams={}, lbmKernel=None, kernelParams={}, halfWayBounceBack=True, **kwargs): """ Creates a channel flow in x direction, which is driven by a constant force along the x axis @@ -104,9 +102,12 @@ def createForceDrivenChannel(force=1e-6, domainSize=None, dim=2, radius=None, le :param optimizationParams: see :mod:`lbmpy.creationfunctions` :param lbmKernel: a LBM function, which would otherwise automatically created :param kernelParams: additional parameters passed to the sweep + :param halfWayBounceBack: determines wall boundary condition: if true half-way bounce back, if false full-way :param kwargs: other parameters are passed on to the method, see :mod:`lbmpy.creationfunctions` :return: instance of :class:`Scenario` """ + wallBoundary = NoSlip() if halfWayBounceBack else NoSlipFullWay() + if domainSize is not None: dim = len(domainSize) else: @@ -137,18 +138,18 @@ def createForceDrivenChannel(force=1e-6, domainSize=None, dim=2, radius=None, le boundaryHandling.setPeriodicity(True, False, False) if dim == 2: for direction in ('N', 'S'): - boundaryHandling.setBoundary(noSlip, sliceFromDirection(direction, dim)) + boundaryHandling.setBoundary(wallBoundary, sliceFromDirection(direction, dim)) elif dim == 3: if roundChannel: - noSlipIdx = boundaryHandling.addBoundary(noSlip) + wallIdx = boundaryHandling.addBoundary(wallBoundary) ff = boundaryHandling.flagField yMid = ff.shape[1] // 2 zMid = ff.shape[2] // 2 y, z = np.meshgrid(range(ff.shape[1]), range(ff.shape[2])) - ff[(y - yMid) ** 2 + (z - zMid) ** 2 > radius ** 2] = noSlipIdx + ff[(y - yMid) ** 2 + (z - zMid) ** 2 > radius ** 2] = wallIdx else: for direction in ('N', 'S', 'T', 'B'): - boundaryHandling.setBoundary(noSlip, sliceFromDirection(direction, dim)) + boundaryHandling.setBoundary(wallBoundary, sliceFromDirection(direction, dim)) assert domainSize is not None if 'forceModel' not in kwargs: @@ -159,7 +160,7 @@ def createForceDrivenChannel(force=1e-6, domainSize=None, dim=2, radius=None, le def createPressureGradientDrivenChannel(pressureDifference, domainSize=None, dim=2, radius=None, length=None, initialVelocity=None, optimizationParams={}, - lbmKernel=None, kernelParams={}, **kwargs): + lbmKernel=None, kernelParams={}, halfWayBounceBack=True, **kwargs): """ Creates a channel flow in x direction, which is driven by two pressure boundaries. Consider using :func:`createForceDrivenChannel` which does not have artifacts an inflow and outflow. @@ -175,9 +176,12 @@ def createPressureGradientDrivenChannel(pressureDifference, domainSize=None, dim :param optimizationParams: see :mod:`lbmpy.creationfunctions` :param lbmKernel: a LBM function, which would otherwise automatically created :param kernelParams: additional parameters passed to the sweep + :param halfWayBounceBack: determines wall boundary condition: if true half-way bounce back, if false full-way :param kwargs: other parameters are passed on to the method, see :mod:`lbmpy.creationfunctions` :return: instance of :class:`Scenario` """ + wallBoundary = NoSlip() if halfWayBounceBack else NoSlipFullWay() + if domainSize is not None: dim = len(domainSize) else: @@ -202,20 +206,18 @@ def createPressureGradientDrivenChannel(pressureDifference, domainSize=None, dim scenario = Scenario(domainSize, kwargs, optimizationParams, lbmKernel=lbmKernel, initialVelocity=initialVelocity, kernelParams=kernelParams) boundaryHandling = scenario.boundaryHandling - pressureBoundaryInflow = partial(fixedDensity, density=1.0 + pressureDifference) - pressureBoundaryInflow.__name__ = "Inflow" + pressureBoundaryInflow = FixedDensity(density=1.0 + pressureDifference) - pressureBoundaryOutflow = partial(fixedDensity, density=1.0) - pressureBoundaryOutflow.__name__ = "Outflow" + pressureBoundaryOutflow = FixedDensity(density=1.0) boundaryHandling.setBoundary(pressureBoundaryInflow, sliceFromDirection('W', dim)) boundaryHandling.setBoundary(pressureBoundaryOutflow, sliceFromDirection('E', dim)) if dim == 2: for direction in ('N', 'S'): - boundaryHandling.setBoundary(noSlip, sliceFromDirection(direction, dim)) + boundaryHandling.setBoundary(wallBoundary, sliceFromDirection(direction, dim)) elif dim == 3: if roundChannel: - noSlipIdx = boundaryHandling.addBoundary(noSlip) + noSlipIdx = boundaryHandling.addBoundary(wallBoundary) ff = boundaryHandling.flagField yMid = ff.shape[1] // 2 zMid = ff.shape[2] // 2 @@ -223,7 +225,7 @@ def createPressureGradientDrivenChannel(pressureDifference, domainSize=None, dim ff[(y - yMid) ** 2 + (z - zMid) ** 2 > radius ** 2] = noSlipIdx else: for direction in ('N', 'S', 'T', 'B'): - boundaryHandling.setBoundary(noSlip, sliceFromDirection(direction, dim)) + boundaryHandling.setBoundary(wallBoundary, sliceFromDirection(direction, dim)) return scenario