Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/drt/darpSolvers.py
169674 views
1
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
2
# Copyright (C) 2007-2025 German Aerospace Center (DLR) and others.
3
# This program and the accompanying materials are made available under the
4
# terms of the Eclipse Public License 2.0 which is available at
5
# https://www.eclipse.org/legal/epl-2.0/
6
# This Source Code may also be made available under the following Secondary
7
# Licenses when the conditions for such availability set forth in the Eclipse
8
# Public License 2.0 are satisfied: GNU General Public License, version 2
9
# or later which is available at
10
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
11
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
12
13
# @file darpSolvers.py
14
# @author Giuliana Armellini
15
# @date 2021-05-27
16
17
"""
18
Contains the methods to solve the Dial a Ride Problem (DARP) of the
19
drtOnline.py tool.
20
Methods available: exhaustive_search and simple_rerouting
21
Track progress https://github.com/eclipse/sumo/issues/8256
22
"""
23
24
from __future__ import print_function
25
import os
26
import sys
27
try:
28
from time import perf_counter
29
except ImportError:
30
from time import clock as perf_counter
31
32
if 'SUMO_HOME' in os.environ:
33
tools = os.path.join(os.environ['SUMO_HOME'], 'tools')
34
sys.path.append(tools)
35
else:
36
sys.exit("please declare environment variable 'SUMO_HOME'")
37
import traci # noqa
38
findRoute = traci.simulation.findRoute
39
40
41
def res_res_pair(options, res1, res2, veh_type, veh_time_pickup,
42
veh_time_dropoff, rv_dict, pairs_dua_times):
43
"""
44
Search all combinations between two reservations. Notation: the first of
45
the reservations is defined as 'res1' and the second as 'res2' and 'p'
46
refers to pick up and 'd' to drop off a reservation. Then 'res2p_res1d'
47
means pickup reservation 2 and then drop off reservation 1.
48
"""
49
50
# combination 1p2p 2p2d 2d1d
51
if (res1.tw_pickup[0] <= res2.tw_pickup[1] and
52
res2.tw_dropoff[0] <= res1.tw_dropoff[1]):
53
# if earliest pick up of req 1 before latest pick up of req 2 and
54
# if earliest drop off of req 2 before latest drop off of req 1
55
56
# calculate travel times for each pair
57
res1p_res2p = pairs_dua_times.get("%s_%s" % (res1.fromEdge,
58
res2.fromEdge))
59
if res1p_res2p is None:
60
res1p_res2p = int(findRoute(res1.fromEdge, res2.fromEdge, veh_type,
61
routingMode=options.routing_mode).travelTime)
62
res1p_res2p += veh_time_pickup
63
res2p_res2d = rv_dict.get('%sy_%sz' % (res2.id, res2.id))
64
if res2p_res2d is None:
65
res2p_res2d = res2.direct + veh_time_dropoff
66
pair = '%sy_%sz' % (res2.id, res2.id) # 2p2d
67
rv_dict[pair] = [res2p_res2d, -1, [res2.id, res2.id]]
68
else:
69
res2p_res2d = res2p_res2d[0]
70
71
res2d_res1d = pairs_dua_times.get("%s_%s" % (res2.toEdge, res1.toEdge))
72
if res2d_res1d is None:
73
res2d_res1d = int(findRoute(res2.toEdge, res1.toEdge, veh_type,
74
routingMode=options.routing_mode).travelTime)
75
res2d_res1d += veh_time_dropoff
76
77
# check if time windows constrains are fulfilled
78
time_res2p = res1.tw_pickup[0] + res1p_res2p
79
if time_res2p > res2.tw_pickup[1]:
80
pass # not possible
81
elif (time_res2p + res2p_res2d) > res2.tw_dropoff[1]:
82
pass # not possible
83
elif (time_res2p + res2p_res2d + res2d_res1d) > res1.tw_dropoff[1]:
84
pass # not possible
85
else:
86
# pairs are possible
87
pair = '%sy_%sy' % (res1.id, res2.id) # 1p2p
88
rv_dict[pair] = [res1p_res2p, 1, [res1.id, res2.id]]
89
pair = '%sz_%sz' % (res2.id, res1.id) # 2d1d
90
rv_dict[pair] = [res2d_res1d, -1, [res2.id, res1.id]]
91
92
# combination 1p2p 2p1d 1d2d
93
if (res1.tw_pickup[0] <= res2.tw_pickup[1] and
94
res1.tw_dropoff[0] <= res2.tw_dropoff[1]):
95
# if earliest pick up of req 1 before latest pick up of req 2 and
96
# if earliest drop off of req 1 before latest drop off of req 2
97
98
# calculate travel times for each pair
99
res1p_res2p = rv_dict.get('%sy_%sy' % (res1.id, res2.id))
100
if res1p_res2p is None:
101
res1p_res2p = pairs_dua_times.get("%s_%s" % (res1.fromEdge,
102
res2.fromEdge))
103
if res1p_res2p is None:
104
res1p_res2p = int(findRoute(res1.fromEdge, res2.fromEdge, veh_type,
105
routingMode=options.routing_mode).travelTime)
106
res1p_res2p += veh_time_pickup
107
else:
108
res1p_res2p = res1p_res2p[0]
109
110
res2p_res1d = pairs_dua_times.get("%s_%s" % (res2.fromEdge,
111
res1.toEdge))
112
if res2p_res1d is None:
113
res2p_res1d = int(findRoute(res2.fromEdge, res1.toEdge, veh_type,
114
routingMode=options.routing_mode).travelTime)
115
res2p_res1d += veh_time_dropoff
116
117
res1d_res2d = pairs_dua_times.get("%s_%s" % (res1.toEdge, res2.toEdge))
118
if res1d_res2d is None:
119
res1d_res2d = int(findRoute(res1.toEdge, res2.toEdge, veh_type,
120
routingMode=options.routing_mode).travelTime)
121
res1d_res2d += veh_time_dropoff
122
123
# check if time windows constrains are fulfilled
124
time_res2p = res1.tw_pickup[0] + res1p_res2p
125
if time_res2p > res2.tw_pickup[1]:
126
pass # not possible
127
elif (time_res2p + res2p_res1d) > res1.tw_dropoff[1]:
128
pass # not possible
129
elif (time_res2p + res2p_res1d + res1d_res2d) > res2.tw_dropoff[1]:
130
pass # not possible
131
else:
132
# pairs are possible
133
pair = '%sy_%sy' % (res1.id, res2.id) # 1p2p
134
if rv_dict.get(pair) is None:
135
rv_dict[pair] = [res1p_res2p, 1, [res1.id, res2.id]]
136
pair = '%sy_%sz' % (res2.id, res1.id) # 2p1d
137
rv_dict[pair] = [res2p_res1d, -1, [res2.id, res1.id]]
138
pair = '%sz_%sz' % (res1.id, res2.id) # 1d2d
139
rv_dict[pair] = [res1d_res2d, -1, [res1.id, res2.id]]
140
141
# combination 2p1p 1p2d 2d1d
142
if (res2.tw_pickup[0] <= res1.tw_pickup[1] and
143
res2.tw_dropoff[0] <= res1.tw_dropoff[1]):
144
# if earliest pick up of req 2 before latest pick up of req 1 and
145
# if earliest drop off of req 2 before latest drop off of req 1
146
147
# calculate travel times for each pair
148
res2p_res1p = pairs_dua_times.get("%s_%s" % (res2.fromEdge,
149
res1.fromEdge))
150
if res2p_res1p is None:
151
res2p_res1p = int(findRoute(res2.fromEdge, res1.fromEdge, veh_type,
152
routingMode=options.routing_mode).travelTime)
153
res2p_res1p += veh_time_pickup
154
155
res1p_res2d = pairs_dua_times.get("%s_%s" % (res1.fromEdge,
156
res2.toEdge))
157
if res1p_res2d is None:
158
res1p_res2d = int(findRoute(res1.fromEdge, res2.toEdge, veh_type,
159
routingMode=options.routing_mode).travelTime)
160
res1p_res2d += veh_time_dropoff
161
162
res2d_res1d = rv_dict.get('%sz_%sz' % (res2.id, res1.id))
163
if res2d_res1d is None:
164
# if 2d1d not added to dict in steps before
165
res2d_res1d = pairs_dua_times.get("%s_%s" % (res2.toEdge,
166
res1.toEdge))
167
if res2d_res1d is None:
168
res2d_res1d = int(findRoute(res2.toEdge, res1.toEdge, veh_type,
169
routingMode=options.routing_mode).travelTime)
170
res2d_res1d += veh_time_dropoff
171
else:
172
res2d_res1d = res2d_res1d[0]
173
174
# check if time windows constrains are fulfilled
175
time_res1p = res2.tw_pickup[0] + res2p_res1p
176
if time_res1p > res1.tw_pickup[1]:
177
pass # not possible
178
elif (time_res1p + res1p_res2d) > res2.tw_dropoff[1]:
179
pass # not possible
180
elif (time_res1p + res1p_res2d + res2d_res1d) > res1.tw_dropoff[1]:
181
pass # not possible
182
else:
183
# pairs are possible
184
pair = '%sy_%sy' % (res2.id, res1.id) # 2p1p
185
rv_dict[pair] = [res2p_res1p, 1, [res2.id, res1.id]]
186
pair = '%sy_%sz' % (res1.id, res2.id) # 1p2d
187
rv_dict[pair] = [res1p_res2d, -1, [res1.id, res2.id]]
188
pair = '%sz_%sz' % (res1.id, res2.id) # 2d1d
189
if rv_dict.get(pair) is None:
190
rv_dict[pair] = [res2d_res1d, -1, [res2.id, res1.id]]
191
192
# combination 2p1p 1p1d 1d2d
193
if (res2.tw_pickup[0] <= res1.tw_pickup[1] and
194
res1.tw_dropoff[0] <= res2.tw_dropoff[1]):
195
# if earliest pick up of req 2 before latest pick up of req 1 and
196
# if earliest drop off of req 1 before latest drop off of req 2
197
198
# calculate travel times for each pair
199
res2p_res1p = rv_dict.get('%sy_%sy' % (res2.id, res1.id), False)
200
res1d_res2d = rv_dict.get('%sz_%sz' % (res1.id, res2.id), False)
201
res1p_res1d = rv_dict.get('%sy_%sz' % (res1.id, res1.id), False)
202
if not res1p_res1d:
203
res1p_res1d = res1.direct + veh_time_dropoff
204
pair = '%sy_%sz' % (res1.id, res1.id) # 1p1d
205
rv_dict[pair] = [res1p_res1d, -1, [res1.id, res1.id]]
206
else:
207
res1p_res1d = res1p_res1d[0]
208
209
if not res2p_res1p or not res1d_res2d:
210
# if 2p1p 1d2d not added to dict in steps before
211
if not res2p_res1p:
212
res2p_res1p = pairs_dua_times.get("%s_%s" % (res2.fromEdge,
213
res1.fromEdge))
214
if res2p_res1p is None:
215
res2p_res1p = int(findRoute(res2.fromEdge, res1.fromEdge,
216
veh_type, routingMode=options.routing_mode).travelTime)
217
res2p_res1p += veh_time_pickup
218
else:
219
res2p_res1p = res2p_res1p[0]
220
if not res1d_res2d:
221
res1d_res2d = pairs_dua_times.get("%s_%s" % (res1.toEdge,
222
res2.toEdge))
223
if res1d_res2d is None:
224
res1d_res2d = int(findRoute(res1.toEdge, res2.toEdge,
225
veh_type, routingMode=options.routing_mode).travelTime)
226
res1d_res2d += veh_time_dropoff
227
else:
228
res1d_res2d = res1d_res2d[0]
229
res1p_res1d = rv_dict.get('%sy_%sz' % (res1.id, res1.id))[0]
230
231
# check if time windows constrains are fulfilled
232
time_res1p = res2.tw_pickup[0] + res2p_res1p
233
if time_res1p > res1.tw_pickup[1]:
234
pass # not possible
235
elif (time_res1p + res1p_res1d) > res1.tw_dropoff[1]:
236
pass # not possible
237
elif (time_res1p + res1p_res1d + res1d_res2d) > res2.tw_dropoff[1]:
238
pass # not possible
239
else:
240
# pairs are possible
241
pair = '%sy_%sy' % (res2.id, res1.id) # 2p1p
242
if not rv_dict.get(pair, False):
243
rv_dict[pair] = [res2p_res1p, 1, [res2.id, res1.id]]
244
pair = '%sz_%sz' % (res1.id, res2.id) # 1d2d
245
if not rv_dict.get(pair, False):
246
rv_dict[pair] = [res1d_res2d, -1, [res1.id, res2.id]]
247
248
# pair 1d2p
249
if res1.tw_dropoff[0] <= res2.tw_pickup[1]:
250
# if earliest drop off of req 1 before latest pick up of req 2
251
252
# calculate travel times for each pair
253
res1d_res2p = pairs_dua_times.get("%s_%s" % (res1.toEdge,
254
res2.fromEdge))
255
if res1d_res2p is None:
256
res1d_res2p = int(findRoute(res1.toEdge, res2.fromEdge, veh_type,
257
routingMode=options.routing_mode).travelTime)
258
res1d_res2p += veh_time_pickup
259
260
# check if time windows constrains are fulfilled
261
if (res1.tw_dropoff[0] + res1d_res2p) < res2.tw_pickup[1]:
262
pair = '%sz_%sy' % (res1.id, res2.id) # 1d2p
263
rv_dict[pair] = [res1d_res2p, 1, [res1.id, res2.id]]
264
265
# pair 2d1p
266
if res2.tw_dropoff[0] <= res1.tw_pickup[1]:
267
# if earliest drop off of req 2 before latest pick up of req 1
268
269
# calculate travel times for each pair
270
res2d_res1p = pairs_dua_times.get("%s_%s" % (res2.toEdge,
271
res1.fromEdge))
272
if res2d_res1p is None:
273
res2d_res1p = int(findRoute(res2.toEdge, res1.fromEdge, veh_type,
274
routingMode=options.routing_mode).travelTime)
275
res2d_res1p += veh_time_pickup
276
277
# check if time windows constrains are fulfilled
278
if (res2.tw_dropoff[0] + res2d_res1p) < res1.tw_pickup[1]:
279
pair = '%sz_%sy' % (res2.id, res1.id) # 2d1p
280
rv_dict[pair] = [res2d_res1p, 1, [res2.id, res1.id]]
281
282
283
def get_rv(options, res_id_new, res_id_picked, res_all,
284
veh_type, veh_time_pickup, veh_time_dropoff, rv_dict, step,
285
veh_edges, pairs_dua_times):
286
"""
287
Generates the reservation-vehicle graph, which has the possible
288
combinations between two reservations and between a reservation and a
289
vehicle with the required travel time, the number of passengers picked up
290
or dropped off and the reservation id and/or vehicle id of the pair.
291
"""
292
# remove reservations already served or rejected due to processing time
293
id_current = []
294
[id_current.extend(veh_id) for veh_id in veh_edges.values()]
295
id_current.extend(res_all.keys())
296
[rv_dict.pop(key) for key in list(rv_dict)
297
if set(rv_dict[key][2]) - set(id_current)]
298
299
res_rv_remove = []
300
res_all_remove = []
301
for res_id, res in res_all.items():
302
if res_id in res_id_new:
303
# if reservation is new
304
305
# check if reservation can be serve for a vehicle on time
306
res_possible = False
307
# add vehicle-reservation pairs
308
for edge, vehicles in veh_edges.items():
309
pickup_time = pairs_dua_times.get("%s_%s" % (edge,
310
res.fromEdge))
311
if pickup_time is None:
312
pickup_time = findRoute(edge, res.fromEdge, veh_type,
313
routingMode=options.routing_mode).travelTime
314
if step+pickup_time > res.tw_pickup[1]:
315
# if vehicle arrives to late
316
continue
317
# if vehicle on time, add to rv graph
318
for veh_id in vehicles:
319
route_id = '%s_%sy' % (veh_id, res_id)
320
rv_dict[route_id] = [pickup_time+veh_time_pickup, 1, [veh_id, res_id]]
321
res_possible = True
322
323
if not res_possible:
324
# reject reservation and remove person from simulation
325
# TODO add no rejection option #8705
326
print("Reservation %s (person %s) cannot be served" %
327
(res_id, res.persons))
328
res_all_remove.append(res_id)
329
res_rv_remove.extend(['%sy' % res_id, '%sz' % res_id])
330
for person in res.persons:
331
traci.person.removeStages(person)
332
continue
333
334
# add direct route pair
335
route_id = '%sy_%sz' % (res_id, res_id) # y: pickup / z: drop off
336
rv_dict[route_id] = [res.direct+veh_time_dropoff, -1, [res_id, res_id]]
337
338
# add reservation-reservation pairs
339
reservations2 = set(res_all.keys()) ^ set(res_all_remove)
340
for res2 in reservations2:
341
if res2 != res_id:
342
res_res_pair(options, res, res_all.get(res2), veh_type,
343
veh_time_pickup, veh_time_dropoff,
344
rv_dict, pairs_dua_times)
345
346
elif not res.vehicle:
347
# if reservation not assigned
348
# check if pick-up still possible
349
if res.tw_pickup[1] < step:
350
# latest pickup time exceed simulation time, reject reservation
351
# TODO add no rejection option #8705
352
print("Reservation %s (person %s) cannot be served" %
353
(res_id, res.persons))
354
res_all_remove.append(res_id)
355
res_rv_remove.extend(['%sy' % res_id, '%sz' % res_id])
356
for person in res.persons:
357
traci.person.removeStages(person)
358
continue
359
360
remove = True
361
for edge, vehicles in veh_edges.items():
362
pickup_time = pairs_dua_times.get("%s_%s" % (edge,
363
res.fromEdge))
364
if pickup_time is None:
365
pickup_time = findRoute(edge, res.fromEdge, veh_type,
366
routingMode=options.routing_mode).travelTime
367
if step+pickup_time <= res.tw_pickup[1]:
368
# if vehicle on time, add to rv graph
369
for veh_id in vehicles:
370
route_id = '%s_%sy' % (veh_id, res_id)
371
if rv_dict.get(route_id, False):
372
rv_dict[route_id][0] = pickup_time+60
373
remove = False
374
else:
375
# remove pair if pick-up not possible
376
for veh_id in vehicles:
377
route_id = '%s_%sy' % (veh_id, res_id)
378
if rv_dict.get(route_id, False):
379
rv_dict.pop(route_id)
380
381
if remove:
382
# if no vehicles can pick up on time -> reject reservation
383
print("Reservation %s (person %s) cannot be served" %
384
(res_id, res.persons))
385
res_all_remove.append(res_id)
386
res_rv_remove.extend(['%sy' % res_id, '%sz' % res_id])
387
for person in res.persons:
388
traci.person.removeStages(person)
389
390
elif res_id not in res_id_picked:
391
# if reservation assigned but not picked up
392
route_id = '%s_%sy' % (res.vehicle, res_id)
393
if rv_dict.get(route_id, False):
394
# update travel time to pickup
395
if traci.vehicle.getRoadID(res.vehicle).startswith(':'):
396
# avoid routing error when in intersection TODO #5829
397
edge_index = traci.vehicle.getRouteIndex(res.vehicle) + 1
398
from_edge = traci.vehicle.getRoute(res.vehicle)[edge_index]
399
else:
400
from_edge = traci.vehicle.getRoadID(res.vehicle)
401
pickup_time = pairs_dua_times.get("%s_%s" % (from_edge,
402
res.fromEdge))
403
if pickup_time is None:
404
pickup_time = findRoute(from_edge, res.fromEdge, veh_type,
405
routingMode=options.routing_mode).travelTime
406
rv_dict[route_id][0] = pickup_time+veh_time_pickup
407
if options.verbose and step+pickup_time > res.tw_pickup[1]: # Debug only
408
print("Time window surpassed by", step+pickup_time - res.tw_pickup[1])
409
410
elif res_id in res_id_picked:
411
# if reservation picked up, delete pick-up pair
412
res_rv_remove.append('%sy' % res_id)
413
# update travel time to drop off
414
if traci.vehicle.getRoadID(res.vehicle).startswith(':'):
415
# avoid routing error when in intersection TODO #5829
416
edge_index = traci.vehicle.getRouteIndex(res.vehicle) + 1
417
from_edge = traci.vehicle.getRoute(res.vehicle)[edge_index]
418
else:
419
from_edge = traci.vehicle.getRoadID(res.vehicle)
420
dropoff_time = pairs_dua_times.get("%s_%s" % (from_edge,
421
res.toEdge))
422
if dropoff_time is None:
423
dropoff_time = int(findRoute(from_edge, res.toEdge, veh_type,
424
routingMode=options.routing_mode).travelTime)
425
route_id = '%s_%sz' % (res.vehicle, res_id)
426
rv_dict[route_id] = [dropoff_time+veh_time_dropoff, -1, [res.vehicle, res_id]]
427
else:
428
print("Error: Reservation state not considered")
429
430
# remove rejected, served and picked up reservations from rv graph
431
if res_rv_remove:
432
[rv_dict.pop(key) for key in list(rv_dict) if set(res_rv_remove) & set(key.split("_"))]
433
434
# remove rejected reservations from res_all
435
if res_all_remove:
436
[res_all.pop(key) for key in res_all_remove]
437
438
439
def simple_rerouting(options, res_id_unassigned, res_id_picked,
440
res_all, fleet, rv_dict, step):
441
"""
442
Search possible trips allowing vehicles to change their trip in real time
443
(rerouting). Not all trips are covered, being the result not exact.
444
"""
445
rtv_dict = {}
446
trip_id = {}
447
# remove reservations already served or rejected due to processing time
448
id_current = []
449
id_current.extend(fleet)
450
id_current.extend(res_all.keys())
451
[rv_dict.pop(key) for key in list(rv_dict)
452
if set(rv_dict[key][2]) - set(id_current)]
453
454
# list with all not served requests needed for res_bin for ILP
455
rtv_res = list(res_all.keys())
456
for veh_id in fleet:
457
veh_bin = [0] * len(fleet) # list of assigned vehicles for ILP
458
veh_bin[fleet.index(veh_id)] = 1
459
veh_capacity = (traci.vehicle.getPersonCapacity(veh_id)
460
- traci.vehicle.getPersonNumber(veh_id))
461
462
# search possible pairs to consider
463
# filter valid reservations id and vehicle id
464
res_assigned_veh = [res_id for res_id, res in res_all.items()
465
if res.vehicle == veh_id]
466
467
filter_valid = list(res_id_unassigned) # add unassigned reservations
468
filter_valid.append(veh_id) # add vehicle element
469
filter_valid.extend(res_assigned_veh) # add reservations assigned
470
471
# possible pairs to consider, sort by increasing travel time
472
pairs = [pair_id for pair_id, pair in
473
sorted(rv_dict.items(), key=lambda pair: pair[1][0])
474
if pair[2][0] in filter_valid and pair[2][1] in filter_valid]
475
476
# reservations already picked up by the vehicle
477
res_picked_veh = list(set(res_assigned_veh) & set(res_id_picked))
478
479
first_pairs = []
480
# get first pairs
481
if res_assigned_veh:
482
# if requests assigned, changes only possible after second stops
483
# to avoid endless detours. A better way for allow small detours
484
# should be consider. TODO for example: tt 1 2 equivalent to
485
# tt 3 1 2, if so, add key to first pairs
486
for next_stop in traci.vehicle.getStops(veh_id):
487
next_act = next_stop.actType.split(",")[0].split(" ")[0]
488
next_id = next_stop.actType.split(",")[0].split(" ")[-1][1:-1]
489
if next_act == 'pickup' and next_id in res_id_picked:
490
# person already picked up, consider next stop
491
continue
492
elif next_act == 'dropOff' and next_id not in res_all.keys():
493
# person already dropped off, consider next stop
494
continue
495
else:
496
# next stop valid
497
if next_act == 'pickup':
498
first_pairs = ["%s_%sy" % (veh_id, next_id)]
499
else:
500
first_pairs = ["%s_%sz" % (veh_id, next_id)]
501
break
502
else:
503
# if not, consider all possible
504
first_pairs = [pair for pair in pairs
505
if pair.split("_")[0] == veh_id]
506
507
for first_pair in first_pairs:
508
# search all possible trips ids starting with this
509
trip = first_pair
510
i = 1
511
while (i <= (len(filter_valid)-1)*2 and
512
len(trip.split("_"))-1 < (len(filter_valid)-1)*2):
513
for pair in pairs:
514
if pair.split("_")[0] != trip.split("_")[-1]:
515
continue # if pairs are not compatible
516
517
trip_new = ("_").join([trip, pair.split("_")[-1]])
518
519
# check if pick up before drop off
520
stops = trip_new.split("_")
521
# consider picked requests
522
trip_pick = list(res_picked_veh)
523
trip_pick.extend([stop[:-1] for stop in trip_new.split('_')[1:]
524
if stop.endswith("y")])
525
trip_drop = [stop[:-1] for stop in trip_new.split('_')[1:]
526
if stop.endswith("z")]
527
if (len(trip_pick) != len(set(trip_pick))
528
or len(trip_drop) != len(set(trip_drop))):
529
continue # stop already in trip
530
531
abort = False
532
for stop in trip_drop:
533
try:
534
if stop in res_picked_veh:
535
# if request already picked up, set index to -1
536
index_pu = -1
537
else:
538
index_pu = stops.index(('%sy' % stop))
539
index_do = stops.index(('%sz' % stop))
540
except ValueError:
541
abort = True
542
break # if stop not even picked up
543
544
if index_pu > index_do:
545
# dropped off before pick up
546
# TODO shouldn't it be covered by time windows?
547
abort = True
548
break
549
if abort:
550
continue
551
552
# calculate number of passengers (pax)
553
# and travel time (trip_time)
554
if trip == first_pair:
555
pax = rv_dict[first_pair][1] + rv_dict[pair][1]
556
trip_time = (rv_dict[first_pair][0] + step
557
+ rv_dict[pair][0])
558
else:
559
pax = trip_id[trip][1] + rv_dict[pair][1]
560
trip_time = trip_id[trip][0] + rv_dict[pair][0]
561
562
# check capacity
563
if pax > veh_capacity:
564
continue # capacity surpass
565
566
# check time window for stop
567
stop_id = pair.split("_")[-1]
568
if stop_id.endswith('y'):
569
stop_tw = res_all[stop_id[:-1]].tw_pickup
570
if trip_time > stop_tw[1]:
571
continue # max stop time surpass
572
elif trip_time < stop_tw[0] and pax == 1:
573
# if vehicle is too early at stop, it can wait
574
# if it is empty (equivalent to pax == 1)
575
trip_time = stop_tw[0] # waiting time at stop
576
else:
577
stop_tw = res_all[stop_id[:-1]].tw_dropoff
578
if trip_time > stop_tw[1]:
579
continue # maximum stop time surpass
580
# TODO trip_time < stop_tw[0] only relevant if drop off time definition is implemented
581
582
# add trip id
583
trip_id[trip_new] = [trip_time, pax]
584
585
# if len(trip_new.split("_")) % 2 == 1:
586
# check if all reservation served
587
if set(trip_pick) == set(trip_drop):
588
# if requests assigned to vehicle, they must be on trip
589
# picked requests already checked with trip_pick
590
if (set(res_assigned_veh)
591
!= set(trip_pick) & set(res_assigned_veh)):
592
# trip not possible
593
continue
594
else:
595
# add possible trip to dict
596
res_bin = [0] * len(rtv_res) # reservation in trip
597
for stop in trip_pick:
598
res_bin[rtv_res.index(stop)] = 1
599
trip_cost = trip_time - sum(res_bin)*options.c_ko
600
601
# rtv_dict format:
602
# trip_id: travel time, vehicle, reservations, cost
603
rtv_dict[trip_new] = [trip_time, veh_bin, res_bin,
604
trip_cost]
605
606
trip = trip_new
607
i += 1
608
if len(fleet) == 1 and rtv_dict:
609
# if one vehicle problem, assign the fastest trip with
610
# max reservations served (equivalent to minor trip_cost)
611
trips_list = list(rtv_dict.keys())
612
costs_list = [value[3] for key, value in rtv_dict.items()]
613
trip_index = costs_list.index(min(costs_list))
614
trip_id = trips_list[trip_index]
615
return {trip_id: rtv_dict[trip_id]}, rtv_res
616
617
return rtv_dict, rtv_res
618
619
620
def search_trips(trip, pairs, res_assigned_veh, res_picked_veh, res_all,
621
rv_dict, rtv_res, veh_capacity, veh_bin, rtv_dict, trips_tree,
622
options, start_time):
623
"""
624
Search all possible new trips by adding a new stop to the current trip.
625
If the maximum search time given is not surpass, all possible trips are
626
consider.
627
"""
628
# trip means [trip_id, trip_time, trip_pax]
629
trip_id, trip_time, trip_pax = trip
630
for pair in pairs:
631
if (perf_counter() - start_time) > options.rtv_time:
632
# limit trip search time
633
break
634
if pair.split("_")[0] != trip_id.split("_")[-1]:
635
continue # if pairs are not compatible
636
637
new_trip_id = ("_").join([trip_id, pair.split("_")[-1]])
638
639
# check if pick up before drop off
640
stops = new_trip_id.split("_")
641
# consider picked requests
642
trip_pick = list(res_picked_veh)
643
trip_pick.extend([stop[:-1] for stop in new_trip_id.split('_')[1:]
644
if stop.endswith("y")])
645
trip_drop = [stop[:-1] for stop in new_trip_id.split('_')[1:]
646
if stop.endswith("z")]
647
if (len(trip_pick) != len(set(trip_pick))
648
or len(trip_drop) != len(set(trip_drop))):
649
continue # stop already in trip
650
651
abort = False
652
for stop in trip_drop:
653
try:
654
if stop in res_picked_veh:
655
# if request already picked up, set index to -1
656
index_pu = -1
657
else:
658
index_pu = stops.index(('%sy' % stop))
659
index_do = stops.index(('%sz' % stop))
660
except ValueError:
661
abort = True
662
break # if stop not even picked up
663
664
if index_pu > index_do:
665
# dropped off before pick up
666
# TODO shouldn't it be covered by time windows?
667
abort = True
668
break
669
if abort:
670
continue
671
672
# calculate number of passengers (pax)
673
# and travel time (trip_time)
674
new_trip_pax = trip_pax + rv_dict[pair][1]
675
new_trip_time = trip_time + rv_dict[pair][0]
676
677
# check capacity
678
if new_trip_pax > veh_capacity:
679
continue # capacity surpass
680
681
# check time window for stop
682
stop_id = pair.split("_")[-1]
683
if stop_id.endswith('y'):
684
stop_tw = res_all[stop_id[:-1]].tw_pickup
685
if new_trip_time > stop_tw[1]:
686
continue # max stop time surpass
687
elif new_trip_time < stop_tw[0] and new_trip_pax == 1:
688
# if vehicle is too early at stop, it can wait
689
# if it is empty (equivalent to pax == 1)
690
new_trip_time = stop_tw[0] # waiting time at stop
691
else:
692
stop_tw = res_all[stop_id[:-1]].tw_dropoff
693
if new_trip_time > stop_tw[1]:
694
continue # max stop time surpass
695
# TODO trip_time < stop_tw[0] only relevant if drop off time definition is implemented
696
697
# add trip id to tree
698
trips_tree.append([new_trip_id, new_trip_time, new_trip_pax])
699
700
# check if all reservation served
701
if set(trip_pick) == set(trip_drop):
702
# if requests assigned to vehicle, they must be on trip
703
# picked requests already checked with extension of trip_pick
704
if set(res_assigned_veh) != set(trip_pick) & set(res_assigned_veh):
705
# trip not possible
706
continue
707
else:
708
# add possible trip to dict
709
res_bin = [0] * len(rtv_res) # reservation in trip
710
for stop in trip_pick:
711
res_bin[rtv_res.index(stop)] = 1
712
trip_cost = new_trip_time - sum(res_bin)*options.c_ko
713
714
# rtv_dict format:
715
# trip_id: travel time, vehicle, reservations, cost
716
rtv_dict[new_trip_id] = [new_trip_time, veh_bin, res_bin,
717
trip_cost]
718
719
return trips_tree, rtv_dict
720
721
722
def exhaustive_search(options, res_id_unassigned, res_id_picked, res_all,
723
fleet, rv_dict, step, memory_problems, veh_edges):
724
"""
725
Search all possible trips for each vehicle allowing dynamic rerouting.
726
If the maximum search time given is not surpass, the method is exact.
727
"""
728
729
rtv_dict = {}
730
# list with all requests needed for res_bin for ILP
731
rtv_res = list(res_all.keys())
732
733
# find unique vehicles to avoid calculating same trips multiple times
734
idle_veh = traci.vehicle.getTaxiFleet(0)
735
for vehicles in veh_edges.values():
736
# equivalent vehicles must be on same edge
737
if len(vehicles) > 1:
738
vehicles_capacity = [traci.vehicle.getPersonCapacity(veh_id) for veh_id in vehicles]
739
vehicles_idle = [veh_id in idle_veh for veh_id in vehicles]
740
vehicles_unique = []
741
for veh_idx, veh_id in enumerate(vehicles):
742
if veh_idx == 0:
743
vehicles_unique.append(veh_id)
744
continue
745
if not vehicles_idle[veh_idx]:
746
# if veh not idle, then is unique
747
vehicles_unique.append(veh_id)
748
continue
749
for compare_veh in range(veh_idx+1):
750
if (vehicles_idle[compare_veh] and
751
vehicles_capacity[veh_idx] == vehicles_capacity[compare_veh]):
752
# if vehicles idle and same capacity -> equivalent
753
vehicles_unique.append(vehicles[compare_veh])
754
break
755
else:
756
vehicles_unique = vehicles
757
758
# find routes for unique vehicles
759
for veh_id in set(vehicles_unique):
760
veh_bin = [0] * len(fleet) # list of assigned vehicles for ILP
761
veh_bin[fleet.index(veh_id)] = 1
762
veh_capacity = (traci.vehicle.getPersonCapacity(veh_id)
763
- traci.vehicle.getPersonNumber(veh_id))
764
765
# search possible pairs to consider
766
# filter valid reservations id and vehicle id
767
res_assigned_veh = [res_id for res_id, res in res_all.items()
768
if res.vehicle == veh_id]
769
770
filter_valid = list(res_id_unassigned) # add unassigned
771
filter_valid.append(veh_id) # add vehicle
772
filter_valid.extend(res_assigned_veh) # add assigned reservations
773
774
# select only pairs with valid elements
775
pairs = [pair_id for pair_id, pair in
776
sorted(rv_dict.items(), key=lambda pair: pair[1][0])
777
if pair[2][0] in filter_valid and
778
pair[2][1] in filter_valid]
779
780
# reservations already picked up by the vehicle
781
res_picked_veh = list(set(res_assigned_veh) & set(res_id_picked))
782
783
trips_tree = [[]] # list with incomplete trips
784
i = 0
785
# get first pairs
786
if res_assigned_veh:
787
# if requests assigned, changes only possible after second
788
# stops to avoid detours. # TODO a better way for allow small
789
# detours should be consider. Ex: tt 1 2 equivalent to tt 3 1 2
790
for next_stop in traci.vehicle.getStops(veh_id):
791
next_act = next_stop.actType.split(",")[0].split(" ")[0]
792
next_id = next_stop.actType.split(",")[0].split(" ")[-1][1:-1]
793
if next_act == 'pickup' and next_id in res_id_picked:
794
# person already picked up, consider next stop
795
continue
796
elif next_act == 'dropOff' and next_id not in res_all.keys():
797
# person already dropped off, consider next stop
798
continue
799
else:
800
# next stop valid
801
if next_act == 'pickup':
802
trip_id = "%s_%sy" % (veh_id, next_id)
803
trip_time = rv_dict[trip_id][0]
804
trips_tree[i] = [[trip_id, trip_time+step, 1]]
805
break
806
else:
807
trip_id = "%s_%sz" % (veh_id, next_id)
808
trip_time = rv_dict[trip_id][0]
809
trips_tree[i] = [[trip_id, trip_time+step, -1]]
810
break
811
if not trips_tree[i]:
812
# if vehicle not assigned or no valid stop found, consider all
813
trips_tree[i] = [[pair, rv_dict[pair][0]+step, 1] for pair in pairs
814
if pair.split("_")[0] == veh_id]
815
816
start_time = perf_counter()
817
while trips_tree[i]:
818
# exhaustive search
819
trips_tree.append([])
820
for trip in trips_tree[i]:
821
trips_tree[i + 1], rtv_dict = search_trips(trip, pairs, res_assigned_veh, res_picked_veh, res_all,
822
rv_dict, rtv_res, veh_capacity, veh_bin, rtv_dict,
823
trips_tree[i + 1], options, start_time)
824
if (perf_counter() - start_time) > options.rtv_time:
825
memory_problems.append(False)
826
break
827
del trips_tree[i][:]
828
i = i + 1 # next tree depth
829
830
# copy the routes found for vehicles with same characteristics
831
for veh_idx, veh_id in enumerate(vehicles):
832
if veh_id in vehicles_unique:
833
# rtv graph already found
834
continue
835
836
equal_veh_id = vehicles_unique[veh_idx]
837
838
veh_bin = [0] * len(fleet) # list of assigned vehicles for ILP
839
veh_bin[fleet.index(veh_id)] = 1
840
copy_rtv_keys = [key for key in rtv_dict.keys() if key.split("_")[0] == equal_veh_id]
841
for key in copy_rtv_keys:
842
new_trip_id = key.replace(equal_veh_id, veh_id)
843
trip_time = rtv_dict[key][0]
844
res_bin = rtv_dict[key][2]
845
trip_cost = rtv_dict[key][3]
846
847
rtv_dict[new_trip_id] = [trip_time, veh_bin, res_bin,
848
trip_cost]
849
850
if len(fleet) == 1 and rtv_dict:
851
# if one vehicle problem, assign the fastest trip with
852
# max reservations served (equivalent to minor trip_cost)
853
trips_list = list(rtv_dict.keys())
854
costs_list = [value[3] for trip, value in rtv_dict.items()]
855
trip_index = costs_list.index(min(costs_list))
856
trip_id = trips_list[trip_index]
857
return {trip_id: rtv_dict[trip_id]}, None, memory_problems
858
859
return rtv_dict, rtv_res, memory_problems
860
861
862
def main(options, step, fleet, veh_type, veh_time_pickup, veh_time_dropoff,
863
res_all, res_id_new, res_id_unassigned, res_id_picked,
864
veh_edges, pairs_dua_times):
865
"""
866
Run specified solver
867
"""
868
# define global variables. Needed to not lose information in the traci loop
869
if 'rv_dict' not in globals():
870
global rv_dict
871
rv_dict = {}
872
if 'exact_sol' not in globals():
873
global exact_sol
874
exact_sol = [True]
875
876
if options.darp_solver == 'exhaustive_search':
877
878
# search reservation-vehicles pairs (RV-Graph)
879
get_rv(options, res_id_new, res_id_picked, res_all,
880
veh_type, veh_time_pickup, veh_time_dropoff, rv_dict, step,
881
veh_edges, pairs_dua_times)
882
883
# search trips (RTV-Graph)
884
rtv_dict, rtv_res, exact_sol = exhaustive_search(options, res_id_unassigned, res_id_picked,
885
res_all, fleet, rv_dict, step,
886
exact_sol, veh_edges)
887
888
return (rtv_dict, rtv_res, exact_sol)
889
890
elif options.darp_solver == 'simple_rerouting':
891
892
# search reservation-vehicles pairs (RV-Graph)
893
get_rv(options, res_id_new, res_id_picked, res_all,
894
veh_type, veh_time_pickup, veh_time_dropoff, rv_dict, step,
895
veh_edges, pairs_dua_times)
896
897
# search trips (RTV-Graph)
898
rtv_dict, rtv_res = simple_rerouting(options, res_id_unassigned,
899
res_id_picked,
900
res_all, fleet, rv_dict, step)
901
902
return (rtv_dict, rtv_res, [False])
903
904