C++接口

本章通过一个简单的示例演示如何使用杉数求解器的 C++ 接口。简单来说,本例演示 如何创建环境和建立模型,添加变量和约束,然后求解的过程。最后求解后如何获得 最优解的步骤也包括在内。

待求解的线性问题数学公式如下所示:

(2)\[\begin{split}\text{最大化:} & \\ & 1.2 x + 1.8 y + 2.1 z \\ \text{约束:} & \\ & 1.5 x + 1.2 y + 1.8 z \leq 2.6 \\ & 0.8 x + 0.6 y + 0.9 z \geq 1.2 \\ \text{变量范围:} & \\ & 0.1 \leq x \leq 0.6 \\ & 0.2 \leq y \leq 1.5 \\ & 0.3 \leq z \leq 2.8 \\\end{split}\]

注意到上述问题其实和 C接口 里通过建模和求解来演示的问题是同一个。

示例解析

使用杉数求解器的 C++ 接口求解与分析上述问题的代码如下。

代码 3 lp_ex1.cpp
 1/*
 2 * This file is part of the Cardinal Optimizer, all rights reserved.
 3 */
 4#include "coptcpp_pch.h"
 5
 6using namespace std;
 7
 8/*
 9 * This example solves the following LP model:
10 *
11 *  Maximize:
12 *    1.2 x + 1.8 y + 2.1 z
13 *
14 *  Subject to:
15 *    R0: 1.5 x + 1.2 y + 1.8 z <= 2.6
16 *    R1: 0.8 x + 0.6 y + 0.9 z >= 1.2
17 *
18 *  Where:
19 *    0.1 <= x <= 0.6
20 *    0.2 <= y <= 1.5
21 *    0.3 <= z <= 2.8
22 */
23int main(int argc, char* argv[])
24{
25  try
26  {
27    Envr env;
28    Model model = env.CreateModel("lp_ex1");
29
30    // Add variables
31    Var x = model.AddVar(0.1, 0.6, 0.0, COPT_CONTINUOUS, "x");
32    Var y = model.AddVar(0.2, 1.5, 0.0, COPT_CONTINUOUS, "y");
33    Var z = model.AddVar(0.3, 2.8, 0.0, COPT_CONTINUOUS, "z");
34
35    // Set objective
36    model.SetObjective(1.2 * x + 1.8 * y + 2.1 * z, COPT_MAXIMIZE);
37
38    // Add linear constraints using linear expression
39    model.AddConstr(1.5 * x + 1.2 * y + 1.8 * z <= 2.6, "R0");
40
41    Expr expr(x, 0.8);
42    expr.AddTerm(y, 0.6);
43    expr += 0.9 * z;
44    model.AddConstr(expr >= 1.2, "R1");
45
46    // Set parameters
47    model.SetDblParam(COPT_DBLPARAM_TIMELIMIT, 10);
48
49    // Solve problem
50    model.Solve();
51
52    // Output solution
53    if (model.GetIntAttr(COPT_INTATTR_HASLPSOL) != 0)
54    {
55      cout << "\nFound optimal solution:" << endl;
56      VarArray vars = model.GetVars();
57      for (int i = 0; i < vars.Size(); i++)
58      {
59        Var var = vars.GetVar(i);
60        cout << "  " << var.GetName() << " = " << var.Get(COPT_DBLINFO_VALUE) << endl;
61      }
62      cout << "Obj = " << model.GetDblAttr(COPT_DBLATTR_LPOBJVAL) << endl;
63    }
64  }
65  catch (CoptException e)
66  {
67    cout << "Error Code = " << e.GetCode() << endl;
68    cout << e.what() << endl;
69  }
70  catch (...)
71  {
72    cout << "Unknown exception occurs!";
73  }
74}

接下来我们将基于上述代码分步骤讲解求解与分析过程。

导入COPT的C++头文件

