Fix for #836

14 views
Skip to first unread message

Friedrich Hagedorn

unread,
May 5, 2008, 12:18:08 PM5/5/08
to sympy-...@googlegroups.com
Its just an attempt. One solution to identify constants and functions is to
append '+ 0*var' to each expression. But I can't prevent this:

In [27]: 0*x
Out[27]: 0

Thats way I did the string operation. I also created two test for it.

identify.patch

Ondrej Certik

unread,
May 5, 2008, 1:04:57 PM5/5/08
to sympy-...@googlegroups.com

Thanks! It fails one doctest, that is trivial to fix:

$ ./setup.py test_doc
running test_doc
Testing docstrings.
...........................................................................................................................................................................F.
======================================================================
FAIL: Doctest: sympy.utilities.lambdify.lambdastr
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python2.5/doctest.py", line 2128, in runTest
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for sympy.utilities.lambdify.lambdastr
File "/home/ondra/ext/sympy/sympy/utilities/lambdify.py", line 27,
in lambdastr

----------------------------------------------------------------------
File "/home/ondra/ext/sympy/sympy/utilities/lambdify.py", line 33, in
sympy.utilities.lambdify.lambdastr
Failed example:
lambdastr([z,y,x], [x,y,z])
Expected:
'lambda x,y,z: (z,y,x)'
Got:
'lambda x,y,z: (z+0*x+0*y,y+0*x+0*z,x+0*y+0*z)'


----------------------------------------------------------------------
Ran 173 tests in 8.401s

FAILED (failures=1)

so I fixed that, and I also moved the numpy tests to test_numpy, see
the attached patch.

However, I am not sure -- do we want lambdify to return stuff like
"0*x" etc? Let's discuss it more and if we agree we do, I'll push it
in.

Ondrej

identify.patch

basti

unread,
May 5, 2008, 1:47:08 PM5/5/08
to sympy-patches
One silly example against this patch are constant functions: f(x) = 1
>> f = lambdify(1, x)

I know in the old implementation this would throw an error, so if we
use this patch I had to implement that again. But I'm not sure if we
should really forbid functions that don't depend on one of their
arguments. Any opinions on that?

Another example would be functions that reduce the dimension of their
arguments, like det, trace, dot, etc.. However, I'm not sure if any of
those are implemented in sympy, so as long as there are no such
functions nothing speaks against it. But the possibility is, that
someone will implement something like that and than we'll have this
problem again.

Because of this reasons I'm against this patch, not -1 but -0.5.

Sebastian

Friedrich Hagedorn

unread,
May 5, 2008, 3:38:25 PM5/5/08
to sympy-...@googlegroups.com
On Mon, May 05, 2008 at 10:47:08AM -0700, basti wrote:
>
> One silly example against this patch are constant functions: f(x) = 1

This was the original aim to get constant functions (to identify numbers
with constant functions).

> >> f = lambdify(1, x)

Hmm, what do you mean? I get this:

In [1]: from numpy import array

In [2]: f=lambdify(1,[x])

In [3]: f(array([0,1,2]))
Out[3]: [1 1 1]

Did you applied the patch to the given revision (befor your changes)?

By, Friedrich


basti

unread,
May 5, 2008, 3:49:58 PM5/5/08
to sympy-patches
> Hmm, what do you mean? I get this:
>
> In [1]: from numpy import array
>
> In [2]: f=lambdify(1,[x])
>
> In [3]: f(array([0,1,2]))
> Out[3]: [1 1 1]

Yes, I also get this, but the function f(x)=1 should return 1 for
anything you put in for x. At least that's what I would expect.

Friedrich Hagedorn

unread,
May 5, 2008, 3:59:42 PM5/5/08
to sympy-...@googlegroups.com

Ok, that's the (sub)problem from #800. In this issue we decided that a
tuple/list means a elementwise evaluation of a function
(numpy behaviour). In this way In[3] means:

0 -> 1
1 -> 1
2 -> 1
...

thats ok, I think. The function f(x) := 1 maps any _number_ to one. If you
what that f(x) := 1 maps vectors to one, we need a new object:

[x,y,z] -> 1
[1,5,3] -> 1
...

