首页 > 解决方案 > 使用 pyomo 和 pao 优化电池容量和运行计划。两阶段随机优化的尝试

问题描述

更新:添加了潜在的答案。

我正在尝试编写一个多目标 pyomo 优化脚本,该脚本基于最小化成本来优化电池容量和运行计划。它应该回答什么是电池的最佳尺寸,通过在价格低时充电和在价格低时放电来减少电费,例如在价格高时尽量减少电网购买。当给定电池容量时,我已经能够优化运行计划。但是,我在实施容量优化时遇到了问题。给出了建筑物的每小时负载曲线(用电量)和每小时的电力购买价格(负载[i] 和价格[i])。成本与电池容量的每千瓦时价格是一个常数。

我正在使用 ipopt 求解器。

我尝试了不同的方法。第一个确实提供了最佳时间表和容量。但是,我不确定这是否是最佳容量的正确方法,因为它是在相同的目标函数中实现的,即最小化电池成本 + 购买电力成本 - 从电池放电的能量成本。

m =  AbstractModel()
m.cap = Var(domain=Reals, bounds=(10,1000)) #battery capacity
m.pe_c = Var(time,domain=NonNegativeReals,bounds=(0,1000)) #charge to battery
m.pe_d = Var(time,domain=NegativeReals,bounds=(-1000,0)) #discharge from battery
m.soc = Var(soc_time,bounds=(value(min_soc),value(max_soc))) #state of charge between 20% and 95% 

def obj_expression(m): 
return  ((m.cap *cost)+sum([price[i]*(load[i]) for i in time])) + sum([price[i]*(m.pe_c[i]) for i in time]) + sum([price[i]*(m.pe_d[i]) for i in time])  
m.OBJ = Objective(rule=obj_expression, sense=minimize) 

def soc_constraint_rule(m,i):
return m.soc[i+1] - m.soc[i] <=  float(100) * dt * (m.pe_d[i] + m.pe_c[i]*energy_efficiency + self_discharge_power) / m.cap
m.soc_constraints = Constraint(time, rule=soc_constraint_rule)

def soc_start_rule(m):  #battery starts at initial value
return m.soc[0] == soc_init
m.soc_start = Constraint(rule=soc_start_rule)

def soc_end_rule(m):  #battery returns to initial value at end of simulation
return m.soc[n] == m.soc[0]
m.soc_end = Constraint(rule=soc_end_rule)

def discharge_rule(m,i): # Cannot discharge more than what battery can hold 
return m.pe_d[i] <= m.cap *C_rate #C_rate is an efficiency constant, in this case 0.9
m.discharge_rule = Constraint(time, rule=discharge_rule)

def charge_rule(m,i): #cannot charge more than battery can hold  
return m.pe_c[i] <= m.cap *C_rate
m.charge_rule = Constraint(time, rule=charge_rule)

instance = m.create_instance()
solver = SolverFactory('ipopt')
solver.solve(instance,tee=True) 

我的第二种方法不能正常工作以优化运营计划以最小化从电网购买能源的成本,但它确实返回了运营计划和容量。在这种方法中,我尝试使用能量状态(SOE)而不是 SOC,使容量成为 SOE 的限制,这应该避免 SOC 约束中的非凸性可能导致 iopt 问题收敛。这个不返回 m.utility 值,我通常不确定目标函数是否正确,以及它是否避免同时发生充电和放电,这在现实生活中是不可能的。

m =  AbstractModel()
m.cap = Var(domain=Reals, bounds=(10,1000)) #battery capacity variable
m.utility = Var(time,domain=NonNegativeReals) #purchase from grid
m.pe_c = Var(time,domain=NonNegativeReals,bounds=(0,1000)) #charge to battery
m.pe_d = Var(time,domain=NegativeReals,bounds=(-1000,0)) #discharge from battery
m.SOE =Var(soe_time, domain=NonNegativeReals,bounds=(0,1000)) #state of energy inside battery

def obj_expression(m):
return  (m.cap*cost) +  sum([price[i]*(m.utility[i]) for i in time])
m.OBJ = Objective(rule=obj_expression, sense=minimize)

