Cplex 学习笔记 2 - 第一个Cplex求解的C#程序

本教程内容大部分来自Cplex官方文档,若涉及侵权请联系我删除

介绍教程

.NET框架中的CPLEX支持创建模型,使用数据填充模型,解决问题以及显示解决方案的结果。
本教程通过.NET框架中的Concert Technology介绍CPLEX。它概述了典型应用程序并强调了以下过程:

创建模型

  • 使用数据(按行,按列或非零)填充模型
  • 求解这个模型
  • 求解后显示结果

本章重点介绍使用C#.NET的示例。您还可以在你的Cplex文件夹中随CPLEX 一起提供的VB.NET(.NET框架中的Visual Basic)示例\例子\ SRC \ VB。由于它们的.NET框架,这些VB.NET示例与某些CPLEX用户可能已经熟悉的传统Visual Basic示例不同。

你将要做什么

本教程将指导您在C#.NET中构建和解决一个小规模的线性规划模型。

CPLEX可以与Concert Technology for .NET用户一起使用,这是一个.NET库,允许您独立于用于解决问题的算法来建模优化问题。它提供了一个可扩展的建模层,适用于现成的各种算法。此建模层使您可以更改模型,而无需完全重写应用程序。

要通过CPLEX with Concert Technology for .NET用户找到问题的解决方案,可以使用三阶段方法:describe,model和solve。

  • 第一阶段是用自然语言描述问题。

  • 第二阶段是使用Concert Technology for .NET用户的类和接口来模拟问题。该模型由数据,决策变量和约束组成。决策变量是问题中的未知信息。每个决策变量都有一个可能值的域。约束是对这些决策变量的值组合的限制或限制。该模型还可以包含目标,可以最大化或最小化的表达。

  • 第三阶段是使用Concert Technology for .NET用户的类来解决问题。解决问题包括为每个决策变量找到一个值,同时满足约束条件,并最大化或最小化一个目标(如果一个目标包含在模型中)。

描述

第一步是用自然语言描述问题并回答有关问题的基本问题。

  • 这个问题中的已知信息是什么?也就是说,有哪些数据?

  • 这个问题中的未知信息是什么?也就是说,决策变量是什么?

  • 这个问题有什么限制?也就是说,决策变量的约束是什么?

  • 解决这个问题的目的是什么?也就是说,目标函数是什么?

注意:

虽然这个过程的描述步骤在像这样的简单问题中看似微不足道,但您会发现花时间充分描述更复杂的问题对于创建成功的应用程序至关重要。如果您花时间描述模型,隔离决策变量,约束和目标,您将能够更快速有效地编写应用程序代码。

模型

第二阶段是让您使用Concert Technology for .NET用户的类来构建问题模型。该模型由决策变量和对这些变量的约束组成。这个问题的模型也包含一个目标

解决

第三阶段是您使用CPLEX类的实例寻找解决方案并解决问题。解决问题包括为每个变量找到一个值,同时满足约束并最小化目标。

描述

提出这些问题来充分描述应用程序开发的优化问题。

本教程的目的是查看构建模型的三种不同方法:按行,按列或按非零。在以这些方式之一构建问题模型之后,应用程序优化问题并显示解决方案。

第一步:描述问题

写出问题的自然语言描述并回答以下问题:
对这个问题了解多少?
这个问题中有哪些未知的信息(决策变量)?
决策变量有哪些限制(约束)?
解决这个问题的目的(目标)是什么?

在C#中构建一个小LP问题
以下是该示例优化的问题的传统公式:

Max x1 + 2x2 + 3x3
Subject to.
-x1 + x2 + x3 ≤ 20
x1 - 3x2 + x3 ≤ 30
0 ≤ x1 ≤ 40
0 ≤ x2 ≤ ∞
0 ≤ x3 ≤ ∞

  • 这个问题的决策变量是什么?
    x1,x2,x3

  • 有什么限制?
    -x1 + x2 + x3 ≤ 20
    x1 - 3x2 + x3 ≤ 30
    0 ≤ x1 ≤ 40
    0 ≤ x2 ≤ ∞
    0 ≤ x3 ≤ ∞

  • 目标是什么?

    Max x1 + 2x2 + 3x3

模型

使用Concert Technology for .NET用户类来构建问题的模型。
在编写问题描述之后,可以使用Concert Technology for .NET用户和CPLEX类来构建模型。

第2步:打开文件

打开yourCPLEXhome文件 \实例的\ src \教程\ LPex1lesson.cs 在您的集成开发环境中,例如Microsoft Visual Studio。

第3步:创建模型对象

转到该文件中的注释步骤3,并添加此语句以创建CPLEX 适合您的应用的模型。

Cplex cplex = new Cplex();

该语句创建了一个类的空实例 CPLEX。在接下来的步骤中,您将添加一些方法,使您的应用程序可以使用行,按列或非零数据为数据填充数据。

第4步:按行填充模型

现在转到该文件中的注释步骤4,并添加这些行以创建一个方法,用行按数据填充空模型。

 internal static void PopulateByRow(IMPModeler model,
                                  INumVar[][] var,
                                  IRange[][] rng) {
 double[] lb = {0.0, 0.0, 0.0};
 double[] ub = {40.0, 
                System.Double.MaxValue, 
                System.Double.MaxValue};
 INumVar[] x  = model.NumVarArray(3, lb, ub);
 var[0] = x;

 double[] objvals = {1.0, 2.0, 3.0};
 model.AddMaximize(model.ScalProd(x, objvals));

 rng[0] = new IRange[2];
 rng[0][0] = model.AddLe(model.Sum(model.Prod(-1.0, x[0]),
                                   model.Prod( 1.0, x[1]),
                                   model.Prod( 1.0, x[2])), 20.0);
 rng[0][1] = model.AddLe(model.Sum(model.Prod( 1.0, x[0]),
                                   model.Prod(-3.0, x[1]),
                                   model.Prod( 1.0, x[2])), 30.0);
 }

这些行使用特定于此特定示例的数据填充模型。但是,您可以从它的使用界面中看到IMPModeler 如何将远程约束 s 添加到模型中。IMPModeler 是Concert Technology界面,通常用于构建数学编程(MP)矩阵模型。您将在步骤5和步骤6中再次看到它的用途。

第5步:按列填充模型

转到文件中的注释步骤5,并添加这些行以创建一个方法,用列按数据填充空模型。

 internal static void PopulateByColumn(IMPModeler model,
                            INumVar[][] var,
                            IRange[][] rng) {
  IObjective obj = model.AddMaximize();

  rng[0] = new IRange[2];
  rng[0][0] = model.AddRange(-System.Double.MaxValue, 20.0);
  rng[0][1] = model.AddRange(-System.Double.MaxValue, 30.0);

  IRange r0 = rng[0][0];
  IRange r1 = rng[0][1];

  var[0] = new INumVar[3];
  var[0][0] = model.NumVar(model.Column(obj,  1.0).And(
                           model.Column(r0,  -1.0).And(
                           model.Column(r1,   1.0))),
                           0.0, 40.0);
  var[0][1] = model.NumVar(model.Column(obj,  2.0).And(
                           model.Column(r0,   1.0).And(
                           model.Column(r1,  -3.0))),
                           0.0, System.Double.MaxValue);
  var[0][2] = model.NumVar(model.Column(obj,  3.0).And(
                           model.Column(r0,   1.0).And(
                           model.Column(r1,   1.0))),
                           0.0, System.Double.MaxValue);
 }

同样,这些行使用特定于此问题的数据填充模型。从中他们可以看到如何使用界面IMPModeler 将列添加到空模型。

虽然对于许多示例而言,按行显示可能看起来最简单和自然,但是有些模型按行列出是更自然或更有效的实现方法。例如,网络结构问题通常很适合按列建模。熟悉矩阵代数的读者可以查看该方法populateByColumn 作为的转置 populateByRow 。

在此方法中,创建范围对象以按列建模,仅具有其下限和上限。没有给出变量的表达式,因为在这一点上构建它们是不可能的,因为还没有创建变量。类似地,目标函数仅在其预期的优化意义下创建,并且没有任何表达。

接下来,创建变量并将其安装在现有范围和目标中。这些新创建的变量通过列对象引入范围和目标,列对象在类中实现IColumn。使用这些方法创建此类的对象Cplex.Column,可以与方法链接在一起 IColumn.And 形成聚合 IColumn 对象。

一个 IColumn 用该方法创建的对象 ICplex.Column包含有关如何使用此列将新变量引入现有建模对象的信息。例如,如果OBJ 是一个 IObjective 宾语, cplex.Column(obj,2.0) 创造一个 IColumn 包含在表达式中安装新变量的信息的对象 IObjective 宾语 OBJ 线性系数为 2.0。同样,对于IRange 约束 RNG ,方法调用 cplex.Column(rng,-1.0) 创造一个 IColumn 包含要在表达式中安装新变量的信息的对象 RNG ,作为系数的线性项 -1.0 。

简而言之,当您使用逐列建模方法时,会创建新列并将其作为变量安装在需要它们的所有现有建模对象中。要使用Concert Technology执行此操作,您需要创建一个IColumn 您要在其中安装新变量的每个建模对象的对象,并使用该方法将它们链接在一起 IColumn.And 。

第6步:使用非零填充模型

转到文件中的注释步骤6,并添加这些行以创建一个方法,用非零的数据填充空模型。

internal static void PopulateByNonzero(IMPModeler model,
                             INumVar[][] var,
                             IRange[][] rng) {
  double[]    lb = {0.0, 0.0, 0.0};
  double[]    ub = {40.0, System.Double.MaxValue, System.Double.MaxValue};
  INumVar[] x  = model.NumVarArray(3, lb, ub);
  var[0] = x;

  double[] objvals = {1.0, 2.0, 3.0};
  model.Add(model.Maximize(model.ScalProd(x, objvals)));

  rng[0] = new IRange[2];
  rng[0][0] = model.AddRange(-System.Double.MaxValue, 20.0);
  rng[0][1] = model.AddRange(-System.Double.MaxValue, 30.0);

  rng[0][0].Expr = model.Sum(model.Prod(-1.0, x[0]),
                             model.Prod( 1.0, x[1]),
                             model.Prod( 1.0, x[2]));
  rng[0][1].Expr = model.Sum(model.Prod( 1.0, x[0]),
                             model.Prod(-3.0, x[1]),
                             model.Prod( 1.0, x[2]));
 }

在这些行中,您可以看到如何使用指示约束矩阵的非零的数据填充空模型。这些行首先为目标和没有表达式的范围创建对象。他们还创建没有列的变量; 也就是说,只有边界的变量。然后,这些行在目标,范围和变量上创建表达式,并将表达式添加到模型中。

第7步:添加界面

转到文件中的注释步骤7,并添加这些行以创建一个方法,告诉用户如何调用此应用程序。

 internal static void Usage() {
    System.Console.WriteLine(“usage:   LPex1 <option>”);
    System.Console.WriteLine(“options: -r   build model row by row”);
    System.Console.WriteLine(“options: -c   build model column by column”);
    System.Console.WriteLine(“options: -n   build model nonzero by nonzero”);
 }

步骤8:添加命令评估程序

转到文件中的注释步骤8,并添加这些行以创建一个switch语句,用于评估应用程序用户可能输入的命令。

     switch ( args[0].ToCharArray()[1] ) {
     case ‘r’: PopulateByRow(cplex, var, rng);
               break;
     case ‘c’: PopulateByColumn(cplex, var, rng);
               break;
     case ‘n’: PopulateByNonzero(cplex, var, rng);
               break;
     default:  Usage();
               return;
     }

求解

添加解决问题的应用程序部分。

在声明决策变量并将约束和目标函数添加到模型后,您的应用程序就可以搜索解决方案了。

第9步:搜索解决方案

转到文件中的步骤9,然后添加此行以使应用程序搜索解决方案。

     if ( cplex.Solve() ) {

第10步:显示解决方案

转到文件中的注释步骤10,并添加这些行以使您的应用程序能够显示在步骤9中找到的任何解决方案。

        double[] x     = cplex.GetValues(var[0]);
        double[] dj    = cplex.GetReducedCosts(var[0]);
        double[] pi    = cplex.GetDuals(rng[0]);
        double[] slack = cplex.GetSlacks(rng[0]);
      
        cplex.Output().WriteLine(“Solution status = “ 
                                 + cplex.GetStatus());
        cplex.Output().WriteLine(“Solution value  = “ 
                                 + cplex.ObjValue);
      
        int nvars = x.Length;
        for (int j = 0; j < nvars; ++j) {
           cplex.Output().WriteLine(“Variable  :” 
                                     + j 
                                     +” Value = “ 
                                     + x[j] 
                                     +” Reduced cost = “ 
                                     + dj[j]);
        }
      
        int ncons = slack.Length;
        for (int i = 0; i < ncons; ++i) {
           cplex.Output().WriteLine(“Constraint:” 
                                     + i 
                                     +” Slack = “ 
                                     + slack[i] 
                                     +” Pi = “ 
                                     + pi[i]);
        }
     }

步骤11:将模型保存到文件中

如果要将模型保存为LP格式的文件,请转到应用程序文件中的注释步骤11,然后添加此行。

     cplex.ExportModel(“lpex1.lp”);

如果您以交互方式执行本教程中的步骤,那么现在您可以编译并执行完整的应用程序。

完整程序

// --------------------------------------------------------------------------
// File: LPex1.cs
// Version 12.9.0
// --------------------------------------------------------------------------
// Licensed Materials - Property of IBM
// 5725-A06 5725-A29 5724-Y48 5724-Y49 5724-Y54 5724-Y55 5655-Y21
// Copyright IBM Corporation 2003, 2019. All Rights Reserved.
//
// US Government Users Restricted Rights - Use, duplication or
// disclosure restricted by GSA ADP Schedule Contract with
// IBM Corp.
// --------------------------------------------------------------------------
//
// LPex1.cs - Entering and optimizing an LP problem
//
// Demonstrates different methods for creating a problem.  The user has to
// choose the method on the command line:
//
//    LPex1  -r     generates the problem by adding constraints
//    LPex1  -c     generates the problem by adding variables
//    LPex1  -n     generates the problem by adding expressions
//

using ILOG.Concert;
using ILOG.CPLEX;


public class LPex1 {
   internal static void Usage() {
      System.Console.WriteLine("usage:   LPex1 <option>");
      System.Console.WriteLine("options:       -r   build model row by row");
      System.Console.WriteLine("options:       -c   build model column by column");
      System.Console.WriteLine("options:       -n   build model nonzero by nonzero");
   }

   public static void Main(string[] args) {
      if ( args.Length != 1 || args[0].ToCharArray()[0] != '-' ) {
         Usage();
         return;
      }

      try {
         // Create the modeler/solver object
         Cplex cplex = new Cplex();

     INumVar[][] var = new INumVar[1][];
     IRange[][]  rng = new IRange[1][];

     // Evaluate command line option and call appropriate populate method.
     // The created ranges and variables are returned as element 0 of arrays
     // var and rng.
     switch ( args[0].ToCharArray()[1] ) {
     case 'r': PopulateByRow(cplex, var, rng);
               break;
     case 'c': PopulateByColumn(cplex, var, rng);
               break;
     case 'n': PopulateByNonzero(cplex, var, rng);
               break;
     default:  Usage();
               return;
     }

     // write model to file
     cplex.ExportModel("lpex1.lp");

     // solve the model and display the solution if one was found
     if ( cplex.Solve() ) {
        double[] x     = cplex.GetValues(var[0]);
        double[] dj    = cplex.GetReducedCosts(var[0]);
        double[] pi    = cplex.GetDuals(rng[0]);
        double[] slack = cplex.GetSlacks(rng[0]);

        cplex.Output().WriteLine("Solution status = " + cplex.GetStatus());
        cplex.Output().WriteLine("Solution value  = " + cplex.ObjValue);

        int nvars = x.Length;
        for (int j = 0; j < nvars; ++j) {
           cplex.Output().WriteLine("Variable   " + j +
                                    ": Value = " + x[j] +
                                    " Reduced cost = " + dj[j]);
        }

        int ncons = slack.Length;
        for (int i = 0; i < ncons; ++i) {
           cplex.Output().WriteLine("Constraint " + i +
                                    ": Slack = " + slack[i] +
                                    " Pi = " + pi[i]);
        }
     }
     cplex.End();
  }
  catch (ILOG.Concert.Exception e) {
     System.Console.WriteLine("Concert exception '" + e + "' caught");
  }

}

