From b913f6bd3241fc5f16ad2e87cbce3e681220d26a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner <inform@tiker.net> Date: Sun, 26 Oct 2014 00:25:24 -0500 Subject: [PATCH] Add mappers and primitives for geometric calculus --- pymbolic/geometric_algebra/mapper.py | 259 +++++++++++++++++++++++ pymbolic/geometric_algebra/primitives.py | 102 +++++++++ 2 files changed, 361 insertions(+) create mode 100644 pymbolic/geometric_algebra/mapper.py create mode 100644 pymbolic/geometric_algebra/primitives.py diff --git a/pymbolic/geometric_algebra/mapper.py b/pymbolic/geometric_algebra/mapper.py new file mode 100644 index 0000000..a3824bc --- /dev/null +++ b/pymbolic/geometric_algebra/mapper.py @@ -0,0 +1,259 @@ +from __future__ import division + +__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +# This is experimental, undocumented, and could go away any second. +# Consider yourself warned. + +from pymbolic.geometric_algebra import MultiVector +import pymbolic.geometric_algebra.primitives as prim +from pymbolic.mapper import ( + CombineMapper as CombineMapperBase, + Collector as CollectorBase, + IdentityMapper as IdentityMapperBase, + WalkMapper as WalkMapperBase + ) +from pymbolic.mapper.stringifier import ( + StringifyMapper as StringifyMapperBase, + PREC_NONE + ) +from pymbolic.mapper.evaluator import ( + EvaluationMapper as EvaluationMapperBase) + + +class IdentityMapper(IdentityMapperBase): + def map_multivector_variable(self, expr): + return expr + + map_nabla = map_multivector_variable + map_nabla_component = map_multivector_variable + + def map_derivative_source(self, expr): + return type(expr)(self.rec(expr.operand), expr.nabla_id) + + +class CombineMapper(CombineMapperBase): + def map_derivative_source(self, expr): + return self.rec(expr.operand) + + +class Collector(CollectorBase): + def map_nabla(self, expr): + return set() + + map_nabla_component = map_nabla + + +class WalkMapper(WalkMapperBase): + def map_nabla(self, expr, *args): + self.visit(expr, *args) + + def map_nabla_component(self, expr, *args): + self.visit(expr, *args) + + def map_derivative_source(self, expr, *args): + if not self.visit(expr, *args): + return + + self.rec(expr.operand) + + +class EvaluationMapper(EvaluationMapperBase): + def map_nabla_component(self, expr): + return expr + + map_nabla = map_nabla_component + + def map_derivative_source(self, expr): + return type(expr)(self.rec(expr.operand), expr.nabla_id) + + +class StringifyMapper(StringifyMapperBase): + def map_nabla(self, expr, enclosing_prec): + return r"\/[%s]" % expr.nabla_id + + def map_nabla_component(self, expr, enclosing_prec): + return r"d/dx%d[%s]" % (expr.ambient_axis, expr.nabla_id) + + def map_derivative_source(self, expr, enclosing_prec): + return r"D[%s](%s)" % (expr.nabla_id, self.rec(expr.operand, PREC_NONE)) + + +# {{{ dimensionalizer + +class Dimensionalizer(EvaluationMapper): + """ + .. attribute:: ambient_dim + + Dimension of ambient space. Must be provided by subclass. + """ + + def map_multivector_variable(self, expr): + from pymbolic.primitives import make_sym_vector + return MultiVector(make_sym_vector(expr.name, self.ambient_dim)) + + def map_nabla(self, expr): + from pytools.obj_array import make_obj_array + return MultiVector(make_obj_array( + [prim.NablaComponent(axis, expr.nabla_id) + for axis in xrange(self.ambient_dim)])) + + def map_derivative_source(self, expr): + rec_op = self.rec(expr.operand) + + if isinstance(rec_op, MultiVector): + from pymbolic.geometric_algebra.primitives import DerivativeSource + return rec_op.map( + lambda coeff: DerivativeSource(coeff, expr.nabla_id)) + else: + return super(Dimensionalizer, self).map_derivative_source(expr) + +# }}} + + +# {{{ derivative binder + +class DerivativeSourceAndNablaComponentCollector(Collector): + def map_nabla(self, expr): + raise RuntimeError("DerivativeOccurrenceMapper must be invoked after " + "Dimensionalizer--Nabla found, not allowed") + + def map_nabla_component(self, expr): + return set([expr]) + + def map_derivative_source(self, expr): + return set([expr]) + + +class NablaComponentToUnitVector(EvaluationMapper): + def __init__(self, nabla_id, ambient_axis): + self.nabla_id = nabla_id + self.ambient_axis = ambient_axis + + def map_nabla_component(self, expr): + if expr.nabla_id == self.nabla_id: + if expr.ambient_axis == self.ambient_axis: + return 1 + else: + return 0 + else: + return EvaluationMapper.map_nabla_component(self, expr) + + +class DerivativeSourceFinder(EvaluationMapper): + """Recurses down until it finds the :class:`pytential.sym.DerivativeSource` + with the right *nabla_id*, then calls :method:`DerivativeBinder.take_derivative` + on the source's argument. + """ + + def __init__(self, nabla_id, binder, ambient_axis): + self.nabla_id = nabla_id + self.binder = binder + self.ambient_axis = ambient_axis + + def map_derivative_source(self, expr): + if expr.nabla_id == self.nabla_id: + return self.binder.take_derivative(self.ambient_axis, expr.operand) + else: + return EvaluationMapper.map_derivative_source(self, expr) + + +class DerivativeBinder(IdentityMapper): + def __init__(self): + self.derivative_collector = DerivativeSourceAndNablaComponentCollector() + + def map_product(self, expr): + # {{{ gather NablaComponents and DerivativeSources + + rec_children = [] + d_source_nabla_ids_per_child = [] + + # id to set((child index, axis), ...) + nabla_finder = {} + + for child_idx, child in enumerate(expr.children): + rec_expr = self.rec(child) + rec_children.append(rec_expr) + + nabla_component_ids = set() + derivative_source_ids = set() + + nablas = [] + for d_or_n in self.derivative_collector(rec_expr): + if isinstance(d_or_n, prim.NablaComponent): + nabla_component_ids.add(d_or_n.nabla_id) + nablas.append(d_or_n) + elif isinstance(d_or_n, prim.DerivativeSource): + derivative_source_ids.add(d_or_n.nabla_id) + else: + raise RuntimeError("unexpected result from " + "DerivativeSourceAndNablaComponentCollector") + + d_source_nabla_ids_per_child.append( + derivative_source_ids - nabla_component_ids) + + for ncomp in nablas: + nabla_finder.setdefault( + ncomp.nabla_id, set()).add((child_idx, ncomp.ambient_axis)) + + # }}} + + # a list of lists, the outer level presenting a sum, the inner a product + result = [rec_children] + + for child_idx, (d_source_nabla_ids, child) in enumerate( + zip(d_source_nabla_ids_per_child, rec_children)): + if not d_source_nabla_ids: + continue + + if len(d_source_nabla_ids) > 1: + raise NotImplementedError("more than one DerivativeSource per " + "child in a product") + + nabla_id, = d_source_nabla_ids + nablas = nabla_finder[nabla_id] + n_axes = max(axis for _, axis in nablas) + 1 + + new_result = [] + for prod_term_list in result: + for axis in xrange(n_axes): + new_ptl = prod_term_list[:] + dsfinder = DerivativeSourceFinder(nabla_id, self, axis) + + new_ptl[child_idx] = dsfinder(new_ptl[child_idx]) + for nabla_child_index, _ in nablas: + new_ptl[nabla_child_index] = \ + NablaComponentToUnitVector(nabla_id, axis)( + new_ptl[nabla_child_index]) + + new_result.append(new_ptl) + + result = new_result + + from pymbolic.primitives import flattened_sum + return flattened_sum( + type(expr)(tuple(prod_term_list)) for prod_term_list in result) + +# }}} + +# vim: foldmethod=marker diff --git a/pymbolic/geometric_algebra/primitives.py b/pymbolic/geometric_algebra/primitives.py new file mode 100644 index 0000000..b6b43d6 --- /dev/null +++ b/pymbolic/geometric_algebra/primitives.py @@ -0,0 +1,102 @@ +from __future__ import division + +__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +# This is experimental, undocumented, and could go away any second. +# Consider yourself warned. + +from pymbolic.primitives import Expression, Variable + + +class MultiVectorVariable(Variable): + mapper_method = "map_multivector_variable" + + +# {{{ geometric calculus + +class _GeometricCalculusExpression(Expression): + def stringifier(self): + from pymbolic.geometric_algebra.mapper import StringifyMapper + return StringifyMapper + + +class NablaComponent(_GeometricCalculusExpression): + def __init__(self, ambient_axis, nabla_id): + self.ambient_axis = ambient_axis + self.nabla_id = nabla_id + + def __getinitargs__(self): + return (self.ambient_axis, self.nabla_id) + + mapper_method = "map_nabla_component" + + +class Nabla(_GeometricCalculusExpression): + def __init__(self, nabla_id): + self.nabla_id = nabla_id + + def __getinitargs__(self): + return (self.nabla_id,) + + def __getitem__(self, index): + if not isinstance(index, int): + raise TypeError("Nabla subscript must be an integer") + + return NablaComponent(index, self.nabla_id) + + mapper_method = "map_nabla" + + +class DerivativeSource(_GeometricCalculusExpression): + def __init__(self, operand, nabla_id=None): + self.operand = operand + self.nabla_id = nabla_id + + def __getinitargs__(self): + return (self.operand, self.nabla_id) + + mapper_method = "map_derivative_source" + + +class Derivative(object): + _next_id = [0] + + def __init__(self): + self.my_id = "id%s" % self._next_id[0] + self._next_id[0] += 1 + + @property + def nabla(self): + return Nabla(self.my_id) + + def __call__(self, operand): + from pymbolic.geometric_algebra import MultiVector + if isinstance(operand, MultiVector): + return operand.map( + lambda coeff: DerivativeSource(coeff, self.my_id)) + else: + return DerivativeSource(operand, self.my_id) + +# }}} + +# vim: foldmethod=marker -- GitLab