表达式

在本节中,我们以两种方式使用“表达式”这个词:首先是这个词的一般意义,其次是用来描述一类名为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组件。