IE盒子

搜索
查看: 95|回复: 2

Cplex && C++

[复制链接]

1

主题

3

帖子

5

积分

新手上路

Rank: 1

积分
5
发表于 2022-11-27 16:10:12 | 显示全部楼层 |阅读模式
前言

Cplex求解器想必接触过建模求解的同学都会多少了解些,是IBM推出的一款求解各种线性规划、非线性规划、整数规划等模型的技术。个人没叫它“软件”是因为相比软件,其更像是一个“库”,提供了各种语言的调用接口。
本人因为用的最多的就是C++,但是在中文网站上发现有关使用C++语言调用Cplex lib的教程很少,本人在使用过程中有了一些心得,因此便想将自己学习过程中使用到的方法都整理出来,供大家参考。(若有错误,恳求批评指正,并希望大家能够积极补充)。
准备工作&项目配置

1、Visual Studio 2022(任意版本皆可,但最好在VS2017之后的版本)
2、Cplex (可在csdn上自行寻找安装包,不嫌麻烦的可以去IBM官网申请下载)
配置过程如下
注:以下所有目录均为默认安装目录,如果自己更改了安装目录,请自行换为自己的实际安装目录
1、打开VS,新建“C++空项目”,这里我们项目起名为“CplexProject”(起名可以任意)。



图 1 创建项目

2、新建项目之后,必须先新建一个源文件,否则无法配置项目(快捷键“Ctrl+Shift+A”,或者右键“CplexProject”添加源文件)。



图 2 添加.cpp源文件

3、随后创建好的源文件内,我们先包含Cplex库的头文件
#include <ilcplex\ilocplex.h>
这时候肯定是报错的,因为我们还没有配置。接下来才开始正式配置项目,
右键CplexProject => 属性



图 3 配置项目属性

在“常规”->“附加包含目录”中添加以下目录
C:\Program Files\IBM\ILOG\CPLEX_Studio128\cplex\include
C:\Program Files\IBM\ILOG\CPLEX_Studio128\concert\include
在“C/C++”->“预处理器”->“预处理器定义”,添加以下宏定义指令
NDEBUG
_CONSOLE
IL_STD
在“C/C++”->“语言”->“符合模式”改为“否”
在“链接器”->“常规”->“附加库目录”输入如下地址
C:\Program Files\IBM\ILOG\CPLEX_Studio128\cplex\lib\x64_windows_vs2017\stat_mda
C:\Program Files\IBM\ILOG\CPLEX_Studio128\concert\lib\x64_windows_vs2017\stat_mda
在“链接器”->“输入”->“附加依赖项”输入如下地址
C:\Program Files\IBM\ILOG\CPLEX_Studio128\concert\lib\x64_windows_vs2017\stat_mda\concert.lib
C:\Program Files\IBM\ILOG\CPLEX_Studio128\cplex\lib\x64_windows_vs2017\stat_mda\cplex1280.lib
C:\Program Files\IBM\ILOG\CPLEX_Studio128\cplex\lib\x64_windows_vs2017\stat_mda\ilocplex.lib
最后,将平台改为“Release + x64",即可发现头文件已经不报错了。



图 4 更改平台与生成.exe

