I try to optimize a simple python algorithm I made that approximately solve the Traveling Salesman Problem :
import math
import random
import matplotlib.pyplot as plt
import datetime
#Distance between two point
def distance(point1, point2):
return math.sqrt((point2[0]-point1[0])**2+(point2[1]-point1[1])**2)
#TSP TimeTraveler Algorithm
def TSP_TimeTraveler(Set_Points):
print("Solving TSP")
#For calculating execution time
time_start = datetime.datetime.now()
#Copy the set points
points = Set_Points.copy()
route = []
#Take 3 points at random
route.append(points.pop(random.randint(0,len(points)-1)))
route.insert(0,points.pop(random.randint(0,len(points)-1)))
route.insert(1,points.pop(random.randint(0,len(points)-1)))
#Calulating the initial route length
Length = distance(route[0],route[1]) + distance(route[1],route[-1]) + distance(route[-1],route[0])
#Time Traveler Algorithm
while len(points)>0 :
print("Points left : ", len(points),' ', end="\r")
#Take a random point from the Set
point = points.pop(random.randint(0,len(points)-1))
###############################################################################################################
#### Finding the closest route segment by calculation all lengths posibilities and finding the minimum one ####
###############################################################################################################
Set_Lengths = []
for i in range(1,len(route)):
#Set of Lengths when the point is on each route segment except the last one
L = Length - distance(route[i-1],route[i]) + distance(route[i-1],point) + distance(point, route[i])
Set_Lengths.append((i,L))
#Adding the last length when the point is on the last segement
L = Length - distance(route[-1],route[0]) + distance(route[-1],point) + distance(point, route[0])
Set_Lengths.append((0,L))
###############################################################################################################
###############################################################################################################
#Sorting the set of lengths
Set_Lengths.sort(key=lambda k: k[1])
#Inserting the point on the minimum length segment
route.insert(Set_Lengths[0][0], point)
#Updating the new route length
Length = Set_Lengths[0][1]
#Connecting the start point with the finish point
route.append(route[0])
#For calculating execution time
time_end = datetime.datetime.now()
delta = (time_end-time_start).total_seconds()
print("Points left : ", len(points),' Done ',)
print("Execution time : ", delta, "secs")
return route
#######################
#Testing the Algorithm#
#######################
#Size of the set
size = 2520
#Generating a set of random 2D points
points = []
for i in range(size):
points.append([random.uniform(0, 100),random.uniform(0, 100)])
#Solve TSP
route = TSP_TimeTraveler(points)
#Plot the solution
plt.scatter(*zip(*points),s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()
The algorithm operate in 3 simple steps :
1/ First step I take 3 points at random from the points set and connect them as the initial route.
2/ Then each next step, I take a point at random from the set of points left. And try to find the closest segment of the route i have and connect it to it.
3/ I keep repeating step 2/ until the set of points left is empty.
Here is a gif of how the algorithm solve a set of 120 points : TimeTravelerAlgorithm.gif
I give it the name "Time Traveler" because it's operate like a greedy salesman algorithm. But instead traveling to the closest new city in the present, the greedy salesman time travel to the past to the closest city he had already visited and go visit that new city then continue his normal route.
The time traveler start a route of 3 cities, and the traveler add a new city each step in his past, until he reach a present where he visited all the cities and returned to his home city.
The algorithm give decent solutions fast for small set of points. Here is the execution time for each number of sets, all are made on a 2.6GHz dual-core Intel Core i5 processor Macbook :
120 points in around 0.03 secs
360 points in around 0.23 secs
2520 points in around 10 secs
10 000 points in around 3 mins
100 000 points in around 5 hours (Solution Map)
The algorithm is far from being optimized, because in some cases it gives cross routes which is suboptimal. And It's all made in pure python. Maybe using numpy or some advance library or even GPU can speed up the program.
I want your review and help on how to optimize it. I try to approximately solve without cross routes for set of points that can be extremely large (from 1 million to 100 billions points).
PS: My algorithm and codes are open. People from internet, feel free to use it in any project or any research you have.
Thanks for the comments. I re-implemented the algorithm using Objects, Sets and Linked list. I also removed the square root from distance function . Now the code look more clean :
import math
import random
import datetime
import matplotlib.pyplot as plt
#Distance between two point
def distance(point1, point2):
return (point2[0]-point1[0])**2 + (point2[1]-point1[1])**2
#Distance between two point
class Node:
def __init__(self, dataval=None):
self.dataval = dataval
self.nextval = None
class TSP_TimeTraveler():
def __init__(self, dataval=None):
self.count = 0
self.position = None
self.length = 0
def get_position():
return self.position
def next_city():
self.position = self.position.nextval
return self.position
#adding a city to the current route with Time Traveler Algorithm :
def add_city(self, point):
node = Node(point)
if self.count <=0 :
self.position = node
elif self.count == 1 :
node.nextval = self.position
self.position.nextval = node
self.length = 2*distance(self.position.dataval,node.dataval)
else :
#Creating the traveler
traveler = self.position
c = traveler.dataval #current position
n = traveler.nextval.dataval #next position
#Calculating the length of adding the city to the path
Min_L = self.length-distance(c,n)+distance(c,node.dataval)+distance(node.dataval,n)
Min_Node = traveler
traveler = traveler.nextval
while traveler != self.position :
c = traveler.dataval #current position
n = traveler.nextval.dataval #next position
#Calculating the length of adding the city to the path
L = self.length-distance(c,n)+distance(c,node.dataval)+distance(node.dataval,n)
#Searching the path to the of city with minimum length
if L < Min_L :
Min_L = L
Min_Node = traveler
traveler = traveler.nextval
#Adding the city to the minimum path
node.nextval = Min_Node.nextval
Min_Node.nextval = node
self.length = Min_L
#Incrementing the number of city in the route
self.count = self.count + 1
#Get the list of the route
def getRoute(self):
result = []
traveler = self.position
result.append(traveler.dataval)
traveler = traveler.nextval
while traveler != self.position :
result.append(traveler.dataval)
traveler = traveler.nextval
result.append(traveler.dataval)
return result
def Solve(self, Set_points):
print("Solving TSP")
#For calculating execution time
time_start = datetime.datetime.now()
#Copy the set points list
points = Set_points.copy()
#Transform the list into set
points = set(tuple(i) for i in points)
#Add
while len(points)>0 :
print("Points left : ", len(points),' ', end="\r")
point = points.pop()
self.add_city(point)
result = self.getRoute()
#For calculating execution time
time_end = datetime.datetime.now()
delta = (time_end-time_start).total_seconds()
print("Points left : ", len(points),' Done ',)
print("Execution time : ", delta, "secs")
return result
#######################
#Testing the Algorithm#
#######################
#Size of the set
size = 120
#Generating a set of random 2D points
points = []
for i in range(size):
points.append((random.uniform(0, 100),random.uniform(0, 100)))
#Solve TSP
TSP = TSP_TimeTraveler()
route = TSP.Solve(points)
#Plot the solution
plt.scatter(*zip(*points),s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()
And using PyPy instead of normal python it runs alot faster :
120 in around 0.03sec
360 in around 0.05sec
2520 in around 0.22sec
10 000 in around 2sec
100 000 in around 7min
The 100 000 case that took before 5 hours, now it's solved in 7min.
Next, I will try to implement a 2-opt with double linked list and KD-tree. So it can solve for large sets without crosses.
I improved the algorithm by adding double linked list and 2-opt at each insertion :
import math
import random
import datetime
import matplotlib.pyplot as plt
#Distance between two point
def distance(point1, point2):
return (point2[0]-point1[0])**2 + (point2[1]-point1[1])**2
#Intersection between two segments
def intersects(p1, q1, p2, q2):
def on_segment(p, q, r):
if r[0] <= max(p[0], q[0]) and r[0] >= min(p[0], q[0]) and r[1] <= max(p[1], q[1]) and r[1] >= min(p[1], q[1]):
return True
return False
def orientation(p, q, r):
val = ((q[1] - p[1]) * (r[0] - q[0])) - ((q[0] - p[0]) * (r[1] - q[1]))
if val == 0 : return 0
return 1 if val > 0 else -1
o1 = orientation(p1, q1, p2)
o2 = orientation(p1, q1, q2)
o3 = orientation(p2, q2, p1)
o4 = orientation(p2, q2, q1)
if o1 != o2 and o3 != o4:
return True
if o1 == 0 and on_segment(p1, q1, p2) : return True
if o2 == 0 and on_segment(p1, q1, q2) : return True
if o3 == 0 and on_segment(p2, q2, p1) : return True
if o4 == 0 and on_segment(p2, q2, q1) : return True
return False
#Distance Double Linked Node
class Node:
def __init__(self, dataval=None):
self.dataval = dataval
self.prevval = None
self.nextval = None
class TSP_TimeTraveler():
def __init__(self):
self.count = 0
self.position = None
self.length = 0
self.traveler = None
self.travelert_past = None
self.is_2opt = True
def get_position():
return self.position
def traveler_init(self):
self.traveler = self.position
self.travelert_past = self.position.prevval
return self.traveler
def traveler_next(self):
if self.traveler.nextval != self.travelert_past:
self.travelert_past = self.traveler
self.traveler = self.traveler.nextval
return self.traveler, False
else :
self.travelert_past = self.traveler
self.traveler = self.traveler.prevval
return self.traveler, True
#adding a city to the current route with Time Traveler Algorithm :
def add_city(self, point):
node = Node(point)
if self.count <=0 :
self.position = node
elif self.count == 1 :
node.nextval = self.position
node.prevval = node
self.position.nextval = node
self.position.prevval = self.position
self.length = 2*distance(self.position.dataval,node.dataval)
elif self.count == 2 :
node.nextval = self.position.nextval
node.prevval = self.position
self.position.nextval.prevval = node
self.position.nextval = node
self.length = 2*distance(self.position.dataval,node.dataval)
else :
#Creating the traveler
traveler = self.traveler_init()
c = traveler #current position
prev = False #inverse link
n, prev = self.traveler_next()
#Calculating the length of adding the city to the path
Min_prev = prev
Min_L = self.length-distance(c.dataval,n.dataval)+distance(c.dataval,node.dataval)+distance(node.dataval,n.dataval)
Min_Node = c
traveler = n
while traveler != self.position :
c = n #current position
n, prev = self.traveler_next()
#Calculating the length of adding the city to the path
L = self.length-distance(c.dataval,n.dataval)+distance(c.dataval,node.dataval)+distance(node.dataval,n.dataval)
#Searching the path to the of city with minimum length
if L < Min_L :
Min_prev = prev
Min_L = L
Min_Node = c
traveler = n
if Min_prev :
Min_Next_Node = Min_Node.prevval
else :
Min_Next_Node = Min_Node.nextval
node.nextval = Min_Next_Node
node.prevval = Min_Node
if Min_prev :
Min_Node.prevval = node
else :
Min_Node.nextval = node
if Min_Next_Node.nextval == Min_Node:
Min_Next_Node.nextval = node
else :
Min_Next_Node.prevval = node
self.length = Min_L
#2-OP
if self.is_2opt == True :
self._2opt(Min_Node, node, Min_Next_Node)
#Incrementing the number of city in the route
self.count = self.count + 1
#apply the 2opt to a-b-c
def _2opt(self, a, b, c):
traveler = self.traveler_init()
c1 = a
c2 = b
n1 = b
n2 = c
c = traveler #current position
t_prev = False
n, t_prev = self.traveler_next()
traveler = n
while traveler != self.position :
cross = False
if (c.dataval != c1.dataval and c.dataval != c2.dataval and n.dataval != c1.dataval and n.dataval != c2.dataval) and intersects(c.dataval, n.dataval, c1.dataval, c2.dataval):
self._2optswap(c,n,c1,c2)
cross = True
a = n
n = c1
c2 = a
if (c.dataval != n1.dataval and c.dataval != n2.dataval and n.dataval != n1.dataval and n.dataval != n2.dataval) and intersects(c.dataval, n.dataval, n1.dataval, n2.dataval):
self._2optswap(c,n,n1,n2)
cross = True
a = n
n = n1
n2 = a
if cross:
return
c = n #current position
n, t_prev = self.traveler_next()
traveler = n
#swap between the crossed segment a-b and c-d
def _2optswap(self, a, b, c, d):
if a.nextval == b :
a.nextval = c
else :
a.prevval = c
if b.prevval == a :
b.prevval = d
else :
b.nextval = d
if c.nextval == d :
c.nextval = a
else :
c.prevval = a
if d.prevval == c :
d.prevval = b
else :
d.nextval = b
self.length = self.length - distance(a.dataval,b.dataval) - distance(c.dataval,d.dataval) + distance(a.dataval,c.dataval) + distance(b.dataval,d.dataval)
#Get the list of the route
def getRoute(self):
result = []
traveler = self.traveler_init()
result.append(traveler.dataval)
traveler, prev = self.traveler_next()
while traveler != self.position :
result.append(traveler.dataval)
traveler, prev = self.traveler_next()
result.append(traveler.dataval)
return result
def Solve(self, Set_points, with_2opt = True):
print("Solving TSP")
#For calculating execution time
time_start = datetime.datetime.now()
#Copy the set points list
points = Set_points.copy()
#Transform the list into set
points = set(tuple(i) for i in points)
#Add
while len(points)>0 :
print("Points left : ", len(points),' ', end="\r")
point = points.pop()
self.add_city(point)
result = self.getRoute()
#For calculating execution time
time_end = datetime.datetime.now()
delta = (time_end-time_start).total_seconds()
L=0
for i in range(len(result)-1):
L = L + math.sqrt((result[i-1][0]-result[i][0])**2 + (result[i-1][1]-result[i][1])**2)
print("Points left : ", len(points),' Done ',)
print("Execution time : ", delta, "secs")
print("Average time per point : ", 1000*delta/len(Set_points), "msecs")
print("Length : ", L)
return result
#######################
#Testing the Algorithm#
#######################
#Size of the set
size = 1000
#Generating a set of random 2D points
points = []
for i in range(size):
points.append((random.uniform(0, 100),random.uniform(0, 100)))
#Solve TSP
TSP = TSP_TimeTraveler()
route = TSP.Solve(points, with_2opt = True)
plt.scatter(*zip(*route), s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()
Now the solution give fast results with no cross routes.
With PyPy it solves 100,000 points with no cross routes in 30 min.
Now Im working on implementing the KD-tree to solve for large sets.
Related
this is sample code of 8 puzzle game which take two matrix initial and goal state .
class Node:
def __init__(self,data,level,fval):
""" Initialize the node with the data, level of the node and the calculated fvalue """
self.data = data
self.level = level
self.fval = fval
def generate_child(self):
""" Generate child nodes from the given node by moving the blank space
either in the four directions {up,down,left,right} """
x,y = self.find(self.data,'_')
""" val_list contains position values for moving the blank space in either of
the 4 directions [up,down,left,right] respectively. """
val_list = [[x,y-1],[x,y+1],[x-1,y],[x+1,y]]
children = []
for i in val_list:
child = self.shuffle(self.data,x,y,i[0],i[1])
if child is not None:
child_node = Node(child,self.level+1,0)
children.append(child_node)
return children
def shuffle(self,puz,x1,y1,x2,y2):
""" Move the blank space in the given direction and if the position value are out
of limits the return None """
if x2 >= 0 and x2 < len(self.data) and y2 >= 0 and y2 < len(self.data):
temp_puz = []
temp_puz = self.copy(puz)
temp = temp_puz[x2][y2]
temp_puz[x2][y2] = temp_puz[x1][y1]
temp_puz[x1][y1] = temp
return temp_puz
else:
return None
def copy(self,root):
""" Copy function to create a similar matrix of the given node"""
temp = []
for i in root:
t = []
for j in i:
t.append(j)
temp.append(t)
return temp
def find(self,puz,x):
""" Specifically used to find the position of the blank space """
for i in range(0,len(self.data)):
for j in range(0,len(self.data)):
if puz[i][j] == x:
return i,j
class Puzzle:
def __init__(self,size):
""" Initialize the puzzle size by the specified size,open and closed lists to empty """
self.n = size
self.open = []
self.closed = []
def accept(self):
""" Accepts the puzzle from the user """
puz = []
for i in range(0,self.n):
temp = input().split(" ")
puz.append(temp)
return puz
def f(self,start,goal):
""" Heuristic Function to calculate hueristic value f(x) = h(x) + g(x) """
return self.h(start.data,goal)+start.level
def h(self,start,goal):
""" Calculates the different between the given puzzles """
temp = 0
for i in range(0,self.n):
for j in range(0,self.n):
if start[i][j] != goal[i][j] and start[i][j] != '_':
temp += 1
return temp
def process(self):
""" Accept Start and Goal Puzzle state"""
print("Enter the start state matrix \n")
start = self.accept()
print("Enter the goal state matrix \n")
goal = self.accept()
start = Node(start,0,0)
start.fval = self.f(start,goal)
""" Put the start node in the open list"""
self.open.append(start)
print("\n")
count=0
while True:
cur = self.open[0]
count=count+1
print("This Node number = \n", count)
print("")
print(" | ")
print(" | ")
print(" \\\'/ \n")
for i in cur.data:
for j in i:
print(j,end=" ")
print("")
""" If the difference between current and goal node is 0 we have reached the goal node"""
if(self.h(cur.data,goal) == 0):
break
for i in cur.generate_child():
i.fval = self.f(i,goal)
self.open.append(i)
self.closed.append(cur)
del self.open[0]
""" sort the opne list based on f value """
self.open.sort(key = lambda x:x.fval,reverse=False)
puz = Puzzle(3)
puz.process()
this code take initial state and goal state and start traversal and stop until
specific or reached goal state
i want to add limit of traversal to this code.so it itterate in
specific boundary either reached goal state or not
Whenever k = 2, the code runs in a loop
if k > 2 it sets all, but one of the centroids location to 0,0
I've reviewed it a couple of times , and it doesn't seem like there are any errors probably some sort of logic flaw. The code starts by having a class and its methods which initiate the centroids, calculate the Euclidean distance, and reassign centroids to the average positions of the points that are in the cluster. It then runs a loop that consists of reassigning and calculating distance until a list of the assignments are equal and then plots it.
class Kmeans:
def __init__(self, K, dataset, centroids, sorting):
self.K = K
self.dataset = dataset
self.centroids = centroids
self.sorting = sorting
#sets starting position of centroids
def initializeCentroids(self):
bigX = 0
bigY = 0
self.centroids = []
for i in self.dataset:
if i[0] > bigX:
bigX = i[0]
if i[1] > bigY:
bigY = i[1]
for q in range(self.K):
self.centroids.append([random.randint(0, bigX), random.randint(0, bigY)])
plt.scatter((self.centroids[0][0], self.centroids[1][0]), (self.centroids[0][1], self.centroids[1][1]))
return self.centroids
#calculates euclidean distance
def calcDistance(self):
self.sorting = []
for w in self.dataset:
print(w)
distances = []
counter = 0
for centr in self.centroids:
distances.append(math.sqrt(abs((centr[0] - w[0] * centr[0] - w[0]) + (centr[1] - w[1] * centr[1] - w[1]))))
counter += 1
if counter > 0:
try:
if distances[0] > distances[1]:
distances.pop(0)
if distances[1] > distances[0]:
distances.pop(1)
counter -= 1
except IndexError:
pass
self.sorting.append([w, counter, distances[0]])
return self.sorting
def reassignCentroids(self):
counter3 = 1
for r in range(len(self.centroids)):
positionsX = []
positionsY = []
for t in self.sorting:
if t[1] == counter3:
positionsX.append(t[0][0])
positionsY.append(t[0][1])
population = len(positionsY)
if population == 0:
population = 1
self.centroids.append([sum(positionsX) / population, sum(positionsY) / population])
counter3 += 1
self.centroids.pop(0)
return
k = 4
dataSetSize = input("Enter the amount of tuples you want generated: ")
data_set = []
for o in range(int(dataSetSize)):
data_set.append((random.randint(0, 1000), random.randint(0, 1000)))
attempt = Kmeans(k, data_set, 0, 0)
attempt.initializeCentroids()
xvals = []
yvals = []
sortCompare = []
# plots
for p in data_set:
xvals.append(p[0])
yvals.append(p[1])
running = True
while running:
if len(sortCompare) > 1:
centroidChoice0 = []
centroidChoice1 = []
for p in sortCompare[0]:
centroidChoice0.append(p[1])
for d in sortCompare[1]:
centroidChoice1.append(d[1])
print(centroidChoice1)
print(attempt.centroids)
if centroidChoice1 == centroidChoice0:
running = False
for m in attempt.centroids:
plt.scatter((attempt.centroids[0][0], attempt.centroids[1][0]), (attempt.centroids[0][1], attempt.centroids[1][1]))
running = False
sortCompare.pop(0)
attempt.calcDistance()
sortCompare.append(attempt.sorting)
attempt.reassignCentroids()
I just create benchmark to compare speed of two implementation of Quick sort.
Iterative and recursion.
I expected than recursive will be slower, but I got that plot (blue is rec):
It's possible that recursion is faster? Maybe I just do some mistake in my code?
Just in case I pase my code.
import time
import random
import sys
arrayList = []
arr = [random.randint(1,15000) for _ in range(1000)]
numbersList = [100000, 300000, 500000, 900000, 1000000, 1500000]
numbersForBenchmark = []
for i in range(len(numbersList)):
arr = [random.randint(1,15000) for _ in range(numbersList[i])]
numbersForBenchmark.append(arr)
print(numbersForBenchmark)
recursionTimeArray = []
iterationTimeArray = []
arrRe = arr
arrIt = arr
def partition(lst, start, end):
pos = start
for i in range(start, end):
if lst[i] < lst[end]: # in your version it always goes from 0
lst[i],lst[pos] = lst[pos],lst[i]
pos += 1
lst[pos],lst[end] = lst[end],lst[pos] # you forgot to put the pivot
# back in its place
return pos
def quick_sort_recursive(lst, start, end):
if start < end: # this is enough to end recursion
pos = partition(lst, start, end)
quick_sort_recursive(lst, start, pos - 1)
quick_sort_recursive(lst, pos + 1, end)
#print(lst)
def iter(arr,l,h):
i = ( l - 1 )
x = arr[h]
for j in range(l , h):
if arr[j] <= x:
# increment index of smaller element
i = i+1
arr[i],arr[j] = arr[j],arr[i]
arr[i+1],arr[h] = arr[h],arr[i+1]
return (i+1)
def quickSortIterative(arr,l,h):
size = h - l + 1
stack = [0] * (size)
top = -1
top = top + 1
stack[top] = l
top = top + 1
stack[top] = h
while top >= 0:
# Pop h and l
h = stack[top]
top = top - 1
l = stack[top]
top = top - 1
p = iter( arr, l, h )
if p-1 > l:
top = top + 1
stack[top] = l
top = top + 1
stack[top] = p - 1
if p+1 < h:
top = top + 1
stack[top] = p + 1
top = top + 1
stack[top] = h
for i in range(len(numbersForBenchmark)):
arrRe = numbersForBenchmark[i][:]
arrIt = numbersForBenchmark[i][:]
n = len(arrIt)
start = time.time()
quickSortIterative(arrIt, 0, n-1)
end = time.time()
ITime = end - start
iterationTimeArray.append(ITime)
try:
n = len(arrRe)
start = time.time()
quick_sort_recursive(arrRe,0,n-1)
end = time.time()
rekTime = end - start
recursionTimeArray.append(rekTime)
except RecursionError as re:
print('Sorry but this maze solver was not able to finish '
'analyzing the maze: {}'.format(re.args[0]))
print("REK time", recursionTimeArray)
print("ITER TIME", iterationTimeArray)
# evenly sampled time at 200ms intervals
import matplotlib.pyplot as plt
plt.plot([10,100,500,1000,5000,8000 ], recursionTimeArray,[10,100,500,1000,5000,8000], iterationTimeArray)
plt.show()
The plots look OK, but I expected a completely different result. Hence my doubts about the results.
I have to implement the Floyd Algorithm in Python.
I have to use this template of code.
The adjacency Matrix is given in the exercise however I have to convert it into the bimatrix as seen below. Then lastly the Floyd algorithm Needs to be executed onto the bimatrix.
My main Problem right now consists of converting the adjacency Matrix into the bimatrix, which contains the distance as well as the previous Knot.
import math
import pprint as pp
def createBiMatrix(mat):
bimatrix = []
pass
return bimatrix
def updateForNode(bimat, node):
pass;
return bimat
def determinePath(bimat, start, end):
recursionStep(bimat, start, end)
print(end)
def recursionStep(bimat, start, end):
pass
return
if __name__ == "__main__":
matsize = 5
mat = [matsize * [math.inf] for i in range(matsize)]
mat[0][0] = 0
mat[0][1] = 2
mat[0][2] = 10
mat[1][1] = 0
mat[1][2] = 3
mat[1][3] = 12
mat[2][0] = 10
mat[2][1] = 3
mat[2][2] = 0
mat[2][4] = 1
mat[3][1] = 12
mat[3][3] = 0
mat[4][2] = 1
mat[4][3] = 6
mat[4][4] = 0
bim = createBiMatrix(mat)
pp.pprint(bim)
for i in range(matsize):
bim = updateForNode(bim, i)
print("Step " + str(i) + ":")
pp.pprint(bim)
start = 0
end = 3
print("shortest path (" + str(start) + " nach " + str(end) + "):")
determinePath(bim, start, end)
Assuming, that the bimatrix is meant to be a list that on index i stores shortest path to vertex i and the preceding vertex
createBiMatrix(mat):
bimatrix = [(math.inf, None) for _ in range(len(mat))]
for from in range(len(mat)):
for to in range(len(mat[0])):
if mat[from][to] < bimatrix[to][0]:
bimatrix[to] = (mat[from][to], from)
return bimatrix
I am trying to understand how to implement a genetic algorithm and wrote a simple string guess. I am having trouble understanding why this solution is not working.
I believe that my problem is in my populating my new generations? The newest generations do not seem to have improved fitness values. I am also not sure if I am doing the crossover and mutation rates correctly. Any help would be really appreciated!
POP_SIZE = 300;
CROSSOVER_RATE = 0.7;
MUTATION_RATE = 0.01
GENESET = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"
target = "Hello World"
RAND_NUM = random.random()
def generateBasePopulation(population_size):
population = dict()
for _ in range(POP_SIZE):
gene = generateParent(len(target))
population[gene] = 0
return population
def generateNewPopulation(population, population_size):
newPopulation = dict()
while(len(newPopulation) <= POP_SIZE):
child_one, child_two = crossover(child_one, child_two)
child_one = mutate(child_one)
child_two = mutate(child_two)
newPopulation[child] = 0
newPopulation[child_two] = 0
return newPopulation
def assignFitness(population):
for x in population:
population[x] = getFitness(x)
def generateParent(length):
genes = list("")
for i in range(0,length):
random_gene = random.choice(GENESET)
genes.append(random_gene)
return(''.join(genes))
def getFitness(candidate):
fitness = 0
for i in range(0, len(candidate) - 1):
if target[i] == candidate[i]:
fitness += 1
return(fitness)
def mutate(parent):
gene_index_to_mutate = random.randint(0, len(parent) - 1)
mutation_value = random.choice(GENESET)
genes = list(parent)
genes[gene_index_to_mutate] = mutation_value
return(''.join(genes))
def crossover(parentA, parentB):
if(RAND_NUM < CROSSOVER_RATE):
random_index = random.randint(0, len(target))
parentASlice = parentA[:random_index]
parentBSlice = parentB[random_index:]
return (parentASlice + parentBSlice), (parentBSlice + parentASlice)
return parentA, parentB
def chooseChild(population):
fitnessSum = sum(population.values())
pick = random.uniform(0, fitnessSum)
current = 0
for pop in population:
current += population[pop]
if current >= pick:
return pop
def main():
population = generateBasePopulation(POP_SIZE)
targetNotFound = True
while(targetNotFound):
assignFitness(population)
if target in population:
print("target found!")
targetNotFound = False
if(targetNotFound):
tempPopulation = generateNewPopulation(population, POP_SIZE)
population.clear()
population = tempPopulation
There are some issues with the generateNewPopulation function.
child_one and child_two are referenced before assignment
You need two individuals from the population to perform the crossover. There are several selection algorithms, but just to give an idea you could start with a form of tournament selection:
def extractFromPopulation(population):
best = random.choice(list(population.keys()))
for _ in range(4):
gene = random.choice(list(population.keys()))
if population[gene] > population[best]:
best = gene
return best
Here the selection pressure (range(4)) is fixed. It's one of the parameters you've to tune in a real case.
Now we have:
def generateNewPopulation(population, population_size):
newPopulation = dict()
while len(newPopulation) <= POP_SIZE:
child_one = extractFromPopulation(population)
child_two = extractFromPopulation(population)
# ...
The code still doesn't work because
new individuals aren't inserted in newPopulation
Just indent the two lines:
newPopulation[child] = 0
newPopulation[child_two] = 0
(they must be part of the while loop)
The revised generateNewPopulation function follows:
def generateNewPopulation(population, population_size):
newPopulation = dict()
while len(newPopulation) <= POP_SIZE:
child_one = extractFromPopulation(population)
child_two = extractFromPopulation(population)
child_one, child_two = crossover(child_one, child_two)
child_one = mutate(child_one)
child_two = mutate(child_two)
newPopulation[child_one] = 0
newPopulation[child_two] = 0
return newPopulation
The crossover function cannot be based on a fixed RAND_NUM value
Delete the RAND_NUM = random.random() assignment and change the crossover function to use a new random value at each call:
def crossover(parentA, parentB):
if random.random() < CROSSOVER_RATE:
random_index = random.randint(0, len(target))
parentASlice = parentA[:random_index]
parentBSlice = parentB[random_index:]
return (parentASlice + parentBSlice), (parentBSlice + parentASlice)
return parentA, parentB
Also the code doesn't correctly perform single point crossover since schemata of the second parent aren't preserved.
You could change many details to improve performance but, as a starting example, it's probably enough as it is (...it works).
Average number of generations to find a solution is about 158 (average on 200 runs).
EDIT (thanks to alexis for the comment)
MUTATION_RATE is unused and a mutation always happens. The mutate function should be something like:
def mutate(parent):
if random.random() < MUTATION_RATE:
gene_index_to_mutate = random.randint(0, len(parent) - 1)
mutation_value = random.choice(GENESET)
genes = list(parent)
genes[gene_index_to_mutate] = mutation_value
return ''.join(genes)
return parent
This fix is particularly important if you keep the roulette wheel selection algorithm (chooseChild often doesn't converge without the fix).