Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/drt/ortools_pdp.py
169673 views
1
# -*- coding: utf-8 -*-
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2021-2025 German Aerospace Center (DLR) and others.
4
# This program and the accompanying materials are made available under the
5
# terms of the Eclipse Public License 2.0 which is available at
6
# https://www.eclipse.org/legal/epl-2.0/
7
# This Source Code may also be made available under the following Secondary
8
# Licenses when the conditions for such availability set forth in the Eclipse
9
# Public License 2.0 are satisfied: GNU General Public License, version 2
10
# or later which is available at
11
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
14
# @file ortools_pdp.py
15
# @author Philip Ritzer
16
# @author Johannes Rummel
17
# @date 2021-12-16
18
19
"""
20
Capacitated vehicle routing problem with pickup and delivery.
21
22
Based on https://developers.google.com/optimization/routing/pickup_delivery#complete_programs
23
"""
24
25
# needed for type alias in python < 3.9
26
from __future__ import annotations
27
from typing import List, Dict, Tuple
28
29
import numpy as np
30
31
from ortools.constraint_solver import routing_enums_pb2
32
from ortools.constraint_solver import pywrapcp
33
import orToolsDataModel
34
35
Node = int
36
Route = List[Node]
37
ORToolsSolution = Dict[int, Tuple[Route, int]]
38
39
40
def get_solution(data: orToolsDataModel.ORToolsDataModel, manager: pywrapcp.RoutingIndexManager,
41
routing: pywrapcp.RoutingModel, solution: pywrapcp.Assignment, verbose: bool) -> ORToolsSolution:
42
"""Returns the solution as a dict with one entry for each
43
vehicle (vehicle id: [0, ..., n_veh-1]) including the
44
route (list of nodes) and costs."""
45
if verbose:
46
print(f'Objective: {solution.ObjectiveValue()}')
47
time_dimension = routing.GetDimensionOrDie('Time')
48
solution_dict = {}
49
total_cost = 0
50
for vehicle_id in range(data.num_vehicles):
51
route = []
52
index = routing.Start(vehicle_id)
53
if verbose:
54
plan_output = 'Route for vehicle %s:\n ' % vehicle_id
55
route_cost = 0
56
route_load = 0
57
while not routing.IsEnd(index):
58
current_node = manager.IndexToNode(index)
59
route_load += data.demands[current_node]
60
time_var = time_dimension.CumulVar(index)
61
if verbose:
62
plan_output += (' %s (L: %s, C: %s, T: (%s,%s))\n -> ' %
63
(current_node, route_load, route_cost, solution.Min(time_var), solution.Max(time_var)))
64
route.append(current_node)
65
previous_index = index
66
index = solution.Value(routing.NextVar(index))
67
route_cost += routing.GetArcCostForVehicle(previous_index, index, vehicle_id)
68
last_node = manager.IndexToNode(index)
69
route_load += data.demands[last_node]
70
time_var = time_dimension.CumulVar(index)
71
route.append(last_node)
72
if verbose:
73
plan_output += (' %s (L: %s, C: %s, T: (%s,%s))\n' %
74
(last_node, route_load, route_cost, solution.Min(time_var), solution.Max(time_var)))
75
plan_output += 'Costs of the route: %s\n' % route_cost
76
print(plan_output)
77
total_cost += route_cost
78
solution_dict[vehicle_id] = (route, route_cost)
79
if verbose:
80
print(f'Total cost of the routes: {total_cost}')
81
return solution_dict
82
83
84
TransitCallbackIndex = int
85
86
87
def set_travel_cost(data: orToolsDataModel.ORToolsDataModel, routing: pywrapcp.RoutingModel,
88
manager: pywrapcp.RoutingIndexManager, verbose: bool) -> TransitCallbackIndex:
89
"""Create and register a transit callback."""
90
91
def distance_callback(from_index, to_index) -> int:
92
"""Returns the distance between the two nodes."""
93
# Convert from routing variable Index to distance matrix NodeIndex.
94
from_node = manager.IndexToNode(from_index)
95
to_node = manager.IndexToNode(to_index)
96
return data.cost_matrix[from_node][to_node]
97
98
if verbose:
99
print(' Register distance callback.')
100
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
101
102
# Define cost of each arc.
103
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
104
105
return transit_callback_index
106
107
108
def add_cost_constraint(data: orToolsDataModel.ORToolsDataModel, routing: pywrapcp.RoutingModel,
109
transit_callback_index: TransitCallbackIndex, verbose: bool) -> pywrapcp.RoutingDimension:
110
# Add costs/distance constraint.
111
if verbose:
112
print(' Add distance constraints...')
113
matrix_costs = int(np.sum(data.cost_matrix))
114
dimension_name = 'Costs'
115
routing.AddDimension(
116
transit_callback_index,
117
0, # no slack
118
matrix_costs, # TODO reasonable max costs/distance
119
True, # start cumul to zero
120
dimension_name)
121
distance_dimension = routing.GetDimensionOrDie(dimension_name)
122
# the following tries to reduce the route costs of the vehicle with maximum costs
123
# distance_dimension.SetGlobalSpanCostCoefficient(0)
124
# the following tries to reduce the sum of all route costs
125
# distance_dimension.SetSpanCostCoefficientForAllVehicles(0)
126
return distance_dimension
127
128
129
def add_transportation_requests_constraint(data: orToolsDataModel.ORToolsDataModel,
130
routing: pywrapcp.RoutingModel, manager: pywrapcp.RoutingIndexManager,
131
solver: pywrapcp.Solver,
132
distance_dimension: pywrapcp.RoutingDimension, verbose: bool):
133
if verbose:
134
print(' Add pickup and delivery constraints...')
135
for request in data.pickups_deliveries:
136
pickup_index = manager.NodeToIndex(request.from_node)
137
delivery_index = manager.NodeToIndex(request.to_node)
138
if verbose:
139
print(f'pickup/dropoff nodes: {request.from_node}/{request.to_node}')
140
routing.AddPickupAndDelivery(pickup_index, delivery_index) # helps the solver
141
# use same veh for pickup and dropoff
142
solver.Add(routing.VehicleVar(pickup_index) == routing.VehicleVar(delivery_index))
143
solver.Add(
144
distance_dimension.CumulVar(pickup_index) <=
145
distance_dimension.CumulVar(delivery_index)) # define order: first pickup then dropoff
146
if request.is_new(): # is that a new request?
147
# allows to reject the order but gives penalty
148
if verbose:
149
print(f'allow to reject new reservation {request.get_id()}')
150
routing.AddDisjunction([pickup_index, delivery_index], 10*data.get_penalty(True), 2)
151
152
153
def add_direct_route_factor_constraint(data: orToolsDataModel.ORToolsDataModel,
154
routing: pywrapcp.RoutingModel, manager: pywrapcp.RoutingIndexManager,
155
solver: pywrapcp.Solver,
156
distance_dimension: pywrapcp.RoutingDimension, verbose: bool):
157
if data.drf == -1:
158
return
159
if verbose:
160
print(' Add direct route factor constraints...')
161
for request in data.pickups_deliveries:
162
# hard constraint for new requests:
163
if request.is_new():
164
add_hard_direct_route_factor_constraint(request, data, manager, solver, distance_dimension, verbose)
165
# soft constraint for old (know) requests:
166
else:
167
add_soft_direct_route_factor_constraint(routing, data, request, solver,
168
manager, distance_dimension, verbose)
169
170
# if possible, let the route costs of the dropoffs less than the drf allows,
171
# else minimize the route costs (in case the costs became larger than expected)
172
for request in data.dropoffs: # TODO: test needed
173
direct_route_cost = request.direct_route_cost
174
direct_route_cost_drf = solver.IntConst(round(direct_route_cost * data.drf))
175
delivery_index = manager.NodeToIndex(request.to_node)
176
distance_dimension.SetCumulVarSoftUpperBound(delivery_index, round(
177
direct_route_cost * data.drf - request.current_route_cost), 100*data.get_penalty())
178
if verbose:
179
print(f"reservation {request.get_id()}: direct route cost {direct_route_cost} and "
180
f"(soft) max cost {direct_route_cost_drf.Value()}, already used costs {request.current_route_cost}")
181
182
183
def add_hard_direct_route_factor_constraint(request: orToolsDataModel.Reservation,
184
data: orToolsDataModel.ORToolsDataModel,
185
manager: pywrapcp.RoutingIndexManager,
186
solver: pywrapcp.Solver,
187
distance_dimension: pywrapcp.RoutingDimension, verbose: bool):
188
pickup_index = manager.NodeToIndex(request.from_node)
189
delivery_index = manager.NodeToIndex(request.to_node)
190
# let the route cost be less or equal the direct route cost times drf
191
direct_route_cost = request.direct_route_cost
192
solver.Add(
193
distance_dimension.CumulVar(delivery_index) - distance_dimension.CumulVar(pickup_index) <= # route cost
194
solver.IntConst(round(direct_route_cost * data.drf))) # direct route cost times direct route factor
195
direct_route_cost_drf = solver.IntConst(round(direct_route_cost * data.drf))
196
if verbose:
197
print(f"reservation {request.get_id()}: direct route cost {direct_route_cost} and "
198
f"(hard) max cost {direct_route_cost_drf.Value()}")
199
200
201
def add_soft_direct_route_factor_constraint(routing: pywrapcp.RoutingModel,
202
data: orToolsDataModel.ORToolsDataModel,
203
request: orToolsDataModel.Reservation,
204
solver: pywrapcp.Solver, manager: pywrapcp.RoutingIndexManager,
205
distance_dimension: pywrapcp.RoutingDimension, verbose: bool):
206
'''If the costs changed and the drf-constraint cannot be hold anymore use a soft constraint.
207
'''
208
matrix_costs = int(np.sum(data.cost_matrix))
209
# Add new dimension only for this request:
210
request_cost_dimension_name = f'request_cost_{request.get_id()}'
211
routing.AddConstantDimensionWithSlack(
212
0, # Transition is 0
213
matrix_costs, # reasonable maximum request costs
214
matrix_costs, # reasonable maximum slack
215
True, # force start request costs with 0
216
request_cost_dimension_name)
217
request_cost_dimension: pywrapcp.RoutingDimension = routing.GetDimensionOrDie(request_cost_dimension_name)
218
pickup_index = manager.NodeToIndex(request.from_node)
219
delivery_index = manager.NodeToIndex(request.to_node)
220
route_start = distance_dimension.CumulVar(pickup_index)
221
route_end = distance_dimension.CumulVar(delivery_index)
222
route_cost = request_cost_dimension.CumulVar(delivery_index)
223
solver.Add(route_cost == route_end - route_start)
224
request_cost_dimension.SetCumulVarSoftUpperBound(
225
delivery_index,
226
round(request.direct_route_cost * data.drf),
227
100*data.get_penalty()
228
)
229
if verbose:
230
print(f"reservation {request.get_id()}: direct route cost {request.direct_route_cost} and "
231
f"(soft) max cost {int(request.direct_route_cost * data.drf)}")
232
233
234
def add_dropoff_constraint(data: orToolsDataModel.ORToolsDataModel,
235
routing: pywrapcp.RoutingModel, manager: pywrapcp.RoutingIndexManager,
236
verbose: bool):
237
if verbose:
238
print(' Add dropoff constraints...')
239
# for veh_index, do_list in enumerate(data['dropoffs']):
240
# if verbose:
241
# print('vehicle %s with %s dropoffs' % (veh_index, len(do_list)))
242
# for do in do_list:
243
# index = manager.NodeToIndex(do[0])
244
# # start node
245
# veh_node = data['starts'][veh_index]
246
# if verbose:
247
# print('vehicle %s (%s), dropoff %s (%s), res_id %s' % (veh_index, veh_node, do[0], index, do[1]))
248
# #routing.VehicleVar(index).SetValues([-1,veh_index])
249
# routing.SetAllowedVehiclesForIndex([veh_index],index)
250
for res in data.dropoffs:
251
if verbose:
252
print(f'reservation {res.get_id()} in veh {res.vehicle.id_vehicle}({res.vehicle.vehicle_index}), '
253
f'droppoff node: {res.to_node}')
254
index = manager.NodeToIndex(res.to_node)
255
routing.SetAllowedVehiclesForIndex([res.vehicle.vehicle_index], index)
256
257
258
def add_allocation_constraint(data: orToolsDataModel.ORToolsDataModel,
259
routing: pywrapcp.RoutingModel, manager: pywrapcp.RoutingIndexManager,
260
verbose: bool):
261
if verbose:
262
print(' Add "no re-allocation" constraints...')
263
for res in data.pickups_deliveries:
264
if res.vehicle: # hasattr(res, 'vehicle_index'):
265
if verbose:
266
print(f'reservation {res.get_id()} in veh id={res.vehicle.vehicle_index}')
267
index = manager.NodeToIndex(res.to_node)
268
routing.SetAllowedVehiclesForIndex([res.vehicle.vehicle_index], index)
269
270
271
def add_capacity_constraint(data: orToolsDataModel.ORToolsDataModel,
272
routing: pywrapcp.RoutingModel, manager: pywrapcp.RoutingIndexManager,
273
verbose: bool):
274
if verbose:
275
print(' Add capacity constraints...')
276
277
def demand_callback(from_index):
278
"""Returns the demand of the node."""
279
# Convert from routing variable Index to demands NodeIndex.
280
from_node = manager.IndexToNode(from_index)
281
return data.demands[from_node]
282
demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
283
routing.AddDimensionWithVehicleCapacity(
284
demand_callback_index,
285
0, # null capacity slack
286
data.vehicle_capacities, # vehicle maximum capacities
287
True, # start cumul to zero
288
'Capacity')
289
290
291
def create_time_dimension(data: orToolsDataModel.ORToolsDataModel,
292
routing: pywrapcp.RoutingModel, manager: pywrapcp.RoutingIndexManager,
293
verbose: bool) -> pywrapcp.RoutingDimension:
294
if verbose:
295
print(' Create time dimension.')
296
297
def time_callback(from_index, to_index):
298
"""Returns the travel time between the two nodes."""
299
# Convert from routing variable Index to time matrix NodeIndex.
300
from_node = manager.IndexToNode(from_index)
301
to_node = manager.IndexToNode(to_index)
302
return data.time_matrix[from_node][to_node]
303
304
time_callback_index = routing.RegisterTransitCallback(time_callback)
305
# routing.SetArcCostEvaluatorOfAllVehicles(time_callback_index)
306
dimension_name = 'Time'
307
routing.AddDimension(
308
time_callback_index,
309
int(data.max_time), # allow waiting time
310
int(data.max_time), # maximum time per vehicle
311
False, # Don't force start cumul to zero.
312
dimension_name)
313
time_dimension = routing.GetDimensionOrDie(dimension_name)
314
return time_dimension
315
316
317
def add_time_windows_constraint(data: orToolsDataModel.ORToolsDataModel,
318
time_dimension: pywrapcp.RoutingDimension, manager: pywrapcp.RoutingIndexManager,
319
verbose: bool):
320
if verbose:
321
print(' Add time windows constraints...')
322
323
depot = data.depot
324
new_requests_nodes = [node for req in data.pickups_deliveries
325
for node in (req.from_node, req.to_node) if req.is_new()]
326
old_requests_nodes = ([req.to_node for req in data.dropoffs] +
327
[node for req in data.pickups_deliveries
328
for node in (req.from_node, req.to_node) if not req.is_new()])
329
# Add time window constraints for each location except depot.
330
for location_idx, time_window in enumerate(data.time_windows):
331
# no time window for depot:
332
if location_idx == depot:
333
continue
334
index = manager.NodeToIndex(location_idx)
335
# hard time window for vehicles and new requests:
336
if location_idx in data.starts + new_requests_nodes:
337
if verbose:
338
print(f'hard time window for node {location_idx}: [{time_window[0]}, {time_window[1]}]')
339
time_dimension.CumulVar(index).SetRange(
340
time_window[0], time_window[1]) # TODO: check if set, else ignore it
341
# soft time window for old requests:
342
if location_idx in old_requests_nodes:
343
if verbose:
344
print(f'soft time window for node {location_idx}: [{time_window[0]}, {time_window[1]}]')
345
time_dimension.SetCumulVarSoftLowerBound(index, time_window[0], 100*data.get_penalty(True))
346
time_dimension.SetCumulVarSoftUpperBound(index, time_window[1], 100*data.get_penalty(True))
347
# time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])
348
349
# TODO: check if the followwing is needed
350
# # Add time window constraints for each vehicle start node.
351
# depot_idx = data['depot']
352
# for vehicle_id in range(data['num_vehicles']):
353
# index = routing.Start(vehicle_id)
354
# time_dimension.CumulVar(index).SetRange(
355
# data['time_windows'][depot_idx][0],
356
# data['time_windows'][depot_idx][1])
357
# for i in range(data['num_vehicles']):
358
# routing.AddVariableMinimizedByFinalizer(
359
# time_dimension.CumulVar(routing.Start(i)))
360
# routing.AddVariableMinimizedByFinalizer(
361
# time_dimension.CumulVar(routing.End(i)))
362
363
364
def add_waiting_time_constraints(data: orToolsDataModel.ORToolsDataModel,
365
manager: pywrapcp.RoutingIndexManager,
366
time_dimension: pywrapcp.RoutingDimension,
367
verbose: bool):
368
"""Adds the constraints related to the maximum waiting times of the requests.
369
"""
370
global_waiting_time = data.waiting_time
371
# -1 means no waiting time is used
372
if global_waiting_time == -1:
373
return
374
if verbose:
375
print(' Add waiting time constraints...')
376
# for now, only a global waiting time for the pick up is introduced
377
# TODO: add special constraints for latests arrival and earliest depart
378
for request in data.pickups_deliveries:
379
pickup_index = manager.NodeToIndex(request.from_node)
380
reservation_time = request.reservation.reservationTime
381
requested_pickup_time = request.get_earliest_pickup() or reservation_time
382
maximum_pickup_time = round(request.get_dropoff_latest() or (requested_pickup_time + global_waiting_time))
383
# add hard constraint for new reservations
384
if request.is_new():
385
if verbose:
386
print(f"reservation {request.get_id()} has a maximum (hard) pickup time at {maximum_pickup_time}")
387
min_time_window = time_dimension.CumulVar(pickup_index).Min()
388
maximum_pickup_time = maximum_pickup_time if min_time_window < maximum_pickup_time else min_time_window
389
time_dimension.CumulVar(pickup_index).SetMax(maximum_pickup_time)
390
# add soft constraint for old reservations
391
else:
392
time_dimension.SetCumulVarSoftUpperBound(
393
pickup_index,
394
maximum_pickup_time,
395
100*data.get_penalty(True)) # cost = coefficient * (cumulVar - maximum_pickup_time)
396
if verbose:
397
print(f"reservation {request.get_id()} has a maximum (soft) pickup time at {maximum_pickup_time}")
398
399
400
def solve_from_initial_solution(routing: pywrapcp.RoutingModel, manager: pywrapcp.RoutingIndexManager,
401
search_parameters: any, data: orToolsDataModel.ORToolsDataModel, verbose: bool):
402
solution_requests = data.initial_routes
403
# get inital solution
404
initial_routes = []
405
if solution_requests is not None:
406
for index_vehicle in solution_requests:
407
# use request ids ([0]) here and align with current status of the requests
408
request_order = solution_requests[index_vehicle][0].copy()
409
for request_id in set(solution_requests[index_vehicle][0]):
410
# 0: done
411
# 1: only drop-off left
412
# 2: pick-up and drop-off left
413
old_status = solution_requests[index_vehicle][0].count(request_id)
414
new_status = 0
415
if request_id in [req.get_id() for req in data.pickups_deliveries]:
416
new_status = 2
417
elif request_id in [req.get_id() for req in data.dropoffs]:
418
new_status = 1
419
if new_status == 0:
420
# remove complete request
421
request_order = [req for req in request_order if req != request_id]
422
if old_status == 2 and new_status == 1:
423
# remove first occurance of the request
424
request_order.remove(request_id)
425
# translate new requests order (ids) to nodes order
426
# (e.g. [0,1,2,1,2] -> [0.to_node, 1.from_node, 2.from_node, 1.to_node, 2.to_node])
427
request_id_set = set(request_order) # e.g. [0,1,2]
428
# first occurance from behind (will be "to_node")
429
reverserd_request_order = request_order.copy()
430
reverserd_request_order.reverse() # e.g. [2,1,2,1,0]
431
first_occurance_from_behind = [reverserd_request_order.index(id) for id in request_id_set] # e.g. [0,1,4]
432
all_requests = data.pickups_deliveries.copy()
433
all_requests.extend(data.dropoffs.copy())
434
nodes_order = []
435
for index, req_id in enumerate(reverserd_request_order):
436
req = [r for r in all_requests if r.get_id() == req_id][0]
437
if index in first_occurance_from_behind:
438
nodes_order.insert(0, manager.NodeToIndex(req.to_node))
439
else:
440
nodes_order.insert(0, manager.NodeToIndex(req.from_node))
441
# nodes_order = solution_requests[index_vehicle][2] # [2] for nodes
442
initial_routes.append(nodes_order)
443
routing.CloseModelWithParameters(search_parameters)
444
if verbose:
445
print('Initial solution:')
446
for index_vehicle, index_list in enumerate(initial_routes):
447
print(f'veh {index_vehicle}: {[manager.IndexToNode(index) for index in index_list]}')
448
initial_solution = routing.ReadAssignmentFromRoutes(initial_routes, True)
449
solution = routing.SolveFromAssignmentWithParameters(initial_solution, search_parameters)
450
return solution
451
452
453
def set_first_solution_heuristic(time_limit_seconds: int, verbose: bool) -> any:
454
if verbose:
455
print(' Set solution heuristic...')
456
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
457
# search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_MOST_CONSTRAINED_ARC
458
# search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
459
search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC
460
461
# GUIDED_LOCAL_SEARCH seems slow
462
# search_parameters.local_search_metaheuristic = routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
463
# search_parameters.local_search_metaheuristic = routing_enums_pb2.LocalSearchMetaheuristic.GREEDY_DESCENT
464
search_parameters.local_search_metaheuristic = routing_enums_pb2.LocalSearchMetaheuristic.AUTOMATIC
465
466
search_parameters.time_limit.FromSeconds(time_limit_seconds)
467
468
search_parameters.sat_parameters.num_search_workers = 8
469
# search_parameters.lns_time_limit.seconds = 7
470
# search_parameters.solution_limit = 100
471
472
# Switch logging on for the search
473
# search_parameters.log_search = True
474
475
return search_parameters
476
477
478
def main(data: orToolsDataModel.ORToolsDataModel,
479
time_limit_seconds: int = 10, verbose: bool = False) -> ORToolsSolution | None:
480
"""Entry point of the program."""
481
# Create the routing index manager.
482
manager = pywrapcp.RoutingIndexManager(
483
len(data.cost_matrix), # number of locations (nodes)
484
data.num_vehicles, # number of vehicles
485
data.starts, data.ends) # start and end locations (nodes)
486
487
# Create Routing Model.
488
routing_parameters = pywrapcp.DefaultRoutingModelParameters()
489
# routing_parameters.solver_parameters.trace_propagation = True
490
# routing_parameters.solver_parameters.trace_search = True
491
routing = pywrapcp.RoutingModel(manager, routing_parameters)
492
493
# get solver
494
solver = routing.solver()
495
496
# define transit_callback and set travel cost
497
transit_callback_index = set_travel_cost(data, routing, manager, verbose)
498
time_dimension = create_time_dimension(data, routing, manager, verbose)
499
# Add costs/distance constraint.
500
distance_dimension = add_cost_constraint(data, routing, transit_callback_index, verbose)
501
502
# Define Transportation Requests.
503
add_transportation_requests_constraint(data, routing, manager, solver, distance_dimension, verbose)
504
505
# Set direct route factor.
506
add_direct_route_factor_constraint(data, routing, manager, solver, distance_dimension, verbose)
507
508
# Force the vehicle to drop-off the reservations it already picked up
509
add_dropoff_constraint(data, routing, manager, verbose)
510
511
# If no reallocation is desired
512
if data.fix_allocation:
513
add_allocation_constraint(data, routing, manager, verbose)
514
515
# Add Capacity constraint.
516
add_capacity_constraint(data, routing, manager, verbose)
517
518
# Add time window constraints.
519
add_time_windows_constraint(data, time_dimension, manager, verbose)
520
521
# Add waiting time constraints.
522
add_waiting_time_constraints(data, manager, time_dimension, verbose)
523
524
print('## Done')
525
# Setting first solution heuristic.
526
search_parameters = set_first_solution_heuristic(time_limit_seconds, verbose)
527
528
# Solve the problem.
529
if verbose:
530
print('Start solving the problem.')
531
if data.initial_routes:
532
# TODO: change ids of the nodes, due to already picked up or droped off requests after last solution!
533
solution = solve_from_initial_solution(routing, manager, search_parameters, data, verbose)
534
else:
535
solution = routing.SolveWithParameters(search_parameters)
536
# solution = routing.SolveWithParameters(search_parameters)
537
538
if solution:
539
return get_solution(data, manager, routing, solution, verbose)
540
else:
541
if verbose:
542
print('There is no solution.')
543
return None
544
545