def battery_rule(m,i):  #to connect the states of the amount of energy stored at, energy discharged from, energy charged to, the battery system
return m.SOE[i+1] == m.SOE[i] + m.pe_c[i] + m.pe_d[i] 
m.battery_rule = Constraint(time, rule=battery_rule)

def capacity_rule(m,i):  # a physical constraint that limits the amount of energy stored in the battery system
return m.SOE[i+1] <= m.cap
m.capacity_rule = Constraint(time, rule=capacity_rule)

def charging_rule(m,i):  # a physical constraint that limits the amount of energy charged to the battery system
return m.pe_c[i] <= m.cap * C_rate
m.charging_rule = Constraint(time, rule=charging_rule)

def discharging_rule(m,i):  # a physical constraint that limits the amount of energy discharged from the battery system
return  m.pe_d[i] <= m.cap * C_rate
m.discharging_rule = Constraint(time, rule=discharging_rule)

def soe_start_rule(m): 
return m.SOE[0] == soe_init
m.soe_start_rule = Constraint(rule=soe_start_rule)

def soe_end_rule(m):
return m.SOE[n] == m.SOE[0]
m.soe_end = Constraint(rule=soe_end_rule)

def demand_rule(m,i): #a physical constraint, representing the law of conservation of energy
return m.pe_d[i] + m.pe_c[i]  + m.utility[i]  ==  load[i]
m.demand_rule = Constraint(time, rule=demand_rule)

instance = m.create_instance()
solver = SolverFactory('ipopt')
solver.solve(instance,tee=True)

我的最后一种方法是使用 PAO 库来访问双层优化,这根本不起作用。这可能是最好的方法,因为它清楚地定义了上层和下层目标,但是我在使用子模型构建迭代约束时遇到了问题。下面的代码返回“TypeError: 'NoneType' object is not iterable”。但是,我确信由于人为错误,这种方法存在更大的逻辑错误。我还尝试通过交换变量 m 将容量 * 成本作为上层目标。与 m.sub; 将 m.sub.cap 转换为 m.cap 并将 m.utility 转换为 m.sub.utility 等,但是当我运行求解器时会返回“AttributeError: 'NoneType' object has no attribute 'transpose'”。

m =  AbstractModel()
m.sub = pao.SubModel()
m.sub.cap = Var(domain=Reals, bounds=(10,1000)) #battery capacity variable
m.utility = Var(time,domain=NonNegativeReals) #purchase from grid
m.pe_c = Var(time,domain=NonNegativeReals,bounds=(0,1000)) #charge to battery
m.pe_d = Var(time,domain=NegativeReals,bounds=(-1000,0)) #discharge from battery
m.SOE =Var(soe_time, domain=NonNegativeReals,bounds=(0,1000)) #state of energy inside battery

def obj_expression(m):
return   sum([price[i]*(m.pe_c[i]) for i in time]) +sum([price[i]*(m.pe_d[i]) for i in time])
m.OBJ = Objective(rule=obj_expression, sense=minimize)

m.sub.obj = Objective(expr=m.sub.cap*cost, sense=minimize)

def battery_rule(m,i):  #to connect the states of the amount of energy stored at, energy discharged from, energy charged to, the battery system
return m.SOE[i+1] == m.SOE[i] + m.pe_c[i] + m.pe_d[i] 
m.battery_rule = Constraint(time, rule=battery_rule)

def capacity_rule(m,i):  # a physical constraint that limits the amount of energy stored in the battery system
return m.SOE[i+1] <= m.sub.cap
m.capacity_rule = Constraint(time, rule=capacity_rule)

def charging_rule(m,i):  # a physical constraint that limits the amount of energy charged to the battery system
return m.pe_c[i] <= m.sub.cap * C_rate
m.charging_rule = Constraint(time, rule=charging_rule)

def discharging_rule(m,i):  # a physical constraint that limits the amount of energy discharged from the battery system
return  m.pe_d[i] <= m.sub.cap * C_rate
m.discharging_rule = Constraint(time, rule=discharging_rule)

def soe_start_rule(m): 
return m.SOE[0] == soe_init
m.soe_start_rule = Constraint(rule=soe_start_rule)

def soe_end_rule(m):
return m.SOE[n] == m.SOE[0]
m.soe_end = Constraint(rule=soe_end_rule)

