Odeint for projectile motion - python

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.

Related

Planetary orbit shown as linear graph using rk4

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.

Brownian motion in python 2D

I want to create a Brownian motion sim
My particle will start at the (0,0), the origin then I've created NumPy random arrays for the x and y direction for example, x = [-2,1,3] and y = [0,-2,1]. Since the particle starts at the origin (0th point) it will to the next point by -2 in the direction and 0 in the y (the 1st point), and then for the 2nd point, it will 1 unit to the right (+1 in the x) and -2 units to the left(-2 in the y).
My question is how would I make it so each point acts as the new origin kind of like adding vectors. I'm thinking I would need some sort of for loop, but not sure how I would set it up.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation as am
np.random.seed(69420)
N=1000 #Number of steps
# Have the particle at the origin in 2D (x,y) plane
# Each step will have a lenght of 1 unit. Lets call it meters
# Each step is one second
# And this simulation will run for 100 seconds
## Now how to make a particle randomly walk???
t=100 # time interval
dt=t/N # delta time from each step
dx = np.random.randint(-5,5,N,dtype=int)# This would be our detla x for each step
dy = np.random.randint(-5,5,N,dtype=int)
dx_0 = np.zeros(N, dtype=int)
dy_0 = np.zeros(N,dtype=int)
X = dx_0+dx
Y = dy_0+dy
print(X)
xdis = np.cumsum(X) #Total distance travled after N steps
ydis = np.cumsum(Y)
plt.plot(dx_0,dy_0,'ko')
plt.plot(xdis,ydis,'b-')
plt.show()
This is what I have so far. Any help is appreciated.
You need to take N steps, starting from (0,0). Track your steps using a variable prev = [0,0], and make changes to it for each step. Simplified code:
prev = [0,0]
for i in range(N):
new_x = prev[0]+dx[i]
new_y = prev[1]+dy[i]
# do what you need with new_x and new_y
prev = [new_x, new_y]
Since it seems like you wish to graph the entire walk, just sum all these steps?
origin = np.array([0, 0])
x_series = np.cumsum(dx) + origin[0]
# (Similarly for y)
This is only for 1 random walker. If you have multiple random walkers - each of them will have a starting point and separate chain of dx-s.

Python Slingshot Model

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:

Why Does My Code Update

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.

projectile motion simple simulation using numpy matplotlib python

I am trying to graph a projectile through time at various angles. The angles range from 25 to 60 and each initial angle should have its own line on the graph. The formula for "the total time the projectile is in the air" is the formula for t. I am not sure how this total time comes into play, because I am supposed to graph the projectile at various times with various initial angles. I imagine that I would need x,x1,x2,x3,x4,x5 and the y equivalents in order to graph all six of the various angles. But I am confused on what to do about the time spent.
import numpy as np
import matplotlib.pylab as plot
#initialize variables
#velocity, gravity
v = 30
g = -9.8
#increment theta 25 to 60 then find t, x, y
#define x and y as arrays
theta = np.arange(25,65,5)
t = ((2 * v) * np.sin(theta)) / g #the total time projectile remains in the #air
t1 = np.array(t) #why are some negative
x = ((v * t1) * np.cos(theta))
y = ((v * t1) * np.sin(theta)) - ((0.5 * g) * (t ** 2))
plot.plot(x,y)
plot.show()
First of all g is positive! After fixing that, let's see some equations:
You know this already, but lets take a second and discuss something. What do you need to know in order to get the trajectory of a particle?
Initial velocity and angle, right? The question is: find the position of the particle after some time given that initial velocity is v=something and theta=something. Initial is important! That's the time when we start our experiment. So time is continuous parameter! You don't need the time of flight.
One more thing: Angles can't just be written as 60, 45, etc, python needs something else in order to work, so you need to write them in numerical terms, (0,90) = (0,pi/2).
Let's see the code:
import numpy as np
import matplotlib.pylab as plot
import math as m
#initialize variables
#velocity, gravity
v = 30
g = 9.8
#increment theta 25 to 60 then find t, x, y
#define x and y as arrays
theta = np.arange(m.pi/6, m.pi/3, m.pi/36)
t = np.linspace(0, 5, num=100) # Set time as 'continous' parameter.
for i in theta: # Calculate trajectory for every angle
x1 = []
y1 = []
for k in t:
x = ((v*k)*np.cos(i)) # get positions at every point in time
y = ((v*k)*np.sin(i))-((0.5*g)*(k**2))
x1.append(x)
y1.append(y)
p = [i for i, j in enumerate(y1) if j < 0] # Don't fall through the floor
for i in sorted(p, reverse = True):
del x1[i]
del y1[i]
plot.plot(x1, y1) # Plot for every angle
plot.show() # And show on one graphic
You are making a number of mistakes.
Firstly, less of a mistake, but matplotlib.pylab is supposedly used to access matplotlib.pyplot and numpy together (for a more matlab-like experience), I think it's more suggested to use matplotlib.pyplot as plt in scripts (see also this Q&A).
Secondly, your angles are in degrees, but math functions by default expect radians. You have to convert your angles to radians before passing them to the trigonometric functions.
Thirdly, your current code sets t1 to have a single time point for every angle. This is not what you need: you need to compute the maximum time t for every angle (which you did in t), then for each angle create a time vector from 0 to t for plotting!
Lastly, you need to use the same plotting time vector in both terms of y, since that's the solution to your mechanics problem:
y(t) = v_{0y}*t - g/2*t^2
This assumes that g is positive, which is again wrong in your code. Unless you set the y axis to point downwards, but the word "projectile" makes me think this is not the case.
So here's what I'd do:
import numpy as np
import matplotlib.pyplot as plt
#initialize variables
#velocity, gravity
v = 30
g = 9.81 #improved g to standard precision, set it to positive
#increment theta 25 to 60 then find t, x, y
#define x and y as arrays
theta = np.arange(25,65,5)[None,:]/180.0*np.pi #convert to radians, watch out for modulo division
plt.figure()
tmax = ((2 * v) * np.sin(theta)) / g
timemat = tmax*np.linspace(0,1,100)[:,None] #create time vectors for each angle
x = ((v * timemat) * np.cos(theta))
y = ((v * timemat) * np.sin(theta)) - ((0.5 * g) * (timemat ** 2))
plt.plot(x,y) #plot each dataset: columns of x and columns of y
plt.ylim([0,35])
plot.show()
I made use of the fact that plt.plot will plot the columns of two matrix inputs versus each other, so no loop over angles is necessary. I also used [None,:] and [:,None] to turn 1d numpy arrays to 2d row and column vectors, respectively. By multiplying a row vector and a column vector, array broadcasting ensures that the resulting matrix behaves the way we want it (i.e. each column of timemat goes from 0 to the corresponding tmax in 100 steps)
Result:

Categories