Google OR-tools Pickups and deliverys Multiple Visits of same node - or-tools

I'm currently using C# and the Google-Or-Tools to solve an VRP problem
Problem Statement:
I have one Depot node 0 and 3 location nodes (1,2) (1',3) (4,5).
Location 1 and 1' is same location (2 order).
VehicleCapacities: 10
At location 1 I need to pickup 10 units
At location 2 I need to dropoff 10 units
At location 1' I need to pickup 5 units
At location 3 I need to dropoff 5 units
Run the program, it displays the following routes
Route 1: 0 Load(0) -> 1 Load(10) -> 2 Load(0) -> 1' Load(5) -> 3 Load(0) -> 4 Load(10) -> 5 Load(0)
https://ibb.co/3sYxQYH (Sorry, I can't upload image)
My question is how to use or-tools to implement a solver that:
Allows each location only visit one time
example
Route 1: 0 Load(0) -> 1 Load(10) -> 2 Load(0) -> 4 Load(10) -> 5 Load(0)
Route 2: 0 Load(0) -> 1' Load(5) -> 3 Load(0)
Location can visits multiple when it's continuous (1 need to pickup 5 unit, 1' need to pickup 5 units)
example: 0 Load(0) -> 1 Load(5) -> 1' Load(10) -> 2 Load(5) -> 3 Load(0) -> 4 Load(10) -> 5 Load(0)

