导读:本期聚焦于风铃创作的《如何在Pyomo中线性化二元变量配对产生的非线性乘积约束?》,敬请观看详情。在Pyomo中构建混合整数规划模型时,如果约束里出现两个二元决策变量相乘,例如 x[i,j] 乘以 y[j,k],模型就会从线性问题变成非线性问题,而实际业务场景往往只需要使用 CBC、GLPK、HiGHS 这类线性求解器。所谓线性化,不是对表达式做近似,而是利用二元变量的取值特性,把乘积替换为一个新的辅助变量,并附加若干线性不等式来精确刻画乘积关系。本文围绕二元变量配对这类高频建模需求,给出从数学原理到 Pyomo 代码落地的完整路径。先解释为什么 z 等于 x 乘 y 在 x y 均为 0 或 1 时可以用 z 小于等于 x、z 小于等于 y、z 大于等于 x 加 y 减 1 这三条约束等价表达,再展示如何在 ConcreteModel 中声明 Var 和 Constraint,并用 CBC 或 GLPK 直接求解,最后讨论多变量乘积、二元与连续变量相乘以及数值容差方面的注意事项。

在混合整数规划中,如果两个二元决策变量相乘,例如 x[i,j] 和 y[j,k],模型表达式就变成了非线性函数。很多实际业务场景只需要使用 CBC、GLPK、HiGHS 这类线性求解器,此时不处理乘积项就无法继续。其实二元变量之间的乘积完全可以精确转化成一组线性约束,关键是把乘积定义为一个新的辅助变量。

如何在Pyomo中线性化二元变量配对产生的非线性乘积约束?

下面从原理推导到 Pyomo 代码实现,再讨论连续变量扩展和常见错误。

一、乘积项为什么会让线性求解器失效

线性规划以及混合整数线性规划要求目标函数和所有约束都是决策变量的线性函数。简单理解,每一项只能是常数乘以单个变量再相加,不允许两个变量相乘、相除或使用指数、对数等非线性函数。二元变量 x 和 y 取值虽然是零或一,但 x * y 在表达式中仍然是二次项。Pyomo 在构造表达式时会保留乘法结构,模型因此被标记为非线性。

一旦模型包含非线性项,CBC、GLPK 等 MILP 求解器无法读取。即便安装了 Ipopt、Bonmin 等非线性求解器,二元变量乘积也会逼着求解器走 MINLP 路线,求解速度通常远慢于等价线性化之后的 MILP。此外,MINLP 求解器对规模更敏感,可能难以得到全局最优解。正确做法是引入辅助变量 z 表示乘积,再添加线性不等式,使 z 的可行域恰好在乘积值为 1 时取 1,其余情况取 0。

线性化之后模型仍然保持 MILP 结构,求解器可以继续使用分支定界和割平面,性能更稳定。尤其当模型中存在大量配对变量时,例如 i、j、k 三个维度的分配关系,预先做好线性化可以显著减少调试时间。

二、二元变量乘积的等价线性化推导

设 x 和 y 都是二元变量,定义辅助变量 z = x * y。由于 x、y 只能取 0 或 1,乘积也只有 0 和 1 两种结果。我们可以用以下三个约束来刻画这个关系:

  • z 小于等于 x
  • z 小于等于 y
  • z 大于等于 x + y - 1

逐一验证四种组合:当 x=0、y=0 时,前两条约束使 z 小于等于 0,第三条约束使 z 大于等于 -1,因此 z=0。当 x=0、y=1 时,第一条约束使 z 小于等于 0,第三条约束使 z 大于等于 0,同样得到 0。当 x=1、y=0 时也类似。只有在 x=1、y=1 时,第三条约束变成 z 大于等于 1,而前两条只限制 z 小于等于 1,于是 z=1。因此三组不等式与乘积定义完全等价。

