flixopt 2.0.1__py3-none-any.whl → 2.1.1__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.

@@ -1,6 +1,6 @@
1
1
  # Release v2.0.1
2
2
 
3
- **Release Date:** 2025-04-20
3
+ **Release Date:** 2025-04-10
4
4
 
5
5
  ## Improvements
6
6
 
@@ -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|..."
@@ -0,0 +1,11 @@
1
+ # Release v2.1.1
2
+
3
+ **Release Date:** 2025-05-08
4
+
5
+ ## Improvements
6
+
7
+ * Improving docstring and tests
8
+
9
+ ## Bug Fixes
10
+
11
+ * Fixing bug in the `_ElementResults.constraints` not returning the constraints but rather the variables
flixopt/components.py CHANGED
@@ -43,7 +43,10 @@ class LinearConverter(Component):
43
43
  label: The label of the Element. Used to identify it in the FlowSystem
44
44
  inputs: The input Flows
45
45
  outputs: The output Flows
46
- on_off_parameters: Information about on and off states. See class OnOffParameters.
46
+ on_off_parameters: Information about on and off state of LinearConverter.
47
+ Component is On/Off, if all connected Flows are On/Off. This induces an On-Variable (binary) in all Flows!
48
+ If possible, use OnOffParameters in a single Flow instead to keep the number of binary variables low.
49
+ See class OnOffParameters.
47
50
  conversion_factors: linear relation between flows.
48
51
  Either 'conversion_factors' or 'piecewise_conversion' can be used!
49
52
  piecewise_conversion: Define a piecewise linear relation between flow rates of different flows.
@@ -60,6 +63,7 @@ class LinearConverter(Component):
60
63
  return self.model
61
64
 
62
65
  def _plausibility_checks(self) -> None:
66
+ super()._plausibility_checks()
63
67
  if not self.conversion_factors and not self.piecewise_conversion:
64
68
  raise PlausibilityError('Either conversion_factors or piecewise_conversion must be defined!')
65
69
  if self.conversion_factors and self.piecewise_conversion:
@@ -213,6 +217,7 @@ class Storage(Component):
213
217
  """
214
218
  Check for infeasible or uncommon combinations of parameters
215
219
  """
220
+ super()._plausibility_checks()
216
221
  if utils.is_number(self.initial_charge_state):
217
222
  if isinstance(self.capacity_in_flow_hours, InvestParameters):
218
223
  if self.capacity_in_flow_hours.fixed_size is None:
@@ -301,6 +306,7 @@ class Transmission(Component):
301
306
  self.absolute_losses = absolute_losses
302
307
 
303
308
  def _plausibility_checks(self):
309
+ super()._plausibility_checks()
304
310
  # check buses:
305
311
  if self.in2 is not None:
306
312
  assert self.in2.bus == self.out1.bus, (
@@ -396,6 +402,7 @@ class LinearConverterModel(ComponentModel):
396
402
  super().__init__(model, element)
397
403
  self.element: LinearConverter = element
398
404
  self.on_off: Optional[OnOffModel] = None
405
+ self.piecewise_conversion: Optional[PiecewiseConversion] = None
399
406
 
400
407
  def do_modeling(self):
401
408
  super().do_modeling()
@@ -426,16 +433,16 @@ class LinearConverterModel(ComponentModel):
426
433
  for flow, piecewise in self.element.piecewise_conversion.items()
427
434
  }
428
435
 
