Derivatives Lab#
This lab allows students to experiment with different differentiation methods, including symbolic, finite difference, complex step differentiation, and automatic differentiation.
Symbolic Differentiation with SymPy#
We first try out symbolic differentiation using scipy
.
This is very similar to the class notes, except we will take derivatives of the \(\text{sinc}(x) = \sin(x) / x\).
# HANDSON: make sure that sympy is installed
!pip install sympy
import sympy as sp
Collecting sympy
Downloading sympy-1.13.3-py3-none-any.whl.metadata (12 kB)
Collecting mpmath<1.4,>=1.1.0 (from sympy)
Downloading mpmath-1.3.0-py3-none-any.whl.metadata (8.6 kB)
Downloading sympy-1.13.3-py3-none-any.whl (6.2 MB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/6.2 MB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.2/6.2 MB 88.7 MB/s eta 0:00:00
?25hDownloading mpmath-1.3.0-py3-none-any.whl (536 kB)
?25l ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/536.2 kB ? eta -:--:--
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 536.2/536.2 kB 45.1 MB/s eta 0:00:00
?25h
Installing collected packages: mpmath, sympy
Successfully installed mpmath-1.3.0 sympy-1.13.3
# HANDSON: use sympy to define the equation f(x) = sin(x) / x ...
x = ...
f = ...
# HANDSON: ... and get its derivative
f_prime = ...
f_prime_simplified = ...
# Display f'(x)
f_prime
Ellipsis
# Display simplified f'(x)
f_prime_simplified
Ellipsis
# We didn't do this in the notes,
# but it is possible to obtain numerical values of functions from sympy.
display(f.evalf(subs={x:1}))
display(f_prime_simplified.evalf(subs={x:1}))
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[6], line 4
1 # We didn't do this in the notes,
2 # but it is possible to obtain numerical values of functions from sympy.
----> 4 display(f.evalf(subs={x:1}))
5 display(f_prime_simplified.evalf(subs={x:1}))
AttributeError: 'ellipsis' object has no attribute 'evalf'
# HANDSON: modify function `f(x)` and confirm that `sympy` is able to
# compute its derivatives
pass
To help visualize the results of derivative, let’s copy the plotting function from the class notes here:
import numpy as np
from matplotlib import pyplot as plt
from typing import Callable
def mkplot(g, X, Fp):
if isinstance(g, Callable):
f = g
else:
f = lambda x: g.evalf(subs={'x': x})
Xd = np.linspace(min(X), max(X), num=1001)
Fd = [f(x) for x in Xd]
plt.plot(Xd, Fd, lw=5, alpha=0.25)
for (x, fp) in zip(X, Fp):
y = f(x)
plt.plot(
[x-0.05, x+0.05],
[y-0.05*fp, y+0.05*fp],
)
X = range(10)
F_prime = [f_prime.evalf(subs={'x':x}) for x in X]
mkplot(f, X, F_prime)
Enhancing Dual Number Autodiff#
In the class, we implemented a Dual-number based autodiff scheme. It supports many basic operators except division and power.
Try implementing these extra operators and test them out with our visualization tool.
def V(x):
"""Select the value from a dual number.
Work for both python built-in numbers (often used in function) and dual numbers.
"""
if isinstance(x, Dual):
return x[0]
else:
return x
def D(x):
"""Select the derivative from a dual number.
Work for both python built-in numbers (often used in function) and dual numbers.
"""
if isinstance(x, Dual):
return x[1]
else:
return 0
class Dual(tuple):
"""Dual number for implementing autodiff in pure python"""
def __new__(self, v, d=1): # tuple is immutable so we cannot use __init__()
return tuple.__new__(Dual, (v, d))
def __add__(self, r):
return Dual(
V(self) + V(r),
D(self) + D(r),
)
def __radd__(self, l):
return self + l # addition commutes
def __sub__(self, r):
return Dual(
V(self) - V(r),
D(self) - D(r),
)
def __rsub__(self, l):
return Dual(
V(l) - V(self),
D(l) - D(self),
)
def __mul__(self, r):
return Dual(
V(self) * V(r),
D(self) * V(r) + V(self) * D(r),
)
def __rmul__(self, l):
return self * l # multiplication commutes
def __truediv__(self, r):
return Dual(
V(self) / V(r),
..., # HANDSON: implement chain-rule for division
)
def __rtruediv__(self, l):
return Dual(
V(l) / V(self),
..., # HANDSON: implement chain-rule for division
)
def __pow__(self, r): # assume r is constant
if r == 0:
return ... # HANDSON: implement chain-rule for power
elif r == 1:
return ... # HANDSON: implement chain-rule for power
else:
return Dual(
V(self)**r,
..., # HANDSON: implement chain-rule for power
)
def sin(x):
return Dual(
np.sin(V(x)),
np.cos(V(x)) * D(x) # chain rule: d/dx sin(x) = cos(x) * x'
)
def f(x):
return (x**2 + 3) / x
# return sin(x) / x
X = np.linspace(1,2,num=11)
F, Fp = f(Dual(X))
mkplot(f, X, Fp)