flixopt 3.0.1__py3-none-any.whl → 6.0.0rc7__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.
Files changed (42) hide show
  1. flixopt/__init__.py +57 -49
  2. flixopt/carrier.py +159 -0
  3. flixopt/clustering/__init__.py +51 -0
  4. flixopt/clustering/base.py +1746 -0
  5. flixopt/clustering/intercluster_helpers.py +201 -0
  6. flixopt/color_processing.py +372 -0
  7. flixopt/comparison.py +819 -0
  8. flixopt/components.py +848 -270
  9. flixopt/config.py +853 -496
  10. flixopt/core.py +111 -98
  11. flixopt/effects.py +294 -284
  12. flixopt/elements.py +484 -223
  13. flixopt/features.py +220 -118
  14. flixopt/flow_system.py +2026 -389
  15. flixopt/interface.py +504 -286
  16. flixopt/io.py +1718 -55
  17. flixopt/linear_converters.py +291 -230
  18. flixopt/modeling.py +304 -181
  19. flixopt/network_app.py +2 -1
  20. flixopt/optimization.py +788 -0
  21. flixopt/optimize_accessor.py +373 -0
  22. flixopt/plot_result.py +143 -0
  23. flixopt/plotting.py +1177 -1034
  24. flixopt/results.py +1331 -372
  25. flixopt/solvers.py +12 -4
  26. flixopt/statistics_accessor.py +2412 -0
  27. flixopt/stats_accessor.py +75 -0
  28. flixopt/structure.py +954 -120
  29. flixopt/topology_accessor.py +676 -0
  30. flixopt/transform_accessor.py +2277 -0
  31. flixopt/types.py +120 -0
  32. flixopt-6.0.0rc7.dist-info/METADATA +290 -0
  33. flixopt-6.0.0rc7.dist-info/RECORD +36 -0
  34. {flixopt-3.0.1.dist-info → flixopt-6.0.0rc7.dist-info}/WHEEL +1 -1
  35. flixopt/aggregation.py +0 -382
  36. flixopt/calculation.py +0 -672
  37. flixopt/commons.py +0 -51
  38. flixopt/utils.py +0 -86
  39. flixopt-3.0.1.dist-info/METADATA +0 -209
  40. flixopt-3.0.1.dist-info/RECORD +0 -26
  41. {flixopt-3.0.1.dist-info → flixopt-6.0.0rc7.dist-info}/licenses/LICENSE +0 -0
  42. {flixopt-3.0.1.dist-info → flixopt-6.0.0rc7.dist-info}/top_level.txt +0 -0
@@ -10,12 +10,12 @@ from typing import TYPE_CHECKING
10
10
  import numpy as np
11
11
 
12
12
  from .components import LinearConverter
13
- from .core import TemporalDataUser, TimeSeriesData
14
13
  from .structure import register_class_for_io
15
14
 
16
15
  if TYPE_CHECKING:
17
16
  from .elements import Flow
18
- from .interface import OnOffParameters
17
+ from .interface import StatusParameters
18
+ from .types import Numeric_TPS
19
19
 
20
20
  logger = logging.getLogger('flixopt')
21
21
 
@@ -31,11 +31,11 @@ class Boiler(LinearConverter):
31
31
 
32
32
  Args:
33
33
  label: The label of the Element. Used to identify it in the FlowSystem.
34
- eta: Thermal efficiency factor (0-1 range). Defines the ratio of thermal
34
+ thermal_efficiency: Thermal efficiency factor (0-1 range). Defines the ratio of thermal
35
35
  output to fuel input energy content.
36
- Q_fu: Fuel input-flow representing fuel consumption.
37
- Q_th: Thermal output-flow representing heat generation.
38
- on_off_parameters: Parameters defining binary operation constraints and costs.
36
+ fuel_flow: Fuel input-flow representing fuel consumption.
37
+ thermal_flow: Thermal output-flow representing heat generation.
38
+ status_parameters: Parameters defining status, startup and shutdown constraints and effects
39
39
  meta_data: Used to store additional information. Not used internally but
40
40
  saved in results. Only use Python native types.
41
41
 
@@ -45,9 +45,9 @@ class Boiler(LinearConverter):
45
45
  ```python
46
46
  gas_boiler = Boiler(
47
47
  label='natural_gas_boiler',
48
- eta=0.85, # 85% thermal efficiency
49
- Q_fu=natural_gas_flow,
50
- Q_th=hot_water_flow,
48
+ thermal_efficiency=0.85, # 85% thermal efficiency
49
+ fuel_flow=natural_gas_flow,
50
+ thermal_flow=hot_water_flow,
51
51
  )
52
52
  ```
53
53
 
@@ -56,18 +56,18 @@ class Boiler(LinearConverter):
56
56
  ```python
57
57
  biomass_boiler = Boiler(
58
58
  label='wood_chip_boiler',
59
- eta=seasonal_efficiency_profile, # Time-varying efficiency
60
- Q_fu=biomass_flow,
61
- Q_th=district_heat_flow,
62
- on_off_parameters=OnOffParameters(
63
- consecutive_on_hours_min=4, # Minimum 4-hour operation
64
- effects_per_switch_on={'startup_fuel': 50}, # Startup fuel penalty
59
+ thermal_efficiency=seasonal_efficiency_profile, # Time-varying efficiency
60
+ fuel_flow=biomass_flow,
61
+ thermal_flow=district_heat_flow,
62
+ status_parameters=StatusParameters(
63
+ min_uptime=4, # Minimum 4-hour operation
64
+ effects_per_startup={'startup_fuel': 50}, # Startup fuel penalty
65
65
  ),
66
66
  )
67
67
  ```
