I am solving two coupled ordinary differential equations using the 4th-order Runge-Kutta method. I am having trouble in printing the values of z as a result of applying this method. The source code is below for reference. Please, help me fix this code by printing the updated values of z and theta. Thank you.
#Import neeeded modules
import numpy as np
import matplotlib.pyplot as plt
#Input parameters
k = 5 #longitudinal torsional constant
delta = 10**-3 #longitudinal torsional constant
I = 10**-4 #Rotational Inertia
eps = 10**-2 #Coupling constant
m = 0.5
#Time Step
#Setting time array for graph visualization
dt = 0.002 #Time Step
tStop = 0.30 #Maximum time for graph visualization derived from Kinematics
t = np.arange(0., tStop+dt, dt) #Array of time
z = np.zeros(len(t))
dz = np.zeros(len(t))
theta = np.zeros(len(t))
dtheta = np.zeros(len(t))
#Functions that include the equations of motion
def dYdt(t,u):
z, dz, theta, dtheta = u
ddz = -(k*z+0.5*eps*theta)/m
ddtheta = -(delta*theta+0.5*eps*z)/I
return np.array([dz, ddz, dtheta, ddtheta])
def rk4(t, u, dt):
for i in range(len(t)-1):
# runge_kutta
k1 = dYdt(t[i], u[i])
k2 = dYdt(t[i] + dt/2, u[i] + dt/2 * k1)
k3 = dYdt(t[i] + dt/2, u[i] + dt/2 * k2)
k4 = dYdt(t[i] + dt, u[i] + dt * k3)
u.append(u[i] + (k1 + 2*k2 + 2*k3 + k4) * dt / 6)
#Unpacking
z, dz, theta, dtheta = np.asarray(u).T
print(z)
Here are the equations of motions I used to come up with the source code. Coupled ODEs
This is what I THINK you want, but I don't know what parameters to pass to rk4. Also, u is a 1D vector, so I don't know what you were expecting z, dz, theta, dtheta = np.asarray(u).T to do. As a result, this code won't run, but it will show you a potential design.
import numpy as np
import matplotlib.pyplot as plt
#Input parameters
k = 5 #longitudinal torsional constant
delta = 10**-3 #longitudinal torsional constant
I = 10**-4 #Rotational Inertia
eps = 10**-2 #Coupling constant
m = 0.5
#Time Step
#Setting time array for graph visualization
dt = 0.002 #Time Step
tStop = 0.30 #Maximum time for graph visualization derived from Kinematics
t = np.arange(0., tStop+dt, dt) #Array of time
#Functions that include the equations of motion
def dYdt(t,u):
z, dz, theta, dtheta = u
ddz = -(k*z+0.5*eps*theta)/m
ddtheta = -(delta*theta+0.5*eps*z)/I
return np.array([dz, ddz, dtheta, ddtheta])
def rk4(t, u, dt):
for i in range(len(t)-1):
# runge_kutta
k1 = dYdt(t[i], u[i])
k2 = dYdt(t[i] + dt/2, u[i] + dt/2 * k1)
k3 = dYdt(t[i] + dt/2, u[i] + dt/2 * k2)
k4 = dYdt(t[i] + dt, u[i] + dt * k3)
u.append(u[i] + (k1 + 2*k2 + 2*k3 + k4) * dt / 6)
#Unpacking
return np.asarray(u).T
z, dz, theta, dtheta = rk4(t, u, dt)
print(z)
Related
In our physics class we have to model a damping torsional pendulum.
Ilustration of torsional pendulum:
We came up with this equation of motion:
Where θ is the angle, A is torsion parameter, B is Newton's parameter, C is Stokes' parameter, and D is friction parameter. We also use the sign function sgn that determines the direction of the acting force upon the pendulum, depending on the current angle from the reference point.
The problem is, that I'm unable to solve it using Runge-Kutta method in Python.
I got a working solution in MATLAB by using Euler's method, which has some flaws, but it is something.
MATLAB Code:
function [theta, dtheta, epsilon] = drt(t, theta0, dtheta0, A, B, C, D)
epsilon = zeros(1, length(t));
theta = epsilon;
dtheta = epsilon;
theta(1) = theta0;
dtheta(1) = dtheta0;
epsilon(1) = A * alpha0 - B * dtheta^2 - C * dtheta - D;
dt = t(2) - t(1);
for i = 1 : (length(t) - 1)
epsilon(i + 1)= - A * theta(i) - B * dtheta(i)^2 * sign(dtheta(i)) - C * dtheta(i) - D * sign(dtheta(i));
dtheta(i + 1)= dtheta(i) + dt * epsilon(i);
theta(i + 1) = theta(i) + dt * dtheta(i);
end
end
We call this MATLAB function like this for example:
t = linspace(0, 10, 100);
theta0 = 90;
dtheta0 = 0;
A = 1;
B = 0.1;
C = 0.1;
D = 0.1;
[theta, dtheta, epsilon] = drt(t, theta0, dtheta0, A, B, C, D);
We can then plot the theta and other values in a graph, which shows us, how the torsional pendulum is being damped by the external forces acting on it.
Python Code:
import numpy as np
import matplotlib.pyplot as plt
# Damping torsional pendulum
def drp(drp_alpha, drp_d_alpha, drp_params):
a = drp_params["tors"]
b = drp_params["newt"]
c = drp_params["stok"]
d = drp_params["fric"]
result = a * drp_alpha - b * np.power(drp_d_alpha, 2) * np.sign(drp_d_alpha) - c * drp_d_alpha - d * np.sign(drp_d_alpha)
return result
# Runge-Kutta 4th Order
# f - function DamRotPen
# x0 - initial condition
# t0 - initial time
# tmax - maximum time
# dt - sample time
def RG4(rg4_f, rg4_x0, rg4_t0, rg4_tmax, rg4_dt):
# Time vector
rg4_t = np.arange(rg4_t0, rg4_tmax, rg4_dt)
# Time vector size
rg4_t_sz = rg4_t.size
# Initialize the array
rg4_alpha = np.zeros(rg4_t_sz)
# Initial value of the system
rg4_alpha[0] = rg4_x0
for k in range(rg4_t_sz - 1):
k1 = dt * f(rg4_t[k], rg4_alpha[k])
k2 = dt * rg4_f(rg4_t[k] + dt / 2, rg4_alpha[k] + k1 / 2)
k3 = dt * rg4_f(rg4_t[k] + dt / 2, rg4_alpha[k] + k2 / 2)
k4 = dt * rg4_f(rg4_t[k] + dt, rg4_alpha[k] + k3)
rg4_d_alpha = (k1 + 2 * k2 + 2 * k3 + k4) / 6
rg4_alpha[k + 1] = rg4_alpha[k] + rg4_d_alpha
return rg4_alpha, rg4_t
# Parameters of the forces acting on the system
# tors - torsion parameter
# newt - Newton's parameter
# stok - Stokes' parameter
# fric - friction parameter
params = {"tors": 1, "newt": 0.1, "stok": 0.1, "fric": 0.1}
# Start parameters
alpha = 90
d_alpha = 0
# Initial time
t0 = 0
# Maximum time
tmax = 120
# Sample time
dt = 0.01
# Define DamRotPen function as 'f' using lambda
f = lambda t, alpha : drp(alpha, d_alpha, params)
# Try to solve this shit
alpha, t = RG4(f, alpha, t0, tmax, dt)
# Plot this shit
plt.plot(t, alpha, "r", "r", label="Position I guess")
plt.xlabel("Time t / s")
plt.grid()
plt.show()
After plotting the values, we can see on the graph, that the θ sky rockets after certain amount of time. I don't know what I'm doing wrong, I tried practically everything, so that's why I'm asking you for help. (Though I think part of the problem might be my misunderstanding on how to implement the Runge-Kutta method, maybe I got the math wrong, etc...)
This python code can solve one non- coupled differential equation:
import numpy as np
import matplotlib.pyplot as plt
import numba
import time
start_time = time.clock()
#numba.jit()
# A sample differential equation "dy / dx = (x - y**2)/2"
def dydx(x, y):
return ((x - y**2)/2)
# Finds value of y for a given x using step size h
# and initial value y0 at x0.
def rungeKutta(x0, y0, x, h):
# Count number of iterations using step size or
# step height h
n = (int)((x - x0)/h)
# Iterate for number of iterations
y = y0
for i in range(1, n + 1):
"Apply Runge Kutta Formulas to find next value of y"
k1 = h * dydx(x0, y)
k2 = h * dydx(x0 + 0.5 * h, y + 0.5 * k1)
k3 = h * dydx(x0 + 0.5 * h, y + 0.5 * k2)
k4 = h * dydx(x0 + h, y + k3)
# Update next value of y
y = y + (1.0 / 6.0)*(k1 + 2 * k2 + 2 * k3 + k4)
# Update next value of x
x0 = x0 + h
return y
def dplot(start,end,steps):
Y=list()
for x in np.linspace(start,end,steps):
Y.append(rungeKutta(x0, y, x , h))
plt.plot(np.linspace(start,end,steps),Y)
print("Execution time:",time.clock() - start_time, "seconds")
plt.show()
start,end = 0, 10
steps = end* 100
x0 = 0
y = 1
h = 0.002
dplot(start,end,steps)
This code can solve this differential equation:
dydx= (x - y**2)/2
Now I have a system of coupled differential equations:
dydt= (x - y**2)/2
dxdt= x*3 + 3y
How can I implement these two as a system of coupled differential equations in the above code?
Is there any more generalized way for system of n-number of coupled differential equations?
With the help of others, I got to this:
import numpy as np
from math import sqrt
import matplotlib.pyplot as plt
import numba
import time
start_time = time.clock()
a=1
b=1
c=1
d=1
# Equations:
#numba.jit()
#du/dt=V(u,t)
def V(u,t):
x, y, vx, vy = u
return np.array([vy,vx,a*x+b*y,c*x+d*y])
def rk4(f, u0, t0, tf , n):
t = np.linspace(t0, tf, n+1)
u = np.array((n+1)*[u0])
h = t[1]-t[0]
for i in range(n):
k1 = h * f(u[i], t[i])
k2 = h * f(u[i] + 0.5 * k1, t[i] + 0.5*h)
k3 = h * f(u[i] + 0.5 * k2, t[i] + 0.5*h)
k4 = h * f(u[i] + k3, t[i] + h)
u[i+1] = u[i] + (k1 + 2*(k2 + k3 ) + k4) / 6
return u, t
u, t = rk4(V, np.array([1., 0., 1. , 0.]) , 0. , 10. , 100000)
x,y, vx,vy = u.T
# plt.plot(t, x, t,y)
plt.semilogy(t, x, t,y)
plt.grid('on')
print("Execution time:",time.clock() - start_time, "seconds")
plt.show()
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I want to numerically compute the Lyapunov Spectrum of the Lorenz System by using the standard method which is described in this Paper, p.81.
One basically need integrate the Lorenz system and the tangential vectors (i used the Runge-Kutta method for this). The evolution equation of the tangential vectors are given by the Jacobi matrix of the Lorenz system. After each iterations one needs to apply the Gram-Schmidt scheme on the vectors and store its lengths. The three Lyapunov exponents are then given by the averages of the stored lengths.
I implemented the above explained scheme in python (used version 3.7.4), but I don't get the correct results.
I thing the bug lies in the Rk4-Method for der vectors, but i cannot find any error...The RK4-method for the trajectories x,y,z works correctly (indicated by the plot) and the implemented Gram-Schmidt scheme is also correctly implemented.
I hope that someone could look through my short code and maybe find my error
Edit: Updated Code
from numpy import array, arange, zeros, dot, log
import matplotlib.pyplot as plt
from numpy.linalg import norm
# Evolution equation of tracjectories and tangential vectors
def f(r):
x = r[0]
y = r[1]
z = r[2]
fx = sigma * (y - x)
fy = x * (rho - z) - y
fz = x * y - beta * z
return array([fx,fy,fz], float)
def jacobian(r):
M = zeros([3,3])
M[0,:] = [- sigma, sigma, 0]
M[1,:] = [rho - r[2], -1, - r[0] ]
M[2,:] = [r[1], r[0], -beta]
return M
def g(d, r):
dx = d[0]
dy = d[1]
dz = d[2]
M = jacobian(r)
dfx = dot(M, dx)
dfy = dot(M, dy)
dfz = dot(M, dz)
return array([dfx, dfy, dfz], float)
# Initial conditions
d = array([[1,0,0], [0,1,0], [0,0,1]], float)
r = array([19.0, 20.0, 50.0], float)
sigma, rho, beta = 10, 45.92, 4.0
T = 10**5 # time steps
dt = 0.01 # time increment
Teq = 10**4 # Transient time
l1, l2, l3 = 0, 0, 0 # Lengths
xpoints, ypoints, zpoints = [], [], []
# Transient
for t in range(Teq):
# RK4 - Method
k1 = dt * f(r)
k11 = dt * g(d, r)
k2 = dt * f(r + 0.5 * k1)
k22 = dt * g(d + 0.5 * k11, r + 0.5 * k1)
k3 = dt * f(r + 0.5 * k2)
k33 = dt * g(d + 0.5 * k22, r + 0.5 * k2)
k4 = dt * f(r + k3)
k44 = dt * g(d + k33, r + k3)
r += (k1 + 2 * k2 + 2 * k3 + k4) / 6
d += (k11 + 2 * k22 + 2 * k33 + k44) / 6
# Gram-Schmidt-Scheme
orth_1 = d[0]
d[0] = orth_1 / norm(orth_1)
orth_2 = d[1] - dot(d[1], d[0]) * d[0]
d[1] = orth_2 / norm(orth_2)
orth_3 = d[2] - (dot(d[2], d[1]) * d[1]) - (dot(d[2], d[0]) * d[0])
d[2] = orth_3 / norm(orth_3)
for t in range(T):
k1 = dt * f(r)
k11 = dt * g(d, r)
k2 = dt * f(r + 0.5 * k1)
k22 = dt * g(d + 0.5 * k11, r + 0.5 * k1)
k3 = dt * f(r + 0.5 * k2)
k33 = dt * g(d + 0.5 * k22, r + 0.5 * k2)
k4 = dt * f(r + k3)
k44 = dt * g(d + k33, r + k3)
r += (k1 + 2 * k2 + 2 * k3 + k4) / 6
d += (k11 + 2 * k22 + 2 * k33 + k44) / 6
orth_1 = d[0] # Gram-Schmidt-Scheme
l1 += log(norm(orth_1))
d[0] = orth_1 / norm(orth_1)
orth_2 = d[1] - dot(d[1], d[0]) * d[0]
l2 += log(norm(orth_2))
d[1] = orth_2 / norm(orth_2)
orth_3 = d[2] - (dot(d[2], d[1]) * d[1]) - (dot(d[2], d[0]) * d[0])
l3 += log(norm(orth_3))
d[2] = orth_3 / norm(orth_3)
# Correct Solution (2.16, 0.0, -32.4)
lya1 = l1 / (dt * T)
lya2 = l2 / (dt * T) - lya1
lya3 = l3 / (dt * T) - lya1 - lya2
lya1, lya2, lya3
# my solution T = 10^5 : (1.3540301507934012, -0.0021967491623752448, -16.351653561383387)
The above code is updated according to Lutz suggestions.
The results look much better but they are still not 100% accurate.
Correct Solution (2.16, 0.0, -32.4)
My solution (1.3540301507934012, -0.0021967491623752448, -16.351653561383387)
The correct solutions are from Wolf's Paper, p.289. On page 290-291 he describes his method, which looks exactly the same as in the paper that i mentioned in the beginning of this post (Paper, p.81).
So there must be another error in my code...
You need to solve the system of point and Jacobian as the (forward) coupled system that it is. In the original source exactly that is done, everything is updated in one RK4 call for the combined system.
So for instance in the second stage, you would mix the operations to have a combined second stage
k2 = dt * f(r + 0.5 * k1)
M = jacobian(r + 0.5 * k1)
k22 = dt * g(d + 0.5 * k11, r + 0.5 * k1)
You could also delegate the computation of M inside the g function, as this is the only place where it is needed, and you increase locality in the scope of variables.
Note that I changed the update of d from k1 to k11, which should be the main source of the error in the numerical result.
Additional remarks on the last code version (2/28/2021):
As said in the comments, the code looks like it will do what the mathematics of the algorithm prescribes. There are two misreadings that prevent the code from returning a result close to the reference:
The parameter in the paper is sigma=16.
The paper uses not the natural logarithm, but the binary one, that is, the magnitude evolution is given as 2^(L_it). So you have to divide the computed exponents by log(2).
Using the method derived in https://scicomp.stackexchange.com/questions/36013/numerical-computation-of-lyapunov-exponent I get the exponents
[2.1531855610566595, -0.00847304754613621, -32.441308372177566]
which is sufficiently close to the reference (2.16, 0.0, -32.4).
I am trying to solve a simple ODE so as to understand the new API of Scipy.
I wrote a routine for Runge Kutta of order 4 to write it and confirmed it with the old API odeint and it matched beautifully. But now that I am trying to get around the solve_ivp, it seems that is not working. What am I getting wrong?
import numpy as np
from matplotlib import pyplot as plt
from scipy.integrate import solve_ivp, odeint
import time
freq = np.arange(1, 10000, 100)
def g(q, t):
return -q ** 3 + np.sin(t)
a = 0
b = 10
npoints = 100
h = (b - a) / npoints
t = np.arange(a, b, h)
output1 = np.zeros(t.shape)
x = 0
for i in range(len(t)):
output1[i] = x
k1 = h * g(x, t[i])
k2 = h * g(x + 0.5 * k1, t[i] + 0.5 * h)
k3 = h * g(x + 0.5 * k2, t[i] + 0.5 * h)
k4 = h * g(x + k3, t[i] + 0.5 * h)
x = x + 1 / 6 * (k1 + 2 * k2 + 2 * k3 + k4)
# ---------------Solving using odeint (old API)---------------#
y1_odeint = odeint(g, 0, t)
#---------------Solving using new API-------------#
y2=solve_ivp(g,(a,b),[0],t_eval=t)
# --------------------Representação gráfica--------------------------#
fig = plt.figure()
ax = fig.add_subplot(121)
ax1=fig.add_subplot(122)
ax.plot(t, output1,label="my own")
ax.plot(t,y1_odeint,label="odeint")
ax.plot(y2.t,np.squeeze(y2.y),label="new API")
ax.legend()
ax.set_title("Output")
ax1.plot(t,output1-np.squeeze(y1_odeint),label="|odeint-my own|")
ax1.legend()
plt.tight_layout()
plt.show()
Take another look at the docstring for solve_ivp. It expects the first argument of g to be t. By default, odeint uses the opposite convention. If you have a recent version of scipy, you can tell odeint that the first argument is t by giving it the argument tfirst=True.
I am desperately trying to use the scipy ODE integrator, but I keep getting the following error :
Y[0] = (1/I3) * T_z(INP[0], INP[1], INP[2], INP[3], INP[4])
TypeError: 'float' object is not subscriptable
My code is the following :
import scipy.integrate as spi
import numpy as np
import pylab as pl
from time import time
#Constants
I3 = 0.00396
lamb = 1
L = 5*10**-1
mu = 1
m = 0.1
Cz = 0.5
rho = 1.2
S = 0.03*0.4
K_z = 1/2*rho*S*Cz
g = 9.81
#Initial conditions
omega0 = 10*2*np.pi
V0 = 25
theta0 =np.pi/2
phi0 = 0
psi0 = -np.pi/9
X0 = 0
Y0 = 0
Z0 = 1.8
#for integration
t_start = 0.0
t_end = 5
t_step = 0.1
t_range = np.arange(t_start, t_end+t_step, t_step)
INPUT = omega0, V0, theta0, phi0, psi0, X0, Y0, Z0 #initial conditions
def diff_eqs(INP,t):
def M(v_G, w_z):
return L*K_z*(v_G**2 + v_G*L*w_z*np.sin(w_z*t_step)+(L*w_z)**2)
def F_x(w_z, v_G, theta, phi, psi):
return K_z*(v_G**2+(L*w_z)**2)*np.sin(theta)*np.sin(phi) + lamb*v_G*(np.cos(psi)*np.cos(phi) - np.cos(theta)*np.sin(phi)*np.sin(psi))
def F_y(w_z, v_G, theta, phi, psi):
return -K_z*(v_G**2+(L*w_z)**2)*np.sin(theta)*np.cos(phi) + lamb*v_G*(np.cos(psi)*np.sin(phi) + np.cos(theta)*np.cos(phi)*np.sin(psi))
def F_z(w_z, v_G, theta, phi, psi):
return -K_z*(v_G**2+(L*w_z)**2)*np.cos(theta) + lamb*v_G*np.sin(theta)*np.sin(psi) - m*g
def T_x(w_z, v_G, theta, phi, psi):
return M(v_G, w_z)*(-np.sin(w_z*t_step)*(np.cos(psi)*np.cos(phi) - np.cos(theta)*np.sin(phi)*np.sin(psi)) \
+ np.cos(w_z*t_step)*(-np.sin(psi)*np.cos(phi) - np.cos(theta)*np.sin(phi)*np.cos(psi))) \
- mu * w_z * (np.sin(theta)*np.sin(phi))
def T_y(w_z, v_G, theta, phi, psi):
return M(v_G, w_z)*(-np.sin(w_z*t_step)*(np.cos(psi)*np.sin(phi) + np.cos(theta)*np.cos(phi)*np.sin(psi)) \
+ np.cos(w_z*t_step)*(-np.sin(psi)*np.sin(phi) - np.cos(theta)*np.cos(phi)*np.cos(psi)))
- mu * w_z * (np.sin(theta)*np.cos(phi))
def T_z(w_z, v_G, theta, phi, psi):
return M(v_G, w_z)*(-np.sin(w_z*t_step)*np.sin(theta)*np.sin(psi) + np.cos(w_z*t_step)*np.sin(theta)*np.cos(psi)) \
- mu * w_z * np.cos(theta)
Y = np.zeros(8)
Y[0] = (1/I3) * T_z(INP[0], INP[1], INP[2], INP[3], INP[4])
Y[1] = -(lamb/m)*F_x(INP[0], INP[1], INP[2], INP[3], INP[4])
Y[2] = (1/(I3*INP[0]))*(-T_y(INP[0], INP[1], INP[2], INP[3], INP[4])*np.cos(INP[4]) - T_x(INP[0], INP[1], INP[2], INP[3], INP[4])*np.sin(INP[4]))
Y[3] = (1/(I3*INP[0]*np.cos(INP[3]))) * (-T_y(INP[0], INP[1], INP[2], INP[3], INP[4])*np.sin(INP[4]) + T_x(INP[0], INP[1], INP[2], INP[3], INP[4])*np.cos(INP[4]))
Y[4] = -(1/(m*INP[1]))*F_y(INP[0], INP[1], INP[2], INP[3], INP[4])
Y[5] = INP[1]*(-np.cos(INP[4])*np.cos(INP[3]) + np.sin(INP[4])*np.sin(INP[3])*np.cos(INP[2]))
Y[6] = INP[1]*(-np.cos(INP[4])*np.sin(INP[3]) - np.sin(INP[4])*np.cos(INP[3])*np.cos(INP[2]))
Y[7] = INP[1]*(-np.sin(INP[4])*np.sin(INP[2]))
return Y
ode = spi.ode(diff_eqs)
# BDF method suited to stiff systems of ODEs
ode.set_integrator('vode',nsteps=500,method='bdf')
ode.set_initial_value(INPUT,t_start)
ts = []
ys = []
while ode.successful() and ode.t < t_end:
ode.integrate(ode.t + t_step)
ts.append(ode.t)
ys.append(ode.y)
t = np.vstack(ts)
I have a set of 8 differentials equations I want to numerically solve. Therefore I have 8 initial values stored in "INPUT". But when I use this variable in ode.set_initial_value(INPUT,t_start), it keeps repeating that the variable is a float ! It has been bugging me for hours and the answer is maybe obvious but I can't see where I made a mistake. And I don't think the equations themselves, even though they are pretty messy, are involved here.
Thanks in advance for your help.
Your argument order is the one required in the ODE function for odeint. For ode you need the order (t, INP).
Try to use the more recent solve_ivp interface, it has about the same functionality of the ode class and about the same compact call structure as odeint.