def match_continuity_eq(continuity_eq): continuity_eq = diff_simplify(continuity_eq) is_compressible = u[0] * Diff(rho, 0) in continuity_eq.args factor = rho if is_compressible else 1 ref_continuity_eq = diff_simplify( Diff(rho, t) + sum(Diff(factor * u[i], i) for i in range(dim))) return ref_continuity_eq - continuity_eq, is_compressible
def free_energy_interfacial(c, surface_tensions, a, epsilon): n = len(c) # this is different than in the paper: the sum is under the condition i < j, not i != j # otherwise the model does not correctly reduce to equation 1.5 return -epsilon * a / 2 * sum( surface_tensions[i, j] * Diff(c[i]) * Diff(c[j]) for i in range(n) for j in range(i))
def force_component(b): r = -sum(Diff(pressure_tensor[a, b], a) for a in range(dim)) r = expand_diff_full(r, functions=functions) if pbs is not None: r += 2 * Diff(pbs, b) * pbs return r
def get_shear_viscosity_candidates(self): result = set() dim = self._method.dim for i, j in symmetric_product(range(dim), range(dim), with_diagonal=False): result.add(-sp.cancel(self._sigmaWithoutErrorTerms[i, j] / (Diff(self.u[i], j) + Diff(self.u[j], i)))) return result
def substitute_laplacian_by_sum(eq, dim): """Substitutes abstract Laplacian represented by ∂∂ by a sum over all dimensions i.e. in case of 3D: ∂∂ is replaced by ∂0∂0 + ∂1∂1 + ∂2∂2 Args: eq: the term where the substitutions should be made dim: spatial dimension, in example above, 3 """ functions = [d.args[0] for d in eq.atoms(Diff)] substitutions = {Diff(Diff(op)): sum(Diff(Diff(op, i), i) for i in range(dim)) for op in functions} return expand_diff_full(eq.subs(substitutions))
def free_energy_functional_3_phases(order_parameters=None, interface_width=interface_width_symbol, transformed=True, include_bulk=True, include_interface=True, expand_derivatives=True, kappa=sp.symbols("kappa_:3")): """Free Energy of ternary multi-component model :cite:`Semprebon2016`. """ kappa_prime = tuple(interface_width ** 2 * k for k in kappa) c = sp.symbols("C_:3") bulk_free_energy = sum(k * C_i ** 2 * (1 - C_i) ** 2 / 2 for k, C_i in zip(kappa, c)) surface_free_energy = sum(k * Diff(C_i) ** 2 / 2 for k, C_i in zip(kappa_prime, c)) f = 0 if include_bulk: f += bulk_free_energy if include_interface: f += surface_free_energy if not transformed: return f if order_parameters: rho, phi, psi = order_parameters else: rho, phi, psi = sp.symbols("rho phi psi") transformation_matrix = sp.Matrix([[1, 1, 1], [1, -1, 0], [0, 0, 1]]) rho_def, phi_def, psi_def = transformation_matrix * sp.Matrix(c) order_param_to_concentration_relation = sp.solve([rho_def - rho, phi_def - phi, psi_def - psi], c) f = f.subs(order_param_to_concentration_relation) if expand_derivatives: f = expand_diff_linear(f, functions=order_parameters) return f, transformation_matrix
def remove_error_terms(expr): rho_diffs_to_zero = {Diff(sp.Symbol("rho"), i): 0 for i in range(3)} expr = expr.subs(rho_diffs_to_zero) if isinstance(expr, sp.Matrix): expr = expr.applyfunc(remove_higher_order_u) else: expr = remove_higher_order_u(expr.expand()) return sp.cancel(expr.expand())
def free_energy_high_density_ratio(eos, density, density_gas, density_liquid, c_liquid_1, c_liquid_2, lambdas, kappas): """ Free energy for a ternary system with high density ratio :cite:`Wohrwag2018` Args: eos: equation of state, has to depend on exactly on symbol, the density density: symbol for density density_gas: numeric value for gas density (can be obtained by `maxwell_construction`) density_liquid: numeric value for liquid density (can be obtained by `maxwell_construction`) c_liquid_1: symbol for concentration of first liquid phase c_liquid_2: symbol for concentration of second liquid phase lambdas: pre-factors of bulk terms, lambdas[0] multiplies the density term, lambdas[1] the first liquid and lambdas[2] the second liquid phase kappas: pre-factors of interfacial terms, order same as for lambdas Returns: free energy expression """ assert eos.atoms(sp.Symbol) == {density} # ---- Part 1: contribution of equation of state, ψ_eos symbolic_integration_constant = sp.Dummy(real=True) psi_eos = free_energy_from_eos(eos, density, symbolic_integration_constant) # accuracy problems in free_energy_from_eos can lead to complex solutions for integration constant psi_eos = remove_small_floats(psi_eos, 1e-14) # integration constant is determined from the condition ψ(ρ_gas) == ψ(ρ_liquid) equal_psi_condition = psi_eos.subs(density, density_gas) - psi_eos.subs(density, density_liquid) solve_res = sp.solve(equal_psi_condition, symbolic_integration_constant) assert len(solve_res) == 1 integration_constant = solve_res[0] psi_eos = psi_eos.subs(symbolic_integration_constant, integration_constant) # energy is shifted by ψ_0 = ψ(ρ_gas) which is also ψ(ρ_liquid) by construction psi_0 = psi_eos.subs(density, density_gas) # ---- Part 2: standard double well potential as bulk term, and gradient squares as interface term def f(c): return c ** 2 * (1 - c) ** 2 f_bulk = (lambdas[0] / 2 * (psi_eos - psi_0) + lambdas[1] / 2 * f(c_liquid_1) + lambdas[2] / 2 * f(c_liquid_2)) f_interface = (kappas[0] / 2 * Diff(density) ** 2 + kappas[1] / 2 * Diff(c_liquid_1) ** 2 + kappas[2] / 2 * Diff(c_liquid_2) ** 2) return f_bulk + f_interface
def chapman_enskog_derivative_recombination(expr, label, eps=sp.Symbol("epsilon"), start_order=1, stop_order=4): """Inverse operation of 'chapman_enskog_derivative_expansion'""" expr = expr.expand() diffs = [ d for d in expr.atoms(Diff) if d.target == label and d.superscript == stop_order - 1 ] for d in diffs: substitution = Diff(d.arg, label) substitution -= sum([ eps**i * Diff(d.arg, label, i) for i in range(start_order, stop_order - 1) ]) expr = expr.subs(d, substitution / eps**(stop_order - 1)) return expr
def d(arg, *args): """Shortcut to create nested derivatives""" assert arg is not None args = sorted(args, reverse=True, key=lambda e: e.name if isinstance(e, sp.Symbol) else e) res = arg for i in args: res = Diff(res, i) return res
def does_approximate_navier_stokes(self): """Returns a set of equations that are required in order for the method to approximate Navier Stokes equations up to second order""" conditions = {0} dim = self._method.dim assert dim > 1 # Check that shear viscosity does not depend on any u derivatives - create conditions (equations) that # have to be fulfilled for this to be the case viscosity_reference = self._sigmaWithoutErrorTerms[0, 1].expand().coeff( Diff( self.u[0], 1)) for i, j in symmetric_product(range(dim), range(dim), with_diagonal=False): term = self._sigmaWithoutErrorTerms[i, j] equal_cross_term_condition = sp.expand( term.coeff(Diff(self.u[i], j)) - viscosity_reference) term = term.subs({Diff(self.u[i], j): 0, Diff(self.u[j], i): 0}) conditions.add(equal_cross_term_condition) for k in range(dim): symmetric_term_condition = term.coeff(Diff(self.u[k], k)) conditions.add(symmetric_term_condition) term = term.subs({Diff(self.u[k], k): 0 for k in range(dim)}) conditions.add(term) bulk_candidates = list( self.get_bulk_viscosity_candidates(-viscosity_reference)) if len(bulk_candidates) > 0: for i in range(1, len(bulk_candidates)): conditions.add(bulk_candidates[0] - bulk_candidates[i]) return conditions
def match_moment_eqs(moment_eqs, is_compressible): shear_and_pressure_eqs = [] for i, mom_eq in enumerate(moment_eqs): factor = rho if is_compressible else 1 ref = diff_simplify( Diff(factor * u[i], t) + sum(Diff(factor * u[i] * u[j], j) for j in range(dim))) shear_and_pressure_eqs.append(diff_simplify(moment_eqs[i]) - ref) # new_filtered pressure term coefficient_arg_sets = [] for i, eq in enumerate(shear_and_pressure_eqs): coefficient_arg_sets.append(set()) eq = eq.expand() assert eq.func == sp.Add for term in eq.args: if term.atoms(CeMoment): continue candidate_list = [e for e in term.atoms(Diff) if e.target == i] if len(candidate_list) != 1: continue coefficient_arg_sets[i].add( (term / candidate_list[0], candidate_list[0].arg)) pressure_terms = set.intersection(*coefficient_arg_sets) sigma_ = sp.zeros(dim) error_terms_ = [] for i, shear_and_pressure_eq in enumerate(shear_and_pressure_eqs): eq_without_pressure = shear_and_pressure_eq - sum( coeff * Diff(arg, i) for coeff, arg in pressure_terms) for d in eq_without_pressure.atoms(Diff): eq_without_pressure = eq_without_pressure.collect(d) sigma_[i, d.target] += eq_without_pressure.coeff(d) * d.arg eq_without_pressure = eq_without_pressure.subs(d, 0) error_terms_.append(eq_without_pressure) pressure_ = [coeff * arg for coeff, arg in pressure_terms] return pressure_, sigma_, error_terms_
def force_computation_equivalence(dim=3, num_phases=4): def Λ(i, j): if i > j: i, j = j, i return sp.Symbol("Lambda_{}{}".format(i, j)) φ = sp.symbols("φ_:{}".format(num_phases)) f_if = sum(Λ(α, β) / 2 * Diff(φ[α]) * Diff(φ[β]) for α in range(num_phases) for β in range(num_phases)) μ = chemical_potentials_from_free_energy(f_if, order_parameters=φ) μ = substitute_laplacian_by_sum(μ, dim=dim) p = sp.Matrix(dim, dim, lambda i, j: pressure_tensor_interface_component_new(f_if, φ, dim, i, j)) force_from_p = force_from_pressure_tensor(p, functions=φ) for d in range(dim): t1 = normalize_diff_order(force_from_p[d]) t2 = expand_diff_full(force_from_phi_and_mu(φ, dim=dim, mu=μ)[d], functions=φ).expand() assert t1 - t2 == 0 print("Success")
def forth_order_isotropic_discretize(field): second_neighbor_stencil = [(i, j) for i in (-2, -1, 0, 1, 2) for j in (-2, -1, 0, 1, 2)] x_diff = FiniteDifferenceStencilDerivation((0, ), second_neighbor_stencil) x_diff.set_weight((2, 0), sp.Rational(1, 10)) x_diff.assume_symmetric(0, anti_symmetric=True) x_diff.assume_symmetric(1) x_diff_stencil = x_diff.get_stencil(isotropic=True) y_diff = FiniteDifferenceStencilDerivation((1, ), second_neighbor_stencil) y_diff.set_weight((0, 2), sp.Rational(1, 10)) y_diff.assume_symmetric(1, anti_symmetric=True) y_diff.assume_symmetric(0) y_diff_stencil = y_diff.get_stencil(isotropic=True) substitutions = {} for i in range(field.index_shape[0]): substitutions.update({ Diff(field(i), 0): x_diff_stencil.apply(field(i)), Diff(field(i), 1): y_diff_stencil.apply(field(i)) }) return substitutions
def chapman_enskog_derivative_expansion(expr, label, eps=sp.Symbol("epsilon"), start_order=1, stop_order=4): """Substitutes differentials with given target and no superscript by the sum: eps**(start_order) * Diff(superscript=start_order) + ... + eps**(stop_order) * Diff(superscript=stop_order)""" diffs = [d for d in expr.atoms(Diff) if d.target == label] subs_dict = { d: sum([ eps**i * Diff(d.arg, d.target, i) for i in range(start_order, stop_order) ]) for d in diffs } return expr.subs(subs_dict)
def free_energy_functional_n_phases_penalty_term(order_parameters, interface_width=interface_width_symbol, kappa=None, penalty_term_factor=0.01): num_phases = len(order_parameters) if kappa is None: kappa = sp.symbols(f"kappa_:{num_phases}") if not hasattr(kappa, "__len__"): kappa = [kappa] * num_phases def f(x): return x ** 2 * (1 - x) ** 2 bulk = sum(f(c) * k / 2 for c, k in zip(order_parameters, kappa)) interface = sum(Diff(c) ** 2 / 2 * interface_width ** 2 * k for c, k in zip(order_parameters, kappa)) bulk_penalty_term = (1 - sum(c for c in order_parameters)) ** 2 return bulk + interface + penalty_term_factor * bulk_penalty_term
def get_bulk_viscosity_candidates(self, viscosity=None): sigma = self._sigmaWithoutErrorTerms assert self._sigmaWithHigherOrderMoments.is_square result = set() if viscosity is None: viscosity = self.get_dynamic_viscosity() for i in range(sigma.shape[0]): bulk_term = sigma[i, i] + 2 * viscosity * Diff(self.u[i], i) bulk_term = bulk_term.expand() for d in bulk_term.atoms(Diff): bulk_term = bulk_term.collect(d) result.add(bulk_term.coeff(d)) bulk_term = bulk_term.subs(d, 0) if bulk_term != 0: return set() if len(result) == 0: result.add(0) return result
def test_fs(): f = sp.Symbol("f", commutative=False) a = Diff(Diff(Diff(f, 1), 0), 0) assert a.is_commutative is False print(str(a)) assert diff(f) == f x, y = sp.symbols("x, y") collected_terms = collect_diffs(diff(x, 0, 0)) assert collected_terms == Diff(Diff(x, 0, -1), 0, -1) src = fields("src : double[2D]") expr = sp.Add(Diff(Diff(src[0, 0])), 10) expected = Diff(Diff(src[0, 0], 0, -1), 0, -1) + Diff( Diff(src[0, 0], 1, -1), 1, -1) + 10 result = replace_generic_laplacian(expr, 3) assert result == expected
def free_energy_functional_n_phases(num_phases=None, surface_tensions=symmetric_symbolic_surface_tension, interface_width=interface_width_symbol, order_parameters=None, include_bulk=True, include_interface=True, symbolic_lambda=False, symbolic_dependent_variable=False, f1=lambda c: c ** 2 * (1 - c) ** 2, f2=lambda c: c ** 2 * (1 - c) ** 2, triple_point_energy=0): r""" Returns a symbolic expression for the free energy of a system with N phases and specified surface tensions. The total free energy is the sum of a bulk and an interface component. .. math :: F_{bulk} = \int \frac{3}{\sqrt{2} \eta} \sum_{\substack{\alpha,\beta=0 \\ \alpha \neq \beta}}^{N-1} \frac{\tau(\alpha,\beta)}{2} \left[ f(\phi_\alpha) + f(\phi_\beta) - f(\phi_\alpha + \phi_\beta) \right] \; d\Omega F_{interface} = \int \sum_{\alpha,\beta=0}^{N-2} \frac{\Lambda_{\alpha\beta}}{2} \left( \nabla \phi_\alpha \cdot \nabla \phi_\beta \right)\; d\Omega \Lambda_{\alpha \beta} = \frac{3 \eta}{\sqrt{2}} \left[ \tau(\alpha,N-1) + \tau(\beta,N-1) - \tau(\alpha,\beta) \right] f(c) = c^2( 1-c)^2 Args: num_phases: number of phases, called N above surface_tensions: surface tension function, called with two phase indices (two integers) interface_width: called :math:`\eta` above, controls the interface width order_parameters: explicitly f1: bulk energy is computed as f1(c_i) + f1(c_j) - f2(c_i + c_j) f2: see f2 triple_point_energy: term multiplying c[i]*c[j]*c[k] for i < j < k Parameters useful for viewing / debugging the function include_bulk: if false no bulk term is added include_interface:if false no interface contribution is added symbolic_lambda: surface energy coefficient is represented by symbol, not in expanded form symbolic_dependent_variable: last phase variable is defined as 1-other_phase_vars, if this is set to True it is represented by phi_A for better readability """ assert not (num_phases is None and order_parameters is None) if order_parameters is None: phi = symbolic_order_parameters(num_phases - 1) else: phi = order_parameters num_phases = len(phi) + 1 if not symbolic_dependent_variable: phi = tuple(phi) + (1 - sum(phi),) else: phi = tuple(phi) + (sp.Symbol("phi_D"), ) if callable(surface_tensions): surface_tensions = surface_tensions else: t = surface_tensions def surface_tensions(i, j): return t if i != j else 0 # Compared to handwritten notes we scale the interface width parameter here to obtain the correct # equations for the interface profile and the surface tensions i.e. to pass tests # test_analyticInterfaceSolution and test_surfaceTensionDerivation interface_width *= sp.sqrt(2) def lambda_coeff(k, l): if symbolic_lambda: symbol_names = (k, l) if k < l else (l, k) return sp.Symbol(f"Lambda_{symbol_names[0]}{symbol_names[1]}") n = num_phases - 1 if k == l: assert surface_tensions(l, l) == 0 return 3 / sp.sqrt(2) * interface_width * (surface_tensions(k, n) + surface_tensions(l, n) - surface_tensions(k, l)) def bulk_term(i, j): return surface_tensions(i, j) / 2 * (f1(phi[i]) + f1(phi[j]) - f2(phi[i] + phi[j])) f_bulk = 3 / sp.sqrt(2) / interface_width * sum(bulk_term(i, j) for i, j in multi_sum(2, num_phases) if i != j) f_interface = sum(lambda_coeff(i, j) / 2 * Diff(phi[i]) * Diff(phi[j]) for i, j in multi_sum(2, num_phases - 1)) for i in range(len(phi)): for j in range(i): for k in range(j): f_bulk += triple_point_energy * phi[i] * phi[j] * phi[k] result = 0 if include_bulk: result += f_bulk if include_interface: result += f_interface return result
def lapl(e): return sum(Diff(Diff(e, i), i) for i in range(dh.dim))
def force_from_phi_and_mu(order_parameters, dim, mu=None): if mu is None: mu = sp.symbols(f"mu_:{len(order_parameters)}") return sp.Matrix([sum(- c_i * Diff(mu_i, a) for c_i, mu_i in zip(order_parameters, mu)) for a in range(dim)])