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.

Files changed (48) hide show
  1. docs/examples/00-Minimal Example.md +1 -1
  2. docs/examples/01-Basic Example.md +1 -1
  3. docs/examples/02-Complex Example.md +1 -1
  4. docs/examples/index.md +1 -1
  5. docs/faq/contribute.md +26 -14
  6. docs/faq/index.md +1 -1
  7. docs/javascripts/mathjax.js +1 -1
  8. docs/user-guide/Mathematical Notation/Bus.md +1 -1
  9. docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +13 -13
  10. docs/user-guide/Mathematical Notation/Flow.md +1 -1
  11. docs/user-guide/Mathematical Notation/LinearConverter.md +2 -2
  12. docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
  13. docs/user-guide/Mathematical Notation/Storage.md +1 -1
  14. docs/user-guide/Mathematical Notation/index.md +1 -1
  15. docs/user-guide/Mathematical Notation/others.md +1 -1
  16. docs/user-guide/index.md +2 -2
  17. flixopt/__init__.py +5 -0
  18. flixopt/aggregation.py +0 -1
  19. flixopt/calculation.py +40 -72
  20. flixopt/commons.py +10 -1
  21. flixopt/components.py +326 -154
  22. flixopt/core.py +459 -966
  23. flixopt/effects.py +67 -270
  24. flixopt/elements.py +76 -84
  25. flixopt/features.py +172 -154
  26. flixopt/flow_system.py +70 -99
  27. flixopt/interface.py +315 -147
  28. flixopt/io.py +27 -56
  29. flixopt/linear_converters.py +3 -3
  30. flixopt/network_app.py +755 -0
  31. flixopt/plotting.py +16 -34
  32. flixopt/results.py +108 -806
  33. flixopt/structure.py +11 -67
  34. flixopt/utils.py +9 -6
  35. {flixopt-2.2.0b0.dist-info → flixopt-2.2.0rc2.dist-info}/METADATA +63 -42
  36. flixopt-2.2.0rc2.dist-info/RECORD +54 -0
  37. {flixopt-2.2.0b0.dist-info → flixopt-2.2.0rc2.dist-info}/WHEEL +1 -1
  38. scripts/extract_release_notes.py +45 -0
  39. docs/release-notes/_template.txt +0 -32
  40. docs/release-notes/index.md +0 -7
  41. docs/release-notes/v2.0.0.md +0 -93
  42. docs/release-notes/v2.0.1.md +0 -12
  43. docs/release-notes/v2.1.0.md +0 -31
  44. docs/release-notes/v2.2.0.md +0 -55
  45. docs/user-guide/Mathematical Notation/Investment.md +0 -115
  46. flixopt-2.2.0b0.dist-info/RECORD +0 -59
  47. {flixopt-2.2.0b0.dist-info → flixopt-2.2.0rc2.dist-info}/licenses/LICENSE +0 -0
  48. {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, Literal, Optional, Union
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, ScenarioData, TimestepData
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 EffectValuesUserScenario, EffectValuesUserTimestep
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: TimestepData, end: TimestepData):
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
- name=f'{name_prefix}|start', data=self.start, has_time_dim=self.has_time_dim, has_scenario_dim=True
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
- def __init__(self, piecewises: Dict[str, Piecewise]):
83
- """
84
- Define a piecewise conversion between multiple Flows.
85
- --> "gaps" can be expressed by a piece not starting at the end of the prior piece: [(1,3), (4,5)]
86
- --> "points" can expressed as piece with same begin and end: [(3,3), (4,4)]
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
- Args:
89
- piecewises: Dict of Piecewises defining the conversion factors. flow labels as keys, piecewise as values
90
- """
91
- self.piecewises = piecewises
92
- self._has_time_dim = True
93
- self.has_time_dim = True # Inital propagation
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
- @property
96
- def has_time_dim(self):
97
- return self._has_time_dim
225
+ """
98
226
 
99
- @has_time_dim.setter
100
- def has_time_dim(self, value):
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
- @property
129
- def has_time_dim(self):
130
- return self._has_time_dim
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
- @has_time_dim.setter
133
- def has_time_dim(self, value):
134
- self._has_time_dim = value
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.piecewise_origin.transform_data(flow_system, f'{name_prefix}|PiecewiseEffects|origin')
141
- for effect, piecewise in self.piecewise_shares.items():
142
- piecewise.transform_data(flow_system, f'{name_prefix}|PiecewiseEffects|{effect}')
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[ScenarioData] = None,
154
- minimum_size: Optional[ScenarioData] = None,
155
- maximum_size: Optional[ScenarioData] = None,
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['EffectValuesUserScenario'] = None,
158
- specific_effects: Optional['EffectValuesUserScenario'] = None, # costs per Flow-Unit/Storage-Size/...
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['EffectValuesUserScenario'] = None,
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: Define the effects of the investment as a piecewise function of the size of the investment.
173
- minimum_size: Minimum possible size of the investment.
174
- maximum_size: Maximum possible size of the investment.
175
- investment_scenarios: For which scenarios to optimize the size for.
176
- - 'individual': Optimize the size of each scenario individually
177
- - List of scenario names: Optimize the size for the passed scenario names (equal size in all). All other scenarios will have the size 0.
178
- - None: Equals to a list of all scenarios (default)
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: EffectValuesUserScenario = fix_effects if fix_effects is not None else {}
181
- self.divest_effects: EffectValuesUserScenario = divest_effects if divest_effects is not None else {}
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: EffectValuesUserScenario = specific_effects if specific_effects is not None else {}
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', name_prefix: str):
191
- self._plausibility_checks(flow_system)
192
- self.fix_effects = flow_system.create_effect_time_series(
193
- label_prefix=name_prefix,
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 if self.fixed_size is not None else self._minimum_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 if self.fixed_size is not None else self._maximum_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['EffectValuesUserTimestep'] = None,
257
- effects_per_running_hour: Optional['EffectValuesUserTimestep'] = None,
258
- on_hours_total_min: Optional[ScenarioData] = None,
259
- on_hours_total_max: Optional[ScenarioData] = None,
260
- consecutive_on_hours_min: Optional[TimestepData] = None,
261
- consecutive_on_hours_max: Optional[TimestepData] = None,
262
- consecutive_off_hours_min: Optional[TimestepData] = None,
263
- consecutive_off_hours_max: Optional[TimestepData] = None,
264
- switch_on_total_max: Optional[ScenarioData] = None,
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: EffectValuesUserTimestep = effects_per_switch_on or {}
288
- self.effects_per_running_hour: EffectValuesUserTimestep = effects_per_running_hour or {}
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: