Runge-Kutta 4 plot time range not limited by tmax - python

I am trying to solve an ODE using the 4th order Runge Kutta method. The problem is that the defined tmax in my code does not seem to affect the time range of the plot. Changing tmax only affects the curve of the plot. Instead the time range is dependent on nT.
#generated electricity
g = 1.0
#rate constants
k_diss = 0.1
k_em = .01
#4th order RK
def RK4(func, yi, ti, dt):
k1 = dt*func(yi, ti)
k2 = dt*func(yi + 0.5 * k1, ti + 0.5 * dt)
k3 = dt*func(yi + 0.5 * k2, ti + 0.5 * dt)
k4 = dt*func(yi + k3,ti + dt)
return yi + 1./6*(k1 + 2*k2 + 2*k3 + k4)
#variation of cEX
def df(t, c, k_diss, k_em):
return np.array([g - 2*k_diss*c[0] - k_em*c[0]])
#time
tmax = 30.0
nT = 500
T = np.linspace(0,tmax,nT)
dT = tmax/(nT-1)
#initial condition
C0, T0 = 1.0, 0.0
#storage
C = np.zeros(nT)
C[0] = C0
#ODE solver
r = ode(df).set_integrator('dopri5')
r.set_initial_value(C0,T0).set_f_params(k_diss,k_em)
#loop over time
iT = 1
for i in range(1,nT):
C[iT] = r.integrate(r.t + dT)
iT += 1
On top of that the plot should be decreasing (can be seen below), not increasing. I have based my code on the following (there tmax does operate the way it is supposed to):
def RK4(func, yi, ti, dt):
k1 = dt*func(yi,ti)
k2 = dt*func(yi+0.5*k1,ti+0.5*dt)
k3 = dt*func(yi+0.5*k2,ti+0.5*dt)
k4 = dt*func(yi+k3,ti+dt)
return yi + 1./6* ( k1+2*k2+2*k3+k4 )
# funtion that defines the concentration of CA
def df(c,t):
return np.array([-k*c[0],k*c[0]]) # return the value
# time argument
tmax,nT = 30.0,50
T = np.linspace(0,tmax,nT)
dT = tmax/(nT-1)
# rate constant
k = 0.2
# declare the array to store
RK = np.zeros((2,nT))
# initial condition
RK[0,0] = 1.
for iT in range(1,nT):
RK[:,iT] = RK4(df,RK[:,iT-1],T[iT-1],dT)
The plot:

I changed the end of the first script to
#loop over time
for iT in range(1,nT):
C[iT] = r.integrate(T[iT])
import matplotlib.pyplot as plt
plt.plot(T,C)
plt.xlabel("time"); plt.ylabel("cEX");
plt.show()
The first change since it is actually your intent that C[i] is the value at time T[i], and as there is a way to express this consistently, why not use it.
Since you did not add your plot commands, one can only guess your error. In all probability it was that you did not include the time in your plot command. If you only write plt.plot(C), then the horizontal axis is filled with the integer indices of C, since the plot procedure is not given knowledge about any other time scale.

Related

Runge Kutta 4th order Python

