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.

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
@@ -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"""
@@ -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
- 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
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
- raise ValueError(
283
- f'Flow {self.label} has both a fixed_relative_profile and an on_off_parameters. This is not supported. '
284
- f'Use relative_minimum and relative_maximum instead, '
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.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. '
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.coords,
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 if self.element.flow_hours_total_min is not None else 0,
366
- upper=self.element.flow_hours_total_max if self.element.flow_hours_total_max is not None else np.inf,
367
- 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),
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.active_data
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[NumericData, NumericData]:
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, relative_maximum * size.fixed_size
439
- 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))
440
464
 
441
465
  @property
442
- def flow_rate_lower_bound_relative(self) -> NumericData:
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.active_data
447
- return fixed_profile.active_data
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) -> NumericData:
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.active_data
455
- return fixed_profile.active_data
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) -> NumericData:
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) -> NumericData:
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.active_data
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(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
+ ),
504
530
  'excess_input',
505
531
  )
506
532
  self.excess_output = self.add(
507
- 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
+ ),
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
  }