Pyomo ccgt 调度优化在模型中引入 min_downtime 约束时不起作用

问题描述 投票:0回答:1

背景:

我正在尝试编写一个 pyomo 脚本,以根据对电价的完美预见来优化调度 ccgt 电厂。 x1、x2、x5是二元变量,分别对应ccgt的不同模式,其中x1是全基本负载模式,x2是部分负载模式,x5是空闲模式。

问题:

在没有 min_downtime 限制的情况下,脚本可以正常且准确地运行。

请求帮助:

有人可以看看我的代码,看看我在 min_downtime 约束中遗漏了什么吗?如果 X5 选择 1,ccgt 需要闲置至少 6 小时。

代码:

from pyomo.environ import *
from pyomo.opt import SolverFactory
import pandas as pd
import matplotlib.pyplot as plt

model = ConcreteModel()

inputs_df = pd.read_excel('input.xlsx')

electricity_prices = pd.DataFrame(inputs_df['PTF'])
ancillary_service_prices = pd.DataFrame(inputs_df['SFC'])
gas_prices = pd.DataFrame(inputs_df['Gasp'])
msp_prices = pd.DataFrame(inputs_df['AUF'])
model.max_gas_quantity = Param(initialize=150000000)  # or any other value

hours = list(electricity_prices.index)

model.X1 = Var(hours, within=Binary)
model.X2 = Var(hours, within=Binary)
model.X5 = Var(hours, within=Binary)
model.startup = Var(hours, within=NonNegativeIntegers)
model.shutdown = Var(hours, within=NonNegativeIntegers)
model.plant_running = Var(hours, within=Binary)
model.plant_idling = Var(hours, within=Binary)


# generation
def generation(model, hour):
    return model.X1[hour]*770 + model.X2[hour]*550 + model.X5[hour]*0 + model.startup[hour]*225 + model.shutdown[hour]*70
model.generation = Expression(hours, rule=generation)

# ancillary services
def ancillary_service(model, hour):
    return model.X1[hour]*0 + model.X2[hour]*220 + model.X5[hour]*0
model.ancillary_service = Expression(hours, rule=ancillary_service)

# gas consumption
def gas_consumption(model, hour):
    return model.X1[hour]*143990 + model.X2[hour]*108350 + model.X5[hour]*0 + model.startup[hour]*58500 + model.shutdown[hour]*17500
model.gas_consumption = Expression(hours, rule=gas_consumption)

# EOH consumption
def eoh_consumption(model, hour):
    return model.X1[hour]*2 + model.X2[hour]*2 + model.X5[hour]*0 + model.startup[hour]*20
model.eoh_consumption = Expression(hours, rule=eoh_consumption)

# Revenues
def electricity_revenue(model, hour):
    return model.generation[hour] * electricity_prices.loc[hour, "PTF"]
model.electricity_revenue = Expression(hours, rule=electricity_revenue)

def ancillary_service_revenue(model, hour):
    return model.ancillary_service[hour] * ancillary_service_prices.loc[hour, "SFC"]
model.ancillary_service_revenue = Expression(hours, rule=ancillary_service_revenue)

# Costs
def gas_cost(model, hour):
    return model.gas_consumption[hour] * gas_prices.loc[hour, "Gasp"]
model.gas_cost = Expression(hours, rule=gas_cost)

def eoh_cost(model, hour):
    return model.eoh_consumption[hour] * 7500
model.eoh_cost = Expression(hours, rule=eoh_cost)

def tso_var_cost(model, hour):
    return model.generation[hour] * 93.25
model.tso_var_cost = Expression(hours, rule=tso_var_cost)

def msp_cost(model, hour):
    if electricity_prices.loc[hour, "PTF"] > msp_prices.loc[hour, "AUF"] :
        return model.generation[hour] * (electricity_prices.loc[hour, "PTF"] - msp_prices.loc[hour, "AUF"])
    else:
        return 0
model.msp_cost = Expression(hours, rule=msp_cost)

# Constraints
def one_mode_per_hour(model, hour):
    return model.X1[hour] + model.X2[hour] + model.X5[hour] == 1
model.one_mode_per_hour = Constraint(hours, rule=one_mode_per_hour)

def running(model, hour):
    return model.plant_running[hour] >= 1 - model.X5[hour]
model.running = Constraint(hours, rule=running)

