表达式
在本节中,我们以两种方式使用“表达式”这个词:首先是这个词的一般意义,其次是用来描述一类名为Expression的Pyomo对象,如表达式对象小节中所述。
生成表达式的规则
目标和约束都使用规则来生成表达式。这些是返回适当表达式的Python函数。这些是一等函数,可以访问全局数据以及传入的数据,包括模型对象。
对模型元素的操作会产生表达式,这在我们迄今为止看到的约束等表达式中似乎很自然。也可以构建表达式。以下示例说明了这一点,同时以名为switch的Python变量的形式引用了全局Python数据:
switch = 3
model.A = RangeSet(1, 10)
model.c = Param(model.A)
model.d = Param()
model.x = Var(model.A, domain=Boolean)
def pi_rule(model):
accexpr = summation(model.c, model.x)
if switch >= 2:
accexpr = accexpr - model.d
return accexpr >= 0.5
PieSlice = Constraint(rule=pi_rule)
在这个例子中,生成的约束取决于名为 switch 的 Python 变量的值。如果该值大于或等于 2,则约束为 summation(model.c, model.x) - model.d >= 0.5;否则,model.d 项不存在。
警告
因为模型元素生成的是表达式,而不是值,所以在抽象模型中,以下内容不会按预期工作!
model.A = RangeSet(1, 10)
model.c = Param(model.A)
model.d = Param()
model.x = Var(model.A, domain=Boolean)
def pi_rule(model):
accexpr = summation(model.c, model.x)
if model.d >= 2: # NOT in an abstract model!!
accexpr = accexpr - model.d
return accexpr >= 0.5
PieSlice = Constraint(rule=pi_rule)
问题在于model.d >= 2产生的是一个表达式,而不是它的评估值。应该使用if value(model.d) >= 2
注意
Pyomo 支持非线性表达式,并且可以调用非线性求解器,如 Ipopt。
分段线性表达式
Pyomo 提供了添加分段约束的功能,形式为 y=f(x),适用于多种形式的函数 f。
除了SOS2、BIGM_SOS1、BIGM_BIN之外的分段类型是按照论文[VAN10]中描述的方式实现的。
约束的声明有两种基本形式:
# model.pwconst = Piecewise(indexes, yvar, xvar, **Keywords)
# model.pwconst = Piecewise(yvar,xvar,**Keywords)
其中 pwconst 可以被替换为适合应用程序的名称。选择取决于 x 和 y 变量是否被索引。如果是这样,它们必须具有相同的索引集,并且这些集作为第一个参数给出。
关键词:
pw_pts={ },[ ],( )
一个列表的字典(其中键是索引集)或单个列表(用于非索引情况或当所有索引使用相同的断点集时),用于定义分段线性函数的域断点集。
注意
pw_pts 始终是必需的。这些提供了分段函数的断点,并且预期完全跨越自变量的边界。
pw_repn=<选项>
指示要使用的分段表示类型。这可能对求解器性能产生重大影响。选项:(默认“SOS2”)
“SOS2” - 使用sos2约束的标准表示。
“BIGM_BIN” - 使用二进制变量的BigM约束。理论上最紧的M值会自动确定。
“BIGM_SOS1” - 使用sos1变量的BigM约束。理论上最紧的M值会自动确定。
“DCC” - 分散凸组合模型。
“DLOG” - 对数分解凸组合模型。
“CC” - 凸组合模型。
“LOG” - 对数分支凸组合。
“MC” - 多项选择模型。
“INC” - 增量(delta)方法。
注意
除了两个BIGM选项外,所有选项都支持阶跃函数。请参考‘force_pw’选项。
pw_constr_type=
指示分段函数的绑定类型。选项:
“UB” - y 变量由分段函数上界。
“LB” - y 变量由分段函数下界限制。
“EQ” - y 变量等于分段函数。
f_rule=f(model,i,j,…,x), { }, [ ], ( )
一个返回数值的对象,该数值是与每个分段域点对应的范围值。对于函数,第一个参数必须是Pyomo模型。最后一个参数是函数评估的域值(不是Pyomo
Var)。中间参数是Piecewise组件的相应索引(如果有)。否则,该对象可以是列表/元组的字典(键与索引集相同)或单个列表/元组(当未使用索引集或所有索引使用相同的分段函数时)。示例:# A function that changes with index def f(model, j, x): if j == 2: return x**2 + 1.0 else: return x**2 + 5.0 # A nonlinear function f = lambda model, x: exp(x) + value(model.p) # A step function f = [0, 0, 1, 1, 2, 2]
force_pw=True/False
使用给定的函数规则和pw_pts,实现了对凸性/凹性的检查。如果(1)函数是凸的并且分段约束是下界,或者(2)函数是凹的并且分段约束是上界,那么分段约束将被线性约束替代。设置‘force_pw=True’将强制使用原始的分段约束,即使这两种情况之一适用。
warning_tol=
为了帮助调试,当连续的分段斜率彼此在
范围内时,会打印一个警告。默认值为1e-8 warn_domain_coverage=True/False
当域变量的可行区域未被分段断点完全覆盖时,打印警告。默认值=True
unbounded_domain_var=True/False
允许使用无界或部分有界的Pyomo Var作为域变量。默认=False
注意
这并不意味着将构造无界的分段段。最外层的分段断点将在每个索引处限制域变量。然而,Var属性的.lb和.ub将不会被修改。
这里是一个赋值给Python字典变量的例子,该变量包含分段约束的关键字:
kwds = {'pw_constr_type': 'EQ', 'pw_repn': 'SOS2', 'sense': maximize, 'force_pw': True}
这里是一个基于之前在符号索引集中给出的例子的简单示例。在这个新例子中,目标函数是c乘以x的四次方的和。在这个例子中,关键字直接传递给Piecewise函数,而没有分配给字典变量。x变量的上限是随意选择的,只是为了制作这个示例。需要注意的是,将作为分段约束中的自变量出现的变量必须有界限。
# ___________________________________________________________________________
#
# 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.
# ___________________________________________________________________________
# abstract2piece.py
# Similar to abstract2.py, but the objective is now c times x to the fourth power
from pyomo.environ import *
model = AbstractModel()
model.I = Set()
model.J = Set()
Topx = 6.1 # range of x variables
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, bounds=(0, Topx))
model.y = Var(model.J, domain=NonNegativeReals)
# to avoid warnings, we set breakpoints at or beyond the bounds
PieceCnt = 100
bpts = []
for i in range(PieceCnt + 2):
bpts.append(float((i * Topx) / PieceCnt))
def f4(model, j, xp):
# we not need j, but it is passed as the index for the constraint
return xp**4
model.ComputeObj = Piecewise(
model.J, model.y, model.x, pw_pts=bpts, pw_constr_type='EQ', f_rule=f4
)
def obj_expression(model):
return summation(model.c, model.y)
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)
一个更高级的示例在BuildAction and BuildCheck中的abstract2piecebuild.py提供。
Expression 对象
Pyomo Expression 对象与 Param 组件非常相似
(带有 mutable=True),不同之处在于基础值可以是数字
常量或 Pyomo 表达式。以下是在 AbstractModel 中表达式
对象的示例。创建了一个索引集为数字 1、2、3 的表达式对象,
并将其初始化为模型变量 x 乘以索引。稍后在模型文件中,为了说明
如何操作,表达式被更改,但仅针对第一个索引更改为
x 的平方。
model = ConcreteModel()
model.x = Var(initialize=1.0)
def _e(m, i):
return m.x * i
model.e = Expression([1, 2, 3], rule=_e)
instance = model.create_instance()
print(value(instance.e[1])) # -> 1.0
print(instance.e[1]()) # -> 1.0
print(instance.e[1].value) # -> a pyomo expression object
# Change the underlying expression
instance.e[1].value = instance.x**2
# ... solve
# ... load results
# print the value of the expression given the loaded optimal solution
print(value(instance.e[1]))
另一种方法是创建可能操作模型对象的Python函数。例如,如果你定义一个函数
def f(x, p):
return x + p
你可以使用或不使用Pyomo建模组件作为参数来调用此函数。例如,f(2,3)将返回一个数字,而f(model.x, 3)由于操作符重载将返回一个Pyomo表达式。
如果你采用这种方法,你应该注意到,在任何使用Pyomo表达式生成另一个表达式的地方(例如,f(model.x, 3) + 5),初始表达式总是被克隆,以便新生成的表达式独立于旧的表达式。例如:
model = ConcreteModel()
model.x = Var()
# create a Pyomo expression
e1 = model.x + 5
# create another Pyomo expression
# e1 is copied when generating e2
e2 = e1 + model.x
如果你想创建一个在其他表达式之间共享的表达式,你可以使用Expression组件。