I am trying to solve this equation using Runge Kutta 4th order:
applying d2Q/dt2=F(y,x,v) and dQ/dt=u Q=y in my program.
I try to run the code but i get this error:
Traceback (most recent call last):
File "C:\Users\Egw\Desktop\Analysh\Askhsh1\asdasda.py", line 28, in <module>
k1 = F(y, u, x) #(x, v, t)
File "C:\Users\Egw\Desktop\Analysh\Askhsh1\asdasda.py", line 13, in F
return ((Vo/L -(R0/L)*u -(R1/L)*u**3 - y*(1/L*C)))
OverflowError: (34, 'Result too large')
I tried using the decimal library but I still couldnt make it work properly.I might have not used it properly tho.
My code is this one:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
#parameters
R0 = 200
R1 = 250
L = 15
h = 0.002
Vo=1000
C=4.2*10**(-6)
t=0.93
def F(y, u, x):
return ((Vo/L -(R0/L)*u -(R1/L)*u**3 - y*(1/L*C)))
xpoints = arange(0,t,h)
ypoints = []
upoints = []
y = 0.0
u = Vo/L
for x in xpoints:
ypoints.append(y)
upoints.append(u)
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = h*(u + 0.5*k1)
k2 = (h*F(y+0.5*m1, u+0.5*k1, x+0.5*h))
m3 = h*(u + 0.5*k2)
k3 = h*F(y+0.5*m2, u+0.5*k2, x+0.5*h)
m4 = h*(u + k3)
k4 = h*F(y+m3, u+k3, x+h)
y += (m1 + 2*m2 + 2*m3 + m4)/6
u += (k1 + 2*k2 + 2*k3 + k4)/6
plot(xpoints, upoints)
show()
plot(xpoints, ypoints)
show()
I expected to get the plots of u and y against t.
Turns out I messed up with the equations I was using for Runge Kutta
The correct code is the following:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
#parameters
R0 = 200
R1 = 250
L = 15
h = 0.002
Vo=1000
C=4.2*10**(-6)
t0=0
#dz/dz
def G(x,y,z):
return Vo/L -(R0/L)*z -(R1/L)*z**3 - y/(L*C)
#dy/dx
def F(x,y,z):
return z
t = np.arange(t0, 0.93, h)
x = np.zeros(len(t))
y = np.zeros(len(t))
z = np.zeros(len(t))
y[0] = 0.0
z[0] = 0
for i in range(1, len(t)):
k0=h*F(x[i-1],y[i-1],z[i-1])
l0=h*G(x[i-1],y[i-1],z[i-1])
k1=h*F(x[i-1]+h*0.5,y[i-1]+k0*0.5,z[i-1]+l0*0.5)
l1=h*G(x[i-1]+h*0.5,y[i-1]+k0*0.5,z[i-1]+l0*0.5)
k2=h*F(x[i-1]+h*0.5,y[i-1]+k1*0.5,z[i-1]+l1*0.5)
l2=h*G(x[i-1]+h*0.5,y[i-1]+k1*0.5,z[i-1]+l1*0.5)
k3=h*F(x[i-1]+h,y[i-1]+k2,z[i-1]+l2)
l3 = h * G(x[i - 1] + h, y[i - 1] + k2, z[i - 1] + l2)
y[i]=y[i-1]+(k0+2*k1+2*k2+k3)/6
z[i] = z[i - 1] + (l0 + 2 * l1 + 2 * l2 + l3) / 6
Q=y
I=z
plot(t, Q)
show()
plot(t, I)
show()
If I may draw your attention to these 4 lines
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = h*(u + 0.5*k1)
k2 = (h*F(y+0.5*m1, u+0.5*k1, x+0.5*h))
You should note a fundamental structural difference between the first two lines and the second pair of lines.
You need to multiply with the step size h also in the first pair.
The next problem is the step size and the cubic term. It contributes a term of size 3*(R1/L)*u^2 ~ 50*u^2 to the Lipschitz constant. In the original IVP per the question with u=Vo/L ~ 70 this term is of size 2.5e+5. To compensate only that term to stay in the stability region of the method, the step size has to be smaller 1e-5.
In the corrected initial conditions with u=0 at the start the velocity u remains below 0.001 so the cubic term does not determine stability, this is now governed by the last term contributing a Lipschitz term of 1/sqrt(L*C) ~ 125. The step size for stability is now 0.02, with 0.002 one can expect quantitatively useful results.
You can use decimal libary for more precision (handle more digits), but it's kind of annoying every value should be the same class (decimal.Decimal).
For example:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
# Import decimal.Decimal as D
import decimal
from decimal import Decimal as D
# Precision
decimal.getcontext().prec = 10_000_000
#parameters
# Every value should be D class (decimal.Decimal class)
R0 = D(200)
R1 = D(250)
L = D(15)
h = D(0.002)
Vo = D(1000)
C = D(4.2*10**(-6))
t = D(0.93)
def F(y, u, x):
# Decomposed for use D
a = D(Vo/L)
b = D(-(R0/L)*u)
c = D(-(R1/L)*u**D(3))
d = D(-y*(D(1)/L*C))
return ((a + b + c + d ))
xpoints = arange(0,t,h)
ypoints = []
upoints = []
y = D(0.0)
u = D(Vo/L)
for x in xpoints:
ypoints.append(y)
upoints.append(u)
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = (h*(u + D(0.5)*k1))
k2 = (h*F(y+D(0.5)*m1, u+D(0.5)*k1, x+D(0.5)*h))
m3 = h*(u + D(0.5)*k2)
k3 = h*F(y+D(0.5)*m2, u+D(0.5)*k2, x+D(0.5)*h)
m4 = h*(u + k3)
k4 = h*F(y+m3, u+k3, x+h)
y += (m1 + D(2)*m2 + D(2)*m3 + m4)/D(6)
u += (k1 + D(2)*k2 + D(2)*k3 + k4)/D(6)
plot(xpoints, upoints)
show()
plot(xpoints, ypoints)
show()
But even with ten million of precision I still get an overflow error. Check the components of the formula, their values are way too high. You can increase precision for handle them, but you'll notice it takes time to calculate them.
Problem implementation using scipy.integrate.odeint and scipy.integrate.solve_ivp.
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint, solve_ivp
# Input data initial conditions
ti = 0.0
tf = 0.5
N = 100000
h = (tf-ti)/N
# Initial conditions
u0 = 0.0
Q0 = 0.0
t_span = np.linspace(ti,tf,N)
r0 = np.array([Q0,u0])
# Parameters
R0 = 200
R1 = 250
L = 15
C = 4.2*10**(-6)
V0 = 1000
# Systems of First Order Equations
# This function is used with odeint, as specified in the documentation for scipy.integrate.odeint
def f(r,t,R0,R1,L,C,V0):
Q,u = r
ode1 = u
ode2 = -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
return np.array([ode1,ode2])
# This function is used in our 4Order Runge-Kutta implementation and in scipy.integrate.solve_ivp
def F(t,r,R0,R1,L,C,V0):
Q,u = r
ode1 = u
ode2 = -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
return np.array([ode1,ode2])
# Resolution with oedint
sol_1 = odeint(f,r0,t_span,args=(R0,R1,L,C,V0))
sol_2 = solve_ivp(fun=F,t_span=(ti,tf), y0=r0, method='LSODA',args=(R0,R1,L,C,V0))
Q_odeint, u_odeint = sol_1[:,0], sol_1[:,1]
Q_solve_ivp, u_solve_ivp = sol_2.y[0,:], sol_2.y[1,:]
# Figures
plt.figure(figsize=[30.0,10.0])
plt.subplot(3,1,1)
plt.grid(color = 'red',linestyle='--',linewidth=0.4)
plt.plot(t_span,Q_odeint,'r',t_span,u_odeint,'b')
plt.xlabel('t(s)')
plt.ylabel('Q(t), u(t)')
plt.subplot(3,1,2)
plt.plot(sol_2.t,Q_solve_ivp,'g',sol_2.t,u_solve_ivp,'y')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel('Q(t), u(t)')
plt.subplot(3,1,3)
plt.plot(Q_solve_ivp,u_solve_ivp,'green')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('Q(t)')
plt.ylabel('u(t)')
plt.show()
Runge-Kutta 4th
# Code development of Runge-Kutta 4 Order
# Parameters
R0 = 200
R1 = 250
L = 15
C = 4.2*10**(-6)
V0 = 1000
# Input data initial conditions #
ti = 0.0
tf = 0.5
N = 100000
h = (tf-ti)/N
# Initial conditions
u0 = 0.0
Q0 = 0.0
# First order ordinary differential equations
def f1(t,Q,u):
return u
def f2(t,Q,u):
return -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
t = np.zeros(N); Q = np.zeros(N); u = np.zeros(N)
t[0] = ti
Q[0] = Q0
u[0] = u0
for i in range(0,N-1,1):
k1 = h*f1(t[i],Q[i],u[i])
l1 = h*f2(t[i],Q[i],u[i])
k2 = h*f1(t[i]+(h/2),Q[i]+(k1/2),u[i]+(l1/2))
l2 = h*f2(t[i]+(h/2),Q[i]+(k1/2),u[i]+(l1/2))
k3 = h*f1(t[i]+(h/2),Q[i]+(k2/2),u[i]+(l2/2))
l3 = h*f2(t[i]+(h/2),Q[i]+(k2/2),u[i]+(l2/2))
k4 = h*f1(t[i]+h,Q[i]+k3,u[i]+l3)
l4 = h*f2(t[i]+h,Q[i]+k3,u[i]+l3)
Q[i+1] = Q[i] + ((k1+2*k2+2*k3+k4)/6)
u[i+1] = u[i] + ((l1+2*l2+2*l3+l4)/6)
t[i+1] = t[i] + h
plt.figure(figsize=[20.0,10.0])
plt.subplot(1,2,1)
plt.plot(t,Q_solve_ivp,'r',t,Q_odeint,'y',t,Q,'b')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel(r'$Q(t)_{Odeint}$, $Q(t)_{RK4}$')
plt.subplot(1,2,2)
plt.plot(t,Q_solve_ivp,'g',t,Q_odeint,'y',t,Q,'b')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel(r'$Q(t)_{solve_ivp}$, $Q(t)_{RK4}$')

