tsp_local.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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. neos14 = [318, 324, 336, 319, 351, 311, 272, 361, 274, 322]
  8. neos15 = [313, 318, 281, 324, 378, 291, 348, 342, 353, 325]
  9. neos16 = [404, 353, 361, 349, 358, 344, 373, 355, 243, 330]
  10. neos36 = 464
  11. # returns a shuffled version of the cities list while maintaining the first & last elements
  12. def rand_order(cities):
  13. # shitty variable name
  14. suburbs = cities[1:-1]
  15. shuffle(suburbs)
  16. suburbs.insert(0, cities[0])
  17. suburbs.append(cities[-1])
  18. return suburbs
  19. # returns distance between 2 vertices
  20. def dist(city1, city2):
  21. return math.sqrt((city1[2] - city2[2]) ** 2 + (city1[3] - city2[3]) ** 2)
  22. # returns total cost/distance of a tour (list of cities)
  23. def length(cities):
  24. sum = 0
  25. for i in range(len(cities) - 1):
  26. sum += dist(cities[i], cities[i+1])
  27. return sum
  28. # returns the best ordered child state of a state (hill climbing, basic) & its cost
  29. def best_child(cities, cost):
  30. bestorder = cities
  31. bestcost = cost
  32. for i in range(1, len(cities) - 2):
  33. for j in range(i, len(cities) - 1):
  34. temporder = copy.deepcopy(cities)
  35. tempcity = temporder[i]
  36. temporder[i] = temporder[j]
  37. temporder[j] = tempcity
  38. templen = length(temporder)
  39. if (templen < cost):
  40. bestorder = temporder
  41. bestcost = templen
  42. return (bestorder, bestcost)
  43. # returns the best child using tabu
  44. # returns the best ordered child state of a state (hill climbing, basic) & its cost
  45. def bester_child(cities, cost, tabu):
  46. bestorder = cities
  47. bestcost = cost
  48. for i in range(1, len(cities) - 2):
  49. for j in range(i, len(cities) - 1):
  50. temporder = copy.deepcopy(cities)
  51. tempcity = temporder[i]
  52. temporder[i] = temporder[j]
  53. temporder[j] = tempcity
  54. if (get_hashable(temporder) in tabu):
  55. continue
  56. templen = length(temporder)
  57. if (templen < cost):
  58. bestorder = temporder
  59. bestcost = templen
  60. return (bestorder, bestcost)
  61. # returns a random child for simulated annealing search
  62. # returns the ordering and the cost of it
  63. def random_child(cities):
  64. #i = randbelow(len(cities) - 3) + 1
  65. i = randint(1, len(cities) - 2)
  66. j = i
  67. while (j == i):
  68. #j = randbelow(len(cities) - 3) + 1
  69. j = randint(1, len(cities) - 2)
  70. temporder = copy.deepcopy(cities)
  71. tempcity = temporder[i]
  72. temporder[i] = temporder[j]
  73. temporder[j] = tempcity
  74. return(temporder, length(temporder))
  75. # returns the basic hill climbing solution for a given input
  76. def sir_edmund_hillary(cities):
  77. curcost = length(cities)
  78. curpath = copy.deepcopy(cities)
  79. steps = 0
  80. # keep trying things til we can no longer try things
  81. while(1):
  82. steps += 1
  83. child = best_child(curpath, curcost)
  84. if (child[1] < curcost):
  85. curcost = child[1]
  86. curpath = child[0]
  87. continue
  88. break
  89. return(curpath, curcost, steps)
  90. # retuns a tuple of the indices (ordered) in a list of cities
  91. def get_hashable(cities):
  92. newlist = []
  93. for i in range(1, len(cities) - 1):
  94. newlist.append(cities[i][1])
  95. return tuple(newlist)
  96. # returns the hill climbing solution w/tabu & sideways moves for a given input
  97. def tenzing_norgay(cities):
  98. curcost = length(cities)
  99. curpath = copy.deepcopy(cities)
  100. tabu = {get_hashable(cities)}
  101. laterals = 0
  102. # keep trying things til we can no longer try things
  103. while(1):
  104. child = bester_child(curpath, curcost, tabu)
  105. if (child[1] < curcost):
  106. curcost = child[1]
  107. curpath = child[0]
  108. tabu.add(get_hashable(curpath))
  109. continue
  110. if (child[1] == curcost and laterals < n):
  111. laterals += 1
  112. curcost = child[1]
  113. curpath = child[0]
  114. tabu.add(get_hashable(curpath))
  115. continue
  116. break
  117. return(curpath, curcost)
  118. # returns the hill climbing solution w/ x random restarts for a given input
  119. # if you want x restarts, call with x + 1
  120. def reinhold_messner(cities, x):
  121. curcost = length(cities)
  122. curpath = copy.deepcopy(cities)
  123. bestcost = length(cities)
  124. bestpath = copy.deepcopy(cities)
  125. # keep trying things til we can no longer try things
  126. while(x):
  127. child = best_child(curpath, curcost)
  128. if (child[1] < curcost):
  129. curcost = child[1]
  130. curpath = child[0]
  131. continue
  132. else:
  133. if (curcost < bestcost):
  134. bestcost = curcost
  135. bestpath = copy.deepcopy(curpath)
  136. curpath = rand_order(cities)
  137. curcost = length(curpath)
  138. x -= 1
  139. return(bestpath, bestcost)
  140. # returns true or false randomly, based on the distribution e^(delta / temp)
  141. def bad_weather(delta, temp):
  142. return random.SystemRandom().random() < math.exp(delta / temp)
  143. # decrements temp by the option specified by opt: 1 is linear, 2 is logarithmic, 3 is exponential
  144. def colder(temp, opt):
  145. if (opt == 1):
  146. return temp - 25
  147. if (opt == 2):
  148. return temp - (math.log(temp) + 2)
  149. else:
  150. return temp - math.exp(-0.001 * temp)
  151. # returns the hill climbing solution using simulated annealing w/ schedule opt
  152. def beck_weathers(cities, opt):
  153. temp = 1000
  154. curcost = length(cities)
  155. curpath = copy.deepcopy(cities)
  156. # keep trying things til we can no longer try things
  157. while(temp > 0):
  158. child = random_child(curpath)
  159. delta = curcost - child[1]
  160. if (delta > 0 or bad_weather(delta, temp)):
  161. curcost = child[1]
  162. curpath = child[0]
  163. temp = colder(temp, opt)
  164. return(curpath, curcost)
  165. # solves the inputted TSP problem (filepath), using algorithm algo
  166. # 1 = hill climbing, 2 = tabu/sideways allowed, 3 = random restarts, 4 = annealing
  167. # x represents the optional parameters for 3 and 4
  168. # specifically, the # of restarts and the annealing schedule, respectively
  169. def solver(problem, algo, *x):
  170. # import map
  171. prob = problem
  172. with open(prob) as file:
  173. prob = file.read().splitlines()
  174. global n
  175. cities = prob[1:]
  176. counter = 0
  177. # convert to list of list of ints
  178. # original format is: letter, coord, coord
  179. # output format is: iD#, letter, coord, coord
  180. for l in cities:
  181. temp = l.split()
  182. temp[1] = int(temp[1])
  183. temp[2] = int(temp[2])
  184. temp.insert(0, counter)
  185. counter += 1
  186. cities[cities.index(l)] = temp
  187. # add in goal state
  188. cities.append(cities[0])
  189. input = rand_order(cities)
  190. if (algo == 1):
  191. result = sir_edmund_hillary(cities)
  192. elif (algo == 2):
  193. result = tenzing_norgay(cities)
  194. elif (algo == 3):
  195. result = reinhold_messner(cities, x)
  196. else:
  197. result = beck_weathers(cities, x)
  198. return result
  199. # main function to get aggregate data and graph stuff
  200. def main():
  201. plt.ioff()
  202. plt.switch_backend('agg')
  203. solution_steps = []
  204. solution_scores = []
  205. for i in range(14, 17):
  206. steps = []
  207. solution_steps.append(steps)
  208. scores = []
  209. solution_scores.append(scores)
  210. for j in range(1, 11):
  211. filepath = "tsp_problems/" + str(i) + "/instance_" + str(j) + ".txt"
  212. prob_steps = 0
  213. prob_scores = 0
  214. # run problem 100 times
  215. for k in range(0, 100):
  216. # path, cost, steps returned
  217. result = solver(filepath, 1)
  218. prob_steps += result[2]
  219. prob_scores += result[1]
  220. steps.append(prob_steps / 100.0)
  221. scores.append(prob_scores / 100.0)
  222. figure, axes = plt.subplots(3, 1, True)
  223. for i in range(0, 3):
  224. axes[i].plot(range(1, 11), solution_steps[i], label = "Steps to Solution", color = 'b')
  225. axes[i].set_xlabel("Problem Instance w/ " + str(i + 14) + " cities")
  226. axes[i].set_ylabel("Steps Taken", color = 'b')
  227. axis_twin = axes[i].twinx()
  228. axis_twin.plot(range(1, 11), solution_scores[i], label = "Solution Cost/Distance", color = 'r')
  229. axis_twin.set_ylabel("Distance in units", color = 'r')
  230. #axes[i].legend()
  231. #axis_twin.legend()
  232. plt.savefig("hill_climbing.pdf")
  233. main()