1. 简介

调用CPLEX构建数学模型即是生成决策变量、目标函数和约束条件的过程,CPLEX有行式建模(row-wise modeling)和列式建模(column-wise modeling)两种方式来实现建模。

行式建模即是将数学模型按行输入到CPLEX对象中,此方式适用于构建决策变量数固定的模型,也便于在算法动态添加约束。列式建模是将数学模型按列输入到CPLEX对象中,每个决策变量对应一列,逐步生成变量及其对应列添加到CPLEX对象中完成建模,此方式便于在算法中动态添加变量,如列生成算法。

下面是一个简单的线下规划模型,后文将以此模型为例进行演示两种建模方式。本文使用代码来c#代码,其他编程语言也可参考,如Java。

min z = 5 x 1 + 6 x 2 + 7 x 3 + 8 x 4 s . t . { x 1 + 2 x 2 + x 3 + x 4 160 2 x 1 + 4 x 3 + 2 x 4 = 200 3 x 1 + x 2 + x 3 + 2 x 4 180 x 1 , x 2 , x 3 , x 4 0 \text{min} \quad z = 5x_1 + 6x_2 + 7x_3 + 8x_4\\ s.t.\begin{cases} x_1 + 2x_2 + x_3 + x_4 &\geq 160 \\ 2x_1 + 4x_3 + 2x_4 &= 200 \\ 3x_1 + x_2 + x_3 + 2x_4 &\leq 180 \\ x_1,x_2,x_3,x_4 \geq0 \end{cases}
// 模型对应的数据:
double[] objCoef = new double[]{5, 6, 7, 8};
double[,] consCoef = new double[,]{{1, 2, 1, 1},
                                   {2, 0, 4, 2},
                                   {3, 1, 1, 2}};
double[] consValue = new double[]{160, 200, 180};

2. 行式建模 (row-wise)

2.1 行式建模步骤

1、创建CPLEX模型对象

Cplex model = new Cplex();

2、创建决策变量

先声明一个变量数组,然后通过CPLEX对象NumVarNumVarArray方法创建所有决策变量,该方法有多个重载,详情参考官方帮助手册。此处NumVar一次仅生成一个决策变量,输入参数分别为(lb, ub, 变量类别, 变量名称);而NumVarArray可通过输入矩阵形式参数一次性生成所有变量,此处未演示。

INumVar[] x = new INumVar[objCoef.Length];
for (int j = 0; j < x.Length; j++) {
    x[j] = model.NumVar(0, double.MaxValue, NumVarType.Float, "x["+j+"]");

3、创建目标函数

创建目标函数和约束条件都是基于CPLEX表达式创建的。首先利用model.NumExpr方法创建一个空的表达式;然后把表达式补充完整,表达式中涉及的加减乘除等运算必须由CPLEX对象内置的方法来进行,如此处的乘法运算model.Sum和加法运算model.Prod;最后将表达式添加为CPLEX对象的目标函数。方法2通过向量数量积来创建更为方便。

//创建方法1
INumExpr obj = model.NumExpr();
for (int j = 0; j < x.Length; j++) {
    obj = model.Sum(obj, model.Prod(objCoef[j], x[j]));
model.AddMinimize(obj);
//创建方法2
model.AddMinimize(model.ScalProd(objCoef,x));

4、创建约束条件

待约束条件表达式创建后,需要调用CPLEX对象的大于等于AddGe、小于等于AddLe或等于AddEq方法将其添加到模型中,此处3个约束条件类型都不一样,3个方法均用了一遍。

for (int i = 0; i < consValue.Length; i++) {
    INumExpr expr = model.NumExpr();
    for (int j = 0; j < x.Length; j++) {
        expr = model.Sum(expr, model.Prod(consCoef[i, j], x[j]));
    if (i==0)
        model.AddGe(expr, consValue[i]);
    else if(i==1)
        model.AddEq(expr, consValue[i]);
        model.AddLe(expr, consValue[i]);

5、求解模型

待模型创建完毕后,调用Solve方法求解模型,并输出结果。

model.Solve();
Console.WriteLine("Solution status: " + model.GetStatus());
Console.WriteLine("Objective vaule: " + model.ObjValue);
for (int j = 0; j < x.Length; j++) {
    Console.WriteLine("x["+j+"]="+model.GetValue(x[j]));

2.3 整体代码

static void Main(string[] args) {
    // data
    double[] objCoef = new double[] { 5, 6, 7, 8 };
    double[,] consCoef = new double[,]{{1, 2, 1, 1},
                           {2, 0, 4, 2},
                           {3, 1, 1, 2}};
    double[] consValue = new double[] { 160, 200, 180 };
    // build model
    Cplex model = new Cplex();
    INumVar[] x = new INumVar[4];
    for (int j = 0; j < x.Length; j++) {
        x[j] = model.NumVar(0, double.MaxValue, NumVarType.Float, "x[" + j + "]");
    INumExpr obj = model.NumExpr();
    for (int j = 0; j < x.Length; j++) {
        obj = model.Sum(obj, model.Prod(objCoef[j], x[j]));
    model.AddMinimize(obj);
    for (int i = 0; i < consValue.Length; i++) {
        INumExpr expr = model.NumExpr();
        for (int j = 0; j < x.Length; j++) {
            expr = model.Sum(expr, model.Prod(consCoef[i, j], x[j]));
        if (i == 0)
            model.AddGe(expr, consValue[i]);
        else if (i == 1)
            model.AddEq(expr, consValue[i]);
            model.AddLe(expr, consValue[i]);
    // solve the model
    model.Solve();
    Console.WriteLine("Solution status: " + model.GetStatus());
    Console.WriteLine("Objective vaule: " + model.ObjValue);
    for (int j = 0; j < x.Length; j++) {
        Console.WriteLine("x["+j+"]="+model.GetValue(x[j]));

输出结果:

Solution status: Optimal
Objective vaule: 680
x[0]=0
x[1]=55
x[2]=50
x[3]=0

3. 列式建模 (column-wise)

3.1 关键对象

列式建模逐步添加模型的列(包括目标函数和约束条件系数)完成建模的,与行式建模有很多大不同。在CPLEX中要首先定义模型的目标函数对象(Objective object)和约束条件对象(Range object),然后借助列对象(Column object)将每一列的系数放入目标函数和约束条件之中,最后生成该列对应的决策变量。

1、列对象 Column object

以下是CPLEX帮助文档中关于列对象的介绍,由于没有针对c#的详细文档,此处放置的是Java的帮助文档,但不影响对这个概念的学习。

从文档可以看到,column方法功能是将新建变量插入建模对象中去,这些建模对象包括目标函数、约束条件,并返回一个包含已插入变量的列对象。一列包含多个变量(多个系数不同的同一决策变量),需要通过and方法将创建的所有column连接起来,最终形成一个完整的列。最后,通过调用unmVar,以得到的列对象为参数生成模型的决策变量,同时这个决策变量会自动添加到所创建的目标函数和约束条件中去。

IloColumn (CPLEX Java API Reference Manual)

Objects of class IloColumn create a variable to use in column-wise modeling. In column-wise modeling, newly constructed variables are inserted into existing modeling objects. The procedure for column-wise modeling is as follows.

Start from an existing set of modeling objects of these classes: IloObjective, IloRange, and IloLPMatrix.

For each modeling object affected by the addition of the new variable, call the method IloMPModeler.column with the object as an argument, along with the other arguments needed to install a new variable in the existing modeling objects. See the documentation of the methods for the details of these arguments. Each of these methods returns an object of type IloColumn that contains information about how to add a new variable to the modeling object for which the IloMPModeler.column method has been called.

The column objects can then be linked to an aggregate column object by the method IloColumn.and. This aggregate object contains information about how to add a new variable to all of the modeling objects represented by its parts.

The column object constructed this way is now ready to be used to create a new variable. This is done by passing the column object as an argument to the constructor methods for variables, for example IloMPModeler.numVar() or IloMPModeler.intVar(). The newly created variable will immediately be part of the existing modeling objects that have been used to construct the column object.

2、约束条件对象 Range object

约束条件对象即是存储模型约束条件信息的对象,行式建模中直接通过表达式创建了约束条件对象放入模型,无需用变量将其引用,在列式建模因为需要动态更改约束条件,因此需要创建变量将约束对象引用起来。创建约束对象的方法有若干,如帮助文档所示。

IloRange (CPLEX Java API Reference Manual)

This is the interface for modeling objects representing ranged constraints of the format lb <= expr <= ub. lb and ub are double values, referred to as the lower bound and upper bound of the ranged constraint, and expr is an expression. Values +- infinity can be used as bounds. This allows you to use IloRange objects to represent more commonly used constraints:

  • for expr == rhs, set `lb = ub = rhs
  • for expr == rhs, set lb = ub = rhs
  • for expr <= rhs, set lb = -infinity and ub = rhs
  • for expr >= rhs, set lb = rhs and ub = infinity
  • IloRange objects can be constructed using a variety of methods defined for IloModeler: addRange(), addEq(), addLe(), addGe(), range(), eq(), le(), and ge(). The methods of IloRange allow you to query the expressions and bounds of a ranged constraint and to change its bounds or replace the expression.

    3.2 列式建模步骤

    1、创建CPLEX模型对象

    Cplex model = new Cplex();
    

    2、创建关键对象

    创建空的目标函数、决策变量和约束条件对象,对约束条件的左右界进行限定。注意,此时决策变量x是完全空的,目标函数obj和约束条件cons已经有实例,但未对实例进行详细定义。这里数组是固定集合,若需动态添加变量的,则将数组改为List数据格式,方便后续动态添加元素。

    IObjective obj = model.AddMinimize();
    INumVar[] x = new INumVar[objCoef.Length];
    IRange[] cons = new IRange[consValue.Length];
    for (int i = 0; i < consValue.Length; i++) {
        if (i==0)
            cons[i] = model.AddRange(consValue[i], double.MaxValue);
        else if (i==1)
            cons[i] = model.AddRange(consValue[i], consValue[i]);
            cons[i] = model.AddRange(-double.MaxValue, consValue[i]);
    

    3、按列创建模型

    通过列对象创建每一列,并生成每列对应的决策变量。model.Column(obj, objCoef[j])表示将系数objCoef[j]插入到目标函数obj之中。

    for (int j = 0; j < x.Length; j++) {
        Column column = model.Column(obj, objCoef[j]);
        for (int i = 0; i < consValue.Length; i++) {
            column = column.And(model.Column(cons[i], consCoef[i,j]));
        x[j] = model.NumVar(column, 0, double.MaxValue, "x[" + j + "]");
    

    4、求解模型

    model.Solve();
    Console.WriteLine("Solution status: " + model.GetStatus());
    Console.WriteLine("Objective vaule: " + model.ObjValue);
    for (int j = 0; j < x.Length; j++) {
        Console.WriteLine("x["+j+"]="+model.GetValue(x[j]));
    

    3.3 整体代码

    static void Main(string[] args) {
        // data
        double[] objCoef = new double[] { 5, 6, 7, 8 };
        double[,] consCoef = new double[,]{{1, 2, 1, 1},
                               {2, 0, 4, 2},
                               {3, 1, 1, 2}};
        double[] consValue = new double[] { 160, 200, 180 };
        // build model
        Cplex model = new Cplex();
        IObjective obj = model.AddMinimize();
        INumVar[] x = new INumVar[objCoef.Length];
        IRange[] cons = new IRange[consValue.Length];
        for (int i = 0; i < consValue.Length; i++) {
            if (i==0)
                cons[i] = model.AddRange(consValue[i], double.MaxValue);
            else if (i==1)
                cons[i] = model.AddRange(consValue[i], consValue[i]);
                cons[i] = model.AddRange(-double.MaxValue, consValue[i]);
        for (int j = 0; j < x.Length; j++) {
            Column column = model.Column(obj, objCoef[j]);
            for (int i = 0; i < consValue.Length; i++) {
                column = column.And(model.Column(cons[i], consCoef[i,j]));
            x[j] = model.NumVar(column, 0, double.MaxValue, "x[" + j + "]");
        // solve the model
        model.Solve();
        Console.WriteLine("Solution status: " + model.GetStatus());
        Console.WriteLine("Objective vaule: " + model.ObjValue);
        for (int j = 0; j < x.Length; j++) {
            Console.WriteLine("x["+j+"]="+model.GetValue(x[j]));
    

    输出结果:

    Solution status: Optimal
    Objective vaule: 680
    x[0]=0
    x[1]=55
    x[2]=50
    x[3]=0
    

    本文介绍了CPLEX行式和列式建模方法,两种方法各有应用场景,若需要详细了解CPLEX中各种建模技巧、函数方法,请参考CPLEX帮助文档中的API说明和案例。

    分类:
    后端