Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make varying period lenghts possible #957

Merged
merged 26 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
38addf9
Add periods matrix
nailend Jul 19, 2023
6375e7b
Rework the _old_capacity_rul_end
nailend Jul 19, 2023
ff2b516
Rework _old_storage_capacity_rule_end
nailend Jul 19, 2023
09770b6
Fix f-string in message
nailend Jul 19, 2023
14abc53
Add test for varying period length in multi-period
nailend Jul 19, 2023
79237a8
Black&isort
nailend Jul 19, 2023
fae1c94
Add constraint test for multi period period length
nailend Jul 19, 2023
d22e472
black
nailend Jul 19, 2023
2179b15
Change iterator and remove unused variables
nailend Jul 20, 2023
0c77bf5
Rework inline comments
nailend Jul 20, 2023
143e0b7
Make black happy
nailend Jul 20, 2023
beb13cf
Remove test
nailend Jul 20, 2023
877cc6a
Add whatsnew
nailend Jul 20, 2023
699a8a8
Extend docstring for old_end rule
nailend Jul 20, 2023
a76caa2
Change to f-string
nailend Jul 20, 2023
b247be1
Apply new decommissiong method to SinkDSM
nailend Jul 20, 2023
73f39f9
Move multi-period-length test to multi-period-constraint-test
nailend Jul 20, 2023
27b2867
Reduce test to bus and storage component only
nailend Jul 20, 2023
909c84b
Add SinkDSM components of each approach
nailend Jul 20, 2023
a54a3de
Fix indentation
nailend Jul 20, 2023
10d1dc1
Black
nailend Jul 20, 2023
427233a
Update LP-file for multi-period varying period length
nailend Jul 20, 2023
7f01ec1
Add some info on period-length to usage.rst
nailend Jul 20, 2023
b01a206
Make flake happy
nailend Jul 24, 2023
3350f5a
Change wording
nailend Aug 7, 2023
889a81a
Merge branch 'dev' into features/make-varying-period-lenghts-possible
p-snft Aug 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
nailend marked this conversation as resolved.
Show resolved Hide resolved

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
Loading