Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
restrepo
GitHub Repository: restrepo/ComputationalMethods
Path: blob/master/material/differential-equations.ipynb
919 views
Kernel: Python 3 (ipykernel)

Open In Colab

Differential Equations

Differential equations is without doubt one of the more useful branch of mathematics in science. They are used to model problems involving the change of some variable with respect to another. Differential equations cover a wide range of different applications, ranging from ordinary differential equations (ODE) until boundary-value problems involving many variables. For the sake of simplicity, throughout this section we shall cover only ODE systems as they are more elemental and equally useful. First, we shall cover first order methods, then second order methods and finally, system of differential equations.

See:

%pylab inline
Populating the interactive namespace from numpy and matplotlib
import numpy as np ## JSAnimation import available at https://github.com/jakevdp/JSAnimation #from JSAnimation import IPython_display from matplotlib import animation

Physical motivation

Momentum

In absence a forces a body moves freely to a constant velocity (See figure)

The quantity which can be associated with the change of speed of a body is the instantaneous momentum of a particle of mass mm moving with instantaneous velocity v\boldsymbol{v}, given by p=γmv,where: γ=11v2/c2,\begin{align} \boldsymbol{p}=&\gamma m \mathbf{v}\,, &\text{where: } \gamma=&\frac{1}{\sqrt{1-{|\boldsymbol{v}|^2}/{c^2}}}\,, \end{align} and c3×108 c\approx 3\times 10^8\ m/s is the speed of light.

If γ1\gamma\approx1, or equivalent, sif vc|\boldsymbol{v}|\ll c: pmv\begin{align} \boldsymbol{p}\approx &m \boldsymbol{v} \end{align}

Equation of motion

Any change of the momentum can be atribuited to some force, F\boldsymbol{F}. In fact, the Newton's second law can be defined in a general way as

The change of the momentum of particle is equal to the net force acting upon the system times the duration of the interaction

For one instaneous duration Δt\Delta t, this law can be written as Δp=FΔt,Δt0 \Delta \boldsymbol{p} = \boldsymbol{F}\Delta t,\qquad \Delta t \to 0 or as the equation of motion, which is the differential equation F=limt0ΔpΔtF=dpdt.\begin{align} \boldsymbol{F}=&\lim_{t\to 0} \frac{\Delta \boldsymbol{p}}{\Delta t} \\ \boldsymbol{F}=&\frac{\operatorname{d}\boldsymbol{p}}{\operatorname{d} t} \,. \end{align} This equation of motion is of general validity and can be applied numerically to solve any problem.

Constant mass

In the special case of constant mass F=ma\begin{align} \boldsymbol{F}=&m \boldsymbol{a} \end{align}

Example: Drag force

For a body falling in the air, in addition to the gravitiy force W\boldsymbol{W}, there is a dragging force in the opposite direction given by [see here for details]img FD=12ρACdv2(t)=12m2ρACdp2(t), F_D=\frac{1}{2}{\rho A C_d} v^2(t)=\frac{1}{2m^2}{\rho A C_d} p^2(t)\,, where

  • AA: is the frontal area on a plane perpendicular to the direction of motion—e.g. for objects with a simple shape, such as a sphere

  • ρ\rho is the density of the air

  • CdC_d: Drag coefficient. Cd=0.346C_d=0.346 for a baseball

  • v(t)v(t): speed of the baseball

  • p(t)p(t): momentum of baseball

The equation of motion is WFD=dpdtdpdt=mgρACd2m2p2(t).\begin{align} W-F_D=& \frac{d p}{dt}\\ \frac{d p}{dt}=&m g - \frac{\rho A C_d}{2m^2} p^2(t). \end{align}

We see then that, in general dpdt=f(t,p). \frac{d p}{dt}=f(t,p)\,.


Mathematical background

Ordinary differential equations normally implies the solution of an initial-value problem, i.e., the solution has to satisfy the differential equation together with some initial condition. Real-life problems usually imply very complicated problems and even non-soluble ones, making any analytical approximation unfeasible. Fortunately, there are two ways to handle this. First, for almost every situation, it is generally posible to simplify the original problem and obtain a simpler one that can be easily solved. Then, using perturbation theory, we can perturbate this solution in order to approximate the real one. This approach is useful, however, it depends very much on the specific problem and a systematic study is rather complicated.

The second approximation, and the one used here, consists of a complete numerical reduction of the problem, solving it exactly within the accuracy allowed by implicit errors of the methods. For this part, we are going to assume well-defined problems, where solutions are expected to be well-behaved.

Euler's method

First order differential equations

This method is the most basic of all methods, however, it is useful to understand basic concepts and definitions of differential equations problems.

Suppose we have a well-posed initial-value problem given by:

dydt=f(t,y),   atb,    y(a)=α\frac{dy}{dt}=f(t,y),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha

Now, let's define a mesh points as a set of values {ti}\{t_i\} where we are going to approximate the solution y(t)y(t). These points can be equal-spaced such that

ti=a+iΔt,    with  i=1,2,3,,N  and Δt=baN.t_i = a+ i \Delta t,\ \ \ \ \mbox{with}\ \ i=1,2,3,\cdots,N \ \ \mbox{and}\ \Delta t = \frac{b-a}{N}.

Here, hh is called the step size of the mesh points.

Now, using the first-order approximation of the derivative that we saw in Numerical Differentiation, we obtain

dydty(t+Δt)y(t)Δt\frac{dy}{dt}\approx \frac{y(t+\Delta t)-y(t)}{\Delta t}

or

dydtt=tiy(ti+1)y(ti)Δt\left.\frac{dy}{dt}\right|_{t=t_i}\approx \frac{y(t_{i+1})-y(t_i)}{\Delta t}

The original problem can be rewritten as

