Search code examples
pythonphysicsprojectile

Animating a Projectile's Trajectory Python


I'm working on learning python more in depth than from my previous questions now that my internship is over and I hit an issue

I'm using a book "Doing Math With Python" by Amit Saha which what I decided to jump to was 'Animating a Projectile's Trajectory. I spent an hour trying to figure it out on my own then another 2 days on the internet and I still can't understand why I'm getting the error I'm getting

AttributeError: 'float' object has no attribute 'append'

if I don't have the float in the code then it doesn't work at all and I get this

TypeError: a float is required

I want to get this project done hopefully before we leave the projectile motion unit in my high school physics just as a cool thing I learned to do. please help. I can get it to draw the trajectory, just not animate it :(

from matplotlib import pyplot as plt
from matplotlib import animation
import math

g = 9.8

def get_intervals(u, theta):

    t_flight = 2*u*math.sin(theta)/g
    intervals = []
    start = 0
    intervals = 0.005
    while start < t_flight:
        intervals.append(start)
        start = start + interval
    return intervals

def update_position(i, circle, intervals, u, theta):

    t = intervals[i]
    x = u*math.cos(theta)*t
    y = u*math.sin(theta)*t - 0.5*g*t*t
    circle.center = x, y
    return circle,

def create_animation(u, theta):

    intervals = get_intervals(u,theta)

    xmin = 0
    xmax = u*math.cos(theta)*intervals[-1]
    ymin = 0
    t_max = u*math.sin(theta)/g
    ymax = u*math.sin(theta)*t_max - 0.5*g*t_max**2
    fig = plt.gcf()
    ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax))

    circle = plt.Circle((xmin, ymin), 1.0)
    ax.add_patch(circle)
    anim = animation.FuncAnimation(fig, update_position,
                        fargs=(circle, intervals, u, theta),
                        frames=len(intervals), interval=1,
                        repeat=False)

    plt.title('Projectile Motion')
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.show()

if __name__ == '__main__':
    try:
        u = float(input('Enter the initial velocity (m/s): '))
        theta = float(input('Enter the angle of projection (degrees): '))
    except ValueError:
        print('You Entered an invalid input')
    else:
        theta = (math.radians(theta))
        create_animation(u, theta)

Solution

  • Your code is very close! Right now there is an error based on the variable intervals being defined twice and the variable interval never being defined. So change intervals = 0.005 to interval = 0.005 as in the following code:

    def get_intervals(u, theta):
    
        t_flight = 2*u*math.sin(theta)/g
        intervals = []
        start = 0
        interval = 0.005
        while start < t_flight:
            intervals.append(start)
            start = start + interval
        return intervals
    

    Now the code will run but the plot will look very different for various velocities and thetas. In fact for many initial conditions, you will only see a plot of blue. Let's look at the problems one by one:

    1. The radius of the ball, rad, is 1 m. If the ball travels less than 1 m in the x-direction or y-direction, the the blue ball will dominate the screen.
    2. The x-axis and y-axis have very different sizes and scales. This will make the ball an oblong oval rather than a circle.

    I changed your create_animation() function to fix these small issues. Please read the comments I've placed to understand the subtle changes

    def create_animation(u, theta):
        intervals = get_intervals(u,theta)
    
        xmin = 0
        xmax = u*math.cos(theta)*intervals[-1]
        ymin = 0
        t_max = u*math.sin(theta)/g
        ymax = u*math.sin(theta)*t_max - 0.5*g*t_max**2
    
        plotmax = max(xmax, ymax) # Pick the largest dimension of the two
        fig = plt.gcf()
    
        # Set both maxima to the same value to make a square plot
        ax = plt.axes(xlim=(xmin, plotmax), ylim=(ymin, plotmax)) 
    
        # Make sure the two axes are scaled the same...
        #    (we want a circle.. not a messed up oval)
        ax.set_aspect('equal')
    
    
        rad = plotmax/20. # Make sure the circle doesn't dominate the plot
    
        circle = plt.Circle((xmin, ymin), rad) # Use rad instead of 1.0
        ax.add_patch(circle)
        anim = animation.FuncAnimation(fig, update_position,
                            fargs=(circle, intervals, u, theta),
                            frames=len(intervals), interval=1,
                            repeat=False)
    
        plt.title('Projectile Motion')
        plt.xlabel('X [m]') # Units are nice :)
        plt.ylabel('Y [m]')
        plt.show()