Search code examples
pythonpython-3.xnumpysympylambdify

Can I use lambdify to evaluate the derivative of a python function?


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.


Solution

  • 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'