oemof / DHNx

District heating system optimisation and simulation models
MIT License
27 stars 12 forks source link

Features/integrate solph model builder #29

Closed joroeder closed 3 years ago

joroeder commented 4 years ago

Implements #28

Generally, this library should allow the optimization of district heating grids with various configurations settings and different approaches. For now, all optimization setting should have in common that an oemof.solph model is built and executed. (Other e.g. heuristic approaches might be possible to add in future in separate PRs)

Regarding the level of detail, firstly, an object-wise consideration should be implemented. That means that every individual house/consumer is represented by a demand or connected load, respectively. Later on (in separate PRs), there should be an aggregation function (e.g. street-wise), because probably the representation of every single house is not efficient regarding computing time especially for large dhs systems.

Generally, the consideration of existing dhs pipes/systems should be possible.

Input data

So, let’s start with what the input data, this could look like that: structure_input_data.pdf

In principle, all that data and structure allows to do two different options:

  1. dimensioning of the heatpipes, all consumers need to be supplied via dhs
  2. integrated "whole-district" optimisation, which means, that decentralized heat supply options can be compared with central heat supply options via dhs

Structure

Maybe, this pseudo code will help for faster understanding.

Overview

The .optimize_investment() method of the ThermalNetwork class calls the function optimize_investment of the optimization module, which is doing that:

def optimize_investment(thermal_network, settings):

    OemofInvestOptimizationModel is set up
    OemofInvestOptimizationModel is solved

    returns the results of the oemof optimization in "oemof solph format"

OemofInvestOptimizationModel setup

The following is happening in the setup method of the OemofInvestOptimizationModel class:

nodes - empty list, which will be filled with all nodes
buses - empty dictionary, which will be filled with all buses
es - oemof solph EnergySystem

def .optimization_modules.dhs_nodes.add_nodes_dhs(invest_options_heatpipes, ...):
    # creates all buses and nodes for the district heating system
    for all consumers : a heat bus is created
        add_buses # extra function is called, where the oemof object is created
    for all forks : a heat bus is created 
        add_buses
    for all producers : a heat bus is created
        add_buses
    for all edges : heatpipelines are created, in detail:
        # (at the moment no bi-directionsl pipes)
        if edge is house connection : one pipe going from 'fork-x' to 'consumers-y' is created
            add_heatpipes
        if edge is producer connection : one pipe going from 'producers-x' to 'forks-y'
            add_heatpipes
        if edge is fork-fork connection : two pipes are created
            add_heatpipes
    return nodes, buses

for consumers-, producers table:
    # creates all buses and nodes for the consumers and producers
    # this must be at least a demand for the consumers, and a source for the producers
    def .optimization_modules.dhs_nodes.add_nodes_houses(invest_options_consumers, ...):
        for all consumers/producers
            # which buses, source,... are created, is defined in the tables of investment_options
            add_buses
            add_sources
            add_demand
            add_transformer
            add_storages
        return nodes, buses

solph.Energysystem.add(*nodes)

print(all nodes, which are created)

So, the oemof components are finally added with the functions in the module .optimization_modules.add_components.py. For each oemof component class, and extra function is used. At the moment, the columns in the csv table in the data folder "invest_options" need to fit to theses functions.

OemofInvestOptimizationModel solve

# build operational oemof model
om = solph.Model(oemof energysystem)
# solve oemof model
om.solve()
# get results of oemof model
oemof energysystem.results = outputlib.processing.results(om)

One important thing is the creation of labels of the oemof components. Here, Tuple as labels (see oemof_example) are used as following table shows:

tag1 tag2 tag3 tag4
consumers heat heatpipe_type_xy id linking to georeferenced object
producers (gas) gasboiler e.g. consumers-45
infrastructure (electricity) battery
...

My thoughts behind the tags were:

So, a gas boiler at a heat generation site could have a label like: producers_heat_gasboiler_producers-2

And a gas boiler of a single house, could look like this: consumers_heat_gasboiler_consumers-145

A pipe of the dhs system could have a label like (using the ids of starting and ending points as id): infrastructure_heat_heatpipe-type-xy_forks-23-forks-98

Hereby, it should be easy to access and filter all oemof results.

I am pleased about feedback and recommendations for improvement!

joroeder commented 4 years ago

hey, at moment, filling the network class with life and adding some more attributes (.invest_options) seems to be a complex process ... So, I just added a hard-coded 'invest_options' case to the load method of the CSVNetworkImporter class ...

joroeder commented 4 years ago

hey, I managed to integrate the oemof model builder of https://github.com/quarree100/dhs_optimization. The investment example is running and an oemof.solph optimization is performed. At the moment, as results, the oemof results are returned as you get them by outputlib.processing.results(om)

