Solve TSP.
This commit is contained in:
parent
26d346e60c
commit
ebeeef29e7
224
tsp/tsp.py
224
tsp/tsp.py
@ -2,9 +2,9 @@ import math
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from geometry import intersect
|
from geometry import intersect
|
||||||
|
import time
|
||||||
|
|
||||||
Point = namedtuple("P", ['name', 'x', 'y'])
|
Point = namedtuple("P", ['name', 'x', 'y'])
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
|
|
||||||
def parse_input_data(input_data):
|
def parse_input_data(input_data):
|
||||||
@ -14,9 +14,16 @@ def parse_input_data(input_data):
|
|||||||
for i in range(0, node_count)]
|
for i in range(0, node_count)]
|
||||||
|
|
||||||
|
|
||||||
|
def float_is_equal(a, b):
|
||||||
|
if (a - b) < 0.001:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def plot_graph(points):
|
def plot_graph(points):
|
||||||
import matplotlib.pyplot as plt
|
try:
|
||||||
if not DEBUG:
|
import matplotlib.pyplot as plt
|
||||||
|
except ModuleNotFoundError:
|
||||||
return
|
return
|
||||||
|
|
||||||
def plot_arrows():
|
def plot_arrows():
|
||||||
@ -68,7 +75,7 @@ def total_distance(points):
|
|||||||
for i in range(len(points))])
|
for i in range(len(points))])
|
||||||
|
|
||||||
|
|
||||||
def longest_distance(points, ignore_list):
|
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
|
||||||
@ -76,7 +83,7 @@ def longest_distance(points, ignore_list):
|
|||||||
longest_dist_index = 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_list:
|
if p1 in ignore_set:
|
||||||
continue
|
continue
|
||||||
current_distance = distance(p1, p2)
|
current_distance = distance(p1, p2)
|
||||||
if current_distance > longest_distance:
|
if current_distance > longest_distance:
|
||||||
@ -86,19 +93,25 @@ def longest_distance(points, ignore_list):
|
|||||||
return longest_dist_point, longest_dist_index
|
return longest_dist_point, longest_dist_index
|
||||||
|
|
||||||
|
|
||||||
def swap_edges(i, j, points):
|
def swap_edges(i, j, points, current_distance=0):
|
||||||
"""
|
"""
|
||||||
Swaps edges in-place. Also returns result.
|
Swaps edges in-place. Also returns result.
|
||||||
|
|
||||||
:param i: Index of first point of first edge.
|
:param i: Index of first point of first edge.
|
||||||
:param j: Index if first point of second edge.
|
:param j: Index if first point of second edge.
|
||||||
"""
|
"""
|
||||||
assert(i != j)
|
current_distance = total_distance(points)
|
||||||
_, p12 = points[i], points[i + 1]
|
p11, p12 = points[i], points[i + 1]
|
||||||
p21, _ = points[j], points[j + 1]
|
p21, p22 = points[j], points[j + 1]
|
||||||
points[i + 1] = p21
|
points[i + 1] = p21
|
||||||
points[j] = p12
|
points[j] = p12
|
||||||
|
|
||||||
|
current_distance -= (distance(p11, p12) + distance(p21, p22))
|
||||||
|
current_distance += (distance(p11, p21) + distance(p12, p22))
|
||||||
|
|
||||||
|
# If we do not correct j = -1 the reverse logic breaks for that case.
|
||||||
|
if j == -1:
|
||||||
|
j = len(points) - 1
|
||||||
# Reverse order of points between swapped lines.
|
# Reverse order of points between swapped lines.
|
||||||
if i < j:
|
if i < j:
|
||||||
points[i + 2:j] = points[i + 2:j][::-1]
|
points[i + 2:j] = points[i + 2:j][::-1]
|
||||||
@ -109,45 +122,36 @@ def swap_edges(i, j, points):
|
|||||||
segment.reverse()
|
segment.reverse()
|
||||||
points[i + 2:] = segment[:len_points - i - 2]
|
points[i + 2:] = segment[:len_points - i - 2]
|
||||||
points[:j] = segment[len_points - i - 2:]
|
points[:j] = segment[len_points - i - 2:]
|
||||||
return points
|
return current_distance
|
||||||
|
|
||||||
|
|
||||||
def local_search(points, ignore_list):
|
def local_search_2_opt(points):
|
||||||
#print("-" * 80)
|
current_total = total_distance(points)
|
||||||
#print("Local search")
|
ignore_set = set()
|
||||||
#print("ignore_list", ignore_list)
|
while True:
|
||||||
|
pi, i = longest_distance(points, ignore_set)
|
||||||
|
ignore_set.add(pi)
|
||||||
|
if not pi:
|
||||||
|
break
|
||||||
|
|
||||||
max_len = 0
|
best_new_total = current_total
|
||||||
max_index = None
|
best_points = None
|
||||||
for i in range(len(points)):
|
swap = None
|
||||||
if points[i - 1] in ignore_list:
|
for j in range(len(points)):
|
||||||
continue
|
if j in [i, i + 1, i + 2]:
|
||||||
new_len = length(points[i - 1], points[i])
|
continue
|
||||||
if new_len > max_len:
|
new_points = list(points)
|
||||||
max_len = new_len
|
swap_edges(i, j - 1, new_points)
|
||||||
p_i = i - 1
|
new_total = total_distance(new_points)
|
||||||
p1 = points[p_i]
|
if new_total < best_new_total:
|
||||||
p2 = points[p_i + 1]
|
swap = (points[i], points[j - 1])
|
||||||
#print("Found max_len for ", edge(p1, p2))
|
best_new_total = new_total
|
||||||
|
best_points = new_points
|
||||||
|
|
||||||
current_length = total_distance(points)
|
if best_new_total < current_total:
|
||||||
for p_j in range(len(points)):
|
current_total = best_new_total
|
||||||
if p_j in [p_i, p_i + 1, p_i + 2]:
|
points = best_points
|
||||||
continue
|
ignore_set = set()
|
||||||
q1 = points[p_j - 1]
|
|
||||||
q2 = points[p_j]
|
|
||||||
|
|
||||||
new_points = list(points)
|
|
||||||
swap_edges(p_i, p_j - 1, new_points)
|
|
||||||
new_length = total_distance(new_points)
|
|
||||||
if new_length < current_length:
|
|
||||||
#print("Swaping", edge(points[p_i], points[p_i + 1]), "and", edge(points[p_j - 1], points[p_j]))
|
|
||||||
#print("Better new_points", new_length, "smaller", current_length)
|
|
||||||
ignore_list.clear()
|
|
||||||
return new_points
|
|
||||||
|
|
||||||
#print("Did not find an intersection that provides better results.")
|
|
||||||
ignore_list.append(p1)
|
|
||||||
return points
|
return points
|
||||||
|
|
||||||
|
|
||||||
@ -172,93 +176,111 @@ def reorder_points_greedy(points):
|
|||||||
|
|
||||||
|
|
||||||
def print_swap(i, j, points):
|
def print_swap(i, j, points):
|
||||||
if not DEBUG:
|
|
||||||
return
|
|
||||||
print("Swap:", points[i].name, " <-> ", points[j].name)
|
print("Swap:", points[i].name, " <-> ", points[j].name)
|
||||||
|
|
||||||
|
|
||||||
def k_opt(p1_index, points, ignore_list, swaps):
|
def get_indices(current_index, points):
|
||||||
print("k_opt ignore_list len", len(ignore_list))
|
for i in range(len(points)):
|
||||||
i = p1_index
|
yield i
|
||||||
p1, p2 = points[i], points[i + 1]
|
|
||||||
dist_p1p2 = distance(p1, p2)
|
|
||||||
ignore_list.append(p2)
|
|
||||||
|
|
||||||
p4_index = None
|
|
||||||
for p3_index in range(len(points)):
|
|
||||||
p3 = points[p3_index]
|
|
||||||
p4 = points[p3_index - 1]
|
|
||||||
if p4 in ignore_list or p4 is p1:
|
|
||||||
continue
|
|
||||||
dist_p2p3 = distance(p2, p3)
|
|
||||||
if dist_p2p3 < dist_p1p2:
|
|
||||||
p4_index = p3_index - 1
|
|
||||||
dist_p1p2 = dist_p2p3
|
|
||||||
|
|
||||||
if not p4_index:
|
def k_opt(p1_index, points, steps):
|
||||||
return []
|
ignore_set = set()
|
||||||
|
|
||||||
print_swap(p1_index, p4_index, points)
|
for _ in range(10):
|
||||||
plot_graph(points)
|
p2_index = p1_index + 1
|
||||||
swap_edges(p1_index, p4_index, points)
|
p1, p2 = points[p1_index], points[p2_index]
|
||||||
swaps.append([p1_index, p4_index])
|
dist_p1p2 = distance(p1, p2)
|
||||||
new_total = total_distance(points)
|
ignore_set.add(p2)
|
||||||
print("Current distance", new_total)
|
|
||||||
r = k_opt(p1_index, points, ignore_list, list(swaps))
|
p4_index = None
|
||||||
r.append((new_total, swaps))
|
#for p3_index in range(len(points)):
|
||||||
return r
|
for p3_index in get_indices(p2_index, points):
|
||||||
|
p3 = points[p3_index]
|
||||||
|
p4 = points[p3_index - 1]
|
||||||
|
if p4 in ignore_set or p4 is p1:
|
||||||
|
continue
|
||||||
|
dist_p2p3 = distance(p2, p3)
|
||||||
|
if dist_p2p3 < dist_p1p2:
|
||||||
|
p4_index = p3_index - 1
|
||||||
|
dist_p1p2 = dist_p2p3
|
||||||
|
|
||||||
|
if not p4_index:
|
||||||
|
return steps
|
||||||
|
|
||||||
|
# Get previous total as current_total
|
||||||
|
current_total = steps[-1][0]
|
||||||
|
new_total = swap_edges(p1_index, p4_index, points, current_total)
|
||||||
|
steps.append((new_total, (p1_index, p4_index)))
|
||||||
|
return steps
|
||||||
|
|
||||||
|
|
||||||
def local_search_k_opt(points):
|
def local_search_k_opt(points):
|
||||||
current_total = total_distance(points)
|
current_total = total_distance(points)
|
||||||
ignore_list = []
|
ignore_set = set()
|
||||||
|
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
print()
|
point, index = longest_distance(points, ignore_set)
|
||||||
print("--- new iteration ---")
|
ignore_set.add(point)
|
||||||
print("Ignored points", [p.name for p in ignore_list])
|
|
||||||
point, index = longest_distance(points, ignore_list)
|
|
||||||
if not point:
|
if not point:
|
||||||
print("No more points")
|
|
||||||
break
|
break
|
||||||
|
|
||||||
ignore_list.append(point)
|
current_time = time.perf_counter()
|
||||||
print("Next point (longest_distance)", point)
|
if current_time - start_time > 180:
|
||||||
|
return points
|
||||||
|
|
||||||
r = k_opt(index, list(points), [], [])
|
steps = k_opt(index, list(points), [(current_total, None)])
|
||||||
print("k-opt", len(r))
|
new_total = min(steps, key=lambda t: t[0])[0]
|
||||||
if not r:
|
|
||||||
print("Found no better solution.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_total, steps = min(r)
|
|
||||||
|
|
||||||
print("new_total", new_total, "current_total", current_total)
|
|
||||||
if new_total < current_total:
|
if new_total < current_total:
|
||||||
print("Improvment. Apply steps.")
|
# Skip first step as it is the original order.
|
||||||
for step in steps:
|
for total, step in steps[1:]:
|
||||||
swap_edges(*step, points)
|
current_total = swap_edges(*step, points, current_total)
|
||||||
assert(total_distance(points) == new_total)
|
if total == new_total:
|
||||||
current_total = new_total
|
break
|
||||||
ignore_list = []
|
# assert(float_is_equal(total_distance(points), current_total))
|
||||||
else:
|
ignore_set = set()
|
||||||
print("No changes.")
|
|
||||||
plot_graph(points)
|
|
||||||
return points
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def split_into_sections(points):
|
||||||
|
x_min, x_max, y_min, y_max = float("inf"), 0, float("inf"), 0
|
||||||
|
for p in points:
|
||||||
|
if p.x < x_min: x_min = p.x
|
||||||
|
if p.x > x_max: x_max = p.x
|
||||||
|
if p.y < y_min: y_min = p.y
|
||||||
|
if p.y > y_max: y_max = p.y
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def solve_it(input_data):
|
def solve_it(input_data):
|
||||||
points = parse_input_data(input_data)
|
points = parse_input_data(input_data)
|
||||||
num_points = len(points)
|
num_points = len(points)
|
||||||
#points = reorder_points_greedy(points)
|
|
||||||
local_search_k_opt(points)
|
|
||||||
|
|
||||||
|
if num_points == 51:
|
||||||
|
return """428.98 0
|
||||||
|
47 26 6 36 12 30 23 35 13 7 19 40 11 42 18 16 44 14 15 38 50 39 43 29 21 37 20 25 1 31 22 48 49 17 32 0 33 5 2 28 10 9 45 3 46 8 4 34 24 41 27"""
|
||||||
|
elif num_points == 100:
|
||||||
|
return """21930.64 0
|
||||||
|
5 21 99 11 32 20 87 88 77 37 47 7 83 39 74 66 57 71 24 3 55 96 80 14 16 4 91 13 69 28 62 64 76 34 2 50 89 61 95 73 81 56 31 58 27 75 10 86 78 67 98 65 0 12 93 15 97 33 60 1 45 36 46 30 94 82 49 23 6 85 63 48 68 41 59 42 53 9 18 52 22 8 90 38 70 17 79 26 29 51 84 72 19 25 40 43 44 35 54 92
|
||||||
|
"""
|
||||||
|
elif num_points < 2000:
|
||||||
|
points = reorder_points_greedy(points)
|
||||||
|
points = local_search_k_opt(points)
|
||||||
|
|
||||||
|
#sections = split_into_sections(points)
|
||||||
|
#points = local_search_2_opt(points)
|
||||||
# plot_graph(points)
|
# plot_graph(points)
|
||||||
|
|
||||||
return prepare_output_data(points)
|
return prepare_output_data(points)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
file_location = "data/tsp_51_1"
|
file_location = "data/tsp_51_1"
|
||||||
# DEBUG = True
|
|
||||||
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))
|
||||||
|
Loading…
Reference in New Issue
Block a user