Drawing Poincare Section using Python

I was about to plot a Poincare section of the following DE, which is quite meaningful to have a periodic potential function V(x) = - cos(x) in this equation.
After calculating the solution using RK4 with time interval dt = 0.001, the one that python drew was as the following plot.
But according to the textbook(referred to 2E by J.M.T. Thompson and H.B. Stewart), the section would look like as
:
it has so much difference. For my personal opinion, since Poincare section does not appear as what writers draw, there must be some error in my code. However, I actually done for other forced oscillation DE, including Duffing's equation, and obtained the identical one as those in the textbook. So, I was wodering if there are some typos in the equation given by the textbook, or somewhere else. I posted my code, but might be quite messy to understand. So appreicate dealing with it.
import numpy as np
import matplotlib.pylab as plt
import matplotlib as mpl
import sys
import time
state = [1]
def print_percent_done(index, total, state, title='Please wait'):
percent_done2 = (index+1)/total*100
percent_done = round(percent_done2, 1)
print(f'\t⏳{title}: {percent_done}% done', end='\r')
if percent_done2 > 99.9 and state[0]:
print('\t✅'); state = [0]
####
no = 1
####
def multiple(n, q):
m = n; i = 0
while m >= 0:
m -= q
i += 1
return min(abs(n - (i - 1)*q), abs(i*q - n))
# system(2)
#Basic info.
filename = 'sinPotentialWell'
# a = 1
# alpha = 0.01
# w = 4
w0 = .5
n = 1000000
h = .01
t_0 = 0
x_0 = 0.1
y_0 = 0
A = [(t_0, x_0, y_0)]
def f(t, x, y):
return y
def g(t, x, y):
return -0.5*y - np.sin(x) + 1.1*np.sin(0.5*t)
for i in range(n):
t0 = A[i][0]; x0 = A[i][1]; y0 = A[i][2]
k1 = f(t0, x0, y0)
u1 = g(t0, x0, y0)
k2 = f(t0 + h/2, x0 + h*k1/2, y0 + h*u1/2)
u2 = g(t0 + h/2, x0 + h*k1/2, y0 + h*u1/2)
k3 = f(t0 + h/2, x0 + h*k2/2, y0 + h*u2/2)
u3 = g(t0 + h/2, x0 + h*k2/2, y0 + h*u2/2)
k4 = f(t0 + h, x0 + h*k3, y0 + h*u3)
u4 = g(t0 + h, x0 + h*k3, y0 + h*u3)
t = t0 + h
x = x0 + (k1 + 2*k2 + 2*k3 + k4)*h/6
y = y0 + (u1 + 2*u2 + 2*u3 + u4)*h/6
A.append([t, x, y])
if i%1000 == 0: print_percent_done(i, n, state, 'Solving given DE')
#phase diagram
print('showing 3d_(x, y, phi) graph')
PHI=[[]]; X=[[]]; Y=[[]]
PHI_period1 = []; X_period1 = []; Y_period1 = []
for i in range(n):
if w0*A[i][0]%(2*np.pi) < 1 and w0*A[i-1][0]%(2*np.pi) > 6:
PHI.append([]); X.append([]); Y.append([])
PHI_period1.append((w0*A[i][0])%(2*np.pi)); X_period1.append(A[i][1]); Y_period1.append(A[i][2])
phi_period1 = np.array(PHI_period1); x_period1 = np.array(X_period1); y_period1 = np.array(Y_period1)
print('showing Poincare Section at phi=0')
plt.plot(x_period1, y_period1, 'gs', markersize = 2)
plt.plot()
plt.title('phi=0 Poincare Section')
plt.xlabel('x'); plt.ylabel('y')
plt.show()
If you factor out some of the computation blocks, you can make the code more flexible and computations more direct. No need to reconstruct something if you can construct it in the first place. You want to catch the points where w0*t is a multiple of 2*pi, so just construct the time loops so you integrate in chunks of 2*pi/w0 and only remember the interesting points.
num_plot_points = 2000
h = .01
t,x,y = t_0,x_0,y_0
x_section,y_section = [],[]
T = 2*np.pi/w0
for k in range(num_plot_points):
t = 0;
while t < T-1.2*h:
x,y = RK4step(t,x,y,h)
t += h
x,y = RK4step(t,x,y,T-t)
if k%100 == 0: print_percent_done(k, num_plot_points, state, 'Solving given DE')
x_section.append(x); y_section.append(y)
with RK4step just containing the code of the RK4 step.
This will not solve the mystery. The veil gets lifted if you consider that x is the angle theta (of a forced pendulum with friction) on a circle. Thus to get points with the same spacial location it needs to be reduced by multiples of 2*pi. Doing that,
plt.plot([x%(2*np.pi) for x in x_section], y_section, 'gs', markersize = 2)
results in the expected plot

