def dual_evaluation(self, fn): tQ, x = self.dual_basis expr = fn(x) # Apply targeted sum factorisation and delta elimination to # the expression sum_indices, factors = delta_elimination(*traverse_product(expr)) expr = sum_factorise(sum_indices, factors) # NOTE: any shape indices in the expression are because the # expression is tensor valued. assert expr.shape == self.value_shape scalar_i = self.base_element.get_indices() scalar_vi = self.base_element.get_value_indices() tensor_i = tuple(gem.Index(extent=d) for d in self._shape) tensor_vi = tuple(gem.Index(extent=d) for d in self._shape) if self._transpose: index_ordering = tensor_i + scalar_i + tensor_vi + scalar_vi else: index_ordering = scalar_i + tensor_i + tensor_vi + scalar_vi tQi = tQ[index_ordering] expri = expr[tensor_i + scalar_vi] evaluation = gem.IndexSum(tQi * expri, x.indices + scalar_vi + tensor_i) # This doesn't work perfectly, the resulting code doesn't have # a minimal memory footprint, although the operation count # does appear to be minimal. evaluation = gem.optimise.contraction(evaluation) return evaluation, scalar_i + tensor_vi
def dual_evaluation(self, fn): '''Get a GEM expression for performing the dual basis evaluation at the nodes of the reference element. Currently only works for flat elements: tensor elements are implemented in :class:`TensorFiniteElement`. :param fn: Callable representing the function to dual evaluate. Callable should take in an :class:`AbstractPointSet` and return a GEM expression for evaluation of the function at those points. :returns: A tuple ``(dual_evaluation_gem_expression, basis_indices)`` where the given ``basis_indices`` are those needed to form a return expression for the code which is compiled from ``dual_evaluation_gem_expression`` (alongside any argument multiindices already encoded within ``fn``) ''' Q, x = self.dual_basis expr = fn(x) # Apply targeted sum factorisation and delta elimination to # the expression sum_indices, factors = delta_elimination(*traverse_product(expr)) expr = sum_factorise(sum_indices, factors) # NOTE: any shape indices in the expression are because the # expression is tensor valued. assert expr.shape == Q.shape[len(Q.shape) - len(expr.shape):] shape_indices = gem.indices(len(expr.shape)) basis_indices = gem.indices(len(Q.shape) - len(expr.shape)) Qi = Q[basis_indices + shape_indices] expri = expr[shape_indices] evaluation = gem.IndexSum(Qi * expri, x.indices + shape_indices) # Now we want to factorise over the new contraction with x, # ignoring any shape indices to avoid hitting the sum- # factorisation index limit (this is a bit of a hack). # Really need to do a more targeted job here. evaluation = gem.optimise.contraction(evaluation, shape_indices) return evaluation, basis_indices
def _collect_monomials(expression, self): """Refactorises an expression into a sum-of-products form, using distributivity rules (i.e. a*(b + c) -> a*b + a*c). Expansion proceeds until all "compound" expressions are broken up. :arg expression: a GEM expression to refactorise :arg self: function for recursive calls :returns: :py:class:`MonomialSum` :raises FactorisationError: Failed to break up some "compound" expressions with expansion. """ # Phase 1: Collect and categorise product terms def stop_at(expr): # Break up compounds only return self.classifier(expr) != COMPOUND common_indices, terms = traverse_product(expression, stop_at=stop_at) common_indices = tuple(common_indices) common_atomics = [] common_others = [] compounds = [] for term in terms: label = self.classifier(term) if label == ATOMIC: common_atomics.append(term) elif label == COMPOUND: compounds.append(term) elif label == OTHER: common_others.append(term) else: raise ValueError("Classifier returned illegal value.") common_atomics = tuple(common_atomics) # Phase 2: Attempt to break up compound terms into summands sums = [] for expr in compounds: summands = traverse_sum(expr, stop_at=stop_at) if len(summands) <= 1 and not isinstance(expr, (Conditional, MathFunction)): # Compound term is not an addition, avoid infinite # recursion and fail gracefully raising an exception. raise FactorisationError(expr) # Recurse into each summand, concatenate their results sums.append(MonomialSum.sum(*map(self, summands))) # Phase 3: Expansion # # Each element of ``sums`` is a MonomialSum. Expansion produces a # series (representing a sum) of products of monomials. result = MonomialSum() for s, a, r in MonomialSum.product(*sums, rename_map=self.rename_map): renamer = make_renamer(self.rename_map) renamer(common_indices) # update current_set s_, applier = renamer(s) all_indices = common_indices + s_ atomics = common_atomics + tuple(map(applier, a)) # All free indices that appear in atomic terms atomic_indices = set().union( *[atomic.free_indices for atomic in atomics]) # Sum indices that appear in atomic terms # (will go to the result :py:class:`Monomial`) sum_indices = tuple(index for index in all_indices if index in atomic_indices) # Sum indices that do not appear in atomic terms # (can factorise them over atomic terms immediately) rest_indices = tuple(index for index in all_indices if index not in atomic_indices) # Not really sum factorisation, but rather just an optimised # way of building a product. rest = sum_factorise(rest_indices, common_others + [applier(r)]) result.add(sum_indices, atomics, rest) return result
def _collect_monomials(expression, self): """Refactorises an expression into a sum-of-products form, using distributivity rules (i.e. a*(b + c) -> a*b + a*c). Expansion proceeds until all "compound" expressions are broken up. :arg expression: a GEM expression to refactorise :arg self: function for recursive calls :returns: :py:class:`MonomialSum` :raises FactorisationError: Failed to break up some "compound" expressions with expansion. """ # Phase 1: Collect and categorise product terms def stop_at(expr): # Break up compounds only return self.classifier(expr) != COMPOUND common_indices, terms = traverse_product(expression, stop_at=stop_at) common_indices = tuple(common_indices) common_atomics = [] common_others = [] compounds = [] for term in terms: label = self.classifier(term) if label == ATOMIC: common_atomics.append(term) elif label == COMPOUND: compounds.append(term) elif label == OTHER: common_others.append(term) else: raise ValueError("Classifier returned illegal value.") common_atomics = tuple(common_atomics) # Phase 2: Attempt to break up compound terms into summands sums = [] for expr in compounds: summands = traverse_sum(expr, stop_at=stop_at) if len(summands) <= 1: # Compound term is not an addition, avoid infinite # recursion and fail gracefully raising an exception. raise FactorisationError(expr) # Recurse into each summand, concatenate their results sums.append(MonomialSum.sum(*map(self, summands))) # Phase 3: Expansion # # Each element of ``sums`` is a MonomialSum. Expansion produces a # series (representing a sum) of products of monomials. result = MonomialSum() for s, a, r in MonomialSum.product(*sums, rename_map=self.rename_map): renamer = make_renamer(self.rename_map) renamer(common_indices) # update current_set s_, applier = renamer(s) all_indices = common_indices + s_ atomics = common_atomics + tuple(map(applier, a)) # All free indices that appear in atomic terms atomic_indices = set().union(*[atomic.free_indices for atomic in atomics]) # Sum indices that appear in atomic terms # (will go to the result :py:class:`Monomial`) sum_indices = tuple(index for index in all_indices if index in atomic_indices) # Sum indices that do not appear in atomic terms # (can factorise them over atomic terms immediately) rest_indices = tuple(index for index in all_indices if index not in atomic_indices) # Not really sum factorisation, but rather just an optimised # way of building a product. rest = sum_factorise(rest_indices, common_others + [applier(r)]) result.add(sum_indices, atomics, rest) return result