68
68
 
69
69
  Note:
70
- The conversion relationship is: Q_th = Q_fu × eta
70
+ The conversion relationship is: thermal_flow = fuel_flow × thermal_efficiency
71
71
 
72
72
  Efficiency should be between 0 and 1, where 1 represents perfect conversion
73
73
  (100% of fuel energy converted to useful thermal output).
@@ -76,31 +76,41 @@ class Boiler(LinearConverter):
76
76
  def __init__(
77
77
  self,
78
78
  label: str,
79
- eta: TemporalDataUser,
80
- Q_fu: Flow,
81
- Q_th: Flow,
82
- on_off_parameters: OnOffParameters | None = None,
79
+ thermal_efficiency: Numeric_TPS | None = None,
80
+ fuel_flow: Flow | None = None,
81
+ thermal_flow: Flow | None = None,
82
+ status_parameters: StatusParameters | None = None,
83
83
  meta_data: dict | None = None,
84
+ color: str | None = None,
84
85
  ):
86
+ # Validate required parameters
87
+ if fuel_flow is None:
88
+ raise ValueError(f"'{label}': fuel_flow is required and cannot be None")
89
+ if thermal_flow is None:
90
+ raise ValueError(f"'{label}': thermal_flow is required and cannot be None")
91
+ if thermal_efficiency is None:
92
+ raise ValueError(f"'{label}': thermal_efficiency is required and cannot be None")
93
+
85
94
  super().__init__(
86
95
  label,
87
- inputs=[Q_fu],
88
- outputs=[Q_th],
89
- conversion_factors=[{Q_fu.label: eta, Q_th.label: 1}],
90
- on_off_parameters=on_off_parameters,
96
+ inputs=[fuel_flow],
97
+ outputs=[thermal_flow],
98
+ status_parameters=status_parameters,
91
99
  meta_data=meta_data,
100
+ color=color,
92
101
  )
93
- self.Q_fu = Q_fu
94
- self.Q_th = Q_th
102
+ self.fuel_flow = fuel_flow
103
+ self.thermal_flow = thermal_flow
104
+ self.thermal_efficiency = thermal_efficiency # Uses setter
95
105
 
96
106
  @property
97
- def eta(self):
98
- return self.conversion_factors[0][self.Q_fu.label]
107
+ def thermal_efficiency(self):
108
+ return self.conversion_factors[0][self.fuel_flow.label]
99
109
 
100
- @eta.setter
101
- def eta(self, value):
102
- check_bounds(value, 'eta', self.label_full, 0, 1)
103
- self.conversion_factors[0][self.Q_fu.label] = value
110
+ @thermal_efficiency.setter
111
+ def thermal_efficiency(self, value):
112
+ check_bounds(value, 'thermal_efficiency', self.label_full, 0, 1)
113
+ self.conversion_factors = [{self.fuel_flow.label: value, self.thermal_flow.label: 1}]
104
114
 
105
115
 
106
116
  @register_class_for_io
@@ -115,12 +125,12 @@ class Power2Heat(LinearConverter):
115
125
 
116
126
  Args:
117
127
  label: The label of the Element. Used to identify it in the FlowSystem.
118
- eta: Thermal efficiency factor (0-1 range). For resistance heating this is
128
+ thermal_efficiency: Thermal efficiency factor (0-1 range). For resistance heating this is
119
129
  typically close to 1.0 (nearly 100% efficiency), but may be lower for
120
130
  electrode boilers or systems with distribution losses.
121
- P_el: Electrical input-flow representing electricity consumption.
122
- Q_th: Thermal output-flow representing heat generation.
123
- on_off_parameters: Parameters defining binary operation constraints and costs.
131
+ electrical_flow: Electrical input-flow representing electricity consumption.
132
+ thermal_flow: Thermal output-flow representing heat generation.
133
+ status_parameters: Parameters defining status, startup and shutdown constraints and effects
124
134
  meta_data: Used to store additional information. Not used internally but
125
135
  saved in results. Only use Python native types.
126
136
 
@@ -130,9 +140,9 @@ class Power2Heat(LinearConverter):
130
140
  ```python
131
141
  electric_heater = Power2Heat(
132
142
  label='resistance_heater',
133
- eta=0.98, # 98% efficiency (small losses)
134
- P_el=electricity_flow,
135
- Q_th=space_heating_flow,
143
+ thermal_efficiency=0.98, # 98% efficiency (small losses)
144
+ electrical_flow=electricity_flow,
145
+ thermal_flow=space_heating_flow,
136
146
  )
137
147
  ```
138
148
 
@@ -141,20 +151,20 @@ class Power2Heat(LinearConverter):
141
151
  ```python
142
152
  electrode_boiler = Power2Heat(
143
153
  label='electrode_steam_boiler',
144
- eta=0.95, # 95% efficiency including boiler losses
145
- P_el=industrial_electricity,
146
- Q_th=process_steam_flow,
147
- on_off_parameters=OnOffParameters(
148
- consecutive_on_hours_min=1, # Minimum 1-hour operation
149
- effects_per_switch_on={'startup_cost': 100},
154
+ thermal_efficiency=0.95, # 95% efficiency including boiler losses
155
+ electrical_flow=industrial_electricity,
156
+ thermal_flow=process_steam_flow,
157
+ status_parameters=StatusParameters(
158
+ min_uptime=1, # Minimum 1-hour operation
159
+ effects_per_startup={'startup_cost': 100},
150
160
  ),
151
161
  )
152
162
  ```
