Ask Your Question
1

Combination of lambda function and plot3d(?)

asked 2010-12-11 16:56:06 +0200

Mike Witt gravatar image

updated 2010-12-13 20:33:03 +0200

I'm having trouble understanding why the following code doesn't work like I'd hoped:

def test1():
    base_frame = point3d((.5,.5,1), color='red', size=10)
    def f(x,y,t): return(sin(pi*x)*sin(pi*y)*e^(-t))
    frames = []
    for t in [0,1]:
        pic = base_frame
        pic += plot3d(lambda x,y:f(x,y,t), (x, 0, 1), (y, 0, 1))  
        #It works if you uncomment the next line:
        #pic.save('/tmp/foo.png')
        frames += [pic,]
    return(frames)

What happens is that both the frames (or all of them if I create more than two) end up containing the last plot. It appears that the plot doesn't get "evaluated" when I want it to. So far, the only mechanism I've found that can force it to be evaluated is inserting a .show() or a .save(), which won't work to well in a larger loop.

Now, I understand that the function "f" I've got here could have been rendered as an expression rather than a function. Then there's no problem. But in the real program, things are more complicated, and (afict) I really need the lambda.

Can anyone help me understand what's happening here, and how I might get this to work?

---------- UPDATE -----------

I realize I haven't been all that clear (in my own mind, as well as in my explanation) of what the problem is here. I think there are two issues (1) Getting the "lambda" thing to work, and (2) getting the plot to "evaluate." Here are three updated test cases, which contain a function that actually requires the lambda: test1() does what I want, but at the price of having to save every frame to a file. test2() and test3() do get the plot3d function to evaluate, but in both of those cases, I lose the "lambda" functionality and so the function being plotted isn't right.

#
# Test file
#

# Make sure I'm using the same function in all three tests.
# I need a function that actually requires the use of lambda
var('x,y,t')
def f(x,y,t):
    if x < .5: return(sin(pi*x)*sin(pi*y))
    else: return(sin(pi*x)*sin(pi*y)*e^(-t))

# Might as well define the base frame here too
base_frame = point3d((.5,.5,1), color='red', size=10)

def test1():
    frames = []
    for t in [0,1]:
        pic = base_frame
        pic += plot3d(lambda x,y:f(x,y,t), (x, 0, 1), (y, 0, 1))
        # This "save" appears to force the line above to be "evaluated"
        pic.save('/tmp/foo.png')
        frames += [pic,]
    return(frames)

def test2():
    frames = []
    for t in [0,1]:
        pic = base_frame
        def g(x,y): return(f(x,y,t))
        pic += plot3d(g(x,y), (x, 0, 1), (y, 0, 1))
        frames += [pic,]
    return(frames)

