Java接口

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

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

(4)\[\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接口 里通过建模和求解来演示的问题是同一个。

示例解析

使用杉数求解器的 Java 接口求解与分析上述问题的代码如下。

代码 5 Lp_ex1.java
 1/*
 2 * This file is part of the Cardinal Optimizer, all rights reserved.
 3 */
 4import copt.*;
 5
 6/*
 7 * This Java example solves the following LP model:
 8 *
 9 * Maximize:
10 *  1.2 x + 1.8 y + 2.1 z
11 *
12 * Subject to:
13 *  1.5 x + 1.2 y + 1.8 z <= 2.6
14 *  0.8 x + 0.6 y + 0.9 z >= 1.2
15 *
16 * where:
17 *  0.1 <= x <= 0.6
18 *  0.2 <= y <= 1.5
19 *  0.3 <= z <= 2.8
20 */
21public class Lp_ex1 {
22  public static void main(final String argv[]) {
23    try {
24      Envr env = new Envr();
25      Model model = env.createModel("lp_ex1");
26
27      /* 
28       * Add variables x, y, z
29       *
30       * obj: 1.2 x + 1.8 y + 2.1 z
31       *
32       * var:
33       *  0.1 <= x <= 0.6
34       *  0.2 <= y <= 1.5
35       *  0.3 <= z <= 2.8
36       */
37      Var x = model.addVar(0.1, 0.6, 1.2, copt.Consts.CONTINUOUS, "x");
38      Var y = model.addVar(0.2, 1.5, 1.8, copt.Consts.CONTINUOUS, "y");
39      Var z = model.addVar(0.3, 2.8, 2.1, copt.Consts.CONTINUOUS, "z");
40
41      /*
42       * Add two constraints using linear expression
43       *
44       * r0: 1.5 x + 1.2 y + 1.8 z <= 2.6
45       * r1: 0.8 x + 0.6 y + 0.9 z >= 1.2
46       */
47      Expr e0 = new Expr(x, 1.5);
48      e0.addTerm(y, 1.2);
49      e0.addTerm(z, 1.8);
50      model.addConstr(e0, copt.Consts.LESS_EQUAL, 2.6, "r0");
51
52      Expr e1 = new Expr(x, 0.8);
53      e1.addTerm(y, 0.6);
54      e1.addTerm(z, 0.9);
55      model.addConstr(e1, copt.Consts.GREATER_EQUAL, 1.2, "r1");
56
57      // Set parameters and attributes
58      model.setDblParam(copt.DblParam.TimeLimit, 10);
59      model.setObjSense(copt.Consts.MAXIMIZE);
60
61      // Solve problem
62      model.solve();
63
64      // Output solution
65      if (model.getIntAttr(copt.IntAttr.HasLpSol) != 0) {
66        System.out.println("\nFound optimal solution:");
67        VarArray vars = model.getVars();
68        for (int i = 0; i < vars.size(); i++) {
69          Var x = vars.getVar(i);
70          System.out.println("  " + x.getName() + " = " + x.get(copt.DblInfo.Value));
71        }
72        System.out.println("Obj = " + model.getDblAttr(copt.DblAttr.LpObjVal));
73      }
74
75      System.out.println("\nDone");
76    } catch (CoptException e) {
77      System.out.println("Error Code = " + e.getCode());
78      System.out.println(e.getMessage());
79    }
80  }
81}

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

导入COPT的Java类

使用杉数求解器的 Java 接口,需要首先导入 COPT 的 Java 类

import copt.*;

创建求解环境和建模

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

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

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

上述代码对 Java 求解环境实例化,并创建了一个名为 “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, 1.2, copt.Consts.CONTINUOUS, "x");
      Var y = model.addVar(0.2, 1.5, 1.8, copt.Consts.CONTINUOUS, "y");
      Var z = model.addVar(0.3, 2.8, 2.1, copt.Consts.CONTINUOUS, "z");

