
本文介绍在 gurobi 中不依赖 solution pool(poolmode)的情况下,通过添加自定义约束强制排除已存在的解(如历史 nba dfs 阵容),重点解决“动态禁止重复解”时因不可哈希对象导致的建模错误,并提供可直接运行的约束构造方法。
本文介绍在 gurobi 中不依赖 solution pool(poolmode)的情况下,通过添加自定义约束强制排除已存在的解(如历史 nba dfs 阵容),重点解决“动态禁止重复解”时因不可哈希对象导致的建模错误,并提供可直接运行的约束构造方法。
在 Gurobi 中,若希望多次求解 MIP 模型并确保每次得到互不相同的整数解(例如生成多个高价值但不重叠的 NBA DFS 阵容),而不启用 PoolMode 或 PoolSolutions,最常用的方法是:在每次获得新解后,向模型中添加一个“no-good constraint”,显式禁止该特定解再次出现。
你遇到的报错(TypeError: unhashable type: 'set')源于试图在 addConstrs() 中直接使用 Python 的 set() 对象参与 Gurobi 表达式构建——而 Gurobi 的约束构造函数要求所有操作符和表达式必须基于 Var、LinExpr 或常量,不能嵌入运行时 Python 集合或列表推导式(尤其是含 .X 属性的访问,它仅在优化完成后才有效,建模阶段根本不可用)。
✅ 正确做法:对每个已有阵容 i ∈ created_lineups,构造一个逻辑排斥约束——即:当前解 x 与 i 至少在一个变量取值上不同。假设 player_pos_map 是二进制决策变量字典(如 x['LeBron_James_SF']),且 i 是一个包含被选中变量名的元组或 frozenset(如 ('LeBron_James_SF', 'Steph_Curry_PG')),则可写为:
# 假设 created_lineups 是 list[tuple[str, ...]],每个 tuple 是已生成阵容的变量名元组
for idx, lineup in enumerate(created_lineups):
# 将 lineup 转为 frozenset 便于快速查找,但约束需用线性形式表达
selected_vars = set(lineup)
# 构造 no-good 约束:sum(x[v] for v in selected) + sum(1 - x[v] for v in all_vars \ selected) <p>⚠️ 注意事项:</p>
- 不要在建模阶段调用 .X:y[x].X == 1 是求解后的属性,建模时 y[x] 还只是 Var 对象,.X 未定义;
- created_lineups 必须是静态已知的解集合(如前几次 run 的结果),不能是实时生成的未定解;
- 若变量总数大,该约束会引入大量项;可考虑只对关键球员(如薪资占比 >15% 的 3–5 人)做轻量级差异化约束以提升性能;
- 更高效替代方案:使用 Model.cbLazy() 在回调中动态添加 cuts(适用于分支切割法),但需启用 LazyConstraints=1。
? 总结:禁止重复解的本质是“使当前解与任一历史解的汉明距离 ≥1”。Gurobi 中必须将其线性化为整数约束,而非依赖 Python 集合运算。正确构造的 no-good 约束既保证解的唯一性,又完全兼容 MIP 求解器的数学语义。










