cplex入门系列(二)--- 线性规划求解

一、cplex项目模板

一般一个的cplex项目,一般分为五个模块,分别是创建模型、定义优化参数、设置目标函数、设置约束和模型求解及输出。下面针对这五个模块使用cplex的Java API来进行介绍。

1.1 创建模型

即在内存中开辟一个空间来实例化IloCplex类;

IloCplex cplex = new IloCplex(); // 创建一个模型

1.2 定义优化参数

这里定义将要求解的优化参数,常见的参数类型包括单个变量、一维及二维数组类型。

1.2.1 单变量定义

在实际生产问题中,常见的变量类型就实数,整数比较常见:
○ 实数型变量 cplex.numVar
○ 整数型变量 cplex.intVar
单变量参数,主要是值变量的取值范围,例如该变量取值范围为0≤x≤5,若不想定义范围也可以设置为-Double.MAX_VALUE和Double.MAX_VALUE,表示负无穷到正无穷;比如cplex.numVar(0,5),表示0≤x≤5

1.2.2 一维数组定义

○ 实数型变量 cplex.numVarArray(num,min,max)
○ 整数型变量 cplex.intVarArray(num,min,max)
参数表示数组的大小(num),最小值(min)和最大值(max),这样定义的就是三个定义域相同的变量;
如果要为每个变量设置不同的范围

double[] rangeVar = {0,3,2,8,1,7}; // 每个变量的最小值和最大值
IloNumVar[] x = new IloNumVar[3];
for(int i=0;i<3;i++){
    x[i] = cplex.numVar(rangeVar[2*i], rangeVar[2*i+1]);
}

这样就将数组中的三个变量设置了不同的取值范围,分别为[0,3],[2,8],[1,7]

1.2.3 二维数组定义

IloNumVar[][] x2array1 = new IloNumVar[2][];
for (int i=0; i<2; i++){
    x2array1[i] = cplex.numVarArray(2, 0.0, 5.0);
}
IloIntVar[][] x2array2 = new IloIntVar[2][];
for (int i=0; i<2; i++){
    x2array2[i] = cplex.intVarArray(2, 0, 5);
}

1.3 设置目标函数和约束

目标函数一般是取一个表达式的最大值或者最小值,约束一般是设定一个表达式的取值范围,一个共同点就是都需要先定义表达式,而且它们定义表达式的方式是完全相同的。

1.3.1 定义表达式

官方根据表达式类型的不同提供了不同的接口,包括:

接口 描述
IloIntExpr/IloNumExpr 整数/实数表达式的基本公共接口
IloLinearIntExpr/IloLinearNumExpr 整数/实数类型变量的一次线性表达式的接口
IloLQIntExpr/IloLQNumExpr 具有线性和二次项的一般表达式
IloQuadIntExpr/IloQuadNumExpr 整数/实数型二次数值表达式

需要根据模型表达式类型选择合适自己的接口形式,比如x_1+2x_2+3x_3,属于线性类型,那么就可以一次线性表达式的接口:

IloNumVar[] x =cplex.numVarArray(3, -5, 5);
IloLinearNumExpr cs = cplex.linearNumExpr();
cs.addTerm(1, x[0]);
cs.addTerm(2, x[1]);
cs.addTerm(3, x[2]);

每个接口都提供了非常多的方法,详细参考官方文档的使用方法和说明。尽管官方为不同形式的表达式提供了不同的接口,但是存在一定的问题,比如无法在一次线性规划表达式中添加二次项,当表达式比较复杂的时候通常不止一种类型。因此一般就用最基本的公式接口IloNumExpr,在这个接口中可以利用cplex模型库中的加减乘除来添加任何形式的表达式。

先创建模型Ilocplex cplex = new IloCplex(); 然后通过cplex.xxx的方式使用模型中的各种运算方法,常用的包括:

方法 说明
sum 求和
diff 求差
prod 乘积
abs 绝对值

有了以上这四种方法基本就可以应对大部分的表达式了,比如x_1+2x_2+3x_3,用基本公共接口表示为:

