C Interface

This chapter illustrate the use of C interface of Cardinal Optimizer through a simple C example. The problem to solve is shown in Eq. 1:

(1)\[\begin{split}\text{Maximize: } & \\ & 1.2 x + 1.8 y + 2.1 z \\ \text{Subject to: } & \\ & 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{Bounds: } & \\ & 0.1 \leq x \leq 0.6 \\ & 0.2 \leq y \leq 1.5 \\ & 0.3 \leq z \leq 2.8\end{split}\]

Example details

The source code for solving the above problem using C API of Cardinal Optimizer is shown in Listing 2:

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

We will explain how to use the C API step by step based on code above, please refer to C API Reference for detailed usage of C API.

Creating the environment

To solve any problem with Cardinal Optimizer, users are required to create optimization environment first, and check if it was created successfully by checking the return value:

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

If non-zero value was returned, it will jump to error reporting code block for detailed information and exit.

Creating the problem

Once the optimization environment was successfully created, users will need to create problem then, the problem is the main structure that consists of variables, constraints etc. Users need to check the return value too.

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

If non-zero value was returned, it will jump to error reporting code block for detailed information and exit.

Adding variables

For linear problem, C API allows users to specify costs of variables in objective, and lower and upper bound simultaneously. For the problem above, we use code below to create variables:

  /*
   * 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;

The argument ncol specify that the number of variables to create is 3, while the argument colcost, collb and colub specify the costs in objective, lower and upper bound respectively. Regarding other arguments of COPT_AddCols for specifing variables types and names, we just pass NULL to them, which means all variables are continuous and names are automatically generated by the Cardinal Optimizer. For the remaining arguments, we passed NULL too for further action.

Similarly, if non-zero value was returned, it will jump to error reporting code block for detailed information and exit.

Adding constraints

The next step to do after adding variables successfully is to add constraints to problem. For the problem above, the implementation is shown below:

  /*
   * 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;

The argument nrow specifies that the number of constraints to create is 2, while argument rowbeg, rowcnt, rowind and rowelem define the coefficient matrix in CSR format. The argument rowsen represents the sense of constraints, while argument rowrhs specifies the right hand side of constraints. For remaining arguments in COPT_AddRows, we simply pass NULL to them.

If the return value is non-zero, then it jump to error reporting code block for detailed information and exit.

Setting parameters and attributes

Users are allowed to set parameters and attributes of problem before solving. For example, to set the time limit to 10 seconds, and to set the optimization direction to maximization, the code is shown below:

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

If non-zero value was returned, then it will jump to error reporting code block for detailed information and exit.

Solve the problem

The next step to do is to solve the problem using code beblow:

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

Non-zero return value indicates unsuccessful solve and jump to error reporting code block for detailed information and exit.

Analyze the solution

Once the solving process was finished, check the solution status first. If it claimed to have found the optimal solution, then use code below to obtain objective value, variables’ solution and basis status:

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

Write problem and solution

Users are allowed not only to save the problem to solve to standard MPS file, but also the solution, basis status and modified parameters to files:

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

Error handling

The error handling block report error code and message by checking if the return value was non-zero:

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

    COPT_GetRetcodeMsg(errcode, errmsg, COPT_BUFFSIZE);
    printf("ERROR %d: %s\n", errcode, errmsg);

    return 0;
  }

Delete environment and problem

Before exiting, delete problem and environment respectively:

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

  COPT_DeleteEnv(&env);

Build and run

To ease the work for running the example for users on different operating systems, we provide Visual Studio project and Makefile for Windows, Linux and MacOS respectively, details are shown below.

Windows

For users on Windows platform, we provide Visual Studio project, all users are required to install Visual Studio 2017 beforehand. Assume that the installation directory is: '<instdir>', users that install the Cardinal Optimizer with executable installer can change directory to '<instdir>\examples\c\vsprojects' and open the Visual Studio project lp_ex1.vcxproj to build the solution. Users that install the Cardinal Optimizer using ZIP-format archive should make sure that all required environment variables are set correctly, see Install Guide for Cardinal Optimizer for details.

Linux and MacOS

For users on Linux or MacOS, we provide Makefile to build the example. Please install GCC toolchain for Linux and Clang toolchain for MacOS, together with the make utility beforehand. What’s more, users should make sure also that all required environment variables are set correctly, see Install Guide for Cardinal Optimizer for details. Let’s assume that the installation directory of Cardinal Optimizer is '<instdir>', then users need to change directory to '<instdir>\examples\c' and execute command make in terminal.