You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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());
}

问题原因分析

  1. SCIP中间解的特性:SCIP在MIP搜索过程中会生成大量临时试探解,这些解是算法剪枝、分支过程中的中间产物,可能不满足所有约束(比如你用1e20大系数实现的逻辑约束),甚至违反变量的定义范围,因此会出现X3=1000这种明显异常的值。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:55:58