Do not copy for k_opt
This commit is contained in:
86
tsp/tsp.py
86
tsp/tsp.py
@@ -1,7 +1,7 @@
|
|||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from random import shuffle
|
from random import shuffle, choice
|
||||||
from map import Map
|
from map import Map
|
||||||
|
|
||||||
|
|
||||||
@@ -119,7 +119,8 @@ def swap_edges(i, j, points, current_distance=0):
|
|||||||
return current_distance
|
return current_distance
|
||||||
|
|
||||||
|
|
||||||
def k_opt(p1, route, steps):
|
def k_opt(p1, route):
|
||||||
|
steps = []
|
||||||
ignore_set = set()
|
ignore_set = set()
|
||||||
|
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
@@ -128,58 +129,58 @@ def k_opt(p1, route, steps):
|
|||||||
ignore_set.add(p2)
|
ignore_set.add(p2)
|
||||||
|
|
||||||
p4 = None
|
p4 = None
|
||||||
# TODO(felixm): Keep track of current indices and then make this more efficient.
|
for p3, dist_p2p3 in p2.neighbors:
|
||||||
for p3 in route.points:
|
|
||||||
if p3 is p2 or p3 is p1 or p3 in ignore_set:
|
if p3 is p2 or p3 is p1 or p3 in ignore_set:
|
||||||
continue
|
continue
|
||||||
p4_ = route.points[(p3.index - 1) % route.len_points]
|
p4_ = route.points[(p3.index - 1) % route.len_points]
|
||||||
if p4_ in ignore_set or p4_ is p1:
|
if p4_ in ignore_set or p4_ is p1:
|
||||||
continue
|
continue
|
||||||
|
# dist_p2p3 = distance(p2, p3)
|
||||||
dist_p2p3 = distance(p2, p3)
|
|
||||||
|
|
||||||
if dist_p2p3 < dist_p1p2:
|
if dist_p2p3 < dist_p1p2:
|
||||||
dist_p1p2 = dist_p2p3
|
dist_p1p2 = dist_p2p3
|
||||||
p4 = p4_
|
p4 = p4_
|
||||||
|
|
||||||
if p4 is None:
|
if p4 is None:
|
||||||
return steps
|
break
|
||||||
|
|
||||||
step = (p1.index, p4.index)
|
step = (p1.index, p4.index)
|
||||||
new_total = route.swap(p1, p4)
|
new_total = route.swap(p1, p4)
|
||||||
steps.append((new_total, step))
|
steps.append((new_total, step))
|
||||||
|
|
||||||
|
|
||||||
return steps
|
return steps
|
||||||
|
|
||||||
|
|
||||||
def local_search_k_opt(route):
|
def local_search_k_opt(route):
|
||||||
current_total = route.total_distance
|
current_total = route.total_distance
|
||||||
ignore_set = set()
|
|
||||||
|
|
||||||
start_time = time.perf_counter()
|
no_improvement_iterations = 0
|
||||||
while True:
|
while no_improvement_iterations < 5:
|
||||||
# TODO(felixm): Get longest distance from heap in route.
|
no_improvement_iterations += 1
|
||||||
point = longest_distance(route.points, ignore_set)
|
for point in list(route.points):
|
||||||
ignore_set.add(point)
|
before_k_opt = current_total
|
||||||
|
steps = k_opt(point, route)
|
||||||
|
if not steps:
|
||||||
|
continue
|
||||||
|
|
||||||
if not point:
|
# Reverse route to status before k_opt. This is cheaper
|
||||||
break
|
# than copying the whole route and allows us to search
|
||||||
|
# the neighborhood faster.
|
||||||
|
for i in range(len(steps), 0, -1):
|
||||||
|
current_total = route.swap(*steps[i - 1][1])
|
||||||
|
assert(float_is_equal(before_k_opt, current_total))
|
||||||
|
|
||||||
if time.perf_counter() - start_time > 10:
|
new_total = min(steps, key=lambda t: t[0])[0]
|
||||||
return
|
if new_total < current_total:
|
||||||
|
for total, step in steps:
|
||||||
|
p1, p4 = step
|
||||||
|
current_total = route.swap(p1, p4)
|
||||||
|
if total == new_total:
|
||||||
|
break
|
||||||
|
assert(float_is_equal(route.total_distance, current_total))
|
||||||
|
|
||||||
copy_route = route.copy()
|
# if no_improvement_iterations > 2:
|
||||||
steps = k_opt(point, copy_route, [(current_total, None)])
|
# current_total = route.swap(choice(route.points), choice(route.points))
|
||||||
new_total = min(steps, key=lambda t: t[0])[0]
|
|
||||||
|
|
||||||
if new_total < current_total:
|
|
||||||
# Skip first step as it is the original order.
|
|
||||||
for total, step in steps[1:]:
|
|
||||||
p1, p4 = step
|
|
||||||
current_total = route.swap(p1, p4)
|
|
||||||
if total == new_total:
|
|
||||||
break
|
|
||||||
assert(float_is_equal(route.total_distance, current_total))
|
|
||||||
ignore_set = set()
|
|
||||||
|
|
||||||
|
|
||||||
class Route(object):
|
class Route(object):
|
||||||
@@ -189,17 +190,6 @@ class Route(object):
|
|||||||
self.total_distance = self.get_total_distance(points)
|
self.total_distance = self.get_total_distance(points)
|
||||||
self.point_id_to_point = {p.id: p for p in self.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):
|
def verify_total_distance(self):
|
||||||
a = self.total_distance
|
a = self.total_distance
|
||||||
b = self.get_total_distance(self.points)
|
b = self.get_total_distance(self.points)
|
||||||
@@ -234,6 +224,9 @@ class Route(object):
|
|||||||
if type(p2) is int:
|
if type(p2) is int:
|
||||||
p2 = self.points[p2]
|
p2 = self.points[p2]
|
||||||
|
|
||||||
|
if p1 is p2:
|
||||||
|
return self.total_distance
|
||||||
|
|
||||||
# Handle case when edge goes over the end of the list.
|
# Handle case when edge goes over the end of the list.
|
||||||
p12 = self.points[(p1.index + 1) % self.len_points]
|
p12 = self.points[(p1.index + 1) % self.len_points]
|
||||||
p21 = self.points[(p2.index + 1) % self.len_points]
|
p21 = self.points[(p2.index + 1) % self.len_points]
|
||||||
@@ -307,17 +300,18 @@ def solve_it(input_data):
|
|||||||
m = Map()
|
m = Map()
|
||||||
|
|
||||||
m.cluster(r.points)
|
m.cluster(r.points)
|
||||||
r.reorder_points_greedy()
|
# r.reorder_points_greedy()
|
||||||
local_search_k_opt(r)
|
local_search_k_opt(r)
|
||||||
m.plot(r.points)
|
# m.plot(r.points)
|
||||||
|
|
||||||
r.verify_total_distance()
|
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 = "data/tsp_51_1"
|
||||||
# file_location = "tsp/data/tsp_6_1"
|
file_location = "data/tsp_200_2"
|
||||||
|
# file_location = "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