C++ Interface

This chapter walks through a simple C++ example to illustrate the use of the COPT C++ interface. In short words, the example creates an environment, builds a model, add variables and constraints, optimizes it, and then outputs the optimal objective value.

The example solves the following linear problem:

(2)\[\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}\]

Note that this is the same problem that was modelled and optimized in chapter of C Interface.

Example details

Below is the source code solving the above problem using COPT C++ interface.

Listing 3 lp_ex1.cpp
 2 * This file is part of the Cardinal Optimizer, all rights reserved.
 3 */
 4#include "coptcpp_pch.h"
 6using namespace std;
 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[])
25  try
26  {
27    Envr env;
28    Model model = env.CreateModel("lp_ex1");
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");
35    // Set objective
36    model.SetObjective(1.2 * x + 1.8 * y + 2.1 * z, COPT_MAXIMIZE);
38    // Add linear constraints using linear expression
39    model.AddConstr(1.5 * x + 1.2 * y + 1.8 * z <= 2.6, "R0");
41    Expr expr(x, 0.8);
42    expr.AddTerm(y, 0.6);
43    expr += 0.9 * z;
44    model.AddConstr(expr >= 1.2, "R1");
46    // Set parameters
47    model.SetDblParam(COPT_DBLPARAM_TIMELIMIT, 10);
49    // Solve problem
50    model.Solve();
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  }

Let’s now walk through the example, line by line, to understand how it achieves the desired result of optimizing the model. Note that the example must include header coptcpp_pch.h.

Creating environment and model

Essentially, any C++ application using Cardinal Optimizer should start with a COPT environment, where user could add one or more models. Note that each model encapsulates a problem and corresponding data.

Furthermore, to create multiple problems, one can load them one by one in the same model, besides the naive option of creating multiple models in the environment.

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

The above call instantiates a COPT environment and a model with name “lp_ex1”.

Adding variables

The next step in our example is to add variables to the model. Variables are added through AddVar() or AddVars() method on the model object. A variable is always associated with a particular model.

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

The first and second arguments to the AddVar() call are the variable lower and upper bounds, respectively. The third argument is the linear objective coefficient (zero here - we’ll set the objective later). The fourth argument is the variable type. Our variables are all continuous in this example. The final argument is the name of the variable.

The AddVar() method has been overloaded to accept several different argument lists. Please refer to C++ API Reference for further details.

The objective is built here using overloaded operators. The C++ API overloads the arithmetic operators to allow you to build linear expressions by COPT variables. The second argument indicates that the sense is maximization.

Adding constraints

The next step in the example is to add the linear constraints. As with variables, constraints are always associated with a specific model. They are created using AddConstr() or AddConstrs() methods on the model object.

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

The first constraint is to use overloaded arithmetic operators to build the linear expression. The comparison operators are also overloaded to make it easier to build constraints.

The second constraint is created by building a linear expression incrementally. That is, an expression can be built by constructor of a variable and its coefficient, by AddTerm() method, and by overloaded operators.

Setting parameters and attributes

The next step in the example is to set parameters and attributes of the problem before optimization.

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

The SetDblParam() call here with COPT_DBLPARAM_TIMELIMIT argument sets solver to optimize up to 10 seconds.

Solving problem

Now that the model has been built, the next step is to optimize it:

    // Solve problem

This routine performs the optimization and populates several internal model attributes (including the status of the optimization, the solution, etc.).

Outputting solution

After solving the problem, one can query the values of the attributes for various of purposes.

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

Spcifically, one can query the COPT_INTATTR_HASLPSOL attribute on the model to know whether we have optimal LP solution; query the COPT_DBLINFO_VALUE attribute of a variable to obtain its solution value; query the COPT_DBLATTR_LPOBJVAL attribute on the model to obtain the objective value for the current solution.

The names and types of all model, variable, and constraint attributes can be found in Attributes of C API reference.

Error handling

Errors in the COPT C++ interface are handled through the C++ exception mechanism. In the example, all COPT statements are enclosed inside a try block, and any associated errors would be caught by the catch block.

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

Build and Run

To build and run the example, users may refer to files under $COPT_HOME/examples/cpp. Specifically, We provide visual studio project on Windows, as well as makefile project on Linux and Mac platforms.

Windows Visual Studio project

For Windows platform, Visual Studio project is located at $COPT_HOME/examples/cpp/vsprojects. Double-clicking the project file lp_ex1.vcxproj will bring Visual Studio. Note that it requires Visual studio 2017 or 2019 installed on Windows 10 to build.

The Visual Studio project has dependency on COPT cpp shared libary copt_cpp.dll, which is refered in project file, along with its import library copt_cpp.lib. The required headers are copt.h, coptcpp.h and coptcpp.idl.h, which declare COPT constants, interfaces and methods exported from copt_cpp.dll. In addition, the example provides class header files under $COPT_HOME/include/coptcpp_inc, which wraps COPT cpp interfaces and redefines overloaded operators.

In simple terms, users only need to include the header file coptcpp_pch.h as shown in the example, configure additional dependencies as copt_cpp.lib, and set the directory of additional link libraries as $(COPT_HOME)/lib, and make sure that the dynamic library copt_cpp.dll has been installed in the appropriate path, and can be loaded at runtime.

To run the example, users should have COPT installed. Specifically, it requires COPT cpp libary, copt_cpp.dll, and valid license files to run. Please refer to Install Guide for Cardinal Optimizer for further details.

It is IMPORTANT to notice that COPT cpp shared library, copt_cpp.dll, is not compatible with gcc compiler on Windows. That is, if you are running this example on Windows, the executable compiled by gcc will not work as expected. This is because GCC is not compabile with Windows SDK. On the other hand, both Clang and Intel compiler work fine, as well as MSVC.

Makefile project

We provide Makefile to build the example for Linux and MacOS platforms. Please make sure tools, gcc and make, are already installed on the platforms. To build the example, change directory to $COPT_HOME/examples/cpp and execute command 'make' on unix terminal.

The project has dependency on COPT cpp shared libary, that is, libcopt_cpp.so on Linux platform and libcopt_cpp.dylib on MacOS. Similar to Windows VS project, user should refer to coptcpp_pch.h under $COPT_HOME/include/coptcpp_inc' to include all necessary headers, as shown in the example.

To run the example, users should have COPT installed. Specifically, it requires COPT cpp libary, libcopt_cpp.so on Linux, libcopt_cpp.dylib on MacOS, and valid license files to run. Alternatively, user might set LD_LIBRARY_PATH and COPT_LICENSE_DIR properly to work around. Please refer to Install Guide for Cardinal Optimizer for further details.