tsp_local.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  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(int((city1[2]) - int(city2[2])) ** 2 + (int(city1[3]) - int(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 = int(randint(1, len(cities) - 2))
  70. j = i
  71. while (j == i):
  72. #j = randbelow(len(cities) - 3) + 1
  73. j = int(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 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, count):
  153. if (opt == 1):
  154. return temp - 15
  155. if (opt == 2):
  156. return temp - (math.log(temp) + 2)
  157. else:
  158. return 500000 * math.exp(-0.0005 * count) - 1
  159. # returns the hill climbing solution using simulated annealing w/ schedule opt
  160. def beck_weathers(cities, opt):
  161. start = time.process_time()
  162. temp = 500000
  163. count = 1
  164. curcost = length(cities)
  165. curpath = copy.deepcopy(cities)
  166. # keep trying things til we can no longer try things
  167. while(temp > 0):
  168. child = random_child(curpath)
  169. delta = curcost - child[1]
  170. if (delta > 0 or bad_weather(delta, temp)):
  171. curcost = child[1]
  172. curpath = child[0]
  173. temp = colder(temp, opt, count)
  174. count += 1
  175. end = time.process_time()
  176. return(curpath, curcost, end - start)
  177. # solves the inputted TSP problem (filepath), using algorithm algo
  178. # 1 = hill climbing, 2 = tabu/sideways allowed, 3 = random restarts, 4 = annealing
  179. # x represents the optional parameters for 3 and 4
  180. # specifically, the # of restarts and the annealing schedule, respectively
  181. def solver(problem, algo, x=None):
  182. # import map
  183. prob = problem
  184. with open(prob) as file:
  185. prob = file.read().splitlines()
  186. global n
  187. cities = prob[1:]
  188. counter = 0
  189. # convert to list of list of ints
  190. # original format is: letter, coord, coord
  191. # output format is: iD#, letter, coord, coord
  192. for l in cities:
  193. temp = l.split()
  194. temp[1] = int(temp[1])
  195. temp[2] = int(temp[2])
  196. temp.insert(0, counter)
  197. counter += 1
  198. cities[cities.index(l)] = temp
  199. # add in goal state
  200. cities.append(cities[0])
  201. input = rand_order(cities)
  202. if (algo == 1):
  203. result = sir_edmund_hillary(cities)
  204. elif (algo == 2):
  205. result = tenzing_norgay(cities)
  206. elif (algo == 3):
  207. result = reinhold_messner(cities, x)
  208. else:
  209. result = beck_weathers(cities, x)
  210. return result
  211. # function to generate PDF results for basic hill climbing
  212. def sir_edmund_hillary_graph():
  213. plt.ioff()
  214. plt.switch_backend('agg')
  215. solution_steps = []
  216. solution_scores = []
  217. good_sol_counts = []
  218. for i in range(14, 17):
  219. steps = []
  220. solution_steps.append(steps)
  221. scores = []
  222. solution_scores.append(scores)
  223. good_sols = []
  224. good_sol_counts.append(good_sols)
  225. for j in range(1, 11):
  226. filepath = "tsp_problems/" + str(i) + "/instance_" + str(j) + ".txt"
  227. prob_steps = 0
  228. prob_scores = 0
  229. good_solutions = 0
  230. # run problem 100 times
  231. for k in range(0, 100):
  232. # path, cost, steps returned
  233. result = solver(filepath, 1)
  234. prob_steps += result[2]
  235. prob_scores += (result[1] / neos[i][j - 1])
  236. if (result[1] <= neos[i][j- 1]):
  237. good_solutions += 1
  238. steps.append(prob_steps / 100.0)
  239. scores.append(prob_scores / 100.0)
  240. good_sols.append(good_solutions)
  241. with PdfPages("hill_climbing.pdf") as pdf:
  242. figure, axes = plt.subplots(3, 1, True)
  243. figure.suptitle("Hill Climbing")
  244. for i in range(0, 3):
  245. axes[i].plot(range(1, 11), solution_steps[i], label = "Steps to Solution", color = 'b')
  246. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  247. axes[i].set_ylabel("Steps Taken", color = 'b')
  248. axis_twin = axes[i].twinx()
  249. axis_twin.plot(range(1, 11), solution_scores[i], label = "Solution Quality", color = 'r')
  250. axis_twin.set_ylabel("Solution Quality", color = 'r')
  251. #axes[i].legend()
  252. #axis_twin.legend()
  253. plt.tight_layout()
  254. plt.subplots_adjust(top=0.92)
  255. pdf.savefig()
  256. plt.close()
  257. figure, axes = plt.subplots(3, 1, True)
  258. figure.suptitle("Hill Climbing Cont.")
  259. for i in range(0, 3):
  260. axes[i].plot(range(1, 11), good_sol_counts[i], color = 'b')
  261. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  262. axes[i].set_ylabel("% of runs <= NEOS", color = 'b')
  263. plt.tight_layout()
  264. plt.subplots_adjust(top=0.92)
  265. pdf.savefig()
  266. plt.close()
  267. # now we can aggregate and replace the old values
  268. for i in range(0, 3):
  269. tempsteps = 0
  270. tempqual = 0
  271. tempgood = 0
  272. for j in range(0, 10):
  273. tempsteps += solution_steps[i][j]
  274. tempqual += solution_scores[i][j]
  275. tempgood += good_sol_counts[i][j]
  276. solution_steps[i] = tempsteps / 10.0
  277. solution_scores[i] = tempqual / 10.0
  278. good_sol_counts[i] = tempgood / 10.0
  279. figure, axes = plt.subplots(3, 1, True)
  280. figure.suptitle("Hill Climbing Aggegated (Averages)")
  281. axes[0].plot(range(14, 17), solution_steps, color = 'b')
  282. axes[0].set_xlabel("Problem Instances w/ x cities")
  283. axes[0].set_ylabel("Steps Used", color = 'b')
  284. axes[0].set_xticks([14, 15, 16])
  285. axes[1].plot(range(14, 17), solution_scores, color = 'b')
  286. axes[1].set_xlabel("Problem Instances w/ x cities")
  287. axes[1].set_ylabel("Solution Quality", color = 'b')
  288. axes[1].set_xticks([14, 15, 16])
  289. axes[2].plot(range(14, 17), good_sol_counts, color = 'b')
  290. axes[2].set_xlabel("Problem Instances w/ x cities")
  291. axes[2].set_ylabel("% of runs <= NEOS", color = 'b')
  292. axes[2].set_xticks([14, 15, 16])
  293. plt.tight_layout()
  294. plt.subplots_adjust(top=0.92)
  295. pdf.savefig()
  296. plt.close()
  297. # function to generate PDF results for basic hill climbing w/ tabu and sideways moves
  298. def tenzing_norgay_graph():
  299. plt.ioff()
  300. plt.switch_backend('agg')
  301. solution_steps = []
  302. solution_scores = []
  303. good_sol_counts = []
  304. for i in range(14, 17):
  305. steps = []
  306. solution_steps.append(steps)
  307. scores = []
  308. solution_scores.append(scores)
  309. good_sols = []
  310. good_sol_counts.append(good_sols)
  311. for j in range(1, 11):
  312. filepath = "tsp_problems/" + str(i) + "/instance_" + str(j) + ".txt"
  313. prob_steps = 0
  314. prob_scores = 0
  315. good_solutions = 0
  316. # run problem 100 times
  317. for k in range(0, 100):
  318. # path, cost, steps returned
  319. result = solver(filepath, 2)
  320. prob_steps += result[2]
  321. prob_scores += (result[1] / neos[i][j - 1])
  322. if (result[1] <= neos[i][j- 1]):
  323. good_solutions += 1
  324. steps.append(prob_steps / 100.0)
  325. scores.append(prob_scores / 100.0)
  326. good_sols.append(good_solutions)
  327. with PdfPages("hill_climbing_tabu.pdf") as pdf:
  328. figure, axes = plt.subplots(3, 1, True)
  329. figure.suptitle("Hill Climbing - Tabu")
  330. for i in range(0, 3):
  331. axes[i].plot(range(1, 11), solution_steps[i], label = "Steps to Solution", color = 'b')
  332. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  333. axes[i].set_ylabel("Steps Taken", color = 'b')
  334. axis_twin = axes[i].twinx()
  335. axis_twin.plot(range(1, 11), solution_scores[i], label = "Solution Quality", color = 'r')
  336. axis_twin.set_ylabel("Solution Quality", color = 'r')
  337. #axes[i].legend()
  338. #axis_twin.legend()
  339. plt.tight_layout()
  340. plt.subplots_adjust(top=0.92)
  341. pdf.savefig()
  342. plt.close()
  343. figure, axes = plt.subplots(3, 1, True)
  344. figure.suptitle("Hill Climbing - Tabu Cont.")
  345. for i in range(0, 3):
  346. axes[i].plot(range(1, 11), good_sol_counts[i], color = 'b')
  347. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  348. axes[i].set_ylabel("% of runs <= NEOS", color = 'b')
  349. plt.tight_layout()
  350. plt.subplots_adjust(top=0.92)
  351. pdf.savefig()
  352. plt.close()
  353. # now we can aggregate and replace the old values
  354. for i in range(0, 3):
  355. tempsteps = 0
  356. tempqual = 0
  357. tempgood = 0
  358. for j in range(0, 10):
  359. tempsteps += solution_steps[i][j]
  360. tempqual += solution_scores[i][j]
  361. tempgood += good_sol_counts[i][j]
  362. solution_steps[i] = tempsteps / 10.0
  363. solution_scores[i] = tempqual / 10.0
  364. good_sol_counts[i] = tempgood / 10.0
  365. figure, axes = plt.subplots(3, 1, True)
  366. figure.suptitle("Hill Climbing - Tabu Aggegated (Averages)")
  367. axes[0].plot(range(14, 17), solution_steps, color = 'b')
  368. axes[0].set_xlabel("Problem Instances w/ x cities")
  369. axes[0].set_ylabel("Steps Used", color = 'b')
  370. axes[0].set_xticks([14, 15, 16])
  371. axes[1].plot(range(14, 17), solution_scores, color = 'b')
  372. axes[1].set_xlabel("Problem Instances w/ x cities")
  373. axes[1].set_ylabel("Solution Quality", color = 'b')
  374. axes[1].set_xticks([14, 15, 16])
  375. axes[2].plot(range(14, 17), good_sol_counts, color = 'b')
  376. axes[2].set_xlabel("Problem Instances w/ x cities")
  377. axes[2].set_ylabel("% of runs <= NEOS", color = 'b')
  378. axes[2].set_xticks([14, 15, 16])
  379. plt.tight_layout()
  380. plt.subplots_adjust(top=0.92)
  381. pdf.savefig()
  382. plt.close()
  383. # function to generate PDF results for basic hill climbing w/ random restarts
  384. def reinhold_messner_graph():
  385. plt.ioff()
  386. plt.switch_backend('agg')
  387. solution_times = []
  388. solution_scores = []
  389. # num of cities
  390. for i in range(14, 17):
  391. times = []
  392. solution_times.append(times)
  393. scores = []
  394. solution_scores.append(scores)
  395. # num of instances
  396. for j in range(1, 3):
  397. filepath = "tsp_problems/" + str(i) + "/instance_" + str(j) + ".txt"
  398. prob_times = []
  399. times.append(prob_times)
  400. prob_scores = []
  401. scores.append(prob_scores)
  402. # num of repeats
  403. for k in range(1, 16):
  404. ktimes = []
  405. prob_times.append(ktimes)
  406. kscores = []
  407. prob_scores.append(kscores)
  408. runtime = 0
  409. qual = 0
  410. # run problem 100 times
  411. for num in range(0, 100):
  412. # path, cost, runtime returned
  413. result = solver(filepath, 3, k)
  414. runtime += result[2]
  415. qual += (result[1] / neos[i][j - 1])
  416. ktimes.append(runtime / 100.0)
  417. kscores.append(qual / 100.0)
  418. with PdfPages("hill_climbing_restarts.pdf") as pdf:
  419. figure, axes = plt.subplots(3, 2, True)
  420. figure.suptitle("Hill Climbing - Restarts")
  421. for i in range(0, 3):
  422. for j in range(0, 2):
  423. axes[i][j].plot(range(1, 16), solution_times[i][j], color = 'b')
  424. axes[i][j].set_xlabel("Problem Instance " + str(j + 1) + " w/ " + str(i + 14) + " cities")
  425. axes[i][j].set_ylabel("Time Taken (s)", color = 'b')
  426. axis_twin = axes[i][j].twinx()
  427. axis_twin.plot(range(1, 16), solution_scores[i][j], color = 'r')
  428. axis_twin.set_ylabel("Solution Quality", color = 'r')
  429. #axes[i].legend()
  430. #axis_twin.legend()
  431. plt.tight_layout()
  432. plt.subplots_adjust(top=0.92)
  433. pdf.savefig()
  434. plt.close()
  435. # function to generate PDF results for basic hill climbing w/ annealing
  436. def beck_weathers_graph():
  437. plt.ioff()
  438. plt.switch_backend('agg')
  439. solution_times = []
  440. solution_scores = []
  441. # num of cities
  442. for i in range(14, 17):
  443. times = []
  444. solution_times.append(times)
  445. scores = []
  446. solution_scores.append(scores)
  447. # num of instances
  448. for j in range(1, 3):
  449. filepath = "tsp_problems/" + str(i) + "/instance_" + str(j) + ".txt"
  450. prob_times = []
  451. times.append(prob_times)
  452. prob_scores = []
  453. scores.append(prob_scores)
  454. # diff schedules
  455. for k in range(1, 4):
  456. ktimes = []
  457. prob_times.append(ktimes)
  458. kscores = []
  459. prob_scores.append(kscores)
  460. runtime = 0
  461. qual = 0
  462. # run problem 100 times
  463. for num in range(0, 100):
  464. # path, cost, runtime returned
  465. result = solver(filepath, 4, k)
  466. runtime += result[2]
  467. qual += (result[1] / neos[i][j - 1])
  468. ktimes.append(runtime / 100.0)
  469. kscores.append(qual / 100.0)
  470. with PdfPages("hill_climbing_annealing.pdf") as pdf:
  471. figure, axes = plt.subplots(3, 2, True)
  472. figure.suptitle("Hill Climbing - Annealing")
  473. for i in range(0, 3):
  474. for j in range(0, 2):
  475. axes[i][j].plot(range(1, 4), solution_times[i][j], color = 'b')
  476. axes[i][j].set_title("Problem Instance " + str(j + 1) + " w/ " + str(i + 14) + " cities")
  477. axes[i][j].set_xlabel("Annealing Schedule")
  478. axes[i][j].set_ylabel("Time Taken (s)", color = 'b')
  479. axis_twin = axes[i][j].twinx()
  480. axis_twin.plot(range(1, 4), solution_scores[i][j], color = 'r')
  481. axis_twin.set_ylabel("Solution Quality", color = 'r')
  482. #axes[i].legend()
  483. #axis_twin.legend()
  484. plt.tight_layout()
  485. plt.subplots_adjust(top=0.85)
  486. pdf.savefig()
  487. plt.close()
  488. # main function that will call needed graphing function(s)
  489. def main():
  490. #sir_edmund_hillary_graph()
  491. #tenzing_norgay_graph()
  492. #reinhold_messner_graph()
  493. beck_weathers_graph()
  494. main()