8.6. JAVA API¶
本章节提供MindOpt的JAVA API手册,内容见下文。
8.6.17. 获取mindoptj¶
引入mindoptj jar包的方式有两种:
将MindOpt安装目录中的
mindoptj.jar添加到 classpath ,或以maven依赖的方式添加mindoptj。
8.6.17.1. 直接引入jar包¶
mindoptj.jar 文件位于MindOpt库目录,位于 ${INSTALL_PREFIX}/mindopt/${VERSION}/${PLATFORM_ARCH}/lib ,如:/home/foo/mindopt/0.0.1/linux64-x86/lib 。
将此jar包的路径添加到 classpath 后,由于jar包在运行时还需要加载一些动态库,还需要确保一系列的变量被正确设置。第一,确保 java.library.path 包含了MindOpt库目录;
第二,要确保 MINDOPT_HOME 环境变量,指向正确的位置,如 export MINDOPT_HOME=/home/foo/mindopt/0.0.1 。
8.6.17.2. 作为maven依赖引入¶
这一节适用于 mindoptj 版本 >= 2.1.0。之前的版本由于性能较差不推荐使用 。
MindOpt为不同的平台架构,提供了不同的jar包。包含:
纯净的jar文件,与安装包中的jar文件相同,不包含动态库。用户负责设置正确的环境,以使动态库可以被正确加载(见上节)。
<dependency> <groupId>com.alibaba.damo</groupId> <artifactId>mindoptj</artifactId> <version>x.y.z</version> </dependency>
平台相关的jar文件,它在内部包含所需要加载的动态库,当前有5个平台架构可选:
win-x64
linux-x64
linux-aarch
osx-x64
osx-aarch
在添加maven依赖时,用 classifier 来指定对应的平台架构,如:
<dependency> <groupId>com.alibaba.damo</groupId> <artifactId>mindoptj</artifactId> <version>x.y.z</version> <classifier>win-x64</classifier> </dependency>
包含所有被支持平台架构动态库的“巨大”jar文件。
<dependency> <groupId>com.alibaba.damo</groupId> <artifactId>mindoptj</artifactId> <version>x.y.z</version> <classifier>portable</classifier> </dependency>
作为最佳实践,下面的maven配置可以根据当前平台架构,自动加载对应的jar包:
<project>
    ...
    <properties>
        ...
        <!-- replace 'some_version' with your expected version -->
        <mindoptj.version>some_version</mindoptj.version>
        ...
    </properties>
    <!-- test current platform & architecture to determine property 'mindoptj.platform' -->
    <profiles>
        <profile>
            <id>win-x64</id>
            <activation>
                <os>
                    <family>windows</family>
                    <arch>x86_64</arch>
                </os>
            </activation>
            <properties>
                <mindoptj.platform>win-x64</mindoptj.platform>
            </properties>
        </profile>
        <profile>
            <id>linux-x64</id>
            <activation>
                <os>
                    <name>linux</name>
                    <family>unix</family>
                    <arch>x86_64</arch>
                </os>
            </activation>
            <properties>
                <mindoptj.platform>linux-x64</mindoptj.platform>
            </properties>
        </profile>
        <profile>
            <id>linux-aarch</id>
            <activation>
                <os>
                    <name>linux</name>
                    <family>unix</family>
                    <arch>aarch64</arch>
                </os>
            </activation>
            <properties>
                <mindoptj.platform>linux-aarch64</mindoptj.platform>
            </properties>
        </profile>
        <profile>
            <id>osx-x64</id>
            <activation>
                <os>
                    <family>mac</family>
                    <arch>x86_64</arch>
                </os>
            </activation>
            <properties>
                <mindoptj.platform>osx-x64</mindoptj.platform>
            </properties>
        </profile>
        <profile>
            <id>osx-aarch</id>
            <activation>
                <os>
                    <family>mac</family>
                    <arch>aarch64</arch>
                </os>
            </activation>
            <properties>
                <mindoptj.platform>osx-aarch</mindoptj.platform>
            </properties>
            </dependencies>
        </profile>
    </profiles>
    <dependencies>
        ...
        <!-- add mindoptj with specified platform -->
        <dependency>
            <groupId>com.alibaba.damo</groupId>
            <artifactId>mindoptj</artifactId>
            <version>${mindoptj.version}</version>
            <classifier>${mindoptj.platform}</classifier>
        </dependency>
        ...
    <dependencies>
    ...
