C#接口

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

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

(3)\[\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# 接口求解与分析上述问题的代码如下。

代码 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}

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

引用 COPT的C# 命名空间

为代码简洁起见,首先引用 COPT 的 C# 命名空间 Copt 。

using Copt;

创建求解环境和建模

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

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

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

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

添加变量

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

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

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

为了适应不同的调用需求,杉数求解器实现了AddVar()的不同形参的几种重载。 请参看 C# API 参考手册里的章节 C#优化建模类

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

添加约束

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

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

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

第二个约束的创建则是通过先建立相关线性表达式的对象,然后再通过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.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");

上述代码中,我们首先查询模型的属性值 Copt.IntAttr.LpStatus 以判断求解是否达到最优; 再查询变量的属性值 Copt.DblInfo.Value 来获得这个变量的值;然后查询模型的属性值 Copt.DblAttr.LpObjVal 来获得目标函数的最优值。

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

错误处理

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

    catch (CoptException e)
    {
      Console.WriteLine("Error Code = {0}", e.GetCode());
      Console.WriteLine(e.Message);
    }

编译与运行

本章节演示的例子,我们已经放入在COPT安装包里,方便用户尝试编译并运行。具体请参看 安装路径下的文件夹 $COPT_HOME/examples/csharp 。这个文件夹包括了例子的 csharp 代码, 以及 dotnet core 2.0 的项目文件。

这个例子可以在 Windows,Linux 和 Mac平台下直接编译并运行。首先需要在使用平台上下载并安装 dotnet core 2.0 。感兴趣的用户可以查看 使用教程 了解更多信息。

Dotnet core 2.0 项目

基于 dotnet core 2.0 的 C# 项目文件 example.csproj 位于 $COPT_HOME/examples/csharp/dotnetprojects 。 将示例文件 lp_ex1.cs 复制到上述目录,并在终端窗口下进入路径 $COPT_HOME/examples/csharp/dotnetprojects , 然后执行命令 'dotnet run --framework netcoreapp2.0' 。对于使用 dotnet core 3.0 的用户,用户只需要在上述目录 执行命令 'dotnet run --framework netcoreapp3.0' 即可运行。

具体来说,编译例子依赖于杉数求解器的基于 dotnet core 2.0 的动态库 copt_dotnet20.dll 。 它不仅定义了杉数求解器的所有 C# 类,还间接加载了两个相关的杉数求解器动态库,分别是 Windows 下的 coptcswrap.dllcopt_cpp.dll , Linux下的 libcoptcswrap.solibcopt_cpp.so ,Mac下的 libcoptcswrap.dyliblibcopt_cpp.dylib 。动态库 coptcswrap 用来衔接杉数求解器的 dotnet 2.0 动态库和 杉数求解器的算法核心库 copt_cpp。核心库真正定义了杉数求解器的所有类,接口以及属性和参数常量。 用户需要确保上述动态库已经安装在合适的路径下,可以在运行中被加载。

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