Do you know what I mean?

By,

Friedrich

Ondrej Certik

unread,
May 5, 2008, 4:37:30 PM5/5/08
to sympy-...@googlegroups.com

Yes. I think we need some way to distinguish between these two cases,
as clearly, both are needed. Any ideas are welcome.

Ondrej

basti

unread,
May 5, 2008, 4:39:26 PM5/5/08
to sympy-patches
Yes you're right with this. If we let all other sympy functions behave
like this (I mean element wise evaluation) we should be consequent and
also do this for user functions. Maybe it would then be cleaner to
write something like:
newlambdify = vectorize(1,2,...)(oldlambdify)

But I'm still not convinced if this should be the default behavior,
since as I pointed out in my first reply, there might be functions
that take a matrix and return a scalar. Then the element wise
evaluation would be wrong.

An example (using the new order of arguments):

>> f = lambdify([x,y], dot(x,y)) #Assuming we have a dot function in sympy like in numpy
>> f([1,2],[1,2])
wanted:5
patch:5
vectorize:[[1,2],[2,4]]
In this case you're patch would also give the right answer, whereas
vectorize would fail.

But look at this example:
>> f = lambdify([x,y], dot(0*x,y)
>> f([1,2],[1,2])
wanted:0
patch:[0,0]
vectorize:[[0,0],[0,0]]
Now your vectorize and your patch fails. Vectorize at least is
consequent and always returns a Matrix, whereas your patch would
return sometimes a vector and sometimes a scalar.
However, since both versions give not the wanted answer, I'm not sure
how we should deal with this.

Sebastian

Friedrich Hagedorn

unread,
May 6, 2008, 4:12:24 AM5/6/08
to sympy-...@googlegroups.com
On Mon, May 05, 2008 at 01:39:26PM -0700, basti wrote:
>
> Yes you're right with this. If we let all other sympy functions behave
> like this (I mean element wise evaluation) we should be consequent and
> also do this for user functions. Maybe it would then be cleaner to
> write something like:
> newlambdify = vectorize(1,2,...)(oldlambdify)

Thats looks good. Then we should have the elementwise evaluation _only_
with the vectorize function. See at the end for the one (easy) solution of
the problem with constant functions.

> But I'm still not convinced if this should be the default behavior,
> since as I pointed out in my first reply, there might be functions
> that take a matrix and return a scalar. Then the element wise
> evaluation would be wrong.

Thats why we have to distinguish these different objects: scalar, vector,
matrix, tensor, function, distribution, ...

Maybe the sympycore could do this whith the algebra approach?

[...]

> But look at this example:
> >> f = lambdify([x,y], dot(0*x,y)
> >> f([1,2],[1,2])

The input should looks like
>>> f(vector([1,2]), vector([1,2]))
for other input objects the dot function should raise an error.

> wanted:0
> patch:[0,0]
You are right but not quite, it should raise an error.

# Assuming to have a ZeroVector = [0,0,...] object
>>> lambdastr([x,y], dot(ZeroVector, y))
'lambda x,y: dot(ZeroVector, y) + 0*x'

This would add an scalar to a vector which is not defined.

> vectorize:[[0,0],[0,0]]
> Now your vectorize and your patch fails. Vectorize at least is
> consequent and always returns a Matrix, whereas your patch would
> return sometimes a vector and sometimes a scalar.

Right, thats inconsequent.

> However, since both versions give not the wanted answer, I'm not sure
> how we should deal with this.

With the vectorize comment above the way would be

In [1]: F=lambdify(1,[x])

In [2]: F=vectorize(0)(F)

In [3]: F([0,1,2,3])
Out[3]: [1, 1, 1, 1]

And with this we dont need the cumbersome way to add '+0*x'. The vectorize
should be the way to evaluate a function elementwise.

By,

Friedrich

Ondrej Certik

unread,
May 6, 2008, 4:17:43 AM5/6/08
to sympy-...@googlegroups.com

Cool. Let's write a test for this and also let's rebase the lambda
arguments patch and push it in.

Ondrej

basti

unread,
May 6, 2008, 8:35:45 AM5/6/08
to sympy-patches
I'm not sure anymore if really all functions in sympy should be
vectorized. An alternative would be to add a new module like
"ndimensional", "ndim", "multidimensional" that provides all kind of
things for working in more dimensions. So for example matrices would
go in there and vectorize and, if someone writes it, vector and tensor
classes. Also special functions like dot, vectorproduct and so on
should be placed there.
The point would be, that all functions that are defined in
sympy.functions for scalars are in the scope sympy.ndim.functions in a
vectorized form. Also lambdify may get an own vectorized version.

What do you think?

Ondrej Certik

unread,
May 6, 2008, 8:44:19 AM5/6/08
to sympy-...@googlegroups.com

For me, any solution is fine as long as it is easy to maintain and it
doesn't break things.
So either way if fine.

Ondrej

Friedrich Hagedorn

unread,
May 6, 2008, 8:57:13 AM5/6/08
to sympy-...@googlegroups.com
On Tue, May 06, 2008 at 05:35:45AM -0700, basti wrote:
> I'm not sure anymore if really all functions in sympy should be
> vectorized.

Why do you think so? Your following ideas are "only" new structures, right?

> An alternative would be to add a new module like
> "ndimensional", "ndim", "multidimensional" that provides all kind of

I am for "ndim" because its the shortest.

> things for working in more dimensions. So for example matrices would
> go in there and vectorize and, if someone writes it, vector and tensor
> classes. Also special functions like dot, vectorproduct and so on
> should be placed there.
> The point would be, that all functions that are defined in
> sympy.functions for scalars are in the scope sympy.ndim.functions in a
> vectorized form. Also lambdify may get an own vectorized version.

This sounds not bad, but what is your real problem?

> What do you think?

I like the way you did it before. Maybe you could include the vectorize
methode in the Function-Class directly so that every function inherits
the ability for vectorizing?

I mean the "only" thing we need to take care are the signatures of the
functions:

scalar -> scalar
scalar -> vector
vector -> scalar
vector -> vector
...

By, Friedrich

basti

unread,
May 6, 2008, 9:40:42 AM5/6/08
to sympy-patches
> This sounds not bad, but what is your real problem?
My problem is that currently all functions that inherit from
sympy.core.function.Function are automatically vectorized although
there are cases where something else might be wanted. You may argue
that this is not the case for the built in functions (I'm not sure
about it, we have to look at every function separately), but as I said
before, there are functions that take something like a list and return
a scalar. These functions wouldn't be happy at all if they get the
list piecewise.

So as I see it we have basically three possible ways to go:

(1) Let all functions be vectorized and provide a way to PREVENT
vectorization of functions if the user doesn't want it.
>> MyFunction([x,y,z], vectorize=False)


(2) Let all functions be vectorized and provide a way to ACTIVATE
vectorization of functions if the user wants it.
>> MyFunction([x,y,z], vectorize=True)


(3) Define TWO versions of the functions. The scalar type in
sympy.functions and the vectorized form in sympy.ndim.functions.

I'm rating this possibilities in the following order: (3), (1), (2)
However, I really see benefits of way (1) but I don't like it that
there is a header that possibly slows down a bit, although it's not
always needed.
Aside from that, I think that version (3) is more transparent to
users, but that's only my opinion and depends highly on documentation.

Ondrej Certik

unread,
May 6, 2008, 10:03:59 AM5/6/08
to sympy-...@googlegroups.com

We also have

(4) -- redefine the __new__ for Functions that don't need vectorize
and don't call the decorator, thus all is fine.


I am for [4].

Ondrej

Friedrich Hagedorn

unread,
May 6, 2008, 12:14:37 PM5/6/08
to sympy-...@googlegroups.com
On Tue, May 06, 2008 at 06:40:42AM -0700, basti wrote:
> > This sounds not bad, but what is your real problem?
> My problem is that currently all functions that inherit from
> sympy.core.function.Function are automatically vectorized although
> there are cases where something else might be wanted. You may argue
> that this is not the case for the built in functions (I'm not sure
> about it, we have to look at every function separately), but as I said
> before, there are functions that take something like a list and return
> a scalar. These functions wouldn't be happy at all if they get the
> list piecewise.

Hm, maybe this is not a problem. Take for example the dot function:

dot : list x list --> scalar

So the dot function should only accept two lists as arguments. The job for
the generalised verctorize decorator would be to take two lists of lists

[listA1, listA2, ...] x [listB1, listB2, ...] --> [scalar1, scalar2, ...]

and return a list of scalars. If we can implement this behaviour for
vectorize then I hope everything is fine, right? Or didnt I catch the
problem?

By,

Friedrich

Friedrich Hagedorn

unread,
May 6, 2008, 3:56:55 PM5/6/08
to sympy-...@googlegroups.com

To get this working the sympy.vectorize should behave like numpy.vectorize.
For now sympy.vectorize behave like this:

In [49]: prod = lambda x,y: x*y

In [50]: vprod = vectorize(0,1)(prod)

In [51]: vprod([2,3], [x,y])
Out[51]: [[2*x, 2*y], [3*x, 3*y]]

The result is ok, but the input for numpy is different:

In [52]: import numpy

In [53]: nvprod = numpy.vectorize(prod)

In [54]: X,Y = numpy.meshgrid([2,3], [x,y])

In [55]: X
Out[55]:
[[2 3]
[2 3]]

In [56]: Y
Out[56]:
[[x x]
[y y]]

In [57]: nvprod(X, Y)
Out[57]:
[[2*x 3*x]
[2*y 3*y]]


I think sympy.vectorize should do the same thing for elementwise
evaluation. And then we can have an generalised vectorize for all
functions. What do you mean?

By, Friedrich

basti

unread,
May 6, 2008, 4:07:20 PM5/6/08
to sympy-patches
There is simply no way for vectorize to know how to deal with nested
lists in general without giving it more information.
Some examples to show this:
A = [[1,2],
[3,4]]

We want sin to be applied element wise:
>> sin(A)
[[sin(1),sin(2)],
[sin(3),sin(4)]]

You proposed that for dot: (I hope I understood it right)
>> dot(A,A)
[5,25]

But I'm also for:
>> trace(A)
5
>> det(A)
-2

I think it's not possible to find a general rule for vectorization for
all functions. Thus, as Ondrej pointed out, we should use the current
implementation of vectorize so that most things work as expected. For
functions that don't fall into this scheme, the __new__ function has
to be rewritten.
Maybe we should improve the vectorize function so that one can easily
define that for example the elements the function should be applied
are the innermost lists or something like that.
Another, I think useful improvement would be the possibility, to pass
a function as creation argument and vectorize then returns a function
that is vectorized in all arguments (or maybe only the first). This
would look nicer to use for users:
>> def mycoolfunction(x,y): ...
>> vectorize(mycoolfunction)

Sebastian

Friedrich Hagedorn

unread,
May 6, 2008, 4:08:22 PM5/6/08
to sympy-...@googlegroups.com

which patch do you mean? The lambdify_change_order.patch from Sebastian?

Friedrich

basti

unread,
May 6, 2008, 4:10:34 PM5/6/08
to sympy-patches
Didn't see your answer before, but it's nice that we have the same
opinion:

""
To get this working the sympy.vectorize should behave like
numpy.vectorize.
""

""
pass a function as creation argument and vectorize then returns a
function
that is vectorized in all arguments (or maybe only the first).
""

;)
Sebastian

Friedrich Hagedorn

unread,
May 6, 2008, 4:24:17 PM5/6/08
to sympy-...@googlegroups.com
On Tue, May 06, 2008 at 01:10:34PM -0700, basti wrote:
>
> Didn't see your answer before, but it's nice that we have the same
> opinion:

Yes, thats good :-)

