简单模型

一个简单的具体Pyomo模型

在Pyomo中,无论是声明为抽象的模型还是声明为具体的模型,都可以获得相同的灵活行为;然而,我们将重点放在一个简单的具体示例上,其中数据被硬编码到模型文件中。Python程序员会很快意识到数据可能来自其他来源。

给定上一节中的以下模型:

\begin{array}{ll} \min & 2 x_1 + 3 x_2\\ \mathrm{s.t.} & 3 x_1 + 4 x_2 \geq 1\\ & x_1, x_2 \geq 0 \end{array}

这可以作为一个具体的模型实现如下:

import pyomo.environ as pyo

model = pyo.ConcreteModel()

model.x = pyo.Var([1,2], domain=pyo.NonNegativeReals)

model.OBJ = pyo.Objective(expr = 2*model.x[1] + 3*model.x[2])

model.Constraint1 = pyo.Constraint(expr = 3*model.x[1] + 4*model.x[2] >= 1)

尽管规则函数也可以用于指定约束和目标,但在本例中我们使用了仅在具体模型中可用的expr选项。此选项提供了表达式的直接规范。

一个简单的抽象Pyomo模型

我们重复上一节的抽象模型:

\begin{array}{lll} \min & \sum_{j=1}^n c_j x_j &\\ \mathrm{s.t.} & \sum_{j=1}^n a_{ij} x_j \geq b_i & \forall i = 1 \ldots m\\ & x_j \geq 0 & \forall j = 1 \ldots n \end{array}

在Pyomo中实现这一点的一种方法如下所示:

import pyomo.environ as pyo

model = pyo.AbstractModel()

model.m = pyo.Param(within=pyo.NonNegativeIntegers)
model.n = pyo.Param(within=pyo.NonNegativeIntegers)

model.I = pyo.RangeSet(1, model.m)
model.J = pyo.RangeSet(1, model.n)

model.a = pyo.Param(model.I, model.J)
model.b = pyo.Param(model.I)
model.c = pyo.Param(model.J)

# the next line declares a variable indexed by the set J
model.x = pyo.Var(model.J, domain=pyo.NonNegativeReals)

def obj_expression(m):
    return pyo.summation(m.c, m.x)

model.OBJ = pyo.Objective(rule=obj_expression)

def ax_constraint_rule(m, i):
    # return the expression for the constraint for i
    return sum(m.a[i,j] * m.x[j] for j in m.J) >= m.b[i]

# the next line creates one constraint for each member of the set model.I
model.AxbConstraint = pyo.Constraint(model.I, rule=ax_constraint_rule)

注意

Python 是逐行解释的。对于需要跨越多行的 Python 语句,使用行继续字符 \(反斜杠)。在 Python 中,缩进具有意义并且必须保持一致。例如,函数定义内的行必须缩进,Python 使用缩进的结束来标记定义的结束。

我们现在将检查这个示例中的代码行。 每个Pyomo模型都需要第一行导入语句。它的目的是让Python识别Pyomo使用的符号。

import pyomo.environ as pyo

模型的声明也是必需的。使用名称model并不是必须的。几乎可以使用任何名称,但在我们的大多数示例中,我们将使用名称model。在这个例子中,我们声明它将是一个抽象模型。

model = pyo.AbstractModel()

我们使用Pyomo的Param组件声明参数\(m\)\(n\)。这个组件可以接受多种参数;这个例子展示了使用within选项,该选项由Pyomo用于验证分配给参数的数据值。如果没有给出这个选项,那么Pyomo不会反对任何类型的数据被分配给这些参数。实际上,分配一个非负整数的值将会导致错误。

model.m = pyo.Param(within=pyo.NonNegativeIntegers)
model.n = pyo.Param(within=pyo.NonNegativeIntegers)

虽然不强制要求,但定义索引集是很方便的。在这个例子中,我们使用RangeSet组件来声明这些集将是从1开始,到由参数model.mmodel.n指定的值结束的整数序列。

model.I = pyo.RangeSet(1, model.m)
model.J = pyo.RangeSet(1, model.n)

系数和右侧数据被定义为索引参数。当集合作为参数传递给Param组件时,它们表示该集合将索引参数。

model.a = pyo.Param(model.I, model.J)
model.b = pyo.Param(model.I)
model.c = pyo.Param(model.J)

Python解释为模型一部分的下一行声明了变量\(x\)Var组件的第一个参数是一个集合,因此它被定义为变量的索引集。在这种情况下,变量只有一个索引集,但可以使用多个集合,就像声明参数model.a时的情况一样。第二个参数指定了变量的域。此信息是模型的一部分,当提供数据并解决模型时,将传递给求解器。指定NonNegativeReals域实现了变量必须大于或等于零的要求。

