如何在 Pyomo 中添加最小值约束
How to add a minimum value constraint in Pyomo
背景 - 我正在尝试优化电池储能系统 (BESS) 的运行。我使用 This Github Project 作为基础。
问题 - 大多数 BESS 系统都有放电深度限制。 IE。对于 400 MWh 的电池,您只能使用大约 320 MWh。最后 20% 基本上是 reserved/unusable。我一直在尝试将其添加为附加约束,但我没有运气
我试过的
我试过为我的电池存储参数设置界限,我试过添加约束(如下所示),但两者都导致了错误 'No value for uninitialized NumericValue object Ein[0]'(Ein 是 Energy In 的参数)
尝试 1
model.S = Var(model.T, bounds=(model.Smin, model.Smax))
尝试 2
def dod_limit(model, t):
"Depth of Discharge limit"
return model.Smin[t] <= model.S[t]
model.dod = Constraint(model.T, rule=dod_limit)
请求帮助
有谁知道我如何写出放电深度限制的约束条件?
完整性的完整代码
# ---------------------
# Main Functions
# ---------------------
def model_to_df(model, first_period, last_period):
"""
Create a dataframe with hourly charge, discharge, state of charge, and
price columns from a pyomo model. Only uses data from between the first
(inclusive) and last (exclusive) hours.
Parameters
----------
model : pyomo model
Model that has been solved
first_period : int
First hour of data to keep
last_period: int
The final hour of data to keep
Returns
-------
dataframe
"""
# Need to increase the first & last hour by 1 because of pyomo indexing
# and add 1 to the value of last model hour because of the range
# second model hour by 1
periods = range(model.T[first_period + 1], model.T[last_period + 1] + 1)
Ein = [value(model.Ein[i]) for i in periods]
Eout = [value(model.Eout[i]) for i in periods]
rrp = [model.P.extract_values()[None][i] for i in periods]
charge_state = [value(model.S[i]) for i in periods]
df_dict = dict(
period=periods,
Ein=Ein,
Eout=Eout,
rrp=rrp,
charge_state=charge_state
)
df = pd.DataFrame(df_dict)
return df
def optimize_year(df, mwh, mw, ef, soc, first_model_period, last_model_period):
"""
Optimize the charge/discharge behavior of a battery storage unit over a
full year. Assume perfect foresight of electricity prices. The battery
has a discharge constraint equal to its storage capacity and round-trip
efficiency of 85%.
Parameters
----------
df : dataframe
dataframe with columns of hourly LBMP and the hour of the year
mwh : int
size of bess
mw : int
storage capacity of bess
ef : double
round trip efficiency of bess
soc : double
State of Charge
first_model_period : int, optional
Set the first hour of the year to be considered in the optimization
(the default is 0)
last_model_period : int, optional
Set the last hour of the year to be considered in the optimization (the
default is 8759)
Returns
-------
dataframe
hourly state of charge, charge/discharge behavior, lbmp, and time stamp
"""
# Filter the data
df = df.loc[first_model_period:last_model_period, :]
model = ConcreteModel()
# Define model parameters
model.T = Set(initialize=df.period.tolist(), doc='periods of year', ordered=True)
model.Rmax = Param(initialize=mw / 2, doc='Max rate of power flow (MW) in or out', within=Any)
model.Smax = Param(initialize=mwh, doc='Max storage (MWh)', within=Any)
model.Smin = Param(initialize=mwh * 0.2, doc='Min storage (MWh)', within=Any)
model.Dmax = Param(initialize=mwh * np.sqrt(ef) * 0.8, doc='Max discharge in 24 hour period', within=Any)
model.P = Param(initialize=df.rrp.tolist(), doc='price for each period', within=Any)
eta = ef # Round trip storage efficiency
# Charge, discharge, and state of charge
# Could use bounds for the first 2 instead of constraints
model.Ein = Var(model.T, domain=NonNegativeReals)
model.Eout = Var(model.T, domain=NonNegativeReals)
model.S = Var(model.T, bounds=(model.Smin, model.Smax))
# Set all constraints
def storage_state(model, t):
'Storage changes with flows in/out and efficiency losses'
# Set first period state of charge to 0
if t == model.T.first():
return model.S[t] == soc
else:
return (model.S[t] == (model.S[t - 1]
+ (model.Ein[t - 1] * np.sqrt(eta))
- (model.Eout[t - 1] / np.sqrt(eta))))
model.charge_state = Constraint(model.T, rule=storage_state)
def dod_limit(model, t):
"Depth of Discharge limit"
return model.Smin[t] <= model.S[t]
model.dod = Constraint(model.T, rule=dod_limit)
def discharge_constraint(model, t):
"Maximum dischage within a single period"
return model.Eout[t] <= model.Rmax
model.discharge = Constraint(model.T, rule=discharge_constraint)
def charge_constraint(model, t):
"Maximum charge within a single period"
return model.Ein[t] <= model.Rmax
model.charge = Constraint(model.T, rule=charge_constraint)
# Without a constraint the model would discharge in the final period
# even when SOC was 0.
def positive_charge(model, t):
'Limit discharge to the amount of charge in battery, including losses'
return model.Eout[t] <= model.S[t] * np.sqrt(eta)
model.positive_charge = Constraint(model.T, rule=positive_charge)
def discharge_limit(model, t):
"Limit on discharge within a 24 hour period"
max_t = model.T.last()
# Check all t until the last 24 hours
if t < max_t-24:
return sum(model.Eout[i] for i in range(t, t+24)) <= model.Dmax
else:
return Constraint.Skip
model.limit_out = Constraint(model.T, rule=discharge_limit)
# Define the battery income, expenses, and profit
income = sum(df.loc[t, 'rrp'] * model.Eout[t] for t in model.T)
expenses = sum(df.loc[t, 'rrp'] * model.Ein[t] for t in model.T)
profit = income - expenses
model.objective = Objective(expr=profit, sense=maximize)
# Solve the model
solver = SolverFactory('glpk')
solver.solve(model)
results_df = model_to_df(model, first_period=first_model_period, last_period=last_model_period)
results_df['local_time'] = df.loc[:, 'local_time']
return results_df
这两种方法中的任何一种都应该有效。如果不做一些工作,你的模型是不可复制的,所以不要快速验证。
关于你的第二种方法,以及你在代码中的内容,意识到你的 model.Smin
没有 索引。它只是一个常数。所以你这里确实有错误:
def dod_limit(model, t):
"Depth of Discharge limit"
return model.Smin[t] <= model.S[t]
应该是:
return model.Smin <= model.S[t] # note model.Smin is not indexed
我担心您显示的错误是由于其他原因造成的。如果上面的这个修复没有解决问题,你能用完整的堆栈跟踪编辑你的 post 吗?
想通了。我在上面的问题中描述的两种方法确实有效,我只需要为我的变量设置初始值。
model.Ein = Var(model.T, bounds=(0, model.Rmax), initialize=0)
model.Eout = Var(model.T, bounds=(0, model.Rmax), initialize=0)
model.S = Var(model.T, bounds=(model.Smin, model.Smax), initialize=soc)
背景 - 我正在尝试优化电池储能系统 (BESS) 的运行。我使用 This Github Project 作为基础。
问题 - 大多数 BESS 系统都有放电深度限制。 IE。对于 400 MWh 的电池,您只能使用大约 320 MWh。最后 20% 基本上是 reserved/unusable。我一直在尝试将其添加为附加约束,但我没有运气
我试过的
我试过为我的电池存储参数设置界限,我试过添加约束(如下所示),但两者都导致了错误 'No value for uninitialized NumericValue object Ein[0]'(Ein 是 Energy In 的参数)
尝试 1
model.S = Var(model.T, bounds=(model.Smin, model.Smax))
尝试 2
def dod_limit(model, t):
"Depth of Discharge limit"
return model.Smin[t] <= model.S[t]
model.dod = Constraint(model.T, rule=dod_limit)
请求帮助
有谁知道我如何写出放电深度限制的约束条件?
完整性的完整代码
# ---------------------
# Main Functions
# ---------------------
def model_to_df(model, first_period, last_period):
"""
Create a dataframe with hourly charge, discharge, state of charge, and
price columns from a pyomo model. Only uses data from between the first
(inclusive) and last (exclusive) hours.
Parameters
----------
model : pyomo model
Model that has been solved
first_period : int
First hour of data to keep
last_period: int
The final hour of data to keep
Returns
-------
dataframe
"""
# Need to increase the first & last hour by 1 because of pyomo indexing
# and add 1 to the value of last model hour because of the range
# second model hour by 1
periods = range(model.T[first_period + 1], model.T[last_period + 1] + 1)
Ein = [value(model.Ein[i]) for i in periods]
Eout = [value(model.Eout[i]) for i in periods]
rrp = [model.P.extract_values()[None][i] for i in periods]
charge_state = [value(model.S[i]) for i in periods]
df_dict = dict(
period=periods,
Ein=Ein,
Eout=Eout,
rrp=rrp,
charge_state=charge_state
)
df = pd.DataFrame(df_dict)
return df
def optimize_year(df, mwh, mw, ef, soc, first_model_period, last_model_period):
"""
Optimize the charge/discharge behavior of a battery storage unit over a
full year. Assume perfect foresight of electricity prices. The battery
has a discharge constraint equal to its storage capacity and round-trip
efficiency of 85%.
Parameters
----------
df : dataframe
dataframe with columns of hourly LBMP and the hour of the year
mwh : int
size of bess
mw : int
storage capacity of bess
ef : double
round trip efficiency of bess
soc : double
State of Charge
first_model_period : int, optional
Set the first hour of the year to be considered in the optimization
(the default is 0)
last_model_period : int, optional
Set the last hour of the year to be considered in the optimization (the
default is 8759)
Returns
-------
dataframe
hourly state of charge, charge/discharge behavior, lbmp, and time stamp
"""
# Filter the data
df = df.loc[first_model_period:last_model_period, :]
model = ConcreteModel()
# Define model parameters
model.T = Set(initialize=df.period.tolist(), doc='periods of year', ordered=True)
model.Rmax = Param(initialize=mw / 2, doc='Max rate of power flow (MW) in or out', within=Any)
model.Smax = Param(initialize=mwh, doc='Max storage (MWh)', within=Any)
model.Smin = Param(initialize=mwh * 0.2, doc='Min storage (MWh)', within=Any)
model.Dmax = Param(initialize=mwh * np.sqrt(ef) * 0.8, doc='Max discharge in 24 hour period', within=Any)
model.P = Param(initialize=df.rrp.tolist(), doc='price for each period', within=Any)
eta = ef # Round trip storage efficiency
# Charge, discharge, and state of charge
# Could use bounds for the first 2 instead of constraints
model.Ein = Var(model.T, domain=NonNegativeReals)
model.Eout = Var(model.T, domain=NonNegativeReals)
model.S = Var(model.T, bounds=(model.Smin, model.Smax))
# Set all constraints
def storage_state(model, t):
'Storage changes with flows in/out and efficiency losses'
# Set first period state of charge to 0
if t == model.T.first():
return model.S[t] == soc
else:
return (model.S[t] == (model.S[t - 1]
+ (model.Ein[t - 1] * np.sqrt(eta))
- (model.Eout[t - 1] / np.sqrt(eta))))
model.charge_state = Constraint(model.T, rule=storage_state)
def dod_limit(model, t):
"Depth of Discharge limit"
return model.Smin[t] <= model.S[t]
model.dod = Constraint(model.T, rule=dod_limit)
def discharge_constraint(model, t):
"Maximum dischage within a single period"
return model.Eout[t] <= model.Rmax
model.discharge = Constraint(model.T, rule=discharge_constraint)
def charge_constraint(model, t):
"Maximum charge within a single period"
return model.Ein[t] <= model.Rmax
model.charge = Constraint(model.T, rule=charge_constraint)
# Without a constraint the model would discharge in the final period
# even when SOC was 0.
def positive_charge(model, t):
'Limit discharge to the amount of charge in battery, including losses'
return model.Eout[t] <= model.S[t] * np.sqrt(eta)
model.positive_charge = Constraint(model.T, rule=positive_charge)
def discharge_limit(model, t):
"Limit on discharge within a 24 hour period"
max_t = model.T.last()
# Check all t until the last 24 hours
if t < max_t-24:
return sum(model.Eout[i] for i in range(t, t+24)) <= model.Dmax
else:
return Constraint.Skip
model.limit_out = Constraint(model.T, rule=discharge_limit)
# Define the battery income, expenses, and profit
income = sum(df.loc[t, 'rrp'] * model.Eout[t] for t in model.T)
expenses = sum(df.loc[t, 'rrp'] * model.Ein[t] for t in model.T)
profit = income - expenses
model.objective = Objective(expr=profit, sense=maximize)
# Solve the model
solver = SolverFactory('glpk')
solver.solve(model)
results_df = model_to_df(model, first_period=first_model_period, last_period=last_model_period)
results_df['local_time'] = df.loc[:, 'local_time']
return results_df
这两种方法中的任何一种都应该有效。如果不做一些工作,你的模型是不可复制的,所以不要快速验证。
关于你的第二种方法,以及你在代码中的内容,意识到你的 model.Smin
没有 索引。它只是一个常数。所以你这里确实有错误:
def dod_limit(model, t):
"Depth of Discharge limit"
return model.Smin[t] <= model.S[t]
应该是:
return model.Smin <= model.S[t] # note model.Smin is not indexed
我担心您显示的错误是由于其他原因造成的。如果上面的这个修复没有解决问题,你能用完整的堆栈跟踪编辑你的 post 吗?
想通了。我在上面的问题中描述的两种方法确实有效,我只需要为我的变量设置初始值。
model.Ein = Var(model.T, bounds=(0, model.Rmax), initialize=0)
model.Eout = Var(model.T, bounds=(0, model.Rmax), initialize=0)
model.S = Var(model.T, bounds=(model.Smin, model.Smax), initialize=soc)