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.