使用杉数求解器的 C++ 接口,需要首先包括 COPT 的 C++ 头文件 coptcpp_pch.h

#include "coptcpp_pch.h"

创建求解环境和建模

对于任意使用杉数求解器 C++ 接口的应用程序,求解步骤的第一步是创建求解环境。从求解环境 出发,用户可以创建一个或者多个模型。注意到每个模型都对应了一个实际的求解问题以及 相关数据,比如参数设置等。

如果用户需要求解多个问题,用户即可以在同一个模型里对多个问题逐个依次求解,也可以在同一 求解环境里创建多个模型来求解。

    Envr env;
    Model model = env.CreateModel("lp_ex1");

上述代码对 C++ 求解环境实例化,并创建了一个名为 “lp_ex1” 的模型对象。

添加变量

接下来,我们演示在模型里添加多个变量。杉数求解器提供包括AddVar()和AddVars()在内的 多种途径来添加变量。注意到变量不是独立存在,总是和某个模型关联的。

    // Add variables
    Var x = model.AddVar(0.1, 0.6, 0.0, COPT_CONTINUOUS, "x");
    Var y = model.AddVar(0.2, 1.5, 0.0, COPT_CONTINUOUS, "y");
    Var z = model.AddVar(0.3, 2.8, 0.0, COPT_CONTINUOUS, "z");

上述代码中AddVar()的第一和第二个参数分别是指变量的下界和上界;第三个参数是指目标函数中的 系数(这里暂设为0,我们会在 SetObjective() 真正设值); 第四个参数是指变量的类型。例子里所有变量的类型都是连续形的。最后一个参数是指变量名。

为了适应不同的调用需求,杉数求解器实现了AddVar()的不同形参的几种重载。细节请参看 C++ API 参考手册

目标函数的创建是调用了 SetObjective() ,并使用了运算符重载。因为 C++ 允许重载算术运算符, 这样建立线性表达式的方式比较直观。这里第二个参数是设置优化目标为最大化。

添加约束

接下来,我们演示在模型里添加多个约束。和变量一样,约束也总是和某个模型关联的。 杉数求解器提供包括AddConstr()和AddConstrs()在内的多种途径来添加约束。

    // Add linear constraints using linear expression
    model.AddConstr(1.5 * x + 1.2 * y + 1.8 * z <= 2.6, "R0");

    Expr expr(x, 0.8);
    expr.AddTerm(y, 0.6);
    expr += 0.9 * z;
    model.AddConstr(expr >= 1.2, "R1");

第一个约束的创建比较直观,因为其线性表达式是通过重载算术运算符和比较运算符的方式生成的。

第二个约束的创建则是通过先建立相关线性表达式的对象,然后再通过AddTerm()添加变量和对应 系数的方式来逐步建立最终的线性表达式。最后约束的生成同样使用了重载的比较运算符。

设置参数和属性

在求解问题之前,还可以通过 SetDblParam() 等方法设置模型的参数和属性。

    // Set parameters
    model.SetDblParam(COPT_DBLPARAM_TIMELIMIT, 10);

这里,调用 SetDblParam() 来设置 COPT_DBLPARAM_TIMELIMIT 参数值,使得求解器在最多执行10秒 后超时退出。

求解问题

到此为止,我们已经建好了模型。接下来,可以求解问题。

    // Solve problem
    model.Solve();

这一步之后,问题已经被求解,并在内部保存了结果,包括求解状态,最优解等属性值。

输出结果

在问题被求解之后,可以通过查询不同的属性值来实现不同的目的。

    // Output solution
    if (model.GetIntAttr(COPT_INTATTR_HASLPSOL) != 0)
    {
      cout << "\nFound optimal solution:" << endl;
      VarArray vars = model.GetVars();
      for (int i = 0; i < vars.Size(); i++)
      {
        Var var = vars.GetVar(i);
        cout << "  " << var.GetName() << " = " << var.Get(COPT_DBLINFO_VALUE) << endl;
      }
      cout << "Obj = " << model.GetDblAttr(COPT_DBLATTR_LPOBJVAL) << endl;
    }