def test3():
    frames = []
    for t in [0,1]:
        pic = base_frame
        ff = lambda t:lambda x,y:f(x,y,t)
        pic += plot3d(ff(t)(x,y), (x, 0, 1), (y ...
(more)
edit retag flag offensive close merge delete

Comments

In your situation, can you replace the line "pic += plot3d(lambda x,y:f(x,y,t), (x, 0, 1), (y, 0, 1))" with lines like "def g(x,y): return f(x,y,t); pic += plot3d(g(x,y), ...)"?

John Palmieri gravatar imageJohn Palmieri ( 2010-12-12 11:05:15 +0200 )edit

Well, I tried it. It appeared to do the same thing as using the lambda. That is, my function evaluated x and y OK, but it didn't force the plot to be evaluated.

Mike Witt gravatar imageMike Witt ( 2010-12-12 12:59:24 +0200 )edit

That's odd, because for me the two behave differently: if I enter your code in the notebook and then evaluate 'test1()[0]', I see different plots with your version vs. the version with "def g(x,y): ...".

John Palmieri gravatar imageJohn Palmieri ( 2010-12-12 21:58:26 +0200 )edit

It's possible that I'm not understanding what you suggested correctly. Or, that there's a difference between the command line and notebook? I updated the question, showing what I tried (test2). Let me know if this wasn't what you meant. Note that I'm doing this all in the command line.

Mike Witt gravatar imageMike Witt ( 2010-12-13 16:01:40 +0200 )edit

In your test2, change the line "pic += ..." to "pic += plot3d(g(x,y), ...)". That change works for me both in the notebook and from the command line. (Right now, you're defining g(x,y) but using it anywhere.)

John Palmieri gravatar imageJohn Palmieri ( 2010-12-13 17:51:23 +0200 )edit

4 Answers

Sort by ยป oldest newest most voted
0

answered 2010-12-22 03:44:14 +0200

Jason Grout gravatar image

updated 2010-12-22 03:47:22 +0200

The following example illustrates the problem Niles saw with using lambda. Notice that the t in the lambda is taken from the current value of t in the global scope:

sage: f=lambda x: (x,t)
sage: t=2
sage: f(1)
(1, 2)
sage: t=3
sage: f(1)
(1, 3)
sage:

See the first few answers of http://stackoverflow.com/questions/938429/scope-of-python-lambda-functions-and-their-parameters for more discussion. Here is one workaround from those answers:

sage: f=lambda x,t=t: (x,t)
sage: t=1
sage: f(1)
(1, 100)
edit flag offensive delete link more
1

answered 2010-12-14 19:46:35 +0200

Mike Witt gravatar image

updated 2010-12-14 19:47:20 +0200

OK, well I think I've found a way to get the plot to evaluate. Apparently the method json_repr() will do it (with much less overhead than actually saving to a file):

def test0():
    var('x,y,t')
    def f(x,y,t):
        if x < .5: return(sin(pi*x)*sin(pi*y))
        else: return(sin(pi*x)*sin(pi*y)*e^(-t))
    base_frame = point3d((.5,.5,1), color='red', size=10)
    frames = []
    for t in [0,1]:
        pic = base_frame
        pic += plot3d(lambda x,y:f(x,y,t), (x, 0, 1), (y, 0, 1))
        # This appears to force the line above to be "evaluated"
        foo = pic.json_repr(pic.default_render_params())
        frames += [pic,]
    return(frames)
edit flag offensive delete link more
0

answered 2010-12-13 22:33:47 +0200

niles gravatar image

updated 2010-12-13 22:54:58 +0200

This is quite puzzling; if you update f to the following, test3 now raises the runtime error (probably test2 too). It seems that the condition x > .5 is being evaluated while x is still a variable instead of a number . . .

def f(x,y,t):
    if x not in RR:
        raise RuntimeError("checking x too soon")
    if x > .5: 
        return(sin(pi*x)*sin(pi*y))
    else: 
        return(sin(pi*x)*sin(pi*y)*e^(-t))

I also noticed that the plot3d function seems to just call parametric_plot3d in the end, so maybe working with parametric_plot3d directly could help focus too.

At this stage I guess I'm inclined to suggest that you define a full-blown class to handle making these plots -- then the value of t can be an attribute of the class, as well as the appropriate function of x,y and the appropriate plot. If namespace issues are part of the problem, then making each frame a new instance of some general class will, I think, help to avoid them.

edit flag offensive delete link more

Comments

Well, it's already a class in the real code :-) But I basically agree. I need to rewrite the code with this issue in mind. It doesn't appear that there's going to be one or two line fix.

Mike Witt gravatar imageMike Witt ( 2010-12-14 12:10:23 +0200 )edit
2

answered 2010-12-12 08:45:02 +0200

niles gravatar image

updated 2010-12-13 22:49:06 +0200

I played with this a little, and I think I've been able to at least focus the problem a little more (using your f above):

sage: lambdas = [lambda x,y:f(x,y,t) for t in [0,1]]
sage: lambdas[0](.5,.5)
e^(-1)*sin(0.500000000000000*pi)^2
sage: lambdas[1](.5,.5)
e^(-1)*sin(0.500000000000000*pi)^2

So one aspect of the problem seems to be that the second lambda is overwriting the first..


UPDATE:

Oh wow -- this is a python bug! (Or is it a feature? PEP 227 seems to address something relevant; note especially the "Discussion" and "Examples" sections)

>>> lambdas = [lambda x,y: x*y*t for t in [1,2]]
>>> lambdas[0](2,3)
12
>>> lambdas[1](2,3)
12

UPDATE 2:

This morning I realized how to work around this problem: use nested lambda functions!

A python example:

>>> f = lambda t: lambda x,y: x*y*t
>>> f(2)(2,3)
12
>>> f(1)(2,3)
6

And a 3d plot example:

def f(x,y,t): return(sin(pi*x)*sin(pi*y)*e^(-t)) 
base_frame = point3d((.5,.5,1), color='red', size=10)
L = lambda t: lambda x,y: f(x,y,t)
var('x,y')
(x, y)
frames = [plot3d(L(t)(x,y), (x, 0, 1), (y, 0, 1))+base_frame for t in [0,1]]

frames[0]

image description

frames[1]

image description

edit flag offensive delete link more

Comments

Fantastic. That does solve the problem!

Mike Witt gravatar imageMike Witt ( 2010-12-13 15:42:21 +0200 )edit

I may have spoken too soon. I'm still trying to sort out the two different issues here. If you have a chance, please take a look at the newly updated ticket.

Mike Witt gravatar imageMike Witt ( 2010-12-13 20:38:06 +0200 )edit

Niles, is this really a Python bug, or is the behavior expected and documented? Lisp-y things sometimes are a little counterintuitive... You might also want to see if it's still in Python 2.7 or 3.x, just out of curiosity...

kcrisman gravatar imagekcrisman ( 2010-12-13 22:04:43 +0200 )edit

Even better, I think you could just do: lambda x,y,t=t: f(x,y,t) to capture the value of t when the lambda function is defined.

Jason Grout gravatar imageJason Grout ( 2010-12-22 03:45:00 +0200 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

1 follower

Stats

Asked: 2010-12-11 16:56:06 +0200

Seen: 1,080 times

Last updated: Dec 22 '10