flixopt 2.1.6__py3-none-any.whl → 2.1.8__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.

Files changed (45) hide show
  1. docs/examples/00-Minimal Example.md +1 -1
  2. docs/examples/01-Basic Example.md +1 -1
  3. docs/examples/02-Complex Example.md +1 -1
  4. docs/examples/index.md +1 -1
  5. docs/faq/contribute.md +26 -14
  6. docs/faq/index.md +1 -1
  7. docs/javascripts/mathjax.js +1 -1
  8. docs/user-guide/Mathematical Notation/Bus.md +1 -1
  9. docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +21 -21
  10. docs/user-guide/Mathematical Notation/Flow.md +3 -3
  11. docs/user-guide/Mathematical Notation/InvestParameters.md +3 -0
  12. docs/user-guide/Mathematical Notation/LinearConverter.md +5 -5
  13. docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
  14. docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
  15. docs/user-guide/Mathematical Notation/Storage.md +2 -2
  16. docs/user-guide/Mathematical Notation/index.md +1 -1
  17. docs/user-guide/Mathematical Notation/others.md +1 -1
  18. docs/user-guide/index.md +2 -2
  19. flixopt/__init__.py +4 -0
  20. flixopt/aggregation.py +33 -32
  21. flixopt/calculation.py +161 -65
  22. flixopt/components.py +687 -154
  23. flixopt/config.py +17 -8
  24. flixopt/core.py +69 -60
  25. flixopt/effects.py +146 -64
  26. flixopt/elements.py +297 -110
  27. flixopt/features.py +78 -71
  28. flixopt/flow_system.py +72 -50
  29. flixopt/interface.py +952 -113
  30. flixopt/io.py +15 -10
  31. flixopt/linear_converters.py +373 -81
  32. flixopt/network_app.py +445 -266
  33. flixopt/plotting.py +215 -87
  34. flixopt/results.py +382 -209
  35. flixopt/solvers.py +25 -21
  36. flixopt/structure.py +41 -39
  37. flixopt/utils.py +10 -7
  38. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/METADATA +64 -53
  39. flixopt-2.1.8.dist-info/RECORD +56 -0
  40. scripts/extract_release_notes.py +5 -5
  41. scripts/gen_ref_pages.py +1 -1
  42. flixopt-2.1.6.dist-info/RECORD +0 -54
  43. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/WHEEL +0 -0
  44. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/licenses/LICENSE +0 -0
  45. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/top_level.txt +0 -0
flixopt/io.py CHANGED
@@ -1,17 +1,21 @@
1
+ from __future__ import annotations
2
+
1
3
  import importlib.util
2
4
  import json
3
5
  import logging
4
6
  import pathlib
5
7
  import re
6
8
  from dataclasses import dataclass
7
- from typing import Dict, Literal, Optional, Tuple, Union
9
+ from typing import TYPE_CHECKING, Literal
8
10
 
9
- import linopy
10
11
  import xarray as xr
11
12
  import yaml
12
13
 
13
14
  from .core import TimeSeries
14
15
 
16
+ if TYPE_CHECKING:
17
+ import linopy
18
+
15
19
  logger = logging.getLogger('flixopt')
16
20
 
17
21
 
@@ -146,7 +150,7 @@ def _process_complex_strings(data):
146
150
  return data
147
151
 
148
152
 
149
- def document_linopy_model(model: linopy.Model, path: pathlib.Path = None) -> Dict[str, str]:
153
+ def document_linopy_model(model: linopy.Model, path: pathlib.Path | None = None) -> dict[str, str]:
150
154
  """
151
155
  Convert all model variables and constraints to a structured string representation.
152
156
  This can take multiple seconds for large models.
@@ -195,14 +199,14 @@ def document_linopy_model(model: linopy.Model, path: pathlib.Path = None) -> Dic
195
199
  if path is not None:
196
200
  if path.suffix not in ['.yaml', '.yml']:
197
201
  raise ValueError(f'Invalid file extension for path {path}. Only .yaml and .yml are supported')
198
- _save_to_yaml(documentation, path)
202
+ _save_to_yaml(documentation, str(path))
199
203
 
200
204
  return documentation
201
205
 
202
206
 
203
207
  def save_dataset_to_netcdf(
204
208
  ds: xr.Dataset,
205
- path: Union[str, pathlib.Path],
209
+ path: str | pathlib.Path,
206
210
  compression: int = 0,
207
211
  ) -> None:
208
212
  """
