Hi Conrad,
Maybe the docs for this can be improved. In fact there should probably
be a separate docs page for exactly this that perhaps also covers some
other common methods such as rewrite.
The xreplace function is the simplest. The signature is
expr2 = expr1.xreplace({old1: new1, old2: new2, ...})
This will recursively walk down through the expression tree replacing
exact subexpressions so that if old1 is somewhere in expr1 then expr2
will have new1 in that place instead:
In [9]: expr1 = x**2 + cos(x)
In [10]: print(expr1)
x**2 + cos(x)
In [11]: print(expr1.xreplace({cos(x): sin(x)}))
x**2 + sin(x)
For all of xreplace, replace and subs the expression tree is evaluated
as it is rebuilt so often inserting new1 causes some part of the
expression to transform e.g. here cos(pi/4) evaluates to sqrt(2)/2 and
(pi/4)**2 evaluates to pi**2/16:
In [12]: print(expr1.xreplace({x: pi/4}))
pi**2/16 + sqrt(2)/2
The replace function is like xreplace but gives more flexibility for
selecting which expressions to replace and what to replace them by.
There are several different options for what the arguments to replace
can be but they all allow for more complex matching than xreplace
which only matches exact subexpressions. This version uses Wild
symbols for pattern matching:
In [19]: expr1 = sin(x) + sin(y)
In [20]: print(expr1)
sin(x) + sin(y)
In [21]: a = Wild('a')
In [22]: print(expr1.replace(sin(a), cos(a)))
cos(x) + cos(y)
It is also possible to pass functions as argument to replace so that
the full Turing complete Python language can be used to select
subexpressions and compute their replacements:
In [24]: print(expr1.replace(lambda e: e.func == cos, lambda e:
sin(e.args[0])))
sin(x) + sin(y)
Both xreplace and replace are purely structural operations on
expression trees. They do not attempt to interpret anything in any
mathematical way and will just replace the exact subexpressions as
instructed. The subs method is supposed to correspond to performing a
mathematically correct substitution meaning that it will interpret a
match differently:
In [31]: print(expr1)
x**4 + x**3 + x**2 + x
In [32]: print(expr1.subs(x**2, y))
x**3 + x + y**2 + y
Here subs decided that even powers of x could be replaced even if e.g.
x**4 was not the exact replacement pattern but it decided against
replacing odd powers since it does not know whether x is sqrt(y) or
-sqrt(y).
Usually when subs is used you do not have this and the replacement
patterns are all just symbol to expression like expr.subs(x,
some_expr). In that case the primary distinction between xreplace and
subs is really that subs distinguishes between free and bound symbols
whereas xreplace does not:
In [33]: expr = Integral(x*y, (x, 0, 1))
In [34]: print(expr)
Integral(x*y, (x, 0, 1))
In [35]: print(expr.xreplace({x:z, y:t}))
Integral(t*z, (z, 0, 1))
In [36]: print(expr.subs({x:z, y:t}))
Integral(t*x, (x, 0, 1))
In [37]: expr.free_symbols
Out[37]: {y}
These are part of a more general feature that different types of
expression can override the behaviour of subs in a way that does not
happen for xreplace and replace. The _eval_subs method is used for
this and you can see that there are many of these methods defined in
the codebase for different kinds of expression (the ones above were
for Pow in power.py and for Integral in expr_with_limits.py):
$ git grep 'def _eval_subs'
sympy/algebras/quaternion.py: def _eval_subs(self, old: Expr,
new: Expr) -> Quaternion: # type: ignore
sympy/concrete/expr_with_limits.py: def _eval_subs(self, old, new):
sympy/core/add.py: def _eval_subs(self, old, new):
sympy/core/basic.py: def _eval_subs(self, old: Basic, new: Basic)
-> Basic | None:
sympy/core/function.py: def _eval_subs(self, old, new):
sympy/core/function.py: def _eval_subs(self, old, new):
sympy/core/function.py: def _eval_subs(self, old, new):
sympy/core/mul.py: def _eval_subs(self, old, new):
sympy/core/numbers.py: def _eval_subs(self, old, new):
sympy/core/numbers.py: def _eval_subs(self, old, new):
sympy/core/numbers.py: def _eval_subs(self, old, new):
sympy/core/power.py: def _eval_subs(self, old, new):
sympy/core/symbol.py: def _eval_subs(self, old, new):
sympy/functions/elementary/exponential.py: def _eval_subs(self, old, new):
sympy/functions/elementary/piecewise.py: def _eval_subs(self, old, new):
sympy/functions/elementary/trigonometric.py: def _eval_subs(self,
old, new):
sympy/geometry/curve.py: def _eval_subs(self, old, new):
sympy/geometry/entity.py: def _eval_subs(self, old, new):
sympy/logic/boolalg.py: def _eval_subs(self, old, new):
sympy/logic/boolalg.py: def _eval_subs(self, old, new):
sympy/logic/boolalg.py: def _eval_subs(self, old, new):
sympy/physics/control/lti.py: def _eval_subs(self, old, new):
sympy/physics/units/quantities.py: def _eval_subs(self, old, new):
sympy/polys/polytools.py: def _eval_subs(f, old, new):
sympy/polys/rootoftools.py: def _eval_subs(self, old, new):
sympy/series/formal.py: def _eval_subs(self, old, new):
sympy/series/fourier.py: def _eval_subs(self, old, new):
sympy/series/order.py: def _eval_subs(self, old, new):
sympy/sets/conditionset.py: def _eval_subs(self, old, new):
sympy/tensor/tensor.py: def _eval_subs(self, old, new):
The xreplace function is the fastest and simplest. The replace
function is the most flexible. The subs function is the slowest and is
the only one that applies any semantic meaning to the substitution.
--
Oscar
> --
> You received this message because you are subscribed to the Google Groups "sympy" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to
sympy+un...@googlegroups.com.
> To view this discussion visit
https://groups.google.com/d/msgid/sympy/CH3PR12MB94327C143742026A16782408CFB2A%40CH3PR12MB9432.namprd12.prod.outlook.com.