tsp_local.py 18 KB

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