基于列生成算法的切料问题建模求解

列生成算法(Column generation)

列生成广泛应用于大规模整数规划问题的求解中,尤其在装箱和切割问题、并行机调度问题、车辆路线问题的相关研究中。另外列生成算法也被用于乘务员调度、通信网络中的信道分配、多商品流等问题的研究中。(Gilmore and Gomory,1961年)首次将列生成技术用于求解一维切割问题(Cutting Stock Problem);(Desrosiers et al,1984)将列生成与分支定界结合以求解带时间窗的车辆路径问题;事实上,列生成得以广泛应用在于其求解(混合)整数规划问题所表现的卓越性能。

列生成算法思想

考虑下面的线性规划问题,称为主问题(Master problem,MP):

MP中有|J|=n个变量和m个约束条件,若n>>m,则想要直接求解是不可能的。相反的,考虑一个限制主问题(Restricted master problem,RMP),其变量为子集 J ′ J' J ⊆ \subseteq J J J,令为 λ ∗ λ^{*} λ是RMP问题的一个最优解(并不一定是MP问题的最优解)。在单纯形法的每一次迭代中,需要找一个检验数为负的非基变量入基,在列生成算法中则是通过一个定价子问题(Pricing problem,PP)实现的。
P P : m i n { c j − π ∗ a j ∣ j ∈ J } PP: min \{c_j-\pi^*a_j|j∈J \} PP:min{cjπajjJ}
P P < 0 PP<0 PP<0,则将对应的变量 λ i \lambda_i λi和其系数列 ( c j , a j ) (c_j,a_j) (cj,aj)加入到RMP中,然后求解新的RMP得到新的最优对偶变量值,重复这个过程直至找不到新的入基变量。此时, λ ∗ \lambda^* λ也是主问题MP的最优解了。列生成的优势在于无需考虑所有候选变量的迭代,而是通过其他的隐式搜索(implicit search)来寻找负消减成本(negative reduced cost,对应于单纯形法迭代中的额检验数)的列作为候选的入基变量。

切料问题(Cutting Stock Problem)

数学模型

列生成算法的一个经典例子是一维切割问题:给定宽度为 W W W的原料,需要切割 d i d_i di个宽度为 w i w_i wi的坯料,如何切割才能使用料最省?
主问题(Mater Problem):

限制主问题(Restricted Mater Problem):

定价子问题(Pricing Problem):

完整代码

分为3个类:Data类存放已知数据;CuttingStockProblem 算法类;Test程序运行。

package colgen_csp;

//class Data {
//    static double _rollWidth = 17;// 木材长度
//    static double[] _size = new double[] { 3, 5, 9 };;// 木材需求尺寸
//    static double[] _amount = new double[] { 25, 20, 15 };;// 需求量
//}
class Data {
    // 木材长度
    static double _rollWidth = 5600;
    // 木材需求尺寸
    static double[] _size = new double[]{1380, 1520, 1560, 1710, 1820, 1880, 1930, 2000, 2050, 2100, 2140, 2150, 2200};
    // 需求量
    static double[] _amount = new double[]{22, 25, 12, 14, 18, 18, 20, 10, 12, 14, 16, 18, 20};
}
package colgen_csp;

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

import static net.mindview.util.Print.*;

public class CuttingStockProblem {

    static double _width;
    static double[] _size;
    static double[] _amount;

    static void init() {
        _width = Data._rollWidth;
        _size = Data._size;
        _amount = Data._amount;

        print("width of the roll: " + _width);
        print("demand width:");
        for (double size : _size)
            printnb(size + " ");

        print();
        print("demand amount:");

        for (double amount : _amount)
            printnb(amount + " ");
    }

    static class IloNumVarArray {
        int _num = 0;
        IloNumVar[] _array = new IloNumVar[32];

        void add(IloNumVar ivar) {
            if (_num >= _array.length) {
                IloNumVar[] array = new IloNumVar[_array.length * 2];
                System.arraycopy(_array, 0, array, 0, _num);
                _array = array;
            }
            _array[_num++] = ivar;
        }

        IloNumVar getElement(int index) {
            return _array[index];
        }

        int getSize() {
            return _num;
        }

    }

