Cbc(Coin或branch and cut)是一个开源的混合整数线性规划求解器,用C++编写。它可以用作可调用库或使用独立的可执行文件。它可以通过各种建模系统、包等以多种方式使用。Clp(Coin或线性规划)是一个开源线性规划求解器。它主要是用来作为一个可调用的库,但也有一个基本的、独立的可执行版本。本文将介绍如何用CLion(CMake)构建工程并能debug源码。

相关教程

相关文献

CMake工程构建(能访问GitHub方法)

博主的环境是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 ()

然后你会看到:
在这里插入图片描述

CMake工程构建(不能访问GitHub方法)

博主的环境是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 ()

CLion debug源码

main.cpp:

#include <iostream>
#include "ClpSimplex.hpp"
#include "CoinBuild.hpp"
#include "CoinHelperFunctions.hpp"
#include "CoinModel.hpp"
#include "CoinTime.hpp"
#include <iomanip>

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

最后尽情地开始Debug吧!!!(上一张成果图):
在这里插入图片描述

CMake打包Cbc.so:

其实通过上述步骤编译后你就会发现多了一个lib目录:
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