如何使用PYOMO创建一个简单的扩容模型,同时最大化收入?

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

我使用 PYOMO 创建了一个容量扩展模型,其中有 2 个主发电机 gen1gen2 以及一个备用发电机 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_usegen2_use 相关的正成本。因此,为了最大化收入,即使满足负载曲线,它们也应该以 100% 的容量运行。这是我得到的输出:

optimal gen1 capacity: 9MW
optimal gen2 capacity: 108MW

虽然我想要这样的调度:

python pyomo capacity-planning
1个回答
0
投票

尝试这样的事情来开始。我通过包含几个不同的生成器选择来避免将容量作为变量,这看起来非常合理。

这还包含一个二进制“使用”变量,我认为您从所描述的问题中需要它。

代码:

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
© www.soinside.com 2019 - 2024. All rights reserved.