How to add time limit to gurobi solver in Pypsa model?

109 views Asked by At

I have create a model using Pypsa and I am using gurobi solver to solve it. I am testing the time limit parameter of Gurobi. Here is how I am adding it to the model :

solverOptions = {
        'LogFile': "gurobiLog",
        'MIPGap': model_inputs['model_data']['solverMipGap'], # 0.001
        'BarConvTol': model_inputs['model_data']['BarConvTol'], # 0.01
        'TimeLimit': 200, # 200 Seconds
    }

    network.lopf(network.snapshots, solver_name='gurobi', solver_options=solverOptions, extra_functionality=extra_functionality)

I have set the time limit of 200 seconds here and I can see that in the Gurobi log file these parameters are getting applied there:

Gurobi 10.0.1 (win64) logging started Tue Dec 12 19:51:07 2023

Set parameter LogFile to value "gurobiLog"
Set parameter MIPGap to value 0.001
Set parameter BarConvTol to value 0.01
Set parameter TimeLimit to value 5

after 5 seconds in the log file I can see the solver is stopped and this message is printed :

Stopped in 136184 iterations and 5.04 seconds (280.23 work units)
Time limit reached

But in the code, I see this error:

network.lopf(network.snapshots, solver_name=solver, solver_options=solverOptions, extra_functionality=extra_functionality)
  File "C:\Users\nvats\AppData\Local\Continuum\anaconda3\envs\production-cost-model_test\lib\site-packages\pypsa\components.py", line 769, in lopf
    return network_lopf(self, **args)
  File "C:\Users\nvats\AppData\Local\Continuum\anaconda3\envs\production-cost-model_test\lib\site-packages\pypsa\opf.py", line 2437, in network_lopf
    extra_postprocessing=extra_postprocessing,
  File "C:\Users\nvats\AppData\Local\Continuum\anaconda3\envs\production-cost-model_test\lib\site-packages\pypsa\opf.py", line 2296, in network_lopf_solve
    options=solver_options
  File "C:\Users\nvats\AppData\Local\Continuum\anaconda3\envs\production-cost-model_test\lib\site-packages\pyomo\opt\base\solvers.py", line 630, in solve
    default_variable_value=self._default_variable_value)
  File "C:\Users\nvats\AppData\Local\Continuum\anaconda3\envs\production-cost-model_test\lib\site-packages\pyomo\core\base\PyomoModel.py", line 228, in load_from
    % str(results.solver.status))
ValueError: Cannot load a SolverResults object with bad status: aborted

I don't understand, it should return the solution that it found by that time but it's saying that it is aborted. Can anyone please help? Is there any other way of doing it? I am also adding my code here:

import pypsa
import numpy as np
import pandas as pd
from pyomo.environ import Constraint
from pyomo.environ import value

start_mt = 1
start_yr = 2022
end_mt = 12
end_yr = 2022
end_day = 31
frequency = 15

snapshots = pd.date_range("{}-{}-01".format(start_yr, start_mt), "{}-{}-{} 23:59".format(end_yr, end_mt, end_day),
                          freq=str(frequency) + "min")
np.random.seed(len(snapshots))

# Create a PyPSA network
network = pypsa.Network()

# Add a load bus
network.add("Bus", "Bus")
network.set_snapshots(snapshots)

load_profile = np.random.randint(2800, 3300, len(snapshots))

# Add the load to the network
network.add("Load", "Load profile", bus="Bus", p_set=load_profile)

