def create_lb_method_from_existing(method, modification_function): """Creates a new method based on an existing method by modifying its collision table. Args: method: old method modification_function: function receiving (moment, equilibrium_value, relaxation_rate) as arguments, i.e. one row of the relaxation table, returning a modified version """ compressible = method.conserved_quantity_computation.compressible if isinstance(method, CenteredCumulantBasedLbMethod): rr_dict = OrderedDict() relaxation_table = (modification_function(m, eq, rr) for m, eq, rr in zip(method.cumulants, method.cumulant_equilibrium_values, method.relaxation_rates)) for cumulant, eq_value, rr in relaxation_table: cumulant = sp.sympify(cumulant) rr_dict[cumulant] = RelaxationInfo(eq_value, rr) return CenteredCumulantBasedLbMethod(method.stencil, rr_dict, method.conserved_quantity_computation, method.force_model, galilean_correction=method.galilean_correction, central_moment_transform_class=method.central_moment_transform_class, cumulant_transform_class=method.cumulant_transform_class) else: relaxation_table = (modification_function(m, eq, rr) for m, eq, rr in zip(method.moments, method.moment_equilibrium_values, method.relaxation_rates)) return create_generic_mrt(method.stencil, relaxation_table, compressible, method.force_model)
def get_equilibrium(self, conserved_quantity_equations=None, subexpressions=False, pre_simplification=False, keep_cqc_subexpressions=True, include_force_terms=False): """Returns equation collection, to compute equilibrium values. The equations have the post collision symbols as left hand sides and are functions of the conserved quantities Args: conserved_quantity_equations: equations to compute conserved quantities. subexpressions: if set to false all subexpressions of the equilibrium assignments are plugged into the main assignments pre_simplification: with or without pre_simplifications for the calculation of the collision keep_cqc_subexpressions: if equilibrium is returned without subexpressions keep_cqc_subexpressions determines if also subexpressions to calculate conserved quantities should be plugged into the main assignments """ r_info_dict = {c: RelaxationInfo(info.equilibrium_value, 1) for c, info in self._moment_to_relaxation_info_dict.items()} ac = self._central_moment_collision_rule(r_info_dict, conserved_quantity_equations, pre_simplification, include_force_terms=include_force_terms, symbolic_relaxation_rates=False) if not subexpressions: if keep_cqc_subexpressions: bs = self._bound_symbols_cqc(conserved_quantity_equations) return ac.new_without_subexpressions(subexpressions_to_keep=bs) else: return ac.new_without_subexpressions() else: return ac
def set_first_moment_relaxation_rate(self, relaxation_rate): for e in MOMENT_SYMBOLS[:self.dim]: assert e in self._momentToRelaxationInfoDict, "First moments are not relaxed separately by this method" for e in MOMENT_SYMBOLS[:self.dim]: prev_entry = self._momentToRelaxationInfoDict[e] new_entry = RelaxationInfo(prev_entry[0], relaxation_rate) self._momentToRelaxationInfoDict[e] = new_entry
def set_first_moment_relaxation_rate(self, relaxation_rate): if self.force_model_rr_override: warn( "Overwriting first-order relaxation rates governed by CenteredCumulantForceModel " "might break your forcing scheme.") for e in MOMENT_SYMBOLS[:self.dim]: assert e in self._cumulant_to_relaxation_info_dict, \ "First cumulants are not relaxed separately by this method" for e in MOMENT_SYMBOLS[:self.dim]: prev_entry = self._cumulant_to_relaxation_info_dict[e] new_entry = RelaxationInfo(prev_entry[0], relaxation_rate) self._cumulant_to_relaxation_info_dict[e] = new_entry
def create_from_equilibrium( stencil, equilibrium, moment_to_relaxation_rate_dict, compressible=False, force_model=None, moment_transform_class=PdfsToMomentsByChimeraTransform): r""" Creates a moment-based LB method using a given equilibrium distribution function Args: stencil: instance of :class:`lbmpy.stencils.LBStencil` equilibrium: list of equilibrium terms, dependent on rho and u, one for each stencil direction moment_to_relaxation_rate_dict: relaxation rate for each moment, or a symbol/float if all should relaxed with the same rate compressible: see create_with_discrete_maxwellian_eq_moments force_model: see create_with_discrete_maxwellian_eq_moments moment_transform_class: class to define the transformation to the moment space """ if any( isinstance(moment_to_relaxation_rate_dict, t) for t in (sp.Symbol, float, int)): moment_to_relaxation_rate_dict = { m: moment_to_relaxation_rate_dict for m in get_default_moment_set_for_stencil(stencil) } mom_to_rr_dict = OrderedDict(moment_to_relaxation_rate_dict) assert len( mom_to_rr_dict ) == stencil.Q, "The number of moments has to be equal to the number of stencil entries" density_velocity_computation = DensityVelocityComputation( stencil, compressible, force_model) rr_dict = OrderedDict([ (mom, RelaxationInfo( discrete_moment(equilibrium, mom, stencil).expand(), rr)) for mom, rr in zip(mom_to_rr_dict.keys(), mom_to_rr_dict.values()) ]) return MomentBasedLbMethod(stencil, rr_dict, density_velocity_computation, force_model, moment_transform_class)
def create_generic_mrt(stencil, moment_eq_value_relaxation_rate_tuples, compressible=False, force_model=None, moment_transform_class=PdfsToMomentsByChimeraTransform): r""" Creates a generic moment-based LB method. Args: stencil: instance of :class:`lbmpy.stencils.LBStencil` moment_eq_value_relaxation_rate_tuples: sequence of tuples containing (moment, equilibrium value, relax. rate) compressible: compressibility, determines calculation of velocity for force models force_model: see create_with_discrete_maxwellian_eq_moments moment_transform_class: class to define the transformation to the moment space """ density_velocity_computation = DensityVelocityComputation( stencil, compressible, force_model) rr_dict = OrderedDict() for moment, eq_value, rr in moment_eq_value_relaxation_rate_tuples: moment = sp.sympify(moment) rr_dict[moment] = RelaxationInfo(eq_value, rr) return MomentBasedLbMethod(stencil, rr_dict, density_velocity_computation, force_model, moment_transform_class)
def set_zeroth_moment_relaxation_rate(self, relaxation_rate): one = sp.Rational(1, 1) prev_entry = self._momentToRelaxationInfoDict[one] new_entry = RelaxationInfo(prev_entry[0], relaxation_rate) self._momentToRelaxationInfoDict[one] = new_entry
def set_zeroth_moment_relaxation_rate(self, relaxation_rate): e = sp.Rational(1, 1) prev_entry = self._moment_to_relaxation_info_dict[e] new_entry = RelaxationInfo(prev_entry[0], relaxation_rate) self._moment_to_relaxation_info_dict[e] = new_entry
def _central_moment_collision_rule(self, moment_to_relaxation_info_dict, conserved_quantity_equations=None, pre_simplification=False, include_force_terms=False, symbolic_relaxation_rates=False): stencil = self.stencil f = self.pre_collision_pdf_symbols density = self.zeroth_order_equilibrium_moment_symbol velocity = self.first_order_equilibrium_moment_symbols cqe = conserved_quantity_equations relaxation_info_dict = dict() subexpressions_relaxation_rates = [] if symbolic_relaxation_rates: subexpressions_relaxation_rates, sd = self._generate_symbolic_relaxation_matrix() for i, moment in enumerate(moment_to_relaxation_info_dict): relaxation_info_dict[moment] = RelaxationInfo(moment_to_relaxation_info_dict[moment][0], sd[i, i]) else: relaxation_info_dict = moment_to_relaxation_info_dict if cqe is None: cqe = self._conserved_quantity_computation.equilibrium_input_equations_from_pdfs(f, False) forcing_subexpressions = AssignmentCollection([]) moment_space_forcing = False if self._force_model is not None: if include_force_terms: moment_space_forcing = self.force_model.has_central_moment_space_forcing forcing_subexpressions = AssignmentCollection(self._force_model.subs_dict_force) else: include_force_terms = False # 1) Get Forward Transformation from PDFs to central moments pdfs_to_c_transform = self.central_moment_transform_class( stencil, self.moments, density, velocity, conserved_quantity_equations=cqe) pdfs_to_c_eqs = pdfs_to_c_transform.forward_transform(f, simplification=pre_simplification) # 2) Collision k_pre = pdfs_to_c_transform.pre_collision_symbols k_post = pdfs_to_c_transform.post_collision_symbols relaxation_infos = [relaxation_info_dict[m] for m in self.moments] relaxation_rates = [info.relaxation_rate for info in relaxation_infos] equilibrium_value = [info.equilibrium_value for info in relaxation_infos] if moment_space_forcing: force_model_terms = self._force_model.central_moment_space_forcing(self) else: force_model_terms = sp.Matrix([0] * stencil.Q) collision_eqs = relax_central_moments(k_pre, k_post, tuple(relaxation_rates), tuple(equilibrium_value), force_terms=force_model_terms) # 3) Get backward transformation from central moments to PDFs post_collision_values = self.post_collision_pdf_symbols c_post_to_pdfs_eqs = pdfs_to_c_transform.backward_transform(post_collision_values, simplification=pre_simplification) # 4) Now, put it all together. all_acs = [] if pdfs_to_c_transform.absorbs_conserved_quantity_equations else [cqe] subexpressions_relaxation_rates = AssignmentCollection(subexpressions_relaxation_rates) all_acs += [subexpressions_relaxation_rates, forcing_subexpressions, pdfs_to_c_eqs, collision_eqs] subexpressions = [ac.all_assignments for ac in all_acs] subexpressions += c_post_to_pdfs_eqs.subexpressions main_assignments = c_post_to_pdfs_eqs.main_assignments # 5) Maybe add forcing terms. if include_force_terms and not moment_space_forcing: force_model_terms = self._force_model(self) force_term_symbols = sp.symbols(f"forceTerm_:{len(force_model_terms)}") force_subexpressions = [Assignment(sym, force_model_term) for sym, force_model_term in zip(force_term_symbols, force_model_terms)] subexpressions += force_subexpressions main_assignments = [Assignment(eq.lhs, eq.rhs + force_term_symbol) for eq, force_term_symbol in zip(main_assignments, force_term_symbols)] return LbmCollisionRule(self, main_assignments, subexpressions)
def create_centered_cumulant_model( stencil, cumulant_to_rr_dict, force_model=None, equilibrium_order=None, c_s_sq=sp.Rational(1, 3), galilean_correction=False, central_moment_transform_class=PdfsToCentralMomentsByShiftMatrix, cumulant_transform_class=CentralMomentsToCumulantsByGeneratingFunc): r"""Creates a cumulant lattice Boltzmann model. Args: stencil: instance of :class:`lbmpy.stencils.LBStencil` cumulant_to_rr_dict: dict that has as many entries as the stencil. Each cumulant, which can be represented by an exponent tuple or in polynomial form is mapped to a relaxation rate. See :func:`lbmpy.methods.default_moment_sets.cascaded_moment_sets_literature` force_model: force model used for the collision. For cumulant LB method a good choice is `lbmpy.methods.centeredcumulant.CenteredCumulantForceModel` equilibrium_order: approximation order of macroscopic velocity :math:`\mathbf{u}` in the equilibrium c_s_sq: Speed of sound squared galilean_correction: special correction for D3Q27 cumulant collisions. See Appendix H in :cite:`geier2015`. Implemented in :mod:`lbmpy.methods.centeredcumulant.galilean_correction` central_moment_transform_class: Class which defines the transformation to the central moment space (see :mod:`lbmpy.moment_transforms`) cumulant_transform_class: Class which defines the transformation from the central moment space to the cumulant space (see :mod:`lbmpy.methods.centeredcumulant.cumulant_transform`) Returns: :class:`lbmpy.methods.centeredcumulant.CenteredCumulantBasedLbMethod` instance """ one = sp.Integer(1) assert len(cumulant_to_rr_dict) == stencil.Q, \ "The number of moments has to be equal to the number of stencil entries" # CQC cqc = DensityVelocityComputation(stencil, True, force_model=force_model) density_symbol = cqc.zeroth_order_moment_symbol velocity_symbols = cqc.first_order_moment_symbols # Equilibrium Values higher_order_polynomials = list( filter(lambda x: get_order(x) > 1, cumulant_to_rr_dict.keys())) # Lower Order Equilibria cumulants_to_relaxation_info_dict = { one: RelaxationInfo(density_symbol, cumulant_to_rr_dict[one]) } for d in MOMENT_SYMBOLS[:stencil.D]: cumulants_to_relaxation_info_dict[d] = RelaxationInfo( 0, cumulant_to_rr_dict[d]) # Polynomial Cumulant Equilibria polynomial_equilibria = get_equilibrium_values_of_maxwell_boltzmann_function( higher_order_polynomials, stencil.D, rho=density_symbol, u=velocity_symbols, c_s_sq=c_s_sq, order=equilibrium_order, space="cumulant") polynomial_equilibria = [density_symbol * v for v in polynomial_equilibria] for i, c in enumerate(higher_order_polynomials): cumulants_to_relaxation_info_dict[c] = RelaxationInfo( polynomial_equilibria[i], cumulant_to_rr_dict[c]) return CenteredCumulantBasedLbMethod( stencil, cumulants_to_relaxation_info_dict, cqc, force_model, galilean_correction=galilean_correction, central_moment_transform_class=central_moment_transform_class, cumulant_transform_class=cumulant_transform_class)
def create_with_discrete_maxwellian_eq_moments( stencil, moment_to_relaxation_rate_dict, compressible=False, force_model=None, equilibrium_order=2, c_s_sq=sp.Rational(1, 3), central_moment_space=False, moment_transform_class=None, central_moment_transform_class=PdfsToCentralMomentsByShiftMatrix): r"""Creates a moment-based LBM by taking a list of moments with corresponding relaxation rate. These moments are relaxed against the moments of the discrete Maxwellian distribution. Args: stencil: instance of :class:`lbmpy.stencils.LBStenil` moment_to_relaxation_rate_dict: dict that has as many entries as the stencil. Each moment, which can be represented by an exponent tuple or in polynomial form (see `lbmpy.moments`), is mapped to a relaxation rate. compressible: incompressible LBM methods split the density into :math:`\rho = \rho_0 + \Delta \rho` where :math:`\rho_0` is chosen as one, and the first moment of the pdfs is :math:`\Delta \rho` . This approximates the incompressible Navier-Stokes equations better than the standard compressible model. force_model: force model instance, or None if no external forces equilibrium_order: approximation order of macroscopic velocity :math:`\mathbf{u}` in the equilibrium c_s_sq: Speed of sound squared central_moment_space: If set to True, an instance of :class:`lbmpy.methods.momentbased.CentralMomentBasedLbMethod` is returned, and the the collision will be performed in the central moment space. moment_transform_class: Class implementing the transform from populations to moment space. central_moment_transform_class: Class implementing the transform from populations to central moment space. Returns: Instance of either :class:`lbmpy.methods.momentbased.MomentBasedLbMethod` or :class:`lbmpy.methods.momentbased.CentralMomentBasedLbMethod` """ mom_to_rr_dict = OrderedDict(moment_to_relaxation_rate_dict) assert len(mom_to_rr_dict) == stencil.Q, \ "The number of moments has to be the same as the number of stencil entries" density_velocity_computation = DensityVelocityComputation( stencil, compressible, force_model) moments = tuple(mom_to_rr_dict.keys()) eq_values = get_moments_of_discrete_maxwellian_equilibrium( stencil, moments, c_s_sq=c_s_sq, compressible=compressible, order=equilibrium_order) if central_moment_space: N = set_up_shift_matrix(moments, stencil) eq_values = sp.simplify(N * sp.Matrix(eq_values)) rr_dict = OrderedDict([ (mom, RelaxationInfo(eq_mom, rr)) for mom, rr, eq_mom in zip( mom_to_rr_dict.keys(), mom_to_rr_dict.values(), eq_values) ]) if central_moment_space: return CentralMomentBasedLbMethod(stencil, rr_dict, density_velocity_computation, force_model, central_moment_transform_class) else: return MomentBasedLbMethod(stencil, rr_dict, density_velocity_computation, force_model, moment_transform_class)
def _centered_cumulant_collision_rule(self, cumulant_to_relaxation_info_dict, conserved_quantity_equations=None, pre_simplification=False, include_force_terms=False, include_galilean_correction=True, symbolic_relaxation_rates=False): # Filter out JobLib warnings. They are not usefull for use: # https://github.com/joblib/joblib/issues/683 filterwarnings("ignore", message="Persisting input arguments took") stencil = self.stencil f = self.pre_collision_pdf_symbols density = self.zeroth_order_equilibrium_moment_symbol velocity = self.first_order_equilibrium_moment_symbols cqe = conserved_quantity_equations relaxation_info_dict = dict() subexpressions_relaxation_rates = [] if symbolic_relaxation_rates: subexpressions_relaxation_rates, sd = self._generate_symbolic_relaxation_matrix( ) for i, cumulant in enumerate(cumulant_to_relaxation_info_dict): relaxation_info_dict[cumulant] = RelaxationInfo( cumulant_to_relaxation_info_dict[cumulant][0], sd[i, i]) else: relaxation_info_dict = cumulant_to_relaxation_info_dict if cqe is None: cqe = self._conserved_quantity_computation.equilibrium_input_equations_from_pdfs( f, False) forcing_subexpressions = AssignmentCollection([]) if self._force_model is not None: forcing_subexpressions = AssignmentCollection( self._force_model.subs_dict_force) # 1) Extract Monomial Cumulants for the higher-order polynomials polynomial_cumulants = relaxation_info_dict.keys() polynomial_cumulants = sorted(list(polynomial_cumulants), key=moment_sort_key) higher_order_polynomials = [ p for p in polynomial_cumulants if get_order(p) > 1 ] monomial_cumulants = sorted(list( extract_monomials(higher_order_polynomials, dim=self.dim)), key=exponent_tuple_sort_key) # 2) Get Forward and Backward Transformations between central moment and cumulant space, # and find required central moments k_to_c_transform = self._cumulant_transform_class( stencil, monomial_cumulants, density, velocity) k_to_c_eqs = cached_forward_transform( k_to_c_transform, simplification=pre_simplification) c_post_to_k_post_eqs = cached_backward_transform( k_to_c_transform, simplification=pre_simplification, omit_conserved_moments=True) central_moments = k_to_c_transform.required_central_moments assert len( central_moments ) == stencil.Q, 'Number of required central moments must match stencil size.' # 3) Get Forward Transformation from PDFs to central moments pdfs_to_k_transform = self._central_moment_transform_class( stencil, None, density, velocity, moment_exponents=central_moments, conserved_quantity_equations=cqe) pdfs_to_k_eqs = cached_forward_transform( pdfs_to_k_transform, f, simplification=pre_simplification, return_monomials=True) # 4) Add relaxation rules for lower order moments lower_order_moments = moments_up_to_order(1, dim=self.dim) lower_order_moment_symbols = [ statistical_quantity_symbol(PRE_COLLISION_MONOMIAL_CENTRAL_MOMENT, exp) for exp in lower_order_moments ] lower_order_relaxation_infos = [ relaxation_info_dict[exponent_to_polynomial_representation(e)] for e in lower_order_moments ] lower_order_relaxation_rates = [ info.relaxation_rate for info in lower_order_relaxation_infos ] lower_order_equilibrium = [ info.equilibrium_value for info in lower_order_relaxation_infos ] lower_order_moment_collision_eqs = relax_lower_order_central_moments( lower_order_moments, tuple(lower_order_moment_symbols), tuple(lower_order_relaxation_rates), tuple(lower_order_equilibrium)) # 5) Add relaxation rules for higher-order, polynomial cumulants poly_relaxation_infos = [ relaxation_info_dict[c] for c in higher_order_polynomials ] poly_relaxation_rates = [ info.relaxation_rate for info in poly_relaxation_infos ] poly_equilibrium = [ info.equilibrium_value for info in poly_relaxation_infos ] if self._galilean_correction and include_galilean_correction: galilean_correction_terms = get_galilean_correction_terms( relaxation_info_dict, density, velocity) else: galilean_correction_terms = None cumulant_collision_eqs = relax_polynomial_cumulants( tuple(monomial_cumulants), tuple(higher_order_polynomials), tuple(poly_relaxation_rates), tuple(poly_equilibrium), pre_simplification, galilean_correction_terms=galilean_correction_terms) # 6) Get backward transformation from central moments to PDFs d = self.post_collision_pdf_symbols k_post_to_pdfs_eqs = cached_backward_transform( pdfs_to_k_transform, d, simplification=pre_simplification, start_from_monomials=True) # 7) That's all. Now, put it all together. all_acs = [] if pdfs_to_k_transform.absorbs_conserved_quantity_equations else [ cqe ] subexpressions_relaxation_rates = AssignmentCollection( subexpressions_relaxation_rates) all_acs += [ subexpressions_relaxation_rates, forcing_subexpressions, pdfs_to_k_eqs, k_to_c_eqs, lower_order_moment_collision_eqs, cumulant_collision_eqs, c_post_to_k_post_eqs ] subexpressions = [ac.all_assignments for ac in all_acs] subexpressions += k_post_to_pdfs_eqs.subexpressions main_assignments = k_post_to_pdfs_eqs.main_assignments # 8) Maybe add forcing terms if CenteredCumulantForceModel was not used if self._force_model is not None and \ not isinstance(self._force_model, CenteredCumulantForceModel) and include_force_terms: force_model_terms = self._force_model(self) force_term_symbols = sp.symbols( f"forceTerm_:{len(force_model_terms)}") force_subexpressions = [ Assignment(sym, force_model_term) for sym, force_model_term in zip( force_term_symbols, force_model_terms) ] subexpressions += force_subexpressions main_assignments = [ Assignment(eq.lhs, eq.rhs + force_term_symbol) for eq, force_term_symbol in zip(main_assignments, force_term_symbols) ] # Aaaaaand we're done. return LbmCollisionRule(self, main_assignments, subexpressions)