Eod.
This commit is contained in:
7
tsp/data/tsp_6_1
Normal file
7
tsp/data/tsp_6_1
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
6
|
||||||
|
0 2
|
||||||
|
5 0
|
||||||
|
8 3
|
||||||
|
8 4
|
||||||
|
6 5
|
||||||
|
2 5
|
||||||
278
tsp/tsp.py
278
tsp/tsp.py
@@ -1,9 +1,9 @@
|
|||||||
import math
|
import math
|
||||||
|
import time
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from collections import namedtuple
|
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
from map import Map
|
from map import Map
|
||||||
import time
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1000000)
|
@lru_cache(maxsize=1000000)
|
||||||
def distance(p1, p2):
|
def distance(p1, p2):
|
||||||
@@ -53,6 +53,12 @@ class Point(object):
|
|||||||
neighbors = [(n, distance(self, n)) for n in neighbors]
|
neighbors = [(n, distance(self, n)) for n in neighbors]
|
||||||
self.neighbors = sorted(neighbors, key=lambda t: t[1])
|
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):
|
def __str__(self):
|
||||||
# m = "P_{}({}, {})".format(self.index, self.x, self.y)
|
# m = "P_{}({}, {})".format(self.index, self.x, self.y)
|
||||||
# m = "P_{}({}, {})".format(self.index, self.cluster_x, self.cluster_y)
|
# m = "P_{}({}, {})".format(self.index, self.cluster_x, self.cluster_y)
|
||||||
@@ -65,92 +71,11 @@ class Point(object):
|
|||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
class Route(object):
|
|
||||||
def __init__(self, points):
|
|
||||||
self.points = points
|
|
||||||
self.len_points = len(points)
|
|
||||||
self.total_distance = self.get_total_distance(self.points)
|
|
||||||
|
|
||||||
def get_total_distance(self, points):
|
|
||||||
""" Calculate the total distance of the point sequence. """
|
|
||||||
# Use negative indexing to get the distance from last to first point
|
|
||||||
return sum([distance(points[i - 1], points[i])
|
|
||||||
for i in range(self.len_points)])
|
|
||||||
|
|
||||||
def swap(self, p1, p2):
|
|
||||||
"""
|
|
||||||
Swaps two edges. p1 is the first point of the first
|
|
||||||
edge and p2 is the first point of the second edge.
|
|
||||||
The first point of edge 1 (p1) points to the first point
|
|
||||||
of edge two (p2) after the swap, while the second point
|
|
||||||
of edge 1 (p12) points to the second point of edge two (p22).
|
|
||||||
This means we swap p12 and p2 and update their indices.
|
|
||||||
|
|
||||||
Before: p1 -> p12 and p2 -> p22
|
|
||||||
After: p1 -> p2 and p12 -> p22
|
|
||||||
|
|
||||||
Afterwards we have to reverse the order of the points between
|
|
||||||
p2 and p12 while those points themselves are no longer touched.
|
|
||||||
|
|
||||||
"""
|
|
||||||
p12 = self.points[(p1.index + 1) % self.len_points]
|
|
||||||
p22 = self.points[(p2.index + 1) % self.len_points]
|
|
||||||
|
|
||||||
# Swap positions in route.
|
|
||||||
self.points[p12.index] = p2
|
|
||||||
self.points[p2.index] = p12
|
|
||||||
# Swap indices.
|
|
||||||
p2.index, p12.index = p12.index, p2.index
|
|
||||||
|
|
||||||
# TODO(felixm): Update self.total_distance.
|
|
||||||
# TODO(felixm): Reverse order between p2 and p12.
|
|
||||||
|
|
||||||
return points
|
|
||||||
|
|
||||||
def reorder_points_greedy(self):
|
|
||||||
best_distance = float("inf")
|
|
||||||
best_solution = None
|
|
||||||
points = self.points
|
|
||||||
|
|
||||||
for i in range(1000):
|
|
||||||
shuffle(points)
|
|
||||||
current_point, points = points[0], points[1:]
|
|
||||||
solution = [current_point]
|
|
||||||
|
|
||||||
while points:
|
|
||||||
next_point = None
|
|
||||||
# Select the closest point as the following one.
|
|
||||||
for neighbor, _ in current_point.neighbors:
|
|
||||||
if neighbor in points:
|
|
||||||
next_point = neighbor
|
|
||||||
points.remove(next_point)
|
|
||||||
break
|
|
||||||
|
|
||||||
# If none of the neighbors could be selected use any point.
|
|
||||||
if next_point is None:
|
|
||||||
next_point = points.pop()
|
|
||||||
|
|
||||||
solution.append(next_point)
|
|
||||||
current_point = next_point
|
|
||||||
|
|
||||||
total_distance = self.get_total_distance(solution)
|
|
||||||
points = solution
|
|
||||||
if total_distance < best_distance:
|
|
||||||
best_distance = total_distance
|
|
||||||
best_solution = solution.copy()
|
|
||||||
|
|
||||||
self.points = best_solution
|
|
||||||
for i, p in enumerate(self.points):
|
|
||||||
p.index = i
|
|
||||||
return self.points
|
|
||||||
|
|
||||||
|
|
||||||
def longest_distance(points, ignore_set):
|
def longest_distance(points, ignore_set):
|
||||||
""" Returns the point and index of the
|
""" Returns the point and index of the
|
||||||
point with the longest distance to the next point. """
|
point with the longest distance to the next point. """
|
||||||
longest_distance = 0
|
longest_distance = 0
|
||||||
longest_dist_point = None
|
longest_dist_point = None
|
||||||
longest_dist_index = None
|
|
||||||
for i in range(len(points)):
|
for i in range(len(points)):
|
||||||
p1, p2 = points[i - 1], points[i]
|
p1, p2 = points[i - 1], points[i]
|
||||||
if p1 in ignore_set:
|
if p1 in ignore_set:
|
||||||
@@ -159,8 +84,7 @@ def longest_distance(points, ignore_set):
|
|||||||
if current_distance > longest_distance:
|
if current_distance > longest_distance:
|
||||||
longest_distance = current_distance
|
longest_distance = current_distance
|
||||||
longest_dist_point = p1
|
longest_dist_point = p1
|
||||||
longest_dist_index = i - 1
|
return longest_dist_point
|
||||||
return longest_dist_point, longest_dist_index
|
|
||||||
|
|
||||||
|
|
||||||
def swap_edges(i, j, points, current_distance=0):
|
def swap_edges(i, j, points, current_distance=0):
|
||||||
@@ -195,66 +119,187 @@ def swap_edges(i, j, points, current_distance=0):
|
|||||||
return current_distance
|
return current_distance
|
||||||
|
|
||||||
|
|
||||||
def k_opt(p1_index, points, steps):
|
def k_opt(p1, route, steps):
|
||||||
ignore_set = set()
|
ignore_set = set()
|
||||||
|
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
p2_index = p1_index + 1
|
p2 = route.points[(p1.index + 1) % route.len_points]
|
||||||
p1, p2 = points[p1_index], points[p2_index]
|
|
||||||
dist_p1p2 = distance(p1, p2)
|
dist_p1p2 = distance(p1, p2)
|
||||||
ignore_set.add(p2)
|
ignore_set.add(p2)
|
||||||
|
|
||||||
p4_index = None
|
p4 = None
|
||||||
# TODO(felixm): Keep track of current indices and then make this more efficient.
|
# TODO(felixm): Keep track of current indices and then make this more efficient.
|
||||||
for p3_index in range(len(points)):
|
for p3 in route.points:
|
||||||
p3 = points[p3_index]
|
if p3 is p2 or p3 is p1 or p3 in ignore_set:
|
||||||
p4 = points[p3_index - 1]
|
continue
|
||||||
if p4 in ignore_set or p4 is p1:
|
p4_ = route.points[(p3.index - 1) % route.len_points]
|
||||||
|
if p4_ in ignore_set or p4_ is p1:
|
||||||
continue
|
continue
|
||||||
dist_p2p3 = distance(p2, p3)
|
|
||||||
if dist_p2p3 < dist_p1p2:
|
|
||||||
p4_index = p3_index - 1
|
|
||||||
dist_p1p2 = dist_p2p3
|
|
||||||
|
|
||||||
if not p4_index:
|
dist_p2p3 = distance(p2, p3)
|
||||||
|
|
||||||
|
if dist_p2p3 < dist_p1p2:
|
||||||
|
dist_p1p2 = dist_p2p3
|
||||||
|
p4 = p4_
|
||||||
|
|
||||||
|
if p4 is None:
|
||||||
return steps
|
return steps
|
||||||
|
|
||||||
# Get previous total as current_total
|
step = (p1.index, p4.index)
|
||||||
current_total = steps[-1][0]
|
new_total = route.swap(p1, p4)
|
||||||
new_total = swap_edges(p1_index, p4_index, points, current_total)
|
steps.append((new_total, step))
|
||||||
steps.append((new_total, (p1_index, p4_index)))
|
|
||||||
return steps
|
return steps
|
||||||
|
|
||||||
|
|
||||||
def local_search_k_opt(points):
|
def local_search_k_opt(route):
|
||||||
current_total = total_distance(points)
|
current_total = route.total_distance
|
||||||
ignore_set = set()
|
ignore_set = set()
|
||||||
|
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
point, index = longest_distance(points, ignore_set)
|
# TODO(felixm): Get longest distance from heap in route.
|
||||||
|
point = longest_distance(route.points, ignore_set)
|
||||||
ignore_set.add(point)
|
ignore_set.add(point)
|
||||||
|
|
||||||
if not point:
|
if not point:
|
||||||
break
|
break
|
||||||
|
|
||||||
current_time = time.perf_counter()
|
if time.perf_counter() - start_time > 10:
|
||||||
if current_time - start_time > 180:
|
return
|
||||||
return points
|
|
||||||
|
|
||||||
steps = k_opt(index, list(points), [(current_total, None)])
|
copy_route = route.copy()
|
||||||
|
steps = k_opt(point, copy_route, [(current_total, None)])
|
||||||
new_total = min(steps, key=lambda t: t[0])[0]
|
new_total = min(steps, key=lambda t: t[0])[0]
|
||||||
|
|
||||||
if new_total < current_total:
|
if new_total < current_total:
|
||||||
# Skip first step as it is the original order.
|
# Skip first step as it is the original order.
|
||||||
for total, step in steps[1:]:
|
for total, step in steps[1:]:
|
||||||
current_total = swap_edges(*step, points, current_total)
|
p1, p4 = step
|
||||||
|
current_total = route.swap(p1, p4)
|
||||||
if total == new_total:
|
if total == new_total:
|
||||||
break
|
break
|
||||||
# assert(float_is_equal(total_distance(points), current_total))
|
assert(float_is_equal(route.total_distance, current_total))
|
||||||
ignore_set = set()
|
ignore_set = set()
|
||||||
|
|
||||||
return points
|
|
||||||
|
class Route(object):
|
||||||
|
def __init__(self, points):
|
||||||
|
self.points = points
|
||||||
|
self.len_points = len(points)
|
||||||
|
self.total_distance = self.get_total_distance(points)
|
||||||
|
self.point_id_to_point = {p.id: p for p in self.points}
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
route = Route([])
|
||||||
|
route.points = [p.copy() for p in self.points]
|
||||||
|
route.len_points = self.len_points
|
||||||
|
route.total_distance = self.total_distance
|
||||||
|
route.point_id_to_point = {p.id: p for p in route.points}
|
||||||
|
return route
|
||||||
|
|
||||||
|
def get_point(self, point):
|
||||||
|
return self.point_id_to_point[point.id]
|
||||||
|
|
||||||
|
def verify_total_distance(self):
|
||||||
|
a = self.total_distance
|
||||||
|
b = self.get_total_distance(self.points)
|
||||||
|
assert(float_is_equal(a, b))
|
||||||
|
|
||||||
|
def get_total_distance(self, points):
|
||||||
|
""" Calculate the total distance of the point sequence. """
|
||||||
|
# Use negative indexing to get the distance from last to first point
|
||||||
|
return sum([distance(points[i - 1], points[i])
|
||||||
|
for i in range(self.len_points)])
|
||||||
|
|
||||||
|
def swap(self, p1, p2):
|
||||||
|
"""
|
||||||
|
Swaps two edges. p1 is the first point of the first
|
||||||
|
edge and p2 is the first point of the second edge.
|
||||||
|
The first point of edge 1 (p1) points to the first point
|
||||||
|
of edge two (p2) after the swap, while the second point
|
||||||
|
of edge 1 (p12) points to the second point of edge two (p22).
|
||||||
|
This means we swap p12 and p2 and update their indices.
|
||||||
|
|
||||||
|
Before: p1 -> p12 and p2 -> p22
|
||||||
|
After: p1 -> p2 and p12 -> p22
|
||||||
|
|
||||||
|
Afterwards we have to reverse the order of the points [p', p'', p''']
|
||||||
|
between p2 and p12 while those points themselves are no longer touched.
|
||||||
|
|
||||||
|
Before swap: [p1, p12, p', p'', p''', p2, p21]
|
||||||
|
After swap: [p1, p2, p', p'', p''', p12, p21]
|
||||||
|
"""
|
||||||
|
if type(p1) is int:
|
||||||
|
p1 = self.points[p1]
|
||||||
|
if type(p2) is int:
|
||||||
|
p2 = self.points[p2]
|
||||||
|
|
||||||
|
# Handle case when edge goes over the end of the list.
|
||||||
|
p12 = self.points[(p1.index + 1) % self.len_points]
|
||||||
|
p21 = self.points[(p2.index + 1) % self.len_points]
|
||||||
|
|
||||||
|
# Update self.total_distance.
|
||||||
|
self.total_distance -= (distance(p1, p12) + distance(p2, p21))
|
||||||
|
self.total_distance += (distance(p1, p2) + distance(p12, p21))
|
||||||
|
|
||||||
|
# Swap positions and indices.
|
||||||
|
self.points[p12.index] = p2
|
||||||
|
self.points[p2.index] = p12
|
||||||
|
p2.index, p12.index = p12.index, p2.index
|
||||||
|
|
||||||
|
# Handle case when p2 was before p1 initially.
|
||||||
|
if p12.index > p2.index:
|
||||||
|
len_revers = p12.index - p2.index
|
||||||
|
else:
|
||||||
|
len_revers = (p12.index + self.len_points) - p2.index
|
||||||
|
|
||||||
|
# Reverse order between p2 and p12.
|
||||||
|
for i in range(1, len_revers // 2 + 1):
|
||||||
|
pa = self.points[(p2.index + i) % self.len_points]
|
||||||
|
pb = self.points[(p12.index - i) % self.len_points]
|
||||||
|
self.points[pa.index] = pb
|
||||||
|
self.points[pb.index] = pa
|
||||||
|
pa.index, pb.index = pb.index, pa.index
|
||||||
|
|
||||||
|
return self.total_distance
|
||||||
|
|
||||||
|
def reorder_points_greedy(self):
|
||||||
|
best_distance = float("inf")
|
||||||
|
best_solution = None
|
||||||
|
points = self.points
|
||||||
|
|
||||||
|
for i in range(1000):
|
||||||
|
shuffle(points)
|
||||||
|
current_point, points = points[0], points[1:]
|
||||||
|
solution = [current_point]
|
||||||
|
|
||||||
|
while points:
|
||||||
|
next_point = None
|
||||||
|
# Select the closest point as the following one.
|
||||||
|
for neighbor, _ in current_point.neighbors:
|
||||||
|
if neighbor in points:
|
||||||
|
next_point = neighbor
|
||||||
|
points.remove(next_point)
|
||||||
|
break
|
||||||
|
|
||||||
|
# If none of the neighbors could be selected use any point.
|
||||||
|
if next_point is None:
|
||||||
|
next_point = points.pop()
|
||||||
|
|
||||||
|
solution.append(next_point)
|
||||||
|
current_point = next_point
|
||||||
|
|
||||||
|
total_distance = self.get_total_distance(solution)
|
||||||
|
points = solution
|
||||||
|
if total_distance < best_distance:
|
||||||
|
best_distance = total_distance
|
||||||
|
best_solution = solution.copy()
|
||||||
|
|
||||||
|
self.points = best_solution
|
||||||
|
self.total_distance = best_distance
|
||||||
|
for i, p in enumerate(self.points):
|
||||||
|
p.index = i
|
||||||
|
return self.points
|
||||||
|
|
||||||
|
|
||||||
def solve_it(input_data):
|
def solve_it(input_data):
|
||||||
@@ -263,15 +308,16 @@ def solve_it(input_data):
|
|||||||
|
|
||||||
m.cluster(r.points)
|
m.cluster(r.points)
|
||||||
r.reorder_points_greedy()
|
r.reorder_points_greedy()
|
||||||
# m.plot(r.points)
|
local_search_k_opt(r)
|
||||||
|
m.plot(r.points)
|
||||||
|
|
||||||
|
r.verify_total_distance()
|
||||||
return prepare_output_data(r.points)
|
return prepare_output_data(r.points)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
file_location = "tsp/data/tsp_51_1"
|
file_location = "tsp/data/tsp_51_1"
|
||||||
|
# file_location = "tsp/data/tsp_6_1"
|
||||||
with open(file_location, 'r') as input_data_file:
|
with open(file_location, 'r') as input_data_file:
|
||||||
input_data = input_data_file.read()
|
input_data = input_data_file.read()
|
||||||
print(solve_it(input_data))
|
print(solve_it(input_data))
|
||||||
|
|||||||
Reference in New Issue
Block a user