From 9e24f65801ca149076122b53f9924c51fb8837d3 Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Wed, 20 Jan 2021 16:56:40 +0100 Subject: [PATCH] Link-Wise Extrapolation Outflow --- lbmpy/advanced_streaming/indexing.py | 4 +- lbmpy/boundaries/boundaryconditions.py | 152 ++++++++++++---------- lbmpy_tests/test_extrapolation_outflow.py | 50 ++++--- 3 files changed, 110 insertions(+), 96 deletions(-) diff --git a/lbmpy/advanced_streaming/indexing.py b/lbmpy/advanced_streaming/indexing.py index 6078cef7..6548235d 100644 --- a/lbmpy/advanced_streaming/indexing.py +++ b/lbmpy/advanced_streaming/indexing.py @@ -133,10 +133,10 @@ class BetweenTimestepsIndexing: inv = True if isinstance(sp.sympify(idx), sp.Integer): - accessor_subs[fa] = accesses[f_dir][idx].get_shifted(*(fa.offsets)) + accessor_subs[fa] = accesses[f_dir][idx].get_shifted(*fa.offsets) else: arr = self._array_symbols(f_dir, inv, idx) - accessor_subs[fa] = self._pdf_field[arr['offsets']](arr['index']).get_shifted(*(fa.offsets)) + accessor_subs[fa] = self._pdf_field[arr['offsets']](arr['index']).get_shifted(*fa.offsets) return assignments.new_with_substitutions(accessor_subs) diff --git a/lbmpy/boundaries/boundaryconditions.py b/lbmpy/boundaries/boundaryconditions.py index 546c87e0..037ba042 100644 --- a/lbmpy/boundaries/boundaryconditions.py +++ b/lbmpy/boundaries/boundaryconditions.py @@ -7,7 +7,7 @@ from pystencils.data_types import create_type from pystencils.sympyextensions import get_symmetric_part from lbmpy.simplificationfactory import create_simplification_strategy from lbmpy.advanced_streaming.indexing import NeighbourOffsetArrays -from pystencils.stencil import offset_to_direction_string, direction_string_to_offset +from pystencils.stencil import offset_to_direction_string, direction_string_to_offset, inverse_direction class LbBoundary: @@ -28,20 +28,21 @@ class LbBoundary: This function defines the boundary behavior and must therefore be implemented by all boundaries. The boundary is defined through a list of sympy equations from which a boundary kernel is generated. + Args: + f_out: a pystencils field acting as a proxy to access the populations streaming out of the current + cell, i.e. the post-collision PDFs of the previous LBM step + f_in: a pystencils field acting as a proxy to access the populations streaming into the current + cell, i.e. the pre-collision PDFs for the next LBM step + dir_symbol: a sympy symbol that can be used as an index to f_out and f_in. It describes the direction + pointing from the fluid to the boundary cell. + inv_dir: an indexed sympy symbol which describes the inversion of a direction index. It can be used in + the indices of f_out and f_in for retrieving the PDF of the inverse direction. + lb_method: an instance of the LB method used. Use this to adapt the boundary to the method + (e.g. compressibility) + index_field: the boundary index field that can be used to retrieve and update boundary data - :param f_out: a pystencils field acting as a proxy to access the populations streaming out of the current - cell, i.e. the post-collision PDFs of the previous LBM step - :param f_in: a pystencils field acting as a proxy to access the populations streaming into the current - cell, i.e. the pre-collision PDFs for the next LBM step - :param dir_symbol: a sympy symbol that can be used as an index to f_out and f_in. It describes the direction - pointing from the fluid to the boundary cell. - :param inv_dir: an indexed sympy symbol which describes the inversion of a direction index. It can be used in - the indices of f_out and f_in for retrieving the PDF of the inverse direction. - :param lb_method: an instance of the LB method used. Use this to adapt the boundary to the method - (e.g. compressibility) - :param index_field: the boundary index field that can be used to retrieve and update boundary data - - :return: list of pystencils assignments, or pystencils.AssignmentCollection + Returns: + list of pystencils assignments, or pystencils.AssignmentCollection """ raise NotImplementedError("Boundary class has to overwrite __call__") @@ -77,6 +78,7 @@ class LbBoundary: def name(self, new_value): self._name = new_value + # end class Boundary @@ -104,6 +106,7 @@ class NoSlip(LbBoundary): return False return self.name == other.name + # end class NoSlip @@ -182,9 +185,8 @@ class UBB(LbBoundary): c_s_sq = sp.Rational(1, 3) weight_of_direction = LbmWeightInfo.weight_of_direction - vel_term = 2 / c_s_sq \ - * sum([d_i * v_i for d_i, v_i in zip(neighbor_offset, velocity)]) \ - * weight_of_direction(direction, lb_method) + vel_term = 2 / c_s_sq * sum([d_i * v_i for d_i, v_i in zip(neighbor_offset, velocity)]) * weight_of_direction( + direction, lb_method) # Better alternative: in conserved value computation # rename what is currently called density to "virtual_density" @@ -202,6 +204,8 @@ class UBB(LbBoundary): else: return [Assignment(f_in(inv_dir[direction]), f_out(direction) - vel_term)] + + # end class UBB @@ -217,14 +221,10 @@ class SimpleExtrapolationOutflow(LbBoundary): Args: normal_direction: direction vector normal to the outflow - stencil: stencil used for the lattice Boltzmann method + stencil: stencil used by the lattice boltzmann method name: optional name of the boundary. """ - # We need each fluid cell only once, the direction of the outflow is given - # in the constructor. - single_link = True - def __init__(self, normal_direction, stencil, name=None): if isinstance(normal_direction, str): normal_direction = direction_string_to_offset(normal_direction, dim=len(stencil[0])) @@ -235,26 +235,35 @@ class SimpleExtrapolationOutflow(LbBoundary): self.normal_direction = normal_direction super(SimpleExtrapolationOutflow, self).__init__(name) + def get_additional_code_nodes(self, lb_method): + """Return a list of code nodes that will be added in the generated code before the index field loop. + + Args: + lb_method: Lattice Boltzmann method. See :func:`lbmpy.creationfunctions.create_lb_method` + + Returns: + list containing NeighbourOffsetArrays + + """ + return [NeighbourOffsetArrays(lb_method.stencil)] + def __call__(self, f_out, f_in, dir_symbol, inv_dir, lb_method, index_field): - stencil = lb_method.stencil + neighbor_offset = NeighbourOffsetArrays.neighbour_offset(dir_symbol, lb_method.stencil) + tangential_offset = tuple(offset - normal for offset, normal in zip(neighbor_offset, self.normal_direction)) + + return Assignment(f_in.center(inv_dir[dir_symbol]), f_out[tangential_offset](inv_dir[dir_symbol])) - boundary_assignments = [] - for i, stencil_dir in enumerate(stencil): - if all(n == 0 or n == -s for s, n in zip(stencil_dir, self.normal_direction)): - asm = Assignment(f_out[self.normal_direction](i), f_out.center(i)) - boundary_assignments.append(asm) - print(boundary_assignments) - return boundary_assignments # end class SimpleExtrapolationOutflow class ExtrapolationOutflow(LbBoundary): r""" Outflow boundary condition :cite:`geier2015`, equation F.2, with u neglected (listed below). - This boundary condition interpolates missing on the boundary in normal direction. For this interpolation, the - PDF values of the last time step are used. They are interpolated between fluid cell and boundary cell. - To get the PDF values from the last time step an index array is used which stores them. + This boundary condition interpolates populations missing on the boundary in normal direction. + For this interpolation, the PDF values of the last time step are used. They are interpolated + between fluid cell and boundary cell. To get the PDF values from the last time step an index + array is used which stores them. .. math :: f_{\overline{1}jkxyzt} = f_{\overline{1}jk(x - \Delta x)yz(t - \Delta t)} c \theta^{\frac{1}{2}} @@ -276,10 +285,6 @@ class ExtrapolationOutflow(LbBoundary): positional arguments, specifying the initial velocity on boundary nodes """ - # We need each fluid cell only once, the direction of the outflow is given - # in the constructor. - single_link = True - def __init__(self, normal_direction, lb_method, dt=1, dx=1, name=None, streaming_pattern='pull', zeroth_timestep=Timestep.BOTH, initial_density=None, initial_velocity=None): @@ -335,56 +340,65 @@ class ExtrapolationOutflow(LbBoundary): return pdf_acc.read_pdf(boundary_data.pdf_array, fluid_cell, j) for entry in boundary_data.index_array: - fluid_cell = tuple(entry[c] for c in coord_names) - boundary_cell = tuple(f + o for f, o in zip(fluid_cell, self.normal_direction)) + center = tuple(entry[c] for c in coord_names) + direction = self.stencil[entry["dir"]] + inv_dir = self.stencil.index(inverse_direction(direction)) + tangential_offset = tuple(offset - normal for offset, normal in zip(direction, self.normal_direction)) + domain_cell = tuple(f + o for f, o in zip(center, tangential_offset)) + outflow_cell = tuple(f + o for f, o in zip(center, direction)) # Initial fluid cell PDF values - for j, stencil_dir in enumerate(self.stencil): - if all(n == 0 or n == -s for s, n in zip(stencil_dir, self.normal_direction)): - entry[f'pdf_{j}'] = pdf_acc.read_pdf(boundary_data.pdf_array, fluid_cell, j) - entry[f'pdf_nd_{j}'] = get_boundary_cell_pdfs(fluid_cell, boundary_cell, j) + entry['pdf'] = pdf_acc.read_pdf(boundary_data.pdf_array, domain_cell, inv_dir) + entry['pdf_nd'] = get_boundary_cell_pdfs(domain_cell, outflow_cell, inv_dir) @property def additional_data(self): - """Used internally only. For the ExtrapolationOutflow information of the precious PDF values is needed. This - information is added to the boundary""" - data = [] - for i, stencil_dir in enumerate(self.stencil): - if all(n == 0 or n == -s for s, n in zip(stencil_dir, self.normal_direction)): - data.append((f'pdf_{i}', create_type("double"))) - data.append((f'pdf_nd_{i}', create_type("double"))) + """Used internally only. For the ExtrapolationOutflow information of the previous PDF values is needed. This + information is stored in the index vector.""" + data = [('pdf', create_type("double")), ('pdf_nd', create_type("double"))] return data @property def additional_data_init_callback(self): - """The initialisation of the additional data is implemented internally for this class. - Thus no callback can be provided""" - if callable(self.init_callback): - return self.init_callback + return self.init_callback + + def get_additional_code_nodes(self, lb_method): + """Return a list of code nodes that will be added in the generated code before the index field loop. + + Args: + lb_method: Lattice Boltzmann method. See :func:`lbmpy.creationfunctions.create_lb_method` + + Returns: + list containing NeighbourOffsetArrays + + """ + return [NeighbourOffsetArrays(lb_method.stencil)] def __call__(self, f_out, f_in, dir_symbol, inv_dir, lb_method, index_field): subexpressions = [] boundary_assignments = [] dtdx = sp.Rational(self.dt, self.dx) - for i, stencil_dir in enumerate(self.stencil): - if all(n == 0 or n == -s for s, n in zip(stencil_dir, self.normal_direction)): + neighbor_offset = NeighbourOffsetArrays.neighbour_offset(dir_symbol, lb_method.stencil) + tangential_offset = tuple(offset - normal for offset, normal in zip(neighbor_offset, self.normal_direction)) - interpolated_pdf_sym = sp.Symbol(f'pdf_inter_{i}') - interpolated_pdf_asm = Assignment(interpolated_pdf_sym, (index_field[0](f'pdf_{i}') * (self.c * dtdx)) - + ((sp.Number(1) - self.c * dtdx) * index_field[0](f'pdf_nd_{i}'))) - subexpressions.append(interpolated_pdf_asm) + interpolated_pdf_sym = sp.Symbol('pdf_inter') + interpolated_pdf_asm = Assignment(interpolated_pdf_sym, (index_field[0]('pdf') * (self.c * dtdx)) + + ((sp.Number(1) - self.c * dtdx) * index_field[0]('pdf_nd'))) + subexpressions.append(interpolated_pdf_asm) - asm = Assignment(f_out[self.normal_direction](i), interpolated_pdf_sym) - boundary_assignments.append(asm) + asm = Assignment(f_in.center(inv_dir[dir_symbol]), interpolated_pdf_sym) + boundary_assignments.append(asm) - asm = Assignment(index_field[0](f'pdf_{i}'), f_out.center(i)) - boundary_assignments.append(asm) + asm = Assignment(index_field[0]('pdf'), f_out[tangential_offset](inv_dir[dir_symbol])) + boundary_assignments.append(asm) - asm = Assignment(index_field[0](f'pdf_nd_{i}'), interpolated_pdf_sym) - boundary_assignments.append(asm) + asm = Assignment(index_field[0]('pdf_nd'), interpolated_pdf_sym) + boundary_assignments.append(asm) return AssignmentCollection(boundary_assignments, subexpressions=subexpressions) + + # end class ExtrapolationOutflow @@ -404,7 +418,6 @@ class FixedDensity(LbBoundary): self._density = density def __call__(self, f_out, f_in, dir_symbol, inv_dir, lb_method, index_field): - def remove_asymmetric_part_of_main_assignments(assignment_collection, degrees_of_freedom): new_main_assignments = [Assignment(a.lhs, get_symmetric_part(a.rhs, degrees_of_freedom)) for a in assignment_collection.main_assignments] @@ -438,6 +451,8 @@ class FixedDensity(LbBoundary): return subexpressions + [Assignment(f_in(inv_dir[dir_symbol]), 2 * eq_component - f_out(dir_symbol))] + + # end class FixedDensity @@ -467,6 +482,8 @@ class NeumannByCopy(LbBoundary): def __eq__(self, other): return type(other) == NeumannByCopy + + # end class NeumannByCopy @@ -479,6 +496,7 @@ class StreamInConstant(LbBoundary): name: optional name of the boundary. """ + def __init__(self, constant, name=None): super(StreamInConstant, self).__init__(name) self._constant = constant diff --git a/lbmpy_tests/test_extrapolation_outflow.py b/lbmpy_tests/test_extrapolation_outflow.py index e44f216c..3ca6468f 100644 --- a/lbmpy_tests/test_extrapolation_outflow.py +++ b/lbmpy_tests/test_extrapolation_outflow.py @@ -1,8 +1,9 @@ +from pystencils.stencil import inverse_direction + from lbmpy.stencils import get_stencil from lbmpy.advanced_streaming.utility import AccessPdfValues, get_timesteps import pytest import numpy as np -import sympy as sp from pystencils.datahandling import create_data_handling from lbmpy.boundaries import LatticeBoltzmannBoundaryHandling, SimpleExtrapolationOutflow, ExtrapolationOutflow @@ -10,6 +11,8 @@ from lbmpy.creationfunctions import create_lb_method from lbmpy.advanced_streaming.utility import streaming_patterns from pystencils.slicing import get_ghost_region_slice +from itertools import product + @pytest.mark.parametrize('stencil', ['D2Q9', 'D3Q27']) @pytest.mark.parametrize('streaming_pattern', streaming_patterns) @@ -19,7 +22,7 @@ def test_pdf_simple_extrapolation(stencil, streaming_pattern): values_per_cell = len(stencil) # Field contains exactly one fluid cell - domain_size = (1,) * dim + domain_size = (3,) * dim for timestep in get_timesteps(streaming_pattern): dh = create_data_handling(domain_size, default_target='cpu') lb_method = create_lb_method(stencil=stencil) @@ -36,30 +39,23 @@ def test_pdf_simple_extrapolation(stencil, streaming_pattern): pdf_arr = dh.cpu_arrays[pdf_field.name] # Set up the domain with artificial PDF values - center = (1,) * dim + # center = (1,) * dim out_access = AccessPdfValues(stencil, streaming_pattern, timestep, 'out') - for q in range(values_per_cell): - out_access.write_pdf(pdf_arr, center, q, q) + for cell in product(*(range(1, 4) for _ in range(dim))): + for q in range(values_per_cell): + out_access.write_pdf(pdf_arr, cell, q, q) # Do boundary handling bh(prev_timestep=timestep) - center = np.array(center) # Check PDF values in_access = AccessPdfValues(stencil, streaming_pattern, timestep.next(), 'in') # Inbound in center cell - for q, streaming_dir in enumerate(stencil): - f = in_access.read_pdf(pdf_arr, center, q) - assert f == q - - # Outbound in neighbors - for normal_dir in stencil[1:]: - for q, streaming_dir in enumerate(stencil): - neighbor = center + np.array(normal_dir) - if all(n == 0 or n == -s for s, n in zip(streaming_dir, normal_dir)): - f = out_access.read_pdf(pdf_arr, neighbor, q) - assert f == q + for cell in product(*(range(1, 4) for _ in range(dim))): + for q in range(values_per_cell): + f = in_access.read_pdf(pdf_arr, cell, q) + assert f == q def test_extrapolation_outflow_initialization_by_copy(): @@ -94,12 +90,12 @@ def test_extrapolation_outflow_initialization_by_copy(): blocks = list(dh.iterate()) index_list = blocks[0][bh._index_array_name].boundary_object_to_index_list[outflow] - assert len(index_list) == 5 + assert len(index_list) == 13 for entry in index_list: - for j, stencil_dir in enumerate(stencil): - if all(n == 0 or n == -s for s, n in zip(stencil_dir, normal_dir)): - assert entry[f'pdf_{j}'] == j - assert entry[f'pdf_nd_{j}'] == j + direction = stencil[entry["dir"]] + inv_dir = stencil.index(inverse_direction(direction)) + assert entry[f'pdf'] == inv_dir + assert entry[f'pdf_nd'] == inv_dir def test_extrapolation_outflow_initialization_by_callback(): @@ -120,7 +116,7 @@ def test_extrapolation_outflow_initialization_by_callback(): normal_dir = (1, 0) density_callback = lambda x, y: 1 velocity_callback = lambda x, y: (0, 0) - outflow = ExtrapolationOutflow(normal_dir, lb_method, + outflow = ExtrapolationOutflow(normal_dir, lb_method, streaming_pattern=streaming_pattern, zeroth_timestep=zeroth_timestep, initial_density=density_callback, @@ -133,8 +129,8 @@ def test_extrapolation_outflow_initialization_by_callback(): blocks = list(dh.iterate()) index_list = blocks[0][bh._index_array_name].boundary_object_to_index_list[outflow] - assert len(index_list) == 5 + assert len(index_list) == 13 for entry in index_list: - for j, stencil_dir in enumerate(stencil): - if all(n == 0 or n == -s for s, n in zip(stencil_dir, normal_dir)): - assert entry[f'pdf_nd_{j}'] == weights[j] + direction = stencil[entry["dir"]] + inv_dir = stencil.index(inverse_direction(direction)) + assert entry[f'pdf_nd'] == weights[inv_dir] -- GitLab