列生成算法

列生成(Column generation)算法是一种用于求解大规模线性优化问题的非常高效的算法,被应用于调度问题、切割问题、车辆路径问题、选址问题等。
列生成算法是一种可用于求解线性规划问题的精确算法,其本质是单纯形法的延伸扩展。

为什么使用列生成算法

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

实现流程

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

切割下料问题

假设我们当前需要解决如下问题:假设某纸卷生产厂家现存长度为16米的纸卷若干,现有三个订单需要满足,订单一需要长度为3米的纸卷25个,订单二需要长度为6的纸卷20个,订单三需要长度为7米的纸卷15个,现需要我们给出最优的切割方案,用最少的原厂纸卷数满足以上三个订单。

根据问题描述,发现存在以下的简单切割方式:

java 调用JavaCompiler 编译失败 java调用cplex_开发语言


(1)全部切割为3米的纸卷,能够切出5个;

(2)全部切割为6米的纸卷,能够切出2个;

(3)全部切割为7米的纸卷,能够切出2个;

(4)其他组合切割方式

若以使用的切割方案进行建模,使用y_i表示第i种切割方案的使用次数,可建立模型如下:

java 调用JavaCompiler 编译失败 java调用cplex_i++_02


其中,a_ij表示第i中方案能够切出纸卷j的个数;

初始模型

根据简单的切割方式,初始模型的系数为:

java 调用JavaCompiler 编译失败 java调用cplex_java_03


java 调用JavaCompiler 编译失败 java调用cplex_java_04

第一次迭代

主问题结果以及影子价格如下:

java 调用JavaCompiler 编译失败 java调用cplex_System_05


建立的子问题以及求解结果如下:

java 调用JavaCompiler 编译失败 java调用cplex_开发语言_06


java 调用JavaCompiler 编译失败 java调用cplex_算法_07


根据子模型的结果,将x_4添加到主模型,得到更新后的主模型:

java 调用JavaCompiler 编译失败 java调用cplex_开发语言_08

第二次迭代

主问题结果以及影子价格如下:

java 调用JavaCompiler 编译失败 java调用cplex_java_09


建立的子问题以及求解结果如下:

java 调用JavaCompiler 编译失败 java调用cplex_java_10


java 调用JavaCompiler 编译失败 java调用cplex_i++_11


根据子模型的结果,将x_5添加到主模型,得到更新后的主模型:

java 调用JavaCompiler 编译失败 java调用cplex_java_12

迭代结果

主模型如下:

java 调用JavaCompiler 编译失败 java调用cplex_i++_13


java 调用JavaCompiler 编译失败 java调用cplex_java_14


模型计算结果显示,总切割数量为: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, );
    //获取初始解方案
    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),));
            //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技术的学习过程。
以上学习笔记,如有侵犯,请立即联系并删除!