To limit the route to visit only one node among a list (here [1, 1']).
I would:
Create a "location_token" RoutingDimension with:
Force all slack to 0
Set capacity vehicle to at least 1
Force start cumul to zero
increase by one when visiting 1, 1'
note: use an unary callback like in the capacity sample (https://developers.google.com/optimization/routing/cvrp#demand)
token_transit_callback(from_index):
from_node = manager.IndexToNode(from_index)
# return one upon visiting 1 or 1'
if from_node in (one_node, one_prime_node):
return 1
return 0
token_transit_callback_index = routing.RegisterUnaryTransitCallback(
token_transit_callback)
routing.AddDimension(
token_transit_callback_index,
0, # no slack
1, # vehicle capacity
True, # force start cumul to zero
'location_token')
Add a constraint to visit 1 or 1' you need the "location_one_token" dimension to be zero (i.e. you didn't already visit some nodes in the list).
node_list = [one_node, one_prime_node] # 1, 1'
location_token_dim = routing.GetDimensionOrDie('location_token')
for node in node_list:
index = manager.NodeToIndex(node)
routing.Solver().Add(location_token_dim.CumulVar(index) < 1)
If you want to allow 1 -> 1' or 1' -> 1, Then I would use a regular callback with the usual two parameters from_index and to_index.
The trick will be to not increase the token count if for transit(1, 1') and transit(1, 1`)
something like this:
token_transit_callback(from_index, to_index):
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
out = 0
# return one upon visiting 1 or 1'
if from_node in (one_node, one_prime_node):
out = 1
# except if next node is 1 or 1'
if to_node in (one_node, one_prime_node):
out = 0
return out

Related

route optimization with precedences between pairs of nodes

For my routing application I have multiple pairs of nodes which must be visited. The order the nodes in each pair is not important, but both nodes in each pair must be visited after each other. Also, I have a max_travel_distance constraint per vehicle and it is not required to visit all nodes if no optimal solution exist. Meaning that if one node in a pair needs to be dropped, both nodes in the pair must be dropped.
For instance I have three pairs: [A,B], [C,D], [E,F], two valid solutions for two vehicles could be:
(if all node pair can be covered)
1) 0->A->B->C->D->0
2) 0->F->E->0
or
(if only [A,B] is within max_travel_distance)
1) 0->A->B->0
2) 0->0
From the pickup and deliveries example I see how I can make sure that both nodes in a pair always is included in the solution. The problem is that I dont see how I can enforce a constraint so that the two nodes from the same pair always is visited directly after each other, and that the order does not matter (A->B and B->A are both ok)
import math
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
def calc_dmat(pairs, depot = [0,0]):
# Distance matrix
ncol = len(pairs)*2
nodes = [depot] + [p for pair in Pairs for p in pair]
dmat = [[math.dist(c,r) for c in nodes] for r in nodes]
return dmat
# Nodes and pairs
A = [1,1] # Coordinate
B = [1,5]
C = [3,1]
D = [3,8]
E = [6,3]
F = [6,19]
Pairs = [
[A,B],
[C,D],
[E,F]
]
data = {}
data["distance_matrix"] = calc_dmat(Pairs)
data["depot"] = 0
data["num_vehicles"]=2
data["pickups_deliveries"] = [
[1,2],
[3,4],
[5,6]
]
def print_solution(data, manager, routing, solution):
if solution is None:
print("No solution")
return
print(f'Objective: {solution.ObjectiveValue()}')
# Display dropped nodes.
dropped_nodes = 'Dropped nodes:'
for node in range(routing.Size()):
if routing.IsStart(node) or routing.IsEnd(node):
continue
if solution.Value(routing.NextVar(node)) == node:
dropped_nodes += ' {}'.format(manager.IndexToNode(node))
print(dropped_nodes)
max_route_distance = 0
max_route_time = 0
for vehicle_id in range(data['num_vehicles']):
index = routing.Start(vehicle_id)
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
route_distance = 0
route_time = 0
while not routing.IsEnd(index):
plan_output += ' {} -> '.format(manager.IndexToNode(index))
previous_index = index
index = solution.Value(routing.NextVar(index))
route_distance += routing.GetArcCostForVehicle(
previous_index, index, vehicle_id)
plan_output += '{}\n'.format(manager.IndexToNode(index))
plan_output += 'Distance of the route: {}m\n'.format(route_distance)
print(plan_output)
max_route_distance = max(route_distance, max_route_distance)
max_route_time = max(route_time, max_route_time)
print('Maximum of the route distances: {}m'.format(max_route_distance))
print('Maximum of the route time: {}m'.format(max_route_time))
manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
data['num_vehicles'], data['depot'])
routing = pywrapcp.RoutingModel(manager)
def distance_callback(from_index, to_index):
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return data["distance_matrix"][from_node][to_node]
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add Distance constraint.
dimension_name = 'Distance'
routing.AddDimension(
transit_callback_index,
0,
18,
True,
dimension_name)
distance_dimension = routing.GetDimensionOrDie(dimension_name)
# Define Transportation Requests.
for request in data['pickups_deliveries']:
pickup_index = manager.NodeToIndex(request[0])
delivery_index = manager.NodeToIndex(request[1])
routing.AddPickupAndDelivery(pickup_index, delivery_index)
routing.solver().Add(
routing.VehicleVar(pickup_index) == routing.VehicleVar(
delivery_index))
routing.solver().Add(
distance_dimension.CumulVar(pickup_index) <=
distance_dimension.CumulVar(delivery_index))
# Omit some nodes
for node in range(1,len(data["distance_matrix"])):
routing.AddDisjunction([manager.NodeToIndex(node)], 13)
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
# Print solution on console.
print_solution(data, manager, routing, solution)
Output:
*Objective: 25
Dropped nodes:
Route for vehicle 0:
0 -> 3 -> 1 -> 2 -> 4 -> 0
Distance of the route: 9m
Route for vehicle 1:
0 -> 5 -> 6 -> 0
Distance of the route: 16m
Maximum of the route distances: 16m
Maximum of the route time: 0m*
I don't see how I can add a constraint making sure both nodes from the same node is picked successively and independently of the order. A solution to this would be highly appreciated.
Set a dimension representing rank (transit is always 1)
Link the vehicle variables of the two nodes in a pair to be equal
Add each one in a disjunction of size 1 (maybe both in a unique disjunction of size 2)
Add a precedence on the cumul variables of the 2 elements of a pair
Here is another approach.
Create 2 fake nodes 'a->b' and 'b->a', do not create the nodes a and b.
Distance from any node c to 'a->b' is distance c to a. Distance from 'a->b' to any node d is distance from b to d.
Same thing for b->a.
Add 'a->b' and 'b->a' in a disjunction.
And solve.

