flixopt 2.1.6__py3-none-any.whl → 2.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flixopt might be problematic. Click here for more details.

Files changed (45) hide show
  1. docs/examples/00-Minimal Example.md +1 -1
  2. docs/examples/01-Basic Example.md +1 -1
  3. docs/examples/02-Complex Example.md +1 -1
  4. docs/examples/index.md +1 -1
  5. docs/faq/contribute.md +26 -14
  6. docs/faq/index.md +1 -1
  7. docs/javascripts/mathjax.js +1 -1
  8. docs/user-guide/Mathematical Notation/Bus.md +1 -1
  9. docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +21 -21
  10. docs/user-guide/Mathematical Notation/Flow.md +3 -3
  11. docs/user-guide/Mathematical Notation/InvestParameters.md +3 -0
  12. docs/user-guide/Mathematical Notation/LinearConverter.md +5 -5
  13. docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
  14. docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
  15. docs/user-guide/Mathematical Notation/Storage.md +2 -2
  16. docs/user-guide/Mathematical Notation/index.md +1 -1
  17. docs/user-guide/Mathematical Notation/others.md +1 -1
  18. docs/user-guide/index.md +2 -2
  19. flixopt/__init__.py +4 -0
  20. flixopt/aggregation.py +33 -32
  21. flixopt/calculation.py +161 -65
  22. flixopt/components.py +687 -154
  23. flixopt/config.py +17 -8
  24. flixopt/core.py +69 -60
  25. flixopt/effects.py +146 -64
  26. flixopt/elements.py +297 -110
  27. flixopt/features.py +78 -71
  28. flixopt/flow_system.py +72 -50
  29. flixopt/interface.py +952 -113
  30. flixopt/io.py +15 -10
  31. flixopt/linear_converters.py +373 -81
  32. flixopt/network_app.py +445 -266
  33. flixopt/plotting.py +215 -87
  34. flixopt/results.py +382 -209
  35. flixopt/solvers.py +25 -21
  36. flixopt/structure.py +41 -39
  37. flixopt/utils.py +10 -7
  38. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/METADATA +64 -53
  39. flixopt-2.1.8.dist-info/RECORD +56 -0
  40. scripts/extract_release_notes.py +5 -5
  41. scripts/gen_ref_pages.py +1 -1
  42. flixopt-2.1.6.dist-info/RECORD +0 -54
  43. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/WHEEL +0 -0
  44. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/licenses/LICENSE +0 -0
  45. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/top_level.txt +0 -0
@@ -2,4 +2,4 @@
2
2
 
3
3
  ```python
4
4
  {! ../examples/00_Minmal/minimal_example.py !}
5
- ```
5
+ ```
@@ -2,4 +2,4 @@
2
2
 
3
3
  ```python
4
4
  {! ../examples/01_Simple/simple_example.py !}
5
- ```
5
+ ```
@@ -7,4 +7,4 @@ This saves the results of a calculation to file and reloads them to analyze the
7
7
  ## Load the Results from file
8
8
  ```python
9
9
  {! ../examples/02_Complex/complex_example_results.py !}
10
- ```
10
+ ```
docs/examples/index.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  Here you can find a collection of examples that demonstrate how to use FlixOpt.
4
4
 
5
- We work on improving this gallery. If you have something to share, please contact us!
5
+ We work on improving this gallery. If you have something to share, please contact us!
docs/faq/contribute.md CHANGED
@@ -4,17 +4,29 @@ We warmly welcome contributions from the community! This guide will help you get
4
4
 
5
5
  ## Development Setup
6
6
  1. Clone the repository `git clone https://github.com/flixOpt/flixopt.git`
