I am trying to use matplotlib to graph a 3D tree. I want a user to be able to drag each node around so that they can view the tree in any manner they choose. My plan is to eventually subclass in order to add data to the nodes and create the tree.
I am having trouble updating the graph while they are dragging the nodes around. I found a way to update the position of the scatter points using:
self.plot.set_offsets([x,y])
self.plot.set_3d_properties([z], 'z')
But cannot seem to find a way of also updating the position and vector of the quivers.
I am also having difficulties with retrieving the mouse position which currently I always find:
ecoor == {'elevation': 30.0, 'azimuth': -60.0}
EDIT: Thanks to ImportanceOfBeingErnest instead of trying to update the plot I removed it and made a new one.
# PlotNode._plot changes
self.plot.remove()
self.plot = axes.scatter([x], [y], [z])
# PlotInterNode._plot changes (both vector updates)
arlen=length(xc - x, yc - y, zc - z)
edge.remove()
edge = axes.quiver(x, y, z, xc - x, yc - y, zc - z, length=arlen,
arrow_length_ratio=.5/arlen, pivot='tail')
self.edges.update({childname:(childnode, edge)})
Here is the original code:
import math
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D
class PlotNode(object):
"""
Class for graph node.
"""
def __init__(self, name, parent):
"""
Initializes PlotNode.
"""
self.__name__ = name
self.parent = parent
self.coordinates = self._make_coordinates()
x, y, z = self.coordinates
self.plot = axes.scatter([x], [y], [z])
self.pressed = False
self.move = False
def name(self):
return self.__name__
COOR = (0, 0, 0)
def _make_coordinates(self):
"""
Finds coordinates from a file or, if not exist, calculate new coordinates.
"""
if PlotNode.COOR[1] == PlotNode.COOR[0]:
PlotNode.COOR = (PlotNode.COOR[0] + 1, 0, 0)
elif PlotNode.COOR[2] == PlotNode.COOR[1]:
PlotNode.COOR = (PlotNode.COOR[0], PlotNode.COOR[1] + 1, 0)
else:
PlotNode.COOR = (PlotNode.COOR[0], PlotNode.COOR[1], PlotNode.COOR[2] + 1)
return (PlotNode.COOR[0], PlotNode.COOR[1], PlotNode.COOR[2])
def _plot(self):
"""
Plots node onto graph.
"""
x, y, z = self.coordinates
#updates the plot coordinates
self.plot.set_offsets([x,y])
self.plot.set_3d_properties([z], 'z')
#updates the parent
if self.parent:
self.parent._plot(self.name())
self.plot.figure.canvas.draw()
def press(self, event):
"""
Mouse press event.
"""
if event.inaxes != self.plot.axes or not self.plot.contains(event)[0]:
return False
self.pressed = True
axes.disable_mouse_rotation() #Make sure node moves instead of plot rotation
return True
def release(self, event):
"""
Mouse release event.
"""
if event.inaxes != self.plot.axes or not self.pressed:
return False
self.pressed = False
if self.move:
self.move = False
x, y, z = self.coordinates
ecoor = to_dict(axes.format_coord(event.xdata, event.ydata))
xe, ye, ze = ecoor.get('x', x), ecoor.get('y', y), ecoor.get('z', z)
self.coordinates = (xe, ye, ze)
self._plot()
else:
self.open()
axes.mouse_init() #Make plot rotation avaliable again
return True
def motion(self, event):
"""
Mouse motion event.
"""
if event.inaxes != self.plot.axes or not self.plot.contains(event)[0]:
return False
if not self.pressed:
return False
self.move = True
x, y, z = self.coordinates
ecoor = to_dict(axes.format_coord(event.xdata, event.ydata))
xe, ye, ze = ecoor.get('x', x), ecoor.get('y', y), ecoor.get('z', z)
self.coordinates = (xe, ye, ze)
self._plot()
return True
def open(self):
print('openned!') #to be changed
class PlotInterNode(PlotNode):
"""
Class for graph folder node.
"""
def __init__(self, name, parent=None):
"""
Initializes PlotDir.
"""
self.edges = {}
PlotNode.__init__(self, name, parent)
def _plot(self, childname=None):
"""
Plots node onto graph.
"""
if childname:
x, y, z = self.coordinates
childnode, edge = self.edges.get(childname)
xc, yc, zc = childnode.coordinates
##update the vector
arlen=length(xc - x, yc - y, zc - z)
##update the arrow length
else:
x, y, z = self.coordinates
for childname in self.edges:
_, edge = self.edges.get(childname)
##update the position of each edge
super()._plot()
self.plot.figure.canvas.draw()
def traverse(self):
"""
Generator that traverses the tree rooted at this node.
"""
yield self
for child in self.edges:
try:
for node in self.edges.get(child)[0].traverse():
yield node
except AttributeError:
yield self.edges.get(child)[0]
def select_node(root, event):
"""
Single event function to handle all node movement.
"""
if event.name == 'button_press_event':
event_fn = lambda self: PlotNode.press(self, event)
elif event.name == 'button_release_event':
event_fn = lambda self: PlotNode.release(self, event)
elif event.name == 'motion_notify_event':
event_fn = lambda self: PlotNode.motion(self, event)
for node in root.traverse():
if event_fn(node):
return #if act on node then end
select_ids = []
def connect_select(root):
"""
Connects select_node to events.
"""
select_ids.append(figure.canvas.mpl_connect('button_press_event', lambda event: select_node(root, event)))
select_ids.append(figure.canvas.mpl_connect('button_release_event', lambda event: select_node(root, event)))
select_ids.append(figure.canvas.mpl_connect('motion_notify_event', lambda event: select_node(root, event)))
def to_dict(string):
"""
Converts a string to a dictionary.
"""
dictionary = {}
for st in string.split(','):
st = st.strip().split('=')
if st[0] not in dictionary:
try:
dictionary.update({st[0]:float(st[1])})
except ValueError:
st[1] = st[1].split(' ')[0]
dictionary.update({st[0]:float(st[1])})
return dictionary
def length(x, y, z):
"""
Returns hypotenuse.
"""
ret = math.sqrt(math.pow(x, 2) + math.pow(y, 2) + math.pow(z, 2))
if not ret:
ret = 1
return ret
figure = pyplot.figure()
axes = figure.add_subplot(111, projection='3d')
root = PlotInterNode('root')
def make_children(node, child_type, number):
x, y, z = node.coordinates
for i in range(number):
child = child_type(str(i), node)
xc, yc, zc = child.coordinates
arlen = length(xc - x, yc - y, zc - z)
edge = axes.quiver(x, y, z, xc - x, yc - y, zc - z, length=arlen, arrow_length_ratio=.5/arlen, pivot='tail')
node.edges.update({child.name():(child, edge)})
def node_depth(node, depth):
if not depth:
make_children(node, PlotNode, 3)
else:
make_children(node, PlotInterNode, 3)
for child in node.edges:
node_depth(node.edges.get(child)[0], depth-1)
node_depth(root, 3)
connect_select(root)
pyplot.show()
EDIT: All I needed to retrieve the mouse position in 3D was add
axes.button_pressed = None
before I called
axes.format_coord(event.xdata, event.ydata)
Thanks to ImportanceOfBeingErnest instead of trying to update the plot I removed it and made a new one.
# PlotNode._plot changes
self.plot.remove()
self.plot = axes.scatter([x], [y], [z])
# PlotInterNode._plot changes (both vector updates)
arlen=length(xc - x, yc - y, zc - z)
edge.remove()
edge = axes.quiver(x, y, z, xc - x, yc - y, zc - z, length=arlen,
arrow_length_ratio=.5/arlen, pivot='tail')
self.edges.update({childname:(childnode, edge)})
For the second part:
All I needed to retrieve the mouse position in 3D was add
axes.button_pressed = None
before I called
axes.format_coord(event.xdata, event.ydata)
Related
I am working on this code challenge:
Given a 2D bot/robot which can only move in four directions, move forward which is UP(U), move backward which is DOWN(D), LEFT(L), RIGHT(R) in a 10x10 grid. The robot can't go beyond the 10x10 area.
Given a string consisting of instructions to move.
Output the coordinates of a robot after executing the instructions. Initial position of robot is at origin(0, 0).
Example:
Input : move = “UDDLRL”
Output : (-1, -1)
Explanation:
Move U : (0, 0)–(0, 1)
Move D : (0, 1)–(0, 0)
Move D : (0, 0)–(0, -1)
Move L : (0, -1)–(-1, -1)
Move R : (-1, -1)–(0, -1)
Move L : (0, -1)–(-1, -1)
Therefore final position after the complete
movement is: (-1, -1)
I got the code working without using the 10x10 grid information. How could I incorporate the 10x10 grid information into my solution in an OOP fashion? My solution doesn't follow the OOP principles.
# function to find final position of
# robot after the complete movement
def finalPosition(move):
l = len(move)
countUp, countDown = 0, 0
countLeft, countRight = 0, 0
# traverse the instruction string 'move'
for i in range(l):
# for each movement increment its respective counter
if (move[i] == 'U'):
countUp += 1
elif(move[i] == 'D'):
countDown += 1
elif(move[i] == 'L'):
countLeft += 1
elif(move[i] == 'R'):
countRight += 1
# required final position of robot
print("Final Position: (", (countRight - countLeft),
", ", (countUp - countDown), ")")
# Driver code
if __name__ == '__main__':
move = "UDDLLRUUUDUURUDDUULLDRRRR"
finalPosition(move)
This fixes it:
class Robot:
class Mover:
def __init__(self, x, y):
self.x, self.y = x, y
def new_pos(self, x, y):
new_x = x + self.x
new_y = y + self.y
if (new_x > 9 or new_y > 9):
raise ValueError("Box dimensions are greater than 10 X 10")
return new_x, new_y
WALKS = dict(U=Mover(0, 1), D=Mover(0, -1),
L=Mover(-1, 0), R=Mover(1, 0))
def move(self, moves):
x = y = 0
for id in moves:
x, y = self.WALKS[id].new_pos(x, y)
return (x,y)
if __name__ == '__main__':
moves2 = "UDDLLRUUUDUURUDDUULLDRRRR"
robot = Robot()
print(robot.move(moves2))
Output :
(2,3)
The way you use your counters makes it less trivial to detect that you would hit the border of the 10x10 grid. Without changing too much, you could replace the countUp and countDown variables by one countVertical variable, and add -1 to it when going up and 1 when going down. Then ignore a move if it would make that counter negative or greater than 9. And obviously you would do the same for horizontal movements.
[Edit: After the edit to your question, it turns out that you want the Y-coordinate to be opposite to what I assumed above. So I have changed the sign of the Y-coordinate updates (+1, -1).]
That's really it.
Now to make this more OOP, you could define a Robot class, which would maintain its x and y coordinate. Anyhow it would be good to remove the print call out of your function, so the function only deals with the movements, not with the reporting (separation of concern).
Here is how it could work:
class Robot:
def __init__(self, x=0, y=0):
self.position(x, y)
def position(self, x, y):
self.x = min(9, max(0, x))
self.y = min(9, max(0, y))
def move(self, moves):
for move in moves:
if move == 'U':
self.position(self.x, self.y + 1)
elif move == 'D':
self.position(self.x, self.y - 1)
elif move == 'L':
self.position(self.x - 1, self.y)
elif move == 'R':
self.position(self.x + 1, self.y)
else:
raise ValueError(f"Invalid direction '{move}'")
if __name__ == '__main__':
moves = "UDDLLRUUUDUURUDDUULLDRRRR"
robot = Robot(0, 0)
robot.move(moves)
print(f"Final position: {robot.x}, {robot.y}")
In Pytorch, how can I make the gradient of a parameter a function itself?
Here is a simple code snippet:
import torch
def fun(q):
def result(w):
l = w * q
l.backward()
return w.grad
return result
w = torch.tensor((2.), requires_grad=True)
q = torch.tensor((3.), requires_grad=True)
f = fun(q)
print(f(w))
In the code above, how can I make f(w) have gradient with respect to q?
EDIT: based on the accepted answer I was able to write a code that works. Essentially I am alternating between 2 optimization steps. For dim == 1 it works and for dim == 2 it does not. I get the error "RuntimeError: Trying to backward through the graph a second time, but the saved intermediate results have already been freed. Specify retain_graph=True when calling backward the first time."
import torch
class f_class():
def __init__(self, dim):
self.dim = dim
if self.dim == 1:
self.w = torch.tensor((3.), requires_grad=True)
elif self.dim == 2:
self.w = [torch.tensor((3.), requires_grad=True), torch.tensor((5.), requires_grad=True)]
else:
raise ValueError("dim 1 or 2")
def forward(self, x):
if self.dim == 1:
return torch.mul(self.w, x)
elif self.dim == 2:
return torch.mul(torch.mul(self.w[0], self.w[1]), x)
def set_w(self, w):
self.w = w
def get_w(self):
return self.w
class g_class():
def __init__(self):
self.q = torch.tensor((4.), requires_grad=True)
def forward(self, f):
return torch.mul(self.q, f)
def set_q(self, q):
self.q = q
def get_q(self):
return self.q
def w_new(f, g, dim):
loss_g = g.forward(f.forward(xd))
if dim == 1:
grads = torch.autograd.grad(loss_g, f.get_w(), create_graph=True, only_inputs=True)[0]
temp = f.get_w().detach() + grads
else:
grads = torch.autograd.grad(loss_g, f.get_w(), create_graph=True, only_inputs=True)
temp = [wi.detach() + gi for wi, gi in zip(f.get_w(), grads)]
return temp
def q_new(f, g):
loss_f = 2 * f.forward(xd)
loss_f.backward()
temp = g.get_q().detach() + g.get_q().grad
temp.requires_grad = True
return temp
dim = 1
xd = torch.tensor((2.))
f = f_class(dim)
g = g_class()
for _ in range(3):
print(f.get_w(), g.get_q())
wnew = w_new(f, g, dim)
f.set_w(wnew)
print(f.get_w(), g.get_q())
qnew = q_new(f, g)
g.set_q(qnew)
print(f.get_w(), g.get_q())
When computing gradients, if you want to construct a computation graph for the gradient itself you need to specify create_graph=True to autograd.
A potential source of error in your code is using Tensor.backward within f. The problem here is that w.grad and q.grad will be populated with the gradient of l. This means that when you call f(w).backward(), the gradients of both f and l will be added to w.grad and q.grad. In effect you will end up with w.grad being equal to dl/dw + df/dw and similarly for q.grad. One way to get around this is to zero the gradients after f(w) but before .backward(). A better way is to use torch.autograd.grad within f. Using the latter approach, the grad attribute of w and q will not be populated when calling f, only when calling .backward(). This leaves room for things like gradient accumulation during training.
import torch
def fun(q):
def result(w):
l = w * q
return torch.autograd.grad(l, w, only_inputs=True, retain_graph=True)[0]
return result
w = torch.tensor((2.), requires_grad=True)
q = torch.tensor((3.), requires_grad=True)
f = fun(q)
f(w).backward()
print('w.grad:', w.grad)
print('q.grad:', q.grad)
which results in
w.grad: None
q.grad: tensor(1.)
Note that w.grad was not populated. This is because f(w) = dl/dw = q is not a function of w, and therefore w is not part of the computation graph. If you're using a standard pytorch optimizer this is fine since None gradients are implicitly assumed to be zero.
If l were instead a non-linear function of w, then w.grad would have been populated after f(w).backward(). For example
import torch
def fun(q):
def result(w):
# now dl/dw = 2 * w * q
l = w**2 * q
return torch.autograd.grad(l, w, only_inputs=True, create_graph=True)[0]
return result
w = torch.tensor((2.), requires_grad=True)
q = torch.tensor((3.), requires_grad=True)
f = fun(q)
f(w).backward()
print('w.grad:', w.grad)
print('q.grad:', q.grad)
which results in
w.grad: tensor(6.)
q.grad: tensor(4.)
Please see the following code:
class PickCursor(object):
def __init__(self, collection, alpha_other=0.3, tolerance=5):
self.collection = collection
self.alpha_other = alpha_other
self.pts= collection.get_offsets()
self.num_pts = len(self.pts)
# Need to set a tolerance to make it take effect
self.collection.set_picker(tolerance)
# Ensure that we have separate colors for each object
self.fc = collection.get_facecolors()
if len(self.fc) == 0:
raise ValueError('Collection must have a facecolor')
elif len(self.fc) == 1:
self.fc = np.tile(self.fc, (self.num_pts, 1))
# self.fc is a 2d array every row follows [r, g, b, a]
self.ind = None
self.point_selected = None
self.canvas = ax.figure.canvas
self.canvas.mpl_connect('pick_event', self.onselect)
def onselect(self, event):
self.ind = event.ind[0]
self.point_selected = self.pts[self.ind]
# Change alpha of other points
self.fc[:, -1] = self.alpha_other
self.fc[self.ind, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
x = np.arange(0, 10)
y = np.arange(0, 10)
scat = ax.scatter(x, y, s=15)
PickCursor(scat)
The above code works! Basically, it will make points unselected transparent. However, if I wrap the code into a function like this:
def func():
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
x = np.arange(0, 10)
y = np.arange(0, 10)
scat = ax.scatter(x, y, s=15)
PickCursor(scat)
func() ## this line does not work!!!
Anyone please shred some light on this? Thanks!
Your class is being garbage collected because you don't retain a reference to it.
def func():
# ...
return PickCursor(scat)
pc = func()
I am creating a simple program which draws a shrinking circle of random color on every clicked location by each mouse click. Each click creates a circle of diameter 50 which starts shrinking till 0 immediately. Each click is supposed to create new shrinking circle.
However, my program stops shrinking of first circle after I click and create another circle. It completely shrinks only the last created circle. All others remain still.
I believe the problem lies in function itself. It calls the same function which is not finished. How to make it run multiple times (on each click separately)? Or do I have it wrong with local and global variables?
Here is my code so far:
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
def klik(event):
global x, y, farba, circ, r
r = 50 #circle diameter
x, y = event.x, event.y #clicked position
color = '#{:06x}'.format(random.randrange(256 ** 3)) #random color picker
circ = c.create_oval(x - r, y - r, x + r, y + r, fill=color) #print circle
print(x, y, farba) #check clicked coordinates, not important
if r < 50: #reset size after each circle
r = 50
shrink()
def shrink():
global circ, x, y, r
print(r) #check if countdown runs correctly
if r > 0:
r -= 1 #diameter shrinking
c.coords(circ, x-r, y-r, x+r, y+r) #changing circle size
c.after(100, shrink) #timer, size 1pt smaller until size is 0
c.bind('<Button-1>', klik)
tkinter.mainloop()
If you move everything into a class then each circle will be its own instance and will not interfere with each other.
Take a look at the below modified version of your code. It is probably not perfect but should be a good foundation for you to work with.
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
class create_circles():
def __init__(self, event):
self.r = 50
self.x, self.y = event.x, event.y
self.color = '#{:06x}'.format(random.randrange(256 ** 3))
self.circ = c.create_oval(self.x - self.r, self.y - self.r, self.x + self.r, self.y + self.r, fill=self.color)
self.shrink()
def shrink(self):
if self.r > 0:
self.r -= 1
c.coords(self.circ, self.x-self.r, self.y-self.r, self.x+self.r, self.y+self.r)
c.after(100, self.shrink)
c.bind('<Button-1>', create_circles)
tkinter.mainloop()
There is another way you can do this outside of a class.
You can use a nested function and avoid global. Your issues in your question is actually being caused because everything relies on global variables.
Try this below code for a non-class option.
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
def klik(event):
r = 50
x, y = event.x, event.y
color = '#{:06x}'.format(random.randrange(256 ** 3))
circ = c.create_oval(x - r, y - r, x + r, y + r, fill=color)
def shrink(r, x, y, color, circ):
if r > 0:
r -= 1
c.coords(circ, x-r, y-r, x+r, y+r)
c.after(100, shrink, r, x, y, color, circ)
shrink(r, x, y, color, circ)
c.bind('<Button-1>', klik)
tkinter.mainloop()
As noted, you do not need classes to solve this nor nested functions. The key, as #LioraHaydont was hinting at, is you need to use local, rather than global variables:
import tkinter as tk
from random import randrange
def klik(event):
r = 50 # circle radius
x, y = event.x, event.y # clicked position
color = '#{:06x}'.format(randrange(256 ** 3)) # random color picker
c = canvas.create_oval(x - r, y - r, x + r, y + r, fill=color) # print circle
canvas.after(100, shrink, c, x, y, r)
def shrink(c, x, y, r):
if r > 0:
r -= 1 # radius shrinking
canvas.coords(c, x - r, y - r, x + r, y + r) # changing circle size
canvas.after(100, shrink, c, x, y, r) # timer, size 1pt smaller until size is 0
canvas = tk.Canvas(width=400, height=300)
canvas.pack()
canvas.bind('<Button-1>', klik)
tk.mainloop()
I'm learning Python from this lecture: Lec 19 | MIT 6.00 Introduction to Computer Science and Programming. I'm using Python 3.6.2, lecture example runs on Python 2.x. Whats the proper way to set values of x and y in function ans_quest?
x, y = loc_list[-1].get_coords()
Can this method be called like this? This was the example in the lecture.
Full code:
import math, random, pylab, copy
class Location(object):
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def move(self, xc, yc):
return Location(self.x+float(xc), self.y+float(yc))
def get_coords(self):
return self.x, self.y
def get_dist(self, other):
ox, oy = other.get_coords()
x_dist = self.x - ox
y_dist = self.y - oy
return math.sqrt(x_dist**2 + y_dist**2)
class Compass_Pt(object):
possibles = ('N', 'S', 'E', 'W')
def __init__(self, pt):
if pt in self.possibles: self.pt = pt
else: raise ValueError('in Compass_Pt.__init__')
def move(self, dist):
if self.pt == 'N': return (0, dist)
elif self.pt == 'S': return (0, -dist)
elif self.pt == 'E': return (dist, 0)
elif self.pt == 'W': return (-dist, 0)
else: raise ValueError('in Compass_Pt.move')
class Field(object):
''' Cartesian plane where object will be located '''
def __init__(self, drunk, loc):
self.drunk = drunk
self.loc = loc
def move(self, cp, dist):
old_loc = self.loc
xc, yc = cp.move(dist)
self.loc = old_loc.move(xc, yc)
def get_loc(self):
return self.loc
def get_drunk(self):
return self.drunk
class Drunk(object):
''' Point itself '''
def __init__(self, name):
self.name = name
def move(self, field, cp, dist = 1):
if field.get_drunk().name != self.name:
raise ValueError('Drunk.move called with drunk not in the field')
for i in range(dist):
field.move(cp, 1)
class Usual_Drunk(Drunk):
def move(self, field, dist = 1):
''' Drunk.move superclass method override. Sends additional cp attribute.'''
cp = random.choice(Compass_Pt.possibles)
Drunk.move(self, field, Compass_Pt(cp), dist)
class Cold_Drunk(Drunk):
def move(self, field, dist = 1):
cp = random.choice(Compass_Pt.possibles)
if cp == 'S':
Drunk.move(self, field, Compass_Pt(cp), 2*dist)
else:
Drunk.move(self, field, Compass_Pt(cp), dist)
class EW_Drunk(Drunk):
def move(self, field, time = 1):
cp = random.choice(Compass_Pt.possibles)
while cp != 'E' and cp != 'W':
cp = random.choice(Compass_Pt.possibles)
Drunk.move(self, field, Compass_Pt(cp), time)
def perform_trial(time, f):
start = f.get_loc()
distances = [0,0]
for t in range(1, time + 1):
f.get_drunk().move(f)
new_loc = f.get_loc()
distance = new_loc.get_dist(start)
distances.append(distance)
return distances
def perform_sim(time, num_trials, drunk_type):
dist_lists = []
loc_lists = []
for trial in range(num_trials):
d = drunk_type('Drunk' + str(trial))
f = Field(d, Location(0, 0))
distances = perform_trial(time, f)
locs = copy.deepcopy(distances)
dist_lists.append(distances)
loc_lists.append(locs)
return dist_lists, loc_lists
def ans_quest(max_time, num_trials, drunk_type, title):
dist_lists, loc_lists = perform_sim(max_time, num_trials, drunk_type)
means = []
for t in range(max_time + 1):
tot = 0.0
for dist_l in dist_lists:
tot += dist_l[t]
means.append(tot/len(dist_lists))
pylab.figure()
pylab.plot(means)
pylab.ylabel('distance')
pylab.xlabel('time')
pylab.title('{} Ave. Distance'.format(title))
lastX = []
lastY = []
for loc_list in loc_lists:
x, y = loc_list[-1].get_coords()
lastX.append(x)
lastY.append(y)
pylab.figure()
pylab.scatter(lastX, lastY)
pylab.ylabel('NW Distance')
pylab.title('{} Final location'.format(title))
pylab.figure()
pylab.hist(lastX)
pylab.xlabel('EW Value')
pylab.ylabel('Number of Trials')
pylab.title('{} Distribution of Final EW Values'.format(title))
num_steps = 50
num_trials = 10
ans_quest(num_steps, num_trials, Usual_Drunk, 'Usual Drunk ' + str(num_trials) + ' Trials')
ans_quest(num_steps, num_trials, Cold_Drunk, 'Cold Drunk ' + str(num_trials) + ' Trials')
ans_quest(num_steps, num_trials, EW_Drunk, 'EW Drunk ' + str(num_trials) + ' Trials')
pylab.show()
Error:
Traceback (most recent call last):
File "/home/tihe/Documents/CODING/Project Home/Python/biased_random_walks.py", line 194, in <module>
ans_quest(num_steps, num_trials, Usual_Drunk, 'Usual Drunk ' + str(num_trials) + ' Trials')
File "/home/tihe/Documents/CODING/Project Home/Python/biased_random_walks.py", line 175, in ans_quest
x, y = loc_list[-1].get_coords()
AttributeError: 'float' object has no attribute 'get_coords'
This method could be called like this if you had a list of Location objects. The error is because the loc_list is populated with distances and not Location objects. That happens in function perform_sim when instead of geting the location you are making a deep copy of distance.
Perhaps you could try something like this:
def perform_trial(time, f):
start = f.get_loc()
distances = [0,0]
locations = []
for t in range(1, time + 1):
f.get_drunk().move(f)
new_loc = f.get_loc()
locations.append(new_loc)
distance = new_loc.get_dist(start)
distances.append(distance)
return distances, locations
def perform_sim(time, num_trials, drunk_type):
dist_lists = []
loc_lists = []
for trial in range(num_trials):
d = drunk_type('Drunk' + str(trial))
f = Field(d, Location(0, 0))
distances, locations = perform_trial(time, f)
dist_lists.append(distances)
loc_lists.append(locations)
return dist_lists, loc_lists
I hope that helped you out.