flixopt 2.1.7__py3-none-any.whl → 2.2.0rc2__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.
- flixopt/__init__.py +1 -0
- flixopt/commons.py +10 -1
- flixopt/components.py +112 -17
- flixopt/elements.py +22 -2
- flixopt/features.py +70 -7
- flixopt/interface.py +267 -8
- {flixopt-2.1.7.dist-info → flixopt-2.2.0rc2.dist-info}/METADATA +1 -1
- {flixopt-2.1.7.dist-info → flixopt-2.2.0rc2.dist-info}/RECORD +11 -11
- {flixopt-2.1.7.dist-info → flixopt-2.2.0rc2.dist-info}/WHEEL +0 -0
- {flixopt-2.1.7.dist-info → flixopt-2.2.0rc2.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.7.dist-info → flixopt-2.2.0rc2.dist-info}/top_level.txt +0 -0
flixopt/__init__.py
CHANGED
flixopt/commons.py
CHANGED
|
@@ -18,7 +18,15 @@ from .core import TimeSeriesData
|
|
|
18
18
|
from .effects import Effect
|
|
19
19
|
from .elements import Bus, Flow
|
|
20
20
|
from .flow_system import FlowSystem
|
|
21
|
-
from .interface import
|
|
21
|
+
from .interface import (
|
|
22
|
+
InvestParameters,
|
|
23
|
+
OnOffParameters,
|
|
24
|
+
Piece,
|
|
25
|
+
Piecewise,
|
|
26
|
+
PiecewiseConversion,
|
|
27
|
+
PiecewiseEffects,
|
|
28
|
+
PiecewiseEffectsPerFlowHour,
|
|
29
|
+
)
|
|
22
30
|
|
|
23
31
|
__all__ = [
|
|
24
32
|
'TimeSeriesData',
|
|
@@ -48,4 +56,5 @@ __all__ = [
|
|
|
48
56
|
'results',
|
|
49
57
|
'linear_converters',
|
|
50
58
|
'solvers',
|
|
59
|
+
'PiecewiseEffectsPerFlowHour',
|
|
51
60
|
]
|
flixopt/components.py
CHANGED
|
@@ -24,9 +24,119 @@ logger = logging.getLogger('flixopt')
|
|
|
24
24
|
|
|
25
25
|
@register_class_for_io
|
|
26
26
|
class LinearConverter(Component):
|
|
27
|
-
"""
|
|
28
|
-
|
|
27
|
+
"""Convert input flows into output flows using linear or piecewise linear conversion factors.
|
|
28
|
+
|
|
29
|
+
This component models conversion equipment where input flows are transformed
|
|
30
|
+
into output flows with fixed or variable conversion ratios, such as:
|
|
31
|
+
|
|
32
|
+
- Heat pumps and chillers with variable efficiency
|
|
33
|
+
- Power plants with fuel-to-electricity conversion
|
|
34
|
+
- Chemical processes with multiple inputs/outputs
|
|
35
|
+
- Pumps and compressors
|
|
36
|
+
- Combined heat and power (CHP) plants
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
label: Unique identifier for the component in the FlowSystem.
|
|
40
|
+
inputs: List of input Flow objects that feed into the converter.
|
|
41
|
+
outputs: List of output Flow objects produced by the converter.
|
|
42
|
+
on_off_parameters: Controls binary on/off behavior of the converter.
|
|
43
|
+
When specified, the component can be completely turned on or off, affecting
|
|
44
|
+
all connected flows. This creates binary variables in the optimization.
|
|
45
|
+
For better performance, consider using OnOffParameters on individual flows instead.
|
|
46
|
+
conversion_factors: Linear conversion ratios between flows as time series data.
|
|
47
|
+
List of dictionaries mapping flow labels to their conversion factors.
|
|
48
|
+
Mutually exclusive with piecewise_conversion.
|
|
49
|
+
piecewise_conversion: Piecewise linear conversion relationships between flows.
|
|
50
|
+
Enables modeling of variable efficiency or discrete operating modes.
|
|
51
|
+
Mutually exclusive with conversion_factors.
|
|
52
|
+
meta_data: Additional information stored with the component.
|
|
53
|
+
Saved in results but not used internally. Use only Python native types.
|
|
54
|
+
|
|
55
|
+
Warning:
|
|
56
|
+
When using `piecewise_conversion` without `on_off_parameters`, flow rates cannot
|
|
57
|
+
reach zero unless explicitly defined with zero-valued pieces (e.g., `fx.Piece(0, 0)`).
|
|
58
|
+
This prevents unintended zero flows and maintains mathematical consistency.
|
|
59
|
+
|
|
60
|
+
To allow zero flow rates:
|
|
61
|
+
|
|
62
|
+
- Add `on_off_parameters` to enable complete shutdown, or
|
|
63
|
+
- Include explicit zero pieces in your `piecewise_conversion` definition
|
|
64
|
+
|
|
65
|
+
This behavior was clarified in v2.1.7 to prevent optimization edge cases.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
Simple heat pump with fixed COP:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
heat_pump = fx.LinearConverter(
|
|
72
|
+
label='heat_pump',
|
|
73
|
+
inputs=[electricity_flow],
|
|
74
|
+
outputs=[heat_flow],
|
|
75
|
+
conversion_factors=[
|
|
76
|
+
{
|
|
77
|
+
'electricity_flow': 1.0, # 1 kW electricity input
|
|
78
|
+
'heat_flow': 3.5, # 3.5 kW heat output (COP=3.5)
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Variable efficiency heat pump:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
heat_pump = fx.LinearConverter(
|
|
88
|
+
label='variable_heat_pump',
|
|
89
|
+
inputs=[electricity_flow],
|
|
90
|
+
outputs=[heat_flow],
|
|
91
|
+
piecewise_conversion=fx.PiecewiseConversion(
|
|
92
|
+
{
|
|
93
|
+
'electricity_flow': fx.Piecewise(
|
|
94
|
+
[
|
|
95
|
+
fx.Piece(0, 10), # Allow zero to 10 kW input
|
|
96
|
+
fx.Piece(10, 25), # Higher load operation
|
|
97
|
+
]
|
|
98
|
+
),
|
|
99
|
+
'heat_flow': fx.Piecewise(
|
|
100
|
+
[
|
|
101
|
+
fx.Piece(0, 35), # COP=3.5 at low loads
|
|
102
|
+
fx.Piece(35, 75), # COP=3.0 at high loads
|
|
103
|
+
]
|
|
104
|
+
),
|
|
105
|
+
}
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Combined heat and power plant:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
chp_plant = fx.LinearConverter(
|
|
114
|
+
label='chp_plant',
|
|
115
|
+
inputs=[natural_gas_flow],
|
|
116
|
+
outputs=[electricity_flow, heat_flow],
|
|
117
|
+
conversion_factors=[
|
|
118
|
+
{
|
|
119
|
+
'natural_gas_flow': 1.0, # 1 MW fuel input
|
|
120
|
+
'electricity_flow': 0.4, # 40% electrical efficiency
|
|
121
|
+
'heat_flow': 0.45, # 45% thermal efficiency
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
on_off_parameters=fx.OnOffParameters(
|
|
125
|
+
min_on_hours=4, # Minimum 4-hour operation
|
|
126
|
+
min_off_hours=2, # Minimum 2-hour downtime
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Note:
|
|
132
|
+
Either `conversion_factors` or `piecewise_conversion` must be specified, but not both.
|
|
133
|
+
The component automatically handles the mathematical relationships between all
|
|
134
|
+
connected flows according to the specified conversion ratios.
|
|
29
135
|
|
|
136
|
+
See Also:
|
|
137
|
+
PiecewiseConversion: For variable efficiency modeling
|
|
138
|
+
OnOffParameters: For binary on/off control
|
|
139
|
+
Flow: Input and output flow definitions
|
|
30
140
|
"""
|
|
31
141
|
|
|
32
142
|
def __init__(
|
|
@@ -39,21 +149,6 @@ class LinearConverter(Component):
|
|
|
39
149
|
piecewise_conversion: Optional[PiecewiseConversion] = None,
|
|
40
150
|
meta_data: Optional[Dict] = None,
|
|
41
151
|
):
|
|
42
|
-
"""
|
|
43
|
-
Args:
|
|
44
|
-
label: The label of the Element. Used to identify it in the FlowSystem
|
|
45
|
-
inputs: The input Flows
|
|
46
|
-
outputs: The output Flows
|
|
47
|
-
on_off_parameters: Information about on and off state of LinearConverter.
|
|
48
|
-
Component is On/Off, if all connected Flows are On/Off. This induces an On-Variable (binary) in all Flows!
|
|
49
|
-
If possible, use OnOffParameters in a single Flow instead to keep the number of binary variables low.
|
|
50
|
-
See class OnOffParameters.
|
|
51
|
-
conversion_factors: linear relation between flows.
|
|
52
|
-
Either 'conversion_factors' or 'piecewise_conversion' can be used!
|
|
53
|
-
piecewise_conversion: Define a piecewise linear relation between flow rates of different flows.
|
|
54
|
-
Either 'conversion_factors' or 'piecewise_conversion' can be used!
|
|
55
|
-
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
56
|
-
"""
|
|
57
152
|
super().__init__(label, inputs, outputs, on_off_parameters, meta_data=meta_data)
|
|
58
153
|
self.conversion_factors = conversion_factors or []
|
|
59
154
|
self.piecewise_conversion = piecewise_conversion
|
flixopt/elements.py
CHANGED
|
@@ -12,8 +12,8 @@ import numpy as np
|
|
|
12
12
|
from .config import CONFIG
|
|
13
13
|
from .core import NumericData, NumericDataTS, PlausibilityError, Scalar, TimeSeriesCollection
|
|
14
14
|
from .effects import EffectValuesUser
|
|
15
|
-
from .features import InvestmentModel, OnOffModel, PreventSimultaneousUsageModel
|
|
16
|
-
from .interface import InvestParameters, OnOffParameters
|
|
15
|
+
from .features import InvestmentModel, OnOffModel, PiecewiseEffectsPerFlowHourModel, PreventSimultaneousUsageModel
|
|
16
|
+
from .interface import InvestParameters, OnOffParameters, PiecewiseEffectsPerFlowHour
|
|
17
17
|
from .structure import Element, ElementModel, SystemModel, register_class_for_io
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
@@ -159,6 +159,7 @@ class Flow(Element):
|
|
|
159
159
|
relative_minimum: NumericDataTS = 0,
|
|
160
160
|
relative_maximum: NumericDataTS = 1,
|
|
161
161
|
effects_per_flow_hour: Optional[EffectValuesUser] = None,
|
|
162
|
+
piecewise_effects_per_flow_hour: Optional[PiecewiseEffectsPerFlowHour] = None,
|
|
162
163
|
on_off_parameters: Optional[OnOffParameters] = None,
|
|
163
164
|
flow_hours_total_max: Optional[Scalar] = None,
|
|
164
165
|
flow_hours_total_min: Optional[Scalar] = None,
|
|
@@ -180,6 +181,7 @@ class Flow(Element):
|
|
|
180
181
|
def: :math:`load\_factor:= sumFlowHours/ (nominal\_val \cdot \Delta t_{tot})`
|
|
181
182
|
load_factor_max: maximal load factor (see minimal load factor)
|
|
182
183
|
effects_per_flow_hour: operational costs, costs per flow-"work"
|
|
184
|
+
piecewise_effects_per_flow_hour: piecewise relation between flow hours and effects
|
|
183
185
|
on_off_parameters: If present, flow can be "off", i.e. be zero (only relevant if relative_minimum > 0)
|
|
184
186
|
Therefore a binary var "on" is used. Further, several other restrictions and effects can be modeled
|
|
185
187
|
through this On/Off State (See OnOffParameters)
|
|
@@ -207,6 +209,7 @@ class Flow(Element):
|
|
|
207
209
|
self.load_factor_max = load_factor_max
|
|
208
210
|
# self.positive_gradient = TimeSeries('positive_gradient', positive_gradient, self)
|
|
209
211
|
self.effects_per_flow_hour = effects_per_flow_hour if effects_per_flow_hour is not None else {}
|
|
212
|
+
self.piecewise_effects_per_flow_hour = piecewise_effects_per_flow_hour
|
|
210
213
|
self.flow_hours_total_max = flow_hours_total_max
|
|
211
214
|
self.flow_hours_total_min = flow_hours_total_min
|
|
212
215
|
self.on_off_parameters = on_off_parameters
|
|
@@ -248,6 +251,8 @@ class Flow(Element):
|
|
|
248
251
|
self.effects_per_flow_hour = flow_system.create_effect_time_series(
|
|
249
252
|
self.label_full, self.effects_per_flow_hour, 'per_flow_hour'
|
|
250
253
|
)
|
|
254
|
+
if self.piecewise_effects_per_flow_hour is not None:
|
|
255
|
+
self.piecewise_effects_per_flow_hour.transform_data(flow_system, self.label_full)
|
|
251
256
|
if self.on_off_parameters is not None:
|
|
252
257
|
self.on_off_parameters.transform_data(flow_system, self.label_full)
|
|
253
258
|
if isinstance(self.size, InvestParameters):
|
|
@@ -398,6 +403,21 @@ class FlowModel(ElementModel):
|
|
|
398
403
|
target='operation',
|
|
399
404
|
)
|
|
400
405
|
|
|
406
|
+
if self.element.piecewise_effects_per_flow_hour is not None:
|
|
407
|
+
self.piecewise_effects = self.add(
|
|
408
|
+
PiecewiseEffectsPerFlowHourModel(
|
|
409
|
+
model=self._model,
|
|
410
|
+
label_of_element=self.label_of_element,
|
|
411
|
+
piecewise_origin=(
|
|
412
|
+
self.flow_rate.name,
|
|
413
|
+
self.element.piecewise_effects_per_flow_hour.piecewise_flow_rate,
|
|
414
|
+
),
|
|
415
|
+
piecewise_shares=self.element.piecewise_effects_per_flow_hour.piecewise_shares,
|
|
416
|
+
zero_point=self.on_off.on if self.on_off is not None else False,
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
self.piecewise_effects.do_modeling()
|
|
420
|
+
|
|
401
421
|
def _create_bounds_for_load_factor(self):
|
|
402
422
|
# TODO: Add Variable load_factor for better evaluation?
|
|
403
423
|
|
flixopt/features.py
CHANGED
|
@@ -12,7 +12,7 @@ import numpy as np
|
|
|
12
12
|
from . import utils
|
|
13
13
|
from .config import CONFIG
|
|
14
14
|
from .core import NumericData, Scalar, TimeSeries
|
|
15
|
-
from .interface import InvestParameters, OnOffParameters, Piecewise
|
|
15
|
+
from .interface import InvestParameters, OnOffParameters, Piece, Piecewise
|
|
16
16
|
from .structure import Model, SystemModel
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger('flixopt')
|
|
@@ -841,7 +841,7 @@ class PiecewiseModel(Model):
|
|
|
841
841
|
label: str = '',
|
|
842
842
|
):
|
|
843
843
|
"""
|
|
844
|
-
Modeling a Piecewise relation between
|
|
844
|
+
Modeling a Piecewise relation between multiple variables.
|
|
845
845
|
The relation is defined by a list of Pieces, which are assigned to the variables.
|
|
846
846
|
Each Piece is a tuple of (start, end).
|
|
847
847
|
|
|
@@ -850,7 +850,9 @@ class PiecewiseModel(Model):
|
|
|
850
850
|
label_of_element: The label of the parent (Element). Used to construct the full label of the model.
|
|
851
851
|
label: The label of the model. Used to construct the full label of the model.
|
|
852
852
|
piecewise_variables: The variables to which the Pieces are assigned.
|
|
853
|
-
zero_point: A variable that can be used to define a zero point for the Piecewise relation.
|
|
853
|
+
zero_point: A variable that can be used to define a zero point for the Piecewise relation.
|
|
854
|
+
If None or False, no zero point is defined. THis leads to 0 not being possible,
|
|
855
|
+
unless its its contained in a Piece.
|
|
854
856
|
as_time_series: Whether the Piecewise relation is defined for a TimeSeries or a single variable.
|
|
855
857
|
"""
|
|
856
858
|
super().__init__(model, label_of_element, label)
|
|
@@ -896,7 +898,7 @@ class PiecewiseModel(Model):
|
|
|
896
898
|
# b) eq: -On(t) + Segment1.onSeg(t) + Segment2.onSeg(t) + ... = 0 zusätzlich kann alles auch Null sein
|
|
897
899
|
if isinstance(self._zero_point, linopy.Variable):
|
|
898
900
|
self.zero_point = self._zero_point
|
|
899
|
-
rhs = self.zero_point
|
|
901
|
+
sign, rhs = '<=', self.zero_point
|
|
900
902
|
elif self._zero_point is True:
|
|
901
903
|
self.zero_point = self.add(
|
|
902
904
|
self._model.add_variables(
|
|
@@ -904,13 +906,15 @@ class PiecewiseModel(Model):
|
|
|
904
906
|
),
|
|
905
907
|
'zero_point',
|
|
906
908
|
)
|
|
907
|
-
rhs = self.zero_point
|
|
909
|
+
sign, rhs = '<=', self.zero_point
|
|
908
910
|
else:
|
|
909
|
-
rhs = 1
|
|
911
|
+
sign, rhs = '=', 1
|
|
910
912
|
|
|
911
913
|
self.add(
|
|
912
914
|
self._model.add_constraints(
|
|
913
|
-
sum([piece.inside_piece for piece in self.pieces])
|
|
915
|
+
sum([piece.inside_piece for piece in self.pieces]),
|
|
916
|
+
sign,
|
|
917
|
+
rhs,
|
|
914
918
|
name=f'{self.label_full}|{variable.name}|single_segment',
|
|
915
919
|
),
|
|
916
920
|
f'{var_name}|single_segment',
|
|
@@ -1079,6 +1083,65 @@ class PiecewiseEffectsModel(Model):
|
|
|
1079
1083
|
)
|
|
1080
1084
|
|
|
1081
1085
|
|
|
1086
|
+
class PiecewiseEffectsPerFlowHourModel(Model):
|
|
1087
|
+
def __init__(
|
|
1088
|
+
self,
|
|
1089
|
+
model: SystemModel,
|
|
1090
|
+
label_of_element: str,
|
|
1091
|
+
piecewise_origin: Tuple[str, Piecewise],
|
|
1092
|
+
piecewise_shares: Dict[str, Piecewise],
|
|
1093
|
+
zero_point: Optional[Union[bool, linopy.Variable]],
|
|
1094
|
+
label: str = 'PiecewiseEffectsPerFlowHour',
|
|
1095
|
+
):
|
|
1096
|
+
super().__init__(model, label_of_element, label)
|
|
1097
|
+
assert len(piecewise_origin[1]) == len(list(piecewise_shares.values())[0]), (
|
|
1098
|
+
'Piece length of variable_segments and share_segments must be equal'
|
|
1099
|
+
)
|
|
1100
|
+
self._zero_point = zero_point
|
|
1101
|
+
self._piecewise_origin = piecewise_origin
|
|
1102
|
+
self._piecewise_shares = piecewise_shares
|
|
1103
|
+
|
|
1104
|
+
self.shares: Dict[str, linopy.Variable] = {}
|
|
1105
|
+
|
|
1106
|
+
self.piecewise_model: Optional[PiecewiseModel] = None
|
|
1107
|
+
|
|
1108
|
+
def do_modeling(self):
|
|
1109
|
+
self.shares = {
|
|
1110
|
+
effect: self.add(
|
|
1111
|
+
self._model.add_variables(coords=self._model.coords, name=f'{self.label_full}|{effect}'), f'{effect}'
|
|
1112
|
+
)
|
|
1113
|
+
for effect in self._piecewise_shares
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
piecewise_variables = {
|
|
1117
|
+
self._piecewise_origin[0]: self._piecewise_origin[1],
|
|
1118
|
+
**{
|
|
1119
|
+
self.shares[effect_label].name: self._piecewise_shares[effect_label]
|
|
1120
|
+
for effect_label in self._piecewise_shares
|
|
1121
|
+
},
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
self.piecewise_model = self.add(
|
|
1125
|
+
PiecewiseModel(
|
|
1126
|
+
model=self._model,
|
|
1127
|
+
label_of_element=self.label_of_element,
|
|
1128
|
+
piecewise_variables=piecewise_variables,
|
|
1129
|
+
zero_point=self._zero_point,
|
|
1130
|
+
as_time_series=True,
|
|
1131
|
+
label='PiecewiseEffectsPerFlowHour',
|
|
1132
|
+
)
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
self.piecewise_model.do_modeling()
|
|
1136
|
+
|
|
1137
|
+
# Shares
|
|
1138
|
+
self._model.effects.add_share_to_effects(
|
|
1139
|
+
name=self.label_of_element,
|
|
1140
|
+
expressions={effect: variable * self._model.hours_per_step for effect, variable in self.shares.items()},
|
|
1141
|
+
target='operation',
|
|
1142
|
+
)
|
|
1143
|
+
|
|
1144
|
+
|
|
1082
1145
|
class PreventSimultaneousUsageModel(Model):
|
|
1083
1146
|
"""
|
|
1084
1147
|
Prevents multiple Multiple Binary variables from being 1 at the same time
|
flixopt/interface.py
CHANGED
|
@@ -63,15 +63,168 @@ class Piecewise(Interface):
|
|
|
63
63
|
|
|
64
64
|
@register_class_for_io
|
|
65
65
|
class PiecewiseConversion(Interface):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
"""Define piecewise linear conversion relationships between multiple flows.
|
|
67
|
+
|
|
68
|
+
This class models complex conversion processes where the relationship between
|
|
69
|
+
input and output flows changes at different operating points, such as:
|
|
70
|
+
|
|
71
|
+
- Variable efficiency equipment (heat pumps, engines, turbines)
|
|
72
|
+
- Multi-stage chemical processes with different conversion rates
|
|
73
|
+
- Equipment with discrete operating modes
|
|
74
|
+
- Systems with capacity constraints and thresholds
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
piecewises: Dictionary mapping flow labels to their Piecewise conversion functions.
|
|
78
|
+
Keys are flow names (e.g., 'electricity_in', 'heat_out', 'fuel_consumed').
|
|
79
|
+
Values are Piecewise objects defining conversion factors at different operating points.
|
|
80
|
+
All Piecewise objects must have the same number of pieces and compatible domains
|
|
81
|
+
to ensure consistent conversion relationships across operating ranges.
|
|
82
|
+
|
|
83
|
+
Note:
|
|
84
|
+
Special modeling features:
|
|
85
|
+
|
|
86
|
+
- **Gaps**: Express forbidden operating ranges by creating non-contiguous pieces.
|
|
87
|
+
Example: `[(0,50), (100,200)]` - cannot operate between 50-100 units
|
|
88
|
+
- **Points**: Express discrete operating points using pieces with identical start/end.
|
|
89
|
+
Example: `[(50,50), (100,100)]` - can only operate at exactly 50 or 100 units
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
Heat pump with variable COP (Coefficient of Performance):
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
PiecewiseConversion(
|
|
96
|
+
{
|
|
97
|
+
'electricity_in': Piecewise(
|
|
98
|
+
[
|
|
99
|
+
Piece(0, 10), # Low load: 0-10 kW electricity
|
|
100
|
+
Piece(10, 25), # High load: 10-25 kW electricity
|
|
101
|
+
]
|
|
102
|
+
),
|
|
103
|
+
'heat_out': Piecewise(
|
|
104
|
+
[
|
|
105
|
+
Piece(0, 35), # Low load COP=3.5: 0-35 kW heat output
|
|
106
|
+
Piece(35, 75), # High load COP=3.0: 35-75 kW heat output
|
|
107
|
+
]
|
|
108
|
+
),
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
# At 15 kW electricity input → 52.5 kW heat output (interpolated)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Engine with fuel consumption and emissions:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
PiecewiseConversion(
|
|
118
|
+
{
|
|
119
|
+
'fuel_input': Piecewise(
|
|
120
|
+
[
|
|
121
|
+
Piece(5, 15), # Part load: 5-15 L/h fuel
|
|
122
|
+
Piece(15, 30), # Full load: 15-30 L/h fuel
|
|
123
|
+
]
|
|
124
|
+
),
|
|
125
|
+
'power_output': Piecewise(
|
|
126
|
+
[
|
|
127
|
+
Piece(10, 25), # Part load: 10-25 kW output
|
|
128
|
+
Piece(25, 45), # Full load: 25-45 kW output
|
|
129
|
+
]
|
|
130
|
+
),
|
|
131
|
+
'co2_emissions': Piecewise(
|
|
132
|
+
[
|
|
133
|
+
Piece(12, 35), # Part load: 12-35 kg/h CO2
|
|
134
|
+
Piece(35, 78), # Full load: 35-78 kg/h CO2
|
|
135
|
+
]
|
|
136
|
+
),
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
```
|
|
71
140
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
141
|
+
Discrete operating modes (on/off equipment):
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
PiecewiseConversion(
|
|
145
|
+
{
|
|
146
|
+
'electricity_in': Piecewise(
|
|
147
|
+
[
|
|
148
|
+
Piece(0, 0), # Off mode: no consumption
|
|
149
|
+
Piece(20, 20), # On mode: fixed 20 kW consumption
|
|
150
|
+
]
|
|
151
|
+
),
|
|
152
|
+
'cooling_out': Piecewise(
|
|
153
|
+
[
|
|
154
|
+
Piece(0, 0), # Off mode: no cooling
|
|
155
|
+
Piece(60, 60), # On mode: fixed 60 kW cooling
|
|
156
|
+
]
|
|
157
|
+
),
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Equipment with forbidden operating range:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
PiecewiseConversion(
|
|
166
|
+
{
|
|
167
|
+
'steam_input': Piecewise(
|
|
168
|
+
[
|
|
169
|
+
Piece(0, 100), # Low pressure operation
|
|
170
|
+
Piece(200, 500), # High pressure (gap: 100-200)
|
|
171
|
+
]
|
|
172
|
+
),
|
|
173
|
+
'power_output': Piecewise(
|
|
174
|
+
[
|
|
175
|
+
Piece(0, 80), # Low efficiency at low pressure
|
|
176
|
+
Piece(180, 400), # High efficiency at high pressure
|
|
177
|
+
]
|
|
178
|
+
),
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Multi-product chemical reactor:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
fx.PiecewiseConversion(
|
|
187
|
+
{
|
|
188
|
+
'feedstock': fx.Piecewise(
|
|
189
|
+
[
|
|
190
|
+
fx.Piece(10, 50), # Small batch: 10-50 kg/h
|
|
191
|
+
fx.Piece(50, 200), # Large batch: 50-200 kg/h
|
|
192
|
+
]
|
|
193
|
+
),
|
|
194
|
+
'product_A': fx.Piecewise(
|
|
195
|
+
[
|
|
196
|
+
fx.Piece(7, 32), # Small batch yield: 70%
|
|
197
|
+
fx.Piece(32, 140), # Large batch yield: 70%
|
|
198
|
+
]
|
|
199
|
+
),
|
|
200
|
+
'product_B': fx.Piecewise(
|
|
201
|
+
[
|
|
202
|
+
fx.Piece(2, 12), # Small batch: 20% to product B
|
|
203
|
+
fx.Piece(12, 45), # Large batch: better selectivity
|
|
204
|
+
]
|
|
205
|
+
),
|
|
206
|
+
'waste': fx.Piecewise(
|
|
207
|
+
[
|
|
208
|
+
fx.Piece(1, 6), # Small batch waste: 10%
|
|
209
|
+
fx.Piece(6, 15), # Large batch waste: 7.5%
|
|
210
|
+
]
|
|
211
|
+
),
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Common Use Cases:
|
|
217
|
+
- Heat pumps/chillers: COP varies with load and ambient conditions
|
|
218
|
+
- Power plants: Heat rate curves showing fuel efficiency vs output
|
|
219
|
+
- Chemical reactors: Conversion rates and selectivity vs throughput
|
|
220
|
+
- Compressors/pumps: Power consumption vs flow rate
|
|
221
|
+
- Multi-stage processes: Different conversion rates per stage
|
|
222
|
+
- Equipment with minimum loads: Cannot operate below threshold
|
|
223
|
+
- Batch processes: Discrete production campaigns
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def __init__(self, piecewises: Dict[str, Piecewise]):
|
|
75
228
|
self.piecewises = piecewises
|
|
76
229
|
|
|
77
230
|
def items(self):
|
|
@@ -102,6 +255,112 @@ class PiecewiseEffects(Interface):
|
|
|
102
255
|
# piecewise.transform_data(flow_system, f'{name_prefix}|PiecewiseEffects|{name}')
|
|
103
256
|
|
|
104
257
|
|
|
258
|
+
@register_class_for_io
|
|
259
|
+
class PiecewiseEffectsPerFlowHour(Interface):
|
|
260
|
+
"""
|
|
261
|
+
Define piecewise linear relationships between flow rate and various effects (costs, emissions, etc.).
|
|
262
|
+
|
|
263
|
+
This class models situations where the relationship between flow rate and effects changes at
|
|
264
|
+
different flow rate levels, such as:
|
|
265
|
+
- Pump efficiency curves across operating ranges
|
|
266
|
+
- Emission factors that vary with operating levels
|
|
267
|
+
- Capacity-dependent transportation costs
|
|
268
|
+
- Decision between different operating modes or suppliers
|
|
269
|
+
- Optional equipment activation with minimum flow requirements
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
piecewise_flow_rate: `Piecewise` defining the valid flow rate segments.
|
|
273
|
+
Each Piece represents a linear segment with (min_flow, max_flow) bounds.
|
|
274
|
+
|
|
275
|
+
piecewise_shares: Dictionary mapping effect names to their `Piecewise`.
|
|
276
|
+
Keys are effect names (e.g., 'Costs', 'CO2', 'Maintenance').
|
|
277
|
+
Values are `Piecewise` objects defining the absolute effect values (not rates/prices).
|
|
278
|
+
|
|
279
|
+
⚠️ IMPORTANT: Values represent total effect amounts, not unit rates.
|
|
280
|
+
For a flow rate of X, the effect value is interpolated from the `Piecewise`.
|
|
281
|
+
This is NOT flow_rate × unit_price (which would be non-linear).
|
|
282
|
+
|
|
283
|
+
Behavior:
|
|
284
|
+
- If the first piece doesn't start at zero, flow rate is automatically bounded
|
|
285
|
+
by piecewise_flow_rate (when OnOffParameters are not used)
|
|
286
|
+
- Each segment represents a linear relationship within that flow rate range
|
|
287
|
+
- Effects are interpolated linearly within each piece
|
|
288
|
+
- All `Piece`s of the different `Piecewise`s at index i are active at the same time
|
|
289
|
+
- A decision whether to utilize the effect can be modeled by defining multiple Pieces for the same flow rate range
|
|
290
|
+
|
|
291
|
+
Examples:
|
|
292
|
+
# Tiered cost structure with increasing rates
|
|
293
|
+
PiecewiseEffectsPerFlowHour(
|
|
294
|
+
piecewise_flow_rate=Piecewise([
|
|
295
|
+
Piece(0, 50), # Low flow segment: 0-50 units
|
|
296
|
+
Piece(50, 200) # High flow segment: 50-200 units
|
|
297
|
+
]),
|
|
298
|
+
piecewise_shares={
|
|
299
|
+
'Costs': Piecewise([
|
|
300
|
+
Piece(0, 500), # At flow=0: cost=0, at flow=50: cost=500
|
|
301
|
+
Piece(500, 2000) # At flow=50: cost=500, at flow=200: cost=2000
|
|
302
|
+
]),
|
|
303
|
+
'CO2': Piecewise([
|
|
304
|
+
Piece(0, 100), # At flow=0: CO2=0, at flow=50: CO2=100
|
|
305
|
+
Piece(100, 800) # At flow=50: CO2=100, at flow=200: CO2=800
|
|
306
|
+
])
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Decision between two suppliers with overlapping flow ranges
|
|
311
|
+
PiecewiseEffectsPerFlowHour(
|
|
312
|
+
piecewise_flow_rate=Piecewise([
|
|
313
|
+
Piece(0, 100), # Supplier A: 0-100 units
|
|
314
|
+
Piece(50, 150) # Supplier B: 50-150 units (overlaps with A)
|
|
315
|
+
]),
|
|
316
|
+
piecewise_shares={
|
|
317
|
+
'Costs': Piecewise([
|
|
318
|
+
Piece(0, 800), # Supplier A: cheaper for low volumes
|
|
319
|
+
Piece(400, 1200) # Supplier B: better rates for high volumes
|
|
320
|
+
])
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
# Flow range 50-100: Optimizer chooses between suppliers based on cost
|
|
324
|
+
|
|
325
|
+
# Optional equipment with minimum activation threshold
|
|
326
|
+
PiecewiseEffectsPerFlowHour(
|
|
327
|
+
piecewise_flow_rate=Piecewise([
|
|
328
|
+
Piece(0, 0), # Equipment off: no flow
|
|
329
|
+
Piece(20, 100) # Equipment on: minimum 20 units required
|
|
330
|
+
]),
|
|
331
|
+
piecewise_shares={
|
|
332
|
+
'Costs': Piecewise([
|
|
333
|
+
Piece(0, 0), # No cost when off
|
|
334
|
+
Piece(200, 800) # Fixed startup cost + variable cost
|
|
335
|
+
]),
|
|
336
|
+
'CO2': Piecewise([
|
|
337
|
+
Piece(0, 0), # No CO2 when off
|
|
338
|
+
Piece(50, 300) # Decreasing CO2 per fuel burn with higher power
|
|
339
|
+
])
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
# Decision: Either flow=0 (off) or flow≥20 (on with minimum threshold)
|
|
343
|
+
|
|
344
|
+
# Equipment efficiency curve (although this might be better modeled as a Flow rather than an effect)
|
|
345
|
+
PiecewiseEffectsPerFlowHour(
|
|
346
|
+
piecewise_flow_rate=Piecewise([Piece(10, 100)]), # Min 10, max 100 units
|
|
347
|
+
piecewise_shares={
|
|
348
|
+
'PowerConsumption': Piecewise([Piece(50, 800)]) # Non-linear efficiency
|
|
349
|
+
}
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
def __init__(self, piecewise_flow_rate: Piecewise, piecewise_shares: Dict[str, Piecewise]):
|
|
355
|
+
self.piecewise_flow_rate = piecewise_flow_rate
|
|
356
|
+
self.piecewise_shares = piecewise_shares
|
|
357
|
+
|
|
358
|
+
def transform_data(self, flow_system: 'FlowSystem', name_prefix: str):
|
|
359
|
+
self.piecewise_flow_rate.transform_data(flow_system, f'{name_prefix}|PiecewiseEffectsPerFlowHour|origin')
|
|
360
|
+
for name, piecewise in self.piecewise_shares.items():
|
|
361
|
+
piecewise.transform_data(flow_system, f'{name_prefix}|PiecewiseEffectsPerFlowHour|{name}')
|
|
362
|
+
|
|
363
|
+
|
|
105
364
|
@register_class_for_io
|
|
106
365
|
class InvestParameters(Interface):
|
|
107
366
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flixopt
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0rc2
|
|
4
4
|
Summary: Vector based energy and material flow optimization framework in Python.
|
|
5
5
|
Author-email: "Chair of Building Energy Systems and Heat Supply, TU Dresden" <peter.stange@tu-dresden.de>, Felix Bumann <felixbumann387@gmail.com>, Felix Panitz <baumbude@googlemail.com>, Peter Stange <peter.stange@tu-dresden.de>
|
|
6
6
|
Maintainer-email: Felix Bumann <felixbumann387@gmail.com>, Peter Stange <peter.stange@tu-dresden.de>
|
|
@@ -18,19 +18,19 @@ docs/user-guide/Mathematical Notation/Piecewise.md,sha256=GgTQ7dVTTb0lq14GFuuZeG
|
|
|
18
18
|
docs/user-guide/Mathematical Notation/Storage.md,sha256=Zk5Irnr4bxIqI2KL2wcbAg8PdFDGTxhDFlRrnvjK6Bk,2200
|
|
19
19
|
docs/user-guide/Mathematical Notation/index.md,sha256=2d6k4zbKET1kg7zBt1fEdKsG8jM5j2IIe6yclMTUlDw,1254
|
|
20
20
|
docs/user-guide/Mathematical Notation/others.md,sha256=z6LOzcnvfI-1qQx0Fg7Q6wSK9tAzH2d34KbW4lYNyCE,48
|
|
21
|
-
flixopt/__init__.py,sha256=
|
|
21
|
+
flixopt/__init__.py,sha256=yW3zslmBnNDYoYTGUq1XkBL7UqpP9O1iDV1d3V00iyM,721
|
|
22
22
|
flixopt/aggregation.py,sha256=xgQu2U5YEbtdDAEMjWiuP9uo_KjhzC95VNmY4ZcSX3I,16939
|
|
23
23
|
flixopt/calculation.py,sha256=pj8nvAG3Vv6NHNyvhU3YbrMDogClFni5CfuJk4631Fw,19914
|
|
24
|
-
flixopt/commons.py,sha256=
|
|
25
|
-
flixopt/components.py,sha256=
|
|
24
|
+
flixopt/commons.py,sha256=6exWKBgcWCUjcqolw1kG6Aee4bm_6DF1kTrSUzZbs1M,1353
|
|
25
|
+
flixopt/components.py,sha256=N5_xmhFtOjCJnNl-o_V12lvUxp5tUyuGQRgkUVaNnuk,36868
|
|
26
26
|
flixopt/config.py,sha256=Kt8QYk7hX5qHcQUtfgjM862C6SQr4K2lDvtk_LLER8Y,9085
|
|
27
27
|
flixopt/config.yaml,sha256=imzAnnhcJhIfKNTTXFB5Td7Pvk5ARn5j720k-oGGRug,392
|
|
28
28
|
flixopt/core.py,sha256=vaIBRQE0w9Ro5Xe_jWHKXMLRenSS2P1Tpy9-geTuWcE,38103
|
|
29
29
|
flixopt/effects.py,sha256=7kwZwfv3oRmkzc30kdjeOyB4qnyA9zxsPun1ysQdDxM,16662
|
|
30
|
-
flixopt/elements.py,sha256=
|
|
31
|
-
flixopt/features.py,sha256=
|
|
30
|
+
flixopt/elements.py,sha256=S89IykQmXxKTtfNn8HW6fWLdb64UePaY0vaittCAJ2M,28153
|
|
31
|
+
flixopt/features.py,sha256=AI3pW8UbyYLZFDIiPtexmNRkLT7UU-ckmcse9lWRPgw,46263
|
|
32
32
|
flixopt/flow_system.py,sha256=Gowg5k7LWlF6VYRHtrIbKVCKWKMrzlZQtBCfdeEq4jQ,19607
|
|
33
|
-
flixopt/interface.py,sha256=
|
|
33
|
+
flixopt/interface.py,sha256=Be3IWcYCLATDCoTliiv6Ky1V_srn1KiCHY3tBriVd_U,22631
|
|
34
34
|
flixopt/io.py,sha256=2QKdtu2-mkzSGBIqHtUcF9UaG32nq9qcIRxZghf1hLw,11284
|
|
35
35
|
flixopt/linear_converters.py,sha256=ej5V_ML_3m1k9HbDnuey6pHEpQtguYkxBXHxWyE9sq0,10936
|
|
36
36
|
flixopt/network_app.py,sha256=oVdARrTDV43H5ZAypJP3dmIL4A9x-Y3ec0zZC5gS8rA,28019
|
|
@@ -39,7 +39,7 @@ flixopt/results.py,sha256=GKSZmz0GCuJwspTC8Ot6MOKinvy_mhnDXCafb_f7uVY,35161
|
|
|
39
39
|
flixopt/solvers.py,sha256=k1bSoiXec3asWED70-erXkgtpn2C8KRBfSZj0FLviSM,2436
|
|
40
40
|
flixopt/structure.py,sha256=CZIz_8UVF56BT4tiDQYzfH4WCR-yaiZHW0--u6olHx4,26281
|
|
41
41
|
flixopt/utils.py,sha256=f-_vFDvvG27-c_VMpzkv3lb79Yny4rvoSmemushbzhU,1687
|
|
42
|
-
flixopt-2.
|
|
42
|
+
flixopt-2.2.0rc2.dist-info/licenses/LICENSE,sha256=HKsZnbrM_3Rvnr_u9cWSG90cBsj5_slaqI_z_qcxnGI,1118
|
|
43
43
|
pics/architecture_flixOpt-pre2.0.0.png,sha256=9RWSA3vys588aadr2437zor-_-xBTQNQ0bAf8xGcu5g,70605
|
|
44
44
|
pics/architecture_flixOpt.png,sha256=KjN1bJwESbkHmTW7UsJ7dZyiKZlTO7Dx20dg8KlR1HU,260219
|
|
45
45
|
pics/flixOpt_plotting.jpg,sha256=zn7ZPAtXm5eRTxtOj86e4-PPhHpCar1jqGh7vMBgQGY,518862
|
|
@@ -48,7 +48,7 @@ pics/pics.pptx,sha256=ImWeGGvjtWJ6BGruipsnZYmWtHj5sWdbw1NSFePbkC8,683344
|
|
|
48
48
|
scripts/extract_release_notes.py,sha256=3UUE4hWhdd2t2m2x0ZpchGP-A0MvfqO2Wc5EdNN-fgE,1249
|
|
49
49
|
scripts/gen_ref_pages.py,sha256=AYRtXyz78x5I_Hn0oRtGVbTxgLLj2QNyRX6vWRefPjc,1960
|
|
50
50
|
tests/ressources/Zeitreihen2020.csv,sha256=kbsDTKZS0iUsNZAS7m3DohzZI_OHHWe44s3GwLvcTLw,1918412
|
|
51
|
-
flixopt-2.
|
|
52
|
-
flixopt-2.
|
|
53
|
-
flixopt-2.
|
|
54
|
-
flixopt-2.
|
|
51
|
+
flixopt-2.2.0rc2.dist-info/METADATA,sha256=OVQsScG4XvEoQVhF3m3qde_FmII3gGFHVRhcwjHklc0,8232
|
|
52
|
+
flixopt-2.2.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
53
|
+
flixopt-2.2.0rc2.dist-info/top_level.txt,sha256=DEuo4R1z7GmEp5R3pjbQEJbaPRjKHFvNX2ceiBnVOL0,32
|
|
54
|
+
flixopt-2.2.0rc2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|