Writing your own transformations, or debugging surprising results, requires seeing the tree SymPy really built.
Every SymPy expression is a tree. Each node has a func (its class, like Add, Mul, Pow, sin) and a tuple of args. Leaves (Symbol, Integer, Rational) have empty args. The key invariant is:
expr.func(*expr.args) == exprso you can rebuild any node from its parts, which is how recursive transformations are written. srepr(expr) prints the full tree.
The canonical forms are worth knowing: there is no Sub or Div. x - y is Add(x, Mul(-1, y)), and x/y is Mul(x, Pow(y, -1)). Arguments of Add and Mul are stored in a canonical order, so x + y and y + x are the same object, but you should never rely on a particular order of args.
Tools for working with trees:
preorder_traversal(e)/postorder_traversal(e)iterate over nodes.e.atoms(Symbol),e.atoms(sin)collect nodes of given types.e.has(x)tests containment;e.find(query)searches.e.xreplace({old: new})replaces exact subtrees with no mathematical reasoning, which is fast and predictable;subsis smarter and slower.e.replace(pattern, func)rewrites with types orWildpatterns.
Because expressions are immutable and hashable, they work as dict keys and are cached aggressively. Structural == compares these trees exactly, which is fast but knows no algebra.
Example
from sympy import symbols, srepr, sin, Add, preorder_traversal, Symbol
x, y = symbols("x y")
e = x - y / 2 + sin(x)
print(srepr(x - y)) # Add(Symbol('x'), Mul(Integer(-1), Symbol('y')))
print(e.func, len(e.args)) # <class 'sympy.core.add.Add'> 3
print(e.func(*e.args) == e) # True
print(sorted(map(str, e.atoms(Symbol)))) # ['x', 'y']
print(e.has(sin)) # True
print(len(list(preorder_traversal(e)))) # 7
def rebuild(node):
if not node.args:
return node
return node.func(*[rebuild(a) for a in node.args])
print(rebuild(e) == e) # True: rebuilt from parts
print(e.xreplace({sin(x): 0})) # x - y/2
print((x + 2*x*y).subs(2*x, y)) # x + y**2Remember
- Every node is func(*args); rebuild with expr.func(*new_args)
- Subtraction and division are Add with -1 and Pow with -1
- xreplace is literal; subs is mathematical; atoms/has/traversals find nodes
Watch out for
- Relying on the order of Add or Mul args, which is canonical but not what you typed.