Gurobi addConstr约束内距离计算实现问题咨询
问题解答
核心结论
- 不能直接在
addConstr()内部调用sklearn的pairwise_distances函数。该函数仅接受固定数值的numpy数组作为输入,无法识别Gurobi的决策变量对象,也不会生成Gurobi可解析的优化表达式,强行调用要么触发类型错误,要么仅计算变量当前初始值的距离作为常数约束,完全达不到约束优化的效果。 - 你关于Gurobi不支持直接在约束中使用
sqrt类通用非线性函数的认知是正确的,但欧氏、曼哈顿距离约束完全不需要依赖sqrt或第三方计算函数,用Gurobi原生语法即可实现,不存在无法手动实现的问题。
正确实现方式
场景1:X、Y均为预先定义的固定numpy数组
这种场景下点对距离是固定常数,不需要把距离计算放到模型构建环节,提前做预处理计算即可:
from sklearn.metrics import pairwise_distances import gurobipy as gp import numpy as np # 提前计算所有固定点对的距离,这一步可以正常用sklearn euclidean_dist_matrix = pairwise_distances(X, Y, metric="euclidean") manhattan_dist_matrix = pairwise_distances(X, Y, metric="manhattan") m = gp.Model() N = X.shape[0] # 根据提前算好的距离值,按业务规则加约束即可 for i in range(N): for j in range(N): # 例:仅对距离<=50的点对加业务约束 if euclidean_dist_matrix[i, j] <= 50: # 此处写入实际需要的约束逻辑 pass
注意:如果X、Y都是固定数组,pairwise_distances的输出<=50是恒真或恒假的常数判断,不需要作为优化约束写入模型。
场景2:Y(或X、或两者同时)是Gurobi决策变量
这种场景下不能用sklearn预计算,需要直接用Gurobi的表达式语法写约束,分两种距离类型处理:
欧氏距离约束(两点距离≤50)
欧氏距离公式为 $\sqrt{\sum_{d=0}{D-1}(x_{i,d}-y_{j,d})2} \leq 50$,由于不等号两侧都是非负值,直接对两边平方去掉sqrt,等价于 $\sum_{d=0}{D-1}(x_{i,d}-y_{j,d})2 \leq 50^2$,这是Gurobi原生支持的二次约束,写法如下:
from gurobipy import GRB m = gp.Model() N, D = X.shape # 定义决策变量Y,若X也是决策变量用相同方式定义即可 Y = m.addMVar((N, D), lb=-GRB.INFINITY, name="Y") threshold = 50 # 逐对添加约束 for i in range(N): for j in range(N): m.addConstr( ((X[i, :] - Y[j, :]) ** 2).sum() <= threshold ** 2, name=f"euc_dist_constr_{i}_{j}" )
如果需要添加距离≥阈值的欧氏约束,平方后的约束属于非凸二次约束,需要在求解前设置m.Params.NonConvex = 2,Gurobi即可正常求解。
曼哈顿距离约束(两点距离≤50)
曼哈顿距离公式为 $\sum_{d=0}^{D-1}|x_{i,d}-y_{j,d}| \leq 50$,Gurobi原生提供绝对值表达式支持,直接调用gp.abs_()即可,框架会自动将其转换为线性约束求解,写法如下:
for i in range(N): for j in range(N): m.addConstr( gp.abs_(X[i, :] - Y[j, :]).sum() <= threshold, name=f"man_dist_constr_{i}_{j}" )
如果追求极致求解效率,也可以手动通过非负辅助变量线性化绝对值,逻辑和Gurobi内部自动处理的过程完全一致。
示例代码存在的问题
- 双层
for循环已经遍历了i、j索引,约束内部又写了for i in range(N)的生成器,存在语法冲突。 addConstr方法单次仅支持添加单个约束,批量添加约束需要使用addConstrs方法,或像示例中一样通过循环逐次调用addConstr。
内容的提问来源于stack exchange,提问作者HKAY
相关产品推荐
相关产品推荐