153
163
 
154
164
  Note:
155
- The conversion relationship is: Q_th = P_el × eta
165
+ The conversion relationship is: thermal_flow = electrical_flow × thermal_efficiency
156
166
 
157
- Unlike heat pumps, Power2Heat systems cannot exceed 100% efficiency (eta ≤ 1.0)
167
+ Unlike heat pumps, Power2Heat systems cannot exceed 100% efficiency (thermal_efficiency ≤ 1.0)
158
168
  as they only convert electrical energy without extracting additional energy
159
169
  from the environment. However, they provide fast response times and precise
160
170
  temperature control.
@@ -163,32 +173,42 @@ class Power2Heat(LinearConverter):
163
173
  def __init__(
164
174
  self,
165
175
  label: str,
166
- eta: TemporalDataUser,
167
- P_el: Flow,
168
- Q_th: Flow,
169
- on_off_parameters: OnOffParameters | None = None,
176
+ thermal_efficiency: Numeric_TPS | None = None,
177
+ electrical_flow: Flow | None = None,
178
+ thermal_flow: Flow | None = None,
179
+ status_parameters: StatusParameters | None = None,
170
180
  meta_data: dict | None = None,
181
+ color: str | None = None,
171
182
  ):
183
+ # Validate required parameters
184
+ if electrical_flow is None:
185
+ raise ValueError(f"'{label}': electrical_flow is required and cannot be None")
186
+ if thermal_flow is None:
187
+ raise ValueError(f"'{label}': thermal_flow is required and cannot be None")
188
+ if thermal_efficiency is None:
189
+ raise ValueError(f"'{label}': thermal_efficiency is required and cannot be None")
190
+
172
191
  super().__init__(
173
192
  label,
174
- inputs=[P_el],
175
- outputs=[Q_th],
176
- conversion_factors=[{P_el.label: eta, Q_th.label: 1}],
177
- on_off_parameters=on_off_parameters,
193
+ inputs=[electrical_flow],
194
+ outputs=[thermal_flow],
195
+ status_parameters=status_parameters,
178
196
  meta_data=meta_data,
197
+ color=color,
179
198
  )
180
199
 
181
- self.P_el = P_el
182
- self.Q_th = Q_th
200
+ self.electrical_flow = electrical_flow
201
+ self.thermal_flow = thermal_flow
202
+ self.thermal_efficiency = thermal_efficiency # Uses setter
183
203
 
184
204
  @property
185
- def eta(self):
186
- return self.conversion_factors[0][self.P_el.label]
205
+ def thermal_efficiency(self):
206
+ return self.conversion_factors[0][self.electrical_flow.label]
187
207
 
188
- @eta.setter
189
- def eta(self, value):
190
- check_bounds(value, 'eta', self.label_full, 0, 1)
191
- self.conversion_factors[0][self.P_el.label] = value
208
+ @thermal_efficiency.setter
209
+ def thermal_efficiency(self, value):
210
+ check_bounds(value, 'thermal_efficiency', self.label_full, 0, 1)
211
+ self.conversion_factors = [{self.electrical_flow.label: value, self.thermal_flow.label: 1}]
192
212
 
193
213
 
194
214
  @register_class_for_io
@@ -203,12 +223,12 @@ class HeatPump(LinearConverter):
203
223
 
204
224
  Args:
205
225
  label: The label of the Element. Used to identify it in the FlowSystem.
206
- COP: Coefficient of Performance (typically 1-20 range). Defines the ratio of
226
+ cop: Coefficient of Performance (typically 1-20 range). Defines the ratio of
207
227
  thermal output to electrical input. COP > 1 indicates the heat pump extracts
208
228
  additional energy from the environment.
209
- P_el: Electrical input-flow representing electricity consumption.
210
- Q_th: Thermal output-flow representing heat generation.
211
- on_off_parameters: Parameters defining binary operation constraints and costs.
229
+ electrical_flow: Electrical input-flow representing electricity consumption.
230
+ thermal_flow: Thermal output-flow representing heat generation.
231
+ status_parameters: Parameters defining status, startup and shutdown constraints and effects
212
232
  meta_data: Used to store additional information. Not used internally but
213
233
  saved in results. Only use Python native types.
214
234
 
@@ -218,9 +238,9 @@ class HeatPump(LinearConverter):
218
238
  ```python
219
239
  air_hp = HeatPump(
220
240
  label='air_source_heat_pump',
221
- COP=3.5, # COP of 3.5 (350% efficiency)
222
- P_el=electricity_flow,
223
- Q_th=heating_flow,
241
+ cop=3.5, # COP of 3.5 (350% efficiency)
242
+ electrical_flow=electricity_flow,
243
+ thermal_flow=heating_flow,
224
244
  )
225
245
  ```
226
246
 
