Pyomo储能系统调度优化
Pyomo energy storage system dispatch optimization
我正在尝试使用 pyomo 为储能系统创建模型优化。使用家庭的千瓦时需求和电价,我想将电池在正确时间充电和放电的成本降至最低。我已经有一个适用于 1 年数据的工作模型,并且该模型能够找到最佳解决方案(请参见下面的代码)。然而,当我试图找到一个只有三个月的模型时(比如从 10 月到 12 月),pyomo returns 终止条件为“未绑定”,但我不明白为什么。
1 年数据的模型:
#Battery parameters
battery_capacity = 252
total_energy = 13.1
usable_energy = 12.4
nominal_voltage = 51.8
ratio = total_energy/usable_energy
power = usable_energy / ratio
nominal_current = usable_energy / nominal_voltage * 1000
recharging_hours = battery_capacity/nominal_current
battery_level_threshold = 0
model = pyo.ConcreteModel()
#Set time period
model.T = pyo.Set(initialize=pyo.RangeSet(len(df_2021)),ordered=True)
#PARAMETERS:
model.b_efficiency = pyo.Param(initialize=0.9)
model.b_min_cap = pyo.Param(initialize=0)
model.b_max_cap = pyo.Param(initialize=12.4)
model.b_charge_power = pyo.Param(initialize=power)
model.b_discharge_power = pyo.Param(initialize=power)
model.spot_prices = pyo.Param(model.T,initialize=dict(enumerate(df_2021["Price/kWh"],1)),within=pyo.Any)
model.demand = pyo.Param(model.T, initialize=dict(enumerate(df_2021["Demand"],1)),within=pyo.Any)
#Variables : also the variable has to be indexed with the time T
model.b_soc = pyo.Var(model.T, domain = pyo.NonNegativeReals, bounds = (model.b_min_cap, model.b_max_cap))
model.b_discharge = pyo.Var(model.T, domain = pyo.NonNegativeReals)
model.b_charge = pyo.Var(model.T, domain = pyo.NonNegativeReals)
model.elect_purchased = pyo.Var(model.T, domain = pyo.NonNegativeReals)
#CONSTRAINTS
#Purchase constraint
def purchase_constraint(model,t):
return model.elect_purchased[t] >= model.demand[t] - model.b_discharge[t] + model.b_charge[t]
#State of charge constraint
def soc_constraint(model,t):
if t == model.T.first():
return model.b_soc[t] == model.b_max_cap / 2 #- model.b_discharge[t] + model.b_charge[t]
else:
return model.b_soc[t] == model.b_soc[t-1] - model.b_discharge[t-1] + model.b_charge[t-1]
#Discharge and charge constraints
def discharge_constraint_1(model,t):
""" Maximum discharge rate within a single hour """
return model.b_discharge[t] <= model.b_discharge_power
def discharge_constraint_2(model,t):<br/>
""" Sets the maximum energy available to be discharged as the SOC - minimum SOC """
return model.b_discharge[t] <= model.b_soc[t] - model.b_min_cap
def charge_constraint_1(model,t):
""" Maximum charge rate within a single hour """
return model.b_charge[t] <= model.b_charge_power
def charge_constraint_2(model,t):<br/>
""" Sets the maximum energy available to be cahrge as the SOC max """
return model.b_charge[t] <= model.b_max_cap - model.b_soc[t]
model.purchase_c = pyo.Constraint(model.T, rule = purchase_constraint)
model.soc_c = pyo.Constraint(model.T, rule = soc_constraint)<br/>
model.discharge_c1 = pyo.Constraint(model.T,rule = discharge_constraint_1)
model.discharge_c2 = pyo.Constraint(model.T,rule = discharge_constraint_2)
model.charge_c1 = pyo.Constraint(model.T,rule = charge_constraint_1)
model.charge_c2 = pyo.Constraint(model.T,rule = charge_constraint_2)
#OBJECTIVE
expr = sum(model.elect_purchased[t] * model.spot_prices[t] for t in model.T)
model.objective = pyo.Objective(rule = expr, sense = pyo.minimize)
opt = pyo.SolverFactory('cbc',executable='/usr/bin/cbc')
results = opt.solve(model)
results.write()
结果1年数据
找到最优解
但是,当我使用相同的模型结构和约束更改数据集时,pyomo 找不到解决方案。
模型3个月:
#Battery parameters
battery_capacity = 252
total_energy = 13.1
usable_energy = 12.4
nominal_voltage = 51.8
ratio = total_energy/usable_energy
power = usable_energy / ratio
nominal_current = usable_energy / nominal_voltage * 1000
recharging_hours = battery_capacity/nominal_current
battery_level_threshold = 0
model = pyo.ConcreteModel()
#Set time period
model.T = pyo.Set(initialize=pyo.RangeSet(len(df_2021)),ordered=True)
#PARAMETERS:
model.b_efficiency = pyo.Param(initialize=0.9)
model.b_min_cap = pyo.Param(initialize=0)
model.b_max_cap = pyo.Param(initialize=12.4)
model.b_charge_power = pyo.Param(initialize=power)
model.b_discharge_power = pyo.Param(initialize=power)
model.spot_prices = pyo.Param(model.T,initialize=dict(enumerate(df_2021["Price/kWh"],1)),within=pyo.Any)
model.demand = pyo.Param(model.T, initialize=dict(enumerate(df_2021["Demand"],1)),within=pyo.Any)
#Variables : also the variable has to be indexed with the time T
model.b_soc = pyo.Var(model.T, domain = pyo.NonNegativeReals, bounds = (model.b_min_cap, model.b_max_cap))
model.b_discharge = pyo.Var(model.T, domain = pyo.NonNegativeReals)
model.b_charge = pyo.Var(model.T, domain = pyo.NonNegativeReals)
model.elect_purchased = pyo.Var(model.T, domain = pyo.NonNegativeReals)
#CONSTRAINTS
#Purchase constraint
def purchase_constraint(model,t):
return model.elect_purchased[t] >= model.demand[t] - model.b_discharge[t] + model.b_charge[t]
#State of charge constraint
def soc_constraint(model,t):
if t == model.T.first():
return model.b_soc[t] == model.b_max_cap / 2 #- model.b_discharge[t] + model.b_charge[t]
else:
return model.b_soc[t] == model.b_soc[t-1] - model.b_discharge[t-1] + model.b_charge[t-1]
#Discharge and charge constraints
def discharge_constraint_1(model,t):
""" Maximum discharge rate within a single hour """
return model.b_discharge[t] <= model.b_discharge_power
def discharge_constraint_2(model,t):<br/>
""" Sets the maximum energy available to be discharged as the SOC - minimum SOC """
return model.b_discharge[t] <= model.b_soc[t] - model.b_min_cap
def charge_constraint_1(model,t):
""" Maximum charge rate within a single hour """
return model.b_charge[t] <= model.b_charge_power
def charge_constraint_2(model,t):<br/>
""" Sets the maximum energy available to be cahrge as the SOC max """
return model.b_charge[t] <= model.b_max_cap - model.b_soc[t]
model.purchase_c = pyo.Constraint(model.T, rule = purchase_constraint)
model.soc_c = pyo.Constraint(model.T, rule = soc_constraint)<br/>
model.discharge_c1 = pyo.Constraint(model.T,rule = discharge_constraint_1)
model.discharge_c2 = pyo.Constraint(model.T,rule = discharge_constraint_2)
model.charge_c1 = pyo.Constraint(model.T,rule = charge_constraint_1)
model.charge_c2 = pyo.Constraint(model.T,rule = charge_constraint_2)
#OBJECTIVE
expr = sum(model.elect_purchased[t] * model.spot_prices[t] for t in model.T)
model.objective = pyo.Objective(rule = expr, sense = pyo.minimize)
opt = pyo.SolverFactory('cbc',executable='/usr/bin/cbc')
results = opt.solv
Pyomo returns:
WARNING: Loading a SolverResults object with a warning status into
model.name="unknown";
- termination condition: unbounded
- message from solver: <undefined>
# ==========================================================
# = Solver Results =
# ==========================================================
# ----------------------------------------------------------
# Problem Information
# ----------------------------------------------------------
Problem:
- Name: unknown
Lower bound: None
Upper bound: inf
Number of objectives: 1
Number of constraints: 16993
Number of variables: 11329
Number of nonzeros: 2832
Sense: minimize
# ----------------------------------------------------------
# Solver Information
# ----------------------------------------------------------
Solver:
- Status: warning
User time: -1.0
System time: 0.45
Wallclock time: 0.58
Termination condition: unbounded
Termination message: Model was proven to be unbounded.
Statistics:
Branch and bound:
Number of bounded subproblems: 0
Number of created subproblems: 0
Black box:
Number of iterations: 0
Error rc: 0
Time: 0.6151924133300781
由于两次运行之间唯一的变化是数据帧的长度,我真的不知道在哪里寻找错误。
鉴于该模型适用于某些数据,但不适用于备用数据源,我们显然可以稍微关注数据集(未显示)。
我们在错误报告中有一个很大的线索,即问题是 unbounded。这意味着在最小化问题的情况下,没有什么可以阻止 objective 函数 运行 趋于无穷大或负无穷大。那么,让我们看看您的 objective 函数。你是:
sum(demand[t] * price[t] for t in T)
您的 demand
变量的域设置为 non-negative 实数(好)并且 price[t]
是从数据中读入的参数,并且您正在最小化。因此,这可能 运行 远离负无穷大的唯一方法是,如果您的数据中存在一个(或多个)负价格,那么求解器将对其采取行动并且 demand
将是无限的。
所以,梳理你的数据集。如果您使用 pandas,只需对价格 < 0 的行使用逻辑搜索,您可能会找到至少一个。然后你必须决定这是一个错字还是负价格是否现实(这可能发生在某些系统中),如果它是合法的,你将不得不施加一些其他约束来限制模型那种情况。
我正在尝试使用 pyomo 为储能系统创建模型优化。使用家庭的千瓦时需求和电价,我想将电池在正确时间充电和放电的成本降至最低。我已经有一个适用于 1 年数据的工作模型,并且该模型能够找到最佳解决方案(请参见下面的代码)。然而,当我试图找到一个只有三个月的模型时(比如从 10 月到 12 月),pyomo returns 终止条件为“未绑定”,但我不明白为什么。
1 年数据的模型:
#Battery parameters
battery_capacity = 252
total_energy = 13.1
usable_energy = 12.4
nominal_voltage = 51.8
ratio = total_energy/usable_energy
power = usable_energy / ratio
nominal_current = usable_energy / nominal_voltage * 1000
recharging_hours = battery_capacity/nominal_current
battery_level_threshold = 0
model = pyo.ConcreteModel()
#Set time period
model.T = pyo.Set(initialize=pyo.RangeSet(len(df_2021)),ordered=True)
#PARAMETERS:
model.b_efficiency = pyo.Param(initialize=0.9)
model.b_min_cap = pyo.Param(initialize=0)
model.b_max_cap = pyo.Param(initialize=12.4)
model.b_charge_power = pyo.Param(initialize=power)
model.b_discharge_power = pyo.Param(initialize=power)
model.spot_prices = pyo.Param(model.T,initialize=dict(enumerate(df_2021["Price/kWh"],1)),within=pyo.Any)
model.demand = pyo.Param(model.T, initialize=dict(enumerate(df_2021["Demand"],1)),within=pyo.Any)
#Variables : also the variable has to be indexed with the time T
model.b_soc = pyo.Var(model.T, domain = pyo.NonNegativeReals, bounds = (model.b_min_cap, model.b_max_cap))
model.b_discharge = pyo.Var(model.T, domain = pyo.NonNegativeReals)
model.b_charge = pyo.Var(model.T, domain = pyo.NonNegativeReals)
model.elect_purchased = pyo.Var(model.T, domain = pyo.NonNegativeReals)
#CONSTRAINTS
#Purchase constraint
def purchase_constraint(model,t):
return model.elect_purchased[t] >= model.demand[t] - model.b_discharge[t] + model.b_charge[t]
#State of charge constraint
def soc_constraint(model,t):
if t == model.T.first():
return model.b_soc[t] == model.b_max_cap / 2 #- model.b_discharge[t] + model.b_charge[t]
else:
return model.b_soc[t] == model.b_soc[t-1] - model.b_discharge[t-1] + model.b_charge[t-1]
#Discharge and charge constraints
def discharge_constraint_1(model,t):
""" Maximum discharge rate within a single hour """
return model.b_discharge[t] <= model.b_discharge_power
def discharge_constraint_2(model,t):<br/>
""" Sets the maximum energy available to be discharged as the SOC - minimum SOC """
return model.b_discharge[t] <= model.b_soc[t] - model.b_min_cap
def charge_constraint_1(model,t):
""" Maximum charge rate within a single hour """
return model.b_charge[t] <= model.b_charge_power
def charge_constraint_2(model,t):<br/>
""" Sets the maximum energy available to be cahrge as the SOC max """
return model.b_charge[t] <= model.b_max_cap - model.b_soc[t]
model.purchase_c = pyo.Constraint(model.T, rule = purchase_constraint)
model.soc_c = pyo.Constraint(model.T, rule = soc_constraint)<br/>
model.discharge_c1 = pyo.Constraint(model.T,rule = discharge_constraint_1)
model.discharge_c2 = pyo.Constraint(model.T,rule = discharge_constraint_2)
model.charge_c1 = pyo.Constraint(model.T,rule = charge_constraint_1)
model.charge_c2 = pyo.Constraint(model.T,rule = charge_constraint_2)
#OBJECTIVE
expr = sum(model.elect_purchased[t] * model.spot_prices[t] for t in model.T)
model.objective = pyo.Objective(rule = expr, sense = pyo.minimize)
opt = pyo.SolverFactory('cbc',executable='/usr/bin/cbc')
results = opt.solve(model)
results.write()
结果1年数据
找到最优解
但是,当我使用相同的模型结构和约束更改数据集时,pyomo 找不到解决方案。
模型3个月:
#Battery parameters
battery_capacity = 252
total_energy = 13.1
usable_energy = 12.4
nominal_voltage = 51.8
ratio = total_energy/usable_energy
power = usable_energy / ratio
nominal_current = usable_energy / nominal_voltage * 1000
recharging_hours = battery_capacity/nominal_current
battery_level_threshold = 0
model = pyo.ConcreteModel()
#Set time period
model.T = pyo.Set(initialize=pyo.RangeSet(len(df_2021)),ordered=True)
#PARAMETERS:
model.b_efficiency = pyo.Param(initialize=0.9)
model.b_min_cap = pyo.Param(initialize=0)
model.b_max_cap = pyo.Param(initialize=12.4)
model.b_charge_power = pyo.Param(initialize=power)
model.b_discharge_power = pyo.Param(initialize=power)
model.spot_prices = pyo.Param(model.T,initialize=dict(enumerate(df_2021["Price/kWh"],1)),within=pyo.Any)
model.demand = pyo.Param(model.T, initialize=dict(enumerate(df_2021["Demand"],1)),within=pyo.Any)
#Variables : also the variable has to be indexed with the time T
model.b_soc = pyo.Var(model.T, domain = pyo.NonNegativeReals, bounds = (model.b_min_cap, model.b_max_cap))
model.b_discharge = pyo.Var(model.T, domain = pyo.NonNegativeReals)
model.b_charge = pyo.Var(model.T, domain = pyo.NonNegativeReals)
model.elect_purchased = pyo.Var(model.T, domain = pyo.NonNegativeReals)
#CONSTRAINTS
#Purchase constraint
def purchase_constraint(model,t):
return model.elect_purchased[t] >= model.demand[t] - model.b_discharge[t] + model.b_charge[t]
#State of charge constraint
def soc_constraint(model,t):
if t == model.T.first():
return model.b_soc[t] == model.b_max_cap / 2 #- model.b_discharge[t] + model.b_charge[t]
else:
return model.b_soc[t] == model.b_soc[t-1] - model.b_discharge[t-1] + model.b_charge[t-1]
#Discharge and charge constraints
def discharge_constraint_1(model,t):
""" Maximum discharge rate within a single hour """
return model.b_discharge[t] <= model.b_discharge_power
def discharge_constraint_2(model,t):<br/>
""" Sets the maximum energy available to be discharged as the SOC - minimum SOC """
return model.b_discharge[t] <= model.b_soc[t] - model.b_min_cap
def charge_constraint_1(model,t):
""" Maximum charge rate within a single hour """
return model.b_charge[t] <= model.b_charge_power
def charge_constraint_2(model,t):<br/>
""" Sets the maximum energy available to be cahrge as the SOC max """
return model.b_charge[t] <= model.b_max_cap - model.b_soc[t]
model.purchase_c = pyo.Constraint(model.T, rule = purchase_constraint)
model.soc_c = pyo.Constraint(model.T, rule = soc_constraint)<br/>
model.discharge_c1 = pyo.Constraint(model.T,rule = discharge_constraint_1)
model.discharge_c2 = pyo.Constraint(model.T,rule = discharge_constraint_2)
model.charge_c1 = pyo.Constraint(model.T,rule = charge_constraint_1)
model.charge_c2 = pyo.Constraint(model.T,rule = charge_constraint_2)
#OBJECTIVE
expr = sum(model.elect_purchased[t] * model.spot_prices[t] for t in model.T)
model.objective = pyo.Objective(rule = expr, sense = pyo.minimize)
opt = pyo.SolverFactory('cbc',executable='/usr/bin/cbc')
results = opt.solv
Pyomo returns:
WARNING: Loading a SolverResults object with a warning status into
model.name="unknown";
- termination condition: unbounded
- message from solver: <undefined>
# ==========================================================
# = Solver Results =
# ==========================================================
# ----------------------------------------------------------
# Problem Information
# ----------------------------------------------------------
Problem:
- Name: unknown
Lower bound: None
Upper bound: inf
Number of objectives: 1
Number of constraints: 16993
Number of variables: 11329
Number of nonzeros: 2832
Sense: minimize
# ----------------------------------------------------------
# Solver Information
# ----------------------------------------------------------
Solver:
- Status: warning
User time: -1.0
System time: 0.45
Wallclock time: 0.58
Termination condition: unbounded
Termination message: Model was proven to be unbounded.
Statistics:
Branch and bound:
Number of bounded subproblems: 0
Number of created subproblems: 0
Black box:
Number of iterations: 0
Error rc: 0
Time: 0.6151924133300781
由于两次运行之间唯一的变化是数据帧的长度,我真的不知道在哪里寻找错误。
鉴于该模型适用于某些数据,但不适用于备用数据源,我们显然可以稍微关注数据集(未显示)。
我们在错误报告中有一个很大的线索,即问题是 unbounded。这意味着在最小化问题的情况下,没有什么可以阻止 objective 函数 运行 趋于无穷大或负无穷大。那么,让我们看看您的 objective 函数。你是:
sum(demand[t] * price[t] for t in T)
您的 demand
变量的域设置为 non-negative 实数(好)并且 price[t]
是从数据中读入的参数,并且您正在最小化。因此,这可能 运行 远离负无穷大的唯一方法是,如果您的数据中存在一个(或多个)负价格,那么求解器将对其采取行动并且 demand
将是无限的。
所以,梳理你的数据集。如果您使用 pandas,只需对价格 < 0 的行使用逻辑搜索,您可能会找到至少一个。然后你必须决定这是一个错字还是负价格是否现实(这可能发生在某些系统中),如果它是合法的,你将不得不施加一些其他约束来限制模型那种情况。