Eval a numpy array to a sympy expr

195 views
Skip to first unread message

Friedrich Hagedorn

unread,
Jan 16, 2008, 6:27:30 AM1/16/08
to sympy
Hello everyone,

I use sympy for my study in electrical engineering to get symbolic
expression. After some calculations (most ().diff and ().subs) I want to
evaluate these expressions with numpy arrays, e.g. for plotting or
root-finding. So I wrote my own version of the Lambda class:

class MyLambda():
def __init__(self, *args):
list = [i for i in args]
self.expr = list.pop()
self.vars = list

def apply(self, *args):
self.sub = {}
for v, a in zip(self.vars, args):
self.sub[v.tostr()] = a
import numpy as p
return eval(self.expr.tostr(), self.sub, vars(p))

def __call__(self, *args):
return self.apply(*args)

So I can do something like that:

>>> from sympy import *
>>> var('x y')
(x, y)
>>> f=MyLambda(x,y, x**2+sin(y))
>>> f(2, 0)
4.0

>>> from numpy import linspace, pi
>>> t = linspace(0, 2*pi, 2000)
>>> y = f(2, t)

>>> from pylab import plot, show
>>> plot(t , y)
[<matplotlib.lines.Line2D instance at 0x8bbeb0c>]
>>> show()

And that eval is really fast :-) If you like it, please integrate this
in the sympy environment. I read the source for the sympy Lambda class,
but I dont have the whole overview to do the right things.

By,
Friedrich Hagedorn

Ondrej Certik

unread,
Jan 16, 2008, 7:19:33 AM1/16/08
to sy...@googlegroups.com
Hi Friedrich,

thank you very much for sharing the code.

> class MyLambda():

^^ it's interesting, that this works in python2.5, but in python2.4, you need
to delete the "()".

I think we almost have this in SymPy already, see below.

We should implement multivariate lambdas:

http://code.google.com/p/sympy/issues/detail?id=619

But those are meant to return sympy expression and not depend on numpy.

What you need is a fast evaluation of sympy expressions (for plotting
and other use) right?

In SymPy, we also have this:

http://hg.sympy.org/sympy/file/94e3b53cc0a2/sympy/utilities/lambdify.py

Look at the docstrings, it's pretty much self explaining:

28 >>> from sympy import symbols
29 >>> x,y,z = symbols('xyz')
30 >>> lambdastr(x**2, [x])
31 'lambda x: (x**2)'
32 >>> lambdastr([z,y,x], [x,y,z])
33 'lambda x,y,z: (z,y,x)'

and

10 >>> from sympy import symbols, sqrt
11 >>> x,y,z = symbols('xyz')
12 >>> f = lambdify(x**2, [x])
13 >>> f(2)
14 4
15 >>> f = lambdify([z,y,x], [x,y,z])
16 >>> f(1,2,3)
17 (3, 2, 1)
18 >>> f = lambdify(sqrt(x), [x])
19 >>> f(4)
20 2.0

Only the order of arguments of lambdify should be swapped.
That is what we use in sympy plotting.

Here is how you would use it to reproduce your example above:

In [1]: from sympy.utilities.lambdify import lambdastr

In [2]: fstr = lambdastr(x**2+sin(y), [x, y])

In [3]: import numpy as p

In [4]: f=eval(fstr, vars(p))

In [5]: f
Out[5]: <function <lambda> at 0xb67f66f4>

In [6]: fstr
Out[6]: 'lambda x,y: (x**2 + sin(y))'

In [7]: from numpy import linspace, pi

In [8]: t = linspace(0, 2*pi, 2000)

In [9]: y = f(2, t)

In [10]: from pylab import plot, show

In [11]: plot(t , y)
Out[11]: [<matplotlib.lines.Line2D instance at 0xb55c8c4c>]

In [12]: show()

What do you think is the most convenient way of using it?
I think something like creating

lambdify_numpy, or something is enough, isn't it?

The code of such a function will be trivial:

fstr = lambdastr(x**2+sin(y), [x, y])
import numpy as p
return eval(fstr, vars(p))


The other option is to instruct Lambda to use numpy, something like this

Lambda([x, y], x**2+sin(y), use_numpy = True)


But I think, from the users point of view, all I need is just a fast
way to evaluate the expressions and I don't care about lambdas, so a
usage
like this:

f = lambdify_numpy(x**2+sin(y))


t = linspace(0, 2*pi, 2000)
y = f(2, t)

is all I need. I think it is actually even faster than your version,
as the "f" is a regular python lambda function.

Let's discuss this and then implement it and document it.

Ondrej

Gael Varoquaux

unread,
Jan 16, 2008, 7:24:33 AM1/16/08
to sy...@googlegroups.com
On Wed, Jan 16, 2008 at 01:19:33PM +0100, Ondrej Certik wrote:
> But I think, from the users point of view, all I need is just a fast
> way to evaluate the expressions and I don't care about lambdas, so a
> usage
> like this:

> f = lambdify_numpy(x**2+sin(y))
> t = linspace(0, 2*pi, 2000)
> y = f(2, t)

> is all I need. I think it is actually even faster than your version,
> as the "f" is a regular python lambda function.

This is Good. However, what is bad from a user point of view is that the
user has to be aware that he is manipulating numpy arrays. Most beginners
don't know this (import numpy is magic for them), they are not really
aware of types. How can this be made magic? I have no solution, I just
know some people are going to hate this, but I hope clever people on this
list are going to find a way to make this friendlier.

Gaël

Friedrich Hagedorn

unread,
Jan 16, 2008, 8:03:39 AM1/16/08
to sy...@googlegroups.com
On Wed, Jan 16, 2008 at 01:19:33PM +0100, Ondrej Certik wrote:
>

Yes.

> In SymPy, we also have this:
>
> http://hg.sympy.org/sympy/file/94e3b53cc0a2/sympy/utilities/lambdify.py
>
> Look at the docstrings, it's pretty much self explaining:
>
> 28 >>> from sympy import symbols
> 29 >>> x,y,z = symbols('xyz')
> 30 >>> lambdastr(x**2, [x])
> 31 'lambda x: (x**2)'
> 32 >>> lambdastr([z,y,x], [x,y,z])
> 33 'lambda x,y,z: (z,y,x)'
>
> and
>
> 10 >>> from sympy import symbols, sqrt
> 11 >>> x,y,z = symbols('xyz')
> 12 >>> f = lambdify(x**2, [x])
> 13 >>> f(2)
> 14 4
> 15 >>> f = lambdify([z,y,x], [x,y,z])
> 16 >>> f(1,2,3)
> 17 (3, 2, 1)
> 18 >>> f = lambdify(sqrt(x), [x])
> 19 >>> f(4)
> 20 2.0
>
> Only the order of arguments of lambdify should be swapped.
> That is what we use in sympy plotting.

I read this, but I havent noticed that this fill my needs :-)

BTW, I didnt realized the difference between Lambda and lambdify yet.

> Here is how you would use it to reproduce your example above:
>
> In [1]: from sympy.utilities.lambdify import lambdastr
>
> In [2]: fstr = lambdastr(x**2+sin(y), [x, y])
>
> In [3]: import numpy as p
>
> In [4]: f=eval(fstr, vars(p))
>
> In [5]: f
> Out[5]: <function <lambda> at 0xb67f66f4>
>
> In [6]: fstr
> Out[6]: 'lambda x,y: (x**2 + sin(y))'
>
> In [7]: from numpy import linspace, pi
>
> In [8]: t = linspace(0, 2*pi, 2000)
>
> In [9]: y = f(2, t)
>
> In [10]: from pylab import plot, show
>
> In [11]: plot(t , y)
> Out[11]: [<matplotlib.lines.Line2D instance at 0xb55c8c4c>]
>
> In [12]: show()

Oh, thats fine :-)

> What do you think is the most convenient way of using it?
> I think something like creating
>
> lambdify_numpy, or something is enough, isn't it?
>
> The code of such a function will be trivial:
>
> fstr = lambdastr(x**2+sin(y), [x, y])
> import numpy as p
> return eval(fstr, vars(p))

If you know the tools, its really simple ;-)

> The other option is to instruct Lambda to use numpy, something like this
>
> Lambda([x, y], x**2+sin(y), use_numpy = True)

Thats not bad.

> But I think, from the users point of view, all I need is just a fast
> way to evaluate the expressions and I don't care about lambdas, so a
> usage
> like this:
>
> f = lambdify_numpy(x**2+sin(y))
> t = linspace(0, 2*pi, 2000)
> y = f(2, t)

But I want to define my order of the varibales.

I have no time to think too deep, but my suggestion is:

f = Lambda(x, y, x**2+sin(y)).eval_numpy()

So you can decide if f is a sympy.Function or a Python lambda function
in the numpy namespace. I prefer the Lambda name because it does the same
thing but return different objects.

By,
Friedrich

Friedrich Hagedorn

unread,
Jan 16, 2008, 8:16:30 AM1/16/08
to sy...@googlegroups.com
On Wed, Jan 16, 2008 at 01:19:33PM +0100, Ondrej Certik wrote:
> fstr = lambdastr(x**2+sin(y), [x, y])
> import numpy as p
> return eval(fstr, vars(p))

This is simpel, as far as the names of the functions are equal in sympy
and numpy. Ive found the first difference:

numpy.arccos <-> sympy.acos

By,
Friedrich

Ondrej Certik

unread,
Jan 17, 2008, 8:13:54 AM1/17/08
to sy...@googlegroups.com

Right. Does your code work for this?

I made an issue for it:

http://code.google.com/p/sympy/issues/detail?id=632

Feel free to help us fix it. :)

Ondrej

Reply all
Reply to author
Forward
0 new messages