Pyomo accesing/retrieving 双变量 - 带二元变量的影子价格

Pyomo accesing/retrieving dual variables - shadow price with binary variables

我对一般优化尤其是 pyomo 还很陌生,所以对于任何菜鸟错误我提前道歉。

我已经使用 [2] 作为起点定义了一个简单的单位承诺练习(来自 [1] 的示例 3.1)。我得到了正确的结果和我的代码 运行s,但我有几个关于如何访问内容的问题。

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import shutil
import sys
import os.path
import pyomo.environ as pyo
import pyomo.gdp as gdp #necessary if you use booleans to select active and innactive units

def bounds_rule(m, n, param='Cap_MW'):
    # m because it pases the module
    # n because it needs a variable from each set, in this case there was only m.N
    return (0, Gen[n][param]) #returns lower and upper bounds.

def unit_commitment():
    m = pyo.ConcreteModel()
    m.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT)
    N=Gen.keys()
    m.N = pyo.Set(initialize=N)    
    
    m.Pgen = pyo.Var(m.N, bounds = bounds_rule) #amount of generation
    m.Rgen = pyo.Var(m.N, bounds = bounds_rule) #amount of generation

    # m.OnOff = pyo.Var(m.N, domain=pyo.Binary) #boolean on/off marker
    
    # objective
    m.cost = pyo.Objective(expr = sum( m.Pgen[n]*Gen[n]['energy_$MWh'] + m.Rgen[n]*Gen[n]['res_$MW'] for n in m.N), sense=pyo.minimize)
    
    # demand
    m.demandP = pyo.Constraint(rule=lambda m: sum(m.Pgen[n] for n in N) == Demand['ener_MWh'])
    m.demandR = pyo.Constraint(rule=lambda m: sum(m.Rgen[n] for n in N) == Demand['res_MW'])
    
    # machine production limits
    # m.lb = pyo.Constraint(m.N, rule=lambda m, n: Gen[n]['Cap_min']*m.OnOff[n] <= m.Pgen[n]+m.Rgen[n] )
    # m.ub = pyo.Constraint(m.N, rule=lambda m, n: Gen[n]['Cap_MW']*m.OnOff[n] >= m.Pgen[n]+m.Rgen[n])
    m.lb = pyo.Constraint(m.N, rule=lambda m, n: Gen[n]['Cap_min'] <= m.Pgen[n]+m.Rgen[n] )
    m.ub = pyo.Constraint(m.N, rule=lambda m, n: Gen[n]['Cap_MW'] >= m.Pgen[n]+m.Rgen[n])

    m.rc = pyo.Suffix(direction=pyo.Suffix.IMPORT)
    
    return m

Gen = {
    'GenA' : {'Cap_MW': 100, 'energy_$MWh': 10, 'res_$MW': 0, 'Cap_min': 0},
    'GenB' : {'Cap_MW': 100, 'energy_$MWh': 30, 'res_$MW': 25, 'Cap_min': 0},
} #starting data

Demand = {
    'ener_MWh': 130, 'res_MW': 20,
} #starting data
m = unit_commitment()
pyo.SolverFactory('glpk').solve(m).write() 
m.display()


df = pd.DataFrame.from_dict([m.Pgen.extract_values(), m.Rgen.extract_values()]).T.rename(columns={0: "P", 1: "R"})
print(df)
print("Cost Function result: " + str(m.cost.expr()) + "$.")

print(m.rc.display())
print(m.dual.display())
print(m.dual[m.demandR])

da= {'duals': m.dual[m.demandP],
     'uslack': m.demandP.uslack(),
     'lslack': m.demandP.lslack(),
    }
db= {'duals': m.dual[m.demandR],
     'uslack': m.demandR.uslack(),
     'lslack': m.demandR.lslack(),
    }

duals = pd.DataFrame.from_dict([da, db]).T.rename(columns={0: "demandP", 1: "demandR"})

print(duals)

我的问题来了。

1-Duals/shadow-price:根据定义,影子价格是约束的对偶变量(m.demandP 和 m.demandR)。有没有一种方法可以访问这些值并将它们放入数据框中,而无需执行我所做的“卑鄙”事情?我的意思是手动定义 da 和 db,然后在两个字典都加入时创建数据框?我想做一些更干净的事情,比如 df,它保存系统中每个生成器的 P 和 R 的结果。

2-通常,在单元组合问题中,使用二进制变量来“标记”或“select”活动和非活动单元。因此,“m.OnOff”变量(注释行)。对于我在 [3] 中发现的内容,对偶在包含二元变量的模型中不存在。之后我重写了问题而不包括二进制文件。在这个所有单位 运行 的简单练习中,这不是问题,但对于较大的单位。我需要能够让优化决定哪些单位会和不会 运行 而且我仍然需要影子价格。有没有办法在包含二进制变量的问题中获得 shadow-price/duals ? 我也放了基于二进制变量的约束定义,以防有人发现它有用。

注意:该代码还 运行 使用二进制变量并获得正确的结果,但是我无法弄清楚如何获得影子价格。因此我的问题。

[1] Morales, J. M.、Conejo, A. J.、Madsen, H.、Pinson, P. 和 Zugno, M. (2013)。将可再生能源纳入电力市场:运营问题(第 205 卷)。施普林格科学与商业媒体。

[2] https://jckantor.github.io/ND-Pyomo-Cookbook/04.06-Unit-Commitment.html

[3]

要回答 1,您可以使用 model.component_objects(pyo.Constraint) 从模型中动态获取约束对象,这将 return 约束的迭代器,这样您就不必对约束名称进行硬编码.对于索引变量,它变得很棘手,因为您必须执行额外的步骤来获取每个索引的松弛,而不仅仅是约束对象。对于对偶,您可以遍历 keys 属性来检索这些值。

duals_dict = {str(key):m.dual[key] for key in m.dual.keys()}

u_slack_dict = {
    # uslacks for non-indexed constraints
    **{str(con):con.uslack() for con in m.component_objects(pyo.Constraint)
       if not con.is_indexed()},
    # indexed constraint uslack
    # loop through the indexed constraints
    # get all the indices then retrieve the slacks for each index of constraint
    **{k:v for con in m.component_objects(pyo.Constraint) if con.is_indexed()
       for k,v in {'{}[{}]'.format(str(con),key):con[key].uslack()
                   for key in con.keys()}.items()}
    }

l_slack_dict = {
    # lslacks for non-indexed constraints
    **{str(con):con.lslack() for con in m.component_objects(pyo.Constraint)
       if not con.is_indexed()},
    # indexed constraint lslack
    # loop through the indexed constraints
    # get all the indices then retrieve the slacks for each index of constraint
    **{k:v for con in m.component_objects(pyo.Constraint) if con.is_indexed()
       for k,v in {'{}[{}]'.format(str(con),key):con[key].lslack()
                   for key in con.keys()}.items()}
    }

# combine into a single df
df = pd.concat([pd.Series(d,name=name)
           for name,d in {'duals':duals_dict,
                          'uslack':u_slack_dict,
                          'lslack':l_slack_dict}.items()],
          axis='columns')

关于2,我同意@Erwin关于用二元变量求解以获得最优解,然后去除二元限制但将变量固定为最优值以获得一些对偶变量值的评论。