上述代码中,我们首先查询模型的属性值 COPT_INTATTR_HASLPSOL 来知道是否生成了LP最优解; 再查询变量的属性值 COPT_DBLINFO_VALUE 来获得这个变量的值;然后查询模型的属性值 COPT_DBLATTR_LPOBJVAL 来获得目标函数的最优值。

关于模型、变量、约束的属性名和类型,请参看 C API 参考手册里的章节 属性

错误处理

杉数求解器 C++ 接口的错误处理使用 C++ 自身的异常处理机制。例子中,整个求解过程都嵌入在 try块里,中间的任意求解错误都会被catch块捕获并显示。

  catch (CoptException e)
  {
    cout << "Error Code = " << e.GetCode() << endl;
    cout << e.what() << endl;
  }
  catch (...)
  {
    cout << "Unknown exception occurs!";
  }

编译与运行

本章节演示的例子,我们已经放入在杉数求解器安装包里,方便用户尝试编译并运行。具体请参看 安装路径下的文件夹 $COPT_HOME/examples/cpp 。 这个文件夹包括了Windows平台下 的 Visual Studio 项目文件,或者 Linux/Mac 平台下的 Makefile 文件。

Windows

1. Visual Studio 项目

Windows 平台下的 Visual Studio 项目文件位于 $COPT_HOME/examples/cpp/vsprojects 。 如果平台已经安装了 Visual Studio 2017 或者更高版本,双击这个项目文件会启动 Visual Studio 来编译和运行。

具体来说,编译例子依赖于杉数求解器 C++ 动态库 copt_cpp.dll ,以及一些相关的头文件 copt.h, coptcpp.h and coptcpp.idl.h 。这些头文件声明了从 C++ 动态库 copt_cpp.dll 导出的函数、接口、参数和属性的常量。进一步,为了方便使用,杉数求解器 C++ 接口还提供了每个导出类的重包装头文件并重载了相关运算符,位于 $COPT_HOME/include/coptcpp_inc

简单来说,用户只需要像例子中所示一样包括头文件 coptcpp_pch.h、配置附加依赖项为 copt_cpp.lib 、设置附加链接库的目录为 $(COPT_HOME)/lib ,并确保动态库 copt_cpp.dll 已经安装在合适的路径下,可以在运行中被加载即可。

除此之外,Windows 系统下 Clang、Intel和上述的MSVC编译器一样,都支持编译COPT的C++例子。

2. MinGW-w64编译器

对于Windows系统,COPT还支持MinGW-w64编译器。对于MinGW编译器的两种版本,我们分别提供了支持MSVCRT的动态库 copt_mmingwcpp.dll 和支持UCRT的动态库 copt_umingwcpp.dll ,请参考 $COPT_HOME/examples/cpp 路径下 Makefile 文件中的相应说明使用。

我们建议默认使用MSVCRT的动态库,如果有问题再尝试UCRT。

Linux和MacOS

在 Linux 和 Mac 平台,我们同样提供了 Makefile 文件方便用户编译和运行 C++ 例子。 假设用户已经在所在平台安装了开发工具 gccmake 。编译 C++例子最简单的方式是 先在终端下进入安装路径下的 C++ 例子文件夹 $COPT_HOME/examples/cpp ,然后执行命令 'make'

类似地,编译例子依赖于杉数求解器的 C++ 动态库,分别是 Linux 平台下的 libcopt_cpp.so , Mac 平台下的 libcopt_cpp.dylib ,还需要包括头文件 $COPT_HOME/include/coptcpp_inc/coptcpp_pch.h

最后,用户只要正确安装了杉数求解器安装包(设置好了动态库路径),并配置了有效的授权文档,就可以 在各大平台下正确执行这个 C++ 例子。安装和授权细节请参考 安装说明