def sort_quanta(distances): """ Solves the travelling salesman problem, with a magic zero-point, to find the shortest Hamiltonian path through the points. Returns the indices of the points in their sorted order. Call via get_codebook. Args: distances (ndarray): The distance matrix from ``get_distances()``. Returns: ndarray. A 1D array of size (n_colours). """ # Set up the file describing the problem. outf = "/tmp/route.tsp" with open(outf, 'w') as f: f.write(dumps_matrix(distances, name="Route")) # Run the solver. tour = run(outf, start=0, solver="LKH") result = np.array(tour['tour']) # Slice off the initial value and the last value to account for the added # colours. Then subtract one to shift indices back to proper range. return result[1:-1] - 1
def test_tour_LKH(matrix): matrix_sym = atsp_tsp(matrix, strategy="avg") outf = "/tmp/myroute_lkh.tsp" with open(outf, 'w') as dest: dest.write(dumps_matrix(matrix_sym, name="My Route")) tour = run(outf, start=10, solver="lkh") assert tour['solution'] == 102975 assert tour['tour'][0] == 10
matrix = [[0, 2910, 4693], [2903, 0, 5839], [4695, 5745, 0]] from pytsp import atsp_tsp, run, dumps_matrix matrix_sym = atsp_tsp(matrix, strategy="avg") outf = "./tsp_dist.tsp" with open(outf, 'w') as dest: dest.write(dumps_matrix(matrix_sym, name="My Route")) tour = run(outf, start=None, solver="lkh") print("tour", tour)
def lkh_TSP(list_of_kingdom_names, starting_kingdom, adjacency_matrix, conquered_kingdoms_indices, N, dict_kingdom_index_to_name, dict_kingdom_name_to_index, tuples_kingdom_name_to_cost, starting_kingdom_index, dict_kingdom_index_to_cost, adjacency_lists): # Build TSP Distance matrix special_nodes = conquered_kingdoms_indices if starting_kingdom_index not in special_nodes: special_nodes.append(starting_kingdom_index) G = adjacency_matrix_to_graph(adjacency_matrix) shortest_lengths = dict(nx.shortest_path_length(G, weight="weight")) shortest_paths = dict(nx.shortest_path(G, weight="weight")) # print(shortest_lengths) N_TSP = len(special_nodes) dict_TSP_index_to_index = { TSP_index: index for TSP_index, index in enumerate(special_nodes) } dict_index_to_TSP_index = { index: TSP_index for TSP_index, index in enumerate(special_nodes) } distance_matrix = [[0 for _ in range(N_TSP)] for _ in range(N_TSP)] # print(special_nodes) # upper_bound = 2**31 for i in range(N_TSP): for j in range(N_TSP): if i != j: edge_weight = int( round(shortest_lengths[dict_TSP_index_to_index[i]][ dict_TSP_index_to_index[j]] + 1)) # distance_matrix[i][j] = min(edge_weight, upper_bound) distance_matrix[i][j] = edge_weight else: distance_matrix[i][j] = 0 # print(distance_matrix) # run TSP concorde matrix_sym = atsp_tsp(distance_matrix, strategy="avg") outf = "/tmp/myroute.tsp" with open(outf, 'w') as dest: dest.write(dumps_matrix(matrix_sym, name="My Route")) try: tour = run(outf, start=dict_index_to_TSP_index[starting_kingdom_index], solver="lkh") # convert to original indices tour_G = [ dict_TSP_index_to_index[TSP_index] for TSP_index in tour["tour"] ] # print(tour_G) # stitch path together edge_list = tour_to_list_of_edges(tour_G) # print(edge_list) stiched_tour = [edge_list[0][0]] # starting node edge_list.append((edge_list[-1][1], starting_kingdom_index)) for i, j in edge_list: stiched_tour.extend(shortest_paths[i][j][1:]) closed_walk = [ dict_kingdom_index_to_name[index] for index in stiched_tour ] except: closed_walk = "Error" # print(closed_walk) return closed_walk
def tsp(self, start_coord): out_f = "./tsp_dist.tsp" with open(out_f, 'w') as dest: dest.write(dumps_matrix(self.dist_matrix, name="TSP_Route")) tour = run(out_f, start=self.back_coords[start_coord], solver="lkh") return tour
def solve(list_of_kingdom_names, starting_kingdom, adjacency_matrix, params=[]): """ Write your algorithm here. Input: list_of_kingdom_names: An list of kingdom names such that node i of the graph corresponds to name index i in the list starting_kingdom: The name of the starting kingdom for the walk adjacency_matrix: The adjacency matrix from the input file Output: Return 2 things. The first is a list of kingdoms representing the walk, and the second is the set of kingdoms that are conquered """ split_size = 15 subgraph = nx.Graph() last_node = None starting_status = [0 for _ in list_of_kingdom_names] sublist = [] starting_index = list_of_kingdom_names.index(starting_kingdom) N = len(list_of_kingdom_names) H = adjacency_matrix_to_graph(adjacency_matrix) result = [] closed_walk, conquered_kingdoms = [], [] stack = [] known = set() stack.append((None, starting_index)) while stack: prev, node = stack.pop() if node in known: continue sublist.append(node) known.add(node) flag = False if len(sublist) == 1: subgraph.add_node(0) elif prev in sublist: subgraph.add_edge(sublist.index(prev), len(sublist) - 1, weight=H[prev][node]['weight']) else: flag = True sublist.pop() if flag or len(sublist) == split_size or len(known) == N: sub_starting_status = [starting_status[node] for node in sublist] sub_result = solver_helper(sublist, subgraph, sub_starting_status, adjacency_matrix) for i in range(len(sub_result)): if sub_result[i] == 2: conquered_kingdoms.append(sublist[i]) for nbr in H.adj[sublist[i]]: starting_status[nbr] = 1 subgraph = nx.Graph() sublist = [] if flag: subgraph.add_node(0) sublist.append(node) # Need to fix the order the neigbhors being added: too costly adjs = list(H.adj[node]) avg = sum([H[node][nbr]['weight'] for nbr in adjs]) / len(adjs) adjs = sorted(adjs, key=lambda nbr: abs(H[node][nbr]['weight'] - avg), reverse=True) for nbr in adjs: stack.append((node, nbr)) if sublist != []: sub_starting_status = [starting_status[node] for node in sublist] sub_result = solver_helper(sublist, subgraph, sub_starting_status, adjacency_matrix) for i in range(len(sub_result)): if sub_result[i] == 2: conquered_kingdoms.append(sublist[i]) tmp_conquered = [(-adjacency_matrix[c][c], c) for c in conquered_kingdoms] while tmp_conquered != []: c = heappop(tmp_conquered)[1] conquered_kingdoms.remove(c) if not nx.is_dominating_set(H, conquered_kingdoms): conquered_kingdoms.append(c) conquered = list(conquered_kingdoms) if starting_index not in conquered: conquered.append(starting_index) C = len(conquered) if C == 1: return [starting_kingdom], [starting_kingdom] elif C == 2: if starting_index == conquered[0]: start, next = conquered else: next, start = conquered _, path = nx.bidirectional_dijkstra(H, start, next) path = [list_of_kingdom_names[c] for c in path] return path + path[::-1][1:], [ list_of_kingdom_names[c] for c in conquered_kingdoms ] else: matrix = np.zeros((C, C)) paths = dict() for i in range(C): for j in range(i + 1, C): matrix[i, j], path = nx.bidirectional_dijkstra( H, conquered[i], conquered[j]) paths[(i, j)] = path paths[(j, i)] = path[::-1] matrix = matrix + matrix.T if np.max(matrix) >= 10e6: matrix = matrix / 10e6 outf = "/tmp/myroute.tsp" with open(outf, 'w') as dest: dest.write(dumps_matrix(matrix, name="My Route")) tour = run(outf, start=conquered.index(starting_index), solver="LKH") tour_path = tour['tour'] for i in range(len(tour_path)): cur = tour_path[i] if i == len(tour_path) - 1: dest = conquered.index(starting_index) else: dest = tour_path[i + 1] closed_walk.append(paths[(cur, dest)]) final_walk = [] for i in range(len(closed_walk)): data = closed_walk[i] if i == 0: for j in data: final_walk.append(list_of_kingdom_names[j]) else: for j in data[1:]: final_walk.append(list_of_kingdom_names[j]) return final_walk, [ list_of_kingdom_names[c] for c in conquered_kingdoms ]
from pytsp import atsp_tsp, run, dumps_matrix # import os if __name__ == "__main__": matrix = [[0, 2910, 4693], [2903, 0, 5839], [4695, 5745, 0]] matrix_sym = atsp_tsp(matrix, strategy="avg") # path = os.path.abspath("cs170-proj") outf = "/tmp/myroute.tsp" with open(outf, 'w') as dest: dest.write(dumps_matrix(matrix_sym, name="My Route")) tour = run(outf, start=1, solver="concorde") print(tour)
def optimal_tour(features, mode, profile, out_points, solver): """ A command line interface for solving the traveling salesman problem Input geojson features with point geometries and output the optimal tour as geojson feature collection. \b $ optimal_tour waypoints.geojson | geojson-summary 19 points and 1 line If using geodesic or directions modes, input must be in lonlat coordinates Directions mode requires a Mapbox account and a valid token set as the MAPBOX_ACCESS_TOKEN environment variable. """ log("Get point features") features = [f for f in features if f['geometry']['type'] == 'Point'] if len(features) <= 2: raise click.UsageError( "Need at least 3 point features to create route") if mode != 'cartesian' and not is_lonlat(features): raise click.UsageError( "For this {} mode, input must be in lonlat coordinates".format( mode)) log("Create travel cost matrix") if mode == 'cartesian': matrix = local_matrix(features, 'cartesian') elif mode == 'geodesic': matrix = local_matrix(features, 'geodesic') elif mode == 'directions': dist_api = mapbox.Distance() res = dist_api.distances(features, profile=profile) if res.status_code == 200: matrix = res.json()['durations'] else: raise Exception( "Got a {0} error from the Distances API: {1}".format( res.status_code, res.content)) log("Prep data") matrix_sym = atsp_tsp(matrix, strategy="avg") outf = "/tmp/myroute.tsp" with open(outf, 'w') as dest: dest.write(dumps_matrix(matrix_sym, name="My Route")) log("Run TSP solver") tour = run(outf, start=0, solver=solver) order = tour['tour'] features_ordered = [features[i] for i in order] log("Create lines connecting the tour") if mode == 'directions': # gather geojson linestring features along actual route via directions directions_api = mapbox.Directions() route_features = [] for chunk in split_overlap(features_ordered + [features_ordered[0]], 24): res = directions_api.directions(chunk, profile='mapbox.' + profile) if res.status_code == 200: route_features.append(res.geojson()['features'][0]) else: raise Exception( "Got a {0} error from the Directions API: {1}".format( res.status_code, res.content)) else: # Alternative, straight line distance between points route_coords = [f['geometry']['coordinates'] for f in features_ordered] route_coords.append(features_ordered[0]['geometry']['coordinates']) route_features = [{ 'type': 'Feature', 'properties': { 'tour': tour }, 'geometry': { 'type': 'LineString', 'coordinates': route_coords } }] # meld into one geojson feature collection log("Output feature collection") out_features = route_features if out_points: out_features += features_ordered collection = {'type': 'FeatureCollection', 'features': out_features} click.echo(json.dumps(collection))