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 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