上述代码中addVar()的第一和第二个参数分别是指变量的下界和上界;第三个参数是指目标函数中的系数; 第四个参数是指变量的类型。例子里所有变量的类型都是连续形的。最后一个参数是指变量名。

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

添加约束

接下来,我们演示在模型里添加多个约束。和变量一样,约束也总是和某个模型关联的。 杉数求解器提供包括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
       */
      Expr e0 = new Expr(x, 1.5);
      e0.addTerm(y, 1.2);
      e0.addTerm(z, 1.8);
      model.addConstr(e0, copt.Consts.LESS_EQUAL, 2.6, "r0");

      Expr e1 = new Expr(x, 0.8);
      e1.addTerm(y, 0.6);
      e1.addTerm(z, 0.9);
      model.addConstr(e1, copt.Consts.GREATER_EQUAL, 1.2, "r1");

上述代码的约束都是通过先建立线性表达式的对象,然后再通过addTerm()添加变量和对应 系数的方式来逐步建立最终的线性表达式。

设置参数和属性

在求解问题之前,还可以通过setDblParam()等方法设置模型的参数和属性。

      // Set parameters and attributes
      model.setDblParam(copt.DblParam.TimeLimit, 10);
      model.setObjSense(copt.Consts.MAXIMIZE);

这里,调用setDblParam()来设置copt.DblParam.TimeLimit参数值,使得求解器在最多执行10秒 后超时退出;调用setObjSense()并使用copt.Consts.MAXIMIZE参数,设置求解目标为最大化。

求解问题

到此为止,我们已经建好了模型。接下来,可以求解问题。

      // Solve problem
      model.solve();

这一步之后,问题已经被求解,并在内部保存了结果,包括求解状态,最优解等属性值。

输出结果

在问题被求解之后,可以通过查询不同的属性值来实现不同的目的。

      // Output solution
      if (model.getIntAttr(copt.IntAttr.HasLpSol) != 0) {
        System.out.println("\nFound optimal solution:");
        VarArray vars = model.getVars();
        for (int i = 0; i < vars.size(); i++) {
          Var x = vars.getVar(i);
          System.out.println("  " + x.getName() + " = " + x.get(copt.DblInfo.Value));
        }
        System.out.println("Obj = " + model.getDblAttr(copt.DblAttr.LpObjVal));
      }

      System.out.println("\nDone");

上述代码中,我们首先查询模型的属性值copt.IntAttr.HasLpSol来知道是否生成了LP最优解; 再查询变量的属性值copt.DblInfo.Value来获得这个变量的值;然后查询模型的属性值 copt.DblAttr.LpObjVal来获得目标函数的最优值。

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

错误处理

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

    } catch (CoptException e) {
      System.out.println("Error Code = " + e.getCode());
      System.out.println(e.getMessage());
    }

编译与运行

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

本例子可以在任意支持Java的平台上运行。首先需要在使用平台上安装Java 8或者更高版本。

Java例子细节

运行Java例子最简单的方式是先在console或者terminal窗口下进入安装路径的Java例子文件夹 $COPT_HOME/examples/java ,然后执行命令 'sh run.sh'

具体来说,编译例子依赖于COPT的Java包 copt_java.jar 。它不仅定义了杉数求解器的所有Java类, 还间接加载了两个相关的COPT动态库,分别是Windows下的 coptjniwrap.dllcopt_cpp.dll , Linux下的 libcoptjniwrap.solibcopt_cpp.so ,Mac下的 libcoptjniwrap.dyliblibcopt_cpp.dylib 。这里,动态库 coptjniwrap 是使用swig对JNI进行包装,用来衔接 COPT的Java包和COPT的C++核心库 copt_cpp。这个核心库真正定义了杉数求解器的所有类,接口以及 属性和参数常量。所以用户需要确保上述动态库已经安装在合适的路径下,可以在运行中被加载。

一般来说,用户只要正确安装了COPT安装包(设置好了动态库路径),并配置了有效的授权文档,就可以 正常执行这个Java例子。安装和授权细节请参考 安装说明