def test_surface_tension_derivation(): """Computes the excess free energy per unit area of an interface transition between two phases which should give exactly the surface tension parameter""" num_phases = 4 eta = sp.Symbol("eta") free_energy = free_energy_functional_n_phases(num_phases, interface_width=eta) phi = symbolic_order_parameters(num_phases) x = sp.Symbol("x") sol = analytic_interface_profile(x, interface_width=eta) for a, b in [(1, 3), (0, 1)]: substitutions = {phi[a]: sol} if b < len(phi) - 1: substitutions[phi[b]] = 1 - sol for i, phi_i in enumerate(phi[:-1]): if i not in (a, b): substitutions[phi_i] = 0 free_energy_2_phase = sp.simplify( evaluate_diffs(free_energy.subs(substitutions), x)) result = cosh_integral(free_energy_2_phase, x) assert result == symmetric_symbolic_surface_tension(a, b)
def test_analytic_interface_solution(): """Ensures that the tanh is an analytical solution for the prescribed free energy / chemical potential """ num_phases = 4 phi = symbolic_order_parameters(num_phases - 1) free_energy = free_energy_functional_n_phases(num_phases, order_parameters=phi).subs( {p: 0 for p in phi[1:]}) mu_diff_eq = chemical_potentials_from_free_energy(free_energy, [phi[0]])[0] x = sp.Symbol("x") sol = analytic_interface_profile(x) inserted = mu_diff_eq.subs(phi[0], sol) assert sp.expand(evaluate_diffs(inserted, x)) == 0
def tanh_test(pf_step, phase0, phase1, expected_interface_width=1, time_steps=10000): """ Initializes a sharp interface and checks if tanh-shaped profile is developing Args: pf_step: phase field scenario / step phase0: index of first phase to initialize phase1: index of second phase to initialize inversely expected_interface_width: interface width parameter alpha that is used in analytical form time_steps: number of time steps run before evaluation Returns: deviation of simulated profile from analytical solution as average(abs(simulation-analytic)) """ import lbmpy.plot as plt from lbmpy.phasefield.analytical import analytic_interface_profile domain_size = pf_step.data_handling.shape pf_step.reset() pf_step.data_handling.fill(pf_step.phi_field_name, 0) init_sharp_interface(pf_step, phase_idx=phase0, inverse=False) init_sharp_interface(pf_step, phase_idx=phase1, inverse=True) pf_step.set_pdf_fields_from_macroscopic_values() pf_step.run(time_steps) vis_width = 20 x = np.arange(vis_width) - (vis_width // 2) analytic = np.array([analytic_interface_profile(x_i - 0.5, expected_interface_width) for x_i in x], dtype=np.float64) step_location = domain_size[0] // 4 simulated = pf_step.phi[step_location - vis_width // 2:step_location + vis_width // 2, 0, phase0] plt.plot(analytic, label='analytic', marker='o') plt.plot(simulated, label='simulated', marker='x') plt.legend() return np.average(np.abs(simulated - analytic))
def analytic_profile(width, alpha): x = np.arange(width) - (width // 2) return np.array([analytic_interface_profile(x_i - 0.5, alpha) for x_i in x], dtype=np.float64)
def galilean_invariance_test(pf_step, velocity=0.05, rounds=3, phase0=0, phase1=1, expected_interface_width=1, init_time_steps=5000): """ Moves interface at constant speed through periodic domain - check if momentum is conserved Args: pf_step: phase field scenario / step velocity: constant velocity to move interface rounds: how many times the interface should travel through the domain phase0: index of first phase to initialize phase1: index of second phase to initialize inversely expected_interface_width: interface width parameter alpha that is used in analytical form init_time_steps: before velocity is set, this many time steps are run to let interface settle to tanh shape Returns: change in velocity """ import lbmpy.plot as plt from lbmpy.phasefield.analytical import analytic_interface_profile domain_size = pf_step.data_handling.shape round_time_steps = int((domain_size[0] + 0.25) / velocity) print("Velocity:", velocity, " Time steps for round:", round_time_steps) pf_step.reset() pf_step.data_handling.fill(pf_step.phi_field_name, 0) init_sharp_interface(pf_step, phase_idx=phase0, inverse=False) init_sharp_interface(pf_step, phase_idx=phase1, inverse=True) pf_step.set_pdf_fields_from_macroscopic_values() print("Running", init_time_steps, "initial time steps") pf_step.run(init_time_steps) pf_step.data_handling.fill(pf_step.vel_field_name, velocity, value_idx=0) pf_step.set_pdf_fields_from_macroscopic_values() step_location = domain_size[0] // 4 vis_width = 20 simulated_profiles = [] def capture_profile(): simulated = pf_step.phi[step_location - vis_width // 2:step_location + vis_width // 2, 0, phase0].copy() simulated_profiles.append(simulated) capture_profile() for rt in range(rounds): print("Running round %d/%d" % (rt + 1, rounds)) pf_step.run(round_time_steps) capture_profile() x = np.arange(vis_width) - (vis_width // 2) ref = np.array([analytic_interface_profile(x_i - 0.5, expected_interface_width) for x_i in x], dtype=np.float64) plt.plot(x, ref, label='analytic', marker='o') for i, profile in enumerate(simulated_profiles): plt.plot(x, profile, label="After %d rounds" % (i,)) plt.legend() return np.average(pf_step.velocity[:, 0, 0]) - velocity