SCIP求解器本身就是用C/C++开发的,对于C/C++提供了灵活的调用接口。即使是在python中调用,也是通过cython等语言实现了这些接口的封装,使其更符合python语言特点。比如pyscipopt.
可以通过下面的网址阅读源码,在写C++代码的时候,可以用作参考:
一、C/C++工程的搭建
有三个条件,需要保证:
1. 环境变量下加入了安装目录下的bin路径,也就是在cmd下敲入scip后,能够进入scip环境。
2. 能够找到头文件路径。
3. 所在环境能够找到libscip.lib,这个文件在安装目录的lib路径下面:
对于第2、3两条,可以将安装路径下的include目录和libscip.lib拷贝出来,放到自己的工程下面,然后加载到工程中。
比如,在工程中新建lib文件夹,将libscip.lib放到里面,新建public文件夹,将include放到里面,下面是用CMake加载这两部分的方式:
cmake_minimum_required (VERSION 3.0)
project(CmakeTest)
include_directories(
public
public/include # 这是scip的头文件
)
add_executable(runTests
scipTest.cpp
)
find_library(scip_static NAMES scip PATHS "lib/") # 这是找到libscip.lib
target_link_libraries(runTests scip_static)
这样,需要加载的头文件和库文件就被加载到工程中了。 可以在scipTest.cpp中调用scip接口了。
其实在安装目录下的lib文件夹中提供了cmake文件夹,可以利用它来实现,可能更加官方。
二、常用的接口介绍
1. 这些都是基本线性问题求解所需的接口:
通过一段代码最能说明问题,
#include <iostream>
#include <scip/scipdefplugins.h>
//目标函数: 2x+3y
//con 2x+3y >= 7
int main(void) {
// creates and initializes SCIP data structures
SCIP * _scip;
SCIPcreate(&_scip);
// include default plugins
SCIPincludeDefaultPlugins(_scip);
// creates empty problem and initializes all solving data structures
SCIPcreateProbBasic(_scip, "test");
//sets objective sense of problem
SCIPsetObjsense(_scip, SCIP_OBJSENSE_MINIMIZE);
// changes the value of an existing SCIP_Real parameter
SCIPsetRealParam(_scip, "limits/gap", 0.00001);
SCIPsetRealParam(_scip, "limits/time", 10);
//--添加变量
SCIP_VAR *xVar = nullptr;
SCIPcreateVarBasic(_scip, &xVar, "_x", 0, SCIPinfinity(_scip), 2.0, SCIP_VARTYPE_INTEGER);
SCIPaddVar(_scip, xVar);
SCIP_VAR *yVar = nullptr;
SCIPcreateVarBasic(_scip, &yVar, "_y", 0, SCIPinfinity(_scip), 3.0, SCIP_VARTYPE_INTEGER);
SCIPaddVar(_scip, yVar);
//--添加约束
SCIP_CONS* zCons;
SCIPcreateConsBasicLinear(_scip, &zCons, "z_cons", 0, NULL, NULL, 7, SCIPinfinity(_scip));
SCIPaddCoefLinear(_scip, zCons, xVar, 2);
SCIPaddCoefLinear(_scip, zCons, yVar, 3);
SCIPaddCons(_scip, zCons);
SCIPsolve(_scip);
//--取最优解
SCIP_Real x_value = SCIPgetSolVal(_scip, SCIPgetBestSol(_scip), xVar);
SCIP_Real y_value = SCIPgetSolVal(_scip, SCIPgetBestSol(_scip), yVar);
//--释放资源
SCIPreleaseVar(_scip, &xVar);
SCIPreleaseVar(_scip, &yVar);
SCIPreleaseCons(_scip, &zCons);
SCIPfree(&_scip);
std::cout << "x_value: " << x_value << std::endl;
std::cout << "y_value: " << y_value << std::endl;
return 0;
}
需要注意的是,通常设置目标函数的方式在添加变量的过程就完成了,因为设置参数的接口中就存在目标函数系数这一项。如果目标函数中没有这个变量,则设置成0即可。
2. 修改目标函数中变量参数的函数
但是有时候为了使流程更加符合一个常规的求解流程,常常将设置目标函数单独拿出来。这就需要在设置参数的时候,将对应变量在目标函数的参数全部设置成0,而在后面统一修改。通过下面的接口就可以实现。
SCIPchgVarObj(_scip, var, 1);
3. 模型的信息打印方法
另外介绍一个调试常用的接口,这个接口可以将模型的信息打印到test.lp的文件里面,你可以在其中看到你设置的变量,约束条件,目标函数的系数等信息。
SCIPwriteLP(_scip, "D:/test.lp");
根据8.0官方接口修改文档:
- calling SCIPwriteLP() is now possible in Solved Stage
- SCIPwrite{LP,MIP} may no longer be called after solving, since the LP data structures may not be valid
4. 模型信息打印方法2
还有一个对于调试很好用的接口:
SCIPwriteOrigProblem(_scip, path, "cip", FALSE);
可以将变量,约束条件等保存到文件中。调用时放到SCIPsolve接口之前,更具体用法可以参照官方文档。
输出的样例如下:
STATISTICS
Problem name : spring
Variables : 18 (11 binary, 1 integer, 0 implicit integer, 6 continuous)
Constraints : 0 initial, 9 maximal
OBJECTIVE
Sense : minimize
VARIABLES
[binary] <wire1>: obj=0, original bounds=[0,1]
[binary] <wire2>: obj=0, original bounds=[0,1]
[binary] <wire3>: obj=0, original bounds=[0,1]
[binary] <wire4>: obj=0, original bounds=[0,1]
[binary] <wire5>: obj=0, original bounds=[0,1]
[binary] <wire6>: obj=0, original bounds=[0,1]
[binary] <wire7>: obj=0, original bounds=[0,1]
[binary] <wire8>: obj=0, original bounds=[0,1]
[binary] <wire9>: obj=0, original bounds=[0,1]
[binary] <wire10>: obj=0, original bounds=[0,1]
[binary] <wire11>: obj=0, original bounds=[0,1]
[integer] <ncoils>: obj=0, original bounds=[0,+inf]
[continuous] <volume>: obj=1, original bounds=[0,+inf]
[continuous] <wirediam>: obj=0, original b