使用Gurobipy.abs_()遇TypeError:LinExpr与GenExprAbs无法执行+=运算
问题解决:Gurobipy中绝对值函数的线性化处理
错误原因
你遇到的TypeError: unsupported operand type(s) for +=: 'gurobipy.LinExpr' and 'GenExprAbs',本质是因为Gurobi不支持将gp.abs_()生成的非线性表达式(GenExprAbs类型)直接与线性表达式(LinExpr类型)相加。整数规划中所有非线性部分(绝对值、除法等)必须通过引入辅助变量+添加线性约束的方式转化为线性形式。
此外你的目标函数还存在两处逻辑/形式问题:
- 人口偏差项的逻辑错误:原代码是对每个选区下的单个precinct人口与目标值的差取绝对值求和,正确逻辑应为每个选区的总人口与目标值的差的绝对值之和。
- 比例偏差项包含除法:
l[i]x[i,j]/sum(x[i,j])是分式,属于非线性表达式,必须线性化处理。
分步解决方案
1. 修正基础变量定义
首先注意:precinct的人口pp应为已知常量,而非决策变量(重分区中人口是固定数据,不是需要优化的变量):
# 替换原pp的变量定义为输入常量 pp = {i: 1980 for i in set_I} # 示例:每个precinct人口1980,5个precinct对应总V=9900
2. 线性化人口偏差的绝对值
为每个选区j的总人口与目标值的偏差引入非负辅助变量,通过约束将绝对值转化为线性形式:
# 定义每个选区的总人口 pop_j = {(j): gp.quicksum(pp[i] * x[i,j] for i in set_I) for j in set_J} # 引入辅助变量存储人口偏差的绝对值 y_pop = {(j): opt_model.addVar(vtype=gp.GRB.CONTINUOUS, lb=0, name=f"y_pop_{j}") for j in set_J} # 添加线性约束替代绝对值 for j in set_J: opt_model.addConstr(pop_j[j] - targ <= y_pop[j], name=f"pop_pos_{j}") opt_model.addConstr(-(pop_j[j] - targ) <= y_pop[j], name=f"pop_neg_{j}")
3. 线性化比例偏差的绝对值
针对abs(选区lean比例 - 整体lean比例)的分式结构,先引入比例变量,再通过辅助变量线性化绝对值:
# 定义每个选区的lean数量、precinct总数 r_j = {(j): gp.quicksum(l[i] * x[i,j] for i in set_I) for j in set_J} s_j = {(j): gp.quicksum(x[i,j] for i in set_I) for j in set_J} # 整体lean比例(线性表达式,n为precinct总数常量) actual_proportion = gp.quicksum(l[i] for i in set_I) / n # 引入比例变量z_j表示选区j的lean比例 z_j = {(j): opt_model.addVar(vtype=gp.GRB.CONTINUOUS, lb=0, ub=1, name=f"z_lean_{j}") for j in set_J} # 引入辅助变量存储比例偏差的绝对值 y_lean = {(j): opt_model.addVar(vtype=gp.GRB.CONTINUOUS, lb=0, name=f"y_lean_{j}") for j in set_J} # 添加线性约束:lean数量 = 比例 * 选区precinct总数 for j in set_J: opt_model.addConstr(r_j[j] == z_j[j] * s_j[j], name=f"lean_ratio_{j}") # 线性化绝对值约束 opt_model.addConstr(z_j[j] - actual_proportion <= y_lean[j], name=f"lean_pos_{j}") opt_model.addConstr(-(z_j[j] - actual_proportion) <= y_lean[j], name=f"lean_neg_{j}")
4. 构建线性目标函数
用辅助变量的和替代原非线性目标:
obj = gp.quicksum(y_pop[j] for j in set_J) + gp.quicksum(y_lean[j] for j in set_J) opt_model.setObjective(obj, gp.GRB.MINIMIZE)
5. 补充必要约束
不要忘记添加precinct的分配约束(每个precinct必须分配到恰好一个选区):
for i in set_I: opt_model.addConstr(gp.quicksum(x[i,j] for j in set_J) == 1, name=f"assign_{i}")
完整可运行代码示例
import gurobipy as gp from gurobipy import GRB import math # 示例集合定义 set_I = {0,1,2,3,4} # 5个precinct set_J = {0,1} # 2个选区 n = len(set_I) m = len(set_J) V = 9900 targ = math.floor(V // m) # 固定precinct人口数据 pp = {i: 1980 for i in set_I} opt_model = gp.Model("Redistricting") # 决策变量定义 l = {(i): opt_model.addVar(vtype=GRB.BINARY, name=f"l_{i}") for i in set_I} x = {(i,j): opt_model.addVar(vtype=GRB.BINARY, name=f"x_{i}_{j}") for i in set_I for j in set_J} # 基础分配约束 for i in set_I: opt_model.addConstr(gp.quicksum(x[i,j] for j in set_J) == 1, name=f"assign_{i}") # 定义选区统计量 pop_j = {(j): gp.quicksum(pp[i] * x[i,j] for i in set_I) for j in set_J} r_j = {(j): gp.quicksum(l[i] * x[i,j] for i in set_I) for j in set_J} s_j = {(j): gp.quicksum(x[i,j] for i in set_I) for j in set_J} actual_proportion = gp.quicksum(l[i] for i in set_I) / n # 线性化人口偏差绝对值 y_pop = {(j): opt_model.addVar(vtype=GRB.CONTINUOUS, lb=0, name=f"y_pop_{j}") for j in set_J} for j in set_J: opt_model.addConstr(pop_j[j] - targ <= y_pop[j], name=f"pop_pos_{j}") opt_model.addConstr(-(pop_j[j] - targ) <= y_pop[j], name=f"pop_neg_{j}") # 线性化比例偏差绝对值 z_j = {(j): opt_model.addVar(vtype=GRB.CONTINUOUS, lb=0, ub=1, name=f"z_lean_{j}") for j in set_J} y_lean = {(j): opt_model.addVar(vtype=GRB.CONTINUOUS, lb=0, name=f"y_lean_{j}") for j in set_J} for j in set_J: opt_model.addConstr(r_j[j] == z_j[j] * s_j[j], name=f"lean_ratio_{j}") opt_model.addConstr(z_j[j] - actual_proportion <= y_lean[j], name=f"lean_pos_{j}") opt_model.addConstr(-(z_j[j] - actual_proportion) <= y_lean[j], name=f"lean_neg_{j}") # 目标函数 obj = gp.quicksum(y_pop[j] for j in set_J) + gp.quicksum(y_lean[j] for j in set_J) opt_model.setObjective(obj, GRB.MINIMIZE) # 求解 opt_model.optimize()
内容的提问来源于stack exchange,提问作者Lyxcoder
相关产品推荐
相关产品推荐

