I have create a capacity expansion model using PYOMO, where there are 2 main generators gen1 and gen2 and one backup generator lost_load. The logic is pretty simple, gen1 and gen2 will run to fulfill the demand profile and they will get the money for that and there is some negative cost associated with gen1 and gen2 capacity. Here is the code:
import datetimeimport pandas as pdimport numpy as npfrom 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 variablemodel.gen1_cap = Var(domain=NonNegativeReals)model.gen1_use = Var(model.m_index, domain=NonNegativeReals)# Gen2 variablesmodel.gen2_cap = Var(domain=NonNegativeReals)model.gen2_use = Var(model.m_index, domain=NonNegativeReals)# Load profilemodel.load_profile = Param(model.m_index, initialize=dict(zip(model.m_index, load_profile)))model.lost_load = Var(model.m_index, domain=NonNegativeReals)# Objective functiondef 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_costmodel.obj = Objective(rule=revenue, sense=maximize)# When i=0def 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 eqmodel.max_gen1 = Constraint(model.m_index, rule=max_gen1)def max_gen2(model, m): eq = model.gen2_use[m] <= model.gen2_cap return eqmodel.max_gen2 = Constraint(model.m_index, rule=max_gen2)Solver = SolverFactory('gurobi')Solver.options['LogFile'] = "gurobiLog"# Solver.options['MIPGap'] = 0.50print('\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')
This model will work if :
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)
But if I set it to (<=) greater than equal to, it gives me an infeasible error. I want this condition because in actuality I want gen1 and gen2 to run at 100% capacity at all 24 instances. After all, in the objective function, we can see the positive cost associated with gen1_use and gen2_use. So to maximize the revenue they should run at 100% capacity even if the load profile is fulfilled. Here is the output I am getting:
optimal gen1 capacity: 9MWoptimal gen2 capacity: 108MW