# Define the generator data dictionary
generator_data = {
    'coal1': {'capacity': 800, 'carrier': 'Coal', 'ramp up': 0.1, 'ramp down': 0.1, 'variable cost': 10, 'co2_emission_factor': 0.95},
    'coal2': {'capacity': 600, 'carrier': 'Coal', 'ramp up': 0.1, 'ramp down': 0.1, 'variable cost': 11, 'co2_emission_factor': 0.95},
    'coal3': {'capacity': 500, 'carrier': 'Coal', 'ramp up': 0.1, 'ramp down': 0.1, 'variable cost': 11, 'co2_emission_factor': 0.95},
    'gas1': {'capacity': 600, 'carrier': 'Gas', 'ramp up': 0.05, 'ramp down': 0.05, 'variable cost': 12, 'co2_emission_factor': 0.45},
    'gas2': {'capacity': 600, 'carrier': 'Gas', 'ramp up': 0.05, 'ramp down': 0.05, 'variable cost': 13, 'co2_emission_factor': 0.45},
    'nuclear1': {'capacity': 300, 'carrier': 'Nuclear', 'ramp up': 0.01, 'ramp down': 0.01, 'variable cost': 4, 'co2_emission_factor': 0.03},
    'nuclear2': {'capacity': 400, 'carrier': 'Nuclear', 'ramp up': 0.01, 'ramp down': 0.01, 'variable cost': 3, 'co2_emission_factor': 0.03},
    'nuclear3': {'capacity': 250, 'carrier': 'Nuclear', 'ramp up': 0.01, 'ramp down': 0.01, 'variable cost': 3, 'co2_emission_factor': 0.03},
    'solar1': {'capacity': 150, 'carrier': 'Solar', 'ramp up': 0.25, 'ramp down': 0.25, 'variable cost': 1, 'co2_emission_factor': 0.0},
    'solar2': {'capacity': 200, 'carrier': 'Solar', 'ramp up': 0.25, 'ramp down': 0.25, 'variable cost': 2, 'co2_emission_factor': 0.0},
    'backup': {'capacity': 1000, 'carrier': 'Import', 'ramp up': 0.25, 'ramp down': 0.25, 'variable cost': 2000, 'co2_emission_factor': 1.0},
}


# Add generators to the network
for name, data in generator_data.items():
    network.add("Generator", name,
                bus="Bus",
                carrier=data['carrier'],
                p_nom=data['capacity'],
                marginal_cost=data['variable cost'],
                ramp_limit_up=data['ramp up'],
                ramp_limit_down=data['ramp down'],
                )

print(network.generators.carrier.values)
network.add("Carrier", "Coal", co2_emissions=0.95)
network.add("Carrier", "Gas", co2_emissions=0.45)
network.add("Carrier", "Nuclear", co2_emissions=0.03)
network.add("Carrier", "Import", co2_emissions=1.0)
network.add("Carrier", "Solar", co2_emissions=0)


network.add(
    "GlobalConstraint",
    "CO2Limit",
    carrier_attribute="co2_emissions",
    sense="<=",
    constant=50000000,
)


solver_name = "gurobi"
solverOptions = {
        'LogFile': "gurobiLog",
        'MIPGap': 0.001,
        'BarConvTol': 0.01,
        'TimeLimit': 5,
    }
network.lopf(network.snapshots, solver_name=solver_name, solver_options=solverOptions)

csv_folder_name = 'model dump'
network.export_to_csv_folder(csv_folder_name)


dispatch = network.generators_t.p
total_gen = dispatch.sum()

co2 = sum([total_gen[gen] * data['co2_emission_factor'] for gen, data in generator_data.items()])
cost = sum([total_gen[gen] * data['variable cost'] for gen, data in generator_data.items()])
print('co2 emission = ', co2)
print('total cost = ', cost)
dispatch['load profile'] = load_profile

dispatch.to_excel('fuel wise dispatch.xlsx')
1

There are 1 answers

2
mattmilten On

I ran your code just fine. The time limit is respected and works as intended. The only thing I had to change was the call to lopf. This caused a deprecation warning that clearly states that the method optimize should be used instead. Everything worked fine with that change. Here's the final output with a time limit of 20 seconds:

INFO:gurobipy.gurobipy:
Solved in 256542 iterations and 13.88 seconds (31.22 work units)
INFO:gurobipy.gurobipy:Solved in 256542 iterations and 13.88 seconds (31.22 work units)
Optimal objective  1.107350697e+09
INFO:gurobipy.gurobipy:Optimal objective  1.107350697e+09
INFO:linopy.constants: Optimization successful: 
Status: ok
Termination condition: optimal
Solution: 385440 primals, 1576779 duals
Objective: 1.11e+09
Solver model: available
Solver message: 2

INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper, Generator-fix-p-ramp_limit_up, Generator-fix-p-ramp_limit_down were not assigned to the network.
INFO:pypsa.io:Exported network model dump has buses, global_constraints, generators, loads, carriers
co2 emission =  50000000.00001598
total cost =  1107350697.049968

By the way: The Pyomo import statements in the beginning are not necessary as no Pyomo code is used here. They can safely be removed.