-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pickup and Delivery with alternatives #968
Comments
Any tips, please? |
Did you try to play with A = routing.NodeToIndex("A")
Ap = routing.NodeToIndex("A'")
B = routing.NodeToIndex("B")
Bp = routing.NodeToIndex("B'")
# Same vehicle
routing.solver().Add(
routing.ActiveVar(A) * routing.VehicleVar(A) + routing.ActiveVar(Ap) * routing.VehicleVar(Ap)
==
routing.ActiveVar(B) * routing.VehicleVar(B) + routing.ActiveVar(Bp) * routing.VehicleVar(Bp)
)
# precedence `time([A,A']) < time([B,B'])`
routing.solver().Add(
routing.ActiveVar(A) * dim.CumulVar(A) + routing.ActiveVar(Ap) * dim.CumulVar(Ap)
<=
routing.ActiveVar(B) * dim.CumulVar(B) + routing.ActiveVar(Bp) * dim.CumulVar(Bp)
) with a disjunction only one |
I may need some homework to fully understand this. Can the first term relate only to A and B, and not Ap and Bp? (I suspect B_Index is typo) constraintActive = routing.ActiveVar(routing.NodeToIndex(A)) *
routing.ActiveVar(routing.NodeToIndex(B_index)) Principally, if we also have A'' and B'' the conditions grow linearly: # Same vehicle
routing.solver().Add(
routing.ActiveVar(A) * routing.VehicleVar(A) + routing.ActiveVar(Ap) * routing.VehicleVar(Ap) +
routing.ActiveVar(App) * routing.VehicleVar(App)
routing.ActiveVar(B) * routing.VehicleVar(B) + routing.ActiveVar(Bp) * routing.VehicleVar(Bp) +
routing.ActiveVar(Bpp) * routing.VehicleVar(Bpp)
)
# precedence time([A,A']) < time([B,B'])
routing.solver().Add(
routing.ActiveVar(A) * dim.CumulVar(A) + routing.ActiveVar(Ap) * dim.CumulVar(Ap) + routing.ActiveVar(App) * dim.CumulVar(App)
<=
routing.ActiveVar(B) * dim.CumulVar(B) + routing.ActiveVar(Bp) * dim.CumulVar(Bp) +
routing.ActiveVar(Bpp) * dim.CumulVar(Bpp)
) |
yup, that's the point since add_disjunction guaranty only ONE node active or none (and you pay the penalty) sorry for the first two line copy/paste from other examples don't need these lines at all... |
Thanks! |
would it also work to use max rather than active var? Something like (in python): # loop over each "group" of pickup alternatess and delivery alternates
# let "all_pickups" be list of alternate pickups, and
# "all_deliver" be list of alternate deliveries
routing.AddDisjunction(all_pickups, penalty)
routing.AddDisjunction(all_deliver, penalty)
pickup_vehicles = [ routing.VehicleVar(routing.NodeToIndex(i)) for i in all_pickups ]
deliver_vehicles = [ routing.VehicleVar(routing.NodeToIndex(i)) for i in all_deliver ]
# values are -1 for inactive, or positive for active
# same vehicle requirement across alternatives
solver.AddConstraint(solver.Max(pickup_vehicles) == solver.Max(deliver_vehicles))
# similar approach to force pickup before delivery
pickup_times = [ time_dimension.CumulVar(routing.NodeToIndex(i)) for i in all_pickups ]
deliver_times = [ time_dimension.CumulVar(routing.NodeToIndex(i)) for i in all_deliver ]
# deliver *after* pickup
solver.AddConstraint(
solver.Max(pickup_times) <= solver.Max(deliver_times)) Haven't tested that but is seems okay to me. One issue I can't see how to solve cleanly is calling the pickup_disjunction_index = routing.AddDisjunction(all_pickups, penalty)
deliver_disjunction_index = routing.AddDisjunction(all_deliver, penalty)
# ...
routing.AddPickupAndDeliverySets(pickup_disjunction_index, deliver_disjunction_index) But... as of 6.10, the |
Actually, what I suggested won't work. I just ran some tests on a simple case and learned by doing... My assumption was that if a node is not active its CumulVar is zero. This is not the case. Rather it is set to the range of the variable (so if it is a time window, it is the time window). Calling Max on that time window gives the max of the time window (I think). Further, playing with an auxiliary dimension that starts and zero and increments in order, what I discovered is that (apparently) if a node is in the solution and then rotated out in favor of another valid alternative, it still keeps its earlier CumulVar values, rather than resetting to zero (or to the min/max of the preset range, if any). So even with a non-ranged variable, the constraints that I wrote above will not be valid, as the |
@jmarca thanks for the feedback ! |
Thanks, James. |
How do I prevent visiting a certain Node A, something like this: |
Following up on my earlier comment, here is what works for me (code updated to fix bugs pointed out by @Mizux): # assuming pd_pairs is a minimal set, and I have other data structures to store alternatives
for pair in pd_pairs.values():
# these solver-indices are not needed anymore
# pickup_index = routing.NodeToIndex(pair[0])
# deliver_index = routing.NodeToIndex(pair[1])
# add pickup disjunction
all_pickups = [ routing.NodeToIndex(pi) for pi in alternatives[pair[0]] ]
# pdi is placeholder until v7.x comes out
pdi = routing.AddDisjunction(all_pickups, penalty)
# add deliver disjunction
all_deliver = [ routing.NodeToIndex(di) for di in alternatives[pair[1]] ]
ddi = routing.AddDisjunction(all_deliver, penalty)
pickup_vehicles = [ routing.VehicleVar(i) for i in all_pickups ]
deliver_vehicles = [ routing.VehicleVar(i) for i in all_deliver ]
pickup_active = [ routing.ActiveVar(i) for i in all_pickups ]
deliver_active = [ routing.ActiveVar(i) for i in all_deliver ]
# same vehicle requirement across alternatives
# haven't thoroughly tested that this works without *_active...works for 2 veh
solver.AddConstraint(solver.Max(pickup_vehicles) == solver.Max(deliver_vehicles))
# timing constraints
pickup_times = [ time_dimension.CumulVar(i) for i in all_pickups ]
deliver_times = [ time_dimension.CumulVar(i) for i in all_deliver ]
pickup_cross_active = [p*a for (p, a) in zip(pickup_times,pickup_active)]
deliver_cross_active = [d*a for (d, a) in zip(deliver_times,deliver_active)]
# deliver *after* pickup
solver.AddConstraint(
solver.Max(pickup_cross_active) <= solver.Max(deliver_cross_active))
# AND, only allow max of 1 hour over travel time from A to B
# need to do nested loop here, because travel times are unique for all OD pairs
for o in all_pickups:
# need node ref here for travel time lookup
o_node = routing.IndexToNode(o)
pickup_cross_active_time = routing.ActiveVar(o) * time_dimension.CumulVar(o)
for d in all_deliver:
# need node ref here for travel time lookup
d_node = routing.IndexToNode(d)
# here I multiply by both active and origin, active at
# destination. Perhaps should just make a
# conditional...need to test to see impact on runtime
deliver_cross_active_time = routing.ActiveVar(o) * routing.ActiveVar(d) * time_dimension.CumulVar(d)
# travel limit is command line option, typically one hour,
# meaning the item is allowed to be in vehicle no more
# than one hour over the base travel time from origin to
# destination. Think passengers getting upset about
# picking up other passengers
travel_time = lookup_fn[o_node][d_node] + travel_limit
# deliver earlier than pickup time + allowed travel time
# if not active, 0 <= 0 + travel time, so satisfied
solver.AddConstraint(
deliver_cross_active_time <= pickup_cross_active_time + travel_time)
# routing.AddPickupAndDeliverySets(pickup_disjunction_index, deliver_disjunction_index)
# this API call isn't available until version 7+
routing.AddPickupAndDelivery(pair[0],pair[1])
# not sure if necessary to do a loop for all combinations of alternatives I ran this a "bunch of times" and manually inspected the output. Did not see any violations of pickups after deliveries, etc. I have not yet automated the checking yet, and I have so far only run with one vehicle (well, multiple vehicles are in problem, but only one is used in the solution because I have only <30 or so items to pickup and deliver during testing). I'll finish up and write this up as a blog post or something. Typical output looks like:
|
@GuyBenhaim, I think if you want to prevent a node from being visited, the most efficient thing is to just drop it from the problem entirely, right? But otherwise I think you're proposed constraint will work. |
Thx, |
@jmarca You code seems ok, except one part IMHO, you use Also after you use note: I'll try soon to write a Pickup & Delivery example (without multi P and D however)... |
@Mizux I think you mean my code...and I see what you mean about Also, it appears the API has changed on the or-tools/ortools/constraint_solver/routing.h Line 539 in 75876db
void AddDisjunction(const std::vector<NodeIndex>& nodes, int64 penalty, int64 max_cardinality); (NodeIndex---is it a node, or an index?) and then had internal calls to node_to_index_[node] inside the routing.cc code.
Update: fixed my code above. |
And relevant to your earlier question @GuyBenhaim, I also did a similar thing to get FIFO constraints working too with alternative origins/destinations. Just multiply everything by the value of ActiveVar, and use solver.Max to get the "actual" value. |
Will check that, Thx. |
@jmarca
v7.0 (currrent master branch):
|
Admin: Does it support built-in FIFO/LIFO? Happy Holidays! *
/\
//\\*
*//||\\
|| |
master is v7.0-beta so all examples on master already use v7.0 API... for PD policy follow (Notification: subscribe) #961 |
On Wed, Jan 16, 2019 at 03:19:31AM -0800, Guy wrote:
Closed #968.
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#968 (comment)
I wrote up the tricky bits of *my* solution to this on my website:
https://activimetrics.com/blog/pdptw/multiple_alternatives/
(And if it is considered bad form to post that link here, please delete this post.)
James
|
with VRPTW.py sample, I have following:
I set different values to penalty, still none of the alternative points returned in result:
One more interesting note, when PARALLEL_CHEAPEST_INSERTION is used, the program finishes without solving. LOG:
I think it is more related to @Mizux`s code at the begining |
Hi, |
@GuyBenhaim Thanx for reply! |
I hope this is not too tricky.
If we have two alternative pickup points A and A' and two alternative drop points B and B'. We can pick from either A or A' and drop at either B or B'. We probably need a disjunction for A and A' and separately for B and B'.
How to create a pickup and drop condition in this case?
Can we combine (A and A') and (B and B') and use one condition?
Or must we have four conditions: A and B, A and B', A' and B, A' and B' ?
(this will become 9 if we also have alternatives A'' and B'')
The text was updated successfully, but these errors were encountered: