flixopt 2.1.1__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 +115 -73
- flixopt/core.py +966 -451
- flixopt/effects.py +269 -65
- flixopt/elements.py +79 -49
- 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 +806 -108
- flixopt/structure.py +64 -10
- flixopt/utils.py +6 -9
- {flixopt-2.1.1.dist-info → flixopt-2.2.0b0.dist-info}/METADATA +1 -1
- {flixopt-2.1.1.dist-info → flixopt-2.2.0b0.dist-info}/RECORD +21 -21
- {flixopt-2.1.1.dist-info → flixopt-2.2.0b0.dist-info}/WHEEL +1 -1
- {flixopt-2.1.1.dist-info → flixopt-2.2.0b0.dist-info}/top_level.txt +0 -1
- docs/release-notes/v2.1.1.md +0 -11
- site/release-notes/_template.txt +0 -32
- {flixopt-2.1.1.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
|
|
@@ -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"""
|
|
@@ -248,10 +248,25 @@ class Flow(Element):
|
|
|
248
248
|
self.effects_per_flow_hour = flow_system.create_effect_time_series(
|
|
249
249
|
self.label_full, self.effects_per_flow_hour, 'per_flow_hour'
|
|
250
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
|
+
|
|
251
264
|
if self.on_off_parameters is not None:
|
|
252
265
|
self.on_off_parameters.transform_data(flow_system, self.label_full)
|
|
253
266
|
if isinstance(self.size, InvestParameters):
|
|
254
|
-
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)
|
|
255
270
|
|
|
256
271
|
def infos(self, use_numpy: bool = True, use_element_label: bool = False) -> Dict:
|
|
257
272
|
infos = super().infos(use_numpy, use_element_label)
|
|
@@ -269,8 +284,8 @@ class Flow(Element):
|
|
|
269
284
|
if np.any(self.relative_minimum > self.relative_maximum):
|
|
270
285
|
raise PlausibilityError(self.label_full + ': Take care, that relative_minimum <= relative_maximum!')
|
|
271
286
|
|
|
272
|
-
if (
|
|
273
|
-
|
|
287
|
+
if not isinstance(self.size, InvestParameters) and (
|
|
288
|
+
np.any(self.size == CONFIG.modeling.BIG) and self.fixed_relative_profile is not None
|
|
274
289
|
): # Default Size --> Most likely by accident
|
|
275
290
|
logger.warning(
|
|
276
291
|
f'Flow "{self.label}" has no size assigned, but a "fixed_relative_profile". '
|
|
@@ -279,15 +294,14 @@ class Flow(Element):
|
|
|
279
294
|
)
|
|
280
295
|
|
|
281
296
|
if self.fixed_relative_profile is not None and self.on_off_parameters is not None:
|
|
282
|
-
|
|
283
|
-
f'Flow {self.label} has both a fixed_relative_profile and an on_off_parameters.
|
|
284
|
-
f'
|
|
285
|
-
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.'
|
|
286
300
|
)
|
|
287
301
|
|
|
288
302
|
if (self.relative_minimum > 0).any() and self.on_off_parameters is None:
|
|
289
303
|
logger.warning(
|
|
290
|
-
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. '
|
|
291
305
|
f'This prevents the flow_rate from switching off (flow_rate = 0). '
|
|
292
306
|
f'Consider using on_off_parameters to allow the flow to be switched on and off.'
|
|
293
307
|
)
|
|
@@ -323,7 +337,7 @@ class FlowModel(ElementModel):
|
|
|
323
337
|
self._model.add_variables(
|
|
324
338
|
lower=self.flow_rate_lower_bound,
|
|
325
339
|
upper=self.flow_rate_upper_bound,
|
|
326
|
-
coords=self._model.
|
|
340
|
+
coords=self._model.get_coords(),
|
|
327
341
|
name=f'{self.label_full}|flow_rate',
|
|
328
342
|
),
|
|
329
343
|
'flow_rate',
|
|
@@ -362,9 +376,9 @@ class FlowModel(ElementModel):
|
|
|
362
376
|
|
|
363
377
|
self.total_flow_hours = self.add(
|
|
364
378
|
self._model.add_variables(
|
|
365
|
-
lower=self.element.flow_hours_total_min
|
|
366
|
-
upper=self.element.flow_hours_total_max
|
|
367
|
-
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),
|
|
368
382
|
name=f'{self.label_full}|total_flow_hours',
|
|
369
383
|
),
|
|
370
384
|
'total_flow_hours',
|
|
@@ -372,7 +386,7 @@ class FlowModel(ElementModel):
|
|
|
372
386
|
|
|
373
387
|
self.add(
|
|
374
388
|
self._model.add_constraints(
|
|
375
|
-
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'),
|
|
376
390
|
name=f'{self.label_full}|total_flow_hours',
|
|
377
391
|
),
|
|
378
392
|
'total_flow_hours',
|
|
@@ -384,13 +398,21 @@ class FlowModel(ElementModel):
|
|
|
384
398
|
# Shares
|
|
385
399
|
self._create_shares()
|
|
386
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
|
+
|
|
387
409
|
def _create_shares(self):
|
|
388
410
|
# Arbeitskosten:
|
|
389
411
|
if self.element.effects_per_flow_hour != {}:
|
|
390
412
|
self._model.effects.add_share_to_effects(
|
|
391
413
|
name=self.label_full, # Use the full label of the element
|
|
392
414
|
expressions={
|
|
393
|
-
effect: self.flow_rate * self._model.hours_per_step * factor.
|
|
415
|
+
effect: self.flow_rate * self._model.hours_per_step * factor.selected_data
|
|
394
416
|
for effect, factor in self.element.effects_per_flow_hour.items()
|
|
395
417
|
},
|
|
396
418
|
target='operation',
|
|
@@ -402,7 +424,7 @@ class FlowModel(ElementModel):
|
|
|
402
424
|
# eq: var_sumFlowHours <= size * dt_tot * load_factor_max
|
|
403
425
|
if self.element.load_factor_max is not None:
|
|
404
426
|
name_short = 'load_factor_max'
|
|
405
|
-
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
|
|
406
428
|
size = self.element.size if self._investment is None else self._investment.size
|
|
407
429
|
|
|
408
430
|
self.add(
|
|
@@ -416,7 +438,7 @@ class FlowModel(ElementModel):
|
|
|
416
438
|
# eq: size * sum(dt)* load_factor_min <= var_sumFlowHours
|
|
417
439
|
if self.element.load_factor_min is not None:
|
|
418
440
|
name_short = 'load_factor_min'
|
|
419
|
-
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
|
|
420
442
|
size = self.element.size if self._investment is None else self._investment.size
|
|
421
443
|
|
|
422
444
|
self.add(
|
|
@@ -428,34 +450,36 @@ class FlowModel(ElementModel):
|
|
|
428
450
|
)
|
|
429
451
|
|
|
430
452
|
@property
|
|
431
|
-
def flow_rate_bounds_on(self) -> Tuple[
|
|
453
|
+
def flow_rate_bounds_on(self) -> Tuple[TimestepData, TimestepData]:
|
|
432
454
|
"""Returns absolute flow rate bounds. Important for OnOffModel"""
|
|
433
455
|
relative_minimum, relative_maximum = self.flow_rate_lower_bound_relative, self.flow_rate_upper_bound_relative
|
|
434
456
|
size = self.element.size
|
|
435
457
|
if not isinstance(size, InvestParameters):
|
|
436
|
-
return relative_minimum * size, relative_maximum * size
|
|
458
|
+
return relative_minimum * extract_data(size), relative_maximum * extract_data(size)
|
|
437
459
|
if size.fixed_size is not None:
|
|
438
|
-
return relative_minimum * size.fixed_size,
|
|
439
|
-
|
|
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))
|
|
440
464
|
|
|
441
465
|
@property
|
|
442
|
-
def flow_rate_lower_bound_relative(self) ->
|
|
466
|
+
def flow_rate_lower_bound_relative(self) -> TimestepData:
|
|
443
467
|
"""Returns the lower bound of the flow_rate relative to its size"""
|
|
444
468
|
fixed_profile = self.element.fixed_relative_profile
|
|
445
469
|
if fixed_profile is None:
|
|
446
|
-
return self.element.relative_minimum
|
|
447
|
-
return fixed_profile
|
|
470
|
+
return extract_data(self.element.relative_minimum)
|
|
471
|
+
return extract_data(fixed_profile)
|
|
448
472
|
|
|
449
473
|
@property
|
|
450
|
-
def flow_rate_upper_bound_relative(self) ->
|
|
474
|
+
def flow_rate_upper_bound_relative(self) -> TimestepData:
|
|
451
475
|
""" Returns the upper bound of the flow_rate relative to its size"""
|
|
452
476
|
fixed_profile = self.element.fixed_relative_profile
|
|
453
477
|
if fixed_profile is None:
|
|
454
|
-
return self.element.relative_maximum
|
|
455
|
-
return fixed_profile
|
|
478
|
+
return extract_data(self.element.relative_maximum)
|
|
479
|
+
return extract_data(fixed_profile)
|
|
456
480
|
|
|
457
481
|
@property
|
|
458
|
-
def flow_rate_lower_bound(self) ->
|
|
482
|
+
def flow_rate_lower_bound(self) -> TimestepData:
|
|
459
483
|
"""
|
|
460
484
|
Returns the minimum bound the flow_rate can reach.
|
|
461
485
|
Further constraining might be done in OnOffModel and InvestmentModel
|
|
@@ -465,18 +489,18 @@ class FlowModel(ElementModel):
|
|
|
465
489
|
if isinstance(self.element.size, InvestParameters):
|
|
466
490
|
if self.element.size.optional:
|
|
467
491
|
return 0
|
|
468
|
-
return self.flow_rate_lower_bound_relative * self.element.size.minimum_size
|
|
469
|
-
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)
|
|
470
494
|
|
|
471
495
|
@property
|
|
472
|
-
def flow_rate_upper_bound(self) ->
|
|
496
|
+
def flow_rate_upper_bound(self) -> TimestepData:
|
|
473
497
|
"""
|
|
474
498
|
Returns the maximum bound the flow_rate can reach.
|
|
475
499
|
Further constraining might be done in OnOffModel and InvestmentModel
|
|
476
500
|
"""
|
|
477
501
|
if isinstance(self.element.size, InvestParameters):
|
|
478
|
-
return self.flow_rate_upper_bound_relative * self.element.size.maximum_size
|
|
479
|
-
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)
|
|
480
504
|
|
|
481
505
|
|
|
482
506
|
class BusModel(ElementModel):
|
|
@@ -497,14 +521,18 @@ class BusModel(ElementModel):
|
|
|
497
521
|
# Fehlerplus/-minus:
|
|
498
522
|
if self.element.with_excess:
|
|
499
523
|
excess_penalty = np.multiply(
|
|
500
|
-
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
|
|
501
525
|
)
|
|
502
526
|
self.excess_input = self.add(
|
|
503
|
-
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
|
+
),
|
|
504
530
|
'excess_input',
|
|
505
531
|
)
|
|
506
532
|
self.excess_output = self.add(
|
|
507
|
-
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
|
+
),
|
|
508
536
|
'excess_output',
|
|
509
537
|
)
|
|
510
538
|
eq_bus_balance.lhs -= -self.excess_input + self.excess_output
|
|
@@ -519,7 +547,8 @@ class BusModel(ElementModel):
|
|
|
519
547
|
inputs.append(self.excess_input.name)
|
|
520
548
|
if self.excess_output is not None:
|
|
521
549
|
outputs.append(self.excess_output.name)
|
|
522
|
-
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]}
|
|
523
552
|
|
|
524
553
|
|
|
525
554
|
class ComponentModel(ElementModel):
|
|
@@ -572,4 +601,5 @@ class ComponentModel(ElementModel):
|
|
|
572
601
|
**super().results_structure(),
|
|
573
602
|
'inputs': [flow.model.flow_rate.name for flow in self.element.inputs],
|
|
574
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],
|
|
575
605
|
}
|