flixopt 2.1.6__py3-none-any.whl → 2.1.7__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.
- docs/examples/00-Minimal Example.md +1 -1
- docs/examples/01-Basic Example.md +1 -1
- docs/examples/02-Complex Example.md +1 -1
- docs/examples/index.md +1 -1
- docs/faq/contribute.md +26 -14
- docs/faq/index.md +1 -1
- docs/javascripts/mathjax.js +1 -1
- docs/user-guide/Mathematical Notation/Bus.md +1 -1
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +13 -13
- docs/user-guide/Mathematical Notation/Flow.md +1 -1
- docs/user-guide/Mathematical Notation/LinearConverter.md +2 -2
- docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
- docs/user-guide/Mathematical Notation/Storage.md +1 -1
- docs/user-guide/Mathematical Notation/index.md +1 -1
- docs/user-guide/Mathematical Notation/others.md +1 -1
- docs/user-guide/index.md +2 -2
- flixopt/__init__.py +4 -0
- flixopt/calculation.py +3 -7
- flixopt/components.py +14 -4
- flixopt/core.py +10 -6
- flixopt/effects.py +2 -1
- flixopt/elements.py +5 -3
- flixopt/features.py +17 -13
- flixopt/flow_system.py +4 -3
- flixopt/network_app.py +371 -228
- flixopt/structure.py +1 -3
- {flixopt-2.1.6.dist-info → flixopt-2.1.7.dist-info}/METADATA +22 -15
- flixopt-2.1.7.dist-info/RECORD +54 -0
- scripts/extract_release_notes.py +5 -5
- flixopt-2.1.6.dist-info/RECORD +0 -54
- {flixopt-2.1.6.dist-info → flixopt-2.1.7.dist-info}/WHEEL +0 -0
- {flixopt-2.1.6.dist-info → flixopt-2.1.7.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.6.dist-info → flixopt-2.1.7.dist-info}/top_level.txt +0 -0
docs/examples/index.md
CHANGED
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 -
|
|
8
|
-
3.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `
|
|
16
|
-
- `ruff
|
|
17
|
-
|
|
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
docs/javascripts/mathjax.js
CHANGED
|
@@ -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`)
|
|
@@ -8,17 +8,17 @@ These arise from so called **Shares**, which originate from **Elements** like [F
|
|
|
8
8
|
Assiziated effects could be:
|
|
9
9
|
- costs - given in [€/kWh]...
|
|
10
10
|
- ...or emissions - given in [kg/kWh].
|
|
11
|
-
-
|
|
11
|
+
-
|
|
12
12
|
Effects are allocated seperatly 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
46
|
The overall sum of investment shares of an Effect $e$ is given by $\eqref{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
|
|
|
@@ -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}
|
|
@@ -129,4 +129,4 @@ With:
|
|
|
129
129
|
|
|
130
130
|
This approach allows for a multi-criteria optimization using both...
|
|
131
131
|
- ... the **Weigted Sum**Method, as the chosen **Objective Effect** can incorporate other Effects.
|
|
132
|
-
- ... the ($\epsilon$-constraint method) by constraining effects.
|
|
132
|
+
- ... the ($\epsilon$-constraint method) by constraining effects.
|
|
@@ -23,4 +23,4 @@ $$
|
|
|
23
23
|
|
|
24
24
|
This mathematical Formulation can be extended or changed when using [OnOffParameters](#onoffparameters)
|
|
25
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.
|
|
26
|
+
which changes the size of the Flow from a constant to an optimization variable.
|
|
@@ -10,7 +10,7 @@ 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)
|
|
@@ -18,4 +18,4 @@ $$
|
|
|
18
18
|
|
|
19
19
|
where $\text a$ can be interpreted as the conversion efficiency of the **LinearTransformer**.
|
|
20
20
|
#### Piecewise Concersion factors
|
|
21
|
-
The conversion efficiency can be defined as a piecewise linear approximation. See [Piecewise](Piecewise.md) for more details.
|
|
21
|
+
The conversion efficiency can be defined as a piecewise linear approximation. See [Piecewise](Piecewise.md) for more details.
|
|
@@ -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$.
|
|
@@ -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
|
|
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
flixopt/calculation.py
CHANGED
|
@@ -62,13 +62,9 @@ class Calculation:
|
|
|
62
62
|
self.folder = pathlib.Path.cwd() / 'results' if folder is None else pathlib.Path(folder)
|
|
63
63
|
self.results: Optional[CalculationResults] = None
|
|
64
64
|
|
|
65
|
-
if not self.folder.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
except FileNotFoundError as e:
|
|
69
|
-
raise FileNotFoundError(
|
|
70
|
-
f'Folder {self.folder} and its parent do not exist. Please create them first.'
|
|
71
|
-
) from e
|
|
65
|
+
if self.folder.exists() and not self.folder.is_dir():
|
|
66
|
+
raise NotADirectoryError(f'Path {self.folder} exists and is not a directory.')
|
|
67
|
+
self.folder.mkdir(parents=False, exist_ok=True)
|
|
72
68
|
|
|
73
69
|
@property
|
|
74
70
|
def main_results(self) -> Dict[str, Union[Scalar, Dict]]:
|
flixopt/components.py
CHANGED
|
@@ -674,7 +674,7 @@ class Source(Component):
|
|
|
674
674
|
outputs: List[Flow] = None,
|
|
675
675
|
meta_data: Optional[Dict] = None,
|
|
676
676
|
prevent_simultaneous_flow_rates: bool = False,
|
|
677
|
-
**kwargs
|
|
677
|
+
**kwargs,
|
|
678
678
|
):
|
|
679
679
|
"""
|
|
680
680
|
Args:
|
|
@@ -694,7 +694,12 @@ class Source(Component):
|
|
|
694
694
|
outputs = [source]
|
|
695
695
|
|
|
696
696
|
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
697
|
-
super().__init__(
|
|
697
|
+
super().__init__(
|
|
698
|
+
label,
|
|
699
|
+
outputs=outputs,
|
|
700
|
+
meta_data=meta_data,
|
|
701
|
+
prevent_simultaneous_flows=outputs if prevent_simultaneous_flow_rates else None,
|
|
702
|
+
)
|
|
698
703
|
|
|
699
704
|
@property
|
|
700
705
|
def source(self) -> Flow:
|
|
@@ -714,7 +719,7 @@ class Sink(Component):
|
|
|
714
719
|
inputs: List[Flow] = None,
|
|
715
720
|
meta_data: Optional[Dict] = None,
|
|
716
721
|
prevent_simultaneous_flow_rates: bool = False,
|
|
717
|
-
**kwargs
|
|
722
|
+
**kwargs,
|
|
718
723
|
):
|
|
719
724
|
"""
|
|
720
725
|
Args:
|
|
@@ -734,7 +739,12 @@ class Sink(Component):
|
|
|
734
739
|
inputs = [sink]
|
|
735
740
|
|
|
736
741
|
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
737
|
-
super().__init__(
|
|
742
|
+
super().__init__(
|
|
743
|
+
label,
|
|
744
|
+
inputs=inputs,
|
|
745
|
+
meta_data=meta_data,
|
|
746
|
+
prevent_simultaneous_flows=inputs if prevent_simultaneous_flow_rates else None,
|
|
747
|
+
)
|
|
738
748
|
|
|
739
749
|
@property
|
|
740
750
|
def sink(self) -> Flow:
|
flixopt/core.py
CHANGED
|
@@ -62,17 +62,21 @@ class DataConverter:
|
|
|
62
62
|
return xr.DataArray(data, coords=coords, dims=dims)
|
|
63
63
|
elif isinstance(data, pd.DataFrame):
|
|
64
64
|
if not data.index.equals(timesteps):
|
|
65
|
-
raise ConversionError(
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
raise ConversionError(
|
|
66
|
+
f"DataFrame index doesn't match timesteps index. "
|
|
67
|
+
f'Its missing the following time steps: {timesteps.difference(data.index)}. '
|
|
68
|
+
f'Some parameters might need an extra timestep at the end.'
|
|
69
|
+
)
|
|
68
70
|
if not len(data.columns) == 1:
|
|
69
71
|
raise ConversionError('DataFrame must have exactly one column')
|
|
70
72
|
return xr.DataArray(data.values.flatten(), coords=coords, dims=dims)
|
|
71
73
|
elif isinstance(data, pd.Series):
|
|
72
74
|
if not data.index.equals(timesteps):
|
|
73
|
-
raise ConversionError(
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
raise ConversionError(
|
|
76
|
+
f"Series index doesn't match timesteps index. "
|
|
77
|
+
f'Its missing the following time steps: {timesteps.difference(data.index)}. '
|
|
78
|
+
f'Some parameters might need an extra timestep at the end.'
|
|
79
|
+
)
|
|
76
80
|
return xr.DataArray(data.values, coords=coords, dims=dims)
|
|
77
81
|
elif isinstance(data, np.ndarray):
|
|
78
82
|
if data.ndim != 1:
|
flixopt/effects.py
CHANGED
|
@@ -94,7 +94,8 @@ class Effect(Element):
|
|
|
94
94
|
f'{self.label_full}|minimum_operation_per_hour', self.minimum_operation_per_hour
|
|
95
95
|
)
|
|
96
96
|
self.maximum_operation_per_hour = flow_system.create_time_series(
|
|
97
|
-
f'{self.label_full}|maximum_operation_per_hour',
|
|
97
|
+
f'{self.label_full}|maximum_operation_per_hour',
|
|
98
|
+
self.maximum_operation_per_hour,
|
|
98
99
|
)
|
|
99
100
|
|
|
100
101
|
self.specific_share_to_other_effects_operation = flow_system.create_effect_time_series(
|
flixopt/elements.py
CHANGED
|
@@ -352,8 +352,10 @@ class FlowModel(ElementModel):
|
|
|
352
352
|
label_of_element=self.label_of_element,
|
|
353
353
|
parameters=self.element.size,
|
|
354
354
|
defining_variable=self.flow_rate,
|
|
355
|
-
relative_bounds_of_defining_variable=(
|
|
356
|
-
|
|
355
|
+
relative_bounds_of_defining_variable=(
|
|
356
|
+
self.flow_rate_lower_bound_relative,
|
|
357
|
+
self.flow_rate_upper_bound_relative,
|
|
358
|
+
),
|
|
357
359
|
on_variable=self.on_off.on if self.on_off is not None else None,
|
|
358
360
|
),
|
|
359
361
|
'investment',
|
|
@@ -448,7 +450,7 @@ class FlowModel(ElementModel):
|
|
|
448
450
|
|
|
449
451
|
@property
|
|
450
452
|
def flow_rate_upper_bound_relative(self) -> NumericData:
|
|
451
|
-
"""
|
|
453
|
+
"""Returns the upper bound of the flow_rate relative to its size"""
|
|
452
454
|
fixed_profile = self.element.fixed_relative_profile
|
|
453
455
|
if fixed_profile is None:
|
|
454
456
|
return self.element.relative_maximum.active_data
|
flixopt/features.py
CHANGED
|
@@ -90,7 +90,10 @@ class InvestmentModel(Model):
|
|
|
90
90
|
# share: divest_effects - isInvested * divest_effects
|
|
91
91
|
self._model.effects.add_share_to_effects(
|
|
92
92
|
name=self.label_of_element,
|
|
93
|
-
expressions={
|
|
93
|
+
expressions={
|
|
94
|
+
effect: -self.is_invested * factor + factor
|
|
95
|
+
for effect, factor in self.parameters.divest_effects.items()
|
|
96
|
+
},
|
|
94
97
|
target='invest',
|
|
95
98
|
)
|
|
96
99
|
|
|
@@ -304,13 +307,11 @@ class StateModel(Model):
|
|
|
304
307
|
)
|
|
305
308
|
|
|
306
309
|
# Constraint: on * upper_bound >= def_var
|
|
307
|
-
self.add(
|
|
308
|
-
self._model.add_constraints(self.on * ub >= def_var, name=f'{self.label_full}|on_con2'), 'on_con2'
|
|
309
|
-
)
|
|
310
|
+
self.add(self._model.add_constraints(self.on * ub >= def_var, name=f'{self.label_full}|on_con2'), 'on_con2')
|
|
310
311
|
else:
|
|
311
312
|
# Case for multiple defining variables
|
|
312
313
|
ub = sum(bound[1] for bound in self._defining_bounds) / nr_of_def_vars
|
|
313
|
-
lb = CONFIG.modeling.EPSILON #TODO: Can this be a bigger value? (maybe the smallest bound?)
|
|
314
|
+
lb = CONFIG.modeling.EPSILON # TODO: Can this be a bigger value? (maybe the smallest bound?)
|
|
314
315
|
|
|
315
316
|
# Constraint: on * epsilon <= sum(all_defining_variables)
|
|
316
317
|
self.add(
|
|
@@ -426,7 +427,9 @@ class SwitchStateModel(Model):
|
|
|
426
427
|
|
|
427
428
|
# Mutual exclusivity constraint
|
|
428
429
|
self.add(
|
|
429
|
-
self._model.add_constraints(
|
|
430
|
+
self._model.add_constraints(
|
|
431
|
+
self.switch_on + self.switch_off <= 1.1, name=f'{self.label_full}|switch_on_or_off'
|
|
432
|
+
),
|
|
430
433
|
'switch_on_or_off',
|
|
431
434
|
)
|
|
432
435
|
|
|
@@ -502,9 +505,7 @@ class ConsecutiveStateModel(Model):
|
|
|
502
505
|
|
|
503
506
|
# Upper bound constraint
|
|
504
507
|
self.add(
|
|
505
|
-
self._model.add_constraints(
|
|
506
|
-
self.duration <= self._state_variable * mega, name=f'{self.label_full}|con1'
|
|
507
|
-
),
|
|
508
|
+
self._model.add_constraints(self.duration <= self._state_variable * mega, name=f'{self.label_full}|con1'),
|
|
508
509
|
'con1',
|
|
509
510
|
)
|
|
510
511
|
|
|
@@ -556,8 +557,8 @@ class ConsecutiveStateModel(Model):
|
|
|
556
557
|
# Set initial value
|
|
557
558
|
self.add(
|
|
558
559
|
self._model.add_constraints(
|
|
559
|
-
self.duration.isel(time=0)
|
|
560
|
-
(hours_per_step.isel(time=0) + self.previous_duration) * self._state_variable.isel(time=0),
|
|
560
|
+
self.duration.isel(time=0)
|
|
561
|
+
== (hours_per_step.isel(time=0) + self.previous_duration) * self._state_variable.isel(time=0),
|
|
561
562
|
name=f'{self.label_full}|initial',
|
|
562
563
|
),
|
|
563
564
|
'initial',
|
|
@@ -568,7 +569,7 @@ class ConsecutiveStateModel(Model):
|
|
|
568
569
|
@property
|
|
569
570
|
def previous_duration(self) -> Scalar:
|
|
570
571
|
"""Computes the previous duration of the state variable"""
|
|
571
|
-
#TODO: Allow for other/dynamic timestep resolutions
|
|
572
|
+
# TODO: Allow for other/dynamic timestep resolutions
|
|
572
573
|
return ConsecutiveStateModel.compute_consecutive_hours_in_state(
|
|
573
574
|
self._previous_states, self._model.hours_per_step.isel(time=0).item()
|
|
574
575
|
)
|
|
@@ -619,7 +620,10 @@ class ConsecutiveStateModel(Model):
|
|
|
619
620
|
f'as {binary_values=}'
|
|
620
621
|
)
|
|
621
622
|
|
|
622
|
-
return np.sum(
|
|
623
|
+
return np.sum(
|
|
624
|
+
binary_values[-nr_of_indexes_with_consecutive_ones:]
|
|
625
|
+
* hours_per_timestep[-nr_of_indexes_with_consecutive_ones:]
|
|
626
|
+
)
|
|
623
627
|
|
|
624
628
|
|
|
625
629
|
class OnOffModel(Model):
|
flixopt/flow_system.py
CHANGED
|
@@ -257,9 +257,9 @@ class FlowSystem:
|
|
|
257
257
|
|
|
258
258
|
if not DASH_CYTOSCAPE_AVAILABLE:
|
|
259
259
|
raise ImportError(
|
|
260
|
-
f
|
|
261
|
-
f
|
|
262
|
-
f
|
|
260
|
+
f'Network visualization requires optional dependencies. '
|
|
261
|
+
f'Install with: pip install flixopt[viz], flixopt[full] or pip install dash dash_cytoscape networkx werkzeug. '
|
|
262
|
+
f'Original error: {VISUALIZATION_ERROR}'
|
|
263
263
|
)
|
|
264
264
|
|
|
265
265
|
if not self._connected:
|
|
@@ -274,6 +274,7 @@ class FlowSystem:
|
|
|
274
274
|
def stop_network_app(self):
|
|
275
275
|
"""Stop the network visualization server."""
|
|
276
276
|
from .network_app import DASH_CYTOSCAPE_AVAILABLE, VISUALIZATION_ERROR
|
|
277
|
+
|
|
277
278
|
if not DASH_CYTOSCAPE_AVAILABLE:
|
|
278
279
|
raise ImportError(
|
|
279
280
|
f'Network visualization requires optional dependencies. '
|