SymPy and Python names: Symbol('x') is Symbol('x') ?

51 views
Skip to first unread message

James Bateman

unread,
Jun 3, 2020, 3:53:07 PM6/3/20
to sympy
I've just discovered a bug in my code which boiled down to the following, where a symbol "y" was given the same SymPy name as an existing symbol.

import sympy as sp
x = sp.Symbol('x')
y = sp.Symbol('y')

x == y # True
x is y # True; expected False
x + y # 2*x; expected x + x (which would have made the bug in my code more apparent)

The behaviour here is very surprising to me.  I would have expected x and y to be different Python objects with __repr__ methods which just so happen to return the same string.  Instead, x and y are apparently different Python names for the same object (x is y).

Is this intentional? I think I must misunderstand some deep design choice in SymPy, and I can't express my confusion well enough to Google it.  Please help!


Jason Moore

unread,
Jun 3, 2020, 4:07:09 PM6/3/20
to sympy
Yes, this is intentional. It is really no different than this:

In [1]: a = 1                                                                                            

In [2]: b = 1                                                                                            

In [3]: type(a)                                                                                          
Out[3]: int

In [4]: type(b)                                                                                          
Out[4]: int

In [5]: a == b                                                                                          
Out[5]: True

In [6]: a is b                                                                                          
Out[6]: True

Jason

--
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 on the web visit https://groups.google.com/d/msgid/sympy/f0084d3b-db98-43cb-becd-020a368aec87%40googlegroups.com.

Jason Moore

unread,
Jun 3, 2020, 4:09:19 PM6/3/20
to sympy
Actually, I don't think your code is valid. You create two distinctly different symbols:

In [1]: import sympy as sm                                                                              

In [2]: x = sm.Symbol('x')                                                                              

In [3]: y = sm.Symbol('y')                                                                              

In [4]: x == y                                                                                          
Out[4]: False

In [5]: x is y                                                                                          
Out[5]: False

In [6]: x + y                                                                                            
Out[6]: x + y

Jason

Aaron Meurer

unread,
Jun 3, 2020, 4:55:01 PM6/3/20
to sympy
You'll need to give more details on what your code is doing. The code
you posted works as expected. Both x == y and x is y should be False
because Symbols compare by name. It is possible the bug is in your own
code somewhere, as it would be difficult for y to "become" x exactly,
but it is also possible you stumbled on a bug in SymPy itself. Without
an example that reproduces the issue, it is impossible to say.

Aaron Meurer
> To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/CAP7f1Ag-%3D%2BT2xX1frdphHhe8G6SBcWX1qvje1WVPNDHehU19Eg%40mail.gmail.com.

James Bateman

unread,
Jun 3, 2020, 6:18:43 PM6/3/20
to sympy
Thank you, but I don't need help debugging my code; I had a typo which boiled down to the example I gave.   My only question was whether this was intended behaviour.