从模型结构看,辅助变量 z 可以被声明为连续变量并设置上下界为 0 到 1。因为约束会强制 z 取到 0 或 1,没有必要再增加一个二元变量。把 z 保持为连续变量可以减少整数变量的数量,对分支定界更友好。如果后续约束要求 z 必须为二元,也可以将 z 声明为 Binary,两种情况在最优解上是等价的。

当需要表示多个二元变量同时为 1 的乘积时,例如 z = x1 * x2 * ... * xn,可以引入边界约束 z 小于等于每个 xi,再加上 z 大于等于所有 xi 之和减去 n-1。这样 z 只有在全部 xi 为 1 时才等于 1,其他情况都为 0。不过变量数量较多时,逐对线性化再组合通常更容易维护。

三、在 Pyomo 中落地:完整模型与代码

下面用一个三层分配示例来演示。假设有若干任务 i、中间节点 j 和资源 k。二元变量 x[i,j] 表示任务 i 是否分配给节点 j,y[j,k] 表示节点 j 是否使用资源 k。当且仅当两个变量同时为 1 时,任务 i 和资源 k 之间才产生配对。为了把配对成本放入目标函数,可以定义 z[i,j,k] 表示 x[i,j] 与 y[j,k] 的乘积。

from pyomo.environ import (
    ConcreteModel, Var, Constraint, Objective, Binary, NonNegativeReals,
    minimize, SolverFactory, Set
)

model = ConcreteModel(name="bilinear_product_linearization")

# 三个简单集合
model.I = Set(initialize=[1, 2, 3])
model.J = Set(initialize=[1, 2, 3])
model.K = Set(initialize=[1, 2, 3])

# 原始二元变量
model.x = Var(model.I, model.J, within=Binary)
model.y = Var(model.J, model.K, within=Binary)

# 辅助变量 z[i,j,k] 表示 x[i,j] * y[j,k]
model.z = Var(model.I, model.J, model.K, within=NonNegativeReals, bounds=(0, 1))

# 每个任务只选择一个中间节点
def row_rule(m, i):
    return sum(m.x[i, j] for j in m.J) == 1
model.row_cons = Constraint(model.I, rule=row_rule)

# 每个中间节点最多使用一个资源
def col_rule(m, j):
    return sum(m.y[j, k] for k in m.K) <= 1
model.col_cons = Constraint(model.J, rule=col_rule)

# 线性化约束一:z 小于等于 x
def lin1(m, i, j, k):
    return m.z[i, j, k] <= m.x[i, j]
model.lin1_cons = Constraint(model.I, model.J, model.K, rule=lin1)

# 线性化约束二:z 小于等于 y
def lin2(m, i, j, k):
    return m.z[i, j, k] <= m.y[j, k]
model.lin2_cons = Constraint(model.I, model.J, model.K, rule=lin2)

# 线性化约束三:z 大于等于 x + y - 1
def lin3(m, i, j, k):
    return m.z[i, j, k] >= m.x[i, j] + m.y[j, k] - 1
model.lin3_cons = Constraint(model.I, model.J, model.K, rule=lin3)

# 目标函数:最小化任务与资源的配对成本
cost = {
    (1, 1): 2, (1, 2): 4, (1, 3): 5,
    (2, 1): 3, (2, 2): 2, (2, 3): 6,
    (3, 1): 4, (3, 2): 1, (3, 3): 3,
}
def obj_rule(m):
    return sum(cost[i, k] * m.z[i, j, k]
               for i in m.I for j in m.J for k in m.K)
model.obj = Objective(rule=obj_rule, sense=minimize)

这段代码中,z 被声明为连续变量并限制在 0 到 1 之间,这是最常用的做法。随后三条线性化约束分别对每个 i、j、k 索引建立,数量等于三层索引组合数。虽然约束数量增加,但模型仍然是 MILP,结构非常清晰。

求解时只需要将模型交给线性求解器即可。例如可以使用 CBC:

solver = SolverFactory("cbc")
solution = solver.solve(model)

