C++集成CPLEX求解整数规划:从环境配置到工程实践
2026/7/18 5:46:26 网站建设 项目流程

1. 项目概述:当C++遇上Cplex,解锁工业级整数规划求解

如果你正在处理生产排程、物流路径优化、资源分配这类问题,并且数据规模不小,那你大概率绕不开“整数规划”这个坎。纯手工推导或者用一些轻量级脚本去试,在复杂约束和大量变量面前,基本是徒劳。这时候,一个强大的求解器就成了刚需。IBM ILOG CPLEX,在运筹优化圈里,基本就是“专业”和“高效”的代名词,尤其擅长处理混合整数规划问题。但很多朋友可能更习惯在C++的环境下构建整个项目的主干逻辑,比如你的仿真引擎、数据处理模块、UI界面都是用C++写的,你肯定不希望为了调用一个求解器,把数据倒腾到另一个平台或者语言里。所以,把C++和Cplex无缝集成起来,让C++程序直接“指挥”Cplex干活,就成了一个非常实际且强大的技术方案。

简单来说,这个项目要做的,就是打通C++应用程序与Cplex求解引擎之间的桥梁。你的C++代码负责定义问题:有哪些决策变量(比如某台机器是否开工、某条路径是否被选择),这些变量有什么限制(比如资源总量、时间窗口),以及要达到什么目标(比如成本最低、利润最大)。然后,通过Cplex提供的C++ API,将这些模型信息“喂”给Cplex求解器。Cplex会在后台施展它的各种高级算法(如分支定界、割平面法),最终把最优解或者可行解返回给你的C++程序。这样一来,你就能在一个统一的C++工程里,完成从数据预处理、建模、求解到结果后处理的全流程,极大地提升了开发效率和系统集成度。

2. 环境搭建与核心库配置详解

集成开发的第一步,也是最容易踩坑的一步,就是环境的正确配置。这里面的门道不少,一步错可能导致后续编译链接各种报错。

2.1 Cplex的获取与安装要点

首先,你需要合法获取IBM ILOG CPLEX。对于学术用户,通常可以通过IBM的学术计划申请免费授权。对于商业用途,则需要购买相应的许可证。下载安装包时,务必选择与你的操作系统和C++编译器版本相匹配的版本。例如,如果你在Windows上使用Visual Studio 2022,就需要下载支持对应VC++工具集的Cplex版本。

安装过程本身是图形化的,比较直观。但有几个关键路径你需要记下来:

  • 安装根目录:比如C:\Program Files\IBM\ILOG\CPLEX_Studio2210。这个目录下包含了所有组件。
  • C++ API头文件路径:通常在安装根目录\cplex\include\ilcplex。你的编译器需要知道这个路径来找到cplex.h,ilcplex\cplex.h等关键头文件。
  • 库文件路径:这在Windows和Linux下区别很大。
    • Windows (Visual Studio):库文件位于安装根目录\cplex\lib\x64_windows_vs2019\stat_mda(具体文件夹名因版本和编译器而异)。你需要的是.lib文件,例如cplex2210.lib
    • Linux/macOS:库文件通常是安装根目录\cplex/lib/x86-64_linux/static_pic/libcplex.a(静态库)或libcplex.so(动态库)。static_pic表示位置无关代码的静态库,兼容性更好。

注意:Cplex的库有多个变体,如stat_mda(静态多线程调试)、stat_mta(静态多线程发布)、x64_windows_vs2019等。务必根据你的项目配置(Debug/Release, 32位/64位)选择正确的库。选错会导致链接错误。

2.2 C++开发环境配置实战(以Visual Studio为例)

假设我们使用Visual Studio 2022进行开发。创建一个新的C++控制台项目后,需要进行如下关键配置:

  1. 包含目录配置:在项目属性 -> C/C++ -> 常规 -> 附加包含目录中,添加Cplex的include目录路径,例如C:\Program Files\IBM\ILOG\CPLEX_Studio2210\cplex\include

  2. 库目录配置:在项目属性 -> 链接器 -> 常规 -> 附加库目录中,添加Cplex库文件所在的目录路径,例如C:\Program Files\IBM\ILOG\CPLEX_Studio2210\cplex\lib\x64_windows_vs2019\stat_mda

  3. 附加依赖项配置:在项目属性 -> 链接器 -> 输入 -> 附加依赖项中,添加需要链接的库文件名,例如cplex2210.lib。你只需要添加Cplex的核心库,其他依赖(如并发运行时)Cplex库通常会帮你处理好。

  4. 预处理器定义:有时,为了使用Cplex的某些特性或避免警告,可能需要添加预处理器定义,例如IL_STD(强制使用标准C++库)。这通常在项目属性 -> C/C++ -> 预处理器 -> 预处理器定义中添加。

  5. 运行时库匹配:确保你的项目运行时库设置与Cplex库的编译设置一致。例如,如果你使用的Cplex库是/MT(静态链接运行时库)编译的,你的项目也应设置为/MT(对于Release)或/MTd(对于Debug)。不一致会导致严重的运行时错误。在项目属性 -> C/C++ -> 代码生成 -> 运行时库中进行设置。