yi+1=yi+f(ti,yi)Δt±Δt22y(ξi)y_{i+1} = y_i + f(t_i, y_i)\Delta t \pm \frac{\Delta t^2}{2}y^{''}(\xi_i)

where the notation y(ti)yiy(t_i)\equiv y_i has been introduced and the last term (error term) can be obtained taking a second-order approximation of the derivative. ξi\xi_i is the value that give the maximum in absolute value for yy''

This equation can be solved recursively as we know the initial value y0=y(a)=αy_0 = y(a) = \alpha.

Second order differential equations

The formalism of the Euler's method can be applied for any system of the form:

dydt=f(t,y),   atb,    y(a)=α\frac{dy}{dt}=f(t,y),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha

However, it is possible to extend this to second-order systems, i.e., systems involving second derivatives. Let's suppose a general system of the form:

d2ydt2+g(t,y)dydt=f(t,y),   atb,    y(a)=α  and y(a)=β\frac{d^2y}{dt^2}+ g(t,y)\frac{dy}{dt}=f(t,y),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha\ \ \mbox{and}\ y'(a) = \beta

For this system we have to provide both, the initial value y(a)y(a) and the initial derivative y(a)y'(a).

Now, let's define a new variable w(t)=y(t)w(t) = y'(t), the previous problem can be then written as

dydt=w(t)dwdt=f(t,y)g(t,y)w,   atb,    y(a)=α  and w(a)=β\begin{align} \frac{dy}{dt}=&w(t) \\ \frac{dw}{dt}=&f(t,y)-g(t,y)w\, ,\ \ \ a\leq t\leq b\,, \ \ \ \ y(a) = \alpha\ \ \mbox{and}\ w(a) = \beta \end{align}

Each of this problem has now the form required by the Euler's algorithm, and the solution can be computed as:

wi+1=wi+[f(ti,yi)g(ti,yi)wi]Δtyi+1=yi+wiΔt,   atb,    y(a)=α  and w(a)=β\begin{align} w_{i+1}=& w_{i} + [f(t_i,y_i)-g(t_i,y_i)w_i]\Delta t \\ y_{i+1}=&y_i + w_i \Delta t\, ,\ \ \ a\leq t\leq b\,, \ \ \ \ y(a) = \alpha\ \ \mbox{and}\ w(a) = \beta \end{align}

Euler method for second order differential equations

We can define the column vectors U=[U1U2]=[y(t)w(t)],V(U,t)=[w(t)f(t,y)g(t,y)w(t)]=[U2f(t,U1)g(t,U1)U2]\begin{align} \boldsymbol{U}=\begin{bmatrix} U_1\\ U_2\\ \end{bmatrix}=&\begin{bmatrix} y(t)\\ w(t)\\ \end{bmatrix}\,,& \boldsymbol{V}(\boldsymbol{U},t)=\begin{bmatrix} w(t)\\ f(t,y)-g(t,y)w(t)\\ \end{bmatrix}=&\begin{bmatrix} U_2\\ f(t,U_1)-g(t,U_1)U_2\\ \end{bmatrix} \end{align} such that

dUdt=V(t,U)\frac{dU}{dt}=V(t,U)

Newton's second law of motion

In fact, the equation of motion can be seen as the system of equations pm=dxdtp=γmvF=dpdt\begin{align} \frac{\boldsymbol{p}}{m}=&\frac{\operatorname{d}\boldsymbol{x}}{\operatorname{d}t}\\ \boldsymbol{p}=& \gamma m \boldsymbol{v}\\ \boldsymbol{F}=&\frac{\operatorname{d}\boldsymbol{p}}{\operatorname{d} t} \end{align}

Example:

An object of 0.5 Kg is launched from the top of a building of 50 m with an horizontal speed of 5 m/s. Study the evolution of the movement

  • Neglecting the air friction

import numpy as np Δt=0.01 #s np.arange( 0,3+Δt,Δt)[:2]
array([0. , 0.01])
pm=dxdtp=γmvF=dpdt\begin{align} \frac{\boldsymbol{p}}{m}=&\frac{\operatorname{d}\boldsymbol{x}}{\operatorname{d}t}\\ \boldsymbol{p}=& \gamma m \boldsymbol{v}\\ \boldsymbol{F}=&\frac{\operatorname{d}\boldsymbol{p}}{\operatorname{d} t} \end{align}
import numpy as np import pandas as pd df=pd.DataFrame() m=.5 ## Kg g=9.8 ## m/s^2 ## intial conditions x=np.array([0,50,0]) #m v=np.array([5,0,0]) ## m/s p=m*v Δt=0.01 #s ## Analysis for the first 3 s df=df.append({'x':x,'p':p},ignore_index=True) Fg=np.array([0,-m*g,0]) #N (Weight) for t in np.arange( 0,3+Δt,Δt): #s p=p+Fg*Δt x=x+(p/m)*Δt df=df.append({'x':x,'p':p},ignore_index=True)
df[:3]
df.to_json('mvto.json')
#pd.read_json('mvto.json').x.str[1]

Remember that in Python an string is also a list:

s='abc'
len(s)
3
s[0]
'a'
s[-1]
'c'

We can use the str attribute

#Second component of vector x: df['x'].str[1][:3]
0 50.00000 1 49.99902 2 49.99706 Name: x, dtype: float64
#py=df.p.str[1]
## x y plt.plot(df.x.str[0],df.x.str[1]) plt.xlabel('$x$ [m]',size=15) plt.ylabel('$y$ [m]',size=15) plt.grid()
Image in a Jupyter notebook

Consider now the case the movement inside a fluid with a dragging force proportinal to the velocity (low velocity case) FD=cv=cpm, \boldsymbol{F}_D=-c \boldsymbol{v} = -c \frac{\boldsymbol{p}}{m}\,, where cc can take the values 0 (not dragging force), 0.1 y 0.5

