正常情况下,使用 pip install mip即可完成这个包的安装。
pip install mip
但是在我安装的使用报了下面这样一个错误:
ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问。: ‘d:\…\anaconda3\lib\site-packages\_cffi_backend.cp38-win_amd64.pyd’
Consider using the --user
option or check the permissions.
如果你遇见了这个错误,可以使用在安装指令中添加 --user 选项,最终命令是:
pip install mip --user
这是mip库的主类,通过该类可以完成模型的构建、决策变量的添加、约束的添加、目标函数的设置、模型的求解以及解的状态查询。
在构造Model对象时,可以不传递任何参数,也可以指定相关参数:
# 方式1
mp = Model()
# 方式2:这种方式指定了模型的名称、模型的目标函数类型、求解模型要调用的求解器;这里指定为Gurobi,还可以使用开源的 CBC
mp = Model(name='TSP',sense=MINIMIZE,solver_name='GRB')
添加约束涉及的api:
# 添加单个约束
model.add_constr(x1 + x2 <= 9, name='约束1')
model += x1 + x2 <= 9, "约束1"
# 添加求和约束
model += xsum(x[i] for i in range(2)) <= 9, '约束1'
添加决策变量涉及的API:
x = model.add_var(name='x', lb=0.0, ub=10.0, var_type='C') # 创建一个连续型决策变量
x = model.add_var_tensor(name='x',shape=(3,3),var_type='B') # 创建3*3个二进制决策变量
x = [[ model.add_var(name='x',var_type='I') for i in range(0,3)] for j in range(0,3)] # 创建3*3个整数决策变量
状态说明:
代码示例:
status = lp.optimize()
if status == OptimizationStatus.OPTIMAL:
print('模型已求解到最优')
下面介绍几个提升编码效率的有用的方法:
要用的类是:ConstrsGenerator,该类的标准定义为:
class myConstrsGen:
def _nint__():
# 类的构造函数
def generate_constrs(model, depth=0, npass=0):
# 编写你的生割代码
# 求解器引擎将自动调用该方法,因此这个方法是生割类必须有的方法,不能少
这些有效不等式是定义在 CutType类中的,主要包含以下几种:
使用方法是:
cp = model.generate_cuts([CutType.GOMORY, CutType.MIR, CutType.ZERO_HALF, CutType.KNAPSACK_COVER])
CutPool类中仅有一个方法, add();可以使用该方法将构造的一个或多个cut存储起来,最后统一添加到数学模型中。其典型的用法是:
cp = CutPool()
cp.add(x1 + x2 <= 12)
cp.add(x1 + x2 >= 5)
for cut in cp.cuts:
model += cut
解释:
for idx in range(multiSol.num_solutions):
print(f'第{idx}个可行解的目标函数值为{multiSol.objective_values[idx]}')
print(f"x1={x1.xi(idx)},x2={x2.xi(idx)}")
给定一组城市坐标,旅行商需要从其中的某一个城市出发,每个城市访问一次,最终返回到他出发的城市。
目标函数是:最小化旅行商访问所有城市的总距离
m i n : ∑ i ∈ N ∑ j ∈ N x i j c i j min:\sum_{i\in N}\sum_{j\in N}x_{ij}c_{ij} min:i∈N∑j∈N∑xijcij
约束条件1:所有节点的出度为1
∑ j ∈ N x i j = 1 , ∀ i ∈ N \sum_{j \in N} x_{ij} = 1,\forall i \in N j∈N∑xij=1,∀i∈N
约束条件2:所有节点的入度为1
∑ i ∈ N x i j = 1 , ∀ j ∈ N \sum_{i \in N} x_{ij} = 1,\forall j \in N i∈N∑xij=1,∀j∈N
约束条件3:MTZ子回路消除约束
u i + 1 ≤ u j + ( n − 1 ) ∗ ( 1 − x i j ) , ∀ i , j ∈ N , i ≠ 0 u_{i} + 1 \leq u_{j} + (n-1)*(1-x_{ij}),\forall i,j \in N, i\neq 0 ui+1≤uj+(n−1)∗(1−xij),∀i,j∈N,i=0
注:MTZ形式的子回路消除约束中,编码时务必要注意 i 的取值不能为0,否则这个约束会在出发点处,产生矛盾;举个例子,假如我们有3个城市,分别记为0,1,2,得到的一条路径为 0-1-2-0;那么对应的 u 的取值为 u 0 = 0 , u 1 = 1 , u 2 = 2 u_{0}=0, u_{1} = 1, u_{2} = 2 u0=0,u1=1,u2=2;由于2还要回到0,这就要求 u 0 = 3 u_{0}=3 u0=3,就跟前面的 u 0 = 0 u_{0}=0 u0=0 向矛盾了。所以这里要对出发的城市做一个特殊处理。
约束条件4:决策变量的定义域约束
u i ∈ [ 0 , n − 1 ] , ∀ i ∈ N x i j ∈ { 0 , 1 } , ∀ i , j ∈ A u_{i} \in [0,n-1],\forall i \in N \\ x_{ij} \in \{0,1\},\forall i,j \in A ui∈[0,n−1],∀i∈Nxij∈{0,1},∀i,j∈A
代码的编写思路:
from mip import *
import numpy as np
import matplotlib.pyplot as plt
# 定义城市列表、城市坐标
num_of_city = 30
coors = np.random.randint(low=0, high=200, size=(num_of_city,2))
# 定义建模需要的节点集合和弧段集合
N = [i for i in range(0, num_of_city)]
A = [(i,j) for i in N for j in N if i!=j]
# 定义成本矩阵
cost = {(i,j):np.sqrt((coors[i,0]-coors[j,0])**2 + (coors[i,1]-coors[j,1])**2) for i,j in A}
# 构建模型
tsp = Model(name='TSP',sense=MINIMIZE,solver_name='GRB')
# 添加决策变量
x = tsp.add_var_tensor(shape=(num_of_city,num_of_city),var_type='B',name='x')
u = tsp.add_var_tensor(shape=(num_of_city,), name='u')
# 设置目标函数
tsp.objective = xsum(x[i,j]*cost[i,j] for i,j in A)
# 添加节点的入度约束
for j in N:
tsp.add_constr(xsum(x[i,j] for i in N if i!=j) == 1)
# 添加节点的出度约束
for i in N:
tsp.add_constr(xsum(x[i,j] for j in N if i!=j) == 1)
# 添加MTZ形式的子回路消除约束
for i,j in A:
if i!=0:
tsp.add_constr(u[i] + 1 <= u[j] + num_of_city*(1-x[i,j]))
tsp.optimize()
tsp.write('tsp.lp')
active_x = [(i,j) for i,j in A if x[i,j].x >= 0.99]
print(active_x)
for i,j in active_x:
plt.plot([coors[i,0],coors[j,0]],[coors[i,1],coors[j,1]], c='c')
plt.show()
在上一个实现方式中,我们追求代码简洁,同时倾向于通过方法调用的方式构建模型;在这个实现这个实现方式中,我们中规中矩的使用官方推荐的方式来构建我们的TSP模型。
from mip import *
import numpy as np
import matplotlib.pyplot as plt
# 定义城市列表、城市坐标
num_of_city = 30
coors = np.random.randint(low=0, high=200, size=(num_of_city,2))
# 定义建模需要的节点集合和弧段集合
N = [i for i in range(0, num_of_city)]
A = [(i,j) for i in N for j in N if i!=j]
# 定义成本矩阵
cost = {(i,j):np.sqrt((coors[i,0]-coors[j,0])**2 + (coors[i,1]-coors[j,1])**2) for i,j in A}
# 构建模型
tsp = Model(name='TSP',sense=MINIMIZE,solver_name='GRB')
# 添加决策变量
x = [[tsp.add_var(var_type=BINARY) for i in N] for j in N]
u = [tsp.add_var(var_type='I') for i in N]
print(x)
# 设置目标函数
tsp.objective = xsum(x[i][j]*cost[i,j] for i,j in A if i!=j)
# 添加节点的入度约束
for j in N:
tsp += xsum(x[i][j] for i in N if i!=j) == 1, f'indegree_{j}'
# 添加节点的出度约束
for i in N:
tsp += xsum(x[i][j] for j in N if i!=j) == 1, f'oudegree_{i}'
# 添加MTZ形式的子回路消除约束
for i,j in A:
if i!=0:
tsp += u[i] + 1 <= u[j] + num_of_city*(1-x[i][j]), f'sec_[{i},{j}]'
tsp.optimize()
tsp.write('tsp.lp')
active_x = [(i,j) for i,j in A if x[i][j].x >= 0.99]
print(active_x)
for i,j in active_x:
plt.plot([coors[i,0],coors[j,0]],[coors[i,1],coors[j,1]], c='c')
plt.show()
两种实现方式的区别在于:
由于子回路消除约束是一类特殊的约束,可以在发现子回路时,再将它加入到模型中也是可以的。因此,我们可以首先求解TSP的线性松弛模型,然后根据松弛解构造图,并利用最小割算法生成子回路。最小割算法在networkX中提供有直接可用的方法 minimum_cut。
from mip import *
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
# 定义城市列表、城市坐标
num_of_city = 30
coors = np.random.randint(low=0, high=200, size=(num_of_city,2))
# 定义建模需要的节点集合和弧段集合
N = [i for i in range(0, num_of_city)]
A = [(i,j) for i in N for j in N if i!=j]
# 定义成本矩阵
cost = {(i,j):np.sqrt((coors[i,0]-coors[j,0])**2 + (coors[i,1]-coors[j,1])**2) for i,j in A}
# 构建模型
tsp = Model(name='TSP',sense=MINIMIZE,solver_name='CBC')
# 添加决策变量
x = tsp.add_var_tensor(shape=(num_of_city,num_of_city),var_type='B',name='x')
u = tsp.add_var_tensor(shape=(num_of_city,), name='u')
# 设置目标函数
tsp.objective = xsum(x[i,j]*cost[i,j] for i,j in A)
# 添加节点的入度约束
for j in N:
tsp.add_constr(xsum(x[i,j] for i in N if i!=j) == 1)
# 添加节点的出度约束
for i in N:
tsp.add_constr(xsum(x[i,j] for j in N if i!=j) == 1)
newConstraints = True
while newConstraints:
tsp.optimize(relax=True)
print("status: {} objective value : {}".format(tsp.status, tsp.objective_value))
G = nx.DiGraph()
for a in A:
G.add_edge(a[0], a[1], capacity=x[a].x)
newConstraints = False
# 使用最小割方法找到子回路时,将子回路加入到模型当中
for (n1, n2) in [(i, j) for (i, j) in A if i != j]:
cut_value, (S, NS) = nx.minimum_cut(G, n1, n2)
if cut_value <= 0.99:
tsp += (xsum(x[a] for a in A if (a[0] in S and a[1] in S)) <= len(S) - 1)
newConstraints = True
# 如果子回路消除约束不产生cut时,我们使用其他的割平面方法产生有效不等式
if not newConstraints and tsp.solver_name.lower() == "cbc":
cp = tsp.generate_cuts([CutType.GOMORY, CutType.MIR, CutType.ZERO_HALF, CutType.KNAPSACK_COVER])
if cp.cuts:
tsp += cp
newConstraints = True
tsp.write('tsp.lp')
active_x = [(i,j) for i,j in A if x[i,j].x >= 0.99]
print(active_x)
for i,j in active_x:
plt.plot([coors[i,0],coors[j,0]],[coors[i,1],coors[j,1]], c='c')
plt.show()
最后我们讨论一种lazy cut的实现方式,在这种方式建立在初始模型是不完整的假设上的,也就是当模型捕获到一个整数解时,他会自动检查这个解是否时可行的;如果得到的整数解时不可行的,那么我们会添加一个或几个约束来确保未来这个不可行的整数解不会再出现。
from typing import List, Tuple
from random import seed, randint
from itertools import product
from math import sqrt
import networkx as nx
from mip import Model, xsum, BINARY, minimize, ConstrsGenerator, CutPool
import matplotlib.pyplot as plt
class SubTourCutGenerator(ConstrsGenerator):
"""Class to generate cutting planes for the TSP"""
def __init__(self, Fl: List[Tuple[int, int]], x_, N_):
self.F, self.x, self.N = Fl, x_, N_
def generate_constrs(self, model: Model, depth: int = 0, npass: int = 0):
xf, N_, cp, G = tsp.translate(self.x), self.N, CutPool(), nx.DiGraph()
for (u, v) in [(k, l) for (k, l) in product(N_, N_) if k != l and xf[k][l]]:
G.add_edge(u, v, capacity=xf[u][v].x)
for (u, v) in F:
val, (S, NS) = nx.minimum_cut(G, u, v)
if val <= 0.99:
aInS = [(xf[i][j], xf[i][j].x) for (i, j) in product(N_, N_) if i != j and xf[i][j] and i in S and j in S]
if sum(f for v, f in aInS) >= (len(S)-1)+1e-4:
cut = xsum(1.0*v for v, fm in aInS) <= len(S)-1
cp.add(cut)
if len(cp.cuts) > 256:
for cut in cp.cuts:
model += cut
return
for cut in cp.cuts:
model += cut
if __name__ == '__main__':
num_of_city = 50 # 城市数目
N = set(range(num_of_city))
seed(0)
coors = [(randint(1, 100), randint(1, 100)) for i in N] # coordinates
Arcs = [(i, j) for (i, j) in product(N, N) if i != j]
# 距离矩阵
c = [[round(sqrt((coors[i][0]-coors[j][0])**2 + (coors[i][1]-coors[j][1])**2)) for j in N] for i in N]
tsp = Model()
# 二进制决策变量表示弧段是否被旅行商使用v
x = [[tsp.add_var(var_type=BINARY) for j in N] for i in N]
# 设置最小化距离目标函数
tsp.objective = minimize(xsum(c[i][j]*x[i][j] for (i, j) in Arcs))
# 出度一次约束
for i in N:
tsp += xsum(x[i][j] for j in N - {i}) == 1
# 入度一次约束
for i in N:
tsp += xsum(x[j][i] for j in N - {i}) == 1
# 移除仅包含两个点的子回路
for (i, j) in Arcs:
tsp += x[i][j] + x[j][i] <= 1
# 对于点集中的每个点,我们找到距离其最远的点,然后首先检查他们是否构成了子回路
F, G = [], nx.DiGraph()
for (i, j) in Arcs:
G.add_edge(i, j, weight=c[i][j])
for i in N:
P, D = nx.dijkstra_predecessor_and_distance(G, source=i)
DS = list(D.items())
DS.sort(key=lambda x: x[1])
F.append((i, DS[-1][0]))
# tsp.cuts_generator = SubTourCutGenerator(F, x, N)
tsp.lazy_constrs_generator = SubTourCutGenerator(F, x, N)
tsp.optimize()
print('解的状态: %s 路径长度 %g' % (tsp.status, tsp.objective_value))
tsp.write('tsp.lp')
active_x = [(i,j) for i,j in Arcs if x[i][j].x >= 0.99]
print(active_x)
for i,j in active_x:
plt.plot([coors[i][0],coors[j][0]],[coors[i][1],coors[j][1]], c='c')
for i,txt in enumerate(N):
plt.annotate(txt,(coors[i][0]+0.5, coors[i][1]+0.5))
plt.show()
下面给出一个简单的线性规划问题,其对应的表达式为:
m a x : 2 x 1 + 3 x 2 − 5 x 3 s . t x 1 + x 2 + x 3 = 7 2 x 1 − 5 x 2 + x 3 ≥ 10 x 1 + 3 x 2 + x 3 ≤ 12 x 1 , x 2 , x 3 ≥ 0 max: 2x_{1} + 3x_{2} - 5x_{3} \\ s.t \\ x_{1} + x_{2} + x_{3} = 7 \\ 2x_{1} - 5x_{2} + x_{3} \geq 10 \\ x_{1} + 3x_{2} + x_{3} \leq 12 \\ x_{1},x_{2},x_{3} \geq 0 max:2x1+3x2−5x3s.tx1+x2+x3=72x1−5x2+x3≥10x1+3x2+x3≤12x1,x2,x3≥0
求解盖模型的代码为:
from mip import *
lp = Model()
x = lp.add_var_tensor(shape=(3,),var_type='C',name='x')
lp.objective = maximize(2*x[0] + 3*x[1] - 5*x[2])
lp += x[0] + x[1] + x[2] == 7, '约束1'
lp += 2*x[0] - 5*x[1] + x[2] >= 10, '约束2'
lp += x[0] + 3*x[1] + x[2] <= 12, '约束3'
lp.optimize()
active_x = [x[i].x for i in range(3)]
print(active_x)
模型的最优解为:[6.428571428571429, 0.5714285714285712, 0.0],最优值为14.571
active_pi = [lp.constr_by_name(f'约束{i}').pi for i in range(1,4)]
print(active_pi)
模型的对偶变量取值为:[2.2857142857142856, -0.14285714285714285, 0.0]
print(f'目标函数值为{lp.objective_value}')
print(f'目标函数值的界为{lp.objective_bound}')
下面给出一个简单的整数规划问题,其对应的表达式为:
m i n : 5 x 1 + 5 x 2 s . t 3 x 1 + x 2 ≥ 6 x 1 + x 2 ≥ 4 x 1 + 3 x 2 ≥ 6 x 1 , x 2 ∈ Z + min: 5x1 + 5x2 \\ s.t \\ 3x1 + x2 \geq 6 \\ x1 + x2 \geq 4 \\ x1 + 3x2 \geq 6 \\ x1, x2 \in Z^{+} min:5x1+5x2s.t3x1+x2≥6x1+x2≥4x1+3x2≥6x1,x2∈Z+
求解盖模型的代码为:
from mip import *
multiSol = Model()
x1 = multiSol.add_var(var_type='I',name='x1')
x2 = multiSol.add_var(var_type='I',name='x1')
multiSol.objective = 5*x1 + 5*x2
multiSol += 3*x1 + x2 >= 6
multiSol += x1 + x2 >= 4
multiSol += x1 + 3*x2 >= 6
status = multiSol.optimize(max_solutions=10)
if status == OptimizationStatus.OPTIMAL:
print('模型已求解到最优')
for idx in range(multiSol.num_solutions):
print(f'第{idx}个可行解的目标函数值为{multiSol.objective_values[idx]}')
print(f"x1={x1.xi(idx)},x2={x2.xi(idx)}")
打印结果:
模型已求解到最优
第0个可行解的目标函数值为20.0
x1=3.0,x2=1.0
第1个可行解的目标函数值为30.0
x1=0.0,x2=6.0