Of course, there are some open questions on where to put some stuff, and maybe move some functions into classes, restructure modules, and so on. And for sure there is room for improvement. So, I am very happy for any ideas and suggestions :)

@jnnr : in the setup method of OemofInvestOptimizationModel there are some general optimization parameters (L55ff) - where shall we put them?

Generally, I got a bit confused, for me it feels that everything is already quite nested ... probably, I need some help with the structure .. I wonder what do we really need at the moment and in future. Maybe, we will see after a while, what is practical ...

jnnr commented 4 years ago

Hey @joroeder, thanks for your effort! I would suggest that I will have a full review and feed back some remarks and ideas and explanation, if I notice that something did not get clear. In the meantime, if you have any specific questions, just let me know!

jnnr commented 4 years ago

I tried to run the example 'input.py', however I got the error

line 284, in add_heatpipes
    nonconvex=False,
TypeError: __init__() got an unexpected keyword argument 'nonconvex'

I guess this is because you are using the nonconvex investment flow of that feature branch: https://github.com/oemof/oemof/pull/636?

Generally, it would be really helpful if you could provide more information with this PR. How does the problem look like that this investment model wants to solve, and what is the concept of this implementation?

joroeder commented 4 years ago

hey, thanks so far for your feedback!

Generally, it would be really helpful if you could provide more information with this PR. How does the problem look like that this investment model wants to solve, and what is the concept of this implementation?

Yeah, I guess this would be very helpful for you, as this is a kind of all in one thing without being well documented yet. I will prepare something, add docstring, and so on ...

I guess this is because you are using the nonconvex investment flow of that feature branch: oemof/oemof#636?

Yes, at the moment, the nonconvex stuff only works with that feature branch .. hm, everything work in progress :D .. maybe, we need to merge this one soon.

jnnr commented 4 years ago

Yeah, I guess this would be very helpful for you, as this is a kind of all in one thing without being well documented yet. I will prepare something, add docstring, and so on ...

Yes, exactly. It is sometimes hard to guess how something is meant. I did not mean docstrings, there I would be fine if the come later. Most helpful would be more text in the first post of this PR.

Yes, at the moment, the nonconvex stuff only works with that feature branch .. hm, everything work in progress :D .. maybe, we need to merge this one soon.

No need to hurry. I checked out the feature branch and now it works, so that's fine :)

jnnr commented 4 years ago

Hey @joroeder, thanks for the extra explanations! I could continue with my review sometimes this week if that is fine?

joroeder commented 4 years ago

Yeah sure, that would be nice. I want to continue with the description above tomorrow.

joroeder commented 4 years ago

Hey, maybe putting the invest_options as attribute of ThermalNetwork does not make sense. I am wondering, does it make more sense to put the invest_option as an attribute to the class OemofInvestOptimizationModel or InvestOptimizationModel or Model ? Since the data is specific for oemof, OemofInvestOptimizationModel is best location for now ...

joroeder commented 4 years ago

I wonder how I should proceed with "storing" the results. At the moment, I put all oemof results in the following attribute ThermalNetwork.results.optimization.

But it would be good to automatically transfer the investment results of the edges (e.g. the dimensioning of the pipes) to ThermalNetwork.components['edges] - either to use the results further (eg for simulation) or to export the network again and to have the results already geo-referenced.

I guess this transfer of results would than be written in the method .optimize_investment of ThermalNetwork. What do you think? Is that fine for now?

joroeder commented 4 years ago

I guess this transfer of results would than be written in the method .optimize_investment of ThermalNetwork. What do you think? Is that fine for now?

Okay, maybe it makes more sense to put the processing of results directly in OemofInvestOptimizationModel, because the results structure is specific to the oemof results ...

jnnr commented 4 years ago

But it would be good to automatically transfer the investment results of the edges (e.g. the dimensioning of the pipes) to ThermalNetwork.components['edges]

I understand that one wants to be able to do this. However, this can lead to confusion, because there is no separation of the input data and the results any more. My idea was to create an additional function that transfers the investment results to the network, something like apply_investment (or another sensible name). You would call that function when you want to couple models, i.e. when one wants to run a simulation model after an investment optmization.

For the general process, I suggest that we use the call on Thursday to discuss these questions. I have the impression that it would help to take one step back and talk so we can get to a common understanding. General ideas on the design of the optimization model should be discussed in the issue #28. Once we have a common picture, it will be easier to proceed the process here.

joroeder commented 4 years ago

Yeah, that's right. I will keep it flexibel within ThermalNetwork.results.optimization, we can discuss that on Thursday, how to handle that ...

joroeder commented 4 years ago

Nonetheless, at the moment, I have time to work on the dhs optimization part in this branch in the loneliness of homeoffice 😉 So, I am planning to continue testing and trying stuff in this branch. For now, I will leave some structural decisions open, and focus on the optimization with oemof itself.