Please note the deliberate mistake of "y = Symbol('x')", which you may have missed when interpreting my question.  True is returned for both "x == y" and "x is y" in SymPy 1.2 (local installation) and SymPy 1.5.1 (http://live.sympy.org).
>>> To unsubscribe from this group and stop receiving emails from it, send an email to sy...@googlegroups.com.
>>> To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/f0084d3b-db98-43cb-becd-020a368aec87%40googlegroups.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 sy...@googlegroups.com.

James Bateman

unread,
Jun 3, 2020, 6:21:41 PM6/3/20
to sympy
Please note the deliberate mistake of "y = Symbol('x')", which you may have missed when interpreting my question.  You have corrected this deliberate mistake in your answer.  My code was valid (it ran and gave the results I quoted) and highlighted what was, to me, a surprising behaviour.

As to this being the same as "a = 1; b = 1; a == b; a is b" that is true for immutables, but not so for e.g. "a = []; b = []" for which vanilla Python gives "a == b" => True and "a is b" => False. 

From this can I infer that the SymPy Symbol class is immutable?  That might clear it up in my mind.
To unsubscribe from this group and stop receiving emails from it, send an email to sy...@googlegroups.com.

Oscar Benjamin

unread,
Jun 3, 2020, 6:30:31 PM6/3/20
to sympy
>> >> On Wed, Jun 3, 2020 at 12:53 PM James Bateman <james....@gmail.com> wrote:
>> >>>
>> >>> I've just discovered a bug in my code which boiled down to the following, where a symbol "y" was given the same SymPy name as an existing symbol.
>> >>>
>> >>> import sympy as sp
>> >>> x = sp.Symbol('x')
>> >>> y = sp.Symbol('y')
>> >>>
>> >>> x == y # True
>> >>> x is y # True; expected False
>> >>> x + y # 2*x; expected x + x (which would have made the bug in my code more apparent)

On Wed, 3 Jun 2020 at 23:18, James Bateman <james....@gmail.com> wrote:
>
> Thank you, but I don't need help debugging my code; I had a typo which boiled down to the example I gave. My only question was whether this was intended behaviour.
>
> Please note the deliberate mistake of "y = Symbol('x')", which you may have missed when interpreting my question. True is returned for both "x == y" and "x is y" in SymPy 1.2 (local installation) and SymPy 1.5.1 (http://live.sympy.org).

I think you maybe forgot to make the deliberate mistake when posting
here (see above).

The name of the Python variable does not need to match the name of the
symbol. Sympy has no way of knowing what name you use for the Python
variable so if you do

x = Symbol('y')

then there is no way for sympy to know that you assigned the result to
a variable called x. That's not so much an intended feature but just
how Python works.

Symbols with the same name (and assumptions) as considered equivalent
in sympy which is useful in many situations. If you want to create a
symbol that will only ever compare equal to itself regardless of the
name of any other symbol then you can use Dummy:
https://docs.sympy.org/latest/modules/core.html#dummy

Dummy is a bit awkward to use in some situations which is why it isn't
the default behaviour for symbols in sympy.

--
Oscar

James Bateman

unread,
Jun 3, 2020, 6:48:49 PM6/3/20
to sympy
Looks like I did indeed miss the deliberate mistake.  Sorry for being an idiot and thank you for taking the time.

For clarity, here is what I intended to post:

import sympy as sp
x = sp.Symbol('x')
y = sp.Symbol('x') # deliberate mistake here
x == y # True
x is y # True

I'm aware of Python names not being accessible through introspection.  It still seems as though two calls to "Symbol('x')" should create two distinct instances of a SymPy symbol, which just happen to share the name.  However, I see the sense in comparing symbols by name.

Has this behaviour changed since a previous version?  I am sure that I had an issue where I had an expression e.g. f=x**2 and differentiating w.r.t a newly created symbol by the name "x" gave zero, because it was not the same "x".  (This works as expected in recent versions and gives f.diff(x) == 2*x, so I can't reproduce this half-remembered claimed behaviour.)

Aaron Meurer

unread,
Jun 3, 2020, 6:56:16 PM6/3/20
to sympy
So the issue here is that SymPy uses an internal cache, which means
that sometimes two equal objects will also be equal in memory. The
second time you called Symbol, the first Symbol('x') was found in the
cache and returned. This keeps only one Symbol('x') object in memory
at a time. Jason's example of a = 1; b = 1 is similar, because Python
itself caches small integers.

SymPy objects are immutable, so it is free to replace two equal
objects by the same object. Practically this means that two
Symbol('x') objects are always the same, so you are free to reuse the
same x as before, or to make a new one. Symbols are compared only by
name and assumptions, so if those are equal, they will be the same.

You should never rely on "is" comparson for SymPy objects (with the
exception of singletonized classes, which are the things on the S
object). Always use == to see if two things are the same.

> Has this behaviour changed since a previous version? I am sure that I had an issue where I had an expression e.g. f=x**2 and differentiating w.r.t a newly created symbol by the name "x" gave zero, because it was not the same "x". (This works as expected in recent versions and gives f.diff(x) == 2*x, so I can't reproduce this half-remembered claimed behaviour.)

The issue was most likely due to assumptions. If two symbols have the
same name but different assumptions, they will be unequal:

>>> x = Symbol('x')
>>> x_positive = Symbol('x', positive=True)
>>> diff(x**2, x_positive)
0
>>> x == x_positive
False

If you use assumptions, it is good practice to always use the same
assumptions for any given symbol name. It also helps to design your
code so that you pass the same symbols around so that they end up
being the same.

Aaron Meurer
> --
> 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 on the web visit https://groups.google.com/d/msgid/sympy/c915ba41-ee86-404e-af07-689f5e89d174%40googlegroups.com.

Aaron Meurer

unread,
Jun 3, 2020, 6:57:35 PM6/3/20
to sympy
On Wed, Jun 3, 2020 at 4:55 PM Aaron Meurer <asme...@gmail.com> wrote:
>
> So the issue here is that SymPy uses an internal cache, which means
> that sometimes two equal objects will also be equal in memory. The
> second time you called Symbol, the first Symbol('x') was found in the
> cache and returned. This keeps only one Symbol('x') object in memory
> at a time. Jason's example of a = 1; b = 1 is similar, because Python
> itself caches small integers.
>
> SymPy objects are immutable, so it is free to replace two equal
> objects by the same object. Practically this means that two
> Symbol('x') objects are always the same, so you are free to reuse the
> same x as before, or to make a new one. Symbols are compared only by
> name and assumptions, so if those are equal, they will be the same.
>
> You should never rely on "is" comparson for SymPy objects (with the
> exception of singletonized classes, which are the things on the S
> object). Always use == to see if two things are the same.
>
> > Has this behaviour changed since a previous version? I am sure that I had an issue where I had an expression e.g. f=x**2 and differentiating w.r.t a newly created symbol by the name "x" gave zero, because it was not the same "x". (This works as expected in recent versions and gives f.diff(x) == 2*x, so I can't reproduce this half-remembered claimed behaviour.)
>
> The issue was most likely due to assumptions. If two symbols have the
> same name but different assumptions, they will be unequal:

And to clarify, this behavior is not new. Symbol equality has worked
like this for a very long time.

Aaron Meurer

James Bateman

unread,
Jun 3, 2020, 7:04:58 PM6/3/20
to sy...@googlegroups.com
Thank you. This has been extremely helpful. Sorry once again for posting the wrong code initially.

David Bailey

unread,
Jun 4, 2020, 7:47:11 AM6/4/20
to sy...@googlegroups.com
On 04/06/2020 00:04, James Bateman wrote:
Thank you. This has been extremely helpful. Sorry once again for posting the wrong code initially.

James,

I found the creation of symbols in SymPy incredibly confusing and counter-intuitive - it was almost spoiling my enjoyment of using the wonderful SymPy system. I decided to work out a systematic way of using SymPy for short calculations that eliminates this confusion.

I avoid all use of Symbol, symbols, or any of the related ways of creating symbols, except for creating special symbols, such as undefined function symbols. I start any use of SymPy thus:

import sympy
from sympy.abc import *
from sympy import *

The module sympy.abc contains symbols for all 52 single-letter variables - which are obviously most useful for algebra - in upper and lower case.

Unfortunately a few upper case symbols are used by SymPy for specific purposes - so the order of the above imports is important - SymPy symbols should override those from abc so they are imported last. This is described in the SymPy documentation:

"As of the time of writing this, the names C, O, S, I, N, E, and Q are colliding with names defined in SymPy. If you import them from both sympy.abc and sympy, the second import will “win”. This is an issue only for * imports, which should only be used for short-lived code such as interactive sessions and throwaway scripts that do not survive until the next SymPy upgrade, where sympy may contain a different set of names."

Using the above, I then choose a few (often zero) variables to use in the traditional Python fashion (i.e. variables that will be assigned values) and I have the rest available for algebra. If you need a lot of Python variables - say in a complicated algorithm - it may be easier to use multi-letter names for them - which totally avoids any clash with symbols.

I don't create any normal symbols explicitly.

Note that the above quote from the SymPy documentation does not abhor the use of 'import *' for "short lived code", which might be better phrased as "small sized code". I suspect that typically most users only want to write a small SymPy 'program' - sometimes just one line - and for those uses 'import *' seems infinitely preferable. Of course, most of the people who post here are seasoned SymPy developers, and  they are writing large amounts (hundreds or thousands of lines)  of SymPy code - symbolic integration algorithms or whatever - and for them I can see that importing the entire contents of SymPy or other modules would be dangerous.

I hope that helps, and doesn't create too much controversy!

David





Reply all
Reply to author
Forward
0 new messages