Skip to content

Commit 334dd93

Browse files
author
Romain MONTAGNÉ
committed
solves augerat data sets
1 parent 15d4529 commit 334dd93

File tree

2 files changed

+339
-0
lines changed

2 files changed

+339
-0
lines changed

cvrp_augerat.py

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
from __future__ import print_function
2+
from ortools.constraint_solver import routing_enums_pb2
3+
from ortools.constraint_solver import pywrapcp
4+
import numpy as np
5+
from pandas import read_csv, DataFrame
6+
import math
7+
import os
8+
import time
9+
10+
11+
class AugeratNodePosition:
12+
"""Stores coordinates of a node of Augerat's instances (set P)."""
13+
14+
def __init__(self, values):
15+
# Node ID
16+
self.name = np.uint32(values[0]).item()
17+
if self.name == 1:
18+
self.name = "Source"
19+
# x coordinate
20+
self.x = np.float64(values[1]).item()
21+
# y coordinate
22+
self.y = np.float64(values[2]).item()
23+
24+
25+
class AugeratNodeDemand:
26+
"""Stores attributes of a node of Augerat's instances (set P)."""
27+
28+
def __init__(self, values):
29+
# Node ID
30+
self.name = np.uint32(values[0]).item()
31+
if self.name == 1:
32+
self.name = "Source"
33+
# demand coordinate
34+
self.demand = np.float64(values[1]).item()
35+
36+
37+
class DataSet:
38+
"""Reads an Augerat instance and stores the network as DiGraph.
39+
40+
Args:
41+
path (str) : Path to data folder.
42+
instance_name (str) : Name of instance to read.
43+
"""
44+
45+
def __init__(self, path, instance_name):
46+
self.data = {}
47+
48+
# Read vehicle capacity
49+
with open(path + instance_name) as fp:
50+
for i, line in enumerate(fp):
51+
if i == 1:
52+
best = line.split()[-1][:-1]
53+
self.best_known_solution = int(best)
54+
if i == 5:
55+
self.max_load = int(line.split()[2])
56+
fp.close()
57+
58+
# Read nodes from txt file
59+
if instance_name[5] == "-":
60+
self.n_vertices = int(instance_name[3:5])
61+
else:
62+
self.n_vertices = int(instance_name[3:6])
63+
df_augerat = read_csv(
64+
path + instance_name,
65+
sep="\t",
66+
skiprows=6,
67+
nrows=self.n_vertices,
68+
)
69+
# Scan each line of the file and add nodes to the network
70+
self.data["locations"] = []
71+
for line in df_augerat.itertuples():
72+
values = line[1].split()
73+
node = AugeratNodePosition(values)
74+
self.data["locations"].append((node.x, node.y))
75+
76+
# Read demand from txt file
77+
df_demand = read_csv(
78+
path + instance_name,
79+
sep="\t",
80+
skiprows=range(7 + self.n_vertices),
81+
nrows=self.n_vertices,
82+
)
83+
self.data["demands"] = []
84+
for line in df_demand.itertuples():
85+
values = line[1].split()
86+
node = AugeratNodeDemand(values)
87+
self.data["demands"].append(node.demand)
88+
89+
# vehicles
90+
self.data["num_vehicles"] = self.n_vertices
91+
self.data["vehicle_capacities"] = [self.max_load] * self.n_vertices
92+
self.data["depot"] = 0
93+
94+
def compute_euclidean_distance_matrix(self, locations):
95+
"""2D Euclidian distance between two nodes"""
96+
distances = {}
97+
for from_counter, from_node in enumerate(locations):
98+
distances[from_counter] = {}
99+
for to_counter, to_node in enumerate(locations):
100+
if from_counter == to_counter:
101+
distances[from_counter][to_counter] = 0
102+
else:
103+
# Euclidean distance
104+
distances[from_counter][to_counter] = int(
105+
math.hypot(
106+
(from_node[0] - to_node[0]), (from_node[1] - to_node[1])
107+
)
108+
)
109+
return distances
110+
111+
def print_solution(self, manager, routing, solution):
112+
"""Prints solution on console."""
113+
total_distance = 0
114+
total_load = 0
115+
for vehicle_id in range(self.data["num_vehicles"]):
116+
index = routing.Start(vehicle_id)
117+
plan_output = "Route for vehicle {}:\n".format(vehicle_id)
118+
route_distance = 0
119+
route_load = 0
120+
while not routing.IsEnd(index):
121+
node_index = manager.IndexToNode(index)
122+
route_load += self.data["demands"][node_index]
123+
plan_output += " {0} Load({1}) -> ".format(node_index, route_load)
124+
previous_index = index
125+
index = solution.Value(routing.NextVar(index))
126+
route_distance += routing.GetArcCostForVehicle(
127+
previous_index, index, vehicle_id
128+
)
129+
plan_output += " {0} Load({1})\n".format(
130+
manager.IndexToNode(index), route_load
131+
)
132+
plan_output += "Distance of the route: {}m\n".format(route_distance)
133+
plan_output += "Load of the route: {}\n".format(route_load)
134+
# if route_load > 0:
135+
# print(plan_output)
136+
total_distance += route_distance
137+
total_load += route_load
138+
# print('Total distance of all routes: {}m'.format(total_distance))
139+
# print('Total load of all routes: {}'.format(total_load))
140+
return total_distance
141+
142+
def main(self, option):
143+
"""Solve the CVRP problem."""
144+
145+
# Create the routing index manager.
146+
manager = pywrapcp.RoutingIndexManager(
147+
self.n_vertices, self.data["num_vehicles"], self.data["depot"]
148+
)
149+
150+
# Create Routing Model.
151+
routing = pywrapcp.RoutingModel(manager)
152+
153+
# Create and register a transit callback.
154+
distance_matrix = self.compute_euclidean_distance_matrix(self.data["locations"])
155+
156+
def distance_callback(from_index, to_index):
157+
"""Returns the distance between the two nodes."""
158+
# Convert from routing variable Index to distance matrix NodeIndex.
159+
from_node = manager.IndexToNode(from_index)
160+
to_node = manager.IndexToNode(to_index)
161+
return distance_matrix[from_node][to_node]
162+
163+
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
164+
165+
# Define cost of each arc.
166+
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
167+
168+
# Add Capacity constraint.
169+
def demand_callback(from_index):
170+
"""Returns the demand of the node."""
171+
# Convert from routing variable Index to demands NodeIndex.
172+
from_node = manager.IndexToNode(from_index)
173+
return self.data["demands"][from_node]
174+
175+
demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
176+
routing.AddDimensionWithVehicleCapacity(
177+
demand_callback_index,
178+
0, # null capacity slack
179+
self.data["vehicle_capacities"], # vehicle maximum capacities
180+
True, # start cumul to zero
181+
"Capacity",
182+
)
183+
184+
# Setting first solution heuristic.
185+
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
186+
search_parameters.first_solution_strategy = option
187+
188+
# search_parameters.local_search_metaheuristic = (
189+
# routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
190+
# )
191+
search_parameters.time_limit.seconds = 10
192+
193+
solution = routing.SolveWithParameters(search_parameters)
194+
195+
# Print solution on console.
196+
if solution:
197+
best_value = self.print_solution(manager, routing, solution)
198+
else:
199+
best_value = None
200+
return best_value
201+
202+
203+
if __name__ == "__main__":
204+
keys = [
205+
"instance",
206+
"nodes",
207+
"algorithm",
208+
"res",
209+
"best known solution",
210+
"gap",
211+
"time (s)",
212+
"vrp",
213+
"time limit (s)",
214+
]
215+
instance = []
216+
nodes = []
217+
alg = []
218+
res = []
219+
best_known_solution = []
220+
gap = []
221+
run_time = []
222+
vrp = []
223+
time_limit = []
224+
for option in [routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC]:
225+
print("")
226+
print("===============")
227+
for file_name in os.listdir("./data/"):
228+
if file_name[-3:] == "vrp": # and file_name == "A-n32-k5.vrp":
229+
print(file_name)
230+
data = DataSet(path="./data/", instance_name=file_name)
231+
instance.append(file_name)
232+
nodes.append(data.n_vertices)
233+
best_known_solution.append(data.best_known_solution)
234+
alg.append("ortools, path cheapest arc")
235+
vrp.append("cvrp")
236+
time_limit.append(10 * 1)
237+
238+
start = time.time()
239+
best_value = data.main(option)
240+
res.append(best_value)
241+
if best_value:
242+
gap.append(
243+
(best_value - data.best_known_solution)
244+
/ data.best_known_solution
245+
* 100
246+
)
247+
else:
248+
gap.append(None)
249+
run_time.append(float(time.time() - start))
250+
251+
values = [
252+
instance,
253+
nodes,
254+
alg,
255+
res,
256+
best_known_solution,
257+
gap,
258+
run_time,
259+
vrp,
260+
time_limit,
261+
]
262+
df = DataFrame(dict(zip(keys, values)), columns=keys)
263+
df.to_csv("ortools_cvrp_augerat.csv", sep=";", index=False)

