5

I asked a question yesterday regarding differentiating a python function and then when I found that none of the answers posted were satisfactory for my need of evaluating (in some variables) and then plotting the derivative, I was able to figure out my own solution.

Previous code:

import sympy as sym
import math


def f(x,y):
    return x**2 + x*y**2


x, y = sym.symbols('x y')

def fprime(x,y):
    return sym.diff(f(x,y),x)

print(fprime(x,y)) #This works.

print(fprime(1,1)) 

New code:

import sympy as sym
import math


def f(x,y):
    return x**2 + x*y**2


x, y = sym.symbols('x y')

def fprime(x,y):
    return sym.diff(f(x,y),x)

print(fprime(x,y)) #This works.

DerivativeOfF = sym.lambdify((x,y),fprime(x,y),"numpy")

print(DerivativeOfF(1,1))

As you can see, I overcame the inability to evaluate the derivative fprime by creating a new function DerivativeOfF which was the "lambdified" version of fprime. From there, I was able to evaluate DerivativeOfF, and also plot it in one of the variables.

My question is: why did this work? What exactly have I done? And what are some downsides to this method? I have tried reading the lambdify documentation but it's extremely confusing to me (I'm a beginner in Python). My guess is that I converted the Python function fprime to a Sympy expression DerivativeOfF, or something like that. Any help explaining what happened and why, and what exactly lambdify does (in layman's terms), would be appreciated.

  • It's probably because the first version substitutes x and y first and then tries to make derivative. – enedil May 31 '17 at 12:29
  • Your `fprime` is a `sympy.Add` object. It is not a Python function (a 'callable'). You have use to `sympy` provided methods or functions to 'evaluate' it. `lambdify` is one such function. `.subs` is another, try: `fp.sums([(x,1), (y,2)])`. I have feeling you are trying to use `sympy` without reading much of its tutorial. Don't confuse `sympy` with basic Python or numpy. – hpaulj May 31 '17 at 16:31
  • @hpaulj You're right that I've dove into sympy without reading all of its tutorial. I get very confused with even the basics of much of these tutorials. Take lambdify for instance: I can't understand a word of its tutorial page. I feel like sometimes it's better to dive into the code especially if you're confused by reading about it. When I get confused and the tutorials aren't helping, that's when I ask a question here. –  May 31 '17 at 16:58
  • @hpaulj What does my lambdify code above do? Does it really convert sympy functions into their numpy counterparts? If that's true, why am I then able to differentiate the lambdify result using sympy, if numpy functions can't be differentiated? Thanks for your time. –  May 31 '17 at 17:00
  • `f` is the Python function. `sym.diff` turned `f` into a symbolic object, and applied symbolic differentiation. The result is a sympy object. `lambdify` creates a Python function from that, the equivalent of `def(a,b): return 2*a + b**2`. – hpaulj May 31 '17 at 17:17
  • @hpaulj Let me change the example. Take `f(x,y)` to be the Python function returning `sym.cos(x) + sym.sin(y)`. If I understand correctly, `flambdify = sym.lambdify((x,y), f(x,y), "numpy")` would make it so `flambdify(x,y) = np.cos(x) + np.sin(y)`. Is this correct? If so, why can I still use `sym.diff` on `flambdify` if it is now composed of numpy sine and cosine, which supposedly can't be differentiated? –  May 31 '17 at 17:22
  • 1
    Correction. `f` is a Python function. `f(x,y)` is a sympy object. It was created implicitly by the `sym.symbols('x y')`. `sym.diff` uses `f(x,y)`, not `f`. You can't call `f(x,y)`, but you can `lambdify` it. – hpaulj May 31 '17 at 17:37
  • @hpaulj Thank you for correcting me. So you can lambdify `f(x,y)`, which will turn any `sym` expressions into, say, `np` expressions. Why can you `sym.differentiate` the lambdified version of `f(x,y)` if it is now composed of `np` expressions? –  May 31 '17 at 17:39

3 Answers3

3

Let's see if I can illustrate this action. I known Python and numpy well, but haven't used sympy much (but have used other symbolic algebra packages like macsyma).

In a ipython numpy session:

In [1]: def f(x,y):
   ...:     return x**2 + x*y**2
   ...: 
In [2]: f(1,3)
Out[2]: 10
In [3]: f(np.arange(1,4), np.arange(10,13))
Out[3]: array([101, 246, 441])

f is a python function; what it returns depends on how the inputs handle operations like *,** and +. Scalars and arrays work. Lists handle + and * (concatenate, replicate) but not **.

In [4]: import sympy as sym
In [5]: x, y = sym.symbols('x y')
In [6]: type(x)
Out[6]: sympy.core.symbol.Symbol
In [7]: x+y
Out[7]: x + y
In [8]: type(_)
Out[8]: sympy.core.add.Add

Defining symbols creates a couple of new objects. They handle + etc in their own symbolic way.

In [9]: fsym = f(x,y)
In [10]: type(fsym)
Out[10]: sympy.core.add.Add
In [11]: print(fsym)
x**2 + x*y**2

Calling f with these 2 symbol objects creates a new sym object. I can also call it with other combinations of symbols and numbers or even arrays.

In [12]: f(x,0)
Out[12]: x**2
In [13]: f(1,x)
Out[13]: x**2 + 1
In [14]: f(np.arange(3), x)
Out[14]: array([0, x**2 + 1, 2*x**2 + 4], dtype=object)

If I pass this Add object to sym.diff I get a new Add object

In [15]: fprime = sym.diff(fsym,x)
In [16]: fprime
Out[16]: 2*x + y**2

Neither fsym nor fprime are callable. They are not Python functions. fsym(1,2) does not work.

But fsym.subs can be used to replace x or/and y with other values, whether numbers or other symbols:

In [19]: fsym.subs(x,1)
Out[19]: y**2 + 1
In [20]: fsym.subs(y,2*x)
Out[20]: 4*x**3 + x**2
In [21]: fsym.subs([(x,1),(y,2)])
Out[21]: 5
In [22]: fprime.subs([(x,1),(y,2)])
Out[22]: 6

lambdify is a sympy function that takes a sympy object and returns a Python function, possibly numpy compatible`.

In [24]: fl = sym.lambdify((x,y), fsym, "numpy")
In [25]: fl
Out[25]: <function numpy.<lambda>>
In [26]: fl(1,2)
Out[26]: 5
In [27]: fl(np.arange(1,4), np.arange(10,13))   # cf with f(same) above
Out[27]: array([101, 246, 441])

This fl function is similar to the original f. It's not identical, for example it has a help/doc expression.

lambdify applied to fprime does the same thing, but with a different symbolic expression:

In [28]: fpl = sym.lambdify((x,y), fprime, "numpy")
In [29]: fpl(1,2)
Out[29]: 6
In [30]: fpl(np.arange(1,4), np.arange(10,13))
Out[30]: array([102, 125, 150])

This transparency between python/numpy functions or expressions and sympy ones has limits. The other (deleted) answer tried to explore those. For example there's a difference between math.sin, numpy.sin and sym.sin.

In these examples, differentiation is done symbolically by the sym.diff function.

In [35]: fsym
Out[35]: x**2 + x*y**2
In [36]: fprime
Out[36]: 2*x + y**2

sym.lambdify is just a way of converting either of these sympy objects into a Python function.

trig example

Picking up the example in the discussion for the other answer

Defining a function that uses the sym versions of sin/cos:

In [53]: def f1(x,y):
    ...:     return sym.sin(x) + x*sym.sin(y)
    ...: 
In [54]: f1(x,y)
Out[54]: x*sin(y) + sin(x)
In [55]: f1(1,2)
Out[55]: sin(1) + sin(2)
In [56]: f1(1, np.arange(3)
...
SympifyError: Sympify of expression 'could not parse '[0 1 2]'' failed, because of exception being raised:
SyntaxError: invalid syntax (<string>, line 1)

I think that because sym.sin(<array>) does not work; it would have to be np.sin(...), but that doesn't work with symbols.

As before we can take symbolic derivatives:

In [57]: sym.diff(f1(x,y),x)
Out[57]: sin(y) + cos(x)
In [58]: sym.diff(f1(x,y),y)
Out[58]: x*cos(y)
In [59]: sym.diff(sym.diff(f1(x,y),x),y)
Out[59]: cos(y)

Again none of these a functions. Evaluation has to be done with subs or lambdify.

In [60]: f2 = sym.lambdify((x,y),f1(x,y),"numpy")
In [61]: f2
Out[61]: <function numpy.<lambda>>
In [62]: f2(1, np.arange(3))
Out[62]: array([ 0.84147098,  1.68294197,  1.75076841])

I can evaluate f2 with array inputs, where as I couldn't with f1. Presumably sympy as substituted np.sin for sym.sin.

In fact when I try to evaluate f2 with a symbol, numpy complains:

In [63]: f2(1,y)
...
/usr/local/lib/python3.5/dist-packages/numpy/__init__.py in <lambda>(_Dummy_30, _Dummy_31)
AttributeError: 'Symbol' object has no attribute 'sin'

In [66]: sym.diff(f2(x,y),x)
 ....
AttributeError: 'Symbol' object has no attribute 'sin'
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • I have yet to read this answer fully but I'm touched that you went through the trouble of writing this up. Thank you very much. I will be reading it and I'll comment if I have any questions. –  May 31 '17 at 22:46
  • I just finished reading your excellent answer. About the example at the end: you say "presumably `sympy` has substituted `np.sin` for `sym.sin`." It seems this should be the case, but we can still apply `sym.diff` to `f2`, which makes me think something else must be going on. Do you know why we can still apply `sym.diff` to `f2` if `f2` should now be composed of numpy functions due to lambdify? –  May 31 '17 at 23:03
  • I get the same error. `sym.diff` doesn't operate on a Python or numpy function, but on a `sym` object, which can be produced by passing symbols to a function (that's not the only way). `f2` does not work with Symbols because of this `np.sin` use. – hpaulj Jun 01 '17 at 01:17
1

In your example sym.lambdify created a python function which looks like

def DerivativeOff(x,y):
    return 2*x + y**2

The numpy backend here does not play any role because multiplication, addition and power taking are original python functions. Thus you can pass any argument to DerivativeOff, especially also sympy symbols. Try DeravativeOff(x,y) at the end of your code.

Now the situation changes if your function contains more complicated expressions which python itself can not handle. Take the following example:

def f2(x,y):
    return sym.sin(x)*y

def fprime2(x,y):
    return sym.diff(f2(x,y),x)

DerivativeOff2 = sym.lambdify((x,y), fprime2(x,y), "numpy")

print(DerivativeOff2(1,2)) #will return a number

print(DerivativeOff2(x,y)) #will give an error

In this example lambdify needs to replace the sin function with some non-standard python. To do so it will resort to numpy (you specified "numpy" for exactly this case). Thus DerivatifeOff2 will look like

def DerivativeOff2(x,y):
    return numpy.cos(x)*y

And clearly numpy can not handle sympy symbols...

Now if you only want to plot, sympy has some plotting module (relying on matplotlib): http://docs.sympy.org/latest/modules/plotting.html

You can even make 3d-plots with that.

EDIT: The following works, too:

import sympy as sym
def f(x,y):
    return sym.sin(x) + x*sym.sin(y)
def fprime(x,y):
    return sym.diff(f(x,y),y)
x, y = sym.symbols('x y')
print(fprime(1,y)) #works perfectly fine
print(fprime(x,1)) #does not work because it would mean to derive with respect to 1
print(fprime(x,y).subs(y,1)) #works, derives with respect to y, then substitutes 1 for y
laolux
  • 1,445
  • 1
  • 17
  • 28
  • I read your answer multiple times. So basically you can't evaluate a sympy expression. Like if I said symcos = sym.cos(x), then I can't do symcos(1). But if I did npcos = np.cos(x), then python can compute npcos(1). And so lambdify will replace all of my sym. functions of the output with np. functions, which then allows me to evaluate them and plot them. And to fill in a (in my opinion) missing detail in your answer, fprime2(x,y) returns sym.cos(x)*y while DerivativeOff2(x,y) returns numpy.cos(x)*y. Is what I've said correct? –  May 31 '17 at 13:50
  • Now I'm doubting your answer because of the following: I've used this in a slightly more complicated way. I have f(x,y) = sym.sin(x) + x*sym.sin(y). I create the python function fprime(x,y) to return sym.diff(f(x,y),x), for example. Now if I want to take the derivative with respect to y of, say, fprime(1,y), I can't do this. But I CAN do this if I lambdify fprime(x,y). Say: fPRIME(x,y) = lambdify((x,y),fprime(x,y),"numpy"). Then I can evaluate fPRIME(1,y), and even differentiate: fyPRIME = sym.diff(fPRIME(1,y),y). Why am I allowed to differentiate fPRIME(1,y) if the sym expressions are –  May 31 '17 at 14:26
  • ...now np expressions? –  May 31 '17 at 14:26
  • Yes, fprime2(x,y) does finally return sym.cos(x)*y. But I can not follow your example npcos=np.cos(x). What is x? It can not be a sympy symbol (numpy does not know about these), and if i set x=27 beforehand, then npcos=np.cos(27) and I can still not do npcos(1). – laolux May 31 '17 at 14:33
  • I really can not understand your second comment. Consider the following: [moved to my answer for proper display, see below EDIT] Certainly does work for me. – laolux May 31 '17 at 14:58
  • Let me play around with it a bit in like an hour and let you know. I want to see why what you've done doesn't work for what I'm doing. If it ends up working I will be a happy camper. Sorry about being so confusing. –  May 31 '17 at 15:04
  • I will try your solution with the (a,b) and (x,1) as soon as I can, but I'm still confused. Wouldn't fprime(x,1) will output sym.diff(f(x,1),1)? And then it would give me the ever present error "can't take 1st derivative of 1" or something like that. Why does replacing (x,y) in the definition with (a,b) fix this error for b=y=1? –  May 31 '17 at 15:08
  • But that doesn't fix my problem. I want to evaluate fprime(1,1) for example. This will output an error since the return is sym.diff(f(1,1),1). –  May 31 '17 at 15:31
  • Kind of moving away from the question, but would work as such: `fprime(x,y).subs(x,1).subs(y,1)`. This calculates fprime with symbols x and y and then replaces x by 1 and y by 1. – laolux May 31 '17 at 15:39
0

lambdify should be used if you want to convert a SymPy expression into a function that can be numerically evaluated. Since your interest here is doing a symbolic calculation (differentiation), you should not use lambdify (at this point in your calculations, anyway).

The problem lies in this code

x, y = sym.symbols('x y')

def fprime(x,y):
    return sym.diff(f(x,y),x)

When you set variable names to a function, like "def fprime(x, y)", these variable names effectively override the variable names of x and y defined above the function for any code inside the function. Thus, the code sym.diff(f(x,y),x) will not operate on the Symbol objects returned from symbols('x y'), but on whatever values are passed in to fprime. Of course, if you pass those objects in as the arguments, it will be the same, but you could pass in anything. It is exactly the same for f(x, y).

Instead, I would avoid using a function at all. Rather, create two symbolic expressions.

x, y = sym.symbols('x y')
expr = x**2 + x*y**2
fprime = diff(expr, x)

Now, to evaluate fprime at a number, you can use subs.

fprime.subs({x: 1, y: 1})

If at this point you want to create a fast function that uses NumPy to evaluate the expression to a number or array of numbers, this is where you would use lambdify.

f = lambdify((x, y), expr, 'numpy')
import numpy as np
f(np.array([1]), np.array([2]))

(also, as a general rule, if you lambdify an expression with 'numpy', you should be passing in NumPy arrays as the arguments to the lambdified function)

asmeurer
  • 86,894
  • 26
  • 169
  • 240
  • Thank you for your great answer. It seems like I will need to use lambdify rather than just subs because I want to, as you said, create a function so that I can then plot the derivative. –  Jun 01 '17 at 20:18
  • 1
    In that case, `lambdify` is what you want. But the general workflow with SymPy is to do all the symbolic computations first, then use lambdify to create a function for numeric computations once you have the expressions that you want. – asmeurer Jun 01 '17 at 22:18
  • Got it. Thank you for the invaluable comments. :) –  Jun 01 '17 at 23:32