【问题标题】:Using piecewise function in objective function in Pyomo在 Pyomo 的目标函数中使用分段函数
【发布时间】:2021-10-23 10:12:52
【问题描述】:

我正在尝试在我的目标函数中使用 Pyomo 的分段线性函数。这个分段线性函数实际上是对一个名为macc 的值数组进行插值,该数组有 401 个值(macc[i],i 从 0 到 400)。你可以在附图中看到macc的值

我的目标函数正在寻找 i 的值 macc[i] 尊重约束。为此,我对数组 macc 进行插值,使其具有连续函数 f。见下文:

c = np.arange(401)
f = pyopiecewise.piecewise(c,macc,validate=False)
model = pyo.ConcreteModel()

#Declare variable
model.x = pyo.Var(domain=pyo.NonNegativeReals, bounds=(5,395), initialize = cp0)

#Declare parameters
model.s = pyo.Param(domain=pyo.NonNegativeReals,initialize=s0)
model.b = pyo.Param(domain=pyo.NonNegativeReals,initialize=b0)
model.tnac = pyo.Param(domain=pyo.NonNegativeReals,initialize=tnac0)

#Objective function
def objective_(m):
    ab = f(m.x)

    e = m.b - ab

    return (e * m.x)

#Constraints
def constraint1(m):

    ab = f(m.x)

    e = m.b - ab

    return e <= (m.tnac + m.s)

但是当我尝试在上面的目标函数中调用此函数 f 时,我收到以下消息,表示目标函数中的表达式 ab = f(m.x)

ERROR: Rule failed when generating expression for Objective Obj with index
    None: PyomoException: Cannot convert non-constant expression to bool. This
    error is usually caused by using an expression in a boolean context such
    as an if statement. For example,
        m.x = Var() if m.x <= 0:
        ...
would cause this exception.

ERROR: Constructing component 'Obj' from data=None failed: PyomoException:
    Cannot convert non-constant expression to bool. This error is usually
caused by using an expression in a boolean context such as an if
statement. For example,
        m.x = Var() if m.x <= 0:
        ...
would cause this exception.

非常欢迎任何关于如何解决这个问题的想法。

如果需要,这里是完整的代码。对于这个示例,我使用函数创建了数组 macc,但实际上它不是来自函数,而是来自内部数据。

import numpy as np
import pyomo.environ as pyo
import pyomo.core.kernel.piecewise_library.transforms as pyopiecewise

#Create macc
# logistic sigmoid function
def logistic(x, L=1, x_0=0, k=1):
    return L / (1 + np.exp(-k * (x - x_0)))


c = np.arange(401)
macc = 2000*logistic(c,L=0.5,x_0 = 60,k=0.02)
macc = macc -macc[0]

f = pyopiecewise.piecewise(c,macc,validate=False)

s0 = 800
b0 = 1000
tnac0 = 100

cp0 = 10
ab0 = 100

model = pyo.ConcreteModel()

#Declare variable
model.x = pyo.Var(domain=pyo.NonNegativeReals, bounds=(5,395), initialize = cp0)

#Declare parameters
model.s = pyo.Param(domain=pyo.NonNegativeReals,initialize=s0)
model.b = pyo.Param(domain=pyo.NonNegativeReals,initialize=b0)
model.tnac = pyo.Param(domain=pyo.NonNegativeReals,initialize=tnac0)

#Objective function
def objective_(m):
    ab = f(m.x)

    e = m.b - ab

    return (e * m.x)

model.Obj = pyo.Objective(rule=objective_)

#Constraints
def constraint1(m):

    ab = f(m.x)

    e = m.b - ab

    return e <= (m.tnac + m.s)

def constraint2(m): 

    ab = f(m.x)

    e = m.b - ab

    return e >= 1

def constraint3(m):

    ab = f(m.x)

    return ab >= 0


model.con1 = pyo.Constraint(rule = constraint1)
model.con2 = pyo.Constraint(rule = constraint2)
model.con3 = pyo.Constraint(rule = constraint3)

And here is my objective function

【问题讨论】:

  • 您是否尝试使用替代变量(比如model.y = pyo.Var())?您不需要评估 Var model.x 中的分段,只需在分段函数中使用输入和输出参数即可。 f = piecewise(c, macc, input=model.x, output=model.y, ,validate=False) 然后只需使用 model.y 来使用评估的分段函数
  • 这个之前的解决方案可能会有所帮助:stackoverflow.com/questions/68626937/… 您正在混合使用 pyomo.environpyomo.kernel 库,该帖子说这是不行的。它也有一些示例参考。不确定这是否是您的问题。您可能想尝试使用kernel 库构建一个“玩具”分段模型(非常简单的模型)并从那里重建,因为您的模型非常小/简单。
  • @pybegginer 感谢您的建议,但我不确定如何实施。你能再扩大一点吗?如果你能告诉我在上面的模型中在哪里写,那会更清楚。谢谢!

