列生成算法
列生成(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技术的学习过程。
以上学习笔记,如有侵犯,请立即联系并删除!