7
- 2. Install the development dependencies `pip install -editable .[dev, docs]`
8
- 3. Run `pytest` and `ruff check .` to ensure your code passes all tests
9
-
10
- ## Documentation
11
- FlixOpt uses [mkdocs](https://www.mkdocs.org/) to generate documentation. To preview the documentation locally, run `mkdocs serve` in the root directory.
12
-
13
- ## Helpful Commands
14
- - `mkdocs serve` to preview the documentation locally. Navigate to `http://127.0.0.1:8000/` to view the documentation.
15
- - `pytest` to run the test suite (You can also run the provided python script `run_all_test.py`)
16
- - `ruff check .` to run the linter
17
- - `ruff check . --fix` to automatically fix linting issues
7
+ 2. Install the development dependencies `pip install -e ".[dev]"`
8
+ 3. Install pre-commit hooks `pre-commit install` (one-time setup)
9
+ 4. Run `pytest` to ensure your code passes all tests
10
+
11
+ ## Code Quality
12
+ We use [Ruff](https://github.com/astral-sh/ruff) for linting and formatting. After the one-time setup above, **code quality checks run automatically on every commit**.
13
+
14
+ To run manually:
15
+ - `ruff check --fix .` to check and fix linting issues
16
+ - `ruff format .` to format code
17
+
18
+ ## Documentation (Optional)
19
+ FlixOpt uses [mkdocs](https://www.mkdocs.org/) to generate documentation.
20
+ To work on documentation:
21
+ ```bash
22
+ pip install -e ".[docs]"
23
+ mkdocs serve
24
+ ```
25
+ Then navigate to http://127.0.0.1:8000/
26
+
27
+ ## Testing
28
+ - `pytest` to run the test suite
29
+ - You can also run the provided python script `run_all_test.py`
18
30
 
19
31
  ---
20
32
  # Best practices
@@ -30,8 +42,8 @@ FlixOpt uses [mkdocs](https://www.mkdocs.org/) to generate documentation. To pre
30
42
  ## Branches
31
43
  As we start to think FlixOpt in **Releases**, we decided to introduce multiple **dev**-branches instead of only one:
32
44
  Following the **Semantic Versioning** guidelines, we introduced:
33
- - `next/patch`: This is where all pull requests for the next patch release (1.0.x) go.
34
- - `next/minor`: This is where all pull requests for the next minor release (1.x.0) go.
45
+ - `next/patch`: This is where all pull requests for the next patch release (1.0.x) go.
46
+ - `next/minor`: This is where all pull requests for the next minor release (1.x.0) go.
35
47
  - `next/major`: This is where all pull requests for the next major release (x.0.0) go.
36
48
 
37
49
  Everything else remains in `feature/...`-branches.
@@ -44,6 +56,6 @@ At some point, `next/minor` or `next/major` will get merged into `main` using a
44
56
  ## Releases
45
57
  As stated, we follow **Semantic Versioning**.
46
58
  Right after one of the 3 [release branches](#branches) is merged into main, a **Tag** should be added to the merge commit and pushed to the main branch. The tag has the form `v1.2.3`.
47
- With this tag, a release with **Release Notes** must be created.
59
+ With this tag, a release with **Release Notes** must be created.
48
60
 
49
61
  *This is our current best practice*
docs/faq/index.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # Frequently Asked Questions
2
2
 
3
- ## Work in progress
3
+ ## Work in progress
@@ -15,4 +15,4 @@ document$.subscribe(() => {
15
15
  MathJax.typesetClear()
16
16
  MathJax.texReset()
17
17
  MathJax.typesetPromise()
18
- })
18
+ })
@@ -30,4 +30,4 @@ With:
30
30
  - $\phi_\text{in}(\text{t}_i)$ and $\phi_\text{out}(\text{t}_i)$ being the missing or excess flow-rate at time $\text{t}_i$, respectively
31
31
  - $\text{t}_i$ being the time step
32
32
  - $s_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty term
33
- - $\text a_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty coefficient (`excess_penalty_per_flow_hour`)
33
+ - $\text a_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty coefficient (`excess_penalty_per_flow_hour`)
@@ -1,24 +1,24 @@
1
1
  ## Effects
2
- [`Effects`][flixopt.effects.Effect] are used to allocate things like costs, emissions, or other "effects" occuring in the system.
2
+ [`Effects`][flixopt.effects.Effect] are used to allocate things like costs, emissions, or other "effects" occurring in the system.
3
3
  These arise from so called **Shares**, which originate from **Elements** like [Flows](Flow.md).
4
4
 
5
5
  **Example:**
6
6
 
7
7
  [`Flows`][flixopt.elements.Flow] have an attribute called `effects_per_flow_hour`, defining the effect amount of per flow hour.
8
- Assiziated effects could be:
8
+ Associated effects could be:
9
9
  - costs - given in [€/kWh]...
10
10
  - ...or emissions - given in [kg/kWh].
11
- -
12
- Effects are allocated seperatly for investments and operation.
11
+ -
12
+ Effects are allocated separately for investments and operation.
13
13
 
14
14
  ### Shares to Effects
15
15
 
16
16
  $$ \label{eq:Share_invest}
17
- s_{l \rightarrow e, \text{inv}} = \sum_{v \in \mathcal{V}_{l, \text{inv}}} v \cdot \text a_{v \rightarrow e}
17
+ s_{l \rightarrow e, \text{inv}} = \sum_{v \in \mathcal{V}_{l, \text{inv}}} v \cdot \text a_{v \rightarrow e}
18
18
  $$
19
19
 
20
20
  $$ \label{eq:Share_operation}
21
- s_{l \rightarrow e, \text{op}}(\text{t}_i) = \sum_{v \in \mathcal{V}_{l,\text{op}}} v(\text{t}_i) \cdot \text a_{v \rightarrow e}(\text{t}_i)
21
+ s_{l \rightarrow e, \text{op}}(\text{t}_i) = \sum_{v \in \mathcal{V}_{l,\text{op}}} v(\text{t}_i) \cdot \text a_{v \rightarrow e}(\text{t}_i)
22
22
  $$
23
23
 
24
24
  With:
@@ -36,26 +36,26 @@ With:
36
36
 
37
37
  ### Shares between different Effects
38
38
 
39
- Furthermore, the Effect $x$ can contribute a share to another Effect ${e} \in \mathcal{E}\backslash x$.
40
- This share is defined by the factor $\text r_{x \rightarrow e}$.
39
+ Furthermore, the Effect $x$ can contribute a share to another Effect ${e} \in \mathcal{E}\backslash x$.
40
+ This share is defined by the factor $\text r_{x \rightarrow e}$.
41
41
 
42
- For example, the Effect "CO$_2$ emissions" (unit: kg)
43
- can cause an additional share to Effect "monetary costs" (unit: €).
42
+ For example, the Effect "CO$_2$ emissions" (unit: kg)
43
+ can cause an additional share to Effect "monetary costs" (unit: €).
44
44
  In this case, the factor $\text a_{x \rightarrow e}$ is the specific CO$_2$ price in €/kg. However, circular references have to be avoided.
45
45
 
46
- The overall sum of investment shares of an Effect $e$ is given by $\eqref{Effect_invest}$
46
+ The overall sum of investment shares of an Effect $e$ is given by $\eqref{eq:Effect_invest}$
47
47
 
48
48
  $$ \label{eq:Effect_invest}
49
- E_{e, \text{inv}} =
50
- \sum_{l \in \mathcal{L}} s_{l \rightarrow e,\text{inv}} +
49
+ E_{e, \text{inv}} =
50
+ \sum_{l \in \mathcal{L}} s_{l \rightarrow e,\text{inv}} +
51
51
  \sum_{x \in \mathcal{E}\backslash e} E_{x, \text{inv}} \cdot \text{r}_{x \rightarrow e,\text{inv}}
52
52
  $$
53
53
 
54
54
  The overall sum of operation shares is given by $\eqref{eq:Effect_Operation}$
55
55
 
56
56
  $$ \label{eq:Effect_Operation}
57
- E_{e, \text{op}}(\text{t}_{i}) =
58
- \sum_{l \in \mathcal{L}} s_{l \rightarrow e, \text{op}}(\text{t}_i) +
57
+ E_{e, \text{op}}(\text{t}_{i}) =
58
+ \sum_{l \in \mathcal{L}} s_{l \rightarrow e, \text{op}}(\text{t}_i) +
59
59
  \sum_{x \in \mathcal{E}\backslash e} E_{x, \text{op}}(\text{t}_i) \cdot \text{r}_{x \rightarrow {e},\text{op}}(\text{t}_i)
60
60
  $$
61
61
 
@@ -68,8 +68,8 @@ With:
68
68
 
69
69
  - $\mathcal{L}$ being the set of all elements in the FlowSystem
70
70
  - $\mathcal{E}$ being the set of all effects in the FlowSystem
71
- - $\text r_{x \rightarrow e, \text{inv}}$ being the factor between the operation part of Effect $x$ and Effect $e$
72
- - $\text r_{x \rightarrow e, \text{op}}(\text{t}_i)$ being the factor between the invest part of Effect $x$ and Effect $e$
71
+ - $\text r_{x \rightarrow e, \text{inv}}$ being the factor between the invest part of Effect $x$ and Effect $e$
72
+ - $\text r_{x \rightarrow e, \text{op}}(\text{t}_i)$ being the factor between the operation part of Effect $x$ and Effect $e$
73
73
 
74
74
  - $\text{t}_i$ being the time step
75
75
  - $s_{l \rightarrow e, \text{inv}}$ being the share of element $l$ to the investment part of effect $e$
@@ -100,7 +100,7 @@ $$
100
100
 
101
101
  Additionally to the user defined [Effects](#effects), a Penalty $\Phi$ is part of every FlixOpt Model.
102
102
  Its used to prevent unsolvable problems and simplify troubleshooting.
103
- Shares to the penalty can originate from every Element and are constructed similarly to
103
+ Shares to the penalty can originate from every Element and are constructed similarly to
104
104
  $\eqref{Share_invest}$ and $\eqref{Share_operation}$.
105
105
 
106
106
  $$ \label{eq:Penalty}
@@ -113,7 +113,7 @@ With:
113
113
  - $\mathcal{T}$ being the set of all timesteps
114
114
  - $s_{l \rightarrow \Phi}$ being the share of element $l$ to the penalty
115
115
 
116
- At the moment, penalties only occur in [Buses](#buses)
116
+ At the moment, penalties only occur in [Buses](Bus.md)
117
117
 
118
118
  ## Objective
119
119
 
@@ -128,5 +128,5 @@ With:
128
128
  - $\Phi$ being the [Penalty](#penalty)
129
129
 
130
130
  This approach allows for a multi-criteria optimization using both...
131
- - ... the **Weigted Sum**Method, as the chosen **Objective Effect** can incorporate other Effects.
132
- - ... the ($\epsilon$-constraint method) by constraining effects.
131
+ - ... the **Weighted Sum** method, as the chosen **Objective Effect** can incorporate other Effects.
132
+ - ... the ($\epsilon$-constraint method) by constraining effects.
@@ -21,6 +21,6 @@ $$
21
21
  $$
22
22
 
23
23
 
24
- This mathematical Formulation can be extended or changed when using [OnOffParameters](#onoffparameters)
25
- to define the On/Off state of the Flow, or [InvestParameters](#investments),
26
- which changes the size of the Flow from a constant to an optimization variable.
24
+ This mathematical formulation can be extended by using [OnOffParameters](./OnOffParameters.md)
25
+ to define the on/off state of the Flow, or by using [InvestParameters](./InvestParameters.md)
26
+ to change the size of the Flow from a constant to an optimization variable.
@@ -0,0 +1,3 @@
1
+ # InvestParameters
2
+
3
+ This is a work in progress.
@@ -1,4 +1,4 @@
1
- [`LinearConverters`][flixopt.components.LinearConverter] define a ratio between incoming and outgoing [Flows](#flows).
1
+ [`LinearConverters`][flixopt.components.LinearConverter] define a ratio between incoming and outgoing [Flows](Flow.md).
2
2
 
3
3
  $$ \label{eq:Linear-Transformer-Ratio}
4
4
  \sum_{f_{\text{in}} \in \mathcal F_{in}} \text a_{f_{\text{in}}}(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = \sum_{f_{\text{out}} \in \mathcal F_{out}} \text b_{f_\text{out}}(\text{t}_i) \cdot p_{f_\text{out}}(\text{t}_i)
@@ -10,12 +10,12 @@ With:
10
10
  - $p_{f_\text{in}}(\text{t}_i)$ and $p_{f_\text{out}}(\text{t}_i)$ being the flow-rate at time $\text{t}_i$ for flow $f_\text{in}$ and $f_\text{out}$, respectively
11
11
  - $\text a_{f_\text{in}}(\text{t}_i)$ and $\text b_{f_\text{out}}(\text{t}_i)$ being the ratio of the flow-rate at time $\text{t}_i$ for flow $f_\text{in}$ and $f_\text{out}$, respectively
12
12
 
13
- With one incoming **Flow** and one outgoing **Flow**, this can be simplified to:
13
+ With one incoming **Flow** and one outgoing **Flow**, this can be simplified to:
14
14
 
15
15
  $$ \label{eq:Linear-Transformer-Ratio-simple}
16
16
  \text a(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = p_{f_\text{out}}(\text{t}_i)
17
17
  $$
18
18
 
19
- where $\text a$ can be interpreted as the conversion efficiency of the **LinearTransformer**.
20
- #### Piecewise Concersion factors
21
- The conversion efficiency can be defined as a piecewise linear approximation. See [Piecewise](Piecewise.md) for more details.
19
+ where $\text a$ can be interpreted as the conversion efficiency of the **LinearConverter**.
20
+ #### Piecewise Conversion factors
21
+ The conversion efficiency can be defined as a piecewise linear approximation. See [Piecewise](Piecewise.md) for more details.
@@ -0,0 +1,3 @@
1
+ # OnOffParameters
2
+
3
+ This is a work in progress.
@@ -40,7 +40,7 @@ Which can also be described as $v \in \{0\} \cup [\text{v}_{\text{start_k}}, \te
40
40
 
41
41
  ## Combining multiple Piecewises
42
42
 
43
- Piecewise allows representing non-linear relationships.
43
+ Piecewise allows representing non-linear relationships.
44
44
  This is a powerful technique in linear optimization to model non-linear behaviors while maintaining the problem's linearity.
45
45
 
46
46
  Therefore, each Piecewise must have the same number of Pieces $k$.
@@ -1,5 +1,5 @@
1
1
  # Storages
2
- **Storages** have one incoming and one outgoing **[Flow](#flows)** with a charging and discharging efficiency.
2
+ **Storages** have one incoming and one outgoing **[Flow](Flow.md)** with a charging and discharging efficiency.
3
3
  A storage has a state of charge $c(\text{t}_i)$ which is limited by its `size` $\text C$ and relative bounds $\eqref{eq:Storage_Bounds}$.
4
4
 
5
5
  $$ \label{eq:Storage_Bounds}
@@ -41,4 +41,4 @@ Where:
41
41
  - $p_{f_\text{in}}(\text{t}_i)$ is the input flow rate at time $\text{t}_i$
42
42
  - $\eta_\text{in}(\text{t}_i)$ is the charging efficiency at time $\text{t}_i$
43
43
  - $p_{f_\text{out}}(\text{t}_i)$ is the output flow rate at time $\text{t}_i$
44
- - $\eta_\text{out}(\text{t}_i)$ is the discharging efficiency at time $\text{t}_i$
44
+ - $\eta_\text{out}(\text{t}_i)$ is the discharging efficiency at time $\text{t}_i$
@@ -14,7 +14,7 @@ FlixOpt uses the following naming conventions:
14
14
 
15
15
  ## Timesteps
16
16
  Time steps are defined as a sequence of discrete time steps $\text{t}_i \in \mathcal{T} \quad \text{for} \quad i \in \{1, 2, \dots, \text{n}\}$ (left-aligned in its timespan).
17
- From this sequence, the corresponding time intervals $\Delta \text{t}_i \in \Delta \mathcal{T}$ are derived as
17
+ From this sequence, the corresponding time intervals $\Delta \text{t}_i \in \Delta \mathcal{T}$ are derived as
18
18
 
19
19
  $$\Delta \text{t}_i = \text{t}_{i+1} - \text{t}_i \quad \text{for} \quad i \in \{1, 2, \dots, \text{n}-1\}$$
20
20
 
@@ -1,3 +1,3 @@
1
1
  # Work in Progress
2
2
 
3
- This is a work in progress.
3
+ This is a work in progress.
docs/user-guide/index.md CHANGED
@@ -6,7 +6,7 @@ FlixOpt is built around a set of core concepts that work together to represent a
6
6
 
7
7
  ### FlowSystem
8
8
 
9
- The [`FlowSystem`][flixopt.flow_system.FlowSystem] is the central organizing unit in FlixOpt.
9
+ The [`FlowSystem`][flixopt.flow_system.FlowSystem] is the central organizing unit in FlixOpt.
10
10
  Every FlixOpt model starts with creating a FlowSystem. It:
11
11
 
12
12
  - Defines the timesteps for the optimization
@@ -40,7 +40,7 @@ Examples:
40
40
  [`Bus`][flixopt.elements.Bus] objects represent nodes or connection points in a FlowSystem. They:
41
41
 
42
42
  - Balance incoming and outgoing flows
43
- - Can represent physical networks like heat, electricity, or gas
43
+ - Can represent physical networks like heat, electricity, or gas
44
44
  - Handle infeasible balances gently by allowing the balance to be closed in return for a big Penalty (optional)
45
45
 
46
46
  ### Components
flixopt/__init__.py CHANGED
@@ -2,6 +2,10 @@
2
2
  This module bundles all common functionality of flixopt and sets up the logging
3
3
  """
4
4
 
5
+ from importlib.metadata import version
6
+
7
+ __version__ = version('flixopt')
8
+
5
9
  from .commons import (
6
10
  CONFIG,
7
11
  AggregatedCalculation,
flixopt/aggregation.py CHANGED
@@ -3,16 +3,15 @@ This module contains the Aggregation functionality for the flixopt framework.
3
3
  Through this, aggregating TimeSeriesData is possible.
4
4
  """
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  import copy
7
9
  import logging
8
10
  import pathlib
9
11
  import timeit
10
- import warnings
11
- from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
12
+ from typing import TYPE_CHECKING
12
13
 
13
- import linopy
14
14
  import numpy as np
15
- import pandas as pd
16
15
 
17
16
  try:
18
17
  import tsam.timeseriesaggregation as tsam
@@ -22,18 +21,20 @@ except ImportError:
22
21
  TSAM_AVAILABLE = False
23
22
 
24
23
  from .components import Storage
25
- from .core import Scalar, TimeSeriesData
26
- from .elements import Component
27
- from .flow_system import FlowSystem
28
24
  from .structure import (
29
- Element,
30
25
  Model,
31
26
  SystemModel,
32
27
  )
33
28
 
34
29
  if TYPE_CHECKING:
30
+ import linopy
31
+ import pandas as pd
35
32
  import plotly.graph_objects as go
36
33
 
34
+ from .core import Scalar, TimeSeriesData
35
+ from .elements import Component
36
+ from .flow_system import FlowSystem
37
+
37
38
  logger = logging.getLogger('flixopt')
38
39
 
39
40
 
@@ -48,9 +49,9 @@ class Aggregation:
48
49
  hours_per_time_step: Scalar,
49
50
  hours_per_period: Scalar,
50
51
  nr_of_periods: int = 8,
51
- weights: Dict[str, float] = None,
52
- time_series_for_high_peaks: List[str] = None,
53
- time_series_for_low_peaks: List[str] = None,
52
+ weights: dict[str, float] | None = None,
53
+ time_series_for_high_peaks: list[str] | None = None,
54
+ time_series_for_low_peaks: list[str] | None = None,
54
55
  ):
55
56
  """
56
57
  Args:
@@ -75,9 +76,9 @@ class Aggregation:
75
76
  self.time_series_for_high_peaks = time_series_for_high_peaks or []
76
77
  self.time_series_for_low_peaks = time_series_for_low_peaks or []
77
78
 
78
- self.aggregated_data: Optional[pd.DataFrame] = None
79
+ self.aggregated_data: pd.DataFrame | None = None
79
80
  self.clustering_duration_seconds = None
80
- self.tsam: Optional[tsam.TimeSeriesAggregation] = None
81
+ self.tsam: tsam.TimeSeriesAggregation | None = None
81
82
 
82
83
  def cluster(self) -> None:
83
84
  """
@@ -140,7 +141,7 @@ class Aggregation:
140
141
  def use_extreme_periods(self):
141
142
  return self.time_series_for_high_peaks or self.time_series_for_low_peaks
142
143
 
143
- def plot(self, colormap: str = 'viridis', show: bool = True, save: Optional[pathlib.Path] = None) -> 'go.Figure':
144
+ def plot(self, colormap: str = 'viridis', show: bool = True, save: pathlib.Path | None = None) -> go.Figure:
144
145
  from . import plotting
145
146
 
146
147
  df_org = self.original_data.copy().rename(
@@ -162,14 +163,14 @@ class Aggregation:
162
163
  figure_like=fig,
163
164
  default_path=pathlib.Path('aggregated data.html'),
164
165
  default_filetype='.html',
165
- user_path=None if isinstance(save, bool) else pathlib.Path(save),
166
+ user_path=save,
166
167
  show=show,
167
- save=True if save else False,
168
+ save=save is not None,
168
169
  )
169
170
 
170
171
  return fig
171
172
 
172
- def get_cluster_indices(self) -> Dict[str, List[np.ndarray]]:
173
+ def get_cluster_indices(self) -> dict[str, list[np.ndarray]]:
173
174
  """
174
175
  Generates a dictionary that maps each cluster to a list of index vectors representing the time steps
175
176
  assigned to that cluster for each period.
@@ -192,7 +193,7 @@ class Aggregation:
192
193
 
193
194
  return index_vectors
194
195
 
195
- def get_equation_indices(self, skip_first_index_of_period: bool = True) -> Tuple[np.ndarray, np.ndarray]:
196
+ def get_equation_indices(self, skip_first_index_of_period: bool = True) -> tuple[np.ndarray, np.ndarray]:
196
197
  """
197
198
  Generates pairs of indices for the equations by comparing index vectors of the same cluster.
198
199
  If `skip_first_index_of_period` is True, the first index of each period is skipped.
@@ -201,7 +202,7 @@ class Aggregation:
201
202
  skip_first_index_of_period (bool): Whether to include or skip the first index of each period.
202
203
 
203
204
  Returns:
204
- Tuple[np.ndarray, np.ndarray]: Two arrays of indices.
205
+ tuple[np.ndarray, np.ndarray]: Two arrays of indices.
205
206
  """
206
207
  idx_var1 = []
207
208
  idx_var2 = []
@@ -237,8 +238,8 @@ class AggregationParameters:
237
238
  aggregate_data_and_fix_non_binary_vars: bool,
238
239
  percentage_of_period_freedom: float = 0,
239
240
  penalty_of_period_freedom: float = 0,
240
- time_series_for_high_peaks: List[TimeSeriesData] = None,
241
- time_series_for_low_peaks: List[TimeSeriesData] = None,
241
+ time_series_for_high_peaks: list[TimeSeriesData] | None = None,
242
+ time_series_for_low_peaks: list[TimeSeriesData] | None = None,
242
243
  ):
243
244
  """
244
245
  Initializes aggregation parameters for time series data
@@ -264,24 +265,24 @@ class AggregationParameters:
264
265
  self.aggregate_data_and_fix_non_binary_vars = aggregate_data_and_fix_non_binary_vars
265
266
  self.percentage_of_period_freedom = percentage_of_period_freedom
266
267
  self.penalty_of_period_freedom = penalty_of_period_freedom
267
- self.time_series_for_high_peaks: List[TimeSeriesData] = time_series_for_high_peaks or []
268
- self.time_series_for_low_peaks: List[TimeSeriesData] = time_series_for_low_peaks or []
268
+ self.time_series_for_high_peaks: list[TimeSeriesData] = time_series_for_high_peaks or []
269
+ self.time_series_for_low_peaks: list[TimeSeriesData] = time_series_for_low_peaks or []
269
270
 
270
271
  @property
271
272
  def use_extreme_periods(self):
272
273
  return self.time_series_for_high_peaks or self.time_series_for_low_peaks
273
274
 
274
275
  @property
275
- def labels_for_high_peaks(self) -> List[str]:
276
+ def labels_for_high_peaks(self) -> list[str]:
276
277
  return [ts.label for ts in self.time_series_for_high_peaks]
277
278
 
278
279
  @property
279
- def labels_for_low_peaks(self) -> List[str]:
280
+ def labels_for_low_peaks(self) -> list[str]:
280
281
  return [ts.label for ts in self.time_series_for_low_peaks]
281
282
 
282
283
  @property
283
- def use_low_peaks(self):
284
- return self.time_series_for_low_peaks is not None
284
+ def use_low_peaks(self) -> bool:
285
+ return bool(self.time_series_for_low_peaks)
285
286
 
286
287
 
287
288
  class AggregationModel(Model):
@@ -295,7 +296,7 @@ class AggregationModel(Model):
295
296
  aggregation_parameters: AggregationParameters,
296
297
  flow_system: FlowSystem,
297
298
  aggregation_data: Aggregation,
298
- components_to_clusterize: Optional[List[Component]],
299
+ components_to_clusterize: list[Component] | None,
299
300
  ):
300
301
  """
301
302
  Modeling-Element for "index-equating"-equations
@@ -314,9 +315,9 @@ class AggregationModel(Model):
314
315
 
315
316
  indices = self.aggregation_data.get_equation_indices(skip_first_index_of_period=True)
316
317
 
317
- time_variables: Set[str] = {k for k, v in self._model.variables.data.items() if 'time' in v.indexes}
318
- binary_variables: Set[str] = {k for k, v in self._model.variables.data.items() if k in self._model.binaries}
319
- binary_time_variables: Set[str] = time_variables & binary_variables
318
+ time_variables: set[str] = {k for k, v in self._model.variables.data.items() if 'time' in v.indexes}
319
+ binary_variables: set[str] = {k for k, v in self._model.variables.data.items() if k in self._model.binaries}
320
+ binary_time_variables: set[str] = time_variables & binary_variables
320
321
 
321
322
  for component in components:
322
323
  if isinstance(component, Storage) and not self.aggregation_parameters.fix_storage_flows:
@@ -336,7 +337,7 @@ class AggregationModel(Model):
336
337
  for variable in self.variables_direct.values():
337
338
  self._model.effects.add_share_to_penalty('Aggregation', variable * penalty)
338
339
 
339
- def _equate_indices(self, variable: linopy.Variable, indices: Tuple[np.ndarray, np.ndarray]) -> None:
340
+ def _equate_indices(self, variable: linopy.Variable, indices: tuple[np.ndarray, np.ndarray]) -> None:
340
341
  assert len(indices[0]) == len(indices[1]), 'The length of the indices must match!!'
341
342
  length = len(indices[0])
342
343