import numpy as np import pandas as pd df=pd.DataFrame() m=.5 #kg g=9.8 #m/s^2 for c in [0,0.1,0.5]: x=np.array([0,50,0]) #m v=np.array([5,0,0]) #m/s p=m*v Δt=0.01 #s t=0 #s df=df.append({'x':x,'p':p,'t':t,'c':c},ignore_index=True) for t in np.arange( 0,3+Δt,Δt): F=np.array([0,-m*g,0])-c*(p/m) p=p+F*Δt x=x+(p/m)*Δt t=t+Δt df=df.append({'x':x,'p':p,'t':t,'c':c},ignore_index=True)
df.c.unique()
array([0. , 0.1, 0.5])

Apply a mask upon the DataFrame:

  • Example

  • Filter c==0

df[df.c==0][:3]
df[df.c==0.5][:3]
%pylab inline
Populating the interactive namespace from numpy and matplotlib
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure() ax = fig.gca(projection='3d') c=0 ax.plot(df[df.c==c].x.str[0].values, df[df.c==c].x.str[1].values, df[df.c==c].x.str[2].values) c=0.1 ax.plot(df[df.c==c].x.str[0].values, df[df.c==c].x.str[1].values, df[df.c==c].x.str[2].values) c=0.5 ax.plot(df[df.c==c].x.str[0].values, df[df.c==c].x.str[1].values, df[df.c==c].x.str[2].values) ax.view_init(80, 250) plt.xlabel('$x$ [m]',size=10) plt.ylabel('$y$ [m]',size=10)
/tmp/ipykernel_61048/2642031594.py:2: MatplotlibDeprecationWarning: Calling gca() with keyword arguments was deprecated in Matplotlib 3.4. Starting two minor releases later, gca() will take no keyword arguments. The gca() function should only be used to get the current axes, or if no axes exist, create new axes with default keyword arguments. To create a new axes with non-default arguments, use plt.axes() or plt.subplot(). ax = fig.gca(projection='3d')
Text(0.5, 0, '$y$ [m]')
Image in a Jupyter notebook
c=0 plt.plot(df[df.c==c].x.str[0], df[df.c==c].x.str[1],label=r'$c=%s$ ' %c) c=0.1 plt.plot(df[df.c==c].x.str[0], df[df.c==c].x.str[1],label=r'$c=%s$ ' %c) c=0.5 plt.plot(df[df.c==c].x.str[0], df[df.c==c].x.str[1],label=r'$c=%s$ ' %c) plt.xlabel('$x$ [m]',size=15) plt.ylabel('$y$ [m]',size=15) plt.legend(loc='best') plt.grid()
Image in a Jupyter notebook

Activity: Find the time to reach the position y=0 in each case. By using an algorithm to find roots

Activity: Repeat the previous analysis for a baseball in the air with a dragging force proportinal to the square of the velocity. The diameter of the baseball is 7.5cm

Example. Spring

In order to apply this, let's assume a simple mass-spring.

The equation of motion according to Newton's second law is

F=kxF = -kx

Using the previous results, we can rewrite this as:

dpdt=kx\frac{d p}{dt} = -k xdxdt=pm\frac{dx}{dt} = \frac{p}{m}

And the equivalent Euler system is vi+1=viΔtkmxixi+1=xi+viΔt,   x(0)=x0  and v(0)=v0\begin{align} v_{i+1}=& v_{i} - \Delta t\frac{k}{m}x_i \\ x_{i+1}=&x_i + v_i \Delta t\, ,\ \ \ x(0) = x_0\ \ \mbox{and}\ v(0) = v_0 \end{align}

Activity

**1.** Using the initial conditions x(0)=0x(0) = 0 and v(0)=3v(0) = 3, solve the previous system. Plot the solutions x(t)x(t) and y(t)y(t) and compare with real solutions. Furthermore, calculate the total energy of the system. What can you conclude about the behaviour of the energy? Does it make any sense?
**2.** Using the same reasoning, derive the equations for a simple pendulum. Compare the solution for small oscillations with the analytic one. What happens when increasing the initial amplitude of the movement?

pm=dxdtp=γmvF=dpdt\begin{align} \frac{\boldsymbol{p}}{m}=&\frac{\operatorname{d}\boldsymbol{x}}{\operatorname{d}t}\\ \boldsymbol{p}=& \gamma m \boldsymbol{v}\\ \boldsymbol{F}=&\frac{\operatorname{d}\boldsymbol{p}}{\operatorname{d} t} \end{align}

1D xi+1=xi+(p/m)Δt x_{i+1}=x_i+(p/m)\Delta t pi+1pi=FΔt p_{i+1}-p_i= F\Delta t pi+1=pi+FΔt p_{i+1}=p_i+F \Delta t

import numpy as np import pandas as pd df=pd.DataFrame() m=.5 #kg g=9.8 #m/s^2 c=0.1 x=50 #m v=0 #m/s p=m*v Δt=0.01 #s t=0 #s df=df.append({'x':x,'p':p,'t':t,'c':c},ignore_index=True) for t in np.arange( 0,3+Δt,Δt): F=-m*g+c*(p/m) p=p+F*Δt x=x+(p/m)*Δt t=t+Δt df=df.append({'x':x,'p':p,'t':t,'c':c},ignore_index=True)
df[df['x']>=0]

Implementation in Scipy

