I have a sympy expression with multiple variables that need to be substituted out. The problem is that the some of the expressions to be substituted in also contain instances of variables that need to be substituted out.
from sympy import *
from sympy.abs import a,b, x,y
expr = a + b
replace = [[a, x+y], [b, 2*a]]
expr.subs(replace) # 2*a + x + y, I want 3*x + 3*y
expr.subs(reversed(replace)) # 3*x + 3*y
result = expr
for _ in replace:
# Applying n times
result = result.subs(replace)
If you do it in the correct order, the replacement will be performed iteratively (unless you use
subs(replacement, simultaneous=True), which performs all the replacements at once).
Your problem then is ordering the replacements correctly. What you want is a topological sort of the replacements. Namely, each replacement is a node in a graph, and there is an edge from
(old1, new1) to
(old2, new2) if
old2 (i.e., it should be replaced first).
SymPy has an implementation of
sympy.utilities.iterables. It takes a list of vertices and a list of edges (tuples of vertices). Say you have
replace = [(y, z + 1), (x, y + z), (z, a)]
We can create a list of edges with
from itertools import combinations edges = [(i, j) for i, j in permutations(replace, 2) if i.has(j)]
Sorting this gives
>>> from sympy import default_sort_key, topological_sort >>> topological_sort([replace, edges], default_sort_key) [(x, y + z), (y, z + 1), (z, a)]
The third argument of
topological_sort is a key used to break ties. Since SymPy objects don't have an implicit ordering defined on them (
TypeError in general), there is a sort key implementation called
default_sort_key that provides a canonical and consistent (but arbitrary) sorting of SymPy objects.
In a situation like the one shown by 404 where there would be an infinite loop,
topological_sort will alert you that there is a cycle
>>> replace = [(x, y+1), (y, x+1)] >>> edges = [(i, j) for i, j in permutations(replace, 2) if i.has(j)] >>> topological_sort([replace, edges], default_sort_key) Traceback (most recent call last): File "<ipython-input-51-72f3bfcfd4ad>", line 1, in <module> topological_sort([replace, edges], default_sort_key) File "/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/utilities/iterables.py", line 882, in topological_sort raise ValueError("cycle detected") ValueError: cycle detected
Honestly, this ought to just be implemented directly in
subs via a keyword argument. See https://github.com/sympy/sympy/issues/6257.