So most of the time I am asking questions on stack exchange I usually have the wrong answer, but this time my code is producing the correct graphs I just want to know why. My question is why does theta update properly despite its dependence on omega coming after theta. By all means run my code if you do not believe me. Just as a warning I am not a computer scientist, I am just a physics student attempting to solve problems using computational methods, but I am interested why it works. Here is my code:
# This program is designed to show the difference between
# undamped, damped, and critically damped oscillators behavior over
# time.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
m = float(raw_input('Enter a mass for the pendelum '))
g = 9.8 # gravity
l = float(raw_input('Enter a number for length '))
theta = float(raw_input('Enter a number for postion (radians) '))
theta1 = float(raw_input('Enter a number for postion (radians) '))
theta2 = float(raw_input('Enter a number for postion (radians) '))
omega = 0
omega1 = 0
omega2 = 0
ArrayTheta = [theta]
ArrayTheta1 = [theta1]
ArrayTheta2 = [theta2]
q1 = float(raw_input('Enter a number for the damping constant '))
q2 = float(raw_input('Enter a number for the damping constant '))
q3 = float(raw_input('Enter a number for the damping constant '))
step = .001
tx = np.arange(0,5,step)
for i in np.arange(step,5,step):
theta = theta + omega*step
ArrayTheta.append(theta)
omega = omega + -(g/l)*np.sin(theta)*step+(-q1*omega*step)
theta1 = theta1 + omega1*step
ArrayTheta1.append(theta1)
omega1 = omega1 - (g/l)*np.sin(theta1)*step+(-q2*omega1*step)
theta2 = theta2 + omega2*step
ArrayTheta2.append(theta2)
omega2 = omega2 - (g/l)*np.sin(theta2)*step+(-q3*omega2*step)
# this does not really make sense to me that theta2 is able to update despite
# omega2 being after it. Clearly I should have put omega2 before theta2 yet
# it still works.
plt.plot(tx,ArrayTheta, color ='blue')
plt.plot(tx,ArrayTheta1, color ='red')
plt.plot(tx,ArrayTheta2, color ='green')
plt.ylabel('Position in Radians')
plt.xlabel('Time')
blue_patch = mpatches.Patch(color='blue', label='Damped q1')
red_patch = mpatches.Patch(color='red', label='Damped q2')
green_patch = mpatches.Patch(color='green', label='Damped q3')
plt.legend(handles=[blue_patch,red_patch,green_patch])
plt.show()
You set omega, omega1 and omega2 to zero in the beginning and you update them in each for loop. You simply don't see, that the first two data points of each theta are the same.
All of your omega variables are initialized to 0 before the loop starts, so on the first iteration they will have that value during the update of their respective theta variables, so the first theta value appended to the results will be the same as the initial value. The update to the omega value changes the loop's behavior on the later iterations.
It might make more sense if you changed your code so that the ArrayTheta lists start empty and your loop started at 0. That way you'd only get the one copy of the initial theta values in the lists, rather than the two your current code puts at the start:
omega = 0
omega1 = 0
omega2 = 0
ArrayTheta = [] # start these as empty lists
ArrayTheta1 = []
ArrayTheta2 = []
# unchanged stuff omitted
for i in np.arange(0,5,step): # start this loop at zero so you have the right number of steps
theta = theta + omega*step # on the first iteration, this doesn't change theta
ArrayTheta.append(theta) # so the first value in the list will be the original theta
omega = omega + -(g/l)*np.sin(theta)*step+(-q1*omega*step) # omega changes if theta != 0
The reason it is working is because you have defined the omega variables
omega = 0
omega1 = 0
omega2 = 0
So when you are at iteration 0 of your for loop, all the omega values are equal to 0 before you calculate theta. Then that makes your theta values as
theta = theta
theta2 = theta2
theta3 = theta3
You then update the value of omega. In iteration 1, the omega variables are equal to what you defined them as in iteration 0. Basically, when you update omega in step n, it is used to update theta in step n+1.
It looks like your thetas are angles, the omegas are their time derivatives, and that the time derivatives of the omega variables depend on the values of theta. You have a second order initial value problem.
Your code as-is works, but you are using naive Euler technique. This "works" because any n-dimensional second degree ODE can be re-expressed as a 2n-dimensional first degree ODE. This however discards a lot of the geometry of the problem.
You would be using the symplectic Euler technique if you change the code so that the derivatives are updated before the angles. This tends to preserve some of the geometry of the problem.
Related
I am trying to simulate 3D plane flight. I have an issue with gamma value (Flight-path angle). It gets out of its bounds and then, the simulation stops. The gamma value is being calculated by this equation:
I turned it into this: m.Equation(gamma.dt()==tf*((L*m.cos(Mu)-mass*g*m.cos(gamma))/mass*V))
The target of the simulation is for the plane to reach certain X an Y values(m.Minimize(w*final*(x-pathx)**2) and m.Minimize(w*final*(pathy-y)**2)), while minimizing fuel consumed m.Maximize(0.2*mass*tf*final).
The solver controls gamma value, by controlling lift coefficient Cl, which affects the lift value L, which, in turn, affects gamma value. The equation that calculates lift L value looks like this: m.Equation(L==0.5*Ro*(V**2)*(Cl)*S). But in the end the solver can not control gamma value till the plane gets to its destination.
What could be messing with it?
My code:
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import math
#Gekko model
m = GEKKO(remote=False)
#Time points
nt = 11
tm = np.linspace(0,100,nt)
m.time = tm
# Variables
Ro = m.Var(value=1.1)#air density
g = m.Const(value=9.80665)
pressure = m.Var(value=101325)#
T = m.Var(value=281,lb=100)#temperature
T0 = m.Const(value=288)#temperature at see level
S = m.Const(value=122.6)
Cd = m.Var(value=0.025)#drag coef 0.06 works
#Cl = m.Const(value=0.3)#lift couef
FuelFlow = m.Var()
D = m.Var(value=10000,lb=0)#drag
Thrmax = m.Const(value=200000)#maximum throttle
Thr = m.Var()
V = m.Var(value=200,lb=45,ub=240)#velocity
gamma = m.Var(value=0,lb=-0.6,ub=1.2)# Flight-path angle
#gammaa = gamma.value
Xi = m.Var(value=0, lb=-2, ub=2.0)# Heading angle
x = m.Var(value=0,lb=-1000,ub=1015000)#x position
y = m.Var(value=0,lb=-1000,ub=1011000)#y position
h = m.Var(value=1000,lb=-20000,ub=50000)# height
targetH = m.Param(value=10000) #target flight altitude
mass = m.Var(value=60000,lb=10000)
pathx = m.Const(value=1000000) #intended distance in x direction
pathy = m.Const(value=1000000) #intended distance in y direction
L = m.Var(value=250000)#lift
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER=10000 # iteration number
#Fixed Variable
tf = m.FV(value=1,lb=0.0001,ub=100.0)#
tf.STATUS = 1
# Controlled parameters
Tcontr = m.MV(value=0.6,lb=0.0,ub=1)# solver controls throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 0
Mu = m.MV(value=0,lb=-1,ub=1)# solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 1e-3
Cl = m.MV(value=0.1,lb=-0.3,ub=0.9)# solver controls lift couef
Cl.STATUS = 1
Cl.DCOST = 1e-3
# Equations
m.Equation(Thr==Tcontr*Thrmax)
m.Equation(FuelFlow==0.75882*(1+(V/2938.5)))
m.Equation(D==0.5*Ro*(V**2)*Cd*S)
m.Equation(mass.dt()==tf*(-Thr*(FuelFlow/60000)))#
m.Equation(V.dt()==tf*((Thr-D-mass*g*m.cos(gamma))/mass)) #
m.Equation(x.dt()==tf*(V*(m.cos(gamma))*(m.cos(Xi))))#
m.Equation(x*final<=pathx)
#pressure and density part
m.Equation(T==T0-(0.0065*h))
m.Equation(pressure==101325*(1-(0.0065*h)/T0)**((g*0.0289652)/(8.31446*0.0065)))#
m.Equation(Ro*(8.31446*T)==(pressure*0.0289652))
#2D addition part
m.Equation(L==0.5*Ro*(V**2)*(Cl)*S)# Problem here or no problem, idk
m.Equation(mass*m.cos(gamma)*V*Xi.dt()==tf*((L*m.sin(Mu)))) #
m.Equation(y.dt()==tf*(V*(m.cos(gamma))*(m.sin(Xi))))#
#m.Equation((y-pathy)*final==0)
m.Equation(y*final<=pathy)
#3D addition part
m.Equation(h.dt()==tf*(V*(m.sin(gamma))))#
m.Equation(h*final<=targetH)
m.Equation(gamma.dt()==tf*((L*m.cos(Mu)-mass*g*m.cos(gamma))/mass*V))#
#Cd equation
m.Equation(Cd==((Cl)**2)/10)
# Objective Function
w = 1e4
m.Minimize(w*final*(x-pathx)**2) #1D part (x)
m.Minimize(w*final*(pathy-y)**2) #2D part (y)
m.Maximize(0.2*mass*tf*final) #objective function
m.options.IMODE = 6
m.options.NODES = 2 # it was 3 before
m.options.MV_TYPE = 1
m.options.SOLVER = 3
#m.open_folder() # to search for infeasibilities
m.solve()
tm = tm * tf.value[0]
fig, axs = plt.subplots(11)
fig.suptitle('Results')
axs[0].plot(tm,Tcontr,'r-',LineWidth=2,label=r'$Tcontr$')
axs[0].legend(loc='best')
axs[1].plot(tm,V.value,'b-',LineWidth=2,label=r'$V$')
axs[1].legend(loc='best')
axs[2].plot(tm,x.value,'r--',LineWidth=2,label=r'$x$')
axs[2].legend(loc='best')
axs[3].plot(tm,D.value,'g-',LineWidth=2,label=r'$D$')
axs[3].legend(loc='best')
axs[4].plot(tm,L.value,'p-',LineWidth=2,label=r'$L$')
axs[4].legend(loc='best')
axs[5].plot(tm,y.value,'p-',LineWidth=2,label=r'$y$')
axs[5].legend(loc='best')
axs[6].plot(tm,Ro.value,'p-',LineWidth=2,label=r'$Ro$')
axs[6].legend(loc='best')
axs[7].plot(tm,h.value,'p-',LineWidth=2,label=r'$h$')
axs[7].legend(loc='best')
axs[8].plot(tm,gamma.value,'p-',LineWidth=2,label=r'$gamma$')
axs[8].legend(loc='best')
axs[9].plot(tm,Cl.value,'p-',LineWidth=2,label=r'$Cl$')
axs[9].legend(loc='best')
axs[10].plot(tm,Cd.value,'p-',LineWidth=2,label=r'$Cd$')
axs[10].legend(loc='best')
plt.xlabel('Time')
#plt.ylabel('Value')
plt.show()
Important update
I want the solver to be able to keep gamma value within its boundaries till the necessary x and y values are achieved. The solver has issues with that.
Exhibit A.
In this case the simulation stopped because it could not keep gamma from exceeding its lower bound. gamma usually gets bigger when L (lift) * cos(mu) is bigger than mass*g*cos(gamma), and its gets lower when the situation is the opposite: mass*g*cos(gamma) is bigger than the L (lift) * cos(mu). Then the question becomes: why did suddenly mass*g*cos(gamma) became so much bigger than the L (lift) * cos(mu)? The g was constant. The mass did not change that much during the last moments of the simulation. The L (lift) did not become particularly small. cos(mu) is usually equal to 1 during this part of the simulation.
Exhibit B.
In this case the simulation stopped, once again, because it could not keep gamma value within its upper bound. It is visible , that Cl value during the last moments of the simulation was rising, it was necessary to keep gamma from exceeding its lower bound, but after that, the gamma value spiked and, for some reason, the solver did not lower Cl value, which forced the gamma value to exceed its upper bound, which forced the simulation to stop before the target goals are achieved.
In this case the issue is: Why does doesn't the solver lower the Cl value to stop gamma from exceeding its upper bound?
I'm not sure exactly what the problem is. It looks like the optimizer is controlling gamma throughout the horizon, and it always stays within its bounds of -0.6 to 1.2. Can you provide more information about what is going wrong?
max(gamma.value)
>>>> 1.2
min(gamma.value)
>>>> -0.6
Here is the solution that I generated by running your code:
Note that you get a fairly different solution if you solve the problem with a higher time resolution:
I am trying to simulate the orbit of a planet around a star using the Runge-Kutta 4 method. After speaking to tutors my code should be correct. However, I am not generating my expected 2D orbital plot but instead a linear plot. This is my first time using solve_ivp to solve a second order differential. Can anyone explain why my plots are wrong?
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
# %% Define derivative function
def f(t, z):
x = z[0] # Position x
y = z[1] # Position y
dx = z[2] # Velocity x
dy = z[3] # Velocity y
G = 6.674 * 10**-11 # Gravitational constant
M = 2 # Mass of binary stars in solar masses
c = 2*G*M
r = np.sqrt(y**2 + x**2) # Distance of planet from stars
zdot = np.empty(6) # Array for integration solutions
zdot[0] = x
zdot[1] = y
zdot[2] = dx # Velocity x
zdot[3] = dy #Velocity y
zdot[4] = (-c/(r**3))*(x) # Acceleration x
zdot[5] = (-c/(r**3))*(y) # Acceleration y
return zdot
# %% Define time spans, initial values, and constants
tspan = np.linspace(0., 10000., 100000000)
xy0 = [0.03, -0.2, 0.008, 0.046, 0.2, 0.3] # Initial positions x,y in R and velocities
# %% Solve differential equation
sol = solve_ivp(lambda t, z: f(t, z), [tspan[0], tspan[-1]], xy0, t_eval=tspan)
# %% Plot
#plot
plt.grid()
plt.subplot(2, 2, 1)
plt.plot(sol.y[0],sol.y[1], color='b')
plt.subplot(2, 2, 2)
plt.plot(sol.t,sol.y[2], color='g')
plt.subplot(2, 2, 3)
plt.plot(sol.t,sol.y[4], color='r')
plt.show()
With the ODE function as given, you are solving in the first components the system
xdot = x
ydot = y
which has well-known exponential solutions. As the exponential factor is the same long both solutions, the xy-plot will move along a line through the origin.
The solution is of course to fill zdot[0:2] with dx,dy, and zdot[2:4] with ax,ay or ddx,ddy or however you want to name the components of the acceleration. Then the initial state also has only 4 components. Or you need to make and treat position and velocity as 3-dimensional.
You need to put units to your constants and care that all use the same units. G as cited is in m^3/kg/s^2, so that any M you define will be in kg, any length is in m and any velocity in m/s. Your constants might appear ridiculously small in that context.
It does not matter what the comment in the code says, there will be no magical conversion. You need to use actual conversion computations to get realistic numbers. For instance using the numbers
G = 6.67408e-11 # m^3 s^-2 kg^-1
AU = 149.597e9 # m
Msun = 1.988435e30 # kg
hour = 60*60 # seconds in an hour
day = hour * 24 # seconds in one day
year = 365.25*day # seconds in a year (not very astronomical)
one could guess that for a sensible binary system of two stars of equal mass one has
M = 2*Msun # now actually 2 sun masses
x0 = 0.03*AU
y0 = -0.2*AU
vx0 = 0.008*AU/day
vy0 = 0.046*AU/day
For the position only AU makes sense as unit, the speed could also be in AU/hour. By https://math.stackexchange.com/questions/4033996/developing-keplers-first-law and Cannot get RK4 to solve for position of orbiting body in Python the speed for a circular orbit of radius R=0.2AU around a combined mass of 2*M is
sqrt(2*M*G/R)=sqrt(4*Msun*G/(0.2*AU)) = 0.00320 * AU/hour = 0.07693 AU/day
which is ... not too unreasonable if the given speeds are actually in AU/day. Invoke the computations from https://math.stackexchange.com/questions/4050575/application-of-the-principle-of-conservation to compute if the Kepler ellipse would look sensible
r0 = (x0**2+y0**2)**0.5
dotr0 = (x0*vx0+y0*vy0)/r0
L = x0*vy0-y0*vx0 # r^2*dotphi = L constant, L^2 = G*M_center*R
dotphi0 = L/r0**2
R = L**2/(G*2*M)
wx = R/r0-1; wy = -dotr0*(R/(G*2*M))**0.5
E = (wx*wx+wy*wy)**0.5; psi = m.atan2(wy,wx)
print(f"half-axis R={R/AU} AU, eccentr. E={E}, init. angle psi={psi}")
print(f"min. rad. = {R/(1+E)/AU} AU, max. rad. = {R/(1-E)/AU} AU")
which returns
half-axis R=0.00750258 AU, eccentr. E=0.96934113, init. angle psi=3.02626659
min. rad. = 0.00380969 AU, max. rad. = 0.24471174 AU
This gives an extremely thin ellipse, which is not that astonishing as the initial velocity points almost directly to the gravity center.
orbit variants with half-day steps marked, lengths in AU
If the velocity components were swapped one would get
half-axis R=0.07528741 AU, eccentr. E=0.62778767, init. angle psi=3.12777251
min. rad. = 0.04625137 AU, max. rad. = 0.20227006 AU
This is a little more balanced.
I am solving an ODE for an harmonic oscillator numerically with Python. When I add a driving force it makes no difference, so I'm guessing something is wrong with the code. Can anyone see the problem? The (h/m)*f0*np.cos(wd*i) part is the driving force.
import numpy as np
import matplotlib.pyplot as plt
# This code solves the ODE mx'' + bx' + kx = F0*cos(Wd*t)
# m is the mass of the object in kg, b is the damping constant in Ns/m
# k is the spring constant in N/m, F0 is the driving force in N,
# Wd is the frequency of the driving force and x is the position
# Setting up
timeFinal= 16.0 # This is how far the graph will go in seconds
steps = 10000 # Number of steps
dT = timeFinal/steps # Step length
time = np.linspace(0, timeFinal, steps+1)
# Creates an array with steps+1 values from 0 to timeFinal
# Allocating arrays for velocity and position
vel = np.zeros(steps+1)
pos = np.zeros(steps+1)
# Setting constants and initial values for vel. and pos.
k = 0.1
m = 0.01
vel0 = 0.05
pos0 = 0.01
freqNatural = 10.0**0.5
b = 0.0
F0 = 0.01
Wd = 7.0
vel[0] = vel0 #Sets the initial velocity
pos[0] = pos0 #Sets the initial position
# Numerical solution using Euler's
# Splitting the ODE into two first order ones
# v'(t) = -(k/m)*x(t) - (b/m)*v(t) + (F0/m)*cos(Wd*t)
# x'(t) = v(t)
# Using the definition of the derivative we get
# (v(t+dT) - v(t))/dT on the left side of the first equation
# (x(t+dT) - x(t))/dT on the left side of the second
# In the for loop t and dT will be replaced by i and 1
for i in range(0, steps):
vel[i+1] = (-k/m)*dT*pos[i] + vel[i]*(1-dT*b/m) + (dT/m)*F0*np.cos(Wd*i)
pos[i+1] = dT*vel[i] + pos[i]
# Ploting
#----------------
# With no damping
plt.plot(time, pos, 'g-', label='Undampened')
# Damping set to 10% of critical damping
b = (freqNatural/50)*0.1
# Using Euler's again to compute new values for new damping
for i in range(0, steps):
vel[i+1] = (-k/m)*dT*pos[i] + vel[i]*(1-(dT*(b/m))) + (F0*dT/m)*np.cos(Wd*i)
pos[i+1] = dT*vel[i] + pos[i]
plt.plot(time, pos, 'b-', label = '10% of crit. damping')
plt.plot(time, 0*time, 'k-') # This plots the x-axis
plt.legend(loc = 'upper right')
#---------------
plt.show()
The problem here is with the term np.cos(Wd*i). It should be np.cos(Wd*i*dT), that is note that dT has been added into the correct equation, since t = i*dT.
If this correction is made, the simulation looks reasonable. Here's a version with F0=0.001. Note that the driving force is clear in the continued oscillations in the damped condition.
The problem with the original equation is that np.cos(Wd*i) just jumps randomly around the circle, rather than smoothly moving around the circle, causing no net effect in the end. This can be best seen by plotting it directly, but the easiest thing to do is run the original form with F0 very large. Below is F0 = 10 (ie, 10000x the value used in the correct equation), but using the incorrect form of the equation, and it's clear that the driving force here just adds noise as it randomly moves around the circle.
Note that your ODE is well behaved and has an analytical solution. So you could utilize sympy for an alternate approach:
import sympy as sy
sy.init_printing() # Pretty printer for IPython
t,k,m,b,F0,Wd = sy.symbols('t,k,m,b,F0,Wd', real=True) # constants
consts = {k: 0.1, # values
m: 0.01,
b: 0.0,
F0: 0.01,
Wd: 7.0}
x = sy.Function('x')(t) # declare variables
dx = sy.Derivative(x, t)
d2x = sy.Derivative(x, t, 2)
# the ODE:
ode1 = sy.Eq(m*d2x + b*dx + k*x, F0*sy.cos(Wd*t))
sl1 = sy.dsolve(ode1, x) # solve ODE
xs1 = sy.simplify(sl1.subs(consts)).rhs # substitute constants
# Examining the solution, we note C3 and C4 are superfluous
xs2 = xs1.subs({'C3':0, 'C4':0})
dxs2 = xs2.diff(t)
print("Solution x(t) = ")
print(xs2)
print("Solution x'(t) = ")
print(dxs2)
gives
Solution x(t) =
C1*sin(3.16227766016838*t) + C2*cos(3.16227766016838*t) - 0.0256410256410256*cos(7.0*t)
Solution x'(t) =
3.16227766016838*C1*cos(3.16227766016838*t) - 3.16227766016838*C2*sin(3.16227766016838*t) + 0.179487179487179*sin(7.0*t)
The constants C1,C2 can be determined by evaluating x(0),x'(0) for the initial conditions.
I have plotted the projectile motion given initial conditions.
What I'm stuck on is trying to make tmax dependent on the initial conditions so if you change the initial conditions tmax changes to a reasonable value. Secondly How would I find the distance to impact, the time of flight and the velocity at impact. These all would depend on when y becomes zero but IDK how to use that info.
Here is my code. Thanks.
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
c=0.65 #Constant friction term
m=0.1 #Object's mass
g=9.81 #Gravitational acceleration
theta=50 #Angle
V0=10 #Initial Velocity
Vx0=np.cos(np.deg2rad(theta))*V0 #Calculates initial speed along the x-axis
Vy0=np.sin(np.deg2rad(theta))*V0 #Calculates initial speed along the y-axis
t0 = 0.0 #Initial time
tmax=1 #Final time
steps=20 #Number of time step
tAF = np.linspace(t0, tmax, steps)# Creates a 1-D array of time values
y0AF = [0.0, Vx0, 0.0, Vy0] #Initial condition for x-position, V along x, y-position, V along y
def derivative(yAF,tF): #Function which calculates the derivatives
Vx = yAF[1] #Element in array for the velocity along x axis
Vy = yAF[3] #Element in array for the velocity along y axis
return [Vx, -c*Vx/m, Vy, -g-c*Vy/m] #Function outputs [dx/dt, dVx/dt, dy/dt, dVy/dt]
yM = odeint(derivative, y0AF, tAF) #Array with the solution for the differential equation
plt.plot(yM[:,0],yM[:,2], '.') #Plots y over x
plt.xlabel('x-distance'), plt.ylabel('y-distance')
plt.show()
MAXy=np.amax(yM[:,2])
print('Maximum height reached is',MAXy)
Instead of trying to integrate the differential equations all in one use of odeint(), you can use multiple steps, changing the initial conditions to continue the integration, e.g. by replacing the line:
yM = odeint(derivative, y0AF, tAF)
... with something like:
y = np.array([y0AF])
tLargeStep = 0.3
i = 0
while 1:
tAF = np.linspace(i*tLargeStep, (i+1)*tLargeStep, steps)
yM = odeint(derivative, y[-1,:], tAF)
y = np.concatenate((y,yM[1:,:]))
if y[-1,2] < 0:
break
i += 1
plt.plot(y[:,0],y[:,2], '.') #Plots y over x
plt.xlabel('x-distance'), plt.ylabel('y-distance')
plt.show()
In this case, the integration will continue large step by large step until y is less than zero at the end of a large step.
Having obtained the data in y, you could use numpy.interpolate() on those data to find the distance to impact, the time of flight and the velocity at impact.
Alternatively, if you use scipy.integrate.solve_ivp() instead of odeint(), solve_ivp() allows you to specify events to track, and to make some of those events terminal, i.e. to make the solver stop computing when the specified event occurs.
I am trying to model firing a projectile from a slingshot.
This is my code:
from pylab import *
import numpy as np
from scipy.integrate import odeint
import seaborn
## set initial conditions and parameters
g = 9.81 # acceleration due to gravity
th = 30 # set launch angle
th = th * np.pi/180. # convert launch angle to radians
v0 = 10.0 # set initial speed
c = 0.5 # controls strength of air drag
d = 0.02 # diameter of the spherical rock
A = pi * (d/2)**2 #the cross-sectional area of the spherical rock
ro = 1.2041 #the density of the medium we are perfoming the launch in
m = 0.01 #mass
x0=0 # specify initial conditions
y0=0
vx0 = v0*sin(th)
vy0 = v0*cos(th)
## defining our model
def slingshot_model(state,time):
z = zeros(4) # create array to hold z vector
z[0] = state[2] # z[0] = x component of velocity
z[1] = state[3] # z[1] = y component of velocity
z[2] = - c*A*ro/2*m*sqrt(z[0]**2 + z[1]**2)*z[0] # z[2] = acceleration in x direction
z[3] = -g/m - c*A*ro/2*m*sqrt(z[0]**2 + z[1]**2)*z[1] # z[3] = acceleration in y direction
return z
## set initial state vector and time array
X0 = [x0, y0, vx0, vy0] # set initial state of the system
t0 = 0
tf = 4 #final time
tau = 0.05 #time step
# create time array starting at t0, ending at tf with a spacing tau
t = arange(t0,tf,tau)
## solve ODE using odeint
X = odeint(slingshot_model,X0,t) # returns an 2-dimensional array with the
# first index specifying the time and the
# second index specifying the component of
# the state vector
# putting ':' as an index specifies all of the elements for
# that index so x, y, vx, and vy are arrays at times specified
# in the time array
x = X[:,0]
y = X[:,1]
vx = X[:,2]
vy = X[:,3]
plt.rcParams['figure.figsize'] = [10, 10]
plot(x,y)
But it gives me this plot that doesn't make sense to me:
What am I missing? The values shouldn't come out like they do, but for the life of me I can't see why.
It is probably something trivial, but I have been staring at this too long, so I figured bringing in a fresh set of eyes is the best course of action.
I think there are at least two major problems with your computations:
Usually angle is defined with regard to the X-axis. Therefore
vx0 = v0*cos(th) # not sin
vy0 = v0*sin(th) # not cos
Most importantly, why are you dividing acceleration of the free fall g by the mass? (see z[3] = -g/m...) This makes no sense to me. DO NOT divide by mass!
EDIT:
Based on your comment and linked formulae, it is clear that your code also suffers from a third mistake: air drag terms should be inverse-proportional to mass: