Unable to reference one particular variable declared outside a function - python

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.

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]

Related

Function not callable anymore after one try

I'm coding a function right now which has a really weird problem. When I define the function Psi(t) and call it to be plotted, it works fine. But, when you call it again to be plotted, it sends an error 'numpy.ndarray' object is not callable. When you click play (on Jupyter notebook) on Psi(t) to define it again then call it to be plotted, it works fine again. You'd have to define it again if you wanna change a parameter then plot Psi(t) again. I don't know if it's with the code or the software that I use for python (VS code). Anyhow, here's the code:
# Constants
m = 9.109e-31 # mass of electron in kg
L = 1e-8 # length of box in m
hbar = 1.0546e-34 # hbar in J/s
x0 = L/2 # midpoint of box
sigma = 1e-10 # width of wave packet in m
kappa = 5e10 # wave number in 1/m
N = 1000 # number of grid slices
def psi0(x):
return np.exp( -(x - x0)**2/(2*sigma**2) )*np.exp(-1j*kappa*x)
#Discrete sine transform
def dst(y):
N = len(y)
y2 = np.empty(2*N,float)
y2[0] = y2[N] = 0.0
y2[1:N] = y[1:]
y2[:N:-1] = -y[1:]
a = -np.imag(rfft(y2))[:N]
a[0] = 0.0
return a
#Inverse discrete sine transform
def idst(a):
N = len(a)
c = np.empty(N+1,complex)
c[0] = c[N] = 0.0
c[1:N] = -1j*a[1:]
y = irfft(c)[:N]
y[0] = 0.0
return y
x_n = np.zeros(N, complex)
xgrid = range(N)
for i in range(N):
x_n[i] = psi0(i*L/N)
alpha = dst(np.real(x_n))
eta = dst(np.imag(x_n))
def Psi(t):
k = np.arange(1, N+1)
energy_k = (k**2*np.pi**2*hbar)/(2*m*L**2)
cos, sin = np.cos(energy_k*t), np.sin(energy_k*t)
re_psi = alpha*cos - eta*sin
im_psi = eta*cos + alpha*sin
psi = re_psi + im_psi
return idst(psi)
Psi = Psi(2e-16)
plt.plot(xgrid,Psi)
plt.show()
I'm hoping someone can help.
On the third-to-last line:
Psi = Psi(2e-16)
You are updating the reference to Psi from the function to the return value. Upon doing so, Psi can no longer be used as a function. It is advisory to never use variables with the same names as functions or classes in your code. Solutions are to either rename the variable, or rename the function and function call.

Animation was deleted without rendering anything. This is most likely unintended?

I'm wondering, what is wrong with this code that tries to reproduce a animated version of Mandelbrot fractals. I think the structure-wise, it should be ok, since I can get a static plot without the animation part, but when I try to run it with animation to perform iterations, it give me error.
Upon execution of the code, I got UserWarning: Animation was deleted without rendering anything. This is most likely unintended. To prevent deletion, assign the Animation to a variable that exists for as long as you need the Animation.
but apparently a variable is assigned to it...
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def mandelbrot(x, y, threshold):
"""Calculates whether the number c = x + i*y belongs to the
Mandelbrot set. In order to belong, the sequence z[i + 1] = z[i]**2 + c
must not diverge after 'threshold' number of steps. The sequence diverges
if the absolute value of z[i+1] is greater than 4.
"""
c = complex(x, y)
z = complex(0, 0)
for i in range(threshold):
z = z**2 + c
if abs(z) > 4.: # it diverged
return i
return threshold - 1 # it didn't diverge
x_start, y_start = -2, -1.5
width, height = 3, 3
density_per_unit = 250
# real and imaginary axis
re = np.linspace(x_start, x_start + width, width * density_per_unit )
im = np.linspace(y_start, y_start + height, height * density_per_unit)
def animate(i):
ax.clear()
ax.set_xticks([], [])
ax.set_yticks([], [])
X = np.empty((len(re), len(im)))
threshold = round(1.15**(i + 1))
# iterations for the current threshold
for i in range(len(re)):
for j in range(len(im)):
X[i, j] = mandelbrot(re[i], im[j], threshold)
# associate colors to the iterations with an iterpolation
img = ax.imshow(X.T, interpolation="bicubic", cmap='magma')
return [img]
fig = plt.figure(figsize=(10, 10))
ax = plt.axes()
anim = FuncAnimation(fig, animate, frames=45, interval=100, repeat=True)