</project>
备注
如果使用了自带动态库的mindoptj(即指定了 classifier),则环境变量 MINDOPT_HOME 将被自动设置,用户设置则会被覆盖。
另外,如果 license 文件(mindopt.lic, fl_client.ini, 或 ce_license.ini)存在于 classpath,
则环境变量 MINDOPT_LICENSE_PATH 也将被自动设置,用户设置的值也将被覆盖。
警告
自动加载jar包是在maven编译阶段确定的,而不是在java运行阶段。比如,在Windows上打包了一个fat jar, 并拷贝到一个Linux Docker去运行,则会发生错误。
平台架构,可以手动指定。在maven编译打包时,加-P参数,如 mvn -P ${PROFILE_ID} 。如果知道jar包将
运行的平台架构是什么,则可以手动指定。否则可以考虑使用“巨大”的跨平台jar包。
8.6.18. Examples¶
定制食谱
有许多种不同的食物,每种食物可以提供不同的营养元素。请问我们该如何做出食物摄入的决策,以使得在满足人体每天所需的各种营养的前提下,能够最小化购买食物所花费的金额?
集合
食物集合 \(F\)
营养集合 \(N\)
参数
每单位1的食物 \(j\) 中关于营养 \(i \in N\) 的含量为 \(a_{ij}\)
获得单位1的食物 \(j\in F\) 的代价为 \(c_j\)
决策变量
\(x_j\) 是某人每天对食物 \(j\in F\) 的摄入量
目标函数
最小化食物的总采购金额: \(\text{minimize}~ \sum_{j\in F} c_{j}x_j\) 。
约束
每人对营养 \(i\in N\) 的需求介于最小值 \(n_{\min,i}\) 和最大值 \(n_{\max,i}\) 之间: \(n_{\min,i} \le \sum_{j\in F} a_{ij} x_j \le n_{\max,i},~~ \forall\, i\in N\) 。
import java.util.HashMap;
import java.util.Map;
public class ExampleDiet {
    public static void main(String[] args) throws MDOException{
        // 建立模型
        MDOEnv env = new MDOEnv();
        MDOModel model = new MDOModel(env);
        model.set(MDO.StringAttr.ModelName, "diet");
        // 定义所需的营养素的摄入量
        Map<String, double[]> requirements = new HashMap<>();
        requirements.put("Calories",    new double[]{ 2000, MDO.INFINITY });
        requirements.put("Carbohydrates",         new double[]{ 350, 375 });
        requirements.put("Protein",       new double[]{ 55, MDO.INFINITY });
        requirements.put("VitA",         new double[]{ 100, MDO.INFINITY });
        requirements.put("VitC",         new double[]{ 100, MDO.INFINITY });
        requirements.put("Calcium",      new double[]{ 100, MDO.INFINITY });
        requirements.put("Iron",         new double[]{ 100, MDO.INFINITY });
        requirements.put("Volume",        new double[]{ -MDO.INFINITY,75 });
        Map<String, double[]> foods = new HashMap<>();
        // 定义摄入的食物的下限,上限,和每单位的价格
        foods.put("Cheeseburger",    new double[]{ 0, MDO.INFINITY, 1.84 });
        foods.put("HamSandwich",     new double[]{ 0, MDO.INFINITY, 2.19 });
        foods.put("Hamburger",       new double[]{ 0, MDO.INFINITY, 1.84 });
        foods.put("FishSandwich",    new double[]{ 0, MDO.INFINITY, 1.44 });
        foods.put("ChickenSandwich", new double[]{ 0, MDO.INFINITY, 2.29 });
        foods.put("Fries",           new double[]{ 0, MDO.INFINITY, 0.77 });
        foods.put("SausageBiscuit",  new double[]{ 0, MDO.INFINITY, 1.29 });
        foods.put("LowfatMilk",      new double[]{ 0, MDO.INFINITY, 0.60 });
        foods.put("OrangeJuice",     new double[]{ 0, MDO.INFINITY, 0.72 });
        // 定义摄入的每单位食物的各种营养素的含量
        String[] nutritionNames = new String[]{ "Calories", "Carbohydrates", "Protein",
                "VitA", "VitC", "Calcium", "Iron", "Volume" };
        int numNutrition = nutritionNames.length;
        String[] foodNames = new String[]{  "Cheeseburger", "HamSandwich", "Hamburger", "FishSandwich",
                "ChickenSandwich", "Fries", "SausageBiscuit", "LowfatMilk", "OrangeJuice"  };
        int numFood = foodNames.length;
        Map<String, Map<String, Double>> reqValues = new HashMap<>();
        double[][] values = new double[][]{
                { 510.0, 34.0, 28.0, 15.0,   6.0, 30.0, 20.0,  4.0 },
                { 370.0, 35.0, 24.0, 15.0,  10.0, 20.0, 20.0,  7.5 },
                { 500.0, 42.0, 25.0,  6.0,   2.0, 25.0, 20.0,  3.5 },
                { 370.0, 38.0, 14.0,  2.0,   0.0, 15.0, 10.0,  5.0 },
                { 400.0, 42.0, 31.0,  8.0,  15.0, 15.0,  8.0,  7.3 },
                { 220.0, 26.0,  3.0,  0.0,  15.0,  0.0,  2.0,  2.6 },
                { 345.0, 27.0, 15.0,  4.0,   0.0, 20.0, 15.0,  4.1 },
                { 110.0, 12.0,  9.0, 10.0, 120.0, 30.0,  0.0,  8.0 },
                {  80.0, 20.0,  1.0,  2.0,   4.0,  2.0,  2.0, 12.0 } };
        for (int i = 0; i < foodNames.length; i++) {
            reqValues.put(foodNames[i], new HashMap<>());
            for (int j = 0; j < nutritionNames.length; j++) {
                reqValues.get(foodNames[i]).put(nutritionNames[j], values[i][j]);
            }
        }
        try {
            // 添加决策变量
            MDOVar[] foodVars = new MDOVar[numFood];
            for (int i = 0; i < numFood; ++i) {
                double[] foodData = foods.get(foodNames[i]);
                foodVars[i] = model.addVar(foodData[0], foodData[1], 0, MDO.CONTINUOUS, foodNames[i]);
             }
            // 添加约束
            // 应满足每日获取的各种营养素在建议的范围内
            for (int i = 0; i < numNutrition; i++) {
                MDOLinExpr linExpr = new MDOLinExpr();
                String nutri = nutritionNames[i];
                for (int j = 0; j < numFood; j++) {
                    String food = foodNames[j];
                    linExpr.addTerm(reqValues.get(food).get(nutri), foodVars[j]);
                 }
                model.addRange(linExpr, requirements.get(nutritionNames[i])[0], requirements.get(nutritionNames[i])[1], nutritionNames[i]);
             }
            // 添加目标函数
            MDOLinExpr linExpr = new MDOLinExpr();
            for (int i = 0; i < numFood; i++) {
                linExpr.addTerm(foods.get(foodNames[i])[2], foodVars[i]);
             }
            model.setObjective(linExpr, MDO.MINIMIZE);
            // 开始优化
            model.optimize();
            model.write("TestJava.mps");
            // 打印结果
            for (MDOVar foodVar : foodVars) {
                System.out.println("You should buy " + foodVar.get(MDO.DoubleAttr.X) + " unit of " + foodVar.get(MDO.StringAttr.VarName));
             }
         } catch (MDOException e) {
            System.out.println(e.getMessage());
         } finally {
            // 释放Model和Environment的资源
            model.dispose();
            env.dispose();
         }
     }
 }
