tsp_local.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. # if you want x restarts, call with x + 1
  118. def reinhold_messner(cities, x):
  119. curcost = length(cities)
  120. curpath = copy.deepcopy(cities)
  121. bestcost = length(cities)
  122. bestpath = copy.deepcopy(cities)
  123. # keep trying things til we can no longer try things
  124. while(x):
  125. child = best_child(curpath, curcost)
  126. if (child[1] < curcost):
  127. curcost = child[1]
  128. curpath = child[0]
  129. continue
  130. else:
  131. if (curcost < bestcost):
  132. bestcost = curcost
  133. bestpath = copy.deepcopy(curpath)
  134. curpath = rand_order(cities)
  135. curcost = length(curpath)
  136. x -= 1
  137. return(bestpath, bestcost)
  138. # returns true or false randomly, based on the distribution e^(delta / temp)
  139. def bad_weather(delta, temp):
  140. return random.SystemRandom().random() < math.exp(delta / temp)
  141. # decrements temp by the option specified by opt: 1 is linear, 2 is logarithmic, 3 is exponential
  142. def colder(temp, opt):
  143. if (opt == 1):
  144. return temp - 25
  145. if (opt == 2):
  146. return temp - (math.log(temp) + 2)
  147. else:
  148. return temp - math.exp(-0.001 * temp)
  149. # returns the hill climbing solution using simulated annealing w/ schedule opt
  150. def beck_weathers(cities, opt):
  151. temp = 1000
  152. curcost = length(cities)
  153. curpath = copy.deepcopy(cities)
  154. # keep trying things til we can no longer try things
  155. while(temp > 0):
  156. child = random_child(curpath)
  157. delta = curcost - child[1]
  158. if (delta > 0 or bad_weather(delta, temp)):
  159. curcost = child[1]
  160. curpath = child[0]
  161. temp = colder(temp, opt)
  162. return(curpath, curcost)
  163. # solves the inputted TSP problem (filepath), using algorithm algo
  164. # 1 = hill climbing, 2 = tabu/sideways allowed, 3 = random restarts, 4 = annealing
  165. # x represents the optional parameters for 3 and 4
  166. # specifically, the # of restarts and the annealing schedule, respectively
  167. def solver(problem, algo, *x):
  168. # import map
  169. prob = problem
  170. with open(prob) as file:
  171. prob = file.read().splitlines()
  172. global n
  173. n = int(prob[0])
  174. cities = prob[1:]
  175. counter = 0
  176. # convert to list of list of ints
  177. # original format is: letter, coord, coord
  178. # output format is: iD#, letter, coord, coord
  179. for l in cities:
  180. temp = l.split()
  181. temp[1] = int(temp[1])
  182. temp[2] = int(temp[2])
  183. temp.insert(0, counter)
  184. counter += 1
  185. cities[cities.index(l)] = temp
  186. # add in goal state
  187. cities.append(cities[0])
  188. input = rand_order(cities)
  189. if (algo == 1):
  190. result = sir_edmund_hillary(cities)
  191. elif (algo == 2):
  192. result = tenzing_norgay(cities)
  193. elif (algo == 3):
  194. result = reinhold_messner(cities, x)
  195. else:
  196. result = beck_weathers(cities, x)
  197. return result
  198. # main function to get aggregate data and graph stuff
  199. def main():
  200. plt.ioff()
  201. plt.switch_backend('agg')
  202. solution_steps = []
  203. solution_scores = []
  204. for i in range(14, 17):
  205. steps = []
  206. solution_steps.append(steps)
  207. scores = []
  208. solution_scores.append(scores)
  209. for j in range(1, 11):
  210. filepath = "tsp_problems/" + str(i) + "/instance_" + str(j) + ".txt"
  211. prob_steps = 0
  212. prob_scores = 0
  213. # run problem 100 times
  214. for k in range(0, 100):
  215. # path, cost, steps returned
  216. result = solver(filepath, 1)
  217. prob_steps += result[2]
  218. prob_scores += result[1]
  219. steps.append(prob_steps / 100.0)
  220. scores.append(prob_scores / 100.0)
  221. figure, axes = plt.subplots(3, 1, True)
  222. for i in range(0, 3):
  223. axes[i].plot(range(1, 11), solution_steps[i], label = "Steps to Solution", color = 'b')
  224. axes[i].set_xlabel("Problem Instance w/ " + str(i) + " cities")
  225. axes[i].set_ylabel("Steps Taken", color = 'b')
  226. axis_twin = axes[i].twinx()
  227. axis_twin.plot(range(1, 11), solution_scores[i], label = "Solution Cost/Distance", color = 'r')
  228. axis_twin.set_ylabel("Distance in units", color = 'r')
  229. #axes[i].legend()
  230. #axis_twin.legend()
  231. plt.savefig("hill_climbing.pdf")
  232. main()