Basic Sink Component oemof

I have 2 questions about the normal sink component in Oemof:

I have a basic wind + PV hybrid system for which I have an hourly time series of capacity factors for both technologies (2 columns with 8760 values each).

  1. I now want to define a sink component in which I insert a constraint that forces the system to generate a minimum annual energy yield of 500 GWh/a (but it can also be more) and do not want to define a fixed hourly time series for the demand (fix=none in the sink component). Do you know how to do this? I tried it with summed_min and summed_max as well as full_load_time_min and full_load_time_max but unfortunately it did not work.

PS: this is an investment optimization and accordingly Oemof will then select the PV and wind installed power so that the total costs are minimal and the constraint of the sink component is also met.

  1. Does anyone know how I can do a pure technical optimization (no investment or dispatch optimization). I simply want to find the PV/wind combination (total system has 200MW) that delivers the highest annual yield with the same CF of both technologies, given the constraint of at least 25% PV capacity.

Greetings,
Aly

Hi @Aly_Elbeltagy,

Thanks for reaching out.

  1. The full_load_time_min should do. Maybe, you define a large nominal_value so that you do not unintentionally constrain the peak capacity from the sources.

  2. I am not sure if I understood this correctly. The “costs” have to be understood in a mathematical (not economical) way. So, what you might want is an investment optimisation with arbitrary costs for both sources.

Cheers,
Patrik

1 Like

Hello @pschoen,

thank you very much for the quick reply, that was much quicker than I thought :).

Unfortunately it still doesn’t work for a reason I can not figure out. Here is my code again maybe it is better to find where the error is.


#load lib
from oemof.tools import logger
import pandas as pd
import matplotlib.pyplot as plt
from oemof import solph
from oemof.tools import economics


# In[321]:


#read input data from excel sheet
timeseries = pd.read_excel('../data/input_data_base.xlsx', 
                           sheet_name='timeseries', 
                           index_col=[0], 
                           parse_dates=True)

# Add timeindex frequency
timeseries.index.freq = '1H'



# Cost Data Reading
costs = pd.read_excel('../data/input_data_base.xlsx', 
                           sheet_name='costs', 
                           index_col=[0], 
                           parse_dates=True)




# In[322]:

#Initialize the energy system
energysystem = solph.EnergySystem(timeindex=timeseries.index)


# In[323]:


#oemof bus
# create electricity bus
bus_electricity = solph.Bus(label='electricity_l')



# add buses to energy model
energysystem.add(bus_electricity)


#%% Create oemof Sinks

# create demand component for the electricity bus 
el_demand = solph.Sink(label='electricty_demand_l', 
                    inputs={bus_electricity: solph.Flow(full_load_time_min = 2628, nominal_value=200)})


#%%
a_wind = economics.annuity(capex=costs['wind']['capex'], 
                            n=costs['wind']['lifetime'],
                            wacc=costs['wind']['wacc'])

a_pv = economics.annuity(capex=costs['pv']['capex'], 
                            n=costs['pv']['lifetime'],
                          wacc=costs['pv']['wacc'])

cc_wind = (a_wind+ costs['wind']['fom'])

cc_pv = (a_pv + costs['pv']['fom'])
#%%Create oemof Sources
# create fixed source object representing wind power plants 
wind = solph.Source(label='wind_l',
                             outputs={bus_electricity: solph.Flow(   
                                     fix=timeseries['wind_west_aswan'], 
                                     investment=solph.Investment(
                                                ep_costs=cc_wind
                                     
                                     ))})
                                 
# create fixed source object representing pv power plants
pv = solph.Source(label='pv_l',
                  outputs={bus_electricity: solph.Flow(   
                                     fix=timeseries['pv_west_as'], 
                                     
                                     investment=solph.Investment(
                                                ep_costs=cc_pv))})
                                

#%% Add all components to the energysystem
energysystem.add(wind,pv,el_demand)


To 2: arbitrary costs will lead to one component being favored which is exactly what I don’t want. I want to set equal economic constraints so that the choice of RE capacity is purely the one that maximizes the energy yield. Maybe defining equal ep_costs for wind and PV would be a possibility … or not ?

Greetings from Frankfurt,
Aly

Hey Aly,

I’m not super experienced with all oemof but I did have a couple ideas to accomplish what you are going for.
The optimization is inherently trying to spend the least/make the most money, so maybe if you made the capex of both of your sources 0 (no penalty for building extra capacity) and then made the variable costs for your sink flow negative, the system would optimize to make the most money, which would be by generating the most electricity. To add to this, you can simply create a summed capacity constraint for PV and Wind that it must stay below 200MW (check custom constraints example), and have the minimum capacity of PV be 50 MW (25% capacity).
I am certain this is not the most elegant solution but I think it would have the affect that you desire.

Edit: clarified some of the cost types

Hope this helps!
Aidan

1 Like

Hi @ahbills,

thank you so much for your answer. I think the approach will probably work out. Now i tried to set a shared limit constraint: def shared_limit( model, quantity, limit_name, components, weights, lower_limit=0, upper_limit=None) to force the hybrid system not the exceed the 200 MW. Sadly my code still does not work and gives me a “Metaclass’ object is not subscriptable” error, that i think comes from my definition of the quantity in the the shared_limit custom constraint. Here’s the relevant part of my code:

#%%Create oemof Sources

                                 
# create fixed source object representing pv power plants
pv = solph.Source(label='pv_l',
                  outputs={bus_electricity: solph.Flow(   
                                     fix=timeseries['pv_west_as'], 
                                     
                                     investment=solph.Investment(
                                                ep_costs=0 , minimum =50))})
     
# create fixed source object representing wind power plants 
wind = solph.Source(label='wind_l',
                             outputs={bus_electricity: solph.Flow(   
                                     fix=timeseries['wind_west_aswan'], 
                                     investment=solph.Investment(
                                                ep_costs=0
                                     
                                     ))})                           

#%% Add all components to the energysystem
energysystem.add(wind,pv,el_demand)



#%% Optimise the energy system



# initialise the operational model
om = solph.Model(energysystem)

components = [wind, pv]

solph.constraints.shared_limit( om , solph.Source ,'limit_cap', components, [1, 1], upper_limit=200)

# if tee_switch is true solver messages will be displayed

om.solve(solver='cbc', solve_kwargs={'tee': False})


Thank you so much for all your support! Great community that I’m proud to be part of.

Edited: a small part of the code was missing :slight_smile:

Greetings,
Aly

Hi again,

In you first code sample, I cannot find the error. It would be helpful if you provided a minimum example including a time series (and not relying on a file).

Thanks to @ahbills comment, I think I now understood question 2. His answer already describes the solution. To implement it, you can use the additional_investment_flow_limit. Your code example uses shared_limit, which limits the instantaneous values rather than the capacity to be installed. (Also, the arguments you hand to the function are not incorrect. 'In particular, solph.source is not a IndexedVar. This is the reason the error is thrown. I think, it would be model.flow, if you wanted to limit the instantaneous value. But you don’t.)

Cheers,
Patrik