@@ -229,18 +249,18 @@ class HeatPump(LinearConverter):
229
249
  ```python
230
250
  ground_hp = HeatPump(
231
251
  label='geothermal_heat_pump',
232
- COP=temperature_dependent_cop, # Time-varying COP based on ground temp
233
- P_el=electricity_flow,
234
- Q_th=radiant_heating_flow,
235
- on_off_parameters=OnOffParameters(
236
- consecutive_on_hours_min=2, # Avoid frequent cycling
237
- effects_per_running_hour={'maintenance': 0.5},
252
+ cop=temperature_dependent_cop, # Time-varying COP based on ground temp
253
+ electrical_flow=electricity_flow,
254
+ thermal_flow=radiant_heating_flow,
255
+ status_parameters=StatusParameters(
256
+ min_uptime=2, # Avoid frequent cycling
257
+ effects_per_active_hour={'maintenance': 0.5},
238
258
  ),
239
259
  )
240
260
  ```
241
261
 
242
262
  Note:
243
- The conversion relationship is: Q_th = P_el × COP
263
+ The conversion relationship is: thermal_flow = electrical_flow × COP
244
264
 
245
265
  COP should be greater than 1 for realistic heat pump operation, with typical
246
266
  values ranging from 2-6 depending on technology and operating conditions.
@@ -250,32 +270,42 @@ class HeatPump(LinearConverter):
250
270
  def __init__(
251
271
  self,
252
272
  label: str,
253
- COP: TemporalDataUser,
254
- P_el: Flow,
255
- Q_th: Flow,
256
- on_off_parameters: OnOffParameters | None = None,
273
+ cop: Numeric_TPS | None = None,
274
+ electrical_flow: Flow | None = None,
275
+ thermal_flow: Flow | None = None,
276
+ status_parameters: StatusParameters | None = None,
257
277
  meta_data: dict | None = None,
278
+ color: str | None = None,
258
279
  ):
280
+ # Validate required parameters
281
+ if electrical_flow is None:
282
+ raise ValueError(f"'{label}': electrical_flow is required and cannot be None")
283
+ if thermal_flow is None:
284
+ raise ValueError(f"'{label}': thermal_flow is required and cannot be None")
285
+ if cop is None:
286
+ raise ValueError(f"'{label}': cop is required and cannot be None")
287
+
259
288
  super().__init__(
260
289
  label,
261
- inputs=[P_el],
262
- outputs=[Q_th],
263
- conversion_factors=[{P_el.label: COP, Q_th.label: 1}],
264
- on_off_parameters=on_off_parameters,
290
+ inputs=[electrical_flow],
291
+ outputs=[thermal_flow],
292
+ conversion_factors=[],
293
+ status_parameters=status_parameters,
265
294
  meta_data=meta_data,
295
+ color=color,
266
296
  )
267
- self.P_el = P_el
268
- self.Q_th = Q_th
269
- self.COP = COP
297
+ self.electrical_flow = electrical_flow
298
+ self.thermal_flow = thermal_flow
299
+ self.cop = cop # Uses setter
270
300
 
271
301
  @property
272
- def COP(self): # noqa: N802
273
- return self.conversion_factors[0][self.P_el.label]
302
+ def cop(self):
303
+ return self.conversion_factors[0][self.electrical_flow.label]
274
304
 
275
- @COP.setter
276
- def COP(self, value): # noqa: N802
277
- check_bounds(value, 'COP', self.label_full, 1, 20)
278
- self.conversion_factors[0][self.P_el.label] = value
305
+ @cop.setter
306
+ def cop(self, value):
307
+ check_bounds(value, 'cop', self.label_full, 1, 20)
308
+ self.conversion_factors = [{self.electrical_flow.label: value, self.thermal_flow.label: 1}]
279
309
 
280
310
 
281
311
  @register_class_for_io
@@ -293,9 +323,9 @@ class CoolingTower(LinearConverter):
293
323
  specific_electricity_demand: Auxiliary electricity demand per unit of cooling
294
324
  power (dimensionless, typically 0.01-0.05 range). Represents the fraction
295
325
  of thermal power that must be supplied as electricity for fans and pumps.
296
- P_el: Electrical input-flow representing electricity consumption for fans/pumps.
297
- Q_th: Thermal input-flow representing waste heat to be rejected to environment.
298
- on_off_parameters: Parameters defining binary operation constraints and costs.
326
+ electrical_flow: Electrical input-flow representing electricity consumption for fans/pumps.
327
+ thermal_flow: Thermal input-flow representing waste heat to be rejected to environment.
328
+ status_parameters: Parameters defining status, startup and shutdown constraints and effects
299
329
  meta_data: Used to store additional information. Not used internally but
300
330
  saved in results. Only use Python native types.
301
331
 
@@ -306,8 +336,8 @@ class CoolingTower(LinearConverter):
306
336
  cooling_tower = CoolingTower(
307
337
  label='process_cooling_tower',
308
338
  specific_electricity_demand=0.025, # 2.5% auxiliary power
309
- P_el=cooling_electricity,
310
- Q_th=waste_heat_flow,
339
+ electrical_flow=cooling_electricity,
340
+ thermal_flow=waste_heat_flow,
311
341
  )
312
342
  ```
313
343
 
@@ -317,17 +347,17 @@ class CoolingTower(LinearConverter):
317
347
  condenser_cooling = CoolingTower(
318
348
  label='power_plant_cooling',
319
349
  specific_electricity_demand=0.015, # 1.5% auxiliary power
320
- P_el=auxiliary_electricity,
321
- Q_th=condenser_waste_heat,
322
- on_off_parameters=OnOffParameters(
323
- consecutive_on_hours_min=4, # Minimum operation time
324
- effects_per_running_hour={'water_consumption': 2.5}, # m³/h
350
+ electrical_flow=auxiliary_electricity,
351
+ thermal_flow=condenser_waste_heat,
352
+ status_parameters=StatusParameters(
353
+ min_uptime=4, # Minimum operation time
354
+ effects_per_active_hour={'water_consumption': 2.5}, # m³/h
325
355
  ),
326
356
  )
327
357
  ```
