Common Operations

What common operations are supported?

Just normal mappers:

  • Evaluation
  • Turning expressions into 'human-readable' strings
  • Performing substitution
  • Taking derivatives
  • Finding variables on which an expression depends
  • Code Generation

Also:

  • Parsing (i.e. turning a string into an expression)

Evaluation

In [2]:
from pymbolic import parse
from pymbolic.mapper.evaluator import EvaluationMapper

expr = parse("(x**2 + y**2)**0.5")
expr
Out[2]:
Power(Sum((Power(Variable('x'), 2), Power(Variable('y'), 2))), 0.5)
In [3]:
print(expr)
(x**2 + y**2)**0.5
In [4]:
evm = EvaluationMapper({"x": 17, "y": 3})



print(evm(expr))
17.26267650163207

This is just a normal mapper, so its behavior can be overridden as described before.

Finding Independent Variables

In [5]:
from pymbolic.mapper.dependency import DependencyMapper

depmap = DependencyMapper()
depmap(expr)
Out[5]:
{Variable('x'), Variable('y')}

Code generation

In [6]:
from pymbolic.mapper.c_code import CCodeMapper

ccm = CCodeMapper()
x = parse("x")
ccm((x+4)**17)
Out[6]:
'pow(x + 4, 17)'

(We're using parse here just to give us a Variable("x") object.)

Common subexpressions

Often, some parts of an expression occur multiple times in a bigger expression.

In [20]:
u = (x+4)**3

h = parse("h")

expr = u + 2*u*h + 4*u*h**2
ccm(expr)
Out[20]:
'pow(x + 4, 3) + 4 * pow(x + 4, 3) * h * h + 2 * pow(x + 4, 3) * h'

Obviously, that doesn't lead to great code. In particular, the redundancy is carried through to the code side.

There is a mechanism to prevent this redundancy. Individual parts of an expression can be tagged as "common subexpressions".

In [21]:
from pymbolic.primitives import CommonSubexpression as CSE

u = CSE((x+4)**3)

h = parse("h")

expr = u + 2*u*h + 4*u*h**2

result = ccm(expr)

for name, value in ccm.cse_name_list:
    print(name, "=", value)
    
print(result)
_cse0 = pow(x + 4, 3)
_cse0 + 4 * _cse0 * h * h + 2 * _cse0 * h

(These names can be customized, in case you're wondering.)