429
- piecewise_conversion = PiecewiseModel(
430
- model=self._model,
431
- label_of_element=self.label_of_element,
432
- label=self.label_full,
433
- piecewise_variables=piecewise_conversion,
434
- zero_point=self.on_off.on if self.on_off is not None else False,
435
- as_time_series=True,
436
+ self.piecewise_conversion = self.add(
437
+ PiecewiseModel(
438
+ model=self._model,
439
+ label_of_element=self.label_of_element,
440
+ piecewise_variables=piecewise_conversion,
441
+ zero_point=self.on_off.on if self.on_off is not None else False,
442
+ as_time_series=True,
443
+ )
436
444
  )
437
- piecewise_conversion.do_modeling()
438
- self.sub_models.append(piecewise_conversion)
445
+ self.piecewise_conversion.do_modeling()
439
446
 
440
447
 
441
448
  class StorageModel(ComponentModel):
flixopt/elements.py CHANGED
@@ -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
- Induces On-Variable in all FLows!
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.
@@ -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
- # TODO: Check for plausibility
82
- pass
89
+ self._check_unique_flow_labels()
83
90
 
84
91
 
85
92
  @register_class_for_io
@@ -186,7 +193,8 @@ class Flow(Element):
186
193
  (relative_minimum and relative_maximum are ignored)
187
194
  used for fixed load or supply profiles, i.g. heat demand, wind-power, solarthermal
188
195
  If the load-profile is just an upper limit, use relative_maximum instead.
189
- previous_flow_rate: previous flow rate of the component.
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.
190
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.
191
199
  """
192
200
  super().__init__(label, meta_data=meta_data)
@@ -313,8 +321,8 @@ class FlowModel(ElementModel):
313
321
  # eq relative_minimum(t) * size <= flow_rate(t) <= relative_maximum(t) * size
314
322
  self.flow_rate: linopy.Variable = self.add(
315
323
  self._model.add_variables(
316
- lower=self.absolute_flow_rate_bounds[0] if self.element.on_off_parameters is None else 0,
317
- upper=self.absolute_flow_rate_bounds[1],
324
+ lower=self.flow_rate_lower_bound,
325
+ upper=self.flow_rate_upper_bound,
318
326
  coords=self._model.coords,
319
327
  name=f'{self.label_full}|flow_rate',
320
328
  ),
@@ -329,7 +337,7 @@ class FlowModel(ElementModel):
329
337
  label_of_element=self.label_of_element,
330
338
  on_off_parameters=self.element.on_off_parameters,
331
339
  defining_variables=[self.flow_rate],
332
- defining_bounds=[self.absolute_flow_rate_bounds],
340
+ defining_bounds=[self.flow_rate_bounds_on],
333
341
  previous_values=[self.element.previous_flow_rate],
334
342
  ),
335
343
  'on_off',
@@ -344,7 +352,8 @@ class FlowModel(ElementModel):
344
352
  label_of_element=self.label_of_element,
345
353
  parameters=self.element.size,
346
354
  defining_variable=self.flow_rate,
347
- relative_bounds_of_defining_variable=self.relative_flow_rate_bounds,
355
+ relative_bounds_of_defining_variable=(self.flow_rate_lower_bound_relative,
356
+ self.flow_rate_upper_bound_relative),
348
357
  on_variable=self.on_off.on if self.on_off is not None else None,
349
358
  ),
350
359
  'investment',
@@ -353,7 +362,7 @@ class FlowModel(ElementModel):
353
362
 
354
363
  self.total_flow_hours = self.add(
355
364
  self._model.add_variables(
356
- lower=self.element.flow_hours_total_min if self.element.flow_hours_total_min is not None else -np.inf,
365
+ lower=self.element.flow_hours_total_min if self.element.flow_hours_total_min is not None else 0,
357
366
  upper=self.element.flow_hours_total_max if self.element.flow_hours_total_max is not None else np.inf,
358
367
  coords=None,
359
368
  name=f'{self.label_full}|total_flow_hours',
@@ -419,9 +428,9 @@ class FlowModel(ElementModel):
419
428
  )
420
429
 
421
430
  @property
422
- def absolute_flow_rate_bounds(self) -> Tuple[NumericData, NumericData]:
431
+ def flow_rate_bounds_on(self) -> Tuple[NumericData, NumericData]:
423
432
  """Returns absolute flow rate bounds. Important for OnOffModel"""
424
- relative_minimum, relative_maximum = self.relative_flow_rate_bounds
433
+ relative_minimum, relative_maximum = self.flow_rate_lower_bound_relative, self.flow_rate_upper_bound_relative
425
434
  size = self.element.size
426
435
  if not isinstance(size, InvestParameters):
427
436
  return relative_minimum * size, relative_maximum * size
@@ -430,12 +439,44 @@ class FlowModel(ElementModel):
430
439
  return relative_minimum * size.minimum_size, relative_maximum * size.maximum_size
431
440
 
432
441
  @property
433
- def relative_flow_rate_bounds(self) -> Tuple[NumericData, NumericData]:
434
- """Returns relative flow rate bounds."""
442
+ def flow_rate_lower_bound_relative(self) -> NumericData:
443
+ """Returns the lower bound of the flow_rate relative to its size"""
435
444
  fixed_profile = self.element.fixed_relative_profile
436
445
  if fixed_profile is None:
437
- return self.element.relative_minimum.active_data, self.element.relative_maximum.active_data
438
- return fixed_profile.active_data, fixed_profile.active_data
446
+ return self.element.relative_minimum.active_data
447
+ return fixed_profile.active_data
448
+
449
+ @property
450
+ def flow_rate_upper_bound_relative(self) -> NumericData:
451
+ """ Returns the upper bound of the flow_rate relative to its size"""
452
+ fixed_profile = self.element.fixed_relative_profile
453
+ if fixed_profile is None:
454
+ return self.element.relative_maximum.active_data
455
+ return fixed_profile.active_data
456
+
457
+ @property
458
+ def flow_rate_lower_bound(self) -> NumericData:
459
+ """
460
+ Returns the minimum bound the flow_rate can reach.
461
+ Further constraining might be done in OnOffModel and InvestmentModel
462
+ """
463
+ if self.element.on_off_parameters is not None:
464
+ return 0
465
+ if isinstance(self.element.size, InvestParameters):
466
+ if self.element.size.optional:
467
+ 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
470
+
471
+ @property
472
+ def flow_rate_upper_bound(self) -> NumericData:
473
+ """
474
+ Returns the maximum bound the flow_rate can reach.
475
+ Further constraining might be done in OnOffModel and InvestmentModel
476
+ """
477
+ 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
439
480
 
440
481
 
441
482
  class BusModel(ElementModel):
@@ -513,7 +554,7 @@ class ComponentModel(ElementModel):
513
554
  self.element.on_off_parameters,
514
555
  self.label_of_element,
515
556
  defining_variables=[flow.model.flow_rate for flow in all_flows],
516
- defining_bounds=[flow.model.absolute_flow_rate_bounds for flow in all_flows],
557
+ defining_bounds=[flow.model.flow_rate_bounds_on for flow in all_flows],
517
558
  previous_values=[flow.previous_flow_rate for flow in all_flows],
518
559
  )
519
560
  )