IloNumVar[] x =cplex.numVarArray(3, -5, 5);
double[] vars = {1,2,3};
IloNumExpr cs = cplex.numExpr();
for(int i=0;i<3;i++){
    cs = cplex.sum(cs, cplex.prod(x[i], vars[i]));
}

虽然这种方式增加了代码量,但是代码可读性增强了,所以这种方式会更好,再比如,求变量x的平方和绝对值之和:

IloNumVar[] x =cplex.numVarArray(3, -5, 5);
IloNumExpr cs1 = cplex.numExpr();
IloNumExpr cs2 = cplex.numExpr();
for(int i=0;i<3;i++){
    cs1 = cplex.sum(cs1, cplex.abs(x[i]));
    cs2 = cplex.sum(cs2, cplex.prod(x[i], x[i]));
}

1.3.2 定义目标函数

假设定义后目标函数的表达式用obj表示:

函数 说明
cplex.addMinimize(obj) 求obj的最小值
cplex.addMaximize(obj) 求obj的最大值

1.3.3 定义约束

假设定义后约束的表达式用cs表示:

函数 说明
cplex.addEq(cs,a) cs=a
cplex.addLe(cs,a) cs{\le}a
cplex.addGe(cs,a) cs{\ge}a
cplex.addRange(a,cs,b) a{\le}cs{\le}b

在添加了约束后我们可以通过cplex.diff(cs,cs)来清空表达式cs,然后就可以在cs中添加新的表达式。

1.4 清空表达式

有时候在定义目标函数和约束时需要通过循环来定义新的表达式,每次重新初始化表达式很麻烦,这时候就需要清空表达式:

  • cplex.diff(cs,cs)
  • cplex.numExpr()
    第一种方式有时候会爆出内存溢出的错误,因此更推荐使用第二种方式。

1.5 模型求解及输出

模型求解以及输出的模板如下所示:

if (cplex.solve()) {
    cplex.output().println("Solution status = " + cplex.getStatus());
    cplex.output().println("Solution value = " + cplex.getObjValue());
    double[] val = cplex.getValues(x);
    for (int j = 0; j < val.length; j++){
        System.out.println("x" + (j+1) + "  = " + val[j]);
    }
}
cplex.end();

其中

函数 说明
cplex.getStatus() 获得模型求解的状态
cplex.getObjValue() 获取目标函数的值
cplex.getValues(x) 获取优化变量的值
cplex.end() 结束模型

模型求解包括以下几个状态

状态 说明
Optimal 找到了一个最优的解决方案
Feasible 找到了一个可行的解决方案
Infeasible 该模型不可行
Error 遇到了错误
Bounded 模型不是无界的
Unbounded 模型是无界的

二、cplex 项目实战

2.1 案例一

该案例为《运筹学》清华大学第四版P15例2-1题目,具体应用场景请参考教材。
目标函数:max⁡ z=2x_1+3x_2
约束条件:
x_1+2x_2≤8
4x_1≤16
4x_2≤12
x_1,x_2≥0

package com.opreation.research;
import ilog.concert.*;
import ilog.cplex.IloCplex;

public class CplexTest {
    public static void main(String[] args) throws IloException {
        try{
            // 1. 创建模型
            IloCplex cplex = new IloCplex();

            // 2. 定义优化参数
            IloNumVar[] x = cplex.numVarArray(2, 0, Double.MAX_VALUE);
            double[] c = {2,3};
            IloNumExpr cs1 = cplex.numExpr();
            for(int i=0;i<2;i++){
                cs1 = cplex.sum(cs1,cplex.prod(c[i], x[i]));
            }


            // 3. 设置目标函数
            cplex.addMaximize(cs1);
            // 4. 设置约束
            cplex.addLe(cplex.sum(x[0], cplex.prod(2, x[1])), 8);
            cplex.addLe(cplex.prod(4, x[0]), 16);
            cplex.addLe(cplex.prod(4, x[1]), 12);

            // 5. 模型求解及输出
            if(cplex.solve()){
                cplex.output().println("Solution status = " + cplex.getStatus());
                cplex.output().println("Solution Value = " + cplex.getObjValue());
                double[] val = cplex.getValues(x);
                for(int j=0;j<2;j++){
                    System.out.println("x" + (j+1) + " = " + val[j]);
                }
            }
            cplex.end();
        } catch (IloException e){
            System.err.println("Concert exception caught: " + e);
        }

    }
}

