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.
- docs/examples/00-Minimal Example.md +1 -1
- docs/examples/01-Basic Example.md +1 -1
- docs/examples/02-Complex Example.md +1 -1
- docs/examples/index.md +1 -1
- docs/faq/contribute.md +26 -14
- docs/faq/index.md +1 -1
- docs/javascripts/mathjax.js +1 -1
- docs/user-guide/Mathematical Notation/Bus.md +1 -1
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +21 -21
- 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 +5 -5
- docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
- docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
- docs/user-guide/Mathematical Notation/Storage.md +2 -2
- docs/user-guide/Mathematical Notation/index.md +1 -1
- docs/user-guide/Mathematical Notation/others.md +1 -1
- docs/user-guide/index.md +2 -2
- flixopt/__init__.py +4 -0
- flixopt/aggregation.py +33 -32
- flixopt/calculation.py +161 -65
- flixopt/components.py +687 -154
- flixopt/config.py +17 -8
- flixopt/core.py +69 -60
- flixopt/effects.py +146 -64
- flixopt/elements.py +297 -110
- flixopt/features.py +78 -71
- flixopt/flow_system.py +72 -50
- flixopt/interface.py +952 -113
- flixopt/io.py +15 -10
- flixopt/linear_converters.py +373 -81
- flixopt/network_app.py +445 -266
- flixopt/plotting.py +215 -87
- flixopt/results.py +382 -209
- flixopt/solvers.py +25 -21
- flixopt/structure.py +41 -39
- flixopt/utils.py +10 -7
- {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/METADATA +64 -53
- flixopt-2.1.8.dist-info/RECORD +56 -0
- scripts/extract_release_notes.py +5 -5
- scripts/gen_ref_pages.py +1 -1
- flixopt-2.1.6.dist-info/RECORD +0 -54
- {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/WHEEL +0 -0
- {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/top_level.txt +0 -0
flixopt/components.py
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
This module contains the basic components of the flixopt framework.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import logging
|
|
6
8
|
import warnings
|
|
7
|
-
from typing import TYPE_CHECKING,
|
|
9
|
+
from typing import TYPE_CHECKING, Literal
|
|
8
10
|
|
|
9
|
-
import linopy
|
|
10
11
|
import numpy as np
|
|
11
12
|
|
|
12
13
|
from . import utils
|
|
@@ -17,6 +18,8 @@ from .interface import InvestParameters, OnOffParameters, PiecewiseConversion
|
|
|
17
18
|
from .structure import SystemModel, register_class_for_io
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
21
|
+
import linopy
|
|
22
|
+
|
|
20
23
|
from .flow_system import FlowSystem
|
|
21
24
|
|
|
22
25
|
logger = logging.getLogger('flixopt')
|
|
@@ -25,40 +28,148 @@ logger = logging.getLogger('flixopt')
|
|
|
25
28
|
@register_class_for_io
|
|
26
29
|
class LinearConverter(Component):
|
|
27
30
|
"""
|
|
28
|
-
Converts input-Flows into output-Flows via linear conversion factors
|
|
31
|
+
Converts input-Flows into output-Flows via linear conversion factors.
|
|
32
|
+
|
|
33
|
+
LinearConverter models equipment that transforms one or more input flows into one or
|
|
34
|
+
more output flows through linear relationships. This includes heat exchangers,
|
|
35
|
+
electrical converters, chemical reactors, and other equipment where the
|
|
36
|
+
relationship between inputs and outputs can be expressed as linear equations.
|
|
37
|
+
|
|
38
|
+
The component supports two modeling approaches: simple conversion factors for
|
|
39
|
+
straightforward linear relationships, or piecewise conversion for complex non-linear
|
|
40
|
+
behavior approximated through piecewise linear segments.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
label: The label of the Element. Used to identify it in the FlowSystem.
|
|
44
|
+
inputs: list of input Flows that feed into the converter.
|
|
45
|
+
outputs: list of output Flows that are produced by the converter.
|
|
46
|
+
on_off_parameters: Information about on and off state of LinearConverter.
|
|
47
|
+
Component is On/Off if all connected Flows are On/Off. This induces an
|
|
48
|
+
On-Variable (binary) in all Flows! If possible, use OnOffParameters in a
|
|
49
|
+
single Flow instead to keep the number of binary variables low.
|
|
50
|
+
conversion_factors: Linear relationships between flows expressed as a list of
|
|
51
|
+
dictionaries. Each dictionary maps flow labels to their coefficients in one
|
|
52
|
+
linear equation. The number of conversion factors must be less than the total
|
|
53
|
+
number of flows to ensure degrees of freedom > 0. Either 'conversion_factors'
|
|
54
|
+
OR 'piecewise_conversion' can be used, but not both.
|
|
55
|
+
For examples also look into the linear_converters.py file.
|
|
56
|
+
piecewise_conversion: Define piecewise linear relationships between flow rates
|
|
57
|
+
of different flows. Enables modeling of non-linear conversion behavior through
|
|
58
|
+
linear approximation. Either 'conversion_factors' or 'piecewise_conversion'
|
|
59
|
+
can be used, but not both.
|
|
60
|
+
meta_data: Used to store additional information about the Element. Not used
|
|
61
|
+
internally, but saved in results. Only use Python native types.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
Simple 1:1 heat exchanger with 95% efficiency:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
heat_exchanger = LinearConverter(
|
|
68
|
+
label='primary_hx',
|
|
69
|
+
inputs=[hot_water_in],
|
|
70
|
+
outputs=[hot_water_out],
|
|
71
|
+
conversion_factors=[{'hot_water_in': 0.95, 'hot_water_out': 1}],
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Multi-input heat pump with COP=3:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
heat_pump = LinearConverter(
|
|
79
|
+
label='air_source_hp',
|
|
80
|
+
inputs=[electricity_in],
|
|
81
|
+
outputs=[heat_output],
|
|
82
|
+
conversion_factors=[{'electricity_in': 3, 'heat_output': 1}],
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Combined heat and power (CHP) unit with multiple outputs:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
chp_unit = LinearConverter(
|
|
90
|
+
label='gas_chp',
|
|
91
|
+
inputs=[natural_gas],
|
|
92
|
+
outputs=[electricity_out, heat_out],
|
|
93
|
+
conversion_factors=[
|
|
94
|
+
{'natural_gas': 0.35, 'electricity_out': 1},
|
|
95
|
+
{'natural_gas': 0.45, 'heat_out': 1},
|
|
96
|
+
],
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Electrolyzer with multiple conversion relationships:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
electrolyzer = LinearConverter(
|
|
104
|
+
label='pem_electrolyzer',
|
|
105
|
+
inputs=[electricity_in, water_in],
|
|
106
|
+
outputs=[hydrogen_out, oxygen_out],
|
|
107
|
+
conversion_factors=[
|
|
108
|
+
{'electricity_in': 1, 'hydrogen_out': 50}, # 50 kWh/kg H2
|
|
109
|
+
{'water_in': 1, 'hydrogen_out': 9}, # 9 kg H2O/kg H2
|
|
110
|
+
{'hydrogen_out': 8, 'oxygen_out': 1}, # Mass balance
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Complex converter with piecewise efficiency:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
variable_efficiency_converter = LinearConverter(
|
|
119
|
+
label='variable_converter',
|
|
120
|
+
inputs=[fuel_in],
|
|
121
|
+
outputs=[power_out],
|
|
122
|
+
piecewise_conversion=PiecewiseConversion(
|
|
123
|
+
{
|
|
124
|
+
'fuel_in': Piecewise(
|
|
125
|
+
[
|
|
126
|
+
Piece(0, 10), # Low load operation
|
|
127
|
+
Piece(10, 25), # High load operation
|
|
128
|
+
]
|
|
129
|
+
),
|
|
130
|
+
'power_out': Piecewise(
|
|
131
|
+
[
|
|
132
|
+
Piece(0, 3.5), # Lower efficiency at part load
|
|
133
|
+
Piece(3.5, 10), # Higher efficiency at full load
|
|
134
|
+
]
|
|
135
|
+
),
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Note:
|
|
142
|
+
Conversion factors define linear relationships where the sum of (coefficient × flow_rate)
|
|
143
|
+
equals zero for each equation: factor1×flow1 + factor2×flow2 + ... = 0
|
|
144
|
+
Conversion factors define linear relationships.
|
|
145
|
+
`{flow1: a1, flow2: a2, ...}` leads to `a1×flow_rate1 + a2×flow_rate2 + ... = 0`
|
|
146
|
+
Unfortunately the current input format doest read intuitively:
|
|
147
|
+
{"electricity": 1, "H2": 50} means that the electricity_in flow rate is multiplied by 1
|
|
148
|
+
and the hydrogen_out flow rate is multiplied by 50. THis leads to 50 electricity --> 1 H2.
|
|
149
|
+
|
|
150
|
+
The system must have fewer conversion factors than total flows (degrees of freedom > 0)
|
|
151
|
+
to avoid over-constraining the problem. For n total flows, use at most n-1 conversion factors.
|
|
152
|
+
|
|
153
|
+
When using piecewise_conversion, the converter operates on one piece at a time,
|
|
154
|
+
with binary variables determining which piece is active.
|
|
29
155
|
|
|
30
156
|
"""
|
|
31
157
|
|
|
32
158
|
def __init__(
|
|
33
159
|
self,
|
|
34
160
|
label: str,
|
|
35
|
-
inputs:
|
|
36
|
-
outputs:
|
|
37
|
-
on_off_parameters: OnOffParameters = None,
|
|
38
|
-
conversion_factors:
|
|
39
|
-
piecewise_conversion:
|
|
40
|
-
meta_data:
|
|
161
|
+
inputs: list[Flow],
|
|
162
|
+
outputs: list[Flow],
|
|
163
|
+
on_off_parameters: OnOffParameters | None = None,
|
|
164
|
+
conversion_factors: list[dict[str, NumericDataTS]] | None = None,
|
|
165
|
+
piecewise_conversion: PiecewiseConversion | None = None,
|
|
166
|
+
meta_data: dict | None = None,
|
|
41
167
|
):
|
|
42
|
-
"""
|
|
43
|
-
Args:
|
|
44
|
-
label: The label of the Element. Used to identify it in the FlowSystem
|
|
45
|
-
inputs: The input Flows
|
|
46
|
-
outputs: The output Flows
|
|
47
|
-
on_off_parameters: Information about on and off state of LinearConverter.
|
|
48
|
-
Component is On/Off, if all connected Flows are On/Off. This induces an On-Variable (binary) in all Flows!
|
|
49
|
-
If possible, use OnOffParameters in a single Flow instead to keep the number of binary variables low.
|
|
50
|
-
See class OnOffParameters.
|
|
51
|
-
conversion_factors: linear relation between flows.
|
|
52
|
-
Either 'conversion_factors' or 'piecewise_conversion' can be used!
|
|
53
|
-
piecewise_conversion: Define a piecewise linear relation between flow rates of different flows.
|
|
54
|
-
Either 'conversion_factors' or 'piecewise_conversion' can be used!
|
|
55
|
-
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
56
|
-
"""
|
|
57
168
|
super().__init__(label, inputs, outputs, on_off_parameters, meta_data=meta_data)
|
|
58
169
|
self.conversion_factors = conversion_factors or []
|
|
59
170
|
self.piecewise_conversion = piecewise_conversion
|
|
60
171
|
|
|
61
|
-
def create_model(self, model: SystemModel) ->
|
|
172
|
+
def create_model(self, model: SystemModel) -> LinearConverterModel:
|
|
62
173
|
self._plausibility_checks()
|
|
63
174
|
self.model = LinearConverterModel(model, self)
|
|
64
175
|
return self.model
|
|
@@ -92,23 +203,24 @@ class LinearConverter(Component):
|
|
|
92
203
|
f'(in flow {flow.label_full}) do not make sense together!'
|
|
93
204
|
)
|
|
94
205
|
|
|
95
|
-
def transform_data(self, flow_system:
|
|
206
|
+
def transform_data(self, flow_system: FlowSystem):
|
|
96
207
|
super().transform_data(flow_system)
|
|
97
208
|
if self.conversion_factors:
|
|
98
209
|
self.conversion_factors = self._transform_conversion_factors(flow_system)
|
|
99
210
|
if self.piecewise_conversion:
|
|
100
211
|
self.piecewise_conversion.transform_data(flow_system, f'{self.label_full}|PiecewiseConversion')
|
|
101
212
|
|
|
102
|
-
def _transform_conversion_factors(self, flow_system:
|
|
213
|
+
def _transform_conversion_factors(self, flow_system: FlowSystem) -> list[dict[str, TimeSeries]]:
|
|
103
214
|
"""macht alle Faktoren, die nicht TimeSeries sind, zu TimeSeries"""
|
|
104
215
|
list_of_conversion_factors = []
|
|
105
216
|
for idx, conversion_factor in enumerate(self.conversion_factors):
|
|
106
217
|
transformed_dict = {}
|
|
107
218
|
for flow, values in conversion_factor.items():
|
|
108
219
|
# TODO: Might be better to use the label of the component instead of the flow
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
220
|
+
ts = flow_system.create_time_series(f'{self.flows[flow].label_full}|conversion_factor{idx}', values)
|
|
221
|
+
if ts is None:
|
|
222
|
+
raise PlausibilityError(f'{self.label_full}: conversion factor for flow "{flow}" must not be None')
|
|
223
|
+
transformed_dict[flow] = ts
|
|
112
224
|
list_of_conversion_factors.append(transformed_dict)
|
|
113
225
|
return list_of_conversion_factors
|
|
114
226
|
|
|
@@ -120,7 +232,137 @@ class LinearConverter(Component):
|
|
|
120
232
|
@register_class_for_io
|
|
121
233
|
class Storage(Component):
|
|
122
234
|
"""
|
|
123
|
-
|
|
235
|
+
A Storage models the temporary storage and release of energy or material.
|
|
236
|
+
|
|
237
|
+
Storages have one incoming and one outgoing Flow, each with configurable efficiency
|
|
238
|
+
factors. They maintain a charge state variable that represents the stored amount,
|
|
239
|
+
bounded by capacity limits and evolving over time based on charging, discharging,
|
|
240
|
+
and self-discharge losses.
|
|
241
|
+
|
|
242
|
+
The storage model handles complex temporal dynamics including initial conditions,
|
|
243
|
+
final state constraints, and time-varying parameters. It supports both fixed-size
|
|
244
|
+
and investment-optimized storage systems with comprehensive techno-economic modeling.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
label: The label of the Element. Used to identify it in the FlowSystem.
|
|
248
|
+
charging: Incoming flow for loading the storage. Represents energy or material
|
|
249
|
+
flowing into the storage system.
|
|
250
|
+
discharging: Outgoing flow for unloading the storage. Represents energy or
|
|
251
|
+
material flowing out of the storage system.
|
|
252
|
+
capacity_in_flow_hours: Nominal capacity/size of the storage in flow-hours
|
|
253
|
+
(e.g., kWh for electrical storage, m³ or kg for material storage). Can be a scalar
|
|
254
|
+
for fixed capacity or InvestParameters for optimization.
|
|
255
|
+
relative_minimum_charge_state: Minimum relative charge state (0-1 range).
|
|
256
|
+
Prevents deep discharge that could damage equipment. Default is 0.
|
|
257
|
+
relative_maximum_charge_state: Maximum relative charge state (0-1 range).
|
|
258
|
+
Accounts for practical capacity limits, safety margins or temperature impacts. Default is 1.
|
|
259
|
+
initial_charge_state: Storage charge state at the beginning of the time horizon.
|
|
260
|
+
Can be numeric value or 'lastValueOfSim', which is recommended for if the initial start state is not known.
|
|
261
|
+
Default is 0.
|
|
262
|
+
minimal_final_charge_state: Minimum absolute charge state required at the end
|
|
263
|
+
of the time horizon. Useful for ensuring energy security or meeting contracts.
|
|
264
|
+
maximal_final_charge_state: Maximum absolute charge state allowed at the end
|
|
265
|
+
of the time horizon. Useful for preventing overcharge or managing inventory.
|
|
266
|
+
eta_charge: Charging efficiency factor (0-1 range). Accounts for conversion
|
|
267
|
+
losses during charging. Default is 1 (perfect efficiency).
|
|
268
|
+
eta_discharge: Discharging efficiency factor (0-1 range). Accounts for
|
|
269
|
+
conversion losses during discharging. Default is 1 (perfect efficiency).
|
|
270
|
+
relative_loss_per_hour: Self-discharge rate per hour (typically 0-0.1 range).
|
|
271
|
+
Represents standby losses, leakage, or degradation. Default is 0.
|
|
272
|
+
prevent_simultaneous_charge_and_discharge: If True, prevents charging and
|
|
273
|
+
discharging simultaneously. Increases binary variables but improves model
|
|
274
|
+
realism and solution interpretation. Default is True.
|
|
275
|
+
meta_data: Used to store additional information about the Element. Not used
|
|
276
|
+
internally, but saved in results. Only use Python native types.
|
|
277
|
+
|
|
278
|
+
Examples:
|
|
279
|
+
Battery energy storage system:
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
battery = Storage(
|
|
283
|
+
label='lithium_battery',
|
|
284
|
+
charging=battery_charge_flow,
|
|
285
|
+
discharging=battery_discharge_flow,
|
|
286
|
+
capacity_in_flow_hours=100, # 100 kWh capacity
|
|
287
|
+
eta_charge=0.95, # 95% charging efficiency
|
|
288
|
+
eta_discharge=0.95, # 95% discharging efficiency
|
|
289
|
+
relative_loss_per_hour=0.001, # 0.1% loss per hour
|
|
290
|
+
relative_minimum_charge_state=0.1, # Never below 10% SOC
|
|
291
|
+
relative_maximum_charge_state=0.9, # Never above 90% SOC
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Thermal storage with cycling constraints:
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
thermal_storage = Storage(
|
|
299
|
+
label='hot_water_tank',
|
|
300
|
+
charging=heat_input,
|
|
301
|
+
discharging=heat_output,
|
|
302
|
+
capacity_in_flow_hours=500, # 500 kWh thermal capacity
|
|
303
|
+
initial_charge_state=250, # Start half full
|
|
304
|
+
# Impact of temperature on energy capacity
|
|
305
|
+
relative_maximum_charge_state=water_temperature_spread / rated_temeprature_spread,
|
|
306
|
+
eta_charge=0.90, # Heat exchanger losses
|
|
307
|
+
eta_discharge=0.85, # Distribution losses
|
|
308
|
+
relative_loss_per_hour=0.02, # 2% thermal loss per hour
|
|
309
|
+
prevent_simultaneous_charge_and_discharge=True,
|
|
310
|
+
)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Pumped hydro storage with investment optimization:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
pumped_hydro = Storage(
|
|
317
|
+
label='pumped_hydro',
|
|
318
|
+
charging=pump_flow,
|
|
319
|
+
discharging=turbine_flow,
|
|
320
|
+
capacity_in_flow_hours=InvestParameters(
|
|
321
|
+
minimum_size=1000, # Minimum economic scale
|
|
322
|
+
maximum_size=10000, # Site constraints
|
|
323
|
+
specific_effects={'cost': 150}, # €150/MWh capacity
|
|
324
|
+
fix_effects={'cost': 50_000_000}, # €50M fixed costs
|
|
325
|
+
),
|
|
326
|
+
eta_charge=0.85, # Pumping efficiency
|
|
327
|
+
eta_discharge=0.90, # Turbine efficiency
|
|
328
|
+
initial_charge_state='lastValueOfSim', # Ensuring no deficit compared to start
|
|
329
|
+
relative_loss_per_hour=0.0001, # Minimal evaporation
|
|
330
|
+
)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Material storage with inventory management:
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
fuel_storage = Storage(
|
|
337
|
+
label='natural_gas_storage',
|
|
338
|
+
charging=gas_injection,
|
|
339
|
+
discharging=gas_withdrawal,
|
|
340
|
+
capacity_in_flow_hours=10000, # 10,000 m³ storage volume
|
|
341
|
+
initial_charge_state=3000, # Start with 3,000 m³
|
|
342
|
+
minimal_final_charge_state=1000, # Strategic reserve
|
|
343
|
+
maximal_final_charge_state=9000, # Prevent overflow
|
|
344
|
+
eta_charge=0.98, # Compression losses
|
|
345
|
+
eta_discharge=0.95, # Pressure reduction losses
|
|
346
|
+
relative_loss_per_hour=0.0005, # 0.05% leakage per hour
|
|
347
|
+
prevent_simultaneous_charge_and_discharge=False, # Allow flow-through
|
|
348
|
+
)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Note:
|
|
352
|
+
Charge state evolution follows the equation:
|
|
353
|
+
charge[t+1] = charge[t] × (1-loss_rate)^hours_per_step +
|
|
354
|
+
charge_flow[t] × eta_charge × hours_per_step -
|
|
355
|
+
discharge_flow[t] × hours_per_step / eta_discharge
|
|
356
|
+
|
|
357
|
+
All efficiency parameters (eta_charge, eta_discharge) are dimensionless (0-1 range).
|
|
358
|
+
The relative_loss_per_hour parameter represents exponential decay per hour.
|
|
359
|
+
|
|
360
|
+
When prevent_simultaneous_charge_and_discharge is True, binary variables are
|
|
361
|
+
created to enforce mutual exclusivity, which increases solution time but
|
|
362
|
+
prevents unrealistic simultaneous charging and discharging.
|
|
363
|
+
|
|
364
|
+
Initial and final charge state constraints use absolute values (not relative),
|
|
365
|
+
matching the capacity_in_flow_hours units.
|
|
124
366
|
"""
|
|
125
367
|
|
|
126
368
|
def __init__(
|
|
@@ -128,43 +370,18 @@ class Storage(Component):
|
|
|
128
370
|
label: str,
|
|
129
371
|
charging: Flow,
|
|
130
372
|
discharging: Flow,
|
|
131
|
-
capacity_in_flow_hours:
|
|
373
|
+
capacity_in_flow_hours: Scalar | InvestParameters,
|
|
132
374
|
relative_minimum_charge_state: NumericData = 0,
|
|
133
375
|
relative_maximum_charge_state: NumericData = 1,
|
|
134
|
-
initial_charge_state:
|
|
135
|
-
minimal_final_charge_state:
|
|
136
|
-
maximal_final_charge_state:
|
|
376
|
+
initial_charge_state: Scalar | Literal['lastValueOfSim'] = 0,
|
|
377
|
+
minimal_final_charge_state: Scalar | None = None,
|
|
378
|
+
maximal_final_charge_state: Scalar | None = None,
|
|
137
379
|
eta_charge: NumericData = 1,
|
|
138
380
|
eta_discharge: NumericData = 1,
|
|
139
381
|
relative_loss_per_hour: NumericData = 0,
|
|
140
382
|
prevent_simultaneous_charge_and_discharge: bool = True,
|
|
141
|
-
meta_data:
|
|
383
|
+
meta_data: dict | None = None,
|
|
142
384
|
):
|
|
143
|
-
"""
|
|
144
|
-
Storages have one incoming and one outgoing Flow each with an efficiency.
|
|
145
|
-
Further, storages have a `size` and a `charge_state`.
|
|
146
|
-
Similarly to the flow-rate of a Flow, the `size` combined with a relative upper and lower bound
|
|
147
|
-
limits the `charge_state` of the storage.
|
|
148
|
-
|
|
149
|
-
For mathematical details take a look at our online documentation
|
|
150
|
-
|
|
151
|
-
Args:
|
|
152
|
-
label: The label of the Element. Used to identify it in the FlowSystem
|
|
153
|
-
charging: ingoing flow.
|
|
154
|
-
discharging: outgoing flow.
|
|
155
|
-
capacity_in_flow_hours: nominal capacity/size of the storage
|
|
156
|
-
relative_minimum_charge_state: minimum relative charge state. The default is 0.
|
|
157
|
-
relative_maximum_charge_state: maximum relative charge state. The default is 1.
|
|
158
|
-
initial_charge_state: storage charge_state at the beginning. The default is 0.
|
|
159
|
-
minimal_final_charge_state: minimal value of chargeState at the end of timeseries.
|
|
160
|
-
maximal_final_charge_state: maximal value of chargeState at the end of timeseries.
|
|
161
|
-
eta_charge: efficiency factor of charging/loading. The default is 1.
|
|
162
|
-
eta_discharge: efficiency factor of uncharging/unloading. The default is 1.
|
|
163
|
-
relative_loss_per_hour: loss per chargeState-Unit per hour. The default is 0.
|
|
164
|
-
prevent_simultaneous_charge_and_discharge: If True, loading and unloading at the same time is not possible.
|
|
165
|
-
Increases the number of binary variables, but is recommended for easier evaluation. The default is True.
|
|
166
|
-
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
167
|
-
"""
|
|
168
385
|
# TODO: fixed_relative_chargeState implementieren
|
|
169
386
|
super().__init__(
|
|
170
387
|
label,
|
|
@@ -189,12 +406,12 @@ class Storage(Component):
|
|
|
189
406
|
self.relative_loss_per_hour: NumericDataTS = relative_loss_per_hour
|
|
190
407
|
self.prevent_simultaneous_charge_and_discharge = prevent_simultaneous_charge_and_discharge
|
|
191
408
|
|
|
192
|
-
def create_model(self, model: SystemModel) ->
|
|
409
|
+
def create_model(self, model: SystemModel) -> StorageModel:
|
|
193
410
|
self._plausibility_checks()
|
|
194
411
|
self.model = StorageModel(model, self)
|
|
195
412
|
return self.model
|
|
196
413
|
|
|
197
|
-
def transform_data(self, flow_system:
|
|
414
|
+
def transform_data(self, flow_system: FlowSystem) -> None:
|
|
198
415
|
super().transform_data(flow_system)
|
|
199
416
|
self.relative_minimum_charge_state = flow_system.create_time_series(
|
|
200
417
|
f'{self.label_full}|relative_minimum_charge_state',
|
|
@@ -231,20 +448,15 @@ class Storage(Component):
|
|
|
231
448
|
maximum_capacity = self.capacity_in_flow_hours
|
|
232
449
|
minimum_capacity = self.capacity_in_flow_hours
|
|
233
450
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
maximum_inital_capacity = minimum_capacity * self.relative_maximum_charge_state.isel(time=1)
|
|
238
|
-
|
|
239
|
-
if self.initial_charge_state > maximum_inital_capacity:
|
|
451
|
+
minimum_initial_capacity = maximum_capacity * self.relative_minimum_charge_state.isel(time=1)
|
|
452
|
+
maximum_initial_capacity = minimum_capacity * self.relative_maximum_charge_state.isel(time=1)
|
|
453
|
+
if self.initial_charge_state > maximum_initial_capacity:
|
|
240
454
|
raise ValueError(
|
|
241
|
-
f'{self.label_full}: {self.initial_charge_state=} '
|
|
242
|
-
f'is above allowed maximum charge_state {maximum_inital_capacity}'
|
|
455
|
+
f'{self.label_full}: {self.initial_charge_state=} is above allowed maximum {maximum_initial_capacity}'
|
|
243
456
|
)
|
|
244
|
-
if self.initial_charge_state <
|
|
457
|
+
if self.initial_charge_state < minimum_initial_capacity:
|
|
245
458
|
raise ValueError(
|
|
246
|
-
f'{self.label_full}: {self.initial_charge_state=} '
|
|
247
|
-
f'is below allowed minimum charge_state {minimum_inital_capacity}'
|
|
459
|
+
f'{self.label_full}: {self.initial_charge_state=} is below allowed minimum {minimum_initial_capacity}'
|
|
248
460
|
)
|
|
249
461
|
elif self.initial_charge_state != 'lastValueOfSim':
|
|
250
462
|
raise ValueError(f'{self.label_full}: {self.initial_charge_state=} has an invalid value')
|
|
@@ -252,42 +464,127 @@ class Storage(Component):
|
|
|
252
464
|
|
|
253
465
|
@register_class_for_io
|
|
254
466
|
class Transmission(Component):
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
467
|
+
"""
|
|
468
|
+
Models transmission infrastructure that transports flows between two locations with losses.
|
|
469
|
+
|
|
470
|
+
Transmission components represent physical infrastructure like pipes, cables,
|
|
471
|
+
transmission lines, or conveyor systems that transport energy or materials between
|
|
472
|
+
two points. They can model both unidirectional and bidirectional flow with
|
|
473
|
+
configurable loss mechanisms and operational constraints.
|
|
474
|
+
|
|
475
|
+
The component supports complex transmission scenarios including relative losses
|
|
476
|
+
(proportional to flow), absolute losses (fixed when active), and bidirectional
|
|
477
|
+
operation with flow direction constraints.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
label: The label of the Element. Used to identify it in the FlowSystem.
|
|
481
|
+
in1: The primary inflow (side A). Pass InvestParameters here for capacity optimization.
|
|
482
|
+
out1: The primary outflow (side B).
|
|
483
|
+
in2: Optional secondary inflow (side B) for bidirectional operation.
|
|
484
|
+
If in1 has InvestParameters, in2 will automatically have matching capacity.
|
|
485
|
+
out2: Optional secondary outflow (side A) for bidirectional operation.
|
|
486
|
+
relative_losses: Proportional losses as fraction of throughput (e.g., 0.02 for 2% loss).
|
|
487
|
+
Applied as: output = input × (1 - relative_losses)
|
|
488
|
+
absolute_losses: Fixed losses that occur when transmission is active.
|
|
489
|
+
Automatically creates binary variables for on/off states.
|
|
490
|
+
on_off_parameters: Parameters defining binary operation constraints and costs.
|
|
491
|
+
prevent_simultaneous_flows_in_both_directions: If True, prevents simultaneous
|
|
492
|
+
flow in both directions. Increases binary variables but reflects physical
|
|
493
|
+
reality for most transmission systems. Default is True.
|
|
494
|
+
meta_data: Used to store additional information. Not used internally but saved
|
|
495
|
+
in results. Only use Python native types.
|
|
496
|
+
|
|
497
|
+
Examples:
|
|
498
|
+
Simple electrical transmission line:
|
|
499
|
+
|
|
500
|
+
```python
|
|
501
|
+
power_line = Transmission(
|
|
502
|
+
label='110kv_line',
|
|
503
|
+
in1=substation_a_out,
|
|
504
|
+
out1=substation_b_in,
|
|
505
|
+
relative_losses=0.03, # 3% line losses
|
|
506
|
+
)
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Bidirectional natural gas pipeline:
|
|
510
|
+
|
|
511
|
+
```python
|
|
512
|
+
gas_pipeline = Transmission(
|
|
513
|
+
label='interstate_pipeline',
|
|
514
|
+
in1=compressor_station_a,
|
|
515
|
+
out1=distribution_hub_b,
|
|
516
|
+
in2=compressor_station_b,
|
|
517
|
+
out2=distribution_hub_a,
|
|
518
|
+
relative_losses=0.005, # 0.5% friction losses
|
|
519
|
+
absolute_losses=50, # 50 kW compressor power when active
|
|
520
|
+
prevent_simultaneous_flows_in_both_directions=True,
|
|
521
|
+
)
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
District heating network with investment optimization:
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
heating_network = Transmission(
|
|
528
|
+
label='dh_main_line',
|
|
529
|
+
in1=Flow(
|
|
530
|
+
label='heat_supply',
|
|
531
|
+
bus=central_plant_bus,
|
|
532
|
+
size=InvestParameters(
|
|
533
|
+
minimum_size=1000, # Minimum 1 MW capacity
|
|
534
|
+
maximum_size=10000, # Maximum 10 MW capacity
|
|
535
|
+
specific_effects={'cost': 200}, # €200/kW capacity
|
|
536
|
+
fix_effects={'cost': 500000}, # €500k fixed installation
|
|
537
|
+
),
|
|
538
|
+
),
|
|
539
|
+
out1=district_heat_demand,
|
|
540
|
+
relative_losses=0.15, # 15% thermal losses in distribution
|
|
541
|
+
)
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Material conveyor with on/off operation:
|
|
545
|
+
|
|
546
|
+
```python
|
|
547
|
+
conveyor_belt = Transmission(
|
|
548
|
+
label='material_transport',
|
|
549
|
+
in1=loading_station,
|
|
550
|
+
out1=unloading_station,
|
|
551
|
+
absolute_losses=25, # 25 kW motor power when running
|
|
552
|
+
on_off_parameters=OnOffParameters(
|
|
553
|
+
effects_per_switch_on={'maintenance': 0.1},
|
|
554
|
+
consecutive_on_hours_min=2, # Minimum 2-hour operation
|
|
555
|
+
switch_on_total_max=10, # Maximum 10 starts per day
|
|
556
|
+
),
|
|
557
|
+
)
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Note:
|
|
561
|
+
The transmission equation balances flows with losses:
|
|
562
|
+
output_flow = input_flow × (1 - relative_losses) - absolute_losses
|
|
563
|
+
|
|
564
|
+
For bidirectional transmission, each direction has independent loss calculations.
|
|
565
|
+
|
|
566
|
+
When using InvestParameters on in1, the capacity automatically applies to in2
|
|
567
|
+
to maintain consistent bidirectional capacity without additional investment variables.
|
|
568
|
+
|
|
569
|
+
Absolute losses force the creation of binary on/off variables, which increases
|
|
570
|
+
computational complexity but enables realistic modeling of equipment with
|
|
571
|
+
standby power consumption.
|
|
572
|
+
|
|
573
|
+
"""
|
|
260
574
|
|
|
261
575
|
def __init__(
|
|
262
576
|
self,
|
|
263
577
|
label: str,
|
|
264
578
|
in1: Flow,
|
|
265
579
|
out1: Flow,
|
|
266
|
-
in2:
|
|
267
|
-
out2:
|
|
268
|
-
relative_losses:
|
|
269
|
-
absolute_losses:
|
|
270
|
-
on_off_parameters: OnOffParameters = None,
|
|
580
|
+
in2: Flow | None = None,
|
|
581
|
+
out2: Flow | None = None,
|
|
582
|
+
relative_losses: NumericDataTS = 0,
|
|
583
|
+
absolute_losses: NumericDataTS | None = None,
|
|
584
|
+
on_off_parameters: OnOffParameters | None = None,
|
|
271
585
|
prevent_simultaneous_flows_in_both_directions: bool = True,
|
|
272
|
-
meta_data:
|
|
586
|
+
meta_data: dict | None = None,
|
|
273
587
|
):
|
|
274
|
-
"""
|
|
275
|
-
Initializes a Transmission component (Pipe, cable, ...) that models the flows between two sides
|
|
276
|
-
with potential losses.
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
label: The label of the Element. Used to identify it in the FlowSystem
|
|
280
|
-
in1: The inflow at side A. Pass InvestmentParameters here.
|
|
281
|
-
out1: The outflow at side B.
|
|
282
|
-
in2: The optional inflow at side B.
|
|
283
|
-
If in1 got InvestParameters, the size of this Flow will be equal to in1 (with no extra effects!)
|
|
284
|
-
out2: The optional outflow at side A.
|
|
285
|
-
relative_losses: The relative loss between inflow and outflow, e.g., 0.02 for 2% loss.
|
|
286
|
-
absolute_losses: The absolute loss, occur only when the Flow is on. Induces the creation of the ON-Variable
|
|
287
|
-
on_off_parameters: Parameters defining the on/off behavior of the component.
|
|
288
|
-
prevent_simultaneous_flows_in_both_directions: If True, inflow and outflow are not allowed to be both non-zero at same timestep.
|
|
289
|
-
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
290
|
-
"""
|
|
291
588
|
super().__init__(
|
|
292
589
|
label,
|
|
293
590
|
inputs=[flow for flow in (in1, in2) if flow is not None],
|
|
@@ -325,12 +622,12 @@ class Transmission(Component):
|
|
|
325
622
|
'Please use Flow in1. The size of in2 is equal to in1. THis is handled internally'
|
|
326
623
|
)
|
|
327
624
|
|
|
328
|
-
def create_model(self, model) ->
|
|
625
|
+
def create_model(self, model) -> TransmissionModel:
|
|
329
626
|
self._plausibility_checks()
|
|
330
627
|
self.model = TransmissionModel(model, self)
|
|
331
628
|
return self.model
|
|
332
629
|
|
|
333
|
-
def transform_data(self, flow_system:
|
|
630
|
+
def transform_data(self, flow_system: FlowSystem) -> None:
|
|
334
631
|
super().transform_data(flow_system)
|
|
335
632
|
self.relative_losses = flow_system.create_time_series(
|
|
336
633
|
f'{self.label_full}|relative_losses', self.relative_losses
|
|
@@ -344,7 +641,7 @@ class TransmissionModel(ComponentModel):
|
|
|
344
641
|
def __init__(self, model: SystemModel, element: Transmission):
|
|
345
642
|
super().__init__(model, element)
|
|
346
643
|
self.element: Transmission = element
|
|
347
|
-
self.on_off:
|
|
644
|
+
self.on_off: OnOffModel | None = None
|
|
348
645
|
|
|
349
646
|
def do_modeling(self):
|
|
350
647
|
"""Initiates all FlowModels"""
|
|
@@ -402,8 +699,8 @@ class LinearConverterModel(ComponentModel):
|
|
|
402
699
|
def __init__(self, model: SystemModel, element: LinearConverter):
|
|
403
700
|
super().__init__(model, element)
|
|
404
701
|
self.element: LinearConverter = element
|
|
405
|
-
self.on_off:
|
|
406
|
-
self.piecewise_conversion:
|
|
702
|
+
self.on_off: OnOffModel | None = None
|
|
703
|
+
self.piecewise_conversion: PiecewiseModel | None = None
|
|
407
704
|
|
|
408
705
|
def do_modeling(self):
|
|
409
706
|
super().do_modeling()
|
|
@@ -416,8 +713,8 @@ class LinearConverterModel(ComponentModel):
|
|
|
416
713
|
# für alle linearen Gleichungen:
|
|
417
714
|
for i, conv_factors in enumerate(self.element.conversion_factors):
|
|
418
715
|
used_flows = set([self.element.flows[flow_label] for flow_label in conv_factors])
|
|
419
|
-
used_inputs:
|
|
420
|
-
used_outputs:
|
|
716
|
+
used_inputs: set[Flow] = all_input_flows & used_flows
|
|
717
|
+
used_outputs: set[Flow] = all_output_flows & used_flows
|
|
421
718
|
|
|
422
719
|
self.add(
|
|
423
720
|
self._model.add_constraints(
|
|
@@ -452,9 +749,9 @@ class StorageModel(ComponentModel):
|
|
|
452
749
|
def __init__(self, model: SystemModel, element: Storage):
|
|
453
750
|
super().__init__(model, element)
|
|
454
751
|
self.element: Storage = element
|
|
455
|
-
self.charge_state:
|
|
456
|
-
self.netto_discharge:
|
|
457
|
-
self._investment:
|
|
752
|
+
self.charge_state: linopy.Variable | None = None
|
|
753
|
+
self.netto_discharge: linopy.Variable | None = None
|
|
754
|
+
self._investment: InvestmentModel | None = None
|
|
458
755
|
|
|
459
756
|
def do_modeling(self):
|
|
460
757
|
super().do_modeling()
|
|
@@ -557,7 +854,7 @@ class StorageModel(ComponentModel):
|
|
|
557
854
|
)
|
|
558
855
|
|
|
559
856
|
@property
|
|
560
|
-
def absolute_charge_state_bounds(self) ->
|
|
857
|
+
def absolute_charge_state_bounds(self) -> tuple[NumericData, NumericData]:
|
|
561
858
|
relative_lower_bound, relative_upper_bound = self.relative_charge_state_bounds
|
|
562
859
|
if not isinstance(self.element.capacity_in_flow_hours, InvestParameters):
|
|
563
860
|
return (
|
|
@@ -571,7 +868,7 @@ class StorageModel(ComponentModel):
|
|
|
571
868
|
)
|
|
572
869
|
|
|
573
870
|
@property
|
|
574
|
-
def relative_charge_state_bounds(self) ->
|
|
871
|
+
def relative_charge_state_bounds(self) -> tuple[NumericData, NumericData]:
|
|
575
872
|
return (
|
|
576
873
|
self.element.relative_minimum_charge_state.active_data,
|
|
577
874
|
self.element.relative_maximum_charge_state.active_data,
|
|
@@ -581,32 +878,105 @@ class StorageModel(ComponentModel):
|
|
|
581
878
|
@register_class_for_io
|
|
582
879
|
class SourceAndSink(Component):
|
|
583
880
|
"""
|
|
584
|
-
|
|
881
|
+
A SourceAndSink combines both supply and demand capabilities in a single component.
|
|
882
|
+
|
|
883
|
+
SourceAndSink components can both consume AND provide energy or material flows
|
|
884
|
+
from and to the system, making them ideal for modeling markets, (simple) storage facilities,
|
|
885
|
+
or bidirectional grid connections where buying and selling occur at the same location.
|
|
886
|
+
|
|
887
|
+
Args:
|
|
888
|
+
label: The label of the Element. Used to identify it in the FlowSystem.
|
|
889
|
+
inputs: Input-flows into the SourceAndSink representing consumption/demand side.
|
|
890
|
+
outputs: Output-flows from the SourceAndSink representing supply/generation side.
|
|
891
|
+
prevent_simultaneous_flow_rates: If True, prevents simultaneous input and output
|
|
892
|
+
flows. This enforces that the component operates either as a source OR sink
|
|
893
|
+
at any given time, but not both simultaneously. Default is True.
|
|
894
|
+
meta_data: Used to store additional information about the Element. Not used
|
|
895
|
+
internally but saved in results. Only use Python native types.
|
|
896
|
+
|
|
897
|
+
Examples:
|
|
898
|
+
Electricity market connection (buy/sell to grid):
|
|
899
|
+
|
|
900
|
+
```python
|
|
901
|
+
electricity_market = SourceAndSink(
|
|
902
|
+
label='grid_connection',
|
|
903
|
+
inputs=[electricity_purchase], # Buy from grid
|
|
904
|
+
outputs=[electricity_sale], # Sell to grid
|
|
905
|
+
prevent_simultaneous_flow_rates=True, # Can't buy and sell simultaneously
|
|
906
|
+
)
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
Natural gas storage facility:
|
|
910
|
+
|
|
911
|
+
```python
|
|
912
|
+
gas_storage_facility = SourceAndSink(
|
|
913
|
+
label='underground_gas_storage',
|
|
914
|
+
inputs=[gas_injection_flow], # Inject gas into storage
|
|
915
|
+
outputs=[gas_withdrawal_flow], # Withdraw gas from storage
|
|
916
|
+
prevent_simultaneous_flow_rates=True, # Injection or withdrawal, not both
|
|
917
|
+
)
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
District heating network connection:
|
|
921
|
+
|
|
922
|
+
```python
|
|
923
|
+
dh_connection = SourceAndSink(
|
|
924
|
+
label='district_heating_tie',
|
|
925
|
+
inputs=[heat_purchase_flow], # Purchase heat from network
|
|
926
|
+
outputs=[heat_sale_flow], # Sell excess heat to network
|
|
927
|
+
prevent_simultaneous_flow_rates=False, # May allow simultaneous flows
|
|
928
|
+
)
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
Industrial waste heat exchange:
|
|
932
|
+
|
|
933
|
+
```python
|
|
934
|
+
waste_heat_exchange = SourceAndSink(
|
|
935
|
+
label='industrial_heat_hub',
|
|
936
|
+
inputs=[
|
|
937
|
+
waste_heat_input_a, # Receive waste heat from process A
|
|
938
|
+
waste_heat_input_b, # Receive waste heat from process B
|
|
939
|
+
],
|
|
940
|
+
outputs=[
|
|
941
|
+
useful_heat_supply_c, # Supply heat to process C
|
|
942
|
+
useful_heat_supply_d, # Supply heat to process D
|
|
943
|
+
],
|
|
944
|
+
prevent_simultaneous_flow_rates=False, # Multiple simultaneous flows allowed
|
|
945
|
+
)
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
Note:
|
|
949
|
+
When prevent_simultaneous_flow_rates is True, binary variables are created to
|
|
950
|
+
ensure mutually exclusive operation between input and output flows, which
|
|
951
|
+
increases computational complexity but reflects realistic market or storage
|
|
952
|
+
operation constraints.
|
|
953
|
+
|
|
954
|
+
SourceAndSink is particularly useful for modeling:
|
|
955
|
+
- Energy markets with bidirectional trading
|
|
956
|
+
- Storage facilities with injection/withdrawal operations
|
|
957
|
+
- Grid tie points with import/export capabilities
|
|
958
|
+
- Waste exchange networks with multiple participants
|
|
959
|
+
|
|
960
|
+
Deprecated:
|
|
961
|
+
The deprecated `sink` and `source` kwargs are accepted for compatibility but will be removed in future releases.
|
|
585
962
|
"""
|
|
586
963
|
|
|
587
964
|
def __init__(
|
|
588
965
|
self,
|
|
589
966
|
label: str,
|
|
590
|
-
inputs:
|
|
591
|
-
outputs:
|
|
967
|
+
inputs: list[Flow] | None = None,
|
|
968
|
+
outputs: list[Flow] | None = None,
|
|
592
969
|
prevent_simultaneous_flow_rates: bool = True,
|
|
593
|
-
meta_data:
|
|
970
|
+
meta_data: dict | None = None,
|
|
594
971
|
**kwargs,
|
|
595
972
|
):
|
|
596
|
-
"""
|
|
597
|
-
Args:
|
|
598
|
-
label: The label of the Element. Used to identify it in the FlowSystem
|
|
599
|
-
outputs: output-flows of this component
|
|
600
|
-
inputs: input-flows of this component
|
|
601
|
-
prevent_simultaneous_flow_rates: If True, inflow and outflow can not be active simultaniously.
|
|
602
|
-
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
603
|
-
"""
|
|
604
973
|
source = kwargs.pop('source', None)
|
|
605
974
|
sink = kwargs.pop('sink', None)
|
|
606
975
|
prevent_simultaneous_sink_and_source = kwargs.pop('prevent_simultaneous_sink_and_source', None)
|
|
607
976
|
if source is not None:
|
|
608
|
-
warnings.
|
|
977
|
+
warnings.warn(
|
|
609
978
|
'The use of the source argument is deprecated. Use the outputs argument instead.',
|
|
979
|
+
DeprecationWarning,
|
|
610
980
|
stacklevel=2,
|
|
611
981
|
)
|
|
612
982
|
if outputs is not None:
|
|
@@ -614,17 +984,19 @@ class SourceAndSink(Component):
|
|
|
614
984
|
outputs = [source]
|
|
615
985
|
|
|
616
986
|
if sink is not None:
|
|
617
|
-
warnings.
|
|
618
|
-
'The use of the sink argument is deprecated. Use the
|
|
987
|
+
warnings.warn(
|
|
988
|
+
'The use of the sink argument is deprecated. Use the inputs argument instead.',
|
|
989
|
+
DeprecationWarning,
|
|
619
990
|
stacklevel=2,
|
|
620
991
|
)
|
|
621
992
|
if inputs is not None:
|
|
622
|
-
raise ValueError('Either sink or
|
|
993
|
+
raise ValueError('Either sink or inputs can be specified, but not both.')
|
|
623
994
|
inputs = [sink]
|
|
624
995
|
|
|
625
996
|
if prevent_simultaneous_sink_and_source is not None:
|
|
626
|
-
warnings.
|
|
997
|
+
warnings.warn(
|
|
627
998
|
'The use of the prevent_simultaneous_sink_and_source argument is deprecated. Use the prevent_simultaneous_flow_rates argument instead.',
|
|
999
|
+
DeprecationWarning,
|
|
628
1000
|
stacklevel=2,
|
|
629
1001
|
)
|
|
630
1002
|
prevent_simultaneous_flow_rates = prevent_simultaneous_sink_and_source
|
|
@@ -633,7 +1005,7 @@ class SourceAndSink(Component):
|
|
|
633
1005
|
label,
|
|
634
1006
|
inputs=inputs,
|
|
635
1007
|
outputs=outputs,
|
|
636
|
-
prevent_simultaneous_flows=inputs + outputs if prevent_simultaneous_flow_rates
|
|
1008
|
+
prevent_simultaneous_flows=(inputs or []) + (outputs or []) if prevent_simultaneous_flow_rates else None,
|
|
637
1009
|
meta_data=meta_data,
|
|
638
1010
|
)
|
|
639
1011
|
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
@@ -650,7 +1022,7 @@ class SourceAndSink(Component):
|
|
|
650
1022
|
@property
|
|
651
1023
|
def sink(self) -> Flow:
|
|
652
1024
|
warnings.warn(
|
|
653
|
-
'The sink property is deprecated. Use the
|
|
1025
|
+
'The sink property is deprecated. Use the inputs property instead.',
|
|
654
1026
|
DeprecationWarning,
|
|
655
1027
|
stacklevel=2,
|
|
656
1028
|
)
|
|
@@ -668,20 +1040,88 @@ class SourceAndSink(Component):
|
|
|
668
1040
|
|
|
669
1041
|
@register_class_for_io
|
|
670
1042
|
class Source(Component):
|
|
1043
|
+
"""
|
|
1044
|
+
A Source generates or provides energy or material flows into the system.
|
|
1045
|
+
|
|
1046
|
+
Sources represent supply points like power plants, fuel suppliers, renewable
|
|
1047
|
+
energy sources, or any system boundary where flows originate. They provide
|
|
1048
|
+
unlimited supply capability subject to flow constraints, demand patterns and effects.
|
|
1049
|
+
|
|
1050
|
+
Args:
|
|
1051
|
+
label: The label of the Element. Used to identify it in the FlowSystem.
|
|
1052
|
+
outputs: Output-flows from the source. Can be single flow or list of flows
|
|
1053
|
+
for sources providing multiple commodities or services.
|
|
1054
|
+
meta_data: Used to store additional information about the Element. Not used
|
|
1055
|
+
internally but saved in results. Only use Python native types.
|
|
1056
|
+
prevent_simultaneous_flow_rates: If True, only one output flow can be active
|
|
1057
|
+
at a time. Useful for modeling mutually exclusive supply options. Default is False.
|
|
1058
|
+
|
|
1059
|
+
Examples:
|
|
1060
|
+
Simple electricity grid connection:
|
|
1061
|
+
|
|
1062
|
+
```python
|
|
1063
|
+
grid_source = Source(label='electrical_grid', outputs=[grid_electricity_flow])
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
Natural gas supply with cost and capacity constraints:
|
|
1067
|
+
|
|
1068
|
+
```python
|
|
1069
|
+
gas_supply = Source(
|
|
1070
|
+
label='gas_network',
|
|
1071
|
+
outputs=[
|
|
1072
|
+
Flow(
|
|
1073
|
+
label='natural_gas_flow',
|
|
1074
|
+
bus=gas_bus,
|
|
1075
|
+
size=1000, # Maximum 1000 kW supply capacity
|
|
1076
|
+
effects_per_flow_hour={'cost': 0.04}, # €0.04/kWh gas cost
|
|
1077
|
+
)
|
|
1078
|
+
],
|
|
1079
|
+
)
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
Multi-fuel power plant with switching constraints:
|
|
1083
|
+
|
|
1084
|
+
```python
|
|
1085
|
+
multi_fuel_plant = Source(
|
|
1086
|
+
label='flexible_generator',
|
|
1087
|
+
outputs=[coal_electricity, gas_electricity, biomass_electricity],
|
|
1088
|
+
prevent_simultaneous_flow_rates=True, # Can only use one fuel at a time
|
|
1089
|
+
)
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
Renewable energy source with investment optimization:
|
|
1093
|
+
|
|
1094
|
+
```python
|
|
1095
|
+
solar_farm = Source(
|
|
1096
|
+
label='solar_pv',
|
|
1097
|
+
outputs=[
|
|
1098
|
+
Flow(
|
|
1099
|
+
label='solar_power',
|
|
1100
|
+
bus=electricity_bus,
|
|
1101
|
+
size=InvestParameters(
|
|
1102
|
+
minimum_size=0,
|
|
1103
|
+
maximum_size=50000, # Up to 50 MW
|
|
1104
|
+
specific_effects={'cost': 800}, # €800/kW installed
|
|
1105
|
+
fix_effects={'cost': 100000}, # €100k development costs
|
|
1106
|
+
),
|
|
1107
|
+
fixed_relative_profile=solar_profile, # Hourly generation profile
|
|
1108
|
+
)
|
|
1109
|
+
],
|
|
1110
|
+
)
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
Deprecated:
|
|
1114
|
+
The deprecated `source` kwarg is accepted for compatibility but will be removed in future releases.
|
|
1115
|
+
"""
|
|
1116
|
+
|
|
671
1117
|
def __init__(
|
|
672
1118
|
self,
|
|
673
1119
|
label: str,
|
|
674
|
-
outputs:
|
|
675
|
-
meta_data:
|
|
1120
|
+
outputs: list[Flow] | None = None,
|
|
1121
|
+
meta_data: dict | None = None,
|
|
676
1122
|
prevent_simultaneous_flow_rates: bool = False,
|
|
677
|
-
**kwargs
|
|
1123
|
+
**kwargs,
|
|
678
1124
|
):
|
|
679
|
-
"""
|
|
680
|
-
Args:
|
|
681
|
-
label: The label of the Element. Used to identify it in the FlowSystem
|
|
682
|
-
outputs: output-flows of source
|
|
683
|
-
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
684
|
-
"""
|
|
685
1125
|
source = kwargs.pop('source', None)
|
|
686
1126
|
if source is not None:
|
|
687
1127
|
warnings.warn(
|
|
@@ -694,7 +1134,12 @@ class Source(Component):
|
|
|
694
1134
|
outputs = [source]
|
|
695
1135
|
|
|
696
1136
|
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
697
|
-
super().__init__(
|
|
1137
|
+
super().__init__(
|
|
1138
|
+
label,
|
|
1139
|
+
outputs=outputs,
|
|
1140
|
+
meta_data=meta_data,
|
|
1141
|
+
prevent_simultaneous_flows=outputs if prevent_simultaneous_flow_rates else None,
|
|
1142
|
+
)
|
|
698
1143
|
|
|
699
1144
|
@property
|
|
700
1145
|
def source(self) -> Flow:
|
|
@@ -708,38 +1153,126 @@ class Source(Component):
|
|
|
708
1153
|
|
|
709
1154
|
@register_class_for_io
|
|
710
1155
|
class Sink(Component):
|
|
1156
|
+
"""
|
|
1157
|
+
A Sink consumes energy or material flows from the system.
|
|
1158
|
+
|
|
1159
|
+
Sinks represent demand points like electrical loads, heat demands, material
|
|
1160
|
+
consumption, or any system boundary where flows terminate. They provide
|
|
1161
|
+
unlimited consumption capability subject to flow constraints, demand patterns and effects.
|
|
1162
|
+
|
|
1163
|
+
Args:
|
|
1164
|
+
label: The label of the Element. Used to identify it in the FlowSystem.
|
|
1165
|
+
inputs: Input-flows into the sink. Can be single flow or list of flows
|
|
1166
|
+
for sinks consuming multiple commodities or services.
|
|
1167
|
+
meta_data: Used to store additional information about the Element. Not used
|
|
1168
|
+
internally but saved in results. Only use Python native types.
|
|
1169
|
+
prevent_simultaneous_flow_rates: If True, only one input flow can be active
|
|
1170
|
+
at a time. Useful for modeling mutually exclusive consumption options. Default is False.
|
|
1171
|
+
|
|
1172
|
+
Examples:
|
|
1173
|
+
Simple electrical demand:
|
|
1174
|
+
|
|
1175
|
+
```python
|
|
1176
|
+
electrical_load = Sink(label='building_load', inputs=[electricity_demand_flow])
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
Heat demand with time-varying profile:
|
|
1180
|
+
|
|
1181
|
+
```python
|
|
1182
|
+
heat_demand = Sink(
|
|
1183
|
+
label='district_heating_load',
|
|
1184
|
+
inputs=[
|
|
1185
|
+
Flow(
|
|
1186
|
+
label='heat_consumption',
|
|
1187
|
+
bus=heat_bus,
|
|
1188
|
+
fixed_relative_profile=hourly_heat_profile, # Demand profile
|
|
1189
|
+
size=2000, # Peak demand of 2000 kW
|
|
1190
|
+
)
|
|
1191
|
+
],
|
|
1192
|
+
)
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
Multi-energy building with switching capabilities:
|
|
1196
|
+
|
|
1197
|
+
```python
|
|
1198
|
+
flexible_building = Sink(
|
|
1199
|
+
label='smart_building',
|
|
1200
|
+
inputs=[electricity_heating, gas_heating, heat_pump_heating],
|
|
1201
|
+
prevent_simultaneous_flow_rates=True, # Can only use one heating mode
|
|
1202
|
+
)
|
|
1203
|
+
```
|
|
1204
|
+
|
|
1205
|
+
Industrial process with variable demand:
|
|
1206
|
+
|
|
1207
|
+
```python
|
|
1208
|
+
factory_load = Sink(
|
|
1209
|
+
label='manufacturing_plant',
|
|
1210
|
+
inputs=[
|
|
1211
|
+
Flow(
|
|
1212
|
+
label='electricity_process',
|
|
1213
|
+
bus=electricity_bus,
|
|
1214
|
+
size=5000, # Base electrical load
|
|
1215
|
+
effects_per_flow_hour={'cost': -0.1}, # Value of service (negative cost)
|
|
1216
|
+
),
|
|
1217
|
+
Flow(
|
|
1218
|
+
label='steam_process',
|
|
1219
|
+
bus=steam_bus,
|
|
1220
|
+
size=3000, # Process steam demand
|
|
1221
|
+
fixed_relative_profile=production_schedule,
|
|
1222
|
+
),
|
|
1223
|
+
],
|
|
1224
|
+
)
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
Deprecated:
|
|
1228
|
+
The deprecated `sink` kwarg is accepted for compatibility but will be removed in future releases.
|
|
1229
|
+
"""
|
|
1230
|
+
|
|
711
1231
|
def __init__(
|
|
712
1232
|
self,
|
|
713
1233
|
label: str,
|
|
714
|
-
inputs:
|
|
715
|
-
meta_data:
|
|
1234
|
+
inputs: list[Flow] | None = None,
|
|
1235
|
+
meta_data: dict | None = None,
|
|
716
1236
|
prevent_simultaneous_flow_rates: bool = False,
|
|
717
|
-
**kwargs
|
|
1237
|
+
**kwargs,
|
|
718
1238
|
):
|
|
719
1239
|
"""
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1240
|
+
Initialize a Sink (consumes flow from the system).
|
|
1241
|
+
|
|
1242
|
+
Supports legacy `sink=` keyword for backward compatibility (deprecated): if `sink` is provided it is used as the single input flow and a DeprecationWarning is issued; specifying both `inputs` and `sink` raises ValueError.
|
|
1243
|
+
|
|
1244
|
+
Parameters:
|
|
1245
|
+
label (str): Unique element label.
|
|
1246
|
+
inputs (list[Flow], optional): Input flows for the sink.
|
|
1247
|
+
meta_data (dict, optional): Arbitrary metadata attached to the element.
|
|
1248
|
+
prevent_simultaneous_flow_rates (bool, optional): If True, prevents simultaneous nonzero flow rates across the element's inputs by wiring that restriction into the base Component setup.
|
|
1249
|
+
|
|
1250
|
+
Note:
|
|
1251
|
+
The deprecated `sink` kwarg is accepted for compatibility but will be removed in future releases.
|
|
724
1252
|
"""
|
|
725
1253
|
sink = kwargs.pop('sink', None)
|
|
726
1254
|
if sink is not None:
|
|
727
1255
|
warnings.warn(
|
|
728
|
-
'The use of the sink argument is deprecated. Use the
|
|
1256
|
+
'The use of the sink argument is deprecated. Use the inputs argument instead.',
|
|
729
1257
|
DeprecationWarning,
|
|
730
1258
|
stacklevel=2,
|
|
731
1259
|
)
|
|
732
1260
|
if inputs is not None:
|
|
733
|
-
raise ValueError('Either sink or
|
|
1261
|
+
raise ValueError('Either sink or inputs can be specified, but not both.')
|
|
734
1262
|
inputs = [sink]
|
|
735
1263
|
|
|
736
1264
|
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
737
|
-
super().__init__(
|
|
1265
|
+
super().__init__(
|
|
1266
|
+
label,
|
|
1267
|
+
inputs=inputs,
|
|
1268
|
+
meta_data=meta_data,
|
|
1269
|
+
prevent_simultaneous_flows=inputs if prevent_simultaneous_flow_rates else None,
|
|
1270
|
+
)
|
|
738
1271
|
|
|
739
1272
|
@property
|
|
740
1273
|
def sink(self) -> Flow:
|
|
741
1274
|
warnings.warn(
|
|
742
|
-
'The sink property is deprecated. Use the
|
|
1275
|
+
'The sink property is deprecated. Use the inputs property instead.',
|
|
743
1276
|
DeprecationWarning,
|
|
744
1277
|
stacklevel=2,
|
|
745
1278
|
)
|