4

I'm trying to animate a projectile motion path using Python. To achieve this I'm using matplotlib's animation module. My full script is below.

#!/usr/bin/env python

import math
import sys

import matplotlib.animation as anim
from matplotlib.pyplot import figure, show

# Gravitational acceleration in m/s/s
g = -9.81
# Starting velocity in m/s.
vel = 100
# Starting angle in radians.
ang = 45 * math.pi / 180
# Starting height in m.
y0 = 0
# Time settings.
t = 0
dt = 0.1
time = -vel**2 * math.sin(2 * ang) / g

def projectile():
    print g, vel, ang, y0, dt, time
    print t # Crashes here with UnboundLocalError: local variable 't' referenced before assignment
    t=0 # With this it works
    x = 0
    y = 0.1
    xc = []
    yc = []

    # Simulate the projectile.
    while t < time:
        x = vel * math.cos(ang) * t
        y = vel * math.sin(ang) * t + (g * t**2) / 2 + y0
        if y < 0:
            break
        t += dt
        xc.append(x)
        yc.append(y)
        yield x, y

def update_frame(sim):
    x, y = sim[0], sim[1]
    line.set_data(x, y)
    return line,

def init():
    line.set_data([], [])
    return line,

# Show the results.
fig = figure()
ax = fig.add_subplot(111)
ax.set_xlim([-5,1500])
ax.set_ylim([0,300])

# ax.plot returns a single-element tuple, hence the comma after line.
line, = ax.plot([], [], 'ro', ms=5)
ani = anim.FuncAnimation(fig=fig, func=update_frame, frames=projectile, blit=True, interval=20, init_func=init, repeat=False)
show()

The problem I have is that I seem to be able to use every variable, except t. I did it to create a stop condition so it would run only once and I later found out about repeat=False, but I'm still curious about this. Why can't I use t inside projectile? I am running Python 2.7.6 with Matplotlib 1.3.1 from the Anaconda 1.9.1 package.

dwitvliet
  • 7,242
  • 7
  • 36
  • 62
tikker
  • 226
  • 1
  • 6

1 Answers1

4

The problem arises because you try to reassign the global variable t.

The variables g, vel, ang, y0, dt, time you only access (without reassigning them), so python tries to access them both locally and then globally. However, when you reassign t with the line t += dt, you are really telling python to create a local variable with the identifier t and assign it the desired value. Therefore, the global identifier t cannot be accessed as you've told python that t is local, and when you try to access t before it is assigned, you the UnboundLocalError is raised.

To tell python to reassign t as a global variable, you simply need to add global t to the beginning of your function:

t = 0
(..)
def projectile():
    print g, vel, ang, y0, dt, time
    global t # now t can be edited as a global variable
    print t #works
    x = 0
    y = 0.1
    xc = []
    yc = []

    while t < time:
        (..)
        t += dt

EDIT:

As flebool pointed out, you can actually still change a global variable as long as you don't reassign the identifier for it. For example, the code below is valid:

>>> l = [1,2,3]
>>> def changelist():
...    l.append(4)
...
>>> changelist()
>>> l
[1,2,3,4]
dwitvliet
  • 7,242
  • 7
  • 36
  • 62
  • This is very misleading. The problem is not in changing the variable `t`, but in assigning the identifier `t` to something else. For example If you take `t=numpy.array([0,3])` defined at the top, and then you try to change `t[1]` within `projectile()` without declaring it `global`, it will still work. – gg349 Jul 30 '14 at 16:02
  • @flebool Thank you, you're right. I've updated my answer accordingly. – dwitvliet Jul 30 '14 at 16:40
  • Well done. I'd say that the citation from the link is still misleading and I would get rid of it, but this is just my opinion – gg349 Jul 30 '14 at 21:07
  • 1
    @flebool No, I agree - citations just look so great, so I was trying my best to keep it. ;) – dwitvliet Jul 30 '14 at 21:19