Skip to content

Commit

Permalink
Merge pull request #957 from oemof/features/make-varying-period-lengh…
Browse files Browse the repository at this point in the history
…ts-possible

Make varying period lenghts possible
  • Loading branch information
p-snft authored Aug 10, 2023
2 parents 869b60b + 889a81a commit 3006fa7
Show file tree
Hide file tree
Showing 22 changed files with 8,740 additions and 143 deletions.
51 changes: 27 additions & 24 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,10 @@ First, you start by defining your energy system as you might have done before, b
my_energysystem = solph.EnergySystem(timeindex=my_index, periods=periods)
If you want to use a multi-period model you have define periods of your energy system explicitly. This way,
you are forced to critically think, e.g. about handling leap years, and take some design decisions.
you are forced to critically think, e.g. about handling leap years, and take some design decisions. It is possible to
define periods with different lengths, but remember that decommissioning of components is possible only at the
beginning of each period. This means that if the life of a component is just a little longer, it will remain for the
entire next period. This can have a particularly large impact the longer your periods are.
To assist you, here is a plain python snippet that includes leap years which you can just copy
and adjust to your needs:
Expand Down Expand Up @@ -1268,18 +1271,18 @@ Besides the `invest` variable, new variables are introduced as well. These are:
Modelling cellular energy systems and modularizing energy system models
-----------------------------------------------------------------------
The cellular approach is a concept proposed by the [VDE-ETG](https://shop.vde.com/en/vde-study-the-cellular-approach). It is
related to smart-grids and multi-microgrid systems but extends both. The idea is to group the components of an energy system
into a hierarchically aggregating structure of cells. For example, the sources, sinks, storages and converters of a household
could be a single cell. Then a group of physically neighboring households could form another cell, consisting of household-cells.
This behaviour can be scaled up. The real game-changer in the cellular approach is the way the cells are operated, which will
The cellular approach is a concept proposed by the [VDE-ETG](https://shop.vde.com/en/vde-study-the-cellular-approach). It is
related to smart-grids and multi-microgrid systems but extends both. The idea is to group the components of an energy system
into a hierarchically aggregating structure of cells. For example, the sources, sinks, storages and converters of a household
could be a single cell. Then a group of physically neighboring households could form another cell, consisting of household-cells.
This behaviour can be scaled up. The real game-changer in the cellular approach is the way the cells are operated, which will
not be covered here. Here, we focus on the way such cellular energy systems can be modeled.
So far, the implementation in solph is just a neat way to group different parts of a larger energy system into cells. However,
So far, the implementation in solph is just a neat way to group different parts of a larger energy system into cells. However,
the implementation can also be regarded as a precursor for further functionality. Decomposition techniques such
as [Benders](https://en.wikipedia.org/wiki/Benders_decomposition) or
[Dantzig-Wolfe](https://en.wikipedia.org/wiki/Dantzig%E2%80%93Wolfe_decomposition) could be implemented in solph. These methods
are dependent on a special constraint matrix structure, which the cellular modelling approach presented here is helping to obtain.
as [Benders](https://en.wikipedia.org/wiki/Benders_decomposition) or
[Dantzig-Wolfe](https://en.wikipedia.org/wiki/Dantzig%E2%80%93Wolfe_decomposition) could be implemented in solph. These methods
are dependent on a special constraint matrix structure, which the cellular modelling approach presented here is helping to obtain.
Modelling procedure
^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -1327,15 +1330,15 @@ Now we can go on and add components to the energy cells just like we do with reg
.. note:: This is just an exemplary piece of code. A (little bit more interesting) working
example can be found in the examples.
The next step would be to model the connections between cells. Here, we resort to the class
:py:class:`oemof.solph.components.Link`. Each connection Link has two inputs (from the
"parent cell" and the "child cell") and two outputs (to the "parent cell" and the "child
cell"). A connection between the "parent cell" `es` and the "child cell" `ec_1` could look
The next step would be to model the connections between cells. Here, we resort to the class
:py:class:`oemof.solph.components.Link`. Each connection Link has two inputs (from the
"parent cell" and the "child cell") and two outputs (to the "parent cell" and the "child
cell"). A connection between the "parent cell" `es` and the "child cell" `ec_1` could look
like this:
.. code-block:: python
connector_el_ec_1 = solph.buses.Bus(
label="connector_el_ec_1",
inputs={
Expand All @@ -1353,25 +1356,25 @@ like this:
)
es.add(connector_el_ec_1)
The `conversion_factors` can be used to model transmission losses. Here, a symmetrical
The `conversion_factors` can be used to model transmission losses. Here, a symmetrical
loss of 15% is assumed.
All connection Links are added to the upmost energy cell.
.. note:: Note that we do not add the energy cells as components to their parent cells!
.. note:: Note that we do not add the energy cells as components to their parent cells!
Instead, the hierarchical structure is flattened and all connections between the cells
are created as depicted above.
The last step is to create (and solve) the model. Again, this is fairly similar to the
regular model creation. However, instead of passing just one instance of
The last step is to create (and solve) the model. Again, this is fairly similar to the
regular model creation. However, instead of passing just one instance of
:py:class:`oemof.solph.EnergySystem`, a list of energy systems is passed.
.. warning::
By convention the first element of the list is assumed to be the upmost energy cell.
By convention the first element of the list is assumed to be the upmost energy cell.
The ordering afterwards does not play a role.
.. note:: The resulting model is monolithic. This means that all components of all energy
cells are actually grouped into one pyomo model. It would, therefore, also be possible
to model all the components in one :py:class:`oemof.solph.EnergySystem` instance and
cells are actually grouped into one pyomo model. It would, therefore, also be possible
to model all the components in one :py:class:`oemof.solph.EnergySystem` instance and
the results would be identical.
.. code-block:: python
Expand All @@ -1385,7 +1388,7 @@ As pointed out above, the resulting model is monolithic. Nonetheless, this model
holds some benefits:
* Better overview through segmentation of the energy system
* (Facilitated) opportunity to model cellular energy systems where the energy exchanged between cells
* (Facilitated) opportunity to model cellular energy systems where the energy exchanged between cells
is of interest
* Segmentation of the energy system is a necessary precursor for distributed optimization via Dantzig-Wolfe
Expand Down
2 changes: 2 additions & 0 deletions docs/whatsnew/v0-5-1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ Bug fixes

* Fixed error when calling `oemof_installation_test` as console script.
* Corrected several typos in the docs.
* Periods with multiple period lengths are now supported in multi-period investment.
* Add missing 'custom_attributes' for the link component

Testing
#######

* Add tests for experimental SinkDSM component.
* Add tests for multi-period investment.

Other changes
#############
Expand Down
6 changes: 3 additions & 3 deletions examples/cellular/cellular.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@
"""


from oemof.solph import EnergySystem
from oemof.solph import Model
from oemof.solph import buses
from oemof.solph import components as cmp
from oemof.solph import EnergySystem

from oemof.solph import create_time_index
from oemof.solph import flows
from oemof.solph import processing, views
from oemof.solph import processing
from oemof.solph import views


def main():
Expand Down
12 changes: 9 additions & 3 deletions examples/electrical/lopf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,17 @@
import pandas as pd
from matplotlib import pyplot as plt
from oemof.network.graph import create_nx_graph
from oemof.solph import EnergySystem, Investment, Model, processing, views
from oemof.solph.components import Sink, Source

from oemof.solph import EnergySystem
from oemof.solph import Investment
from oemof.solph import Model
from oemof.solph import processing
from oemof.solph import views
from oemof.solph.buses.experimental import ElectricalBus
from oemof.solph.flows.experimental import ElectricalLine
from oemof.solph.components import Sink
from oemof.solph.components import Source
from oemof.solph.flows import Flow
from oemof.solph.flows.experimental import ElectricalLine

try:
import pygraphviz as pygz
Expand Down
12 changes: 6 additions & 6 deletions examples/excel_reader/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@
"""

import os
import logging
import pandas as pd
import os

import networkx as nx
import pandas as pd
from matplotlib import pyplot as plt
from oemof.network.graph import create_nx_graph
from oemof.tools import logger
from oemof import solph

from oemof.network.graph import create_nx_graph
from matplotlib import pyplot as plt
import networkx as nx
from oemof import solph


def nodes_from_excel(filename):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@
__copyright__ = "oemof developer group"
__license__ = "MIT"

import numpy as np
import os
import pandas as pd
import time
from datetime import datetime, timedelta
from oemof import solph
import warnings
from datetime import datetime
from datetime import timedelta

import numpy as np
import pandas as pd

from oemof import solph

try:
import matplotlib.pyplot as plt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@

import numpy as np
import pandas as pd
from oemof import solph
from oemof.tools import economics

from oemof import solph

try:
import matplotlib.pyplot as plt
except ImportError:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@

import numpy as np
import pandas as pd
from oemof import solph
from oemof.tools import economics

from oemof import solph

try:
import matplotlib.pyplot as plt
except ImportError:
Expand Down
3 changes: 2 additions & 1 deletion examples/start_and_shutdown_costs/startup_shutdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
`MIT license <https://github.com/oemof/oemof-solph/blob/dev/LICENSE>`_
"""
import matplotlib.pyplot as plt
import pandas as pd

from oemof import solph
import matplotlib.pyplot as plt


def main():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@
import warnings

import pandas as pd

# Default logger of oemof
from oemof.tools import economics
from oemof.tools import logger

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@
import warnings

import pandas as pd

# Default logger of oemof
from oemof.tools import economics
from oemof.tools import logger

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@
import warnings

import pandas as pd

# Default logger of oemof
from oemof.tools import economics
from oemof.tools import logger

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@
import warnings

import pandas as pd

# Default logger of oemof
from oemof.tools import economics
from oemof.tools import logger

Expand Down
15 changes: 9 additions & 6 deletions examples/storage_level_constraint/storage_level_constraint.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import pandas as pd
from oemof.solph import Bus, EnergySystem, Flow, Model
from oemof.solph.components import GenericStorage, Source, Sink
from oemof.solph.processing import results

import matplotlib.pyplot as plt
import pandas as pd

from oemof.solph import Bus
from oemof.solph import EnergySystem
from oemof.solph import Flow
from oemof.solph import Model
from oemof.solph.components import GenericStorage
from oemof.solph.components import Sink
from oemof.solph.components import Source
from oemof.solph.constraints import storage_level_constraint

from oemof.solph.processing import results

es = EnergySystem(
timeindex=pd.date_range("2022-01-01", freq="1H", periods=24),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
pip install oemof.solph
"""
import pandas as pd

from oemof import solph

try:
Expand Down
21 changes: 21 additions & 0 deletions src/oemof/solph/_energy_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def __init__(
warnings.warn(msg, debugging.SuspiciousUsageWarning)
self.periods = periods
self._extract_periods_years()
self._extract_periods_matrix()

def _extract_periods_years(self):
"""Map simulation years to the respective period based on time indices
Expand All @@ -192,6 +193,26 @@ def _extract_periods_years(self):

self.periods_years = periods_years

def _extract_periods_matrix(self):
"""Determines a matrix describing the temporal distance to each period.
Rows represent investment/commissioning periods, columns represent
decommissioning periods. The values describe the temporal distance
between each investment period to each decommissioning period.
Returns
-------
period_distance_matrix: np.array
"""
periods_matrix = []
if self.periods is not None:
period_years = np.array(self.periods_years)
for v in period_years:
row = period_years - v
row = np.where(row < 0, 0, row)
periods_matrix.append(row)
self.periods_matrix = np.array(periods_matrix)


def create_time_index(
year: int = None,
Expand Down
Loading

0 comments on commit 3006fa7

Please sign in to comment.