flixopt 2.2.0b0__py3-none-any.whl → 2.2.0rc2__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 +13 -13
- docs/user-guide/Mathematical Notation/Flow.md +1 -1
- docs/user-guide/Mathematical Notation/LinearConverter.md +2 -2
- docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
- docs/user-guide/Mathematical Notation/Storage.md +1 -1
- 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 +5 -0
- flixopt/aggregation.py +0 -1
- flixopt/calculation.py +40 -72
- flixopt/commons.py +10 -1
- flixopt/components.py +326 -154
- flixopt/core.py +459 -966
- flixopt/effects.py +67 -270
- flixopt/elements.py +76 -84
- flixopt/features.py +172 -154
- flixopt/flow_system.py +70 -99
- flixopt/interface.py +315 -147
- flixopt/io.py +27 -56
- flixopt/linear_converters.py +3 -3
- flixopt/network_app.py +755 -0
- flixopt/plotting.py +16 -34
- flixopt/results.py +108 -806
- flixopt/structure.py +11 -67
- flixopt/utils.py +9 -6
- {flixopt-2.2.0b0.dist-info → flixopt-2.2.0rc2.dist-info}/METADATA +63 -42
- flixopt-2.2.0rc2.dist-info/RECORD +54 -0
- {flixopt-2.2.0b0.dist-info → flixopt-2.2.0rc2.dist-info}/WHEEL +1 -1
- scripts/extract_release_notes.py +45 -0
- docs/release-notes/_template.txt +0 -32
- docs/release-notes/index.md +0 -7
- docs/release-notes/v2.0.0.md +0 -93
- docs/release-notes/v2.0.1.md +0 -12
- docs/release-notes/v2.1.0.md +0 -31
- docs/release-notes/v2.2.0.md +0 -55
- docs/user-guide/Mathematical Notation/Investment.md +0 -115
- flixopt-2.2.0b0.dist-info/RECORD +0 -59
- {flixopt-2.2.0b0.dist-info → flixopt-2.2.0rc2.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.2.0b0.dist-info → flixopt-2.2.0rc2.dist-info}/top_level.txt +0 -0
flixopt/components.py
CHANGED
|
@@ -3,13 +3,14 @@ This module contains the basic components of the flixopt framework.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
import warnings
|
|
6
7
|
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Set, Tuple, Union
|
|
7
8
|
|
|
8
9
|
import linopy
|
|
9
10
|
import numpy as np
|
|
10
11
|
|
|
11
12
|
from . import utils
|
|
12
|
-
from .core import
|
|
13
|
+
from .core import NumericData, NumericDataTS, PlausibilityError, Scalar, TimeSeries
|
|
13
14
|
from .elements import Component, ComponentModel, Flow
|
|
14
15
|
from .features import InvestmentModel, OnOffModel, PiecewiseModel
|
|
15
16
|
from .interface import InvestParameters, OnOffParameters, PiecewiseConversion
|
|
@@ -23,9 +24,119 @@ logger = logging.getLogger('flixopt')
|
|
|
23
24
|
|
|
24
25
|
@register_class_for_io
|
|
25
26
|
class LinearConverter(Component):
|
|
26
|
-
"""
|
|
27
|
-
|
|
27
|
+
"""Convert input flows into output flows using linear or piecewise linear conversion factors.
|
|
28
|
+
|
|
29
|
+
This component models conversion equipment where input flows are transformed
|
|
30
|
+
into output flows with fixed or variable conversion ratios, such as:
|
|
31
|
+
|
|
32
|
+
- Heat pumps and chillers with variable efficiency
|
|
33
|
+
- Power plants with fuel-to-electricity conversion
|
|
34
|
+
- Chemical processes with multiple inputs/outputs
|
|
35
|
+
- Pumps and compressors
|
|
36
|
+
- Combined heat and power (CHP) plants
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
label: Unique identifier for the component in the FlowSystem.
|
|
40
|
+
inputs: List of input Flow objects that feed into the converter.
|
|
41
|
+
outputs: List of output Flow objects produced by the converter.
|
|
42
|
+
on_off_parameters: Controls binary on/off behavior of the converter.
|
|
43
|
+
When specified, the component can be completely turned on or off, affecting
|
|
44
|
+
all connected flows. This creates binary variables in the optimization.
|
|
45
|
+
For better performance, consider using OnOffParameters on individual flows instead.
|
|
46
|
+
conversion_factors: Linear conversion ratios between flows as time series data.
|
|
47
|
+
List of dictionaries mapping flow labels to their conversion factors.
|
|
48
|
+
Mutually exclusive with piecewise_conversion.
|
|
49
|
+
piecewise_conversion: Piecewise linear conversion relationships between flows.
|
|
50
|
+
Enables modeling of variable efficiency or discrete operating modes.
|
|
51
|
+
Mutually exclusive with conversion_factors.
|
|
52
|
+
meta_data: Additional information stored with the component.
|
|
53
|
+
Saved in results but not used internally. Use only Python native types.
|
|
54
|
+
|
|
55
|
+
Warning:
|
|
56
|
+
When using `piecewise_conversion` without `on_off_parameters`, flow rates cannot
|
|
57
|
+
reach zero unless explicitly defined with zero-valued pieces (e.g., `fx.Piece(0, 0)`).
|
|
58
|
+
This prevents unintended zero flows and maintains mathematical consistency.
|
|
59
|
+
|
|
60
|
+
To allow zero flow rates:
|
|
61
|
+
|
|
62
|
+
- Add `on_off_parameters` to enable complete shutdown, or
|
|
63
|
+
- Include explicit zero pieces in your `piecewise_conversion` definition
|
|
64
|
+
|
|
65
|
+
This behavior was clarified in v2.1.7 to prevent optimization edge cases.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
Simple heat pump with fixed COP:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
heat_pump = fx.LinearConverter(
|
|
72
|
+
label='heat_pump',
|
|
73
|
+
inputs=[electricity_flow],
|
|
74
|
+
outputs=[heat_flow],
|
|
75
|
+
conversion_factors=[
|
|
76
|
+
{
|
|
77
|
+
'electricity_flow': 1.0, # 1 kW electricity input
|
|
78
|
+
'heat_flow': 3.5, # 3.5 kW heat output (COP=3.5)
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Variable efficiency heat pump:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
heat_pump = fx.LinearConverter(
|
|
88
|
+
label='variable_heat_pump',
|
|
89
|
+
inputs=[electricity_flow],
|
|
90
|
+
outputs=[heat_flow],
|
|
91
|
+
piecewise_conversion=fx.PiecewiseConversion(
|
|
92
|
+
{
|
|
93
|
+
'electricity_flow': fx.Piecewise(
|
|
94
|
+
[
|
|
95
|
+
fx.Piece(0, 10), # Allow zero to 10 kW input
|
|
96
|
+
fx.Piece(10, 25), # Higher load operation
|
|
97
|
+
]
|
|
98
|
+
),
|
|
99
|
+
'heat_flow': fx.Piecewise(
|
|
100
|
+
[
|
|
101
|
+
fx.Piece(0, 35), # COP=3.5 at low loads
|
|
102
|
+
fx.Piece(35, 75), # COP=3.0 at high loads
|
|
103
|
+
]
|
|
104
|
+
),
|
|
105
|
+
}
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Combined heat and power plant:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
chp_plant = fx.LinearConverter(
|
|
114
|
+
label='chp_plant',
|
|
115
|
+
inputs=[natural_gas_flow],
|
|
116
|
+
outputs=[electricity_flow, heat_flow],
|
|
117
|
+
conversion_factors=[
|
|
118
|
+
{
|
|
119
|
+
'natural_gas_flow': 1.0, # 1 MW fuel input
|
|
120
|
+
'electricity_flow': 0.4, # 40% electrical efficiency
|
|
121
|
+
'heat_flow': 0.45, # 45% thermal efficiency
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
on_off_parameters=fx.OnOffParameters(
|
|
125
|
+
min_on_hours=4, # Minimum 4-hour operation
|
|
126
|
+
min_off_hours=2, # Minimum 2-hour downtime
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Note:
|
|
132
|
+
Either `conversion_factors` or `piecewise_conversion` must be specified, but not both.
|
|
133
|
+
The component automatically handles the mathematical relationships between all
|
|
134
|
+
connected flows according to the specified conversion ratios.
|
|
28
135
|
|
|
136
|
+
See Also:
|
|
137
|
+
PiecewiseConversion: For variable efficiency modeling
|
|
138
|
+
OnOffParameters: For binary on/off control
|
|
139
|
+
Flow: Input and output flow definitions
|
|
29
140
|
"""
|
|
30
141
|
|
|
31
142
|
def __init__(
|
|
@@ -34,25 +145,10 @@ class LinearConverter(Component):
|
|
|
34
145
|
inputs: List[Flow],
|
|
35
146
|
outputs: List[Flow],
|
|
36
147
|
on_off_parameters: OnOffParameters = None,
|
|
37
|
-
conversion_factors: List[Dict[str,
|
|
148
|
+
conversion_factors: List[Dict[str, NumericDataTS]] = None,
|
|
38
149
|
piecewise_conversion: Optional[PiecewiseConversion] = None,
|
|
39
150
|
meta_data: Optional[Dict] = None,
|
|
40
151
|
):
|
|
41
|
-
"""
|
|
42
|
-
Args:
|
|
43
|
-
label: The label of the Element. Used to identify it in the FlowSystem
|
|
44
|
-
inputs: The input Flows
|
|
45
|
-
outputs: The output Flows
|
|
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 On-Variable (binary) in all Flows!
|
|
48
|
-
If possible, use OnOffParameters in a single Flow instead to keep the number of binary variables low.
|
|
49
|
-
See class OnOffParameters.
|
|
50
|
-
conversion_factors: linear relation between flows.
|
|
51
|
-
Either 'conversion_factors' or 'piecewise_conversion' can be used!
|
|
52
|
-
piecewise_conversion: Define a piecewise linear relation between flow rates of different flows.
|
|
53
|
-
Either 'conversion_factors' or 'piecewise_conversion' can be used!
|
|
54
|
-
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
55
|
-
"""
|
|
56
152
|
super().__init__(label, inputs, outputs, on_off_parameters, meta_data=meta_data)
|
|
57
153
|
self.conversion_factors = conversion_factors or []
|
|
58
154
|
self.piecewise_conversion = piecewise_conversion
|
|
@@ -86,9 +182,9 @@ class LinearConverter(Component):
|
|
|
86
182
|
if self.piecewise_conversion:
|
|
87
183
|
for flow in self.flows.values():
|
|
88
184
|
if isinstance(flow.size, InvestParameters) and flow.size.fixed_size is None:
|
|
89
|
-
|
|
90
|
-
f'
|
|
91
|
-
f'(in {
|
|
185
|
+
raise PlausibilityError(
|
|
186
|
+
f'piecewise_conversion (in {self.label_full}) and variable size '
|
|
187
|
+
f'(in flow {flow.label_full}) do not make sense together!'
|
|
92
188
|
)
|
|
93
189
|
|
|
94
190
|
def transform_data(self, flow_system: 'FlowSystem'):
|
|
@@ -96,7 +192,6 @@ class LinearConverter(Component):
|
|
|
96
192
|
if self.conversion_factors:
|
|
97
193
|
self.conversion_factors = self._transform_conversion_factors(flow_system)
|
|
98
194
|
if self.piecewise_conversion:
|
|
99
|
-
self.piecewise_conversion.has_time_dim = True
|
|
100
195
|
self.piecewise_conversion.transform_data(flow_system, f'{self.label_full}|PiecewiseConversion')
|
|
101
196
|
|
|
102
197
|
def _transform_conversion_factors(self, flow_system: 'FlowSystem') -> List[Dict[str, TimeSeries]]:
|
|
@@ -128,17 +223,16 @@ class Storage(Component):
|
|
|
128
223
|
label: str,
|
|
129
224
|
charging: Flow,
|
|
130
225
|
discharging: Flow,
|
|
131
|
-
capacity_in_flow_hours: Union[
|
|
132
|
-
relative_minimum_charge_state:
|
|
133
|
-
relative_maximum_charge_state:
|
|
134
|
-
initial_charge_state: Union[
|
|
135
|
-
minimal_final_charge_state: Optional[
|
|
136
|
-
maximal_final_charge_state: Optional[
|
|
137
|
-
eta_charge:
|
|
138
|
-
eta_discharge:
|
|
139
|
-
relative_loss_per_hour:
|
|
226
|
+
capacity_in_flow_hours: Union[Scalar, InvestParameters],
|
|
227
|
+
relative_minimum_charge_state: NumericData = 0,
|
|
228
|
+
relative_maximum_charge_state: NumericData = 1,
|
|
229
|
+
initial_charge_state: Union[Scalar, Literal['lastValueOfSim']] = 0,
|
|
230
|
+
minimal_final_charge_state: Optional[Scalar] = None,
|
|
231
|
+
maximal_final_charge_state: Optional[Scalar] = None,
|
|
232
|
+
eta_charge: NumericData = 1,
|
|
233
|
+
eta_discharge: NumericData = 1,
|
|
234
|
+
relative_loss_per_hour: NumericData = 0,
|
|
140
235
|
prevent_simultaneous_charge_and_discharge: bool = True,
|
|
141
|
-
balanced: bool = False,
|
|
142
236
|
meta_data: Optional[Dict] = None,
|
|
143
237
|
):
|
|
144
238
|
"""
|
|
@@ -164,7 +258,6 @@ class Storage(Component):
|
|
|
164
258
|
relative_loss_per_hour: loss per chargeState-Unit per hour. The default is 0.
|
|
165
259
|
prevent_simultaneous_charge_and_discharge: If True, loading and unloading at the same time is not possible.
|
|
166
260
|
Increases the number of binary variables, but is recommended for easier evaluation. The default is True.
|
|
167
|
-
balanced: Wether to equate the size of the charging and discharging flow. Only if not fixed.
|
|
168
261
|
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
169
262
|
"""
|
|
170
263
|
# TODO: fixed_relative_chargeState implementieren
|
|
@@ -179,18 +272,17 @@ class Storage(Component):
|
|
|
179
272
|
self.charging = charging
|
|
180
273
|
self.discharging = discharging
|
|
181
274
|
self.capacity_in_flow_hours = capacity_in_flow_hours
|
|
182
|
-
self.relative_minimum_charge_state:
|
|
183
|
-
self.relative_maximum_charge_state:
|
|
275
|
+
self.relative_minimum_charge_state: NumericDataTS = relative_minimum_charge_state
|
|
276
|
+
self.relative_maximum_charge_state: NumericDataTS = relative_maximum_charge_state
|
|
184
277
|
|
|
185
278
|
self.initial_charge_state = initial_charge_state
|
|
186
279
|
self.minimal_final_charge_state = minimal_final_charge_state
|
|
187
280
|
self.maximal_final_charge_state = maximal_final_charge_state
|
|
188
281
|
|
|
189
|
-
self.eta_charge:
|
|
190
|
-
self.eta_discharge:
|
|
191
|
-
self.relative_loss_per_hour:
|
|
282
|
+
self.eta_charge: NumericDataTS = eta_charge
|
|
283
|
+
self.eta_discharge: NumericDataTS = eta_discharge
|
|
284
|
+
self.relative_loss_per_hour: NumericDataTS = relative_loss_per_hour
|
|
192
285
|
self.prevent_simultaneous_charge_and_discharge = prevent_simultaneous_charge_and_discharge
|
|
193
|
-
self.balanced = balanced
|
|
194
286
|
|
|
195
287
|
def create_model(self, model: SystemModel) -> 'StorageModel':
|
|
196
288
|
self._plausibility_checks()
|
|
@@ -202,83 +294,55 @@ class Storage(Component):
|
|
|
202
294
|
self.relative_minimum_charge_state = flow_system.create_time_series(
|
|
203
295
|
f'{self.label_full}|relative_minimum_charge_state',
|
|
204
296
|
self.relative_minimum_charge_state,
|
|
205
|
-
|
|
297
|
+
needs_extra_timestep=True,
|
|
206
298
|
)
|
|
207
299
|
self.relative_maximum_charge_state = flow_system.create_time_series(
|
|
208
300
|
f'{self.label_full}|relative_maximum_charge_state',
|
|
209
301
|
self.relative_maximum_charge_state,
|
|
210
|
-
|
|
302
|
+
needs_extra_timestep=True,
|
|
211
303
|
)
|
|
212
304
|
self.eta_charge = flow_system.create_time_series(f'{self.label_full}|eta_charge', self.eta_charge)
|
|
213
305
|
self.eta_discharge = flow_system.create_time_series(f'{self.label_full}|eta_discharge', self.eta_discharge)
|
|
214
306
|
self.relative_loss_per_hour = flow_system.create_time_series(
|
|
215
307
|
f'{self.label_full}|relative_loss_per_hour', self.relative_loss_per_hour
|
|
216
308
|
)
|
|
217
|
-
if not isinstance(self.initial_charge_state, str):
|
|
218
|
-
self.initial_charge_state = flow_system.create_time_series(
|
|
219
|
-
f'{self.label_full}|initial_charge_state', self.initial_charge_state, has_time_dim=False
|
|
220
|
-
)
|
|
221
|
-
self.minimal_final_charge_state = flow_system.create_time_series(
|
|
222
|
-
f'{self.label_full}|minimal_final_charge_state', self.minimal_final_charge_state, has_time_dim=False
|
|
223
|
-
)
|
|
224
|
-
self.maximal_final_charge_state = flow_system.create_time_series(
|
|
225
|
-
f'{self.label_full}|maximal_final_charge_state', self.maximal_final_charge_state, has_time_dim=False
|
|
226
|
-
)
|
|
227
309
|
if isinstance(self.capacity_in_flow_hours, InvestParameters):
|
|
228
|
-
self.capacity_in_flow_hours.transform_data(flow_system
|
|
229
|
-
else:
|
|
230
|
-
self.capacity_in_flow_hours = flow_system.create_time_series(
|
|
231
|
-
f'{self.label_full}|capacity_in_flow_hours', self.capacity_in_flow_hours, has_time_dim=False
|
|
232
|
-
)
|
|
310
|
+
self.capacity_in_flow_hours.transform_data(flow_system)
|
|
233
311
|
|
|
234
312
|
def _plausibility_checks(self) -> None:
|
|
235
313
|
"""
|
|
236
314
|
Check for infeasible or uncommon combinations of parameters
|
|
237
315
|
"""
|
|
238
316
|
super()._plausibility_checks()
|
|
239
|
-
if
|
|
240
|
-
if self.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
317
|
+
if utils.is_number(self.initial_charge_state):
|
|
318
|
+
if isinstance(self.capacity_in_flow_hours, InvestParameters):
|
|
319
|
+
if self.capacity_in_flow_hours.fixed_size is None:
|
|
320
|
+
maximum_capacity = self.capacity_in_flow_hours.maximum_size
|
|
321
|
+
minimum_capacity = self.capacity_in_flow_hours.minimum_size
|
|
322
|
+
else:
|
|
323
|
+
maximum_capacity = self.capacity_in_flow_hours.fixed_size
|
|
324
|
+
minimum_capacity = self.capacity_in_flow_hours.fixed_size
|
|
247
325
|
else:
|
|
248
|
-
maximum_capacity = self.capacity_in_flow_hours
|
|
249
|
-
minimum_capacity = self.capacity_in_flow_hours
|
|
250
|
-
else:
|
|
251
|
-
maximum_capacity = self.capacity_in_flow_hours
|
|
252
|
-
minimum_capacity = self.capacity_in_flow_hours
|
|
253
|
-
|
|
254
|
-
# initial capacity >= allowed min for maximum_size:
|
|
255
|
-
minimum_inital_capacity = maximum_capacity * self.relative_minimum_charge_state.isel(time=0)
|
|
256
|
-
# initial capacity <= allowed max for minimum_size:
|
|
257
|
-
maximum_inital_capacity = minimum_capacity * self.relative_maximum_charge_state.isel(time=0)
|
|
258
|
-
# TODO: index=1 ??? I think index 0
|
|
259
|
-
|
|
260
|
-
if (self.initial_charge_state > maximum_inital_capacity).any():
|
|
261
|
-
raise ValueError(
|
|
262
|
-
f'{self.label_full}: {self.initial_charge_state=} '
|
|
263
|
-
f'is above allowed maximum charge_state {maximum_inital_capacity}'
|
|
264
|
-
)
|
|
265
|
-
if (self.initial_charge_state < minimum_inital_capacity).any():
|
|
266
|
-
raise ValueError(
|
|
267
|
-
f'{self.label_full}: {self.initial_charge_state=} '
|
|
268
|
-
f'is below allowed minimum charge_state {minimum_inital_capacity}'
|
|
269
|
-
)
|
|
326
|
+
maximum_capacity = self.capacity_in_flow_hours
|
|
327
|
+
minimum_capacity = self.capacity_in_flow_hours
|
|
270
328
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
f'
|
|
280
|
-
|
|
281
|
-
|
|
329
|
+
# initial capacity >= allowed min for maximum_size:
|
|
330
|
+
minimum_inital_capacity = maximum_capacity * self.relative_minimum_charge_state.isel(time=1)
|
|
331
|
+
# initial capacity <= allowed max for minimum_size:
|
|
332
|
+
maximum_inital_capacity = minimum_capacity * self.relative_maximum_charge_state.isel(time=1)
|
|
333
|
+
|
|
334
|
+
if self.initial_charge_state > maximum_inital_capacity:
|
|
335
|
+
raise ValueError(
|
|
336
|
+
f'{self.label_full}: {self.initial_charge_state=} '
|
|
337
|
+
f'is above allowed maximum charge_state {maximum_inital_capacity}'
|
|
338
|
+
)
|
|
339
|
+
if self.initial_charge_state < minimum_inital_capacity:
|
|
340
|
+
raise ValueError(
|
|
341
|
+
f'{self.label_full}: {self.initial_charge_state=} '
|
|
342
|
+
f'is below allowed minimum charge_state {minimum_inital_capacity}'
|
|
343
|
+
)
|
|
344
|
+
elif self.initial_charge_state != 'lastValueOfSim':
|
|
345
|
+
raise ValueError(f'{self.label_full}: {self.initial_charge_state=} has an invalid value')
|
|
282
346
|
|
|
283
347
|
|
|
284
348
|
@register_class_for_io
|
|
@@ -296,8 +360,8 @@ class Transmission(Component):
|
|
|
296
360
|
out1: Flow,
|
|
297
361
|
in2: Optional[Flow] = None,
|
|
298
362
|
out2: Optional[Flow] = None,
|
|
299
|
-
relative_losses: Optional[
|
|
300
|
-
absolute_losses: Optional[
|
|
363
|
+
relative_losses: Optional[NumericDataTS] = None,
|
|
364
|
+
absolute_losses: Optional[NumericDataTS] = None,
|
|
301
365
|
on_off_parameters: OnOffParameters = None,
|
|
302
366
|
prevent_simultaneous_flows_in_both_directions: bool = True,
|
|
303
367
|
meta_data: Optional[Dict] = None,
|
|
@@ -380,7 +444,7 @@ class TransmissionModel(ComponentModel):
|
|
|
380
444
|
def do_modeling(self):
|
|
381
445
|
"""Initiates all FlowModels"""
|
|
382
446
|
# Force On Variable if absolute losses are present
|
|
383
|
-
if (self.element.absolute_losses is not None) and np.any(self.element.absolute_losses.
|
|
447
|
+
if (self.element.absolute_losses is not None) and np.any(self.element.absolute_losses.active_data != 0):
|
|
384
448
|
for flow in self.element.inputs + self.element.outputs:
|
|
385
449
|
if flow.on_off_parameters is None:
|
|
386
450
|
flow.on_off_parameters = OnOffParameters()
|
|
@@ -417,14 +481,14 @@ class TransmissionModel(ComponentModel):
|
|
|
417
481
|
# eq: out(t) + on(t)*loss_abs(t) = in(t)*(1 - loss_rel(t))
|
|
418
482
|
con_transmission = self.add(
|
|
419
483
|
self._model.add_constraints(
|
|
420
|
-
out_flow.model.flow_rate == -in_flow.model.flow_rate * (self.element.relative_losses.
|
|
484
|
+
out_flow.model.flow_rate == -in_flow.model.flow_rate * (self.element.relative_losses.active_data - 1),
|
|
421
485
|
name=f'{self.label_full}|{name}',
|
|
422
486
|
),
|
|
423
487
|
name,
|
|
424
488
|
)
|
|
425
489
|
|
|
426
490
|
if self.element.absolute_losses is not None:
|
|
427
|
-
con_transmission.lhs += in_flow.model.on_off.on * self.element.absolute_losses.
|
|
491
|
+
con_transmission.lhs += in_flow.model.on_off.on * self.element.absolute_losses.active_data
|
|
428
492
|
|
|
429
493
|
return con_transmission
|
|
430
494
|
|
|
@@ -452,10 +516,8 @@ class LinearConverterModel(ComponentModel):
|
|
|
452
516
|
|
|
453
517
|
self.add(
|
|
454
518
|
self._model.add_constraints(
|
|
455
|
-
sum([flow.model.flow_rate * conv_factors[flow.label].
|
|
456
|
-
== sum(
|
|
457
|
-
[flow.model.flow_rate * conv_factors[flow.label].selected_data for flow in used_outputs]
|
|
458
|
-
),
|
|
519
|
+
sum([flow.model.flow_rate * conv_factors[flow.label].active_data for flow in used_inputs])
|
|
520
|
+
== sum([flow.model.flow_rate * conv_factors[flow.label].active_data for flow in used_outputs]),
|
|
459
521
|
name=f'{self.label_full}|conversion_{i}',
|
|
460
522
|
)
|
|
461
523
|
)
|
|
@@ -495,15 +557,12 @@ class StorageModel(ComponentModel):
|
|
|
495
557
|
lb, ub = self.absolute_charge_state_bounds
|
|
496
558
|
self.charge_state = self.add(
|
|
497
559
|
self._model.add_variables(
|
|
498
|
-
lower=lb,
|
|
499
|
-
upper=ub,
|
|
500
|
-
coords=self._model.get_coords(extra_timestep=True),
|
|
501
|
-
name=f'{self.label_full}|charge_state',
|
|
560
|
+
lower=lb, upper=ub, coords=self._model.coords_extra, name=f'{self.label_full}|charge_state'
|
|
502
561
|
),
|
|
503
562
|
'charge_state',
|
|
504
563
|
)
|
|
505
564
|
self.netto_discharge = self.add(
|
|
506
|
-
self._model.add_variables(coords=self._model.
|
|
565
|
+
self._model.add_variables(coords=self._model.coords, name=f'{self.label_full}|netto_discharge'),
|
|
507
566
|
'netto_discharge',
|
|
508
567
|
)
|
|
509
568
|
# netto_discharge:
|
|
@@ -518,17 +577,17 @@ class StorageModel(ComponentModel):
|
|
|
518
577
|
)
|
|
519
578
|
|
|
520
579
|
charge_state = self.charge_state
|
|
521
|
-
rel_loss = self.element.relative_loss_per_hour.
|
|
580
|
+
rel_loss = self.element.relative_loss_per_hour.active_data
|
|
522
581
|
hours_per_step = self._model.hours_per_step
|
|
523
582
|
charge_rate = self.element.charging.model.flow_rate
|
|
524
583
|
discharge_rate = self.element.discharging.model.flow_rate
|
|
525
|
-
eff_charge = self.element.eta_charge.
|
|
526
|
-
eff_discharge = self.element.eta_discharge.
|
|
584
|
+
eff_charge = self.element.eta_charge.active_data
|
|
585
|
+
eff_discharge = self.element.eta_discharge.active_data
|
|
527
586
|
|
|
528
587
|
self.add(
|
|
529
588
|
self._model.add_constraints(
|
|
530
589
|
charge_state.isel(time=slice(1, None))
|
|
531
|
-
== charge_state.isel(time=slice(None, -1)) * (1 - rel_loss
|
|
590
|
+
== charge_state.isel(time=slice(None, -1)) * ((1 - rel_loss) ** hours_per_step)
|
|
532
591
|
+ charge_rate * eff_charge * hours_per_step
|
|
533
592
|
- discharge_rate * eff_discharge * hours_per_step,
|
|
534
593
|
name=f'{self.label_full}|charge_state',
|
|
@@ -550,34 +609,29 @@ class StorageModel(ComponentModel):
|
|
|
550
609
|
# Initial charge state
|
|
551
610
|
self._initial_and_final_charge_state()
|
|
552
611
|
|
|
553
|
-
if self.element.balanced:
|
|
554
|
-
self.add(
|
|
555
|
-
self._model.add_constraints(
|
|
556
|
-
self.element.charging.model._investment.size * 1 == self.element.discharging.model._investment.size * 1,
|
|
557
|
-
name=f'{self.label_full}|balanced_sizes',
|
|
558
|
-
),
|
|
559
|
-
'balanced_sizes'
|
|
560
|
-
)
|
|
561
|
-
|
|
562
612
|
def _initial_and_final_charge_state(self):
|
|
563
613
|
if self.element.initial_charge_state is not None:
|
|
564
614
|
name_short = 'initial_charge_state'
|
|
565
615
|
name = f'{self.label_full}|{name_short}'
|
|
566
616
|
|
|
567
|
-
if
|
|
617
|
+
if utils.is_number(self.element.initial_charge_state):
|
|
568
618
|
self.add(
|
|
569
619
|
self._model.add_constraints(
|
|
570
|
-
self.charge_state.isel(time=0) == self.
|
|
620
|
+
self.charge_state.isel(time=0) == self.element.initial_charge_state, name=name
|
|
571
621
|
),
|
|
572
622
|
name_short,
|
|
573
623
|
)
|
|
574
|
-
|
|
624
|
+
elif self.element.initial_charge_state == 'lastValueOfSim':
|
|
575
625
|
self.add(
|
|
576
626
|
self._model.add_constraints(
|
|
577
|
-
self.charge_state.isel(time=0) == self.
|
|
627
|
+
self.charge_state.isel(time=0) == self.charge_state.isel(time=-1), name=name
|
|
578
628
|
),
|
|
579
629
|
name_short,
|
|
580
630
|
)
|
|
631
|
+
else: # TODO: Validation in Storage Class, not in Model
|
|
632
|
+
raise PlausibilityError(
|
|
633
|
+
f'initial_charge_state has undefined value: {self.element.initial_charge_state}'
|
|
634
|
+
)
|
|
581
635
|
|
|
582
636
|
if self.element.maximal_final_charge_state is not None:
|
|
583
637
|
self.add(
|
|
@@ -598,7 +652,7 @@ class StorageModel(ComponentModel):
|
|
|
598
652
|
)
|
|
599
653
|
|
|
600
654
|
@property
|
|
601
|
-
def absolute_charge_state_bounds(self) -> Tuple[
|
|
655
|
+
def absolute_charge_state_bounds(self) -> Tuple[NumericData, NumericData]:
|
|
602
656
|
relative_lower_bound, relative_upper_bound = self.relative_charge_state_bounds
|
|
603
657
|
if not isinstance(self.element.capacity_in_flow_hours, InvestParameters):
|
|
604
658
|
return (
|
|
@@ -612,10 +666,10 @@ class StorageModel(ComponentModel):
|
|
|
612
666
|
)
|
|
613
667
|
|
|
614
668
|
@property
|
|
615
|
-
def relative_charge_state_bounds(self) -> Tuple[
|
|
669
|
+
def relative_charge_state_bounds(self) -> Tuple[NumericData, NumericData]:
|
|
616
670
|
return (
|
|
617
|
-
self.element.relative_minimum_charge_state,
|
|
618
|
-
self.element.relative_maximum_charge_state,
|
|
671
|
+
self.element.relative_minimum_charge_state.active_data,
|
|
672
|
+
self.element.relative_maximum_charge_state.active_data,
|
|
619
673
|
)
|
|
620
674
|
|
|
621
675
|
|
|
@@ -628,52 +682,170 @@ class SourceAndSink(Component):
|
|
|
628
682
|
def __init__(
|
|
629
683
|
self,
|
|
630
684
|
label: str,
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
685
|
+
inputs: List[Flow] = None,
|
|
686
|
+
outputs: List[Flow] = None,
|
|
687
|
+
prevent_simultaneous_flow_rates: bool = True,
|
|
634
688
|
meta_data: Optional[Dict] = None,
|
|
689
|
+
**kwargs,
|
|
635
690
|
):
|
|
636
691
|
"""
|
|
637
692
|
Args:
|
|
638
693
|
label: The label of the Element. Used to identify it in the FlowSystem
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
694
|
+
outputs: output-flows of this component
|
|
695
|
+
inputs: input-flows of this component
|
|
696
|
+
prevent_simultaneous_flow_rates: If True, inflow and outflow can not be active simultaniously.
|
|
642
697
|
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
643
698
|
"""
|
|
699
|
+
source = kwargs.pop('source', None)
|
|
700
|
+
sink = kwargs.pop('sink', None)
|
|
701
|
+
prevent_simultaneous_sink_and_source = kwargs.pop('prevent_simultaneous_sink_and_source', None)
|
|
702
|
+
if source is not None:
|
|
703
|
+
warnings.deprecated(
|
|
704
|
+
'The use of the source argument is deprecated. Use the outputs argument instead.',
|
|
705
|
+
stacklevel=2,
|
|
706
|
+
)
|
|
707
|
+
if outputs is not None:
|
|
708
|
+
raise ValueError('Either source or outputs can be specified, but not both.')
|
|
709
|
+
outputs = [source]
|
|
710
|
+
|
|
711
|
+
if sink is not None:
|
|
712
|
+
warnings.deprecated(
|
|
713
|
+
'The use of the sink argument is deprecated. Use the outputs argument instead.',
|
|
714
|
+
stacklevel=2,
|
|
715
|
+
)
|
|
716
|
+
if inputs is not None:
|
|
717
|
+
raise ValueError('Either sink or outputs can be specified, but not both.')
|
|
718
|
+
inputs = [sink]
|
|
719
|
+
|
|
720
|
+
if prevent_simultaneous_sink_and_source is not None:
|
|
721
|
+
warnings.deprecated(
|
|
722
|
+
'The use of the prevent_simultaneous_sink_and_source argument is deprecated. Use the prevent_simultaneous_flow_rates argument instead.',
|
|
723
|
+
stacklevel=2,
|
|
724
|
+
)
|
|
725
|
+
prevent_simultaneous_flow_rates = prevent_simultaneous_sink_and_source
|
|
726
|
+
|
|
644
727
|
super().__init__(
|
|
645
728
|
label,
|
|
646
|
-
inputs=
|
|
647
|
-
outputs=
|
|
648
|
-
prevent_simultaneous_flows=
|
|
729
|
+
inputs=inputs,
|
|
730
|
+
outputs=outputs,
|
|
731
|
+
prevent_simultaneous_flows=inputs + outputs if prevent_simultaneous_flow_rates is True else None,
|
|
649
732
|
meta_data=meta_data,
|
|
650
733
|
)
|
|
651
|
-
self.
|
|
652
|
-
|
|
653
|
-
|
|
734
|
+
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
735
|
+
|
|
736
|
+
@property
|
|
737
|
+
def source(self) -> Flow:
|
|
738
|
+
warnings.warn(
|
|
739
|
+
'The source property is deprecated. Use the outputs property instead.',
|
|
740
|
+
DeprecationWarning,
|
|
741
|
+
stacklevel=2,
|
|
742
|
+
)
|
|
743
|
+
return self.outputs[0]
|
|
744
|
+
|
|
745
|
+
@property
|
|
746
|
+
def sink(self) -> Flow:
|
|
747
|
+
warnings.warn(
|
|
748
|
+
'The sink property is deprecated. Use the outputs property instead.',
|
|
749
|
+
DeprecationWarning,
|
|
750
|
+
stacklevel=2,
|
|
751
|
+
)
|
|
752
|
+
return self.inputs[0]
|
|
753
|
+
|
|
754
|
+
@property
|
|
755
|
+
def prevent_simultaneous_sink_and_source(self) -> bool:
|
|
756
|
+
warnings.warn(
|
|
757
|
+
'The prevent_simultaneous_sink_and_source property is deprecated. Use the prevent_simultaneous_flow_rates property instead.',
|
|
758
|
+
DeprecationWarning,
|
|
759
|
+
stacklevel=2,
|
|
760
|
+
)
|
|
761
|
+
return self.prevent_simultaneous_flow_rates
|
|
654
762
|
|
|
655
763
|
|
|
656
764
|
@register_class_for_io
|
|
657
765
|
class Source(Component):
|
|
658
|
-
def __init__(
|
|
766
|
+
def __init__(
|
|
767
|
+
self,
|
|
768
|
+
label: str,
|
|
769
|
+
outputs: List[Flow] = None,
|
|
770
|
+
meta_data: Optional[Dict] = None,
|
|
771
|
+
prevent_simultaneous_flow_rates: bool = False,
|
|
772
|
+
**kwargs,
|
|
773
|
+
):
|
|
659
774
|
"""
|
|
660
775
|
Args:
|
|
661
776
|
label: The label of the Element. Used to identify it in the FlowSystem
|
|
662
|
-
|
|
777
|
+
outputs: output-flows of source
|
|
663
778
|
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
664
779
|
"""
|
|
665
|
-
|
|
666
|
-
|
|
780
|
+
source = kwargs.pop('source', None)
|
|
781
|
+
if source is not None:
|
|
782
|
+
warnings.warn(
|
|
783
|
+
'The use of the source argument is deprecated. Use the outputs argument instead.',
|
|
784
|
+
DeprecationWarning,
|
|
785
|
+
stacklevel=2,
|
|
786
|
+
)
|
|
787
|
+
if outputs is not None:
|
|
788
|
+
raise ValueError('Either source or outputs can be specified, but not both.')
|
|
789
|
+
outputs = [source]
|
|
790
|
+
|
|
791
|
+
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
792
|
+
super().__init__(
|
|
793
|
+
label,
|
|
794
|
+
outputs=outputs,
|
|
795
|
+
meta_data=meta_data,
|
|
796
|
+
prevent_simultaneous_flows=outputs if prevent_simultaneous_flow_rates else None,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
@property
|
|
800
|
+
def source(self) -> Flow:
|
|
801
|
+
warnings.warn(
|
|
802
|
+
'The source property is deprecated. Use the outputs property instead.',
|
|
803
|
+
DeprecationWarning,
|
|
804
|
+
stacklevel=2,
|
|
805
|
+
)
|
|
806
|
+
return self.outputs[0]
|
|
667
807
|
|
|
668
808
|
|
|
669
809
|
@register_class_for_io
|
|
670
810
|
class Sink(Component):
|
|
671
|
-
def __init__(
|
|
811
|
+
def __init__(
|
|
812
|
+
self,
|
|
813
|
+
label: str,
|
|
814
|
+
inputs: List[Flow] = None,
|
|
815
|
+
meta_data: Optional[Dict] = None,
|
|
816
|
+
prevent_simultaneous_flow_rates: bool = False,
|
|
817
|
+
**kwargs,
|
|
818
|
+
):
|
|
672
819
|
"""
|
|
673
820
|
Args:
|
|
674
821
|
label: The label of the Element. Used to identify it in the FlowSystem
|
|
675
|
-
|
|
676
|
-
|
|
822
|
+
inputs: output-flows of source
|
|
823
|
+
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
677
824
|
"""
|
|
678
|
-
|
|
679
|
-
|
|
825
|
+
sink = kwargs.pop('sink', None)
|
|
826
|
+
if sink is not None:
|
|
827
|
+
warnings.warn(
|
|
828
|
+
'The use of the sink argument is deprecated. Use the outputs argument instead.',
|
|
829
|
+
DeprecationWarning,
|
|
830
|
+
stacklevel=2,
|
|
831
|
+
)
|
|
832
|
+
if inputs is not None:
|
|
833
|
+
raise ValueError('Either sink or outputs can be specified, but not both.')
|
|
834
|
+
inputs = [sink]
|
|
835
|
+
|
|
836
|
+
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
837
|
+
super().__init__(
|
|
838
|
+
label,
|
|
839
|
+
inputs=inputs,
|
|
840
|
+
meta_data=meta_data,
|
|
841
|
+
prevent_simultaneous_flows=inputs if prevent_simultaneous_flow_rates else None,
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
@property
|
|
845
|
+
def sink(self) -> Flow:
|
|
846
|
+
warnings.warn(
|
|
847
|
+
'The sink property is deprecated. Use the outputs property instead.',
|
|
848
|
+
DeprecationWarning,
|
|
849
|
+
stacklevel=2,
|
|
850
|
+
)
|
|
851
|
+
return self.inputs[0]
|