我使用 PYOMO 创建了一个容量扩展模型,其中有 2 个主发电机 gen1 和 gen2 以及一个备用发电机 lost_load。逻辑非常简单,第一代和第二代将运行以满足需求概况,他们将为此获得资金,并且第一代和第二代容量会产生一些负成本。这是代码:
import datetime
import pandas as pd
import numpy as np
from pyomo.environ import *
model = ConcreteModel()
np.random.seed(24)
load_profile = np.random.randint(90, 120, 24)
model.m_index = Set(initialize=list(range(len(load_profile))))
model.grid = Var(model.m_index, domain=NonNegativeReals)
# Gen1 variable
model.gen1_cap = Var(domain=NonNegativeReals)
model.gen1_use = Var(model.m_index, domain=NonNegativeReals)
# Gen2 variables
model.gen2_cap = Var(domain=NonNegativeReals)
model.gen2_use = Var(model.m_index, domain=NonNegativeReals)
# Load profile
model.load_profile = Param(model.m_index, initialize=dict(zip(model.m_index, load_profile)))
model.lost_load = Var(model.m_index, domain=NonNegativeReals)
# Objective function
def revenue(model):
total_revenue = sum(
model.gen1_use[m] * 5.2 +
model.gen2_use[m] * 6.1 +
model.lost_load[m] * -100
for m in model.m_index)
total_fixed_cost = model.gen1_cap * -45 + model.gen2_cap * -50
total_cost = total_revenue + total_fixed_cost
return total_cost
model.obj = Objective(rule=revenue, sense=maximize)
# When i=0
def energy_balance1(model, m):
return model.grid[m] <= model.gen1_use[m] + model.gen2_use[m] + model.lost_load[m]
model.energy_balance1 = Constraint(model.m_index, rule=energy_balance1)
def grid_limit(model, m):
return model.grid[m] == model.load_profile[m]
model.grid_limit = Constraint(model.m_index, rule=grid_limit)
def max_gen1(model, m):
eq = model.gen1_use[m] <= model.gen1_cap
return eq
model.max_gen1 = Constraint(model.m_index, rule=max_gen1)
def max_gen2(model, m):
eq = model.gen2_use[m] <= model.gen2_cap
return eq
model.max_gen2 = Constraint(model.m_index, rule=max_gen2)
Solver = SolverFactory('gurobi')
Solver.options['LogFile'] = "gurobiLog"
# Solver.options['MIPGap'] = 0.50
print('\nConnecting to Gurobi Server...')
results = Solver.solve(model)
if (results.solver.status == SolverStatus.ok):
if (results.solver.termination_condition == TerminationCondition.optimal):
print("\n\n***Optimal solution found***")
print('obj returned:', round(value(model.obj), 2))
else:
print("\n\n***No optimal solution found***")
if (results.solver.termination_condition == TerminationCondition.infeasible):
print("Infeasible solution")
exit()
else:
print("\n\n***Solver terminated abnormally***")
exit()
grid_use = []
gen1 = []
gen2 = []
lost_load = []
load = []
for i in range(len(load_profile)):
grid_use.append(value(model.grid[i]))
gen1.append(value(model.gen1_use[i]))
gen2.append(value(model.gen2_use[i]))
lost_load.append(value(model.lost_load[i]))
load.append(value(model.load_profile[i]))
print('gen1 capacity: ', value(model.gen1_cap))
print('gen2 capacity: ', value(model.gen2_cap))
pd.DataFrame({
'Grid': grid_use,
'Gen1': gen1,
'Gen2': gen2,
'Shortfall': lost_load,
'Load': load
}).to_excel('capacity expansion.xlsx')
此模型将在以下情况下工作:
def energy_balance1(model, m):
return model.grid[m] == model.gen1_use[m] + model.gen2_use[m] + model.lost_load[m]
model.energy_balance1 = Constraint(model.m_index, rule=energy_balance1)
但是如果我将其设置为 (<=) 大于等于),则会出现不可行的错误。我想要这个条件,因为实际上我希望 gen1 和 gen2 在所有 24 个实例中以 100% 容量 运行。毕竟,在目标函数中,我们可以看到与 gen1_use 和 gen2_use 相关的正成本。因此,为了最大化收入,即使满足负载曲线,它们也应该以 100% 的容量运行。这是我得到的输出:
optimal gen1 capacity: 9MW
optimal gen2 capacity: 108MW
尝试这样的事情来开始。我通过包含几个不同的生成器选择来避免将容量作为变量,这看起来非常合理。
这还包含一个二进制“使用”变量,我认为您从所描述的问题中需要它。
import pyomo.environ as pyo
demand = [100, 200, 50, 22, 300]
solar_wind = [20, 100, 100, 0, 100]
net_demand = [d - s for (d, s) in zip(demand, solar_wind)]
print(net_demand)
# generator options...
gen_capacity = {'gen1': 25, 'gen2': 25, 'gen3': 50, 'gen4': 100}
running_cost = {'gen1': 12, 'gen2': 12, 'gen3': 30, 'gen4': 50}
shortage_cost = 100
m = pyo.ConcreteModel()
### SETS
m.T = pyo.Set(initialize=range(len(demand))) # time periods
m.G = pyo.Set(initialize=gen_capacity.keys()) # generators
### VARS
m.install = pyo.Var(m.G, domain=pyo.Binary)
m.use = pyo.Var(m.G, m.T, domain=pyo.Binary)
m.shortage = pyo.Var(m.T, domain=pyo.NonNegativeReals)
### PARAMS
m.use_cost = pyo.Param(m.G, initialize=running_cost)
m.capacity = pyo.Param(m.G, initialize=gen_capacity)
### OBJ
m.cost = pyo.Objective(expr=sum(m.use[g, t] * m.use_cost[g] for g in m.G for t in m.T) +
sum(m.shortage[t] * shortage_cost for t in m.T),
sense=pyo.minimize)
### CONSTRAINTS
def demand(m, t):
return sum(m.use[g, t] * m.capacity[g] for g in m.G) + m.shortage[t] >= net_demand[t]
m.C1 = pyo.Constraint(m.T, rule=demand, doc='meet demand')
def use_if_installed(m, g, t):
return m.use[g, t] <= m.install[g]
m.C2 = pyo.Constraint(m.G, m.T, rule=use_if_installed, doc='can only use if installed')
m.C3 = pyo.Constraint(expr=sum(m.install[g] for g in m.G) <= 2, doc='install at most 2 gens')
solver = pyo.SolverFactory('cbc')
result = solver.solve(m)
print(result)
m.display()
[80, 100, -50, 22, 200]
Problem:
- Name: unknown
Lower bound: 5210.0
Upper bound: 5210.0
Number of objectives: 1
Number of constraints: 25
Number of variables: 28
Number of binary variables: 24
Number of integer variables: 24
Number of nonzeros: 24
Sense: minimize
Solver:
- Status: ok
User time: -1.0
System time: 0.0
Wallclock time: 0.0
Termination condition: optimal
Termination message: Model was solved to optimality (subject to tolerances), and an optimal solution is available.
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.012147188186645508
Solution:
- number of solutions: 0
number of solutions displayed: 0
Model unknown
Variables:
install : Size=4, Index=G
Key : Lower : Value : Upper : Fixed : Stale : Domain
gen1 : 0 : 0.0 : 1 : False : False : Binary
gen2 : 0 : 0.0 : 1 : False : False : Binary
gen3 : 0 : 1.0 : 1 : False : False : Binary
gen4 : 0 : 1.0 : 1 : False : False : Binary
use : Size=20, Index=use_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
('gen1', 0) : 0 : 0.0 : 1 : False : False : Binary
('gen1', 1) : 0 : 0.0 : 1 : False : False : Binary
('gen1', 2) : 0 : 0.0 : 1 : False : False : Binary
('gen1', 3) : 0 : 0.0 : 1 : False : False : Binary
('gen1', 4) : 0 : 0.0 : 1 : False : False : Binary
('gen2', 0) : 0 : 0.0 : 1 : False : False : Binary
('gen2', 1) : 0 : 0.0 : 1 : False : False : Binary
('gen2', 2) : 0 : 0.0 : 1 : False : False : Binary
('gen2', 3) : 0 : 0.0 : 1 : False : False : Binary
('gen2', 4) : 0 : 0.0 : 1 : False : False : Binary
('gen3', 0) : 0 : 0.0 : 1 : False : False : Binary
('gen3', 1) : 0 : 0.0 : 1 : False : False : Binary
('gen3', 2) : 0 : 0.0 : 1 : False : False : Binary
('gen3', 3) : 0 : 1.0 : 1 : False : False : Binary
('gen3', 4) : 0 : 1.0 : 1 : False : False : Binary
('gen4', 0) : 0 : 1.0 : 1 : False : False : Binary
('gen4', 1) : 0 : 1.0 : 1 : False : False : Binary
('gen4', 2) : 0 : 0.0 : 1 : False : False : Binary
('gen4', 3) : 0 : 0.0 : 1 : False : False : Binary
('gen4', 4) : 0 : 1.0 : 1 : False : False : Binary
shortage : Size=5, Index=T
Key : Lower : Value : Upper : Fixed : Stale : Domain
0 : 0 : 0.0 : None : False : False : NonNegativeReals
1 : 0 : 0.0 : None : False : False : NonNegativeReals
2 : 0 : 0.0 : None : False : False : NonNegativeReals
3 : 0 : 0.0 : None : False : False : NonNegativeReals
4 : 0 : 50.0 : None : False : False : NonNegativeReals
Objectives:
cost : Size=1, Index=None, Active=True
Key : Active : Value
None : True : 5210.0
Constraints:
C1 : Size=5
Key : Lower : Body : Upper
0 : 80.0 : 100.0 : None
1 : 100.0 : 100.0 : None
2 : -50.0 : 0.0 : None
3 : 22.0 : 50.0 : None
4 : 200.0 : 200.0 : None
C2 : Size=20
Key : Lower : Body : Upper
('gen1', 0) : None : 0.0 : 0.0
('gen1', 1) : None : 0.0 : 0.0
('gen1', 2) : None : 0.0 : 0.0
('gen1', 3) : None : 0.0 : 0.0
('gen1', 4) : None : 0.0 : 0.0
('gen2', 0) : None : 0.0 : 0.0
('gen2', 1) : None : 0.0 : 0.0
('gen2', 2) : None : 0.0 : 0.0
('gen2', 3) : None : 0.0 : 0.0
('gen2', 4) : None : 0.0 : 0.0
('gen3', 0) : None : -1.0 : 0.0
('gen3', 1) : None : -1.0 : 0.0
('gen3', 2) : None : -1.0 : 0.0
('gen3', 3) : None : 0.0 : 0.0
('gen3', 4) : None : 0.0 : 0.0
('gen4', 0) : None : 0.0 : 0.0
('gen4', 1) : None : 0.0 : 0.0
('gen4', 2) : None : -1.0 : 0.0
('gen4', 3) : None : -1.0 : 0.0
('gen4', 4) : None : 0.0 : 0.0
C3 : Size=1
Key : Lower : Body : Upper
None : None : 2.0 : 2.0