def _create_pdf_hierarchy(self, taylored_equation): """ Expresses the expanded pdfs f^1, f^2, .. as functions of the equilibrium f^0. Returns a list where element [1] is the equation for f^1 etc. """ chapman_enskog_hierarchy = chapman_enskog_ansatz( taylored_equation, spatial_derivative_orders=None, pdfs=(['f', 0, self.order + 1], ), commutative=False) chapman_enskog_hierarchy = [ chapman_enskog_hierarchy[i] for i in range(self.order + 1) ] inserted_hierarchy = [] raw_hierarchy = [] substitution_dict = {} for ce_eq, f_i in zip(chapman_enskog_hierarchy, self.f_syms): new_eq = -1 / self.collision_op_sym * (ce_eq - self.collision_op_sym * f_i) raw_hierarchy.append(new_eq) new_eq = expand_diff_linear(new_eq.subs(substitution_dict), functions=self.f_syms + [self.force_sym]) if new_eq: substitution_dict[f_i] = new_eq inserted_hierarchy.append(new_eq) return inserted_hierarchy, raw_hierarchy
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 chemical_potentials_from_free_energy(free_energy, order_parameters=None): """Computes chemical potentials as functional derivative of free energy.""" symbols = free_energy.atoms(sp.Symbol) if order_parameters is None: order_parameters = [s for s in symbols if s.name.startswith(order_parameter_symbol_name)] order_parameters.sort(key=lambda e: e.name) order_parameters = order_parameters[:-1] constants = [s for s in symbols if s not in order_parameters] return sp.Matrix([expand_diff_linear(functional_derivative(free_energy, op), constants=constants) for op in order_parameters])
def get_taylor_expanded_lb_equation(pdf_symbol_name="f", pdfs_after_collision_operator="\\Omega f", velocity_name="c", dim=3, taylor_order=2, shift=True): dim_labels = [sp.Rational(i, 1) for i in range(dim)] c = sp.Matrix([ expanded_symbol(velocity_name, subscript=label) for label in dim_labels ]) dt, t = sp.symbols("Delta_t t") pdf = sp.Symbol(pdf_symbol_name) collided_pdf = sp.Symbol(pdfs_after_collision_operator) dt_operator = DiffOperator(target=t) dx_operator = sp.Matrix([DiffOperator(target=l) for l in dim_labels]) taylor_operator = sum(dt**k * (dt_operator + c.dot(dx_operator))**k / sp.functions.factorial(k) for k in range(1, taylor_order + 1)) functions = [pdf, collided_pdf] eq_4_5 = taylor_operator - dt * collided_pdf applied_eq_4_5 = expand_diff_linear( DiffOperator.apply(eq_4_5, pdf, apply_to_constants=False), functions) if shift: operator = ((dt / 2) * (dt_operator + c.dot(dx_operator))).expand() op_times_eq_4_5 = expand_diff_linear( DiffOperator.apply(operator, applied_eq_4_5, apply_to_constants=False), functions).expand() op_times_eq_4_5 = normalize_diff_order(op_times_eq_4_5, functions) eq_4_7 = (applied_eq_4_5 - op_times_eq_4_5).subs( dt**(taylor_order + 1), 0) else: eq_4_7 = applied_eq_4_5.subs(dt**(taylor_order + 1), 0) eq_4_7 = eq_4_7.subs(dt, 1) return eq_4_7.expand()
def determine_viscosities(self, coordinate): """Matches the first order term of the momentum equation to Navier stokes. Automatically neglects higher order velocity terms and rho derivatives The bulk viscosity is predicted differently than by the normal Navier Stokes analysis...why?? Args: coordinate: which momentum equation to use i.e. x,y or z, to approximate Navier Stokes all have to return the same result """ dim = self.method.dim 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 s = functools.partial(multidimensional_sum, dim=dim) kd = kronecker_delta eta, eta_b = sp.symbols("nu nu_B") u = sp.symbols("u_:3")[:dim] a = coordinate navier_stokes_ref = eta * sum( d(u[a], b, b) + d(u[b], b, a) for b, in s(1)) + (eta_b - 2 * eta / 3) * sum( d(u[g], b, g) * kd(a, b) for b, g in s(2)) navier_stokes_ref = -navier_stokes_ref.expand() first_order_terms = self.get_momentum_equation(coordinate, order=1) first_order_terms = remove_higher_order_u(first_order_terms) first_order_terms = expand_diff_linear(first_order_terms, constants=[sp.Symbol("rho")]) match_coeff_equations = [] for item in navier_stokes_ref.atoms(Diff): match_coeff_equations.append( navier_stokes_ref.coeff(item) - first_order_terms.coeff(item)) return sp.solve(match_coeff_equations, [eta, eta_b])
def _compute_moments(self, recombined_eq, symbols_to_values): eq = recombined_eq.expand() assert eq.func is sp.Add new_products = [] for product in eq.args: assert product.func is sp.Mul derivative = None new_prod = 1 for arg in reversed(normalize_product(product)): if isinstance(arg, Diff): assert derivative is None, "More than one derivative term in the product" derivative = arg arg = arg.get_arg_recursive( ) # new argument is inner part of derivative if arg in symbols_to_values: arg = symbols_to_values[arg] have_shape = hasattr(arg, 'shape') and hasattr( new_prod, 'shape') if have_shape and arg.shape == new_prod.shape and arg.shape[ 1] == 1: # since sympy 1.9 sp.matrix_multiply_elementwise does not work anymore in this case new_prod = sp.Matrix(np.multiply(new_prod, arg)) else: new_prod = arg * new_prod if new_prod == 0: break if new_prod == 0: continue new_prod = sp.expand(sum(new_prod)) if derivative is not None: new_prod = derivative.change_arg_recursive(new_prod) new_products.append(new_prod) return normalize_diff_order( expand_diff_linear(sp.Add(*new_products), functions=self.physical_variables))
def diff_simplify(eq): variables = eq.atoms(CeMoment) variables.update(funcs) return expand_diff_products(expand_diff_linear(eq, variables)).expand()
def chapman_enskog_ansatz(equation, time_derivative_orders=(1, 3), spatial_derivative_orders=(1, 2), pdfs=(['f', 0, 3], ['\\Omega f', 1, 3]), commutative=True): r"""Uses a Chapman Enskog Ansatz to expand given equation. Args: equation: equation to expand time_derivative_orders: tuple describing range for time derivative to expand spatial_derivative_orders: tuple describing range for spatial derivatives to expand pdfs: symbols to expand: sequence of triples (symbol_name, start_order, end_order) commutative: can be set to False to have non-commutative pdf symbols Returns: tuple mapping epsilon order to equation """ t, eps = sp.symbols("t epsilon") # expand time derivatives if time_derivative_orders: equation = chapman_enskog_derivative_expansion(equation, t, eps, *time_derivative_orders) # expand spatial derivatives if spatial_derivative_orders: spatial_derivatives = [ a for a in equation.atoms(Diff) if str(a.target) != 't' ] labels = set(a.target for a in spatial_derivatives) for label in labels: equation = chapman_enskog_derivative_expansion( equation, label, eps, *spatial_derivative_orders) # expand pdfs subs_dict = {} expanded_pdf_symbols = [] max_expansion_order = spatial_derivative_orders[ 1] if spatial_derivative_orders else 10 for pdf_name, start_order, stop_order in pdfs: if isinstance(pdf_name, sp.Symbol): pdf_name = pdf_name.name expanded_pdf_symbols += [ expanded_symbol(pdf_name, superscript=i, commutative=commutative) for i in range(start_order, stop_order) ] pdf_symbol = sp.Symbol(pdf_name, commutative=commutative) subs_dict[pdf_symbol] = sum( eps**i * expanded_symbol(pdf_name, superscript=i, commutative=commutative) for i in range(start_order, stop_order)) max_expansion_order = max(max_expansion_order, stop_order) equation = equation.subs(subs_dict) equation = expand_diff_linear( equation, functions=expanded_pdf_symbols).expand().collect(eps) result = { eps_order: equation.coeff(eps**eps_order) for eps_order in range(1, 2 * max_expansion_order) } result[0] = equation.subs(eps, 0) return result
def take_moments(eqn, pdf_to_moment_name=(('f', '\\Pi'), ('\\Omega f', '\\Upsilon')), velocity_name='c', max_expansion=5, use_one_neighborhood_aliasing=False): pdf_symbols = [ tuple( expanded_symbol(name, superscript=i) for i in range(max_expansion)) for name, _ in pdf_to_moment_name ] velocity_terms = tuple( expanded_symbol(velocity_name, subscript=i) for i in range(3)) def determine_f_index(factor): FIndex = namedtuple("FIndex", ['moment_name', 'superscript']) for symbol_list_id, pdf_symbols_element in enumerate(pdf_symbols): try: return FIndex(pdf_to_moment_name[symbol_list_id][1], pdf_symbols_element.index(factor)) except ValueError: pass return None def handle_product(product_term): f_index = None derivative_term = None c_indices = [] rest = 1 for factor in normalize_product(product_term): if isinstance(factor, Diff): assert f_index is None f_index = determine_f_index(factor.get_arg_recursive()) derivative_term = factor elif factor in velocity_terms: c_indices += [velocity_terms.index(factor)] else: new_f_index = determine_f_index(factor) if new_f_index is None: rest *= factor else: assert not (new_f_index and f_index) f_index = new_f_index moment_tuple = [0] * len(velocity_terms) for c_idx in c_indices: moment_tuple[c_idx] += 1 moment_tuple = tuple(moment_tuple) if use_one_neighborhood_aliasing: moment_tuple = non_aliased_moment(moment_tuple) result = CeMoment(f_index.moment_name, moment_tuple, f_index.superscript) if derivative_term is not None: result = derivative_term.change_arg_recursive(result) result *= rest return result functions = sum(pdf_symbols, ()) eqn = expand_diff_linear(eqn, functions).expand() if eqn.func == sp.Mul: return handle_product(eqn) else: assert eqn.func == sp.Add return sum(handle_product(t) for t in eqn.args)
def __init__(self, method, constants=None): cqc = method.conserved_quantity_computation self._method = method self._moment_cache = LbMethodEqMoments(method) self.rho = cqc.defined_symbols(order=0)[1] self.u = cqc.defined_symbols(order=1)[1] self.t = sp.Symbol("t") self.epsilon = sp.Symbol("epsilon") taylored_lb_eq = get_taylor_expanded_lb_equation(dim=self._method.dim) self.equations_by_order = chapman_enskog_ansatz(taylored_lb_eq) # Taking moments c = sp.Matrix([ expanded_symbol("c", subscript=i) for i in range(self._method.dim) ]) moments_until_order1 = [1] + list(c) moments_order2 = [c_i * c_j for c_i, c_j in symmetric_product(c, c)] symbolic_relaxation_rates = [ rr for rr in method.relaxation_rates if isinstance(rr, sp.Symbol) ] if constants is None: constants = set(symbolic_relaxation_rates) else: constants.update(symbolic_relaxation_rates) self.constants = constants o_eps_moments1 = [ expand_diff_linear(self._take_and_insert_moments( self.equations_by_order[1] * moment), constants=constants) for moment in moments_until_order1 ] o_eps_moments2 = [ expand_diff_linear(self._take_and_insert_moments( self.equations_by_order[1] * moment), constants=constants) for moment in moments_order2 ] o_eps_sq_moments1 = [ expand_diff_linear(self._take_and_insert_moments( self.equations_by_order[2] * moment), constants=constants) for moment in moments_until_order1 ] self._equationsWithHigherOrderMoments = [ self._ce_recombine(ord1 * self.epsilon + ord2 * self.epsilon**2) for ord1, ord2 in zip(o_eps_moments1, o_eps_sq_moments1) ] self.higher_order_moments = compute_higher_order_moment_subs_dict( tuple(o_eps_moments1 + o_eps_moments2)) # Match to Navier stokes compressible, pressure, sigma = match_to_navier_stokes( self._equationsWithHigherOrderMoments) self.compressible = compressible self.pressure_equation = pressure self._sigmaWithHigherOrderMoments = sigma self._sigma = sigma.subs(self.higher_order_moments).expand().applyfunc( self._ce_recombine) self._sigmaWithoutErrorTerms = remove_error_terms(self._sigma)
def __init__(self, method, order=4): self.method = method dim = method.dim moment_computation = LbMethodEqMoments(method) eps, collision_operator, f, dt = sp.symbols("epsilon B f Delta_t") self.dt = dt expanded_pdf_symbols = [ expanded_symbol("f", superscript=i) for i in range(0, order + 1) ] feq = expanded_pdf_symbols[0] c = sp.Matrix([expanded_symbol("c", subscript=i) for i in range(dim)]) dx = sp.Matrix([DiffOperator(target=l) for l in range(dim)]) differential_operator = sum((dt * eps * c.dot(dx))**n / sp.factorial(n) for n in range(1, order + 1)) taylor_expansion = DiffOperator.apply(differential_operator.expand(), f, apply_to_constants=False) eps_dict = chapman_enskog_ansatz( taylor_expansion, spatial_derivative_orders= None, # do not expand the differential operator pdfs=(['f', 0, order + 1], )) # expand only the 'f' terms self.scale_hierarchy = [ -collision_operator * eps_dict[i] for i in range(0, order + 1) ] self.scale_hierarchy_raw = self.scale_hierarchy.copy() expanded_pdfs = [feq, self.scale_hierarchy[1]] subs_dict = {expanded_pdf_symbols[1]: self.scale_hierarchy[1]} for i in range(2, len(self.scale_hierarchy)): eq = self.scale_hierarchy[i].subs(subs_dict) eq = expand_diff_linear(eq, functions=expanded_pdf_symbols) eq = normalize_diff_order(eq, functions=expanded_pdf_symbols) subs_dict[expanded_pdf_symbols[i]] = eq expanded_pdfs.append(eq) self.scale_hierarchy = expanded_pdfs constants = sp.Matrix(method.relaxation_rates).atoms(sp.Symbol) recombined = -sum(self.scale_hierarchy[n] for n in range(1, order + 1)) # Eq 18a recombined = sp.cancel( recombined / (dt * collision_operator)).expand() # cancel common factors def handle_postcollision_values(expr): expr = expr.expand() assert isinstance(expr, sp.Add) result = 0 for summand in expr.args: moment = summand.atoms(CeMoment) moment = moment.pop() collision_operator_exponent = normalize_product(summand).count( collision_operator) if collision_operator_exponent == 0: result += summand else: substitutions = { collision_operator: 1, moment: -moment_computation.get_post_collision_moment( moment, -collision_operator_exponent), } result += summand.subs(substitutions) return result # Continuity equation (mass transport) cont_eq = take_moments(recombined, max_expansion=(order + 1) * 2) cont_eq = handle_postcollision_values(cont_eq) cont_eq = expand_diff_linear(cont_eq, constants=constants).expand().collect(dt) self.continuity_equation_with_moments = cont_eq cont_eq = insert_moments(cont_eq, moment_computation, use_solvability_conditions=False) cont_eq = expand_diff_linear(cont_eq, constants=constants).expand().collect(dt) self.continuity_equation = cont_eq # Momentum equation (momentum transport) self.momentum_equations_with_moments = [] self.momentum_equations = [] for h in range(dim): mom_eq = take_moments(recombined * c[h], max_expansion=(order + 1) * 2) mom_eq = handle_postcollision_values(mom_eq) mom_eq = expand_diff_linear( mom_eq, constants=constants).expand().collect(dt) self.momentum_equations_with_moments.append(mom_eq) mom_eq = insert_moments(mom_eq, moment_computation, use_solvability_conditions=False) mom_eq = expand_diff_linear( mom_eq, constants=constants).expand().collect(dt) self.momentum_equations.append(mom_eq)