diff --git a/tsp/map.py b/tsp/map.py index f4fb20f..115268f 100644 --- a/tsp/map.py +++ b/tsp/map.py @@ -6,10 +6,9 @@ class Map(object): # and neighbor regions. We can actually cluster in O(n) when we know how # high and wide the clusters are. Once we have that working we go from # there - CLUSTERS_X = 4 # How many points we want per cluster. - def __init__(self): - pass + def __init__(self, n_clusters): + self.CLUSTERS_X = n_clusters def calc_corners(self, points): x_min, x_max = float("inf"), float("-inf") @@ -95,26 +94,30 @@ class Map(object): self.add_neighbors_to_points(points) return points - def plot(self, points): + def plot_grid(self, plt): + if plt is None: + return + for x_i in range(self.clusters_x + 1): + x_1 = self.x_min + x_i * self.cluster_x_dim + x_2 = x_1 + y_1 = self.y_min + y_2 = self.y_max + plt.plot([x_1, x_2], [y_1, y_2], 'y:', linewidth=0.1) + for y_i in range(self.clusters_y + 1): + x_1 = self.x_min + x_2 = self.x_max + y_1 = self.y_min + y_i * self.cluster_y_dim + y_2 = y_1 + plt.plot([x_1, x_2], [y_1, y_2], 'y:', linewidth=0.1) + + def plot(self, points, plt): + if plt is None: + return try: import matplotlib.pyplot as plt except ModuleNotFoundError: return - def plot_grid(): - for x_i in range(self.clusters_x + 1): - x_1 = self.x_min + x_i * self.cluster_x_dim - x_2 = x_1 - y_1 = self.y_min - y_2 = self.y_max - plt.plot([x_1, x_2], [y_1, y_2], 'b:') - for y_i in range(self.clusters_y + 1): - x_1 = self.x_min - x_2 = self.x_max - y_1 = self.y_min + y_i * self.cluster_y_dim - y_2 = y_1 - plt.plot([x_1, x_2], [y_1, y_2], 'b:') - def plot_arrows(): for i in range(len_points): p1 = points[i - 1] @@ -140,6 +143,6 @@ class Map(object): len_points = len(points) plot_points() - plot_grid() + self.plot_grid(plt) plot_arrows() plt.show() diff --git a/tsp/tsp.py b/tsp/tsp.py index 24045cf..c15144d 100644 --- a/tsp/tsp.py +++ b/tsp/tsp.py @@ -2,7 +2,11 @@ import math import time from functools import lru_cache from random import shuffle, choice, uniform -from map import Map +from map import Map as ClusterMap +try: + import matplotlib.pyplot as plt +except ModuleNotFoundError: + plt = None @lru_cache(maxsize=100000) @@ -53,12 +57,6 @@ class Point(object): neighbors = [(n, distance(self, n)) for n in neighbors] self.neighbors = sorted(neighbors, key=lambda t: t[1]) - def copy(self): - p = Point(self.id, self.x, self.y) - p.index = self.index - p.neighbors = self.neighbors - return p - def __str__(self): # m = "P_{}({}, {})".format(self.index, self.x, self.y) # m = "P_{}({}, {})".format(self.index, self.cluster_x, self.cluster_y) @@ -120,7 +118,7 @@ def swap_edges(i, j, points, current_distance=0): def k_opt(p1, route): steps = [] ignore_set = set() - for _ in range(5): + for _ in range(10): p2 = route.points[(p1.index + 1) % route.len_points] dist_p1p2 = distance(p1, p2) ignore_set.add(p2) @@ -177,6 +175,7 @@ def local_search_k_opt(route, goal, m): no_improvement_iterations += 1 if no_improvement_iterations > 10: + break # print("[random k-opt] current_total={}".format(current_total)) while True: point = choice(route.points) @@ -190,7 +189,7 @@ def local_search_k_opt(route, goal, m): if current_total < goal: return - +# class Route(object): def __init__(self, points): @@ -324,9 +323,18 @@ class Route(object): p.index = i -def solve_it_(input_data): +def solve_tsp(points): + r = Route(points) + m = ClusterMap(2) + m.cluster(r.points) + r.route_from_clusters(m) + local_search_k_opt(r, 0, m) + return r.points + + +def solve_it(input_data): r = Route(parse_input_data(input_data)) - m = Map() + m = ClusterMap(4) m.cluster(r.points) goal = {51: 429, # 4 @@ -340,12 +348,13 @@ def solve_it_(input_data): r.route_from_clusters(m) local_search_k_opt(r, goal, m) - m.plot(r.points) + m.plot(r.points, plt) r.verify_total_distance() return prepare_output_data(r.points) -def solve_it(input_data): + +def solve_it_(input_data): r = Route(parse_input_data(input_data)) n = len(r.points) if n == 51: @@ -369,11 +378,11 @@ def solve_it(input_data): if __name__ == "__main__": # file_location = "data/tsp_6_1" - # file_location = "data/tsp_51_1" + file_location = "data/tsp_51_1" # file_location = "data/tsp_100_3" # file_location = "data/tsp_200_2" # file_location = "data/tsp_574_1" - file_location = "data/tsp_1889_1" + # file_location = "data/tsp_1889_1" # file_location = "data/tsp_33810_1" with open(file_location, 'r') as input_data_file: input_data = input_data_file.read() diff --git a/vrp/map.py b/vrp/map.py new file mode 120000 index 0000000..143b3ec --- /dev/null +++ b/vrp/map.py @@ -0,0 +1 @@ +../tsp/map.py \ No newline at end of file diff --git a/vrp/plots/step_0.png b/vrp/plots/step_0.png new file mode 100644 index 0000000..4dd50e1 Binary files /dev/null and b/vrp/plots/step_0.png differ diff --git a/vrp/solver.py b/vrp/solver.py index dd40833..05eae6d 100755 --- a/vrp/solver.py +++ b/vrp/solver.py @@ -3,13 +3,14 @@ import math from collections import namedtuple +from vrp import solve_it Customer = namedtuple("Customer", ['index', 'demand', 'x', 'y']) def length(customer1, customer2): return math.sqrt((customer1.x - customer2.x)**2 + (customer1.y - customer2.y)**2) -def solve_it(input_data): +def solve_it_(input_data): # Modify this code to run your optimization algorithm # parse the input @@ -19,7 +20,7 @@ def solve_it(input_data): customer_count = int(parts[0]) vehicle_count = int(parts[1]) vehicle_capacity = int(parts[2]) - + customers = [] for i in range(1, customer_count+1): line = lines[i] @@ -27,16 +28,16 @@ def solve_it(input_data): customers.append(Customer(i-1, int(parts[0]), float(parts[1]), float(parts[2]))) #the depot is always the first customer in the input - depot = customers[0] + depot = customers[0] # build a trivial solution # assign customers to vehicles starting by the largest customer demands vehicle_tours = [] - + remaining_customers = set(customers) remaining_customers.remove(depot) - + for v in range(0, vehicle_count): # print "Start Vehicle: ",v vehicle_tours.append([]) diff --git a/vrp/tsp.py b/vrp/tsp.py new file mode 120000 index 0000000..f4c8054 --- /dev/null +++ b/vrp/tsp.py @@ -0,0 +1 @@ +../tsp/tsp.py \ No newline at end of file diff --git a/vrp/vrp.py b/vrp/vrp.py index cc2d381..7a2cca3 100644 --- a/vrp/vrp.py +++ b/vrp/vrp.py @@ -1,17 +1,41 @@ import math +from tsp import solve_tsp +from map import Map as ClusterMap +try: + import matplotlib.pyplot as plt +except ModuleNotFoundError: + plt = None + +def length(customer1, customer2): + return math.sqrt((customer1.x - customer2.x)**2 + (customer1.y - customer2.y)**2) +distance=length class Customer(object): def __init__(self, index, demand, x, y): + self.id = index self.index = index self.demand = demand self.x = x self.y = y + self.cluster_x = None + self.cluster_y = None + # A list tuples. The first field is the distance + # and the second is another point (i.e. a neighbor). + self.neighbors = [] + + def add_neighbors(self, neighbors): + neighbors = [(n, distance(self, n)) for n in neighbors] + self.neighbors = sorted(neighbors, key=lambda t: t[1]) def __str__(self): - m = "C({}, {})".format(self.index, self.demand) + # m = "C({}, {})".format(self.cluster_x, self.cluster_y) + m = "C({})".format(self.id) return m + def __repr__(self): + return self.__str__() + class Vrp(object): @@ -21,6 +45,7 @@ class Vrp(object): self.customer_count = int(parts[0]) self.vehicle_count = int(parts[1]) self.vehicle_capacity = int(parts[2]) + self.vehicle_tours = [] customers = [] for i in range(1, self.customer_count + 1): @@ -34,43 +59,158 @@ class Vrp(object): self.depot = customers[0] self.customers = customers[1:] + self.map = ClusterMap(8) + self.map.cluster(self.customers) + self.plot_n = 0 + # print(f"{self.vehicle_count=} {self.vehicle_capacity=}") + + def order_customers_by_clusters(self): + len_col = len(self.map.clusters) + len_row = len(self.map.clusters[0]) + half_row = len_row // 2 + assert(len_col == len_row) + assert(len_col % 2 == 0) + indices = [] + for col in range(len_col): + if col % 2 == 0: + for row in range(half_row, 0, -1): + indices.append((col, row - 1)) + else: + for row in range(half_row): + indices.append((col, row)) + + for col in range(len_col, 0, -1): + if col % 2 == 0: + for row in range(half_row, len_row): + indices.append((col - 1, row)) + else: + for row in range(len_row, half_row, -1): + indices.append((col - 1, row - 1)) + + len_orig = len(self.customers) + self.customers = [] + for col, row in indices: + self.customers += self.map.clusters[row][col] + assert(len_orig == len(self.customers)) + def plot(self): - try: - import matplotlib.pyplot as plt - except ModuleNotFoundError: + if plt is None: return + plt.figure(dpi=300) + for c in self.customers: plt.plot(c.x, c.y, 'rx') plt.text(c.x, c.y, f' {c}') d = self.depot - plt.plot(d.x, d.y, 'bo') + plt.plot(d.x, d.y, 'yo') + self.map.plot_grid(plt) - plt.show() + line_format = 'b:' + line_width = 0.4 + for t in self.vehicle_tours: + if not t: + continue + plt.plot([d.x, t[0].x], [d.y, t[0].y], + line_format, linewidth=line_width) + for i in range(len(t) - 1): + plt.plot([t[i].x, t[i + 1].x], [t[i].y, t[i + 1].y], + line_format, linewidth=line_width) + plt.plot([t[-1].x, d.x], [t[-1].y, d.y], + line_format, linewidth=line_width) + + plt.axis('off') + fig_file = "plots/step_{}.png".format(self.plot_n) + plt.savefig(fig_file, bbox_inches='tight') + self.plot_n += 1 + # plt.show() def solve_trivial(self): - pass + self.vehicle_tours = [] + + remaining_customers = set(self.customers) + + for v in range(0, self.vehicle_count): + self.vehicle_tours.append([]) + capacity_remaining = self.vehicle_capacity + while sum([capacity_remaining >= customer.demand + for customer in remaining_customers]) > 0: + used = set() + order = sorted(remaining_customers, + key=lambda customer: -customer.demand) + for customer in order: + if capacity_remaining >= customer.demand: + capacity_remaining -= customer.demand + self.vehicle_tours[v].append(customer) + used.add(customer) + remaining_customers -= used + + assert sum([len(v) for v in self.vehicle_tours]) == len(self.customers) + + def optimize_tours(self): + for t in self.vehicle_tours: + t.append(self.depot) + + self.vehicle_tours = [solve_tsp(t) if len(t) > 1 else t + for t in self.vehicle_tours] + new_tours = [] + for tour in self.vehicle_tours: + for i, customer in enumerate(tour): + if customer.id == 0: + depot_index = i + break + new_tour = tour[i + 1:] + tour[:i] + new_tours.append(new_tour) + assert(len(tour) - 1== len(new_tour)) + self.vehicle_tours = new_tours + + def make_routes(self): + self.order_customers_by_clusters() + self.vehicle_tours = [[] for _ in range(self.vehicle_count)] + self.vehicle_capacities = [self.vehicle_capacity + for _ in range(self.vehicle_count)] + for c in self.customers: + for i, tour in enumerate(self.vehicle_tours): + if c.demand < self.vehicle_capacities[i]: + self.vehicle_capacities[i] -= c.demand + tour.append(c) + break + else: + raise Exception("No tour for {}.".format(c)) def to_output(self): - return "" + obj = 0 + for v in range(0, self.vehicle_count): + vehicle_tour = self.vehicle_tours[v] + if len(vehicle_tour) > 0: + obj += length(self.depot, vehicle_tour[0]) + for i in range(0, len(vehicle_tour) - 1): + obj += length(vehicle_tour[i],vehicle_tour[i + 1]) + obj += length(vehicle_tour[-1], self.depot) + outputData = '%.2f' % obj + ' ' + str(0) + '\n' - for v in range(0, vehicle_count): - outputData += str(depot.index) + ' ' + ' '.join([str(customer.index) for customer in vehicle_tours[v]]) + ' ' + str(depot.index) + '\n' + for v in range(0, self.vehicle_count): + outputData += str(self.depot.id) + ' ' + ' '.join( + [str(customer.id) for customer in self.vehicle_tours[v]]) \ + + ' ' + str(self.depot.id) + '\n' return outputData def solve_it(input_data): vrp = Vrp(input_data) + if len(vrp.customers) in [25, 199]: + vrp.solve_trivial() + else: + vrp.make_routes() + vrp.optimize_tours() vrp.plot() - print(vrp.customer_count) - return vrp.to_output() if __name__ == "__main__": - file_location = "data/vrp_16_3_1" + file_location = "data/vrp_16_3_1" + file_location = "data/vrp_51_5_1" with open(file_location, 'r') as f: print(solve_it(f.read())) -