for i in model.I:
    for j in model.J:
        for k in model.K:
            if model.z[i, j, k].value > 0.5:
                print(f"i={i}, j={j}, k={k}, z={model.z[i,j,k].value:.2f}")

如果本机没有安装 CBC,也可以把 "cbc" 替换为 "glpk" 或 "appsi_highs"。判断辅助变量取值时使用 0.5 作为阈值,而不是严格等于 1,能够避免求解器数值容差带来的误判。求解完成后,z 的值就对应原始乘积项,目标函数和约束都无需再出现 x[i,j] * y[j,k]。

四、扩展到二元变量与连续变量的乘积

另一种常见场景是二元变量 x 与连续变量 y 相乘,比如 x 表示某条产线是否启用,y 表示产线产量,z = x * y 表示实际产量。这种形式只需要一个 Big-M 约束组,其中 U 是 y 的上界,L 是下界。

当 x=0 时,约束 z 大于等于 L*x 且小于等于 U*x 会强制 z=0。当 x=1 时,另外两条约束 z 小于等于 y - L*(1-x) 和 z 大于等于 y - U*(1-x) 则强制 z=y。整体写出来如下:

from pyomo.environ import Var, Binary, NonNegativeReals

# y 在 0 到 100 之间连续变化
model.x = Var(within=Binary)
model.y = Var(bounds=(0, 100))
model.z = Var(within=NonNegativeReals)

U = 100
L = 0

def c1(m):
    return m.z <= U * m.x
model.c1 = Constraint(rule=c1)

def c2(m):
    return m.z >= L * m.x
model.c2 = Constraint(rule=c2)

def c3(m):
    return m.z <= m.y - L * (1 - m.x)
model.c3 = Constraint(rule=c3)

def c4(m):
    return m.z >= m.y - U * (1 - m.x)
model.c4 = Constraint(rule=c4)

这里的 U 和 L 必须紧贴 y 的真实取值范围。上界取得过大会造成 Big-M 约束松弛,分支定界时需要更多节点才能收敛;上界取得过小则可能切掉最优解。工程上应优先根据业务含义给出尽量紧的上界,而不是随意填一个很大的数。

与二元乘积不同,二元与连续变量的乘积线性化中,当 x 是二进制时这些约束是精确的,并非近似。它们经常用于固定成本、启停决策、批量生产等模型。如果 y 也有整数限制,只需把 y 声明为 Integer 或相关类型,线性化结构完全相同。

五、调试方法与常见误区

在 Pyomo 中处理乘积约束时,最常见的错误是只在数学层面知道线性化,却没有把原始乘积从模型中移除。比如目标函数里仍然写着 cost * model.x[i,j] * model.y[j,k],同时又加上了辅助变量约束。这样模型依然是非线性,求解器一样报错。正确做法是所有需要乘积的地方都使用辅助变量 z。

如果求解时 Pyomo 提示无法将模型写入 LP 文件,或者求解器返回失败状态,可以先用 model.pprint() 检查约束。重点查看目标函数和约束中是否还残留两个变量相乘的项。另一个常见问题是辅助变量没有设置上下界,导致某些约束没有把 z 限制住,模型出现无界或不可行。对于二元乘积,设置 bounds=(0, 1) 通常会立刻解决。

数值容差也需要留意。当求解器返回 z=0.999999 或 1.000001 时,业务上应该按 0.5 的阈值判断,而不是严格比较是否等于 1。如果模型规模很大,可以考虑把辅助变量保持为连续类型,并通过减少 Big-M 值或引入 SOS1 约束来改善 LP 松弛。总的来说,二元变量乘积的线性化是一项基础但非常实用的建模技术,掌握之后可以放心地在 Pyomo 中使用线性求解器处理大量配对关系。

Pyomo线性化二元变量非线性乘积约束修改时间:2026-09-22 18:18:04

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0922/60578.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。