diff --git a/lbmpy/phasefield_allen_cahn/derivatives.py b/lbmpy/phasefield_allen_cahn/derivatives.py new file mode 100644 index 0000000000000000000000000000000000000000..2a5f45b981a277489c99e8fa3730f4ca16a71154 --- /dev/null +++ b/lbmpy/phasefield_allen_cahn/derivatives.py @@ -0,0 +1,53 @@ +def laplacian(phi_field): + r""" + Get a symbolic expression for the laplacian. + Args: + phi_field: the phase-field on which the laplacian is applied + """ + lp_phi = 16.0 * ((phi_field[1, 0, 0]) + (phi_field[-1, 0, 0]) + + (phi_field[0, 1, 0]) + (phi_field[0, -1, 0]) + + (phi_field[0, 0, 1]) + (phi_field[0, 0, -1])) \ + + 1.0 * ( + (phi_field[1, 1, 1]) + (phi_field[-1, 1, 1]) + + (phi_field[1, -1, 1]) + (phi_field[-1, -1, 1]) + + (phi_field[1, 1, -1]) + (phi_field[-1, 1, -1]) + + (phi_field[1, -1, -1]) + (phi_field[-1, -1, -1])) \ + + 4.0 * ( + (phi_field[1, 1, 0]) + (phi_field[-1, 1, 0]) + + (phi_field[1, -1, 0]) + (phi_field[-1, -1, 0]) + + (phi_field[1, 0, 1]) + (phi_field[-1, 0, 1]) + + (phi_field[1, 0, -1]) + (phi_field[-1, 0, -1]) + + (phi_field[0, 1, 1]) + (phi_field[0, -1, 1]) + + (phi_field[0, 1, -1]) + (phi_field[0, -1, -1])) \ + - 152.0 * phi_field[0, 0, 0] + + lp_phi = lp_phi / 36 + + return lp_phi + + +def isotropic_gradient(phi_field): + r""" + Get a symbolic expression for the isotropic gradient of the phase-field + Args: + phi_field: the phase-field on which the isotropic gradient is applied + """ + grad_phi_x = 16.00 * (phi_field[1, 0, 0] - phi_field[-1, 0, 0])\ + + (phi_field[1, 1, 1] - phi_field[-1, 1, 1] + phi_field[1, -1, 1] - phi_field[-1, -1, 1] + + phi_field[1, 1, -1] - phi_field[-1, 1, -1] + phi_field[1, -1, -1] - phi_field[-1, -1, -1])\ + + 4.00 * (phi_field[1, 1, 0] - phi_field[-1, 1, 0] + phi_field[1, -1, 0] - phi_field[-1, -1, 0] + + phi_field[1, 0, 1] - phi_field[-1, 0, 1] + phi_field[1, 0, -1] - phi_field[-1, 0, -1]) + grad_phi_y = 16.00 * (phi_field[0, 1, 0] - phi_field[0, -1, 0]) \ + + (phi_field[1, 1, 1] + phi_field[-1, 1, 1] - phi_field[1, -1, 1] - phi_field[-1, -1, 1] + + phi_field[1, 1, -1] + phi_field[-1, 1, -1] - phi_field[1, -1, -1] - phi_field[-1, -1, -1])\ + + 4.00 * (phi_field[1, 1, 0] + phi_field[-1, 1, 0] - phi_field[1, -1, 0] - phi_field[-1, -1, 0] + + phi_field[0, 1, 1] - phi_field[0, -1, 1] + phi_field[0, 1, -1] - phi_field[0, -1, -1]) + grad_phi_z = 16.00 * (phi_field[0, 0, 1] - phi_field[0, 0, -1]) \ + + (phi_field[1, 1, 1] + phi_field[-1, 1, 1] + phi_field[1, -1, 1] + phi_field[-1, -1, 1] + - phi_field[1, 1, -1] - phi_field[-1, 1, -1] - phi_field[1, -1, -1] - phi_field[-1, -1, -1])\ + + 4.00 * (phi_field[1, 0, 1] + phi_field[-1, 0, 1] - phi_field[1, 0, -1] - phi_field[-1, 0, -1] + + phi_field[0, 1, 1] + phi_field[0, -1, 1] - phi_field[0, 1, -1] - phi_field[0, -1, -1]) + + grad = [grad_phi_x / 72, grad_phi_y / 72, grad_phi_z / 72] + + return grad diff --git a/lbmpy/phasefield_allen_cahn/force_model.py b/lbmpy/phasefield_allen_cahn/force_model.py index 548a32538bf353c058011bc7a0a9654067636de0..85b405116a6145561e70cc96936f22eee09f280e 100644 --- a/lbmpy/phasefield_allen_cahn/force_model.py +++ b/lbmpy/phasefield_allen_cahn/force_model.py @@ -14,21 +14,24 @@ class MultiphaseForceModel: self._rho = rho def __call__(self, lb_method): + stencil = lb_method.stencil - simple = Simple(self._force) + force_symp = sp.symbols("force_:{}".format(lb_method.dim)) + simple = Simple(force_symp) force = [f / self._rho for f in simple(lb_method)] moment_matrix = lb_method.moment_matrix relaxation_rates = sp.Matrix(np.diag(lb_method.relaxation_rates)) mrt_collision_op = moment_matrix.inv() * relaxation_rates * moment_matrix - return -0.5 * mrt_collision_op * sp.Matrix(force) + sp.Matrix(force) + result = -0.5 * mrt_collision_op * sp.Matrix(force) + sp.Matrix(force) - def macroscopic_velocity_shift(self, density): - return default_velocity_shift(self._rho, self._force) + for i in range(0, len(stencil)): + result[i] = result[i].simplify() -# -------------------------------- Helper functions ------------------------------------------------------------------ + subs_dict = dict(zip(force_symp, self._force)) + for i in range(0, len(stencil)): + result[i] = result[i].subs(subs_dict) -def default_velocity_shift(density, force): - return [f_i / (2 * density) for f_i in force] + return result diff --git a/lbmpy/phasefield_allen_cahn/kernel_equations.py b/lbmpy/phasefield_allen_cahn/kernel_equations.py index 17549923402a495e876a8705772821a4cc37303c..34cfbc758ac5b490ffcbfaad744cb3c988f29ef9 100644 --- a/lbmpy/phasefield_allen_cahn/kernel_equations.py +++ b/lbmpy/phasefield_allen_cahn/kernel_equations.py @@ -1,7 +1,10 @@ +from pystencils.field import Field +from lbmpy.creationfunctions import update_with_default_parameters +from lbmpy.fieldaccess import StreamPushTwoFieldsAccessor, CollideOnlyInplaceAccessor from pystencils.fd.derivation import FiniteDifferenceStencilDerivation from lbmpy.maxwellian_equilibrium import get_weights from pystencils import Assignment, AssignmentCollection -from pystencils.simp import sympy_cse_on_assignment_list +from lbmpy.phasefield_allen_cahn.derivatives import laplacian, isotropic_gradient import sympy as sp import numpy as np @@ -30,15 +33,16 @@ def chemical_potential_symbolic(phi_field, stencil, beta, kappa): deriv.set_weight((1, 1), sp.Rational(1, 6)) deriv.set_weight((0, -1), sp.Rational(2, 3)) deriv.set_weight((0, 0), sp.Rational(-10, 3)) - if len(stencil) == 27: - deriv.set_weight((1, 1, 1), sp.Rational(1, 48)) - # assume the stencil is isotropic - res = deriv.get_stencil(isotropic=True) + # assume the stencil is isotropic + res = deriv.get_stencil(isotropic=True) + lap = res.apply(phi_field.center) + else: + lap = laplacian(phi_field) # get the chemical potential mu = 4.0 * beta * phi_field.center * (phi_field.center - 1.0) * (phi_field.center - 0.5) - \ - kappa * res.apply(phi_field.center) + kappa * lap return mu @@ -50,28 +54,27 @@ def isotropic_gradient_symbolic(phi_field, stencil): stencil: stencil of the lattice Boltzmann step """ dimensions = len(stencil[0]) - deriv = FiniteDifferenceStencilDerivation((0, ), stencil) + deriv = FiniteDifferenceStencilDerivation((0,), stencil) deriv.assume_symmetric(0, anti_symmetric=True) deriv.assume_symmetric(1, anti_symmetric=False) if dimensions == 3: deriv.assume_symmetric(2, anti_symmetric=False) - if len(stencil) == 19: + if len(stencil) == 9: + res = deriv.get_stencil(isotropic=True) + grad = [res.apply(phi_field.center), res.rotate_weights_and_apply(phi_field.center, (0, 1)), 0] + elif len(stencil) == 19: deriv.set_weight((0, 0, 0), sp.Integer(0)) deriv.set_weight((1, 0, 0), sp.Rational(1, 6)) deriv.set_weight((1, 1, 0), sp.Rational(1, 12)) - elif len(stencil) == 27: - deriv.set_weight((0, 0, 0), sp.Integer(0)) - deriv.set_weight((1, 1, 1), sp.Rational(1, 3360)) - res = deriv.get_stencil(isotropic=True) - if dimensions == 2: - grad = [res.apply(phi_field.center), res.rotate_weights_and_apply(phi_field.center, (0, 1)), 0] - else: + res = deriv.get_stencil(isotropic=True) grad = [res.apply(phi_field.center), res.rotate_weights_and_apply(phi_field.center, (1, 0)), res.rotate_weights_and_apply(phi_field.center, (2, 1))] + else: + grad = isotropic_gradient(phi_field) return grad @@ -83,10 +86,10 @@ def normalized_isotropic_gradient_symbolic(phi_field, stencil): phi_field: the phase-field on which the normalized isotropic gradient is applied stencil: stencil of the lattice Boltzmann step """ - isotropic_gradient = isotropic_gradient_symbolic(phi_field, stencil) - tmp = (sum(map(lambda x: x * x, isotropic_gradient_symbolic(phi_field, stencil))) + 1.e-12) ** 0.5 + iso_grad = isotropic_gradient_symbolic(phi_field, stencil) + tmp = (sum(map(lambda x: x * x, isotropic_gradient_symbolic(phi_field, stencil))) + 1.e-32) ** 0.5 - result = [x / tmp for x in isotropic_gradient] + result = [x / tmp for x in iso_grad] return result @@ -99,8 +102,8 @@ def pressure_force(phi_field, stencil, density_heavy, density_light): density_heavy: density of the heavier fluid density_light: density of the lighter fluid """ - isotropic_gradient = isotropic_gradient_symbolic(phi_field, stencil) - result = list(map(lambda x: -sp.symbols("rho") * ((density_heavy - density_light) / 3) * x, isotropic_gradient)) + iso_grad = isotropic_gradient_symbolic(phi_field, stencil) + result = list(map(lambda x: sp.Rational(-1, 3) * sp.symbols("rho") * (density_heavy - density_light) * x, iso_grad)) return result @@ -118,51 +121,47 @@ def viscous_force(lb_velocity_field, phi_field, mrt_method, tau, density_heavy, stencil = mrt_method.stencil dimensions = len(stencil[0]) - isotropic_gradient = isotropic_gradient_symbolic(phi_field, stencil) - - weights = get_weights(stencil, c_s_sq=sp.Rational(1, 3)) - - op = sp.symbols("rho") - - gamma = mrt_method.get_equilibrium_terms() - gamma = gamma.subs({sp.symbols("rho"): 1}) + iso_grad = isotropic_gradient_symbolic(phi_field, stencil) moment_matrix = mrt_method.moment_matrix - relaxation_rates = sp.Matrix(np.diag(mrt_method.relaxation_rates)) - mrt_collision_op = moment_matrix.inv() * relaxation_rates * moment_matrix + rel = mrt_method.relaxation_rates + eq = mrt_method.moment_equilibrium_values + eq = np.array(eq) + + g_vals = [lb_velocity_field.center(i) for i, _ in enumerate(stencil)] + m0 = np.dot(moment_matrix, g_vals) - # get the non equilibrium - non_equilibrium = [lb_velocity_field.center(i) - (weights[i] * op + gamma[i] - weights[i]) - for i, _ in enumerate(stencil)] - non_equilibrium = np.dot(mrt_collision_op, non_equilibrium) + m = m0 - eq + m = m * rel + non_equilibrium = np.dot(moment_matrix.inv(), m) stress_tensor = [0] * 6 # Calculate Stress Tensor MRT for i, d in enumerate(stencil): - stress_tensor[0] += non_equilibrium[i] * (d[0] * d[0]) - stress_tensor[1] += non_equilibrium[i] * (d[1] * d[1]) + stress_tensor[0] = sp.Add(stress_tensor[0], non_equilibrium[i] * (d[0] * d[0])) + stress_tensor[1] = sp.Add(stress_tensor[1], non_equilibrium[i] * (d[1] * d[1])) if dimensions == 3: - stress_tensor[2] += non_equilibrium[i] * (d[2] * d[2]) - stress_tensor[3] += non_equilibrium[i] * (d[1] * d[2]) - stress_tensor[4] += non_equilibrium[i] * (d[0] * d[2]) + stress_tensor[2] = sp.Add(stress_tensor[2], non_equilibrium[i] * (d[2] * d[2])) + stress_tensor[3] = sp.Add(stress_tensor[3], non_equilibrium[i] * (d[1] * d[2])) + stress_tensor[4] = sp.Add(stress_tensor[4], non_equilibrium[i] * (d[0] * d[2])) - stress_tensor[5] += non_equilibrium[i] * (d[0] * d[1]) + stress_tensor[5] = sp.Add(stress_tensor[5], non_equilibrium[i] * (d[0] * d[1])) density_difference = density_heavy - density_light # Calculate Viscous Force MRT - fmx = (0.5 - tau) * (stress_tensor[0] * isotropic_gradient[0] - + stress_tensor[5] * isotropic_gradient[1] - + stress_tensor[4] * isotropic_gradient[2]) * density_difference + fmx = (0.5 - tau) * (stress_tensor[0] * iso_grad[0] + + stress_tensor[5] * iso_grad[1] + + stress_tensor[4] * iso_grad[2]) * density_difference - fmy = (0.5 - tau) * (stress_tensor[5] * isotropic_gradient[0] - + stress_tensor[1] * isotropic_gradient[1] - + stress_tensor[3] * isotropic_gradient[2]) * density_difference + fmy = (0.5 - tau) * (stress_tensor[5] * iso_grad[0] + + stress_tensor[1] * iso_grad[1] + + stress_tensor[3] * iso_grad[2]) * density_difference - fmz = (0.5 - tau) * (stress_tensor[4] * isotropic_gradient[0] - + stress_tensor[3] * isotropic_gradient[1] - + stress_tensor[2] * isotropic_gradient[2]) * density_difference + fmz = (0.5 - tau) * (stress_tensor[4] * iso_grad[0] + + stress_tensor[3] * iso_grad[1] + + stress_tensor[2] * iso_grad[2]) * density_difference return [fmx, fmy, fmz] @@ -177,18 +176,18 @@ def surface_tension_force(phi_field, stencil, beta, kappa): kappa: coefficient related to surface tension and interface thickness """ chemical_potential = chemical_potential_symbolic(phi_field, stencil, beta, kappa) - isotropic_gradient = isotropic_gradient_symbolic(phi_field, stencil) - return [chemical_potential * x for x in isotropic_gradient] + iso_grad = isotropic_gradient_symbolic(phi_field, stencil) + return [chemical_potential * x for x in iso_grad] -def hydrodynamic_force(lb_velocity_field, phi_field, mrt_method, tau, +def hydrodynamic_force(lb_velocity_field, phi_field, lb_method, tau, density_heavy, density_light, kappa, beta, body_force): r""" Get a symbolic expression for the hydrodynamic force Args: lb_velocity_field: hydrodynamic distribution function phi_field: phase-field - mrt_method: mrt lattice boltzmann method used for hydrodynamics + lb_method: Lattice boltzmann method used for hydrodynamics tau: relaxation time of the hydrodynamic lattice boltzmann step density_heavy: density of the heavier fluid density_light: density of the lighter fluid @@ -196,10 +195,10 @@ def hydrodynamic_force(lb_velocity_field, phi_field, mrt_method, tau, kappa: coefficient related to surface tension and interface thickness body_force: force acting on the fluids. Usually the gravity """ - stencil = mrt_method.stencil + stencil = lb_method.stencil dimensions = len(stencil[0]) fp = pressure_force(phi_field, stencil, density_heavy, density_light) - fm = viscous_force(lb_velocity_field, phi_field, mrt_method, tau, density_heavy, density_light) + fm = viscous_force(lb_velocity_field, phi_field, lb_method, tau, density_heavy, density_light) fs = surface_tension_force(phi_field, stencil, beta, kappa) result = [] @@ -226,53 +225,127 @@ def interface_tracking_force(phi_field, stencil, interface_thickness): return result -def get_assignment_list_stream_hydro(lb_vel_field, lb_vel_field_tmp, mrt_method, force_g, velocity, rho): +def get_update_rules_velocity(src_field, u_in, lb_method, force, density): r""" - Returns an assignment list of the streaming step for the hydrodynamic lattice Boltzmann step. In the assignment list - also the update for the velocity is integrated - Args: - lb_vel_field: source field of velocity distribution function - lb_vel_field_tmp: destination field of the velocity distribution function - mrt_method: lattice Boltzmann method of the hydrodynamic lattice Boltzmann step - force_g: hydrodynamic force - velocity: velocity field - rho: interpolated density of the two fluids - """ - - stencil = mrt_method.stencil + Get assignments to update the velocity with a force shift + Args: + src_field: the source field of the hydrodynamic distribution function + u_in: velocity field + lb_method: mrt lattice boltzmann method used for hydrodynamics + force: force acting on the hydrodynamic lb step + density: the interpolated density of the simulation + """ + stencil = lb_method.stencil dimensions = len(stencil[0]) - velocity_symbol_list = [lb_vel_field.center(i) for i, _ in enumerate(stencil)] - velocity_tmp_symbol_list = [lb_vel_field_tmp.center(i) for i, _ in enumerate(stencil)] + moment_matrix = lb_method.moment_matrix + eq = lb_method.moment_equilibrium_values + + first_eqs = lb_method.first_order_equilibrium_moment_symbols + indices = list() + for i in range(dimensions): + indices.append(eq.index(first_eqs[i])) + + src = [src_field.center(i) for i, _ in enumerate(stencil)] + m0 = np.dot(moment_matrix, src) + + update_u = list() + update_u.append(Assignment(sp.symbols("rho"), m0[0])) - g_subs_dic = dict(zip(velocity_symbol_list, velocity_tmp_symbol_list)) u_symp = sp.symbols("u_:{}".format(dimensions)) + zw = sp.symbols("zw_:{}".format(dimensions)) + for i in range(dimensions): + update_u.append(Assignment(zw[i], u_in.center_vector[i])) + + subs_dict = dict(zip(u_symp, zw)) + for i in range(dimensions): + update_u.append(Assignment(u_in.center_vector[i], m0[indices[i]] + force[i].subs(subs_dict) / density / 2)) + + return update_u + + +def get_collision_assignments_hydro(collision_rule=None, density=1, optimization={}, **kwargs): + r""" + Get collision assignments for the hydrodynamic lattice Boltzmann step. Here the force gets applied in the moment + space. Afterwards the transformation back to the pdf space happens. + Args: + collision_rule: arbitrary collision rule, e.g. created with create_lb_collision_rule + density: the interpolated density of the simulation + optimization: for details see createfunctions.py + """ + params, opt_params = update_with_default_parameters(kwargs, optimization) + + lb_method = params['lb_method'] + + stencil = lb_method.stencil + dimensions = len(stencil[0]) + + field_data_type = 'float64' if opt_params['double_precision'] else 'float32' + q = len(stencil) + + u_in = params['velocity_input'] + force = params['force'] - a = [0] * dimensions - for i, direction in enumerate(stencil): - for j in range(dimensions): - a[j] += velocity_tmp_symbol_list[i] * direction[j] + if opt_params['symbolic_field'] is not None: + src_field = opt_params['symbolic_field'] + elif opt_params['field_size']: + field_size = [s + 2 for s in opt_params['field_size']] + [q] + src_field = Field.create_fixed_size(params['field_name'], field_size, index_dimensions=1, + layout=opt_params['field_layout'], dtype=field_data_type) + else: + src_field = Field.create_generic(params['field_name'], spatial_dimensions=collision_rule.method.dim, + index_shape=(q,), layout=opt_params['field_layout'], dtype=field_data_type) + + if opt_params['symbolic_temporary_field'] is not None: + dst_field = opt_params['symbolic_temporary_field'] + else: + dst_field = src_field.new_field_with_different_name(params['temporary_field_name']) + + moment_matrix = lb_method.moment_matrix + rel = lb_method.relaxation_rates + eq = lb_method.moment_equilibrium_values + + first_eqs = lb_method.first_order_equilibrium_moment_symbols + indices = list() + for i in range(dimensions): + indices.append(eq.index(first_eqs[i])) - pull_g = list() - inv_dir = 0 - for i, direction in enumerate(stencil): - inv_direction = tuple(-e for e in direction) - pull_g.append(Assignment(lb_vel_field_tmp(i), lb_vel_field[inv_direction](i))) - inv_dir += lb_vel_field[inv_direction](i) + eq = np.array(eq) + g_vals = [src_field.center(i) for i, _ in enumerate(stencil)] + m0 = np.dot(moment_matrix, g_vals) + + mf = np.zeros(len(stencil), dtype=object) for i in range(dimensions): - pull_g.append(Assignment(velocity.center_vector[i], a[i] + 0.5 * force_g[i].subs(g_subs_dic) / rho)) + mf[indices[i]] = force[i] / density + + m = sp.symbols("m_:{}".format(len(stencil))) - subexpression = [Assignment(sp.symbols("rho"), inv_dir)] + update_m = get_update_rules_velocity(src_field, u_in, lb_method, force, density) + u_symp = sp.symbols("u_:{}".format(dimensions)) for i in range(dimensions): - subexpression.append(Assignment(u_symp[i], velocity.center_vector[i])) + update_m.append(Assignment(u_symp[i], u_in.center_vector[i])) + + for i in range(0, len(stencil)): + update_m.append(Assignment(m[i], m0[i] - (m0[i] - eq[i] + mf[i] / 2) * rel[i] + mf[i])) + + update_g = list() + var = np.dot(moment_matrix.inv(), m) + if params['kernel_type'] == 'collide_stream_push': + push_accessor = StreamPushTwoFieldsAccessor() + post_collision_accesses = push_accessor.write(dst_field, stencil) + else: + collide_accessor = CollideOnlyInplaceAccessor() + post_collision_accesses = collide_accessor.write(src_field, stencil) - ac_g = AssignmentCollection(main_assignments=pull_g, subexpressions=subexpression) + for i in range(0, len(stencil)): + update_g.append(Assignment(post_collision_accesses[i], var[i])) - ac_g.main_assignments = sympy_cse_on_assignment_list(ac_g.main_assignments) + hydro_lb_update_rule = AssignmentCollection(main_assignments=update_g, + subexpressions=update_m) - return ac_g + return hydro_lb_update_rule def initializer_kernel_phase_field_lb(phi_field_distributions, phi_field, velocity, mrt_method, interface_thickness): @@ -295,7 +368,7 @@ def initializer_kernel_phase_field_lb(phi_field_distributions, phi_field, veloci gamma = mrt_method.get_equilibrium_terms() gamma = gamma.subs({sp.symbols("rho"): 1}) gamma_init = gamma.subs({x: y for x, y in zip(u_symp, velocity.center_vector)}) - # create the kernels for the initialization of the g and h field + # create the kernels for the initialization of the h field h_updates = list() def scalar_product(a, b): diff --git a/lbmpy/phasefield_allen_cahn/parameter_calculation.py b/lbmpy/phasefield_allen_cahn/parameter_calculation.py index 6e3454020799b1c38e8c32f1c475b82dbabe3fd0..2f3a84b2455498eca5d1733df3aea6a1458de01c 100644 --- a/lbmpy/phasefield_allen_cahn/parameter_calculation.py +++ b/lbmpy/phasefield_allen_cahn/parameter_calculation.py @@ -18,16 +18,21 @@ def calculate_parameters_rti(reference_length=256, reference_length: reference length of the RTI reference_time: chosen reference time density_heavy: density of the heavier fluid - capillary_number: capillary number of the simulation - reynolds_number: reynolds number of the simulation - atwood_number: atwood number of the simulation - peclet_number: peclet number of the simulation - density_ratio: density ration of the heavier and the lighter fluid + capillary_number: capillary number of the simulation represents the relative effect of viscous drag + forces versus surface tension forces + reynolds_number: reynolds number of the simulation is the ratio between the viscous forces in a fluid + and the inertial forces + atwood_number: atwood number of the simulation is a dimensionless density ratio + peclet_number: peclet number of the simulation is the ratio of the rate of advection + of a physical quantity by the flow to the rate of diffusion of the same quantity + driven by an appropriate gradient + density_ratio: density ratio of the heavier and the lighter fluid viscosity_ratio: viscosity ratio of the heavier and the lighter fluid """ + # calculate the gravitational acceleration and the reference velocity - gravitational_acceleration = reference_length / (reference_time ** 2 * atwood_number) - reference_velocity = math.sqrt(gravitational_acceleration * reference_length) + g = reference_length / (reference_time ** 2 * atwood_number) + reference_velocity = math.sqrt(g * reference_length) dynamic_viscosity_heavy = (density_heavy * reference_velocity * reference_length) / reynolds_number dynamic_viscosity_light = dynamic_viscosity_heavy / viscosity_ratio @@ -49,8 +54,62 @@ def calculate_parameters_rti(reference_length=256, "dynamic_viscosity_light": dynamic_viscosity_light, "relaxation_time_heavy": relaxation_time_heavy, "relaxation_time_light": relaxation_time_light, - "gravitational_acceleration": -gravitational_acceleration, + "gravitational_acceleration": -g, "mobility": mobility, "surface_tension": surface_tension } return parameters + + +def calculate_dimensionless_rising_bubble(reference_time=18000, + density_heavy=1.0, + bubble_radius=16, + bond_number=1, + reynolds_number=40, + density_ratio=1000, + viscosity_ratio=100): + r""" + Calculate the simulation parameters for a rising bubble. The parameter calculation follows the ideas of Weber and + is based on the Reynolds number. This means the rising velocity of the bubble is implicitly stated with the + Reynolds number + + Args: + reference_time: chosen reference time + density_heavy: density of the heavier fluid + bubble_radius: initial radius of the rising bubble + bond_number: also called eötvös number is measuring the importance of gravitational forces compared to + surface tension forces + reynolds_number: reynolds number of the simulation is the ratio between the viscous forces in a fluid + and the inertial forces + density_ratio: density ratio of the heavier and the lighter fluid + viscosity_ratio: viscosity ratio of the heavier and the lighter fluid + """ + + bubble_diameter = bubble_radius * 2 + g = bubble_diameter / (reference_time ** 2) + + density_light = density_heavy / density_ratio + + dynamic_viscosity_heavy = (density_heavy * math.sqrt(g * bubble_diameter ** 3)) / reynolds_number + dynamic_viscosity_light = dynamic_viscosity_heavy / viscosity_ratio + + kinematic_viscosity_heavy = dynamic_viscosity_heavy / density_heavy + kinematic_viscosity_light = dynamic_viscosity_light / density_light + + relaxation_time_heavy = 3 * kinematic_viscosity_heavy + relaxation_time_light = 3 * kinematic_viscosity_light + + surface_tension = (density_heavy - density_light) * g * bubble_diameter ** 2 / bond_number + # calculation of the Morton number + # Mo = gravitational_acceleration * dynamic_viscosity_heavy / (density_heavy * surface_tension ** 3) + + parameters = { + "density_light": density_light, + "dynamic_viscosity_heavy": dynamic_viscosity_heavy, + "dynamic_viscosity_light": dynamic_viscosity_light, + "relaxation_time_heavy": relaxation_time_heavy, + "relaxation_time_light": relaxation_time_light, + "gravitational_acceleration": -g, + "surface_tension": surface_tension + } + return parameters diff --git a/lbmpy_tests/phasefield_allen_cahn/test_codegen_3D.py b/lbmpy_tests/phasefield_allen_cahn/test_codegen_3D.py new file mode 100644 index 0000000000000000000000000000000000000000..f3d1b711df158c19f13be24230abf494eb3f4963 --- /dev/null +++ b/lbmpy_tests/phasefield_allen_cahn/test_codegen_3D.py @@ -0,0 +1,142 @@ +from lbmpy.phasefield_allen_cahn.analytical import analytic_rising_speed +from lbmpy.phasefield_allen_cahn.parameter_calculation import calculate_dimensionless_rising_bubble, \ + calculate_parameters_rti +from pystencils import fields, AssignmentCollection +from pystencils.simp import sympy_cse + +from lbmpy.creationfunctions import create_lb_method, create_lb_update_rule +from lbmpy.stencils import get_stencil +from lbmpy.methods.creationfunctions import create_with_discrete_maxwellian_eq_moments + +from lbmpy.phasefield_allen_cahn.kernel_equations import initializer_kernel_hydro_lb, \ + initializer_kernel_phase_field_lb, get_collision_assignments_hydro, interface_tracking_force, hydrodynamic_force +from lbmpy.phasefield_allen_cahn.force_model import MultiphaseForceModel + +from collections import OrderedDict + +import numpy as np + +stencil_phase = get_stencil("D3Q15") +stencil_hydro = get_stencil("D3Q27") +assert (len(stencil_phase[0]) == len(stencil_hydro[0])) +dimensions = len(stencil_hydro[0]) + +parameters = calculate_dimensionless_rising_bubble(reference_time=18000, + density_heavy=1.0, + bubble_radius=16, + bond_number=30, + reynolds_number=420, + density_ratio=1000, + viscosity_ratio=100) + +np.isclose(parameters["density_light"], 0.001, rtol=1e-05, atol=1e-08, equal_nan=False) +np.isclose(parameters["gravitational_acceleration"], -9.876543209876543e-08, rtol=1e-05, atol=1e-08, equal_nan=False) + +parameters = calculate_parameters_rti(reference_length=128, + reference_time=18000, + density_heavy=1.0, + capillary_number=9.1, + reynolds_number=128, + atwood_number=1.0, + peclet_number=744, + density_ratio=3, + viscosity_ratio=3) + +np.isclose(parameters["density_light"], 1/3, rtol=1e-05, atol=1e-08, equal_nan=False) +np.isclose(parameters["gravitational_acceleration"], -3.9506172839506174e-07, rtol=1e-05, atol=1e-08, equal_nan=False) +np.isclose(parameters["mobility"], 0.0012234169653524492, rtol=1e-05, atol=1e-08, equal_nan=False) + +rs = analytic_rising_speed(1-6, 20, 0.01) +np.isclose(rs, 16666.666666666668, rtol=1e-05, atol=1e-08, equal_nan=False) + +density_liquid = 1.0 +density_gas = 0.001 +surface_tension = 0.0001 +M = 0.02 + +# phase-field parameter +drho3 = (density_liquid - density_gas) / 3 +# interface thickness +W = 5 +# coefficient related to surface tension +beta = 12.0 * (surface_tension / W) +# coefficient related to surface tension +kappa = 1.5 * surface_tension * W +# relaxation rate allen cahn (h) +w_c = 1.0 / (0.5 + (3.0 * M)) + +# fields +u = fields("vel_field(" + str(dimensions) + "): [" + str(dimensions) + "D]", layout='fzyx') +C = fields("phase_field: [" + str(dimensions) + "D]", layout='fzyx') +force = fields("force(" + str(dimensions) + "): [" + str(dimensions) + "D]", layout='fzyx') + +h = fields("lb_phase_field(" + str(len(stencil_phase)) + "): [" + str(dimensions) + "D]", layout='fzyx') +h_tmp = fields("lb_phase_field_tmp(" + str(len(stencil_phase)) + "): [" + str(dimensions) + "D]", layout='fzyx') + +g = fields("lb_velocity_field(" + str(len(stencil_hydro)) + "): [" + str(dimensions) + "D]", layout='fzyx') +g_tmp = fields("lb_velocity_field_tmp(" + str(len(stencil_hydro)) + "): [" + str(dimensions) + "D]", layout='fzyx') + +# calculate the relaxation rate for the hydro lb as well as the body force +density = density_gas + C.center * (density_liquid - density_gas) +# force acting on all phases of the model +body_force = np.array([0, 1e-6, 0]) + +relaxation_time = 0.03 + 0.5 +relaxation_rate = 1.0 / relaxation_time + +method_phase = create_lb_method(stencil=stencil_phase, method='srt', relaxation_rate=w_c, compressible=True) + +mrt = create_lb_method(method="mrt", stencil=stencil_hydro, relaxation_rates=[1, 1, relaxation_rate, 1, 1, 1, 1]) +rr_dict = OrderedDict(zip(mrt.moments, mrt.relaxation_rates)) + +method_hydro = create_with_discrete_maxwellian_eq_moments(stencil_hydro, rr_dict, compressible=False) + +# create the kernels for the initialization of the g and h field +h_updates = initializer_kernel_phase_field_lb(h, C, u, method_phase, W) +g_updates = initializer_kernel_hydro_lb(g, u, method_hydro) + +force_h = [f / 3 for f in interface_tracking_force(C, stencil_phase, W)] +force_model_h = MultiphaseForceModel(force=force_h) + +force_g = hydrodynamic_force(g, C, method_hydro, relaxation_time, density_liquid, density_gas, kappa, beta, body_force) +force_model_g = MultiphaseForceModel(force=force_g, rho=density) + +h_tmp_symbol_list = [h_tmp.center(i) for i, _ in enumerate(stencil_phase)] +sum_h = np.sum(h_tmp_symbol_list[:]) + +method_phase = create_lb_method(stencil=stencil_phase, + method='srt', + relaxation_rate=w_c, + compressible=True, + force_model=force_model_h) + +allen_cahn_lb = create_lb_update_rule(lb_method=method_phase, + velocity_input=u, + compressible=True, + optimization={"symbolic_field": h, + "symbolic_temporary_field": h_tmp}, + kernel_type='stream_pull_collide') + +allen_cahn_lb.set_main_assignments_from_dict({**allen_cahn_lb.main_assignments_dict, **{C.center: sum_h}}) +allen_cahn_update_rule = AssignmentCollection(main_assignments=allen_cahn_lb.main_assignments, + subexpressions=allen_cahn_lb.subexpressions) +allen_cahn_update_rule = sympy_cse(allen_cahn_update_rule) + +# --------------------------------------------------------------------------------------------------------- + +method_hydro = create_with_discrete_maxwellian_eq_moments(stencil_hydro, rr_dict, force_model=force_model_g) + +hydro_lb_update_rule = get_collision_assignments_hydro(lb_method=method_hydro, + density=density, + velocity_input=u, + force=force_g, + optimization={"symbolic_field": g, + "symbolic_temporary_field": g_tmp}, + kernel_type='collide_only') + +# streaming of the hydrodynamic distribution +stream_hydro = create_lb_update_rule(stencil=stencil_hydro, + optimization={"symbolic_field": g, + "symbolic_temporary_field": g_tmp}, + kernel_type='stream_pull_only') + diff --git a/lbmpy_tests/phasefield_allen_cahn/test_phase_field_allen_cahn.ipynb b/lbmpy_tests/phasefield_allen_cahn/test_phase_field_allen_cahn.ipynb index 5b9312181b5d84af027faaab77760e973c12d69f..1551072c00c8e3d24bc8a543d51df76d14c84fca 100644 --- a/lbmpy_tests/phasefield_allen_cahn/test_phase_field_allen_cahn.ipynb +++ b/lbmpy_tests/phasefield_allen_cahn/test_phase_field_allen_cahn.ipynb @@ -47,8 +47,8 @@ "outputs": [], "source": [ "stencil_phase = get_stencil(\"D2Q9\")\n", - "stencil_velo = get_stencil(\"D2Q9\")\n", - "assert(len(stencil_phase[0]) == len(stencil_velo[0]))\n", + "stencil_hydro = get_stencil(\"D2Q9\")\n", + "assert(len(stencil_phase[0]) == len(stencil_hydro[0]))\n", "\n", "dimensions = len(stencil_phase[0])" ] @@ -112,12 +112,12 @@ "domain_size = (Nx, Ny)\n", "dh = ps.create_data_handling((domain_size), periodicity = (True, False), parallel=False, default_target=target)\n", "\n", - "g = dh.add_array(\"g\", values_per_cell=len(stencil_velo))\n", + "g = dh.add_array(\"g\", values_per_cell=len(stencil_hydro))\n", "dh.fill(\"g\", 0.0, ghost_layers=True)\n", "h = dh.add_array(\"h\",values_per_cell=len(stencil_phase))\n", "dh.fill(\"h\", 0.0, ghost_layers=True)\n", "\n", - "g_tmp = dh.add_array(\"g_tmp\", values_per_cell=len(stencil_velo))\n", + "g_tmp = dh.add_array(\"g_tmp\", values_per_cell=len(stencil_hydro))\n", "dh.fill(\"g_tmp\", 0.0, ghost_layers=True)\n", "h_tmp = dh.add_array(\"h_tmp\",values_per_cell=len(stencil_phase))\n", "dh.fill(\"h_tmp\", 0.0, ghost_layers=True)\n", @@ -161,7 +161,7 @@ "metadata": {}, "outputs": [], "source": [ - "if len(stencil_velo) == 9:\n", + "if len(stencil_hydro) == 9:\n", " # for D2Q9 a self defined method is used\n", " x, y, z = MOMENT_SYMBOLS\n", " moment_table = [sp.Integer(1),\n", @@ -176,14 +176,14 @@ "\n", " relax_table = [1, 1, 1, 1, 1, 1, 1, s8, s8]\n", " rr_dict = OrderedDict(zip(moment_table, relax_table))\n", - "elif len(stencil_velo) == 19:\n", - " mrt = create_lb_method(method=\"mrt\", stencil=stencil_velo, relaxation_rates=[1, 1, 1, 1, s8, 1, 1])\n", + "elif len(stencil_hydro) == 19:\n", + " mrt = create_lb_method(method=\"mrt\", stencil=stencil_hydro, relaxation_rates=[1, 1, 1, 1, s8, 1, 1])\n", " rr_dict = OrderedDict(zip(mrt.moments, mrt.relaxation_rates))\n", "else:\n", - " mrt = create_lb_method(method=\"mrt\", stencil=stencil_velo, relaxation_rates=[1, 1, s8, 1, 1, 1, 1])\n", + " mrt = create_lb_method(method=\"mrt\", stencil=stencil_hydro, relaxation_rates=[1, 1, s8, 1, 1, 1, 1])\n", " rr_dict = OrderedDict(zip(mrt.moments, mrt.relaxation_rates))\n", "\n", - "method_velo = create_with_discrete_maxwellian_eq_moments(stencil_velo, rr_dict, compressible=False)" + "method_hydro = create_with_discrete_maxwellian_eq_moments(stencil_hydro, rr_dict, compressible=False)" ] }, { @@ -216,7 +216,7 @@ "outputs": [], "source": [ "h_updates = initializer_kernel_phase_field_lb(h, C, u, method_phase, W)\n", - "g_updates = initializer_kernel_hydro_lb(g, u, method_velo)\n", + "g_updates = initializer_kernel_hydro_lb(g, u, method_hydro)\n", "\n", "h_init = ps.create_kernel(h_updates, target=dh.default_target, cpu_openmp=True).compile()\n", "g_init = ps.create_kernel(g_updates, target=dh.default_target, cpu_openmp=True).compile()" @@ -231,7 +231,7 @@ "force_h = [f / 3 for f in interface_tracking_force(C, stencil_phase, W)]\n", "force_model_h = MultiphaseForceModel(force=force_h)\n", "\n", - "force_g = hydrodynamic_force(g, C, method_velo, tau, rho_H, rho_L, kappa, beta, body_force)\n", + "force_g = hydrodynamic_force(g, C, method_hydro, tau, rho_H, rho_L, kappa, beta, body_force)\n", "force_model_g = MultiphaseForceModel(force=force_g, rho=rho)" ] }, @@ -270,16 +270,17 @@ "\n", "## collision of g\n", "#force_model = forcemodels.Guo(force_g(0, MRT_collision_op))\n", - "method_velo = create_with_discrete_maxwellian_eq_moments(stencil_velo, rr_dict, force_model=force_model_g)\n", + "method_hydro = create_with_discrete_maxwellian_eq_moments(stencil_hydro, rr_dict, force_model=force_model_g)\n", "\n", - "hydro_lb = create_lb_update_rule(lb_method=method_velo,\n", - " velocity_input = u,\n", - " compressible = False,\n", - " optimization = {\"symbolic_field\": g,\n", - " \"symbolic_temporary_field\": g_tmp},\n", - " kernel_type = 'collide_only')\n", + "hydro_lb_update_rule = get_collision_assignments_hydro(lb_method=method_hydro,\n", + " density=rho,\n", + " velocity_input=u,\n", + " force = force_g,\n", + " optimization={\"symbolic_field\": g,\n", + " \"symbolic_temporary_field\": g_tmp},\n", + " kernel_type='collide_only')\n", "\n", - "ast_hydro_lb = ps.create_kernel(hydro_lb, target=dh.default_target, cpu_openmp=True)\n", + "ast_hydro_lb = ps.create_kernel(hydro_lb_update_rule, target=dh.default_target, cpu_openmp=True)\n", "kernel_hydro_lb = ast_hydro_lb.compile()" ] }, @@ -295,11 +296,11 @@ "periodic_BC_C = dh.synchronization_function(C.name, target=dh.default_target, optimization = {\"openmp\": True})\n", "\n", "# No slip boundary for the phasefield lbm\n", - "bh_allen_cahn = LatticeBoltzmannBoundaryHandling(allen_cahn_lb.method, dh, 'h',\n", + "bh_allen_cahn = LatticeBoltzmannBoundaryHandling(method_phase, dh, 'h',\n", " target=dh.default_target, name='boundary_handling_h')\n", "\n", "# No slip boundary for the velocityfield lbm\n", - "bh_hydro = LatticeBoltzmannBoundaryHandling(hydro_lb.method, dh, 'g' ,\n", + "bh_hydro = LatticeBoltzmannBoundaryHandling(method_hydro, dh, 'g' ,\n", " target=dh.default_target, name='boundary_handling_g')\n", "\n", "wall = NoSlip()\n", @@ -327,7 +328,10 @@ "metadata": {}, "outputs": [], "source": [ - "ac_g = get_assignment_list_stream_hydro(g, g_tmp, method_velo, force_g, u, rho)\n", + "ac_g = create_lb_update_rule(stencil = stencil_hydro,\n", + " optimization={\"symbolic_field\": g,\n", + " \"symbolic_temporary_field\": g_tmp},\n", + " kernel_type='stream_pull_only')\n", "ast_g = ps.create_kernel(ac_g, target=dh.default_target, cpu_openmp=True)\n", "stream_g = ast_g.compile()" ] @@ -361,7 +365,18 @@ "metadata": { "scrolled": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/markus/miniconda3/lib/python3.7/site-packages/ipykernel_launcher.py:6: DeprecationWarning: Numpy has detected that you (may be) writing to an array with\n", + "overlapping memory from np.broadcast_arrays. If this is intentional\n", + "set the WRITEABLE flag True or make a copy immediately before writing.\n", + " \n" + ] + } + ], "source": [ "Initialize_distributions()\n", "\n", @@ -409,7 +424,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.7.4" } }, "nbformat": 4,