设施选址
当前有两个商场,商场的位置已经确定。由若干建造仓库的备选位置,已知其坐标和其建造成本。我们假定从仓库到商场的运输成本与货物数量无关但与其之间的距离有关,请找到最小成本的仓库建造和运输方案。
集合
备选仓库 \(F\)
商场 \(M\)
参数
从仓库 \(i\in F\) 向商场 \(j \in M\) 运输的成本为 \(a_{ij}\) 。
建造仓库 \(i\in F\) 的代价为 \(c_j\) 。
商场 \(j \in M\) 的需求的货物量为 \(d_{ij}\) 。
决策变量
\(x_i\) 是是否在备选位置 \(i\in F\) 建造仓库。其取值必须为0或1,值为0时表示不建造,值为1时表示建造。
\(y_{ij}\) 表示是在从备选位置 \(i\in F\) 的仓库向 \(j \in M\) 的商场运输的距离货物数量。
目标函数
最小化仓库建造和商品运输的成本: \(\text{minimize}~ \sum_{i\in F} c_{i}x_i+\sum_{i\in F,j \in M} a_{ij}y_{ij}\) :
\(\text{minimize}~ \sum_{i\in F} c_{i}x_i + \sum_{i\in F,j \in M} a_{ij}y_{ij}\)
约束
\(\sum_{i\in F} y_{ij} = d_{j}, ~~ \forall j\in M\)
\(x_i d_j - \sum_{k\in M} y_{ik} = 0, ~~ \forall i \in F, j \in M\)
import java.util.*;
// 本例子的目标是为了找到最小成本的仓库建造和运输方案
public class ExampleFacility {
    public static void main(String[] args) throws MDOException {
        MDOEnv env = new MDOEnv();
        MDOModel model = new MDOModel(env);
        model.set(MDO.StringAttr.ModelName, "Facility");
        // 有两个商场,商场的位置已经确定,分别是(0, 1.7)和(1.4, 2.9), 所需要的货物重量为100单位和200单位
        Map<List<Double>, Integer> marketInfo = new HashMap<>();
        marketInfo.put(Arrays.asList(0.0, 1.7), 100);
        marketInfo.put(Arrays.asList(1.4, 2.9), 200);
        List<List<Double>> marketKeys = new ArrayList<>();
        marketKeys.add(Arrays.asList(0.0, 1.7));
        marketKeys.add(Arrays.asList(1.4, 2.9));
        int marketNum = marketInfo.size();
        // 仓库位置和建造成本
        Map<List<Integer>, Double> facilitiesInfo = new HashMap<>();
        facilitiesInfo.put(Arrays.asList(0, 1), 3.0);
        facilitiesInfo.put(Arrays.asList(0, 2), 1.0);
        facilitiesInfo.put(Arrays.asList(1, 0), 1.5);
        facilitiesInfo.put(Arrays.asList(1, 1), 1.3);
        facilitiesInfo.put(Arrays.asList(1, 2), 1.8);
        facilitiesInfo.put(Arrays.asList(2, 0), 1.6);
        facilitiesInfo.put(Arrays.asList(2, 1), 1.1);
        facilitiesInfo.put(Arrays.asList(2, 2), 1.9);
        List<List<Integer>> facilitiesKeys = new ArrayList<>();
        facilitiesKeys.add(Arrays.asList(0, 1));
        facilitiesKeys.add(Arrays.asList(0, 2));
        facilitiesKeys.add(Arrays.asList(1, 0));
        facilitiesKeys.add(Arrays.asList(1, 1));
        facilitiesKeys.add(Arrays.asList(1, 2));
        facilitiesKeys.add(Arrays.asList(2, 0));
        facilitiesKeys.add(Arrays.asList(2, 1));
        facilitiesKeys.add(Arrays.asList(2, 2));
        int facilitiesNum = facilitiesInfo.size();
        double transportFeePerM = 1.23;
        try {
            // 添加决策变量
            MDOVar[] xVars = model.addVars(facilitiesNum, MDO.BINARY);
            MDOVar[][] yVars = new MDOVar[marketNum][facilitiesNum];
            for (int i = 0; i < marketNum; i++) {
                for (int j = 0; j < facilitiesNum; j++) {
                    yVars[i][j] = model.addVar(0, MDO.INFINITY, 0, MDO.CONTINUOUS, String.valueOf(i) + String.valueOf(j));
                }
            }
            // 增加约束
            for (int i = 0; i < marketNum; i++) {
            // 约束1 已经决定建造的仓库必须满足所有商场的货物需求
                MDOLinExpr linExpr = new MDOLinExpr();
                for (int j = 0; j < facilitiesNum; j++) {
                    linExpr.addTerm(1, yVars[i][j]);
                    MDOLinExpr lhe = new MDOLinExpr();
                    lhe.addTerm(1.0 / marketInfo.get(marketKeys.get(i)), yVars[i][j]);
                    model.addConstr(lhe, MDO.LESS_EQUAL, xVars[j], "is_built[" + i + "," + j + "]");
                }
                // 约束2 如果不建仓库,则此仓库位置运送给所有商场的货物为0
                model.addConstr(linExpr, MDO.EQUAL, marketInfo.get(marketKeys.get(i)), "is_satisfy_" + i);
            }
            // 增加目标函数: 最小化运输费用和建造仓库的费用的总和
            // 假设从a地运往b地的运输费用只和距离有关,和货物重量无关
            MDOLinExpr objective = new MDOLinExpr();
            for (int j = 0; j < facilitiesNum; j++) {
                objective.addTerm(facilitiesInfo.get(facilitiesKeys.get(j)), xVars[j]);
            }
            for (int j = 0; j < facilitiesNum; j++) {
                for (int i = 0; i < marketNum; i++) {
                    objective.addTerm(calculateTransportationFee(marketKeys.get(i), facilitiesKeys.get(j), transportFeePerM), yVars[i][j]);
                }
            }
            model.setObjective(objective, MDO.MINIMIZE);
            // 开始优化
            model.optimize();
            // 打印结果
            for (int i = 0; i < facilitiesNum; i++) {
                MDOVar x = xVars[i];
                if (x.get(MDO.DoubleAttr.X) == 1) {
                    System.out.println("The No." + i + " warehouse should be built at (" + facilitiesKeys.get(i).get(0)
                    + ", " + facilitiesKeys.get(i).get(1) + ")");
                }
            }
            model.write("TestFacility.mps");
            System.out.println(objective.getValue());
        } catch (MDOException e) {
            System.out.println(e.getMessage());
        }finally {
            model.dispose();
            env.dispose();
        }
    }
    private static double calculateTransportationFee(List<Double> pos1, List<Integer> pos2, double transportFeePerM) {
        double x1 = pos1.get(0) - pos2.get(0);
        double x2 = pos1.get(1) - pos2.get(1);
        return Math.sqrt(x1 * x1 + x2 * x2) * transportFeePerM;
    }
}
人力分配
在一周中,工厂每天需要的工人数目不同。现在已知每个工人的日工资及其在一周中可以出勤的日期。需要计算如何安排工人,才能在满足工厂运行要求的情况下做到工资成本最低。
集合
一周日期 \(D = 1,2,3...,7\)
工厂需要的工人数目 \(N\)
工人 \(S\)
参数
工厂第 \(i \in D\) 天所需的工人数目为 \(n_i\)
工人 \(j\in S\) 的日工资为 \(s_j\)
表示工人可工作时间的序列,共有 \(T\) 组工人-工作时间数对 \((d_i,t_i)\) ,其中 \(d_i\in S,t_i \in D, i = 1,2,3...T\) 。
目标函数
最小化支付的工资: \(\text{minimize}~ \sum_{i=1,2,3...T} x_is_{d_i}\)
决策变量
\(x_i( i = 1,2,3...T)\) 表示可工作时间序列中,当日改工人是否上班。其取值必须为0或1,值为0时表示不上班,值为1时表示上班。
约束
\(\sum_{d_i=r} x_i = n_{r}, ~~\forall r\in D\)
import java.util.*;
public class ExampleWorkforce {
    public static void main(String[] args) throws MDOException {
        MDOEnv env = new MDOEnv();
        MDOModel model = new MDOModel(env);
        // 每天需要的人力数
        String[] dayName = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
        int[] workersPerDay = {  3, 1, 4, 2, 1, 3, 3 };
        // 每个工人一天的工资
        Map<String, Integer> workers = new HashMap<>();
        workers.put("Xiaoming", 13);
        workers.put("Huahua",   10);
        workers.put("HongHong", 11);
        workers.put("Dahua",    8);
        workers.put("Lihua",    9);
        workers.put("Niuniu",   14);
        workers.put("Gouzi",    14);
        List<String> workers_name = new ArrayList<>(workers.keySet());
        // 每个工人可以出勤的时间
        List<String[]> availability = new ArrayList<>();
        availability.add(new String[]{ "Xiaoming",   "Tuesday" });
        availability.add(new String[]{ "Xiaoming",   "Wednesday" });
        availability.add(new String[]{ "Xiaoming",   "Friday" });
        availability.add(new String[]{ "Xiaoming",   "Sunday" });
        availability.add(new String[]{ "Huahua",     "Monday" });
        availability.add(new String[]{ "Huahua",     "Tuesday" });
        availability.add(new String[]{ "Huahua",     "Friday" });
        availability.add(new String[]{ "Huahua",     "Saturday" });
        availability.add(new String[]{ "HongHong",   "Wednesday" });
        availability.add(new String[]{ "HongHong",   "Thursday" });
        availability.add(new String[]{ "HongHong",   "Friday" });
        availability.add(new String[]{ "HongHong",   "Sunday" });
        availability.add(new String[]{ "Dahua",      "Tuesday" });
        availability.add(new String[]{ "Dahua",      "Wednesday" });
        availability.add(new String[]{ "Dahua",      "Friday" });
        availability.add(new String[]{ "Dahua",      "Saturday" });
        availability.add(new String[]{ "Lihua",      "Monday" });
        availability.add(new String[]{ "Lihua",      "Tuesday" });
        availability.add(new String[]{ "Lihua",      "Wednesday" });
        availability.add(new String[]{ "Lihua",      "Thursday" });
        availability.add(new String[]{ "Lihua",      "Friday" });
        availability.add(new String[]{ "Lihua",      "Sunday" });
        availability.add(new String[]{ "Niuniu",     "Monday" });
        availability.add(new String[]{ "Niuniu",     "Tuesday" });
        availability.add(new String[]{ "Niuniu",     "Wednesday" });
        availability.add(new String[]{ "Niuniu",     "Saturday" });
        availability.add(new String[]{ "Gouzi",      "Monday" });
        availability.add(new String[]{ "Gouzi",      "Tuesday" });
        availability.add(new String[]{ "Gouzi",      "Wednesday" });
        availability.add(new String[]{ "Gouzi",      "Friday" });
        availability.add(new String[]{ "Gouzi",      "Saturday" });
        availability.add(new String[]{ "Gouzi",      "Sunday" });
        try {
            // 添加决策变量
            // x[(worker, day)]这个变量代表该工人是否在当天工作
            // 用(worker, day)来初始化决策变量,可以保证每个工人只在他们允许的天内出勤
            MDOVar[] x = new MDOVar[availability.size()];
            for (int i = 0; i < availability.size(); i++) {
                String[] worker_day = availability.get(i);
                x[i] = model.addVar(0, 1, 0, MDO.BINARY, "schedule_" + worker_day[0] + "," + worker_day[1]);
            }
            // 增加约束
            // 约束: 满足每天的人力需求
            Map<String, MDOLinExpr> day_count = new HashMap<>();
            for (String day : dayName) {
                MDOLinExpr le = new MDOLinExpr();
                day_count.put(day, le);
            }
            for (int i = 0; i < availability.size(); i++) {
                MDOLinExpr expr = new MDOLinExpr();
                String[] worker_day = availability.get(i);
                day_count.get(worker_day[1]).addTerm(1, x[i]);
            }
            for (int i = 0; i < dayName.length; i++) {
                MDOLinExpr expr = day_count.get(dayName[i]);
                model.addConstr(expr, MDO.EQUAL, workersPerDay[i], "c1" + availability.get(i)[1]);
            }
            // 增加目标函数
            MDOLinExpr obj = new MDOLinExpr();
            for (int i = 0; i < availability.size(); i++) {
                obj.addTerm(workers.get(availability.get(i)[0]), x[i]);
            }
            model.setObjective(obj, MDO.MINIMIZE);
            // 开始优化
            model.optimize();
            // 打印结果
            for (int i = 0; i < availability.size(); i++) {
                if (x[i].get(MDO.DoubleAttr.X) > 0) {
                    System.out.println(availability.get(i)[0] + " should work at " + availability.get(i)[1]);
                }
            }
            System.out.println("The total cost is " + model.get(MDO.DoubleAttr.ObjVal));
        } catch (Exception e) {
            System.out.println("Exception during optimization");
            e.printStackTrace();
        } finally {
            model.dispose();
            env.dispose();
        }
    }
}