data/A-n32-k5.vrp

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
NAME : A-n32-k5
2+
COMMENT : (Augerat et al, Min no of trucks: 5, Optimal value: 784)
3+
TYPE : CVRP
4+
DIMENSION : 32
5+
EDGE_WEIGHT_TYPE : EUC_2D
6+
CAPACITY : 100
7+
NODE_COORD_SECTION
8+
1 82 76
9+
2 96 44
10+
3 50 5
11+
4 49 8
12+
5 13 7
13+
6 29 89
14+
7 58 30
15+
8 84 39
16+
9 14 24
17+
10 2 39
18+
11 3 82
19+
12 5 10
20+
13 98 52
21+
14 84 25
22+
15 61 59
23+
16 1 65
24+
17 88 51
25+
18 91 2
26+
19 19 32
27+
20 93 3
28+
21 50 93
29+
22 98 14
30+
23 5 42
31+
24 42 9
32+
25 61 62
33+
26 9 97
34+
27 80 55
35+
28 57 69
36+
29 23 15
37+
30 20 70
38+
31 85 60
39+
32 98 5
40+
DEMAND_SECTION
41+
1 0
42+
2 19
43+
3 21
44+
4 6
45+
5 19
46+
6 7
47+
7 12
48+
8 16
49+
9 6
50+
10 16
51+
11 8
52+
12 14
53+
13 21
54+
14 16
55+
15 3
56+
16 22
57+
17 18
58+
18 19
59+
19 1
60+
20 24
61+
21 8
62+
22 12
63+
23 4
64+
24 8
65+
25 24
66+
26 24
67+
27 2
68+
28 20
69+
29 15
70+
30 2
71+
31 14
72+
32 9
73+
DEPOT_SECTION
74+
1
75+
-1
76+
EOF

0 commit comments

Comments
 (0)