Dijkstra algorithm in python using dictionaries - python

Dear computer science enthusiasts,
I have stumbled upon an issue when trying to implement the Dijkstra-algorithm to determine the shortest path between a starting node and all other nodes in a graph.
To be precise I will provide you with as many code snippets and information as I consider useful to the case. However, should you miss anything, please let me know.
I implemented a PQueue class to handle Priority Queues of each individual node and it looks like this:
class PQueue:
def __init__(self):
self.items = []
def push(self, u, value):
self.items.append((u, value))
# insertion sort
j = len(self.items) - 1
while j > 0 and self.items[j - 1][1] > value:
self.items[j] = self.items[j - 1] # Move element 1 position backwards
j -= 1
# node u now belongs to position j
self.items[j] = (u, value)
def decrease_key(self, u, value):
for i in range(len(self.items)):
if self.items[i][0] == u:
self.items[i][1] = value
j = i
break
# insertion sort
while j > 0 and self.items[j - 1][1] > value:
self.items[j] = self.items[j - 1] # Move element 1 position backwards
j -= 1
# node u now belongs to position j
self.items[j] = (u, value)
def pop_min(self):
if len(self.items) == 0:
return None
self.items.__delitem__(0)
return self.items.index(min(self.items))
In case you're not too sure about what the Dijkstra-algorithm is, you can refresh your knowledge here.
Now to get to the actual problem, I declared a function dijkstra:
def dijkstra(self, start):
# init
totalCosts = {} # {"node"= cost,...}
prevNodes = {} # {"node"= prevNode,...}
minPQ = PQueue() # [[node, cost],...]
visited = set()
# start init
totalCosts[str(start)] = 0
prevNodes[str(start)] = start
minPQ.push(start, 0)
# set for all other nodes cost to inf
for node in range(self.graph.length): # #nodes
if node != start:
totalCosts[str(node)] = np.inf
while len(minPQ.items) != 0: # Main loop
# remove smallest item
curr_node = minPQ.items[0][0] # get index/number of curr_node
minPQ.pop_min()
visited.add(curr_node)
# check neighbors
for neighbor in self.graph.adj_list[curr_node]:
# check if visited
if neighbor not in visited:
# check cost and put it in totalCost and update prev node
cost = self.graph.val_edges[curr_node][neighbor] # update cost of curr_node -> neighbor
minPQ.push(neighbor, cost)
totalCosts[str(neighbor)] = cost # update cost
prevNodes[str(neighbor)] = curr_node # update prev
# calc alternate path
altpath = totalCosts[str(curr_node)] + self.graph.val_edges[curr_node][neighbor]
# val_edges is a adj_matrix with values for the connecting edges
if altpath < totalCosts[str(neighbor)]: # check if new path is better
totalCosts[str(neighbor)] = altpath
prevNodes[str(neighbor)] = curr_node
minPQ.decrease_key(neighbor, altpath)
Which in my eyes should solve the problem mentioned above (optimal path for a starting node to every other node). But it does not. Can someone help me clean up this mess that I have been trying to debug for a while now. Thank you in advance!
Assumption:
In fact I realized that my dictionaries used to store the previously visited nodes (prevNodes) and the one where I save the corresponding total cost of visiting a node (totalCosts) are unequally long. And I do not understand why.

Related

Time limit exceeded when finding tree height

