Arlen
Arlen

Reputation: 6835

what's the reason for the unexpect behavior of plot()

def f(u):
    value = 0.0
    if u > -1 and u < 1:
        value = u * u
    return value

Given the above, the following produces the expected plot:

plot(f,(x,-5,5))

But plot(f(x),(x,-5,5)) just draws a horizontal line. Can anyone explain what's going on?

Upvotes: 1

Views: 126

Answers (3)

kcrisman
kcrisman

Reputation: 4402

Here is a (possibly) simpler solution for now, using lambdas.

sage: plot(lambda x:f(x), (x,-5,5))

plot of your function

Upvotes: 0

huon
huon

Reputation: 102166

Similar to what @Ignacio said, the cause is the function being called once. The problem with this vs other functions like sin is the conditional. The if statement is evaluated when the function is called and not preserved as a symbolic statement. That is, the u > -1 and u < 1[1] is evaluated on the first function call and result is treated accordingly (i.e. left at 0).

As an illustration of what is happening:

sage: x = var('x')
sage: print ":)" if x > 0 else ":("
:(

There is no way to get around this in general[2], because Python has to evaluate the condition in the if statement to work out which code path to take when the function is called.

Best case solution

There is a solution that should work (but doesn't yet). Sage provides Piecewise, so you can define f as:

f = Piecewise([((-5, -1), ConstantFunction(0)), 
               ((-1, 1),     x*x), 
               ((1, 5),   ConstantFunction(0))],
              x)

Unfortunately, the implementation of Piecewise is as yet incomplete, and severely lacking, so the only way to plot this seems to be:

f.plot()

(Limitations: trying to call f with a variable causes errors; it doesn't work with the conventional plot; you can't restrict the domain in Piecewise.plot, it plots the whole thing (hence why I restricted it to ±5); it doesn't cope with infinite intervals.)

Working solution

You could also just detect whether the argument to f is a number or variable and do the appropriate action based on that:

def f(u):
    try:
        float(u) # see it's a number by trying to convert
        return u*u if -1 < u < 1 else 0.0
    except TypeError: # the conversion failed
        if callable(f):
            return lambda uu: f(u(uu))
        else:
            return f

Note the callable call, it checks to see if u is a function (in some sense), if so returns the composition of f with u.

This version allows us to do things like:

sage: f(10)
0.0
sage: f(x)(0.5)
0.25
sage: f(x+3)(-2.2)
0.64

and it also works perfectly fine with plot, in either form. (Although it warns about DeprecationWarnings because of the u(uu) syntax; there are ways to get around this using u.variables but they are fairly awkward.)

Note: This "working" solution is quite fragile, and very suboptimal; the Piecewise version would be the correct solution, if it worked.

[1]: Python actually allows you to write this as -1 < u < 1. Pretty cool.

[2]: Although in some special cases you can, e.g. if you know x > 0, then you can use assume(x > 0) which means the example will print :).

Upvotes: 1

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798914

The former passes the function, allowing it to be called inside plot(). The latter calls the function once and passes the returned value, resulting in the same value each time.

Upvotes: 1

Related Questions