© 2018 Suzy Beeler. This work is licensed under a Creative Commons Attribution License CC-BY 4.0. All code contained herein is licensed under an MIT license
This exercise was generated from a Jupyter notebook. You can download the notebook here.
This tutorial will illustrate the concept of Taylor series expansion, where a given function can be approximated with a polynomial function. We will see that increasing the order of the polynomial results in ever better approximations.
# import the necessary modules
import numpy as np
import matplotlib.pyplot as plt
# allows us to compute factorials
import math
# For pretty plots
import seaborn as sns
rc={'lines.linewidth': 2, 'axes.labelsize': 14, 'axes.titlesize': 14, \
'xtick.labelsize' : 14, 'ytick.labelsize' : 14}
sns.set(rc=rc)
The Taylor series expansion of $e^x$ is given by
$$e^x = 1 \ + \ x + \ \frac{x^2}{2!} \ + \ \frac{x^3}{3!} \ + \ \frac{x^4}{4!} \ + \ . \ .\ . \ + \ \frac{x^n}{n!}. \tag{1}$$
We see that each new term in this series takes on the form of $\frac{x^n}{n!}$. This means we can iteratively determine the Taylor series expansion for an increasing number of terms. That is, we can use the $0$th order approximation to get the $1$st order approximation, and we can use the $1$st order approximation to get the $2$nd order approximation, and so on. To do this, we can store our $y$ values in a 2D array, where the rows correspond to the order of the polynomial we are using and the columns correspond to increasing values of $x$ for which we are computing.
# max number of terms we want to expand to
n_terms = 5
# range of x values to look at
x_vals = np.linspace(0,2,100)
# intialize the array of y_vals
y_vals = np.zeros([n_terms,len(x_vals)])
# loop through the number of terms
for n in range(n_terms):
# special case for 0th order
if n == 0:
y_vals[n,:] = np.ones(len(x_vals))
# otherwise nth order is n-1th order plus new term
else:
new_term = x_vals**n / math.factorial(n)
y_vals[n,:] = y_vals[n-1,:] + new_term
We can now loop through our $y$ values array to plot our approximations for an increasing number of terms. We can also plot the real function, $e^x$, for comparison.
# loop through number of terms and plot
for n in range(n_terms):
label = str(n) + " order"
plt.plot(x_vals,y_vals[n,:], label=label)
# plot the real function
plt.plot(x_vals, np.exp(x_vals), label="$e^x$")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
Nice! We see how increasing the order of our polynomial brings us ever closer to the true form of the $e^x$ function. Using this code a template, we can now compute the Taylor series expansion for a number of different functions. The only things we may need to change are the $0$th order term and the form of each new term.
The Taylor series expansion of $cos(x)$ is given by
$$cos(x) = 1 \ - \ \frac{x^2}{2!} \ + \ \frac{x^4}{4!} \ - \ \frac{x^6}{6!} \ + \ \frac{x^8}{8!} \ - \ . \ . \ . \ + \ (-1)^n \frac{x^{2n}}{(2n)!}. \tag{2}$$
# max number of terms we want to expand to
n_terms = 5
# range of x values to look at
x_vals = np.linspace(0,2*np.pi,100)
# intialize the y_vals
y_vals = np.zeros([n_terms,len(x_vals)])
# loop through the number of terms
for n in range(n_terms):
# special case for 0th order
if n == 0:
y_vals[n,:] = np.ones(len(x_vals))
# otherwise nth order is n-1th order plus new term
else:
new_term = (-1)**n * x_vals**(2*n) / math.factorial(2*n)
y_vals[n,:] = y_vals[n-1,:] + new_term
# loop through number of terms and plot
for n in range(n_terms):
label = str(n) + " order"
plt.plot(x_vals,y_vals[n,:], label=label)
# plot the real function
plt.plot(x_vals, np.cos(x_vals), label="$cos(x)$")
plt.xlabel("x")
plt.ylabel("y")
plt.ylim([-1.1,1.1]) # set y-axis to make it easier to see
plt.legend()
The Taylor series expansion of $ln(1+x)$ is given by
$$ \ln ( 1 + x ) = x \ - \ \frac { x ^ { 2 } } { 2 } \ + \ \frac { x ^ { 3 } } { 3 } \ - \ \frac { x ^ { 4 } } { 4 } \ + \ \frac { x ^ { 5 } } { 5 } \ - \ . \ . \ . \ + \ (-1)^{n} \frac{x^{n+1}}{n+1}. \tag{3}$$
# max number of terms we want to expand to
n_terms = 5
# range of x values to look at
x_vals = np.linspace(0,1,100)
# intialize the y_vals
y_vals = np.zeros([n_terms,len(x_vals)])
# loop through the number of terms
for n in range(n_terms):
# special case for 0th order
if n == 0:
y_vals[n,:] = x_vals
# otherwise nth order is n-1th order plus new term
else:
new_term = (-1)**n * x_vals**(n+1) / (n+1)
y_vals[n,:] = y_vals[n-1,:] + new_term
# loop through number of terms and plot
for n in range(n_terms):
label = str(n) + " order"
plt.plot(x_vals,y_vals[n,:], label=label)
# plot the real function
plt.plot(x_vals, np.log(1 + x_vals), label="$ln(1+x)$")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
The Taylor series expansion of $\frac{1}{1+x}$ is given by
$$ \frac { 1 } { 1 + x } \ = \ 1 \ - \ x \ + \ x ^ { 2 } \ - \ x ^ { 3 } \ + \ x^4 \ - \ . \ . \ . \ + \ (-1)^n x^n. \tag{4} $$
# max number of terms we want to expand to
n_terms = 5
# range of x values to look at
x_vals = np.linspace(0,1,100)
# intialize the y_vals
y_vals = np.zeros([n_terms,len(x_vals)])
# loop through the number of terms
for n in range(n_terms):
# special case for 0th order
if n == 0:
y_vals[n,:] = np.ones(len(x_vals))
# otherwise nth order is n-1th order plus new term
else:
new_term = (-1)**n * (x_vals**n)
y_vals[n,:] = y_vals[n-1,:] + new_term
# loop through number of terms and plot
for n in range(n_terms):
label = str(n) + " order"
plt.plot(x_vals,y_vals[n,:], label=label)
# plot the real function
plt.plot(x_vals, 1/(1+x_vals), label="$1/(1+x)$")
plt.xlabel("x")
plt.ylabel("y")
plt.ylim([0,1.1])
plt.legend()