local variable referenced before assignment | matplotlib

I want to visualize the central limit theorem for an exemplary PDF. The following code works as I wish:
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
x, y = sp.symbols('x y')
dens = sp.exp(-x) #PDF
def sum(dens,n):
"""
Parameters
----------
dens : probabilty density function
n : amount of iteration
Returns
-------
pdf after n iterations
"""
dens2 = dens.subs(x, y-x) #PDF for one further summed random variable
i=1
while i<=n:
int = sp.integrate(dens*dens2, (x,0,y))
int = int.subs(y,x)
dens = int
i += 1
return int
#plot for n th iteration
n1 = 1
n2 = 20
n3=50
n4 = 100
X1 = np.linspace(0,200,num=1000)
Y1 = sp.lambdify(x,sum(dens,n1),'numpy')
plt.plot(X1, Y1(X1),label="n=1")
X2 = np.linspace(0,200,num=1000)
Y2 = sp.lambdify(x,sum(dens,n2),'numpy')
plt.plot(X2, Y2(X2),label="n=20")
X3 = np.linspace(0,200,num=1000)
Y3 = sp.lambdify(x,sum(dens,n3),'numpy')
plt.plot(X3, Y3(X3),label="n=50")
X4 = np.linspace(0,200,num=1000)
Y4 = sp.lambdify(x,sum(dens,n4),'numpy')
plt.plot(X4, Y4(X4),label="n=100")
plt.legend()
plt.show()
Now I'd like to do the plot for all the n possibilities (later I want to try to animate it, but at first I need to understand how to do this loop). Thus I want to do the plot using a loop instead of creating the plots separately as above. But this gives me the error
Traceback (most recent call last):
File "C:\Users\user\Desktop\ZGS.py", line 71, in
Y = sp.lambdify(x,sum(dens,k),'numpy')
File "C:\Users\user\Desktop\ZGS.py", line 32, in sum
return int
UnboundLocalError: local variable 'int' referenced before assignment
I tried some things such as global int but this creates problems within sympy. Why can I use different variables for n when plotting separately but get this error when assigning n using a loop?
n=100
for k in range(n):
X = np.linspace(0,200,num=1000)
Y = sp.lambdify(x,sum(dens,k),'numpy')
plt.plot(X, Y(X))
plt.show()
How can this problem be solved?
for k in range(n):
...
Y = sp.lambdify(x,sum(dens,k),'numpy')
...
On the first iteration k is zero.
>>> for k in range(3):
... print(k)
...
0
1
2
When dens is called with k == 0 - while i<=n is False and nothing in that while loop is processed. When return int is processed int does not exist.
range takes an optional start argument which would alleviate your error:
>>> for k in range(1,3+1):
... print(k)
...
1
2
3
>>>

Simulating the trajectory of a particle from rest under gravity