Condition to compare to previous period rate?

I'm having trouble to come with a condition to compare rate variation between different time periods
VAR flag =
CALCULATE (
MAX('rates'[€/KG]),
'rates', EARLIER ( 'rates'[START_ETD] ) > 'rates'[START_ETD], 'rates'[LP_DESC] = EARLIER ('rates'[LP_DESC])
)
RETURN
'rates'[€/KG]
- IF ( flag = BLANK (), 'rates'[€/KG], flag )
The idea would be not to get the MAX but the previous rate number before the time period/date we are considering! Could someone help me?
Table
LP_DESC €/kg START_ETD END_ETD VARIATION
A 2,5 1/07/2022 14/07/2022 1,5
A 3 15/07/2022 31/07/2022 0,5
B 1,5 1/07/2022 14/07/2022 -3
B 3,5 15/07/2022 31/07/2022 2
A 3,5 1/06/2022 14/06/2022 -
A 1 15/06/2022 31/06/2022 0,5
B 2,5 1/06/2022 14/06/2022 -
B 4,5 15/06/2022 31/06/2022 2

Table sort by month

I have a table in MATLAB with attributes in the first three columns and data from the fourth column onwards. I was trying to sort the entire table based on the first three columns. However, one of the columns (Column C) contains months ('January', 'February' ...etc). The sortrows function would only let me choose 'ascend' or 'descend' but not a custom option to sort by month. Any help would be greatly appreciated. Below is the code I used.
sortrows(Table, {'Column A','Column B','Column C'} , {'ascend' , 'ascend' , '???' } )
As #AnonSubmitter85 suggested, the best thing you can do is to convert your month names to numeric values from 1 (January) to 12 (December) as follows:
c = {
7 1 'February';
1 0 'April';
2 1 'December';
2 1 'January';
5 1 'January';
};
t = cell2table(c,'VariableNames',{'ColumnA' 'ColumnB' 'ColumnC'});
t.ColumnC = month(datenum(t.ColumnC,'mmmm'));
This will facilitate the access to a standard sorting criterion for your ColumnC too (in this example, ascending):
t = sortrows(t,{'ColumnA' 'ColumnB' 'ColumnC'},{'ascend', 'ascend', 'ascend'});
If, for any reason that is unknown to us, you are forced to keep your months as literals, you can use a workaround that consists in sorting a clone of the table using the approach described above, and then applying to it the resulting indices:
c = {
7 1 'February';
1 0 'April';
2 1 'December';
2 1 'January';
5 1 'January';
};
t_original = cell2table(c,'VariableNames',{'ColumnA' 'ColumnB' 'ColumnC'});
t_clone = t_original;
t_clone.ColumnC = month(datenum(t_clone.ColumnC,'mmmm'));
[~,idx] = sortrows(t_clone,{'ColumnA' 'ColumnB' 'ColumnC'},{'ascend', 'ascend', 'ascend'});
t_original = t_original(idx,:);

Coffeescript - Improve algorithm for increasing grouping

