def test_save_data(): domain_shape = (2, 2) dh = create_data_handling(domain_size=domain_shape, default_ghost_layers=1, parallel=True) dh.add_array("src", values_per_cell=9) dh.fill("src", 1.0, ghost_layers=True) dh.add_array("dst", values_per_cell=9) dh.fill("dst", 1.0, ghost_layers=True) dh.save_all(str(INPUT_FOLDER) + '/datahandling_parallel_save_test')
def test_load_data(): domain_shape = (2, 2) dh = create_data_handling(domain_size=domain_shape, default_ghost_layers=1, parallel=True) dh.add_array("src", values_per_cell=9) dh.fill("src", 0.0, ghost_layers=True) dh.add_array("dst", values_per_cell=9) dh.fill("dst", 0.0, ghost_layers=True) dh.load_all(str(INPUT_FOLDER) + '/datahandling_parallel_load_test') assert np.all(dh.gather_array('src')) == 1 assert np.all(dh.gather_array('src')) == 1
def test_timeloop(): dh = create_data_handling(domain_size=(2, 2), periodicity=True) pre = dh.add_array('pre_run_field', values_per_cell=1) dh.fill("pre_run_field", 0.0, ghost_layers=True) f = dh.add_array('field', values_per_cell=1) dh.fill("field", 0.0, ghost_layers=True) post = dh.add_array('post_run_field', values_per_cell=1) dh.fill("post_run_field", 0.0, ghost_layers=True) single_step = dh.add_array('single_step_field', values_per_cell=1) dh.fill("single_step_field", 0.0, ghost_layers=True) pre_assignments = Assignment(pre.center, pre.center + 1) pre_kernel = create_kernel(pre_assignments).compile() assignments = Assignment(f.center, f.center + 1) kernel = create_kernel(assignments).compile() post_assignments = Assignment(post.center, post.center + 1) post_kernel = create_kernel(post_assignments).compile() single_step_assignments = Assignment(single_step.center, single_step.center + 1) single_step_kernel = create_kernel(single_step_assignments).compile() fixed_steps = 2 timeloop = TimeLoop(steps=fixed_steps) assert timeloop.fixed_steps == fixed_steps def pre_run(): dh.run_kernel(pre_kernel) def post_run(): dh.run_kernel(post_kernel) def single_step_run(): dh.run_kernel(single_step_kernel) timeloop.add_pre_run_function(pre_run) timeloop.add_post_run_function(post_run) timeloop.add_single_step_function(single_step_run) timeloop.add_call(kernel, {'field': dh.cpu_arrays["field"]}) # the timeloop is initialised with 2 steps. This means a single time step consists of two steps. # Therefore, we have 2 main iterations and one single step iteration in this configuration timeloop.run(time_steps=5) assert np.all(dh.cpu_arrays["pre_run_field"] == 1.0) assert np.all(dh.cpu_arrays["field"] == 2.0) assert np.all(dh.cpu_arrays["single_step_field"] == 1.0) assert np.all(dh.cpu_arrays["post_run_field"] == 1.0) seconds = 2 start = time.perf_counter() timeloop.run_time_span(seconds=seconds) end = time.perf_counter() np.testing.assert_almost_equal(seconds, end - start, decimal=2)
def test_extrapolation_outflow_initialization_by_copy(): stencil = LBStencil(Stencil.D2Q9) domain_size = (1, 5) streaming_pattern = 'esotwist' zeroth_timestep = 'even' pdf_acc = AccessPdfValues(stencil, streaming_pattern=streaming_pattern, timestep=zeroth_timestep, streaming_dir='out') dh = create_data_handling(domain_size, default_target=Target.CPU) lb_method = create_lb_method(stencil=stencil) pdf_field = dh.add_array('f', values_per_cell=stencil.Q) dh.fill(pdf_field.name, np.nan, ghost_layers=True) pdf_arr = dh.cpu_arrays[pdf_field.name] bh = LatticeBoltzmannBoundaryHandling(lb_method, dh, pdf_field.name, streaming_pattern=streaming_pattern, target=Target.CPU) for y in range(1, 6): for j in range(stencil.Q): pdf_acc.write_pdf(pdf_arr, (1, y), j, j) normal_dir = (1, 0) outflow = ExtrapolationOutflow(normal_dir, lb_method, streaming_pattern=streaming_pattern, zeroth_timestep=zeroth_timestep) boundary_slice = get_ghost_region_slice(normal_dir) bh.set_boundary(outflow, boundary_slice) bh.prepare() blocks = list(dh.iterate()) index_list = blocks[0][ bh._index_array_name].boundary_object_to_index_list[outflow] assert len(index_list) == 13 for entry in index_list: 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_pdf_simple_extrapolation(stencil_enum, streaming_pattern): stencil = LBStencil(stencil_enum) # Field contains exactly one fluid cell domain_size = (3, ) * stencil.D for timestep in get_timesteps(streaming_pattern): dh = create_data_handling(domain_size, default_target=Target.CPU) lb_method = create_lb_method(lbm_config=LBMConfig(stencil=stencil)) pdf_field = dh.add_array('f', values_per_cell=stencil.Q) dh.fill(pdf_field.name, np.nan, ghost_layers=True) bh = LatticeBoltzmannBoundaryHandling(lb_method, dh, pdf_field.name, streaming_pattern, target=Target.CPU) # Set up outflows in all directions for normal_dir in stencil[1:]: boundary_obj = SimpleExtrapolationOutflow(normal_dir, stencil) boundary_slice = get_ghost_region_slice(normal_dir) bh.set_boundary(boundary_obj, boundary_slice) pdf_arr = dh.cpu_arrays[pdf_field.name] # Set up the domain with artificial PDF values # center = (1,) * dim out_access = AccessPdfValues(stencil, streaming_pattern, timestep, 'out') for cell in product(*(range(1, 4) for _ in range(stencil.D))): for q in range(stencil.Q): out_access.write_pdf(pdf_arr, cell, q, q) # Do boundary handling bh(prev_timestep=timestep) # Check PDF values in_access = AccessPdfValues(stencil, streaming_pattern, timestep.next(), 'in') # Inbound in center cell for cell in product(*(range(1, 4) for _ in range(stencil.D))): for q in range(stencil.Q): f = in_access.read_pdf(pdf_arr, cell, q) assert f == q
def create_fully_periodic_flow(initial_velocity, periodicity_in_kernel=False, lbm_kernel=None, data_handling=None, parallel=False, **kwargs): """Creates a fully periodic setup with prescribed velocity field. Args: initial_velocity: numpy array that defines an initial velocity for each cell. The shape of this array determines the domain size. periodicity_in_kernel: don't use boundary handling for periodicity, but directly generate the kernel periodic lbm_kernel: a LBM function, which would otherwise automatically created data_handling: data handling instance that is used to create the necessary fields. If a data handling is passed the periodicity and parallel arguments are ignored. parallel: True for distributed memory parallelization with walberla kwargs: other parameters are passed on to the method, see :mod:`lbmpy.creationfunctions` Returns: instance of :class:`Scenario` """ if 'optimization' not in kwargs: kwargs['optimization'] = {} else: kwargs['optimization'] = kwargs['optimization'].copy() domain_size = initial_velocity.shape[:-1] if periodicity_in_kernel: kwargs['optimization']['builtin_periodicity'] = (True, True, True) if data_handling is None: data_handling = create_data_handling( domain_size, periodicity=not periodicity_in_kernel, default_ghost_layers=1, parallel=parallel) step = LatticeBoltzmannStep(data_handling=data_handling, name="periodic_scenario", lbm_kernel=lbm_kernel, **kwargs) for b in step.data_handling.iterate(ghost_layers=False): np.copyto(b[step.velocity_data_name], initial_velocity[b.global_slice]) step.set_pdf_fields_from_macroscopic_values() return step
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_extrapolation_outflow_initialization_by_callback(): stencil = LBStencil(Stencil.D2Q9) domain_size = (1, 5) streaming_pattern = 'esotwist' zeroth_timestep = 'even' dh = create_data_handling(domain_size, default_target=Target.CPU) lb_method = create_lb_method(stencil=stencil) pdf_field = dh.add_array('f', values_per_cell=stencil.Q) dh.fill(pdf_field.name, np.nan, ghost_layers=True) bh = LatticeBoltzmannBoundaryHandling(lb_method, dh, pdf_field.name, streaming_pattern=streaming_pattern, target=Target.CPU) normal_dir = (1, 0) outflow = ExtrapolationOutflow(normal_direction=normal_dir, lb_method=lb_method, streaming_pattern=streaming_pattern, zeroth_timestep=zeroth_timestep, initial_density=lambda x, y: 1, initial_velocity=lambda x, y: (0, 0)) boundary_slice = get_ghost_region_slice(normal_dir) bh.set_boundary(outflow, boundary_slice) bh.prepare() weights = [w.evalf() for w in lb_method.weights] blocks = list(dh.iterate()) index_list = blocks[0][ bh._index_array_name].boundary_object_to_index_list[outflow] assert len(index_list) == 13 for entry in index_list: direction = stencil[entry["dir"]] inv_dir = stencil.index(inverse_direction(direction)) assert entry[f'pdf_nd'] == weights[inv_dir]
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 test_fully_periodic_flow(target, stencil, streaming_pattern): gpu = False if target == Target.GPU: gpu = True # Stencil stencil = LBStencil(stencil) # Streaming inplace = is_inplace(streaming_pattern) timesteps = get_timesteps(streaming_pattern) zeroth_timestep = timesteps[0] # Data Handling and PDF fields domain_size = (30, ) * stencil.D periodicity = (True, ) * stencil.D dh = create_data_handling(domain_size=domain_size, periodicity=periodicity, default_target=target) pdfs = dh.add_array('pdfs', stencil.Q) if not inplace: pdfs_tmp = dh.add_array_like('pdfs_tmp', pdfs.name) # LBM Streaming and Collision lbm_config = LBMConfig(stencil=stencil, method=Method.SRT, relaxation_rate=1.0, streaming_pattern=streaming_pattern) lbm_opt = LBMOptimisation(symbolic_field=pdfs) config = CreateKernelConfig(target=target) if not inplace: lbm_opt = replace(lbm_opt, symbolic_temporary_field=pdfs_tmp) lb_collision = create_lb_collision_rule(lbm_config=lbm_config, lbm_optimisation=lbm_opt, config=config) lb_method = lb_collision.method lb_kernels = [] for t in timesteps: lbm_config = replace(lbm_config, timestep=t) lb_kernels.append( create_lb_function(collision_rule=lb_collision, lbm_config=lbm_config, lbm_optimisation=lbm_opt)) # Macroscopic Values density = 1.0 density_field = dh.add_array('rho', 1) u_x = 0.01 velocity = (u_x, ) * stencil.D velocity_field = dh.add_array('u', stencil.D) u_ref = np.full(domain_size + (stencil.D, ), u_x) setter = macroscopic_values_setter(lb_method, density, velocity, pdfs, streaming_pattern=streaming_pattern, previous_timestep=zeroth_timestep) setter_kernel = create_kernel( setter, config=CreateKernelConfig(target=target, ghost_layers=1)).compile() getter_kernels = [] for t in timesteps: getter = macroscopic_values_getter(lb_method, density_field, velocity_field, pdfs, streaming_pattern=streaming_pattern, previous_timestep=t) getter_kernels.append( create_kernel(getter, config=CreateKernelConfig(target=target, ghost_layers=1)).compile()) # Periodicity periodicity_handler = LBMPeriodicityHandling( stencil, dh, pdfs.name, streaming_pattern=streaming_pattern) # Initialization and Timestep current_timestep = zeroth_timestep def init(): global current_timestep current_timestep = zeroth_timestep dh.run_kernel(setter_kernel) def one_step(): global current_timestep # Periodicty periodicity_handler(current_timestep) # Here, the next time step begins current_timestep = current_timestep.next() # LBM Step dh.run_kernel(lb_kernels[current_timestep.idx]) # Field Swaps if not inplace: dh.swap(pdfs.name, pdfs_tmp.name) # Macroscopic Values dh.run_kernel(getter_kernels[current_timestep.idx]) # Run the simulation init() for _ in range(100): one_step() # Evaluation if gpu: dh.to_cpu(velocity_field.name) u = dh.gather_array(velocity_field.name) # Equal to the steady-state velocity field up to numerical errors assert_allclose(u, u_ref) # Flow must be equal up to numerical error for all streaming patterns global all_results for key, prev_u in all_results.items(): if key[0] == stencil: prev_pattern = key[1] assert_allclose( u, prev_u, err_msg= f'Velocity field for {streaming_pattern} differed from {prev_pattern}!' ) all_results[(stencil, streaming_pattern)] = u
def __init__(self, stencil, streaming_pattern, wall_boundary=None, target=Target.CPU): if wall_boundary is None: wall_boundary = NoSlip() self.target = target self.gpu = target in [Target.GPU] # Stencil self.stencil = stencil self.q = stencil.Q self.dim = stencil.D # Streaming self.streaming_pattern = streaming_pattern self.inplace = is_inplace(self.streaming_pattern) self.timesteps = get_timesteps(streaming_pattern) self.zeroth_timestep = self.timesteps[0] # Domain, Data Handling and PDF fields self.pipe_length = 60 self.pipe_radius = 15 self.domain_size = (self.pipe_length, ) + (2 * self.pipe_radius,) * (self.dim - 1) self.periodicity = (True, ) + (False, ) * (self.dim - 1) self.force = (0.0001, ) + (0.0,) * (self.dim - 1) self.dh = create_data_handling(domain_size=self.domain_size, periodicity=self.periodicity, default_target=self.target) self.pdfs = self.dh.add_array('pdfs', self.q) if not self.inplace: self.pdfs_tmp = self.dh.add_array_like('pdfs_tmp', self.pdfs.name) # LBM Streaming and Collision lbm_config = LBMConfig(stencil=stencil, method=Method.SRT, relaxation_rate=1.0, force_model=ForceModel.GUO, force=self.force, streaming_pattern=streaming_pattern) lbm_opt = LBMOptimisation(symbolic_field=self.pdfs) config = CreateKernelConfig(target=self.target) if not self.inplace: lbm_opt = replace(lbm_opt, symbolic_temporary_field=self.pdfs_tmp) self.lb_collision = create_lb_collision_rule(lbm_config=lbm_config, lbm_optimisation=lbm_opt) self.lb_method = self.lb_collision.method self.lb_kernels = [] for t in self.timesteps: lbm_config = replace(lbm_config, timestep=t) self.lb_kernels.append(create_lb_function(collision_rule=self.lb_collision, lbm_config=lbm_config, lbm_optimisation=lbm_opt, config=config)) # Macroscopic Values self.density = 1.0 self.density_field = self.dh.add_array('rho', 1) u_x = 0.0 self.velocity = (u_x,) * self.dim self.velocity_field = self.dh.add_array('u', self.dim) setter = macroscopic_values_setter( self.lb_method, self.density, self.velocity, self.pdfs, streaming_pattern=self.streaming_pattern, previous_timestep=self.zeroth_timestep) self.init_kernel = create_kernel(setter, config=CreateKernelConfig(target=target, ghost_layers=1)).compile() self.getter_kernels = [] for t in self.timesteps: getter = macroscopic_values_getter( self.lb_method, self.density_field, self.velocity_field, self.pdfs, streaming_pattern=self.streaming_pattern, previous_timestep=t) self.getter_kernels.append(create_kernel(getter, config=CreateKernelConfig(target=target, ghost_layers=1)).compile()) # Periodicity self.periodicity_handler = LBMPeriodicityHandling( self.stencil, self.dh, self.pdfs.name, streaming_pattern=self.streaming_pattern) # Boundary Handling self.wall = wall_boundary self.bh = LatticeBoltzmannBoundaryHandling( self.lb_method, self.dh, self.pdfs.name, streaming_pattern=self.streaming_pattern, target=self.target) self.bh.set_boundary(boundary_obj=self.wall, mask_callback=self.mask_callback) self.current_timestep = self.zeroth_timestep