def backward_transform(self, pdf_symbols, simplification=True, subexpression_base='sub_k_to_f', start_from_monomials=False): r"""Returns an assignment collection containing equations for post-collision populations, expressed in terms of the post-collision polynomial moments by matrix-multiplication. The moment transformation matrix :math:`M` provided by :func:`lbmpy.moments.moment_matrix` is inverted and used to compute the pre-collision moments as :math:`\mathbf{f}^{\ast} = M^{-1} \cdot \mathbf{M}_{\mathrm{post}}`, which is returned element-wise. **Simplifications** If simplification is enabled, the equations for populations :math:`f_i` and :math:`f_{\bar{i}}` of opposite stencil directions :math:`\mathbf{c}_i` and :math:`\mathbf{c}_{\bar{i}} = - \mathbf{c}_i` are split into their symmetric and antisymmetric parts :math:`f_i^{\mathrm{sym}}, f_i^{\mathrm{anti}}`, such that .. math:: f_i = f_i^{\mathrm{sym}} + f_i^{\mathrm{anti}} f_{\bar{i}} = f_i^{\mathrm{sym}} - f_i^{\mathrm{anti}} Args: pdf_symbols: List of symbols that represent the post-collision populations simplification: Simplification specification. See :class:`AbstractMomentTransform` subexpression_base: The base name used for any subexpressions of the transformation. start_from_monomials: Return equations for monomial moments. Use only when specifying ``moment_exponents`` in constructor! """ simplification = self._get_simp_strategy(simplification, 'backward') if start_from_monomials: assert len(self.moment_exponents) == self.q, "Could not derive invertible monomial transform." \ f"Expected {self.q} monomials, but got {len(self.moment_exponents)}." mm_inv = moment_matrix(self.moment_exponents, self.stencil).inv() post_collision_moments = self.post_collision_monomial_symbols else: mm_inv = self.inv_moment_matrix post_collision_moments = self.post_collision_symbols m_to_f_vec = mm_inv * sp.Matrix(post_collision_moments) main_assignments = [ Assignment(f, eq) for f, eq in zip(pdf_symbols, m_to_f_vec) ] symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection(main_assignments, subexpression_symbol_generator=symbol_gen) ac.add_simplification_hint('stencil', self.stencil) ac.add_simplification_hint('post_collision_pdf_symbols', pdf_symbols) if simplification: ac = simplification.apply(ac) return ac
def fluctuation_correction(method, rng_generator, amplitudes=SymbolGen("phi")): """Returns additive correction terms to be added to the the collided pdfs""" conserved_moments = {sp.sympify(1), *MOMENT_SYMBOLS} # A diagonal matrix containing the random fluctuations random_matrix = sp.Matrix([0 if m in conserved_moments else (next(rng_generator) - 0.5) * sp.sqrt(12) for m in method.moments]) amplitude_matrix = sp.diag(*[v for v, _ in zip(iter(amplitudes), method.moments)]) # corrections are applied in real space hence we need to convert to real space here return method.moment_matrix.inv() * amplitude_matrix * random_matrix
def backward_transform(self, pdf_symbols, simplification=True, subexpression_base='sub_k_to_f', start_from_monomials=False): r"""Returns an assignment collection containing equations for post-collision populations, expressed in terms of the post-collision polynomial central moments using the backward fast central moment transform. First, monomial central moments are obtained from the polynomial moments by multiplication with :math:`P^{-1}`. Then, the elementwise equations of the matrix multiplication :math:`K^{-1} \cdot \mathbf{K}` with the monomial central moment matrix (see `PdfsToCentralMomentsByMatrix`) are recursively simplified by extracting certain linear combinations of velocities, to obtain equations similar to the ones given in :cite:`geier2015`. The backward transform is designed for D3Q27, inherently generalizes to D2Q9, and is tested for D3Q19. It also returns correct equations for D3Q15, whose efficiency is however questionable. **De-Aliasing**: See `FastCentralMomentTransform.forward_transform`. Args: pdf_symbols: List of symbols that represent the post-collision populations simplification: Simplification specification. See :class:`AbstractMomentTransform` subexpression_base: The base name used for any subexpressions of the transformation. start_from_monomials: Return equations for monomial moments. Use only when specifying ``moment_exponents`` in constructor! """ simplification = self._get_simp_strategy(simplification, 'backward') post_collision_moments = self.post_collision_symbols post_collision_monomial_moments = self.post_collision_monomial_symbols subexpressions = [] if not start_from_monomials: monomial_eqs = self.poly_to_mono_matrix * sp.Matrix(post_collision_moments) subexpressions += [Assignment(m, v) for m, v in zip(post_collision_monomial_moments, monomial_eqs)] raw_equations = self.inv_monomial_matrix * sp.Matrix(post_collision_monomial_moments) raw_equations = [Assignment(f, eq) for f, eq in zip(pdf_symbols, raw_equations)] symbol_gen = SymbolGen(subexpression_base) ac = self._split_backward_equations(raw_equations, symbol_gen) ac.subexpressions = subexpressions + ac.subexpressions if simplification: ac = simplification.apply(ac) return ac
def forward_transform(self, pdf_symbols, simplification=True, subexpression_base='sub_f_to_M', return_monomials=False): r"""Returns an assignment collection containing equations for pre-collision polynomial moments, expressed in terms of the pre-collision populations by matrix-multiplication. The moment transformation matrix :math:`M` provided by :func:`lbmpy.moments.moment_matrix` is used to compute the pre-collision moments as :math:`\mathbf{M} = M \cdot \mathbf{f}`, which is returned element-wise. Args: pdf_symbols: List of symbols that represent the pre-collision populations simplification: Simplification specification. See :class:`AbstractMomentTransform` subexpression_base: The base name used for any subexpressions of the transformation. return_monomials: Return equations for monomial moments. Use only when specifying ``moment_exponents`` in constructor! """ simplification = self._get_simp_strategy(simplification, 'forward') if return_monomials: assert len(self.moment_exponents) == self.q, "Could not derive invertible monomial transform." \ f"Expected {self.q} monomials, but got {len(self.moment_exponents)}." mm = moment_matrix(self.moment_exponents, self.stencil) pre_collision_moments = self.pre_collision_monomial_symbols else: mm = self.moment_matrix pre_collision_moments = self.pre_collision_symbols f_to_m_vec = mm * sp.Matrix(pdf_symbols) main_assignments = [ Assignment(m, eq) for m, eq in zip(pre_collision_moments, f_to_m_vec) ] symbol_gen = SymbolGen(symbol=subexpression_base) ac = AssignmentCollection(main_assignments, subexpression_symbol_generator=symbol_gen) if simplification: ac = simplification.apply(ac) return ac
def forward_transform(self, cumulant_base=PRE_COLLISION_CUMULANT, central_moment_base=PRE_COLLISION_MONOMIAL_CENTRAL_MOMENT, simplification=True, subexpression_base='sub_k_to_C'): simplification = self._get_simp_strategy(simplification) main_assignments = [] for exp in self.cumulant_exponents: eq = self.cumulant_from_central_moments(exp, central_moment_base) c_symbol = statistical_quantity_symbol(cumulant_base, exp) main_assignments.append(Assignment(c_symbol, eq)) symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection( main_assignments, subexpression_symbol_generator=symbol_gen) if simplification: ac = simplification.apply(ac) return ac
def relax_polynomial_cumulants(monomial_exponents, polynomials, relaxation_rates, equilibrium_values, pre_simplification, galilean_correction_terms=None, pre_collision_base=PRE_COLLISION_CUMULANT, post_collision_base=POST_COLLISION_CUMULANT, subexpression_base='sub_col'): mon_to_poly_matrix = monomial_to_polynomial_transformation_matrix( monomial_exponents, polynomials) mon_vec = sp.Matrix([ statistical_quantity_symbol(pre_collision_base, exp) for exp in monomial_exponents ]) equilibrium_vec = sp.Matrix(equilibrium_values) relaxation_matrix = sp.diag(*relaxation_rates) subexpressions = [] poly_vec = mon_to_poly_matrix * mon_vec relaxed_polys = poly_vec + relaxation_matrix * (equilibrium_vec - poly_vec) if galilean_correction_terms is not None: relaxed_polys = add_galilean_correction(relaxed_polys, polynomials, galilean_correction_terms) subexpressions = galilean_correction_terms.all_assignments relaxed_monos = mon_to_poly_matrix.inv() * relaxed_polys main_assignments = [ Assignment(statistical_quantity_symbol(post_collision_base, exp), v) for exp, v in zip(monomial_exponents, relaxed_monos) ] symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection(main_assignments, subexpressions=subexpressions, subexpression_symbol_generator=symbol_gen) if pre_simplification == 'default_with_cse': ac = sympy_cse(ac) return ac
def backward_transform(self, cumulant_base=POST_COLLISION_CUMULANT, central_moment_base=POST_COLLISION_MONOMIAL_CENTRAL_MOMENT, simplification=True, omit_conserved_moments=False, subexpression_base='sub_C_to_k'): simplification = self._get_simp_strategy(simplification) main_assignments = [] for exp in self.central_moment_exponents: if omit_conserved_moments and get_order(exp) <= 1: continue eq = self.central_moment_from_cumulants(exp, cumulant_base) k_symbol = statistical_quantity_symbol(central_moment_base, exp) main_assignments.append(Assignment(k_symbol, eq)) symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection(main_assignments, subexpression_symbol_generator=symbol_gen) if simplification: ac = simplification.apply(ac) return ac
def backward_transform(self, pdf_symbols, simplification=True, subexpression_base='sub_k_to_f', start_from_monomials=False): r"""Returns an assignment collection containing equations for post-collision populations, expressed in terms of the post-collision polynomial central moments by matrix-multiplication. The moment transformation matrix :math:`K` provided by :func:`lbmpy.moments.moment_matrix` is inverted and used to compute the pre-collision moments as :math:`\mathbf{f}^{\ast} = K^{-1} \cdot \mathbf{K}_{\mathrm{post}}`, which is returned element-wise. Args: pdf_symbols: List of symbols that represent the post-collision populations simplification: Simplification specification. See :class:`AbstractMomentTransform` subexpression_base: The base name used for any subexpressions of the transformation. start_from_monomials: Return equations for monomial moments. Use only when specifying ``moment_exponents`` in constructor! """ simplification = self._get_simp_strategy(simplification) if start_from_monomials: assert len(self.moment_exponents) == self.q, "Could not derive invertible monomial transform." \ f"Expected {self.q} monomials, but got {len(self.moment_exponents)}." mm_inv = moment_matrix(self.moment_exponents, self.stencil).inv() shift_inv = set_up_shift_matrix(self.moment_exponents, self.stencil, self.equilibrium_velocity).inv() km_inv = mm_inv * shift_inv post_collision_moments = self.post_collision_monomial_symbols else: km_inv = self.backward_matrix post_collision_moments = self.post_collision_symbols m_to_f_vec = km_inv * sp.Matrix(post_collision_moments) main_assignments = [Assignment(f, eq) for f, eq in zip(pdf_symbols, m_to_f_vec)] symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection(main_assignments, subexpression_symbol_generator=symbol_gen) if simplification: ac = simplification.apply(ac) return ac
def test_assignment_collection(): x, y, z, t = sp.symbols("x y z t") symbol_gen = SymbolGen("a") ac = AssignmentCollection([Assignment(z, x + y)], [], subexpression_symbol_generator=symbol_gen) lhs = ac.add_subexpression(t) assert lhs == sp.Symbol("a_0") ac.subexpressions.append(Assignment(t, 3)) ac.topological_sort(sort_main_assignments=False, sort_subexpressions=True) assert ac.subexpressions[0].lhs == t assert ac.new_with_inserted_subexpression(sp.Symbol("not_defined")) == ac ac_inserted = ac.new_with_inserted_subexpression(t) ac_inserted2 = ac.new_without_subexpressions({lhs}) assert all(a == b for a, b in zip(ac_inserted.all_assignments, ac_inserted2.all_assignments)) print(ac_inserted) assert ac_inserted.subexpressions[0] == Assignment(lhs, 3) assert 'a_0' in str(ac_inserted) assert '<table' in ac_inserted._repr_html_()
def forward_transform(self, pdf_symbols, simplification=True, subexpression_base='sub_f_to_k', return_monomials=False): r"""Returns equations for polynomial central moments, computed from pre-collision populations through a cascade of three steps. First, the monomial raw moment vector :math:`\mathbf{m}` is computed using the raw-moment chimera transform (see `lbmpy.moment_transforms.PdfsToMomentsByChimeraTransform`). Then, the monomial shift matrix :math:`N` provided by `lbmpy.moments.set_up_shift_matrix` is used to compute the monomial central moment vector as :math:`\mathbf{\kappa} = N \mathbf{m}`. Lastly, the polynomial central moments are computed using the polynomialization matrix as :math:`\mathbf{K} = P \mathbf{\kappa}`. **Conserved Quantity Equations** If given, this transform absorbs the conserved quantity equations and simplifies them using the raw moment equations, if simplification is enabled. **Simplification** If simplification is enabled, the absorbed conserved quantity equations are - if possible - rewritten using the monomial symbols. If the conserved quantities originate somewhere else than in the lower-order moments (like from an external field), they are not affected by this simplification. The relations between conserved quantities and raw moments are used to simplify the equations obtained from the shift matrix. Further, these equations are simplified by recursively inserting lower-order moments into equations for higher-order moments. **De-Aliasing** If more than :math:`q` monomial moments are extracted from the polynomial set, they are de-aliased and reduced to a set of only :math:`q` moments using the same rules as for raw moments. For polynomialization, a special reduced matrix :math:`\tilde{P}` is used, which is computed using `lbmpy.moments.central_moment_reduced_monomial_to_polynomial_matrix`. Args: pdf_symbols: List of symbols that represent the pre-collision populations simplification: Simplification specification. See :class:`AbstractMomentTransform` subexpression_base: The base name used for any subexpressions of the transformation. return_monomials: Return equations for monomial moments. Use only when specifying ``moment_exponents`` in constructor! """ simplification = self._get_simp_strategy(simplification, 'forward') raw_moment_base = self.raw_moment_transform.mono_base_pre central_moment_base = self.mono_base_pre mono_rm_symbols = self.raw_moment_transform.pre_collision_monomial_symbols mono_cm_symbols = self.pre_collision_monomial_symbols rm_ac = self.raw_moment_transform.forward_transform(pdf_symbols, simplification=False, return_monomials=True) cq_symbols_to_moments = self.raw_moment_transform.get_cq_to_moment_symbols_dict(raw_moment_base) rm_to_cm_vec = self.shift_matrix * sp.Matrix(mono_rm_symbols) cq_subs = dict() if simplification: from lbmpy.methods.momentbased.momentbasedsimplifications import ( substitute_moments_in_conserved_quantity_equations) rm_ac = substitute_moments_in_conserved_quantity_equations(rm_ac) # Compute replacements for conserved moments in terms of the CQE rm_asm_dict = rm_ac.main_assignments_dict for cq_sym, moment_sym in cq_symbols_to_moments.items(): cq_eq = rm_asm_dict[cq_sym] solutions = sp.solve(cq_eq - cq_sym, moment_sym) if len(solutions) > 0: cq_subs[moment_sym] = solutions[0] rm_to_cm_vec = fast_subs(rm_to_cm_vec, cq_subs) rm_to_cm_dict = {cm: rm for cm, rm in zip(mono_cm_symbols, rm_to_cm_vec)} if simplification: rm_to_cm_dict = self._simplify_raw_to_central_moments( rm_to_cm_dict, self.moment_exponents, raw_moment_base, central_moment_base) rm_to_cm_dict = self._undo_remaining_cq_subexpressions(rm_to_cm_dict, cq_subs) subexpressions = rm_ac.all_assignments if return_monomials: main_assignments = [Assignment(lhs, rhs) for lhs, rhs in rm_to_cm_dict.items()] else: subexpressions += [Assignment(lhs, rhs) for lhs, rhs in rm_to_cm_dict.items()] poly_eqs = self.mono_to_poly_matrix * sp.Matrix(mono_cm_symbols) main_assignments = [Assignment(m, v) for m, v in zip(self.pre_collision_symbols, poly_eqs)] symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection(main_assignments=main_assignments, subexpressions=subexpressions, subexpression_symbol_generator=symbol_gen) if simplification: ac = simplification.apply(ac) return ac
def forward_transform(self, pdf_symbols, simplification=True, subexpression_base='sub_f_to_k', return_monomials=False): r"""Returns an assignment collection containing equations for pre-collision polynomial central moments, expressed in terms of the pre-collision populations. The monomial central moments are computed from populations through the central-moment chimera transform: .. math:: f_{xyz} &:= f_i \text{ such that } c_i = (x,y,z)^T \\ \kappa_{xy|\gamma} &:= \sum_{z \in \{-1, 0, 1\} } f_{xyz} \cdot (z - u_z)^{\gamma} \\ \kappa_{x|\beta \gamma} &:= \sum_{y \in \{-1, 0, 1\}} \kappa_{xy|\gamma} \cdot (y - u_y)^{\beta} \\ \kappa_{\alpha \beta \gamma} &:= \sum_{x \in \{-1, 0, 1\}} \kappa_{x|\beta \gamma} \cdot (x - u_x)^{\alpha} The polynomial moments are afterward computed from the monomials by matrix-multiplication using the polynomialization matrix :math:`P`. **De-Aliasing** If more than :math:`q` monomial moments are extracted from the polynomial set, they are de-aliased and reduced to a set of only :math:`q` moments using the same rules as for raw moments. For polynomialization, a special reduced matrix :math:`\tilde{P}` is used, which is computed using `lbmpy.moments.central_moment_reduced_monomial_to_polynomial_matrix`. Args: pdf_symbols: List of symbols that represent the pre-collision populations simplification: Simplification specification. See :class:`AbstractMomentTransform` subexpression_base: The base name used for any subexpressions of the transformation. return_monomials: Return equations for monomial moments. Use only when specifying ``moment_exponents`` in constructor! """ simplification = self._get_simp_strategy(simplification, 'forward') monomial_symbol_base = self.mono_base_pre def _partial_kappa_symbol(fixed_directions, remaining_exponents): fixed_str = '_'.join(str(direction) for direction in fixed_directions).replace('-', 'm') exp_str = '_'.join(str(exp) for exp in remaining_exponents).replace('-', 'm') return sp.Symbol(f"partial_{monomial_symbol_base}_{fixed_str}_e_{exp_str}") partial_sums_dict = dict() monomial_moment_eqs = [] def collect_partial_sums(exponents, dimension=0, fixed_directions=tuple()): if dimension == self.dim: # Base Case if fixed_directions in self.stencil: return pdf_symbols[self.stencil.index(fixed_directions)] else: return 0 else: # Recursive Case summation = sp.sympify(0) for d in [-1, 0, 1]: next_partial = collect_partial_sums( exponents, dimension=dimension + 1, fixed_directions=fixed_directions + (d,)) summation += next_partial * (d - self.equilibrium_velocity[dimension])**exponents[dimension] if dimension == 0: lhs_symbol = sq_sym(monomial_symbol_base, exponents) monomial_moment_eqs.append(Assignment(lhs_symbol, summation)) else: lhs_symbol = _partial_kappa_symbol(fixed_directions, exponents[dimension:]) partial_sums_dict[lhs_symbol] = summation return lhs_symbol for e in self.moment_exponents: collect_partial_sums(e) subexpressions = [Assignment(lhs, rhs) for lhs, rhs in partial_sums_dict.items()] if return_monomials: main_assignments = monomial_moment_eqs else: subexpressions += monomial_moment_eqs moment_eqs = self.mono_to_poly_matrix * sp.Matrix(self.pre_collision_monomial_symbols) main_assignments = [Assignment(m, v) for m, v in zip(self.pre_collision_symbols, moment_eqs)] symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection(main_assignments, subexpressions=subexpressions, subexpression_symbol_generator=symbol_gen) if simplification: ac = self._simplify_lower_order_moments(ac, monomial_symbol_base, return_monomials) ac = simplification.apply(ac) return ac
def backward_transform(self, pdf_symbols, simplification=True, subexpression_base='sub_k_to_f', start_from_monomials=False): r"""Returns an assignment collection containing equations for post-collision populations, expressed in terms of the post-collision polynomial moments by matrix-multiplication. The post-collision monomial moments :math:`\mathbf{m}_{\mathrm{post}}` are first obtained from the polynomials. Then, the monomial transformation matrix :math:`M_r` provided by :func:`lbmpy.moments.moment_matrix` is inverted and used to compute the post-collision populations as :math:`\mathbf{f}_{\mathrm{post}} = M_r^{-1} \cdot \mathbf{m}_{\mathrm{post}}`. **De-Aliasing** See `PdfsToMomentsByChimeraTransform.forward_transform`. **Simplifications** If simplification is enabled, the equations for populations :math:`f_i` and :math:`f_{\bar{i}}` of opposite stencil directions :math:`\mathbf{c}_i` and :math:`\mathbf{c}_{\bar{i}} = - \mathbf{c}_i` are split into their symmetric and antisymmetric parts :math:`f_i^{\mathrm{sym}}, f_i^{\mathrm{anti}}`, such that .. math:: f_i = f_i^{\mathrm{sym}} + f_i^{\mathrm{anti}} f_{\bar{i}} = f_i^{\mathrm{sym}} - f_i^{\mathrm{anti}} Args: pdf_symbols: List of symbols that represent the post-collision populations simplification: Simplification specification. See :class:`AbstractMomentTransform` subexpression_base: The base name used for any subexpressions of the transformation. start_from_monomials: Return equations for monomial moments. Use only when specifying ``moment_exponents`` in constructor! """ simplification = self._get_simp_strategy(simplification, 'backward') post_collision_moments = self.post_collision_symbols post_collision_monomial_moments = self.post_collision_monomial_symbols subexpressions = [] if not start_from_monomials: raw_moment_eqs = self.poly_to_mono_matrix * sp.Matrix( post_collision_moments) subexpressions += [ Assignment(rm, v) for rm, v in zip( post_collision_monomial_moments, raw_moment_eqs) ] rm_to_f_vec = self.inv_moment_matrix * sp.Matrix( post_collision_monomial_moments) main_assignments = [ Assignment(f, eq) for f, eq in zip(pdf_symbols, rm_to_f_vec) ] symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection(main_assignments, subexpressions=subexpressions, subexpression_symbol_generator=symbol_gen) ac.add_simplification_hint('stencil', self.stencil) ac.add_simplification_hint('post_collision_pdf_symbols', pdf_symbols) if simplification: ac = simplification.apply(ac) return ac
def forward_transform(self, pdf_symbols, simplification=True, subexpression_base='sub_f_to_m', return_monomials=False): r"""Returns an assignment collection containing equations for pre-collision polynomial moments, expressed in terms of the pre-collision populations, using the raw moment chimera transform. The chimera transform for raw moments is given by :cite:`geier2015` : .. math:: f_{xyz} &:= f_i \text{ such that } c_i = (x,y,z)^T \\ m_{xy|\gamma} &:= \sum_{z \in \{-1, 0, 1\} } f_{xyz} \cdot z^{\gamma} \\ m_{x|\beta \gamma} &:= \sum_{y \in \{-1, 0, 1\}} m_{xy|\gamma} \cdot y^{\beta} \\ m_{\alpha \beta \gamma} &:= \sum_{x \in \{-1, 0, 1\}} m_{x|\beta \gamma} \cdot x^{\alpha} The obtained raw moments are afterward combined to the desired polynomial moments. **Conserved Quantity Equations** If given, this transform absorbs the conserved quantity equations and simplifies them using the monomial raw moment equations, if simplification is enabled. **De-Aliasing** If more than :math:`q` monomial moments are extracted from the polynomial set, the polynomials are de-aliased by eliminating aliases according to the stencil using `lbmpy.moments.non_aliased_polynomial_raw_moments`. **Simplification** If simplification is enabled, the absorbed conserved quantity equations are - if possible - rewritten using the monomial symbols. If the conserved quantities originate somewhere else than in the lower-order moments (like from an external field), they are not affected by this simplification. Args: pdf_symbols: List of symbols that represent the pre-collision populations simplification: Simplification specification. See :class:`AbstractMomentTransform` subexpression_base: The base name used for any subexpressions of the transformation. return_monomials: Return equations for monomial moments. Use only when specifying ``moment_exponents`` in constructor! """ simplification = self._get_simp_strategy(simplification, 'forward') monomial_symbol_base = self.mono_base_pre def _partial_kappa_symbol(fixed_directions, remaining_exponents): fixed_str = '_'.join( str(direction) for direction in fixed_directions).replace('-', 'm') exp_str = '_'.join( str(exp) for exp in remaining_exponents).replace('-', 'm') return sp.Symbol( f"partial_{monomial_symbol_base}_{fixed_str}_e_{exp_str}") partial_sums_dict = dict() monomial_eqs = [] def collect_partial_sums(exponents, dimension=0, fixed_directions=tuple()): if dimension == self.dim: # Base Case if fixed_directions in self.stencil: return pdf_symbols[self.stencil.index(fixed_directions)] else: return 0 else: # Recursive Case summation = sp.sympify(0) for d in [-1, 0, 1]: next_partial = collect_partial_sums( exponents, dimension=dimension + 1, fixed_directions=fixed_directions + (d, )) summation += next_partial * d**exponents[dimension] if dimension == 0: lhs_symbol = sq_sym(monomial_symbol_base, exponents) monomial_eqs.append(Assignment(lhs_symbol, summation)) else: lhs_symbol = _partial_kappa_symbol(fixed_directions, exponents[dimension:]) partial_sums_dict[lhs_symbol] = summation return lhs_symbol for e in self.moment_exponents: collect_partial_sums(e) main_assignments = self.cqe.main_assignments.copy( ) if self.cqe is not None else [] subexpressions = self.cqe.subexpressions.copy( ) if self.cqe is not None else [] subexpressions += [ Assignment(lhs, rhs) for lhs, rhs in partial_sums_dict.items() ] if return_monomials: main_assignments += monomial_eqs else: subexpressions += monomial_eqs moment_eqs = self.mono_to_poly_matrix * sp.Matrix( self.pre_collision_monomial_symbols) main_assignments += [ Assignment(m, v) for m, v in zip(self.pre_collision_symbols, moment_eqs) ] symbol_gen = SymbolGen(subexpression_base) ac = AssignmentCollection(main_assignments, subexpressions=subexpressions, subexpression_symbol_generator=symbol_gen) ac.add_simplification_hint( 'cq_symbols_to_moments', self.get_cq_to_moment_symbols_dict(monomial_symbol_base)) if simplification: ac = simplification.apply(ac) return ac
import pytest import sympy as sp import pystencils as ps from pystencils import Assignment, AssignmentCollection from pystencils.astnodes import Conditional from pystencils.simp.assignment_collection import SymbolGen a, b, c = sp.symbols("a b c") x, y, z, t = sp.symbols("x y z t") symbol_gen = SymbolGen("a") f = ps.fields("f(2) : [2D]") d = ps.fields("d(2) : [2D]") def test_assignment_collection(): ac = AssignmentCollection([Assignment(z, x + y)], [], subexpression_symbol_generator=symbol_gen) lhs = ac.add_subexpression(t) assert lhs == sp.Symbol("a_0") ac.subexpressions.append(Assignment(t, 3)) ac.topological_sort(sort_main_assignments=False, sort_subexpressions=True) assert ac.subexpressions[0].lhs == t assert ac.new_with_inserted_subexpression(sp.Symbol("not_defined")) == ac ac_inserted = ac.new_with_inserted_subexpression(t) ac_inserted2 = ac.new_without_subexpressions({lhs}) assert all(a == b for a, b in zip(ac_inserted.all_assignments, ac_inserted2.all_assignments))