标签: python pyomo


【解决方案1】:

@RonB

正如 AirSquid 评论的那样,您正在使用 kernelenviron 命名空间。您应该避免这种混合,因为几种方法可能不兼容。

代替使用__call__() (f(model.x)) 方法显式评估分段函数,您可以使用输入、输出参数(在环境层中称为xvaryvar)以定义的形式输出评估变量。

使用environ层,分段函数在pyo.Piecewise中可用

import numpy as np
import pyomo.environ as pyo

#Create macc
# logistic sigmoid function
def logistic(x, L=1, x_0=0, k=1):
    return L / (1 + np.exp(-k * (x - x_0)))

c = np.linspace(0,400,400)
macc = 2000*logistic(c,L=0.5,x_0 = 60,k=0.02)
macc = macc -macc[0]

s0 = 800
b0 = 1000
tnac0 = 100

cp0 = 10
ab0 = 100

model = pyo.ConcreteModel()

#Declare variable
model.x = pyo.Var(domain=pyo.NonNegativeReals, bounds=(5,395), initialize = cp0)
model.y = pyo.Var()
model.piecewise = pyo.Piecewise(model.y, model.x, pw_pts=list(c), f_rule=list(macc), pw_constr_type='EQ', pw_repn='DCC')

#Declare parameters
model.s = pyo.Param(domain=pyo.NonNegativeReals,initialize=s0)
model.b = pyo.Param(domain=pyo.NonNegativeReals,initialize=b0)
model.tnac = pyo.Param(domain=pyo.NonNegativeReals,initialize=tnac0)


model.Obj = pyo.Objective(expr= model.b*model.x - model.y*model.x, sense=pyo.minimize)

model.con1 = pyo.Constraint(expr=model.b - model.y <= model.tnac + model.s)
model.con2 = pyo.Constraint(expr=model.b - model.y >= 1)
model.con3 = pyo.Constraint(expr= model.y >= 0)
solver = pyo.SolverFactory('ipopt')
solver.solve(model, tee=True)

在这种建模方法中,您不会遇到在每个方程(约束或目标)中评估 model.piecewise(model.x) 的问题,而是只需使用等同于评估的 model.y

现在,我不知道您的问题,但我猜您的目标不是凸的,这可能是优化中的另一个问题。您可以使用Gurobi 来解决此类问题,但在这种情况下,由于model.y 依赖于model.x 并且model.x 是有界的,因此要达到model.x 上限以使目标尽可能低(由于您没有在目标中声明任何意义,我假设您想要最小化)。我认为你应该检查你的目标是否代表你的想法。

你的目标函数正在做这样的事情

【讨论】:

  • 抱歉,我忘记了对数组 macc 的一项重要更正:macc = macc -macc[0] 我在上面的代码中更正了。目标函数变得非常不同,总是在增加。这使问题更简单,因为我想最小化,求解器只需要找到尊重约束的最低值。
  • 否则你的方法似乎有效,谢谢。但我不能确定,因为我无法使用 ipopt 求解器解决问题。我收到消息“求解器不支持 SOS 2 级约束”。我正在尝试为 MINLP 问题寻找另一个免费的求解器。 (我真正的问题是二进制变量)。
  • macc 的更改不是问题,但肯定是更改找到的解决方案。关于Solver does not support SOS level 2 constraints 不是什么大问题,因为SOS2 只是pyomo 默认使用的分段表示,您可以将其更改为使用ipopt,只需添加参数pw_repn='DCC',例如,使用另一种方法。 model.piecewise = pyo.Piecewise(model.y, model.x, pw_pts=list(c), f_rule=list(macc), pw_constr_type='EQ', pw_repn='DCC') 这将解决问题 --> x=21.728, y=99.99, Obj=19,555.8 这是使用 ipopt 的结果
  • 太棒了,它有效!非常感谢,我永远不会独自找到它。
  • 太棒了。我希望您考虑对我的答案进行投票,或者如果它解决了您的问题并且您认为是最好的方法,请用支票将其标记为答案
猜你喜欢
  • 2021-08-04
  • 1970-01-01
  • 2020-02-06
  • 2021-07-29
  • 2021-05-27
  • 2017-02-15
  • 2020-07-01
  • 2021-08-27
  • 2019-07-10
相关资源
最近更新 更多