I hope you can help! I need to show the trajectory of a particle that is under gravity of g = -9.81 m/s2 and time step of dt = 0.05 sec, where the position and velocity of the particle are:
x_1 = x_0 + v_x1 * dt
y_1 = y_0 + v_y1 * dt
v_x1 = v_x0
v_y1 = v_y0 + g * dt
This is what I should achieve:
This is what I've done so far:
import numpy as np
import matplotlib.pyplot as plt
plt.figure(1, figsize=(12,12))
ax = plt.subplot(111, aspect='equal')
ax.set_ylim(0,50)
ax.set_title('Boom --- Weeee! --- Ooof')
r = np.array([0.,0.,15.,30.])
g = -9.81
dt = 0.05
y = 0
x = 0
while y > 0:
plt.plot(x_1,y_2,':', ms=2)
x_1 = v_x1 * dt
y_1 = v_y1 * dt
v_x1 = v_x0
v_y1 = v_y0 + g * dt
This doesn't produce an image only the plt.figure stated in the beginning, I've tried to integrate the r vector into the loop but I can't figure out how.
Thank you.
Here's a modified version of your code that I believe gives you the result you desire (you may want to choose different initial velocity values):
import matplotlib.pyplot as plt
# Set up our plot surface
plt.figure(1, figsize=(12,12))
ax = plt.subplot()
# ax = plt.subplot(111, aspect='equal')
# ax.set_ylim(0,50)
ax.set_title('Boom --- Weeee! --- Ooof')
# Initial conditions
g = -9.81
dt = 0.05
y = 0
x = 0
v_x = 5
v_y = 5
# Create our lists that will store our data points
points_x = []
points_y = []
while True:
# Add the current position of our projectile to our data
points_x.append(x)
points_y.append(y)
# Move our projectile along
x += v_x * dt
y += v_y * dt
# If our projectile falls below the X axis (y < 0), end the simulation
if y < 0:
break
# Update our y velocity per gravity
v_y = v_y + g * dt
# Plot our data
ax.plot(points_x, points_y)
# Show the plot on the screen
plt.show()
I'm sorry if I could have made fewer changes. Here are the substantive ones I can think of:
You weren't using the r value you computed, so got rid of it, along with the import of numpy that was then no longer needed.
I took out calls you made explicitly size your plot. You're better off letting the plot library decide upon the bounds of the plot for you
I don't know if there's another way to do it, but I've always supplied data to the plotting library as arrays of points rather than by providing the points one at a time. So here, I collect up all of the x and y coordinates into two lists while running the simulation, and then add those arrays to the plot at the end to plot the data.
The x_0 vs x_1, etc., got confusing for me. I didn't see any reason to keep track of multiple position and velocity values, so I reduced the code down to using just one set of positions and velocities, x, y, v_x and v_y.
See the comments for more info
Result:

Issues with matplotlib Func.Animation

