I am trying to write a search algorithm that takes in a start point and then return the path to the end point, I originally tried just doing it via some nested for loops and a list of lists so that I could just loop through and try to find a path but the RAM requirements convinced me to try it using a class-based system. However, all it is doing is taking like 2gb of RAM and 100% of one of my CPU cores and just sitting there without exiting. If anyone sees a problem in my code, any help would be greatly appreciated.
import csv
import math
from multiprocessing import Process
from rplidar import RPLidar
import heapq
lidar = RPLidar('/dev/ttyUSB0')
file = "lidar01.csv"
def calc_offset():
# take in argos ros data and calculate offset
x_offset = 0
y_offset = 0
return x_offset, y_offset
def find_fix_quad_convert(x, y):
offset_x, offset_y = calc_offset()
if x >= 0 and y >= 0:
x = abs(x + 12000 + offset_x)
y = abs(y + offset_y)
return x,y
elif x < 0 and y >= 0:
x = abs(x - 12000 + offset_x)
y = abs(y + offset_x)
return x,y
elif x < 0 and y < 0:
x = abs(x - 12000 + offset_x)
y = abs(y - 12000 + offset_y)
return x,y
elif x >= 0 and y < 0:
x = abs(x + 12000 + offset_x)
y = abs(y - 12000 + offset_y)
return x,y
def scan():
try:
for scan in enumerate(lidar.iter_scans()):
list_version_data = list(scan)
for data in list_version_data:
if isinstance(data, list):
for indiv_data_points in data:
if isinstance(indiv_data_points, tuple):
list_indiv_data_points = list(indiv_data_points)
list_indiv_data_points.pop(0)
angle = list_indiv_data_points[0]
distance = list_indiv_data_points[1]
length = distance
angle = angle
angle = math.radians(angle)
x,y = (length * math.cos(angle)), (length * math.sin(angle))
x = int(x)
y = int(y)
new_x,new_y = find_fix_quad_convert(x,y)
with open(file=file, mode="w") as f:
writer = csv.writer(f)
writer.writerow([new_x,new_y])
except Exception as e:
print(e)
pass
def eliminate_duplicates():
unique_coords = set()
with open(file, 'r') as f:
reader = csv.reader(f)
for row in reader:
coord = (row[0], row[1])
if coord not in unique_coords:
unique_coords.add(coord)
with open(file, 'w') as f:
writer = csv.writer(f)
for coord in unique_coords:
writer.writerow(coord)
# create the node class that takes in the individual data points and creates a node for the nav graph
class Node:
def __init__(self, x, y):
self.x = x
self.y = y
self.neighbors = []
self.parent = None
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return self.f < other.f
def scan_eliminate_duplicates():
scan_process = Process(target=scan)
eliminate_duplicates_process = Process(target=eliminate_duplicates)
scan_process.start()
scan_process.join()
eliminate_duplicates_process.start()
eliminate_duplicates_process.join()
def find_path(start, end, nodes):
open_set = []
closed_set = set()
start.f = 0
heapq.heappush(open_set, start)
while open_set:
current_node = heapq.heappop(open_set)
closed_set.add(current_node)
if current_node == end:
print(f"Path found: {0}".format(construct_path(current_node)))
return construct_path(current_node)
for neighbor in current_node.neighbors:
if neighbor in closed_set:
continue
tentative_g = current_node.f + 1
if neighbor not in open_set or tentative_g < neighbor.f:
neighbor.parent = current_node
neighbor.f = tentative_g
if neighbor not in open_set:
heapq.heappush(open_set, neighbor)
return None
def construct_path(node):
path = []
while node.parent:
path.append((node.x, node.y))
node = node.parent
return path[::-1]
if __name__ == "__main__":
scan_elim_dupl_process = Process(target=scan_eliminate_duplicates)
nodes = []
with open(file, "r") as f:
reader = csv.reader(f)
for row in reader:
node = Node(int(float(row[0])), int(float(row[1])))
nodes.append(node)
# set start and end nodes
start = Node(3201, 3201)
end = Node(23000, 23000)
# connect the nodes to their neighbors
for i, node in enumerate(nodes):
for j in range(i+1, len(nodes)):
if abs(node.x - nodes[j].x) <= 1 and abs(node.y - nodes[j].y) <= 1:
node.neighbors.append(nodes[j])
nodes[j].neighbors.append(node)
find_path_process = Process(target=find_path, args=(start, end, nodes))
scan_elim_dupl_process.start(), find_path_process.start()
scan_elim_dupl_process.join(), find_path_process.join()
CSV Data(example):
-224.45409129769087,-75.30553365940557
-225.4021550412925,-75.62361405501024
-221.37533513849013,-86.0186665341958
-222.02088232366805,-83.6318737815909
-219.05825287406182,-90.4570718504838
-216.1406631194247,-97.22249609167298
-212.35203834877927,-105.80252506022047
-210.74781416150145,-110.5864314739799
-209.03673236351906,-114.8298503124623
-207.00083783790242,-118.46055518359869
-202.61438759451943,-126.76123200607452
-200.80257079121006,-132.35776351858277
-198.46526749871208,-137.60010027854142
-200.72914689131528,-136.5114357417897
-198.8372035969446,-141.42053056663028
-195.46212772818174,-148.12872484421098
-192.555826974252,-155.49438413737627
-191.2678199044531,-159.4290471306835
-204.80806340541443,-686.6046221546457
-189.9329560617947,-692.9413555284663
-174.4435476087335,-698.0327346891975
-157.25903052807882,-703.8971230352976
-142.50543063768973,-710.3467126965301
-44.080364168658264,-424.9699801100761
-12.039081185863548,-420.3276228422303
151.3137816891034,-880.5943387683925
171.58805621421078,-880.1807209116937
192.6920068673774,-879.3860659513674
213.97191813826333,-877.540073585379
235.7914668768005,-874.2611933215878
257.2898049769299,-872.088022366397
279.8247876333225,-870.8993344388122
301.1827105821156,-869.3037299163104
323.32023991345227,-866.9207489513143
344.60980320968105,-865.141980273634
368.38864460533046,-862.6319067399766
390.5808612705762,-860.7811297357389
413.666591519788,-858.81634448839
437.78734499029486,-856.893985896942
462.98529035913396,-854.9354849408629
488.6789701052747,-851.7727773161384
513.1091380975454,-851.3254444692665
540.2315442531086,-849.5265982262719
566.5849433716348,-845.456423740787
593.946149447507,-843.3387347658589
620.2144841174974,-841.046368335817
649.1761458917417,-837.0071051700481
678.6559458023329,-834.341870714362
709.32872426918,-831.894418437014
739.9610013434269,-829.0562580373138
772.0166065569867,-826.5297086011094
807.4258588219725,-823.4373352026249
841.6994505337709,-821.2904693049519
878.8426320460152,-818.1908032961703
917.0058399786907,-814.8716782076648
953.9215320868914,-809.3421468211068
989.2825428144441,-801.5520026689394
1089.7385480638236,-803.6080492775999
1124.2452992304018,-789.0340659048534
1161.1577259536114,-774.4874420920189
1207.7231504414601,-765.7054210907444
1256.6619459378417,-758.3474244247931
1312.4869985934681,-749.6733478810021
1436.2817842205613,-736.2680465130896
560.4785752204706,-119.13339883857641
561.947341484636,-105.41939052351769
562.7845996626268,-91.97041308256136
562.0728347190211,-80.08864445677706
572.2556983202762,-67.73092528507895
3073.007418570301,775.0941671254502
3076.1322580025953,851.280443670507
3085.7160627583366,932.367218447319
3079.5934798899584,1010.8439845590693
3065.409617566463,1086.4593591253329
3049.6010101414113,1162.0524639380467
553.4062949898384,280.6451899919539
517.2886535413827,292.48164287240894
504.22866463801756,302.9711475658394
493.01596196440715,311.63411839577225
457.35133669092596,320.98491760053656
448.3587658812583,330.0681438088732
438.5893339206918,341.3172405124065
430.36674574745746,351.23313362315815
396.83008722312223,357.699516877629
385.27345578604894,365.4112262460958
375.7639852425002,372.7022805064037
366.43229041712107,379.4517446786384
355.57191272554525,387.6140024311522
346.70803875198897,395.5623033919553
332.8988762943624,398.49579754616076
315.8504082369599,398.19163993804005
300.8542343626438,400.6091981795568
289.23868520741775,402.93545758531144
277.5650855793172,406.2285357620096
272.3911822392343,417.2003191972799
262.73233641927243,427.16971966616535
253.20792971902577,432.01424377837924
249.1985319999312,447.5490522267691
292.7465904906362,640.521269167627
272.64249172224527,636.02678536952
804.8967316661586,3209.614034639233
724.5030467972489,3205.9040686959897
646.5556113779995,3209.2685932928116
567.8035492364211,3204.8395735160475
486.7038419116777,3179.46404607261
409.31126573218995,3155.564654343928
335.12423534291634,3147.2077698947405
111.3757146106589,3140.2755472561585
39.18188674771358,3123.254237130063
-35.079705269662,3137.303884592341
-108.12103095017433,1135.8656798522752
-135.12589586812848,1133.7257405412631
-478.8748400704411,2350.463879758102
-523.4289972333165,2298.6579421165134
-321.0672961603582,1006.7950902920995
-337.5896468300176,906.691502305599
-362.5912398939693,906.686187582095
-386.4405220459153,909.0180336059789
-348.11121502543114,476.6693240324135
-709.9009007847953,689.6707718650517
-705.7154352397234,654.50131738936
-225.61815673209847,73.37581245076781
-224.96174273211477,60.673011355377774
-221.7058096465867,57.33702092846706
-218.36099776953716,54.2537985130275
-217.26139804313215,45.62329361569259
-215.60048241436377,36.98640943229841
-212.9460118930461,31.409529108961046
-210.41143597187929,27.876336062182475
-209.29301055390292,20.037420824145237
-207.148062758457,18.806384432377264
-205.20651537090765,10.97889107922301
-203.40844501247898,6.103646254929753
-201.49568420532205,1.3188050004214316
-199.7214487838248,-3.3771875414384667
-198.08854835941534,-7.9993442768494845
-196.3529499548831,-12.493259943472601
-194.511648279817,-16.964114579569504
-192.81795218045707,-21.3831082150133
-191.27242817254086,-25.765446260062987
-189.63616139426645,-30.0354172877243
-188.37840041163838,-34.43585716012369
-226.49932324600476,-68.91022470651103
-226.1737175842578,-73.28377701863081
-224.04801115257553,-79.54590623391874
-221.56247177423467,-85.53549614804056
-217.15764842108885,-95.55165216898537
-215.7143937962088,-98.164659165782
-213.46548271945335,-102.96352600484683
-212.697138624531,-105.10703935006946
-210.6167482193979,-110.29799576368899
-205.56454558388867,-120.93579949249667
-203.34370277416045,-126.06308358156993
-202.659741276799,-128.5652723935235
-198.05436632376652,-137.315213942561
-200.01801379745294,-136.66695524713327
-195.89271630106168,-144.21028465470746
-196.10659851005252,-147.2744530487792
-191.887743885396,-155.12787063120754
-190.4663185563427,-159.99732496386864
-240.31744343455787,-628.0953561212486
-194.36681058896912,-689.8928217060037
-179.58107052974322,-695.954166312259
-162.78643452157806,-701.6128841717147
-148.54493769413048,-708.3420529556655
-48.61733572180791,-427.74595809582235
-36.52211770286281,-421.42037791081987
-14.701911453818868,-417.7413719032436
121.73474418061696,-881.8875861238097
142.58207691392846,-880.0242049755851
164.10647050731015,-880.0804672515084
185.3187865324572,-878.9254859532392
207.6388003465223,-877.5187696514856
227.2478526229215,-875.7459540176426
247.99909241264257,-872.9562991138248
269.3479160261434,-870.7950103970358
292.78503356447436,-868.7390210072583
313.92451252961064,-866.9114144092499
335.83498334245536,-864.2959137143787
358.6456719461373,-861.8558649764493
381.566664921735,-860.1673559374968
403.5993305416195,-860.0044071900775
427.212685906662,-858.0277215803786
451.6489095368339,-855.0451815630497
476.82947920642863,-854.1701881122556
501.87089118531856,-853.0713165268506
528.0321382457586,-850.695964184392
554.4110689448593,-846.6240999590187
579.1151539748655,-843.6031062867585
606.096392136026,-841.4369634379586
635.4714561227303,-839.0067213397381
664.7834755172479,-837.1522744872694
695.0210066681005,-833.8201546437099
724.3369310372825,-830.5058761595194
755.2483075753681,-828.7349132289179
790.0536662312429,-825.3008266532705
823.3915496973328,-822.0453794572566
857.6177770538111,-819.1994558599753
896.6246516997196,-816.2233052078068
933.9759389836386,-814.1314054866272
965.93739908313,-802.0256797961757
1106.8124778852348,-795.8748025270977
1145.3782516272215,-784.4403885569442
1185.8751583397407,-772.8779715017727
1231.3279001110159,-763.8275999256613
1283.8274490578733,-756.2321608114493
1339.6622193807364,-748.3283958021902
1399.0838013856292,-739.5071106489357
559.0470439861833,-113.7394065850111
556.9664328067189,-99.30486003493867
558.3537490936984,-86.40654415616522
565.1640657918545,-74.09169142097093
576.7394965367929,-61.412972037564295
3083.2240320377678,726.8903842841579
3075.974377735485,801.7687883765465
3077.959314383161,878.9581668099685
3081.5656872937416,958.6946737068328
3079.603817010731,1036.9621884393555
3065.787463056901,1112.0695993790728
3041.893912737369,1184.8382478850592
565.3389250472349,272.8771927560765
548.9161042252287,281.1968581300259
512.1181201590852,295.48535243684216
500.3702034741492,306.4395894385034
489.0168090088083,315.1120800712703
453.6658726040589,323.5967220699212
445.1879662404556,334.3325249129779
436.9264391821313,344.2518689036028
404.1328350154679,352.8581891672072
393.65549849104536,359.33876566238513
382.9718830476642,366.0158456883065
373.6310075466433,373.4272789977725
362.9032487936391,381.7941782099644
354.38182598682874,389.7166713270566
342.20930022858226,397.153443063338
One problem is that this line does not behave like you seem to be expecting:
for scan in enumerate(lidar.iter_scans()):
Looking at the source code, this appears to iterate through scans as they come in. In other words, it's a continual stream of incoming data. You need to update your code to have a non-error exit condition. The README in the source repo has this as an example:
for i, scan in enumerate(lidar.iter_scans()):
print('%d: Got %d measurments' % (i, len(scan)))
if i > 10:
break
Another problem is that you've got multiple processes running, which makes debugging significantly more challenging. I would suggest simplifying your __main__ section to this until you've made sure your find_path method is correct:
if __name__ == "__main__":
nodes = []
with open(file, "r") as f:
reader = csv.reader(f)
for row in reader:
node = Node(int(float(row[0])), int(float(row[1])))
nodes.append(node)
# set start and end nodes
start = Node(3201, 3201)
end = Node(23000, 23000)
# connect the nodes to their neighbors
for i, node in enumerate(nodes):
for j in range(i+1, len(nodes)):
if abs(node.x - nodes[j].x) <= 1 and abs(node.y - nodes[j].y) <= 1:
node.neighbors.append(nodes[j])
nodes[j].neighbors.append(node)
find_path(start, end, nodes)
It would also be helpful for readability if you moved most of this into a separate read_nodes method.
I'm trying to learn search algorithms in order to prepare my master thesis, so I have a TSP problem in which I want to find the best and minimal route to visite all the states , I'm using a .txt file named cities__coordinates.txt that contains the coordinates for every state, so to read the data I've found this source code that has a class to read the data you'll find it here:
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import copy
import re
import math
class Data():
'''
the format of solomon dataset
'''
def __init__(self):
self.customerNum = 0 # the number of customers
self.nodeNum = 0 # the sum of customers and depots
self.vehicleNum = 0
self.capacity = 0
self.cor_X = []
self.cor_Y = []
self.demand = []
self.readyTime = []
self.dueTime = []
self.serviceTime = []
self.disMatrix = {}
def read_data(self, path, customerNum, depotNum):
'''
function to read solomom data from .txt files, notice that it must be solomon dataset
INPUT
# data : class Data
# path : Data path
# customerNum : the number of customer
OutPut : none
'''
self.customerNum = customerNum
self.nodeNum = customerNum + depotNum
f = open('cities__coordinates.txt', 'r')
lines = f.readlines()
count = 0
for line in lines:
count = count + 1
if(count == 5):
line = line[:-1].strip()
str = re.split(r" +", line)
self.vehicleNum = float(str[0])
self.capacity = float(str[1])
elif(count >= 10 and count <= 10 + customerNum):
line = line[:-1]
str = re.split(r" +", line)
self.cor_X.append(float(str[2]))
self.cor_Y.append(float(str[3]))
self.demand.append(float(str[4]))
self.readyTime.append(float(str[5]))
self.dueTime.append(float(str[6]))
self.serviceTime.append(float(str[7]))
# compute the distance matrix
self.disMatrix = {}
for i in range(0, self.nodeNum):
dis_temp={}
for j in range(0, self.nodeNum):
dis_temp[j] = int(math.hypot(self.cor_X[i] - self.cor_X[j],self.cor_Y[i] - self.cor_Y[j]))
self.disMatrix[i] = dis_temp
def plot_nodes(self):
'''
Description: function to plot
'''
Graph = nx.DiGraph()
nodes_name = [str(x) for x in list(range(self.nodeNum))]
Graph.add_nodes_from(nodes_name)
cor_xy = np.array([self.cor_X,self.cor_Y]).T.astype(int)
pos_location = {nodes_name[i]:x for i,x in enumerate(cor_xy)}
nodes_color_dict = ['r'] + ['gray'] * (self.nodeNum-1)
nx.draw_networkx(Graph,pos_location,node_size=200,node_color=nodes_color_dict,labels=None)
plt.show(Graph)
def plot_route(self,route,color='k'):
Graph = nx.DiGraph()
nodes_name = [0]
cor_xy=[[self.cor_X[0] , self.cor_Y[0]]]
edge = []
edges = [[0,route[0]]]
for i in route :
nodes_name.append(i)
cor_xy.append([self.cor_X[i] , self.cor_Y[i]])
edge.append(i)
if len(edge) == 2 :
edges.append(copy.deepcopy(edge))
edge.pop(0)
edges.append([route[-1],0])
Graph.add_nodes_from(nodes_name)
Graph.add_edges_from(edges)
pos_location = {nodes_name[i]:x for i,x in enumerate(cor_xy)}
nodes_color_dict = ['r'] + ['gray'] * (len(route))
nx.draw_networkx(Graph,pos_location,node_size=200,node_color=nodes_color_dict,edge_color=color, labels=None)
plt.show(Graph)
so in read_data function I've changed the path to my .txt file , and for the code which will calculate all the distance and took the tabu search and all the staffs, here it is the code:
from itertools import combinations
import os,sys,copy
import numpy as np
import time
from Datareader import Data
import matplotlib.pyplot as plt
class Tabu():
def __init__(self,disMatrix,max_iters=200,maxTabuSize=20):
"""parameters definition"""
self.disMatrix = disMatrix
self.maxTabuSize = maxTabuSize
self.max_iters = max_iters
self.tabu_list=[]
def get_route_distance(self,route):
'''
Description: function to calculate total distance of a route. evaluate function.
parameters: route : list
return : total distance : folat
'''
routes = [0] + route + [0] # add the start and end point
total_distance = 0
for i,n in enumerate(routes):
if i != 0 :
total_distance = total_distance + self.disMatrix[last_pos][n]
last_pos = n
return total_distance
def exchange(self,s1,s2,arr):
"""
function to Swap positions of two elements in an arr
Args: int,int,list
s1 : target 1
s2 : target 2
arr : target array
Ouput: list
current_list : target array
"""
current_list = copy.deepcopy(arr)
index1 , index2 = current_list.index(s1) , current_list.index(s2) # get index
current_list[index1], current_list[index2]= arr[index2] , arr[index1]
return current_list
def generate_initial_solution(self,num=10,mode='greedy'):
"""
function to get the initial solution,there two different way to generate route_init.
Args:
num : int
the number of points
mode : string
"greedy" : advance step by choosing optimal one
"random" : randomly generate a series number
Ouput: list
s_init : initial solution route_init
"""
if mode == 'greedy':
route_init=[0]
for i in range(num):
best_distance = 10000000
for j in range(num+1):
if self.disMatrix[i][j] < best_distance and j not in route_init:
best_distance = self.disMatrix[i][j]
best_candidate = j
route_init.append(best_candidate)
route_init.remove(0)
if mode == 'random':
route_init = np.arange(1,num+1) #init solution from 1 to num
np.random.shuffle(route_init) #shuffle the list randomly
return list(route_init)
def tabu_search(self,s_init):
"""tabu search"""
s_best = s_init
bestCandidate = copy.deepcopy(s_best)
routes , temp_tabu = [] , [] # init
routes.append(s_best)
while(self.max_iters):
self.max_iters -= 1 # Number of iterations
neighbors = copy.deepcopy(s_best)
for s in combinations(neighbors, 2):
sCandidate = self.exchange(s[0],s[1],neighbors) # exchange number to generate candidates
if s not in self.tabu_list and self.get_route_distance(sCandidate) < self.get_route_distance(bestCandidate):
bestCandidate = sCandidate
temp_tabu = s
if self.get_route_distance(bestCandidate) < self.get_route_distance(s_best): # record the best solution
s_best = bestCandidate
if temp_tabu not in self.tabu_list:
self.tabu_list.append(temp_tabu)
if len(self.tabu_list) > self.maxTabuSize :
self.tabu_list.pop(0)
routes.append(bestCandidate)
return s_best, routes
if __name__ == "__main__":
data = Data()
data.read_data(path='cities__coordinates.txt',customerNum=100,depotNum=1) # change the path
""" Tabu :
disMatrix : the distance matrix from 0 to X , 0 represernt starting and stopping point。
for example: disMatrix = [[0,3,4,...
1,0,5,...
3,5,0,...]]
that means the distance from 0 to 0 is 0, from 0 to 1 is 3,... from 1 to 3 is 5....
max_iters : maximum iterations
maxTabuSize : maximum iterations
"""
tsp = Tabu(disMatrix=data.disMatrix,max_iters=10,maxTabuSize=10)
# two different way to generate initial solution
# num : the number of points
s_init = tsp.generate_initial_solution(num=10,mode='greedy') # mode = "greedy" or "random"
print('init route : ' , s_init)
print('init distance : ' , tsp.get_route_distance(s_init))
start = time.time()
best_route , routes = tsp.tabu_search(s_init) # tabu search
end = time.time()
print('best route : ' , best_route)
print('best best_distance : ' , tsp.get_route_distance(best_route))
print('the time cost : ',end - start )
# plot the result changes with iterations
results=[]
for i in routes:
results.append(tsp.get_route_distance(i))
plt.plot(np.arange(len(results)) , results)
plt.show()
# plot the route
data.plot_route(best_route)
when I execute it, it takes a little time and then it shows me this error :
Traceback (most recent call last):
File "C:/Users/malle/OneDrive/Desktop/TS.py", line 100, in <module>
data.read_data(path='cities__coordinates.txt',customerNum=100,depotNum=1) # change the path
File "C:/Users/malle/OneDrive/Desktop\Datareader.py", line 49, in read_data
self.cor_X.append(float(str[2]))
IndexError: list index out of range
anyone can help to resolve this problem please ?
My code dies after about 140+ iterations, and I don't know why. I guess memory leak is a possibility, but I couldn't find it. I also found out that changing some arithmetic constants can prolong the time until the crash.
I have a genetic algorithm that tries to find best (i.e. minimal steps) route from point A (src) to point B (dst).
I create a list of random chromosomes, where each chromosome has:
src + dst [always the same]
list of directions (random)
I then run the algorithm:
find best route and draw it (for visualization purposes)
Given a probability P - replace the chromosomes with cross-overs (i.e. pick 2, and take the "end" of one's directions, and replace the "end" of the second's)
Given probability Q - mutate (replace the next direction with a random direction)
This all goes well, and most of the times I do find a route (usually not the ideal one), but sometimes, when it searches for a long time (say, about 140+ iterations) it just crushes. No warning. No error.
How can I prevent that (a simple iteration limit can work, but I do want the algorithm to run for a long time [~2000+ iterations])?
I think the relevant parts of the code are:
update function inside GUI class
which calls to cross_over
When playing with the update_fitness() score values (changing score -= (weight+1)*2000*(shift_x + shift_y) to score -= (weight+1)*2*(shift_x + shift_y) it runs for a longer time. Could be some kind of an arithmetic overflow?
import tkinter as tk
from enum import Enum
from random import randint, sample
from copy import deepcopy
from time import sleep
from itertools import product
debug_flag = False
class Direction(Enum):
Up = 0
Down = 1
Left = 2
Right = 3
def __str__(self):
return str(self.name)
def __repr__(self):
return str(self.name)[0]
# A chromosome is a list of directions that should lead the way from src to dst.
# Each step in the chromosome is a direction (up, down, right ,left)
# The chromosome also keeps track of its route
class Chromosome:
def __init__(self, src = None, dst = None, length = 10, directions = None):
self.MAX_SCORE = 1000000
self.route = [src]
if not directions:
self.directions = [Direction(randint(0,3)) for i in range(length)]
else:
self.directions = directions
self.src = src
self.dst = dst
self.fitness = self.MAX_SCORE
def __str__(self):
return str(self.fitness)
def __repr__(self):
return self.__str__()
def set_src(self, pixel):
self.src = pixel
def set_dst(self, pixel):
self.dst = pixel
def set_directions(self, ls):
self.directions = ls
def update_fitness(self):
# Higher score - a better fitness
score = self.MAX_SCORE - len(self.route)
score += 4000*(len(set(self.route)) - len(self.route)) # penalize returning to the same cell
score += (self.dst in self.route) * 500 # bonus routes that get to dst
for weight,cell in enumerate(self.route):
shift_x = abs(cell[0] - self.dst[0])
shift_y = abs(cell[1] - self.dst[1])
score -= (weight+1)*2000*(shift_x + shift_y) # penalize any wrong turn
self.fitness = max(score, 0)
def update(self, mutate_chance = 0.9):
# mutate #
self.mutate(chance = mutate_chance)
# move according to direction
last_cell = self.route[-1]
try:
direction = self.directions[len(self.route) - 1]
except IndexError:
print('No more directions. Halting')
return
if direction == Direction.Down:
x_shift, y_shift = 0, 1
elif direction == Direction.Up:
x_shift, y_shift = 0, -1
elif direction == Direction.Left:
x_shift, y_shift = -1, 0
elif direction == Direction.Right:
x_shift, y_shift = 1, 0
new_cell = last_cell[0] + x_shift, last_cell[1] + y_shift
self.route.append(new_cell)
self.update_fitness()
def cross_over(p1, p2, loc = None):
# find the cross_over point
if not loc:
loc = randint(0,len(p1.directions))
# choose one of the parents randomly
x = randint(0,1)
src_parent = (p1, p2)[x]
dst_parent = (p1, p2)[1 - x]
son = deepcopy(src_parent)
son.directions[loc:] = deepcopy(dst_parent.directions[loc:])
return son
def mutate(self, chance = 1):
if 100*chance > randint(0,99):
self.directions[len(self.route) - 1] = Direction(randint(0,3))
class GUI:
def __init__(self, rows = 10, cols = 10, iteration_timer = 100, chromosomes = [], cross_over_chance = 0.5, mutation_chance = 0.3, MAX_ITER = 100):
self.rows = rows
self.cols = cols
self.canv_w = 800
self.canv_h = 800
self.cell_w = self.canv_w // cols
self.cell_h = self.canv_h // rows
self.master = tk.Tk()
self.canvas = tk.Canvas(self.master, width = self.canv_w, height = self.canv_h)
self.canvas.pack()
self.rect_dict = {}
self.iteration_timer = iteration_timer
self.iterations = 0
self.MAX_ITER = MAX_ITER
self.chromosome_list = chromosomes
self.src = chromosomes[0].src # all chromosomes share src + dst
self.dst = chromosomes[0].dst
self.prev_best_route = []
self.cross_over_chance = cross_over_chance
self.mutation_chance = mutation_chance
self.no_obstacles = True
# init grid #
for r in range(rows):
for c in range(cols):
self.rect_dict[(r, c)] = self.canvas.create_rectangle(r *self.cell_h, c *self.cell_w,
(1+r)*self.cell_h, (1+c)*self.cell_w,
fill="gray")
# init grid #
# draw src + dst #
self.color_src_dst()
# draw src + dst #
# after + mainloop #
self.master.after(iteration_timer, self.start_gui)
tk.mainloop()
# after + mainloop #
def start_gui(self):
self.start_msg = self.canvas.create_text(self.canv_w // 2,3*self.canv_h // 4, fill = "black", font = "Times 25 bold underline",
text="Starting new computation.\nPopulation size = %d\nCross-over chance = %.2f\nMutation chance = %.2f" %
(len(self.chromosome_list), self.cross_over_chance, self.mutation_chance))
self.master.after(2000, self.update)
def end_gui(self, msg="Bye Bye!"):
self.master.wm_attributes('-alpha', 0.9) # transparency
self.canvas.create_text(self.canv_w // 2,3*self.canv_h // 4, fill = "black", font = "Times 25 bold underline", text=msg)
cell_ls = []
for idx,cell in enumerate(self.prev_best_route):
if cell in cell_ls:
continue
cell_ls.append(cell)
self.canvas.create_text(cell[0]*self.cell_w, cell[1]*self.cell_h, fill = "purple", font = "Times 16 bold italic", text=str(idx+1))
self.master.after(3000, self.master.destroy)
def color_src_dst(self):
r_src = self.rect_dict[self.src]
r_dst = self.rect_dict[self.dst]
c_src = 'blue'
c_dst = 'red'
self.canvas.itemconfig(r_src, fill=c_src)
self.canvas.itemconfig(r_dst, fill=c_dst)
def color_route(self, route, color):
for cell in route:
try:
self.canvas.itemconfig(self.rect_dict[cell], fill=color)
except KeyError:
# out of bounds -> ignore
continue
# keep the src + dst
self.color_src_dst()
# keep the src + dst
def compute_shortest_route(self):
if self.no_obstacles:
return (1 +
abs(self.chromosome_list[0].dst[0] - self.chromosome_list[0].src[0]) +
abs(self.chromosome_list[0].dst[1] - self.chromosome_list[0].src[1]))
else:
return 0
def create_weighted_chromosome_list(self):
ls = []
for ch in self.chromosome_list:
tmp = [ch] * (ch.fitness // 200000)
ls.extend(tmp)
return ls
def cross_over(self):
new_chromosome_ls = []
weighted_ls = self.create_weighted_chromosome_list()
while len(new_chromosome_ls) < len(self.chromosome_list):
try:
p1, p2 = sample(weighted_ls, 2)
son = Chromosome.cross_over(p1, p2)
if son in new_chromosome_ls:
continue
else:
new_chromosome_ls.append(son)
except ValueError:
continue
return new_chromosome_ls
def end_successfully(self):
self.end_gui(msg="Got to destination in %d iterations!\nBest route length = %d" % (len(self.prev_best_route), self.compute_shortest_route()))
def update(self):
# first time #
self.canvas.delete(self.start_msg)
# first time #
# end #
if self.iterations >= self.MAX_ITER:
self.end_gui()
return
# end #
# clean the previously best chromosome route #
self.color_route(self.prev_best_route[1:], 'gray')
# clean the previously best chromosome route #
# cross over #
if 100*self.cross_over_chance > randint(0,99):
self.chromosome_list = self.cross_over()
# cross over #
# update (includes mutations) all chromosomes #
for ch in self.chromosome_list:
ch.update(mutate_chance=self.mutation_chance)
# update (includes mutations) all chromosomes #
# show all chromsome fitness values #
if debug_flag:
fit_ls = [ch.fitness for ch in self.chromosome_list]
print(self.iterations, sum(fit_ls) / len(fit_ls), fit_ls)
# show all chromsome fitness values #
# find and display best chromosome #
best_ch = max(self.chromosome_list, key=lambda ch : ch.fitness)
self.prev_best_route = deepcopy(best_ch.route)
self.color_route(self.prev_best_route[1:], 'gold')
# find and display best chromosome #
# check if got to dst #
if best_ch.dst == best_ch.route[-1]:
self.end_successfully()
return
# check if got to dst #
# after + update iterations #
self.master.after(self.iteration_timer, self.update)
self.iterations += 1
# after + update iterations #
def main():
iter_timer, ITER = 10, 350
r,c = 20,20
s,d = (13,11), (7,8)
population_size = [80,160]
cross_over_chance = [0.2,0.4,0.5]
for pop_size, CO_chance in product(population_size, cross_over_chance):
M_chance = 0.7 - CO_chance
ch_ls = [Chromosome(src=s, dst=d, directions=[Direction(randint(0,3)) for i in range(ITER)]) for i in range(pop_size)]
g = GUI(rows=r, cols=c, chromosomes = ch_ls, iteration_timer=iter_timer,
cross_over_chance=CO_chance, mutation_chance=M_chance, MAX_ITER=ITER-1)
del(ch_ls)
del(g)
if __name__ == "__main__":
main()
I do not know if you know the Python Profiling tool of Visual Studio, but it is quite useful in cases as yours (though I usually program with editors, like VS Code).
I have run your program and, as you said, it sometimes crashes. I have analyzed the code with the profiling tool and it seems that the problem is the function cross_over, specifically the random function:
I would strongly suggest reviewing your cross_over and mutation functions. The random function should not be called so many times (2 millions).
I have previously programmed Genetic Algorithms and, to me, it seems that your program is falling into a local minimum. What is suggested in these cases is playing with the percentage of mutation. Try to increase it a little bit so that you could get out of the local minimum.