tsp_local.py 8.5 KB

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