> ""
> To get this working the sympy.vectorize should behave like
> numpy.vectorize.
> ""
>
> ""
> pass a function as creation argument and vectorize then returns a
> function
> that is vectorized in all arguments (or maybe only the first).
> ""

But there is a difference, Out[4] and Out[7] are not the same:

In [1]: import numpy

In [2]: prod = lambda x,y: x*y

In [3]: prod = vectorize(0,1)(prod)

In [4]: prod([2,3], [x,y])
Out[4]: [[2*x, 2*y], [3*x, 3*y]]

In [5]: prod = lambda x,y: x*y

In [6]: prod = num
numbers numerics numpy

In [6]: prod = numpy.ve

In [6]: prod = numpy.vectorize(prod)

In [7]: prod([2,3], [x,y])
Out[7]: [2*x 3*y]

Thats why, numpy uses the meshgrid matrices. Should we port meshgrid to
sympy?

Friedrich

basti

unread,
May 6, 2008, 4:46:50 PM5/6/08
to sympy-patches
You are right, they behave differently. But now I'm not so sure
anymore if I want the same behaviour in sympy. It is for example not
so useful for the diff function, since the two Arguments have to have
the same shape (or one has to be a scalar).

In [1]: from sympy import *

In [2]: import numpy

In [3]: var("x y")
Out[3]: (x, y)

