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.

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 NumericData, NumericDataTS, PlausibilityError, Scalar, TimeSeriesCollection
14
- from .effects import EffectValuesUser
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
- 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.
@@ -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[NumericDataTS] = 1e5, meta_data: Optional[Dict] = None
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[Scalar, InvestParameters] = None,
158
- fixed_relative_profile: Optional[NumericDataTS] = None,
159
- relative_minimum: NumericDataTS = 0,
160
- relative_maximum: NumericDataTS = 1,
161
- effects_per_flow_hour: Optional[EffectValuesUser] = None,
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[Scalar] = None,
164
- flow_hours_total_min: Optional[Scalar] = None,
165
- load_factor_min: Optional[Scalar] = None,
166
- load_factor_max: Optional[Scalar] = None,
167
- previous_flow_rate: Optional[NumericData] = None,
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 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.
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
- self.size == CONFIG.modeling.BIG and self.fixed_relative_profile is not None
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
- raise ValueError(
282
- f'Flow {self.label} has both a fixed_relative_profile and an on_off_parameters. This is not supported. '
283
- f'Use relative_minimum and relative_maximum instead, '
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.active_data} and no on_off_parameters. '
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.coords,
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 if self.element.flow_hours_total_min is not None else 0,
365
- upper=self.element.flow_hours_total_max if self.element.flow_hours_total_max is not None else np.inf,
366
- coords=None,
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.active_data
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[NumericData, NumericData]:
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, relative_maximum * size.fixed_size
438
- return relative_minimum * size.minimum_size, relative_maximum * size.maximum_size
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) -> NumericData:
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.active_data
446
- return fixed_profile.active_data
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) -> NumericData:
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.active_data
454
- return fixed_profile.active_data
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) -> NumericData:
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) -> NumericData:
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.active_data
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(lower=0, coords=self._model.coords, name=f'{self.label_full}|excess_input'),
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(lower=0, coords=self._model.coords, name=f'{self.label_full}|excess_output'),
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
  }