2.3 第一个验证程序:环境测试

配置完成后,强烈建议编写一个最简单的程序来测试环境是否通畅。这个程序不求解复杂问题,只测试基本的API调用和链接。

#include <ilcplex/ilocplex.h> #include <iostream> int main() { // 尝试创建CPLEX环境对象,这是所有操作的起点 IloEnv env; try { std::cout << "CPLEX环境创建成功!版本信息:" << env.getVersion() << std::endl; // 可以进一步创建一个简单的空模型 IloModel model(env); IloCplex cplex(model); std::cout << "CPLEX求解器对象创建成功!" << std::endl; // 正常结束,关闭环境 env.end(); } catch (IloException& e) { // 捕获CPLEX特有的异常 std::cerr << "CPLEX异常: " << e << std::endl; env.end(); return -1; } catch (...) { // 捕获其他所有异常 std::cerr << "未知异常发生." << std::endl; env.end(); return -1; } return 0; }

如果这个程序能成功编译、链接并运行,输出Cplex的版本信息,那么恭喜你,最艰难的环境关已经过了。如果出现“无法打开ilcplex/ilocplex.h文件”之类的错误,检查包含目录;如果出现“无法解析的外部符号”链接错误,检查库目录和附加依赖项。

3. 整数规划模型构建的核心步骤解析

环境搭好,我们就进入核心环节:用C++代码“描述”一个整数规划问题。Cplex的C++ API采用了一种非常直观的“建模式”编程接口,你需要理解几个核心类的用法。

3.1 理解核心类:Env, Model, Cplex, Var, Range

  • IloEnv(环境类):这是所有CPLEX对象的“生存空间”。任何其他CPLEX对象(模型、变量、约束)都必须创建在一个IloEnv对象的上下文中。通常一个应用只有一个IloEnv实例,并在最后调用env.end()来清理所有关联资源。务必确保env.end()被调用,否则可能导致内存泄漏。
  • IloModel(模型类):是问题定义的容器。你把变量、目标函数、约束都添加到这个模型里。
  • IloCplex(求解器类):它接收一个IloModel,并负责求解它。你可以通过这个类设置求解参数、触发求解、获取解的状态和值。
  • IloNumVar(变量类):代表决策变量。创建时需要指定变量类型(连续IloFloat、整数IloInt、0-1布尔IloBool)、下界和上界。
  • IloRange(约束类):代表线性约束,形式为lower <= expression <= upperexpression是由变量和系数构成的线性表达式。

3.2 一个完整的建模示例:背包问题

我们以经典的0-1背包问题为例,构建一个完整的模型。假设有5件物品,其价值和重量如下,背包容量为10,求最大总价值。

#include <ilcplex/ilocplex.h> #include <vector> #include <iostream> int main() { IloEnv env; try { // 1. 定义数据 std::vector<int> value = {10, 20, 15, 25, 30}; // 价值 std::vector<int> weight = {2, 5, 3, 6, 9}; // 重量 int capacity = 10; // 背包容量 int numItems = value.size(); // 2. 创建模型和求解器对象 IloModel model(env); IloCplex cplex(model); // 3. 创建决策变量:x[i] = 1 表示选择物品i,否则为0 IloBoolVarArray x(env, numItems); for (int i = 0; i < numItems; ++i) { // 变量名有助于调试和输出 x[i].setName(("x" + std::to_string(i)).c_str()); } // 4. 添加目标函数:最大化总价值 IloExpr objExpr(env); for (int i = 0; i < numItems; ++i) { objExpr += value[i] * x[i]; } model.add(IloMaximize(env, objExpr)); objExpr.end(); // 表达式使用完毕后需要显式结束 // 5. 添加约束:总重量不超过容量 IloExpr weightExpr(env); for (int i = 0; i < numItems; ++i) { weightExpr += weight[i] * x[i]; } model.add(weightExpr <= capacity); weightExpr.end(); // 6. 求解模型 if (!cplex.solve()) { std::cerr << "求解失败: " << cplex.getStatus() << std::endl; env.end(); return -1; } // 7. 获取并输出结果 std::cout << "求解状态: " << cplex.getStatus() << std::endl; std::cout << "最优目标值: " << cplex.getObjValue() << std::endl; std::cout << "选择的物品: "; for (int i = 0; i < numItems; ++i) { if (cplex.getValue(x[i]) > 0.5) { // 0-1变量,判断是否接近1 std::cout << i << " "; } } std::cout << std::endl; // 8. 输出每个变量的具体值 IloNumArray vals(env); cplex.getValues(vals, x); std::cout << "变量值: " << vals << std::endl; } catch (IloException& e) { std::cerr << "CPLEX错误: " << e << std::endl; } catch (...) { std::cerr << "未知错误" << std::endl; } env.end(); return 0; }

