MPSolver<SCIP>回调变量值混乱越界问题技术求助
MPSolver(SCIP)回调中变量值混乱但最终结果正确的问题
我给MPSolver
输出示例
回调输出:
IsSolution:1, X1=0, X2=0, X3=1000
最终结果:Result:0, X1=1000, X2=0, X3=0
测试代码
#include "ortools/linear_solver/linear_solver.h" #include <memory> #include <iostream> #include <stdio.h> using namespace operations_research; void RangeHelper(MPConstraint* c) {} template<class ...T> void RangeHelper(MPConstraint* c, MPVariable* x0, double c0, T... TT) { c->SetCoefficient(x0, c0); RangeHelper(c, TT...); } template<class ...T> static MPConstraint* Ranger(MPSolver& solver, MPVariable* condition, double RangeL, T... TT) { MPConstraint* c = solver.MakeRowConstraint(RangeL, solver.infinity()); c->SetCoefficient(condition, 1e20); RangeHelper(c, TT...); return c; } struct CMPCBK : MPCallback { const MPVariable* X1, * X2, * X3; CMPCBK(const MPVariable* X1, const MPVariable* X2, const MPVariable* X3) : X1(X1), X2(X2), X3(X3), MPCallback(0, 0) {} void RunCallback(MPCallbackContext* r) { if (r->Event() == MPCallbackEvent::kMipSolution) { printf("IsSolution:%d, X1 = %f, X2 = %f, X3 = %f\n", r->CanQueryVariableValues(), r->VariableValue(X1), r->VariableValue(X2), r->VariableValue(X3)); } } }; int main() { std::unique_ptr<MPSolver> solver(MPSolver::CreateSolver("SCIP")); const double INF = solver->infinity(); MPVariable* X1 = solver->MakeNumVar(-INF, INF, "a"); MPVariable* X2 = solver->MakeNumVar(0, INF, "b"); MPConstraint* C1 = solver->MakeRowConstraint(0, 0); MPVariable* X3 = solver->MakeIntVar(0, 1, "c"); Ranger(*solver, X3, 1000, X2, -1, X1, 1); C1->SetCoefficient(X3, 1); MPObjective* obj = solver->MutableObjective(); obj->SetCoefficient(X1, 1); obj->SetMinimization(); CMPCBK mpcbk(X1, X2, X3); solver->SetCallback(&mpcbk); printf("Result:%d, ", solver->Solve()); printf("X1 = %f, X2 = %f, X3 = %f\n", X1->solution_value(), X2->solution_value(), X3->solution_value()); }
问题原因分析
- SCIP中间解的特性:SCIP在MIP搜索过程中会生成大量临时试探解,这些解是算法剪枝、分支过程中的中间产物,可能不满足所有约束(比如你用1e20大系数实现的逻辑约束),甚至违反变量的定义范围,因此会出现
X3=1000这种明显异常的值。 CanQueryVariableValues()的含义:该返回值仅表示当前上下文允许查询变量值,不保证解的可行性,不能作为判断解是否有效的依据。
解决方案
1. 在回调中主动验证解的有效性
在使用回调获取的变量值前,先验证变量是否符合定义范围,以及是否满足核心约束:
void RunCallback(MPCallbackContext* r) { if (r->Event() == MPCallbackEvent::kMipSolution) { double x1 = r->VariableValue(X1); double x2 = r->VariableValue(X2); double x3 = r->VariableValue(X3); // 验证整数变量X3的范围 if (x3 < -1e-6 || x3 > 1.0 + 1e-6) return; // 验证逻辑约束是否满足 if (1e20 * x3 + x1 - x2 < 1000 - 1e-6) return; // 仅输出有效中间解 printf("有效中间解: X1 = %.2f, X2 = %.2f, X3 = %.0f\n", x1, x2, x3); } }
2. 替换大系数约束的实现方式
用1e20这种超大系数实现逻辑约束容易引发数值不稳定,建议改用OR-Tools原生的逻辑约束写法:
// 替代原Ranger函数的逻辑:当X3=1时,X1 - X2 >=1000 MPConstraint* logic_constraint = solver->MakeRowConstraint(-INF, INF); logic_constraint->SetCoefficient(X3, 1000); logic_constraint->SetCoefficient(X1, 1); logic_constraint->SetCoefficient(X2, -1); // 等价于:X1 - X2 >= 1000 * X3
这种写法更贴合MIP求解器的处理逻辑,减少数值异常的概率。
3. 配置SCIP仅返回可行中间解
通过设置SCIP的参数,限制回调只接收经过可行性校验的中间解:
// 在创建solver后添加参数配置 solver->SetSolverSpecificParameter("misc/allowunsolvedsols", "false"); // 可选:限制中间解的数量,避免输出过多 solver->SetSolverSpecificParameter("limits/solutions", "50");
注:不同版本SCIP的参数名称可能略有差异,可参考SCIP官方文档确认。
内容的提问来源于stack exchange,提问作者l4m2
相关产品推荐
相关产品推荐

