## Creating a function from a symbolic expression
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``).


```python
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 )}
$$


```python
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:


```python
expr.subs({x: 17, y: 42}).evalf()
```

However, if we need to do this repeatedly it can be quite slow:


```python
%timeit expr.subs({x: 17, y: 42}).evalf()
```

even compared to a simple lambda function:


```python
import math
f = lambda x, y: 3*x**2 + math.log(x**2 + y**2 + 1)
f(17, 42)
```


```python
%timeit f(17, 42)
```

SymPy can also create a function analogous to f above. The function for doing so is called ``lambdify``:


```python
g = sym.lambdify([x, y], expr, modules=['math'])
g(17, 42)
```


```python
%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:


```python
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](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) (handling of different shapes) then works as expected:


```python
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``:


```python
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))
```

## Exercise: Create a function from a symbolic expression
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$.


```python
xplot = np.outer(np.linspace(-5, 5, 100), np.ones(100))
yplot = xplot.T
```


```python
%load_ext scipy2017codegen.exercise
```

*Use either the *``%exercise``* or *``%load``* magic to get the exercise / solution respecitvely*


```python
%exercise exercise_lambdify_expr.py
```

Replace **???** with the correct expression above.


```python
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
%matplotlib inline
```


```python
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))
```


```python

```

328a626c67fde65d4aee2644003c302fd85af33a
ipynb
Jupyter Notebook
notebooks/22-lambdify.ipynb
gvvynplaine/scipy-2017-codegen-tutorial
4bd0cdb1bdbdc796bb90c08114a00e390b3d3026
BSD-3-Clause
2017-05-31T21:01:22.000Z
2022-02-14T04:26:01.000Z
notebooks/22-lambdify.ipynb
gvvynplaine/scipy-2017-codegen-tutorial
4bd0cdb1bdbdc796bb90c08114a00e390b3d3026
BSD-3-Clause
2017-06-06T19:05:04.000Z
2018-08-29T23:15:19.000Z
notebooks/22-lambdify.ipynb
gvvynplaine/scipy-2017-codegen-tutorial
4bd0cdb1bdbdc796bb90c08114a00e390b3d3026
BSD-3-Clause
2017-06-06T14:45:12.000Z
2021-09-25T14:11:06.000Z
Qwen/Qwen-72B
1. YES
2. YES