How can we add waiting time penalty in multiple pickup and drop time windows or something to minimize overall waiting time while visiting each location?
def apply_time_windows(self, data, manager, routing, time_callback):
time_cb_index = routing.RegisterTransitCallback(time_callback)
routing.AddDimension(
time_cb_index,
4000, # slack
1440000, # max time
False, # don't force start at 0
"Time"
)
time_dim = routing.GetDimensionOrDie("Time")
for order_idx, (pickup_loc, delivery_loc) in enumerate(data["pickups_deliveries"][1:], start=1):
# node indices
pickup_index = manager.NodeToIndex(pickup_loc)
delivery_index = manager.NodeToIndex(delivery_loc)
pickup_windows = data["pickup_time_windows"][order_idx]
delivery_windows = data["time_windows"][order_idx]
travel_time = data["time_matrix"][pickup_loc][delivery_loc]
# Find earliest pickup start and latest drop end
earliest_pickup = min(s for s, e in pickup_windows)
latest_drop = max(e for s, e in delivery_windows)
available_window = latest_drop - earliest_pickup
if travel_time > available_window:
if data.get("enable_hard_constraint_tw"):
# Hard constraint: do nothing, let solver fail if infeasible
pass
else:
# Soft constraint: update latest drop window end to allow breach
# Update all delivery windows' end to 20000
delivery_windows = [(s, 20000) for (s, e) in delivery_windows]
data["time_windows"][order_idx] = delivery_windows
# pickup
p_min = min(s for s, e in pickup_windows)
p_max = max(e for s, e in pickup_windows)
time_dim.CumulVar(pickup_index).SetRange(p_min, p_max)
# delivery
d_min = min(s for s, e in delivery_windows)
d_max = max(e for s, e in delivery_windows)
time_dim.CumulVar(delivery_index).SetRange(d_min, d_max)
# pickup remove intervals
p_wins_sorted = sorted(pickup_windows)
for i in range(len(p_wins_sorted) - 1):
end_i = p_wins_sorted[i][1]
next_start = p_wins_sorted[i+1][0]
# if there is a gap → block it
if end_i < next_start:
time_dim.CumulVar(pickup_index).RemoveInterval(end_i, next_start)
# delivery remove intervals
d_wins_sorted = sorted(delivery_windows)
for i in range(len(d_wins_sorted) - 1):
end_i = d_wins_sorted[i][1]
next_start = d_wins_sorted[i+1][0]
if end_i < next_start:
time_dim.CumulVar(delivery_index).RemoveInterval(end_i, next_start)
# time_dim.SetGlobalSpanCostCoefficient(1)
self.apply_travel_time_constraint(
data,
routing,
time_dim,
pickup_loc,
delivery_loc,
pickup_index,
delivery_index,
)
def apply_travel_time_constraint(
self,
data,
routing,
time_dimension,
pickup_loc,
delivery_loc,
pickup_index,
delivery_index,
):
travel_time = data["time_matrix"][pickup_loc][delivery_loc]
logging.info(f"Applying travel time constraint: pickup {pickup_loc} to delivery {delivery_loc} with travel time {travel_time}")
routing.solver().Add(
time_dimension.CumulVar(delivery_index)
>= time_dimension.CumulVar(pickup_index) + travel_time
)
max_waiting_time = 4000
routing.solver().Add(
time_dimension.CumulVar(delivery_index)
<= time_dimension.CumulVar(pickup_index)
+ travel_time
+ max_waiting_time
)