What is the difference between subs, replace, and xreplace in sympy?
I have been using subs for substitution of symbolics. I was discussing with someone and they recommended using replace instead of subs for most cases. Then I also stumbled on xreplace.
For my application I am substituting an expression in for a variable. Is one of these recommended over the other for that? What are the main uses for each? Which one would generally be recommended?
Credit goes to the official docs for all quotes and basics of examples, but in an attempt to make a direct and thorough comparison
| subs | replace | xreplace | |
|---|---|---|---|
| signature | subs(*args, **kwargs) |
replace(query, value, map=False, simultaneous=True, exact=None) |
xreplace(rule) |
| docstring | Substitutes old for new in an expression after sympifying args. | Replace matching subexpressions of self with value. | Replace occurrences of objects within the expression. |
| "see also" link blurb | substitution of subexpressions as defined by the objects themselves. | replacement capable of doing wildcard-like matching, parsing of match, and conditional replacements | exact node replacement in expr tree; also capable of using matching rules |
| replacement design | logical replacement | query or rule-matching | pattern replacement |
| accepts mapping of replacements | True dict or new:old pairs |
False one at a time, though it can have complex logic |
True dict of patterns and replacements |
For single expressions, subs replaces on real equivalence, extracting sub-expressions and replacing them, while replace and xreplace match on literal patterns - for example, see how they match x**2
>>> (x**2 + x**4).subs(x**2, y) # logical/mathematical replacement
y**2 + y
>>> (x**2 + x**4).xreplace({x**2: y}) # pattern replacement (multiple)
x**4 + y
>>> (x**2 + x**4).replace(x**2, y) # pattern replacement (single)
x**4 + y
However, while subs and xreplace have fairly simple acceptable arguments, replace has a very complex syntax, replacing via some types, pattern matching(see warnings!), or filters .. and further with the first argument modifying what format the second replacement argument may take, from a direct expression to a callable, which may additionally be passed either the match itself or the match's arguments!
replace by type
>>> (x**2 + x**4).replace(Pow, lambda a,b: Pow(a, b+1)) # unpacks Pow args
x**5 + x**3
>>> (x**2 + x**4).replace(Pow, lambda a,b: tan(a)**(b+1))
tan(x)**5 + tan(x)**3
>>> (x**2 + x**4).replace(Number, lambda: 10) # make every number 10
2*x**10
replace by pattern
>>> (x**2 + x**4).replace(Wild('a', exclude=[Pow, 4]), lambda a: y)
y**4 + y**y
replace by callable filter
>>> (x**2 + x**4).replace(lambda expr: expr**2 == x**2, lambda a: y)
y**4 + y**2
>>> (x**2 + x**4).replace(lambda expr: expr%2==0, lambda a: Pow(y, a))
x**(y**2) + x**(y**4)
match command match(pattern, old=False) (and matches helper)
Note unbounded Symbols are ignored and ordering matters (see Warnings below)
>>> (x**2 + x**4).match(Wild('n')**4 + (Wild('m')**2))
{n_: x, m_: x}
The subs docs makes special note that .evalf() supports substituting too, and when numerically evaluating after substituting, this takes the passed precision into account, potentially getting a more expected result
>>> (1/x).evalf(subs={x: 3.0}, n=21)
0.333333333333333333333
>>> (1/x).subs({x: 3.0}).evalf(21)
0.333333333333333314830
the rewrite command rewrite(*args, deep=True, **hints)
Rewrite self using a defined rule.
Rewriting transforms an expression to another, which is mathematically equivalent but structurally different. For example you can rewrite trigonometric functions as complex exponentials or combinatorial functions as gamma function.
>>> expr = cos(x) + I*sin(x)
>>> expr.rewrite(exp)
exp(I*x)
>>> expr.rewrite(sin, exp)
exp(I*x)/2 + cos(x) - exp(-I*x)/2
WildWild can match very unexpectedly if not properly constrained - check out my answer to What's the actual behavior of expr.replace() when using "exact=False"?!
Further beware of sensitivity to ordering and the real representation when doing pattern matches - this somewhat non-obvious behavior happens because replace treats the passed expression as a pattern, which may not be an exact match when additional Symbols become involved!
>>> (x**2 + x**4).match(Wild('n')**2 + (Wild('m')**4))
{n_: x**2, m_: sqrt(x)}
>>> (x**2 + x**4).match(Wild('n')**4 + (Wild('m')**2))
{n_: x, m_: x}
>>> srepr(x**2 + x**4)
"Add(Pow(Symbol('x'), Integer(4)), Pow(Symbol('x'), Integer(2)))"
>>> (x**2 + x**4 - y**3).replace(x**2 + x**4, y) # fails
x**4 + x**2 - y**3
>>> (x**2 + x**4 - y**3).replace(x**4 + x**2, y) # closer
x**4 + x**2 - y**3
>>> srepr(x**2 + x**4 - y**3) # actually of the form Add(a, b, c)
"Add(Pow(Symbol('x'), Integer(4)), Pow(Symbol('x'), Integer(2)), Mul(Integer(-1), Pow(Symbol('y'), Integer(3))))"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With