328
358
 
329
359
  Note:
330
- The conversion relationship is: P_el = Q_th × specific_electricity_demand
360
+ The conversion relationship is: electrical_flow = thermal_flow × specific_electricity_demand
331
361
 
332
362
  The cooling tower consumes electrical power proportional to the thermal load.
333
363
  No thermal energy is produced - all thermal input is rejected to the environment.
@@ -339,34 +369,40 @@ class CoolingTower(LinearConverter):
339
369
  def __init__(
340
370
  self,
341
371
  label: str,
342
- specific_electricity_demand: TemporalDataUser,
343
- P_el: Flow,
344
- Q_th: Flow,
345
- on_off_parameters: OnOffParameters | None = None,
372
+ specific_electricity_demand: Numeric_TPS,
373
+ electrical_flow: Flow | None = None,
374
+ thermal_flow: Flow | None = None,
375
+ status_parameters: StatusParameters | None = None,
346
376
  meta_data: dict | None = None,
377
+ color: str | None = None,
347
378
  ):
379
+ # Validate required parameters
380
+ if electrical_flow is None:
381
+ raise ValueError(f"'{label}': electrical_flow is required and cannot be None")
382
+ if thermal_flow is None:
383
+ raise ValueError(f"'{label}': thermal_flow is required and cannot be None")
384
+
348
385
  super().__init__(
349
386
  label,
350
- inputs=[P_el, Q_th],
387
+ inputs=[electrical_flow, thermal_flow],
351
388
  outputs=[],
352
- conversion_factors=[{P_el.label: -1, Q_th.label: specific_electricity_demand}],
353
- on_off_parameters=on_off_parameters,
389
+ status_parameters=status_parameters,
354
390
  meta_data=meta_data,
391
+ color=color,
355
392
  )
356
393
 
357
- self.P_el = P_el
358
- self.Q_th = Q_th
359
-
360
- check_bounds(specific_electricity_demand, 'specific_electricity_demand', self.label_full, 0, 1)
394
+ self.electrical_flow = electrical_flow
395
+ self.thermal_flow = thermal_flow
396
+ self.specific_electricity_demand = specific_electricity_demand # Uses setter
361
397
 
362
398
  @property
363
399
  def specific_electricity_demand(self):
364
- return self.conversion_factors[0][self.Q_th.label]
400
+ return self.conversion_factors[0][self.thermal_flow.label]
365
401
 
366
402
  @specific_electricity_demand.setter
367
403
  def specific_electricity_demand(self, value):
368
404
  check_bounds(value, 'specific_electricity_demand', self.label_full, 0, 1)
369
- self.conversion_factors[0][self.Q_th.label] = value
405
+ self.conversion_factors = [{self.electrical_flow.label: -1, self.thermal_flow.label: value}]
370
406
 
371
407
 
372
408
  @register_class_for_io
@@ -381,14 +417,14 @@ class CHP(LinearConverter):
381
417
 
382
418
  Args:
383
419
  label: The label of the Element. Used to identify it in the FlowSystem.
384
- eta_th: Thermal efficiency factor (0-1 range). Defines the fraction of fuel
420
+ thermal_efficiency: Thermal efficiency factor (0-1 range). Defines the fraction of fuel
385
421
  energy converted to useful thermal output.
386
- eta_el: Electrical efficiency factor (0-1 range). Defines the fraction of fuel
422
+ electrical_efficiency: Electrical efficiency factor (0-1 range). Defines the fraction of fuel
387
423
  energy converted to electrical output.
388
- Q_fu: Fuel input-flow representing fuel consumption.
389
- P_el: Electrical output-flow representing electricity generation.
390
- Q_th: Thermal output-flow representing heat generation.
391
- on_off_parameters: Parameters defining binary operation constraints and costs.
424
+ fuel_flow: Fuel input-flow representing fuel consumption.
425
+ electrical_flow: Electrical output-flow representing electricity generation.
426
+ thermal_flow: Thermal output-flow representing heat generation.
427
+ status_parameters: Parameters defining status, startup and shutdown constraints and effects
392
428
  meta_data: Used to store additional information. Not used internally but
393
429
  saved in results. Only use Python native types.
394
430
 
@@ -398,11 +434,11 @@ class CHP(LinearConverter):
398
434
  ```python
399
435
  gas_chp = CHP(
400
436
  label='natural_gas_chp',
401
- eta_th=0.45, # 45% thermal efficiency
402
- eta_el=0.35, # 35% electrical efficiency (80% total)
403
- Q_fu=natural_gas_flow,
404
- P_el=electricity_flow,
405
- Q_th=district_heat_flow,
437
+ thermal_efficiency=0.45, # 45% thermal efficiency
438
+ electrical_efficiency=0.35, # 35% electrical efficiency (80% total)
439
+ fuel_flow=natural_gas_flow,
440
+ electrical_flow=electricity_flow,
441
+ thermal_flow=district_heat_flow,
406
442
  )
407
443
  ```
