Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does sympy work? How does it interact with the interactive Python shell, and how does the interactive Python shell work?

What happens internally when I press Enter?

My motivation for asking, besides plain curiosity, is to figure out what happens when you

from sympy import *

and enter an expression. How does it go from Enter to calling

__sympifyit_wrapper(a,b)

in sympy.core.decorators? (That's the first place winpdb took me when I tried inspecting an evaluation.) I would guess that there is some built-in eval function that gets called normally, and is overridden when you import sympy?

like image 891
daltonb Avatar asked Jul 07 '10 03:07

daltonb


People also ask

What is SymPy used for in Python?

SymPy is a Python library for symbolic mathematics. It aims to become a full-featured computer algebra system (CAS) while keeping the code as simple as possible in order to be comprehensible and easily extensible.

What is an interactive shell in Python?

Python interactive shell is also known as Integrated Development Environment (IDLE). With the Python installer, two interactive shells are provided: one is IDLE (Python GUI) and the other is Python (command line). Both can be used for running simple programs.


1 Answers

All right after playing around with it some more I think I've got it.. when I first asked the question I didn't know about operator overloading.

So, what's going on in this python session?

>>> from sympy import *
>>> x = Symbol(x)
>>> x + x
2*x

It turns out there's nothing special about how the interpreter evaluates the expression; the important thing is that python translates

x + x

into

x.__add__(x)

and Symbol inherits from the Basic class, which defines __add__(self, other) to return Add(self, other). (These classes are found in sympy.core.symbol, sympy.core.basic, and sympy.core.add if you want to take a look.)

So as Jerub was saying, Symbol.__add__() has a decorator called _sympifyit which basically converts the second argument of a function into a sympy expression before evaluating the function, in the process returning a function called __sympifyit_wrapper which is what I saw before.

Using objects to define operations is a pretty slick concept; by defining your own operators and string representations you can implement a trivial symbolic algebra system quite easily:

symbolic.py --

class Symbol(object):
    def __init__(self, name):
        self.name = name
    def __add__(self, other):
        return Add(self, other)
    def __repr__(self):
        return self.name

class Add(object):
    def __init__(self, left, right):
        self.left = left
        self.right = right
    def __repr__(self):
        return self.left + '+' + self.right

Now we can do:

>>> from symbolic import *
>>> x = Symbol('x')
>>> x+x
x+x

With a bit of refactoring it can easily be extended to handle all basic arithmetic:

class Basic(object):
    def __add__(self, other):
        return Add(self, other)
    def __radd__(self, other): # if other hasn't implemented __add__() for Symbols
        return Add(other, self)
    def __mul__(self, other):
        return Mul(self, other)
    def __rmul__(self, other):
        return Mul(other, self)
    # ...

class Symbol(Basic):
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return self.name

class Operator(Basic):
    def __init__(self, symbol, left, right):
        self.symbol = symbol
        self.left = left
        self.right = right
    def __repr__(self):
        return '{0}{1}{2}'.format(self.left, self.symbol, self.right)

class Add(Operator):
    def __init__(self, left, right):
        self.left = left
        self.right = right
        Operator.__init__(self, '+', left, right)

class Mul(Operator):
    def __init__(self, left, right):
        self.left = left
        self.right = right
        Operator.__init__(self, '*', left, right)

# ...

With just a bit more tweaking we can get the same behavior as the sympy session from the beginning.. we'll modify Add so it returns a Mul instance if its arguments are equal. This is a bit trickier since we have get to it before instance creation; we have to use __new__() instead of __init__():

class Add(Operator):
    def __new__(cls, left, right):
        if left == right:
            return Mul(2, left)
        return Operator.__new__(cls)
    ...

Don't forget to implement the equality operator for Symbols:

class Symbol(Basic):
    ...
    def __eq__(self, other):
        if type(self) == type(other):
            return repr(self) == repr(other)
        else:
            return False
    ...

And voila. Anyway, you can think of all kinds of other things to implement, like operator precedence, evaluation with substitution, advanced simplification, differentiation, etc., but I think it's pretty cool that the basics are so simple.

like image 88
daltonb Avatar answered Oct 11 '22 13:10

daltonb