首页>软件资讯>常见问题

常见问题

Gurobi新版非线性约束API建模技巧

发布时间:2026-04-22 11:11:17人气:2

本文所述非线性约束指的是一些比较特殊的复杂的非线性,例如指数函数,对数函数,三角函数等这些非线性约束,一般的二次约束较为简单不在本文讨论范畴之内。 在Gurobi之前的版本中一般采用 addGenConstrExp, addGenConstrLog, addGenConstrSin 这类API进行建模。


而在最新的Gurobi13中,Gurobi官方文档已经明确把这些老版本中的非线性约束API函数标记为 deprecated,并建议搭建在python中改用 nonlinear function + gurobipy.nlfunc 来进行建模。需要强调的是 这一次并不是简单的API函数的改名,而是从建模表达方式到求解器内部处理逻辑都发生了变化的全面升级。


1 Gurobi 13 中新的复杂非线性约束建模API怎么样?

Gurobi 官方文档中,gurobipy.nlfunc 被定义为一组 Nonlinear Expression Helper Functions。这些函数接收建模对象或数值,返回 NLExpr 或 MNLExpr,然后你可以把这些表达式直接放进 addConstr 里,构造非线性约束。官方示例就是:


model.addConstr(z == nlfunc.sin(x + y))

nlfunc 的核心思想不是“先创建一个函数约束对象”,而是“先构造一个非线性表达式,再把表达式放入约束”。目前文档列出的 helper 包括 sqrt、sin、cos、tan、exp、log、log2、log10、logistic、tanh、signpow、square 等,详细用法见 gurobi说明文档:https://docs.gurobi.com/projects/optimizer/en/current/reference/python/nlfunc.html


一个最简单的指数函数例子可以这样写:


import gurobipy as gp

from gurobipy import GRB, nlfunc


m = gp.Model()


x = m.addVar(lb=-2, ub=2, name="x")

y = m.addVar(lb=0, name="y")


m.addConstr(y == nlfunc.exp(x), name="exp_constr")

m.setObjective(y, GRB.MINIMIZE)


m.optimize()

如果要写更复杂一点的复合函数,nlfunc 的优势就更明显了。例如:


import gurobipy as gp

from gurobipy import GRB, nlfunc


m = gp.Model()


x1 = m.addVar(lb=-2, ub=2, name="x1")

x2 = m.addVar(lb=-2, ub=2, name="x2")

x3 = m.addVar(lb=-1, ub=1, name="x3")

z  = m.addVar(lb=-GRB.INFINITY, name="z")


m.addConstr(

    z == nlfunc.exp(2 * x1 - 3 * x2) + nlfunc.sin(x3 + x1),

    name="compound_nl"

)


m.setObjective(z, GRB.MINIMIZE)

m.optimize()

这种写法的优点是:数学表达式长什么样,代码里就几乎长什么样。在 Python 接口中,官方也明确说 nonlinear constraints 可以“自然地”通过算术运算符和 nonlinear helper functions 来表达。


2 新的非线性约束建模和老的约束建模有什么区别?

老 API 的基本语义:y = f(x), 旧的这批接口,例如:


addGenConstrExp(x, y)

addGenConstrLog(x, y)

addGenConstrLogistic(x, y)

addGenConstrPow(x, y, a)

addGenConstrSin(x, y)

它们的共同特点是:每个函数约束本质上都是一个标量输入、一个标量输出的关系,也就是形如 y = f(x)。例如 addGenConstrExp(x, y) 表示 y = exp(x),addGenConstrLogistic(x, y) 表示 y = 1 / (1 + exp(-x))。


这意味着:如果你的模型里只有一个很简单的单变量函数关系,那么旧 API 其实并不难用;但只要表达式开始“复合化”,事情就会迅速变繁琐。


新 API 的基本语义:y = f(x_1, x_2, ..., x_n), 新的 nonlinear constraints 不再局限于“单变量函数链接”,而是可以直接表达 复合、多变量、嵌套 的非线性关系。也就是说,它可以更自然地描述:


仿射表达式作为函数输入;

多个非线性函数之间的复合;

多个变量共同参与的复杂函数结构。

下面给出一个直观对比例子就可以看出新老API之间的差异,假设我们要建下面这个约束:


用新的 nlfunc,可以直接写成:


import gurobipy as gp

from gurobipy import nlfunc


m = gp.Model()

x1 = m.addVar(lb=-2, ub=2, name="x1")

x2 = m.addVar(lb=-2, ub=2, name="x2")

x3 = m.addVar(lb=-2, ub=2, name="x3")

z  = m.addVar(name="z")


m.addConstr(

    z == nlfunc.exp(2 * x1 - 3 * x2) + nlfunc.sin(x1 + x3),

    name="new_style"

)