this is my code to find the height of a tree of up to 10^5 nodes. May I know why I get the following error?
Warning, long feedback: only the beginning and the end of the feedback message is shown, and the middle was replaced by " ... ". Failed case #18/24: time limit exceeded
Input:
100000
Your output:
stderr:
(Time used: 6.01/3.00, memory used: 24014848/2147483648.)
Is there a way to speed up this algo?
This is the exact problem description:
Problem Description
Task. You are given a description of a rooted tree. Your task is to compute and output its height. Recall
that the height of a (rooted) tree is the maximum depth of a node, or the maximum distance from a
leaf to the root. You are given an arbitrary tree, not necessarily a binary tree.
Input Format. The first line contains the number of nodes 𝑛. The second line contains 𝑛 integer numbers
from βˆ’1 to 𝑛 βˆ’ 1 β€” parents of nodes. If the 𝑖-th one of them (0 ≀ 𝑖 ≀ 𝑛 βˆ’ 1) is βˆ’1, node 𝑖 is the root,
otherwise it’s 0-based index of the parent of 𝑖-th node. It is guaranteed that there is exactly one root.
It is guaranteed that the input represents a tree.
Constraints. 1 ≀ 𝑛 ≀ 105
Output Format. Output the height of the tree.
# python3
import sys, threading
from collections import deque, defaultdict
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27) # new thread will get stack of such size
class TreeHeight:
def read(self):
self.n = int(sys.stdin.readline())
self.parent = list(map(int, sys.stdin.readline().split()))
def compute_height(self):
height = 0
nodes = [[] for _ in range(self.n)]
for child_index in range(self.n):
if self.parent[child_index] == -1:
# child_index = child value
root = child_index
nodes[0].append(root)
# do not add to index
else:
parent_index = None
counter = -1
updating_child_index = child_index
while parent_index != -1:
parent_index = self.parent[updating_child_index]
updating_child_index = parent_index
counter += 1
nodes[counter].append(child_index)
# nodes[self.parent[child_index]].append(child_index)
nodes2 = list(filter(lambda x: x, nodes))
height = len(nodes2)
return(height)
def main():
tree = TreeHeight()
tree.read()
print(tree.compute_height())
threading.Thread(target=main).start()
First, why are you using threading? Threading isn't good. It is a source of potentially hard to find race conditions and confusing complexity. Plus in Python, thanks to the GIL, you often don't get any performance win.
That said, your algorithm essentially looks like this:
for each node:
travel all the way to the root
record its depth
If the tree is entirely unbalanced and has 100,000 nodes, then for each of 100,000 nodes you have to visit an average of 50,000 other nodes for roughly 5,000,000,000 operations. This takes a while.
What you need to do is stop constantly traversing the tree back to the root to find the depths. Something like this should work.
import sys
class TreeHeight:
def read(self):
self.n = int(sys.stdin.readline())
self.parent = list(map(int, sys.stdin.readline().split()))
def compute_height(self):
height = [None for _ in self.parent]
todo = list(range(self.n))
while 0 < len(todo):
node = todo.pop()
if self.parent[node] == -1:
height[node] = 1
elif height[node] is None:
if height[self.parent[node]] is None:
# We will try again after doing our parent
todo.append(node)
todo.append(self.parent[node])
else:
height[node] = height[self.parent[node]] + 1
return max(height)
if __name__ == "__main__":
tree = TreeHeight()
tree.read()
print(tree.compute_height())
(Note, I switched to a standard indent, and then made that indent 4. See this classic study for evidence that an indent in the 2-4 range is better for comprehension than an indent of 8. And, of course, the pep8 standard for Python specifies 4 spaces.)
Here is the same code showing how to handle accidental loops, and hardcode a specific test case.
import sys
class TreeHeight:
def read(self):
self.n = int(sys.stdin.readline())
self.parent = list(map(int, sys.stdin.readline().split()))
def compute_height(self):
height = [None for _ in self.parent]
todo = list(range(self.n))
in_redo = set()
while 0 < len(todo):
node = todo.pop()
if self.parent[node] == -1:
height[node] = 1
elif height[node] is None:
if height[self.parent[node]] is None:
if node in in_redo:
# This must be a cycle, lie about its height.
height[node] = -100000
else:
in_redo.add(node)
# We will try again after doing our parent
todo.append(node)
todo.append(self.parent[node])
else:
height[node] = height[self.parent[node]] + 1
return max(height)
if __name__ == "__main__":
tree = TreeHeight()
# tree.read()
tree.n = 5
tree.parent = [-1, 0, 4, 0, 3]
print(tree.compute_height())

Graph: Path with the maximum probability

enter image description here
I am trying to get the maximum probability path. I am getting the probability but not able to get the path(example: 1-2-3-4).
from heapq import heappush, heappop
class Solution:
def maxProbability(self, n, edges, succProb, start, end):
graph = self.build_graph(edges, succProb)
seen = set()
max_heap = [(-1, start)]
while max_heap:
prob, cur = heappop(max_heap)
# Maintain a seen set so that we do not visit a vertex we already processed
# This is to avoid putting us in an infinite loop since a path from start to end is not always guaranteed
seen.add(cur)
if cur == end:
return -prob
for neigh, p in graph.get(cur, []):
if not neigh in seen:
new_prob = -1 * abs(prob*p)
heappush(max_heap, (new_prob, neigh))
# No path from start to end
return 0
def build_graph(self, edges, succProb):
graph = {}
for i in range(len(edges)):
cur_edge = edges[i]
cur_prob = succProb[i]
graph.setdefault(cur_edge[0], []).append((cur_edge[1], cur_prob))
graph.setdefault(cur_edge[1], []).append((cur_edge[0], cur_prob))
return graph

Efficient algorithm to generate random multigraph (undirected) given nodes and degree

I wrote a simple algorithm to generate 1000 random graphs given total nodes and associated node degree. Graph is undirected with multiple edges and no self-loops. My current algorithm is very slow:
Graph properties: no self-loops, undirected, multiple edges (i.e. many edges b/w same pair of vertices)
Pick 2 random node ids from node list using np.random
Check if node degree is full, if not connect 2 nodes and add to dictionary with connections
If a certain node has reached node degree removed it from list so it is not picked again.
randomness is graph generation is crucial as graphs form part of a statistical test
Algorithm reaches exception, where only 1 node is left out (mostly the node with large degree compared to other nodes) In such a case I take the intermediate generated graph and start breaking random edges and connecting these edges to the left out node until all nodes reach their original node degree.
This above is the bottleneck I suppose as I generate a graph and degenerate it later due to the exception.
every random graph in 1000 iterations must be different that older ones. I check this later in the code by appending to a list. If any 2 graphs are exactly same regenerate
all graphs are realisable given above conditions as I already have original graphs with these configs to start with, I am just generating 1000 random versions of these original graphs
from collections import defaultdict
for iteration in range(1000): # 1000 random graphs
all_stop = False
all_stop_2 = False
count = count +1
success_dict = defaultdict(list)
popped_dict = defaultdict(dict)
grid_dens_dict = defaultdict(set) # final graph being built
# pfxlinks = [edge1,edge2,edge3,edge4,........edgen] # just a placeholder edge list to help me identify which node connected with which node in success_dict
pfxlinks = [i for i in range(842814)] # total 842814 edges each connected to a node in grid_ids_withlinks , so if we add all node degrees in grid_ids_withlinks we get (842814 * 2)
grid_ids_withlinks = {1107415: 751065,1125583: 15256,1144686: 108969,1115625: 17038,1081048: 6749,1103814: 6476,1108340: 107431,1111992: 45946,1117451: 3594,1093803: 10860,1117452: 2126,1089226: 52518,1082859: 21211,1105613: 94587,1092862: 43891,1083786: 17073,1092899: 999,1141954: 4347,1106506: 2072,1094690: 119736,1116547: 3284,1104705: 2404,1135637: 3815,1121070:16598,1087417: 4514,1103777: 310,1114682: 4265,1091948: 5468,1093788: 2176, 1098316: 2067,1105597: 19090,1141055: 8454,1097427: 3041,1092875: 4159,1086500: 2204,1095619: 9732,1087430: 2041,1112884: 2167,1097413: 17056,1107414: 34769,1111088: 2025,1083768: 2176,1130180: 1886, 1144699: 988,1146499: 6818,1111081: 12509,1104687: 6186,1092866: 4272,1091037: 3,1121044: 39,1098333: 294,1118359: 27,1151091: 21,1107441: 10766,1141094: 3523,1102898: 53,1115634: 2199,1100140: 4347,1086515: 3029,1116505: 238,1082883: 4070,1118366:2065,1102866: 1590,1115631: 4345,1091990: 2131,1144703: 4053,1075589: 19,1081062: 2124,1097425: 11,1133804: 8,1112864: 158,1088307: 112,1138312: 112,1127446: 6245,1108356: 155,1082874: 6315,1115640: 3978,1107432: 2234,1131077: 2032,1115590: 2672,1094696: 13,1136502: 52,1094683: 20,1110183: 2,1113821: 56,1106515: 6,1120183: 11,1083765: 23,1101079: 6,1091944: 12,1085599: 10,1083783: 25,1148339: 6}
# dict with node_id : node degree (total nodes: 93)
for pfxpair in pfxlinks:
start_put = False
end_put = False
if all_stop_2 == True:
break
while True:
if all_stop == True:
all_stop_2 = True
break
try:
grid_start_id, grid_end_id = (np.random.choice(list(grid_ids_withlinks.keys()),size = 2, replace = False)) # get 2 random node ids
grid_start_id = int(grid_start_id)
grid_end_id = int(grid_end_id)
if start_put == False:
start_value = grid_dens_dict.get(grid_start_id) # if node id exists in my dict and node degree hasnt reached capacity
start_process_condition = (not start_value) or ( (start_value) and (len(grid_dens_dict[grid_start_id]) < grid_ids_withlinks[grid_start_id]) )
if start_process_condition:
grid_dens_dict[grid_start_id].add(pfxpair)
start_put = True
if len(grid_dens_dict[grid_start_id]) == grid_ids_withlinks[grid_start_id]: # node degree for node full, remove from dict
try:
#print('deleted key: ',grid_start_id, 'with size:',grid_dens_dict[grid_start_id],'Capacity:',grid_ids_withlinks[grid_start_id])
popped_dict[grid_start_id] = {'orig_capacity': grid_ids_withlinks[grid_start_id],'size':len(grid_dens_dict[grid_start_id]) }
grid_ids_withlinks.pop(grid_start_id)
except:
print('already popped')
else:
print('check')
if end_put == False:
end_value = grid_dens_dict.get(grid_end_id)
if (not end_value) or (end_value and (len(grid_dens_dict[grid_end_id]) < grid_ids_withlinks[grid_end_id])):
grid_dens_dict[grid_end_id].add(pfxpair)
end_put = True
if len(grid_dens_dict[grid_end_id]) == grid_ids_withlinks[grid_end_id]:
try:
#print('deleted key: ',grid_end_id, 'with size:',grid_dens_dict[grid_end_id],'Capacity:',grid_ids_withlinks[grid_end_id])
popped_dict[grid_end_id] = {'orig_capacity': grid_ids_withlinks[grid_end_id],'size':len(grid_dens_dict[grid_end_id]) }
grid_ids_withlinks.pop(grid_end_id)
except: # only 1 node left with large degree, start breaking edges
print('already popped')
else:
print('check')
if (start_put == False and end_put == True): # only end while when both nodes have been assigned a link
grid_dens_dict[grid_end_id].discard(pfxpair)
end_put = False
if (start_put == True and end_put == False):
grid_dens_dict[grid_start_id].discard(pfxpair)
start_put = False
if start_put == True and end_put == True:
success_dict[pfxpair].append((grid_start_id,grid_end_id))
break
except:
#print('In except block')
grid2grid = defaultdict(list)
for k,v in success_dict.items():
grid2grid[v[0][0]].append(v[0][1])
grid2grid[v[0][1]].append(v[0][0])
# ppick 2 random gridids
while True:
pop_id1, pop_id2 = (np.random.choice(list(grid_dens_dict.keys()),size = 2, replace = False)) # get 2 random node ids for popping
pop_id1 = int(pop_id1)
pop_id2 = int(pop_id2)
if (pop_id1 != list(grid_ids_withlinks.keys())[0] and pop_id2 != list(grid_ids_withlinks.keys())[0] and (pop_id1 in grid2grid[pop_id2] and pop_id2 in grid2grid[pop_id1])): ##have an assigned link
grid2grid[pop_id1].remove(pop_id2)
grid2grid[pop_id2].remove(pop_id1)
grid2grid[list(grid_ids_withlinks.keys())[0]].append(pop_id1)
grid2grid[list(grid_ids_withlinks.keys())[0]].append(pop_id2)
grid2grid[pop_id1].append(list(grid_ids_withlinks.keys())[0])
grid2grid[pop_id2].append(list(grid_ids_withlinks.keys())[0])
if len(grid2grid[list(grid_ids_withlinks.keys())[0]]) == grid_ids_withlinks[list(grid_ids_withlinks.keys())[0]]:
for k,v in grid2grid.items():
grid2grid[k] = Counter(v)
if len(all_itr_list) != 0:
# check if current grpah same as any previous
for graph in all_itr_list:
#same_counter = 0
#for a,b in graph.items():
shared_items = {k: graph[k] for k in graph if k in grid2grid and graph[k] == grid2grid[k]}
len(shared_items)
if len(shared_items) == grid_ids_withlinks: # total no of grids
#print('no of same nodes: ',len(shared_items))
break
all_itr_list.append(grid2grid)
filename = 'path'
with open(filename,'wb') as handle:
pickle.dump(grid2grid, handle)
all_stop = True
break
print('iteration no:',count)
if all_stop == False:
grid2grid = defaultdict(list)
for k,v in success_dict.items():
grid2grid[v[0][0]].append(v[0][1])
grid2grid[v[0][1]].append(v[0][0])
for k,v in grid2grid.items():
grid2grid[k] = Counter(v)
all_itr_list.append(grid2grid)
filename = 'path'
with open(filename,'wb') as handle:
pickle.dump(grid2grid, handle)
> ##from answer
import pickle
from collections import defaultdict
import numpy as np
for iteration in range(1000):
graph = defaultdict(list)
filename2 = r'path'
filename3 = r'path'
with open(filename2,'rb') as handle:
pfxlinks = pickle.load(handle,encoding ='latin-1')
with open(filename3,'rb') as handle:
grid_ids_withlinks = pickle.load(handle,encoding ='latin-1')
nodes = list(grid_ids_withlinks.keys())
degrees = list(grid_ids_withlinks.values())
while len(nodes) > 0:
# Get a random index from current nodes
node_idx = np.random.randint(0, len(nodes)-1)
# Store the node and its corresponding degree
node = nodes[node_idx]
degree = degrees[node_idx]
# Swap that node and its degree with the last node/degree and pop
# This helps us to remove them in O(1) time
# We don't need them anymore since we are going to exhaust the required edges
# for this particular node.
# This also prevents self-edges.
nodes[node_idx], nodes[-1] = nodes[-1], nodes[node_idx]
nodes.pop()
degrees[node_idx], degrees[-1] = degrees[-1], degrees[node_idx]
degrees.pop()
for _ in range(degree): # this is the amount of edges this node needs
# To make sure we don't get out of bounds.
# This could potentially happen unless
# there is a guarantee that the degrees and number of nodes
# are made such that they fit exactly
if len(nodes) == 0:
break
neighbor_idx = np.random.randint(0, len(nodes)-1)
graph[node].append(nodes[neighbor_idx])
graph[nodes[neighbor_idx]].append(node)
degrees[neighbor_idx] -= 1
if degrees[neighbor_idx] == 0:
# we need to remove the neighbor node if it has its maximum edges already
nodes[neighbor_idx], nodes[-1] = nodes[-1], nodes[neighbor_idx]
nodes.pop()
degrees[neighbor_idx], degrees[-1] = degrees[-1], degrees[neighbor_idx]
degrees.pop()
print('done')
Since you have also posted parts of the code that are unrelated to the core algorithm, it makes going through the code and finding bottlenecks relatively difficult.
Here's an algorithm that is faster from what I've seen in your code. It runs in O(n * m) for creating each graph, where n is the number of nodes, and m is the max degree that any of the nodes can have. In other words it's O(V + E) where V is the number of vertices and E the number of edges.
Create a list for the nodes, called nodes, like [1, 2, ..., n].
Create a corresponding list for degrees, called degrees, where degrees[i] is the degree of nodes[i].
Create a store for your graph however you like it. Adjacency list, matrix. Just make sure that adding edges to the graph is of O(1) complexity. Let's call this graph. A defaultdict(list) from collections in python would make a good adjacency list. For this algorithm I assume graph is a defaultdict(list).
Run a while loop on nodes. while len(nodes) > 0: and do as follows:
# Get a random index from current nodes
node_idx = random.randint(0, len(nodes)-1)
# Store the node and its corresponding degree
node = nodes[node_idx]
degree = degrees[node_idx]
# Swap that node and its degree with the last node/degree and pop
# This helps us to remove them in O(1) time
# We don't need them anymore since we are going to exhaust the required edges
# for this particular node.
# This also prevents self-edges.
nodes[node_idx], nodes[-1] = nodes[-1], nodes[node_idx]
nodes.pop()
degrees[node_idx], degrees[-1] = degrees[-1], degrees[node_idx]
degrees.pop()
for _ in degree: # this is the amount of edges this node needs
# To make sure we don't get out of bounds.
# This could potentially happen unless
# there is a guarantee that the degrees and number of nodes
# are made such that they fit exactly
if len(nodes) == 0:
break
neighbor_idx = random.randint(0, len(nodes)-1)
graph[node].append(nodes[neighbor_idx])
graph[nodes[neighbor_idx]].append(node)
degrees[neighbor_idx] -= 1
if degrees[neighbor_idx] == 0:
# we need to remove the neighbor node if it has its maximum edges already
nodes[neighbor_idx], nodes[-1] = nodes[-1], nodes[neighbor_idx]
nodes.pop()
degrees[neighbor_idx], degrees[-1] = degrees[-1], degrees[neighbor_idx]
degrees.pop()
This algorithm potentially leaves one node at the end that has not all its required edges, but this isn't a shortcoming of the algorithm but can happen if the number of edges for the nodes dosn't work out. I'm not sure how to express is mathematically though.
Also note that this algorithm could produce multiple edges between two nodes. It isn't clear to me if this is allowed or not for the particular graph you are looking for. If so, the code can be ammended such that it avoids such edges without sacrificing the time complexity. But it has the potential to leave multiple nodes with less edges than required. This wouldn't be a shortcoming of the algorithm but a result of how the degrees for particular nodes are defined.

TypeError: unsupported operand type(s) for *: 'function' and 'float'

I can't seem to figure out why I keep getting this error. The command line error traceback looks like this:
The point of the following code is to essentially give artificial intelligence to Pacman; keep him away from unscared ghosts, while eating all the food and capsules on a map. Most of code for this was given by the prof for an AI class, which can be found here.
The evaluationFunction method returns a very simple heuristic value that takes into consideration the distances to ghosts, food and capsules. The getAction function is found within my ExpectimaxAgent class (passed arg is MulitAgentSearchAgent), and it gathers all relevant information together, iterates through all possible actions and passes the info along to expectimax. The expectimax function is supposed to calculate a heuristic value which when returned to getAction is compared to the other action-heuristic values, and the one with the highest heuristic is chosen as the best action.
This should be all the relevant code for this error (if not I'll add more, also a quick apology for the noob mistakes in this question, I'm first time poster):
class ReflexAgent(Agent):
def getAction(self, gameState):
# Collect legal moves and successor states
legalMoves = gameState.getLegalActions()
# Choose one of the best actions
scores = [self.evaluationFunction(gameState, action) for action in legalMoves]
bestScore = max(scores)
bestIndices = [index for index in range(len(scores)) if scores[index] == bestScore]
chosenIndex = random.choice(bestIndices) # Pick randomly among the best
return legalMoves[chosenIndex]
def evaluationFunction(self, currentGameState, action):
successorGameState = currentGameState.generatePacmanSuccessor(action)
oldPos = currentGameState.getPacmanPosition()
newPos = successorGameState.getPacmanPosition()
newFood = successorGameState.getFood()
newGhostStates = successorGameState.getGhostStates()
# heuristic baseline
heuristic = 0.0
# ghost heuristic
for ghost in newGhostStates:
ghostDist = manhattanDistance(ghost.getPosition(), newPos)
if ghostDist <= 1:
if ghost.scaredTimer != 0:
heuristic += 2000
else:
heuristic -= 200
# capsule heuristic
for capsule in currentGameState.getCapsules():
capsuleDist = manhattanDistance(capsule, newPos)
if capsuleDist == 0:
heuristic += 100
else:
heuristic += 10.0/capsuleDist
# food heuristic
for x in xrange(newFood.width):
for y in xrange(newFood.height):
if (newFood[x][y]):
foodDist = manhattanDistance(newPos, (x,y))
if foodDist == 0:
heuristic += 100
else:
heuristic += 1.0/(foodDist ** 2)
if currentGameState.getNumFood() > successorGameState.getNumFood():
heuristic += 100
if action == Directions.STOP:
heuristic -= 5
return heuristic
def scoreEvaluationFunction(currentGameState):
return currentGameState.getScore()
class MultiAgentSearchAgent(Agent):
def __init__(self, evalFn = 'scoreEvaluationFunction', depth = '2'):
self.index = 0 # Pacman is always agent index 0
self.evaluationFunction = util.lookup(evalFn, globals())
self.depth = int(depth)
class ExpectimaxAgent(MultiAgentSearchAgent):
def getAction(self, gameState):
# Set v to smallest float value (-infinity)
v = float("-inf")
bestAction = []
# Pacman is agent == 0
agent = 0
# All legal actions which Pacman can make from his current location
actions = gameState.getLegalActions(agent)
# All successors determined from all the legal actions
successors = [(action, gameState.generateSuccessor(agent, action)) for action in actions]
# Iterate through all successors
for successor in successors:
# Expectimax function call (actor = 1, agentList = total number of agents, state = successor[1], depth = self.depth, evalFunct = self.evaluationFunction)
temp = expectimax(1, range(gameState.getNumAgents()), successor[1], self.depth, self.evaluationFunction)
# temp is greater than -infinity (or previously set value)
if temp > v:
# Set v to the new value of temp
v = temp
# Make the best action equal to successor[0]
bestAction = successor[0]
return bestAction
def expectimax(agent, agentList, state, depth, evalFunct):
# Check if won, lost or depth is less than/equal to 0
if depth <= 0 or state.isWin() == True or state.isLose() == True:
# return evalFunct
return evalFunct
# Check to see if agent is Pacman
if agent == 0:
# Set v to smallest float value (-infinity)
v = float("-inf")
# Otherwise, agent is ghost
else:
# Set v to 0
v = 0
# All possible legal actions for Pacman/Ghost(s)
actions = state.getLegalActions(agent)
# All successors determined from all the legal actions for the passed actor (either Pacman or Ghost(s))
successors = [state.generateSuccessor(agent, action) for action in actions]
# Find the inverse of the length of successors
p = 1.0/len(successors)
# Iterate through the length of successors
for j in range(len(successors)):
# Temp var to store the current successor at location j
successor = successors[j]
# Check if agent is Pacman
if agent == 0:
# Set v to the max of its previous value or recursive call to expectimax
v = max(v, expectimax(agentList[agent + 1], agentList, successor, depth, evalFunct))
# Check if agent is equal to ghost 2
elif agent == agentList[-1]:
# Increment v by the recursive call to p times expectimax (with agent=agentlist[0], agentList, state=successor, depth-=1, evalFunct)
v += expectimax(agentList[0], agentList, successor, depth - 1, evalFunct) * p
# Otherwise
else:
# Increment v by p times the recursive call to expectimax (with agent=agentList[agent+1], agentList, state=successor, depth, evalFunct)
v += expectimax(agentList[agent + 1], agentList, successor, depth, evalFunct) * p
return v
I've looked at a few other posts on here, and around the inter-webs, but have found nothing that seems to be similar to my issue. I've tried to pass the value to a temp variable, even tried to move the multiplication before the function call, but those changes have given me the exact same error, on the exact same line.
The error was the first return inside of the expectimax function. Instead of:
def expectimax(agent, agentList, state, depth, evalFunct):
# Check if won, lost or depth is less than/equal to 0
if depth <= 0 or state.isWin() == True or state.isLose() == True:
# return evalFunct
return evalFunct <--- cause of the error
It should have been:
return evalFunct(state)
This is because (as was pointed out) evalFunct only points to the evaluation function the user chose (from the command line arguments).

