Python Knapsack Branch and Bound - python

I have spent a week working on this branch and bound code for the knapsack problem, and I have looked at numerous articles and books on the subject. However, when I am running my code I don't get the result I expect. Input is received from a text file, such as this:
12
4 1
1 1
3 2
2 3
where the first line is the capacity, and each subsequent line are value/weight pairs. The result I get from using this file is '8' instead of '10' (unless I am mistaken and all the items won't fit in the knapsack). Here is my code:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import Queue
from collections import namedtuple
Item = namedtuple("Item", ['index', 'value', 'weight', 'level', 'bound', 'contains'])
class Node:
def __init__(self, level, value, weight, bound, contains):
self.level = level
self.value = value
self.weight = weight
self.bound = bound
self.contains = contains
def upper_bound(u, k, n, v, w):
if u.weight > k:
return 0
else:
bound = u.value
wt = u.weight
j = u.level + 1
while j < n and wt + w[j] <= k:
bound += v[j]
wt += w[j]
j += 1
# fill knapsack with fraction of a remaining item
if j < n:
bound += (k - wt) * (v[j] / w[j])
return bound
def knapsack(items, capacity):
item_count = len(items)
v = [0]*item_count
w = [0]*item_count
# sort items by value to weight ratio
items = sorted(items, key=lambda k: k.value/k.weight, reverse = True)
for i,item in enumerate(items, 0):
v[i] = int(item.value)
w[i] = int(item.weight)
q = Queue.Queue()
root = Node(0, 0, 0, 0.0, [])
root.bound = upper_bound(root, capacity, item_count, v, w)
q.put(root)
value = 0
taken = [0]*item_count
best = set()
while not q.empty():
c = q.get()
if c.bound > value:
level = c.level+1
# check 'left' node (if item is added to knapsack)
left = Node(c.value + v[level], c.weight + w[level], level, 0.0, c.contains[:])
left.contains.append(level)
if left.weight <= capacity and left.value > value:
value = left.value
best |= set(left.contains)
left.bound = upper_bound(left, capacity, item_count, v, w)
if left.bound > value:
q.put(left)
# check 'right' node (if items is not added to knapsack)
right = Node(c.value, c.weight, level, 0.0, c.contains[:])
right.contains.append(level)
right.bound = upper_bound(right, capacity, item_count, v, w)
if right.bound > value:
q.put(right)
for b in best:
taken[b] = 1
value = sum([i*j for (i,j) in zip(v,taken)])
return str(value)
Are my indices off? Am I not traversing the tree or calculating the bounds correctly?

def upper_bound(u, k, n, v, w):
if u.weight > k:
return 0
else:
bound = u.value
wt = u.weight
j = u.level
while j < n and wt + w[j] <= k:
bound += v[j]
wt += w[j]
j += 1
# fill knapsack with fraction of a remaining item
if j < n:
bound += (k - wt) * float(v[j])/ w[j]
return bound
def knapsack(items, capacity):
item_count = len(items)
v = [0]*item_count
w = [0]*item_count
# sort items by value to weight ratio
items = sorted(items, key=lambda k: float(k.value)/k.weight, reverse = True)
for i,item in enumerate(items, 0):
v[i] = int(item.value)
w[i] = int(item.weight)
q = Queue.Queue()
root = Node(0, 0, 0, 0.0,[])
root.bound = upper_bound(root, capacity, item_count, v, w)
q.put(root)
value = 0
taken = [0]*item_count
best = set()
while not q.empty():
c = q.get()
if c.bound > value:
level = c.level+1
# check 'left' node (if item is added to knapsack)
left = Node(level,c.value + v[level-1], c.weight + w[level-1], 0.0, c.contains[:])
left.bound = upper_bound(left, capacity, item_count, v, w)
left.contains.append(level)
if left.weight <= capacity:
if left.value > value:
value = left.value
best = set(left.contains)
if left.bound > value:
q.put(left)
# check 'right' node (if items is not added to knapsack)
right = Node(level,c.value, c.weight, 0.0, c.contains[:])
right.bound = upper_bound(right, capacity, item_count, v, w)
if right.weight <= capacity:
if right.value > value:
value = right.value
best = set(right.contains)
if right.bound > value:
q.put(right)
for b in best:
taken[b-1] = 1
value = sum([i*j for (i,j) in zip(v,taken)])
return str(value)

I think you only want to calculate the bound if you're not taking the item. If you take the item, that means that your bound is still attainable. If you don't, you have to readjust your expectations.

import functools
class solver():
def __init__(self, Items, capacity):
self.sortedItems = list(filter(lambda x: x.value > 0, Items))
self.sortedItems = sorted(self.sortedItems, key=lambda x:float(x.weight)/float(x.value))
self.numItems = len(Items)
self.capacity = capacity
self.bestSolution = solution(0, self.capacity)
def isOptimisitcBetter(self, sol, newItemIdx):
newItem = self.sortedItems[newItemIdx]
rhs = (sol.value + (sol.capacity/newItem.weight)*newItem.value)
return rhs > self.bestSolution.value
def explore(self, sol, itemIndex):
if itemIndex < self.numItems:
if self.isOptimisitcBetter(sol, itemIndex):
self.exploreLeft(sol, itemIndex)
self.exploreRight(sol, itemIndex)
def exploreLeft(self, sol, itemIndex):
newItem = self.sortedItems[itemIndex]
thisSol = sol.copy()
if thisSol.addItem(newItem):
if thisSol.value > self.bestSolution.value:
self.bestSolution = thisSol
self.explore(thisSol, itemIndex+1)
def exploreRight(self, sol, itemIndex):
self.explore(sol, itemIndex+1)
def solveWrapper(self):
self.explore(solution(0, self.capacity), 0)
class solution():
def __init__(self, value, capacity, items=set()):
self.value, self.capacity = value, capacity
self.items = items.copy()
def copy(self):
return solution(self.value, self.capacity, self.items)
def addItem(self, newItem):
remainingCap = self.capacity-newItem.weight
if remainingCap < 0:
return False
self.items.add(newItem)
self.capacity = remainingCap
self.value+=newItem.value
return True
solver = solver(items, capacity)
solver.solveWrapper()
bestSol = solver.bestSolution

Related

Nodes in Ascending Order

I have a class named Poly that takes in a list of tuples and converts them into polynomials... now I'm running into some issues in my insert function and can't figure out how to order the information ascending based on power
Current Output Based on Input:
>>> i = [(2,3),(2,1),(3,4),(2,3),(3,1)]
>>> p = Poly(i)
>>> print(p)
'4x^3 + 2x + 3x + 3x^4'
Expected Output:
>>> i = [(2,3),(2,1),(3,4),(2,3),(3,1)]
>>> p = Poly(i)
>>> print(p)
'5x + 4x^3 + 3x^4'
The solution must have the lowest powers on the left and the highest powers on the right. So in ascending order.
My Code:
class Poly:
class Node:
def __init__(self, coef, power, next):
self._coef = coef
self._power = power
self._next = next
def __init__(self, lst):
self._head = None
self._size = 0
for elem in lst:
self.insert(elem)
def insert(self, tup):
node = self.Node(tup[0], tup[1], None)
if node._coef == 0:
return
if self.isEmpty():
self._head = node
self._size += 1
return
if self._head._power == node._power:
self._head._coef += node._coef
self._size += 1
return
curr = self._head
while curr._next and node._power >= curr._next._power:
if node._power == curr._power:
curr._coef += node._coef
return
curr = curr._next
self._size += 1
node._next = curr._next
curr._next = node
Thanks in advance for any help!

Update the class attribute value

I met a problem with call the value from one class into a new iterative loop outside of that class. The code is shown below: (data and newdata are vectors)
class A:
def __init__(self, k, tol=0.0001, max_iter=300):
self.k = k
self.tol = tol
self.max_iter = max_iter
def fit(self, data):
self.centroids = {}
for i in range(self.k):
self.centroids[i] = data[i+50]
for i in range(self.max_iter):
self.classifications = {}
for i in range(self.k):
self.classifications[i] = []
for featureset in data:
distances = [np.linalg.norm(featureset - self.centroids[centroid]) for centroid in self.centroids]
classification = distances.index(min(distances))
self.classifications[classification].append(featureset)
prev_centroids = dict(self.centroids)
for classification in self.classifications:
self.centroids[classification] = np.average(self.classifications[classification], axis=0)
optimized = True
for c in self.centroids:
original_centroid = prev_centroids[c]
current_centroid = self.centroids[c]
if np.sum((current_centroid - original_centroid) / original_centroid * 100.0) > self.tol:
#print(np.sum((current_centroid - original_centroid) / original_centroid * 100.0))
optimized = False
if optimized:
break
def cluster_labels(self,data):
cluster_labels = []
for featureset in data:
distances=[np.linalg.norm(featureset - self.centroids[centroid]) for centroid in self.centroids]
cluster_labels.append(distances.index(min(distances)))
return cluster_labels
def predict(self, data):
distances = [np.linalg.norm(data - self.centroids[centroid]) for centroid in self.centroids]
classification = distances.index(min(distances))
return classification
def update(self, new_data, delta):
for featureset in new_data:
distances = [np.linalg.norm(featureset - self.centroids[centroid]) for centroid in self.centroids]
if min(distances) < delta:
classification = distances.index(min(distances))
self.classifications[classification].append(featureset)
self.centroids[classification] = np.average(self.classifications[classification], axis=0)
else:
self.centroids[self.k] = featureset
self.classifications[self.k] = []
self.classifications[self.k].append(featureset)
self.k = self.k + 1
k = self.k
print (k)
return k
class Recorder:
def __init__(rec):
rec.p = pyaudio.PyAudio()
rec.stream = rec.p.open(format = pyaudio.paInt16, channels = 1, rate = 44100, input = True, input_device_index = 2, frames_per_buffer = chunk)
def write():
a = A(k=3)
a.fit(data)
k=a.update(newdata,20)
for num in range(1,100):
rec.Recorder()
rec.write()
Initially, I want to set k =3. And then, the value of k should be updated with k=a.update(newdata,20) However,now for every running, the value of K is staying at 3. And if I set k = 3 outside of the classes it always shows the error :
UnboundLocalError: local variable 'k' referenced before assignment
How could I solve this problem?
The issue is in this function:
def update(self, new_data, delta):
for featureset in new_data:
distances = [np.linalg.norm(featureset - self.centroids[centroid]) for centroid in self.centroids]
if min(distances) < delta:
classification = distances.index(min(distances))
self.classifications[classification].append(featureset)
self.centroids[classification] = np.average(self.classifications[classification], axis=0)
else:
self.centroids[self.k] = featureset
self.classifications[self.k] = []
self.classifications[self.k].append(featureset)
self.k = self.k + 1
k = self.k
You are only setting the "k" value inside the "else" block. Leaving out anything unrelated it looks like this:
def update(self, new_data, delta):
for featureset in new_data:
...
if min(distances) < delta:
...
else:
...
k = self.k
print (k) # <-- error here
return k # <-- error here
In the case where min(dinstances) >= delta, k will not be set and you will get the error you report.
You have two options:
Add a k = ... line into the if-block where min(distances) < delta
Add a k = ... line just above the if-block (still inside the for-block) to set a "default" value for k
On review it is also possible that you just need to return self.k instead of just k.

TypeError: 'list' object is not callable , problem with passing generated array to second iteration

I am trying to run 500 iterations in optimization problem. I am doing some changes on pop array and than I am trying to pass it again to the next iteration, however run into error, tho the first iteration is working all fine, I get pop array and then pass it to created_moved_pop and create_star_pop and get some problems. Will be very thankful if someone could explain my why this happens
error trace
C:\Users\yuliy\PycharmProjects\method_deform_stars\venv\Scripts\python.exe C:/Users/yuliy/PycharmProjects/method_deform_stars/DS.py
[0.8575697060274371, 0.8802225709314421, 0.6098937002728221, 0.5482650148523068, 0.5395302259903021, 0.6330576538506912, 0.734280095260012, 0.6826885236666879, 0.5797401283594749, 0.8381278588403586, 0.4983449567579089, 0.37081148554598065, 0.19916270916904044, 0.7590390380364216, 0.8272752130297748, 0.8837021413140848, 0.9750382019031415, 0.5436068899712437, 0.6490739970397773, 0.3014768191053475]
Traceback (most recent call last):
File "C:/Users/yuliy/PycharmProjects/method_deform_stars/DS.py", line 70, in
star_pop = create_star_pop(pop)
[(0.11503376215798591, 6.794025806650792), (0.5133530350808552, 1.0230252795290697), (0.37081148554598065, 0.8887201815324006), (0.4201038734097051, 0.8215339609930865), (0.6098937002728221, 0.7952234761836543), (0.19916270916904044, 0.7689552603259296), (0.250319764137194, 0.7111682294644993), (0.3014768191053475, 0.7008819653567403), (0.6582300480283956, 0.6632231712798371), (0.6666685334677784, 0.658688271415733), (0.7646482839856097, 0.6322183223530311), (0.8120560994714594, 0.6155315414048562), (0.7590390380364216, 0.59962403681057), (0.8609150000772217, 0.569512653796447), (0.8083043720319294, 0.5354111445749077), (0.620024614496207, 0.4887918787850577), (0.5035114962372264, 0.4844464118877576), (0.8670977366853939, 0.48321853250106644), (0.5541193285153655, 0.4821747663938167), (0.8575697060274371, 0.47491541406252397)]
File "C:/Users/yuliy/PycharmProjects/method_deform_stars/DS.py", line 60, in create_star_pop
new_element = star_pop(population)
TypeError: 'list' object is not callable
[0.11503376215798591, 0.5133530350808552, 0.37081148554598065, 0.4201038734097051, 0.6098937002728221, 0.19916270916904044, 0.250319764137194, 0.3014768191053475, 0.6582300480283956, 0.6666685334677784, 0.7646482839856097, 0.8120560994714594, 0.7590390380364216, 0.8609150000772217, 0.8083043720319294, 0.620024614496207, 0.5035114962372264, 0.8670977366853939, 0.5541193285153655, 0.8575697060274371]
Process finished with exit code 1
import numpy as np
import math
import random
import operator
# Global variables
a = 0.1
b = 1
def function(x):
return (math.sin(40*math.pi*x)+math.pow(x-1, 4))/(2*x)
def initial_pop():
pop = np.random.uniform(a, b, 20)
pop = pop.tolist()
return pop
def moving_pop(population):
# e c
rand_item = population[random.randrange(len(population))]
# print(rand_item)
direction_arr = [-1, 1]
direction = direction_arr[random.randrange(len(direction_arr))]
# print(direction)
new_element = rand_item + direction * np.random.normal(0, 0.2)
if new_element > b:
extra = new_element - b
new_element = a + extra
if new_element < a:
extra = abs(new_element - a)
new_element = b - extra
# print(new_element)
return new_element
def create_moved_pop(population):
new_population = []
for x in range(0, 20):
new_element = moving_pop(population)
new_population.append(new_element)
# print(new_population)
return new_population
def star_pop(population):
random_item1 = population[random.randrange(len(population))]
random_item2 = population[random.randrange(len(population))]
while random_item2 == random_item1:
random_item2 = population[random.randrange(len(population))]
e_star = (random_item1 + random_item2)/2
return e_star
def create_star_pop(population):
star_population = []
for x in range(0, 20):
new_element = star_pop(population)
star_population.append(new_element)
# print(new_population)
return star_population
pop = initial_pop()
print(pop)
for i in range(0, 500):
moved_pop = create_moved_pop(pop)
star_pop = create_star_pop(pop)
pop_combined = sorted(sorted(pop) + sorted(moved_pop) +
sorted(star_pop))
y_array = []
for x in range(0, len(pop_combined)):
y_array.append(function(pop_combined[x]))
x_y_array = dict(zip(pop_combined, y_array))
sorted_x_y_array = sorted(x_y_array.items(), key=operator.itemgetter(1), reverse=True)
sorted_x_y_array = sorted_x_y_array[0:20]
print(sorted_x_y_array)
pop.clear()
for x in sorted_x_y_array:
pop.append(x[0])
print(pop)
you are re-defining star_pop as list
star_pop = create_star_pop(pop)
find the below fixed code
import numpy as np
import math
import random
import operator
# Global variables
a = 0.1
b = 1
def function(x):
return (math.sin(40*math.pi*x)+math.pow(x-1, 4))/(2*x)
def initial_pop():
pop = np.random.uniform(a, b, 20)
pop = pop.tolist()
return pop
def moving_pop(population):
# e c
rand_item = population[random.randrange(len(population))]
# print(rand_item)
direction_arr = [-1, 1]
direction = direction_arr[random.randrange(len(direction_arr))]
# print(direction)
new_element = rand_item + direction * np.random.normal(0, 0.2)
if new_element > b:
extra = new_element - b
new_element = a + extra
if new_element < a:
extra = abs(new_element - a)
new_element = b - extra
# print(new_element)
return new_element
def create_moved_pop(population):
new_population = []
for x in range(0, 20):
new_element = moving_pop(population)
new_population.append(new_element)
# print(new_population)
return new_population
def star_pop(population):
random_item1 = population[random.randrange(len(population))]
random_item2 = population[random.randrange(len(population))]
while random_item2 == random_item1:
random_item2 = population[random.randrange(len(population))]
e_star = (random_item1 + random_item2)/2
return e_star
def create_star_pop(population):
star_population = []
for x in range(0, 20):
new_element = star_pop(population)
star_population.append(new_element)
# print(new_population)
return star_population
pop = initial_pop()
print(pop)
for i in range(0, 500):
moved_pop = create_moved_pop(pop)
star_pop_ = create_star_pop(pop)
pop_combined = sorted(sorted(pop) + sorted(moved_pop) +
sorted(star_pop_))
y_array = []
for x in range(0, len(pop_combined)):
y_array.append(function(pop_combined[x]))
x_y_array = dict(zip(pop_combined, y_array))
sorted_x_y_array = sorted(x_y_array.items(), key=operator.itemgetter(1), reverse=True)
sorted_x_y_array = sorted_x_y_array[0:20]
print(sorted_x_y_array)
pop.clear()
for x in sorted_x_y_array:
pop.append(x[0])
print(pop)
You got a method called star_pop and an object called star_pop, in the 2nd iteration of the for loop you are trying
new_element = star_pop(population)
after doing
star_pop = create_star_pop(pop)
which returns a list
I think you mixed up your names. You can fix it by renaming either the function or the star_pop list.
Unrelated to that, you dont need to include the 0 in range(0, 500). Ranges always start at 0 by default.

Multivariable Cumulants and Moments in python

In Mathematica I can convert multivariable moments in cumulants and back using MomentConvert:
MomentConvert[Cumulant[{2, 2,1}], "Moment"] // TraditionalForm
as one can try in wolframcloud.
I would like to do exactly the same in python. Is there any library in python capable of this?
At least the one direction I now programmed by myself:
# from http://code.activestate.com/recipes/577211-generate-the-partitions-of-a-set-by-index/
from collections import defaultdict
class Partition:
def __init__(self, S):
self.data = list(S)
self.m = len(S)
self.table = self.rgf_table()
def __getitem__(self, i):
#generates set partitions by index
if i > len(self) - 1:
raise IndexError
L = self.unrank_rgf(i)
result = self.as_set_partition(L)
return result
def __len__(self):
return self.table[self.m,0]
def as_set_partition(self, L):
# Transform a restricted growth function into a partition
n = max(L[1:]+[1])
m = self.m
data = self.data
P = [[] for _ in range(n)]
for i in range(m):
P[L[i+1]-1].append(data[i])
return P
def rgf_table(self):
# Compute the table values
m = self.m
D = defaultdict(lambda:1)
for i in range(1,m+1):
for j in range(0,m-i+1):
D[i,j] = j * D[i-1,j] + D[i-1,j+1]
return D
def unrank_rgf(self, r):
# Unrank a restricted growth function
m = self.m
L = [1 for _ in range(m+1)]
j = 1
D = self.table
for i in range(2,m+1):
v = D[m-i,j]
cr = j*v
if cr <= r:
L[i] = j + 1
r -= cr
j += 1
else:
L[i] = r // v + 1
r %= v
return L
# S = set(range(4))
# P = Partition(S)
# for x in P:
# print (x)
# using https://en.wikipedia.org/wiki/Cumulant#Joint_cumulants
import math
def Cum2Mom(arr, state):
def E(op):
return qu.expect(op, state)
def Arr2str(arr,sep):
r = ''
for i,x in enumerate(arr):
r += str(x)
if i<len(arr)-1:
r += sep
return r
if isinstance( arr[0],str):
myprod = lambda x: Arr2str(x,'*')
mysum = lambda x: Arr2str(x,'+')
E=lambda x: 'E('+str(x)+')'
myfloat = str
else:
myfloat = lambda x: x
myprod = np.prod
mysum = sum
S = set(range(len(arr)))
P = Partition(S)
return mysum([
myprod([myfloat(math.factorial(len(pi)-1) * (-1)**(len(pi)-1))
,myprod([
E(myprod([
arr[i]
for i in B
]))
for B in pi])])
for pi in P])
print(Cum2Mom(['a','b','c','d'],1) )
import qutip as qu
print(Cum2Mom([qu.qeye(3) for i in range(3)],qu.qeye(3)) )
It's designed to work with qutip opjects and it also works with strings to verify the correct separation and prefactors.
Exponents of the variables can be represented by repeating the variable.

Bidirectional A* not finding the shortest path

I'm implementing bidirectional A* algorithm in Python 2.7.12 and testing it on the map of Romania from from Russell and Norvig, Chapter 3. The edges have weights and the aim is to find the shortest path between two nodes.
Here is the visualization of the testing graph:
The example where my Bidirectional A* is failing is that where the starting point is 'a' and the goal is 'u'. This is the path that my implementation has found:
The length of ['a', 's', 'f', 'b', 'u'] is 535.
This is the actual shortest path from 'a' to 'u':
The length of ['a', 's', 'r', 'p', 'b', 'u'] is 503.
As we can see, my implementation failed to find the shortest path. I think that the problem may be in my stopping conditions, but I don't know.
This is the python script with my implementation of A* (I used Euclidean distance as a heuristic) and few other help classes and functions:
from __future__ import division
import math
from networkx import *
import random
import pickle
import sys
import heapq
import matplotlib.pyplot as plt
class PriorityQueue():
"""Implementation of a priority queue"""
def __init__(self):
self.queue = []
self.node_finder = dict()
self.current = 0
self.REMOVED_SYMBOL = '<removed>'
def next(self):
if self.current >=len(self.queue):
self.current
raise StopIteration
out = self.queue[self.current]
self.current += 1
return out
def pop(self):
while self.queue:
node = heapq.heappop(self.queue)
nodeId = node[1]
if nodeId is not self.REMOVED_SYMBOL:
try:
del self.node_finder[nodeId]
except KeyError:
dummy=1
return node
def remove(self, nodeId):
node = self.node_finder[nodeId]
node[1] = self.REMOVED_SYMBOL
def __iter__(self):
return self
def __str__(self):
return 'PQ:[%s]'%(', '.join([str(i) for i in self.queue]))
def append(self, node):
nodeId = node[1]
nodePriority = node[0]
node = [nodePriority, nodeId]
self.node_finder[nodeId] = node
heapq.heappush(self.queue, node)
def update(self, node):
nodeId = node[1]
nodePriority = node[0]
node = [nodePriority, nodeId]
self.remove(nodeId)
self.node_finder[nodeId] = node
heapq.heappush(self.queue, node)
def getPriority(self, nodeId):
return self.node_finder[nodeId][0]
def __contains__(self, key):
self.current = 0
return key in [n for v,n in self.queue]
def __eq__(self, other):
return self == other
def size(self):
return len(self.queue)
def clear(self):
self.queue = []
def top(self):
return self.queue[0]
__next__ = next
def bidirectional_a_star(graph, start, goal):
if start == goal:
return []
pq_s = PriorityQueue()
pq_t = PriorityQueue()
closed_s = dict()
closed_t = dict()
g_s = dict()
g_t = dict()
g_s[start] = 0
g_t[goal] = 0
cameFrom1 = dict()
cameFrom2 = dict()
def euclidean_distance(graph, v, goal):
xv, yv = graph.node[v]['pos']
xg, yg = graph.node[goal]['pos']
return ((xv-xg)**2 + (yv-yg)**2)**0.5
def h1(v): # heuristic for forward search (from start to goal)
return euclidean_distance(graph, v, goal)
def h2(v): # heuristic for backward search (from goal to start)
return euclidean_distance(graph, v, start)
cameFrom1[start] = False
cameFrom2[goal] = False
pq_s.append((0+h1(start), start))
pq_t.append((0+h2(goal), goal))
done = False
i = 0
mu = 10**301 # 10**301 plays the role of infinity
connection = None
while pq_s.size() > 0 and pq_t.size() > 0 and done == False:
i = i + 1
if i % 2 == 1: # alternate between forward and backward A*
fu, u = pq_s.pop()
closed_s[u] = True
for v in graph[u]:
weight = graph[u][v]['weight']
if v in g_s:
if g_s[u] + weight < g_s[v]:
g_s[v] = g_s[u] + weight
cameFrom1[v] = u
if v in closed_s:
del closed_s[v]
if v in pq_s:
pq_s.update((g_s[v]+h1(v), v))
else:
pq_s.append((g_s[v]+h1(v), v))
else:
g_s[v] = g_s[u] + weight
cameFrom1[v] = u
pq_s.append((g_s[v]+h1(v), v))
if v in closed_t:
if g_s[u] + weight + g_t[v] < mu:
mu = g_s[u] + weight + g_t[v]
connection = v
done = True
else:
fu, u = pq_t.pop()
closed_t[u] = True
for v in graph[u]:
weight = graph[u][v]['weight']
if v in g_t:
if g_t[u] + weight < g_t[v]:
g_t[v] = g_t[u] + weight
cameFrom2[v] = u
if v in closed_t:
del closed_t[v]
if v in pq_t:
pq_t.update((g_t[v]+h2(v), v))
else:
pq_t.append((g_t[v]+h2(v), v))
else:
g_t[v] = g_t[u] + weight
cameFrom2[v] = u
pq_t.append((g_t[v]+h2(v), v))
if v in closed_s:
if g_t[u] + weight + g_s[v] < mu:
mu = g_t[u] + weight + g_s[v]
connection = v
done = True
if u in closed_s and u in closed_t:
if g_s[u] + g_t[u] < mu:
mu = g_s[u] + g_t[u]
connection = u
stopping_distance = min(min([f for (f,x) in pq_s]), min([f for (f,x) in pq_t]))
if mu <= stopping_distance:
done = True
#connection = u
continue
if connection is None:
# start and goal are not connected
return None
#print cameFrom1
#print cameFrom2
path = []
current = connection
#print current
while current != False:
#print predecessor
path = [current] + path
current = cameFrom1[current]
current = connection
successor = cameFrom2[current]
while successor != False:
path = path + [successor]
current = successor
successor = cameFrom2[current]
return path
# This function visualizes paths
def draw_graph(graph, node_positions={}, start=None, goal=None, path=[]):
explored = list(graph.get_explored_nodes())
labels ={}
for node in graph:
labels[node]=node
if not node_positions:
node_positions = networkx.spring_layout(graph)
edge_labels = networkx.get_edge_attributes(graph,'weight')
networkx.draw_networkx_nodes(graph, node_positions)
networkx.draw_networkx_edges(graph, node_positions, style='dashed')
networkx.draw_networkx_edge_labels(graph, node_positions, edge_labels=edge_labels)
networkx.draw_networkx_labels(graph,node_positions, labels)
networkx.draw_networkx_nodes(graph, node_positions, nodelist=explored, node_color='g')
if path:
edges = [(path[i], path[i+1]) for i in range(0, len(path)-1)]
networkx.draw_networkx_edges(graph, node_positions, edgelist=edges, edge_color='b')
if start:
networkx.draw_networkx_nodes(graph, node_positions, nodelist=[start], node_color='b')
if goal:
networkx.draw_networkx_nodes(graph, node_positions, nodelist=[goal], node_color='y')
plt.plot()
plt.show()
# this function calculates the length of the path
def calculate_length(graph, path):
pairs = zip(path, path[1:])
return sum([graph.get_edge_data(a, b)['weight'] for a, b in pairs])
#Romania map data from Russell and Norvig, Chapter 3.
romania = pickle.load(open('romania_graph.pickle', 'rb'))
node_positions = {n: romania.node[n]['pos'] for n in romania.node.keys()}
start = 'a'
goal = 'u'
path = bidirectional_a_star(romania, start, goal)
print "This is the path found by bidirectional A* :", path
print "Its length :", calculate_length(romania, path)
# visualize my path
draw_graph(romania, node_positions=node_positions, start=start, goal=goal, path=path)
# compare to the true shortest path between start and goal
true_path = networkx.shortest_path(romania, start, goal, weight='weight')
print "This is the actual shortest path: ", true_path
print "Its lenght: ", calculate_length(romania, true_path)
#visualize true_path
draw_graph(romania, node_positions=node_positions, start=start, goal=goal, path=true_path)
Pickle data for Romania can be downloaded from here.
I corrected some errors in PriorityQueue and bidirectional_a_star. It's working fine now.
The corrected code for the class and the function is as follows:
class PriorityQueue():
"""Implementation of a priority queue"""
def __init__(self):
self.queue = []
self.node_finder = dict()
self.current = 0
self.REMOVED_SYMBOL = '<removed>'
def next(self):
if self.current >=len(self.queue):
self.current
raise StopIteration
out = self.queue[self.current]
while out == self.REMOVED_SYMBOL:
self.current += 1
out = self.queue[self.current]
self.current += 1
return out
def pop(self):
# TODO: finish this
while self.queue:
node = heapq.heappop(self.queue)
nodeId = node[1]
if nodeId is not self.REMOVED_SYMBOL:
try:
del self.node_finder[nodeId]
except KeyError:
dummy=1
return node
#raise KeyError('pop from an empty priority queue')
def remove(self, nodeId):
node = self.node_finder[nodeId]
node[1] = self.REMOVED_SYMBOL
def __iter__(self):
return self
def __str__(self):
return 'PQ:[%s]'%(', '.join([str(i) for i in self.queue]))
def append(self, node):
# node = (priority, nodeId)
nodeId = node[1]
nodePriority = node[0]
node = [nodePriority, nodeId]
self.node_finder[nodeId] = node
heapq.heappush(self.queue, node)
def update(self, node):
nodeId = node[1]
nodePriority = node[0]
node = [nodePriority, nodeId]
self.remove(nodeId)
self.node_finder[nodeId] = node
heapq.heappush(self.queue, node)
def getPriority(self, nodeId):
return self.node_finder[nodeId][0]
def __contains__(self, key):
self.current = 0
return key in [n for v,n in self.queue]
def __eq__(self, other):
return self == other
def size(self):
return len([1 for priority, node in self.queue if node!=self.REMOVED_SYMBOL])
def clear(self):
self.queue = []
def top(self):
return self.queue[0]
__next__ = next
def bidirectional_a_star(graph, start, goal):
if start == goal:
return []
pq_s = PriorityQueue()
pq_t = PriorityQueue()
closed_s = dict()
closed_t = dict()
g_s = dict()
g_t = dict()
g_s[start] = 0
g_t[goal] = 0
cameFrom1 = dict()
cameFrom2 = dict()
def euclidean_distance(graph, v, goal):
xv, yv = graph.node[v]['pos']
xg, yg = graph.node[goal]['pos']
return ((xv-xg)**2 + (yv-yg)**2)**0.5
def h1(v): # heuristic for forward search (from start to goal)
return euclidean_distance(graph, v, goal)
def h2(v): # heuristic for backward search (from goal to start)
return euclidean_distance(graph, v, start)
cameFrom1[start] = False
cameFrom2[goal] = False
pq_s.append((0+h1(start), start))
pq_t.append((0+h2(goal), goal))
done = False
i = 0
mu = 10**301 # 10**301 plays the role of infinity
connection = None
while pq_s.size() > 0 and pq_t.size() > 0 and done == False:
i = i + 1
if i % 2 == 1: # alternate between forward and backward A*
fu, u = pq_s.pop()
closed_s[u] = True
for v in graph[u]:
weight = graph[u][v]['weight']
if v in g_s:
if g_s[u] + weight < g_s[v]:
g_s[v] = g_s[u] + weight
cameFrom1[v] = u
if v in closed_s:
del closed_s[v]
if v in pq_s:
pq_s.update((g_s[v]+h1(v), v))
else:
pq_s.append((g_s[v]+h1(v), v))
else:
g_s[v] = g_s[u] + weight
cameFrom1[v] = u
pq_s.append((g_s[v]+h1(v), v))
else:
fu, u = pq_t.pop()
closed_t[u] = True
for v in graph[u]:
weight = graph[u][v]['weight']
if v in g_t:
if g_t[u] + weight < g_t[v]:
g_t[v] = g_t[u] + weight
cameFrom2[v] = u
if v in closed_t:
del closed_t[v]
if v in pq_t:
pq_t.update((g_t[v]+h2(v), v))
else:
pq_t.append((g_t[v]+h2(v), v))
else:
g_t[v] = g_t[u] + weight
cameFrom2[v] = u
pq_t.append((g_t[v]+h2(v), v))
if u in closed_s and u in closed_t:
if g_s[u] + g_t[u] < mu:
mu = g_s[u] + g_t[u]
connection = u
try:
stopping_distance = max(min([f for (f,x) in pq_s]), min([f for (f,x) in pq_t]))
except ValueError:
continue
if mu <= stopping_distance:
done = True
connection = u
continue
if connection is None:
# start and goal are not connected
return None
#print cameFrom1
#print cameFrom2
path = []
current = connection
#print current
while current != False:
#print predecessor
path = [current] + path
current = cameFrom1[current]
current = connection
successor = cameFrom2[current]
while successor != False:
path = path + [successor]
current = successor
successor = cameFrom2[current]
return path

Categories