flixopt 2.1.0__py3-none-any.whl → 2.2.0b0__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/release-notes/v2.2.0.md +55 -0
- docs/user-guide/Mathematical Notation/Investment.md +115 -0
- flixopt/calculation.py +65 -37
- flixopt/components.py +119 -74
- flixopt/core.py +966 -451
- flixopt/effects.py +269 -65
- flixopt/elements.py +83 -52
- flixopt/features.py +134 -85
- flixopt/flow_system.py +99 -16
- flixopt/interface.py +142 -51
- flixopt/io.py +56 -27
- flixopt/linear_converters.py +3 -3
- flixopt/plotting.py +34 -16
- flixopt/results.py +807 -109
- flixopt/structure.py +64 -10
- flixopt/utils.py +6 -9
- {flixopt-2.1.0.dist-info → flixopt-2.2.0b0.dist-info}/METADATA +1 -1
- {flixopt-2.1.0.dist-info → flixopt-2.2.0b0.dist-info}/RECORD +21 -20
- {flixopt-2.1.0.dist-info → flixopt-2.2.0b0.dist-info}/WHEEL +1 -1
- {flixopt-2.1.0.dist-info → flixopt-2.2.0b0.dist-info}/top_level.txt +0 -1
- site/release-notes/_template.txt +0 -32
- {flixopt-2.1.0.dist-info → flixopt-2.2.0b0.dist-info}/licenses/LICENSE +0 -0
flixopt/elements.py
CHANGED
|
@@ -10,8 +10,8 @@ import linopy
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
12
|
from .config import CONFIG
|
|
13
|
-
from .core import
|
|
14
|
-
from .effects import
|
|
13
|
+
from .core import PlausibilityError, Scalar, ScenarioData, TimestepData, extract_data
|
|
14
|
+
from .effects import EffectValuesUserTimestep
|
|
15
15
|
from .features import InvestmentModel, OnOffModel, PreventSimultaneousUsageModel
|
|
16
16
|
from .interface import InvestParameters, OnOffParameters
|
|
17
17
|
from .structure import Element, ElementModel, SystemModel, register_class_for_io
|
|
@@ -47,8 +47,8 @@ class Component(Element):
|
|
|
47
47
|
inputs: input flows.
|
|
48
48
|
outputs: output flows.
|
|
49
49
|
on_off_parameters: Information about on and off state of Component.
|
|
50
|
-
Component is On/Off, if all connected Flows are On/Off.
|
|
51
|
-
|
|
50
|
+
Component is On/Off, if all connected Flows are On/Off. This induces an On-Variable (binary) in all Flows!
|
|
51
|
+
If possible, use OnOffParameters in a single Flow instead to keep the number of binary variables low.
|
|
52
52
|
See class OnOffParameters.
|
|
53
53
|
prevent_simultaneous_flows: Define a Group of Flows. Only one them can be on at a time.
|
|
54
54
|
Induces On-Variable in all Flows! If possible, use OnOffParameters in a single Flow instead.
|
|
@@ -96,7 +96,7 @@ class Bus(Element):
|
|
|
96
96
|
"""
|
|
97
97
|
|
|
98
98
|
def __init__(
|
|
99
|
-
self, label: str, excess_penalty_per_flow_hour: Optional[
|
|
99
|
+
self, label: str, excess_penalty_per_flow_hour: Optional[TimestepData] = 1e5, meta_data: Optional[Dict] = None
|
|
100
100
|
):
|
|
101
101
|
"""
|
|
102
102
|
Args:
|
|
@@ -154,17 +154,17 @@ class Flow(Element):
|
|
|
154
154
|
self,
|
|
155
155
|
label: str,
|
|
156
156
|
bus: str,
|
|
157
|
-
size: Union[
|
|
158
|
-
fixed_relative_profile: Optional[
|
|
159
|
-
relative_minimum:
|
|
160
|
-
relative_maximum:
|
|
161
|
-
effects_per_flow_hour: Optional[
|
|
157
|
+
size: Union[ScenarioData, InvestParameters] = None,
|
|
158
|
+
fixed_relative_profile: Optional[TimestepData] = None,
|
|
159
|
+
relative_minimum: TimestepData = 0,
|
|
160
|
+
relative_maximum: TimestepData = 1,
|
|
161
|
+
effects_per_flow_hour: Optional[EffectValuesUserTimestep] = None,
|
|
162
162
|
on_off_parameters: Optional[OnOffParameters] = None,
|
|
163
|
-
flow_hours_total_max: Optional[
|
|
164
|
-
flow_hours_total_min: Optional[
|
|
165
|
-
load_factor_min: Optional[
|
|
166
|
-
load_factor_max: Optional[
|
|
167
|
-
previous_flow_rate: Optional[
|
|
163
|
+
flow_hours_total_max: Optional[ScenarioData] = None,
|
|
164
|
+
flow_hours_total_min: Optional[ScenarioData] = None,
|
|
165
|
+
load_factor_min: Optional[ScenarioData] = None,
|
|
166
|
+
load_factor_max: Optional[ScenarioData] = None,
|
|
167
|
+
previous_flow_rate: Optional[ScenarioData] = None,
|
|
168
168
|
meta_data: Optional[Dict] = None,
|
|
169
169
|
):
|
|
170
170
|
r"""
|
|
@@ -193,7 +193,8 @@ class Flow(Element):
|
|
|
193
193
|
(relative_minimum and relative_maximum are ignored)
|
|
194
194
|
used for fixed load or supply profiles, i.g. heat demand, wind-power, solarthermal
|
|
195
195
|
If the load-profile is just an upper limit, use relative_maximum instead.
|
|
196
|
-
previous_flow_rate: previous flow rate of the
|
|
196
|
+
previous_flow_rate: previous flow rate of the flow. Used to determine if and how long the
|
|
197
|
+
flow is already on / off. If None, the flow is considered to be off for one timestep.
|
|
197
198
|
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
198
199
|
"""
|
|
199
200
|
super().__init__(label, meta_data=meta_data)
|
|
@@ -247,10 +248,25 @@ class Flow(Element):
|
|
|
247
248
|
self.effects_per_flow_hour = flow_system.create_effect_time_series(
|
|
248
249
|
self.label_full, self.effects_per_flow_hour, 'per_flow_hour'
|
|
249
250
|
)
|
|
251
|
+
self.flow_hours_total_max = flow_system.create_time_series(
|
|
252
|
+
f'{self.label_full}|flow_hours_total_max', self.flow_hours_total_max, has_time_dim=False
|
|
253
|
+
)
|
|
254
|
+
self.flow_hours_total_min = flow_system.create_time_series(
|
|
255
|
+
f'{self.label_full}|flow_hours_total_min', self.flow_hours_total_min, has_time_dim=False
|
|
256
|
+
)
|
|
257
|
+
self.load_factor_max = flow_system.create_time_series(
|
|
258
|
+
f'{self.label_full}|load_factor_max', self.load_factor_max, has_time_dim=False
|
|
259
|
+
)
|
|
260
|
+
self.load_factor_min = flow_system.create_time_series(
|
|
261
|
+
f'{self.label_full}|load_factor_min', self.load_factor_min, has_time_dim=False
|
|
262
|
+
)
|
|
263
|
+
|
|
250
264
|
if self.on_off_parameters is not None:
|
|
251
265
|
self.on_off_parameters.transform_data(flow_system, self.label_full)
|
|
252
266
|
if isinstance(self.size, InvestParameters):
|
|
253
|
-
self.size.transform_data(flow_system)
|
|
267
|
+
self.size.transform_data(flow_system, self.label_full)
|
|
268
|
+
else:
|
|
269
|
+
self.size = flow_system.create_time_series(f'{self.label_full}|size', self.size, has_time_dim=False)
|
|
254
270
|
|
|
255
271
|
def infos(self, use_numpy: bool = True, use_element_label: bool = False) -> Dict:
|
|
256
272
|
infos = super().infos(use_numpy, use_element_label)
|
|
@@ -268,8 +284,8 @@ class Flow(Element):
|
|
|
268
284
|
if np.any(self.relative_minimum > self.relative_maximum):
|
|
269
285
|
raise PlausibilityError(self.label_full + ': Take care, that relative_minimum <= relative_maximum!')
|
|
270
286
|
|
|
271
|
-
if (
|
|
272
|
-
|
|
287
|
+
if not isinstance(self.size, InvestParameters) and (
|
|
288
|
+
np.any(self.size == CONFIG.modeling.BIG) and self.fixed_relative_profile is not None
|
|
273
289
|
): # Default Size --> Most likely by accident
|
|
274
290
|
logger.warning(
|
|
275
291
|
f'Flow "{self.label}" has no size assigned, but a "fixed_relative_profile". '
|
|
@@ -278,15 +294,14 @@ class Flow(Element):
|
|
|
278
294
|
)
|
|
279
295
|
|
|
280
296
|
if self.fixed_relative_profile is not None and self.on_off_parameters is not None:
|
|
281
|
-
|
|
282
|
-
f'Flow {self.label} has both a fixed_relative_profile and an on_off_parameters.
|
|
283
|
-
f'
|
|
284
|
-
f'if you want to allow flows to be switched on and off.'
|
|
297
|
+
logger.warning(
|
|
298
|
+
f'Flow {self.label} has both a fixed_relative_profile and an on_off_parameters.'
|
|
299
|
+
f'This will allow the flow to be switched on and off, effectively differing from the fixed_flow_rate.'
|
|
285
300
|
)
|
|
286
301
|
|
|
287
302
|
if (self.relative_minimum > 0).any() and self.on_off_parameters is None:
|
|
288
303
|
logger.warning(
|
|
289
|
-
f'Flow {self.label} has a relative_minimum of {self.relative_minimum.
|
|
304
|
+
f'Flow {self.label} has a relative_minimum of {self.relative_minimum.selected_data} and no on_off_parameters. '
|
|
290
305
|
f'This prevents the flow_rate from switching off (flow_rate = 0). '
|
|
291
306
|
f'Consider using on_off_parameters to allow the flow to be switched on and off.'
|
|
292
307
|
)
|
|
@@ -322,7 +337,7 @@ class FlowModel(ElementModel):
|
|
|
322
337
|
self._model.add_variables(
|
|
323
338
|
lower=self.flow_rate_lower_bound,
|
|
324
339
|
upper=self.flow_rate_upper_bound,
|
|
325
|
-
coords=self._model.
|
|
340
|
+
coords=self._model.get_coords(),
|
|
326
341
|
name=f'{self.label_full}|flow_rate',
|
|
327
342
|
),
|
|
328
343
|
'flow_rate',
|
|
@@ -361,9 +376,9 @@ class FlowModel(ElementModel):
|
|
|
361
376
|
|
|
362
377
|
self.total_flow_hours = self.add(
|
|
363
378
|
self._model.add_variables(
|
|
364
|
-
lower=self.element.flow_hours_total_min
|
|
365
|
-
upper=self.element.flow_hours_total_max
|
|
366
|
-
coords=
|
|
379
|
+
lower=extract_data(self.element.flow_hours_total_min, 0),
|
|
380
|
+
upper=extract_data(self.element.flow_hours_total_max, np.inf),
|
|
381
|
+
coords=self._model.get_coords(time_dim=False),
|
|
367
382
|
name=f'{self.label_full}|total_flow_hours',
|
|
368
383
|
),
|
|
369
384
|
'total_flow_hours',
|
|
@@ -371,7 +386,7 @@ class FlowModel(ElementModel):
|
|
|
371
386
|
|
|
372
387
|
self.add(
|
|
373
388
|
self._model.add_constraints(
|
|
374
|
-
self.total_flow_hours == (self.flow_rate * self._model.hours_per_step).sum(),
|
|
389
|
+
self.total_flow_hours == (self.flow_rate * self._model.hours_per_step).sum('time'),
|
|
375
390
|
name=f'{self.label_full}|total_flow_hours',
|
|
376
391
|
),
|
|
377
392
|
'total_flow_hours',
|
|
@@ -383,13 +398,21 @@ class FlowModel(ElementModel):
|
|
|
383
398
|
# Shares
|
|
384
399
|
self._create_shares()
|
|
385
400
|
|
|
401
|
+
def results_structure(self):
|
|
402
|
+
return {
|
|
403
|
+
**super().results_structure(),
|
|
404
|
+
'start': self.element.bus if self.element.is_input_in_component else self.element.component,
|
|
405
|
+
'end': self.element.component if self.element.is_input_in_component else self.element.bus,
|
|
406
|
+
'component': self.element.component,
|
|
407
|
+
}
|
|
408
|
+
|
|
386
409
|
def _create_shares(self):
|
|
387
410
|
# Arbeitskosten:
|
|
388
411
|
if self.element.effects_per_flow_hour != {}:
|
|
389
412
|
self._model.effects.add_share_to_effects(
|
|
390
413
|
name=self.label_full, # Use the full label of the element
|
|
391
414
|
expressions={
|
|
392
|
-
effect: self.flow_rate * self._model.hours_per_step * factor.
|
|
415
|
+
effect: self.flow_rate * self._model.hours_per_step * factor.selected_data
|
|
393
416
|
for effect, factor in self.element.effects_per_flow_hour.items()
|
|
394
417
|
},
|
|
395
418
|
target='operation',
|
|
@@ -401,7 +424,7 @@ class FlowModel(ElementModel):
|
|
|
401
424
|
# eq: var_sumFlowHours <= size * dt_tot * load_factor_max
|
|
402
425
|
if self.element.load_factor_max is not None:
|
|
403
426
|
name_short = 'load_factor_max'
|
|
404
|
-
flow_hours_per_size_max = self._model.hours_per_step.sum() * self.element.load_factor_max
|
|
427
|
+
flow_hours_per_size_max = self._model.hours_per_step.sum('time') * self.element.load_factor_max
|
|
405
428
|
size = self.element.size if self._investment is None else self._investment.size
|
|
406
429
|
|
|
407
430
|
self.add(
|
|
@@ -415,7 +438,7 @@ class FlowModel(ElementModel):
|
|
|
415
438
|
# eq: size * sum(dt)* load_factor_min <= var_sumFlowHours
|
|
416
439
|
if self.element.load_factor_min is not None:
|
|
417
440
|
name_short = 'load_factor_min'
|
|
418
|
-
flow_hours_per_size_min = self._model.hours_per_step.sum() * self.element.load_factor_min
|
|
441
|
+
flow_hours_per_size_min = self._model.hours_per_step.sum('time') * self.element.load_factor_min
|
|
419
442
|
size = self.element.size if self._investment is None else self._investment.size
|
|
420
443
|
|
|
421
444
|
self.add(
|
|
@@ -427,34 +450,36 @@ class FlowModel(ElementModel):
|
|
|
427
450
|
)
|
|
428
451
|
|
|
429
452
|
@property
|
|
430
|
-
def flow_rate_bounds_on(self) -> Tuple[
|
|
453
|
+
def flow_rate_bounds_on(self) -> Tuple[TimestepData, TimestepData]:
|
|
431
454
|
"""Returns absolute flow rate bounds. Important for OnOffModel"""
|
|
432
455
|
relative_minimum, relative_maximum = self.flow_rate_lower_bound_relative, self.flow_rate_upper_bound_relative
|
|
433
456
|
size = self.element.size
|
|
434
457
|
if not isinstance(size, InvestParameters):
|
|
435
|
-
return relative_minimum * size, relative_maximum * size
|
|
458
|
+
return relative_minimum * extract_data(size), relative_maximum * extract_data(size)
|
|
436
459
|
if size.fixed_size is not None:
|
|
437
|
-
return relative_minimum * size.fixed_size,
|
|
438
|
-
|
|
460
|
+
return (relative_minimum * extract_data(size.fixed_size),
|
|
461
|
+
relative_maximum * extract_data(size.fixed_size))
|
|
462
|
+
return (relative_minimum * extract_data(size.minimum_size),
|
|
463
|
+
relative_maximum * extract_data(size.maximum_size))
|
|
439
464
|
|
|
440
465
|
@property
|
|
441
|
-
def flow_rate_lower_bound_relative(self) ->
|
|
466
|
+
def flow_rate_lower_bound_relative(self) -> TimestepData:
|
|
442
467
|
"""Returns the lower bound of the flow_rate relative to its size"""
|
|
443
468
|
fixed_profile = self.element.fixed_relative_profile
|
|
444
469
|
if fixed_profile is None:
|
|
445
|
-
return self.element.relative_minimum
|
|
446
|
-
return fixed_profile
|
|
470
|
+
return extract_data(self.element.relative_minimum)
|
|
471
|
+
return extract_data(fixed_profile)
|
|
447
472
|
|
|
448
473
|
@property
|
|
449
|
-
def flow_rate_upper_bound_relative(self) ->
|
|
474
|
+
def flow_rate_upper_bound_relative(self) -> TimestepData:
|
|
450
475
|
""" Returns the upper bound of the flow_rate relative to its size"""
|
|
451
476
|
fixed_profile = self.element.fixed_relative_profile
|
|
452
477
|
if fixed_profile is None:
|
|
453
|
-
return self.element.relative_maximum
|
|
454
|
-
return fixed_profile
|
|
478
|
+
return extract_data(self.element.relative_maximum)
|
|
479
|
+
return extract_data(fixed_profile)
|
|
455
480
|
|
|
456
481
|
@property
|
|
457
|
-
def flow_rate_lower_bound(self) ->
|
|
482
|
+
def flow_rate_lower_bound(self) -> TimestepData:
|
|
458
483
|
"""
|
|
459
484
|
Returns the minimum bound the flow_rate can reach.
|
|
460
485
|
Further constraining might be done in OnOffModel and InvestmentModel
|
|
@@ -464,18 +489,18 @@ class FlowModel(ElementModel):
|
|
|
464
489
|
if isinstance(self.element.size, InvestParameters):
|
|
465
490
|
if self.element.size.optional:
|
|
466
491
|
return 0
|
|
467
|
-
return self.flow_rate_lower_bound_relative * self.element.size.minimum_size
|
|
468
|
-
return self.flow_rate_lower_bound_relative * self.element.size
|
|
492
|
+
return self.flow_rate_lower_bound_relative * extract_data(self.element.size.minimum_size)
|
|
493
|
+
return self.flow_rate_lower_bound_relative * extract_data(self.element.size)
|
|
469
494
|
|
|
470
495
|
@property
|
|
471
|
-
def flow_rate_upper_bound(self) ->
|
|
496
|
+
def flow_rate_upper_bound(self) -> TimestepData:
|
|
472
497
|
"""
|
|
473
498
|
Returns the maximum bound the flow_rate can reach.
|
|
474
499
|
Further constraining might be done in OnOffModel and InvestmentModel
|
|
475
500
|
"""
|
|
476
501
|
if isinstance(self.element.size, InvestParameters):
|
|
477
|
-
return self.flow_rate_upper_bound_relative * self.element.size.maximum_size
|
|
478
|
-
return self.flow_rate_upper_bound_relative * self.element.size
|
|
502
|
+
return self.flow_rate_upper_bound_relative * extract_data(self.element.size.maximum_size)
|
|
503
|
+
return self.flow_rate_upper_bound_relative * extract_data(self.element.size)
|
|
479
504
|
|
|
480
505
|
|
|
481
506
|
class BusModel(ElementModel):
|
|
@@ -496,14 +521,18 @@ class BusModel(ElementModel):
|
|
|
496
521
|
# Fehlerplus/-minus:
|
|
497
522
|
if self.element.with_excess:
|
|
498
523
|
excess_penalty = np.multiply(
|
|
499
|
-
self._model.hours_per_step, self.element.excess_penalty_per_flow_hour.
|
|
524
|
+
self._model.hours_per_step, self.element.excess_penalty_per_flow_hour.selected_data
|
|
500
525
|
)
|
|
501
526
|
self.excess_input = self.add(
|
|
502
|
-
self._model.add_variables(
|
|
527
|
+
self._model.add_variables(
|
|
528
|
+
lower=0, coords=self._model.get_coords(), name=f'{self.label_full}|excess_input'
|
|
529
|
+
),
|
|
503
530
|
'excess_input',
|
|
504
531
|
)
|
|
505
532
|
self.excess_output = self.add(
|
|
506
|
-
self._model.add_variables(
|
|
533
|
+
self._model.add_variables(
|
|
534
|
+
lower=0, coords=self._model.get_coords(), name=f'{self.label_full}|excess_output'
|
|
535
|
+
),
|
|
507
536
|
'excess_output',
|
|
508
537
|
)
|
|
509
538
|
eq_bus_balance.lhs -= -self.excess_input + self.excess_output
|
|
@@ -518,7 +547,8 @@ class BusModel(ElementModel):
|
|
|
518
547
|
inputs.append(self.excess_input.name)
|
|
519
548
|
if self.excess_output is not None:
|
|
520
549
|
outputs.append(self.excess_output.name)
|
|
521
|
-
return {**super().results_structure(), 'inputs': inputs, 'outputs': outputs
|
|
550
|
+
return {**super().results_structure(), 'inputs': inputs, 'outputs': outputs,
|
|
551
|
+
'flows': [flow.label_full for flow in self.element.inputs + self.element.outputs]}
|
|
522
552
|
|
|
523
553
|
|
|
524
554
|
class ComponentModel(ElementModel):
|
|
@@ -571,4 +601,5 @@ class ComponentModel(ElementModel):
|
|
|
571
601
|
**super().results_structure(),
|
|
572
602
|
'inputs': [flow.model.flow_rate.name for flow in self.element.inputs],
|
|
573
603
|
'outputs': [flow.model.flow_rate.name for flow in self.element.outputs],
|
|
604
|
+
'flows': [flow.label_full for flow in self.element.inputs + self.element.outputs],
|
|
574
605
|
}
|