In [1]:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import odeint
from IPython.html.widgets import interact, fixed
Euler's method is the simplest numerical approach for solving a first order ODE numerically. Given the differential equation
$$ \frac{dy}{dx} = f(y(x), x) $$with the initial condition:
$$ y(x_0)=y_0 $$Euler's method performs updates using the equations:
$$ y_{n+1} = y_n + h f(y_n,x_n) $$$$ h = x_{n+1} - x_n $$Write a function solve_euler
that implements the Euler method for a 1d ODE and follows the specification described in the docstring:
In [2]:
def solve_euler(derivs, y0, x):
"""Solve a 1d ODE using Euler's method.
Parameters
----------
derivs : function
The derivative of the diff-eq with the signature deriv(y,x) where
y and x are floats.
y0 : float
The initial condition y[0] = y(x[0]).
x : np.ndarray, list, tuple
The array of times at which of solve the diff-eq.
Returns
-------
y : np.ndarray
Array of solutions y[i] = y(x[i])
"""
h=x[-1]-x[-2]
data = [y0]
for t in x[1:]:
data.append(data[-1]+h*derivs(data[-1],t))
return data
In [3]:
assert np.allclose(solve_euler(lambda y, x: 1, 0, [0,1,2]), [0,1,2])
The midpoint method is another numerical method for solving the above differential equation. In general it is more accurate than the Euler method. It uses the update equation:
$$ y_{n+1} = y_n + h f\left(y_n+\frac{h}{2}f(y_n,x_n),x_n+\frac{h}{2}\right) $$Write a function solve_midpoint
that implements the midpoint method for a 1d ODE and follows the specification described in the docstring:
In [4]:
def solve_midpoint(derivs, y0, x):
"""Solve a 1d ODE using the Midpoint method.
Parameters
----------
derivs : function
The derivative of the diff-eq with the signature deriv(y,x) where y
and x are floats.
y0 : float
The initial condition y[0] = y(x[0]).
x : np.ndarray, list, tuple
The array of times at which of solve the diff-eq.
Returns
-------
y : np.ndarray
Array of solutions y[i] = y(x[i])
"""
h=x[-1]-x[-2]
data = [y0]
for t in x[1:]:
data.append(data[-1]+h*derivs(data[-1]+h/2*derivs(data[-1],t),t+h/2))
return data
In [5]:
assert np.allclose(solve_euler(lambda y, x: 1, 0, [0,1,2]), [0,1,2])
You are now going to solve the following differential equation:
$$ \frac{dy}{dx} = x + 2y $$which has the analytical solution:
$$ y(x) = 0.25 e^{2x} - 0.5 x - 0.25 $$First, write a solve_exact
function that compute the exact solution and follows the specification described in the docstring:
In [6]:
def solve_exact(x):
"""compute the exact solution to dy/dx = x + 2y.
Parameters
----------
x : np.ndarray
Array of x values to compute the solution at.
Returns
-------
y : np.ndarray
Array of solutions at y[i] = y(x[i]).
"""
data = np.array(.25*np.exp(2*x)-.5*x-.25)
return data
In [7]:
assert np.allclose(solve_exact(np.array([0,1,2])),np.array([0., 1.09726402, 12.39953751]))
In the following cell you are going to solve the above ODE using four different algorithms:
odeint
Here are the details:
derivs
function for the above differential equation.solve_euler
, solve_midpoint
, odeint
and solve_exact
functions to compute
the solutions using the 4 approaches.Visualize the solutions on a sigle figure with two subplots:
Your visualization should have legends, labeled axes, titles and be customized for beauty and effectiveness.
While your final plot will use $N=10$ points, first try making $N$ larger and smaller to see how that affects the errors of the different approaches.
In [46]:
x = np.linspace(0,1,11)
def derivs(y, x):
dy = x+2*y
return dy
euler_error = np.array(solve_euler(derivs, 0, x))-solve_exact(x)
midpoint_error = np.array(solve_midpoint(derivs, 0, x))-solve_exact(x)
odeint_error = np.array(odeint(derivs, 0, x)).flatten()-solve_exact(x)
f = plt.figure(figsize = (9,6))
ax = plt.subplot(211)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')
plt.plot(x,solve_euler(derivs, 0, x), label="Euler")
plt.plot(x,solve_midpoint(derivs, 0, x), label="Midpoint")
plt.plot(x,solve_exact(x), label="Exact")
plt.plot(x,odeint(derivs, 0, x), label="ODEInt")
plt.ylabel("y(x)")
plt.xlabel("x")
plt.title(r"Numerical Solutions to $\frac{dy}{dx}=x+2y$")
plt.legend(loc = "best")
ax = plt.subplot(212)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')
plt.plot(x,abs(euler_error), label = "Euler Error")
plt.plot(x,abs(midpoint_error), label = "Midpoint Error")
plt.plot(x,abs(odeint_error), label = "ODEInt Error")
plt.ylabel("Errors")
plt.xlabel("x")
plt.title(r"Errors of numerical solutions to $\frac{dy}{dx}=x+2y$")
plt.legend(loc = "best")
plt.tight_layout()
In [9]:
assert True # leave this for grading the plots
In [ ]: