java调用cplex实现列生成算法求解切割下料问题

列生成算法

列生成(Column generation)算法是一种用于求解大规模线性优化问题的非常高效的算法,被应用于调度问题、切割问题、车辆路径问题、选址问题等。
列生成算法是一种可用于求解线性规划问题的精确算法,其本质是单纯形法的延伸扩展。
列生成算法的原理详细介绍可参考博客【干货 | 10分钟带你彻底了解column generation

为什么使用列生成算法

在一般的线性规划问题当中,变量数和约束数较少,求解器或者手写单纯形法均能对问题进行求解,再不济花费时间成本进行算法迭代,同样也可以实现求解。
但是,变量数达到10000个时,是否还能够顺利进行求解?不妨回顾一下单纯形法的求解方式,其需要不断的寻找可以进基和出基变量,直至检验数为大于0,才能得到问题的解。那么在10000个甚至更多个变量中寻找进基变量,显然时间成本是不可接受的。
因而,针对变量数量较多或者变量数量远高于约束数量的线性规划问题,列生成算法是高效的。

实现流程

(1)确定主问题;
(2)确定初始可行解方案;
(3)计算主问题的对偶问题或者影子价格;
(4)确定子问题;子问题目标函数为检验数,约束为列需要满足的条件;
(5)若检验数大于0,继续;否则,转向(7);
(6)将子问题结果作为列加入主问题当中,更新主问题,转向(3);
(7)输出结果。
列生成算法的实现过程是在初始解方案的基础上,根据子问题的结果不断添加可行的进基列到主问题中,从而实现主问题有效变量的添加,极大地避免了进基变量的选择过程。

切割下料问题

假设我们当前需要解决如下问题:假设某纸卷生产厂家现存长度为16米的纸卷若干,现有三个订单需要满足,订单一需要长度为3米的纸卷25个,订单二需要长度为6的纸卷20个,订单三需要长度为7米的纸卷15个,现需要我们给出最优的切割方案,用最少的原厂纸卷数满足以上三个订单。
根据问题描述,发现存在以下的简单切割方式:
在这里插入图片描述
(1)全部切割为3米的纸卷,能够切出5个;
(2)全部切割为6米的纸卷,能够切出2个;
(3)全部切割为7米的纸卷,能够切出2个;
(4)其他组合切割方式
若以使用的切割方案进行建模,使用y_i表示第i种切割方案的使用次数,可建立模型如下:
在这里插入图片描述
其中,a_ij表示第i中方案能够切出纸卷j的个数;

初始模型

根据简单的切割方式,初始模型的系数为:
在这里插入图片描述
在这里插入图片描述

第一次迭代

主问题结果以及影子价格如下:
在这里插入图片描述
建立的子问题以及求解结果如下:
在这里插入图片描述
在这里插入图片描述
根据子模型的结果,将x_4添加到主模型,得到更新后的主模型:
在这里插入图片描述

第二次迭代

主问题结果以及影子价格如下:
在这里插入图片描述
建立的子问题以及求解结果如下:
在这里插入图片描述
在这里插入图片描述
根据子模型的结果,将x_5添加到主模型,得到更新后的主模型:
在这里插入图片描述

迭代结果

主模型如下:
在这里插入图片描述
在这里插入图片描述
模型计算结果显示,总切割数量为:20.0。

算法代码

import ilog.concert.*;
import ilog.cplex.IloCplex;

import java.util.ArrayList;
import java.util.Arrays;

//    模型使用数据类
class DataCuttingStock{
    //木材长度
    double rollWidth=16;
    //需求量
    double[] demand={25,20,15};
    //切割方式
    double[] cutSize={3,6,7};
}

public class ColumnGenerationCuttingStockDemo {
    //模型使用变量
    private static class IloNumVarArray {
        //已添加的变量数量
        int addNum = 0;
        //定义变量集合:初始长度为32或者根据data类进行设置
        IloNumVar[] varsArray = new IloNumVar[32];

        //变量添加
        void add(IloNumVar vars) {
            if (addNum >= varsArray.length) {
                varsArray = Arrays.copyOf(varsArray, varsArray.length * 2);
            }
            varsArray[addNum++] = vars;
        }

        //变量获取
        IloNumVar getElement(int i) {
            return varsArray[i];
        }

        //变量数量获取
        int getSize() {
            return addNum;
        }
    }

    //    定义数据
    DataCuttingStock data=new DataCuttingStock();

    public ColumnGenerationCuttingStockDemo(DataCuttingStock data) throws IloException {
        this.data = data;
    }

    //木材长度
    double rollWidth = data.rollWidth;
    //需求量
    double[] demand = data.demand;
    //切割方式
    double[] cutSize = data.cutSize;
    //变量数量
    int variableNum = cutSize.length;
    //变量集合
    IloNumVarArray cut = new IloNumVarArray();
    //切割方案
    ArrayList<int[]> cutMethod = new ArrayList<int[]>();
    //=========================设置主模型===================
    IloCplex masterPro = new IloCplex();
    IloObjective cost = masterPro.addMinimize();
    //添加约束范围
    IloRange[] cons = new IloRange[variableNum];
    //=========================设置子模型===================
    IloCplex subPro = new IloCplex();
    IloObjective reduceCost = subPro.addMinimize();
    IloNumVar[] subVar = subPro.numVarArray(variableNum, 0, Integer.MAX_VALUE, IloNumVarType.Int);
    //获取初始解方案
    private void getInitCutMethod() {
        for (int i = 0; i < variableNum; i++) {
            int[] cutPlan = new int[variableNum];
            cutPlan[i] = (int) (rollWidth / cutSize[i]);
            cutMethod.add(cutPlan);
        }
        for (int i = 0; i < variableNum; i++) {
            for (int j = 0; j < variableNum; j++) {
                System.out.print(cutMethod.get(i)[j] + ",");
            }
            System.out.println();
        }
    }

    //构建主模型
    private void buildMasterModel() throws IloException {
        //添加约束范围
        for (int i = 0; i < cons.length; i++) {
            cons[i] = masterPro.addRange(demand[i], Double.MAX_VALUE);
        }
        //按列添加模型
        for (int i = 0; i < variableNum; i++) {
            IloColumn column = masterPro.column(cost, 1.0).and(masterPro.column(cons[i], cutMethod.get(i)[i]));
            cut.add(masterPro.numVar(column, 0, Double.MAX_VALUE,IloNumVarType.Float));
        }
        //设置求解参数
        masterPro.setParam(IloCplex.Param.RootAlgorithm, IloCplex.Algorithm.Primal);
    }

    //构建子模型
    private void buildSubModel() throws IloException {
        subPro.addRange(-Double.MAX_VALUE, subPro.scalProd(cutSize, subVar), rollWidth);
    }

    //使用列生成算法求解切割下料问题
    private void solveMethod() throws IloException {
        getInitCutMethod();
        buildMasterModel();
        buildSubModel();
        //新方案
        int[] newPlan = new int[variableNum];
        //迭代计数
        int iterCount = 1;
        //循环添加可行列
        for (; ; ) {
            System.out.println("第" + iterCount + "次迭代");
            //主模型求解
            System.out.println("主模型:");
            System.out.println(masterPro);
            masterPro.setOut(null);
            masterPro.solve();
            masterPro.exportModel("master" + iterCount + ".lp");
            System.out.println("主问题的目标函数为:" + masterPro.getObjValue());
            //获取影子价格
            double[] price = masterPro.getDuals(cons);
            System.out.println("影子价格:");
            for (int i = 0; i < price.length; i++) {
                System.out.print(price[i] + "\t");
            }
            System.out.println();
            //根据影子价格添加子模型目标函数
            reduceCost.setExpr(subPro.diff(1, subPro.scalProd(price, subVar)));
            System.out.println("子模型:");
            System.out.println(subPro);
            //子模型求解
            subPro.setOut(null);
            subPro.solve();
            subPro.exportModel("sub" + iterCount + ".lp");
            System.out.println("子模型目标值:"+subPro.getObjValue());
            System.out.println("子模型结果:");
            for (int i = 0; i < subVar.length; i++) {
                System.out.print((int) subPro.getValue(subVar[i])+"\t");
            }
            System.out.println();
            //若子模型目标值大于0.终止迭代(检验数大于0,无进基列)
            if (subPro.getObjValue() >= 0) break;
            //根据子模型变量值确定新方案
            for (int i = 0; i < newPlan.length; i++) {
                newPlan[i] = (int) subPro.getValue(subVar[i]);
            }
            cutMethod.add(newPlan);
            //主模型添加新列
            IloColumn newCol = masterPro.column(cost, 1);
            for (int i = 0; i < newPlan.length; i++) {
                newCol = newCol.and(masterPro.column(cons[i], cutMethod.get(cutMethod.size() - 1)[i]));
            }
            cut.add(masterPro.numVar(newCol, 0, Double.MAX_VALUE,IloNumVarType.Float));
            //记录操作数
            iterCount++;
        }
        printResult();
    }