@@ -216,6 +220,7 @@ def save_dataset_to_netcdf(
216
220
  Raises:
217
221
  ValueError: If the path has an invalid file extension.
218
222
  """
223
+ path = pathlib.Path(path)
219
224
  if path.suffix not in ['.nc', '.nc4']:
220
225
  raise ValueError(f'Invalid file extension for path {path}. Only .nc and .nc4 are supported')
221
226
 
@@ -234,11 +239,11 @@ def save_dataset_to_netcdf(
234
239
  path,
235
240
  encoding=None
236
241
  if not apply_encoding
237
- else {data_var: {'zlib': True, 'complevel': 5} for data_var in ds.data_vars},
242
+ else {data_var: {'zlib': True, 'complevel': compression} for data_var in ds.data_vars},
238
243
  )
239
244
 
240
245
 
241
- def load_dataset_from_netcdf(path: Union[str, pathlib.Path]) -> xr.Dataset:
246
+ def load_dataset_from_netcdf(path: str | pathlib.Path) -> xr.Dataset:
242
247
  """
243
248
  Load a dataset from a netcdf file. Load the attrs from the 'attrs' attribute.
244
249
 
@@ -248,7 +253,7 @@ def load_dataset_from_netcdf(path: Union[str, pathlib.Path]) -> xr.Dataset:
248
253
  Returns:
249
254
  Dataset: Loaded dataset.
250
255
  """
251
- ds = xr.load_dataset(path)
256
+ ds = xr.load_dataset(str(path))
252
257
  ds.attrs = json.loads(ds.attrs['attrs'])
253
258
  return ds
254
259
 
@@ -273,7 +278,7 @@ class CalculationResultsPaths:
273
278
  self.flow_system = self.folder / f'{self.name}--flow_system.nc4'
274
279
  self.model_documentation = self.folder / f'{self.name}--model_documentation.yaml'
275
280
 
276
- def all_paths(self) -> Dict[str, pathlib.Path]:
281
+ def all_paths(self) -> dict[str, pathlib.Path]:
277
282
  """Return a dictionary of all paths."""
278
283
  return {
279
284
  'linopy_model': self.linopy_model,
@@ -297,7 +302,7 @@ class CalculationResultsPaths:
297
302
  f'Folder {self.folder} and its parent do not exist. Please create them first.'
298
303
  ) from e
299
304
 
300
- def update(self, new_name: Optional[str] = None, new_folder: Optional[pathlib.Path] = None) -> None:
305
+ def update(self, new_name: str | None = None, new_folder: pathlib.Path | None = None) -> None:
301
306
  """Update name and/or folder and refresh all paths."""
302
307
  if new_name is not None:
303
308
  self.name = new_name
@@ -2,40 +2,86 @@
2
2
  This Module contains high-level classes to easily model a FlowSystem.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import logging
6
- from typing import Dict, Optional
8
+ from typing import TYPE_CHECKING
7
9
 
8
10
  import numpy as np
9
11
 
10
12
  from .components import LinearConverter
11
13
  from .core import NumericDataTS, TimeSeriesData
12
- from .elements import Flow
13
- from .interface import OnOffParameters
14
14
  from .structure import register_class_for_io
15
15
 
16
+ if TYPE_CHECKING:
17
+ from .elements import Flow
18
+ from .interface import OnOffParameters
19
+
16
20
  logger = logging.getLogger('flixopt')
17
21
 
18
22
 
19
23
  @register_class_for_io
20
24
  class Boiler(LinearConverter):
25
+ """
26
+ A specialized LinearConverter representing a fuel-fired boiler for thermal energy generation.
27
+
28
+ Boilers convert fuel input into thermal energy with a specified efficiency factor.
29
+ This is a simplified wrapper around LinearConverter with predefined conversion
30
+ relationships for thermal generation applications.
31
+
32
+ Args:
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
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.
39
+ meta_data: Used to store additional information. Not used internally but
40
+ saved in results. Only use Python native types.
41
+
42
+ Examples:
43
+ Natural gas boiler:
44
+
45
+ ```python
46
+ gas_boiler = Boiler(
47
+ label='natural_gas_boiler',
48
+ eta=0.85, # 85% thermal efficiency
49
+ Q_fu=natural_gas_flow,
50
+ Q_th=hot_water_flow,
51
+ )
52
+ ```
53
+
54
+ Biomass boiler with seasonal efficiency variation:
55
+
56
+ ```python
57
+ biomass_boiler = Boiler(
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
65
+ ),
66
+ )
67
+ ```
68
+
69
+ Note:
70
+ The conversion relationship is: Q_th = Q_fu × eta
71
+
72
+ Efficiency should be between 0 and 1, where 1 represents perfect conversion
73
+ (100% of fuel energy converted to useful thermal output).
74
+ """
75
+
21
76
  def __init__(
22
77
  self,
23
78
  label: str,
24
79
  eta: NumericDataTS,
25
80
  Q_fu: Flow,
26
81
  Q_th: Flow,
27
- on_off_parameters: OnOffParameters = None,
28
- meta_data: Optional[Dict] = None,
82
+ on_off_parameters: OnOffParameters | None = None,
83
+ meta_data: dict | None = None,
29
84
  ):
30
- """
31
- Args:
32
- label: The label of the Element. Used to identify it in the FlowSystem
33
- eta: thermal efficiency.
34
- Q_fu: fuel input-flow
35
- Q_th: thermal output-flow.
36
- on_off_parameters: Parameters defining the on/off behavior of the component.
37
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
38
- """
39
85
  super().__init__(
40
86
  label,
41
87
  inputs=[Q_fu],
@@ -59,24 +105,70 @@ class Boiler(LinearConverter):
59
105
 
60
106
  @register_class_for_io
61
107
  class Power2Heat(LinearConverter):
108
+ """
109
+ A specialized LinearConverter representing electric resistance heating or power-to-heat conversion.
110
+
111
+ Power2Heat components convert electrical energy directly into thermal energy through
112
+ resistance heating elements, electrode boilers, or other direct electric heating
113
+ technologies. This is a simplified wrapper around LinearConverter with predefined
114
+ conversion relationships for electric heating applications.
115
+
116
+ Args:
117
+ 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
119
+ typically close to 1.0 (nearly 100% efficiency), but may be lower for
120
+ 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.
124
+ meta_data: Used to store additional information. Not used internally but
125
+ saved in results. Only use Python native types.
126
+
127
+ Examples:
128
+ Electric resistance heater:
129
+
130
+ ```python
131
+ electric_heater = Power2Heat(
132
+ label='resistance_heater',
133
+ eta=0.98, # 98% efficiency (small losses)
134
+ P_el=electricity_flow,
135
+ Q_th=space_heating_flow,
136
+ )
137
+ ```
138
+
139
+ Electrode boiler for industrial steam:
140
+
141
+ ```python
142
+ electrode_boiler = Power2Heat(
143
+ 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},
150
+ ),
151
+ )
152
+ ```
153
+
154
+ Note:
155
+ The conversion relationship is: Q_th = P_el × eta
156
+
157
+ Unlike heat pumps, Power2Heat systems cannot exceed 100% efficiency (eta ≤ 1.0)
158
+ as they only convert electrical energy without extracting additional energy
159
+ from the environment. However, they provide fast response times and precise
160
+ temperature control.
161
+ """
162
+
62
163
  def __init__(
63
164
  self,
64
165
  label: str,
65
166
  eta: NumericDataTS,
66
167
  P_el: Flow,
67
168
  Q_th: Flow,
68
- on_off_parameters: OnOffParameters = None,
69
- meta_data: Optional[Dict] = None,
169
+ on_off_parameters: OnOffParameters | None = None,
170
+ meta_data: dict | None = None,
70
171
  ):
71
- """
72
- Args:
73
- label: The label of the Element. Used to identify it in the FlowSystem
74
- eta: thermal efficiency.
75
- P_el: electric input-flow
76
- Q_th: thermal output-flow.
77
- on_off_parameters: Parameters defining the on/off behavior of the component.
78
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
79
- """
80
172
  super().__init__(
81
173
  label,
82
174
  inputs=[P_el],
@@ -101,24 +193,69 @@ class Power2Heat(LinearConverter):
101
193
 
102
194
  @register_class_for_io
103
195
  class HeatPump(LinearConverter):
196
+ """
197
+ A specialized LinearConverter representing an electric heat pump for thermal energy generation.
198
+
199
+ Heat pumps convert electrical energy into thermal energy with a Coefficient of
200
+ Performance (COP) greater than 1, making them more efficient than direct electric
201
+ heating. This is a simplified wrapper around LinearConverter with predefined
202
+ conversion relationships for heat pump applications.
203
+
204
+ Args:
205
+ 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
207
+ thermal output to electrical input. COP > 1 indicates the heat pump extracts
208
+ 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.
212
+ meta_data: Used to store additional information. Not used internally but
213
+ saved in results. Only use Python native types.
214
+
215
+ Examples:
216
+ Air-source heat pump with constant COP:
217
+
218
+ ```python
219
+ air_hp = HeatPump(
220
+ 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,
224
+ )
225
+ ```
226
+
227
+ Ground-source heat pump with temperature-dependent COP:
228
+
229
+ ```python
230
+ ground_hp = HeatPump(
231
+ 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},
238
+ ),
239
+ )
240
+ ```
241
+
242
+ Note:
243
+ The conversion relationship is: Q_th = P_el × COP
244
+
245
+ COP should be greater than 1 for realistic heat pump operation, with typical
246
+ values ranging from 2-6 depending on technology and operating conditions.
247
+ Higher COP values indicate more efficient heat extraction from the environment.
248
+ """
249
+
104
250
  def __init__(
105
251
  self,
106
252
  label: str,
107
253
  COP: NumericDataTS,
108
254
  P_el: Flow,
109
255
  Q_th: Flow,
110
- on_off_parameters: OnOffParameters = None,
111
- meta_data: Optional[Dict] = None,
256
+ on_off_parameters: OnOffParameters | None = None,
257
+ meta_data: dict | None = None,
112
258
  ):
113
- """
114
- Args:
115
- label: The label of the Element. Used to identify it in the FlowSystem
116
- COP: Coefficient of performance.
117
- P_el: electricity input-flow.
118
- Q_th: thermal output-flow.
119
- on_off_parameters: Parameters defining the on/off behavior of the component.
120
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
121
- """
122
259
  super().__init__(
123
260
  label,
124
261
  inputs=[P_el],
@@ -143,24 +280,71 @@ class HeatPump(LinearConverter):
143
280
 
144
281
  @register_class_for_io
145
282
  class CoolingTower(LinearConverter):
283
+ """
284
+ A specialized LinearConverter representing a cooling tower for waste heat rejection.
285
+
286
+ Cooling towers consume electrical energy (for fans, pumps) to reject thermal energy
287
+ to the environment through evaporation and heat transfer. The electricity demand
288
+ is typically a small fraction of the thermal load being rejected. This component
289
+ has no thermal outputs as the heat is rejected to the environment.
290
+
291
+ Args:
292
+ label: The label of the Element. Used to identify it in the FlowSystem.
293
+ specific_electricity_demand: Auxiliary electricity demand per unit of cooling
294
+ power (dimensionless, typically 0.01-0.05 range). Represents the fraction
295
+ 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.
299
+ meta_data: Used to store additional information. Not used internally but
300
+ saved in results. Only use Python native types.
301
+
302
+ Examples:
303
+ Industrial cooling tower:
304
+
305
+ ```python
306
+ cooling_tower = CoolingTower(
307
+ label='process_cooling_tower',
308
+ specific_electricity_demand=0.025, # 2.5% auxiliary power
309
+ P_el=cooling_electricity,
310
+ Q_th=waste_heat_flow,
311
+ )
312
+ ```
313
+
314
+ Power plant condenser cooling:
315
+
316
+ ```python
317
+ condenser_cooling = CoolingTower(
318
+ label='power_plant_cooling',
319
+ 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
325
+ ),
326
+ )
327
+ ```
328
+
329
+ Note:
330
+ The conversion relationship is: P_el = Q_th × specific_electricity_demand
331
+
332
+ The cooling tower consumes electrical power proportional to the thermal load.
333
+ No thermal energy is produced - all thermal input is rejected to the environment.
334
+
335
+ Typical specific electricity demands range from 1-5% of the thermal cooling load,
336
+ depending on tower design, climate conditions, and operational requirements.
337
+ """
338
+
146
339
  def __init__(
147
340
  self,
148
341
  label: str,
149
342
  specific_electricity_demand: NumericDataTS,
150
343
  P_el: Flow,
151
344
  Q_th: Flow,
152
- on_off_parameters: OnOffParameters = None,
153
- meta_data: Optional[Dict] = None,
345
+ on_off_parameters: OnOffParameters | None = None,
346
+ meta_data: dict | None = None,
154
347
  ):
155
- """
156
- Args:
157
- label: The label of the Element. Used to identify it in the FlowSystem
158
- specific_electricity_demand: auxiliary electricty demand per cooling power, i.g. 0.02 (2 %).
159
- P_el: electricity input-flow.
160
- Q_th: thermal input-flow.
161
- on_off_parameters: Parameters defining the on/off behavior of the component.
162
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
163
- """
164
348
  super().__init__(
165
349
  label,
166
350
  inputs=[P_el, Q_th],
@@ -187,6 +371,69 @@ class CoolingTower(LinearConverter):
187
371
 
188
372
  @register_class_for_io
189
373
  class CHP(LinearConverter):
374
+ """
375
+ A specialized LinearConverter representing a Combined Heat and Power (CHP) unit.
376
+
377
+ CHP units simultaneously generate both electrical and thermal energy from a single
378
+ fuel input, providing higher overall efficiency than separate generation. This is
379
+ a wrapper around LinearConverter with predefined conversion relationships for
380
+ cogeneration applications.
381
+
382
+ Args:
383
+ 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
385
+ energy converted to useful thermal output.
386
+ eta_el: Electrical efficiency factor (0-1 range). Defines the fraction of fuel
387
+ 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.
392
+ meta_data: Used to store additional information. Not used internally but
393
+ saved in results. Only use Python native types.
394
+
395
+ Examples:
396
+ Natural gas CHP unit:
397
+
398
+ ```python
399
+ gas_chp = CHP(
400
+ 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,
406
+ )
407
+ ```
408
+
409
+ Industrial CHP with operational constraints:
410
+
411
+ ```python
412
+ industrial_chp = CHP(
413
+ 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
423
+ ),
424
+ )
425
+ ```
426
+
427
+ Note:
428
+ The conversion relationships are:
429
+ - Q_th = Q_fu × eta_th (thermal output)
430
+ - P_el = Q_fu × eta_el (electrical output)
431
+
432
+ Total efficiency (eta_th + eta_el) should be ≤ 1.0, with typical combined
433
+ efficiencies of 80-90% for modern CHP units. This provides significant
434
+ efficiency gains compared to separate heat and power generation.
435
+ """
436
+
190
437
  def __init__(
191
438
  self,
192
439
  label: str,
@@ -195,20 +442,9 @@ class CHP(LinearConverter):
195
442
  Q_fu: Flow,
196
443
  P_el: Flow,
197
444
  Q_th: Flow,
198
- on_off_parameters: OnOffParameters = None,
199
- meta_data: Optional[Dict] = None,
445
+ on_off_parameters: OnOffParameters | None = None,
446
+ meta_data: dict | None = None,
200
447
  ):
201
- """
202
- Args:
203
- label: The label of the Element. Used to identify it in the FlowSystem
204
- eta_th: thermal efficiency.
205
- eta_el: electrical efficiency.
206
- Q_fu: fuel input-flow.
207
- P_el: electricity output-flow.
208
- Q_th: heat output-flow.
209
- on_off_parameters: Parameters defining the on/off behavior of the component.
210
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
211
- """
212
448
  heat = {Q_fu.label: eta_th, Q_th.label: 1}
213
449
  electricity = {Q_fu.label: eta_el, P_el.label: 1}
214
450
 
@@ -248,6 +484,70 @@ class CHP(LinearConverter):
248
484
 
249
485
  @register_class_for_io
250
486
  class HeatPumpWithSource(LinearConverter):
487
+ """
488
+ A specialized LinearConverter representing a heat pump with explicit heat source modeling.
489
+
490
+ This component models a heat pump that extracts thermal energy from a heat source
491
+ (ground, air, water) and upgrades it using electrical energy to provide higher-grade
492
+ thermal output. Unlike the simple HeatPump class, this explicitly models both the
493
+ heat source extraction and electrical consumption with their interdependent relationships.
494
+
495
+ Args:
496
+ 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
498
+ 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
502
+ (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.
505
+ meta_data: Used to store additional information. Not used internally but
506
+ saved in results. Only use Python native types.
507
+
508
+ Examples:
509
+ Ground-source heat pump with explicit ground coupling:
510
+
511
+ ```python
512
+ ground_source_hp = HeatPumpWithSource(
513
+ 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,
518
+ )
519
+ ```
520
+
521
+ Air-source heat pump with temperature-dependent performance:
522
+
523
+ ```python
524
+ waste_heat_pump = HeatPumpWithSource(
525
+ 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},
533
+ ),
534
+ )
535
+ ```
536
+
537
+ Note:
538
+ 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
542
+
543
+ This formulation explicitly tracks the heat source, which is
544
+ important for systems where the source capacity or temperature is limited,
545
+ or where the impact of heat extraction must be considered.
546
+
547
+ COP should be > 1 for thermodynamically valid operation, with typical
548
+ values of 2-6 depending on source and sink temperatures.
549
+ """
550
+
251
551
  def __init__(
252
552
  self,
253
553
  label: str,
@@ -255,29 +555,14 @@ class HeatPumpWithSource(LinearConverter):
255
555
  P_el: Flow,
256
556
  Q_ab: Flow,
257
557
  Q_th: Flow,
258
- on_off_parameters: OnOffParameters = None,
259
- meta_data: Optional[Dict] = None,
558
+ on_off_parameters: OnOffParameters | None = None,
559
+ meta_data: dict | None = None,
260
560
  ):
261
- """
262
- Args:
263
- label: The label of the Element. Used to identify it in the FlowSystem
264
- COP: Coefficient of performance.
265
- Q_ab: Heatsource input-flow.
266
- P_el: electricity input-flow.
267
- Q_th: thermal output-flow.
268
- on_off_parameters: Parameters defining the on/off behavior of the component.
269
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
270
- """
271
-
272
- # super:
273
- electricity = {P_el.label: COP, Q_th.label: 1}
274
- heat_source = {Q_ab.label: COP / (COP - 1), Q_th.label: 1}
275
-
276
561
  super().__init__(
277
562
  label,
278
563
  inputs=[P_el, Q_ab],
279
564
  outputs=[Q_th],
280
- conversion_factors=[electricity, heat_source],
565
+ conversion_factors=[{P_el.label: COP, Q_th.label: 1}, {Q_ab.label: COP / (COP - 1), Q_th.label: 1}],
281
566
  on_off_parameters=on_off_parameters,
282
567
  meta_data=meta_data,
283
568
  )
@@ -285,15 +570,22 @@ class HeatPumpWithSource(LinearConverter):
285
570
  self.Q_ab = Q_ab
286
571
  self.Q_th = Q_th
287
572
 
573
+ if np.any(np.asarray(self.COP) <= 1):
574
+ raise ValueError(f'{self.label_full}.COP must be strictly > 1 for HeatPumpWithSource.')
575
+
288
576
  @property
289
577
  def COP(self): # noqa: N802
290
- return self.conversion_factors[0][self.Q_th.label]
578
+ return self.conversion_factors[0][self.P_el.label]
291
579
 
292
580
  @COP.setter
293
581
  def COP(self, value): # noqa: N802
294
582
  check_bounds(value, 'COP', self.label_full, 1, 20)
295
- self.conversion_factors[0][self.Q_th.label] = value
296
- self.conversion_factors[1][self.Q_th.label] = value / (value - 1)
583
+ if np.any(np.asarray(value) <= 1):
584
+ raise ValueError(f'{self.label_full}.COP must be strictly > 1 for HeatPumpWithSource.')
585
+ 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},
588
+ ]
297
589
 
298
590
 
299
591
  def check_bounds(