在 Gurobi Python 中正确制定约束

问题描述

我正在使用带有 gurobi 的 Python IDE 来建模和解决作业问题。这个问题虽然没有说明,但似乎是一个 OVRP(开放式车辆路由问题)(有一些额外的限制)。我现在的代码是:

from itertools import combinations
import gurobipy as gp
from gurobipy import GRB

def find_length(list):
    length = 0
    for i in range(len(list)):
        length += distances[list[i][0]][list[i][1]]
    return length


def find_tours(list):
    tours = []
    for start in list:
        this_tour = []
        begin = start[0]
        end = start[1]
        if begin == 0:

            this_tour.append(start)
            current = start
            while next_route_exists(current,list):
                current = find_next_route(current,list)
                this_tour.append(current)

            tours.append(this_tour)
    return tours


def find_next_route(current_route,list):
    end = current_route[1]
    for tuple in list:
        if tuple[0] == end:
            list.remove(tuple)
            return tuple


def next_route_exists(current_route,list):
    end = current_route[1]
    for tuple in list:
        if tuple[0] == end:
            return True
    return False


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) for i,j in model._vars.keys() if vals[i,j] > 0.5)

        tours = find_tours(selected)
        for tour in tours:
            if find_length(tour) > 400:
                # add subtour elimination constr. for every pair of cities in tour
                model.cblazy(gp.quicksum(distances[i][j] for i,j in tour) <= 400)


# distances between cities

distances = [[0,180,240,85,285,205,235,255,155,120,230,340,220,160,240],# Α
             [0,150,125,100,175,25,65,95,210,55,135,215],# Β1
             [0,105,45,245,195,225,310,115,50],# Β2
             [0,215,170,130,110,260,200,75,150],# Β3
             [0,90,190,50,185],# Β4
             [0,70,40,120],# Β5
             [0,55],# Β6
             [0,250,165,290,20],# Β7
             [0,# Β8
             [0,145,265,230],# Β9
             [0,# Β10
             [0,185,175],# Β11
             [0,270],# Β12
             [0,90],# Β13
             [0,0]]  # B14

n = 15
for i in range(n):
    for j in range(n):
        if j > i:
            distances[j][i] = distances[i][j]

dist = {(i,j): distances[i][j] for i in range(n) for j in range(n)}
m = gp.Model()

vars = m.addVars(dist.keys(),obj=dist,vtype=GRB.BINARY,name='route')

m.addConstrs(vars.sum(i,'*') <= 1 for i in range(1,n))  # a city might be the departure
m.addConstrs(vars.sum('*',i) == 1 for i in range(1,n))  # every city must be the destination

m.addConstr(vars.sum(0,'*') >= 1,"start")  # Could have more than 1 vehicles
m.addConstr(vars.sum('*',0) == 0)  # can't go back to start
m.addConstr(vars.sum(0,'*') <= 8,"start")  # can't have more than 8 vehicle

m.addConstrs(vars[i,i] == 0 for i in range(1,n))  # can't go to  current city from current city
m.addConstrs(vars[i,j] + vars[j,i] <= 1 for i in range(1,n) for j in range(1,n) if  j != i)  # eliminate double edges between cities

route_km = vars.prod(dist)
m.addConstr(route_km <= 400 * vars.sum(0,'*'))  # total km driven




# total vehicles used

total_vehicles = vars.sum(0,'*')
m.setobjective(total_vehicles,GRB.MINIMIZE)

# optimize model

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

# get edges

vals = m.getAttr('x',vars)
selected = gp.tuplelist((i,j in vals.keys() if vals[i,j] > 0.5)
print('')
tours = find_tours(selected)

i = 0
total=0
for tour in tours:
    i += 1
    print('Tour No ' + str(i) + ':')
    print(tour)
    print('is ' + str(find_length(tour)) + ' km long')
    print('')
    total+=find_length(tour)
print('Total : ' + str(total) + ' km')

我现在遇到的问题是,由于我是 gurobi 的新手,我无法表达我想正确添加的约束,这会导致出现错误解决方案。问题在于这个约束:

route_km = vars.prod(dist)
m.addConstr(route_km <= 400 * vars.sum(0,'*'))  # total km driven

这是错误的。我想添加一个约束,每辆卡车最多可以行驶 400 公里。如果选择了另一个“城市”,则一旦行驶或无法行驶该距离,就必须部署一辆新卡车(卡车总数为 8,如约束中所述)。如果制定了这个约束,我相信我也可以消除这个懒惰的约束:

tours = find_tours(selected)
for tour in tours:
    if find_length(tour) > 400:
        # add subtour elimination constr. for every pair of cities in tour
        model.cblazy(gp.quicksum(distances[i][j] for i,j in tour) <= 400)

在此先感谢您的帮助,

乔治

解决方法

延迟约束最适合具有相对大量约束的模型。例如,在旅行商问题的传统 MIP 模型中,存在指数数量的 subtour 消除约束,因此将这些添加为惰性约束是有意义的。

您似乎希望根据行驶总距离对卡车进行容量限制。在 HP Williams 的书 Model Building in Mathematical Programming 中,丢失行李的例子非常相似; here is a link to a Jupiter notebook for this example on the Gurobi website