diff --git a/lbmpy/boundaries/boundaryconditions.py b/lbmpy/boundaries/boundaryconditions.py index f6dd1bd5dd6c025b12f7efc5c0b5ded73e0497fc..d863e7b67dd64b6af19eac805ed39188e2e3a537 100644 --- a/lbmpy/boundaries/boundaryconditions.py +++ b/lbmpy/boundaries/boundaryconditions.py @@ -79,6 +79,14 @@ class LbBoundary: def name(self, new_value): self._name = new_value + def __hash__(self): + return hash(self.name) + + def __eq__(self, other): + if not isinstance(other, type(self)): + return False + return self.__dict__ == other.__dict__ + # end class Boundary @@ -99,15 +107,6 @@ class NoSlip(LbBoundary): def __call__(self, f_out, f_in, dir_symbol, inv_dir, lb_method, index_field): return Assignment(f_in(inv_dir[dir_symbol]), f_out(dir_symbol)) - def __hash__(self): - return hash(self.name) - - def __eq__(self, other): - if not isinstance(other, NoSlip): - return False - return self.__dict__ == other.__dict__ - - # end class NoSlip @@ -213,15 +212,6 @@ class UBB(LbBoundary): return [Assignment(f_in(inv_dir[direction]), f_out(direction) - vel_term)] - def __hash__(self): - return hash(self.name) - - def __eq__(self, other): - if not isinstance(other, UBB): - return False - return self.__dict__ == other.__dict__ - - # end class UBB @@ -269,15 +259,6 @@ class SimpleExtrapolationOutflow(LbBoundary): return Assignment(f_in.center(inv_dir[dir_symbol]), f_out[tangential_offset](inv_dir[dir_symbol])) - def __hash__(self): - return hash(self.name) - - def __eq__(self, other): - if not isinstance(other, SimpleExtrapolationOutflow): - return False - return self.__dict__ == other.__dict__ - - # end class SimpleExtrapolationOutflow @@ -425,15 +406,6 @@ class ExtrapolationOutflow(LbBoundary): return AssignmentCollection(boundary_assignments, subexpressions=subexpressions) - def __hash__(self): - return hash(self.name) - - def __eq__(self, other): - if not isinstance(other, ExtrapolationOutflow): - return False - return self.__dict__ == other.__dict__ - - # end class ExtrapolationOutflow @@ -487,15 +459,6 @@ class FixedDensity(LbBoundary): return subexpressions + [Assignment(f_in(inv_dir[dir_symbol]), 2 * eq_component - f_out(dir_symbol))] - def __hash__(self): - return hash(self.name) - - def __eq__(self, other): - if not isinstance(other, FixedDensity): - return False - return self.__dict__ == other.__dict__ - - # end class FixedDensity @@ -530,15 +493,6 @@ class DiffusionDirichlet(LbBoundary): return [Assignment(f_in(inv_dir[dir_symbol]), 2 * w_dir * self.concentration - f_out(dir_symbol))] - def __hash__(self): - return hash(self.name) - - def __eq__(self, other): - if not isinstance(other, DiffusionDirichlet): - return False - return self.__dict__ == other.__dict__ - - # end class DiffusionDirichlet @@ -562,15 +516,6 @@ class NeumannByCopy(LbBoundary): return [Assignment(f_in(inv_dir[dir_symbol]), f_out(inv_dir[dir_symbol])), Assignment(f_out[neighbour_offset](dir_symbol), f_out(dir_symbol))] - def __hash__(self): - return hash(self.name) - - def __eq__(self, other): - if not isinstance(other, NeumannByCopy): - return False - return self.__dict__ == other.__dict__ - - # end class NeumannByCopy @@ -603,12 +548,4 @@ class StreamInConstant(LbBoundary): return [Assignment(f_in(inv_dir[dir_symbol]), self.constant), Assignment(f_out[neighbour_offset](dir_symbol), self.constant)] - def __hash__(self): - return hash(self.name) - - def __eq__(self, other): - if not isinstance(other, StreamInConstant): - return False - return self.__dict__ == other.__dict__ - # end class StreamInConstant diff --git a/lbmpy/turbulence_models.py b/lbmpy/turbulence_models.py index 1a9bf911b38ff8d43e6507e4183a70e90e9988bc..b73708818e9e54faba6a70754a4a8e01078afb58 100644 --- a/lbmpy/turbulence_models.py +++ b/lbmpy/turbulence_models.py @@ -18,6 +18,29 @@ def frobenius_norm(matrix, factor=1): def add_smagorinsky_model(collision_rule, smagorinsky_constant, omega_output_field=None): + r""" Adds a smagorinsky model to a lattice Boltzmann collision rule. To add the Smagorinsky model to a LB scheme + one has to first compute the strain rate tensor $S_{ij}$ in each cell, and compute the turbulent + viscosity :math:`nu_t` from it. Then the local relaxation rate has to be adapted to match the total viscosity + :math `\nu_{total}` instead of the standard viscosity :math `\nu_0`. + + A fortunate property of LB methods is, that the strain rate tensor can be computed locally from the + non-equilibrium part of the distribution function. This is somewhat surprising, since the strain rate tensor + contains first order derivatives. The strain rate tensor can be obtained by + + .. math :: + S_{ij} = - \frac{3 \omega_s}{2 \rho_{(0)}} \Pi_{ij}^{(neq)} + + where :math `\omega_s` is the relaxation rate that determines the viscosity, :math `\rho_{(0)}` is :math `\rho` + in compressible models and :math `1` for incompressible schemes. + :math `\Pi_{ij}^{(neq)}` is the second order moment tensor of the non-equilibrium part of + the distribution functions + :math `f^{(neq)} = f - f^{(eq)}` and can be computed as + + .. math :: + \Pi_{ij}^{(neq)} = \sum_q c_{qi} c_{qj} \; f_q^{(neq)} + + + """ method = collision_rule.method omega_s = get_shear_relaxation_rate(method) if isinstance(omega_s, float) or isinstance(omega_s, int): diff --git a/lbmpy/updatekernels.py b/lbmpy/updatekernels.py index f979880b0eaea78800bff18ba4d592b49160408f..5092617f7b468b7aa7fdb2d3af15ac4767c2be30 100644 --- a/lbmpy/updatekernels.py +++ b/lbmpy/updatekernels.py @@ -12,30 +12,32 @@ from pystencils.sympyextensions import fast_subs # -------------------------------------------- LBM Kernel Creation ----------------------------------------------------- -def create_lbm_kernel(collision_rule, input_field, output_field, accessor): +def create_lbm_kernel(collision_rule, src_field, dst_field=None, accessor=StreamPullTwoFieldsAccessor()): """Replaces the pre- and post collision symbols in the collision rule by field accesses. Args: collision_rule: instance of LbmCollisionRule, defining the collision step - input_field: field used for reading pdf values - output_field: field used for writing pdf values - if accessor.is_inplace this parameter is ignored + src_field: field used for reading pdf values + dst_field: field used for writing pdf values if accessor.is_inplace this parameter is ignored accessor: instance of PdfFieldAccessor, defining where to read and write values - to create e.g. a fused stream-collide kernel + to create e.g. a fused stream-collide kernel See 'fieldaccess.PdfFieldAccessor' Returns: LbmCollisionRule where pre- and post collision symbols have been replaced """ if accessor.is_inplace: - output_field = input_field + dst_field = src_field + + if not accessor.is_inplace and dst_field is None: + raise ValueError("For two field accessors a destination field has to be provided") method = collision_rule.method pre_collision_symbols = method.pre_collision_pdf_symbols post_collision_symbols = method.post_collision_pdf_symbols substitutions = {} - input_accesses = accessor.read(input_field, method.stencil) - output_accesses = accessor.write(output_field, method.stencil) + input_accesses = accessor.read(src_field, method.stencil) + output_accesses = accessor.write(dst_field, method.stencil) for (idx, offset), input_access, output_access in zip(enumerate(method.stencil), input_accesses, output_accesses): substitutions[pre_collision_symbols[idx]] = input_access @@ -55,19 +57,26 @@ def create_lbm_kernel(collision_rule, input_field, output_field, accessor): return result -def create_stream_only_kernel(stencil, src_field, dst_field, accessor=StreamPullTwoFieldsAccessor()): +def create_stream_only_kernel(stencil, src_field, dst_field=None, accessor=StreamPullTwoFieldsAccessor()): """Creates a stream kernel, without collision. Args: - stencil: lattice Boltzmann stencil which is used - src_field: Field the pre-streaming values are read from - dst_field: Field the post-streaming values are written to - accessor: Field accessor which is used to create the update rule. See 'fieldaccess.PdfFieldAccessor' + stencil: lattice Boltzmann stencil which is used in the form of a tuple of tuples + src_field: field used for reading pdf values + dst_field: field used for writing pdf values if accessor.is_inplace this parameter is ignored + accessor: instance of PdfFieldAccessor, defining where to read and write values + to create e.g. a fused stream-collide kernel See 'fieldaccess.PdfFieldAccessor' Returns: AssignmentCollection of the stream only update rule """ - temporary_symbols = sp.symbols(f'tmp_:{len(stencil)}') + if accessor.is_inplace: + dst_field = src_field + + if not accessor.is_inplace and dst_field is None: + raise ValueError("For two field accessors a destination field has to be provided") + + temporary_symbols = sp.symbols(f'streamed_:{len(stencil)}') subexpressions = [Assignment(tmp, acc) for tmp, acc in zip(temporary_symbols, accessor.read(src_field, stencil))] main_assignments = [Assignment(acc, tmp) for acc, tmp in zip(accessor.write(dst_field, stencil), temporary_symbols)] return AssignmentCollection(main_assignments, subexpressions=subexpressions) @@ -103,13 +112,34 @@ def create_stream_pull_only_kernel(stencil, numpy_arr=None, src_field_name="src" return create_stream_only_kernel(stencil, src, dst, accessor=StreamPullTwoFieldsAccessor()) -def create_stream_pull_with_output_kernel(lb_method, src_field, dst_field, output): +def create_stream_pull_with_output_kernel(lb_method, src_field, dst_field=None, output=None, + accessor=StreamPullTwoFieldsAccessor()): + """Creates a stream kernel, without collision but macroscopic quantaties like density or velocity can be calculated. + + Args: + lb_method: lattice Boltzmann method see 'creationfunctions.create_lb_method' + src_field: field used for reading pdf values + dst_field: field used for writing pdf values if accessor.is_inplace this parameter is ignored + output: dictonary which containes macroscopic quantities as keys which should be calculated and fields as + values which should be used to write the data e.g.: {'density': density_field} + accessor: instance of PdfFieldAccessor, defining where to read and write values + to create e.g. a fused stream-collide kernel See 'fieldaccess.PdfFieldAccessor' + + Returns: + AssignmentCollection of the stream only update rule + """ + if accessor.is_inplace: + dst_field = src_field + + if not accessor.is_inplace and dst_field is None: + raise ValueError("For two field accessors a destination field has to be provided") + stencil = lb_method.stencil cqc = lb_method.conserved_quantity_computation streamed = sp.symbols(f"streamed_:{len(stencil)}") - accessor = StreamPullTwoFieldsAccessor() stream_assignments = [Assignment(a, b) for a, b in zip(streamed, accessor.read(src_field, stencil))] - output_eq_collection = cqc.output_equations_from_pdfs(streamed, output) + output_eq_collection = cqc.output_equations_from_pdfs(streamed, output) if output\ + else AssignmentCollection(main_assignments=[]) write_eqs = [Assignment(a, b) for a, b in zip(accessor.write(dst_field, stencil), streamed)] subexpressions = stream_assignments + output_eq_collection.subexpressions diff --git a/lbmpy_tests/test_boundary_handling.py b/lbmpy_tests/test_boundary_handling.py index 9be3e4e85fb42056962639d9dfb83cb34f1cd792..13f6880efbe1d4bccd0dedb49a32cf30618976eb 100644 --- a/lbmpy_tests/test_boundary_handling.py +++ b/lbmpy_tests/test_boundary_handling.py @@ -120,33 +120,45 @@ def test_boundary_utility_functions(): noslip = NoSlip("noslip") assert noslip == NoSlip("noslip") assert not noslip == NoSlip("test") + assert not noslip == UBB((0, 0), name="ubb") + + assert noslip.name == "noslip" + noslip.name = "test name setter" + assert noslip.name == "test name setter" ubb = UBB((0, 0), name="ubb") assert ubb == UBB((0, 0), name="ubb") assert not noslip == UBB((0, 0), name="test") + assert not ubb == NoSlip("noslip") simple_extrapolation = SimpleExtrapolationOutflow(normal_direction=stencil[4], stencil=stencil, name="simple") assert simple_extrapolation == SimpleExtrapolationOutflow(normal_direction=stencil[4], stencil=stencil, name="simple") assert not simple_extrapolation == SimpleExtrapolationOutflow(normal_direction=stencil[4], stencil=stencil, name="test") + assert not simple_extrapolation == NoSlip("noslip") outflow = ExtrapolationOutflow(normal_direction=stencil[4], lb_method=method, name="outflow") assert outflow == ExtrapolationOutflow(normal_direction=stencil[4], lb_method=method, name="outflow") assert not outflow == ExtrapolationOutflow(normal_direction=stencil[4], lb_method=method, name="test") + assert not outflow == simple_extrapolation density = FixedDensity(density=1.0, name="fixedDensity") assert density == FixedDensity(density=1.0, name="fixedDensity") assert not density == FixedDensity(density=1.0, name="test") + assert not density == UBB((0, 0), name="ubb") diffusion = DiffusionDirichlet(concentration=1.0, name="diffusion") assert diffusion == DiffusionDirichlet(concentration=1.0, name="diffusion") assert not diffusion == DiffusionDirichlet(concentration=1.0, name="test") + assert not diffusion == density neumann = NeumannByCopy(name="Neumann") assert neumann == NeumannByCopy(name="Neumann") assert not neumann == NeumannByCopy(name="test") + assert not neumann == diffusion stream = StreamInConstant(constant=1.0, name="stream") assert stream == StreamInConstant(constant=1.0, name="stream") assert not stream == StreamInConstant(constant=1.0, name="test") + assert not stream == noslip