As practice, and as a precursor to a more complex, larger project I have in mind, I have created a random walk script using the Turtle module. I realize that there are simpler ways to do the random walk without having to find the neighboring coordinates, but as far as I can tell this is necessary for the larger implementation.
The problem I am having is that python is reaching its maximum recursion depth when it finds that it has visited every adjacent cell in the getnext() function. I'm not sure how I would escape that loop and continue on as normal should that occur.
import turtle
import random
class cell(object):
def __init__(self, pos, visited = False):
self.xCoord = pos[0]
self.yCoord = pos[1]
self.visited = visited
self.neigh = []
self.neighbors = self.getneighbors()
def getneighbors(self):
for j in (-1, 0, 1):
for i in (-1, 0, 1):
self.neigh.append((self.xCoord+i, self.yCoord+j))
def getnext():
nextindex = random.randint(0, len(c.neigh)-1)
nextcoordt = c.neigh[nextindex]
nextcoord = list(c.neigh[nextindex])
if nextcoordt in coords:
getnext()
else:
turtle.goto(nextcoord[0], nextcoord[1])
coords = {}
turtle.setup(width =200, height = 200, startx = 0, starty = 0)
turtle.trace = False
for i in range(1000):
c = cell(list(turtle.pos()))
coords[turtle.pos()] = (c)
getnext()
Furthermore this is actually my first true application of OOP and I was wondering if this was a good way to use it.
Thanks a lot!
If your random walk finds that it has visited every adjacent cell, it would loop forever. Since you're using recursion it quickly exceeds the maximum recursion limit.
I'm sure this could be written in an OOP way, but the problems are more in your use of recursion than whether the cell class is useful. For example, I've simplified your code to run in a linear fashion. The changes are:
Eliminate the (0, 0) direction since it makes no forward progress. (optional depending on your goal, i.e. if you consider "staying put" a valid move or not).
Uses random.choice() to pick the direction of the next move.
Removes recursion in favor of calculating the next coordinate by adding the direction vector to the current position. A simple loop suffices.
Doesn't check the next position against a recent history of positions, since a move back to a previous space is perfectly valid for randomness.
Code:
import itertools
import random
import turtle
# change step size if you like
STEP = 1
PTS = [-STEP, 0, STEP]
DIRS = [(x, y) for x in PTS for y in PTS if x or y]
turtle.setup(width=400, height=400, startx=0, starty=0)
turtle.trace = False
pos = turtle.pos()
for i in range(1000):
px, py = turtle.pos()
# direction of next move
xd, yd = random.choice(DIRS)
# set pos to current pos + direction vector
turtle.goto(px + xd, py + yd)
Related
As shown in the documentation of Open3D, you can use the get_view_control.rotate() function to rotate the object inside the viewer. But it does not specify the type (degree, radian etc.). If I use a value of around 2100 it looks like a full turn, but after putting those in a loop, it turns out this is not the exact value for turning 360 degrees. Also I don't see it mentioned anywhere in the documentation of Open3D.
I want to capture depth images at different angles for a full 360 degree (x,y,z). This is a piece of my code:
class Viewer:
def __init__(self, on, of, fd): #objectname, objectFile and folderdirectory
self.index = 0
self.objectName = on
self.objectFile = of
self.folderDirectory = fd
self.vis = o3d.visualization.Visualizer()
self.view = o3d.visualization.ViewControl()
self.pcd = o3d.io.read_triangle_mesh(self.folderDirectory + self.objectFile)
def depthFullCapture(self, times):
self.numberOfTimes = times
def captureDepth(vis):
print('Capturing')
self.depth = vis.capture_depth_float_buffer(False)
plt.imsave((self.folderDirectory + 'images/' + self.objectName + '_{:05d}.png'.format(self.index)),np.asarray(self.depth), dpi = 1)
np.savetxt((self.folderDirectory + 'text/' + self.objectName + '_{:05d}.txt'.format(self.index)),self.depth,fmt='%.2f',delimiter=',')
vis.register_animation_callback(rotate)
def rotate(vis):
print('Rotating')
ctr = vis.get_view_control()
if(self.index % 25 == 0):
self.vis.reset_view_point(True)
ctr.rotate(0,((2100/25)*(self.index/25)))
else:
ctr.rotate(84, 0)
ctr.set_zoom(0.75)
self.index += 1
if not (self.index == 625):
vis.register_animation_callback(captureDepth)
else:
vis.register_animation_callback(None)
vis.destroy_window()
self.vis.create_window(width = 200, height = 200)
self.vis.add_geometry(self.pcd)
self.vis.register_animation_callback(captureDepth)
self.vis.run()
So can anyone explain the correct value/type for turning a certain degrees? Or is there another/better way to do this? Thanks in advance! If anything is not clear, please ask :)
The actual answer can be found in the C documentation:
const double open3d::visualization::ViewControl::ROTATION_RADIAN_PER_PIXEL = 0.003
the rotation units are pixels:
x and y are the distances the mouse cursor has moved. xo and yo are the original point coordinate the mouse cursor started to move from. Coordinates are measured in screen coordinates relative to the top-left corner of the window client area.
You were very close.
0.003 [radian/pixel] * (180/pi) [degrees/radian] = 0.1719 [degrees/pixel]
OR
5.8178 [pixels/degree]
Taking
360 [degrees/rotation] * 5.8178 [pixels/degree] = 2094.3951 [pixels/rotation]
As I know from the example in Open3D Docs (see also this link), get_view_control.rotate() takes 4 arguments: x, y, xo, and yo, all of them float values in degrees.
Surely this answer comes too late and can be expanded, maybe you can tell us what you learnt!
I have made a class which initiates and updates the CA data, and I have made a function 'Simulate' which updates the cells based on the rule that fire spreads across trees, and leaves empty spaces. Empty spaces are replaced with trees based on a given probability.
There is a problem where it appears my function is applying the rule to the current time data holder, rather than the previous time data holder. I have set prevstate = self.state to act as a temporary data holder for the previous iteration, but running small tests I find that it gives the same results as if I didn't include this line at all. What am I doing wrong?
import numpy as np
import random
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, colorConverter
from matplotlib.animation import FuncAnimation
#dimentions:
x = 10
y = 10
lighting = 0 #set to 0 for testing
grow = 0.3
#parameter values
empty = 0
tree = 1
fire = 2
random.seed(1)
#CA Rule definition
def update(mat, i, j, lighting, grow, prob):
if mat[i, j] == empty:
if prob < grow:
return tree
else:
return empty
elif mat[i, j] == tree:
if max(mat[i-1, j], mat[i+1, j], mat[i, j-1], mat[i, j+1]) == fire:
return fire
elif prob < lighting:
return fire
else:
return tree
else:
return empty
########## Data Holder
class Simulation:
def __init__(self):
self.frame = 0
#self.state = np.random.randint(2, size=(x, y)) commented out for testing
self.state = np.ones((x, y))
self.state[5, 5] = 2 #initial fire started at this location for testing
def updateS(self):
prevstate = self.state #line of code i think should be passing previous iteration through rule
for i in range(1, y-1):
for j in range(1, x-1):
prob = random.random()
self.state[i, j] = update(prevstate, i, j, lighting, grow, prob)
def step(self):
self.updateS()
self.frame += 1
simulation = Simulation()
figure = plt.figure()
ca_plot = plt.imshow(simulation.state, cmap='seismic', interpolation='bilinear', vmin=empty, vmax=fire)
plt.colorbar(ca_plot)
transparent = colorConverter.to_rgba('black', alpha=0)
#wall_colormap = LinearSegmentedColormap.from_list('my_colormap', [transparent, 'green'], 2)
def animation_func(i):
simulation.step()
ca_plot.set_data(simulation.state)
return ca_plot
animation = FuncAnimation(figure, animation_func, interval=1000)
mng = plt.get_current_fig_manager()
mng.window.showMaximized()
plt.show()
Any comments on better ways to implement a CA are most welcome!
Python assignments are pointers... So when you update self.state, then prevstate is also updated.
I expect if you set to:
prevstate = copy.copy(self.state)
That should fix your problem.
Copy docs
As jabberwocky correctly notes, your problem is that the line prevstate = self.state makes prevstate a new reference to the same numpy array as self.state, so that modifying the contents of one also modifies the other.
Instead of copying the array on every iteration, however, a slightly more efficient solution would be to preallocate two arrays and swap them, something like this:
class Simulation:
def __init__(self):
self.frame = 0
self.state = np.ones((x, y))
self.state[5, 5] = 2
self.prevstate = np.ones((x, y)) # <-- add this line
def updateS(self):
self.state, self.prevstate = self.prevstate, self.state # <-- swap the buffers
for i in range(1, y-1):
for j in range(1, x-1):
prob = random.random()
self.state[i, j] = update(self.prevstate, i, j, lighting, grow, prob)
I say "slightly" because all you're really saving is a numpy array copy and some work for the garbage collector. However, if you optimize your inner state update loop enough — maybe e.g. implementing the CA rule using numba — the relative cost of an extra array copy will start to be significant. In any case, there are no real down sides to using this "double buffering" method, so it's a good habit to pick up.
I am writing a python class to find a solution to the 8 queens problem. How can I implement backtracking properly in my solve method? I think the recursion should work, however, the program stops after the solution is not found on the first try, and backtracking does not take place. All helper methods work properly.
EMPTY = 0
QUEEN = 1
RESTRICTED = 2
class Board:
# initializes a 8x8 array
def __init__ (self):
self.board = [[EMPTY for x in range(8)] for y in range(8)]
# pretty prints board
def printBoard(self):
for row in self.board:
print(row)
# places a queen on a board
def placeQueen(self, x, y):
# restricts row
self.board[y] = [RESTRICTED for i in range(8)]
# restricts column
for row in self.board:
row[x] = RESTRICTED
# places queen
self.board[y][x] = QUEEN
self.fillDiagonal(x, y, 0, 0, -1, -1) # restricts top left diagonal
self.fillDiagonal(x, y, 7, 0, 1, -1) # restructs top right diagonal
self.fillDiagonal(x, y, 0, 7, -1, 1) # restricts bottom left diagonal
self.fillDiagonal(x, y, 7, 7, 1, 1) # restricts bottom right diagonal
# restricts a diagonal in a specified direction
def fillDiagonal(self, x, y, xlim, ylim, xadd, yadd):
if x != xlim and y != ylim:
self.board[y + yadd][x + xadd] = RESTRICTED
self.fillDiagonal(x + xadd, y + yadd, xlim, ylim, xadd, yadd)
# recursively places queens such that no queen shares a row or
# column with another queen, or in other words, no queen sits on a
# restricted square. Should solve by backtracking until solution is found.
def solve(self, queens):
if queens == 8:
return True
for i in range(8):
if self.board[i][queens] == EMPTY:
self.placeQueen(queens, i)
if self.solve(queens - 1):
return True
self.board[i][queens] = RESTRICTED
return False
b1 = Board()
b1.solve(7)
b1.printBoard()
Is my problem in the lack of a deep copy of the board before adding the queen, or is it just a lack of backtracking?
It's both: you have only one copy of the board in your entire program. You fill it as best you can, until all squares are occupied or restricted; the search fails, and you return from solve. With no mechanism to reset the board, your program ends.
Backtracking would make this simple, at the cost of multiple intermediate boards. Instead of having a single board object ... make a deep copy, place the queen, mark the appropriate RESTRICTED squares, and pass that altered copy to the next level. If you return with failure, let that copy evaporate naturally, being a local variable.
I am making a snake game in pygame, and i need to make an array of pygame rects. When i was testing the code to see if the basic idea works, it didn't. When it was supposed to print
[[0,0],
[10,0],
[20,0],
and so on until it got to the biggest x value, and then it would add ten to the y value, it just prints the x values when the y value is always 0. I am new to pygame and python, so any help would be appreciated.
My code:
class Grid:
def __init__(self, gridSize):
self.gridSize = gridSize
self.numX = int(screenX / gridSize)
self.numY = int(screenX / gridSize)
self.xList = []
for y in range(0, self.numY * 10, 10):
for x in range(0, self.numX * 10, 10):
self.xList.append((x,y))
if y == 0:
self.array = np.array(self.xList)
else:
np.append(self.array, self.xList)
self.xList = []
print(self.array)
Most (if not all) numpy commands don't modify their arrays in-place. They return a new array, but the old array stays as it is.
Thus, under your else, you'll need
else:
self.array = np.append(self.array, self.xList)
This will update self.array so that it holds the new, appended array.
It also explains why you're only seeing print-outs for y = 0 and not other values. (You could possibly arrive at this same conclusion by debugging and stepping through your code. Maybe next time? :-) )
For starters, you aren't iterating over the Y range:
self.numY = int(screenX / gridSize) needs to be self.numY = int(screenY/ gridSize)
I'm trying to understand the A* path finding algorithm, and how to implement it in a python program. I found this website which does a pretty good job explaining how the algorithm itself works, as well as providing some example code.
Here is where I get stuck:
def make_graph(mapinfo):
nodes = [[AStarGridNode(x, y) for y in range(mapinfo.height)] for x in range(mapinfo.width)]
graph = {}
for x, y in product(range(mapinfo.width), range(mapinfo.height)):
node = nodes[x][y]
graph[node] = []
for i, j in product([-1, 0, 1], [-1, 0, 1]):
if not (0 <= x + i < mapinfo.width): continue
if not (0 <= y + j < mapinfo.height): continue
graph[nodes[x][y]].append(nodes[x+i][y+j])
return graph, nodes
graph, nodes = make_graph({"width": 8, "height": 8})
paths = AStarGrid(graph)
start, end = nodes[1][1], nodes[5][7]
path = paths.search(start, end)
if path is None:
print "No path found"
else:
print "Path found:", path
I don't understand how the "mapinfo" object is supposed to look. I manage to the the program working by replacing the mapinfo variables with some numbers, but can't figure out how an entire list would work, especially if we want walls included. Can you provide some clarification / examples?
The mapinfo object (as presented in the code given) is a dictionary argument passed into the make_graph() function and is being used to store the dimensions (width and height) of the grid to be searched.
You could define it before the function call and then pass it to the function like:
mapinfo = {"width": 8, "height": 8}
graph, nodes = make_graph(mapinfo)
The problem is that the make_graph() function tries to access the width and height values in mapinfo directly (such as by mapinfo.height), which results in an exception AttributeError: 'dict' object has no attribute 'height'.
Two options I can think of are:
Change the statements in make_graph() to access the dictionary elements by key instead of by attribute by changing all mapinfo.height to mapinfo['height'] and similarly for the width), or
Create a MapInfo class with the attributes you need, and pass an instance of it to the make_graph() function instead of a dictionary.
class MapInfo(object):
def __init__(self, width, height):
self.width = width
self.height = height
# ...
mapinfo = MapInfo(width=8, height=8)
graph, nodes = make_graph(mapinfo)
You'll have to do more if you want to include impassable terrain, such as walls.
Perhaps extend the MapInfo class by giving it another attribute:
def __init__(self, width, height, impassable=[]):
"""Create a MapInfo object representing the search area and obstacles.
Args:
width: Integer representing the width of the area
height: Integer representing the height of the area
impassable: List of (x, y) tuples representing impassable obstacles.
"""
self.width = width
self.height = height
self.impassable = impassable
Next you would need to modify the make_graph() function to only add edges between two grid spaces if the target area is not impassable.
for i, j in product([-1, 0, 1], [-1, 0, 1]):
# Check that we are inside the grid area.
if not (0 <= x + i < mapinfo.width): continue
if not (0 <= y + j < mapinfo.height): continue
# Check if the target area is impassable.
if (x + i, y + j) in mapinfo.impassable: continue
# All looks good. Add target space as reachable from current (x, y) space.
graph[nodes[x][y]].append(nodes[x+i][y+j])
You would then modify your mapinfo instance definition as necessary with the additional impassable areas:
impassable = [(3, 3), (3, 4), (3, 5)] # modify to your needs
mapinfo = MapInfo(width=8, height=8, impassable=impassable)