这段代码清晰地展示了建模的流程:数据准备 -> 创建变量 -> 构建目标函数 -> 添加约束 -> 求解 -> 获取解。IloExpr类用于临时构建线性表达式,用完后必须调用.end()释放资源,这是Cplex API内存管理的一个特点,需要特别注意。

3.3 处理更复杂的约束与变量类型

实际问题的约束远不止简单的线性不等式。你可能需要处理:

  • 等式约束model.add(expr == 100);
  • 变量上下界:在创建变量时指定,如IloNumVar var(env, 0.0, IloInfinity, IloFloat);创建一个非负连续变量。IloInfinity代表无穷大。
  • 整数变量和0-1变量:使用IloIntIloBool类型。对于广义的整数变量,也可以创建连续变量后,通过model.add(IloConversion(env, var, ILOINT));将其转换为整数变量,但直接创建IloIntVar更清晰。
  • 分段线性约束、二次约束、逻辑约束:Cplex也支持,但API更为复杂,需要查阅专门文档。

4. 求解策略与性能调优实战

模型建好了,直接调用cplex.solve()可能对于小问题没问题,但对于大规模整数规划,默认设置往往无法在可接受时间内求得最优解。这时就需要进行求解策略和参数调优。

4.1 关键求解参数设置

Cplex提供了上百个参数来控制求解过程。通过IloCplex对象的setParam方法进行设置。以下是一些最常用、对性能影响最大的参数:

IloCplex cplex(model); // 设置最大求解时间(秒) cplex.setParam(IloCplex::TiLim, 3600); // 1小时超时 // 设置相对最优间隙容忍度。当 |bestbound - bestinteger| / (1e-10+|bestinteger|) < gap 时停止。 cplex.setParam(IloCplex::EpGap, 0.01); // 1%的gap // 设置绝对最优间隙容忍度 cplex.setParam(IloCplex::EpAGap, 0.1); // 设置线程数,充分利用多核CPU cplex.setParam(IloCplex::Threads, 0); // 0表示使用所有可用线程 // 强调更侧重寻找可行解还是提升边界 cplex.setParam(IloCplex::MIPEmphasis, 1); // 1=平衡,2=侧重可行性,3=侧重最优性 // 设置节点选择策略 cplex.setParam(IloCplex::NodeSel, 1); // 1=深度优先,2=最佳边界优先 // 打开详细输出,用于调试 cplex.setParam(IloCplex::Param::MIP::Display, 2);

参数调优心得:没有一套放之四海而皆准的参数。EpGap是控制求解精度的关键,在时间和精度之间权衡。对于初期调试,可以设一个较大的EpGap(如0.05)快速获得一个可行解。Threads参数在多核机器上效果显著。MIPEmphasis在你觉得可行解很难找时,可以设为2;在找到可行解后想证明最优性时,可以设为3。

4.2 利用回调函数实现高级控制

回调函数是Cplex提供的一个强大机制,允许你在求解过程中插入自定义逻辑。最常用的是“惰性约束回调”和“用户割回调”。

  • 惰性约束回调 (Lazy Constraint):有些约束非常庞大,显式添加到模型里会让模型变得臃肿。惰性约束允许你一开始不添加它们,只有当求解器找到一个候选整数解时,才检查这个解是否违反了这些约束。如果违反,就动态添加这个约束(割),并拒绝当前解。这在处理旅行商问题的子环消除约束时非常经典。
