Gurobi 干货 · 基础架构
Part A · 从 Python 到 Gurobi Model
我们使用 Python package gurobipy 来调用 Gurobi Optimizer。变量、表达式、约束、目标函数以及求解接口,都通过这个 package
提供。
gurobipy 是什么?
import gurobipy as gp
from gurobipy import GRB
gurobipy 是 Gurobi 的 Python package。使用 gp 作为缩写后,可以通过
gp.Model()、gp.quicksum() 等函数建模。
m = gp.Model("mip1")
gp.Model() 会创建一个 Gurobi model object。这里模型名称是 "mip1",后续通过变量 m
来操作这个模型。
Methods / Functions:执行操作,例如
m.addVar()、m.addConstr()、m.optimize()。
Attributes:读取模型信息或结果,例如
m.Status、m.ObjVal、m.Runtime。
Part B · 一个 Gurobi 模型的 6 个核心组成
gp.Model()
addVar / addVars
setObjective
addConstr / addConstrs
model.optimize()
Status / ObjVal / X
Part C · 常用建模函数及其参数
addVar():添加一个变量
x = m.addVar(
lb=0,
ub=1,
obj=0,
vtype=GRB.BINARY,
name="x"
)
lb / ub:下界 /
上界;obj:变量在线性目标中的系数;vtype:变量类型;name:变量名称。
addVars():批量添加变量
x = m.addVars(
I, G,
lb=0,
vtype=GRB.BINARY,
name="x"
)
前面的 I, G 是索引集合;其他常用参数与 addVar() 类似。得到的 x[i,g] 可以按索引访问。
setObjective():设置目标函数
m.setObjective(
40*x1 + 30*x2,
GRB.MAXIMIZE
)
第一个参数是目标 expression;第二个参数是目标方向,例如 GRB.MAXIMIZE 或 GRB.MINIMIZE。
addConstr():添加一条约束
m.addConstr(
2*x1 + x2 <= 100,
name="resource1"
)
第一个参数是约束表达式;name 是可选的约束名称,后续查看模型和 dual value 时很有用。
addConstrs():批量添加约束
m.addConstrs(
(gp.quicksum(x[i,g] for g in G) == 1
for i in I),
name="assign"
)
第一个参数通常是一个 generator expression,用来表示一整族约束;name 为这组约束设置统一前缀。
optimize():调用求解器
m.optimize()
通常不需要传参数。模型、变量、目标和约束都添加完以后,调用它开始求解。
GRB 是什么?
GRB 是 gurobipy 中集中存放常用常量的对象。我们经常用它指定变量类型、目标方向和判断求解状态。
GRB.CONTINUOUSGRB.BINARYGRB.INTEGER
GRB.MAXIMIZEGRB.MINIMIZE
GRB.OPTIMALGRB.INFEASIBLEGRB.UNBOUNDEDGRB.TIME_LIMIT
Part D · Expression:目标函数和约束的“中间语言”
Gurobi 不是只接受单个变量。像 2*x + 3*y、x + y + z 这样的线性组合会形成
expression,然后再被放进目标函数或约束中。
expr = 40*x1 + 30*x2
m.setObjective(expr, GRB.MAXIMIZE)
m.addConstr(2*x1 + x2 <= 100)
左侧的 2*x1 + x2 就是一个 linear expression。
Part E · 如何自助查 API
不会用某个 API?先用 help()
help(gp.Model)
help(m.addVar)
help(m.addVars)
help(m.setObjective)
help(m.addConstr)
help(m.addConstrs)
help(gp.quicksum)
如何读取基本求解结果
model.Status求解状态:最优?不可行?无界?model.ObjVal当前已求得解的目标函数值。x.X变量 x 在当前解中的取值。常见状态
| Status | 含义 | 下一步? |
|---|---|---|
GRB.OPTIMAL |
已按容差证明当前模型的最优解 | 检查模型含义并解释决策 |
GRB.INFEASIBLE |
不存在同时满足全部约束的解 | 检查约束冲突、数据和单位 |
GRB.UNBOUNDED |
目标可以无限改善 | 检查是否漏了容量/上界等约束 |
GRB.TIME_LIMIT |
达到时间上限后停止 | 检查问题是否太难,查看可行解、bound 和 gap |
练习 1 · A Simple Example
- Create a model — 创建模型
- Add variables to the model — 添加决策变量
- Add objective to the model — 添加目标函数
- Add constraints to the model — 添加约束
- Let the model solve itself — 调用求解器
- Get the solution and objective values — 读取解与目标值
查看基础代码框架
下面只给出程序骨架。请根据上面的数学模型补全变量、目标函数和约束。
import gurobipy as gp
from gurobipy import GRB
# 1. Create a model ( gp.Model )
# 2. Add variables to the model ( m.addVar(...) )
# 3. Add objective to the model ( m.setObjective() )
# 4. Add constraints to the model ( m.addConstrs() )
# 5. Solve the model (m.optimize())
# 6. Get the solution and objective values (m.Status,x.X,m.ObjVal)
Gurobi 干货 · 规模化建模
当模型变大以后,如何批量创建变量、添加约束、写表达式?
addVars():批量创建变量
I = range(4)
G = range(2)
x = m.addVars(I, G,
vtype=GRB.BINARY,
name="x")
x[i,g] 对应数学模型中的 xig。Gurobi 返回的是可按索引访问的 tupledict。
addConstrs() + quicksum()
m.addConstrs(
(gp.quicksum(x[i,g] for g in G) == 1
for i in I),
name="assign"
)
一行代码表达一整族约束,不需要复制粘贴很多次 addConstr()。
进一步查看模型信息
model.getVars()查看所有变量及其属性。model.getConstrs()查看所有线性约束。model.write("model.lp")把模型导出成易读的 LP 文件,特别适合查错。model.NumVars模型变量总数。model.NumConstrs模型线性约束总数。model.Runtime最近一次 optimize() 的求解时间。课堂练习 2 · Speed Dating
回想 HW1 中的 Speed Dating 分组问题。现在不仅要决定谁和谁在同一组,还要决定组内每一轮的见面顺序。共有 4 位男生、4 位女生,分为 2 组;每组 2 位男生、2 位女生,并进行 2 轮 Speed Dating。
决策变量
xig:男生 i 是否进入组 g
yjg:女生 j 是否进入组 g
wijg:男生 i 与女生 j 是否同时位于组 g
zijgr:男生 i 与女生 j 是否在组 g 的第 r 轮见面
课堂数据:匹配得分 pij
| F1 | F2 | F3 | F4 | |
|---|---|---|---|---|
| M1 | 9 | 2 | 7 | 4 |
| M2 | 6 | 8 | 3 | 5 |
| M3 | 4 | 7 | 9 | 2 |
| M4 | 5 | 3 | 6 | 8 |
完整数学模型
给出的代码:分组部分已经写好
下面的代码只实现变量、分组约束和同组 linking。由你补全“见面顺序”约束和目标函数。
import gurobipy as gp
from gurobipy import GRB
M = range(4)
F = range(4)
G = range(2)
R = range(2)
p = [
[9, 2, 7, 4],
[6, 8, 3, 5],
[4, 7, 9, 2],
[5, 3, 6, 8]
]
# 1. Create a model ( gp.Model )
# 2. Add variables to the model ( m.addVars(...) )
# 3. Add objective to the model ( m.setObjective() )
# 4. Add constraints to the model ( m.addConstrs() )
# 5. Solve the model (m.optimize())
# 6. Get the solution and objective values (输出 ObjVal, Runtime, NumVars, NumConstrs)
提交一个最优的分组与轮次安排
每轮每组有两场同时进行的见面。组号、轮次和桌位存在对称性,只要满足约束且达到最优目标值即可。
Gurobi 干货 · 拓展知识
最优解并不是 solver 能告诉你的全部信息。这里集中看两类非常实用的内容:LP 的 dual value,以及 LP/MIP 的 Solver Log。
Dual Value · 查询影子价格
对于 LP,如果把某条线性约束保存为对象,例如 c1、c2,求解后可以分别查询:
c1.Pi
c2.Pi
c1.Pi 表示约束 c1 的 dual value / shadow price;c2.Pi 同理,表示约束
c2 的 shadow price。
直观上,在当前最优 basis 有效的局部范围内,shadow price 描述对应约束 RHS 放松 1 单位时最优目标值的边际变化。
c1、c2。求解后用
c1.Pi 和 c2.Pi 查询两个资源约束的 shadow price。
Solver Log:从日志看求解器在做什么
不需要看懂每一行。先认识模型大小、LP relaxation 和 MIP 搜索中的几个关键信号。
模型大小:Rows / Columns / Nonzeros
Optimize a model with 706 rows, 700 columns and 2400 nonzeros (Min)
Variable types: 0 continuous, 700 integer (100 binary)
LP 一般用什么方法?
Iteration、Objective、Primal Inf. 和 Dual Inf.。
Rows / Columns
删掉可消除的变量/约束
求连续最优解
满足可行性与最优性容差
Iteration Objective Primal Inf. Dual Inf. Time
0 1.200000e+03 8.000000e+01 0.000000e+00 0s
17 2.164000e+03 0.000000e+00 0.000000e+00 0s
Optimal objective 2.164000000e+03
IP / MIP:Branch-and-Bound 是基本框架
整数规划不能只求一次 LP。基本思想是 Branch-and-Bound;现代 MIP solver 还会加入 cutting planes、heuristics 和 presolve,因此常被称为 branch-and-cut。
包含整数变量
先去掉整数性求 LP relaxation
分枝、界、cuts、heuristics
Incumbent 与 BestBd 接近
Root relaxation: objective 102.4
Nodes | Incumbent BestBd Gap | Time
0 | 140.0 102.4 26.9%| 0s
500 | 110.0 106.5 3.2%| 2s
1240 | 110.0 110.0 0.0%| 4s
Optimal solution found
学习资源 · 如何借助 AI 快速学习 Gurobi
参数、属性、Python API、日志与算法的完整官方文档 Gurobi Help Center
常见安装、license、infeasibility、performance 问题 Gurobi Modeling Examples
Notebook 形式的真实建模案例
课堂进度
练习 1 与练习 2 决定后续内容是否解锁;Dual Challenge 作为拓展练习计入完成进度。
0 / 3 completed