0

I find reading all values of an assignment, obtained from

assignment = routing.SolveWithParameters(search_params)

of routing problems with time windows quiet tricky. First of all, there are nodes and indices. I obtain the indices of a vehicle (route) via

index = routing.Start(vehicle)
indices = [index]
while not routing.IsEnd(index):
    index = assignment.Value(routing.NextVar(index))
    indices.append(index)

and the corresponding nodes are obtained by

nodes = [routing.IndexToNode(x) for x in indices]

For a particular routing problem with 5 stops and depot=0 the solver finds an assignment with the following indices and nodes:

vehicle: 0
indices: [0, 1, 6]
nodes:   [0, 1, 0]

vehicle: 1
indices: [5, 4, 3, 2, 7]
nodes:   [0, 4, 3, 2, 0]

So there are three more indices than nodes, because each vehicle starts and ends in the depot. I have defined a cost of 1 for every transit and reading the cost values via

cost_dimension = routing.GetDimensionOrDie("cost")
cost_vars = [cost_dimension.CumulVar(x) for x in indices]
costs = [assignment.Value(x) for x in cost_vars]

seems to work:

vehicle: 0
indices: [0, 1, 6]
nodes:   [0, 1, 0]
costs:   [0, 1, 2]

vehicle: 1
indices: [5, 4, 3, 2, 7]
nodes:   [0, 4, 3, 2, 0]
costs:   [0, 1, 2, 3, 4]

But when I add time constraints I run into problems. Let's first look at the code that defines the problem. The time unit is minutes.

def time_function(x,y):
    return 30

evaluator = time_function
slack_max = 40
capacity = 24*60
fix_start_cumul_to_zero = False
name = "time"
routing.AddDimension(evaluator, slack_max, capacity, fix_start_cumul_to_zero, name)
time_dimension = routing.GetDimensionOrDie("time")

time_windows = [(7*60, 8*60),(9*60, 10*60),(9*60, 10*60),
                (9*60, 10*60),(9*60, 10*60)]
for node, time_window in enumerate(time_windows):
    time_dimension.CumulVar(node).SetRange(time_window[0], time_window[1])
    routing.AddToAssignment(time_dimension.SlackVar(node))

So each trip takes 30 minutes, vehicles may be idle at a stop for 40 minutes (slack_max=40) and each stop should be serviced between 9am and 10am. The range constraints that are enforced via time_windows[0] are intended to define the starting times of each trip in the morning. But since the depot is the first and last stop of each route they could also be interpreted as arriving times in the evening.

So here is my first difficulty with time windows: the depot appears twice on each route but the range constraint is defined on nodes. I am assuming that the routing model is not designed to take two windows for the depot?

Let me continue to get to the second part of my question. I set fix_start_cumul_to_zero = False so that routes may start at any time. Also note that routing.AddToAssignment(time_dimension.SlackVar(node)) is supposed to give me access to the slack variables later. Now, when I inspect the time values per index, via

time_vars = [time_dimension.CumulVar(x) for x in indices]
times.append([assignment.Value(x) for x in time_vars])

formated with datetime, I get reasonable results:

vehicle: 0
indices: [0, 1, 6]
nodes:   [0, 1, 0]
costs:   [0, 1, 2]
times:   ['7:50:00', '9:00:00', '9:30:00']

vehicle: 1
indices: [5, 4, 3, 2, 7]
nodes:   [0, 4, 3, 2, 0]
costs:   [0, 1, 2, 3, 4]
times:   ['7:50:00', '9:00:00', '9:30:00', '10:00:00', '10:30:00']

The solver apparently favors early departure times. Given the max slack of 40min, each vehicle might also start a bit later, e.g. at 8am.

The trouble begins when I try to read the slack variables via

slack_vars = [time_dimension.SlackVar(x) for x in indices]
slacks = [assignment.Value(x) for x in slack_vars]

The program crashes with the message:

SystemError: returned NULL without setting an error

which suggests that time_dimension does not have a slack variable for every index. Is that right? Why not?

Thanks for reading this excessive post. Here are the two questions:

  1. Is it possible to define arrival and departure time windows for the depot?
  2. How to properly read the slacks for time windows for all nodes, including the depo?
Karussell
  • 17,085
  • 16
  • 97
  • 197
Leevi L
  • 1,538
  • 2
  • 13
  • 28

1 Answers1

0

I'll answer question 2 first, since 1 is a guess from this answer...

2) First, Slack variable is only needed if you have a next node so there is not slack var for the end node.
basically if j is next(i) then cumul(j) = cumul(i) + transit(i,j) + slack(i)

You must use "index" not "node_index" for accessing/setting SlackVar.
i.e. there is N node_index (i.e. N locations including the depot) but M = N-1/*depot*/ + num_vehicle*2 /*Start, End for each vehicle*/ index i.e. each vehicle has a specific objet instance for its start and end node -> that's why you nee to use routing.Start(i) to get the "start" index of the i-th vehicle (e.d.: in fact NodeToIndex(0) give you the start index for the vehicle 0).

def add_time_window_constraints(routing, data, time_evaluator):
    """Add Global Span constraint"""
    time = "Time"
    horizon = 120
    routing.AddDimension(
        time_evaluator,
        horizon, # allow waiting time
        horizon, # maximum time per vehicle
        True, # start cumul to zero
        time)
    time_dimension = routing.GetDimensionOrDie(time)
    for location_idx, time_window in enumerate(data.time_windows):
        if location_idx == 0:
            continue
        time_dimension.CumulVar(routing.NodeToIndex(location_idx)).SetRange(time_window[0], time_window[1])
        routing.AddToAssignment(time_dimension.SlackVar(routing.NodeToIndex(location_idx)))
    for vehicle_id in xrange(data.num_vehicles):
        routing.AddToAssignment(time_dimension.SlackVar(routing.Start(vehicle_id)))
        # slack not defined for vehicle ends
        #routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id)))

1) /!\ NOT TESTED /!\ For time windows for departure I would use:

for vehicle_id in xrange(data.num_vehicles):
    time_dimension.CumulVar(routing.Start(vehicle_id)).SetRange(begin, end)

/!\ you must set cumul_start_to_zero to False when creating the dimension if begin if > 0 otherwise it won't find a solution obviously /!\

i.e. You have to set it for each vehicle...

Your code barely work because node_index == index for the first nodes then it start to crash....

ps: I'm working on fixing the doc and examples for the index/node_index usage

Full example and tracked issue in google/or-tools #708.
ps: Thanks for having spotted this issue ;)

Mizux
  • 8,222
  • 7
  • 32
  • 48