class MyLazyCallback : public IloCplex::LazyConstraintCallbackI { IloNumVarArray _vars; public: MyLazyCallback(IloEnv env, IloNumVarArray vars) : LazyConstraintCallbackI(env), _vars(vars) {} // 回调函数主体 void main() override { // 获取当前节点的候选整数解 IloNumArray vals(getEnv()); getValues(vals, _vars); // 检查是否违反某个复杂约束(这里用伪代码) if (violatesMyConstraint(vals)) { // 构建一个割(约束) IloExpr cut(getEnv()); // ... 根据vals构造cut表达式 ... // 添加惰性约束 add(cut <= someBound).end(); cut.end(); } vals.end(); } // 必须实现的克隆方法 IloCplex::CallbackI* duplicateCallback() const override { return new (getEnv()) MyLazyCallback(getEnv(), _vars); } }; // 注册回调 MyLazyCallback* cb = new MyLazyCallback(env, x); cplex.use(&cb);
  • 用户割回调 (User Cut Callback):与惰性约束在找到整数解后触发不同,用户割可以在任何节点(包括非整数节点)添加,用于收紧线性规划松弛的边界,从而加速求解。

重要提示:在回调函数中,绝对不要修改模型的主体(如添加普通约束、修改变量边界),也不要调用cplex.solve()等会触发求解的方法。只能通过add()方法添加惰性约束或用户割。违规操作会导致未定义行为。

4.3 模型预处理与启发式算法

在调用求解之前,对模型进行预处理也能极大提升性能。