%pylab inline
%pylab is deprecated, use %matplotlib inline and import the required libraries. Populating the interactive namespace from numpy and matplotlib
/usr/local/lib/python3.9/dist-packages/IPython/core/magics/pylab.py:162: UserWarning: pylab import has clobbered these variables: ['f'] `%matplotlib` prevents importing * from pylab and numpy warn("pylab import has clobbered these variables: %s" % clobbered +
import scipy.integrate as integrate import numpy as np

First order ordinary differential equations

integrate.odeint: Integrate a system of ordinary differential equations

Uso básico

integrate.odeint(func,α,t)

Solves the initial value problem for stiff or non-stiff systems of first order ordinary differential equations:

dydt=f(y,t)\frac{dy}{dt}=f(y,t)

where yy can be a vector. a<t<ba<t<b and y(a)=αy(a)=\alpha

Example

Consider the following differential equation

dy(t)dt=ky(t),\frac{dy(t)}{dt}=-k y(t),

where kk is constant and with initial condition y(0)=y0=5y(0)=y_0=5

def f(y,t): #t is mandatory even if no explicit k = 0.3 return -k * y

For a first evolution after one step of Δt=0.1\Delta t=0.1: with initial condition y(0)=y0=5y(0)=y_0=5

## initial condition y0 = 5 t=[0,0.1] integrate.odeint(f,y0,t)
array([[5. ], [4.85222774]])

while the evolution from 0 to 20 s is

y=y0+f(y0,t)*0.1 y
4.85
## time points t = np.linspace(0,20) ## solve ODE y = integrate.odeint(f,y0,t) ## plot results plt.plot(t,y) plt.xlabel('$t$',size=15) plt.ylabel('$y(t)$',size=15) plt.grid() plt.show()
Image in a Jupyter notebook

Compare with the analytical solution y(t)=y0ekty(t)=y_0\operatorname{e}^{-kt}

Since dydt=[d(kt)dt]y0ekt=k(y0ekt)=ky(t) \frac{dy}{dt}=\left[\frac{d (-kt)}{dt}\right] y_0\operatorname{e}^{-kt}=-k \left(y_0\operatorname{e}^{-kt}\right)=-k\,y(t)

## time points t = np.linspace(0,20) #k = 0.3 solve ODE y0=5 y = integrate.odeint(f,y0,t) k=0.3 ## plot results plt.plot(t,y,'c-',label='ODE solution') plt.plot(t,y0*np.exp(-k*t),'k:',label='Analytical solution') plt.xlabel('$t$',size=15) plt.ylabel('$y(t)$',size=15) plt.grid() plt.legend(loc='best') plt.show()
Image in a Jupyter notebook

Second order differential equations

Although first-order schemes like Euler's method are illustrative and allow a good understanding of the numerical problem, real applications cannot be dealt with them, instead more precise and accurate high-order methods must be invoked. In this section we shall cover a well-known family of numerical integrators, the Runge-Kutta methods.

import scipy.integrate as integrate

Example: From http://sam-dolan.staff.shef.ac.uk/mas212/

As explained before, we need to write a second order ordinary differential equations in terms of first order matricial ordinary differential equation. In terms of a parameter, xx, this implay to have a column vector $$ U=\begin{bmatrix} y\\ z\\ \end{bmatrix} $44 dUdt=V(U,x). \frac{dU}{dt}=V(U,x). $

Suppose we have a second-order ODE such as a damped simple harmonic motion equation, with parameter xx (instead tt) y+2y+2y=cos(2x),y(0)=0,  y(0)=0 \quad y'' + 2 y' + 2 y = \cos(2x), \quad \quad y(0) = 0, \; y'(0) = 0 We can turn this into two first-order equations by defining a new depedent variable. For example, zyz+2z+2y=cos(2x),z(0)=y(0)=0. \quad z \equiv y' \quad \Rightarrow \quad z' + 2 z + 2y = \cos(2x), \quad z(0)=y(0) = 0. So that z=2z2y+cos(2x),z(0)=y(0)=0. z' =- 2 z - 2y + \cos(2x), \quad z(0)=y(0) = 0. where dUdx=[yz] \frac{dU}{dx}=\begin{bmatrix}y'\\z'\end{bmatrix} We can solve this system of ODEs using "odeint" with lists, as follows:

Let U=[U0U1]=[yz]. U=\begin{bmatrix} U_0\\ U_1 \end{bmatrix}=\begin{bmatrix} y\\ z \end{bmatrix}. Therefore ddx[yz]=[z2z2y+cos(2x)]ddxU=[U12U12U0+cos(2x)]\begin{align} \frac{\operatorname{d}}{\operatorname{d} x} \begin{bmatrix} y\\ z\\ \end{bmatrix}=& \begin{bmatrix} z\\ -2z-2y+\cos(2x)\\ \end{bmatrix}\\ \frac{\operatorname{d}}{\operatorname{d} x} U=& \begin{bmatrix} U_1\\ -2U_1-2U_0+\cos(2x)\\ \end{bmatrix} \end{align}

Implementation by using only UU

def dU_dx(U, x): ''' Here U is a vector such that y=U[0] and z=U[1]. This function should return [y', z'] ''' return [ U[1], -2*U[1] - 2*U[0] + np.cos(2*x)]
U0=[0,0] ## → x=0

x→0.1

integrate.odeint(dU_dx, U0, [0,0.1])
array([[0. , 0. ], [0.00465902, 0.08970031]])
array( [[0. , 0. ],#→ U(0) [0.00465902, 0.08970031]] #→ U(0.1)=[y(0.1),z(0.1)=y'(0.1)] )
array([[0. , 0. ], [0.00465902, 0.08970031]])
U0 = [0, 0] xs = np.linspace(0, 50, 200) Us = integrate.odeint(dU_dx, U0, xs)
Us[:3]
array([[0. , 0. ], [0.02601336, 0.1841925 ], [0.08083294, 0.229454 ]])
Us.shape
(200, 2)

The first column, Us[:,0] → y

Us[:,0][:3]
array([0. , 0.02601336, 0.08083294])
ys = Us[:,0] #First column is extracted plt.xlabel("$x$",size=15) plt.ylabel("$y$",size=15) plt.title("Damped harmonic oscillator") plt.plot(xs,ys);
Image in a Jupyter notebook
zs = Us[:,1] plt.xlabel("$x$",size=15) plt.ylabel("$z=y'$",size=15) plt.title("Damped harmonic oscillator") plt.plot(xs,zs);
Image in a Jupyter notebook

Implementation by using explicitly y(x)y(x) and z(x)z(x) ddx[yz]=[z2z2y+cos(2x)]\begin{align} \frac{\operatorname{d}}{\operatorname{d} x} \begin{bmatrix} y\\ z\\ \end{bmatrix}=& \begin{bmatrix} z\\ -2z-2y+\cos(2x)\\ \end{bmatrix} \end{align}

def dU_dx(U, x): ''' Here U is a vector such that y=U[0] and z=U[1]. This function should return [y', z'] ''' y,z=U ## y → U[0]; z → U[1] return [ z, -2*z - 2*y + np.cos(2*x)] U0 = [0, 0] xs = np.linspace(0, 50, 200) Us = integrate.odeint(dU_dx, U0, xs)
ys = Us[:,0] plt.xlabel("$x$",size=15) plt.ylabel("$y$",size=15) plt.title("Damped harmonic oscillator") plt.plot(xs,ys);
Image in a Jupyter notebook

Activity: Apply the previous example to the problem of parabolic motion with air friction:

pm=dxdtF=dpdt\begin{align} \frac{\boldsymbol{p}}{m}=&\frac{\operatorname{d}\boldsymbol{x}}{\operatorname{d}t}\\ \boldsymbol{F}=&\frac{\operatorname{d}\boldsymbol{p}}{\operatorname{d} t} \end{align}ddt[xp]=[p/mmgcp/m],\begin{align} \frac{\operatorname{d}}{\operatorname{d} t} \begin{bmatrix} \boldsymbol{x}\\ \boldsymbol{p}\\ \end{bmatrix}=& \begin{bmatrix} \boldsymbol{p}/m\\ -m\boldsymbol{g}-c \boldsymbol{p}/m \\ \end{bmatrix}\,, \end{align}

where g=(0,g)\boldsymbol{g}=(0,g), and such that in two dimensions

U=[U0U1U2U3]=[xypxpy]\boldsymbol{U}=\begin{bmatrix} {U}_0\\ {U}_1\\ {U}_2\\ {U}_3\\ \end{bmatrix}=\begin{bmatrix} {x}\\ y\\ p_x\\ p_y\\ \end{bmatrix}ddtU=[U2/mU3/mcU2/mmgcU3/m]\begin{align} \frac{\operatorname{d}}{\operatorname{d} t} \boldsymbol{U}=& \begin{bmatrix} {U}_2/m\\ {U}_3/m\\ -c{U}_2/m\\ -m{g}-c{U}_3/m\\ \end{bmatrix} \end{align}
def dU_dt(U, t,c=0.,m=.5,g=9.8): ''' Here U is a vector such that y=U[0] and z=U[1]. This function should return [y', z'] ''' return [U[2]/m, U[3]/m, -c*U[2]/m, -m*g-c*U[3]/m] m=0.5 ## p_x(0)=5*m → v(0)=5 m/s U0 = [0, 50,m*5,0] t = np.linspace(0, 3, 200)
isinstance((0.1),float)
True
isinstance((0.1,),tuple)
True
Us = integrate.odeint(dU_dt, U0, t ) xs = Us[:,0] ys = Us[:,1] plt.plot(xs,ys,label=r'$c=0$') Us = integrate.odeint(dU_dt, U0, t,args=(0.1,) ) xs = Us[:,0] ys = Us[:,1] plt.plot(xs,ys,label=r'$c=0.1$') Us = integrate.odeint(dU_dt, U0, t,args=(0.5,) ) xs = Us[:,0] ys = Us[:,1] plt.plot(xs,ys,label=r'$c=0,5$') plt.xlabel('$x$ [m]',size=15) plt.ylabel('$y$ [m]',size=15) plt.legend(loc='best') plt.grid()
Image in a Jupyter notebook

Activity: Implemet directly: ddt[xp]=[p/mmgcp/m],\begin{align} \frac{\operatorname{d}}{\operatorname{d} t} \begin{bmatrix} \boldsymbol{x}\\ \boldsymbol{p}\\ \end{bmatrix}=& \begin{bmatrix} \boldsymbol{p}/m\\ -m\boldsymbol{g}-c \boldsymbol{p}/m \\ \end{bmatrix}\,, \end{align} as ddt[xypxpy]=[px/mpy/mcpx/mmgcpy/m],\begin{align} \frac{\operatorname{d}}{\operatorname{d} t} \begin{bmatrix} x\\ y\\ p_x\\ p_y \end{bmatrix}=& \begin{bmatrix} p_x/m\\ p_y/m\\ -c p_x/m \\ -mg-c p_y/m \\ \end{bmatrix}\,, \end{align}

def dU_dt(U, t,c=0.,m=.5,g=9.8): ''' ... ''' x,y,px,py=U return [px/m, py/m, -c*px/m, -m*g-c*py/m] m=0.5 U0 = [0, 50,5*m,0] t = np.linspace(0, 3, 200) c=0 Us = integrate.odeint(dU_dt, U0, t,args=(c,) ) ## c = 0.5 xs = Us[:,0] ys = Us[:,1] plt.plot(xs,ys,label=f'c={c}') c=0.1 Us = integrate.odeint(dU_dt, U0, t,args=(c,) ) ## c = 0.5 xs = Us[:,0] ys = Us[:,1] plt.plot(xs,ys,label=f'c={c}') c=0.5 Us = integrate.odeint(dU_dt, U0, t,args=(c,) ) ## c = 0.5 xs = Us[:,0] ys = Us[:,1] plt.plot(xs,ys,label=f'c={c}') plt.xlabel('$x$ [m]',size=15 ) plt.ylabel('$y$ [m]',size=15 ) plt.legend(loc='best') plt.grid()
Image in a Jupyter notebook
%pylab inline
Populating the interactive namespace from numpy and matplotlib

Example 2

In this example we are going to use the Scipy implementation for mapping the phase space of a pendulum.

The equations of the pendulum are given by:

dθdt=ω\frac{d\theta}{dt} = \omegadωdt=glsinθ\frac{d\omega}{dt} = -\frac{g}{l}\sin \thetaddtU=(ωglsinθ),\begin{align} \frac{d}{dt} U=\begin{pmatrix}\omega\\ -\dfrac{g}{l}\sin\theta \end{pmatrix}, \end{align}

where U=(θω).\begin{align} U=\begin{pmatrix}\theta\\ \omega \end{pmatrix}. \end{align}

from scipy import integrate def dU_dt(U, t,l=1,g=9.8): ''' Here U is a vector such that θ=U[0] and ω=U[1]. This function should return [θ', ω'] ''' θ,ω=U return [ω, -g/l*np.sin( θ ) ]
tmax = 6*np.pi #s omega_max = 8 #rad/s
Nic = 1000 #Maxim angular velocity theta0s = np.random.uniform(-4*np.pi,4*np.pi,Nic) omega0s = np.random.uniform(-omega_max,omega_max,Nic)

TAREA: Generar un rango aleatorio uniforme de varios ordenes de magnitud. En particular, 1000 números aleatorios entre 3×1063\times 10^{-6} hasta 5×1045\times 10^{4}

theta0s[0]
1.3936715764887904
U0=[theta0s[0],omega0s[0]] U0
[1.3936715764887904, 7.007902033513252]
t=np.linspace(0,tmax,400) Us=integrate.odeint(dU_dt,U0,t)
plt.plot(Us[:,0],Us[:,1],lw = 1, color = "black" ) plt.xlim(-10,10) plt.ylim(-10,10) plt.xlabel(r'$\theta$ [rad]',size=15) plt.ylabel(r'$\omega$ [rad/s]',size=15)
Text(0, 0.5, '$\\omega$ [rad/s]')
Image in a Jupyter notebook
list( zip([1,2],[3,4]) )
[(1, 3), (2, 4)]
j=0 plt.figure( figsize = (8,5) ) for theta0, omega0 in zip(theta0s, omega0s): t=np.linspace(0,tmax,400) U0=[theta0,omega0] Us=integrate.odeint(dU_dt,U0,t) plt.plot(Us[:,0]/np.pi,Us[:,1],lw = 0.1, color = "black" ) #Format of figure plt.xlabel( "$\\theta/\pi$", fontsize = 18 ) plt.ylabel( "$\omega$ [rad/s]", fontsize = 18 ) plt.xlim( (-3, 3) ) plt.ylim( (-omega_max, omega_max) ) plt.title( "Phase space of a pendulum" ) plt.grid(1)
Image in a Jupyter notebook

The nearly closed curves around (0,0) represent the regular small swings of the pendulum near its rest position. The oscillatory curves up and down of the closed curves represent the movement when the pendulum start at θ=0\theta=0 but with high enough angular speed such that the pendulum goes all the way around. Of course, its angular speed will slow down on the way up but then it will speed up on the way down again. In the absence of friction, it just keeps spinning around indefinitely. The counterclockwise motions of the pendulum of this kind are shown in the graph by the wavy lines at the top with positive angular speed, while the curves on the bottom which go from right to left represent clockwise rotations. The phase space have a periodicity of 2π2\pi.

Small oscillation

t=np.linspace(0,tmax,400) Us=integrate.odeint(dU_dt,[0,2],t) plt.plot(Us[:,0],Us[:,1],lw = 1, color = "black" ) plt.xlim(-8,8) plt.ylim(-10,10)
(-10.0, 10.0)
Image in a Jupyter notebook

All around

t=np.linspace(0,tmax,400) Us=integrate.odeint(dU_dt,[0,7],t) plt.plot(Us[:,0],Us[:,1],lw = 1, color = "black" ) plt.xlim(-8,8) plt.ylim(-10,10)
(-10.0, 10.0)
Image in a Jupyter notebook
t=np.linspace(0,tmax,400) Us=integrate.odeint(dU_dt,[0,-7],t) plt.plot(Us[:,0],Us[:,1],lw = 1, color = "black" ) plt.xlim(-8,8) plt.ylim(-10,10)
(-10.0, 10.0)
Image in a Jupyter notebook
plt.plot(Us[:,0],Us[:,1],lw = 1, color = "black" ) plt.xlim(-8,8) plt.ylim(-10,10)
(-10.0, 10.0)
Image in a Jupyter notebook

Activity: Check the anlytical expression for the period of a pendulum


Appendix

Activity

Using the previous example, explore the phase space of a simple oscillator and a damped pendulum.

Fourth-order Runge-Kutta method

Finally, the most used general purpose method is the fourth-order Runge-Kutta scheme. Its derivation follows the same previous reasoning, however the procedure is rather long and it makes no sense to reprouce it here. Instead, we will give the direct algorithm:

Let's assume again a problem of the form:

dydt=y=f(t,y),   atb,    y(a)=α\frac{dy}{dt}=y'=f(t,y),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha

The Runge-Kutta-4 (RK4) method allows us to predict the solution at the time t+ht+h as:

y(t+h)=y(t)+16(K0+2K1+2K2+K3)y(t+h) = y(t) + \frac{1}{6}( \mathbf{K}_0 + 2\mathbf{K}_1 + 2\mathbf{K}_2 + \mathbf{K}_3 )

where:

K0=hf(t,y)\mathbf{K}_0 = hf(t,y)K1=hf(t+h2,y+K02)\mathbf{K}_1 = hf\left( t + \frac{h}{2},y + \frac{\mathbf{K}_0}{2}\right)K2=hf(t+h2,y+K12)\mathbf{K}_2 = hf\left( t + \frac{h}{2},y + \frac{\mathbf{K}_1}{2}\right)K3=hf(t+h,y+K2)\mathbf{K}_3 = hf\left( t + h,y + \mathbf{K}_2\right)

Activity

The Lorenz attractor is a common set of differential equations of some models of terrestrial atmosphere studies. It is historically known as one of the first system to exhibit deterministic caos. The equations are:
dxdt=a(yx)\frac{dx}{dt} = a(y-x)dydt=x(bz)y\frac{dy}{dt} = x(b-z)-ydzdt=xycz\frac{dz}{dt} = xy-cz

with a=10a = 10, b=28b=28 and c=8/3c = 8/3 the solution shows periodic orbits.

Write a routine that gives a step of RK4 and integrate the previous system. Plot the resulting 3D solution (x,y,z)(x,y,z).

Second-order Runge-Kutta methods

For this method, let's assume a problem of the form:

dydt=y=f(t,y),   atb,    y(a)=α\frac{dy}{dt}=y'=f(t,y),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha

Now, we want to know the solution in the next timestep, i.e. y(t+h)y(t+h). For this, we propose the next solution:

y(t+h)=y(t)+c0f(t,y)h+c1f[t+ph,y+qhf(t,y)]hy(t+h) = y(t) + c_0 f(t,y)h + c_1f[ t+ph, y+qhf(t,y) ]h

determining the coefficients c0,c1,qc_0, c_1, q and pp, we will have the complete approximated solution of the problem.

One way to determine them is by comparing with the taylor expansion around tt

y(t+h)=y(t)+f(t,y)h+12(ft+fy)h2+O(h3)y(t+h) = y(t) + f(t,y)h + \frac{1}{2}\left( \frac{\partial f}{\partial t} + \frac{\partial f}{\partial y} \right)h^2 + \mathcal{O}(h^3)

Now, we can also expand the function f[t+ph,y+qhf(t,y)]f[ t+ph, y+qhf(t,y) ] around the point (t,y)(t,y), yielding:

f[t+ph,y+qhf(t,y)]=f(t,y)+ftph+fyqh+O(h2)f[ t+ph, y+qhf(t,y) ] = f(t,y) + \frac{\partial f}{\partial t}ph + \frac{\partial f}{\partial y}qh + \mathcal{O}(h^2)

Replacing this into the original expression:

y(t+h)=y(t)+c0f(t,y)h+c1[f(t,y)+ftph+fyqh]h+O(h3)y(t+h) = y(t) + c_0 f(t,y)h + c_1\left[ f(t,y) + \frac{\partial f}{\partial t}ph + \frac{\partial f}{\partial y}qh \right]h + \mathcal{O}(h^3)

ordering the terms we obtain:

y(t+h)=y(t)+(c0+c1)f(t,y)h+c1[ftp+fyq]h2+O(h3)y(t+h) = y(t) + (c_0+c_1) f(t,y)h + c_1\left[ \frac{\partial f}{\partial t}p + \frac{\partial f}{\partial y}q \right]h^2 + \mathcal{O}(h^3)

Equalling the next conditions are obtained:

c0+c1=1   c1p=12   c1q=12c_0 + c_1 =1 \ \ \ c_1p=\frac{1}{2}\ \ \ c_1q = \frac{1}{2}

This set of equations are undetermined so there are several solutions, each one yielding a different method:

ParseError: KaTeX parse error: Undefined control sequence: \matrix at position 1: \̲m̲a̲t̲r̲i̲x̲{ c_0 = 0 & c_…

The algorithm is then:

y(t+h)=y(t)+(c1+c2)K1y(t+h) = y(t) + (c_1+c_2)\mathbf{K}_1

with

K1=hf(t+pt,y+qK0)\mathbf{K}_1 = hf( t+pt, y+q\mathbf{K}_0 )K0=hf(t,y)\mathbf{K}_0 = hf(t,y)

All these methods constitute the second-order Runge-Kutta methods.

Two-Point Boundary Value Problems

Up to now we have solved initial value problems of the form:

dydt=y=f(t,y),   atb,    y(a)=α\frac{dy}{dt}=y'=f(t,y),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha

Second order equations can be similarly planted as

d2ydt2=y=f(t,y,y),   atb,    y(a)=α   y(a)=u\frac{d^2y}{dt^2}=y''=f(t,y,y'),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha \ \ \ y'(a) = u

This type of systems can be readily solved by defining the auxiliar variable w=yw = y', turning it into a first order system of equations.

Now, we shall solve two-point boundary problem, where we have two conditions on the solution y(t)y(t) instead of having the function and its derivative at some initial point, i.e.

d2ydt2=y=f(t,y,y),   atb,    y(a)=α   y(b)=β\frac{d^2y}{dt^2}=y''=f(t,y,y'),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha \ \ \ y(b) = \beta

In spite of its similar form to the initial value problem, two-point boundary problems pose a increased difficulty for numerical methods. The main reason of this is the iterative procedure performed by numerical approaches, where from an initial point, further points are found. Trying to fit two different values at different points implies then a continuous readjusting of the solution.

A common way to solve these problems is by turning them into a initial-value problem

d2ydt2=y=f(t,y,y),   atb,    y(a)=α   y(a)=u\frac{d^2y}{dt^2}=y''=f(t,y,y'),\ \ \ a\leq t\leq b, \ \ \ \ y(a) = \alpha \ \ \ y'(a) = u

Let's suppose some choice of uu, integrating by using some of the previous methods, we obtain the final boundary condition y(b)=θy(b)=\theta. If the produced value is not the one we wanted from our initial problem, we try another value uu. This can be repeated until we get a reasonable approach to y(b)=βy(b)=\beta. This method works fine, but it is so expensive and terribly inefficient.

Note when we change uu, the final boundary value also change, so we can assume y(b)=θy(b) = \theta. The solution to the problem can be thought then as a root-finding problem:

y(b)=θ(u)=βy(b) = \theta(u) = \beta

or

r(u)θ(u)β=0 r(u) \equiv \theta(u) - \beta = 0

where r(u)r(u) is the residual function. This problem can be thus solved using some of the methods previously seen for the root-finding problem.

Example 3

A very simplified model of interior solid planets consists of a set of spherically symmetric radial layers, where the next properties are computed: density ρ(r)\rho(r), enclosed mass m(r)m(r), gravity g(r)g(r) and pressure P(r)P(r). Each of these properties are assumed to be only radially dependent. The set of equations that rules the planetary interior is:

Hydrostatic equilibrium equation

dPdr=ρ(r)g(r)\frac{dP}{dr} = -\rho(r)g(r)

Adams-Williamson equation

dgdr=4πGρ(r)2Gm(r)r3\frac{dg}{dr} = 4\pi G\rho(r) - \frac{2Gm(r)}{r^3}

Continuity equation

dmdr=4πr2ρ(r)\frac{dm}{dr} = 4\pi r^2 \rho(r)

Equation of state

dρdr=ρ(r)2g(r)Ks\frac{d\rho}{dr} = -\frac{\rho(r)^2g(r)}{K_s}

For accurate results the term KsK_s, called the adiabatic bulk modulus, is temperature and radii dependent. However, for the sake of simplicity we shall assume a constant value.

Solving simultaneously the previous set of equations, we can find the complete internal structure of a planet.

We have four functions to be determined and four equations, so the problem is solvable. It is only necessary to provide a set of boundary conditions of the form:

ρ(R)=ρsurf,   m(R)=Mp,   g(R)=gsurf,   P(R)=Patm\rho(R) = \rho_{surf},\ \ \ m(R) = M_p, \ \ \ g(R) = g_{surf},\ \ \ P(R) = P_{atm}

where RR is the planetary radius, ρsurf\rho_{surf} the surface density, MpM_p the mass of the planet, gsurfg_{surf} the surface gravity and PatmP_{atm} the atmospheric pressure. However, there is a problem, we do not know the planetary radius RR, so an extra condition is required to determine this value. This is reached through the physical condition m(0)=0m(0) = 0, this is, the enclosed mass at a radius r=0r = 0 (center of the planet) must be 0.

The two-value boundary nature of this problem lies then in fitting the mass function at m(R)=Mpm(R) = M_p and at m(0)=0m(0) = 0. To do so, let's call the residual mass m(0)=Mrm(0) = M_r. This value should depend on the chosen radius RR, so a large value would imply a mass defect Mr(R)<0M_r(R)<0, and a small value a mass excess Mr(0)>0M_r(0)>0. The problem is then solving the radius RR for which m(0)=Mr(R)=0m(0) = M_r(R) = 0. This can be done by using the bisection method.

For this problem, we are going to assume an one-layer planet made of perovskite, so Ks200 GPaK_s \approx 200\ GPa. A planet mass equal to earth, so Mp=5.97219×1024 kgM_p = 5.97219 \times 10^{24}\ kg, a surface density ρsurf=3500 kg/m3\rho_{surf} = 3500\ kg/m^3 and a atmospheric pressure of Patm=1 atm=1×105 PaP_{atm} = 1\ atm = 1\times 10^5\ Pa.

Implementation of the pendulum phase space

#RK2 integrator def RK2_step( f, y, t, h ): #Creating solutions K0 = h*f(t, y) K1 = h*f(t + 0.5*h, y + 0.5*K0) y1 = y + K1 #Returning solution return y1

The phase space of a dynamical system is a space in which all the possible states of that system are univocally represented. For the case of the pendulum, a complete state of the system is given by the set (θ,ω)(\theta, \omega), so its phase space is two-dimensional. In order to explore all the possible states, we are going to generate a set of initial conditions and integrate them.

#======================================================== #Defining parameters #======================================================== #Gravity g = 9.8 #Pendulum's lenght l = 1.0 #Number of initial conditions Nic = 1000 #Maxim angular velocity omega_max = 8 #Maxim time of integration tmax = 6*np.pi #Timestep h = 0.01 #======================================================== #Dynamical function of the system #======================================================== def function( t, y ): #Using the convention y = [theta, omega] theta = y[0] omega = y[1] #Derivatives dtheta = omega domega = -g/l*np.sin(theta) return np.array([dtheta, domega])
#======================================================== #Generating set of initial conditions #======================================================== theta0s = -4*np.pi + np.random.random(Nic)*8*np.pi omega0s = -omega_max + np.random.random(Nic)*2*omega_max
#======================================================== #Integrating and plotting the solution for each IC #======================================================== #Setting figure plt.figure( figsize = (8,5) ) jj=0 for theta0, omega0 in zip(theta0s, omega0s): #Arrays for solutions time = [0,] theta = [theta0,] omega = [omega0,] for i, t in zip(range(int(tmax/h)), np.arange( 0, tmax, h )): #Building current condition y = [theta[i], omega[i]] #Integrating the system thetai, omegai = RK2_step( function, y, t, h ) #Appending new components theta.append( thetai ) omega.append( omegai ) time.append( t ) #if i==10: ## break #Plotting solution plt.plot( theta, omega, lw=0.1, color = "blue" ) if jj==50: break jj=jj+1 #Format of figure plt.xlabel( "$\\theta$", fontsize = 18 ) plt.ylabel( "$\omega$", fontsize = 18 ) plt.xlim( (-2*np.pi-2, 2*np.pi+2) ) plt.ylim( (-omega_max-3, omega_max+3) ) plt.title( "Phase space of a pendulum" ) plt.grid(1)
Image in a Jupyter notebook