408
444
 
@@ -411,25 +447,25 @@ class CHP(LinearConverter):
411
447
  ```python
412
448
  industrial_chp = CHP(
413
449
  label='industrial_chp',
414
- eta_th=0.40,
415
- eta_el=0.38,
416
- Q_fu=fuel_gas_flow,
417
- P_el=plant_electricity,
418
- Q_th=process_steam,
419
- on_off_parameters=OnOffParameters(
420
- consecutive_on_hours_min=8, # Minimum 8-hour operation
421
- effects_per_switch_on={'startup_cost': 5000},
422
- on_hours_total_max=6000, # Annual operating limit
450
+ thermal_efficiency=0.40,
451
+ electrical_efficiency=0.38,
452
+ fuel_flow=fuel_gas_flow,
453
+ electrical_flow=plant_electricity,
454
+ thermal_flow=process_steam,
455
+ status_parameters=StatusParameters(
456
+ min_uptime=8, # Minimum 8-hour operation
457
+ effects_per_startup={'startup_cost': 5000},
458
+ active_hours_max=6000, # Annual operating limit
423
459
  ),
424
460
  )
425
461
  ```
426
462
 
427
463
  Note:
428
464
  The conversion relationships are:
429
- - Q_th = Q_fu × eta_th (thermal output)
430
- - P_el = Q_fu × eta_el (electrical output)
465
+ - thermal_flow = fuel_flow × thermal_efficiency (thermal output)
466
+ - electrical_flow = fuel_flow × electrical_efficiency (electrical output)
431
467
 
432
- Total efficiency (eta_th + eta_el) should be ≤ 1.0, with typical combined
468
+ Total efficiency (thermal_efficiency + electrical_efficiency) should be ≤ 1.0, with typical combined
433
469
  efficiencies of 80-90% for modern CHP units. This provides significant
434
470
  efficiency gains compared to separate heat and power generation.
435
471
  """
@@ -437,49 +473,68 @@ class CHP(LinearConverter):
437
473
  def __init__(
438
474
  self,
439
475
  label: str,
440
- eta_th: TemporalDataUser,
441
- eta_el: TemporalDataUser,
442
- Q_fu: Flow,
443
- P_el: Flow,
444
- Q_th: Flow,
445
- on_off_parameters: OnOffParameters | None = None,
476
+ thermal_efficiency: Numeric_TPS | None = None,
477
+ electrical_efficiency: Numeric_TPS | None = None,
478
+ fuel_flow: Flow | None = None,
479
+ electrical_flow: Flow | None = None,
480
+ thermal_flow: Flow | None = None,
481
+ status_parameters: StatusParameters | None = None,
446
482
  meta_data: dict | None = None,
483
+ color: str | None = None,
447
484
  ):
448
- heat = {Q_fu.label: eta_th, Q_th.label: 1}
449
- electricity = {Q_fu.label: eta_el, P_el.label: 1}
485
+ # Validate required parameters
486
+ if fuel_flow is None:
487
+ raise ValueError(f"'{label}': fuel_flow is required and cannot be None")
488
+ if electrical_flow is None:
489
+ raise ValueError(f"'{label}': electrical_flow is required and cannot be None")
490
+ if thermal_flow is None:
491
+ raise ValueError(f"'{label}': thermal_flow is required and cannot be None")
492
+ if thermal_efficiency is None:
493
+ raise ValueError(f"'{label}': thermal_efficiency is required and cannot be None")
494
+ if electrical_efficiency is None:
495
+ raise ValueError(f"'{label}': electrical_efficiency is required and cannot be None")
450
496
 
451
497
  super().__init__(
452
498
  label,
453
- inputs=[Q_fu],
454
- outputs=[Q_th, P_el],
455
- conversion_factors=[heat, electricity],
456
- on_off_parameters=on_off_parameters,
499
+ inputs=[fuel_flow],
500
+ outputs=[thermal_flow, electrical_flow],
501
+ conversion_factors=[{}, {}],
502
+ status_parameters=status_parameters,
457
503
  meta_data=meta_data,
504
+ color=color,
458
505
  )
459
506
 
460
- self.Q_fu = Q_fu
461
- self.P_el = P_el
462
- self.Q_th = Q_th
463
-
464
- check_bounds(eta_el + eta_th, 'eta_th+eta_el', self.label_full, 0, 1)
507
+ self.fuel_flow = fuel_flow
508
+ self.electrical_flow = electrical_flow
509
+ self.thermal_flow = thermal_flow
510
+ self.thermal_efficiency = thermal_efficiency # Uses setter
511
+ self.electrical_efficiency = electrical_efficiency # Uses setter
512
+
513
+ check_bounds(
514
+ electrical_efficiency + thermal_efficiency,
515
+ 'thermal_efficiency+electrical_efficiency',
516
+ self.label_full,
517
+ 0,
518
+ 1,
519
+ )
465
520
 
466
521
  @property
467
- def eta_th(self):
468
- return self.conversion_factors[0][self.Q_fu.label]
522
+ def thermal_efficiency(self):
523
+ return self.conversion_factors[0][self.fuel_flow.label]
469
524
 
470
- @eta_th.setter
471
- def eta_th(self, value):
472
- check_bounds(value, 'eta_th', self.label_full, 0, 1)
473
- self.conversion_factors[0][self.Q_fu.label] = value
525
+ @thermal_efficiency.setter
526
+ def thermal_efficiency(self, value):
527
+ check_bounds(value, 'thermal_efficiency', self.label_full, 0, 1)
528
+ self.conversion_factors[0] = {self.fuel_flow.label: value, self.thermal_flow.label: 1}
474
529
 
475
530
  @property
476
- def eta_el(self):
477
- return self.conversion_factors[1][self.Q_fu.label]
531
+ def electrical_efficiency(self):
532
+ return self.conversion_factors[1][self.fuel_flow.label]
478
533
 
479
- @eta_el.setter
480
- def eta_el(self, value):
481
- check_bounds(value, 'eta_el', self.label_full, 0, 1)
482
- self.conversion_factors[1][self.Q_fu.label] = value
534
+ @electrical_efficiency.setter
535
+ def electrical_efficiency(self, value):
536
+ check_bounds(value, 'electrical_efficiency', self.label_full, 0, 1)
537
+ self.conversion_factors[1] = {self.fuel_flow.label: value, self.electrical_flow.label: 1}
483
538
 
484
539
 
485
540
  @register_class_for_io
@@ -494,14 +549,14 @@ class HeatPumpWithSource(LinearConverter):
494
549
 
495
550
  Args:
496
551
  label: The label of the Element. Used to identify it in the FlowSystem.
497
- COP: Coefficient of Performance (typically 1-20 range). Defines the ratio of
552
+ cop: Coefficient of Performance (typically 1-20 range). Defines the ratio of
498
553
  thermal output to electrical input. The heat source extraction is automatically
499
- calculated as Q_ab = Q_th × (COP-1)/COP.
500
- P_el: Electrical input-flow representing electricity consumption for compressor.
501
- Q_ab: Heat source input-flow representing thermal energy extracted from environment
554
+ calculated as heat_source_flow = thermal_flow × (COP-1)/COP.
555
+ electrical_flow: Electrical input-flow representing electricity consumption for compressor.
556
+ heat_source_flow: Heat source input-flow representing thermal energy extracted from environment
502
557
  (ground, air, water source).
503
- Q_th: Thermal output-flow representing useful heat delivered to the application.
504
- on_off_parameters: Parameters defining binary operation constraints and costs.
558
+ thermal_flow: Thermal output-flow representing useful heat delivered to the application.
559
+ status_parameters: Parameters defining status, startup and shutdown constraints and effects
505
560
  meta_data: Used to store additional information. Not used internally but
506
561
  saved in results. Only use Python native types.
507
562
 
@@ -511,10 +566,10 @@ class HeatPumpWithSource(LinearConverter):
511
566
  ```python
512
567
  ground_source_hp = HeatPumpWithSource(
513
568
  label='geothermal_heat_pump',
514
- COP=4.5, # High COP due to stable ground temperature
515
- P_el=electricity_flow,
516
- Q_ab=ground_heat_extraction, # Heat extracted from ground loop
517
- Q_th=building_heating_flow,
569
+ cop=4.5, # High COP due to stable ground temperature
570
+ electrical_flow=electricity_flow,
571
+ heat_source_flow=ground_heat_extraction, # Heat extracted from ground loop
572
+ thermal_flow=building_heating_flow,
518
573
  )
519
574
  ```
520
575
 
@@ -523,22 +578,22 @@ class HeatPumpWithSource(LinearConverter):
523
578
  ```python
524
579
  waste_heat_pump = HeatPumpWithSource(
525
580
  label='waste_heat_pump',
526
- COP=temperature_dependent_cop, # Varies with temperature of heat source
527
- P_el=electricity_consumption,
528
- Q_ab=industrial_heat_extraction, # Heat extracted from a industrial process or waste water
529
- Q_th=heat_supply,
530
- on_off_parameters=OnOffParameters(
531
- consecutive_on_hours_min=0.5, # 30-minute minimum runtime
532
- effects_per_switch_on={'costs': 1000},
581
+ cop=temperature_dependent_cop, # Varies with temperature of heat source
582
+ electrical_flow=electricity_consumption,
583
+ heat_source_flow=industrial_heat_extraction, # Heat extracted from a industrial process or waste water
584
+ thermal_flow=heat_supply,
585
+ status_parameters=StatusParameters(
586
+ min_uptime=0.5, # 30-minute minimum runtime
587
+ effects_per_startup={'costs': 1000},
533
588
  ),
534
589
  )
535
590
  ```
536
591
 
537
592
  Note:
538
593
  The conversion relationships are:
539
- - Q_th = P_el × COP (thermal output from electrical input)
540
- - Q_ab = Q_th × (COP-1)/COP (heat source extraction)
541
- - Energy balance: Q_th = P_el + Q_ab
594
+ - thermal_flow = electrical_flow × COP (thermal output from electrical input)
595
+ - heat_source_flow = thermal_flow × (COP-1)/COP (heat source extraction)
596
+ - Energy balance: thermal_flow = electrical_flow + heat_source_flow
542
597
 
543
598
  This formulation explicitly tracks the heat source, which is
544
599
  important for systems where the source capacity or temperature is limited,
@@ -551,49 +606,58 @@ class HeatPumpWithSource(LinearConverter):
551
606
  def __init__(
552
607
  self,
553
608
  label: str,
554
- COP: TemporalDataUser,
555
- P_el: Flow,
556
- Q_ab: Flow,
557
- Q_th: Flow,
558
- on_off_parameters: OnOffParameters | None = None,
609
+ cop: Numeric_TPS | None = None,
610
+ electrical_flow: Flow | None = None,
611
+ heat_source_flow: Flow | None = None,
612
+ thermal_flow: Flow | None = None,
613
+ status_parameters: StatusParameters | None = None,
559
614
  meta_data: dict | None = None,
615
+ color: str | None = None,
560
616
  ):
617
+ # Validate required parameters
618
+ if electrical_flow is None:
619
+ raise ValueError(f"'{label}': electrical_flow is required and cannot be None")
620
+ if heat_source_flow is None:
621
+ raise ValueError(f"'{label}': heat_source_flow is required and cannot be None")
622
+ if thermal_flow is None:
623
+ raise ValueError(f"'{label}': thermal_flow is required and cannot be None")
624
+ if cop is None:
625
+ raise ValueError(f"'{label}': cop is required and cannot be None")
626
+
561
627
  super().__init__(
562
628
  label,
563
- inputs=[P_el, Q_ab],
564
- outputs=[Q_th],
565
- conversion_factors=[{P_el.label: COP, Q_th.label: 1}, {Q_ab.label: COP / (COP - 1), Q_th.label: 1}],
566
- on_off_parameters=on_off_parameters,
629
+ inputs=[electrical_flow, heat_source_flow],
630
+ outputs=[thermal_flow],
631
+ status_parameters=status_parameters,
567
632
  meta_data=meta_data,
633
+ color=color,
568
634
  )
569
- self.P_el = P_el
570
- self.Q_ab = Q_ab
571
- self.Q_th = Q_th
572
-
573
- if np.any(np.asarray(self.COP) <= 1):
574
- raise ValueError(f'{self.label_full}.COP must be strictly > 1 for HeatPumpWithSource.')
635
+ self.electrical_flow = electrical_flow
636
+ self.heat_source_flow = heat_source_flow
637
+ self.thermal_flow = thermal_flow
638
+ self.cop = cop # Uses setter
575
639
 
576
640
  @property
577
- def COP(self): # noqa: N802
578
- return self.conversion_factors[0][self.P_el.label]
579
-
580
- @COP.setter
581
- def COP(self, value): # noqa: N802
582
- check_bounds(value, 'COP', self.label_full, 1, 20)
583
- if np.any(np.asarray(value) <= 1):
584
- raise ValueError(f'{self.label_full}.COP must be strictly > 1 for HeatPumpWithSource.')
641
+ def cop(self):
642
+ return self.conversion_factors[0][self.electrical_flow.label]
643
+
644
+ @cop.setter
645
+ def cop(self, value):
646
+ check_bounds(value, 'cop', self.label_full, 1, 20)
647
+ if np.any(np.asarray(value) == 1):
648
+ raise ValueError(f'{self.label_full}.cop must be strictly !=1 for HeatPumpWithSource.')
585
649
  self.conversion_factors = [
586
- {self.P_el.label: value, self.Q_th.label: 1},
587
- {self.Q_ab.label: value / (value - 1), self.Q_th.label: 1},
650
+ {self.electrical_flow.label: value, self.thermal_flow.label: 1},
651
+ {self.heat_source_flow.label: value / (value - 1), self.thermal_flow.label: 1},
588
652
  ]
589
653
 
590
654
 
591
655
  def check_bounds(
592
- value: TemporalDataUser,
656
+ value: Numeric_TPS,
593
657
  parameter_label: str,
594
658
  element_label: str,
595
- lower_bound: TemporalDataUser,
596
- upper_bound: TemporalDataUser,
659
+ lower_bound: Numeric_TPS,
660
+ upper_bound: Numeric_TPS,
597
661
  ) -> None:
598
662
  """
599
663
  Check if the value is within the bounds. The bounds are exclusive.
@@ -605,19 +669,16 @@ def check_bounds(
605
669
  lower_bound: The lower bound.
606
670
  upper_bound: The upper bound.
607
671
  """
608
- if isinstance(value, TimeSeriesData):
609
- value = value.data
610
- if isinstance(lower_bound, TimeSeriesData):
611
- lower_bound = lower_bound.data
612
- if isinstance(upper_bound, TimeSeriesData):
613
- upper_bound = upper_bound.data
614
- if not np.all(value > lower_bound):
672
+ # Convert to array for shape and statistics
673
+ value_arr = np.asarray(value)
674
+
675
+ if not np.all(value_arr > lower_bound):
615
676
  logger.warning(
616
- f"'{element_label}.{parameter_label}' is equal or below the common lower bound {lower_bound}."
617
- f' {parameter_label}.min={np.min(value)}; {parameter_label}={value}'
677
+ f"'{element_label}.{parameter_label}' <= lower bound {lower_bound}. "
678
+ f'{parameter_label}.min={float(np.min(value_arr))}, shape={np.shape(value_arr)}'
618
679
  )
619
- if not np.all(value < upper_bound):
680
+ if not np.all(value_arr < upper_bound):
620
681
  logger.warning(
621
- f"'{element_label}.{parameter_label}' exceeds or matches the common upper bound {upper_bound}."
622
- f' {parameter_label}.max={np.max(value)}; {parameter_label}={value}'
682
+ f"'{element_label}.{parameter_label}' >= upper bound {upper_bound}. "
683
+ f'{parameter_label}.max={float(np.max(value_arr))}, shape={np.shape(value_arr)}'
623
684
  )