Coverage for python/lsst/sims/featureScheduler/utils/tsp.py : 10%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
2import scipy.spatial as spatial
3import itertools
4from collections import deque
6# Solve Traveling Salesperson using convex hulls.
7# re-write of https://github.com/jameskrysiak/ConvexSalesman/blob/master/convex_salesman.py
8# This like a good explination too https://www.youtube.com/watch?v=syRSy1MFuho
11def generate_dist_matrix(towns):
12 """Generate the matrix for the distance between town i and j
14 Parameters
15 ----------
16 towns : np.array
17 The x,y positions of the towns
18 """
20 x = towns[:, 0]
21 y = towns[:, 1]
22 # Broadcast to i,j
23 x_dist = x - x[:, np.newaxis]
24 y_dist = y - y[:, np.newaxis]
25 distances = np.sqrt(x_dist**2 + y_dist**2)
26 return distances
29def route_length(town_indx, dist_matrix):
30 """Find the length of a route
32 Parameters
33 ----------
34 town_indx : array of int
35 The indices of the towns.
36 dist_matrix : np.array
37 The matrix where the (i,j) elements are the distance
38 between the ith and jth town
39 """
41 # This closes the path and return to the start
42 town_i = town_indx
43 town_j = np.roll(town_indx, -1)
44 distances = dist_matrix[town_i, town_j]
45 return np.sum(distances)
48def generate_hulls(towns):
49 """Given an array of x,y points, sort them into concentric hulls
51 Parameters
52 ----------
53 towns : np.array (n,2)
54 Array of town x,y positions
56 Returns
57 -------
58 list of lists of the indices of the concentric hulls
59 """
61 # The indices we have to sort
62 all_indices = np.arange(towns.shape[0])
63 # array to note if a town has been used in a hull
64 indices_used = np.zeros(towns.shape[0], dtype=bool)
65 results = []
67 # Continue until every point is inside a convex hull.
68 while False in indices_used:
69 # Try to find the convex hull of the remaining points.
70 try:
71 new_hull = spatial.ConvexHull(towns[all_indices[~indices_used]])
72 new_indices = all_indices[~indices_used][new_hull.vertices]
73 results.append(new_indices.tolist())
74 indices_used[new_indices] = True
76 # In a degenerate case (fewer than three points, points collinear)
77 # Add all of the remaining points to the innermost convex hull.
78 except:
79 results.append(all_indices[~indices_used].tolist())
80 indices_used[~indices_used] = True
81 return results
83 return results
86def merge_hulls(indices_lists, dist_matrix):
87 """Combine the hulls
89 Parameters
90 ----------
91 indices_list : list of lists with ints
92 dist_matric : np.array
93 """
94 # start with the outer hull one. Use deque to rotate fast.
95 collapsed_indices = deque(indices_lists[0])
96 for ind_list in indices_lists[1:]:
97 # insert each point indvidually
98 for indx in ind_list:
99 possible_results = []
100 possible_lengths = []
101 dindex = deque([indx])
102 # In theory, I think this could loop over fewer points. Only need to check
103 # points that can "see" the inner points?
104 for i in range(len(collapsed_indices)):
105 collapsed_indices.rotate(1)
106 possible_results.append(collapsed_indices + dindex)
107 possible_lengths.append(route_length(possible_results[-1], dist_matrix))
108 best = np.min(np.where(possible_lengths == np.min(possible_lengths)))
109 collapsed_indices = possible_results[best]
110 return list(collapsed_indices)
113def three_opt(route, dist_matrix):
114 """Iterates over all possible 3-opt transformations.
116 Parameters
117 ---------
118 route : list
119 The indices of the route
120 dist_matrix : np.array
121 Distance matrix for the towns
123 Returns
124 -------
125 min_route : list
126 The new route
127 min_length : float
128 The length of the new route
130 """
131 # The combinations of three places that we can split each route.
132 combinations = list(itertools.combinations(range(len(route)), 3))
134 min_route = route
135 min_length = route_length(min_route, dist_matrix)
137 for cuts in combinations:
138 # The three chunks that the route is broken into based on the cuts.
139 c1 = route[cuts[0]:cuts[1]]
140 c2 = route[cuts[1]:cuts[2]]
141 c3 = route[cuts[2]:] + route[:cuts[0]]
143 # Reversed chunks 2 and 3.
144 rc2 = c2[::-1]
145 rc3 = c3[::-1]
147 # The unique permutations of all of those chunks.
148 route_perms = [c1+c2+c3, c1+c3+c2, c1+rc2+c3, c1+c3+rc2,
149 c1+c2+rc3, c1+rc3+c2, c1+rc2+rc3, c1+rc3+rc2]
151 # Find the smallest of these permutations.
152 for perm in route_perms:
153 temp_length = route_length(perm, dist_matrix)
154 if temp_length < min_length:
155 min_length = temp_length
156 min_route = perm
158 return min_route, min_length
161def tsp_convex(towns, optimize=False, niter=10):
162 """Find a route through towns
164 Parameters
165 ----------
166 towns : np.array (shape n,2)
167 The points to find a path through
168 optimize : bool (False)
169 Optional to run the 3-opt transformation to optimize route
170 niter : int (10)
171 Max number of iterations to run on optimize loop.
173 Returns
174 -------
175 indices that order towns.
176 """
177 hull_verts = generate_hulls(towns)
178 dist_matrix = generate_dist_matrix(towns)
179 route = merge_hulls(hull_verts, dist_matrix)
180 if optimize:
181 distance = route_length(route, dist_matrix)
182 iter_count = 0
183 optimized = False
184 while not optimized:
185 new_route, new_distance = three_opt(route, dist_matrix)
186 if new_distance < distance:
187 route = new_route
188 distance = new_distance
189 iter_count += 1
190 else:
191 optimized = True
192 if iter_count == niter:
193 return route
194 return route