C接口

本章通过一个简单的示例演示如何使用杉数求解器的C接口,待求解的问题数学形式如 公式 1 所示:

(1)\[\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接口求解与分析上述问题的代码见 代码 2

代码 2 lp_ex1.c
  1/*
  2 * This file is part of the Cardinal Optimizer, all rights reserved.
  3 */
  4
  5/*
  6 * The problem to solve:
  7 *
  8 *  Maximize:
  9 *    1.2 x + 1.8 y + 2.1 z
 10 *
 11 *  Subject to:
 12 *    1.5 x + 1.2 y + 1.8 z <= 2.6
 13 *    0.8 x + 0.6 y + 0.9 z >= 1.2
 14 *
 15 *  where:
 16 *    0.1 <= x <= 0.6
 17 *    0.2 <= y <= 1.5
 18 *    0.3 <= z <= 2.8
 19 */
 20
 21#include "copt.h"
 22
 23#include <stdio.h>
 24#include <stdlib.h>
 25
 26int main(int argc, char* argv[])
 27{
 28  int errcode = 0;
 29
 30  copt_env* env = NULL;
 31  copt_prob* prob = NULL;
 32
 33  // Create COPT environment
 34  errcode = COPT_CreateEnv(&env);
 35  if (errcode)
 36    goto COPT_EXIT;
 37
 38  // Create COPT problem
 39  errcode = COPT_CreateProb(env, &prob);
 40  if (errcode)
 41    goto COPT_EXIT;
 42  
 43  /*
 44   * Add variables
 45   *
 46   *   obj: 1.2 C0 + 1.8 C1 + 2.1 C2
 47   *
 48   *   var:
 49   *     0.1 <= C0 <= 0.6
 50   *     0.2 <= C1 <= 1.5
 51   *     0.3 <= C2 <= 2.8
 52   *
 53   */
 54  int ncol = 3;
 55  double colcost[] = {1.2, 1.8, 2.1};
 56  double collb[] = {0.1, 0.2, 0.3};
 57  double colub[] = {0.6, 1.5, 1.8};
 58
 59  errcode = COPT_AddCols(prob, ncol, colcost, NULL, NULL, NULL, NULL, NULL, collb, colub, NULL);
 60  if (errcode)
 61    goto COPT_EXIT;
 62
 63  /*
 64   * Add constraints
 65   *
 66   *   r0: 1.5 C0 + 1.2 C1 + 1.8 C2 <= 2.6
 67   *   r1: 0.8 C0 + 0.6 C1 + 0.9 C2 >= 1.2
 68   */
 69  int nrow = 2;
 70  int rowbeg[] = {0, 3};
 71  int rowcnt[] = {3, 3};
 72  int rowind[] = {0, 1, 2, 0, 1, 2};
 73  double rowelem[] = {1.5, 1.2, 1.8, 0.8, 0.6, 0.9};
 74  char rowsen[] = {COPT_LESS_EQUAL, COPT_GREATER_EQUAL};
 75  double rowrhs[] = {2.6, 1.2};
 76
 77  errcode = COPT_AddRows(prob, nrow, rowbeg, rowcnt, rowind, rowelem, rowsen, rowrhs, NULL, NULL);
 78  if (errcode)
 79    goto COPT_EXIT;
 80
 81  // Set parameters and attributes
 82  errcode = COPT_SetDblParam(prob, COPT_DBLPARAM_TIMELIMIT, 10);
 83  if (errcode)
 84    goto COPT_EXIT;
 85  errcode = COPT_SetObjSense(prob, COPT_MAXIMIZE);
 86  if (errcode)
 87    goto COPT_EXIT;
 88
 89  // Solve problem
 90  errcode = COPT_SolveLp(prob);
 91  if (errcode)
 92    goto COPT_EXIT;
 93
 94  // Analyze solution
 95  int lpstat = COPT_LPSTATUS_UNSTARTED;
 96  double lpobjval;
 97  double* lpsol = NULL;
 98  int* colstat = NULL;
 99
100  errcode = COPT_GetIntAttr(prob, COPT_INTATTR_LPSTATUS, &lpstat);
101  if (errcode)
102    goto COPT_EXIT;
103
104  if (lpstat == COPT_LPSTATUS_OPTIMAL)
105  {
106    lpsol = (double*)malloc(ncol * sizeof(double));
107    colstat = (int*)malloc(ncol * sizeof(int));
108
109    errcode = COPT_GetLpSolution(prob, lpsol, NULL, NULL, NULL);
110    if (errcode)
111      goto COPT_EXIT;
112
113    errcode = COPT_GetBasis(prob, colstat, NULL);
114    if (errcode)
115      goto COPT_EXIT;
116
117    errcode = COPT_GetDblAttr(prob, COPT_DBLATTR_LPOBJVAL, &lpobjval);
118    if (errcode)
119      goto COPT_EXIT;
120
121    printf("\nObjective value: %.6f\n", lpobjval);
122
123    printf("Variable solution: \n");
124    for (int i = 0; i < ncol; ++i)
125      printf("  x[%d] = %.6f\n", i, lpsol[i]);
126
127    printf("Variable basis status: \n");
128    for (int i = 0; i < ncol; ++i)
129      printf("  x[%d]: %d\n", i, colstat[i]);
130
131    free(lpsol);
132    free(colstat);
133  }
134
135  // Write problem, solution and modified parameters to files
136  errcode = COPT_WriteMps(prob, "lp_ex1.mps");
137  if (errcode)
138    goto COPT_EXIT;
139  errcode = COPT_WriteBasis(prob, "lp_ex1.bas");
140  if (errcode)
141    goto COPT_EXIT;
142  errcode = COPT_WriteSol(prob, "lp_ex1.sol");
143  if (errcode)
144    goto COPT_EXIT;
145  errcode = COPT_WriteParam(prob, "lp_ex1.par");
146  if (errcode)
147    goto COPT_EXIT;
148
149  // Error handling
150COPT_EXIT:
151  if (errcode)
152  {
153    char errmsg[COPT_BUFFSIZE];
154
155    COPT_GetRetcodeMsg(errcode, errmsg, COPT_BUFFSIZE);
156    printf("ERROR %d: %s\n", errcode, errmsg);
157
158    return 0;
159  }
160
161  // Delete problem and environment
162  COPT_DeleteProb(&prob);
163
164  COPT_DeleteEnv(&env);
165
166  return 0;
167}

接下来我们将基于上述代码分步骤讲解求解与分析过程,详细的C接口使用说明请用户查阅 C API 参考手册

创建环境

对于任意求解任务,杉数求解器要求首先创建求解环境,并通过检查返回值判断环境是否创建成功。

  // Create COPT environment
  errcode = COPT_CreateEnv(&env);
  if (errcode)
    goto COPT_EXIT;

若返回值非0,则跳转至错误处理步骤并反馈相应的错误代号和错误信息,然后退出程序。

创建问题

创建求解环境成功后,用户需要创建问题,问题中将包括待求解的变量、约束等信息,通过检查返回值判断 问题是否创建成功。

  // Create COPT problem
  errcode = COPT_CreateProb(env, &prob);
  if (errcode)
    goto COPT_EXIT;

若返回值非0,则跳转至错误处理步骤并反馈相应的错误代号和错误信息,然后退出程序。

添加变量

对于线性模型,在创建变量时允许同时指定变量在目标函数中的系数、变量上下界等信息。对于本章节的示例 问题,通过下述代码创建待求解变量:

  /*
   * Add variables
   *
   *   obj: 1.2 C0 + 1.8 C1 + 2.1 C2
   *
   *   var:
   *     0.1 <= C0 <= 0.6
   *     0.2 <= C1 <= 1.5
   *     0.3 <= C2 <= 2.8
   *
   */
  int ncol = 3;
  double colcost[] = {1.2, 1.8, 2.1};
  double collb[] = {0.1, 0.2, 0.3};
  double colub[] = {0.6, 1.5, 1.8};

  errcode = COPT_AddCols(prob, ncol, colcost, NULL, NULL, NULL, NULL, NULL, collb, colub, NULL);
  if (errcode)
    goto COPT_EXIT;

参数 ncol 指定了待创建变量的数目为3,参数 colcostcollbcolub 分别 指定了待添加变量在目标函数中的系数、上下界信息。对于函数 COPT_AddCols 的其它参数如: 变量类型、变量名称均传入 NULL 表示默认为连续变量且变量名称由求解器自动生成。对于约束矩阵 系数相关参数,传入 NULL 表示留待后续其它函数调用进行添加。

类似地,若返回值非0,则跳转至错误处理步骤并反馈相应的错误代码和错误信息,然后退出程序。

添加约束

添加变量成功后,进一步添加作用于变量的约束条件。对于本章节的示例问题,其代码实现如下:

  /*
   * Add constraints
   *
   *   r0: 1.5 C0 + 1.2 C1 + 1.8 C2 <= 2.6
   *   r1: 0.8 C0 + 0.6 C1 + 0.9 C2 >= 1.2
   */
  int nrow = 2;
  int rowbeg[] = {0, 3};
  int rowcnt[] = {3, 3};
  int rowind[] = {0, 1, 2, 0, 1, 2};
  double rowelem[] = {1.5, 1.2, 1.8, 0.8, 0.6, 0.9};
  char rowsen[] = {COPT_LESS_EQUAL, COPT_GREATER_EQUAL};
  double rowrhs[] = {2.6, 1.2};

  errcode = COPT_AddRows(prob, nrow, rowbeg, rowcnt, rowind, rowelem, rowsen, rowrhs, NULL, NULL);
  if (errcode)
    goto COPT_EXIT;

参数 nrow 指定了待创建的约束数目为2,参数 rowbegrowcntrowindrowelem 分别表示以CSR格式表示的约束系数对应的稀疏矩阵。参数 rowsen 表示约束的方向, 参数 rowrhs 表示约束的右端项。对于函数 COPT_AddRows 中的其它参数均传入 NULL

同样,若返回值为0,则跳转至错误处理步骤并进行后续处理。

设置参数和属性

用户可以在求解问题前设置求解参数和问题相关属性,如设置求解时间限制为10秒,设置问题求解方向为 最大化,代码如下:

  // Set parameters and attributes
  errcode = COPT_SetDblParam(prob, COPT_DBLPARAM_TIMELIMIT, 10);
  if (errcode)
    goto COPT_EXIT;
  errcode = COPT_SetObjSense(prob, COPT_MAXIMIZE);
  if (errcode)
    goto COPT_EXIT;

若设置失败,则返回非0值,并跳转至错误处理步骤进行后续处理。

求解问题

当前文所述的步骤都完成后,调用下述函数求解问题:

  // Solve problem
  errcode = COPT_SolveLp(prob);
  if (errcode)
    goto COPT_EXIT;

若返回值非0,则跳转至错误处理步骤进行后续处理。

分析结果

求解完成后,首先获取问题求解状态,若状态为找到了最优解,则进一步获取目标函数值、各变量的取值及其 基状态信息,代码实现如下:

  // Analyze solution
  int lpstat = COPT_LPSTATUS_UNSTARTED;
  double lpobjval;
  double* lpsol = NULL;
  int* colstat = NULL;

  errcode = COPT_GetIntAttr(prob, COPT_INTATTR_LPSTATUS, &lpstat);
  if (errcode)
    goto COPT_EXIT;

  if (lpstat == COPT_LPSTATUS_OPTIMAL)
  {
    lpsol = (double*)malloc(ncol * sizeof(double));
    colstat = (int*)malloc(ncol * sizeof(int));

    errcode = COPT_GetLpSolution(prob, lpsol, NULL, NULL, NULL);
    if (errcode)
      goto COPT_EXIT;

    errcode = COPT_GetBasis(prob, colstat, NULL);
    if (errcode)
      goto COPT_EXIT;

    errcode = COPT_GetDblAttr(prob, COPT_DBLATTR_LPOBJVAL, &lpobjval);
    if (errcode)
      goto COPT_EXIT;

    printf("\nObjective value: %.6f\n", lpobjval);

    printf("Variable solution: \n");
    for (int i = 0; i < ncol; ++i)
      printf("  x[%d] = %.6f\n", i, lpsol[i]);

    printf("Variable basis status: \n");
    for (int i = 0; i < ncol; ++i)
      printf("  x[%d]: %d\n", i, colstat[i]);

    free(lpsol);
    free(colstat);
  }

问题与结果文件输出

用户可以将当前求解的问题保存为标准的MPS模型文件,以及输出变量结果文件、基状态信息文件和修改过的 参数文件,代码实现如下:

  // Write problem, solution and modified parameters to files
  errcode = COPT_WriteMps(prob, "lp_ex1.mps");
  if (errcode)
    goto COPT_EXIT;
  errcode = COPT_WriteBasis(prob, "lp_ex1.bas");
  if (errcode)
    goto COPT_EXIT;
  errcode = COPT_WriteSol(prob, "lp_ex1.sol");
  if (errcode)
    goto COPT_EXIT;
  errcode = COPT_WriteParam(prob, "lp_ex1.par");
  if (errcode)
    goto COPT_EXIT;

错误处理

若在调用C接口的过程中返回值非0,则跳转至该步骤,输出错误代号与错误信息:

    goto COPT_EXIT;
  errcode = COPT_WriteParam(prob, "lp_ex1.par");
  if (errcode)
    goto COPT_EXIT;

  // Error handling
COPT_EXIT:
  if (errcode)
  {
    char errmsg[COPT_BUFFSIZE];

删除环境与问题

求解完成后,分别删除问题与环境:

  // Delete problem and environment
  COPT_DeleteProb(&prob);

  COPT_DeleteEnv(&env);

编译与运行

为了便于用户在不同的操作系统下编译与运行示例程序,我们在Windows、Linux和MacOS平台分别提供了 Visual Studio工程和Makefile文件,具体说明如下。

Windows

对于Windows平台的用户,我们提供了Visual Studio工程文件,请用户确保计算机中已下载与安装 Visual Studio 2017。假定杉数求解器的安装路径为:'<instdir>' ,对于下载可执行安装程序 进行安装的用户,则可以直接打开路径 '<instdir>\examples\c\vsprojects' 下的 Visual Studio工程文件 lp_ex1.vcxproj 生成解决方案即可。对于下载ZIP包安装的用户,请确保 已正确配置相关环境变量,详情见 杉数求解器安装说明

Linux和MacOS

对于Linux和Mac平台的用户,我们提供了Makefile文件,请用户确保计算机中已分别安装GCC和Clang 编译器工具链,以及make工具,并正确配置杉数求解器的必要的环境变量,详情见 杉数求解器安装说明 。假定杉数求解器的安装路径为:<instdir> , 则请用户切换到路径 '<instdir>\examples\c' 下,并在终端输入命令 make 即可编译成功。