列生成算法(Column Generation)是被公认解决大规模线性规划问题的有效算法,多在大规模的机组排班领域。其主要思想是将问题转换为一个可以处理的等价问题,转化的目的:
l 将问题转化为具体某种特殊结构的又有现成方法求解的问题;
l 将问题化为规模较小的子问题;
应用列生成算法成败的关键在于将原问题划分为主问题(main problem)和子问题(sub problem)。如何将原问题巧妙的构造列向量,并将原始约束划分到这两组约束是利用列生成算法的关键因素。
列生成算法的核心思想非常朴素,借鉴单纯形法迭代中的“进基”思想,实际的原始问题可能很多列向量,但是真正“入基”的列向量却只占很少的一部分,因此如果能人工干预挑选出那些“质量比较好”的列向量进行求解,将大大地提高求解效率。因此列生成就是利用子问题筛选出那些“高质量列”,加入主模型中,这样主模型只对质量较好的列进行优化。
接下来以IBM ILOG CPLEX 内置的板材切割问题(cutstock)详细阐述(建模语言选用ILOG开发的OPL语言)
工厂有一批钢板,长宽固定。现需要在卷钢的宽方向进行切割成不同宽度切割成某干小的钢片,且每种宽度的钢片确定的数量需求和相应的切割成本,如何最小成本地利用现有钢材来满足指定需求的各种宽度的钢片数及是待优化的问题。
该问题的实质是一张原始钢板切到最终每种小港片的数量,假设需求m种小钢片,则可以用如下列向量表示,其中x表示每种宽度的切割数量,每一个列向量p 即是一种切割模式(pattern)。
l 每种宽度的小钢板有数量要求;
l 一块原始钢材切成的所有小钢片的宽度之和要小于等于原始钢板的宽度;
借助上述的列向量的创建规则,我们优化的重点在于结合各种宽度类型需求的比例的结构特征,找出性价比较高的切割方式(patterns),并计算出这些切割方式的切割次数。所以将原始问题划分为了两个问题:主问题(main problem)求每种pattern 的切割次数、子问题(sub problem)为按照各种宽度的需求结构筛选出高性价比的patterns。
每种pattern的结构;
每种pattern的切割次数:cuts;
接下来以OPL语言构建模型:
主问题的对偶解即为每种小钢片在需求结构下的最佳定价;将对偶问题的解纳入子问题,在每种钢片的价格为输入参数,以原始钢板的宽度为约束,建立以创造最大利润为目标,求出最佳的切割pattern。
子问题的price也就是主问题对偶问题的解。
接下来借助OPL语言将上述问题实现:
// Common width of the rolls to be cut.
int RollWidth = ...;
// Number of items types to be cuts
int NbItems = ...;
range Items = 1..NbItems;
// Size of each of the items
int Size[Items] = ...;
// Number of items of each types to be cuts
int Amount[Items] = ...;
// Pattern of roll cutting that are generated.
// Some simple default pattern are given initially in cutstock.dat
tuple pattern {
key int id;
int cost;
int fill[Items];
}
{pattern} Patterns = ...;
// dual values used to fill in the sub model.
float Duals[Items] = ...;
// How many of each pattern is to be cut
dvar float Cut[Patterns] in 0..1000000;
// Minimize cost : here each patteran as the same constant cost so that
// we minimize the number of rolls used.
minimize
sum( p in Patterns )
p.cost * Cut[p];
subject to {
// Unique constraint in the master model is to cover the item demand.
forall( i in Items )
ctFill:
sum( p in Patterns )
p.fill[i] * Cut[p] >= Amount[i];
}
tuple r {
pattern p;
float cut;
};
{r} Result = { | p in Patterns : Cut[p] > 1e-3};
// set dual values used to fill in the sub model.
execute FillDuals {
for(var i in Items) {
Duals[i] = ctFill[i].dual;
}
}
int RollWidth = ...;
range Items = 1..5;
int Size[Items] = ...;
float Duals[Items] = ...;
dvar int Use[Items] in 0..100000;
minimize
1 - sum(i in Items) Duals[i] * Use[i];
subject to {
ctFill:
sum(i in Items) Size[i] * Use[i] <= RollWidth;
}
main {
var status = 0;
thisOplModel.generate();
// This is an epsilon value to check if reduced cost is strictly negative
var RC_EPS = 1.0e-6;
// Retrieving model definition, engine and data elements from this OPL model
// to reuse them later
var masterSource = new IloOplModelSource("cutstock-main.mod");
var masterDef = new IloOplModelDefinition(masterSource);
var masterCplex = cplex;
var masterData = thisOplModel.dataElements;
// Creating the master-model
var masterOpl = new IloOplModel(masterDef, masterCplex);
masterOpl.addDataSource(masterData);
masterOpl.generate();
// Preparing sub-model source, definition and engine
var subSource = new IloOplModelSource("cutstock-sub.mod");
var subDef = new IloOplModelDefinition(subSource);
var subCplex = new IloCplex();
var best;
var curr = Infinity;
while ( best != curr ) {
best = curr;
writeln("Solve master.");
if ( masterCplex.solve() ) {
masterOpl.postProcess();
curr = masterCplex.getObjValue();
writeln();
writeln("MASTER OBJECTIVE: ",curr);
} else {
writeln("No solution to master problem!");
masterOpl.end();
break;
}
// Ceating the sub model
var subOpl = new IloOplModel(subDef,subCplex);
// Using data elements from the master model.
var subData = new IloOplDataElements();
subData.RollWidth = masterOpl.RollWidth;
subData.Size = masterOpl.Size;
subData.Duals = masterOpl.Duals;
subOpl.addDataSource(subData);
subOpl.generate();
// Previous master model is not needed anymore.
masterOpl.end();
writeln("Solve sub.");
if ( subCplex.solve() &&
subCplex.getObjValue() <= -RC_EPS) {
writeln();
writeln("SUB OBJECTIVE: ",subCplex.getObjValue());
} else {
writeln("No new good pattern, stop.");
subData.end();
subOpl.end();
break;
}
// prepare next iteration
masterData.Patterns.add(masterData.Patterns.size,1,subOpl.Use.solutionValue);
masterOpl = new IloOplModel(masterDef,masterCplex);
masterOpl.addDataSource(masterData);
masterOpl.generate();
// End sub model
subData.end();
subOpl.end();
}
// Check solution value
if (Math.abs(curr - 46.25)>=0.0001) {
status = -1;
writeln("Unexpected objective value");
}
subDef.end();
subCplex.end();
subSource.end();
status;
}