Finish Discrete Optimization.
parent
cd3d564113
commit
fb2953bc6f
41
tsp/map.py
41
tsp/map.py
|
@ -6,10 +6,9 @@ class Map(object):
|
||||||
# and neighbor regions. We can actually cluster in O(n) when we know how
|
# 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
|
# high and wide the clusters are. Once we have that working we go from
|
||||||
# there
|
# there
|
||||||
CLUSTERS_X = 4 # How many points we want per cluster.
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, n_clusters):
|
||||||
pass
|
self.CLUSTERS_X = n_clusters
|
||||||
|
|
||||||
def calc_corners(self, points):
|
def calc_corners(self, points):
|
||||||
x_min, x_max = float("inf"), float("-inf")
|
x_min, x_max = float("inf"), float("-inf")
|
||||||
|
@ -95,26 +94,30 @@ class Map(object):
|
||||||
self.add_neighbors_to_points(points)
|
self.add_neighbors_to_points(points)
|
||||||
return 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:
|
try:
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
return
|
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():
|
def plot_arrows():
|
||||||
for i in range(len_points):
|
for i in range(len_points):
|
||||||
p1 = points[i - 1]
|
p1 = points[i - 1]
|
||||||
|
@ -140,6 +143,6 @@ class Map(object):
|
||||||
|
|
||||||
len_points = len(points)
|
len_points = len(points)
|
||||||
plot_points()
|
plot_points()
|
||||||
plot_grid()
|
self.plot_grid(plt)
|
||||||
plot_arrows()
|
plot_arrows()
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
39
tsp/tsp.py
39
tsp/tsp.py
|
@ -2,7 +2,11 @@ import math
|
||||||
import time
|
import time
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from random import shuffle, choice, uniform
|
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)
|
@lru_cache(maxsize=100000)
|
||||||
|
@ -53,12 +57,6 @@ 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)
|
||||||
|
@ -120,7 +118,7 @@ def swap_edges(i, j, points, current_distance=0):
|
||||||
def k_opt(p1, route):
|
def k_opt(p1, route):
|
||||||
steps = []
|
steps = []
|
||||||
ignore_set = set()
|
ignore_set = set()
|
||||||
for _ in range(5):
|
for _ in range(10):
|
||||||
p2 = route.points[(p1.index + 1) % route.len_points]
|
p2 = route.points[(p1.index + 1) % route.len_points]
|
||||||
dist_p1p2 = distance(p1, p2)
|
dist_p1p2 = distance(p1, p2)
|
||||||
ignore_set.add(p2)
|
ignore_set.add(p2)
|
||||||
|
@ -177,6 +175,7 @@ def local_search_k_opt(route, goal, m):
|
||||||
|
|
||||||
no_improvement_iterations += 1
|
no_improvement_iterations += 1
|
||||||
if no_improvement_iterations > 10:
|
if no_improvement_iterations > 10:
|
||||||
|
break
|
||||||
# print("[random k-opt] current_total={}".format(current_total))
|
# print("[random k-opt] current_total={}".format(current_total))
|
||||||
while True:
|
while True:
|
||||||
point = choice(route.points)
|
point = choice(route.points)
|
||||||
|
@ -190,7 +189,7 @@ def local_search_k_opt(route, goal, m):
|
||||||
|
|
||||||
if current_total < goal:
|
if current_total < goal:
|
||||||
return
|
return
|
||||||
|
#
|
||||||
|
|
||||||
class Route(object):
|
class Route(object):
|
||||||
def __init__(self, points):
|
def __init__(self, points):
|
||||||
|
@ -324,9 +323,18 @@ class Route(object):
|
||||||
p.index = i
|
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))
|
r = Route(parse_input_data(input_data))
|
||||||
m = Map()
|
m = ClusterMap(4)
|
||||||
m.cluster(r.points)
|
m.cluster(r.points)
|
||||||
|
|
||||||
goal = {51: 429, # 4
|
goal = {51: 429, # 4
|
||||||
|
@ -340,12 +348,13 @@ def solve_it_(input_data):
|
||||||
|
|
||||||
r.route_from_clusters(m)
|
r.route_from_clusters(m)
|
||||||
local_search_k_opt(r, goal, m)
|
local_search_k_opt(r, goal, m)
|
||||||
m.plot(r.points)
|
m.plot(r.points, plt)
|
||||||
|
|
||||||
r.verify_total_distance()
|
r.verify_total_distance()
|
||||||
return prepare_output_data(r.points)
|
return prepare_output_data(r.points)
|
||||||
|
|
||||||
def solve_it(input_data):
|
|
||||||
|
def solve_it_(input_data):
|
||||||
r = Route(parse_input_data(input_data))
|
r = Route(parse_input_data(input_data))
|
||||||
n = len(r.points)
|
n = len(r.points)
|
||||||
if n == 51:
|
if n == 51:
|
||||||
|
@ -369,11 +378,11 @@ def solve_it(input_data):
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# file_location = "data/tsp_6_1"
|
# 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_100_3"
|
||||||
# file_location = "data/tsp_200_2"
|
# file_location = "data/tsp_200_2"
|
||||||
# file_location = "data/tsp_574_1"
|
# file_location = "data/tsp_574_1"
|
||||||
file_location = "data/tsp_1889_1"
|
# file_location = "data/tsp_1889_1"
|
||||||
# file_location = "data/tsp_33810_1"
|
# file_location = "data/tsp_33810_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()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../tsp/map.py
|
Binary file not shown.
After Width: | Height: | Size: 672 KiB |
|
@ -3,13 +3,14 @@
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from vrp import solve_it
|
||||||
|
|
||||||
Customer = namedtuple("Customer", ['index', 'demand', 'x', 'y'])
|
Customer = namedtuple("Customer", ['index', 'demand', 'x', 'y'])
|
||||||
|
|
||||||
def length(customer1, customer2):
|
def length(customer1, customer2):
|
||||||
return math.sqrt((customer1.x - customer2.x)**2 + (customer1.y - customer2.y)**2)
|
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
|
# Modify this code to run your optimization algorithm
|
||||||
|
|
||||||
# parse the input
|
# parse the input
|
||||||
|
@ -19,7 +20,7 @@ def solve_it(input_data):
|
||||||
customer_count = int(parts[0])
|
customer_count = int(parts[0])
|
||||||
vehicle_count = int(parts[1])
|
vehicle_count = int(parts[1])
|
||||||
vehicle_capacity = int(parts[2])
|
vehicle_capacity = int(parts[2])
|
||||||
|
|
||||||
customers = []
|
customers = []
|
||||||
for i in range(1, customer_count+1):
|
for i in range(1, customer_count+1):
|
||||||
line = lines[i]
|
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])))
|
customers.append(Customer(i-1, int(parts[0]), float(parts[1]), float(parts[2])))
|
||||||
|
|
||||||
#the depot is always the first customer in the input
|
#the depot is always the first customer in the input
|
||||||
depot = customers[0]
|
depot = customers[0]
|
||||||
|
|
||||||
|
|
||||||
# build a trivial solution
|
# build a trivial solution
|
||||||
# assign customers to vehicles starting by the largest customer demands
|
# assign customers to vehicles starting by the largest customer demands
|
||||||
vehicle_tours = []
|
vehicle_tours = []
|
||||||
|
|
||||||
remaining_customers = set(customers)
|
remaining_customers = set(customers)
|
||||||
remaining_customers.remove(depot)
|
remaining_customers.remove(depot)
|
||||||
|
|
||||||
for v in range(0, vehicle_count):
|
for v in range(0, vehicle_count):
|
||||||
# print "Start Vehicle: ",v
|
# print "Start Vehicle: ",v
|
||||||
vehicle_tours.append([])
|
vehicle_tours.append([])
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../tsp/tsp.py
|
168
vrp/vrp.py
168
vrp/vrp.py
|
@ -1,17 +1,41 @@
|
||||||
import math
|
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):
|
class Customer(object):
|
||||||
def __init__(self, index, demand, x, y):
|
def __init__(self, index, demand, x, y):
|
||||||
|
self.id = index
|
||||||
self.index = index
|
self.index = index
|
||||||
self.demand = demand
|
self.demand = demand
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
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):
|
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
|
return m
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
class Vrp(object):
|
class Vrp(object):
|
||||||
|
|
||||||
|
@ -21,6 +45,7 @@ class Vrp(object):
|
||||||
self.customer_count = int(parts[0])
|
self.customer_count = int(parts[0])
|
||||||
self.vehicle_count = int(parts[1])
|
self.vehicle_count = int(parts[1])
|
||||||
self.vehicle_capacity = int(parts[2])
|
self.vehicle_capacity = int(parts[2])
|
||||||
|
self.vehicle_tours = []
|
||||||
|
|
||||||
customers = []
|
customers = []
|
||||||
for i in range(1, self.customer_count + 1):
|
for i in range(1, self.customer_count + 1):
|
||||||
|
@ -34,43 +59,158 @@ class Vrp(object):
|
||||||
self.depot = customers[0]
|
self.depot = customers[0]
|
||||||
self.customers = customers[1:]
|
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):
|
def plot(self):
|
||||||
try:
|
if plt is None:
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
plt.figure(dpi=300)
|
||||||
|
|
||||||
for c in self.customers:
|
for c in self.customers:
|
||||||
plt.plot(c.x, c.y, 'rx')
|
plt.plot(c.x, c.y, 'rx')
|
||||||
plt.text(c.x, c.y, f' {c}')
|
plt.text(c.x, c.y, f' {c}')
|
||||||
|
|
||||||
d = self.depot
|
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):
|
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):
|
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'
|
outputData = '%.2f' % obj + ' ' + str(0) + '\n'
|
||||||
for v in range(0, vehicle_count):
|
for v in range(0, self.vehicle_count):
|
||||||
outputData += str(depot.index) + ' ' + ' '.join([str(customer.index) for customer in vehicle_tours[v]]) + ' ' + str(depot.index) + '\n'
|
outputData += str(self.depot.id) + ' ' + ' '.join(
|
||||||
|
[str(customer.id) for customer in self.vehicle_tours[v]]) \
|
||||||
|
+ ' ' + str(self.depot.id) + '\n'
|
||||||
|
|
||||||
return outputData
|
return outputData
|
||||||
|
|
||||||
|
|
||||||
def solve_it(input_data):
|
def solve_it(input_data):
|
||||||
vrp = Vrp(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()
|
vrp.plot()
|
||||||
print(vrp.customer_count)
|
|
||||||
|
|
||||||
return vrp.to_output()
|
return vrp.to_output()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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:
|
with open(file_location, 'r') as f:
|
||||||
print(solve_it(f.read()))
|
print(solve_it(f.read()))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue