本教程内容大部分来自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
步骤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
}
// 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";
}
}