tsp_local.py 7.2 KB

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