def test_free_slip_equivalence(): # check if Free slip BC does the same if the normal direction is specified or not stencil = LBStencil(Stencil.D2Q9) dh = create_data_handling(domain_size=(4, 4), periodicity=(False, False)) src1 = dh.add_array('src1', values_per_cell=stencil.Q, alignment=True) src2 = dh.add_array('src2', values_per_cell=stencil.Q, alignment=True) dh.fill('src1', 0.0, ghost_layers=True) dh.fill('src2', 0.0, ghost_layers=True) shape = dh.gather_array('src1', ghost_layers=True).shape num = 0 for x in range(shape[0]): for y in range(shape[1]): for direction in range(shape[2]): dh.cpu_arrays['src1'][x, y, direction] = num dh.cpu_arrays['src2'][x, y, direction] = num num += 1 method = create_lb_method(lbm_config=LBMConfig( stencil=stencil, method=Method.SRT, relaxation_rate=1.8)) bh1 = LatticeBoltzmannBoundaryHandling(method, dh, 'src1', name="bh1") free_slip1 = FreeSlip(stencil=stencil) bh1.set_boundary(free_slip1, slice_from_direction('N', dh.dim)) bh2 = LatticeBoltzmannBoundaryHandling(method, dh, 'src2', name="bh2") free_slip2 = FreeSlip(stencil=stencil, normal_direction=(0, -1)) bh2.set_boundary(free_slip2, slice_from_direction('N', dh.dim)) bh1() bh2() assert np.array_equal(dh.cpu_arrays['src1'], dh.cpu_arrays['src2'])
def test_boundary_data_setter(): dh = SerialDataHandling(domain_size=(7, 7)) src = dh.add_array('src') dh.fill("src", 0.0, ghost_layers=True) dh.fill("src", 1.0, ghost_layers=False) boundary_stencil = [(1, 0), (-1, 0), (0, 1), (0, -1)] boundary_handling = BoundaryHandling(dh, src.name, boundary_stencil, name="boundary_handling", target=Target.CPU) neumann = Neumann() for d in 'N': boundary_handling.set_boundary(neumann, slice_from_direction(d, dim=2)) boundary_handling.prepare() for b in dh.iterate(ghost_layers=True): index_array_bd = b[boundary_handling._index_array_name] data_setter = index_array_bd.boundary_object_to_data_setter[ boundary_handling.boundary_objects[0]] y_pos = data_setter.boundary_cell_positions(1) assert all(y_pos == 5.5) assert np.all(data_setter.link_offsets() == [0, -1]) assert np.all(data_setter.link_positions(1) == 6.)
def test_dirichlet(with_indices): value = (1, 20, 3) if with_indices else 1 dh = SerialDataHandling(domain_size=(7, 7)) src = dh.add_array('src', values_per_cell=3 if with_indices else 1) dh.cpu_arrays.src[...] = np.random.rand(*src.shape) boundary_stencil = [(1, 0), (-1, 0), (0, 1), (0, -1)] boundary_handling = BoundaryHandling(dh, src.name, boundary_stencil) dirichlet = Dirichlet(value) assert dirichlet.name == 'Dirichlet' dirichlet.name = "wall" assert dirichlet.name == 'wall' for d in ('N', 'S', 'W', 'E'): boundary_handling.set_boundary(dirichlet, slice_from_direction(d, dim=2)) boundary_handling() assert all( [np.allclose(a, np.array(value)) for a in dh.cpu_arrays.src[1:-2, 0]]) assert all( [np.allclose(a, np.array(value)) for a in dh.cpu_arrays.src[1:-2, -1]]) assert all( [np.allclose(a, np.array(value)) for a in dh.cpu_arrays.src[0, 1:-2]]) assert all( [np.allclose(a, np.array(value)) for a in dh.cpu_arrays.src[-1, 1:-2]])
def test_boundary_utility(): dh = SerialDataHandling(domain_size=(7, 7)) src = dh.add_array('src') dh.fill("src", 0.0, ghost_layers=True) boundary_stencil = [(1, 0), (-1, 0), (0, 1), (0, -1)] boundary_handling = BoundaryHandling(dh, src.name, boundary_stencil, name="boundary_handling", target=Target.CPU) neumann = Neumann() dirichlet = Dirichlet(2) for d in ('N', 'S', 'W', 'E'): boundary_handling.set_boundary(neumann, slice_from_direction(d, dim=2)) boundary_handling.set_boundary(neumann, (slice(2, 4, None), slice(2, 4, None))) boundary_handling.prepare() assert boundary_handling.get_flag( boundary_handling.boundary_objects[0]) == 2 assert boundary_handling.shape == dh.shape assert boundary_handling.flag_array_name == 'boundary_handlingFlags' mask_neumann = boundary_handling.get_mask( (slice(0, 7), slice(0, 7)), boundary_handling.boundary_objects[0]) np.testing.assert_almost_equal(mask_neumann[1:3, 1:3], 2) mask_domain = boundary_handling.get_mask((slice(0, 7), slice(0, 7)), "domain") assert np.sum(mask_domain) == 7**2 - 4 def set_sphere(x, y): mid = (4, 4) radius = 2 return (x - mid[0])**2 + (y - mid[1])**2 < radius**2 boundary_handling.set_boundary(dirichlet, mask_callback=set_sphere, force_flag_value=4) mask_dirichlet = boundary_handling.get_mask( (slice(0, 7), slice(0, 7)), boundary_handling.boundary_objects[1]) assert np.sum(mask_dirichlet) == 48 assert boundary_handling.set_boundary("domain") == 1 assert boundary_handling.set_boundary(dirichlet, mask_callback=set_sphere, force_flag_value=8, replace=False) == 4 assert boundary_handling.set_boundary(dirichlet, force_flag_value=16, replace=False) == 4 assert boundary_handling.set_boundary_where_flag_is_set( boundary_handling.boundary_objects[0], 16) == 16
def test_parallel_datahandling_boundary_conditions(): pytest.importorskip('waLBerla.cuda') dh = create_data_handling(domain_size=(7, 7), periodicity=True, parallel=True, default_target=pystencils.Target.GPU) src = dh.add_array('src', values_per_cell=1) dh.fill(src.name, 0.0, ghost_layers=True) dh.fill(src.name, 1.0, ghost_layers=False) src2 = dh.add_array('src2', values_per_cell=1) src_cpu = dh.add_array('src_cpu', values_per_cell=1, gpu=False) dh.fill(src_cpu.name, 0.0, ghost_layers=True) dh.fill(src_cpu.name, 1.0, ghost_layers=False) boundary_stencil = [(1, 0), (-1, 0), (0, 1), (0, -1)] boundary_handling_cpu = BoundaryHandling(dh, src_cpu.name, boundary_stencil, name="boundary_handling_cpu", target=pystencils.Target.CPU) boundary_handling = BoundaryHandling(dh, src.name, boundary_stencil, name="boundary_handling_gpu", target=pystencils.Target.GPU) neumann = Neumann() for d in ('N', 'S', 'W', 'E'): boundary_handling.set_boundary(neumann, slice_from_direction(d, dim=2)) boundary_handling_cpu.set_boundary(neumann, slice_from_direction(d, dim=2)) boundary_handling.prepare() boundary_handling_cpu.prepare() boundary_handling_cpu() dh.all_to_gpu() boundary_handling() dh.all_to_cpu() for block in dh.iterate(): np.testing.assert_almost_equal(block[src_cpu.name], block[src.name]) assert dh.custom_data_names == ('boundary_handling_cpuIndexArrays', 'boundary_handling_gpuIndexArrays') dh.swap(src.name, src2.name, gpu=True)
def create_lid_driven_cavity(domain_size=None, lid_velocity=0.005, lbm_kernel=None, parallel=False, data_handling=None, **kwargs): """Creates a lid driven cavity scenario. Args: domain_size: tuple specifying the number of cells in each dimension lid_velocity: x velocity of lid in lattice coordinates. lbm_kernel: a LBM function, which would otherwise automatically created kwargs: other parameters are passed on to the method, see :mod:`lbmpy.creationfunctions` parallel: True for distributed memory parallelization with walberla data_handling: see documentation of :func:`create_fully_periodic_flow` Returns: instance of :class:`Scenario` """ assert domain_size is not None or data_handling is not None if data_handling is None: optimization = kwargs.get('optimization', None) target = optimization.get('target', None) if optimization else None data_handling = create_data_handling(domain_size, periodicity=False, default_ghost_layers=1, parallel=parallel, default_target=target) step = LatticeBoltzmannStep(data_handling=data_handling, lbm_kernel=lbm_kernel, name="ldc", **kwargs) my_ubb = UBB(velocity=[lid_velocity, 0, 0][:step.method.dim]) step.boundary_handling.set_boundary(my_ubb, slice_from_direction('N', step.dim)) for direction in ('W', 'E', 'S') if step.dim == 2 else ('W', 'E', 'S', 'T', 'B'): step.boundary_handling.set_boundary( NoSlip(), slice_from_direction(direction, step.dim)) return step
def test_boundary_gpu(): pytest.importorskip('pycuda') dh = SerialDataHandling(domain_size=(7, 7), default_target=Target.GPU) src = dh.add_array('src') dh.fill("src", 0.0, ghost_layers=True) dh.fill("src", 1.0, ghost_layers=False) src_cpu = dh.add_array('src_cpu', gpu=False) dh.fill("src_cpu", 0.0, ghost_layers=True) dh.fill("src_cpu", 1.0, ghost_layers=False) boundary_stencil = [(1, 0), (-1, 0), (0, 1), (0, -1)] boundary_handling_cpu = BoundaryHandling(dh, src_cpu.name, boundary_stencil, name="boundary_handling_cpu", target=Target.CPU) boundary_handling = BoundaryHandling(dh, src.name, boundary_stencil, name="boundary_handling_gpu", target=Target.GPU) neumann = Neumann() for d in ('N', 'S', 'W', 'E'): boundary_handling.set_boundary(neumann, slice_from_direction(d, dim=2)) boundary_handling_cpu.set_boundary(neumann, slice_from_direction(d, dim=2)) boundary_handling.prepare() boundary_handling_cpu.prepare() boundary_handling_cpu() dh.all_to_gpu() boundary_handling() dh.all_to_cpu() np.testing.assert_almost_equal(dh.cpu_arrays["src_cpu"], dh.cpu_arrays["src"])
def test_add_fix_steps(): dh = SerialDataHandling(domain_size=(7, 7)) src = dh.add_array('src') dh.fill("src", 0.0, ghost_layers=True) dh.fill("src", 1.0, ghost_layers=False) boundary_stencil = [(1, 0), (-1, 0), (0, 1), (0, -1)] boundary_handling = BoundaryHandling(dh, src.name, boundary_stencil, name="boundary_handling", target=pystencils.Target.CPU) neumann = Neumann() for d in ('N', 'S', 'W', 'E'): boundary_handling.set_boundary(neumann, slice_from_direction(d, dim=2)) timeloop = TimeLoop(steps=1) boundary_handling.add_fixed_steps(timeloop) timeloop.run() assert np.sum(dh.cpu_arrays['src']) == 7 * 7 + 7 * 4
def test_kernel_vs_copy_boundary(): dh = SerialDataHandling(domain_size=(7, 7)) src = dh.add_array('src') dst_builtin = dh.add_array_like('dst_builtin', 'src') dst_python_copy = dh.add_array_like('dst_python_copy', 'src') dst_handling = dh.add_array_like('dst_handling', 'src') src_arr = np.arange(dh.shape[0] * dh.shape[1]).reshape(dh.shape) def reset_src(): for block in dh.iterate(ghost_layers=True, inner_ghost_layers=True): np.copyto(block['src'], np.random.rand(*block.shape)) for block in dh.iterate(ghost_layers=False, inner_ghost_layers=True): np.copyto(block['src'], src_arr) for b in dh.iterate(ghost_layers=False, inner_ghost_layers=True): np.copyto(b['dst_builtin'], 42) np.copyto(b['dst_python_copy'], 43) np.copyto(b['dst_handling'], 44) flags = dh.add_array('flags', dtype=np.uint8) dh.fill(flags.name, 0) borders = ['N', 'S', 'E', 'W'] for d in borders: dh.fill(flags.name, 1, slice_obj=slice_from_direction(d, dim=2), ghost_layers=True, inner_ghost_layers=True) rhs = sum(src.neighbors([(1, 0), (-1, 0), (0, 1), (0, -1)])) simple_kernel = create_kernel([Assignment(dst_python_copy.center, rhs)]).compile() kernel_handling = create_kernel([Assignment(dst_handling.center, rhs)]).compile() assignments_with_boundary = add_neumann_boundary( [Assignment(dst_builtin.center, rhs)], fields=[src], flag_field=flags, boundary_flag=1) kernel_with_boundary = create_kernel(assignments_with_boundary).compile() # ------ Method 1: Built-in boundary reset_src() dh.run_kernel(kernel_with_boundary) # ------ Method 2: Using python to copy out the values (reference) reset_src() for b in dh.iterate(): arr = b['src'] arr[:, 0] = arr[:, 1] arr[:, -1] = arr[:, -2] arr[0, :] = arr[1, :] arr[-1, :] = arr[-2, :] dh.run_kernel(simple_kernel) # ------ Method 3: Using boundary handling to copy out the values reset_src() boundary_stencil = [(1, 0), (-1, 0), (0, 1), (0, -1)] boundary_handling = BoundaryHandling(dh, src.name, boundary_stencil) neumann = Neumann() assert neumann.name == 'Neumann' neumann.name = "wall" assert neumann.name == 'wall' assert neumann.additional_data_init_callback is None assert len(neumann.additional_data) == 0 for d in ('N', 'S', 'W', 'E'): boundary_handling.set_boundary(neumann, slice_from_direction(d, dim=2)) boundary_handling() dh.run_kernel(kernel_handling) python_copy_result = dh.gather_array('dst_python_copy') builtin_result = dh.gather_array('dst_builtin') handling_result = dh.gather_array('dst_handling') np.testing.assert_almost_equal(python_copy_result, builtin_result) np.testing.assert_almost_equal(python_copy_result, handling_result) with TemporaryDirectory() as tmp_dir: pytest.importorskip('pyevtk') boundary_handling.geometry_to_vtk(file_name=os.path.join( tmp_dir, 'test_output1'), ghost_layers=False) boundary_handling.geometry_to_vtk(file_name=os.path.join( tmp_dir, 'test_output2'), ghost_layers=True) boundaries = list(boundary_handling._boundary_object_to_boundary_info. keys()) + ['domain'] boundary_handling.geometry_to_vtk(file_name=os.path.join( tmp_dir, 'test_output3'), boundaries=boundaries[0], ghost_layers=False)
def create_channel(domain_size=None, force=None, pressure_difference=None, u_max=None, diameter_callback=None, duct=False, wall_boundary=NoSlip(), parallel=False, data_handling=None, **kwargs): """Create a channel scenario (2D or 3D). The channel can be driven either by force, velocity inflow or pressure difference. Choose one and pass exactly one of the parameters 'force', 'pressure_difference' or 'u_max'. Args: domain_size: size of the simulation domain. First coordinate is the flow direction. force: Periodic channel, driven by a body force. Pass force in flow direction in lattice units here. pressure_difference: Inflow and outflow are fixed pressure conditions, with the given pressure difference. u_max: Parabolic velocity profile prescribed at inflow, pressure boundary =1.0 at outflow. diameter_callback: optional callback for channel with varying diameters. Only valid if duct=False. The callback receives x coordinate array and domain_size and returns a an array of diameters of the same shape duct: if true the channel has rectangular instead of circular cross section wall_boundary: instance of boundary class that should be set at the channel walls parallel: True for distributed memory parallelization with walberla data_handling: see documentation of :func:`create_fully_periodic_flow` kwargs: all other keyword parameters are passed directly to scenario class. """ assert domain_size is not None or data_handling is not None if [bool(p) for p in (force, pressure_difference, u_max)].count(True) != 1: raise ValueError( "Please specify exactly one of the parameters 'force', 'pressure_difference' or 'u_max'" ) periodicity = (True, False, False) if force else (False, False, False) if data_handling is None: dim = len(domain_size) assert dim in (2, 3) data_handling = create_data_handling(domain_size, periodicity=periodicity[:dim], default_ghost_layers=1, parallel=parallel) dim = data_handling.dim if force: kwargs['force'] = tuple([force, 0, 0][:dim]) assert data_handling.periodicity[0] step = LatticeBoltzmannStep(data_handling=data_handling, name="force_driven_channel", **kwargs) elif pressure_difference: inflow = FixedDensity(1.0 + pressure_difference) outflow = FixedDensity(1.0) step = LatticeBoltzmannStep(data_handling=data_handling, name="pressure_driven_channel", **kwargs) step.boundary_handling.set_boundary(inflow, slice_from_direction('W', dim)) step.boundary_handling.set_boundary(outflow, slice_from_direction('E', dim)) elif u_max: if duct: raise NotImplementedError( "Velocity inflow for duct flows not yet implemented") step = LatticeBoltzmannStep(data_handling=data_handling, name="velocity_driven_channel", **kwargs) diameter = diameter_callback(np.array([ 0 ]), domain_size)[0] if diameter_callback else min(domain_size[1:]) add_pipe_inflow_boundary(step.boundary_handling, u_max, slice_from_direction('W', dim), flow_direction=0, diameter=diameter) outflow = FixedDensity(1.0) step.boundary_handling.set_boundary(outflow, slice_from_direction('E', dim)) else: assert False directions = ('N', 'S', 'T', 'B') if dim == 3 else ('N', 'S') for direction in directions: step.boundary_handling.set_boundary( wall_boundary, slice_from_direction(direction, dim)) if duct and diameter_callback is not None: raise ValueError( "For duct flows, passing a diameter callback does not make sense.") if not duct: diameter = min(step.boundary_handling.shape[1:]) add_pipe_walls(step.boundary_handling, diameter_callback if diameter_callback else diameter, wall_boundary) return step
def rod_simulation(stencil_name, diameter, parallel=False, entropic=False, re=150, time_to_simulate=17.0, eval_interval=0.5, write_vtk=True, write_numpy=True): if stencil_name == 'D3Q19_new': stencil = Stencil.D3Q19 maxwellian_moments = True elif stencil_name == 'D3Q19_old': stencil = Stencil.D3Q19 maxwellian_moments = False elif stencil_name == 'D3Q27': stencil = Stencil.D3Q27 maxwellian_moments = False else: raise ValueError("Unknown stencil name " + stencil_name) d = diameter length = 16 * d u_max_at_throat = 0.07 u_max_at_inflow = 0.07 / (3**2) # Geometry parameters inflow_area = int(1.5 * d) constriction_length = int(1.8904 * d) throat_length = int(10 / 3 * d) constriction_diameter = int(d / 3) u_mean_at_throat = u_max_at_throat / 2 lattice_viscosity = u_mean_at_throat * constriction_diameter / re omega = relaxation_rate_from_lattice_viscosity(lattice_viscosity) lbm_config = LBMConfig(stencil=LBStencil(stencil), compressible=True, method=Method.SRT, relaxation_rate=omega, entropic=entropic, maxwellian_moments=maxwellian_moments) kernel_params = {} print("ω=", omega) dh = create_data_handling(domain_size=(length, d, d), parallel=parallel) sc = LatticeBoltzmannStep(data_handling=dh, kernel_params=kernel_params, name=stencil_name, lbm_config=lbm_config) # ----------------- Boundary Setup -------------------------------- def pipe_geometry(x, *_): # initialize with full diameter everywhere result = np.ones_like(x) * d # constriction constriction_begin = inflow_area constriction_end = constriction_begin + constriction_length c_x = np.linspace(0, 1, constriction_length) result[constriction_begin:constriction_end] = d * ( 1 - c_x) + constriction_diameter * c_x # throat throat_start = constriction_end throat_end = throat_start + throat_length result[throat_start:throat_end] = constriction_diameter return result bh = sc.boundary_handling add_pipe_inflow_boundary(bh, u_max_at_inflow, make_slice[0, :, :]) outflow = FixedDensity(1.0) bh.set_boundary(outflow, slice_from_direction('E', 3)) wall = NoSlip() add_pipe_walls(bh, pipe_geometry, wall) # ----------------- Run -------------------------------------------- scenario_name = stencil_name if not os.path.exists(scenario_name): os.mkdir(scenario_name) def to_time_steps(non_dimensional_time): return int(diameter / (u_max_at_inflow / 2) * non_dimensional_time) time_steps = to_time_steps(time_to_simulate) eval_interval = to_time_steps(eval_interval) print("Total number of time steps", time_steps) for i in range(time_steps // eval_interval): sc.run(eval_interval) if write_vtk: sc.write_vtk() vel = sc.velocity[:, :, :, :] max_vel = np.max(vel) print("Time steps:", sc.time_steps_run, "/", time_steps, "Max velocity: ", max_vel) if np.isnan(max_vel): raise ValueError("Simulation went unstable") if write_numpy: np.save(scenario_name + f'/velocity_{sc.time_steps_run}', sc.velocity_slice().filled(0.0))
def test_diffusion(): """ Runs the "Diffusion from Plate in Uniform Flow" benchmark as it is described in [ch. 8.6.3, The Lattice Boltzmann Method, Krüger et al.]. dC/dy = 0 ┌───────────────┐ │ → → → │ C = 0 │ → u → │ dC/dx = 0 │ → → → │ └───────────────┘ C = 1 The analytical solution is given by: C(x,y) = 1 * erfc(y / sqrt(4Dx/u)) The hydrodynamic field is not simulated, instead a constant velocity is assumed. """ pytest.importorskip("pycuda") # Parameters domain_size = (1600, 160) omega = 1.38 diffusion = (1 / omega - 0.5) / 3 velocity = 0.05 time_steps = 50000 stencil = LBStencil(Stencil.D2Q9) target = ps.Target.GPU # Data Handling dh = ps.create_data_handling(domain_size=domain_size, default_target=target) vel_field = dh.add_array('vel_field', values_per_cell=stencil.D) dh.fill('vel_field', velocity, 0, ghost_layers=True) dh.fill('vel_field', 0.0, 1, ghost_layers=True) con_field = dh.add_array('con_field', values_per_cell=1) dh.fill('con_field', 0.0, ghost_layers=True) pdfs = dh.add_array('pdfs', values_per_cell=stencil.Q) dh.fill('pdfs', 0.0, ghost_layers=True) pdfs_tmp = dh.add_array('pdfs_tmp', values_per_cell=stencil.Q) dh.fill('pdfs_tmp', 0.0, ghost_layers=True) # Lattice Boltzmann method lbm_config = LBMConfig(stencil=stencil, method=Method.MRT, relaxation_rates=[1, 1.5, 1, 1.5, 1], velocity_input=vel_field, output={'density': con_field}, compressible=True, weighted=True, kernel_type='stream_pull_collide') lbm_opt = LBMOptimisation(symbolic_field=pdfs, symbolic_temporary_field=pdfs_tmp) config = ps.CreateKernelConfig(target=dh.default_target, cpu_openmp=True) method = create_lb_method(lbm_config=lbm_config) method.set_conserved_moments_relaxation_rate(omega) lbm_config = replace(lbm_config, lb_method=method) update_rule = create_lb_update_rule(lbm_config=lbm_config, lbm_optimisation=lbm_opt) kernel = ps.create_kernel(update_rule, config=config).compile() # PDF initalization init = pdf_initialization_assignments(method, con_field.center, vel_field.center_vector, pdfs.center_vector) dh.run_kernel(ps.create_kernel(init).compile()) dh.all_to_gpu() # Boundary Handling bh = LatticeBoltzmannBoundaryHandling(update_rule.method, dh, 'pdfs', name="bh", target=dh.default_target) add_box_boundary(bh, boundary=NeumannByCopy()) bh.set_boundary(DiffusionDirichlet(0), slice_from_direction('W', dh.dim)) bh.set_boundary(DiffusionDirichlet(1), slice_from_direction('S', dh.dim)) # Timeloop for i in range(time_steps): bh() dh.run_kernel(kernel) dh.swap("pdfs", "pdfs_tmp") dh.all_to_cpu() # Verification x = np.arange(1, domain_size[0], 1) y = np.arange(0, domain_size[1], 1) X, Y = np.meshgrid(x, y) analytical = np.zeros(domain_size) analytical[1:, :] = np.vectorize(math.erfc)( Y / np.vectorize(math.sqrt)(4 * diffusion * X / velocity)).transpose() simulated = dh.gather_array('con_field', ghost_layers=False) residual = 0 for i in x: for j in y: residual += (simulated[i, j] - analytical[i, j])**2 residual = math.sqrt(residual / (domain_size[0] * domain_size[1])) assert residual < 1e-2
def flow_around_sphere(stencil, galilean_correction, L_LU, total_steps): if galilean_correction and stencil.Q != 27: return True target = Target.GPU streaming_pattern = 'aa' timesteps = get_timesteps(streaming_pattern) u_max = 0.05 Re = 500000 kinematic_viscosity = (L_LU * u_max) / Re initial_velocity = (u_max, ) + (0, ) * (stencil.D - 1) omega_v = relaxation_rate_from_lattice_viscosity(kinematic_viscosity) channel_size = (10 * L_LU, ) + (5 * L_LU, ) * (stencil.D - 1) sphere_position = (channel_size[0] // 3, ) + (channel_size[1] // 2, ) * (stencil.D - 1) sphere_radius = L_LU // 2 lbm_config = LBMConfig(stencil=stencil, method=Method.CUMULANT, relaxation_rate=omega_v, galilean_correction=galilean_correction) lbm_opt = LBMOptimisation(pre_simplification=True) config = CreateKernelConfig(target=target) lb_method = create_lb_method(lbm_config=lbm_config) def get_extrapolation_kernel(timestep): boundary_assignments = [] indexing = BetweenTimestepsIndexing( pdf_field, stencil, streaming_pattern=streaming_pattern, prev_timestep=timestep) f_out, _ = indexing.proxy_fields for i, d in enumerate(stencil): if d[0] == -1: asm = Assignment(f_out.neighbor(0, 1)(i), f_out.center(i)) boundary_assignments.append(asm) boundary_assignments = indexing.substitute_proxies( boundary_assignments) iter_slice = get_slice_before_ghost_layer((1, ) + (0, ) * (stencil.D - 1)) extrapolation_ast = create_kernel(boundary_assignments, config=CreateKernelConfig( iteration_slice=iter_slice, ghost_layers=1, target=target)) return extrapolation_ast.compile() dh = create_data_handling(channel_size, periodicity=False, default_layout='fzyx', default_target=target) u_field = dh.add_array('u', stencil.D) rho_field = dh.add_array('rho', 1) pdf_field = dh.add_array('pdfs', stencil.Q) dh.fill(u_field.name, 0.0, ghost_layers=True) dh.fill(rho_field.name, 0.0, ghost_layers=True) dh.to_gpu(u_field.name) dh.to_gpu(rho_field.name) lbm_opt = replace(lbm_opt, symbolic_field=pdf_field) bh = LatticeBoltzmannBoundaryHandling(lb_method, dh, pdf_field.name, streaming_pattern=streaming_pattern, target=target) wall = NoSlip() inflow = UBB(initial_velocity) bh.set_boundary(inflow, slice_from_direction('W', stencil.D)) directions = ('N', 'S', 'T', 'B') if stencil.D == 3 else ('N', 'S') for direction in directions: bh.set_boundary(wall, slice_from_direction(direction, stencil.D)) outflow_kernels = [ get_extrapolation_kernel(Timestep.EVEN), get_extrapolation_kernel(Timestep.ODD) ] def sphere_boundary_callback(x, y, z=None): x = x - sphere_position[0] y = y - sphere_position[1] z = z - sphere_position[2] if z is not None else 0 return np.sqrt(x**2 + y**2 + z**2) <= sphere_radius bh.set_boundary(wall, mask_callback=sphere_boundary_callback) init_eqs = pdf_initialization_assignments( lb_method, 1.0, initial_velocity, pdf_field, streaming_pattern=streaming_pattern, previous_timestep=timesteps[0]) init_kernel = create_kernel(init_eqs, config=config).compile() output = {'density': rho_field, 'velocity': u_field} lbm_config = replace(lbm_config, output=output) lb_collision_rule = create_lb_collision_rule(lb_method=lb_method, lbm_config=lbm_config, lbm_optimisation=lbm_opt) lb_kernels = [] for t in timesteps: lbm_config = replace(lbm_config, timestep=t) lbm_config = replace(lbm_config, streaming_pattern=streaming_pattern) lb_kernels.append( create_lb_function(collision_rule=lb_collision_rule, lbm_config=lbm_config, lbm_optimisation=lbm_opt, config=config)) timestep = timesteps[0] dh.run_kernel(init_kernel) stability_check_frequency = 1000 for i in range(total_steps): bh(prev_timestep=timestep) dh.run_kernel(outflow_kernels[timestep.idx]) timestep = timestep.next() dh.run_kernel(lb_kernels[timestep.idx]) if i % stability_check_frequency == 0: dh.to_cpu(u_field.name) assert np.isfinite(dh.cpu_arrays[u_field.name]).all()