def idling(model, hour):
    return model.plant_idling[hour] >= model.X5[hour]
model.idling = Constraint(hours, rule=idling)

def start_flag(model, hour):
    if hour > len(hours)-2:
        return Constraint.Skip
    else:
        return model.startup[hour] >= model.X5[hour] - model.X5[hour+1]
model.start_flag = Constraint(hours, rule=start_flag)

def stop_flag(model, hour):
    if hour > len(hours)-2:
        return Constraint.Skip
    else:
        return model.shutdown[hour+1] >= model.X5[hour+1] - model.X5[hour]
model.stop_flag = Constraint(hours, rule=stop_flag)


window_size = 6
def rolling_min_downtime(model, hour):
    preceding_periods = {hour_prime for hour_prime in hours if hour - window_size <= hour_prime < hour}
    return 6 - sum(model.X5[hour_prime] for hour_prime in preceding_periods) <= 1
eval_periods = {hour for hour in hours if hour >= window_size}
model.rolling_min_downtime = Constraint(eval_periods, rule=rolling_min_downtime)


def total_gas(model):
    return sum(model.gas_consumption[hour] for hour in hours) <= model.max_gas_quantity
model.total_gas = Constraint(rule=total_gas)

# Objective Function
def objective(model):
    return sum(model.electricity_revenue[hour] for hour in hours) + sum(model.ancillary_service_revenue[hour] for hour in hours) \
- sum(model.gas_cost[hour] for hour in hours) - sum(model.eoh_cost[hour] for hour in hours) - sum(model.tso_var_cost[hour] for hour in hours) - sum(model.msp_cost[hour] for hour in hours)

model.obj = Objective(rule=objective, sense=maximize)


solver = SolverFactory('glpk')
result = solver.solve(model)



optimization pyomo dispatch
1个回答
0
投票

请看下面。我修改了一些东西。无法测试,因为没有提供数据。如果这不能按预期工作并且您被卡住了,请编辑您的问题并在表中包含大约 20 行数据。

实现的逻辑是:

  • 标记停止事件以捕获它发生在哪个时间段
  • 停止事件后接下来的5个时间段,您可以抑制运行模式

代码:

from pyomo.environ import *
from pyomo.opt import SolverFactory
import pandas as pd
import matplotlib.pyplot as plt

model = ConcreteModel()

inputs_df = pd.read_excel('input.xlsx')

electricity_prices = pd.DataFrame(inputs_df['PTF'])
ancillary_service_prices = pd.DataFrame(inputs_df['SFC'])
gas_prices = pd.DataFrame(inputs_df['Gasp'])
msp_prices = pd.DataFrame(inputs_df['AUF'])
model.max_gas_quantity = Param(initialize=150000000)  # or any other value

hours = list(electricity_prices.index)

model.X1 = Var(hours, within=Binary)
model.X2 = Var(hours, within=Binary)
model.X5 = Var(hours, within=Binary)
model.startup = Var(hours, within=NonNegativeIntegers)
model.shutdown = Var(hours, within=NonNegativeIntegers)
# model.plant_running = Var(hours, within=Binary)
# model.plant_idling = Var(hours, within=Binary)


# generation
def generation(model, hour):
    return model.X1[hour]*770 + model.X2[hour]*550 + model.X5[hour]*0 + model.startup[hour]*225 + model.shutdown[hour]*70
model.generation = Expression(hours, rule=generation)

# ancillary services
def ancillary_service(model, hour):
    return model.X1[hour]*0 + model.X2[hour]*220 + model.X5[hour]*0
model.ancillary_service = Expression(hours, rule=ancillary_service)

# gas consumption
def gas_consumption(model, hour):
    return model.X1[hour]*143990 + model.X2[hour]*108350 + model.X5[hour]*0 + model.startup[hour]*58500 + model.shutdown[hour]*17500
model.gas_consumption = Expression(hours, rule=gas_consumption)

# EOH consumption
def eoh_consumption(model, hour):
    return model.X1[hour]*2 + model.X2[hour]*2 + model.X5[hour]*0 + model.startup[hour]*20
model.eoh_consumption = Expression(hours, rule=eoh_consumption)

# Revenues
def electricity_revenue(model, hour):
    return model.generation[hour] * electricity_prices.loc[hour, "PTF"]