  • 探查 (Probing)cplex.setParam(IloCplex::Probe, 3);设置探查强度。探查会临时固定变量为0或1,推断其他变量的可能取值,从而发现固定的变量和冗余的约束。
  • 对称性处理:如果问题存在很多对称解(例如,分配相同的工人到相同的任务),会大大增加分支定界树的规模。可以尝试设置cplex.setParam(IloCplex::Symmetry, 3);来启用对称性破缺。
  • 使用初始解 (Warm Start):如果你通过启发式方法已经得到了一个不错的可行解,可以将其提供给Cplex作为起点,能显著缩短求解时间。
IloNumArray startVals(env, numVars); // ... 填充startVals为你的启发式解 ... cplex.addMIPStart(x, startVals); startVals.end();

5. 错误处理、调试与结果解析

即使一切顺利,你也需要妥善处理各种求解结果,并具备调试模型的能力。

5.1 求解状态与结果获取

cplex.solve()返回一个布尔值,但更重要的是cplex.getStatus()返回的状态码。

状态码 (IloCplex::Status)含义后续操作建议
Optimal找到了最优解。可以安全获取目标函数值和变量值。
Feasible找到了可行解,但未证明最优(可能因时间或gap限制停止)。解是可行的,可以获取。检查EpGap和求解时间。
Infeasible模型被证明无可行解。检查约束条件是否互相矛盾。使用cplex.refineConflict()分析冲突约束。
Unbounded目标函数值在可行域上无界(对于最大化问题趋于正无穷,最小化问题负无穷)。检查是否遗漏了必要的约束,或者目标函数系数设置错误。
Error求解过程中发生错误。检查模型数据是否有非法值(如NaN),或内存是否不足。
Unknown求解过程因某种原因中断,状态未知。检查日志,可能是用户中断或资源耗尽。

获取解的值:

if (cplex.getStatus() == IloAlgorithm::Optimal || cplex.getStatus() == IloAlgorithm::Feasible) { double objVal = cplex.getObjValue(); // 获取目标函数值 IloNumArray varVals(env); cplex.getValues(varVals, x); // 获取所有变量x的值 for (int i = 0; i < numVars; ++i) { std::cout << x[i].getName() << " = " << varVals[i] << std::endl; } varVals.end(); }

5.2 模型调试与冲突约束分析

当模型求解失败或结果不合理时,调试是关键。

  1. 输出模型到文件:可以将模型导出为LP格式,用其他工具(如文本编辑器、Cplex自带的交互式优化器)查看。

    cplex.exportModel("my_model.lp");

    打开.lp文件,你可以清晰地看到所有变量、目标函数和约束,检查是否有建模错误。

  2. 分析不可行模型:如果状态是Infeasible,使用冲突约束分析功能,它能帮你找出导致不可行的一组最小约束。

    if (cplex.getStatus() == IloAlgorithm::Infeasible) { IloConstraintArray cons(env); IloNumArray prefs(env); // 为所有约束分配一个偏好值(通常为1) for (IloModel::Iterator it(model); it.ok(); ++it) { if ((*it).isConstraint()) { cons.add(*it); prefs.add(1.0); } } if (cplex.refineConflict(cons, prefs)) { IloCplex::ConflictStatusArray conflict = cplex.getConflict(cons); std::cout << "冲突约束:" << std::endl; for (int i = 0; i < cons.getSize(); ++i) { if (conflict[i] == IloCplex::ConflictMember || conflict[i] == IloCplex::ConflictPossibleMember) { std::cout << " " << cons[i] << std::endl; } } } cons.end(); prefs.end(); }
  3. 检查变量和约束的松弛值:对于可行但非最优的解,或者想了解约束的紧密度,可以获取松弛变量的值或约束的“松弛量”。

5.3 性能分析与日志解读

将求解器的输出级别调高(cplex.setParam(IloCplex::Param::MIP::Display, 4)),运行模型,观察控制台输出的日志。你需要关注:

  • 节点处理速度:每秒处理多少个节点?速度过慢可能意味着线性规划松弛求解太耗时。
  • 边界改进情况:最佳整数界和最佳目标边界之间的差距如何缩小?如果很久不缩小,可能需要调整分支策略或启发式参数。
  • 切割生成:加入了多少割平面?它们对边界的提升效果如何?
  • 内存使用:节点树是否过大导致内存不足?

根据日志信息,有针对性地调整NodeSelVarSelCuts等参数。例如,如果日志显示“太多切割被生成但效果甚微”,可以尝试降低切割生成的强度cplex.setParam(IloCplex::CutsFactor, 0.5);

6. 工程化集成与内存管理最佳实践

在大型项目中,C++与Cplex的集成不仅仅是调用API,更需要考虑代码结构、异常安全和资源管理。

6.1 封装求解器类

为了避免IloEnv和模型对象在代码中到处传递,也为了更好的异常安全,建议将Cplex求解功能封装在一个独立的类中。

class MIPSolver { private: IloEnv _env; IloModel _model; IloCplex _cplex; IloNumVarArray _vars; bool _modelBuilt; public: MIPSolver() : _env(), _model(_env), _cplex(_model), _vars(_env), _modelBuilt(false) { // 可以在这里设置一些默认参数 _cplex.setParam(IloCplex::EpGap, 0.0001); _cplex.setParam(IloCplex::Threads, 0); } ~MIPSolver() { // 按创建相反的顺序释放资源 _vars.end(); _cplex.end(); _model.end(); _env.end(); } // 禁止拷贝 MIPSolver(const MIPSolver&) = delete; MIPSolver& operator=(const MIPSolver&) = delete; void buildModel(const ProblemData& data) { // 清空旧模型(如果需要) _model = IloModel(_env); _cplex = IloCplex(_model); _vars = IloNumVarArray(_env); // ... 根据data构建变量、目标、约束 ... _modelBuilt = true; } SolveResult solve() { if (!_modelBuilt) { throw std::runtime_error("Model not built before solving."); } SolveResult result; try { if (!_cplex.solve()) { result.status = _cplex.getStatus(); return result; } result.status = IloAlgorithm::Optimal; // 或Feasible result.objectiveValue = _cplex.getObjValue(); IloNumArray vals(_env); _cplex.getValues(vals, _vars); // 将vals存储到result中... vals.end(); } catch (const IloException& e) { std::cerr << "Solver error: " << e << std::endl; result.status = IloAlgorithm::Error; } return result; } };

这种封装将Cplex的复杂生命周期管理和错误处理隔离在类内部,对外提供干净的接口。

6.2 异常安全与资源管理

C++ API中的许多对象(IloExpr,IloNumArray等)需要手动调用.end()来释放资源。为了确保异常发生时资源也能被正确释放,有两条黄金法则:

  1. RAII(资源获取即初始化):尽可能使用局部对象。当IloExprIloNumArray作为局部变量时,在其作用域结束时(即使因为异常跳出),析构函数会被调用,而Cplex API对象的析构函数会自动调用.end()

    void addConstraint(IloModel& model, IloNumVarArray& x, ...) { IloExpr expr(env); // RAII对象 // ... 构建expr ... model.add(expr <= bound); // 不需要显式调用 expr.end(); 析构函数会处理 } // 此处expr析构,资源释放
  2. 在try-catch块外声明Env,在finally块中结束:确保IloEnv env在try块外声明,并在所有清理工作完成后调用env.end()。通常将env.end()放在catch块之后。

    IloEnv env; // 在try外声明 try { // ... 使用env创建各种对象 ... } catch (IloException& e) { std::cerr << e << std::endl; // 注意:这里不能调用env.end(),因为catch块内可能还需要访问env相关的对象 // 但通常这些对象会在栈展开过程中被析构。 } // 确保所有在env中创建的对象都已析构后,再结束env env.end();

6.3 处理大规模模型:增量建模与内存

当变量和约束数量极大(数十万甚至上百万)时,一次性构建整个模型可能消耗大量内存和时间。可以考虑增量式建模:

  • 分块构建:先添加核心变量和约束,求解一个简化模型。然后根据结果,动态添加其他变量和约束(例如,惰性约束回调本身就是一种增量式添加)。
  • 使用稀疏数据结构:在构建约束时,避免使用密集循环。如果约束矩阵非常稀疏,直接构造稀疏的行。
  • 及时清理中间对象:在构建模型的大循环中,对于临时使用的IloExpr对象,应在每次迭代结束后立即调用.end(),而不是等到整个模型构建完毕。

此外,对于超大规模问题,可能需要调整Cplex的内存参数:

cplex.setParam(IloCplex::WorkMem, 4096); // 设置工作内存为4GB cplex.setParam(IloCplex::NodeFileInd, 2); // 当节点树内存超过WorkMem时,使用磁盘文件存储(2=压缩存储)

7. 从文件读取数据与模型持久化

在实际项目中,模型数据通常来自外部文件(如CSV、JSON、数据库)。将建模逻辑与数据读取分离是良好的实践。

7.1 读取数据并构建模型

#include <fstream> #include <sstream> #include <vector> struct ProblemData { std::vector<double> cost; std::vector<std::vector<double>> constraintCoeffs; std::vector<double> constraintRHS; }; ProblemData readDataFromCSV(const std::string& filename) { ProblemData data; std::ifstream file(filename); std::string line; // ... 解析CSV文件,填充data结构 ... return data; } void buildModelFromData(IloEnv& env, IloModel& model, IloNumVarArray& vars, const ProblemData& data) { int nVars = data.cost.size(); int nConstraints = data.constraintRHS.size(); // 创建变量 vars = IloNumVarArray(env, nVars, 0.0, IloInfinity, IloFloat); // 创建目标函数 IloExpr objExpr(env); for (int i = 0; i < nVars; ++i) { objExpr += data.cost[i] * vars[i]; } model.add(IloMinimize(env, objExpr)); objExpr.end(); // 创建约束 for (int c = 0; c < nConstraints; ++c) { IloExpr constrExpr(env); for (int i = 0; i < nVars; ++i) { double coeff = data.constraintCoeffs[c][i]; if (coeff != 0.0) { // 稀疏性检查 constrExpr += coeff * vars[i]; } } model.add(constrExpr <= data.constraintRHS[c]); constrExpr.end(); } }

7.2 模型导出与导入

有时你需要保存模型供以后使用,或者在不同环境间传递。

  • 导出为LP或MPS文件:这是标准格式,可以被大多数优化软件读取。
    cplex.exportModel("model.lp"); // 导出为LP格式 cplex.exportModel("model.mps"); // 导出为MPS格式
  • 导入模型:Cplex也可以从文件读取模型。
    IloModel model(env); IloCplex cplex(env); cplex.importModel(model, "model.lp", objSense); // objSense是IloObjective::Sense类型 // 然后可以像平常一样求解model cplex.extract(model); cplex.solve();

7.3 保存与加载求解状态(高级)

对于长时间运行的任务,你可能希望保存求解的中间状态(分支定界树),以便以后从该点继续求解。这可以通过“节点文件”来实现。

// 求解前,设置节点文件 cplex.setParam(IloCplex::WorkMem, 1024); // 内存限制1GB cplex.setParam(IloCplex::NodeFileInd, 3); // 节点文件策略:3=压缩且允许从文件继续 // 求解... cplex.solve(); // 如果想保存当前状态(例如,达到时间限制后) cplex.writeNodes("mysearchstate.node"); // 以后,可以读取这个状态继续求解 cplex.readNodes("mysearchstate.node"); cplex.solve(); // 将从保存的状态继续搜索

这个功能对于需要在计算集群上分片运行超大规模问题的场景非常有用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询