def test_exotic_boundaries(): step = LatticeBoltzmannStep((50, 50), relaxation_rate=1.8, compressible=False, periodicity=False) add_box_boundary(step.boundary_handling, NeumannByCopy()) step.boundary_handling.set_boundary(StreamInConstant(0), make_slice[0, :]) step.run(100) assert np.max(step.velocity[:, :, :]) < 1e-13
def test_pipe(): """Ensures that pipe can be set up in 2D, 3D, with constant and callback diameter No tests are done that geometry is indeed correct""" def diameter_callback(x, domain_shape): d = domain_shape[1] y = np.ones_like(x) * d y[x > 0.5 * domain_shape[0]] = int(0.3 * d) return y plot = False for domain_size in [(30, 10, 10), (30, 10)]: for diameter in [5, 10, diameter_callback]: sc = LatticeBoltzmannStep(domain_size=domain_size, method=Method.SRT, relaxation_rate=1.9) add_pipe_walls(sc.boundary_handling, diameter) if plot: if len(domain_size) == 2: plt.boundary_handling(sc.boundary_handling) plt.title(f"2D, diameter={str(diameter,)}") plt.show() elif len(domain_size) == 3: plt.subplot(1, 2, 1) plt.boundary_handling(sc.boundary_handling, make_slice[0.5, :, :]) plt.title(f"3D, diameter={str(diameter,)}") plt.subplot(1, 2, 2) plt.boundary_handling(sc.boundary_handling, make_slice[:, 0.5, :]) plt.title(f"3D, diameter={str(diameter, )}") plt.show()
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_slice_mask_combination(): sc = LatticeBoltzmannStep(domain_size=(30, 30), method=Method.SRT, relaxation_rate=1.9) def callback(*coordinates): x = coordinates[0] print("x", coordinates[0][:, 0]) print("y", coordinates[1][0, :]) print(x.shape) return np.ones_like(x, dtype=bool) sc.boundary_handling.set_boundary(NoSlip(), make_slice[6:7, -1], callback)
def set_initial_velocity(lb_step: LatticeBoltzmannStep, u_0: float) -> None: """Initializes velocity field of a fully periodic scenario with eddies. Args: lb_step: fully periodic 3D scenario u_0: maximum velocity """ from numpy import cos, sin assert lb_step.dim == 3, "Works only for 3D scenarios" assert tuple(lb_step.data_handling.periodicity) == (True, True, True), "Scenario has to be fully periodic" for b in lb_step.data_handling.iterate(ghost_layers=False, inner_ghost_layers=False): velocity = b[lb_step.velocity_data_name] coordinates = b.midpoint_arrays x, y, z = [c / s * 2 * np.pi for c, s in zip(coordinates, lb_step.data_handling.shape)] velocity[..., 0] = u_0 * sin(x) * (cos(3 * y) * cos(z) - cos(y) * cos(3 * z)) velocity[..., 1] = u_0 * sin(y) * (cos(3 * z) * cos(x) - cos(z) * cos(3 * x)) velocity[..., 2] = u_0 * sin(z) * (cos(3 * x) * cos(y) - cos(x) * cos(3 * y)) lb_step.set_pdf_fields_from_macroscopic_values()
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 run(re=6000, eval_interval=0.05, total_time=3.0, domain_size=100, u_0=0.05, initialization_relaxation_rate=None, vtk_output=False, parallel=False, **kwargs): """Runs the kida vortex simulation. Args: re: Reynolds number eval_interval: interval in non-dimensional time to evaluate flow properties total_time: non-dimensional time of complete simulation domain_size: integer (not tuple) since domain is cubic u_0: maximum lattice velocity initialization_relaxation_rate: if not None, an advanced initialization scheme is run to initialize higher order moments correctly vtk_output: if vtk files are written out parallel: MPI parallelization with walberla **kwargs: passed to LbStep Returns: dictionary with simulation results """ domain_shape = (domain_size, domain_size, domain_size) relaxation_rate = relaxation_rate_from_reynolds_number(re, u_0, domain_size) dh = ps.create_data_handling(domain_shape, periodicity=True, parallel=parallel) rr_subs = {'viscosity': relaxation_rate, 'trt_magic': relaxation_rate_from_magic_number(relaxation_rate), 'free': sp.Symbol("rr_f")} if 'relaxation_rates' in kwargs: kwargs['relaxation_rates'] = [rr_subs[r] if isinstance(r, str) else r for r in kwargs['relaxation_rates']] else: kwargs['relaxation_rates'] = [relaxation_rate] dh.log_on_root("Running kida vortex scenario of size {} with {}".format(domain_size, kwargs)) dh.log_on_root("Compiling method") lb_step = LatticeBoltzmannStep(data_handling=dh, name="kida_vortex", **kwargs) set_initial_velocity(lb_step, u_0) residuum, init_steps = np.nan, 0 if initialization_relaxation_rate is not None: dh.log_on_root("Running iterative initialization", level='PROGRESS') residuum, init_steps = lb_step.run_iterative_initialization(initialization_relaxation_rate, convergence_threshold=1e-12, max_steps=100000, check_residuum_after=2 * domain_size) dh.log_on_root("Iterative initialization finished after {} steps at residuum {}".format(init_steps, residuum)) total_time_steps = normalized_time_to_time_step(total_time, domain_size, u_0) eval_time_steps = normalized_time_to_time_step(eval_interval, domain_size, u_0) initial_energy = parallel_mean(lb_step, mean_kinetic_energy, all_reduce=False) times = [] energy_list = [] enstrophy_list = [] mlups_list = [] energy_spectrum_arr = None while lb_step.time_steps_run < total_time_steps: mlups = lb_step.benchmark_run(eval_time_steps, number_of_cells=domain_size**3) if vtk_output: lb_step.write_vtk() current_time = time_step_to_normalized_time(lb_step.time_steps_run, domain_size, u_0) current_kinetic_energy = parallel_mean(lb_step, mean_kinetic_energy) current_enstrophy = parallel_mean(lb_step, mean_enstrophy) is_stable = np.isfinite(lb_step.data_handling.max(lb_step.velocity_data_name)) and current_enstrophy < 1e4 if not is_stable: dh.log_on_root("Simulation got unstable - stopping", level='WARNING') break if current_time >= 0.5 and energy_spectrum_arr is None and domain_size <= 600: dh.log_on_root("Calculating energy spectrum") gathered_velocity = lb_step.velocity[:, :, :, :] if gathered_velocity is not None: energy_spectrum_arr = energy_density_spectrum(gathered_velocity) else: energy_spectrum_arr = False if dh.is_root: current_kinetic_energy /= initial_energy current_enstrophy *= domain_size ** 2 times.append(current_time) energy_list.append(current_kinetic_energy) enstrophy_list.append(current_enstrophy) mlups_list.append(mlups) dh.log_on_root("Progress: {current_time:.02f} / {total_time} at {mlups:.01f} MLUPS\t" "Enstrophy {current_enstrophy:.04f}\t" "KinEnergy {current_kinetic_energy:.06f}".format(**locals())) if dh.is_root: return { 'initialization_residuum': residuum, 'initialization_steps': init_steps, 'time': times, 'kinetic_energy': energy_list, 'enstrophy': enstrophy_list, 'mlups': np.average(mlups_list), 'energy_spectrum': list(energy_spectrum_arr), 'stable': bool(np.isfinite(lb_step.data_handling.max(lb_step.velocity_data_name))) } else: return None
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 __init__(self, free_energy, order_parameters, domain_size=None, data_handling=None, name='pf', hydro_lbm_parameters=MappingProxyType({}), hydro_dynamic_relaxation_rate=1.0, cahn_hilliard_relaxation_rates=1.0, cahn_hilliard_gammas=1, density_order_parameter=None, optimization=None, kernel_params=MappingProxyType({}), dx=1, dt=1, solve_cahn_hilliard_with_finite_differences=False, order_parameter_force=None, transformation_matrix=None, concentration_to_order_parameters=None, order_parameters_to_concentrations=None, homogeneous_neumann_boundaries=False, discretization='standard'): if optimization is None: optimization = {'openmp': False, 'target': Target.CPU} openmp = optimization.get('openmp', False) target = optimization.get('target', Target.CPU) if data_handling is None: data_handling = create_data_handling(domain_size, periodicity=True, parallel=False) self.free_energy = free_energy self.concentration_to_order_parameter = concentration_to_order_parameters self.order_parameters_to_concentrations = order_parameters_to_concentrations if transformation_matrix: op_transformation = np.array(transformation_matrix).astype(float) op_transformation_inv = np.array( transformation_matrix.inv()).astype(float) axes = ([-1], [1]) if self.concentration_to_order_parameter is None: self.concentration_to_order_parameter = lambda c: np.tensordot( c, op_transformation, axes=axes) if self.order_parameters_to_concentrations is None: self.order_parameters_to_concentrations = lambda p: np.tensordot( p, op_transformation_inv, axes=axes) self.chemical_potentials = chemical_potentials_from_free_energy( free_energy, order_parameters) # ------------------ Adding arrays --------------------- gpu = target == Target.GPU self.gpu = gpu self.num_order_parameters = len(order_parameters) self.order_parameters = order_parameters pressure_tensor_size = len( symmetric_tensor_linearization(data_handling.dim)) self.name = name self.phi_field_name = name + "_phi" self.mu_field_name = name + "_mu" self.vel_field_name = name + "_u" self.force_field_name = name + "_F" self.pressure_tensor_field_name = name + "_P" self.data_handling = data_handling dh = self.data_handling phi_size = len(order_parameters) self.phi_field = dh.add_array(self.phi_field_name, values_per_cell=phi_size, gpu=gpu, latex_name='φ') self.mu_field = dh.add_array(self.mu_field_name, values_per_cell=phi_size, gpu=gpu, latex_name="μ") self.vel_field = dh.add_array(self.vel_field_name, values_per_cell=data_handling.dim, gpu=gpu, latex_name="u") self.force_field = dh.add_array(self.force_field_name, values_per_cell=dh.dim, gpu=gpu, latex_name="F") self.pressure_tensor_field = data_handling.add_array( self.pressure_tensor_field_name, gpu=gpu, values_per_cell=pressure_tensor_size, latex_name='P') self.flag_interface = FlagInterface(data_handling, 'flags') # ------------------ Creating kernels ------------------ phi = tuple(self.phi_field(i) for i in range(len(order_parameters))) F = self.free_energy.subs( {old: new for old, new in zip(order_parameters, phi)}) if homogeneous_neumann_boundaries: def apply_neumann_boundaries(eqs): fields = [ data_handling.fields[self.phi_field_name], data_handling.fields[self.pressure_tensor_field_name] ] flag_field = data_handling.fields[ self.flag_interface.flag_field_name] return add_neumann_boundary(eqs, fields, flag_field, "neumann_flag", inverse_flag=False) else: def apply_neumann_boundaries(eqs): return eqs # μ and pressure tensor update self.phi_sync = data_handling.synchronization_function( [self.phi_field_name], target=target) self.mu_eqs = mu_kernel(F, phi, self.phi_field, self.mu_field, dx) self.pressure_tensor_eqs = pressure_tensor_kernel( self.free_energy, order_parameters, self.phi_field, self.pressure_tensor_field, dx=dx, discretization=discretization) mu_and_pressure_tensor_eqs = self.mu_eqs + self.pressure_tensor_eqs mu_and_pressure_tensor_eqs = apply_neumann_boundaries( mu_and_pressure_tensor_eqs) self.mu_and_pressure_tensor_kernel = create_kernel( sympy_cse_on_assignment_list(mu_and_pressure_tensor_eqs), target=target, cpu_openmp=openmp).compile() # F Kernel extra_force = sp.Matrix([0] * self.data_handling.dim) if order_parameter_force is not None: for order_parameter_idx, force in order_parameter_force.items(): extra_force += sp.Matrix([ f_i * self.phi_field(order_parameter_idx) for f_i in force ]) self.force_eqs = force_kernel_using_pressure_tensor( self.force_field, self.pressure_tensor_field, dx=dx, extra_force=extra_force, discretization=discretization) self.force_from_pressure_tensor_kernel = create_kernel( apply_neumann_boundaries(self.force_eqs), target=target, cpu_openmp=openmp).compile() self.pressure_tensor_sync = data_handling.synchronization_function( [self.pressure_tensor_field_name], target=target) hydro_lbm_parameters = hydro_lbm_parameters.copy() # Hydrodynamic LBM if density_order_parameter is not None: density_idx = order_parameters.index(density_order_parameter) hydro_lbm_parameters['compute_density_in_every_step'] = True hydro_lbm_parameters['density_data_name'] = self.phi_field_name hydro_lbm_parameters['density_data_index'] = density_idx if 'optimization' not in hydro_lbm_parameters: hydro_lbm_parameters['optimization'] = optimization else: hydro_lbm_parameters['optimization'].update(optimization) self.hydro_lbm_step = LatticeBoltzmannStep( data_handling=data_handling, name=name + '_hydroLBM', relaxation_rate=hydro_dynamic_relaxation_rate, compute_velocity_in_every_step=True, force=tuple([self.force_field(i) for i in range(dh.dim)]), velocity_data_name=self.vel_field_name, kernel_params=kernel_params, flag_interface=self.flag_interface, time_step_order='collide_stream', **hydro_lbm_parameters) # Cahn-Hilliard LBMs if not hasattr(cahn_hilliard_relaxation_rates, '__len__'): cahn_hilliard_relaxation_rates = [cahn_hilliard_relaxation_rates ] * len(order_parameters) if not hasattr(cahn_hilliard_gammas, '__len__'): cahn_hilliard_gammas = [cahn_hilliard_gammas ] * len(order_parameters) self.cahn_hilliard_steps = [] if solve_cahn_hilliard_with_finite_differences: if density_order_parameter is not None: raise NotImplementedError( "density_order_parameter not supported when " "CH is solved with finite differences") ch_step = CahnHilliardFDStep( self.data_handling, self.phi_field_name, self.mu_field_name, self.vel_field_name, target=target, dx=dx, dt=dt, mobilities=1, equation_modifier=apply_neumann_boundaries) self.cahn_hilliard_steps.append(ch_step) else: for i, op in enumerate(order_parameters): if op == density_order_parameter: continue ch_method = cahn_hilliard_lb_method( self.hydro_lbm_step.method.stencil, self.mu_field(i), relaxation_rate=cahn_hilliard_relaxation_rates[i], gamma=cahn_hilliard_gammas[i]) ch_step = LatticeBoltzmannStep( data_handling=data_handling, relaxation_rate=1, lb_method=ch_method, velocity_input_array_name=self.vel_field.name, density_data_name=self.phi_field.name, stencil='D3Q19' if self.data_handling.dim == 3 else 'D2Q9', compute_density_in_every_step=True, density_data_index=i, flag_interface=self.hydro_lbm_step.boundary_handling. flag_interface, name=name + f"_chLbm_{i}", optimization=optimization) self.cahn_hilliard_steps.append(ch_step) self._vtk_writer = None self.run_hydro_lbm = True self.density_order_parameter = density_order_parameter self.time_steps_run = 0 self.reset() self.neumann_flag = 0
class PhaseFieldStep: def __init__(self, free_energy, order_parameters, domain_size=None, data_handling=None, name='pf', hydro_lbm_parameters=MappingProxyType({}), hydro_dynamic_relaxation_rate=1.0, cahn_hilliard_relaxation_rates=1.0, cahn_hilliard_gammas=1, density_order_parameter=None, optimization=None, kernel_params=MappingProxyType({}), dx=1, dt=1, solve_cahn_hilliard_with_finite_differences=False, order_parameter_force=None, transformation_matrix=None, concentration_to_order_parameters=None, order_parameters_to_concentrations=None, homogeneous_neumann_boundaries=False, discretization='standard'): if optimization is None: optimization = {'openmp': False, 'target': Target.CPU} openmp = optimization.get('openmp', False) target = optimization.get('target', Target.CPU) if data_handling is None: data_handling = create_data_handling(domain_size, periodicity=True, parallel=False) self.free_energy = free_energy self.concentration_to_order_parameter = concentration_to_order_parameters self.order_parameters_to_concentrations = order_parameters_to_concentrations if transformation_matrix: op_transformation = np.array(transformation_matrix).astype(float) op_transformation_inv = np.array( transformation_matrix.inv()).astype(float) axes = ([-1], [1]) if self.concentration_to_order_parameter is None: self.concentration_to_order_parameter = lambda c: np.tensordot( c, op_transformation, axes=axes) if self.order_parameters_to_concentrations is None: self.order_parameters_to_concentrations = lambda p: np.tensordot( p, op_transformation_inv, axes=axes) self.chemical_potentials = chemical_potentials_from_free_energy( free_energy, order_parameters) # ------------------ Adding arrays --------------------- gpu = target == Target.GPU self.gpu = gpu self.num_order_parameters = len(order_parameters) self.order_parameters = order_parameters pressure_tensor_size = len( symmetric_tensor_linearization(data_handling.dim)) self.name = name self.phi_field_name = name + "_phi" self.mu_field_name = name + "_mu" self.vel_field_name = name + "_u" self.force_field_name = name + "_F" self.pressure_tensor_field_name = name + "_P" self.data_handling = data_handling dh = self.data_handling phi_size = len(order_parameters) self.phi_field = dh.add_array(self.phi_field_name, values_per_cell=phi_size, gpu=gpu, latex_name='φ') self.mu_field = dh.add_array(self.mu_field_name, values_per_cell=phi_size, gpu=gpu, latex_name="μ") self.vel_field = dh.add_array(self.vel_field_name, values_per_cell=data_handling.dim, gpu=gpu, latex_name="u") self.force_field = dh.add_array(self.force_field_name, values_per_cell=dh.dim, gpu=gpu, latex_name="F") self.pressure_tensor_field = data_handling.add_array( self.pressure_tensor_field_name, gpu=gpu, values_per_cell=pressure_tensor_size, latex_name='P') self.flag_interface = FlagInterface(data_handling, 'flags') # ------------------ Creating kernels ------------------ phi = tuple(self.phi_field(i) for i in range(len(order_parameters))) F = self.free_energy.subs( {old: new for old, new in zip(order_parameters, phi)}) if homogeneous_neumann_boundaries: def apply_neumann_boundaries(eqs): fields = [ data_handling.fields[self.phi_field_name], data_handling.fields[self.pressure_tensor_field_name] ] flag_field = data_handling.fields[ self.flag_interface.flag_field_name] return add_neumann_boundary(eqs, fields, flag_field, "neumann_flag", inverse_flag=False) else: def apply_neumann_boundaries(eqs): return eqs # μ and pressure tensor update self.phi_sync = data_handling.synchronization_function( [self.phi_field_name], target=target) self.mu_eqs = mu_kernel(F, phi, self.phi_field, self.mu_field, dx) self.pressure_tensor_eqs = pressure_tensor_kernel( self.free_energy, order_parameters, self.phi_field, self.pressure_tensor_field, dx=dx, discretization=discretization) mu_and_pressure_tensor_eqs = self.mu_eqs + self.pressure_tensor_eqs mu_and_pressure_tensor_eqs = apply_neumann_boundaries( mu_and_pressure_tensor_eqs) self.mu_and_pressure_tensor_kernel = create_kernel( sympy_cse_on_assignment_list(mu_and_pressure_tensor_eqs), target=target, cpu_openmp=openmp).compile() # F Kernel extra_force = sp.Matrix([0] * self.data_handling.dim) if order_parameter_force is not None: for order_parameter_idx, force in order_parameter_force.items(): extra_force += sp.Matrix([ f_i * self.phi_field(order_parameter_idx) for f_i in force ]) self.force_eqs = force_kernel_using_pressure_tensor( self.force_field, self.pressure_tensor_field, dx=dx, extra_force=extra_force, discretization=discretization) self.force_from_pressure_tensor_kernel = create_kernel( apply_neumann_boundaries(self.force_eqs), target=target, cpu_openmp=openmp).compile() self.pressure_tensor_sync = data_handling.synchronization_function( [self.pressure_tensor_field_name], target=target) hydro_lbm_parameters = hydro_lbm_parameters.copy() # Hydrodynamic LBM if density_order_parameter is not None: density_idx = order_parameters.index(density_order_parameter) hydro_lbm_parameters['compute_density_in_every_step'] = True hydro_lbm_parameters['density_data_name'] = self.phi_field_name hydro_lbm_parameters['density_data_index'] = density_idx if 'optimization' not in hydro_lbm_parameters: hydro_lbm_parameters['optimization'] = optimization else: hydro_lbm_parameters['optimization'].update(optimization) self.hydro_lbm_step = LatticeBoltzmannStep( data_handling=data_handling, name=name + '_hydroLBM', relaxation_rate=hydro_dynamic_relaxation_rate, compute_velocity_in_every_step=True, force=tuple([self.force_field(i) for i in range(dh.dim)]), velocity_data_name=self.vel_field_name, kernel_params=kernel_params, flag_interface=self.flag_interface, time_step_order='collide_stream', **hydro_lbm_parameters) # Cahn-Hilliard LBMs if not hasattr(cahn_hilliard_relaxation_rates, '__len__'): cahn_hilliard_relaxation_rates = [cahn_hilliard_relaxation_rates ] * len(order_parameters) if not hasattr(cahn_hilliard_gammas, '__len__'): cahn_hilliard_gammas = [cahn_hilliard_gammas ] * len(order_parameters) self.cahn_hilliard_steps = [] if solve_cahn_hilliard_with_finite_differences: if density_order_parameter is not None: raise NotImplementedError( "density_order_parameter not supported when " "CH is solved with finite differences") ch_step = CahnHilliardFDStep( self.data_handling, self.phi_field_name, self.mu_field_name, self.vel_field_name, target=target, dx=dx, dt=dt, mobilities=1, equation_modifier=apply_neumann_boundaries) self.cahn_hilliard_steps.append(ch_step) else: for i, op in enumerate(order_parameters): if op == density_order_parameter: continue ch_method = cahn_hilliard_lb_method( self.hydro_lbm_step.method.stencil, self.mu_field(i), relaxation_rate=cahn_hilliard_relaxation_rates[i], gamma=cahn_hilliard_gammas[i]) ch_step = LatticeBoltzmannStep( data_handling=data_handling, relaxation_rate=1, lb_method=ch_method, velocity_input_array_name=self.vel_field.name, density_data_name=self.phi_field.name, stencil='D3Q19' if self.data_handling.dim == 3 else 'D2Q9', compute_density_in_every_step=True, density_data_index=i, flag_interface=self.hydro_lbm_step.boundary_handling. flag_interface, name=name + f"_chLbm_{i}", optimization=optimization) self.cahn_hilliard_steps.append(ch_step) self._vtk_writer = None self.run_hydro_lbm = True self.density_order_parameter = density_order_parameter self.time_steps_run = 0 self.reset() self.neumann_flag = 0 @property def vtk_writer(self): if self._vtk_writer is None: self._vtk_writer = self.data_handling.create_vtk_writer( self.name, [ self.phi_field_name, self.mu_field_name, self.vel_field_name, self.force_field_name ]) return self._vtk_writer @property def shape(self): return self.data_handling.shape def write_vtk(self): self.vtk_writer(self.time_steps_run) def reset(self): # Init φ and μ self.data_handling.fill(self.phi_field_name, 0.0) self.data_handling.fill( self.phi_field_name, 1.0 if self.density_order_parameter is not None else 0.0, value_idx=0) self.data_handling.fill(self.mu_field_name, 0.0) self.data_handling.fill(self.force_field_name, 0.0) self.data_handling.fill(self.vel_field_name, 0.0) self.set_pdf_fields_from_macroscopic_values() self.time_steps_run = 0 def set_pdf_fields_from_macroscopic_values(self): self.hydro_lbm_step.set_pdf_fields_from_macroscopic_values() for ch_step in self.cahn_hilliard_steps: ch_step.set_pdf_fields_from_macroscopic_values() def pre_run(self): if self.gpu: self.data_handling.to_gpu(self.phi_field_name) self.data_handling.to_gpu(self.mu_field_name) self.data_handling.to_gpu(self.force_field_name) self.hydro_lbm_step.pre_run() for ch_step in self.cahn_hilliard_steps: ch_step.pre_run() def post_run(self): if self.gpu: self.data_handling.to_cpu(self.phi_field_name) self.data_handling.to_cpu(self.mu_field_name) self.data_handling.to_cpu(self.force_field_name) if self.run_hydro_lbm: self.hydro_lbm_step.post_run() for ch_step in self.cahn_hilliard_steps: ch_step.post_run() def time_step(self): neumann_flag = self.neumann_flag self.phi_sync() self.data_handling.run_kernel(self.mu_and_pressure_tensor_kernel, neumann_flag=neumann_flag) self.pressure_tensor_sync() self.data_handling.run_kernel(self.force_from_pressure_tensor_kernel, neumann_flag=neumann_flag) if self.run_hydro_lbm: self.hydro_lbm_step.time_step() for ch_lbm in self.cahn_hilliard_steps: ch_lbm.time_step() self.time_steps_run += 1 @property def boundary_handling(self): return self.hydro_lbm_step.boundary_handling def set_concentration(self, slice_obj, concentration): if self.concentration_to_order_parameter is not None: phi = self.concentration_to_order_parameter(concentration) else: phi = np.array(concentration) for b in self.data_handling.iterate(slice_obj): for i in range(phi.shape[-1]): b[self.phi_field_name][..., i] = phi[i] def set_single_concentration(self, slice_obj, phase_idx, value=1): num_phases = self.concentration[:, :, :].shape[-1] zeros = [0] * num_phases zeros[phase_idx] = value self.set_concentration(slice_obj, zeros) def set_density(self, slice_obj, value): for b in self.data_handling.iterate(slice_obj): for i in range(self.num_order_parameters): b[self.hydro_lbm_step.density_data_name].fill(value) def run(self, time_steps): self.pre_run() for i in range(time_steps): self.time_step() self.post_run() def _get_slice(self, data_name, slice_obj): if slice_obj is None: slice_obj = make_slice[:, :] if self.data_handling.dim == 2 else make_slice[:, :, 0.5] return self.data_handling.gather_array(data_name, slice_obj).squeeze() def phi_slice(self, slice_obj=None): return self._get_slice(self.phi_field_name, slice_obj) def concentration_slice(self, slice_obj=None): if slice_obj is not None and len(slice_obj) > self.data_handling.dim: assert len(slice_obj) - 1 == self.data_handling.dim last_index = slice_obj[-1] slice_obj = slice_obj[:-1] else: last_index = None phi = self.phi_slice(slice_obj) result = self.order_parameters_to_concentrations( phi) if self.order_parameters_to_concentrations else phi if last_index is not None: result = result[..., last_index] return result def mu_slice(self, slice_obj=None): return self._get_slice(self.mu_field_name, slice_obj) def velocity_slice(self, slice_obj=None): return self._get_slice(self.vel_field_name, slice_obj) def force_slice(self, slice_obj=None): return self._get_slice(self.force_field_name, slice_obj) @property def phi(self): return SlicedGetter(self.phi_slice) @property def concentration(self): return SlicedGetter(self.concentration_slice) @property def mu(self): return SlicedGetter(self.mu_slice) @property def velocity(self): return SlicedGetter(self.velocity_slice) @property def force(self): return SlicedGetter(self.force_slice)
def test_image(): pytest.importorskip('scipy.ndimage') sc = LatticeBoltzmannStep(domain_size=(50, 40), method=Method.SRT, relaxation_rate=1.9) add_black_and_white_image(sc.boundary_handling, get_test_image_path(), keep_aspect_ratio=True)