如果用旧的 function constraints,通常就得拆成好几步:


import gurobipy as gp

m = gp.Model()


x1 = m.addVar(lb=-2, ub=2, name="x1")

x2 = m.addVar(lb=-2, ub=2, name="x2")

x3 = m.addVar(lb=-2, ub=2, name="x3")

z  = m.addVar(name="z")


t_exp = m.addVar(name="t_exp")

v_exp = m.addVar(lb=0, name="v_exp")

t_sin = m.addVar(name="t_sin")

v_sin = m.addVar(name="v_sin")



m.addConstr(t_exp == 2 * x1 - 3 * x2, name="link_exp_arg")

m.addGenConstrExp(t_exp, v_exp, name="exp_part")

m.addConstr(t_sin == x1 + x3, name="link_sin_arg")

m.addGenConstrSin(t_sin, v_sin, name="sin_part")

m.addConstr(z == v_exp + v_sin, name="sum_part")

你会发现,新写法里数学结构一目了然;旧写法里为了把复合函数拆成若干个 y = f(x) 小块,不得不人为引入中间变量 t_exp、v_exp、t_sin、v_sin。这类“人为拆解”在复杂模型里会迅速膨胀:变量更多,约束更多,代码更难读,调试也更痛苦。


这里需要强调一点:新 API 可以减少很多中间变量,但并不是说所有辅助变量都会消失。 更准确地说,是大量仅仅为了衔接单变量函数约束而存在的建模层中间变量,往往可以不再显式写出来。


如果你的老模型本来就是非常简单的单函数关系,比如只写 y = exp(x),那么新旧代码的差距并不会特别夸张。但当表达式里出现“仿射变换 + 非线性 + 再叠加别的非线性”这类结构时,nlfunc 的优势就会非常明显。


3 新版非线性API建模并不是简单的语法糖

很多人第一次看到 nlfunc,会觉得它只是把原来那种:


t = addGenConstrExp(t, y)

改成了:


addConstr(y == nlfunc.exp(...))

如上一节所述尤其是在比较复杂的非线性约束多次复活的情况下可以简化代码的同时少一些中间辅助变量。表面看这确实是像“语法糖”,但从求解器内部逻辑看,事情没有这么简单。接下来让我们分析旧版和新版API在求解器内部算法的本质区别。


3.1 旧 function constraints 的两条求解路线

旧的 function constraints 主要有两种处理路线:


直接走 spatial branch-and-bound;

或者先做 静态分段线性近似(static piecewise-linear approximation),再转成 MILP 来求。

旧 function constraints 下求解器可以走动态外逼近的路线;也可以走静态分段线性化转化MILP求解。


3.2 新 nonlinear constraints 的关键变化

新的 nonlinear constraints 支持 dynamic outer approximation,并且支持 multivariate compound nonlinear constraints。这意味着新 API 不只是“代码更短”,而是把求解器看到的问题,从“若干个人工拆开的单函数链接”升级成了“一个完整的复合非线性表达式树”。


nonlinear constraints 在内部会表示成 expression tree。树的叶子是变量或常数,内部节点是加、减、乘、除、指数、对数、三角函数等操作。


这一点背后的工程意义很大:


求解器看到的是整棵复合表达式,而不是你手工拆出来的一串“中间变量 + 小约束”;

这样能更直接保留原始数学结构;

对复杂非线性来说,这会改变求解器内部能利用的信息形态。

从工程上看,当模型是复合、多变量、嵌套的非线性结构时,直接把表达式树交给求解器,往往不只是代码更优雅,也可能改变外逼近、分支、界收紧以及整体搜索的效果。


3.3 个人使用感受

对新的非线性建模API函数使用下来整体感受一句话概括:新的非线性约束建模对于难解的问题加速会比较明显,对简单问题未必有性能提升。


当然这也是一个正常现象,我们尝试简单从算法原理上来进行一点粗浅的理解。


如果你的老模型以前主要依赖静态 PWL,那么切换到 nonlinear constraints + dynamic OA,可能会显著改善精度和搜索效率;

如果你的老模型本来就已经在用 function constraints 的 nonlinear 路线,那算法层面的差距就会缩小,此时新 API 的收益更多来自:更自然的建模方式、更少的手工拆解、更完整的表达式结构保留;

对一些非常简单的小模型,新的 nonlinear constraints 也可能带来额外开销,因此并不保证所有实例都更快。

我个人觉得新版的 nonlinear constraints 从各个方面来看都是一个不错的改进,如果你还在使用老版的那些非线性约束API建议可以考虑切换到新版。



上一条:代码编程建议你用Cursor

下一条:没有了!