How to correctly solve 1D wave equation to get displacement profile (periodic Boundary Condition problem)?

I'm trying to solve a 1D wave equation for the pile with periodic BC (periodic load).
I'm pretty sure about my discretization formulas. The only thing I'm not sure about is the periodic BC and time (t) in there ==> sin(omega*t).
When I set it up as it is right now, it's giving me a weird displacement profile. However, if I set it up to be sin(omega*1) or sin(omega*2),... etc, it resembles a sine wave, but it basically means that sin(omega*t), i.e. sin(2*pi*f*t), is equal to 0 when t is an integer value..
I code everything up in Jupyter Notebook together with the visualization part, but the solution is nowhere near the propagating sine wave.
Here is the relevant Python code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
def oned_wave(L, dz, T, p0, Ep, ro, f):
"""Solve u_tt=c^2*u_xx on (0,L)x(0,T]"""
"""Assume C = 1"""
p = p0
E = Ep
ro = ro
omega = 2*np.pi*f
c = np.sqrt(E/ro)
C = 1 # Courant number
Nz = int(round(L/dz))
z = np.linspace(0, L, Nz+1) # Mesh points in space
dt = dz/c # Time step based on Courant Number
Nt = int(round(T/dt))
t = np.linspace(0, Nt*dt, Nt+1) # Mesh points in time
C2 = C**2 # Help variable in the scheme
# Make sure dz and dt are compatible with z and t
dz = z[1] - z[0]
dt = t[1] - t[0]
w = np.zeros(Nz+1) # Solution array at new time level
w_n = np.zeros(Nz+1) # Solution at 1 time level back
w_nm1 = np.zeros(Nz+1) # Solution at 2 time levels back
# Set initial condition into w_n
for i in range(0,Nz+1):
w_n[i] = 0
result_matrix = w_n[:] # Solution matrix where each row is displacement at given time step
# Special formula for first time step
for i in range(1, Nz):
w[i] = 0.5*C2 * w_n[i-1] + (1 - C2) * w_n[i] + 0.5*C2 * w_n[i+1]
# Set BC
w[0] = (1 - C2) * w_n[i] + C2 * w_n[i+1] - C2*dz*((p*np.sin(omega*dt))/E) # this is where, I think, the mistake is: sin(omega*t)
w[Nz] = 0
result_matrix = np.vstack((result_matrix, w)) # Append a row to the solution matrix
w_nm1[:] = w_n; w_n[:] = w # Switch variables before next step
for n in range(1, Nt):
# Update all inner points at time t[n+1]
for i in range(1, Nz):
w[i] = - w_nm1[i] + C2 * w_n[i-1] + 2*(1 - C2) * w_n[i] + C2 * w_n[i+1]
# Set BC
w[0] = - w_nm1[i] + 2*(1 - C2) * w_n[i] + 2*C2 * w_n[i+1] - 2*dz*((p*np.sin(omega*(dt*n)))/E) # this is where, I think, the mistake is: sin(omega*t)
w[Nz] = 0
result_matrix = np.vstack((result_matrix, w)) # Append a row to the solution matrix
w_nm1[:] = w_n; w_n[:] = w # Switch variables before next step
return result_matrix
My Jupyter Notebook document.

