建模与优化 · Computer Lab

Getting Started with Gurobi

1

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() 等函数建模。

Model 是什么?
m = gp.Model("mip1")

gp.Model() 会创建一个 Gurobi model object。这里模型名称是 "mip1",后续通过变量 m 来操作这个模型。

Model 的两类常用成员

Methods / Functions:执行操作,例如 m.addVar()、m.addConstr()、m.optimize()。

Attributes:读取模型信息或结果,例如 m.Status、m.ObjVal、m.Runtime。

Part B · 一个 Gurobi 模型的 6 个核心组成

Model创建一个优化模型对象。
gp.Model()
Variables我们可以控制的决策。
addVar / addVars
Objective希望最大化或最小化什么。
setObjective
Constraints决策必须遵守的规则。
addConstr / addConstrs
Optimize真正调用求解器。
model.optimize()
Solution读取状态、目标值和变量值。
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.CONTINUOUS
GRB.BINARY
GRB.INTEGER
目标方向 GRB.MAXIMIZE
GRB.MINIMIZE
常见状态 GRB.OPTIMAL
GRB.INFEASIBLE
GRB.UNBOUNDED
GRB.TIME_LIMIT

Part D · Expression:目标函数和约束的“中间语言”

Gurobi 不是只接受单个变量。像 2*x + 3*y、x + y + z 这样的线性组合会形成 expression,然后再被放进目标函数或约束中。

先构造 expression,再用于目标
expr = 40*x1 + 30*x2
m.setObjective(expr, GRB.MAXIMIZE)
expression 也直接出现在约束中
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)
2

如何读取基本求解结果

model.Status求解状态:最优?不可行?无界?
model.ObjVal当前已求得解的目标函数值。
x.X变量 x 在当前解中的取值。

常见状态

Status 含义 下一步?
GRB.OPTIMAL 已按容差证明当前模型的最优解 检查模型含义并解释决策
GRB.INFEASIBLE 不存在同时满足全部约束的解 检查约束冲突、数据和单位
GRB.UNBOUNDED 目标可以无限改善 检查是否漏了容量/上界等约束
GRB.TIME_LIMIT 达到时间上限后停止 检查问题是否太难,查看可行解、bound 和 gap
3

练习 1 · A Simple Example

课堂任务1
max   x + y + 2z
s.t.   x + 2y + 3z ≤ 4
x + y ≥ 1
x,y,z ∈ {0,1}
提示 · 按照 Gurobi 建模的 6 个步骤完成程序
  1. Create a model — 创建模型
  2. Add variables to the model — 添加决策变量
  3. Add objective to the model — 添加目标函数
  4. Add constraints to the model — 添加约束
  5. Let the model solve itself — 调用求解器
  6. 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)


        
🔒 完成课堂练习 1 后解锁:“规模化建模”与课堂练习 2 · Speed Dating。
4

Gurobi 干货 · 规模化建模

当模型变大以后,如何批量创建变量、添加约束、写表达式?

addVars():批量创建变量

Mathematical model
xig ∈ {0,1},   i∈I, g∈G
I = range(4)
G = range(2)

x = m.addVars(I, G,
              vtype=GRB.BINARY,
              name="x")

x[i,g] 对应数学模型中的 xig。Gurobi 返回的是可按索引访问的 tupledict。

addConstrs() + quicksum()

Mathematical constraint
Σg∈G xig = 1,   ∀i∈I
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() 的求解时间。
5

课堂练习 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

完整数学模型

max   Σg,i,j pij wijg
Σg xig = 1   ∀i
Σg yjg = 1   ∀j
Σi xig = 2,   Σj yjg = 2   ∀g
wijg ≤ xig,   wijg ≤ yjg
Σr zijgr = wijg   ∀i,j,g
Σj zijgr = xig   ∀i,g,r
Σi zijgr = yjg   ∀j,g,r
x,y,w,z ∈ {0,1}

给出的代码:分组部分已经写好

下面的代码只实现变量、分组约束和同组 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) 

提交一个最优的分组与轮次安排

每轮每组有两场同时进行的见面。组号、轮次和桌位存在对称性,只要满足约束且达到最优目标值即可。

🔒 完成课堂练习 2 后解锁:拓展知识(Dual Value 与 Solver Log)。
6

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 单位时最优目标值的边际变化。

max   40x₁ + 30x₂
2x₁ + x₂ ≤ 100   (Resource 1)
x₁ + 2x₂ ≤ 80   (Resource 2)
x₁,x₂ ≥ 0
你的任务:自己把两条资源约束保存成对象,例如 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)
Rows线性约束数量。
Columns决策变量数量。
Nonzeros约束矩阵中非零系数数量。
Variable types连续、整数、二元变量的组成。

LP 一般用什么方法?

Simplex课堂学习的 primal simplex / dual simplex 是 LP 的经典核心算法。Simplex 日志中常见 Iteration、Objective、Primal Inf. 和 Dual Inf.。
Barrier / Concurrent实际工业求解器并不保证每个 LP 都只使用 simplex。Gurobi 可以根据问题和参数选择 simplex、barrier,或采用 concurrent 策略让多个方法竞争。
LP Model
Rows / Columns
→
Presolve
删掉可消除的变量/约束
→
Simplex / Barrier
求连续最优解
→
Optimal
满足可行性与最优性容差
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。

MIP Model
包含整数变量
→
Root relaxation
先去掉整数性求 LP relaxation
→
Branch-and-Cut
分枝、界、cuts、heuristics
→
Gap closes
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
Root relaxation在根节点把整数变量放松为连续变量得到的 LP;它给出重要的 bound。
NodesBranch-and-Bound / Branch-and-Cut 树中已经探索的节点数。
Incumbent当前找到的最好整数可行解。
BestBd / Gap当前最优界,以及 incumbent 与 bound 之间还差多远。
9

学习资源 · 如何借助 AI 快速学习 Gurobi

Gurobi Documentation
参数、属性、Python API、日志与算法的完整官方文档
Gurobi Help Center
常见安装、license、infeasibility、performance 问题
Gurobi Modeling Examples
Notebook 形式的真实建模案例

课堂进度

练习 1 与练习 2 决定后续内容是否解锁;Dual Challenge 作为拓展练习计入完成进度。

0 / 3 completed