Oemof Problem: Investment Decision

Hello community,

I’m struggling with the simulation of the following scenario in oemof and was wondering if any of you could help me with it.

I would like to make a decision, if the implementation of a technology is worthwhile. Regardless of the capacity required for supply, the capacity of the technology should be fixed. For example, the question could be: Is it worthwhile investing in a 10 kW gas heating system, at periodic costs of 100 €, or not?
If I use the following transformer for this question (ep_costs=10, minimum=0, maximum=10), the capacity is optimized (e.g. to 5.5 kW) and only for the optimized capacity costs are considered (55 €):

solph.Transformer(    
    label=’gas_heating’,    
    inputs={naturalgas_bus: solph.Flow(variable_costs=0.07)},  
    outputs={heat_bus: solph.Flow(investment=solph.Investment(ep_costs=10,
                                                              minimum=0,
                                                              maximum=10,
                                                              existing=0
                                                              ))}

If I use again the following transformer (ep_costs=10, minimum=10, maximum=10), the correct costs (100€) will be considered, but the capacity of 10 kW will be added in any case. So, there is no investment decision:

solph.Transformer(    
    label=’gas_heating’,    
    inputs={naturalgas_bus: solph.Flow(variable_costs=0.07)},  
    outputs={heat_bus: solph.Flow(investment=solph.Investment(ep_costs=10,
                                                               minimum=10,
                                                               maximum=10,
                                                               existing=0
                                                               ))}

Is there any possibility, to make an investment decision, so that there is only the possibility of full investment (10 kW) or no investment at all (0 kW)?

I would be very happy if someone could help me with this question. Many thanks for any help in advance!

Best regards,
Christian

My usual disclaimer of only being vaguely aware of how oemof actually works applies! You could introduce a binary variable to guarantee the selected capacity is either zero or nameplate. Or an integer variable to guarantee some multiple of that nameplate capacity. Or if you don’t want the computational overhead of creating a mixed‑integer problem, you might be able to creatively apply the big M method together with conventional constraints. Or failing that, run your model twice: first, to see what continuous capacities are selected, and then second to either explicitly include or exclude these facilities in a new scenario. This is, in effect, coupling a model with itself and can be automated! I once did this to “fake” shut‑down in an operational model using Perl for the data processing (because Python had not yet been invented back then). HTH, R.

Correction: Python was released in 1991 and the model coupling I mentioned was around 2002. So more accurate to say that Python was still to overtake Perl in terms of popularity.

Hello, Robbie,
thank you for your quick response. I understand the approaches you mentioned, I just don’t know how I can implement them in oemof. Running the model twice is unfortunately not a solution in my case, as I am investigating several independent investments in my model.

How can I introduce a binary variable for investment decisions in oemof?

I would be very grateful for further help!
Many greetings,
Christian

You first need to formulate the mathematical equations to yield the result you want. This is a standard procedure in mixed‑integer linear programming so just search the web for the details. Then you need to create or derive a new module or class (or whatever encapsulation oemof is using here) to embed these equations, marking the binary variable as binary as you feed this information to the solver interface. And you’re done. Hopefully someone who understands oemof will turn up and advise on implementation! HTH, R.

Thank you again for your help, Robbie! Yes, hopefully some oemof user turns up!

Best regards,
Christian

Hi Christian,

thanks for your question. As I understand it, the current oemof version v0.3.3 does not have the feature you have mind. However, there is an open pull request that aims to implement a binary variable for investment. Have a look and contact the developers that work on that feature if you want to learn more.

Best, Jann

@cklm If you still need this feature you should write a comment in the open pull request . If more people are interested in a feature, developer might be motivated to develop it faster.

Hi @jann.launer and @uwe.krien,
thank you very much for the hint for the pullrequest. I am happy that the feature will be available in the future! I will leave a comment in the pull request!

To comment as a site moderator, it is better to use @‑handles and not real names in postings. There are several advantages: clickability, notifications, and GDPR erasure. This last point means that if someone requests we erase their personal details under European data protection law, the forum software will replace all instances of that particular data subject’s handle with a unique machine‑generated version. We have already had one such request. I am about to edit the above post to reflect this preferred practice. Just letting people know :slight_smile:

I have the same problem. I suppose there is no feature in oemof available yet. Does somebody know some kind of workaround for it? Thanks in advance!

The PR #636 mentioned above is merged but the link changed, so you could not find it.

With oemof.solph > v0.4.0 the following example of J. Röder schould work (sorry, some parts are german, but you may get the idea).

# -*- coding: utf-8 -*-
"""
Example that shows how to use "Offset-Invest".

SPDX-License-Identifier: MIT

Johannes Röder <https://www.uni-bremen.de/en/res/team/johannes-roeder-m-sc>

"""

import logging
import os

import pandas as pd
from matplotlib import pyplot as plt
from oemof import solph
from oemof.network.network import Node

data = [0, 15, 30, 35, 20, 25, 27, 10, 5, 2, 15, 40, 20, 0, 0]

# select periods
periods = 14

# create an energy system
idx = pd.date_range('1/1/2017', periods=periods, freq='H')
es = solph.EnergySystem(timeindex=idx)
Node.registry = es

bus_0 = solph.Bus(label='bus_0')
bus_1 = solph.Bus(label='bus_1')

c_0 = 10
c_1 = 100

solph.Source(label='source_0',
             outputs={bus_0: solph.Flow(variable_costs=c_0)})

solph.Source(label='source_1',
             outputs={bus_1: solph.Flow(variable_costs=c_1)})

solph.Sink(label='demand', inputs={
    bus_1: solph.Flow(fix=data,  nominal_value=1)})

# parameter
p_install_min = 20
p_install_max = 35000
c_fix = 2000
c_var = 180
eta = 0.8

# non offset invest
trafo = solph.Transformer(
    label='transformer',
    inputs={bus_0: solph.Flow()},
    outputs={bus_1: solph.Flow(
        nominal_value=None,
        investment=solph.Investment(
            ep_costs=c_var,
            maximum=p_install_max,
            minimum=p_install_min,
            nonconvex=True,
            offset=c_fix),
        )},
    conversion_factors={bus_1: eta})

# create an optimization problem and solve it
om = solph.Model(es)

# export lp file
filename = os.path.join(
    solph.helpers.extend_basic_path('lp_files'), 'OffsetInvestor.lp')
logging.info('Store lp-file in {0}.'.format(filename))
om.write(filename, io_options={'symbolic_solver_labels': True})

# solve model
om.solve(solver='cbc', solve_kwargs={'tee': True})

# create result object
results = solph.processing.results(om)

bus1 = solph.views.node(results, 'bus_1')["sequences"]

# plot the time series (sequences) of a specific component/bus
if plt is not None:
    bus1.plot(kind='line', drawstyle='steps-mid')
    plt.legend()
    plt.show()

# Nachvollziehen der Berechnung
# Kosten Invest
p_invest = solph.views.node(
    results, 'transformer')['scalars'][(('transformer', 'bus_1'), 'invest')]
invest_binary = solph.views.node(results, 'transformer')['scalars'][
    (('transformer', 'bus_1'), 'invest_status')]
c_invest = p_invest * c_var + c_fix*invest_binary

# costs analysis
e_source_0 = solph.views.node(results, 'source_0')['sequences'][
    (('source_0', 'bus_0'), 'flow')].sum()
c_source_0 = c_0 * e_source_0
e_source_1 = solph.views.node(results, 'source_1')['sequences'][
    (('source_1', 'bus_1'), 'flow')].sum()
c_source_1 = c_1 * e_source_1

c_total = c_invest + c_source_0 + c_source_1

es.results['meta'] = solph.processing.meta_results(om)
objective = pd.DataFrame.from_dict(es.results['meta']).at[
    'Lower bound', 'objective']

print('  objective:', objective)
print('  berechnet:', c_total)

print('')
print('Max. zulässige Investleistung', p_install_max)
print('Erforderlicher Mindest-Invest', p_install_min)
print('Installierte Leistung:', p_invest)
print('Maximale Leistung - demand:', solph.views.node(results, 'bus_1')[
    'sequences'][(('bus_1', 'demand'), 'flow')].max())
print('Maximale Leistung im Einsatz', solph.views.node(results, 'transformer')[
    'sequences'][(('transformer', 'bus_1'), 'flow')].max())
if p_invest > max(data):
    print('Anlage wurde überdimensioniert')

Great, thank you @uwe.krien !!

Thank you very much!!!