Cannot get RK4 to solve for position of orbiting body in Python

I am trying to solve for the position of a body orbiting a much more massive body, using the idealization that the much more massive body doesn't move. I am trying to solve for the position in cartesian coordinates using 4th order Runge-Kutta in python.
Here is my code:
dt = .1
t = np.arange(0,10,dt)
vx = np.zeros(len(t))
vy = np.zeros(len(t))
x = np.zeros(len(t))
y = np.zeros(len(t))
vx[0] = 10 #initial x velocity
vy[0] = 10 #initial y velocity
x[0] = 10 #initial x position
y[0] = 0 #initial y position
M = 20
def fx(x,y,t): #x acceleration
return -G*M*x/((x**2+y**2)**(3/2))
def fy(x,y,t): #y acceleration
return -G*M*y/((x**2+y**2)**(3/2))
def rkx(x,y,t,dt): #runge-kutta for x
kx1 = dt * fx(x,y,t)
mx1 = dt * x
kx2 = dt * fx(x + .5*kx1, y + .5*kx1, t + .5*dt)
mx2 = dt * (x + kx1/2)
kx3 = dt * fx(x + .5*kx2, y + .5*kx2, t + .5*dt)
mx3 = dt * (x + kx2/2)
kx4 = dt * fx(x + kx3, y + x3, t + dt)
mx4 = dt * (x + kx3)
return (kx1 + 2*kx2 + 2*kx3 + kx4)/6
return (mx1 + 2*mx2 + 2*mx3 + mx4)/6
def rky(x,y,t,dt): #runge-kutta for y
ky1 = dt * fy(x,y,t)
my1 = dt * y
ky2 = dt * fy(x + .5*ky1, y + .5*ky1, t + .5*dt)
my2 = dt * (y + ky1/2)
ky3 = dt * fy(x + .5*ky2, y + .5*ky2, t + .5*dt)
my3 = dt * (y + ky2/2)
ky4 = dt * fy(x + ky3, y + ky3, t + dt)
my4 = dt * (y + ky3)
return (ky1 + 2*ky2 + 2*ky3 + ky4)/6
return (my1 + 2*my2 + 2*my3 + my4)/6
for n in range(1,len(t)): #solve using RK4 functions
vx[n] = vx[n-1] + fx(x[n-1],y[n-1],t[n-1])*dt
vy[n] = vy[n-1] + fy(x[n-1],y[n-1],t[n-1])*dt
x[n] = x[n-1] + vx[n-1]*dt
y[n] = y[n-1] + vy[n-1]*dt
Originally, no matter which way I tweaked the code, I was getting an error on my for loop, either "object of type 'float' has no len()" (I didn't understand what float python could be referring to), or "setting an array element with a sequence" (I also didn't understand what sequence it meant). I've managed to get rid of the errors, but my results are just wrong. I get vx and vy arrays of 10s, an x array of integers from 10. to 109., and a y array of integers from 0. to 99.
I suspect there are issues with fx(x,y,t) and fy(x,y,t) or with the way I have coded the runge-kutta functions to go with fx and fy, because I've used the same runge-kutta code for other functions and it works fine.
I greatly appreciate any help in figuring out why my code isn't working. Thank you.
Physics
The Newton law gives you a second order ODE u''=F(u) with u=[x,y]. Using v=[x',y'] you get the first order system
u' = v
v' = F(u)
which is 4-dimensional and has to be solved using a 4 dimensional state. The only reduction available is to use the Kepler laws which allow to reduce the system to a scalar order one ODE for the angle. But that is not the task here.
But to get the scales correct, for a circular orbit of radius R with angular velocity w one gets the identity w^2*R^3=G*M which implies that the speed along the orbit is w*R=sqrt(G*M/R) and period T=2*pi*sqrt(R^3/(G*M)). With the data given, R ~ 10, w ~ 1, thus G*M ~ 1000 for a close-to-circular orbit, so with M=20 this would require G between 50 and 200, with an orbital period of about 2*pi ~ 6. The time span of 10 could represent one half to about 2 or 3 orbits.
Euler method
You correctly implemented the Euler method to calculate values in the last loop of your code. That it may look un-physical can be because the Euler method continuously increases the orbit, as it moves to the outside of convex trajectories following the tangent. In your implementation this outward spiral can be seen for G=100.
This can be reduced in effect by choosing a smaller step size, such as dt=0.001.
You should select the integration time to be a good part of a full orbit to get a presentable result, with above parameters you get about 2 loops, which is good.
RK4 implementation
You made several errors. Somehow you lost the velocities, the position updates should be based on the velocities.
Then you should have halted at fx(x + .5*kx1, y + .5*kx1, t + .5*dt) to reconsider your approach as that is inconsistent with any naming convention. The consistent, correct variant is
fx(x + .5*kx1, y + .5*ky1, t + .5*dt)
which shows that you can not decouple the integration of a coupled system, as you need the y updates alongside the x updates. Further, the function values are the accelerations, thus update the velocities. The position updates use the velocities of the current state. Thus the step should start as
kx1 = dt * fx(x,y,t) # vx update
mx1 = dt * vx # x update
ky1 = dt * fy(x,y,t) # vy update
my1 = dt * vy # y update
kx2 = dt * fx(x + 0.5*mx1, y + 0.5*my1, t + 0.5*dt)
mx2 = dt * (vx + 0.5*kx1)
ky2 = dt * fy(x + 0.5*mx1, y + 0.5*my1, t + 0.5*dt)
my2 = dt * (vy + 0.5*ky1)
etc.
However, as you see, this already starts to become unwieldy. Assemble the state into a vector and use a vector valued function for the system equations
M, G = 20, 100
def orbitsys(u):
x,y,vx,vy = u
r = np.hypot(x,y)
f = G*M/r**3
return np.array([vx, vy, -f*x, -f*y]);
Then you can use a cook-book implementation of the Euler or Runge-Kutta step
def Eulerstep(f,u,dt): return u+dt*f(u)
def RK4step(f,u,dt):
k1 = dt*f(u)
k2 = dt*f(u+0.5*k1)
k3 = dt*f(u+0.5*k2)
k4 = dt*f(u+k3)
return u + (k1+2*k2+2*k3+k4)/6
and combine them into an integration loop
def Eulerintegrate(f, y0, tspan):
y = np.zeros([len(tspan),len(y0)])
y[0,:]=y0
for k in range(1, len(tspan)):
y[k,:] = Eulerstep(f, y[k-1], tspan[k]-tspan[k-1])
return y
def RK4integrate(f, y0, tspan):
y = np.zeros([len(tspan),len(y0)])
y[0,:]=y0
for k in range(1, len(tspan)):
y[k,:] = RK4step(f, y[k-1], tspan[k]-tspan[k-1])
return y
and invoke them with your given problem
dt = .1
t = np.arange(0,10,dt)
y0 = np.array([10, 0.0, 10, 10])
sol_euler = Eulerintegrate(orbitsys, y0, t)
x,y,vx,vy = sol_euler.T
plt.plot(x,y)
sol_RK4 = RK4integrate(orbitsys, y0, t)
x,y,vx,vy = sol_RK4.T
plt.plot(x,y)
You are not using rkx, rky functions anywhere!
There are two return at the end of function definition you should use
return [(kx1 + 2*kx2 + 2*kx3 + kx4)/6, (mx1 + 2*mx2 + 2*mx3 + mx4)/6] (as pointed out by #eapetcho). Also, your implementation of Runge-Kutta is not clear to me.
You have dv/dt so you solve for v and then update r accordingly.
for n in range(1,len(t)): #solve using RK4 functions
vx[n] = vx[n-1] + rkx(vx[n-1],vy[n-1],t[n-1])*dt
vy[n] = vy[n-1] + rky(vx[n-1],vy[n-1],t[n-1])*dt
x[n] = x[n-1] + vx[n-1]*dt
y[n] = y[n-1] + vy[n-1]*dt
Here is my version of the code
import numpy as np
#constants
G=1
M=1
h=0.1
#initiating variables
rt = np.arange(0,10,h)
vx = np.zeros(len(rt))
vy = np.zeros(len(rt))
rx = np.zeros(len(rt))
ry = np.zeros(len(rt))
#initial conditions
vx[0] = 10 #initial x velocity
vy[0] = 10 #initial y velocity
rx[0] = 10 #initial x position
ry[0] = 0 #initial y position
def fx(x,y): #x acceleration
return -G*M*x/((x**2+y**2)**(3/2))
def fy(x,y): #y acceleration
return -G*M*y/((x**2+y**2)**(3/2))
def rk4(xj, yj):
k0 = h*fx(xj, yj)
l0 = h*fx(xj, yj)
k1 = h*fx(xj + 0.5*k0 , yj + 0.5*l0)
l1 = h*fy(xj + 0.5*k0 , yj + 0.5*l0)
k2 = h*fx(xj + 0.5*k1 , yj + 0.5*l1)
l2 = h*fy(xj + 0.5*k1 , yj + 0.5*l1)
k3 = h*fx(xj + k2, yj + l2)
l3 = h*fy(xj + k2, yj + l2)
xj1 = xj + (1/6)*(k0 + 2*k1 + 2*k2 + k3)
yj1 = yj + (1/6)*(l0 + 2*l1 + 2*l2 + l3)
return (xj1, yj1)
for t in range(1,len(rt)):
nv = rk4(vx[t-1],vy[t-1])
[vx[t],vy[t]] = nv
rx[t] = rx[t-1] + vx[t-1]*h
ry[t] = ry[t-1] + vy[t-1]*h
I suspect there are issues with fx(x,y,t) and fy(x,y,t)
This is the case, I just checked my code for fx=3 and fy=y and I got a nice trajectory.
Here is the ry vs rx plot:

RK4 implementation to solve Lotka Volterra Diff-EQ

I am trying to understand what's wrong with the code I have butchered together. The code below is one of many implementations I have done today to solve the Lotka Volterra Differential equations (2 Systems), it is the one that I have brought the closest to the desired result.
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
def rk4( f, x0, t ):
"""
4th order Runge-Kutta method implementation to solve x' = f(x,t) with x(t[0]) = x0.
USE:
x = rk4(f, x0, t)
INPUT:
f - function of x and t equal to dx/dt.
x0 - the initial condition(s).
Specifies the value of x # t = t[0] (initial).
Can be a scalar of a vector (NumPy Array)
Example: [x0, y0] = [500, 20]
t - a time vector (array) at which the values of the solution are computed at.
t[0] is considered as the initial time point
h = t[i+1] - t[i] determines the step size h as suggested by the algorithm
Example: t = np.linspace( 0, 500, 200 ), creates 200 time points between 0 and 500
increasing the number of points in the intervall automatically decreases the step size
OUTPUT:
x - An array containing the solution evaluated at each point in the t array.
"""
n = len( t )
x = np.array( [ x0 ] * n ) # creating an array of length n
for i in xrange( n - 1 ):
h = t[i+1] - t[i] # step size, dependent on the time vector.
# starting below - the implementation of the RK4 algorithm:
# for further informations visit http://en.wikipedia.org/wiki/Runge-Kutta_methods
# k1 is the increment based on the slope at the beginning of the interval (same as Euler)
# k2 is the increment based on the slope at the midpoint of the interval (with x + 0.5 * k1)
# k3 is AGAIN the increment based on the slope at the midpoint (with x + 0.5 * k2)
# k4 is the increment based on the slope at the end of the interval
k1 = f( x[i], t[i] )
k2 = f( x[i] + 0.5 * k1, t[i] + 0.5 * h )
k3 = f( x[i] + 0.5 * k2, t[i] + 0.5 * h )
k4 = f( x[i] + h * k3, t[i] + h )
# finally computing the weighted average and storing it in the x-array
x[i+1] = x[i] + h * ( ( k1 + 2.0 * ( k2 + k3 ) + k4 ) / 6.0 )
return x
# model
def model(state,t):
"""
A function that creates an array containing the Lotka Volterra Differential equation
Parameter assignement convention:
a natural growth rate of the preys
b chance of being eaten by a predator
c dying rate of the predators per week
d chance of catching a prey
"""
x,y = state # will corresponding to initial conditions
# consider it as a vector too
a = 0.08
b = 0.002
c = 0.2
d = 0.0004
return np.array([ x*(a-b*y) , -y*(c - d*x) ]) # corresponds to [dx/dt, dy/dt]
################################################################
# initial conditions for the system
x0 = 500
y0 = 20
# vector of times
t = np.linspace( 0, 500, 1000 )
result = rk4( model, [x0,y0], t )
print result
plt.plot(t,result)
plt.xlabel('Time')
plt.ylabel('Population Size')
plt.legend(('x (prey)','y (predator)'))
plt.title('Lotka-Volterra Model')
plt.show()
The above code produces the following output
however if I move the from pylab import * code right above the initial conditions I get the correct output
why does this happen and how can I fix this?
pylab defines its own implementation of rk4, which it takes from matplotlib:
In [1]: import pylab
In [2]: pylab.rk4
Out[2]: <function matplotlib.mlab.rk4>
When you do a wildcard import like from pylab import *, you will override any local functions with the same name.
In particular, here you're redefining your own rk4 implementation (ie, the code you've written is never used).
This is why you should never do a wildcard import like that. pylab is particularly problematic, in that it defines several functions (such as any and all) which have completely different outputs than the python builtins for certain inputs.
Anyway, the root cause of your problem seems to be that your RK4 implementation is incorrect.
You need to use the step size in your calculation of k_n.
For example, here's a small snippet of my own RK4 implementation (which, I'll admit, is tuned for speed rather than readability):
while not target(xs):
...
# Do RK4
self.f(xs, self.k1)
self.f(xs + halfh*self.k1, self.k2)
self.f(xs + halfh*self.k2, self.k3)
self.f(xs + self.h*self.k3, self.k4)
xs += sixthh*(self.k1 + self.k2 + self.k2 + self.k3 + self.k3 \
+ self.k4)
You'll note that the entire state vector is multiplied by h, not just the time component.
Try fixing that up in your own code and seeing if the result is the same.
(In my opinion, the habit of wiki etc of treating time as a special case is a cause of a lot of these problems. Your time vector, ts, is simply a special derivative where t' = 1.)
So for your own code, I believe, but haven't tested, that something like this should work:
k1 = f( x[i], t[i] )
k2 = f( x[i] + 0.5 * h * k1, t[i] + 0.5 * h ) ## changed to use h
k3 = f( x[i] + 0.5 * h * k2, t[i] + 0.5 * h ) ## changed to use h
k4 = f( x[i] + h * k3, t[i] + h )
Try
import pylab
help(pylab.rk4)
You'll find a long explanation of the pylab.rk4 command.
This is why it's not a good idea to use from x import *. It's much better to do import pylab as py and then this won't be an issue.
Be aware that even with moving your import command, any later call you might have to rk4 will fail.

Categories