Traversing Expression Trees

In [2]:
import pymbolic.primitives as p
x = p.Variable("x")
In [3]:
u = (x+3)**5
u
Out[3]:
Power(Sum((Variable('x'), 3)), 5)

Traversal

Many options to walk this expression.

  • One big recursive function with many if isinstance checks
  • "Visitor pattern" -> Define a class, dispatch to a different method for each node type
In [4]:
p.Sum.mapper_method
Out[4]:
'map_sum'
In [5]:
from pymbolic.mapper import WalkMapper

class MyMapper(WalkMapper):
    def map_sum(self, expr):
        print("sum", expr.children)
In [6]:
u = (x+3)**5
u
Out[6]:
Power(Sum((Variable('x'), 3)), 5)
In [7]:
mymapper = MyMapper()
mymapper(u)
sum (Variable('x'), 3)

Recursive Traversal

What if there is another sum nested inside our existing one?

In [8]:
u = (x+3)**5 + 5
u
Out[8]:
Sum((Power(Sum((Variable('x'), 3)), 5), 5))
In [9]:
mymapper(u)
sum (Power(Sum((Variable('x'), 3)), 5), 5)

What do you notice? Is something missing?

Improve implementation as MyMapper2:

In [10]:
from pymbolic.mapper import WalkMapper

class MyMapper2(WalkMapper):
    def map_sum(self, expr):
        print("sum", expr.children)

        for ch in expr.children:
            self.rec(ch)
In [11]:
mymapper2 = MyMapper2()
mymapper2(u)
sum (Power(Sum((Variable('x'), 3)), 5), 5)
sum (Variable('x'), 3)

Mapper Inheritance

  • Above: What about map_variable? map_power?
  • Mappers inherit all non-overridden behavior from their superclasses.

This makes it easy to inherit a base behavior and then selectively change a few pieces.

Mappers with Values

  • Mappers do more than just traverse
  • They can also return a value
    • What type? Any desired one.

For example: Could return a string.

In [12]:
from pymbolic.mapper import RecursiveMapper
class MyStringifier(RecursiveMapper):
    def map_sum(self, expr):
        return "+".join(self.rec(ch) for ch in expr.children)
    
    def map_product(self, expr):
        return "*".join(self.rec(ch) for ch in expr.children)
    
    def map_variable(self, expr):
        return expr.name
    
    def map_constant(self, expr):
        return str(expr)
In [13]:
u = (x * 5)+(x * 7)



mystrifier = MyStringifier()

mystrifier(u)
Out[13]:
'x*5+x*7'

Mappers can also return another expression. IdentityMapper is a base that returns an identical (deep) copy of an expression:

In [14]:
from pymbolic.mapper import IdentityMapper

idmap = IdentityMapper()
u2 = idmap(u)
print(u2 == u)
print(u2 is u)
True
False

Term Rewriting

IdentityMapper can be used as a convenient base for term rewriting.

As a very simple example, let us

  • Change the name of all variables by appending a prime
  • Change all products to sums
In [15]:
class MyIdentityMapper(IdentityMapper):
    def map_variable(self, expr):
        return p.Variable(expr.name + "'")

    def map_product(self, expr):
        return p.Sum(tuple(self.rec(ch) for ch in expr.children))
In [16]:
u = (x*3)*(x+17)**3

myidmap = MyIdentityMapper()
print(myidmap(u))
x' + 3 + (x' + 17)**3
In [ ]: