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 CHANGED
@@ -22,6 +22,7 @@ from .commons import (
22
22
  Piecewise,
23
23
  PiecewiseConversion,
24
24
  PiecewiseEffects,
25
+ PiecewiseEffectsPerFlowHour,
25
26
  SegmentedCalculation,
26
27
  Sink,
27
28
  Source,
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 InvestParameters, OnOffParameters, Piece, Piecewise, PiecewiseConversion, PiecewiseEffects
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
- Converts input-Flows into output-Flows via linear conversion factors
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 miultiple variables.
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. If None or False, no zero point is defined.
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]) <= rhs,
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
- def __init__(self, piecewises: Dict[str, Piecewise]):
67
- """
68
- Define a piecewise conversion between multiple Flows.
69
- --> "gaps" can be expressed by a piece not starting at the end of the prior piece: [(1,3), (4,5)]
70
- --> "points" can expressed as piece with same begin and end: [(3,3), (4,4)]
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
- Args:
73
- piecewises: Dict of Piecewises defining the conversion factors. flow labels as keys, piecewise as values
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.1.7
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=xTxHXC-_lWa65roGUM3J3HXfiYcMCJHoQsuL5mS0Xkg,688
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=ZNlUN1z-h9OGHPo-s-n5OLlJaoPZKVGcAdRyGKpMk4M,1256
25
- flixopt/components.py,sha256=o9H4yfwuI_Y31BuYZH_NkVYm_Du4aoAiVTtHO_KhMUg,33297
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=MDOQb17FkUGN1bgdbAMoavBuBmVtN6evDbLT7ExL4pI,26944
31
- flixopt/features.py,sha256=JgvwIK5o8GKJBUnicfMe7sfXbzojFQGfAkCpGVwzKww,43983
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=uXf6Z29OfHpIRsS1-oZZ6SSuy8FLe13FjtqzHPqzzQE,12088
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.1.7.dist-info/licenses/LICENSE,sha256=HKsZnbrM_3Rvnr_u9cWSG90cBsj5_slaqI_z_qcxnGI,1118
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.1.7.dist-info/METADATA,sha256=_cFPusrwdee_4dJRFbKQWGhTWZOHTrWsT4PdBVhLhJA,8229
52
- flixopt-2.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- flixopt-2.1.7.dist-info/top_level.txt,sha256=DEuo4R1z7GmEp5R3pjbQEJbaPRjKHFvNX2ceiBnVOL0,32
54
- flixopt-2.1.7.dist-info/RECORD,,
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,,