In [4]: prod = lambda x,y:x*y

In [5]: numpy.vectorize(prod)
Out[5]: <numpy.lib.function_base.vectorize object at 0x8696e8c>

In [6]: f = numpy.vectorize(prod)

In [7]: f(2, [1,2])
Out[7]: array([2, 4])

In [8]: f([2,3,4], [1,2])
---------------------------------------------------------------------------
<type 'exceptions.ValueError'> Traceback (most recent call
last)

/home/sebastian/workspace/sympy/sympy/utilities/<ipython console> in
<module>()

/usr/lib/python2.5/site-packages/numpy/lib/function_base.py in
__call__(self, *args)
981 newargs =
[array(arg,copy=False,subok=True,dtype=object) for arg in args]
982 if self.nout == 1:
--> 983 _res = array(self.ufunc(*newargs),copy=False,
984 subok=True,dtype=self.otypes[0])
985 else:

<type 'exceptions.ValueError'>: shape mismatch: objects cannot be
broadcast to a single shape

In [9]: diff([(x+y)**2, (x+y), 1], [x,y])
Out[9]: [[2*x + 2*y, 2*x + 2*y], [1, 1], [0, 0]]

Friedrich Hagedorn

unread,
May 6, 2008, 4:58:47 PM5/6/08
to sympy-...@googlegroups.com
On Tue, May 06, 2008 at 01:07:20PM -0700, basti wrote:
>
> There is simply no way for vectorize to know how to deal with nested
> lists in general without giving it more information.
[...]

