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.
- flixopt/__init__.py +57 -49
- flixopt/carrier.py +159 -0
- flixopt/clustering/__init__.py +51 -0
- flixopt/clustering/base.py +1746 -0
- flixopt/clustering/intercluster_helpers.py +201 -0
- flixopt/color_processing.py +372 -0
- flixopt/comparison.py +819 -0
- flixopt/components.py +848 -270
- flixopt/config.py +853 -496
- flixopt/core.py +111 -98
- flixopt/effects.py +294 -284
- flixopt/elements.py +484 -223
- flixopt/features.py +220 -118
- flixopt/flow_system.py +2026 -389
- flixopt/interface.py +504 -286
- flixopt/io.py +1718 -55
- flixopt/linear_converters.py +291 -230
- flixopt/modeling.py +304 -181
- flixopt/network_app.py +2 -1
- flixopt/optimization.py +788 -0
- flixopt/optimize_accessor.py +373 -0
- flixopt/plot_result.py +143 -0
- flixopt/plotting.py +1177 -1034
- flixopt/results.py +1331 -372
- flixopt/solvers.py +12 -4
- flixopt/statistics_accessor.py +2412 -0
- flixopt/stats_accessor.py +75 -0
- flixopt/structure.py +954 -120
- flixopt/topology_accessor.py +676 -0
- flixopt/transform_accessor.py +2277 -0
- flixopt/types.py +120 -0
- flixopt-6.0.0rc7.dist-info/METADATA +290 -0
- flixopt-6.0.0rc7.dist-info/RECORD +36 -0
- {flixopt-3.0.1.dist-info → flixopt-6.0.0rc7.dist-info}/WHEEL +1 -1
- flixopt/aggregation.py +0 -382
- flixopt/calculation.py +0 -672
- flixopt/commons.py +0 -51
- flixopt/utils.py +0 -86
- flixopt-3.0.1.dist-info/METADATA +0 -209
- flixopt-3.0.1.dist-info/RECORD +0 -26
- {flixopt-3.0.1.dist-info → flixopt-6.0.0rc7.dist-info}/licenses/LICENSE +0 -0
- {flixopt-3.0.1.dist-info → flixopt-6.0.0rc7.dist-info}/top_level.txt +0 -0
flixopt/linear_converters.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
34
|
+
thermal_efficiency: Thermal efficiency factor (0-1 range). Defines the ratio of thermal
|
|
35
35
|
output to fuel input energy content.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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:
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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=[
|
|
88
|
-
outputs=[
|
|
89
|
-
|
|
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.
|
|
94
|
-
self.
|
|
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
|
|
98
|
-
return self.conversion_factors[0][self.
|
|
107
|
+
def thermal_efficiency(self):
|
|
108
|
+
return self.conversion_factors[0][self.fuel_flow.label]
|
|
99
109
|
|
|
100
|
-
@
|
|
101
|
-
def
|
|
102
|
-
check_bounds(value, '
|
|
103
|
-
self.conversion_factors[
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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:
|
|
165
|
+
The conversion relationship is: thermal_flow = electrical_flow × thermal_efficiency
|
|
156
166
|
|
|
157
|
-
Unlike heat pumps, Power2Heat systems cannot exceed 100% efficiency (
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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=[
|
|
175
|
-
outputs=[
|
|
176
|
-
|
|
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.
|
|
182
|
-
self.
|
|
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
|
|
186
|
-
return self.conversion_factors[0][self.
|
|
205
|
+
def thermal_efficiency(self):
|
|
206
|
+
return self.conversion_factors[0][self.electrical_flow.label]
|
|
187
207
|
|
|
188
|
-
@
|
|
189
|
-
def
|
|
190
|
-
check_bounds(value, '
|
|
191
|
-
self.conversion_factors[
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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:
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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=[
|
|
262
|
-
outputs=[
|
|
263
|
-
conversion_factors=[
|
|
264
|
-
|
|
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.
|
|
268
|
-
self.
|
|
269
|
-
self.
|
|
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
|
|
273
|
-
return self.conversion_factors[0][self.
|
|
302
|
+
def cop(self):
|
|
303
|
+
return self.conversion_factors[0][self.electrical_flow.label]
|
|
274
304
|
|
|
275
|
-
@
|
|
276
|
-
def
|
|
277
|
-
check_bounds(value, '
|
|
278
|
-
self.conversion_factors[
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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:
|
|
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:
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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=[
|
|
387
|
+
inputs=[electrical_flow, thermal_flow],
|
|
351
388
|
outputs=[],
|
|
352
|
-
|
|
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.
|
|
358
|
-
self.
|
|
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.
|
|
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[
|
|
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
|
-
|
|
420
|
+
thermal_efficiency: Thermal efficiency factor (0-1 range). Defines the fraction of fuel
|
|
385
421
|
energy converted to useful thermal output.
|
|
386
|
-
|
|
422
|
+
electrical_efficiency: Electrical efficiency factor (0-1 range). Defines the fraction of fuel
|
|
387
423
|
energy converted to electrical output.
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
-
|
|
430
|
-
-
|
|
465
|
+
- thermal_flow = fuel_flow × thermal_efficiency (thermal output)
|
|
466
|
+
- electrical_flow = fuel_flow × electrical_efficiency (electrical output)
|
|
431
467
|
|
|
432
|
-
Total efficiency (
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
449
|
-
|
|
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=[
|
|
454
|
-
outputs=[
|
|
455
|
-
conversion_factors=[
|
|
456
|
-
|
|
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.
|
|
461
|
-
self.
|
|
462
|
-
self.
|
|
463
|
-
|
|
464
|
-
|
|
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
|
|
468
|
-
return self.conversion_factors[0][self.
|
|
522
|
+
def thermal_efficiency(self):
|
|
523
|
+
return self.conversion_factors[0][self.fuel_flow.label]
|
|
469
524
|
|
|
470
|
-
@
|
|
471
|
-
def
|
|
472
|
-
check_bounds(value, '
|
|
473
|
-
self.conversion_factors[0]
|
|
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
|
|
477
|
-
return self.conversion_factors[1][self.
|
|
531
|
+
def electrical_efficiency(self):
|
|
532
|
+
return self.conversion_factors[1][self.fuel_flow.label]
|
|
478
533
|
|
|
479
|
-
@
|
|
480
|
-
def
|
|
481
|
-
check_bounds(value, '
|
|
482
|
-
self.conversion_factors[1]
|
|
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
|
-
|
|
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
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
504
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
-
|
|
540
|
-
-
|
|
541
|
-
- Energy balance:
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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=[
|
|
564
|
-
outputs=[
|
|
565
|
-
|
|
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.
|
|
570
|
-
self.
|
|
571
|
-
self.
|
|
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
|
|
578
|
-
return self.conversion_factors[0][self.
|
|
579
|
-
|
|
580
|
-
@
|
|
581
|
-
def
|
|
582
|
-
check_bounds(value, '
|
|
583
|
-
if np.any(np.asarray(value)
|
|
584
|
-
raise ValueError(f'{self.label_full}.
|
|
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.
|
|
587
|
-
{self.
|
|
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:
|
|
656
|
+
value: Numeric_TPS,
|
|
593
657
|
parameter_label: str,
|
|
594
658
|
element_label: str,
|
|
595
|
-
lower_bound:
|
|
596
|
-
upper_bound:
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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}'
|
|
617
|
-
f'
|
|
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(
|
|
680
|
+
if not np.all(value_arr < upper_bound):
|
|
620
681
|
logger.warning(
|
|
621
|
-
f"'{element_label}.{parameter_label}'
|
|
622
|
-
f'
|
|
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
|
)
|