    static void writeAnsRMP(IloCplex RMP, IloNumVarArray vars, IloRange[] Fill) throws IloException {
        print();
        print("using" + RMP.getObjValue() + " rolls");

        print();
        for (int i = 0; i < vars.getSize(); i++) {
            System.out.format("using pattern" + i + "=" + RMP.getValue(vars.getElement(i)) + "times");
        }

        print();
        for (int i = 0; i < Fill.length; i++) {
            print("Duals" + i + "=" + RMP.getDual(Fill[i]));
        }

        print();
    }

    static void writeAnsPP(IloCplex PP, IloNumVar[] Use) throws IloException {
        print();
        print("reduced cost = " + PP.getObjValue());

        print();
        /*PP.getObjValue() <= -RC_EPS*/
        if (PP.getObjValue() < 0) {
            for (int i = 0; i < Use.length; i++)
                System.out.println("  Use" + i + " = " + PP.getValue(Use[i]));// 输出新的切法
            System.out.println();
        }
    }

    static void writeAns(IloCplex RMP, IloNumVarArray vars) throws IloException {
        print();
        print("Best solution uses " + RMP.getObjValue() + "rolls");
        for (int i = 0; i < vars.getSize(); i++) {
            print(RMP.getValue(vars.getElement(i)));
        }
    }

    public static void solution() {
        init();
        try {
             限制主问题 //

            // 建立Cplex参数
            IloCplex RMP = new IloCplex();
            RMP.setOut(null);
            // 最小值问题
            IloObjective RMP_Obj = RMP.addMinimize();
            // 建立cplex需求量约束数组,添加约束
            IloRange[] Fill = new IloRange[_amount.length];
            for (int i = 0; i < _amount.length; i++) {
                Fill[i] = RMP.addRange(_amount[i], Double.MAX_VALUE);
            }

            IloNumVarArray vars = new IloNumVarArray();

            int len = _size.length;
            for (int j = 0; j < len; j++) {
                IloColumn col = RMP.column(RMP_Obj, 1.0).
                        and(RMP.column(Fill[j], (int) (_width / _size[j])));

                vars.add(RMP.numVar(col, 0.0, Double.MAX_VALUE));
            }

            //选择控制器
            RMP.setParam(IloCplex.Param.RootAlgorithm, IloCplex.Algorithm.Primal);

             定价子问题 //

            IloCplex PP = new IloCplex();
            PP.setOut(null);

            IloObjective ReducedCost = PP.addMinimize();
            //定义决策变量取值范围
            IloNumVar[] Use = PP.numVarArray(len, 0, Double.MAX_VALUE, IloNumVarType.Int);
            PP.addRange(-Double.MAX_VALUE, PP.scalProd(_size, Use), _width);

             列生成 //
            double[] newPatt;
            for (; ; ) {
                 限制主问题 //
                RMP.solve();
                writeAnsRMP(RMP, vars, Fill);
                double[] price = RMP.getDuals(Fill);

                 定价子问题 //
                ReducedCost.setExpr(PP.diff(1., PP.scalProd(Use, price)));
                PP.solve();
                writeAnsPP(PP, Use);
                if (PP.getObjValue() >= 0)// PP.getObjValue() > -RC_EPS
                    break;
                newPatt = PP.getValues(Use);
                IloColumn column = RMP.column(RMP_Obj, 1.);
                for (int i = 0; i < newPatt.length; i++) {
                    column = column.and(RMP.column(Fill[i], newPatt[i]));
                }
                vars.add(RMP.numVar(column, 0., Double.MAX_VALUE));
            }

            for (int i = 0; i < vars.getSize(); i++) {
                RMP.add(RMP.conversion(vars.getElement(i), IloNumVarType.Int));
            }

            RMP.solve();
            // 输出最优切法所需木材数以及每种切法所需木材数
            writeAns(RMP, vars);
            System.out.println("Solution status: " + RMP.getStatus());
            RMP.end();
            PP.end();
        } catch (IloException e) {
            e.printStackTrace();
        }

    }
}

package colgen_csp;

public class Test {
    public static void main(String[] args) {
        CuttingStockProblem csp = new CuttingStockProblem();
        csp.solution();
    }
}

Reference

  • Carvalho, J.M.V., 1998. Exact Solution of Cutting Stock Problems Using Column Generation and Branch-and-Bound. Int Trans Operational Res 5, 35–44.
  • https://blog.csdn.net/u014090505/article/details/89019327
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值