delete random edges of a node till degree = 1 networkx

If I create a bipartite graph G using random geomtric graph where nodes are connected within a radius. I then want to make sure all nodes have a particular degree (i.e. only one or two edges).
My main aim is to take one of the node sets (i.e node type a) and for each node make sure it has a maximum degree set by me. So for instance if a take node i that has a degree of 4, delete random edges of node i until its degree is 1.
I wrote the following code to run in the graph generator after generating edges. It deletes edges but not until all nodes have the degree of 1.
for n in G:
mu = du['G.degree(n)']
while mu > 1:
G.remove_edge(u,v)
if mu <=1:
break
return G
full function below:
import networkx as nx
import random
def my_bipartite_geom_graph(a, b, radius, dim):
G=nx.Graph()
G.add_nodes_from(range(a+b))
for n in range(a):
G.node[n]['pos']=[random.random() for i in range(0,dim)]
G.node[n]['type'] = 'A'
for n in range(a, a+b):
G.node[n]['pos']=[random.random() for i in range(0,dim)]
G.node[n]['type'] = 'B'
nodesa = [(node, data) for node, data in G.nodes(data=True) if data['type'] == 'A']
nodesb = [(node, data) for node, data in G.nodes(data=True) if data['type'] == 'B']
while nodesa:
u,du = nodesa.pop()
pu = du['pos']
for v,dv in nodesb:
pv = dv['pos']
d = sum(((a-b)**2 for a,b in zip(pu,pv)))
if d <= radius**2:
G.add_edge(u,v)
for n in nodesa:
mu = du['G.degree(n)']
while mu > 1:
G.remove_edge(u,v)
if mu <=1:
break
return G
Reply to words like jared. I tried using you code plus a couple changes I had to make:
def hamiltPath(graph):
maxDegree = 2
remaining = graph.nodes()
newGraph = nx.Graph()
while len(remaining) > 0:
node = remaining.pop()
neighbors = [n for n in graph.neighbors(node) if n in remaining]
if len(neighbors) > 0:
neighbor = neighbors[0]
newGraph.add_edge(node, neighbor)
if len(newGraph.neighbors(neighbor)) >= maxDegree:
remaining.remove(neighbor)
return newGraph
This ends up removing nodes from the final graph which I had hoped it would not.
Suppose we have a Bipartite graph. If you want each node to have degree 0, 1 or 2, one way to do this would be the following. If you want to do a matching, either look up the algorithm (I don't remember it), or change maxDegree to 1 and I think it should work as a matching instead. Regardless, let me know if this doesn't do what you want.
def hamiltPath(graph):
"""This partitions a bipartite graph into a set of components with each
component consisting of a hamiltonian path."""
# The maximum degree
maxDegree = 2
# Get all the nodes. We will process each of these.
remaining = graph.vertices()
# Create a new empty graph to which we will add pairs of nodes.
newGraph = Graph()
# Loop while there's a remaining vertex.
while len(remaining) > 0:
# Get the next arbitrary vertex.
node = remaining.pop()
# Now get its neighbors that are in the remaining set.
neighbors = [n for n in graph.neighbors(node) if n in remaining]
# If this list of neighbors is non empty, then add (node, neighbors[0])
# to the new graph.
if len(neighbors) > 0:
# If this is not an optimal algorithm, I suspect the selection
# a vertex in this indexing step is the crux. Improve this
# selection and the algorthim might be optimized, if it isn't
# already (optimized in result not time or space complexity).
neighbor = neighbors[0]
newGraph.addEdge(node, neighbor)
# "node" has already been removed from the remaining vertices.
# We need to remove "neighbor" if its degree is too high.
if len(newGraph.neighbors(neighbor)) >= maxDegree:
remaining.remove(neighbor)
return newGraph
class Graph:
"""A graph that is represented by pairs of vertices. This was created
For conciseness, not efficiency"""
def __init__(self):
self.graph = set()
def addEdge(self, a, b):
"""Adds the vertex (a, b) to the graph"""
self.graph = self.graph.union({(a, b)})
def neighbors(self, node):
"""Returns all of the neighbors of a as a set. This is safe to
modify."""
return (set(a[0] for a in self.graph if a[1] == node).
union(
set(a[1] for a in self.graph if a[0] == node)
))
def vertices(self):
"""Returns a set of all of the vertices. This is safe to modify."""
return (set(a[1] for a in self.graph).
union(
set(a[0] for a in self.graph)
))
def __repr__(self):
result = "\n"
for (a, b) in self.graph:
result += str(a) + "," + str(b) + "\n"
# Remove the leading and trailing white space.
result = result[1:-1]
return result
graph = Graph()
graph.addEdge("0", "4")
graph.addEdge("1", "8")
graph.addEdge("2", "8")
graph.addEdge("3", "5")
graph.addEdge("3", "6")
graph.addEdge("3", "7")
graph.addEdge("3", "8")
graph.addEdge("3", "9")
graph.addEdge("3", "10")
graph.addEdge("3", "11")
print(graph)
print()
print(hamiltPath(graph))
# Result of this is:
# 10,3
# 1,8
# 2,8
# 11,3
# 0,4
I don't know if it is your problem but my wtf detector is going crazy when I read those two final blocks:
while nodesa:
u,du = nodesa.pop()
pu = du['pos']
for v,dv in nodesb:
pv = dv['pos']
d = sum(((a-b)**2 for a,b in zip(pu,pv)))
if d <= radius**2:
G.add_edge(u,v)
for n in nodesa:
mu = du['G.degree(n)']
while mu > 1:
G.remove_edge(u,v)
if mu <=1:
break
you never go inside the for loop, since nodesa needs to be empty to reach it
even if nodesa is not empty, if mu is an int, you have an infinite loop in in your last nested while since you never modify it.
even if you manage to break from this while statement, then you have mu > 1 == False. So you immediatly break out of your for loop
Are you sure you are doing what you want here? can you add some comments to explain what is going on in this part?

Categories