# the next line declares a variable indexed by the set J
model.x = pyo.Var(model.J, domain=pyo.NonNegativeReals)

注意

在Python中,因此在Pyomo中,井号后的任何文本都被视为注释。

在抽象模型中,Pyomo 表达式通常通过使用 Python def 语句定义的函数提供给目标和约束声明。def 语句为函数及其参数建立名称。当 Pyomo 使用函数获取目标或约束表达式时,它总是将模型(即自身)作为第一个参数传递,因此在 Pyomo 中声明此类函数时,模型始终是第一个形式参数。如果需要,其他参数紧随其后。由于求和是优化模型中极为常见的部分,Pyomo 提供了一个灵活的函数来适应它。当给定两个参数时,summation() 函数返回一个表达式,表示两个参数在其索引上的乘积之和。当然,这仅在两个参数具有相同索引时有效。如果只给定一个参数,则返回该参数在所有索引上的和的表达式。因此,在此示例中,当 summation() 传递参数 m.c, m.x 时,它返回表达式 \(\sum_{j=1}^{n}c_{j} x_{j}\) 的内部表示。

def obj_expression(m):
    return pyo.summation(m.c, m.x)

要声明一个目标函数,使用名为Objective的Pyomo组件。rule参数给出了返回目标表达式的函数名称。默认的sense是最小化。对于最大化,必须使用sense=pyo.maximize参数。声明的名称,在本例中为OBJ,出现在一些报告中,并且几乎可以是任何名称。

model.OBJ = pyo.Objective(rule=obj_expression)

约束的声明是类似的。声明一个函数来生成约束表达式。在这种情况下,可能会有多个相同形式的约束,因为我们在表达式 \(\sum_{j=1}^n a_{ij} x_j \geq b_i \;\;\forall i = 1 \ldots m\) 中通过 \(i\) 索引约束,这表明我们需要为从1到 \(m\) 的每个 \(i\) 值提供一个约束。为了通过 \(i\) 参数化表达式,我们将其作为声明约束表达式的函数的形式参数。从技术上讲,我们可以使用任何东西作为这个参数,但这可能会引起混淆。在这种情况下,使用 i 来表示 \(i\) 似乎是合理的。

def ax_constraint_rule(m, i):
    # return the expression for the constraint for i
    return sum(m.a[i,j] * m.x[j] for j in m.J) >= m.b[i]

注意

在Python中,索引位于方括号内,函数参数位于圆括号内。

为了声明使用此表达式的约束,我们使用Pyomo的Constraint组件,该组件接受多种参数。在这种情况下,我们的模型指定我们可以有多个相同形式的约束,并且我们已经创建了一个集合model.I,这些约束可以在该集合上进行索引,因此这是约束声明的第一个参数。下一个参数给出了将用于生成约束表达式的规则。总的来说,这个约束声明表示将创建一个由集合model.I索引的约束列表,并且对于model.I的每个成员,将调用函数ax_constraint_rule,并将模型对象以及model.I的成员传递给它。

# the next line creates one constraint for each member of the set model.I
model.AxbConstraint = pyo.Constraint(model.I, rule=ax_constraint_rule)

在面向对象的视角下,我们会说model对象是AbstractModel类的一个实例,而model.J是一个包含在这个模型中的Set对象。Pyomo中的许多建模组件可以选择性地指定为索引组件:使用一个或多个值引用的组件集合。在这个例子中,参数model.c是用集合model.J进行索引的。

为了使用这个模型,必须为参数的值提供数据。这里是一个提供数据的文件(以AMPL的“.dat”格式)。

# one way to input the data in AMPL format
# for indexed parameters, the indexes are given before the value

param m := 1 ;
param n := 2 ;

param a :=
1 1 3
1 2 4
;

param c:=
1 2
2 3
;

param b := 1 1 ;

有多种格式可以用于向Pyomo模型提供数据,但AMPL格式非常适合我们的目的,因为它包含了数据元素的名称以及数据本身。在AMPL数据文件中,井号后的文本被视为注释。行通常不重要,但语句必须以分号结束。

对于这个特定的数据文件,有一个约束条件,因此model.m的值将为一,并且有两个变量(即向量model.x有两个元素),因此model.n的值将为二。这两个赋值是通过标准赋值完成的。请注意,在AMPL格式输入中,模型的名称被省略了。

param m := 1 ;
param n := 2 ;