运行结果

Tried aggregator 1 time.
LP Presolve eliminated 2 rows and 0 columns.
Reduced LP has 1 rows, 2 columns, and 2 nonzeros.
Presolve time = 0.00 sec. (0.00 ticks)

Iteration log . . .
Iteration:     1   Dual objective     =            14.000000
Solution status = Optimal
Solution Value = 14.0
x1 = 4.0
x2 = 2.0

可以根据上面的运行结果获悉,该问题为LP(线性规划)求解问题,迭代了一次,对偶目标解为14,解的状态为Optimal,即找到了一个最优的解决方案,目标函数的最优解为14,对应的变量x1和x2分别取4和2。

2.2 案例二

该案例为《运筹学》清华大学第四版P60例2-10题目,具体应用场景请参考教材。
目标函数:min⁡ z=0x_1+0.1x_2+0.2x_3+0.3x_4+0.8x_5
约束条件:
x_1+2x_2+x_4=100
2x_3+2x_4+x_5=100
3x_1+x_2+2x_3=3x_5=100
x_1,x_2,x_3,x_4,x_5≥0

package com.opreation.research;
import ilog.concert.*;
import ilog.cplex.IloCplex;

import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;

import java.util.HashSet;
import java.util.Random;

public class CplexTest {

    public static void main(String[] args) throws IloException {
        try{


            // 1. 创建模型
            IloCplex cplex = new IloCplex();


            // 2. 定义优化参数
            IloNumVar[] x = cplex.numVarArray(5, 0, Double.MAX_VALUE);
            double[] c = {0, 0.1, 0.2, 0.3, 0.8};
            IloNumExpr cs1 = cplex.numExpr();
            for(int i=0;i<5;i++){
                cs1 = cplex.sum(cs1,cplex.prod(c[i], x[i]));
            }


            // 3. 设置目标函数
            cplex.addMinimize(cs1);
            // 4. 设置约束
            cplex.addEq(cplex.sum(x[0], cplex.prod(2, x[1]), x[3]), 100);
            cplex.addEq(cplex.sum(cplex.prod(2, x[2]), cplex.prod(2, x[3]), x[4]), 100);
            cplex.addEq(cplex.sum(cplex.prod(3, x[0]), x[1], cplex.prod(2, x[2]), cplex.prod(3, x[4])), 100);
            cplex.addGe(x[0], 0);
            cplex.addGe(x[1], 0);
            cplex.addGe(x[2], 0);
            cplex.addGe(x[3], 0);
            cplex.addGe(x[4], 0);


//            // 5. 模型求解及输出
            if(cplex.solve()){
                cplex.output().println("Solution status = " + cplex.getStatus());
                cplex.output().println("Solution Value = " + cplex.getObjValue());
                double[] val = cplex.getValues(x);
                for(int j=0;j<5;j++){
                    System.out.println("x" + (j+1) + " = " + val[j]);
                }

            }
            cplex.end();
        } catch (IloException e){
            System.err.println("Concert exception caught: " + e);
        }

    }
}

运行结果

Tried aggregator 1 time.
LP Presolve eliminated 5 rows and 0 columns.
Reduced LP has 3 rows, 5 columns, and 10 nonzeros.
Presolve time = 0.02 sec. (0.00 ticks)
Initializing dual steep norms . . .

Iteration log . . .
Iteration:     1   Dual objective     =            10.000000
Solution status = Optimal
Solution Value = 16.0
x1 = 0.0
x2 = 40.0
x3 = 30.0
x4 = 20.0
x5 = 0.0

可以根据上面的运行结果获悉,该问题为LP(线性规划)求解问题,迭代了一次,对偶目标解为10,解的状态为Optimal,即找到了一个最优的解决方案,目标函数的最优解为16,对应的变量x2、x3、x4分别取40、30和20。这好像和教材上的解不一样,带入原约束条件都满足,带入目标函数其方案同样是使用了90跟原材料制造了100套钢材。这里似乎出现了第二个最优解,这也是实际问题中经常出现的情况,多个最优解,这里在下一节进行讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容