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.
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))
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.
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.
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'