> I think it's not possible to find a general rule for vectorization for
> all functions.

But I think this should be the final solution. The only way to get out
of this problem is to use different objects for the different things
(as we decided before). Then the evaluation of (matrix-like) nested lists
should be unique for vectorize:

# vec is the new proposed object
dot([vec1, vec2], [vec3, vec4]) == [dot(vec1, vec3), dot(vec2, vec4)]

> Thus, as Ondrej pointed out, we should use the current
> implementation of vectorize so that most things work as expected. For
> functions that don't fall into this scheme, the __new__ function has
> to be rewritten.

That sounds good. And if we have the vector-object we dont need to
rewrite any __new__ function (I hope).

> Maybe we should improve the vectorize function so that one can easily
> define that for example the elements the function should be applied
> are the innermost lists or something like that.

Maybe, I am unsure. Lets try to implement the Vector, Matrix object
at first.

> Another, I think useful improvement would be the possibility, to pass
> a function as creation argument and vectorize then returns a function
> that is vectorized in all arguments (or maybe only the first). This
> would look nicer to use for users:
> >> def mycoolfunction(x,y): ...
> >> vectorize(mycoolfunction)

You mean a wrapper around vectorize

def vectorize(func, *args):
return Vectorize(*args)(func)

? (then the capital 'Vectorize' should the class name)

Friedrich

Friedrich Hagedorn

unread,
May 6, 2008, 5:07:46 PM5/6/08
to sympy-...@googlegroups.com
On Tue, May 06, 2008 at 01:46:50PM -0700, basti wrote:
>
> You are right, they behave differently. But now I'm not so sure
> anymore if I want the same behaviour in sympy. It is for example not
> so useful for the diff function, since the two Arguments have to have
> the same shape (or one has to be a scalar).
>
> In [1]: from sympy import *
>
> In [9]: diff([(x+y)**2, (x+y), 1], [x,y])
> Out[9]: [[2*x + 2*y, 2*x + 2*y], [1, 1], [0, 0]]