(注意:目前配置后,在运行时可能会出现”找不到cplex1280.dll“的报错,这是因为可执行文件在当前文件夹下找不到运行所需动态链接库,解决方法有两个:1、找到cplex1280.dll并复制到可执行文件的文件夹下。可执行文件的位置可以在生成解决方案后,点击 图4 中箭头指向的图标,点开当前文件夹的上一级文件夹,随后可看到”x64“->”Release“,就是生成的可执行文件所在目录,将cplex1280.dll文件复制到该目录即可。



图 5 可执行文件根目录

2、除了上述方法,另一种方法就是找到cplex1280所在的目录,复制,然后在 图 3 项目配置界面找到”调试“->”环境“,新增如下语句(后面是cplex1280.dll所在文件夹位置,如果你也是默认安装,应该也是这个文件夹,保存即可。)
PATH=C:\Program Files\IBM\ILOG\CPLEX_Studio128\cplex\bin\x64_win64
输入模型

Cplex Concert Technology对于C++自定义类型进行了一些封装与重命名,但是在程序的最开始,请输入下面这句宏定义
ILOSTLBEGIN // 等同于 using namespace std; 声明命名空间输入模型需要在程序开头先创建环境变量,再创建模型对象,在程序末尾创建求解对象。整体代码框架如下
#include <ilcplex/ilocplex.h>
ILOSTLBEGIN

int main()
{
    IloEnv env;//创建环境变量
    IloModel model(env);//创建模型对象
    /*
    * 中间部分输入模型代码,如目标函数、约束条件等
    */
    IloCplex cplex(model);//创建求解对象
    cplex.extract(model);//抽取模型
    cplex.solve();//求解
    /*
    * 自己根据需求对结果进行输出
    */
    cplex.end();//释放对象
    model.end();
    env.end();
    return EXIT_SUCCESS;// 等于return 0;
}下面对于上述注释细节部分进行介绍
基础类型:

IloInt -> int(整数)
IloNum -> double(浮点数)
IloBool -> bool(布尔值)
IloInt -> 整数类型变量
IloNum -> 浮点类型变量
IloBool ->布尔类型变量
数组:

IloArray -> 相当于一个模板向量,可以用其来创建任意类型的数组,也可以用来嵌套IloArray,用法类似vector。
IloNumArray -> 创建包含IloNum类型数据的数组
IloIntArray -> 创建包含IloInt类型数据的数组
IloBoolArray -> 创建包含Iloint类型数据的数组
IloNumVarArray -> 创建包含IloNum类型数据的变量数组
IloIntVarArray -> 创建包含IloInt类型数据的变量数组
IloBoolVarArray -> 创建包含Iloint类型数据的变量数组
参数数组

1、比如我想创建一个数组存放10个点的需求,则可以
IloArray<IloNum> demands;这样就创建了一个空数组demands,数组里每个元素为IloNum类型。但是目前大小是0,所以暂时不可以通过下标访问该数组,可以通过setSize()成员函数设置数组大小
demands.setSize(10);将demands的大小设置为10,然后通过下标读取和写入。
2、另一种写入的方式类似于vector的push_back成员函数
demands.add(2.2);就可以向demands数组尾插入一个2.2(数组的size也随着增加,capacity不一定)。
创建变量

在Cplex Concert Technology语法中,凡是涉及到设置决策变量(也就是模型最后要求解的变量)的数据类型,都会带上“Var"。
创建一个变量 x>0 ,因为该变量的取值是大于0的任何实数,所以它最终解出来的值肯定是IloNum(double)类型。
IloNumVar x(env, 0, IloInfinity, "变量x");//参数如下(环境变量[,下界[,上界[,变量名(字符串)]])创建0-1变量 x_{i}\in[0,1],i\in(0,10) (一维),该变量只能取0或1,所以用IloBool(bool)类型表示最为合适。另一种方法是设置为整数IloInt(int)变量,然后设置该变量的上下界,但是我在测试的时候发现求解会抛出异常。(暂时不知道什么原因,猜测可能是上界设为1的时候变量取不到1的原因,等后续测试看看)。
IloArray<IloBoolVar> x(env);
x.setSize(10);//设定数组大小为10
for (IloInt i = 0; i < x.getSize(); i++){
    x = IloBoolVar(env);
}
//或者通过下面方式
IloArray<IloIntVar> x(env);
for (size_t i = 0; i < 10; ++i)
{
    x.add(IloIntVar(env, 0, 1));//往后插入10次IloIntVar决策变量
}创建0-1变量 y_{i,j}\in[0,1],i\in(0,10) (二维)
//方法1
IloArray<IloBoolVarArray> y(env);
y.setSize(10);
for (int i = 0; i < y.getSize(); i++) {
    y = IloBoolVarArray(env, 10);
    for (int j = 0; j < y.getSize(); j++) {
        y[j] = IloBoolVar(env);
    }
}


//方法2
IloArray<IloBoolVarArray> y(env);
y.setSize(10);
for (auto i = 0; i < y.getSize(); i++)
{
    y.setSize(10);
}
for (int i = 0; i < y.getSize(); i++) {
    for (int j = 0; j < y.getSize(); j++) {
        y[j] = IloBoolVar(env);
    }
}因为设置的是决策变量,需要求解才能知道具体值是多少,所以不是初始化为具体的值,而是一个变量类型。
设置目标函数

一般来说,模型的目标函数是与决策变量参数相关的一个期望值,举个简单的例子
min  2x+3y
在代码中,我们需要先输入表达式2x+3y,语法如下
IloExpr e(env);//声明一个表达式对象
e = 2 * x + 3 * y;//输入表达式随后将其作为约束条件放入模型对象中,在前面我们已经声明了model对象,添加约束需要调用model的add方法
model.add(IloMinimize(env, e));//IloMinimize是求最小化的意思,对应的IloMaximize是求最大化
e.end();//释放对象下面再举一个稍微复杂的形式,也是建模用的最多的情况
max  \sum_{i=0}^{n}{2x_{i}} + \sum_{j=0}^{m}{3y_{j}}
同样的,先输入表达式
IloExpr e(env);
for (size_t i = 0; i <= n; ++i) {
    e += 2 * x;
}
for (size_t j = 0; j <= m; ++j) {
    e += 3 * y[j];
}
model.add(IloMaximize(env, e));
e.end();//随时释放是个好习惯,否则后面在使用e这个变量会报错添加约束条件

与添加目标函数类似,如添加约束条件如下
1、普通约束
s.t.   x+y<4
代码如下
model.add(x + y < 4);2、累加形式约束
s.t.  
\sum_{i=0}^{n}{x_{i}}<10                (1)
y_{i}>1.2,\forall i\in n           (2)
代码如下
//约束(1)
IloExpr e(env);
for (size_t i = 0; i <= n; ++i) {
    e += x;
}
model.add(e < 10);
e.end();

//约束(2)
for (size_t i = 0; i <= n; ++i) {
    model.add(y > 1.2);
}3、逻辑约束
逻辑约束指的是当达到某种条件时,添加该约束,否则不施加该约束。常见的方式是大M法,但更加推荐使用Cplex的IloIfThen接口,本文直接引用IBM官网的例子
例子如下
//只有x同时大于等于y和z的时候,x不能小于等于300或者y大于等于700
model.add(IloIfThen(env, (x >= y && x >= z), IloNot(x <= 300 || y >= 700)));其他参数设置

1、设置求解输出参数
Cplex默认在控制台输出求解记录,如果不希望控制台输出一大堆求解过程,则可以通过设置cplex对象参数
cplex.setOut(env.getNullStream());如果希望将求解过程输出到文档中保存下来,则需要将参数设置为ofstream读写对象
#include <fstream> //需要添加头文件
...
ofstream fout(outputFilePath);//outputFilePath为需要打开的文件的路径
cplex.setOut(fout);
...
fout.close();//关闭文件读取2、设置求解时间限制
在模型复杂的情况下,Cplex求解时间可能会过长,因此需要设置一定的时间上限,到达上限后自动停止求解并输出结果,时间以秒为单位。
cplex.setParam(IloCplex::TiLim, 3600);//设置最大运行时间3600s3、获得结果详细信息
想要获取求解最终的目标函数值以及决策变量的信息,需要先调用cplex对象的solve()方法,若成果求解,该方法会返回真。随后再获取具体信息
if (cplex.solve()) {
    cplex.getStatus();//获取当前解的状态,Infeasible\ Feasible\ Optimal
    //cplex.getObjValue();获取目标函数值,该方法无需指明对象,默认获取的是IloMinimize/IloMaximize的值
    //可以通过env.out()输出,该方法等同于iostream中的std::cout
    env.out() << cplex.getObjValue() << "\n";
    //获取决策变量
    env.out() << cplex.getValue(x);
}<hr/>
回复

使用道具 举报

1

主题

5

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2022-11-27 16:10:36 | 显示全部楼层
太棒了,支持!最近也在学习[爱]
回复

使用道具 举报

0

主题

5

帖子

0

积分

新手上路

Rank: 1

积分
0
发表于 2022-11-27 16:11:16 | 显示全部楼层
[机智]谢谢浏览,哈哈
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表