flixopt 2.1.7__py3-none-any.whl → 2.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flixopt might be problematic. Click here for more details.
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +8 -8
- docs/user-guide/Mathematical Notation/Flow.md +3 -3
- docs/user-guide/Mathematical Notation/InvestParameters.md +3 -0
- docs/user-guide/Mathematical Notation/LinearConverter.md +3 -3
- docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
- docs/user-guide/Mathematical Notation/Storage.md +1 -1
- flixopt/aggregation.py +33 -32
- flixopt/calculation.py +158 -58
- flixopt/components.py +673 -150
- flixopt/config.py +17 -8
- flixopt/core.py +59 -54
- flixopt/effects.py +144 -63
- flixopt/elements.py +292 -107
- flixopt/features.py +61 -58
- flixopt/flow_system.py +69 -48
- flixopt/interface.py +952 -113
- flixopt/io.py +15 -10
- flixopt/linear_converters.py +373 -81
- flixopt/network_app.py +73 -39
- flixopt/plotting.py +215 -87
- flixopt/results.py +382 -209
- flixopt/solvers.py +25 -21
- flixopt/structure.py +41 -37
- flixopt/utils.py +10 -7
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/METADATA +46 -42
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/RECORD +30 -28
- scripts/gen_ref_pages.py +1 -1
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/WHEEL +0 -0
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/top_level.txt +0 -0
flixopt/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
|
|
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) ->
|
|
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:
|
|
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':
|
|
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:
|
|
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) ->
|
|
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:
|
|
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
|
flixopt/linear_converters.py
CHANGED
|
@@ -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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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=[
|
|
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.
|
|
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
|
-
|
|
296
|
-
|
|
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(
|