flixopt 2.0.1__py3-none-any.whl → 2.1.0__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.0.1.md +1 -1
- docs/release-notes/v2.1.0.md +31 -0
- flixopt/components.py +13 -9
- flixopt/elements.py +54 -14
- flixopt/features.py +407 -331
- flixopt/interface.py +5 -5
- flixopt/structure.py +5 -5
- {flixopt-2.0.1.dist-info → flixopt-2.1.0.dist-info}/METADATA +7 -6
- {flixopt-2.0.1.dist-info → flixopt-2.1.0.dist-info}/RECORD +12 -11
- {flixopt-2.0.1.dist-info → flixopt-2.1.0.dist-info}/WHEEL +0 -0
- {flixopt-2.0.1.dist-info → flixopt-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.0.1.dist-info → flixopt-2.1.0.dist-info}/top_level.txt +0 -0
docs/release-notes/v2.0.1.md
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Release v2.1.0
|
|
2
|
+
|
|
3
|
+
**Release Date:** 2025-04-11
|
|
4
|
+
|
|
5
|
+
## Improvements
|
|
6
|
+
|
|
7
|
+
* Add logger warning if relative_minimum is used without on_off_parameters in Flow, as this prevents the flow_rate from switching "OFF"
|
|
8
|
+
* Python 3.13 support added
|
|
9
|
+
* Greatly improved internal testing infrastructure by leveraging linopy's testing framework
|
|
10
|
+
|
|
11
|
+
## Bug Fixes
|
|
12
|
+
|
|
13
|
+
* Bugfixing the lower bound of `flow_rate` when using optional investments without OnOffParameters.
|
|
14
|
+
* Fixes a Bug that prevented divest effects from working.
|
|
15
|
+
* added lower bounds of 0 to two unbounded vars (only numerical better)
|
|
16
|
+
|
|
17
|
+
## Breaking Changes
|
|
18
|
+
|
|
19
|
+
* We restructured the modeling of the On/Off state of FLows or Components. This leads to slightly renaming of variables and constraints.
|
|
20
|
+
|
|
21
|
+
### Variable renaming
|
|
22
|
+
* "...|consecutive_on_hours" is now "...|ConsecutiveOn|hours"
|
|
23
|
+
* "...|consecutive_off_hours" is now "...|ConsecutiveOff|hours"
|
|
24
|
+
|
|
25
|
+
### Constraint renaming
|
|
26
|
+
* "...|consecutive_on_hours_con1" is now "...|ConsecutiveOn|con1"
|
|
27
|
+
* "...|consecutive_on_hours_con2a" is now "...|ConsecutiveOn|con2a"
|
|
28
|
+
* "...|consecutive_on_hours_con2b" is now "...|ConsecutiveOn|con2b"
|
|
29
|
+
* "...|consecutive_on_hours_initial" is now "...|ConsecutiveOn|initial"
|
|
30
|
+
* "...|consecutive_on_hours_minimum_duration" is now "...|ConsecutiveOn|minimum"
|
|
31
|
+
The same goes for "...|consecutive_off..." --> "...|ConsecutiveOff|..."
|
flixopt/components.py
CHANGED
|
@@ -60,6 +60,7 @@ class LinearConverter(Component):
|
|
|
60
60
|
return self.model
|
|
61
61
|
|
|
62
62
|
def _plausibility_checks(self) -> None:
|
|
63
|
+
super()._plausibility_checks()
|
|
63
64
|
if not self.conversion_factors and not self.piecewise_conversion:
|
|
64
65
|
raise PlausibilityError('Either conversion_factors or piecewise_conversion must be defined!')
|
|
65
66
|
if self.conversion_factors and self.piecewise_conversion:
|
|
@@ -213,6 +214,7 @@ class Storage(Component):
|
|
|
213
214
|
"""
|
|
214
215
|
Check for infeasible or uncommon combinations of parameters
|
|
215
216
|
"""
|
|
217
|
+
super()._plausibility_checks()
|
|
216
218
|
if utils.is_number(self.initial_charge_state):
|
|
217
219
|
if isinstance(self.capacity_in_flow_hours, InvestParameters):
|
|
218
220
|
if self.capacity_in_flow_hours.fixed_size is None:
|
|
@@ -301,6 +303,7 @@ class Transmission(Component):
|
|
|
301
303
|
self.absolute_losses = absolute_losses
|
|
302
304
|
|
|
303
305
|
def _plausibility_checks(self):
|
|
306
|
+
super()._plausibility_checks()
|
|
304
307
|
# check buses:
|
|
305
308
|
if self.in2 is not None:
|
|
306
309
|
assert self.in2.bus == self.out1.bus, (
|
|
@@ -396,6 +399,7 @@ class LinearConverterModel(ComponentModel):
|
|
|
396
399
|
super().__init__(model, element)
|
|
397
400
|
self.element: LinearConverter = element
|
|
398
401
|
self.on_off: Optional[OnOffModel] = None
|
|
402
|
+
self.piecewise_conversion: Optional[PiecewiseConversion] = None
|
|
399
403
|
|
|
400
404
|
def do_modeling(self):
|
|
401
405
|
super().do_modeling()
|
|
@@ -426,16 +430,16 @@ class LinearConverterModel(ComponentModel):
|
|
|
426
430
|
for flow, piecewise in self.element.piecewise_conversion.items()
|
|
427
431
|
}
|
|
428
432
|
|
|
429
|
-
piecewise_conversion =
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
433
|
+
self.piecewise_conversion = self.add(
|
|
434
|
+
PiecewiseModel(
|
|
435
|
+
model=self._model,
|
|
436
|
+
label_of_element=self.label_of_element,
|
|
437
|
+
piecewise_variables=piecewise_conversion,
|
|
438
|
+
zero_point=self.on_off.on if self.on_off is not None else False,
|
|
439
|
+
as_time_series=True,
|
|
440
|
+
)
|
|
436
441
|
)
|
|
437
|
-
piecewise_conversion.do_modeling()
|
|
438
|
-
self.sub_models.append(piecewise_conversion)
|
|
442
|
+
self.piecewise_conversion.do_modeling()
|
|
439
443
|
|
|
440
444
|
|
|
441
445
|
class StorageModel(ComponentModel):
|
flixopt/elements.py
CHANGED
|
@@ -57,6 +57,7 @@ class Component(Element):
|
|
|
57
57
|
super().__init__(label, meta_data=meta_data)
|
|
58
58
|
self.inputs: List['Flow'] = inputs or []
|
|
59
59
|
self.outputs: List['Flow'] = outputs or []
|
|
60
|
+
self._check_unique_flow_labels()
|
|
60
61
|
self.on_off_parameters = on_off_parameters
|
|
61
62
|
self.prevent_simultaneous_flows: List['Flow'] = prevent_simultaneous_flows or []
|
|
62
63
|
|
|
@@ -77,9 +78,15 @@ class Component(Element):
|
|
|
77
78
|
infos['outputs'] = [flow.infos(use_numpy, use_element_label) for flow in self.outputs]
|
|
78
79
|
return infos
|
|
79
80
|
|
|
81
|
+
def _check_unique_flow_labels(self):
|
|
82
|
+
all_flow_labels = [flow.label for flow in self.inputs + self.outputs]
|
|
83
|
+
|
|
84
|
+
if len(set(all_flow_labels)) != len(all_flow_labels):
|
|
85
|
+
duplicates = {label for label in all_flow_labels if all_flow_labels.count(label) > 1}
|
|
86
|
+
raise ValueError(f'Flow names must be unique! "{self.label_full}" got 2 or more of: {duplicates}')
|
|
87
|
+
|
|
80
88
|
def _plausibility_checks(self) -> None:
|
|
81
|
-
|
|
82
|
-
pass
|
|
89
|
+
self._check_unique_flow_labels()
|
|
83
90
|
|
|
84
91
|
|
|
85
92
|
@register_class_for_io
|
|
@@ -313,8 +320,8 @@ class FlowModel(ElementModel):
|
|
|
313
320
|
# eq relative_minimum(t) * size <= flow_rate(t) <= relative_maximum(t) * size
|
|
314
321
|
self.flow_rate: linopy.Variable = self.add(
|
|
315
322
|
self._model.add_variables(
|
|
316
|
-
lower=self.
|
|
317
|
-
upper=self.
|
|
323
|
+
lower=self.flow_rate_lower_bound,
|
|
324
|
+
upper=self.flow_rate_upper_bound,
|
|
318
325
|
coords=self._model.coords,
|
|
319
326
|
name=f'{self.label_full}|flow_rate',
|
|
320
327
|
),
|
|
@@ -329,7 +336,7 @@ class FlowModel(ElementModel):
|
|
|
329
336
|
label_of_element=self.label_of_element,
|
|
330
337
|
on_off_parameters=self.element.on_off_parameters,
|
|
331
338
|
defining_variables=[self.flow_rate],
|
|
332
|
-
defining_bounds=[self.
|
|
339
|
+
defining_bounds=[self.flow_rate_bounds_on],
|
|
333
340
|
previous_values=[self.element.previous_flow_rate],
|
|
334
341
|
),
|
|
335
342
|
'on_off',
|
|
@@ -344,7 +351,8 @@ class FlowModel(ElementModel):
|
|
|
344
351
|
label_of_element=self.label_of_element,
|
|
345
352
|
parameters=self.element.size,
|
|
346
353
|
defining_variable=self.flow_rate,
|
|
347
|
-
relative_bounds_of_defining_variable=self.
|
|
354
|
+
relative_bounds_of_defining_variable=(self.flow_rate_lower_bound_relative,
|
|
355
|
+
self.flow_rate_upper_bound_relative),
|
|
348
356
|
on_variable=self.on_off.on if self.on_off is not None else None,
|
|
349
357
|
),
|
|
350
358
|
'investment',
|
|
@@ -353,7 +361,7 @@ class FlowModel(ElementModel):
|
|
|
353
361
|
|
|
354
362
|
self.total_flow_hours = self.add(
|
|
355
363
|
self._model.add_variables(
|
|
356
|
-
lower=self.element.flow_hours_total_min if self.element.flow_hours_total_min is not None else
|
|
364
|
+
lower=self.element.flow_hours_total_min if self.element.flow_hours_total_min is not None else 0,
|
|
357
365
|
upper=self.element.flow_hours_total_max if self.element.flow_hours_total_max is not None else np.inf,
|
|
358
366
|
coords=None,
|
|
359
367
|
name=f'{self.label_full}|total_flow_hours',
|
|
@@ -419,9 +427,9 @@ class FlowModel(ElementModel):
|
|
|
419
427
|
)
|
|
420
428
|
|
|
421
429
|
@property
|
|
422
|
-
def
|
|
430
|
+
def flow_rate_bounds_on(self) -> Tuple[NumericData, NumericData]:
|
|
423
431
|
"""Returns absolute flow rate bounds. Important for OnOffModel"""
|
|
424
|
-
relative_minimum, relative_maximum = self.
|
|
432
|
+
relative_minimum, relative_maximum = self.flow_rate_lower_bound_relative, self.flow_rate_upper_bound_relative
|
|
425
433
|
size = self.element.size
|
|
426
434
|
if not isinstance(size, InvestParameters):
|
|
427
435
|
return relative_minimum * size, relative_maximum * size
|
|
@@ -430,12 +438,44 @@ class FlowModel(ElementModel):
|
|
|
430
438
|
return relative_minimum * size.minimum_size, relative_maximum * size.maximum_size
|
|
431
439
|
|
|
432
440
|
@property
|
|
433
|
-
def
|
|
434
|
-
"""Returns relative
|
|
441
|
+
def flow_rate_lower_bound_relative(self) -> NumericData:
|
|
442
|
+
"""Returns the lower bound of the flow_rate relative to its size"""
|
|
435
443
|
fixed_profile = self.element.fixed_relative_profile
|
|
436
444
|
if fixed_profile is None:
|
|
437
|
-
return self.element.relative_minimum.active_data
|
|
438
|
-
return fixed_profile.active_data
|
|
445
|
+
return self.element.relative_minimum.active_data
|
|
446
|
+
return fixed_profile.active_data
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def flow_rate_upper_bound_relative(self) -> NumericData:
|
|
450
|
+
""" Returns the upper bound of the flow_rate relative to its size"""
|
|
451
|
+
fixed_profile = self.element.fixed_relative_profile
|
|
452
|
+
if fixed_profile is None:
|
|
453
|
+
return self.element.relative_maximum.active_data
|
|
454
|
+
return fixed_profile.active_data
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
def flow_rate_lower_bound(self) -> NumericData:
|
|
458
|
+
"""
|
|
459
|
+
Returns the minimum bound the flow_rate can reach.
|
|
460
|
+
Further constraining might be done in OnOffModel and InvestmentModel
|
|
461
|
+
"""
|
|
462
|
+
if self.element.on_off_parameters is not None:
|
|
463
|
+
return 0
|
|
464
|
+
if isinstance(self.element.size, InvestParameters):
|
|
465
|
+
if self.element.size.optional:
|
|
466
|
+
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
|
|
469
|
+
|
|
470
|
+
@property
|
|
471
|
+
def flow_rate_upper_bound(self) -> NumericData:
|
|
472
|
+
"""
|
|
473
|
+
Returns the maximum bound the flow_rate can reach.
|
|
474
|
+
Further constraining might be done in OnOffModel and InvestmentModel
|
|
475
|
+
"""
|
|
476
|
+
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
|
|
439
479
|
|
|
440
480
|
|
|
441
481
|
class BusModel(ElementModel):
|
|
@@ -513,7 +553,7 @@ class ComponentModel(ElementModel):
|
|
|
513
553
|
self.element.on_off_parameters,
|
|
514
554
|
self.label_of_element,
|
|
515
555
|
defining_variables=[flow.model.flow_rate for flow in all_flows],
|
|
516
|
-
defining_bounds=[flow.model.
|
|
556
|
+
defining_bounds=[flow.model.flow_rate_bounds_on for flow in all_flows],
|
|
517
557
|
previous_values=[flow.previous_flow_rate for flow in all_flows],
|
|
518
558
|
)
|
|
519
559
|
)
|