只有一个约束,因此model.a只需要两个值。在AMPL格式中为数组和向量赋值时,一种方法是提供索引和值。行1 2 4导致model.a[1,2]获得值4。由于model.c只有一个索引,因此只需要一个索引值,例如,行1 2导致model.c[1]获得值2。在AMPL格式的数据文件中,换行通常不重要,因此model.b的单个索引的赋值在一行上给出,因为这样易于阅读。

param a :=
 1 1 3
 1 2 4
 ;

 param c:=
 1 2
 2 3
 ;

 param b := 1 1 ;

符号索引集

在使用Pyomo(或任何其他AML)时,通过使用包含字符串的索引集而不是由\(1,\ldots,m\)或从1到\(n\)的求和隐含的索引集,以更抽象的方式编写抽象模型是很方便的。这样做时,集合的大小由输入隐含,而不是直接指定。此外,索引条目可能没有真正的顺序。通常,在同一模型中需要混合使用整数、索引和字符串作为索引。为了开始说明一般索引,考虑一个稍微不同的Pyomo实现,我们刚刚展示的模型。

#  ___________________________________________________________________________
#
#  Pyomo: Python Optimization Modeling Objects
#  Copyright (c) 2008-2024
#  National Technology and Engineering Solutions of Sandia, LLC
#  Under the terms of Contract DE-NA0003525 with National Technology and
#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
#  rights in this software.
#  This software is distributed under the 3-clause BSD License.
#  ___________________________________________________________________________

# abstract2.py


from pyomo.environ import *

model = AbstractModel()

model.I = Set()
model.J = Set()

model.a = Param(model.I, model.J)
model.b = Param(model.I)
model.c = Param(model.J)

# the next line declares a variable indexed by the set J
model.x = Var(model.J, domain=NonNegativeReals)


def obj_expression(model):
    return summation(model.c, model.x)


model.OBJ = Objective(rule=obj_expression)


def ax_constraint_rule(model, i):
    # return the expression for the constraint for i
    return sum(model.a[i, j] * model.x[j] for j in model.J) >= model.b[i]


# the next line creates one constraint for each member of the set model.I
model.AxbConstraint = Constraint(model.I, rule=ax_constraint_rule)

要获取相同的实例化模型,可以使用以下数据文件。

# abstract2a.dat AMPL format

set I := 1 ;
set J := 1 2 ;

param a :=
1 1 3
1 2 4
;

param c:=
1 2
2 3
;

param b := 1 1 ;

然而,这个模型也可以使用有意义的索引为相同一般形式的问题提供不同的数据。

# abstract2.dat AMPL data format

set I := TV Film ;
set J := Graham John Carol ;

param a :=
TV  Graham 3
TV John 4.4
TV Carol 4.9
Film Graham 1
Film John 2.4
Film Carol 1.1
;

param c := [*]
 Graham 2.2
 John 3.1416
 Carol 3
;

param b := TV 1 Film 1 ;

解决简单示例

Pyomo 支持建模和脚本编写,但不会自动安装求解器。为了求解模型,计算机上必须安装一个求解器。如果有求解器,则可以使用 pyomo 命令来求解问题实例。

假设名为 glpk(也称为 glpsol)的求解器已安装在计算机上。进一步假设抽象模型位于名为 abstract1.py 的文件中,其数据文件位于名为 abstract1.dat 的文件中。在命令提示符下,如果两个文件都在当前目录中,可以使用以下命令获得解决方案:

pyomo solve abstract1.py abstract1.dat --solver=glpk

由于glpk是默认的求解器,实际上没有必要指定它,因此可以省略--solver选项。

注意

命令行选项名称前有两个破折号,例如 solver

继续这个例子,如果安装了CPLEX,那么它可以被列为求解器。使用CPLEX求解的命令是

pyomo solve abstract1.py abstract1.dat --solver=cplex

这将在屏幕上产生以下输出:

[    0.00] Setting up Pyomo environment
[    0.00] Applying Pyomo preprocessing actions
[    0.07] Creating model
[    0.15] Applying solver
[    0.37] Processing results
Number of solutions: 1
Solution Information
Gap: 0.0
Status: optimal
Function Value: 0.666666666667
Solver results file: results.json
[    0.39] Applying Pyomo postprocessing actions
[    0.39] Pyomo Finished

方括号中的数字表示每个步骤所需的时间。结果被写入名为results.json的文件中,该文件具有特殊的结构,使其对后处理非常有用。要查看写入屏幕的结果摘要,请使用--summary选项:

pyomo solve abstract1.py abstract1.dat --solver=cplex --summary

要查看Pyomo命令行选项的列表,请使用:

pyomo solve --help

注意

help之前有两个破折号。

对于一个具体的模型,Pyomo命令行上没有指定数据文件。