Example #1
0
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()
Example #3
0
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)
Example #5
0
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()
Example #6
0
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
Example #7
0
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
Example #8
0
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
Example #9
0
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))
Example #10
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
Example #11
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)
Example #12
0
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)