I am aware that there is a TSP example on the Gurobi website. I took a great amount of time understanding it, however I was not able to (completely). Therefor I decided to make a more simple one by my self.
The problem: I am not able to get rid of sub-tours and I don't know how and which constraint I should include to visit pick up points before going to the deliver point. It is not necessary to immediately go from pick up point to deliver point. It is fine if multiple pick up points are visited before going to the deliver points.
My code does the following: it generates random orders containing: pick up points, deliver points and amount of packages to be send (if this succeeds I also want to include delivery times, capacity of the vehicle, multiple vehicles, etc.). After generating the orders it creates a distance matrix between the vehicle, pick up points and deliver points. Then I use the Gurobi optimizer to find the optimal route. I was able to add constraints that prevent traveling from point i to point i. I added the constraint that every point should be visited and since it is a symmetrical matrix, I also added the constraint that prevents traveling between two points: 0 - 4 - 0 - 4 -...
# Using spyder 3.6 and gurobi. For simplicity I only added the DISTANCE matrix, not how the orders and distance matrix are created
import numpy as np
from gurobipy import *
DISTANCE = [[0, 96.64884893, 94.41398202, 88.23264702, 18.86796226, 97.52948272, 105.99056562],
[96.64884893, 0, 183.14202139, 19.02629759, 80.77747211, 194.17775362, 189.1692364],
[94.41398202, 183.14202139, 0, 169.53170795, 113.25193155, 55.22680509, 122.58874337],
[88.23264702, 19.02629759, 169.53170795, 0, 74.81310046, 185.01081049, 187.01069488],
[18.86796226, 80.77747211, 113.25193155, 74.81310046, 0, 114.28035702, 113.35784049],
[97.52948272, 194.17775362, 55.22680509, 185.01081049, 114.28035702, 0, 73.00684899],
[105.99056562, 189.1692364, 122.58874337, 187.01069488, 113.35784049, 73.00684899, 0]]
dist = np.array(DISTANCE)
n=dist.shape[0]
# Create model
m = Model('Pickup_Deliver_Optimizer')
# Add variables
x = {}
for i in range(n):
for j in range(n):
x[i,j] = m.addVar(vtype=GRB.BINARY)
# Objective function
obj = (quicksum(x[i,j]*DISTANCE[i][j] for i in range(n) for j in range(n)))
m.setObjective(obj, GRB.MINIMIZE)
# Constraints
# Constraint that does not allow to travel from point i to point i
for i in range(n):
m.addConstr(x[i,i],GRB.EQUAL,0)
# To prevent the vehicle from returning to the same point from the previous point: if x[i,j] == 1, then x[j,i] == 0
m.addConstrs((x[i,j] == 1) >> (x[j,i] == 0) for i in range(n) for j in range(n))
# Visit all points by making a connection between two points
for i in range(n):
m.addConstr(quicksum(x[i,j] for j in range(n)),GRB.EQUAL,1)
m.addConstr(quicksum(x[j,i] for j in range(n)),GRB.EQUAL,1)
# The vehicle should return to its starting point
m.addConstr(quicksum(x[i,0] for i in range(n)),GRB.EQUAL,1)
# The vehicle always starts at its starting point
m.addConstr(quicksum(x[0,i] for i in range(n)),GRB.EQUAL,1)
m.update()
m.optimize()
Using the TSP example from Gurobi, the optimal solution should be ~522. The result I get is ~457. The difference is the result of different routes. According to the Gurobi example the correct should be [0, 4, 1, 3, 2, 5, 6, 0]. My code creates the following two loops: [0, 4, 1, 3, 0] and [2, 5 ,6, 2]. The two routes combined have a shorter distance than the one route from the Gurobi example, thus I get the two routes as the optimal solution. I know what goes wrong, but I have no idea how to solve this issue in terms of addConstr(). I looked online what theory is behind the subtour, according to the Miller-Tucker-Zemlin method on Wikipedia https://en.wikipedia.org/wiki/Travelling_salesman_problem, I would have to add a new constraint 'u'. However, I would think it could be solved easier by adding a constraint like this:
# Create one route
for j in range(n):
if j != 0:
m.addConstrs((x[i,j] == 1) >> (quicksum(x[j,k] == 1)) for i in range(n) for k in range(n))
This line of code does not work, but it would seem like something like this should solve the issue. It would force the nodes to be connected with each other.
The second issue that I can't seem to solve is visiting the pick up point before the deliver point. Odd rows/columns in the DISTANCE matrix represent pick up points and even rows/columns represent deliver points (row/column 0 is the starting point of the vehicle). Can I solve this with the current variable x[i,j] or do I need to add an additional variable (which I expect)?
Any help is much appreciated.