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/interface.py
CHANGED
|
@@ -4,14 +4,14 @@ These are tightly connected to features.py
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
from typing import TYPE_CHECKING, Dict, Iterator, List,
|
|
7
|
+
from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Union
|
|
8
8
|
|
|
9
9
|
from .config import CONFIG
|
|
10
|
-
from .core import NumericDataTS, Scalar
|
|
10
|
+
from .core import NumericData, NumericDataTS, Scalar
|
|
11
11
|
from .structure import Interface, register_class_for_io
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING: # for type checking and preventing circular imports
|
|
14
|
-
from .effects import
|
|
14
|
+
from .effects import EffectValuesUser, EffectValuesUserScalar
|
|
15
15
|
from .flow_system import FlowSystem
|
|
16
16
|
|
|
17
17
|
|
|
@@ -20,7 +20,7 @@ logger = logging.getLogger('flixopt')
|
|
|
20
20
|
|
|
21
21
|
@register_class_for_io
|
|
22
22
|
class Piece(Interface):
|
|
23
|
-
def __init__(self, start:
|
|
23
|
+
def __init__(self, start: NumericData, end: NumericData):
|
|
24
24
|
"""
|
|
25
25
|
Define a Piece, which is part of a Piecewise object.
|
|
26
26
|
|
|
@@ -30,15 +30,10 @@ class Piece(Interface):
|
|
|
30
30
|
"""
|
|
31
31
|
self.start = start
|
|
32
32
|
self.end = end
|
|
33
|
-
self.has_time_dim = False
|
|
34
33
|
|
|
35
34
|
def transform_data(self, flow_system: 'FlowSystem', name_prefix: str):
|
|
36
|
-
self.start = flow_system.create_time_series(
|
|
37
|
-
|
|
38
|
-
)
|
|
39
|
-
self.end = flow_system.create_time_series(
|
|
40
|
-
name=f'{name_prefix}|end', data=self.end, has_time_dim=self.has_time_dim, has_scenario_dim=True
|
|
41
|
-
)
|
|
35
|
+
self.start = flow_system.create_time_series(f'{name_prefix}|start', self.start)
|
|
36
|
+
self.end = flow_system.create_time_series(f'{name_prefix}|end', self.end)
|
|
42
37
|
|
|
43
38
|
|
|
44
39
|
@register_class_for_io
|
|
@@ -51,17 +46,6 @@ class Piecewise(Interface):
|
|
|
51
46
|
pieces: The pieces of the piecewise.
|
|
52
47
|
"""
|
|
53
48
|
self.pieces = pieces
|
|
54
|
-
self._has_time_dim = False
|
|
55
|
-
|
|
56
|
-
@property
|
|
57
|
-
def has_time_dim(self):
|
|
58
|
-
return self._has_time_dim
|
|
59
|
-
|
|
60
|
-
@has_time_dim.setter
|
|
61
|
-
def has_time_dim(self, value):
|
|
62
|
-
self._has_time_dim = value
|
|
63
|
-
for piece in self.pieces:
|
|
64
|
-
piece.has_time_dim = value
|
|
65
49
|
|
|
66
50
|
def __len__(self):
|
|
67
51
|
return len(self.pieces)
|
|
@@ -79,28 +63,169 @@ class Piecewise(Interface):
|
|
|
79
63
|
|
|
80
64
|
@register_class_for_io
|
|
81
65
|
class PiecewiseConversion(Interface):
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
66
|
+
"""Define piecewise linear conversion relationships between multiple flows.
|
|
67
|
+
|
|
68
|
+
This class models complex conversion processes where the relationship between
|
|
69
|
+
input and output flows changes at different operating points, such as:
|
|
70
|
+
|
|
71
|
+
- Variable efficiency equipment (heat pumps, engines, turbines)
|
|
72
|
+
- Multi-stage chemical processes with different conversion rates
|
|
73
|
+
- Equipment with discrete operating modes
|
|
74
|
+
- Systems with capacity constraints and thresholds
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
piecewises: Dictionary mapping flow labels to their Piecewise conversion functions.
|
|
78
|
+
Keys are flow names (e.g., 'electricity_in', 'heat_out', 'fuel_consumed').
|
|
79
|
+
Values are Piecewise objects defining conversion factors at different operating points.
|
|
80
|
+
All Piecewise objects must have the same number of pieces and compatible domains
|
|
81
|
+
to ensure consistent conversion relationships across operating ranges.
|
|
82
|
+
|
|
83
|
+
Note:
|
|
84
|
+
Special modeling features:
|
|
85
|
+
|
|
86
|
+
- **Gaps**: Express forbidden operating ranges by creating non-contiguous pieces.
|
|
87
|
+
Example: `[(0,50), (100,200)]` - cannot operate between 50-100 units
|
|
88
|
+
- **Points**: Express discrete operating points using pieces with identical start/end.
|
|
89
|
+
Example: `[(50,50), (100,100)]` - can only operate at exactly 50 or 100 units
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
Heat pump with variable COP (Coefficient of Performance):
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
PiecewiseConversion(
|
|
96
|
+
{
|
|
97
|
+
'electricity_in': Piecewise(
|
|
98
|
+
[
|
|
99
|
+
Piece(0, 10), # Low load: 0-10 kW electricity
|
|
100
|
+
Piece(10, 25), # High load: 10-25 kW electricity
|
|
101
|
+
]
|
|
102
|
+
),
|
|
103
|
+
'heat_out': Piecewise(
|
|
104
|
+
[
|
|
105
|
+
Piece(0, 35), # Low load COP=3.5: 0-35 kW heat output
|
|
106
|
+
Piece(35, 75), # High load COP=3.0: 35-75 kW heat output
|
|
107
|
+
]
|
|
108
|
+
),
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
# At 15 kW electricity input → 52.5 kW heat output (interpolated)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Engine with fuel consumption and emissions:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
PiecewiseConversion(
|
|
118
|
+
{
|
|
119
|
+
'fuel_input': Piecewise(
|
|
120
|
+
[
|
|
121
|
+
Piece(5, 15), # Part load: 5-15 L/h fuel
|
|
122
|
+
Piece(15, 30), # Full load: 15-30 L/h fuel
|
|
123
|
+
]
|
|
124
|
+
),
|
|
125
|
+
'power_output': Piecewise(
|
|
126
|
+
[
|
|
127
|
+
Piece(10, 25), # Part load: 10-25 kW output
|
|
128
|
+
Piece(25, 45), # Full load: 25-45 kW output
|
|
129
|
+
]
|
|
130
|
+
),
|
|
131
|
+
'co2_emissions': Piecewise(
|
|
132
|
+
[
|
|
133
|
+
Piece(12, 35), # Part load: 12-35 kg/h CO2
|
|
134
|
+
Piece(35, 78), # Full load: 35-78 kg/h CO2
|
|
135
|
+
]
|
|
136
|
+
),
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Discrete operating modes (on/off equipment):
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
PiecewiseConversion(
|
|
145
|
+
{
|
|
146
|
+
'electricity_in': Piecewise(
|
|
147
|
+
[
|
|
148
|
+
Piece(0, 0), # Off mode: no consumption
|
|
149
|
+
Piece(20, 20), # On mode: fixed 20 kW consumption
|
|
150
|
+
]
|
|
151
|
+
),
|
|
152
|
+
'cooling_out': Piecewise(
|
|
153
|
+
[
|
|
154
|
+
Piece(0, 0), # Off mode: no cooling
|
|
155
|
+
Piece(60, 60), # On mode: fixed 60 kW cooling
|
|
156
|
+
]
|
|
157
|
+
),
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Equipment with forbidden operating range:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
PiecewiseConversion(
|
|
166
|
+
{
|
|
167
|
+
'steam_input': Piecewise(
|
|
168
|
+
[
|
|
169
|
+
Piece(0, 100), # Low pressure operation
|
|
170
|
+
Piece(200, 500), # High pressure (gap: 100-200)
|
|
171
|
+
]
|
|
172
|
+
),
|
|
173
|
+
'power_output': Piecewise(
|
|
174
|
+
[
|
|
175
|
+
Piece(0, 80), # Low efficiency at low pressure
|
|
176
|
+
Piece(180, 400), # High efficiency at high pressure
|
|
177
|
+
]
|
|
178
|
+
),
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Multi-product chemical reactor:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
fx.PiecewiseConversion(
|
|
187
|
+
{
|
|
188
|
+
'feedstock': fx.Piecewise(
|
|
189
|
+
[
|
|
190
|
+
fx.Piece(10, 50), # Small batch: 10-50 kg/h
|
|
191
|
+
fx.Piece(50, 200), # Large batch: 50-200 kg/h
|
|
192
|
+
]
|
|
193
|
+
),
|
|
194
|
+
'product_A': fx.Piecewise(
|
|
195
|
+
[
|
|
196
|
+
fx.Piece(7, 32), # Small batch yield: 70%
|
|
197
|
+
fx.Piece(32, 140), # Large batch yield: 70%
|
|
198
|
+
]
|
|
199
|
+
),
|
|
200
|
+
'product_B': fx.Piecewise(
|
|
201
|
+
[
|
|
202
|
+
fx.Piece(2, 12), # Small batch: 20% to product B
|
|
203
|
+
fx.Piece(12, 45), # Large batch: better selectivity
|
|
204
|
+
]
|
|
205
|
+
),
|
|
206
|
+
'waste': fx.Piecewise(
|
|
207
|
+
[
|
|
208
|
+
fx.Piece(1, 6), # Small batch waste: 10%
|
|
209
|
+
fx.Piece(6, 15), # Large batch waste: 7.5%
|
|
210
|
+
]
|
|
211
|
+
),
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
```
|
|
87
215
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
216
|
+
Common Use Cases:
|
|
217
|
+
- Heat pumps/chillers: COP varies with load and ambient conditions
|
|
218
|
+
- Power plants: Heat rate curves showing fuel efficiency vs output
|
|
219
|
+
- Chemical reactors: Conversion rates and selectivity vs throughput
|
|
220
|
+
- Compressors/pumps: Power consumption vs flow rate
|
|
221
|
+
- Multi-stage processes: Different conversion rates per stage
|
|
222
|
+
- Equipment with minimum loads: Cannot operate below threshold
|
|
223
|
+
- Batch processes: Discrete production campaigns
|
|
94
224
|
|
|
95
|
-
|
|
96
|
-
def has_time_dim(self):
|
|
97
|
-
return self._has_time_dim
|
|
225
|
+
"""
|
|
98
226
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
self._has_time_dim = value
|
|
102
|
-
for piecewise in self.piecewises.values():
|
|
103
|
-
piecewise.has_time_dim = value
|
|
227
|
+
def __init__(self, piecewises: Dict[str, Piecewise]):
|
|
228
|
+
self.piecewises = piecewises
|
|
104
229
|
|
|
105
230
|
def items(self):
|
|
106
231
|
return self.piecewises.items()
|
|
@@ -122,24 +247,118 @@ class PiecewiseEffects(Interface):
|
|
|
122
247
|
"""
|
|
123
248
|
self.piecewise_origin = piecewise_origin
|
|
124
249
|
self.piecewise_shares = piecewise_shares
|
|
125
|
-
self._has_time_dim = False
|
|
126
|
-
self.has_time_dim = False # Inital propagation
|
|
127
250
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
251
|
+
def transform_data(self, flow_system: 'FlowSystem', name_prefix: str):
|
|
252
|
+
raise NotImplementedError('PiecewiseEffects is not yet implemented for non scalar shares')
|
|
253
|
+
# self.piecewise_origin.transform_data(flow_system, f'{name_prefix}|PiecewiseEffects|origin')
|
|
254
|
+
# for name, piecewise in self.piecewise_shares.items():
|
|
255
|
+
# piecewise.transform_data(flow_system, f'{name_prefix}|PiecewiseEffects|{name}')
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@register_class_for_io
|
|
259
|
+
class PiecewiseEffectsPerFlowHour(Interface):
|
|
260
|
+
"""
|
|
261
|
+
Define piecewise linear relationships between flow rate and various effects (costs, emissions, etc.).
|
|
262
|
+
|
|
263
|
+
This class models situations where the relationship between flow rate and effects changes at
|
|
264
|
+
different flow rate levels, such as:
|
|
265
|
+
- Pump efficiency curves across operating ranges
|
|
266
|
+
- Emission factors that vary with operating levels
|
|
267
|
+
- Capacity-dependent transportation costs
|
|
268
|
+
- Decision between different operating modes or suppliers
|
|
269
|
+
- Optional equipment activation with minimum flow requirements
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
piecewise_flow_rate: `Piecewise` defining the valid flow rate segments.
|
|
273
|
+
Each Piece represents a linear segment with (min_flow, max_flow) bounds.
|
|
274
|
+
|
|
275
|
+
piecewise_shares: Dictionary mapping effect names to their `Piecewise`.
|
|
276
|
+
Keys are effect names (e.g., 'Costs', 'CO2', 'Maintenance').
|
|
277
|
+
Values are `Piecewise` objects defining the absolute effect values (not rates/prices).
|
|
278
|
+
|
|
279
|
+
⚠️ IMPORTANT: Values represent total effect amounts, not unit rates.
|
|
280
|
+
For a flow rate of X, the effect value is interpolated from the `Piecewise`.
|
|
281
|
+
This is NOT flow_rate × unit_price (which would be non-linear).
|
|
282
|
+
|
|
283
|
+
Behavior:
|
|
284
|
+
- If the first piece doesn't start at zero, flow rate is automatically bounded
|
|
285
|
+
by piecewise_flow_rate (when OnOffParameters are not used)
|
|
286
|
+
- Each segment represents a linear relationship within that flow rate range
|
|
287
|
+
- Effects are interpolated linearly within each piece
|
|
288
|
+
- All `Piece`s of the different `Piecewise`s at index i are active at the same time
|
|
289
|
+
- A decision whether to utilize the effect can be modeled by defining multiple Pieces for the same flow rate range
|
|
290
|
+
|
|
291
|
+
Examples:
|
|
292
|
+
# Tiered cost structure with increasing rates
|
|
293
|
+
PiecewiseEffectsPerFlowHour(
|
|
294
|
+
piecewise_flow_rate=Piecewise([
|
|
295
|
+
Piece(0, 50), # Low flow segment: 0-50 units
|
|
296
|
+
Piece(50, 200) # High flow segment: 50-200 units
|
|
297
|
+
]),
|
|
298
|
+
piecewise_shares={
|
|
299
|
+
'Costs': Piecewise([
|
|
300
|
+
Piece(0, 500), # At flow=0: cost=0, at flow=50: cost=500
|
|
301
|
+
Piece(500, 2000) # At flow=50: cost=500, at flow=200: cost=2000
|
|
302
|
+
]),
|
|
303
|
+
'CO2': Piecewise([
|
|
304
|
+
Piece(0, 100), # At flow=0: CO2=0, at flow=50: CO2=100
|
|
305
|
+
Piece(100, 800) # At flow=50: CO2=100, at flow=200: CO2=800
|
|
306
|
+
])
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Decision between two suppliers with overlapping flow ranges
|
|
311
|
+
PiecewiseEffectsPerFlowHour(
|
|
312
|
+
piecewise_flow_rate=Piecewise([
|
|
313
|
+
Piece(0, 100), # Supplier A: 0-100 units
|
|
314
|
+
Piece(50, 150) # Supplier B: 50-150 units (overlaps with A)
|
|
315
|
+
]),
|
|
316
|
+
piecewise_shares={
|
|
317
|
+
'Costs': Piecewise([
|
|
318
|
+
Piece(0, 800), # Supplier A: cheaper for low volumes
|
|
319
|
+
Piece(400, 1200) # Supplier B: better rates for high volumes
|
|
320
|
+
])
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
# Flow range 50-100: Optimizer chooses between suppliers based on cost
|
|
324
|
+
|
|
325
|
+
# Optional equipment with minimum activation threshold
|
|
326
|
+
PiecewiseEffectsPerFlowHour(
|
|
327
|
+
piecewise_flow_rate=Piecewise([
|
|
328
|
+
Piece(0, 0), # Equipment off: no flow
|
|
329
|
+
Piece(20, 100) # Equipment on: minimum 20 units required
|
|
330
|
+
]),
|
|
331
|
+
piecewise_shares={
|
|
332
|
+
'Costs': Piecewise([
|
|
333
|
+
Piece(0, 0), # No cost when off
|
|
334
|
+
Piece(200, 800) # Fixed startup cost + variable cost
|
|
335
|
+
]),
|
|
336
|
+
'CO2': Piecewise([
|
|
337
|
+
Piece(0, 0), # No CO2 when off
|
|
338
|
+
Piece(50, 300) # Decreasing CO2 per fuel burn with higher power
|
|
339
|
+
])
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
# Decision: Either flow=0 (off) or flow≥20 (on with minimum threshold)
|
|
343
|
+
|
|
344
|
+
# Equipment efficiency curve (although this might be better modeled as a Flow rather than an effect)
|
|
345
|
+
PiecewiseEffectsPerFlowHour(
|
|
346
|
+
piecewise_flow_rate=Piecewise([Piece(10, 100)]), # Min 10, max 100 units
|
|
347
|
+
piecewise_shares={
|
|
348
|
+
'PowerConsumption': Piecewise([Piece(50, 800)]) # Non-linear efficiency
|
|
349
|
+
}
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
"""
|
|
131
353
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
self.
|
|
135
|
-
self.piecewise_origin.has_time_dim = value
|
|
136
|
-
for piecewise in self.piecewise_shares.values():
|
|
137
|
-
piecewise.has_time_dim = value
|
|
354
|
+
def __init__(self, piecewise_flow_rate: Piecewise, piecewise_shares: Dict[str, Piecewise]):
|
|
355
|
+
self.piecewise_flow_rate = piecewise_flow_rate
|
|
356
|
+
self.piecewise_shares = piecewise_shares
|
|
138
357
|
|
|
139
358
|
def transform_data(self, flow_system: 'FlowSystem', name_prefix: str):
|
|
140
|
-
self.
|
|
141
|
-
for
|
|
142
|
-
piecewise.transform_data(flow_system, f'{name_prefix}|
|
|
359
|
+
self.piecewise_flow_rate.transform_data(flow_system, f'{name_prefix}|PiecewiseEffectsPerFlowHour|origin')
|
|
360
|
+
for name, piecewise in self.piecewise_shares.items():
|
|
361
|
+
piecewise.transform_data(flow_system, f'{name_prefix}|PiecewiseEffectsPerFlowHour|{name}')
|
|
143
362
|
|
|
144
363
|
|
|
145
364
|
@register_class_for_io
|
|
@@ -150,15 +369,14 @@ class InvestParameters(Interface):
|
|
|
150
369
|
|
|
151
370
|
def __init__(
|
|
152
371
|
self,
|
|
153
|
-
fixed_size: Optional[
|
|
154
|
-
minimum_size: Optional[
|
|
155
|
-
maximum_size: Optional[
|
|
372
|
+
fixed_size: Optional[Union[int, float]] = None,
|
|
373
|
+
minimum_size: Optional[Union[int, float]] = None,
|
|
374
|
+
maximum_size: Optional[Union[int, float]] = None,
|
|
156
375
|
optional: bool = True, # Investition ist weglassbar
|
|
157
|
-
fix_effects: Optional['
|
|
158
|
-
specific_effects: Optional['
|
|
376
|
+
fix_effects: Optional['EffectValuesUserScalar'] = None,
|
|
377
|
+
specific_effects: Optional['EffectValuesUserScalar'] = None, # costs per Flow-Unit/Storage-Size/...
|
|
159
378
|
piecewise_effects: Optional[PiecewiseEffects] = None,
|
|
160
|
-
divest_effects: Optional['
|
|
161
|
-
investment_scenarios: Optional[Union[Literal['individual'], List[Union[int, str]]]] = None,
|
|
379
|
+
divest_effects: Optional['EffectValuesUserScalar'] = None,
|
|
162
380
|
):
|
|
163
381
|
"""
|
|
164
382
|
Args:
|
|
@@ -169,99 +387,58 @@ class InvestParameters(Interface):
|
|
|
169
387
|
specific_effects: Specific costs, e.g., in €/kW_nominal or €/m²_nominal.
|
|
170
388
|
Example: {costs: 3, CO2: 0.3} with costs and CO2 representing an Object of class Effect
|
|
171
389
|
(Attention: Annualize costs to chosen period!)
|
|
172
|
-
piecewise_effects:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
390
|
+
piecewise_effects: Linear piecewise relation [invest_pieces, cost_pieces].
|
|
391
|
+
Example 1:
|
|
392
|
+
[ [5, 25, 25, 100], # size in kW
|
|
393
|
+
{costs: [50,250,250,800], # €
|
|
394
|
+
PE: [5, 25, 25, 100] # kWh_PrimaryEnergy
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
Example 2 (if only standard-effect):
|
|
398
|
+
[ [5, 25, 25, 100], # kW # size in kW
|
|
399
|
+
[50,250,250,800] # value for standart effect, typically €
|
|
400
|
+
] # €
|
|
401
|
+
(Attention: Annualize costs to chosen period!)
|
|
402
|
+
(Args 'specific_effects' and 'fix_effects' can be used in parallel to Investsizepieces)
|
|
403
|
+
minimum_size: Min nominal value (only if: size_is_fixed = False). Defaults to CONFIG.modeling.EPSILON.
|
|
404
|
+
maximum_size: Max nominal value (only if: size_is_fixed = False). Defaults to CONFIG.modeling.BIG.
|
|
179
405
|
"""
|
|
180
|
-
self.fix_effects:
|
|
181
|
-
self.divest_effects:
|
|
406
|
+
self.fix_effects: EffectValuesUser = fix_effects or {}
|
|
407
|
+
self.divest_effects: EffectValuesUser = divest_effects or {}
|
|
182
408
|
self.fixed_size = fixed_size
|
|
183
409
|
self.optional = optional
|
|
184
|
-
self.specific_effects:
|
|
410
|
+
self.specific_effects: EffectValuesUser = specific_effects or {}
|
|
185
411
|
self.piecewise_effects = piecewise_effects
|
|
186
412
|
self._minimum_size = minimum_size if minimum_size is not None else CONFIG.modeling.EPSILON
|
|
187
413
|
self._maximum_size = maximum_size if maximum_size is not None else CONFIG.modeling.BIG # default maximum
|
|
188
|
-
self.investment_scenarios = investment_scenarios
|
|
189
414
|
|
|
190
|
-
def transform_data(self, flow_system: 'FlowSystem'
|
|
191
|
-
self.
|
|
192
|
-
self.
|
|
193
|
-
|
|
194
|
-
effect_values=self.fix_effects,
|
|
195
|
-
label_suffix='fix_effects',
|
|
196
|
-
has_time_dim=False,
|
|
197
|
-
has_scenario_dim=True,
|
|
198
|
-
)
|
|
199
|
-
self.divest_effects = flow_system.create_effect_time_series(
|
|
200
|
-
label_prefix=name_prefix,
|
|
201
|
-
effect_values=self.divest_effects,
|
|
202
|
-
label_suffix='divest_effects',
|
|
203
|
-
has_time_dim=False,
|
|
204
|
-
has_scenario_dim=True,
|
|
205
|
-
)
|
|
206
|
-
self.specific_effects = flow_system.create_effect_time_series(
|
|
207
|
-
label_prefix=name_prefix,
|
|
208
|
-
effect_values=self.specific_effects,
|
|
209
|
-
label_suffix='specific_effects',
|
|
210
|
-
has_time_dim=False,
|
|
211
|
-
has_scenario_dim=True,
|
|
212
|
-
)
|
|
213
|
-
if self.piecewise_effects is not None:
|
|
214
|
-
self.piecewise_effects.has_time_dim = False
|
|
215
|
-
self.piecewise_effects.transform_data(flow_system, f'{name_prefix}|PiecewiseEffects')
|
|
216
|
-
|
|
217
|
-
self._minimum_size = flow_system.create_time_series(
|
|
218
|
-
f'{name_prefix}|minimum_size', self.minimum_size, has_time_dim=False, has_scenario_dim=True
|
|
219
|
-
)
|
|
220
|
-
self._maximum_size = flow_system.create_time_series(
|
|
221
|
-
f'{name_prefix}|maximum_size', self.maximum_size, has_time_dim=False, has_scenario_dim=True
|
|
222
|
-
)
|
|
223
|
-
if self.fixed_size is not None:
|
|
224
|
-
self.fixed_size = flow_system.create_time_series(
|
|
225
|
-
f'{name_prefix}|fixed_size', self.fixed_size, has_time_dim=False, has_scenario_dim=True
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
def _plausibility_checks(self, flow_system):
|
|
229
|
-
if isinstance(self.investment_scenarios, list):
|
|
230
|
-
if not set(self.investment_scenarios).issubset(flow_system.time_series_collection.scenarios):
|
|
231
|
-
raise ValueError(
|
|
232
|
-
f'Some scenarios in investment_scenarios are not present in the time_series_collection: '
|
|
233
|
-
f'{set(self.investment_scenarios) - set(flow_system.time_series_collection.scenarios)}'
|
|
234
|
-
)
|
|
235
|
-
if self.investment_scenarios is not None:
|
|
236
|
-
if not self.optional:
|
|
237
|
-
if self.minimum_size is not None or self.fixed_size is not None:
|
|
238
|
-
logger.warning(
|
|
239
|
-
'When using investment_scenarios, minimum_size and fixed_size should only ne used if optional is True.'
|
|
240
|
-
'Otherwise the investment cannot be 0 incertain scenarios while being non-zero in others.'
|
|
241
|
-
)
|
|
415
|
+
def transform_data(self, flow_system: 'FlowSystem'):
|
|
416
|
+
self.fix_effects = flow_system.effects.create_effect_values_dict(self.fix_effects)
|
|
417
|
+
self.divest_effects = flow_system.effects.create_effect_values_dict(self.divest_effects)
|
|
418
|
+
self.specific_effects = flow_system.effects.create_effect_values_dict(self.specific_effects)
|
|
242
419
|
|
|
243
420
|
@property
|
|
244
421
|
def minimum_size(self):
|
|
245
|
-
return self.fixed_size
|
|
422
|
+
return self.fixed_size or self._minimum_size
|
|
246
423
|
|
|
247
424
|
@property
|
|
248
425
|
def maximum_size(self):
|
|
249
|
-
return self.fixed_size
|
|
426
|
+
return self.fixed_size or self._maximum_size
|
|
250
427
|
|
|
251
428
|
|
|
252
429
|
@register_class_for_io
|
|
253
430
|
class OnOffParameters(Interface):
|
|
254
431
|
def __init__(
|
|
255
432
|
self,
|
|
256
|
-
effects_per_switch_on: Optional['
|
|
257
|
-
effects_per_running_hour: Optional['
|
|
258
|
-
on_hours_total_min: Optional[
|
|
259
|
-
on_hours_total_max: Optional[
|
|
260
|
-
consecutive_on_hours_min: Optional[
|
|
261
|
-
consecutive_on_hours_max: Optional[
|
|
262
|
-
consecutive_off_hours_min: Optional[
|
|
263
|
-
consecutive_off_hours_max: Optional[
|
|
264
|
-
switch_on_total_max: Optional[
|
|
433
|
+
effects_per_switch_on: Optional['EffectValuesUser'] = None,
|
|
434
|
+
effects_per_running_hour: Optional['EffectValuesUser'] = None,
|
|
435
|
+
on_hours_total_min: Optional[int] = None,
|
|
436
|
+
on_hours_total_max: Optional[int] = None,
|
|
437
|
+
consecutive_on_hours_min: Optional[NumericData] = None,
|
|
438
|
+
consecutive_on_hours_max: Optional[NumericData] = None,
|
|
439
|
+
consecutive_off_hours_min: Optional[NumericData] = None,
|
|
440
|
+
consecutive_off_hours_max: Optional[NumericData] = None,
|
|
441
|
+
switch_on_total_max: Optional[int] = None,
|
|
265
442
|
force_switch_on: bool = False,
|
|
266
443
|
):
|
|
267
444
|
"""
|
|
@@ -284,8 +461,8 @@ class OnOffParameters(Interface):
|
|
|
284
461
|
switch_on_total_max: max nr of switchOn operations
|
|
285
462
|
force_switch_on: force creation of switch on variable, even if there is no switch_on_total_max
|
|
286
463
|
"""
|
|
287
|
-
self.effects_per_switch_on:
|
|
288
|
-
self.effects_per_running_hour:
|
|
464
|
+
self.effects_per_switch_on: EffectValuesUser = effects_per_switch_on or {}
|
|
465
|
+
self.effects_per_running_hour: EffectValuesUser = effects_per_running_hour or {}
|
|
289
466
|
self.on_hours_total_min: Scalar = on_hours_total_min
|
|
290
467
|
self.on_hours_total_max: Scalar = on_hours_total_max
|
|
291
468
|
self.consecutive_on_hours_min: NumericDataTS = consecutive_on_hours_min
|
|
@@ -314,15 +491,6 @@ class OnOffParameters(Interface):
|
|
|
314
491
|
self.consecutive_off_hours_max = flow_system.create_time_series(
|
|
315
492
|
f'{name_prefix}|consecutive_off_hours_max', self.consecutive_off_hours_max
|
|
316
493
|
)
|
|
317
|
-
self.on_hours_total_max = flow_system.create_time_series(
|
|
318
|
-
f'{name_prefix}|on_hours_total_max', self.on_hours_total_max, has_time_dim=False
|
|
319
|
-
)
|
|
320
|
-
self.on_hours_total_min = flow_system.create_time_series(
|
|
321
|
-
f'{name_prefix}|on_hours_total_min', self.on_hours_total_min, has_time_dim=False
|
|
322
|
-
)
|
|
323
|
-
self.switch_on_total_max = flow_system.create_time_series(
|
|
324
|
-
f'{name_prefix}|switch_on_total_max', self.switch_on_total_max, has_time_dim=False
|
|
325
|
-
)
|
|
326
494
|
|
|
327
495
|
@property
|
|
328
496
|
def use_off(self) -> bool:
|