-
Notifications
You must be signed in to change notification settings - Fork 63
/
Copy pathdataset.py
330 lines (287 loc) · 14.3 KB
/
dataset.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
from time import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
import math
from scipy.spatial.distance import pdist, squareform
from sklearn.decomposition import PCA
import os
from tsptw_with_ortools import Solver
from config import get_config, print_config
# Compute a sequence's reward
def reward(tsptw_sequence,speed):
# Convert sequence to tour (end=start)
tour = np.concatenate((tsptw_sequence,np.expand_dims(tsptw_sequence[0],0)))
# Compute tour length
inter_city_distances = np.sqrt(np.sum(np.square(tour[:-1,:2]-tour[1:,:2]),axis=1))
distance = np.sum(inter_city_distances)
# Compute develiry times at each city and count late cities
elapsed_time = -10
late_cities = 0
for i in range(tsptw_sequence.shape[0]-1):
travel_time = inter_city_distances[i]/speed
tw_open = tour[i+1,2]
tw_close = tour[i+1,3]
elapsed_time += travel_time
if elapsed_time <= tw_open:
elapsed_time = tw_open
elif elapsed_time > tw_close:
late_cities += 1
# Reward
return distance + 100000000*late_cities
# Swap city[i] with city[j] in sequence
def swap2opt(tsptw_sequence,i,j):
new_tsptw_sequence = np.copy(tsptw_sequence)
new_tsptw_sequence[i:j+1] = np.flip(tsptw_sequence[i:j+1], axis=0) # flip or swap ?
return new_tsptw_sequence
# One step of 2opt = one double loop and return first improved sequence
def step2opt(tsptw_sequence,speed):
seq_length = tsptw_sequence.shape[0]
distance = reward(tsptw_sequence,speed)
for i in range(1,seq_length-1):
for j in range(i+1,seq_length):
new_tsptw_sequence = swap2opt(tsptw_sequence,i,j)
new_distance = reward(new_tsptw_sequence,speed)
if new_distance < distance:
return new_tsptw_sequence
return tsptw_sequence
class DataGenerator(object):
# Initialize a DataGenerator
def __init__(self,config):
self.batch_size = config.batch_size
self.dimension = config.input_dimension
self.max_length = config.max_length
self.speed = config.speed
self.kNN = config.kNN # int for random k_nearest_neighbor
self.width = config.width_mean, config.width_std # time window width gaussian distribution [mean,std]
self.pretrain = config.pretrain
# Create Solver and Data Generator
self.solver = Solver(self.max_length, self.speed) # reference solver for TSP-TW (Google_OR_tools)
# Solve an instance with reference solver
def solve_instance(self, sequence, tw_open, tw_close):
# Calculate distance matrix
precision = 1 #20 #int(self.speed)
dist_array = pdist(sequence)
dist_matrix = squareform(dist_array)
# Call OR Tools to solve instance
demands = np.zeros(tw_open.size)
tour, tour_length, delivery_time = self.solver.run(precision*dist_matrix, demands, precision*(1+tw_open), precision*(1+tw_close)) # a tour is a permutation + start_index # Rq: +1 for depot offset
tour_length= tour_length/precision
delivery_time = np.asarray(delivery_time)/precision - 1 # offset -1 because depot opens at -1
return tour[:-1], tour_length, delivery_time
# Iterate step2opt max_iter times
def loop2opt(self, tsptw_sequence, max_iter=2000, speed=1.):
best_reward = reward(tsptw_sequence,speed)
new_tsptw_sequence = np.copy(tsptw_sequence)
for _ in range(max_iter):
new_tsptw_sequence = step2opt(new_tsptw_sequence,speed)
new_reward = reward(new_tsptw_sequence,speed)
if new_reward < best_reward:
best_reward = new_reward
else:
break
return new_tsptw_sequence, best_reward
def get_tour_length(self, sequence):
# Convert sequence to tour (end=start)
tour = np.concatenate((sequence,np.expand_dims(sequence[0],0)))
# Compute tour length
inter_city_distances = np.sqrt(np.sum(np.square(tour[:-1]-tour[1:]),axis=1))
return np.sum(inter_city_distances)
# Reorder sequence with random k NN (TODO: Less computations)
def k_nearest_neighbor(self, sequence):
# Calculate dist_matrix
dist_array = pdist(sequence)
dist_matrix = squareform(dist_array)
# Construct tour
new_sequence = [sequence[0]]
current_city = 0
visited_cities = [0]
for i in range(1,len(sequence)):
j = np.random.randint(0,min(len(sequence)-i,self.kNN))
next_city = [index for index in dist_matrix[current_city].argsort() if index not in visited_cities][j]
visited_cities.append(next_city)
new_sequence.append(sequence[next_city])
current_city = next_city
return np.asarray(new_sequence)
# Generate random TSP-TW instance
def gen_instance(self, test_mode=True, seed=0):
if seed!=0: np.random.seed(seed)
# Randomly generate (max_length+1) city integer coordinates in [0,100[ # Rq: +1 for depot
sequence = np.random.randint(100, size=(self.max_length+1, self.dimension))
if self.pretrain == False:
sequence = self.k_nearest_neighbor(sequence) # k nearest neighbour tour (reverse order - depot end)
# Principal Component Analysis to center & rotate coordinates
pca = PCA(n_components=self.dimension)
sequence_ = pca.fit_transform(sequence)
# TW constraint 1 (open time)
if self.pretrain == True:
tw_open = np.random.randint(100, size=(self.max_length, 1)) # t_open random integer in [0,100[
tw_open = np.concatenate((tw_open,[[-1]]), axis=0) # depot opens at -1
tw_open[::-1].sort(axis=0) # sort cities by TW open constraint (reverse order)
else: # Open time defined by kNN tour
ordered_seq = sequence[::-1]
inter_city_distances = np.sqrt(np.sum(np.square(ordered_seq[1:]-ordered_seq[:-1]),axis=1))
time_at_cities = np.cumsum(inter_city_distances/self.speed,axis=0)
time_at_cities = np.expand_dims(np.floor(time_at_cities).astype(int),axis=1)
tw_open = np.concatenate(([[-1]],time_at_cities), axis=0) # depot opens at -1
tw_open = tw_open[::-1] # TW open constraint sorted (reverse order) Rq: depot = tw_open[-1], tw_width[-1] and sequence[-1]
# TW constraint 2 (time width): Gaussian or uniform distribution
tw_width = np.abs(np.random.normal(loc=self.width[0], scale=self.width[1], size=(self.max_length, 1))) # gaussian distribution
tw_width = np.concatenate((tw_width,[[1]]), axis=0) # depot opened for 1
tw_width = np.ceil(tw_width).astype(int)
tw_close = tw_open+tw_width
# TW feature 1 = Centered mean time (invariance)
tw_mean_ = (tw_open+tw_close)/2
tw_mean_ -= np.mean(tw_mean_)
# TW feature 2 = Width
tw_width_ = tw_width
print(tw_width_)
# Concatenate input (sorted by time) and scale to [0,1[
input_ = np.concatenate((sequence_,tw_mean_,tw_width_), axis=1)/100
if test_mode == True:
return input_, sequence, tw_open, tw_close
else:
return input_
# Generate random batch for training procedure
def train_batch(self):
input_batch = []
for _ in range(self.batch_size):
# Generate random TSP-TW instance
input_ = self.gen_instance(test_mode=False)
# Store batch
input_batch.append(input_)
return input_batch
# Generate random batch for testing procedure
def test_batch(self, seed=0):
# Generate random TSP-TW instance
input_, or_sequence, tw_open, tw_close = self.gen_instance(test_mode=True, seed=seed)
# Store batch
input_batch = np.tile(input_,(self.batch_size,1,1))
return input_batch, or_sequence, tw_open, tw_close
# Plot a tour
def visualize_2D_trip(self,trip,tw_open,tw_close):
plt.figure(figsize=(30,30))
rcParams.update({'font.size': 22})
# Plot cities
colors = ['red'] # Depot is first city
for i in range(len(tw_open)-1):
colors.append('blue')
plt.scatter(trip[:,0], trip[:,1], color=colors, s=200)
# Plot tour
tour=np.array(list(range(len(trip))) + [0])
X = trip[tour, 0]
Y = trip[tour, 1]
plt.plot(X, Y,"--", markersize=100)
# Annotate cities with TW
tw_open = np.rint(tw_open)
tw_close = np.rint(tw_close)
time_window = np.concatenate((tw_open,tw_close),axis=1)
for tw, (x, y) in zip(time_window,(zip(X,Y))):
plt.annotate(tw,xy=(x, y))
plt.xlim(0,60)
plt.ylim(0,60)
plt.show()
# Heatmap of permutations (x=cities; y=steps)
def visualize_sampling(self,permutations):
max_length = len(permutations[0])
grid = np.zeros([max_length,max_length]) # initialize heatmap grid to 0
transposed_permutations = np.transpose(permutations)
for t, cities_t in enumerate(transposed_permutations): # step t, cities chosen at step t
city_indices, counts = np.unique(cities_t,return_counts=True,axis=0)
for u,v in zip(city_indices, counts):
grid[t][u]+=v # update grid with counts from the batch of permutations
# plot heatmap
fig = plt.figure()
rcParams.update({'font.size': 22})
ax = fig.add_subplot(1,1,1)
ax.set_aspect('equal')
plt.imshow(grid, interpolation='nearest', cmap='gray')
plt.colorbar()
plt.title('Sampled permutations')
plt.ylabel('Time t')
plt.xlabel('City i')
plt.show()
# Heatmap of attention (x=cities; y=steps)
def visualize_attention(self,attention):
# plot heatmap
fig = plt.figure()
rcParams.update({'font.size': 22})
ax = fig.add_subplot(1,1,1)
ax.set_aspect('equal')
plt.imshow(attention, interpolation='nearest', cmap='hot')
plt.colorbar()
plt.title('Attention distribution')
plt.ylabel('Step t')
plt.xlabel('Attention_t')
plt.show()
def load_Dumas(self,dir_='n20w100'):
dataset = {}
shrinkage = 80
for file_name in os.listdir('benchmark/'+dir_):
if 'solution' in file_name: continue
# Gather data
data = open('benchmark/'+dir_+'/'+file_name, 'r')
x,y,t_open,t_close = [],[],[],[]
for i,line in enumerate(data):
if i>5:
line = line.split()
if line[0]!='999':
x.append(int(float(line[1])))
y.append(int(float(line[2])))
t_open.append(int(float(line[4])))
t_close.append(int(float(line[5])))
# TW constraint 1 (open time)
t_open = np.asarray(t_open) # open time
sorted_index = np.argsort(t_open)[::-1] # sort cities by TW open constraint (reverse order)
tw_open = t_open[sorted_index]
tw_open = np.expand_dims(tw_open,axis=1)
tw_open_ = shrinkage*tw_open/tw_open[0]-1 # scale open time in [0,100[ (depot opens at -1)
# TW constraint 2 (close time) ############################### RESCALE ??
t_close = np.asarray(t_close)-1 ############################### depot ?
tw_close = t_close[sorted_index]
tw_close = np.expand_dims(tw_close,axis=1)
tw_close_ = shrinkage*tw_close/tw_open[0]-1 # scale close time
tw_close_[-1] = 0 # depot open till 0
# Coordinates
seq = np.stack((x,y),axis=1) # city integer coordinates in [0,100[ ############################### RESCALE ??
sequence = seq[sorted_index]
pca = PCA(n_components=self.dimension) # Principal Component Analysis to center & rotate coordinates
sequence_ = pca.fit_transform(sequence)
sequence_ = self.speed*shrinkage*sequence_/tw_open[0] # scale sequence
# TW feature 1 = Centered mean time (invariance)
tw_mean_ = (tw_open_+tw_close_)/2
tw_mean_ -= np.mean(tw_mean_)
# TW feature 2 = Width
tw_width_ = tw_close_-tw_open_
# Concatenate input (sorted by time) and scale to [0,1[
input_ = np.concatenate((sequence_,tw_mean_,tw_width_), axis=1)/100
# Gather solution
solution = open('benchmark/'+dir_+'/'+file_name+'.solution', 'r')
for i,line in enumerate(solution):
if i==0: opt_permutation = np.asarray(line.split()).astype(int)-1
if i==1: opt_length = int(line.split()[0])
opt_length = self.get_tour_length(seq[opt_permutation])/100
# Save data
dataset[file_name]={'input_': input_, 'sequence':sequence, 'tw_open':tw_open, 'tw_close':tw_close, 'optimal_sequence':seq[opt_permutation],
'optimal_tw_open':np.expand_dims(t_open[opt_permutation],axis=1), 'optimal_tw_close':np.expand_dims(t_close[opt_permutation],axis=1), 'optimal_length':opt_length}
return dataset
if __name__ == "__main__":
# Config
config, _ = get_config()
dataset = DataGenerator(config)
# Generate some data
#input_batch = dataset.train_batch()
input_batch, or_sequence, tw_open, tw_close = dataset.test_batch(seed=0)
print()
# Some print
#print('Input batch: \n',100*input_batch)
#print(np.rint(np.mean(100*input_batch,1)))
# 2D plot for coord batch
#dataset.visualize_2D_trip(or_sequence[::-1], tw_open[::-1], tw_close[::-1])
# Solve to optimality and plot solution
#or_permutation, or_tour_length, or_delivery_time = dataset.solve_instance(or_sequence, tw_open, tw_close)
#print('Solver tour length: \n', or_tour_length/100)
#print('Time var: \n', or_delivery_time)
#dataset.visualize_2D_trip(or_sequence[or_permutation], tw_open[or_permutation], tw_close[or_permutation])
#dataset.visualize_sampling([or_permutation])
dataset.load_Dumas()