tsp_local.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. import sys
  2. import copy
  3. import math
  4. from heapq import heappush, heappop
  5. from random import shuffle, sample, randint, SystemRandom
  6. import matplotlib.pyplot as plt
  7. from matplotlib.backends.backend_pdf import PdfPages
  8. # globals to contain reference solutions
  9. neos14 = [318, 324, 336, 319, 351, 311, 272, 361, 274, 322]
  10. neos15 = [313, 318, 281, 324, 378, 291, 348, 342, 353, 325]
  11. neos16 = [404, 353, 361, 349, 358, 344, 373, 355, 243, 330]
  12. neos36 = 464
  13. neos = {14: neos14, 15: neos15, 16: neos16, 36: neos36}
  14. # returns a shuffled version of the cities list while maintaining the first & last elements
  15. def rand_order(cities):
  16. # shitty variable name
  17. suburbs = cities[1:-1]
  18. shuffle(suburbs)
  19. suburbs.insert(0, cities[0])
  20. suburbs.append(cities[-1])
  21. return suburbs
  22. # returns distance between 2 vertices
  23. def dist(city1, city2):
  24. return math.sqrt((city1[2] - city2[2]) ** 2 + (city1[3] - city2[3]) ** 2)
  25. # returns total cost/distance of a tour (list of cities)
  26. def length(cities):
  27. sum = 0
  28. for i in range(len(cities) - 1):
  29. sum += dist(cities[i], cities[i+1])
  30. return sum
  31. # returns the best ordered child state of a state (hill climbing, basic) & its cost
  32. def best_child(cities, cost):
  33. bestorder = cities
  34. bestcost = cost
  35. for i in range(1, len(cities) - 2):
  36. for j in range(i, len(cities) - 1):
  37. temporder = copy.deepcopy(cities)
  38. tempcity = temporder[i]
  39. temporder[i] = temporder[j]
  40. temporder[j] = tempcity
  41. templen = length(temporder)
  42. if (templen < cost):
  43. bestorder = temporder
  44. bestcost = templen
  45. return (bestorder, bestcost)
  46. # returns the best child using tabu
  47. # returns the best ordered child state of a state (hill climbing, basic) & its cost
  48. def bester_child(cities, cost, tabu):
  49. bestorder = cities
  50. bestcost = cost
  51. for i in range(1, len(cities) - 2):
  52. for j in range(i, len(cities) - 1):
  53. temporder = copy.deepcopy(cities)
  54. tempcity = temporder[i]
  55. temporder[i] = temporder[j]
  56. temporder[j] = tempcity
  57. if (get_hashable(temporder) in tabu):
  58. continue
  59. templen = length(temporder)
  60. if (templen < cost):
  61. bestorder = temporder
  62. bestcost = templen
  63. return (bestorder, bestcost)
  64. # returns a random child for simulated annealing search
  65. # returns the ordering and the cost of it
  66. def random_child(cities):
  67. #i = randbelow(len(cities) - 3) + 1
  68. i = randint(1, len(cities) - 2)
  69. j = i
  70. while (j == i):
  71. #j = randbelow(len(cities) - 3) + 1
  72. j = randint(1, len(cities) - 2)
  73. temporder = copy.deepcopy(cities)
  74. tempcity = temporder[i]
  75. temporder[i] = temporder[j]
  76. temporder[j] = tempcity
  77. return(temporder, length(temporder))
  78. # returns the basic hill climbing solution for a given input
  79. def sir_edmund_hillary(cities):
  80. curcost = length(cities)
  81. curpath = copy.deepcopy(cities)
  82. steps = 0
  83. # keep trying things til we can no longer try things
  84. while(1):
  85. steps += 1
  86. child = best_child(curpath, curcost)
  87. if (child[1] < curcost):
  88. curcost = child[1]
  89. curpath = child[0]
  90. continue
  91. break
  92. return(curpath, curcost, steps)
  93. # retuns a tuple of the indices (ordered) in a list of cities
  94. def get_hashable(cities):
  95. newlist = []
  96. for i in range(1, len(cities) - 1):
  97. newlist.append(cities[i][1])
  98. return tuple(newlist)
  99. # returns the hill climbing solution w/tabu & sideways moves for a given input
  100. def tenzing_norgay(cities):
  101. curcost = length(cities)
  102. curpath = copy.deepcopy(cities)
  103. tabu = {get_hashable(cities)}
  104. laterals = 0
  105. # keep trying things til we can no longer try things
  106. while(1):
  107. child = bester_child(curpath, curcost, tabu)
  108. if (child[1] < curcost):
  109. curcost = child[1]
  110. curpath = child[0]
  111. tabu.add(get_hashable(curpath))
  112. continue
  113. if (child[1] == curcost and laterals < len(cities)):
  114. laterals += 1
  115. curcost = child[1]
  116. curpath = child[0]
  117. tabu.add(get_hashable(curpath))
  118. continue
  119. break
  120. return(curpath, curcost)
  121. # returns the hill climbing solution w/ x random restarts for a given input
  122. # if you want x restarts, call with x + 1
  123. def reinhold_messner(cities, x):
  124. curcost = length(cities)
  125. curpath = copy.deepcopy(cities)
  126. bestcost = length(cities)
  127. bestpath = copy.deepcopy(cities)
  128. # keep trying things til we can no longer try things
  129. while(x):
  130. child = best_child(curpath, curcost)
  131. if (child[1] < curcost):
  132. curcost = child[1]
  133. curpath = child[0]
  134. continue
  135. else:
  136. if (curcost < bestcost):
  137. bestcost = curcost
  138. bestpath = copy.deepcopy(curpath)
  139. curpath = rand_order(cities)
  140. curcost = length(curpath)
  141. x -= 1
  142. return(bestpath, bestcost)
  143. # returns true or false randomly, based on the distribution e^(delta / temp)
  144. def bad_weather(delta, temp):
  145. return random.SystemRandom().random() < math.exp(delta / temp)
  146. # decrements temp by the option specified by opt: 1 is linear, 2 is logarithmic, 3 is exponential
  147. def colder(temp, opt):
  148. if (opt == 1):
  149. return temp - 25
  150. if (opt == 2):
  151. return temp - (math.log(temp) + 2)
  152. else:
  153. return temp - math.exp(-0.001 * temp)
  154. # returns the hill climbing solution using simulated annealing w/ schedule opt
  155. def beck_weathers(cities, opt):
  156. temp = 1000
  157. curcost = length(cities)
  158. curpath = copy.deepcopy(cities)
  159. # keep trying things til we can no longer try things
  160. while(temp > 0):
  161. child = random_child(curpath)
  162. delta = curcost - child[1]
  163. if (delta > 0 or bad_weather(delta, temp)):
  164. curcost = child[1]
  165. curpath = child[0]
  166. temp = colder(temp, opt)
  167. return(curpath, curcost)
  168. # solves the inputted TSP problem (filepath), using algorithm algo
  169. # 1 = hill climbing, 2 = tabu/sideways allowed, 3 = random restarts, 4 = annealing
  170. # x represents the optional parameters for 3 and 4
  171. # specifically, the # of restarts and the annealing schedule, respectively
  172. def solver(problem, algo, *x):
  173. # import map
  174. prob = problem
  175. with open(prob) as file:
  176. prob = file.read().splitlines()
  177. global n
  178. cities = prob[1:]
  179. counter = 0
  180. # convert to list of list of ints
  181. # original format is: letter, coord, coord
  182. # output format is: iD#, letter, coord, coord
  183. for l in cities:
  184. temp = l.split()
  185. temp[1] = int(temp[1])
  186. temp[2] = int(temp[2])
  187. temp.insert(0, counter)
  188. counter += 1
  189. cities[cities.index(l)] = temp
  190. # add in goal state
  191. cities.append(cities[0])
  192. input = rand_order(cities)
  193. if (algo == 1):
  194. result = sir_edmund_hillary(cities)
  195. elif (algo == 2):
  196. result = tenzing_norgay(cities)
  197. elif (algo == 3):
  198. result = reinhold_messner(cities, x)
  199. else:
  200. result = beck_weathers(cities, x)
  201. return result
  202. # function to generate PDF results for basic hill climbing
  203. def sir_edmund_hillary_graph():
  204. plt.ioff()
  205. plt.switch_backend('agg')
  206. solution_steps = []
  207. solution_scores = []
  208. good_sol_counts = []
  209. for i in range(14, 17):
  210. steps = []
  211. solution_steps.append(steps)
  212. scores = []
  213. solution_scores.append(scores)
  214. good_sols = []
  215. good_sol_counts.append(good_sols)
  216. for j in range(1, 11):
  217. filepath = "tsp_problems/" + str(i) + "/instance_" + str(j) + ".txt"
  218. prob_steps = 0
  219. prob_scores = 0
  220. good_solutions = 0
  221. # run problem 100 times
  222. for k in range(0, 100):
  223. # path, cost, steps returned
  224. result = solver(filepath, 1)
  225. prob_steps += result[2]
  226. prob_scores += (result[1] / neos[i][j - 1])
  227. if (result[1] <= neos[i][j- 1]):
  228. good_solutions += 1
  229. steps.append(prob_steps / 100.0)
  230. scores.append(prob_scores / 100.0)
  231. good_sols.append(good_solutions)
  232. with PdfPages("hill_climbing.pdf") as pdf:
  233. figure, axes = plt.subplots(3, 1, True)
  234. figure.suptitle("Hill Climbing")
  235. for i in range(0, 3):
  236. axes[i].plot(range(1, 11), solution_steps[i], label = "Steps to Solution", color = 'b')
  237. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  238. axes[i].set_ylabel("Steps Taken", color = 'b')
  239. axis_twin = axes[i].twinx()
  240. axis_twin.plot(range(1, 11), solution_scores[i], label = "Solution Quality", color = 'r')
  241. axis_twin.set_ylabel("Solution Quality", color = 'r')
  242. #axes[i].legend()
  243. #axis_twin.legend()
  244. plt.tight_layout()
  245. plt.subplots_adjust(top=0.92)
  246. pdf.savefig()
  247. plt.close()
  248. figure, axes = plt.subplots(3, 1, True)
  249. figure.suptitle("Hill Climbing Cont.")
  250. for i in range(0, 3):
  251. axes[i].plot(range(1, 11), good_sol_counts[i], color = 'b')
  252. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  253. axes[i].set_ylabel("% of runs <= NEOS", color = 'b')
  254. plt.tight_layout()
  255. plt.subplots_adjust(top=0.92)
  256. pdf.savefig()
  257. plt.close()
  258. # now we can aggregate and replace the old values
  259. for i in range(0, 3):
  260. tempsteps = 0
  261. tempqual = 0
  262. tempgood = 0
  263. for j in range(0, 10):
  264. tempsteps += solution_steps[i][j]
  265. tempqual += solution_scores[i][j]
  266. tempgood += good_sol_counts[i][j]
  267. solution_steps[i] = tempsteps / 10.0
  268. solution_scores[i] = tempqual / 10.0
  269. good_sol_counts[i] = tempgood / 10.0
  270. figure, axes = plt.subplots(3, 1, True)
  271. figure.suptitle("Hill Climbing Aggegated (Averages)")
  272. axes[0].plot(range(14, 17), solution_steps, color = 'b')
  273. axes[0].set_xlabel("Problem Instances w/ x cities")
  274. axes[0].set_ylabel("Steps Used", color = 'b')
  275. axes[0].set_xticks([14, 15, 16])
  276. axes[1].plot(range(14, 17), solution_scores, color = 'b')
  277. axes[1].set_xlabel("Problem Instances w/ x cities")
  278. axes[1].set_ylabel("Solution Quality", color = 'b')
  279. axes[1].set_xticks([14, 15, 16])
  280. axes[2].plot(range(14, 17), good_sol_counts, color = 'b')
  281. axes[2].set_xlabel("Problem Instances w/ x cities")
  282. axes[2].set_ylabel("% of runs <= NEOS", color = 'b')
  283. axes[2].set_xticks([14, 15, 16])
  284. plt.tight_layout()
  285. plt.subplots_adjust(top=0.92)
  286. pdf.savefig()
  287. plt.close()
  288. # function to generate PDF results for basic hill climbing
  289. def tenzing_norgay_graph():
  290. plt.ioff()
  291. plt.switch_backend('agg')
  292. solution_steps = []
  293. solution_scores = []
  294. good_sol_counts = []
  295. for i in range(14, 17):
  296. steps = []
  297. solution_steps.append(steps)
  298. scores = []
  299. solution_scores.append(scores)
  300. good_sols = []
  301. good_sol_counts.append(good_sols)
  302. for j in range(1, 11):
  303. filepath = "tsp_problems/" + str(i) + "/instance_" + str(j) + ".txt"
  304. prob_steps = 0
  305. prob_scores = 0
  306. good_solutions = 0
  307. # run problem 100 times
  308. for k in range(0, 100):
  309. # path, cost, steps returned
  310. result = solver(filepath, 1)
  311. prob_steps += result[2]
  312. prob_scores += (result[1] / neos[i][j - 1])
  313. if (result[1] <= neos[i][j- 1]):
  314. good_solutions += 1
  315. steps.append(prob_steps / 100.0)
  316. scores.append(prob_scores / 100.0)
  317. good_sols.append(good_solutions)
  318. with PdfPages("hill_climbing_tabu.pdf") as pdf:
  319. figure, axes = plt.subplots(3, 1, True)
  320. figure.suptitle("Hill Climbing - Tabu")
  321. for i in range(0, 3):
  322. axes[i].plot(range(1, 11), solution_steps[i], label = "Steps to Solution", color = 'b')
  323. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  324. axes[i].set_ylabel("Steps Taken", color = 'b')
  325. axis_twin = axes[i].twinx()
  326. axis_twin.plot(range(1, 11), solution_scores[i], label = "Solution Quality", color = 'r')
  327. axis_twin.set_ylabel("Solution Quality", color = 'r')
  328. #axes[i].legend()
  329. #axis_twin.legend()
  330. plt.tight_layout()
  331. plt.subplots_adjust(top=0.92)
  332. pdf.savefig()
  333. plt.close()
  334. figure, axes = plt.subplots(3, 1, True)
  335. figure.suptitle("Hill Climbing - Tabu Cont.")
  336. for i in range(0, 3):
  337. axes[i].plot(range(1, 11), good_sol_counts[i], color = 'b')
  338. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  339. axes[i].set_ylabel("% of runs <= NEOS", color = 'b')
  340. plt.tight_layout()
  341. plt.subplots_adjust(top=0.92)
  342. pdf.savefig()
  343. plt.close()
  344. # now we can aggregate and replace the old values
  345. for i in range(0, 3):
  346. tempsteps = 0
  347. tempqual = 0
  348. tempgood = 0
  349. for j in range(0, 10):
  350. tempsteps += solution_steps[i][j]
  351. tempqual += solution_scores[i][j]
  352. tempgood += good_sol_counts[i][j]
  353. solution_steps[i] = tempsteps / 10.0
  354. solution_scores[i] = tempqual / 10.0
  355. good_sol_counts[i] = tempgood / 10.0
  356. figure, axes = plt.subplots(3, 1, True)
  357. figure.suptitle("Hill Climbing - Tabu Aggegated (Averages)")
  358. axes[0].plot(range(14, 17), solution_steps, color = 'b')
  359. axes[0].set_xlabel("Problem Instances w/ x cities")
  360. axes[0].set_ylabel("Steps Used", color = 'b')
  361. axes[0].set_xticks([14, 15, 16])
  362. axes[1].plot(range(14, 17), solution_scores, color = 'b')
  363. axes[1].set_xlabel("Problem Instances w/ x cities")
  364. axes[1].set_ylabel("Solution Quality", color = 'b')
  365. axes[1].set_xticks([14, 15, 16])
  366. axes[2].plot(range(14, 17), good_sol_counts, color = 'b')
  367. axes[2].set_xlabel("Problem Instances w/ x cities")
  368. axes[2].set_ylabel("% of runs <= NEOS", color = 'b')
  369. axes[2].set_xticks([14, 15, 16])
  370. plt.tight_layout()
  371. plt.subplots_adjust(top=0.92)
  372. pdf.savefig()
  373. plt.close()
  374. # main function that will call needed graphing function(s)
  375. def main():
  376. #sir_edmund_hillary_graph()
  377. tenzing_norgay_graph()
  378. main()