Hi Conrad,

Yes, I can review a pull request for this.

Probably the place to put it is in doc/src/explanation/ but it could
also make sense to go in the intro tutorial.

Oscar

On Sat, 27 Dec 2025 at 13:07, 'Conrad Schiff' via sympy
<[email protected]> wrote:
>
> Hi Oscar,
>
>   Thank you for the thorough reply.  I will digest it and try to put a 
> beginner touch on it.  Would you be willing to review it and, if it is 
> acceptable, I would be happy for it to be used in the documentation.  I don't 
> know the development hierarchy so feel free to point me to other people, in 
> other directions, or suggest other things (including to stand down on behalf 
> of SymPy 🙂).
>
> Conrad
> ________________________________
> From: [email protected] <[email protected]> on behalf of Oscar 
> Benjamin <[email protected]>
> Sent: Saturday, December 27, 2025 8:03 AM
> To: [email protected] <[email protected]>
> Subject: Re: [sympy] subs v. replace v. xreplace - help needed on a minimal 
> viable example of their difference
>
> 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
>
> On Thu, 25 Dec 2025 at 15:57, 'Conrad Schiff' via sympy
> <[email protected]> wrote:
> >
> > All,
> >
> >   I am trying to understand the actual differences between the 'subs', 
> > 'replace', and 'xreplace'.  I've reviewed the documentation (some of it 
> > seems stale) and various discussions in this group.  Perhaps I've missed 
> > something but I've not found a side-by-side minimal viable example of that 
> > shows when to pick amongst these.
> >
> >   Does such a thing exist?  If it doesn't is there any interest in making 
> > such a thing.  I would be happy to take the first draft of documenting this 
> > if (and only if) I can get help from the rest of the group.
> >
> > Thank you,
> > Conrad Schiff, PhD
> > Professor of Physics
> > Capitol Technology University
> >
> > --
> > 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 [email protected].
> > To view this discussion visit 
> > https://groups.google.com/d/msgid/sympy/CH3PR12MB94327C143742026A16782408CFB2A%40CH3PR12MB9432.namprd12.prod.outlook.com.
>
> --
> 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 [email protected].
> To view this discussion visit 
> https://groups.google.com/d/msgid/sympy/CAHVvXxS8qOU4Hc-nAX4niFhTaGJaQ%2Bt7MQwMGfG0oPn38LSUbw%40mail.gmail.com.
>
> --
> 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 [email protected].
> To view this discussion visit 
> https://groups.google.com/d/msgid/sympy/CH3PR12MB9432C4D8378063949646B961CFB1A%40CH3PR12MB9432.namprd12.prod.outlook.com.

-- 
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 [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/sympy/CAHVvXxSTCKDP-wjpU6mO3qN5dsY%2B0c3EQ1%3DrgD58Pwg-ZPawjg%40mail.gmail.com.

Reply via email to