model.electricity_revenue = Expression(hours, rule=electricity_revenue)

def ancillary_service_revenue(model, hour):
    return model.ancillary_service[hour] * ancillary_service_prices.loc[hour, "SFC"]
model.ancillary_service_revenue = Expression(hours, rule=ancillary_service_revenue)

# Costs
def gas_cost(model, hour):
    return model.gas_consumption[hour] * gas_prices.loc[hour, "Gasp"]
model.gas_cost = Expression(hours, rule=gas_cost)

def eoh_cost(model, hour):
    return model.eoh_consumption[hour] * 7500
model.eoh_cost = Expression(hours, rule=eoh_cost)

def tso_var_cost(model, hour):
    return model.generation[hour] * 93.25
model.tso_var_cost = Expression(hours, rule=tso_var_cost)

def msp_cost(model, hour):
    if electricity_prices.loc[hour, "PTF"] > msp_prices.loc[hour, "AUF"] :
        return model.generation[hour] * (electricity_prices.loc[hour, "PTF"] - msp_prices.loc[hour, "AUF"])
    else:
        return 0
model.msp_cost = Expression(hours, rule=msp_cost)

# Constraints
def one_mode_per_hour(model, hour):
    return model.X1[hour] + model.X2[hour] + model.X5[hour] == 1
model.one_mode_per_hour = Constraint(hours, rule=one_mode_per_hour)

# NOT NEEDED.  If you know X5, then you know IDLE vs. RUNNING so this is duplicate
# Information...

# def running(model, hour):
#     return model.plant_running[hour] >= 1 - model.X5[hour]
# model.running = Constraint(hours, rule=running)

# def idling(model, hour):
#     return model.plant_idling[hour] >= model.X5[hour]
# model.idling = Constraint(hours, rule=idling)

#  NOT NEEDED ?
# def start_flag(model, hour):
#     if hour > len(hours)-2:
#         return Constraint.Skip
#     else:
#         return model.startup[hour] >= model.X5[hour] - model.X5[hour+1]
# model.start_flag = Constraint(hours, rule=start_flag)


# re-aligned to capture the first period where mode == 5
def stop_flag(model, hour):
        return model.shutdown[hour] >= model.X5[hour] - model.X5[hour-1]
model.stop_flag = Constraint(hours[1:], rule=stop_flag)

# now that we have the start of shutdown flagged, we just need to suppress
# modes 1 and 2 for the next 5 time periods
suppress_periods = 5
def suppress_running_modes(model, hour):
    following_periods = {hour_prime for hour_prime in hours 
                        if hour_prime > hour
                        and hour_prime <= hour + suppress_periods}
    return sum(model.X1[h] + model.X2[h] for h in following_periods) <= suppress_periods*(1-model.shutdown[hour]))
model.suppress_start = Constraint(hours, rule=suppress_running_modes)

# need to capture a startup event.  Note: this doesnt "charge" for startup in 1st period... model could
# be running at start with no charge....
def startup_capture(model, hour):
    return model.startup[hour] >= model.X5[hour - 1] - model.X5[hour]
model.startup_capture = Constraint(hours[1:], rule=startup_capture)


# window_size = 6
# def rolling_min_downtime(model, hour):
#     preceding_periods = {hour_prime for hour_prime in hours if hour - window_size <= hour_prime < hour}
#     return 6 - sum(model.X5[hour_prime] for hour_prime in preceding_periods) <= 1
# eval_periods = {hour for hour in hours if hour >= window_size}
# model.rolling_min_downtime = Constraint(eval_periods, rule=rolling_min_downtime)


def total_gas(model):
    return sum(model.gas_consumption[hour] for hour in hours) <= model.max_gas_quantity
model.total_gas = Constraint(rule=total_gas)

# Objective Function
def objective(model):
    return sum(model.electricity_revenue[hour] for hour in hours) + sum(model.ancillary_service_revenue[hour] for hour in hours) \
- sum(model.gas_cost[hour] for hour in hours) - sum(model.eoh_cost[hour] for hour in hours) - sum(model.tso_var_cost[hour] for hour in hours) - sum(model.msp_cost[hour] for hour in hours)

model.obj = Objective(rule=objective, sense=maximize)


solver = SolverFactory('glpk')
result = solver.solve(model)
© www.soinside.com 2019 - 2024. All rights reserved.