0

I'm trying to build a model using the traveling salesman as a starting point. Instead of being just one traveling salesman I need it to be multiple salesmans that have to reach the same end node and then come back to the origin node. The logic is the same, trying to minimize distance traveled by all salesmans and that between all of them, they cover every node (city). I implemented the model using excel's solver, but it has problems with subtours, so I decided to use gurobi since it had pre made constraint to fix that in the traveling salesman example.

The basic optimization model is this:

enter image description here

Plus the subtour constraints.

The model I'm doing is more complicated because it needs times of arrivals, volume restrictions and others, so if I can't make this work, I surely cannot proceed.

Before typing the code my inputs are:

nodes = ['Node 1', 'Node 2', ... , 'Node9'] 
dist = {(Node1,Node2): 0.03, ..., (Node9, Node8): 0.5} #--> Distances between nodes for different nodes
salesmans = ['salesman1', 'salesman2']

The code I used in gurobi/python is:

import math
import json
from itertools import combinations,product

def subtourelim(model, where):
    if where == GRB.Callback.MIPSOL:
        # make a list of edges selected in the solution
        vals = model.cbGetSolution(model._vars)
        selected = gp.tuplelist((i, j, k) for i, j, k in model._vars.keys()
                         if vals[i, j, k] > 0.5)
        # find the shortest cycle in the selected edge list
        tour = subtour(selected)
        if len(tour) < len(nodes):
            # add subtour elimination constr. for every pair of cities in subtour
            model.cbLazy(gp.quicksum(model._vars[i, j, k] for i, j, k in combinations(tour, 2))
                     <= len(tour)-1)
def subtour(edges):
    unvisited = nodes[:]
    cycle = nodes[:] # Dummy - guaranteed to be replaced
    while unvisited:  # true if list is non-empty
        thiscycle = []
        neighbors = unvisited
        while neighbors:
            current = neighbors[0]
            thiscycle.append(current)
            unvisited.remove(current)
            neighbors = [j for i, j, k in edges.select(current, '*')
                     if j in unvisited]
        if len(thiscycle) <= len(cycle):
            cycle = thiscycle # New shortest subtour
    return cycle

import gurobipy as gp
from gurobipy import GRB

m = gp.Model()

# Variables: is city 'i' adjacent to city 'j' on the tour?
vars = m.addVars(dist.keys(), salesmans, obj=dist, vtype=GRB.BINARY, name='asignacion')


# Constraints: A node can't be visited by itself o leave to visit itself
for i, j, k in vars.keys():
    if i==j:
        m.addConstr(vars[i, j, k] == 0)

# From each node you have to visit one other node
m.addConstrs((vars.sum(i,'*','*') == 1 for i in nodes))
# Each node has to be visited once
m.addConstrs((vars.sum('*',j,'*') == 1 for j in nodes))


# Optimize the model
m._vars = vars
m.Params.lazyConstraints = 1
m.optimize(subtourelim)

When I just try to add those few constraints, the model has errors on the subtour functions that I don't understand

ValueError                                Traceback (most recent call last)
callback.pxi in gurobipy.CallbackClass.callback()

<ipython-input-2-a1cb8952ed8c> in subtourelim(model, where)
     13         if len(tour) < len(nodes):
     14             # add subtour elimination constr. for every pair of cities in subtour
---> 15             model.cbLazy(gp.quicksum(model._vars[i, j, k] for i, j, k in combinations(tour, 2))
     16                          <= len(tour)-1)

gurobi.pxi in gurobipy.quicksum()

<ipython-input-2-a1cb8952ed8c> in <genexpr>(.0)
     13         if len(tour) < len(nodes):
     14             # add subtour elimination constr. for every pair of cities in subtour
---> 15             model.cbLazy(gp.quicksum(model._vars[i, j, k] for i, j, k in combinations(tour, 2))
     16                          <= len(tour)-1)

ValueError: not enough values to unpack (expected 3, got 2)

If anyone can help me I would be very very grateful :)

  • What is your application here? Is it for commercial use? – roganjosh May 25 '20 at 20:17
  • If it is, I've not used Gurobi myself, but I've struggled to reconcile how I could get all the constraints lined up to solve commercial problems. Full disclosure, I'm a listed contributor from years ago, but I would suggest [jsprit](https://github.com/graphhopper/jsprit), though it is in Java. I use it on my site [here](https://www.jpilkington.com/vehicle_routing/). It's sadly not Python, but it's pre-packaged for these problems. – roganjosh May 25 '20 at 20:28
  • Thanks @roganjosh but I don't know Java :( . No, it's not for commercial use. University project that probably won't even be implemented. – FelipeMedLev May 25 '20 at 20:30
  • Neither do I :P – roganjosh May 25 '20 at 20:31
  • Oh! Thanks I just checked it and it solves traveling salesman and vehicle routing, which is pretty much this problem. – FelipeMedLev May 25 '20 at 20:32
  • Formulating the mTSP as a single MIP is possible ([link](https://yetanothermathprogrammingconsultant.blogspot.com/2009/03/mtspvrp-example.html)) but for larger problems, you will need other approaches (e.g. specialized algorithms such as column generation, or settle for heuristics). – Erwin Kalvelagen May 25 '20 at 22:46
  • @ErwinKalvelagen I think I can make it work if I get the subtour eliminations correct. There's a problem in the subtour_elim function (which I dont really understand) when I add the "k" subindex. The difference with the mTSP is that I need to know which salesman follows each path, since I'll be using trucks with capacity. – FelipeMedLev May 25 '20 at 22:57

2 Answers2

1

Your problem is unrelated to gurobi.

for i, j, k in combinations(tour, 2)

combinations(l, r) returns tuples with length r, in this case 2. You try to unpack it into three elements. Did you mean combinations(tour, 3)? I can give more accurate advice if you do not leave out the subtour constraints from your question.

orlp
  • 112,504
  • 36
  • 218
  • 315
  • Thanks! Yes that's a mistake I did. I just know the basics of python so I probably did many mistakes in python when I tried adding the third element. What do you mean by "I can give more accurate advice if you do not leave out the subtour constraints from your question."? – FelipeMedLev May 25 '20 at 23:38
  • @FelipeMedLev Well you included many constraints but not the subtour constraints. I can't tell you what the correct Python translation is if you do not specify the constraint itself. – orlp May 25 '20 at 23:48
  • Oh. No the constraint is included. It's actually a lazy constraint so when it solves the model, it evokes the function of "subtour_elim". – FelipeMedLev May 25 '20 at 23:52
0

Thanks to @orlp that hinted me my mistake, I had to fix the "subtour_elim" function like this:

def subtourelim(model, where):
    if where == GRB.Callback.MIPSOL:
        # make a list of edges selected in the solution
        vals = model.cbGetSolution(model._vars)
        selected = gp.tuplelist((i, j, k) for i, j, k in model._vars.keys()
                             if vals[i, j, k] > 0.01)
        # find the shortest cycle in the selected edge list
        tour = subtour(selected)
        if len(tour) < len(nodes):
            # add subtour elimination constr. for every pair of cities in subtour
            for k in salesmans:
                model.cbLazy(gp.quicksum(model._vars[i, j, k] for i, j in combinations(tour, 2)) <= len(tour)-1)