flixopt 2.1.7__py3-none-any.whl → 2.1.9__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/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +8 -8
- docs/user-guide/Mathematical Notation/Flow.md +3 -3
- docs/user-guide/Mathematical Notation/InvestParameters.md +3 -0
- docs/user-guide/Mathematical Notation/LinearConverter.md +3 -3
- docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
- docs/user-guide/Mathematical Notation/Storage.md +1 -1
- flixopt/aggregation.py +33 -32
- flixopt/calculation.py +158 -58
- flixopt/components.py +673 -150
- flixopt/config.py +17 -8
- flixopt/core.py +59 -54
- flixopt/effects.py +144 -63
- flixopt/elements.py +292 -107
- flixopt/features.py +61 -58
- flixopt/flow_system.py +69 -48
- flixopt/interface.py +952 -113
- flixopt/io.py +15 -10
- flixopt/linear_converters.py +373 -81
- flixopt/network_app.py +73 -39
- flixopt/plotting.py +215 -87
- flixopt/results.py +382 -209
- flixopt/solvers.py +25 -21
- flixopt/structure.py +41 -37
- flixopt/utils.py +10 -7
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/METADATA +46 -42
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/RECORD +30 -28
- scripts/gen_ref_pages.py +1 -1
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/WHEEL +0 -0
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/top_level.txt +0 -0
flixopt/features.py
CHANGED
|
@@ -3,18 +3,21 @@ This module contains the features of the flixopt framework.
|
|
|
3
3
|
Features extend the functionality of Elements.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
6
8
|
import logging
|
|
7
|
-
from typing import
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
8
10
|
|
|
9
11
|
import linopy
|
|
10
12
|
import numpy as np
|
|
11
13
|
|
|
12
|
-
from . import utils
|
|
13
14
|
from .config import CONFIG
|
|
14
15
|
from .core import NumericData, Scalar, TimeSeries
|
|
15
|
-
from .interface import InvestParameters, OnOffParameters, Piecewise
|
|
16
16
|
from .structure import Model, SystemModel
|
|
17
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .interface import InvestParameters, OnOffParameters, Piecewise
|
|
20
|
+
|
|
18
21
|
logger = logging.getLogger('flixopt')
|
|
19
22
|
|
|
20
23
|
|
|
@@ -26,16 +29,16 @@ class InvestmentModel(Model):
|
|
|
26
29
|
model: SystemModel,
|
|
27
30
|
label_of_element: str,
|
|
28
31
|
parameters: InvestParameters,
|
|
29
|
-
defining_variable:
|
|
30
|
-
relative_bounds_of_defining_variable:
|
|
31
|
-
label:
|
|
32
|
-
on_variable:
|
|
32
|
+
defining_variable: linopy.Variable,
|
|
33
|
+
relative_bounds_of_defining_variable: tuple[NumericData, NumericData],
|
|
34
|
+
label: str | None = None,
|
|
35
|
+
on_variable: linopy.Variable | None = None,
|
|
33
36
|
):
|
|
34
37
|
super().__init__(model, label_of_element, label)
|
|
35
|
-
self.size:
|
|
36
|
-
self.is_invested:
|
|
38
|
+
self.size: Scalar | linopy.Variable | None = None
|
|
39
|
+
self.is_invested: linopy.Variable | None = None
|
|
37
40
|
|
|
38
|
-
self.piecewise_effects:
|
|
41
|
+
self.piecewise_effects: PiecewiseEffectsModel | None = None
|
|
39
42
|
|
|
40
43
|
self._on_variable = on_variable
|
|
41
44
|
self._defining_variable = defining_variable
|
|
@@ -205,14 +208,14 @@ class StateModel(Model):
|
|
|
205
208
|
self,
|
|
206
209
|
model: SystemModel,
|
|
207
210
|
label_of_element: str,
|
|
208
|
-
defining_variables:
|
|
209
|
-
defining_bounds:
|
|
210
|
-
previous_values:
|
|
211
|
+
defining_variables: list[linopy.Variable],
|
|
212
|
+
defining_bounds: list[tuple[NumericData, NumericData]],
|
|
213
|
+
previous_values: list[NumericData | None] | None = None,
|
|
211
214
|
use_off: bool = True,
|
|
212
|
-
on_hours_total_min:
|
|
213
|
-
on_hours_total_max:
|
|
214
|
-
effects_per_running_hour:
|
|
215
|
-
label:
|
|
215
|
+
on_hours_total_min: NumericData | None = 0,
|
|
216
|
+
on_hours_total_max: NumericData | None = None,
|
|
217
|
+
effects_per_running_hour: dict[str, NumericData] | None = None,
|
|
218
|
+
label: str | None = None,
|
|
216
219
|
):
|
|
217
220
|
"""
|
|
218
221
|
Models binary state variables based on a continous variable.
|
|
@@ -240,7 +243,7 @@ class StateModel(Model):
|
|
|
240
243
|
self._effects_per_running_hour = effects_per_running_hour or {}
|
|
241
244
|
|
|
242
245
|
self.on = None
|
|
243
|
-
self.total_on_hours:
|
|
246
|
+
self.total_on_hours: linopy.Variable | None = None
|
|
244
247
|
self.off = None
|
|
245
248
|
|
|
246
249
|
def do_modeling(self):
|
|
@@ -345,7 +348,7 @@ class StateModel(Model):
|
|
|
345
348
|
return 1 - self.previous_states
|
|
346
349
|
|
|
347
350
|
@staticmethod
|
|
348
|
-
def compute_previous_states(previous_values:
|
|
351
|
+
def compute_previous_states(previous_values: list[NumericData | None] | None, epsilon: float = 1e-5) -> np.ndarray:
|
|
349
352
|
"""Computes the previous states {0, 1} of defining variables as a binary array from their previous values."""
|
|
350
353
|
if not previous_values or all([val is None for val in previous_values]):
|
|
351
354
|
return np.array([0])
|
|
@@ -369,8 +372,8 @@ class SwitchStateModel(Model):
|
|
|
369
372
|
label_of_element: str,
|
|
370
373
|
state_variable: linopy.Variable,
|
|
371
374
|
previous_state=0,
|
|
372
|
-
switch_on_max:
|
|
373
|
-
label:
|
|
375
|
+
switch_on_max: Scalar | None = None,
|
|
376
|
+
label: str | None = None,
|
|
374
377
|
):
|
|
375
378
|
super().__init__(model, label_of_element, label)
|
|
376
379
|
self._state_variable = state_variable
|
|
@@ -454,10 +457,10 @@ class ConsecutiveStateModel(Model):
|
|
|
454
457
|
model: SystemModel,
|
|
455
458
|
label_of_element: str,
|
|
456
459
|
state_variable: linopy.Variable,
|
|
457
|
-
minimum_duration:
|
|
458
|
-
maximum_duration:
|
|
459
|
-
previous_states:
|
|
460
|
-
label:
|
|
460
|
+
minimum_duration: NumericData | None = None,
|
|
461
|
+
maximum_duration: NumericData | None = None,
|
|
462
|
+
previous_states: NumericData | None = None,
|
|
463
|
+
label: str | None = None,
|
|
461
464
|
):
|
|
462
465
|
"""
|
|
463
466
|
Model and constraint the consecutive duration of a state variable.
|
|
@@ -576,7 +579,7 @@ class ConsecutiveStateModel(Model):
|
|
|
576
579
|
|
|
577
580
|
@staticmethod
|
|
578
581
|
def compute_consecutive_hours_in_state(
|
|
579
|
-
binary_values: NumericData, hours_per_timestep:
|
|
582
|
+
binary_values: NumericData, hours_per_timestep: int | float | np.ndarray
|
|
580
583
|
) -> Scalar:
|
|
581
584
|
"""
|
|
582
585
|
Computes the final consecutive duration in state 'on' (=1) in hours, from a binary array.
|
|
@@ -616,7 +619,7 @@ class ConsecutiveStateModel(Model):
|
|
|
616
619
|
if len(hours_per_timestep) < nr_of_indexes_with_consecutive_ones:
|
|
617
620
|
raise ValueError(
|
|
618
621
|
f'When trying to calculate the consecutive duration, the length of the last duration '
|
|
619
|
-
f'({
|
|
622
|
+
f'({nr_of_indexes_with_consecutive_ones}) is longer than the provided hours_per_timestep ({len(hours_per_timestep)}), '
|
|
620
623
|
f'as {binary_values=}'
|
|
621
624
|
)
|
|
622
625
|
|
|
@@ -637,10 +640,10 @@ class OnOffModel(Model):
|
|
|
637
640
|
model: SystemModel,
|
|
638
641
|
on_off_parameters: OnOffParameters,
|
|
639
642
|
label_of_element: str,
|
|
640
|
-
defining_variables:
|
|
641
|
-
defining_bounds:
|
|
642
|
-
previous_values:
|
|
643
|
-
label:
|
|
643
|
+
defining_variables: list[linopy.Variable],
|
|
644
|
+
defining_bounds: list[tuple[NumericData, NumericData]],
|
|
645
|
+
previous_values: list[NumericData | None],
|
|
646
|
+
label: str | None = None,
|
|
644
647
|
):
|
|
645
648
|
"""
|
|
646
649
|
Constructor for OnOffModel
|
|
@@ -786,9 +789,9 @@ class PieceModel(Model):
|
|
|
786
789
|
as_time_series: bool = True,
|
|
787
790
|
):
|
|
788
791
|
super().__init__(model, label_of_element, label)
|
|
789
|
-
self.inside_piece:
|
|
790
|
-
self.lambda0:
|
|
791
|
-
self.lambda1:
|
|
792
|
+
self.inside_piece: linopy.Variable | None = None
|
|
793
|
+
self.lambda0: linopy.Variable | None = None
|
|
794
|
+
self.lambda1: linopy.Variable | None = None
|
|
792
795
|
self._as_time_series = as_time_series
|
|
793
796
|
|
|
794
797
|
def do_modeling(self):
|
|
@@ -835,8 +838,8 @@ class PiecewiseModel(Model):
|
|
|
835
838
|
self,
|
|
836
839
|
model: SystemModel,
|
|
837
840
|
label_of_element: str,
|
|
838
|
-
piecewise_variables:
|
|
839
|
-
zero_point:
|
|
841
|
+
piecewise_variables: dict[str, Piecewise],
|
|
842
|
+
zero_point: bool | linopy.Variable | None,
|
|
840
843
|
as_time_series: bool,
|
|
841
844
|
label: str = '',
|
|
842
845
|
):
|
|
@@ -858,8 +861,8 @@ class PiecewiseModel(Model):
|
|
|
858
861
|
self._zero_point = zero_point
|
|
859
862
|
self._as_time_series = as_time_series
|
|
860
863
|
|
|
861
|
-
self.pieces:
|
|
862
|
-
self.zero_point:
|
|
864
|
+
self.pieces: list[PieceModel] = []
|
|
865
|
+
self.zero_point: linopy.Variable | None = None
|
|
863
866
|
|
|
864
867
|
def do_modeling(self):
|
|
865
868
|
for i in range(len(list(self._piecewise_variables.values())[0])):
|
|
@@ -922,30 +925,30 @@ class ShareAllocationModel(Model):
|
|
|
922
925
|
self,
|
|
923
926
|
model: SystemModel,
|
|
924
927
|
shares_are_time_series: bool,
|
|
925
|
-
label_of_element:
|
|
926
|
-
label:
|
|
927
|
-
label_full:
|
|
928
|
-
total_max:
|
|
929
|
-
total_min:
|
|
930
|
-
max_per_hour:
|
|
931
|
-
min_per_hour:
|
|
928
|
+
label_of_element: str | None = None,
|
|
929
|
+
label: str | None = None,
|
|
930
|
+
label_full: str | None = None,
|
|
931
|
+
total_max: Scalar | None = None,
|
|
932
|
+
total_min: Scalar | None = None,
|
|
933
|
+
max_per_hour: NumericData | None = None,
|
|
934
|
+
min_per_hour: NumericData | None = None,
|
|
932
935
|
):
|
|
933
936
|
super().__init__(model, label_of_element=label_of_element, label=label, label_full=label_full)
|
|
934
937
|
if not shares_are_time_series: # If the condition is True
|
|
935
938
|
assert max_per_hour is None and min_per_hour is None, (
|
|
936
939
|
'Both max_per_hour and min_per_hour cannot be used when shares_are_time_series is False'
|
|
937
940
|
)
|
|
938
|
-
self.total_per_timestep:
|
|
939
|
-
self.total:
|
|
940
|
-
self.shares:
|
|
941
|
-
self.share_constraints:
|
|
941
|
+
self.total_per_timestep: linopy.Variable | None = None
|
|
942
|
+
self.total: linopy.Variable | None = None
|
|
943
|
+
self.shares: dict[str, linopy.Variable] = {}
|
|
944
|
+
self.share_constraints: dict[str, linopy.Constraint] = {}
|
|
942
945
|
|
|
943
|
-
self._eq_total_per_timestep:
|
|
944
|
-
self._eq_total:
|
|
946
|
+
self._eq_total_per_timestep: linopy.Constraint | None = None
|
|
947
|
+
self._eq_total: linopy.Constraint | None = None
|
|
945
948
|
|
|
946
949
|
# Parameters
|
|
947
950
|
self._shares_are_time_series = shares_are_time_series
|
|
948
|
-
self._total_max = total_max if
|
|
951
|
+
self._total_max = total_max if total_max is not None else np.inf
|
|
949
952
|
self._total_min = total_min if total_min is not None else -np.inf
|
|
950
953
|
self._max_per_hour = max_per_hour if max_per_hour is not None else np.inf
|
|
951
954
|
self._min_per_hour = min_per_hour if min_per_hour is not None else -np.inf
|
|
@@ -1028,9 +1031,9 @@ class PiecewiseEffectsModel(Model):
|
|
|
1028
1031
|
self,
|
|
1029
1032
|
model: SystemModel,
|
|
1030
1033
|
label_of_element: str,
|
|
1031
|
-
piecewise_origin:
|
|
1032
|
-
piecewise_shares:
|
|
1033
|
-
zero_point:
|
|
1034
|
+
piecewise_origin: tuple[str, Piecewise],
|
|
1035
|
+
piecewise_shares: dict[str, Piecewise],
|
|
1036
|
+
zero_point: bool | linopy.Variable | None,
|
|
1034
1037
|
label: str = 'PiecewiseEffects',
|
|
1035
1038
|
):
|
|
1036
1039
|
super().__init__(model, label_of_element, label)
|
|
@@ -1040,9 +1043,9 @@ class PiecewiseEffectsModel(Model):
|
|
|
1040
1043
|
self._zero_point = zero_point
|
|
1041
1044
|
self._piecewise_origin = piecewise_origin
|
|
1042
1045
|
self._piecewise_shares = piecewise_shares
|
|
1043
|
-
self.shares:
|
|
1046
|
+
self.shares: dict[str, linopy.Variable] = {}
|
|
1044
1047
|
|
|
1045
|
-
self.piecewise_model:
|
|
1048
|
+
self.piecewise_model: PiecewiseModel | None = None
|
|
1046
1049
|
|
|
1047
1050
|
def do_modeling(self):
|
|
1048
1051
|
self.shares = {
|
|
@@ -1100,7 +1103,7 @@ class PreventSimultaneousUsageModel(Model):
|
|
|
1100
1103
|
def __init__(
|
|
1101
1104
|
self,
|
|
1102
1105
|
model: SystemModel,
|
|
1103
|
-
variables:
|
|
1106
|
+
variables: list[linopy.Variable],
|
|
1104
1107
|
label_of_element: str,
|
|
1105
1108
|
label: str = 'PreventSimultaneousUsage',
|
|
1106
1109
|
):
|
flixopt/flow_system.py
CHANGED
|
@@ -2,50 +2,71 @@
|
|
|
2
2
|
This module contains the FlowSystem class, which is used to collect instances of many other classes by the end User.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import json
|
|
6
8
|
import logging
|
|
7
|
-
import pathlib
|
|
8
9
|
import warnings
|
|
9
10
|
from io import StringIO
|
|
10
|
-
from typing import TYPE_CHECKING,
|
|
11
|
+
from typing import TYPE_CHECKING, Literal
|
|
11
12
|
|
|
12
|
-
import numpy as np
|
|
13
13
|
import pandas as pd
|
|
14
|
-
import xarray as xr
|
|
15
14
|
from rich.console import Console
|
|
16
15
|
from rich.pretty import Pretty
|
|
17
16
|
|
|
18
17
|
from . import io as fx_io
|
|
19
|
-
from .core import NumericData,
|
|
18
|
+
from .core import NumericData, TimeSeries, TimeSeriesCollection, TimeSeriesData
|
|
20
19
|
from .effects import Effect, EffectCollection, EffectTimeSeries, EffectValuesDict, EffectValuesUser
|
|
21
20
|
from .elements import Bus, Component, Flow
|
|
22
|
-
from .structure import CLASS_REGISTRY, Element, SystemModel
|
|
21
|
+
from .structure import CLASS_REGISTRY, Element, SystemModel
|
|
23
22
|
|
|
24
23
|
if TYPE_CHECKING:
|
|
24
|
+
import pathlib
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
25
27
|
import pyvis
|
|
28
|
+
import xarray as xr
|
|
26
29
|
|
|
27
30
|
logger = logging.getLogger('flixopt')
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
class FlowSystem:
|
|
31
34
|
"""
|
|
32
|
-
A FlowSystem organizes the high level Elements (Components & Effects).
|
|
35
|
+
A FlowSystem organizes the high level Elements (Components, Buses & Effects).
|
|
36
|
+
|
|
37
|
+
This is the main container class that users work with to build and manage their System.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
timesteps: The timesteps of the model.
|
|
41
|
+
hours_of_last_timestep: The duration of the last time step. Uses the last time interval if not specified
|
|
42
|
+
hours_of_previous_timesteps: The duration of previous timesteps.
|
|
43
|
+
If None, the first time increment of time_series is used.
|
|
44
|
+
This is needed to calculate previous durations (for example consecutive_on_hours).
|
|
45
|
+
If you use an array, take care that its long enough to cover all previous values!
|
|
46
|
+
|
|
47
|
+
Notes:
|
|
48
|
+
- Creates an empty registry for components and buses, an empty EffectCollection, and a placeholder for a SystemModel.
|
|
49
|
+
- The instance starts disconnected (self._connected == False) and will be connected automatically when trying to solve a calculation.
|
|
33
50
|
"""
|
|
34
51
|
|
|
35
52
|
def __init__(
|
|
36
53
|
self,
|
|
37
54
|
timesteps: pd.DatetimeIndex,
|
|
38
|
-
hours_of_last_timestep:
|
|
39
|
-
hours_of_previous_timesteps:
|
|
55
|
+
hours_of_last_timestep: float | None = None,
|
|
56
|
+
hours_of_previous_timesteps: int | float | np.ndarray | None = None,
|
|
40
57
|
):
|
|
41
58
|
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
Initialize a FlowSystem that manages components, buses, effects, and their time-series.
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
timesteps: DatetimeIndex defining the primary timesteps for the system's TimeSeriesCollection.
|
|
63
|
+
hours_of_last_timestep: Duration (in hours) of the final timestep; if None, inferred from timesteps or defaults in TimeSeriesCollection.
|
|
64
|
+
hours_of_previous_timesteps: Scalar or array-like durations (in hours) for the preceding timesteps; used to configure non-uniform timestep lengths.
|
|
65
|
+
|
|
66
|
+
Notes:
|
|
67
|
+
Creates an empty registry for components and buses, an empty EffectCollection, and a placeholder for a SystemModel.
|
|
68
|
+
The instance starts disconnected (self._connected == False) and with no active network visualization app.
|
|
69
|
+
This can also be triggered manually with `_connect_network()`.
|
|
49
70
|
"""
|
|
50
71
|
self.time_series_collection = TimeSeriesCollection(
|
|
51
72
|
timesteps=timesteps,
|
|
@@ -54,10 +75,10 @@ class FlowSystem:
|
|
|
54
75
|
)
|
|
55
76
|
|
|
56
77
|
# defaults:
|
|
57
|
-
self.components:
|
|
58
|
-
self.buses:
|
|
78
|
+
self.components: dict[str, Component] = {}
|
|
79
|
+
self.buses: dict[str, Bus] = {}
|
|
59
80
|
self.effects: EffectCollection = EffectCollection()
|
|
60
|
-
self.model:
|
|
81
|
+
self.model: SystemModel | None = None
|
|
61
82
|
|
|
62
83
|
self._connected = False
|
|
63
84
|
|
|
@@ -83,7 +104,7 @@ class FlowSystem:
|
|
|
83
104
|
return flow_system
|
|
84
105
|
|
|
85
106
|
@classmethod
|
|
86
|
-
def from_dict(cls, data:
|
|
107
|
+
def from_dict(cls, data: dict) -> FlowSystem:
|
|
87
108
|
"""
|
|
88
109
|
Load a FlowSystem from a dictionary.
|
|
89
110
|
|
|
@@ -112,7 +133,7 @@ class FlowSystem:
|
|
|
112
133
|
return flow_system
|
|
113
134
|
|
|
114
135
|
@classmethod
|
|
115
|
-
def from_netcdf(cls, path:
|
|
136
|
+
def from_netcdf(cls, path: str | pathlib.Path):
|
|
116
137
|
"""
|
|
117
138
|
Load a FlowSystem from a netcdf file
|
|
118
139
|
"""
|
|
@@ -144,7 +165,7 @@ class FlowSystem:
|
|
|
144
165
|
f'Tried to add incompatible object to FlowSystem: {type(new_element)=}: {new_element=} '
|
|
145
166
|
)
|
|
146
167
|
|
|
147
|
-
def to_json(self, path:
|
|
168
|
+
def to_json(self, path: str | pathlib.Path):
|
|
148
169
|
"""
|
|
149
170
|
Saves the flow system to a json file.
|
|
150
171
|
This not meant to be reloaded and recreate the object,
|
|
@@ -156,7 +177,7 @@ class FlowSystem:
|
|
|
156
177
|
with open(path, 'w', encoding='utf-8') as f:
|
|
157
178
|
json.dump(self.as_dict('stats'), f, indent=4, ensure_ascii=False)
|
|
158
179
|
|
|
159
|
-
def as_dict(self, data_mode: Literal['data', 'name', 'stats'] = 'data') ->
|
|
180
|
+
def as_dict(self, data_mode: Literal['data', 'name', 'stats'] = 'data') -> dict:
|
|
160
181
|
"""Convert the object to a dictionary representation."""
|
|
161
182
|
data = {
|
|
162
183
|
'components': {
|
|
@@ -190,7 +211,7 @@ class FlowSystem:
|
|
|
190
211
|
ds.attrs = self.as_dict(data_mode='name')
|
|
191
212
|
return ds
|
|
192
213
|
|
|
193
|
-
def to_netcdf(self, path:
|
|
214
|
+
def to_netcdf(self, path: str | pathlib.Path, compression: int = 0, constants_in_dataset: bool = True):
|
|
194
215
|
"""
|
|
195
216
|
Saves the FlowSystem to a netCDF file.
|
|
196
217
|
Args:
|
|
@@ -204,15 +225,13 @@ class FlowSystem:
|
|
|
204
225
|
|
|
205
226
|
def plot_network(
|
|
206
227
|
self,
|
|
207
|
-
path:
|
|
208
|
-
controls:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Literal['nodes', 'edges', 'layout', 'interaction', 'manipulation', 'physics', 'selection', 'renderer']
|
|
212
|
-
],
|
|
228
|
+
path: bool | str | pathlib.Path = 'flow_system.html',
|
|
229
|
+
controls: bool
|
|
230
|
+
| list[
|
|
231
|
+
Literal['nodes', 'edges', 'layout', 'interaction', 'manipulation', 'physics', 'selection', 'renderer']
|
|
213
232
|
] = True,
|
|
214
233
|
show: bool = False,
|
|
215
|
-
) ->
|
|
234
|
+
) -> pyvis.network.Network | None:
|
|
216
235
|
"""
|
|
217
236
|
Visualizes the network structure of a FlowSystem using PyVis, saving it as an interactive HTML file.
|
|
218
237
|
|
|
@@ -227,7 +246,7 @@ class FlowSystem:
|
|
|
227
246
|
show: Whether to open the visualization in the web browser.
|
|
228
247
|
|
|
229
248
|
Returns:
|
|
230
|
-
-
|
|
249
|
+
- 'pyvis.network.Network' | None: The `Network` instance representing the visualization, or `None` if `pyvis` is not installed.
|
|
231
250
|
|
|
232
251
|
Examples:
|
|
233
252
|
>>> flow_system.plot_network()
|
|
@@ -245,7 +264,7 @@ class FlowSystem:
|
|
|
245
264
|
|
|
246
265
|
def start_network_app(self):
|
|
247
266
|
"""Visualizes the network structure of a FlowSystem using Dash, Cytoscape, and networkx.
|
|
248
|
-
Requires optional dependencies: dash, dash-cytoscape, networkx, werkzeug.
|
|
267
|
+
Requires optional dependencies: dash, dash-cytoscape, dash-daq, networkx, flask, werkzeug.
|
|
249
268
|
"""
|
|
250
269
|
from .network_app import DASH_CYTOSCAPE_AVAILABLE, VISUALIZATION_ERROR, flow_graph, shownetwork
|
|
251
270
|
|
|
@@ -258,7 +277,8 @@ class FlowSystem:
|
|
|
258
277
|
if not DASH_CYTOSCAPE_AVAILABLE:
|
|
259
278
|
raise ImportError(
|
|
260
279
|
f'Network visualization requires optional dependencies. '
|
|
261
|
-
f'Install with: pip install flixopt[
|
|
280
|
+
f'Install with: `pip install flixopt[network_viz]`, `pip install flixopt[full]` '
|
|
281
|
+
f'or: `pip install dash dash-cytoscape dash-daq networkx werkzeug`. '
|
|
262
282
|
f'Original error: {VISUALIZATION_ERROR}'
|
|
263
283
|
)
|
|
264
284
|
|
|
@@ -278,7 +298,8 @@ class FlowSystem:
|
|
|
278
298
|
if not DASH_CYTOSCAPE_AVAILABLE:
|
|
279
299
|
raise ImportError(
|
|
280
300
|
f'Network visualization requires optional dependencies. '
|
|
281
|
-
f'Install with: pip install flixopt[
|
|
301
|
+
f'Install with: `pip install flixopt[network_viz]`, `pip install flixopt[full]` '
|
|
302
|
+
f'or: `pip install dash dash-cytoscape dash-daq networkx werkzeug`. '
|
|
282
303
|
f'Original error: {VISUALIZATION_ERROR}'
|
|
283
304
|
)
|
|
284
305
|
|
|
@@ -295,7 +316,7 @@ class FlowSystem:
|
|
|
295
316
|
finally:
|
|
296
317
|
self._network_app = None
|
|
297
318
|
|
|
298
|
-
def network_infos(self) ->
|
|
319
|
+
def network_infos(self) -> tuple[dict[str, dict[str, str]], dict[str, dict[str, str]]]:
|
|
299
320
|
if not self._connected:
|
|
300
321
|
self._connect_network()
|
|
301
322
|
nodes = {
|
|
@@ -328,9 +349,9 @@ class FlowSystem:
|
|
|
328
349
|
def create_time_series(
|
|
329
350
|
self,
|
|
330
351
|
name: str,
|
|
331
|
-
data:
|
|
352
|
+
data: NumericData | TimeSeriesData | TimeSeries | None,
|
|
332
353
|
needs_extra_timestep: bool = False,
|
|
333
|
-
) ->
|
|
354
|
+
) -> TimeSeries | None:
|
|
334
355
|
"""
|
|
335
356
|
Tries to create a TimeSeries from NumericData Data and adds it to the time_series_collection
|
|
336
357
|
If the data already is a TimeSeries, nothing happens and the TimeSeries gets reset and returned
|
|
@@ -353,10 +374,10 @@ class FlowSystem:
|
|
|
353
374
|
|
|
354
375
|
def create_effect_time_series(
|
|
355
376
|
self,
|
|
356
|
-
label_prefix:
|
|
377
|
+
label_prefix: str | None,
|
|
357
378
|
effect_values: EffectValuesUser,
|
|
358
|
-
label_suffix:
|
|
359
|
-
) ->
|
|
379
|
+
label_suffix: str | None = None,
|
|
380
|
+
) -> EffectTimeSeries | None:
|
|
360
381
|
"""
|
|
361
382
|
Transform EffectValues to EffectTimeSeries.
|
|
362
383
|
Creates a TimeSeries for each key in the nested_values dictionary, using the value as the data.
|
|
@@ -365,13 +386,13 @@ class FlowSystem:
|
|
|
365
386
|
followed by the label of the Effect in the nested_values and the label_suffix.
|
|
366
387
|
If the key in the EffectValues is None, the alias 'Standard_Effect' is used
|
|
367
388
|
"""
|
|
368
|
-
|
|
369
|
-
if
|
|
389
|
+
effect_values_dict: EffectValuesDict | None = self.effects.create_effect_values_dict(effect_values)
|
|
390
|
+
if effect_values_dict is None:
|
|
370
391
|
return None
|
|
371
392
|
|
|
372
393
|
return {
|
|
373
394
|
effect: self.create_time_series('|'.join(filter(None, [label_prefix, effect, label_suffix])), value)
|
|
374
|
-
for effect, value in
|
|
395
|
+
for effect, value in effect_values_dict.items()
|
|
375
396
|
}
|
|
376
397
|
|
|
377
398
|
def create_model(self) -> SystemModel:
|
|
@@ -417,14 +438,14 @@ class FlowSystem:
|
|
|
417
438
|
|
|
418
439
|
# Add Bus if not already added (deprecated)
|
|
419
440
|
if flow._bus_object is not None and flow._bus_object not in self.buses.values():
|
|
420
|
-
self._add_buses(flow._bus_object)
|
|
421
441
|
warnings.warn(
|
|
422
442
|
f'The Bus {flow._bus_object.label} was added to the FlowSystem from {flow.label_full}.'
|
|
423
443
|
f'This is deprecated and will be removed in the future. '
|
|
424
444
|
f'Please pass the Bus.label to the Flow and the Bus to the FlowSystem instead.',
|
|
425
|
-
|
|
445
|
+
DeprecationWarning,
|
|
426
446
|
stacklevel=1,
|
|
427
447
|
)
|
|
448
|
+
self._add_buses(flow._bus_object)
|
|
428
449
|
|
|
429
450
|
# Connect Buses
|
|
430
451
|
bus = self.buses.get(flow.bus)
|
|
@@ -454,10 +475,10 @@ class FlowSystem:
|
|
|
454
475
|
return value
|
|
455
476
|
|
|
456
477
|
@property
|
|
457
|
-
def flows(self) ->
|
|
478
|
+
def flows(self) -> dict[str, Flow]:
|
|
458
479
|
set_of_flows = {flow for comp in self.components.values() for flow in comp.inputs + comp.outputs}
|
|
459
480
|
return {flow.label_full: flow for flow in set_of_flows}
|
|
460
481
|
|
|
461
482
|
@property
|
|
462
|
-
def all_elements(self) ->
|
|
483
|
+
def all_elements(self) -> dict[str, Element]:
|
|
463
484
|
return {**self.components, **self.effects.effects, **self.flows, **self.buses}
|