You are right. Its easier not to use the numpy.meshgrid here. And I
have tested the numpy.meshgrid didnt solve our nested list problem :-(

Friedrich

basti

unread,
May 6, 2008, 5:32:11 PM5/6/08
to sympy-patches
>> You mean a wrapper around vectorize
No I don't mean an extra wrapper, I intended to replace the __init__
method by a __new__ method and then return the vectorized function
instantly. But this is implementation detail.

I'm somehow loosing track of what we have agreed on and what not.
Maybe we should start a wiki page and collect everything where we
found consent.

Sebastian

Ondrej Certik

unread,
May 6, 2008, 6:22:13 PM5/6/08
to sympy-...@googlegroups.com


Yeah me too. Feel free to start the wiki page.

Ondrej

Friedrich Hagedorn

unread,
May 7, 2008, 5:44:29 AM5/7/08
to sympy-...@googlegroups.com

Hm, but now I have one thing against the behavior in [9] again. What is one
would only this behaviour:

>>> diff([(x+y)**2, (x+3*y), z**2], [x,y,z])
[2*x + 2*y, 3, 2*z]

So that every item-pair should be evaluted. Then the current solution in
[9] does a littel bit too much. The way out would be to take a meshgrid
port again?

Friedrich

Friedrich Hagedorn

unread,
May 7, 2008, 6:53:29 AM5/7/08
to sympy-...@googlegroups.com
On Tue, May 06, 2008 at 02:32:11PM -0700, basti wrote:
> I'm somehow loosing track of what we have agreed on and what not.
> Maybe we should start a wiki page and collect everything where we
> found consent.

I can not start a new wiki page. Thats why I try to sum up my opinion
here.

*********************************************************************

(1) The argument of a function should be a matrix:

>>> M = Matrix(1,3,[x,y,z])
>>> f = Function('f')
>>> f(M)
f(M)


(1a) With this possibility a convenient vector object could be created:

>>> r = vector(M)
>>> (r[0], r[1], r[2]) == (r.x, r.y, r.z)
True
>>> f(r)
f(r)

I would like to have this r.x syntax because in manualy calculation one
would write r := (r_x, r_y, r_z).


(1b) Then the derivative should be generalised (jacobian matrix)

>>> diff(r.x**2 + r.y**2 + r.z**2, r)
[2*r.x, 2*r.y, 2*r.z]

The result should be a matrix (or a vector in this simpel case).
With this matrix syntax the div, grad, rot operators could be implemented
(Ondrej wrote a script in #823).


(2) All multiple elementwise evaluation of a function should be done
with the vectorize function.


(2a) And I think it's better if sympy.vectorize behaves exactly like
numpy.vectorize because I want this

In [29]: import numpy

In [30]: f=Function('f')

In [31]: F=numpy.vectorize(f)

In [32]: F([x,y], [2,3])
Out[32]: [f(x, 2) f(y, 3)]

And not this

In [33]: F=vectorize(0,1)(f)

In [34]: F([x,y], [2,3])
Out[34]: [[f(x, 2), f(x, 3)], [f(y, 2), f(y, 3)]]


(2b) For the nested evaluations like in [34] the meshgrid should be used

In [36]: F=numpy.vectorize(f)

In [37]: X, Y = numpy.meshgrid([x,y], [2,3])

In [38]: F(X, Y)
Out[38]:
[[f(x, 2) f(y, 2)]
[f(x, 3) f(y, 3)]]


(2c) With the suggestions above the multiple evaluation of functions
with vector arguments should'nt be a problem

>>> v1 = vector(1,2)
>>> dot(v1, v1)
5
>>> dot = vectorize(dot)
>>> v2 = vector(1,3)
>>> dot([v1, v2], [v1, v2])
[5, 10]

*********************************************************************

I hope I explaind my thoughts clearly. What do you think?

By,

Friedrich

Reply all
Reply to author
Forward
0 new messages