1

I've written a code which looks at projectile motion of an object with drag. I'm using odeint from scipy to do the forward Euler method. The integration runs until a time limit is reached. I would like to stop integrating either when this limit is reached or when the output for ry = 0 (i.e. the projectile has landed).

def f(y, t):
    # takes vector y(t) and time t and returns function y_dot = F(t,y) as a vector
    # y(t) = [vx(t), vy(t), rx(t), ry(t)]

    # magnitude of velocity calculated
    v = np.sqrt(y[0] ** 2 + y[1] **2)

    # define new drag constant k
    k = cd * rho * v * A / (2 * m)

    return [-k * y[0], -k * y[1] - g, y[0], y[1]] 


def solve_f(v0, ang, h0, tstep, tmax=1000):
    # uses the forward Euler time integration method to solve the ODE using f function
    # create vector for time steps based on args
    t = np.linspace(0, tmax, tmax / tstep)

    # convert angle from degrees to radians
    ang = ang * np.pi / 180

    return odeint(f, [v0 * np.cos(ang), v0 * np.sin(ang), 0, h0], t)

solution = solve_f(v0, ang, h0, tstep)

I've tried several loops and similar to try to stop integrating when ry = 0. And found this question below but have not been able to implement something similar with odeint. solution[:,3] is the output column for ry. Is there a simple way to do this with odeint?

Lutz Lehmann
  • 25,219
  • 2
  • 22
  • 51
Jack
  • 13
  • 5

2 Answers2

0

Checkout scipy.integrate.ode here. It is more flexible than odeint and helps with what you want to do.

A simple example using a vertical shot, integrated until it touches ground:

from scipy.integrate import ode, odeint
import scipy.constants as SPC

def f(t, y):
    return [y[1], -SPC.g]

v0 = 10
y0 = 0

r = ode(f)
r.set_initial_value([y0, v0], 0)

dt = 0.1
while r.successful() and r.y[0] >= 0:
    print('time: {:.3f}, y: {:.3f}, vy: {:.3f}'.format(r.t + dt, *r.integrate(r.t + dt)))

Each time you call r.integrate, r will store current time and y value. You can pass them to a list if you want to store them.

Tarifazo
  • 4,118
  • 1
  • 9
  • 22
  • You can do the same with odeint, `y = odeint(f, y, [t, t+dt])[-1]` performs a time step of size `dt`. – Lutz Lehmann Dec 10 '18 at 13:35
  • You can, but I find an object oriented ODE integrator more confortable for controlling integration. In your solution you also need to specify the initial condition in each step using the last solution (which `ode` does automatically) – Tarifazo Dec 10 '18 at 14:45
  • Thanks for the suggestion to use ode. I've rewritten my function and now I can stop integrating when the output reaches a certain value. However the integration doesn't seem to be evaluating properly, I've updated the question with revised code. Thanks, Jack. – Jack Dec 11 '18 at 13:54
  • Don't worry, I had my angles in degrees, all sorted now! – Jack Dec 11 '18 at 13:56
0

Let's solve this as the boundary value problem that it is. We have the conditions x(0)=0, y(0)=h0, vx(0)=0, vy(0)=0 and y(T)=0. To have a fixed integration interval, set t=T*s, which means that dx/ds=T*dx/dt=T*vx etc.

def fall_ode(t,u,p):
    vx, vy, rx, ry = u
    T = p[0]
    # magnitude of velocity calculated
    v = np.hypot(vx, vy)
    # define new drag constant k
    k = cd * rho * v * A / (2 * m)

    return np.array([-k * vx, -k * vy - g, vx, vy])*T 

def solve_fall(v0, ang, h0):
    # convert angle from degrees to radians
    ang = ang * np.pi / 180
    vx0, vy0 = v0*np.cos(ang), v0*np.sin(ang)

    def fall_bc(y0, y1, p): return [ y0[0]-vx0, y0[1]-vy0, y0[2], y0[3]-h0, y1[3] ]

    t_init = [0,1]
    u_init = [[0,0],[0,0],[0,0], [h0,0]]
    p_init = [1]
    res = solve_bvp(fall_ode, fall_bc, t_init, u_init, p_init)
    print res.message
    if res.success: 
        print "time to ground: ", res.p[0]
    # res.sol is a dense output, evaluation interpolates the computed values
    return res.sol

sol = solve_fall(300, 30, 20)
s = np.linspace(0,1,501)
u = sol(s)
vx, vy, rx, ry = u
plt.plot(rx, ry)
Lutz Lehmann
  • 25,219
  • 2
  • 22
  • 51