background
I am trying to write an pyomo script to optimally dispatch a gas plant based on perfect foresight of electricity prices. I believe I am 90% of the way there, just a few issues.
Problem
My script works, but the solver is never dispatching the plant, even where it should be, in the example provided below, manually I can calculate at least $8131 of potential profit.
I suspect the reason for my zero results is due to how I've written the constraints, of which there are 2;
- Gas Plant takes 10 minutes to boot up from a cold start
- Once warmed up, the gas plant has a min load it must operate at/above.
- Gas Plant can only consume 9000 GJ of gas in a single day
Specifically on further testing, I think it is the 'gas_volume_used' constraint which is causing the issue.
Help Requested
Could someone please have a look at my code and see what I am missing in the constraint equations?
Code
import pandas as pd
from pyomo.environ import *
from pyomo.core import *
# ---------------------
# Main Functions
# ---------------------
def model_to_df(model, first_period, last_period):
# Need to increase the first & last period by 1 because of pyomo indexing
# and add 1 to the value of last model period because of the range
periods = range(model.T[first_period + 1], model.T[last_period + 1] + 1)
rrp = [model.P.extract_values()[None][i] for i in periods]
Gascold = [value(model.Gascold[i]) for i in periods]
Gaswarm = [value(model.Gaswarm[i]) for i in periods]
Gashot = [value(model.Gashot[i]) for i in periods]
GasUnit_mw = [value(model.GasUnit_mw[i]) for i in periods]
GasUse = [value(model.GasUse[i]) for i in periods]
df_dict = dict(
period=periods,
rrp=rrp,
Gascold=Gascold,
Gaswarm=Gaswarm,
Gashot=Gashot,
GasUnit_mw=GasUnit_mw,
GasUse=GasUse
)
df = pd.DataFrame(df_dict)
return df
def optimize_year(df, gunits, gnet, gprice, vom, cold_gas_p_unit, hr, max_gas, sys_use,
first_model_period, last_model_period):
"""
Optimize behavior of a gas plant over a time period
Assume perfect foresight of electricity prices.
Parameters
----------
df : dataframe
dataframe with columns of hourly LBMP and the hour of the year
gunits : int
number of gas turbines
gnet : double
size in MW's of each gas turbine
gprice : double
the price of gas $/GJ
vom : double
the variable cost of gas $/MWh dispatched
cold_gas_p_unit : double
the amount of gas in GJ used in 5 minutes while booting up from cold start
hr : double
heat rate, the amount of gas consumed per MWh produced (GJ/MWh)
sys_use : double
auxillary gas usage, percentage
max_gas : double
the maximum amount of gas (GJ) available to use in a period
first_model_period : int, optional
Set the first period of the year to be considered in the optimization
last_model_period : int, optional
Set the last period of the year to be considered in the optimization
Returns
-------
dataframe
Gas plant operational stats 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.P = Param(initialize=df.rrp.tolist(), doc='price for each period', within=Any)
model.MaxGas = Param(initialize=(gnet/12) * gunits * hr * (1+sys_use), doc='Max gas usage (GJ) in interval', within=Any)
model.MaxVol = Param(initialize=max_gas, doc='Max gas usage (GJ) in 24 hour period', within=Any)
model.MaxMW = Param(initialize=(gnet/12) * gunits, doc='Max MWs', within=Any)
model.Gascold = Var(model.T, doc='binary', within=Binary, initialize=1)
model.Gaswarm = Var(model.T, doc='binary', within=Binary, initialize=0)
model.Gashot = Var(model.T, doc='binary', within=Binary, initialize=0)
model.GasUse = Var(model.T, bounds=(0, model.MaxGas))
model.GasUnit_mw = Var(model.T, bounds=(0, model.MaxMW))
# *********************
# GAS CONSTRAINTS
# *********************
"Ensure that all three Gas State flags sum to 1"
def set_on(model, t):
return model.Gascold[t] + model.Gaswarm[t] + model.Gashot[t] == 1
model.limit_flags = Constraint(model.T, rule=set_on)
"Set t0 to GAS COLD = 1"
def gas_cold(model, t):
if t == model.T.first():
return model.Gascold[t] == 1
else:
return model.Gascold[t] >= 0
model.gas_cold_check = Constraint(model.T, rule=gas_cold)
"Gas can only warm up from cold"
def gas_warm(model, t):
if t < 2:
return model.Gaswarm[t] == 0
elif (model.Gaswarm[t-2] + model.Gaswarm[t-1] + model.Gascold[t-1]) == 1:
return model.Gaswarm[t] >= 0
else:
return model.Gaswarm[t] == 0
model.gas_warm_check = Constraint(model.T, rule=gas_warm)
"gas can only get hot from warm"
def gas_hot(model, t):
if t <= 2:
return model.Gashot[t] == 0
elif value(model.Gaswarm[t-2] + model.Gaswarm[t-1] + model.Gashot[t-2] + model.Gashot[t-1]) < 2:
return model.Gashot[t] == 0
else:
return model.Gashot[t] >= 0
model.gas_hot_check = Constraint(model.T, rule=gas_hot)
"Gas hot min load"
def gas_hot_min_load(model, t):
if model.Gashot[t] == 1:
return model.GasUnit_mw[t] >= 30 / 12
else:
return model.GasUnit_mw[t] == 0
model.gas_hot_min_load_check = Constraint(model.T, rule=gas_hot_min_load)
"Gas volume used"
def gas_volume_used(model, t):
'how much gas is being used?'
return model.GasUse[t] == (model.GasUnit_mw[t] * hr * (1+sys_use)) + (model.Gaswarm[t] * gunits * cold_gas_p_unit * (1+sys_use))
model.gas_vol = Constraint(model.T, rule=gas_volume_used)
"only 9000 GJ of gas can be used in a single day"
def daily_max_gas(model, t):
min_t = model.T.first()
max_t = model.T.last()
# Check all t until the last 24 hours
if t <= max_t:
return sum(model.GasUse[i] for i in range(min_t, max_t+1)) <= model.MaxVol
else:
return Constraint.Skip
model.max_gas_vol = Constraint(model.T, rule=daily_max_gas)
# Define the income, expenses, and profit
gas_income = sum(df.loc[t, 'rrp'] * model.GasUnit_mw[t] for t in model.T)
gas_expenses = sum(model.GasUse[t] * gprice + model.GasUnit_mw[t] * vom for t in model.T)
profit = gas_income - gas_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
# *********************
# RUN SCRIPT
# *********************
# Import file
df = pd.read_csv('raw_prices.csv')
# New Index
df['period'] = df.reset_index().index
# get current Year
first_model_period = 0
last_model_period = df['period'].iloc[-1]
# Gas Inputs
gunits = 3 # No units
gnet = 58.5 # MW/unit
srmc = 115.49 # $/MWh
vom = 6.18 # $/MWh
gprice = 10.206 # $/GJ
cold_gas_p_unit = 15.75 # GJ/unit/5mins
hr = 10.5 # GJ/MWh
sys_use = 0.02 # %
max_gas = 9000 # GJ
# Run Model
results_df = optimize_year(df=df, gunits=gunits, gnet=gnet, gprice=gprice, vom=vom, cold_gas_p_unit=cold_gas_p_unit,
hr=hr, max_gas=max_gas, sys_use=sys_use,
first_model_period=first_model_period, last_model_period=last_model_period)
# ---------------------
# Write the file out and remove the index
# ---------------------
print('ran successfully')
results_df.to_csv('optimised_output.csv', index=False)
Example Data
local_time rrp
3/07/2022 16:40 64.99
3/07/2022 16:45 73.10744
3/07/2022 16:50 74.21746
3/07/2022 16:55 95.90964
3/07/2022 17:00 89.60728
3/07/2022 17:05 90.37018
3/07/2022 17:10 92.52947
3/07/2022 17:15 100.1449
3/07/2022 17:20 103.70199
3/07/2022 17:25 290.2848
3/07/2022 17:30 115.40232
3/07/2022 17:35 121.00009
3/07/2022 17:40 294.62543
3/07/2022 17:45 121
3/07/2022 17:50 294.69446
3/07/2022 17:55 124.90664
3/07/2022 18:00 117.21929
3/07/2022 18:05 115.60193
3/07/2022 18:10 121
3/07/2022 18:15 148.20186
3/07/2022 18:20 123.11763
3/07/2022 18:25 120.99996
3/07/2022 18:30 121
3/07/2022 18:35 121
3/07/2022 18:40 121
3/07/2022 18:45 120.77726
3/07/2022 18:50 105.44435
3/07/2022 18:55 104.22928
3/07/2022 19:00 102.26646
3/07/2022 19:05 99.5896
3/07/2022 19:10 95.5881
3/07/2022 19:15 88
3/07/2022 19:20 88
3/07/2022 19:25 88
3/07/2022 19:30 96.97537
3/07/2022 19:35 88
3/07/2022 19:40 86.39322
3/07/2022 19:45 88
3/07/2022 19:50 85.97703
3/07/2022 19:55 86
3/07/2022 20:00 87.61047
3/07/2022 20:05 88
3/07/2022 20:10 88
3/07/2022 20:15 85.34908
3/07/2022 20:20 85.1
3/07/2022 20:25 71.01181
3/07/2022 20:30 84.88422
3/07/2022 20:35 85.39669
3/07/2022 20:40 86.02542
3/07/2022 20:45 84.87425
3/07/2022 20:50 70.0652
3/07/2022 20:55 70.65106
3/07/2022 21:00 70.53063
3/07/2022 21:05 68.27
3/07/2022 21:10 68.86159
3/07/2022 21:15 68.32018
3/07/2022 21:20 67.30473
3/07/2022 21:25 66.78292
3/07/2022 21:30 66.02195
3/07/2022 21:35 66.40755