一、引言
本问题被用来作为cplex说明书讲解列生成算法的源代码,对于刚刚接触列生成算法的小伙伴在看cplex说明书中的讲解以及其源代码时会有一定的困难,所以我主要针对cuttingstock问题使用列生成算法求解来讲解。(这部分内容需要对单纯形以及对偶问题和列生成算法有一定理解)
我们所熟悉的单纯形算法是通过矩阵行变换计算reducecost来确定进基变量和出基变量,当矩阵的行数有限、列数比较多时,通过单纯形的计算过程我们知道,真正起作用的列就是矩阵的基,所以我们可以使用较少的列来完成最优解的求解而并不需要列举所有的列,当行数很多列数有限时候,可以从对偶来理解问题,在实际中,我们遇到的问题通常是约束有限,但是变量有很多,此时就是行数有限、列数比较多的情况,随着变量的增加,问题的规模是指数增加,所以列生成算法在这种情况下求解起来是相对效果比较好的。
二、问题描述
cuttingstock问题实际上是描述现有标准的卷材,其可以切割成长度较小的带材,带材表示客户需要的,给定标准卷材115米,带材是尺寸为25, 40, 50, 55, 70;带材需求的数量为50, 36, 24, 8, 30;如何分配切割卷材使得浪费最少、使用的卷材数量最少并且满足所有客户的需要,这就是经典的cuttingstockproblem。
三、代码理解
主问题模型:cutOpt
主问题目标函数:RollsUsed
主问题的约束矩阵:Fill
主问题决策变量:Cut
子问题模型:patGen
子问题目标函数:ReducedCost
子问题决策变量:Use
前面主要用于定义主子问题模型需要的环境变量等。
后面是代码的核心部分,首先求解主问题,然后求解对偶变量。将其加入子问题的目标系数中求解子问题,此时子问题的目标函数就是所有非基变量中最小的reducecost,如果最小reducecost的加入都不能使得目标函数的值再减小,说明达到了最优值就可以退出迭代输出最优解,但如果这个reducecost的加入可以使得目标函数值减小,那就可以把子问题求出的这一列加入到主问题目标矩阵中循环迭代。
IloEnv env;
try {
IloInt i, j;
IloNum rollWidth; //标准钢宽度
IloNumArray amount(env); //需要数量
IloNumArray size(env); //需要尺寸
/*if (argc > 1)
readData(argv[1], rollWidth, size, amount);
else
readData("../../../examples/data/cutstock.dat",
rollWidth, size, amount);*/
readData("cutstock.txt",rollWidth, size, amount); //读入数据
/// CUTTING-OPTIMIZATION PROBLEM ///
//构建主问题模型
IloModel cutOpt(env);
//建立主问题目标到主问题模型中
IloObjective RollsUsed = IloAdd(cutOpt, IloMinimize(env));
//给主问题模型增加约束 大于amount数组 小于无穷
IloRangeArray Fill = IloAdd(cutOpt,IloRangeArray(env, amount, IloInfinity));
//声明主问题的决策变量
IloNumVarArray Cut(env);
//卷材的种类
IloInt nWdth = size.getSize();
//初始化决策变量 以及具体的目标函数和约束
for (j = 0; j < nWdth; j++) {
Cut.add(IloNumVar(RollsUsed(1) + Fill[j](int(rollWidth / size[j]))));
}
//声明主问题的求解器
IloCplex cutSolver(cutOpt);
/// PATTERN-GENERATION PROBLEM ///
//声明子问题的模型
IloModel patGen(env);
//建立子问题的目标函数(目前恒为1)后期动态加入
IloObjective ReducedCost = IloAdd(patGen, IloMinimize(env, 1));
//建立子问题的决策变量以及其范围
IloNumVarArray Use(env, nWdth, 0.0, IloInfinity, ILOINT);
//添加约束条件
patGen.add(IloScalProd(size, Use) <= rollWidth); //IloScalProd 应该是数组按位相乘累加
//声明子问题的求解器
IloCplex patSolver(patGen);
/// COLUMN-GENERATION PROCEDURE ///
//存储子问题的求解结果 影子价格
IloNumArray price(env, nWdth);
//存储允许新加入的列
IloNumArray newPatt(env, nWdth);
/// COLUMN-GENERATION PROCEDURE ///
for (;;) {
/// OPTIMIZE OVER CURRENT PATTERNS ///
//首先求解主问题的结果
cutSolver.solve();
//输出(主问题决策变量的值)cut以及其对偶值
report1(cutSolver, Cut, Fill);
/// FIND AND ADD A NEW PATTERN ///
for (i = 0; i < nWdth; i++) {
price[i] = -cutSolver.getDual(Fill[i]);
}
//给子问题的目标函数添加决策变量并且更新其系数(对偶值)
ReducedCost.setLinearCoefs(Use, price);
//求解价格子问题
patSolver.solve();
//输出当前求解出来的列的reducecost,并且如果reducecost小于0,输出当前的解 新的一列(Use)
report2(patSolver, Use, ReducedCost);
//判断是否找到了最优解
if (patSolver.getValue(ReducedCost) > -RC_EPS) break;
//将新列复制
patSolver.getValues(newPatt, Use);
//用新加入的列的来更新主问题的目标函数
Cut.add(IloNumVar(RollsUsed(1) + Fill(newPatt)));
}
以上是我加了注释的一部分代码,实际的代码中为了保证求得的整数解,其强制把结果整数化了,但了解整数规划的都知道这样做不一定会得到全局最优解,但列生成是对主问题的松弛,用来求解线性规划问题,无法对整数线性规划问题求解得到精确的整数解,如果要得到整数解,我们需要借助分支定界算法结合起来使用分支定价算法来对整数线性规划问题得到整数解。(后续将对cuttingstock问题使用分支定价算法求解)