I want to plot the motion of a double pendulum with a spring in python. I need to plot the theta1, theta2, r, and their first derivatives. I have found my equations for the motion, which are second-order ODEs so I then converted them to first-order ODEs where x1=theta1, x2=theta1-dot, y1=theta2, y2=theta2-dot, z1=r, and z2=r-dot. Here is a picture of the double pendulum problem: enter image description here
Here is my code:
from scipy.integrate import solve_ivp
from numpy import pi, sin, cos, linspace
g = 9.806 #Gravitational acceleration
l0 = 1 #Natural length of spring is 1
k = 2 #K value for spring is 2
OA = 2 #Length OA is 2
m = 1 #Mass of the particles is 1
def pendulumDynamics1(t, x): #Function to solve for theta-1 double-dot
x1 = x[0]
x2 = x[1]
y1 = y[0]
y2 = y[1]
z1 = z[0]
z2 = z[1]
Fs = -k*(z1-l0)
T = m*(x2**2)*OA + m*g*cos(x1) + Fs*cos(y1-x1)
x1dot = x2
x2dot = (Fs*sin(y1-x1) - m*g*sin(x1))/(m*OA) # angles are in radians
return [x1dot,x2dot]
def pendulumDynamics2(t, y): #Function to solve for theta-2 double-dot
x1 = x[0]
x2 = x[1]
y1 = y[0]
y2 = y[1]
z1 = z[0]
z2 = z[1]
Fs = -k*(z1-l0)
y1dot = y2
y2dot = (-g*sin(y1) - (Fs*cos(y1-x1)*sin(x1))/m + g*cos(y1-x1)*sin(x1) - x2*z1*sin(x1))/z1
return [y1dot,y2dot]
def pendulumDynamics3(t, z): #Function to solve for r double-dot (The length AB which is the spring)
x1 = x[0]
x2 = x[1]
y1 = y[0]
y2 = y[1]
z1 = z[0]
z2 = z[1]
Fs = -k*(z1-l0)
z1dot = z2
z2dot = g*cos(y1) - Fs/m + (y2**2)*z1 + x2*OA*cos(y1-x1) - (Fs*(sin(y1-x1))**2)/m + g*sin(x1)*sin(y1-x1)
return [z1dot,z2dot]
# Define initial conditions, etc
d2r = pi/180
x0 = [30*d2r, 0] # start from 30 deg, with zero velocity
y0 = [60*d2r, 0] # start from 60 deg, with zero velocity
z0 = [1, 0] #Start from r=1
t0 = 0
tf = 10
#Integrate dynamics, initial value problem
sol1 = solve_ivp(pendulumDynamics1,[t0,tf],x0,dense_output=True) # Save as a continuous solution
sol2 = solve_ivp(pendulumDynamics2,[t0,tf],y0,dense_output=True) # Save as a continuous solution
sol3 = solve_ivp(pendulumDynamics3,[t0,tf],z0,dense_output=True) # Save as a continuous solution
t = linspace(t0,tf,200) # determine solution at these times
dt = t[1]-t[0]
x = sol1.sol(t)
y = sol2.sol(t)
z = sol3.sol(t)
I have 3 functions in my code, each to solve for x, y, and z. I then use solve_ivp function to solve for x, and y, and z. The error in the code is:
`File "C:\Users\omora\OneDrive\Dokument\AERO 211\project.py", line 13, in pendulumDynamics1
y1 = y[0]
NameError: name 'y' is not defined`
I don't understand why it is saying that y is not defined, because I defined it in my functions.
Your system is closed without friction, thus can be captured by the Lagrange or Hamiltonian formalism. You have 3 position variables, thus a 6-dimensional dynamical state, complemented either by the velocities or the impulses.
Let q_k be theta_1, theta_2, r, Dq_k their time derivatives and p_k the impulse variables to q_k, then the dynamics can be realized by
def DoublePendulumSpring(u,t,params):
m_1, l_1, m_2, l_2, k, g = params
q_1,q_2,q_3 = u[:3]
p = u[3:]
A = [[l_1**2*(m_1 + m_2), l_1*m_2*q_3*cos(q_1 - q_2), -l_1*m_2*sin(q_1 - q_2)],
[l_1*m_2*q_3*cos(q_1 - q_2), m_2*q_3**2, 0],
[-l_1*m_2*sin(q_1 - q_2), 0, m_2]]
Dq = np.linalg.solve(A,p)
Dq_1,Dq_2,Dq_3 = Dq
T1 = Dq_2*q_3*sin(q_1 - q_2) + Dq_3*cos(q_1 - q_2)
T3 = Dq_1*l_1*cos(q_1 - q_2) + Dq_2*q_3
Dp = [-l_1*(m_2*Dq_1*T1 + g*(m_1+m_2)*sin(q_1)),
l_1*m_2*Dq_1*T1 - g*m_2*q_3*sin(q_2),
m_2*Dq_2*T3 + g*m_2*cos(q_2) + k*(l_2 - q_3) ]
return [*Dq, *Dp]
For a derivation see the Euler-Lagrange equations and their connection to the Hamilton equations. You might get asked about such a derivation.
This, after suitable defining the parameter tuple and initial conditions, can be fed to odeint and produces a solution that can then be plotted, animated or otherwise examined. The lower bob traces a path like the one below, not periodic and not very deterministic. (The fulcrum and the arc of the upper bob are also inserted, but less interesting.)
def pendulumDynamics1(t, x):
x1 = x[0]
x2 = x[1]
y1 = y[0]
y2 = y[1]
z1 = z[0]
z2 = z[1]
You only pass x as a parameter. The code inside the function has no idea what y and z refer to.
You will need to change the function call to also include those variables.
def pendulumDynamics1(t, x, y, z):
Related
I would like to solve two coupled equations in time and space, using the scikits.odes.dae solver.
My equations are as follows:
dy1/dt = dy1/dz + y2
y1 = 5 * y2
The code I have written is the following
import matplotlib.pyplot as plt
import numpy as np
from scikits.odes import dae
N = 51 #number os spacesteps
L = 1.0 #[m] length of sorbent bed, also a guess
dz = L/(N-1) #[m] length of space step
time = np.arange(0, 1.5, 0.1)
dydz = 1
y0 = [1, 0.2] #initial values y0[0] = y1 and y0[1] = y2
yp0 = [1, 1] #initial guess for \dot{y1} and \dot{y2}
def trial_space(t, y, ydot, result):
result[0] = ydot[0] - 6 * dydz + y[1]
result[1] = y[0] - 5 * y[1]
solver = dae('ida', trial_space)
solution = = solver.solve(time, y0, yp0)
Currently, I am feeding the solver a constant value of dydz, but actually I would like to use a central differencing scheme to obtain
dy/dz[i] = (y[i+1] - y[i-1])/(2*dz)
How do I integrate this into the solver?
I have tried adapting euler method code, and am using euler to calculate the first value so there are two for me to use in verlet but when i plot the graph i just get two perpendicular straight lines.
this is the code:
for t in t_array:
if t == 0:
x0 = x
x1 = x0
a = -k * x1 / m
x2 = x1 + dt * v
v = v + dt * a
x_list.append(x2)
v_list.append(v)
else:
x2 = x1
x1 = x0
x2 = 2*x1 - x0 + dt**2*a
v = (1/dt)*(x2 - x1)
x_list.append(x2)
v_list.append(v)
and then i plot a graph using matplotlib.
The parametric equations of the two curves are as follows:
Curve1: r(t) = (2(t-sin(t)),2(1 -cos(t)))
Curve2: s(t) = (2t - sin(t),2 - cos(t))
I need to find the points of intersection in the region [0,4π].
I was able to plot the graph for the mentioned region and observed 4 points of intersection. But I am not able to determine the exact points of intersection.
For non-parametric equations, fsolve from sympy can be used, but the curves which are given in their parametric forms, I am not able to find a workaround.
t = np.arange(-0.25*np.pi,4.25*np.pi,0.01)
rx = np.zeros(len(t))
ry = np.zeros(len(t))
for i in t:
rx = 2*(t - np.sin(t))
ry = 2*(1 - np.cos(t))
sx = np.zeros(len(t))
sy = np.zeros(len(t))
for i in t:
sx = 2*t - np.sin(t)
sy = 2 - np.cos(t)
plt.plot(rx,ry)
plt.plot(sx,sy)
For a given x you can find t for each curve and see if the corresponding y are the same. You can step over the x range with some grid looking for such locations where the thee curves hit and use bisection to zero in on a more precise x. Since you can't solve the parametrix x(t) - x for t, nsolve will have to be used to find an approximate t. Something like this finds values for your 4 roots (confirmed graphically) after correcting your OP equation for Curve1 to be the same as in the code you wrote afterwards.
f = lambda xx: a[1].subs(t, tt)-b[1].subs(t,nsolve(b[0]-xx,tlast))
tlast = 0 # guess for t for a given xx to be updated as we go
tol = 1e-9 # how tight the bounds on x must be for a solution
dx = 0.1
for ix in range(300):
xx = ix*dx
tt=nsolve(a[0]-xx,tlast)
y2 = f(xx)
if ix != 0 and yold*y2 < 0 and tt<4*pi:
tlast = tt # updating guess for t
# bisect for better xx now that bounding xx are found
x1 = xx-dx
x2 = xx
y1 = yold
while x2 - x1 > tol:
xm = (x1 + x2)/2
ym = f(xm)
if ym*y1 < 0:
y2 = ym
x2 = xm
elif ym != 0:
y1 = ym
x1 = xm
else:
break
print(xm) # a solution
yold = y2
I don't know of a more automated way to do this in SymPy.
The two curves don't intersect at the same time (that would be points where sin(t) = cos(t) = 0, which has no solutions). So you really want to know when
R = (2*t1 - 2*sin(t1), 2 - 2*cos(t1))
S = (2*t2 - sin(t2), 2 - cos(t2))
intersect.
This is two equations with two unknowns, so it is straightforward to solve with sympy.nsolve. You have to fiddle with the starting values a bit to find the ones that converge to different solutions. If you know what they are roughly from the graph that is the best place to start.
>>> t1, t2 = symbols('t1 t2')
>>> R = (2*t1 - 2*sin(t1), 2 - 2*cos(t1))
>>> S = (2*t2 - sin(t2), 2 - cos(t2))
>>> nsolve([R[0] - S[0], R[1] - S[1]], [t1, t2], [1, 1])
Matrix([
[ 1.09182358380672],
[0.398264297579454]])
>>> nsolve([R[0] - S[0], R[1] - S[1]], [t1, t2], [5, 5])
Matrix([
[5.19136172337286],
[5.88492100960013]])
>>> nsolve([R[0] - S[0], R[1] - S[1]], [t1, t2], [7, 7])
Matrix([
[7.37500889098631],
[6.68144960475904]])
>>> nsolve([R[0] - S[0], R[1] - S[1]], [t1, t2], [10, 10])
Matrix([
[11.4745470305524],
[12.1681063167797]])
Currently, I solve the following ODE system of equations using odeint
dx/dt = (-x + u)/2.0
dy/dt = (-y + x)/5.0
initial conditions: x = 0, y = 0
However, I would like to use solve_ivp which seems to be the recommended option for this type of problems, but honestly I don't know how to adapt the code...
Here is the code I'm using with odeint:
import numpy as np
from scipy.integrate import odeint, solve_ivp
import matplotlib.pyplot as plt
def model(z, t, u):
x = z[0]
y = z[1]
dxdt = (-x + u)/2.0
dydt = (-y + x)/5.0
dzdt = [dxdt, dydt]
return dzdt
def main():
# initial condition
z0 = [0, 0]
# number of time points
n = 401
# time points
t = np.linspace(0, 40, n)
# step input
u = np.zeros(n)
# change to 2.0 at time = 5.0
u[51:] = 2.0
# store solution
x = np.empty_like(t)
y = np.empty_like(t)
# record initial conditions
x[0] = z0[0]
y[0] = z0[1]
# solve ODE
for i in range(1, n):
# span for next time step
tspan = [t[i-1], t[i]]
# solve for next step
z = odeint(model, z0, tspan, args=(u[i],))
# store solution for plotting
x[i] = z[1][0]
y[i] = z[1][1]
# next initial condition
z0 = z[1]
# plot results
plt.plot(t,u,'g:',label='u(t)')
plt.plot(t,x,'b-',label='x(t)')
plt.plot(t,y,'r--',label='y(t)')
plt.ylabel('values')
plt.xlabel('time')
plt.legend(loc='best')
plt.show()
main()
It's important that solve_ivp expects f(t, z) as right-hand side of the ODE. If you don't want to change your ode function and also want to pass your parameter u, I recommend to define a wrapper function:
def model(z, t, u):
x = z[0]
y = z[1]
dxdt = (-x + u)/2.0
dydt = (-y + x)/5.0
dzdt = [dxdt, dydt]
return dzdt
def odefun(t, z):
if t < 5:
return model(z, t, 0)
else:
return model(z, t, 2)
Now it's easy to call solve_ivp:
def main():
# initial condition
z0 = [0, 0]
# number of time points
n = 401
# time points
t = np.linspace(0, 40, n)
# step input
u = np.zeros(n)
# change to 2.0 at time = 5.0
u[51:] = 2.0
res = solve_ivp(fun=odefun, t_span=[0, 40], y0=z0, t_eval=t)
x = res.y[0, :]
y = res.y[1, :]
# plot results
plt.plot(t,u,'g:',label='u(t)')
plt.plot(t,x,'b-',label='x(t)')
plt.plot(t,y,'r--',label='y(t)')
plt.ylabel('values')
plt.xlabel('time')
plt.legend(loc='best')
plt.show()
main()
Note that without passing t_eval=t, the solver will automatically choose the time points inside tspan at which the solution will be stored.
As a test for a more complicated system, I want to solve a differential equation dw/dz = w where the function w = w(z) is complex valued and z = x+iy as usual. The boundary conditions are w = i when z = i. The solution is of course complex and defined on the argand plane. I was hoping to solve this with some standard ODE solvers in python. My method is to first define a grid in the argand plane (lines of constant x and y) and then loop through each grid line and call an ODE solver on each iteration. In the below code I am attempting to integrate my differential equation between 1j and 2j, but the resulting vector of w is just 1j! Can anyone advise me what to do? Thanks
from scipy.integrate import ode
import numpy as np
from matplotlib.pylab import *
def myodeint(func, w0, z):
w0 = np.array(w0, complex)
func2 = lambda z, w: func(w, z) # odeint has these the other way :/
z0 = z[0]
solver = ode(func2).set_integrator('zvode').set_initial_value(w0, z0)
w = [solver.integrate(zp) for zp in z[1:]]
w.insert(0, w0)
return np.array(w)
def func2(w, z, alpha):
return alpha*w
if __name__ == '__main__':
# Set grid size in z plane
x_max = 3
x_min = 0
y_max = 3
y_min = 0
# Set grid resolution
dx = 0.1
dy = 0.1
# Number of nodes
x_nodes = int(np.floor((x_max-x_min)/dx)+1)
y_nodes = int(np.floor((y_max-y_min)/dy)+1)
# Create array to store value of w(z) at each node
ww = np.zeros((y_nodes,x_nodes), complex)
# Set boundary condition: w = w0 at x = x0, y = y0
x0 = 0
y0 = 1
i0 = (x0-x_min)/dx
j0 = (y_max-y0)/dy
w0 = 1j
ww[j0,i0] = w0
z0 = 1j
alpha = 1
z = np.linspace(z0, z0+1j, 200)
w = myodeint(lambda w, z: func2(w, z, alpha), [w0, 0, 0], z)