I'm trying to do some animation with matplotlib (Conways game of life, to be specific) and have some problems with the .FuncAnimation
I figured out diffrent cases wich partly worked (but not the way I want) or result in diffrent errors. I would like to understand the errors and work out a proper version of the code. Thanks for your help!
The function called through the .FuncAnimation is gameoflife wich uses the variables w, h, grid to uptdate the image.
For the whole commented code see below.
Case 1: Global Variables
If I use global variables everthing works fine.
I define w, h, grid global before i call gameoflife(self) through anim = animation.FuncAnimation(fig, gameoflife)
In gameoflife(self) i also define w, h, grid as global variables
w, h, grid = "something"
def gameoflife(self):
global w
global h
global grid
.
.
.
img = ax.imshow(grid)
return img
fig, ax = plt.subplots()
plt.axis('off')
img = ax.imshow(grid)
anim = animation.FuncAnimation(fig, gameoflife)
plt.show()
As said, this results in the animation as wanted. But I would like to get rid of the global variables, because of which I tried something else:
Case 2: Passing Objects
I don't defined w, h, grid as globals in gameoflife but passed them with anim = animation.FuncAniation(fig, gameoflife(w,h,grid)).
(I know that w, h, grid are still global in my example. I work on another version where they are not but since the errors are the same I think this simplyfied version should do it.)
This results in the following Error:
TypeError: 'AxesImage' object is not callable
I dont understand this error, since I don't call ax with the code changes.
w, h, grid = "something"
def gameoflife(w, h, grid):
.
.
.
img = ax.imshow(grid)
return img
fig, ax = plt.subplots()
plt.axis('off')
img = ax.imshow(grid)
anim = animation.FuncAnimation(fig, gameoflife(w,h,grid))
plt.show()
Case 3: Passing Objects with fargs
In the third case I try to pass w, h, grid with the "frags" argument of .FuncAnimation resulting in just the first frame. (Or the first two, depending how you see it. The "frist" frame is accually drawn through img = ax.imshow(grid))
w, h, grid = "something"
def gameoflife(self, w, h, grid):
.
.
.
img = ax.imshow(grid)
return img
fig, ax = plt.subplots()
plt.axis('off')
img = ax.imshow(grid)
anim = animation.FuncAnimation(fig, gameoflife, fargs=(w,h,grid))
plt.show()
Complete Code
I hope its properly commented ;)
There are two parts (beginning and end) where you can comment/uncomment parts to generate the respective case. By deafault its Case 1.
import random
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
##defining grid size
w= 20
h = 20
##generating random grid
grid = np.array([[random.randint(0,1) for x in range(w)] for y in range(h)])
######
# Choose for diffrent cases
######
##Case 1: Global Variables
def gameoflife(self):
global w
global h
global grid
##Case 2: Passing Objects
#def gameoflife(w, h, grid):
##Case 3: Passing Objects with fargs
#def gameoflife(self, w, h, grid):
####### Choose part over
# wt, ht as test values for position
# x,y to set calculation position
wt = w-1
ht = h-1
x,y = -1,0 #results in 0,0 for the first postion
# defining grid for calculation (calgrid)
calgrid = np.array([[0 for x in range(w)] for y in range(h)])
# testing for last position
while y<ht or x<wt:
# moving position through the grid
if x == wt:
y +=1
x = 0
else:
x += 1
#sorrounding cells check value
scv = 0
#counting living cells around position x,y
#if-else for exeptions at last column and row
if y == ht:
if x == wt:
scv = grid[x-1][y-1] + grid[x][y-1] + grid[0][y-1] + grid[x-1][y] + grid[0][y] + grid[x-1][0] + grid[x][0] + grid[0][0]
else:
scv = grid[x-1][y-1] + grid[x][y-1] + grid[x+1][y-1] + grid[x-1][y] + grid[x+1][y] + grid[x-1][0] + grid[x][0] + grid[x+1][0]
else:
if x == wt:
scv = grid[x-1][y-1] + grid[x][y-1] + grid[0][y-1] + grid[x-1][y] + grid[0][y] + grid[x-1][y+1] + grid[x][y+1] + grid[0][y+1]
else:
scv = grid[x-1][y-1] + grid[x][y-1] + grid[x+1][y-1] + grid[x-1][y] + grid[x+1][y] + grid[x-1][y+1] + grid[x][y+1] + grid[x+1][y+1]
# test cell to condidions and write result in calgrid
if grid[x][y] == 0:
if scv == 3:
calgrid [x][y] = 1
else :
if 1<scv<4:
calgrid [x][y] = 1
# updating grid, generating img and return it
grid = calgrid
img = ax.imshow(grid)
return img
fig, ax = plt.subplots()
plt.axis('off')
img = ax.imshow(grid) #generates "first" Frame from seed
#####
# Choose vor Case
#####
## Case 1: Global Variables
anim = animation.FuncAnimation(fig, gameoflife)
## Case 2: Passing Variables
#anim = anim = animation.FuncAnimation(fig, gameoflife(w,h,grid))
## Case 3: Passing Variables with fargs
#anim = animation.FuncAnimation(fig, gameoflife, fargs=(w,h,grid))
####### Choose part over
plt.show()
Tanks for help and everything
Greetings Tobias
Case 2: You call the function and pass the result into FuncAnimation.
def gameoflife(w,h,grid):
# ...
return ax.imshow(grid)
anim = animation.FuncAnimation(fig, gameoflife(w,h,grid))
Is essentially the same as
anim = animation.FuncAnimation(fig, ax.imshow(grid))
which will not work because the second argument is expected to be a function, not the return of a function (in this case an image).
To explain this better, consider a simple test case. g is a function and expects a function as input. It will return the function evaluated at 4. If you supply a function f, all works as expected, but if you supply the return of a function, it would fail if the return is not itself a function, which can be evaluated.
def f(x):
return 3*x
def g(func):
return func(4)
g(f) # works as expected
g(f(2)) # throws TypeError: 'int' object is not callable
Case 3: You calling the function repeatedly with the same arguments
In the case of
anim = animation.FuncAnimation(fig, gameoflife, fargs=(w,h,grid))
you call the function gameoflife with the same initial arguments w,h,grid for each frame in the animation. Hence you get a static animation (the plot is animated, but each frame is the same, because the same arguments are used).
Conclusion. Stay with Case 1
Because case 1 is working fine, I don't know why not use it. A more elegant way would be to use a class and use class variables as e.g. in this question.

Categories