Update google_solver/google_model.py
Browse files- google_solver/google_model.py +57 -62
google_solver/google_model.py
CHANGED
@@ -1,52 +1,39 @@
|
|
1 |
from __future__ import print_function
|
2 |
-
from ortools.constraint_solver import routing_enums_pb2
|
3 |
-
from ortools.constraint_solver import pywrapcp
|
4 |
import torch
|
5 |
from google_solver.convert_data import convert_data
|
6 |
|
7 |
|
8 |
-
class GoogleActor
|
|
|
|
|
|
|
9 |
|
10 |
def __init__(self, scale_factor=100):
|
11 |
-
|
12 |
-
if scale_factor is None:
|
13 |
-
self.scale_factor = 1
|
14 |
-
else:
|
15 |
-
self.scale_factor = scale_factor
|
16 |
-
|
17 |
|
18 |
def __call__(self, input):
|
19 |
-
|
20 |
drive_times = []
|
21 |
data = convert_data(input, self.scale_factor)
|
22 |
-
batch_size = len(data)
|
23 |
for datum in data:
|
24 |
routing, assignment = self.compute_route(datum)
|
25 |
total_time = self.compute_total_time(datum, routing, assignment)
|
26 |
drive_times.append(total_time)
|
27 |
|
28 |
-
|
29 |
-
return drive_times
|
30 |
-
|
31 |
-
|
32 |
-
def compute_distance(self, routing, assignment, num_nodes):
|
33 |
-
"""Prints solution on console."""
|
34 |
-
cumulative_route_distance = 0
|
35 |
-
for vehicle_id in range(num_nodes):
|
36 |
-
index = routing.Start(vehicle_id)
|
37 |
-
route_distance = 0
|
38 |
-
while not routing.IsEnd(index):
|
39 |
-
previous_index = index
|
40 |
-
index = assignment.Value(routing.NextVar(index))
|
41 |
-
route_distance += routing.GetArcCostForVehicle(
|
42 |
-
previous_index, index, vehicle_id)
|
43 |
-
cumulative_route_distance += route_distance
|
44 |
|
45 |
-
|
46 |
-
|
|
|
47 |
|
|
|
|
|
|
|
|
|
48 |
|
49 |
-
|
|
|
|
|
50 |
time_dimension = routing.GetDimensionOrDie('Time')
|
51 |
total_time = 0
|
52 |
for vehicle_id in range(data['num_vehicles']):
|
@@ -55,74 +42,82 @@ class GoogleActor(object):
|
|
55 |
index = assignment.Value(routing.NextVar(index))
|
56 |
time_var = time_dimension.CumulVar(index)
|
57 |
total_time += assignment.Min(time_var)
|
58 |
-
total_time = total_time/self.scale_factor
|
59 |
-
return total_time
|
60 |
-
|
61 |
|
|
|
62 |
|
63 |
def compute_route(self, input):
|
|
|
|
|
|
|
|
|
|
|
64 |
|
|
|
|
|
|
|
65 |
distance_matrix = input['distance_matrix']
|
66 |
time_matrix = input['time_matrix']
|
67 |
time_windows = input['time_windows']
|
68 |
num_vehicles = input['num_vehicles']
|
69 |
depot = input['depot']
|
70 |
|
71 |
-
|
72 |
manager = pywrapcp.RoutingIndexManager(len(time_matrix), num_vehicles, depot)
|
73 |
routing = pywrapcp.RoutingModel(manager)
|
74 |
|
75 |
def time_callback(from_index, to_index):
|
76 |
-
"""Returns the travel time between the two nodes."""
|
77 |
-
# Convert from routing variable Index to time matrix NodeIndex.
|
78 |
from_node = manager.IndexToNode(from_index)
|
79 |
to_node = manager.IndexToNode(to_index)
|
80 |
return time_matrix[from_node][to_node]
|
81 |
|
82 |
transit_callback_index = routing.RegisterTransitCallback(time_callback)
|
83 |
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
|
84 |
-
|
85 |
routing.AddDimension(
|
86 |
transit_callback_index,
|
87 |
-
10000, #
|
88 |
-
10000, #
|
89 |
-
False, # Don't force start cumul to zero
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
|
|
|
|
96 |
continue
|
97 |
index = manager.NodeToIndex(location_idx)
|
98 |
-
|
99 |
-
time_dimension.CumulVar(index).SetRange(a, b)
|
100 |
|
101 |
-
#
|
|
|
102 |
for vehicle_id in range(num_vehicles):
|
103 |
index = routing.Start(vehicle_id)
|
104 |
-
|
105 |
-
time_dimension.CumulVar(index).SetRange(a, b)
|
106 |
|
|
|
107 |
for i in range(num_vehicles):
|
108 |
-
routing.AddVariableMinimizedByFinalizer(
|
109 |
-
|
110 |
-
routing.AddVariableMinimizedByFinalizer(
|
111 |
-
time_dimension.CumulVar(routing.End(i)))
|
112 |
|
113 |
-
|
114 |
-
|
115 |
-
assignment = routing.SolveWithParameters(search_parameters)
|
116 |
|
|
|
117 |
return routing, assignment
|
118 |
|
119 |
|
|
|
|
|
|
|
120 |
|
|
|
|
|
121 |
|
122 |
-
|
|
|
|
|
123 |
validation_dataset.device = 'cpu'
|
124 |
data = validation_dataset.get_data()
|
125 |
model = GoogleActor(scale_factor=100)
|
126 |
-
|
127 |
-
return scores
|
128 |
-
|
|
|
1 |
from __future__ import print_function
|
2 |
+
from ortools.constraint_solver import routing_enums_pb2, pywrapcp
|
|
|
3 |
import torch
|
4 |
from google_solver.convert_data import convert_data
|
5 |
|
6 |
|
7 |
+
class GoogleActor:
|
8 |
+
"""
|
9 |
+
Wrapper class to evaluate VRP solutions using Google's OR-Tools solver.
|
10 |
+
"""
|
11 |
|
12 |
def __init__(self, scale_factor=100):
|
13 |
+
self.scale_factor = scale_factor if scale_factor is not None else 1
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
def __call__(self, input):
|
|
|
16 |
drive_times = []
|
17 |
data = convert_data(input, self.scale_factor)
|
|
|
18 |
for datum in data:
|
19 |
routing, assignment = self.compute_route(datum)
|
20 |
total_time = self.compute_total_time(datum, routing, assignment)
|
21 |
drive_times.append(total_time)
|
22 |
|
23 |
+
return torch.tensor(drive_times).float()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
+
def compute_total_time(self, data, routing, assignment):
|
26 |
+
"""
|
27 |
+
Computes the total time spent across all routes.
|
28 |
|
29 |
+
Args:
|
30 |
+
data (dict): Problem data with time matrix and vehicle count.
|
31 |
+
routing (RoutingModel): OR-Tools routing model.
|
32 |
+
assignment (Assignment): OR-Tools assignment solution.
|
33 |
|
34 |
+
Returns:
|
35 |
+
float: Total time (scaled back).
|
36 |
+
"""
|
37 |
time_dimension = routing.GetDimensionOrDie('Time')
|
38 |
total_time = 0
|
39 |
for vehicle_id in range(data['num_vehicles']):
|
|
|
42 |
index = assignment.Value(routing.NextVar(index))
|
43 |
time_var = time_dimension.CumulVar(index)
|
44 |
total_time += assignment.Min(time_var)
|
|
|
|
|
|
|
45 |
|
46 |
+
return total_time / self.scale_factor
|
47 |
|
48 |
def compute_route(self, input):
|
49 |
+
"""
|
50 |
+
Solves the routing problem using OR-Tools.
|
51 |
+
|
52 |
+
Args:
|
53 |
+
input (dict): Data containing distance, time matrix, time windows, and depot index.
|
54 |
|
55 |
+
Returns:
|
56 |
+
RoutingModel, Assignment: OR-Tools routing and solution.
|
57 |
+
"""
|
58 |
distance_matrix = input['distance_matrix']
|
59 |
time_matrix = input['time_matrix']
|
60 |
time_windows = input['time_windows']
|
61 |
num_vehicles = input['num_vehicles']
|
62 |
depot = input['depot']
|
63 |
|
|
|
64 |
manager = pywrapcp.RoutingIndexManager(len(time_matrix), num_vehicles, depot)
|
65 |
routing = pywrapcp.RoutingModel(manager)
|
66 |
|
67 |
def time_callback(from_index, to_index):
|
|
|
|
|
68 |
from_node = manager.IndexToNode(from_index)
|
69 |
to_node = manager.IndexToNode(to_index)
|
70 |
return time_matrix[from_node][to_node]
|
71 |
|
72 |
transit_callback_index = routing.RegisterTransitCallback(time_callback)
|
73 |
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
|
74 |
+
|
75 |
routing.AddDimension(
|
76 |
transit_callback_index,
|
77 |
+
10000, # Allow waiting time
|
78 |
+
10000, # Max time per vehicle
|
79 |
+
False, # Don't force start cumul to zero
|
80 |
+
'Time'
|
81 |
+
)
|
82 |
+
|
83 |
+
time_dimension = routing.GetDimensionOrDie('Time')
|
84 |
+
|
85 |
+
# Time windows for all locations except depot
|
86 |
+
for location_idx, (start, end) in enumerate(time_windows):
|
87 |
+
if location_idx == depot:
|
88 |
continue
|
89 |
index = manager.NodeToIndex(location_idx)
|
90 |
+
time_dimension.CumulVar(index).SetRange(int(start), int(end))
|
|
|
91 |
|
92 |
+
# Time windows for vehicle start (depot)
|
93 |
+
depot_start, depot_end = time_windows[depot]
|
94 |
for vehicle_id in range(num_vehicles):
|
95 |
index = routing.Start(vehicle_id)
|
96 |
+
time_dimension.CumulVar(index).SetRange(int(depot_start), int(depot_end))
|
|
|
97 |
|
98 |
+
# Finalizer hints for optimization
|
99 |
for i in range(num_vehicles):
|
100 |
+
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.Start(i)))
|
101 |
+
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))
|
|
|
|
|
102 |
|
103 |
+
search_params = pywrapcp.DefaultRoutingSearchParameters()
|
104 |
+
search_params.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC
|
|
|
105 |
|
106 |
+
assignment = routing.SolveWithParameters(search_params)
|
107 |
return routing, assignment
|
108 |
|
109 |
|
110 |
+
def evaluate_google_model(validation_dataset):
|
111 |
+
"""
|
112 |
+
Evaluate the validation dataset using Google OR-Tools model.
|
113 |
|
114 |
+
Args:
|
115 |
+
validation_dataset (Dataset): A dataset with a get_data method.
|
116 |
|
117 |
+
Returns:
|
118 |
+
torch.Tensor: Scores for each batch item.
|
119 |
+
"""
|
120 |
validation_dataset.device = 'cpu'
|
121 |
data = validation_dataset.get_data()
|
122 |
model = GoogleActor(scale_factor=100)
|
123 |
+
return model(data)
|
|
|
|