Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sympy classes Zero, One and NegativeOne, why they exist?

Tags:

python

sympy

Today I found this

>>> type(1)
<class 'sympy.core.numbers.One'>
>>> type(0)
<class 'sympy.core.numbers.Zero'>
>>> type(-1)
<class 'sympy.core.numbers.NegativeOne'>
>>> type(2)
<class 'sympy.core.numbers.Integer'>

I looked the documentation from sympy about those types, but it doesn't say anything about why they exist. Is there a reason to have 3 special singleton classes for -1, 0 and 1?

Edit: I saw this at the SymPy online shell

like image 839
Mr. E Avatar asked Jan 27 '16 17:01

Mr. E


People also ask

How accurate is SymPy?

SymPy keeps track of the precision of Float objects. The default precision is 15 digits. When an expression involving a Float is evaluated, the result will be expressed to 15 digits of precision but those digits (depending on the numbers involved with the calculation) may not all be significant.

How do you define a Symbol in SymPy?

SymPy also has a Symbols() function that can define multiple symbols at once. String contains names of variables separated by comma or space. In SymPy's abc module, all Latin and Greek alphabets are defined as symbols. Hence, instead of instantiating Symbol object, this method is convenient.

How do you evaluate a SymPy expression?

To evaluate a numerical expression into a floating point number, use evalf . SymPy can evaluate floating point expressions to arbitrary precision. By default, 15 digits of precision are used, but you can pass any number as the argument to evalf .

How define SymPy function?

sympy. Function is for undefined functions. Like if f = Function('f') then f(x) remains unevaluated in expressions. Then f(Symbol('x')) will give a symbolic x**2 + 1 and f(1) will give 2 .


2 Answers

Every number in SymPy is represented by an instance of the class Number. Floats, Integers and Rationals are subclasses of Number. Zero is a subclass of Integer.

You can inspect the full class lineage of an object by calling its class's mro (method resolution order) method:

In [34]: from sympy import S
In [38]: type(S.Zero).mro()
Out[38]: 
[sympy.core.numbers.Zero,
 sympy.core.numbers.IntegerConstant,
 sympy.core.numbers.Integer,            <-- Zero is a kind of Integer
 sympy.core.numbers.Rational,
 sympy.core.numbers.Number,
 sympy.core.expr.AtomicExpr,
 sympy.core.basic.Atom,
 sympy.core.expr.Expr,
 sympy.core.basic.Basic,
 sympy.core.evalf.EvalfMixin,
 object]

These subclasses "teach" SymPy how to manipulate and simplify expressions symbolically. As a example, instances of the Rational class are negated this way:

def __neg__(self):
    return Rational(-self.p, self.q)

That is to say, if x is an instance of Rational, then -x causes x.__neg__() to be called. Meanwhile, instances of the Integer class, are negated by

def __neg__(self):
    return Integer(-self.p)

And if the object is, in particular, an instance of Zero, then its negation is defined by:

@staticmethod
def __neg__():
    return S.Zero    # the negation of Zero is still Zero

Zero, One and MinusOne also implement a _eval_power method which "teaches" these objects how to evaluate x raised to a power (where x is Zero, One or MinusOne). For example, Zero raised to a positive expression equals itself:

def _eval_power(self, expt):
    if expt.is_positive:
        return self
    ...

One raised to anything equals itself:

def _eval_power(self, expt):
    return self

If you peruse the source code for the sympy.core.numbers module, you'll find loads of definitions which are in effect teaching SymPy how to do symbolic arithmetic. It's not too different from what children are taught in math class, except that it is expressed in computer-ese.

You might be wondering why there isn't a special class for every integer. Integers besides Zero, One and MinusOne are treated as instances of the general Integer class. Their rules of addition and multiplication and so on are laid out there. Unlike Zero, One and MinusOne which are instantated when the module is loaded, other Integers are cached only as needed:

def __new__(cls, i):
    ...
    try:
        return _intcache[ival]   # <-- return the cached Integer if seen before
    except KeyError:           
        obj = Expr.__new__(cls)  # <-- create a new Integer if ival not in _intcache
        obj.p = ival

        _intcache[ival] = obj
        return obj
like image 91
unutbu Avatar answered Nov 13 '22 20:11

unutbu


First, note that type(1) gave you type(Integer(1)) because SymPy Live wraps integer literals in Integer() automatically (this is to avoid a gotcha where 1/2 evaluates to 0.5 instead of Rational(1, 2)). But note that in a regular Python session type(1) is int.

There are several objects in SymPy which are implemented as singletons, meaning only one instance will ever exist. You can see these all on the S object

In [13]: dir(S)
Out[13]:
['Catalan',
 'ComplexInfinity',
 'Complexes',
 'EulerGamma',
 'Exp1',
 'GoldenRatio',
 'Half',
 'IdentityFunction',
 'ImaginaryUnit',
 'Infinity',
 'NaN',
 'Naturals0',
 'NegativeInfinity',
 'NegativeOne',
 'One',
 'Pi',
 'Reals',
 'Zero',
 '__call__',
 '__class__',
 '__delattr__',
 '__doc__',
 '__format__',
 '__getattr__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 '_classes_to_install',
 'false',
 'register',
 'true']

(ignore the ones that start with _; those are Python internal methods)

The reason this is done is that these objects are used a lot. 0, 1, and -1 are very common objects. Every time you write 1/x it is represented internally as Pow(x, -1). x - y is represented as Add(x, Mul(-1, y)). For 0, it appears quite often in all sorts of symbolic calculations. 1 is also common. By having a single instance, SymPy enables two optimizations. First, it saves memory. Second, you can compare against these objects using is comparison, like x is S.One. Because only one instance can ever exist Integer(1) is always the same as S.One.

(also, I should note that some of the objects in S aren't actually that common, like Catalan and EulerGamma. I guess they were added more for convenience than anything)

like image 39
asmeurer Avatar answered Nov 13 '22 22:11

asmeurer