In SymPy there is a function to create a Python function which evaluates (usually numerically) an expression. SymPy allows the user to define the signature of this function (which is convenient when working with e.g. a numerical solver in scipy
).
import sympy as sym
sym.init_printing()
We will look at an arbitrary expression $f(x, y)$:
$$ f(x, y) = 3 x^{2} + \log{\left (x^{2} + y^{2} + 1 \right )} $$
x, y = sym.symbols('x y')
expr = 3*x**2 + sym.log(x**2 + y**2 + 1)
expr
One way to evaluate above expression numerically is to invoke the subs
method followed by the evalf
method:
expr.subs({x: 17, y: 42}).evalf()
However, if we need to do this repeatedly it can be quite slow:
%timeit expr.subs({x: 17, y: 42}).evalf()
even compared to a simple lambda function:
import math
f = lambda x, y: 3*x**2 + math.log(x**2 + y**2 + 1)
f(17, 42)
%timeit f(17, 42)
SymPy can also create a function analogous to f above. The function for doing so is called lambdify
:
g = sym.lambdify([x, y], expr, modules=['math'])
g(17, 42)
%timeit g(17, 42)
Note how we specified modules
above: it tells lambdify
to use math.log
, if we don't specify modules SymPy will (since v1.1) use numpy
by default. This can be useful when dealing with arrays in the input:
import numpy as np
xarr = np.linspace(17, 18, 5)
h = sym.lambdify([x, y], expr)
out = h(xarr, 42)
out.shape
NumPy's broadcasting (handling of different shapes) then works as expected:
yarr = np.linspace(42, 43, 7).reshape((1, 7))
out2 = h(xarr.reshape((5, 1)), yarr) # if we would try to use g() here, it would fail
out2.shape
Behind the scenes lambdify
constructs a string representation of the Python code and uses Python's eval
function to compile the function.
Let's now look at how we can get a specific function signature from lambdify
:
z = z1, z2, z3 = sym.symbols('z:3')
expr2 = x*y*(z1 + z2 + z3)
func2 = sym.lambdify([x, y, z], expr2)
func2(1, 2, (3, 4, 5))
Plot $z(x, y) = \frac{\partial^2 f(x,y)}{\partial x \partial y}$ from above ($f(x, y)$ is available as expr
) as a surface plot for $-5 < x < 5, -5 < y < 5$.
xplot = np.outer(np.linspace(-5, 5, 100), np.ones(100))
yplot = xplot.T
%load_ext scipy2017codegen.exercise
Use either the %exercise
or %load
magic to get the exercise / solution respecitvely
# %exercise exercise_lambdify_expr.py
d2fdxdy = expr.diff(x, y) # EXERCISE: x, y
func = sym.lambdify([x, y], d2fdxdy) # EXERCISE: lambdify
zplot = func(xplot, yplot)
Replace ??? with the correct expression above.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(15, 13))
ax = plt.axes(projection='3d')
ax.plot_surface(xplot, yplot, zplot, cmap=plt.cm.coolwarm)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('$%s$' % sym.latex(d2fdxdy))