   // The following methods all populate the problem with data for the following
   // linear program:
   //
   //    Maximize
   //     x1 + 2 x2 + 3 x3
   //    Subject To
   //     - x1 + x2 + x3 <= 20
   //     x1 - 3 x2 + x3 <= 30
   //    Bounds
   //     0 <= x1 <= 40
   //    End
   //
   // using the IMPModeler API

   internal static void PopulateByRow(IMPModeler model,
                                      INumVar[][] var,
                                      IRange[][] rng) {
      double[]  lb      = {0.0, 0.0, 0.0};
      double[]  ub      = {40.0, double.MaxValue, double.MaxValue};
      string[]  varname = {"x1", "x2", "x3"};
      INumVar[] x       = model.NumVarArray(3, lb, ub, varname);
      var[0] = x;

      double[] objvals = {1.0, 2.0, 3.0};
      model.AddMaximize(model.ScalProd(x, objvals));

      rng[0] = new IRange[2];
      rng[0][0] = model.AddLe(model.Sum(model.Prod(-1.0, x[0]),
                                        model.Prod( 1.0, x[1]),
                                        model.Prod( 1.0, x[2])), 20.0, "c1");
      rng[0][1] = model.AddLe(model.Sum(model.Prod( 1.0, x[0]),
                                        model.Prod(-3.0, x[1]),
                                        model.Prod( 1.0, x[2])), 30.0, "c2");
   }

   internal static void PopulateByColumn(IMPModeler model,
                                         INumVar[][] var,
                                         IRange[][] rng) {
      IObjective obj = model.AddMaximize();

      rng[0] = new IRange[2];
      rng[0][0] = model.AddRange(-double.MaxValue, 20.0, "c1");
      rng[0][1] = model.AddRange(-double.MaxValue, 30.0, "c2");

      IRange r0 = rng[0][0];
      IRange r1 = rng[0][1];

      var[0] = new INumVar[3];
      var[0][0] = model.NumVar(model.Column(obj,  1.0).And(
                               model.Column(r0,  -1.0).And(
                               model.Column(r1,   1.0))),
                               0.0, 40.0, "x1");
      var[0][1] = model.NumVar(model.Column(obj,  2.0).And(
                               model.Column(r0,   1.0).And(
                               model.Column(r1,  -3.0))),
                               0.0, double.MaxValue, "x2");
      var[0][2] = model.NumVar(model.Column(obj,  3.0).And(
                              model.Column(r0,   1.0).And(
                               model.Column(r1,   1.0))),
                               0.0, double.MaxValue, "x3");
   }

   internal static void PopulateByNonzero(IMPModeler model,
                                          INumVar[][] var,
                                          IRange[][] rng) {
      double[]  lb = {0.0, 0.0, 0.0};
      double[]  ub = {40.0, double.MaxValue, double.MaxValue};
      INumVar[] x  = model.NumVarArray(3, lb, ub);
      var[0] = x;

      double[] objvals = {1.0, 2.0, 3.0};
      model.Add(model.Maximize(model.ScalProd(x, objvals)));

      rng[0] = new IRange[2];
      rng[0][0] = model.AddRange(-double.MaxValue, 20.0);
      rng[0][1] = model.AddRange(-double.MaxValue, 30.0);

      rng[0][0].Expr = model.Sum(model.Prod(-1.0, x[0]),
                                 model.Prod( 1.0, x[1]),
                                 model.Prod( 1.0, x[2]));
      rng[0][1].Expr = model.Sum(model.Prod( 1.0, x[0]),
                                 model.Prod(-3.0, x[1]),
                                 model.Prod( 1.0, x[2]));
      x[0].Name = "x1";
      x[1].Name = "x2";
      x[2].Name = "x3";

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