The code below works but I am wondering if there is a better way that maybe uses some of the features of coffeescript that I am unfamiliar with.
The problem is this, I need to page items but the paging increases each time.
If I take the number 20 for example, it would create the following pages:
1 - 3
4 - 7
8 - 15
16 - 20
I have the following test and code which does pass:
module 'Remainder',
setup: ->
#remainder = 20
test 'splits remainder incrementally', ->
parts = #remainder.increasingSplit()
equal parts[0], '1 - 3', ''
equal parts[1], '4 - 7', ''
equal parts[2], '8 - 15', ''
equal parts[3], '16 - 20', ''
Number.prototype.increasingSplit = ->
start = 1
nextSplit = 3
parts = []
finished = false
while !finished
if nextSplit > #
parts.push "#{start} - #{#}"
break
parts.push "#{start} - #{nextSplit}"
start = nextSplit + 1
nextSplit = nextSplit * 2 + 1
parts
Without changing the algorithm too much, you can try this:
Number::increasingSplit = ->
start = 1
nextSplit = 3
parts = []
while start <= #
parts.push "#{start} - #{Math.min nextSplit, #}"
start = nextSplit + 1
nextSplit = nextSplit * 2 + 1
parts
The changes were:
replacing .prototype with ::,
removing of the finished variable (which was not being used effectively because the break anyway) and the break altogether and changing the condition to start <= #,
using only one parts.push <part>, with the minimum between nextSplit and # as the top.
Also, i'd advice against extending the Number prototype in this case. Extending the prototype of primitive types can sometimes cause weird problems, like:
Number::isFour = -> # is 4
console.log 4.isFour() # -> false
That happens because inside that function # will be a Number object instead of a primitive number, thus making the === 4 comparison always fail. That would not happen if you define isFour as a standalone function:
isFour = (n) -> n is 4
console.log isFour 4 # -> true
So, i'd prefer this version of incrasingSplit:
increasingSplit = (n) ->
start = 1
nextSplit = 3
parts = []
while start <= n
parts.push "#{start} - #{Math.min nextSplit, n}"
start = nextSplit + 1
nextSplit = nextSplit * 2 + 1
parts
Finally, if you don't mind recursion, you can go with a more FP-style algorithm :)
increasingSplit = (n, start = 1, nextSplit = 3) ->
if start > n
[]
else
part = "#{start} - #{Math.min nextSplit, n}"
rest = increasingSplit n, nextSplit + 1, nextSplit * 2 + 1
[part, rest...]

Facebook interview: find out the order that gives max sum by selecting boxes with number in a ring, when the two next to it is destroyed

Didn't find any similar question about this.
This is a final round Facebook question:
You are given a ring of boxes. Each box has a non-negative number on it, can be duplicate.
Write a function/algorithm that will tell you the order at which you select the boxes, that will give you the max sum.
The catch is, if you select a box, it is taken off the ring, and so are the two boxes next to it (to the right and the left of the one you selected).
so if I have a ring of
{10 3 8 12}
If I select 12, 8 and 10 will be destroyed and you are left with 3.
The max will be selecting 8 first then 10, or 10 first then 8.
I tried re-assign the boxes their value by take its own value and then subtracts the two next to is as the cost.
So the old ring is {10 3 8 12}
the new ring is {-5, -15, -7, -6}, and I will pick the highest.
However, this definitely doesn't work if you have { 10, 19, 10, 0}, you should take the two 10s, but the algorithm will take the 19 and 0.
Help please?
It is most likely dynamic programming, but I don't know how.
The ring can be any size.
Here's some python that solves the problem:
def sublist(i,l):
if i == 0:
return l[2:-1]
elif i == len(l)-1:
return l[1:-2]
else:
return l[0:i-1] + l[i+2:]
def val(l):
if len(l) <= 3:
return max(l)
else:
return max([v+val(m) for v,m in [(l[u],sublist(u,l)) for u in range(len(l))]])
def print_indices(l):
print("Total:",val(l))
while l:
vals = [v+val(m) for v,m in [(l[u],sublist(u,l)) for u in range(len(l)) if sublist(u,l)]]
if vals:
i = vals.index(max(vals))
else:
i = l.index(max(l))
print('choice:',l[i],'index:',i,'new list:',sublist(i,l))
l = sublist(i,l)
print_indices([10,3,8,12])
print_indices([10,19,10,0])
Output:
Total: 18
choice: 10 index: 0 new list: [8]
choice: 8 index: 0 new list: []
Total: 20
choice: 10 index: 0 new list: [10]
choice: 10 index: 0 new list: []
No doubt it could be optimized a bit. The key bit is val(), which calculates the total value of a given ring. The rest is just bookkeeping.