If you do this in the correct order, the replacement will be performed iteratively (unless you use subs(replacement, simultaneous=True) , which performs all the replacements at once).
Then your task organizes the replacements correctly. What you want is a topological view for substitutions. Namely, each replacement is a node in the graph, and there is an edge from (old1, new1) to (old2, new2) if new1 contains old2 (i.e., it should be replaced first).
SymPy has an implementation of topological_sort in sympy.utilities.iterables . It takes a list of vertices and a list of edges (vertex tuples). Let's say you
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[1].has(j[0])]
Sorting 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 to topological_sort is the key used to break ties. Since SymPy objects do not have implicit ordering on them ( < and > raise TypeError in general), there is an implementation of the sort key called default_sort_key , which provides canonical and sequential (but arbitrary) sorting of SymPy objects.
In a situation like the one shown in 404, where there will be an infinite loop, topological_sort warn you that there is a loop
>>> replace = [(x, y+1), (y, x+1)] >>> edges = [(i, j) for i, j in permutations(replace, 2) if i[1].has(j[0])] >>> 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 should just be implemented directly in subs using the keyword argument. See https://github.com/sympy/sympy/issues/6257 .