def demand_rule(m,i): #a physical constraint, representing the law of conservation of energy
return m.pe_d[i] + m.pe_c[i]  + m.utility[i]  ==  load[i]
m.demand_rule = Constraint(time, rule=demand_rule)

nlp = pao.Solver('ipopt')
opt = pao.Solver('pao.pyomo.REG', nlp_solver=nlp)
results = opt.solve(m)

我写这篇文章是希望有人做过类似的多目标两阶段随机优化,并且能够看到我的人为错误或给我反馈什么方法更好。请询问我是否有不清楚的地方,因为我可以提供更多详细信息。

更新:我正在玩两个新的约束,它们可能对上述模型有帮助,也可能没有帮助。他们在下面

def cost_reduction(m,i): #making sure the without-battery cost of electricity is higher than the with-battery cost of electricity.
return (m.pe_c[i]*price[i]) + (m.pe_d[i]*price[i]) + (load[i]*price[i]) <= (load[i]*price[i])
m.cost_reduction = Constraint(time, rule=cost_reduction)

def battery_cost(m,i): #make sure recover battery cost M.pe_d is a negative number and m.year is bounded between 0,20.
return (m.cap*cost) + (m.pe_d[i]*price[i]) *m.year ==0 #I am aware that this is not the correct way but it shows the idea of a constraint that attempts to ensure a payback
m.battery_cost = Constraint(time, rule=battery_cost)

标签: pythonoptimizationpyomo

解决方案


我相信我已经使用 gurobi 求解器解决了它。必须更改 demand_rule 并添加 binary_charge_constraint

m =  ConcreteModel()
m.cap = Var(domain=Reals, bounds=(10,1000))#battery capacity variable
m.utility = Var(time,domain=NonNegativeReals) #purchase from grid
m.pe_c = Var(time,domain=NonNegativeReals,bounds=(0,1000)) #charge to battery
m.pe_d = Var(time,domain=NonNegativeReals,bounds=(0,1000)) #discharge from battery
m.SOE =Var(soe_time, domain=NonNegativeReals,bounds=(0,1000)) #state of energy inside battery

def obj_expression(m):
return (m.cap*cost) +  sum([price[i]*(m.utility[i]) for i in time]) 
m.OBJ = Objective(rule=obj_expression, sense=minimize) 

def battery_rule(m,i):  #to connect the states of the amount of energy stored at, energy discharged from, energy charged to, the battery system
return m.SOE[i+1] == m.SOE[i] + m.pe_c[i] - m.pe_d[i] 
m.battery_rule = Constraint(time, rule=battery_rule)

def capacity_rule(m,i):  # a physical constraint that limits the amount of energy stored in the battery system
return m.SOE[i] <= m.cap
m.capacity_rule = Constraint(time, rule=capacity_rule)

def charging_rule(m,i):  # a physical constraint that limits the amount of energy charged to the battery system
return m.pe_c[i] <= m.cap * C_rate
m.charging_rule = Constraint(time, rule=charging_rule)

def discharging_rule(m,i):  # a physical constraint that limits the amount of energy discharged from the battery system
return  m.pe_d[i] <= m.cap * C_rate
m.discharging_rule = Constraint(time, rule=discharging_rule)

def soe_start_rule(m): 
return m.SOE[0] == soe_init
m.soe_start_rule = Constraint(rule=soe_start_rule)

def soe_end_rule(m):
return m.SOE[n] == m.SOE[0]
m.soe_end = Constraint(rule=soe_end_rule)

def demand_rule(m,i): #a physical constraint, representing the law of conservation of energy
return m.pe_d[i] - m.pe_c[i]  + m.utility[i]  ==  load[i]
m.demand_rule = Constraint(time, rule=demand_rule)

def binary_charge_constraint(m,i): #making sure it is not charging while discharging and viceversa
return m.pe_d[i]*m.pe_c[i]== 0
m.binary_charge_constraint = Constraint(time, rule=binary_charge_constraint)

instance = m.create_instance()
opt = SolverFactory("gurobi")
opt.options['NonConvex'] = 2
results = opt.solve(instance, tee=True)

推荐阅读