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:

(3)\[\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 4 lp_ex1.cs
 1/*
 2 * This file is part of the Cardinal Optimizer, all rights reserved.
 3 */
 4using Copt;
 5using System;
 6
 7/*
 8 * This C# example solves the following LP model:
 9 *
10 * 
11 * Maximize:
12 *  1.2 x + 1.8 y + 2.1 z
13 *
14 * Subject to:
15 *  1.5 x + 1.2 y + 1.8 z <= 2.6
16 *  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 */
23public class lp_ex1
24{
25  public static void Main()
26  {
27    try
28    {
29      Envr env = new Envr();
30      Model model = env.CreateModel("lp_ex1");
31
32      /* 
33       * Add variables x, y, z
34       *
35       * obj: 1.2 x + 1.8 y + 2.1 z
36       *
37       * var:
38       *  0.1 <= x <= 0.6
39       *  0.2 <= y <= 1.5
40       *  0.3 <= z <= 2.8
41       */
42      Var x = model.AddVar(0.1, 0.6, 0.0, Copt.Consts.CONTINUOUS, "x");
43      Var y = model.AddVar(0.2, 1.5, 0.0, Copt.Consts.CONTINUOUS, "y");
44      Var z = model.AddVar(0.3, 2.8, 0.0, Copt.Consts.CONTINUOUS, "z");
45
46      model.SetObjective(1.2 * x + 1.8 * y + 2.1 * z, Copt.Consts.MAXIMIZE);
47
48      /*
49       * Add two constraints using linear expression
50       *
51       * r0: 1.5 x + 1.2 y + 1.8 z <= 2.6
52       * r1: 0.8 x + 0.6 y + 0.9 z >= 1.2
53       */
54      model.AddConstr(1.5 * x + 1.2 * y + 1.8 * z <= 2.6, "r0");
55
56      Expr expr = new Expr(x, 0.8);
57      expr.AddTerm(y, 0.6);
58      expr += 0.9 * z;
59      model.AddConstr(expr >= 1.2, "r1");
60
61      // Set parameters
62      model.SetDblParam(Copt.DblParam.TimeLimit, 10);
63
64      // Solve problem
65      model.Solve();
66
67      // Output solution
68      if (model.GetIntAttr(Copt.IntAttr.LpStatus) == Copt.Status.OPTIMAL)
69      {
70        Console.WriteLine("\nFound optimal solution:");
71        VarArray vars = model.GetVars();
72        for (int i = 0; i < vars.Size(); i++)
73        {
74          Var x = vars.GetVar(i);
75          Console.WriteLine("  {0} = {1}", x.GetName(), x.Get(Copt.DblInfo.Value));
76        }
77        Console.WriteLine("Obj = {0}", model.GetDblAttr(Copt.DblAttr.LpObjVal));
78      }
79
80      Console.WriteLine("\nDone");
81    }
82    catch (CoptException e)
83    {
84      Console.WriteLine("Error Code = {0}", e.GetCode());
85      Console.WriteLine(e.Message);
86    }
87  }
88}

Let’s now walk through the example, line by line, to understand how it achieves the desired result of optimizing the model.

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 = new Envr();
      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 x, y, z
       *
       * obj: 1.2 x + 1.8 y + 2.1 z
       *
       * var:
       *  0.1 <= x <= 0.6
       *  0.2 <= y <= 1.5
       *  0.3 <= z <= 2.8
       */
      Var x = model.AddVar(0.1, 0.6, 0.0, Copt.Consts.CONTINUOUS, "x");
      Var y = model.AddVar(0.2, 1.5, 0.0, Copt.Consts.CONTINUOUS, "y");
      Var z = model.AddVar(0.3, 2.8, 0.0, Copt.Consts.CONTINUOUS, "z");

      model.SetObjective(1.2 * x + 1.8 * y + 2.1 * z, Copt.Consts.MAXIMIZE);

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 Model of 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 two constraints using linear expression
       *
       * r0: 1.5 x + 1.2 y + 1.8 z <= 2.6
       * r1: 0.8 x + 0.6 y + 0.9 z >= 1.2
       */
      model.AddConstr(1.5 * x + 1.2 * y + 1.8 * z <= 2.6, "r0");

      Expr expr = new 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(), 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
      model.Solve();

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.LpStatus) == Copt.Status.OPTIMAL)
      {
        Console.WriteLine("\nFound optimal solution:");
        VarArray vars = model.GetVars();
        for (int i = 0; i < vars.Size(); i++)
        {
          Var x = vars.GetVar(i);
          Console.WriteLine("  {0} = {1}", x.GetName(), x.Get(Copt.DblInfo.Value));
        }
        Console.WriteLine("Obj = {0}", model.GetDblAttr(Copt.DblAttr.LpObjVal));
      }

      Console.WriteLine("\nDone");

Spcifically, one can query the Copt.IntAttr.LpStatus attribute of the model to determine whether we have found 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 Constants 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)
    {
      Console.WriteLine("Error Code = {0}", e.GetCode());
      Console.WriteLine(e.Message);
    }

Build and Run

To build and run csharp example, users may refer to project under $COPT_HOME/examples/csharp. Specifically, We provide a csharp project file in cross-platform framework of dotnet core 2.0. This example shows a single project working on Windows, as well as Linux and Mac platforms.

First of all, download and install dotnet core 2.0 on your platform. To get started, follow instructions in the dotnet core docs.

Dotnet core 2.0 project

The dotnet core 2.0 project file example.csproj example locates in folder $COPT_HOME/examples/csharp/dotnetprojects. Copy example file lp_ex1.cs to this folder and change directory to there by Windows command line prompt, then run with command 'dotnet run --framework netcoreapp2.0'. For users of dotnet core 3.0, just run with 'dotnet run --framework netcoreapp3.0' instead will work too.

This csharp project has dependency on COPT dotnet 2.0 shared libary copt_dotnet20.dll, which is refered in the project file and defines all managed classes of COPT solver. In addition, copt_dotnet20.dll loads two shared libraries, that is, coptcswrap.dll and copt_cpp.dll on Windows, libcoptcswrap.so and libcopt_cpp.so on Linux, libcoptcswrap.dylib and libcopt_cpp.dylib on Mac respectively. Note that coptcswrap library is a bridge between managed COPT library and native library copt_cpp.dll, which declares and implements COPT constants, interfaces and methods. So users should make sure they are installed properly on runtime search paths.

In summary, to run csharp example, users should have COPT installed properly. Specifically, it requires three related COPT shared libaries existing on runtime search paths, and valid license files to run. Please refer to Install Guide for Cardinal Optimizer for further details.