    //生成最终结果
    private void printResult() throws IloException {
        for (int i = 0; i < cut.getSize(); i++) {
            //直接将变量转换为整数
            masterPro.add(masterPro.conversion(cut.getElement(i),IloNumVarType.Int));
            //masterPro.add(cut.getElement(i));
        }
        System.out.println("最终模型:");
        System.out.println(masterPro);
        masterPro.solve();
        masterPro.setOut(null);
        masterPro.exportModel("lp1.lp");
        System.out.println("总切割数量为:" + masterPro.getObjValue());
    }

    public static void main(String[] args) throws IloException {
        DataCuttingStock data = new DataCuttingStock();
        ColumnGenerationCuttingStockDemo CGM = new ColumnGenerationCuttingStockDemo(data);
        CGM.solveMethod();

    }
}

========================================
今天到此为止,后续记录其他cplex技术的学习过程。
以上学习笔记,如有侵犯,请立即联系并删除!

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
CPLEX是一款高效的数学优化求解器,可以用来求解各种类型的优化问题,包括车辆路径规划问题(CVRP)。Java可以通过JNI(Java Native Interface)技术来调用C++编写的CPLEX库,从而实现对CVRP问题的求解。 以下是一个简单的Java调用CPLEX求解CVRP问题的示例代码: ```java import ilog.concert.*; import ilog.cplex.*; public class CVRP { public static void main(String[] args) { try { // 创建Cplex对象 IloCplex cplex = new IloCplex(); // 创建变量 int n = 10; // 节点数 int m = 3; // 车辆数 double[][] d = new double[n][n]; // 距离矩阵 IloIntVar[][] x = new IloIntVar[n][n]; // x[i][j]表示从i到j是否有路径 IloIntVar[] u = new IloIntVar[n]; // u[i]表示第i个节点的负荷 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { x[i][j] = cplex.boolVar(); } u[i] = cplex.intVar(0, 10000); // 假设每个节点的最大负荷为10000 } // 添加约束 for (int i = 1; i < n; i++) { IloLinearNumExpr expr1 = cplex.linearNumExpr(); for (int j = 0; j < n; j++) { if (j != i) { expr1.addTerm(1.0, x[i][j]); } } cplex.addEq(expr1, 1.0); // 每个节点只能被访问一次 IloLinearNumExpr expr2 = cplex.linearNumExpr(); for (int j = 0; j < n; j++) { if (j != i) { expr2.addTerm(1.0, x[j][i]); } } cplex.addEq(expr2, 1.0); // 每个节点只能从一个节点到达 cplex.addLe(u[i], m); // 每个车辆的最大负荷不能超过车辆数 } for (int j = 0; j < n; j++) { IloLinearNumExpr expr3 = cplex.linearNumExpr(); for (int i = 0; i < n; i++) { if (i != j) { expr3.addTerm(1.0, x[i][j]); } } cplex.addEq(expr3, 1.0); // 每个节点只能到达一次 } for (int i = 1; i < n; i++) { for (int j = 1; j < n; j++) { if (i != j) { cplex.addLe(cplex.sum(u[j], cplex.prod(-1, u[i]), cplex.prod(n - 1, x[i][j])), n - 2); // 车辆容量约束 } } } // 添加目标函数 IloLinearNumExpr obj = cplex.linearNumExpr(); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (i != j) { obj.addTerm(d[i][j], x[i][j]); } } } cplex.addMinimize(obj); // 求解问题 cplex.solve(); // 输出结果 System.out.println("Objective value = " + cplex.getObjValue()); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (i != j && cplex.getValue(x[i][j]) > 0.9) { System.out.println("Vehicle " + (i + 1) + " -> " + (j + 1)); } } } // 释放资源 cplex.end(); } catch (IloException e) { e.printStackTrace(); } } } ``` 这段代码实现了一个简单的CVRP求解过程,其中包括创建变量和约束、设置目标函数、求解问题以及输出结果。需要注意的是,在实际的CVRP问题中,还需要考虑许多其他因素,如时间窗口、多重配送等,需要根据实际情况进行相应的调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南音小榭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值