Cbc(Coin或branch and cut)是一个开源的混合整数线性规划求解器,用C++编写。它可以用作可调用库或使用独立的可执行文件。它可以通过各种建模系统、包等以多种方式使用。Clp(Coin或线性规划)是一个开源线性规划求解器。它主要是用来作为一个可调用的库,但也有一个基本的、独立的可执行版本。本文将介绍如何用CLion(CMake)构建工程并能debug源码。
博主的环境是Ubuntu 20.04 LTS。
首先打开CLion创建新工程:
修改CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(CbcProject)
set(PROJECT_ROOT_PATH "${PROJECT_SOURCE_DIR}")
# 可以把cbc打包后的.so文件放进lib目录下
set(LIBRARY_OUTPUT_PATH "${PROJECT_ROOT_PATH}/${OUTDIRS}/lib/")
# 可以把输出文件放进bin目录下
set(EXECUTABLE_OUTPUT_PATH "${PROJECT_ROOT_PATH}/${OUTDIRS}/bin/")
# Build Cbc dependencies.
set(BUILD_DEPS ON)
if (BUILD_DEPS)
set(BUILD_CoinUtils ON)
set(BUILD_Osi ON)
set(BUILD_Clp ON)
set(BUILD_Cgl ON)
set(BUILD_Cbc ON)
endif ()
include(FetchContent)
# 填充期间的日志输出可能非常冗长,使得配置阶段非常嘈杂。此缓存选项(ON默认情况下)隐藏所有填充输出,除非遇到错误。如果遇到挂起下载的问题,
# 暂时关闭此选项可能有助于诊断导致问题的内容群体。
set(FETCHCONTENT_QUIET OFF)
# 避免每次下载编译及其所有依赖
get_filename_component(_deps "../_deps" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
# 在大多数情况下,保存的详细信息没有指定与用于内部子构建、最终源和构建区域的目录相关的任何选项。通常最好将这些决定留给FetchContent 模块来代表项目处理。
# 缓存变量控制收集所有内容填充目录的FETCHCONTENT_BASE_DIR 点,但在大多数情况下,开发人员不需要更改它。
# 默认位置是${CMAKE_BINARY_DIR}/_deps,但如果开发人员更改此值,他们应该致力于保持路径短且刚好低于构建树的顶层,以避免在 Windows 上遇到路径长度问题。
set(FETCHCONTENT_BASE_DIR ${_deps})
# ##############################################################################
# Coinutils
# ##############################################################################
# Coin-OR does not support C++17/C++20 (use of 'register' storage class specifier)
set(CMAKE_CXX_STANDARD 11)
if (BUILD_CoinUtils)
message(CHECK_START "Fetching CoinUtils")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
CoinUtils
GIT_REPOSITORY "https://github.com/Mizux/CoinUtils.git"
GIT_TAG "cmake/2.11.6"
)
FetchContent_MakeAvailable(CoinUtils)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
# ##############################################################################
# Osi
# ##############################################################################
if (BUILD_Osi)
message(CHECK_START "Fetching Osi")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
Osi
GIT_REPOSITORY "https://github.com/Mizux/Osi.git"
GIT_TAG "cmake/0.108.7"
)
FetchContent_MakeAvailable(Osi)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
# ##############################################################################
# Clp
# ##############################################################################
if (BUILD_Clp)
message(CHECK_START "Fetching Clp")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
Clp
GIT_REPOSITORY "https://github.com/Mizux/Clp.git"
GIT_TAG "cmake/1.17.7"
)
FetchContent_MakeAvailable(Clp)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
# ##############################################################################
# Cgl
# ##############################################################################
if (BUILD_Cgl)
message(CHECK_START "Fetching Cgl")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
Cgl
GIT_REPOSITORY "https://github.com/Mizux/Cgl.git"
GIT_TAG "cmake/0.60.5"
)
FetchContent_MakeAvailable(Cgl)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
# ##############################################################################
# Cbc
# ##############################################################################
if (BUILD_Cbc)
message(CHECK_START "Fetching Cbc")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
Cbc
GIT_REPOSITORY "https://github.com/Mizux/Cbc.git"
GIT_TAG "cmake/2.10.7"
)
FetchContent_MakeAvailable(Cbc)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
add_executable(${PROJECT_NAME} main.cpp)
if (BUILD_DEPS)
set(COINOR_DEPS Coin::CbcSolver Coin::OsiCbc Coin::ClpSolver Coin::OsiClp)
target_link_libraries(${PROJECT_NAME} PUBLIC ${COINOR_DEPS})
endif ()
博主的环境是Ubuntu 20.04 LTS。
首先打开CLion创建新工程:
下载:coin-or/CoinUtils-cmake-2.11.6 Osi-cmake-0.108.7 Clp-cmake-1.17.7 Cgl-cmake-0.60.5 Cbc-cmake-2.10.7源码
解压到:
修改CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(CbcProject)
set(PROJECT_ROOT_PATH "${PROJECT_SOURCE_DIR}")
# 可以把cbc打包后的.so文件放进lib目录下
set(LIBRARY_OUTPUT_PATH "${PROJECT_ROOT_PATH}/${OUTDIRS}/lib/")
# 可以把输出文件放进bin目录下
set(EXECUTABLE_OUTPUT_PATH "${PROJECT_ROOT_PATH}/${OUTDIRS}/bin/")
# Build Cbc dependencies.
set(BUILD_DEPS ON)
if (BUILD_DEPS)
set(BUILD_CoinUtils ON)
set(BUILD_Osi ON)
set(BUILD_Clp ON)
set(BUILD_Cgl ON)
set(BUILD_Cbc ON)
endif ()
include(FetchContent)
# 填充期间的日志输出可能非常冗长,使得配置阶段非常嘈杂。此缓存选项(ON默认情况下)隐藏所有填充输出,除非遇到错误。如果遇到挂起下载的问题,
# 暂时关闭此选项可能有助于诊断导致问题的内容群体。
set(FETCHCONTENT_QUIET OFF)
# 避免每次下载编译及其所有依赖
get_filename_component(_deps "../_deps" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
# 在大多数情况下,保存的详细信息没有指定与用于内部子构建、最终源和构建区域的目录相关的任何选项。通常最好将这些决定留给FetchContent 模块来代表项目处理。
# 缓存变量控制收集所有内容填充目录的FETCHCONTENT_BASE_DIR 点,但在大多数情况下,开发人员不需要更改它。
# 默认位置是${CMAKE_BINARY_DIR}/_deps,但如果开发人员更改此值,他们应该致力于保持路径短且刚好低于构建树的顶层,以避免在 Windows 上遇到路径长度问题。
set(FETCHCONTENT_BASE_DIR ${_deps})
# ##############################################################################
# Coinutils
# ##############################################################################
# Coin-OR does not support C++17/C++20 (use of 'register' storage class specifier)
set(CMAKE_CXX_STANDARD 11)
if (BUILD_CoinUtils)
message(CHECK_START "Fetching CoinUtils")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
CoinUtils
URL "${PROJECT_SOURCE_DIR}/deps/CoinUtils-cmake-2.11.6.zip"
)
FetchContent_MakeAvailable(CoinUtils)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
# ##############################################################################
# Osi
# ##############################################################################
if (BUILD_Osi)
message(CHECK_START "Fetching Osi")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
Osi
URL "${PROJECT_SOURCE_DIR}/deps/Osi-cmake-0.108.7.zip"
)
FetchContent_MakeAvailable(Osi)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
# ##############################################################################
# Clp
# ##############################################################################
if (BUILD_Clp)
message(CHECK_START "Fetching Clp")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
Clp
URL "${PROJECT_SOURCE_DIR}/deps/Clp-cmake-1.17.7.zip"
)
FetchContent_MakeAvailable(Clp)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
# ##############################################################################
# Cgl
# ##############################################################################
if (BUILD_Cgl)
message(CHECK_START "Fetching Cgl")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
Cgl
URL "${PROJECT_SOURCE_DIR}/deps/Cgl-cmake-0.60.5.zip"
)
FetchContent_MakeAvailable(Cgl)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
# ##############################################################################
# Cbc
# ##############################################################################
if (BUILD_Cbc)
message(CHECK_START "Fetching Cbc")
list(APPEND CMAKE_MESSAGE_INDENT " ")
FetchContent_Declare(
Cbc
URL "${PROJECT_SOURCE_DIR}/deps/Cbc-cmake-2.10.7.zip"
)
FetchContent_MakeAvailable(Cbc)
list(POP_BACK CMAKE_MESSAGE_INDENT)
message(CHECK_PASS "fetched")
endif ()
add_executable(${PROJECT_NAME} main.cpp)
if (BUILD_DEPS)
set(COINOR_DEPS Coin::CbcSolver Coin::OsiCbc Coin::ClpSolver Coin::OsiClp)
target_link_libraries(${PROJECT_NAME} PUBLIC ${COINOR_DEPS})
endif ()
main.cpp:
#include
#include "ClpSimplex.hpp"
#include "CoinBuild.hpp"
#include "CoinHelperFunctions.hpp"
#include "CoinModel.hpp"
#include "CoinTime.hpp"
#include
int main() {
// Empty model
ClpSimplex model;
// Objective - just nonzeros
int objIndex[] = {0, 2};
double objValue[] = {1.0, 4.0};
// Upper bounds - as dense vector
double upper[] = {2.0, COIN_DBL_MAX, 4.0};
// Create space for 3 columns
model.resize(0, 3);
// Fill in
int i;
// Virtuous way
// First objective
for (i = 0; i < 2; i++)
model.setObjectiveCoefficient(objIndex[i], objValue[i]);
// Now bounds (lower will be zero by default but do again)
for (i = 0; i < 3; i++) {
model.setColumnLower(i, 0.0);
model.setColumnUpper(i, upper[i]);
}
/*
We could also have done in non-virtuous way e.g.
double * objective = model.objective();
and then set directly
*/
// Faster to add rows all at once - but this is easier to show
// Now add row 1 as >= 2.0
int row1Index[] = {0, 2};
double row1Value[] = {1.0, 1.0};
model.addRow(2, row1Index, row1Value,
2.0, COIN_DBL_MAX);
// Now add row 2 as == 1.0
int row2Index[] = {0, 1, 2};
double row2Value[] = {1.0, -5.0, 1.0};
model.addRow(3, row2Index, row2Value,
1.0, 1.0);
// solve
model.dual();
/*
Adding one row at a time has a significant overhead so let's
try a more complicated but faster way
First time adding in 10000 rows one by one
*/
model.allSlackBasis();
ClpSimplex modelSave = model;
double time1 = CoinCpuTime();
int k;
for (k = 0; k < 10000; k++) {
int row2Index[] = {0, 1, 2};
double row2Value[] = {1.0, -5.0, 1.0};
model.addRow(3, row2Index, row2Value,
1.0, 1.0);
}
printf("Time for 10000 addRow is %g\n", CoinCpuTime() - time1);
model.dual();
model = modelSave;
// Now use build
CoinBuild buildObject;
time1 = CoinCpuTime();
for (k = 0; k < 10000; k++) {
int row2Index[] = {0, 1, 2};
double row2Value[] = {1.0, -5.0, 1.0};
buildObject.addRow(3, row2Index, row2Value,
1.0, 1.0);
}
model.addRows(buildObject);
printf("Time for 10000 addRow using CoinBuild is %g\n", CoinCpuTime() - time1);
model.dual();
model = modelSave;
int del[] = {0, 1, 2};
model.deleteRows(2, del);
// Now use build +-1
CoinBuild buildObject2;
time1 = CoinCpuTime();
for (k = 0; k < 10000; k++) {
int row2Index[] = {0, 1, 2};
double row2Value[] = {1.0, -1.0, 1.0};
buildObject2.addRow(3, row2Index, row2Value,
1.0, 1.0);
}
model.addRows(buildObject2, true);
printf("Time for 10000 addRow using CoinBuild+-1 is %g\n", CoinCpuTime() - time1);
model.dual();
model = modelSave;
model.deleteRows(2, del);
// Now use build +-1
CoinModel modelObject2;
time1 = CoinCpuTime();
for (k = 0; k < 10000; k++) {
int row2Index[] = {0, 1, 2};
double row2Value[] = {1.0, -1.0, 1.0};
modelObject2.addRow(3, row2Index, row2Value,
1.0, 1.0);
}
model.addRows(modelObject2, true);
printf("Time for 10000 addRow using CoinModel+-1 is %g\n", CoinCpuTime() - time1);
model.dual();
model = ClpSimplex();
// Now use build +-1
CoinModel modelObject3;
time1 = CoinCpuTime();
for (k = 0; k < 10000; k++) {
int row2Index[] = {0, 1, 2};
double row2Value[] = {1.0, -1.0, 1.0};
modelObject3.addRow(3, row2Index, row2Value,
1.0, 1.0);
}
model.loadProblem(modelObject3, true);
printf("Time for 10000 addRow using CoinModel load +-1 is %g\n", CoinCpuTime() - time1);
model.writeMps("xx.mps");
model.dual();
model = modelSave;
// Now use model
CoinModel modelObject;
time1 = CoinCpuTime();
for (k = 0; k < 10000; k++) {
int row2Index[] = {0, 1, 2};
double row2Value[] = {1.0, -5.0, 1.0};
modelObject.addRow(3, row2Index, row2Value,
1.0, 1.0);
}
model.addRows(modelObject);
printf("Time for 10000 addRow using CoinModel is %g\n", CoinCpuTime() - time1);
model.dual();
model.writeMps("b.mps");
// Method using least memory - but most complicated
time1 = CoinCpuTime();
// Assumes we know exact size of model and matrix
// Empty model
ClpSimplex model2;
{
// Create space for 3 columns and 10000 rows
int numberRows = 10000;
int numberColumns = 3;
// This is fully dense - but would not normally be so
int numberElements = numberRows * numberColumns;
// Arrays will be set to default values
model2.resize(numberRows, numberColumns);
double *elements = new double[numberElements];
CoinBigIndex *starts = new CoinBigIndex[numberColumns + 1];
int *rows = new int[numberElements];;
int *lengths = new int[numberColumns];
// Now fill in - totally unsafe but ....
// no need as defaults to 0.0 double * columnLower = model2.columnLower();
double *columnUpper = model2.columnUpper();
double *objective = model2.objective();
double *rowLower = model2.rowLower();
double *rowUpper = model2.rowUpper();
// Columns - objective was packed
for (k = 0; k < 2; k++) {
int iColumn = objIndex[k];
objective[iColumn] = objValue[k];
}
for (k = 0; k < numberColumns; k++)
columnUpper[k] = upper[k];
// Rows
for (k = 0; k < numberRows; k++) {
rowLower[k] = 1.0;
rowUpper[k] = 1.0;
}
// Now elements
double row2Value[] = {1.0, -5.0, 1.0};
CoinBigIndex put = 0;
for (k = 0; k < numberColumns; k++) {
starts[k] = put;
lengths[k] = numberRows;
double value = row2Value[k];
for (int i = 0; i < numberRows; i++) {
rows[put] = i;
elements[put] = value;
put++;
}
}
starts[numberColumns] = put;
// assign to matrix
CoinPackedMatrix *matrix = new CoinPackedMatrix(true, 0.0, 0.0);
matrix->assignMatrix(true, numberRows, numberColumns, numberElements,
elements, rows, starts, lengths);
ClpPackedMatrix *clpMatrix = new ClpPackedMatrix(matrix);
model2.replaceMatrix(clpMatrix, true);
printf("Time for 10000 addRow using hand written code is %g\n", CoinCpuTime() - time1);
// If matrix is really big could switch off creation of row copy
// model2.setSpecialOptions(256);
}
model2.dual();
model2.writeMps("a.mps");
// Print column solution
int numberColumns = model.numberColumns();
// Alternatively getColSolution()
double *columnPrimal = model.primalColumnSolution();
// Alternatively getReducedCost()
double *columnDual = model.dualColumnSolution();
// Alternatively getColLower()
double *columnLower = model.columnLower();
// Alternatively getColUpper()
double *columnUpper = model.columnUpper();
// Alternatively getObjCoefficients()
double *columnObjective = model.objective();
int iColumn;
std::cout << " Primal Dual Lower Upper Cost"
<< std::endl;
for (iColumn = 0; iColumn < numberColumns; iColumn++) {
double value;
std::cout << std::setw(6) << iColumn << " ";
value = columnPrimal[iColumn];
if (fabs(value) < 1.0e5)
std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value
<< std::resetiosflags(std::ios::fixed);
else
std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value
<< std::resetiosflags(std::ios::scientific);
value = columnDual[iColumn];
if (fabs(value) < 1.0e5)
std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value
<< std::resetiosflags(std::ios::fixed);
else
std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value
<< std::resetiosflags(std::ios::scientific);
value = columnLower[iColumn];
if (fabs(value) < 1.0e5)
std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value
<< std::resetiosflags(std::ios::fixed);
else
std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value
<< std::resetiosflags(std::ios::scientific);
value = columnUpper[iColumn];
if (fabs(value) < 1.0e5)
std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value
<< std::resetiosflags(std::ios::fixed);
else
std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value
<< std::resetiosflags(std::ios::scientific);
value = columnObjective[iColumn];
if (fabs(value) < 1.0e5)
std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value
<< std::resetiosflags(std::ios::fixed);
else
std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value
<< std::resetiosflags(std::ios::scientific);
std::cout << std::endl;
}
std::cout << "--------------------------------------" << std::endl;
return 0;
}