flixopt 2.1.7__py3-none-any.whl → 2.1.8__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/components.py CHANGED
@@ -2,11 +2,12 @@
2
2
  This module contains the basic components of the flixopt framework.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import logging
6
8
  import warnings
7
- from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Set, Tuple, Union
9
+ from typing import TYPE_CHECKING, Literal
8
10
 
9
- import linopy
10
11
  import numpy as np
11
12
 
12
13
  from . import utils
@@ -17,6 +18,8 @@ from .interface import InvestParameters, OnOffParameters, PiecewiseConversion
17
18
  from .structure import SystemModel, register_class_for_io
18
19
 
19
20
  if TYPE_CHECKING:
21
+ import linopy
22
+
20
23
  from .flow_system import FlowSystem
21
24
 
22
25
  logger = logging.getLogger('flixopt')
@@ -25,40 +28,148 @@ logger = logging.getLogger('flixopt')
25
28
  @register_class_for_io
26
29
  class LinearConverter(Component):
27
30
  """
28
- Converts input-Flows into output-Flows via linear conversion factors
31
+ Converts input-Flows into output-Flows via linear conversion factors.
32
+
33
+ LinearConverter models equipment that transforms one or more input flows into one or
34
+ more output flows through linear relationships. This includes heat exchangers,
35
+ electrical converters, chemical reactors, and other equipment where the
36
+ relationship between inputs and outputs can be expressed as linear equations.
37
+
38
+ The component supports two modeling approaches: simple conversion factors for
39
+ straightforward linear relationships, or piecewise conversion for complex non-linear
40
+ behavior approximated through piecewise linear segments.
41
+
42
+ Args:
43
+ label: The label of the Element. Used to identify it in the FlowSystem.
44
+ inputs: list of input Flows that feed into the converter.
45
+ outputs: list of output Flows that are produced by the converter.
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
48
+ On-Variable (binary) in all Flows! If possible, use OnOffParameters in a
49
+ single Flow instead to keep the number of binary variables low.
50
+ conversion_factors: Linear relationships between flows expressed as a list of
51
+ dictionaries. Each dictionary maps flow labels to their coefficients in one
52
+ linear equation. The number of conversion factors must be less than the total
53
+ number of flows to ensure degrees of freedom > 0. Either 'conversion_factors'
54
+ OR 'piecewise_conversion' can be used, but not both.
55
+ For examples also look into the linear_converters.py file.
56
+ piecewise_conversion: Define piecewise linear relationships between flow rates
57
+ of different flows. Enables modeling of non-linear conversion behavior through
58
+ linear approximation. Either 'conversion_factors' or 'piecewise_conversion'
59
+ can be used, but not both.
60
+ meta_data: Used to store additional information about the Element. Not used
61
+ internally, but saved in results. Only use Python native types.
62
+
63
+ Examples:
64
+ Simple 1:1 heat exchanger with 95% efficiency:
65
+
66
+ ```python
67
+ heat_exchanger = LinearConverter(
68
+ label='primary_hx',
69
+ inputs=[hot_water_in],
70
+ outputs=[hot_water_out],
71
+ conversion_factors=[{'hot_water_in': 0.95, 'hot_water_out': 1}],
72
+ )
73
+ ```
74
+
75
+ Multi-input heat pump with COP=3:
76
+
77
+ ```python
78
+ heat_pump = LinearConverter(
79
+ label='air_source_hp',
80
+ inputs=[electricity_in],
81
+ outputs=[heat_output],
82
+ conversion_factors=[{'electricity_in': 3, 'heat_output': 1}],
83
+ )
84
+ ```
85
+
86
+ Combined heat and power (CHP) unit with multiple outputs:
87
+
88
+ ```python
89
+ chp_unit = LinearConverter(
90
+ label='gas_chp',
91
+ inputs=[natural_gas],
92
+ outputs=[electricity_out, heat_out],
93
+ conversion_factors=[
94
+ {'natural_gas': 0.35, 'electricity_out': 1},
95
+ {'natural_gas': 0.45, 'heat_out': 1},
96
+ ],
97
+ )
98
+ ```
99
+
100
+ Electrolyzer with multiple conversion relationships:
101
+
102
+ ```python
103
+ electrolyzer = LinearConverter(
104
+ label='pem_electrolyzer',
105
+ inputs=[electricity_in, water_in],
106
+ outputs=[hydrogen_out, oxygen_out],
107
+ conversion_factors=[
108
+ {'electricity_in': 1, 'hydrogen_out': 50}, # 50 kWh/kg H2
109
+ {'water_in': 1, 'hydrogen_out': 9}, # 9 kg H2O/kg H2
110
+ {'hydrogen_out': 8, 'oxygen_out': 1}, # Mass balance
111
+ ],
112
+ )
113
+ ```
114
+
115
+ Complex converter with piecewise efficiency:
116
+
117
+ ```python
118
+ variable_efficiency_converter = LinearConverter(
119
+ label='variable_converter',
120
+ inputs=[fuel_in],
121
+ outputs=[power_out],
122
+ piecewise_conversion=PiecewiseConversion(
123
+ {
124
+ 'fuel_in': Piecewise(
125
+ [
126
+ Piece(0, 10), # Low load operation
127
+ Piece(10, 25), # High load operation
128
+ ]
129
+ ),
130
+ 'power_out': Piecewise(
131
+ [
132
+ Piece(0, 3.5), # Lower efficiency at part load
133
+ Piece(3.5, 10), # Higher efficiency at full load
134
+ ]
135
+ ),
136
+ }
137
+ ),
138
+ )
139
+ ```
140
+
141
+ Note:
142
+ Conversion factors define linear relationships where the sum of (coefficient × flow_rate)
143
+ equals zero for each equation: factor1×flow1 + factor2×flow2 + ... = 0
144
+ Conversion factors define linear relationships.
145
+ `{flow1: a1, flow2: a2, ...}` leads to `a1×flow_rate1 + a2×flow_rate2 + ... = 0`
146
+ Unfortunately the current input format doest read intuitively:
147
+ {"electricity": 1, "H2": 50} means that the electricity_in flow rate is multiplied by 1
148
+ and the hydrogen_out flow rate is multiplied by 50. THis leads to 50 electricity --> 1 H2.
149
+
150
+ The system must have fewer conversion factors than total flows (degrees of freedom > 0)
151
+ to avoid over-constraining the problem. For n total flows, use at most n-1 conversion factors.
152
+
153
+ When using piecewise_conversion, the converter operates on one piece at a time,
154
+ with binary variables determining which piece is active.
29
155
 
30
156
  """
31
157
 
32
158
  def __init__(
33
159
  self,
34
160
  label: str,
35
- inputs: List[Flow],
36
- outputs: List[Flow],
37
- on_off_parameters: OnOffParameters = None,
38
- conversion_factors: List[Dict[str, NumericDataTS]] = None,
39
- piecewise_conversion: Optional[PiecewiseConversion] = None,
40
- meta_data: Optional[Dict] = None,
161
+ inputs: list[Flow],
162
+ outputs: list[Flow],
163
+ on_off_parameters: OnOffParameters | None = None,
164
+ conversion_factors: list[dict[str, NumericDataTS]] | None = None,
165
+ piecewise_conversion: PiecewiseConversion | None = None,
166
+ meta_data: dict | None = None,
41
167
  ):
42
- """
43
- Args:
44
- label: The label of the Element. Used to identify it in the FlowSystem
45
- inputs: The input Flows
46
- outputs: The output Flows
47
- on_off_parameters: Information about on and off state of LinearConverter.
48
- Component is On/Off, if all connected Flows are On/Off. This induces an On-Variable (binary) in all Flows!
49
- If possible, use OnOffParameters in a single Flow instead to keep the number of binary variables low.
50
- See class OnOffParameters.
51
- conversion_factors: linear relation between flows.
52
- Either 'conversion_factors' or 'piecewise_conversion' can be used!
53
- piecewise_conversion: Define a piecewise linear relation between flow rates of different flows.
54
- Either 'conversion_factors' or 'piecewise_conversion' can be used!
55
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
56
- """
57
168
  super().__init__(label, inputs, outputs, on_off_parameters, meta_data=meta_data)
58
169
  self.conversion_factors = conversion_factors or []
59
170
  self.piecewise_conversion = piecewise_conversion
60
171
 
61
- def create_model(self, model: SystemModel) -> 'LinearConverterModel':
172
+ def create_model(self, model: SystemModel) -> LinearConverterModel:
62
173
  self._plausibility_checks()
63
174
  self.model = LinearConverterModel(model, self)
64
175
  return self.model
@@ -92,23 +203,24 @@ class LinearConverter(Component):
92
203
  f'(in flow {flow.label_full}) do not make sense together!'
93
204
  )
94
205
 
95
- def transform_data(self, flow_system: 'FlowSystem'):
206
+ def transform_data(self, flow_system: FlowSystem):
96
207
  super().transform_data(flow_system)
97
208
  if self.conversion_factors:
98
209
  self.conversion_factors = self._transform_conversion_factors(flow_system)
99
210
  if self.piecewise_conversion:
100
211
  self.piecewise_conversion.transform_data(flow_system, f'{self.label_full}|PiecewiseConversion')
101
212
 
102
- def _transform_conversion_factors(self, flow_system: 'FlowSystem') -> List[Dict[str, TimeSeries]]:
213
+ def _transform_conversion_factors(self, flow_system: FlowSystem) -> list[dict[str, TimeSeries]]:
103
214
  """macht alle Faktoren, die nicht TimeSeries sind, zu TimeSeries"""
104
215
  list_of_conversion_factors = []
105
216
  for idx, conversion_factor in enumerate(self.conversion_factors):
106
217
  transformed_dict = {}
107
218
  for flow, values in conversion_factor.items():
108
219
  # TODO: Might be better to use the label of the component instead of the flow
109
- transformed_dict[flow] = flow_system.create_time_series(
110
- f'{self.flows[flow].label_full}|conversion_factor{idx}', values
111
- )
220
+ ts = flow_system.create_time_series(f'{self.flows[flow].label_full}|conversion_factor{idx}', values)
221
+ if ts is None:
222
+ raise PlausibilityError(f'{self.label_full}: conversion factor for flow "{flow}" must not be None')
223
+ transformed_dict[flow] = ts
112
224
  list_of_conversion_factors.append(transformed_dict)
113
225
  return list_of_conversion_factors
114
226
 
@@ -120,7 +232,137 @@ class LinearConverter(Component):
120
232
  @register_class_for_io
121
233
  class Storage(Component):
122
234
  """
123
- Used to model the storage of energy or material.
235
+ A Storage models the temporary storage and release of energy or material.
236
+
237
+ Storages have one incoming and one outgoing Flow, each with configurable efficiency
238
+ factors. They maintain a charge state variable that represents the stored amount,
239
+ bounded by capacity limits and evolving over time based on charging, discharging,
240
+ and self-discharge losses.
241
+
242
+ The storage model handles complex temporal dynamics including initial conditions,
243
+ final state constraints, and time-varying parameters. It supports both fixed-size
244
+ and investment-optimized storage systems with comprehensive techno-economic modeling.
245
+
246
+ Args:
247
+ label: The label of the Element. Used to identify it in the FlowSystem.
248
+ charging: Incoming flow for loading the storage. Represents energy or material
249
+ flowing into the storage system.
250
+ discharging: Outgoing flow for unloading the storage. Represents energy or
251
+ material flowing out of the storage system.
252
+ capacity_in_flow_hours: Nominal capacity/size of the storage in flow-hours
253
+ (e.g., kWh for electrical storage, m³ or kg for material storage). Can be a scalar
254
+ for fixed capacity or InvestParameters for optimization.
255
+ relative_minimum_charge_state: Minimum relative charge state (0-1 range).
256
+ Prevents deep discharge that could damage equipment. Default is 0.
257
+ relative_maximum_charge_state: Maximum relative charge state (0-1 range).
258
+ Accounts for practical capacity limits, safety margins or temperature impacts. Default is 1.
259
+ initial_charge_state: Storage charge state at the beginning of the time horizon.
260
+ Can be numeric value or 'lastValueOfSim', which is recommended for if the initial start state is not known.
261
+ Default is 0.
262
+ minimal_final_charge_state: Minimum absolute charge state required at the end
263
+ of the time horizon. Useful for ensuring energy security or meeting contracts.
264
+ maximal_final_charge_state: Maximum absolute charge state allowed at the end
265
+ of the time horizon. Useful for preventing overcharge or managing inventory.
266
+ eta_charge: Charging efficiency factor (0-1 range). Accounts for conversion
267
+ losses during charging. Default is 1 (perfect efficiency).
268
+ eta_discharge: Discharging efficiency factor (0-1 range). Accounts for
269
+ conversion losses during discharging. Default is 1 (perfect efficiency).
270
+ relative_loss_per_hour: Self-discharge rate per hour (typically 0-0.1 range).
271
+ Represents standby losses, leakage, or degradation. Default is 0.
272
+ prevent_simultaneous_charge_and_discharge: If True, prevents charging and
273
+ discharging simultaneously. Increases binary variables but improves model
274
+ realism and solution interpretation. Default is True.
275
+ meta_data: Used to store additional information about the Element. Not used
276
+ internally, but saved in results. Only use Python native types.
277
+
278
+ Examples:
279
+ Battery energy storage system:
280
+
281
+ ```python
282
+ battery = Storage(
283
+ label='lithium_battery',
284
+ charging=battery_charge_flow,
285
+ discharging=battery_discharge_flow,
286
+ capacity_in_flow_hours=100, # 100 kWh capacity
287
+ eta_charge=0.95, # 95% charging efficiency
288
+ eta_discharge=0.95, # 95% discharging efficiency
289
+ relative_loss_per_hour=0.001, # 0.1% loss per hour
290
+ relative_minimum_charge_state=0.1, # Never below 10% SOC
291
+ relative_maximum_charge_state=0.9, # Never above 90% SOC
292
+ )
293
+ ```
294
+
295
+ Thermal storage with cycling constraints:
296
+
297
+ ```python
298
+ thermal_storage = Storage(
299
+ label='hot_water_tank',
300
+ charging=heat_input,
301
+ discharging=heat_output,
302
+ capacity_in_flow_hours=500, # 500 kWh thermal capacity
303
+ initial_charge_state=250, # Start half full
304
+ # Impact of temperature on energy capacity
305
+ relative_maximum_charge_state=water_temperature_spread / rated_temeprature_spread,
306
+ eta_charge=0.90, # Heat exchanger losses
307
+ eta_discharge=0.85, # Distribution losses
308
+ relative_loss_per_hour=0.02, # 2% thermal loss per hour
309
+ prevent_simultaneous_charge_and_discharge=True,
310
+ )
311
+ ```
312
+
313
+ Pumped hydro storage with investment optimization:
314
+
315
+ ```python
316
+ pumped_hydro = Storage(
317
+ label='pumped_hydro',
318
+ charging=pump_flow,
319
+ discharging=turbine_flow,
320
+ capacity_in_flow_hours=InvestParameters(
321
+ minimum_size=1000, # Minimum economic scale
322
+ maximum_size=10000, # Site constraints
323
+ specific_effects={'cost': 150}, # €150/MWh capacity
324
+ fix_effects={'cost': 50_000_000}, # €50M fixed costs
325
+ ),
326
+ eta_charge=0.85, # Pumping efficiency
327
+ eta_discharge=0.90, # Turbine efficiency
328
+ initial_charge_state='lastValueOfSim', # Ensuring no deficit compared to start
329
+ relative_loss_per_hour=0.0001, # Minimal evaporation
330
+ )
331
+ ```
332
+
333
+ Material storage with inventory management:
334
+
335
+ ```python
336
+ fuel_storage = Storage(
337
+ label='natural_gas_storage',
338
+ charging=gas_injection,
339
+ discharging=gas_withdrawal,
340
+ capacity_in_flow_hours=10000, # 10,000 m³ storage volume
341
+ initial_charge_state=3000, # Start with 3,000 m³
342
+ minimal_final_charge_state=1000, # Strategic reserve
343
+ maximal_final_charge_state=9000, # Prevent overflow
344
+ eta_charge=0.98, # Compression losses
345
+ eta_discharge=0.95, # Pressure reduction losses
346
+ relative_loss_per_hour=0.0005, # 0.05% leakage per hour
347
+ prevent_simultaneous_charge_and_discharge=False, # Allow flow-through
348
+ )
349
+ ```
350
+
351
+ Note:
352
+ Charge state evolution follows the equation:
353
+ charge[t+1] = charge[t] × (1-loss_rate)^hours_per_step +
354
+ charge_flow[t] × eta_charge × hours_per_step -
355
+ discharge_flow[t] × hours_per_step / eta_discharge
356
+
357
+ All efficiency parameters (eta_charge, eta_discharge) are dimensionless (0-1 range).
358
+ The relative_loss_per_hour parameter represents exponential decay per hour.
359
+
360
+ When prevent_simultaneous_charge_and_discharge is True, binary variables are
361
+ created to enforce mutual exclusivity, which increases solution time but
362
+ prevents unrealistic simultaneous charging and discharging.
363
+
364
+ Initial and final charge state constraints use absolute values (not relative),
365
+ matching the capacity_in_flow_hours units.
124
366
  """
125
367
 
126
368
  def __init__(
@@ -128,43 +370,18 @@ class Storage(Component):
128
370
  label: str,
129
371
  charging: Flow,
130
372
  discharging: Flow,
131
- capacity_in_flow_hours: Union[Scalar, InvestParameters],
373
+ capacity_in_flow_hours: Scalar | InvestParameters,
132
374
  relative_minimum_charge_state: NumericData = 0,
133
375
  relative_maximum_charge_state: NumericData = 1,
134
- initial_charge_state: Union[Scalar, Literal['lastValueOfSim']] = 0,
135
- minimal_final_charge_state: Optional[Scalar] = None,
136
- maximal_final_charge_state: Optional[Scalar] = None,
376
+ initial_charge_state: Scalar | Literal['lastValueOfSim'] = 0,
377
+ minimal_final_charge_state: Scalar | None = None,
378
+ maximal_final_charge_state: Scalar | None = None,
137
379
  eta_charge: NumericData = 1,
138
380
  eta_discharge: NumericData = 1,
139
381
  relative_loss_per_hour: NumericData = 0,
140
382
  prevent_simultaneous_charge_and_discharge: bool = True,
141
- meta_data: Optional[Dict] = None,
383
+ meta_data: dict | None = None,
142
384
  ):
143
- """
144
- Storages have one incoming and one outgoing Flow each with an efficiency.
145
- Further, storages have a `size` and a `charge_state`.
146
- Similarly to the flow-rate of a Flow, the `size` combined with a relative upper and lower bound
147
- limits the `charge_state` of the storage.
148
-
149
- For mathematical details take a look at our online documentation
150
-
151
- Args:
152
- label: The label of the Element. Used to identify it in the FlowSystem
153
- charging: ingoing flow.
154
- discharging: outgoing flow.
155
- capacity_in_flow_hours: nominal capacity/size of the storage
156
- relative_minimum_charge_state: minimum relative charge state. The default is 0.
157
- relative_maximum_charge_state: maximum relative charge state. The default is 1.
158
- initial_charge_state: storage charge_state at the beginning. The default is 0.
159
- minimal_final_charge_state: minimal value of chargeState at the end of timeseries.
160
- maximal_final_charge_state: maximal value of chargeState at the end of timeseries.
161
- eta_charge: efficiency factor of charging/loading. The default is 1.
162
- eta_discharge: efficiency factor of uncharging/unloading. The default is 1.
163
- relative_loss_per_hour: loss per chargeState-Unit per hour. The default is 0.
164
- prevent_simultaneous_charge_and_discharge: If True, loading and unloading at the same time is not possible.
165
- Increases the number of binary variables, but is recommended for easier evaluation. The default is True.
166
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
167
- """
168
385
  # TODO: fixed_relative_chargeState implementieren
169
386
  super().__init__(
170
387
  label,
@@ -189,12 +406,12 @@ class Storage(Component):
189
406
  self.relative_loss_per_hour: NumericDataTS = relative_loss_per_hour
190
407
  self.prevent_simultaneous_charge_and_discharge = prevent_simultaneous_charge_and_discharge
191
408
 
192
- def create_model(self, model: SystemModel) -> 'StorageModel':
409
+ def create_model(self, model: SystemModel) -> StorageModel:
193
410
  self._plausibility_checks()
194
411
  self.model = StorageModel(model, self)
195
412
  return self.model
196
413
 
197
- def transform_data(self, flow_system: 'FlowSystem') -> None:
414
+ def transform_data(self, flow_system: FlowSystem) -> None:
198
415
  super().transform_data(flow_system)
199
416
  self.relative_minimum_charge_state = flow_system.create_time_series(
200
417
  f'{self.label_full}|relative_minimum_charge_state',
@@ -231,20 +448,15 @@ class Storage(Component):
231
448
  maximum_capacity = self.capacity_in_flow_hours
232
449
  minimum_capacity = self.capacity_in_flow_hours
233
450
 
234
- # initial capacity >= allowed min for maximum_size:
235
- minimum_inital_capacity = maximum_capacity * self.relative_minimum_charge_state.isel(time=1)
236
- # initial capacity <= allowed max for minimum_size:
237
- maximum_inital_capacity = minimum_capacity * self.relative_maximum_charge_state.isel(time=1)
238
-
239
- if self.initial_charge_state > maximum_inital_capacity:
451
+ minimum_initial_capacity = maximum_capacity * self.relative_minimum_charge_state.isel(time=1)
452
+ maximum_initial_capacity = minimum_capacity * self.relative_maximum_charge_state.isel(time=1)
453
+ if self.initial_charge_state > maximum_initial_capacity:
240
454
  raise ValueError(
241
- f'{self.label_full}: {self.initial_charge_state=} '
242
- f'is above allowed maximum charge_state {maximum_inital_capacity}'
455
+ f'{self.label_full}: {self.initial_charge_state=} is above allowed maximum {maximum_initial_capacity}'
243
456
  )
244
- if self.initial_charge_state < minimum_inital_capacity:
457
+ if self.initial_charge_state < minimum_initial_capacity:
245
458
  raise ValueError(
246
- f'{self.label_full}: {self.initial_charge_state=} '
247
- f'is below allowed minimum charge_state {minimum_inital_capacity}'
459
+ f'{self.label_full}: {self.initial_charge_state=} is below allowed minimum {minimum_initial_capacity}'
248
460
  )
249
461
  elif self.initial_charge_state != 'lastValueOfSim':
250
462
  raise ValueError(f'{self.label_full}: {self.initial_charge_state=} has an invalid value')
@@ -252,42 +464,127 @@ class Storage(Component):
252
464
 
253
465
  @register_class_for_io
254
466
  class Transmission(Component):
255
- # TODO: automatic on-Value in Flows if loss_abs
256
- # TODO: loss_abs must be: investment_size * loss_abs_rel!!!
257
- # TODO: investmentsize only on 1 flow
258
- # TODO: automatic investArgs for both in-flows (or alternatively both out-flows!)
259
- # TODO: optional: capacities should be recognised for losses
467
+ """
468
+ Models transmission infrastructure that transports flows between two locations with losses.
469
+
470
+ Transmission components represent physical infrastructure like pipes, cables,
471
+ transmission lines, or conveyor systems that transport energy or materials between
472
+ two points. They can model both unidirectional and bidirectional flow with
473
+ configurable loss mechanisms and operational constraints.
474
+
475
+ The component supports complex transmission scenarios including relative losses
476
+ (proportional to flow), absolute losses (fixed when active), and bidirectional
477
+ operation with flow direction constraints.
478
+
479
+ Args:
480
+ label: The label of the Element. Used to identify it in the FlowSystem.
481
+ in1: The primary inflow (side A). Pass InvestParameters here for capacity optimization.
482
+ out1: The primary outflow (side B).
483
+ in2: Optional secondary inflow (side B) for bidirectional operation.
484
+ If in1 has InvestParameters, in2 will automatically have matching capacity.
485
+ out2: Optional secondary outflow (side A) for bidirectional operation.
486
+ relative_losses: Proportional losses as fraction of throughput (e.g., 0.02 for 2% loss).
487
+ Applied as: output = input × (1 - relative_losses)
488
+ absolute_losses: Fixed losses that occur when transmission is active.
489
+ Automatically creates binary variables for on/off states.
490
+ on_off_parameters: Parameters defining binary operation constraints and costs.
491
+ prevent_simultaneous_flows_in_both_directions: If True, prevents simultaneous
492
+ flow in both directions. Increases binary variables but reflects physical
493
+ reality for most transmission systems. Default is True.
494
+ meta_data: Used to store additional information. Not used internally but saved
495
+ in results. Only use Python native types.
496
+
497
+ Examples:
498
+ Simple electrical transmission line:
499
+
500
+ ```python
501
+ power_line = Transmission(
502
+ label='110kv_line',
503
+ in1=substation_a_out,
504
+ out1=substation_b_in,
505
+ relative_losses=0.03, # 3% line losses
506
+ )
507
+ ```
508
+
509
+ Bidirectional natural gas pipeline:
510
+
511
+ ```python
512
+ gas_pipeline = Transmission(
513
+ label='interstate_pipeline',
514
+ in1=compressor_station_a,
515
+ out1=distribution_hub_b,
516
+ in2=compressor_station_b,
517
+ out2=distribution_hub_a,
518
+ relative_losses=0.005, # 0.5% friction losses
519
+ absolute_losses=50, # 50 kW compressor power when active
520
+ prevent_simultaneous_flows_in_both_directions=True,
521
+ )
522
+ ```
523
+
524
+ District heating network with investment optimization:
525
+
526
+ ```python
527
+ heating_network = Transmission(
528
+ label='dh_main_line',
529
+ in1=Flow(
530
+ label='heat_supply',
531
+ bus=central_plant_bus,
532
+ size=InvestParameters(
533
+ minimum_size=1000, # Minimum 1 MW capacity
534
+ maximum_size=10000, # Maximum 10 MW capacity
535
+ specific_effects={'cost': 200}, # €200/kW capacity
536
+ fix_effects={'cost': 500000}, # €500k fixed installation
537
+ ),
538
+ ),
539
+ out1=district_heat_demand,
540
+ relative_losses=0.15, # 15% thermal losses in distribution
541
+ )
542
+ ```
543
+
544
+ Material conveyor with on/off operation:
545
+
546
+ ```python
547
+ conveyor_belt = Transmission(
548
+ label='material_transport',
549
+ in1=loading_station,
550
+ out1=unloading_station,
551
+ absolute_losses=25, # 25 kW motor power when running
552
+ on_off_parameters=OnOffParameters(
553
+ effects_per_switch_on={'maintenance': 0.1},
554
+ consecutive_on_hours_min=2, # Minimum 2-hour operation
555
+ switch_on_total_max=10, # Maximum 10 starts per day
556
+ ),
557
+ )
558
+ ```
559
+
560
+ Note:
561
+ The transmission equation balances flows with losses:
562
+ output_flow = input_flow × (1 - relative_losses) - absolute_losses
563
+
564
+ For bidirectional transmission, each direction has independent loss calculations.
565
+
566
+ When using InvestParameters on in1, the capacity automatically applies to in2
567
+ to maintain consistent bidirectional capacity without additional investment variables.
568
+
569
+ Absolute losses force the creation of binary on/off variables, which increases
570
+ computational complexity but enables realistic modeling of equipment with
571
+ standby power consumption.
572
+
573
+ """
260
574
 
261
575
  def __init__(
262
576
  self,
263
577
  label: str,
264
578
  in1: Flow,
265
579
  out1: Flow,
266
- in2: Optional[Flow] = None,
267
- out2: Optional[Flow] = None,
268
- relative_losses: Optional[NumericDataTS] = None,
269
- absolute_losses: Optional[NumericDataTS] = None,
270
- on_off_parameters: OnOffParameters = None,
580
+ in2: Flow | None = None,
581
+ out2: Flow | None = None,
582
+ relative_losses: NumericDataTS = 0,
583
+ absolute_losses: NumericDataTS | None = None,
584
+ on_off_parameters: OnOffParameters | None = None,
271
585
  prevent_simultaneous_flows_in_both_directions: bool = True,
272
- meta_data: Optional[Dict] = None,
586
+ meta_data: dict | None = None,
273
587
  ):
274
- """
275
- Initializes a Transmission component (Pipe, cable, ...) that models the flows between two sides
276
- with potential losses.
277
-
278
- Args:
279
- label: The label of the Element. Used to identify it in the FlowSystem
280
- in1: The inflow at side A. Pass InvestmentParameters here.
281
- out1: The outflow at side B.
282
- in2: The optional inflow at side B.
283
- If in1 got InvestParameters, the size of this Flow will be equal to in1 (with no extra effects!)
284
- out2: The optional outflow at side A.
285
- relative_losses: The relative loss between inflow and outflow, e.g., 0.02 for 2% loss.
286
- absolute_losses: The absolute loss, occur only when the Flow is on. Induces the creation of the ON-Variable
287
- on_off_parameters: Parameters defining the on/off behavior of the component.
288
- prevent_simultaneous_flows_in_both_directions: If True, inflow and outflow are not allowed to be both non-zero at same timestep.
289
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
290
- """
291
588
  super().__init__(
292
589
  label,
293
590
  inputs=[flow for flow in (in1, in2) if flow is not None],
@@ -325,12 +622,12 @@ class Transmission(Component):
325
622
  'Please use Flow in1. The size of in2 is equal to in1. THis is handled internally'
326
623
  )
327
624
 
328
- def create_model(self, model) -> 'TransmissionModel':
625
+ def create_model(self, model) -> TransmissionModel:
329
626
  self._plausibility_checks()
330
627
  self.model = TransmissionModel(model, self)
331
628
  return self.model
332
629
 
333
- def transform_data(self, flow_system: 'FlowSystem') -> None:
630
+ def transform_data(self, flow_system: FlowSystem) -> None:
334
631
  super().transform_data(flow_system)
335
632
  self.relative_losses = flow_system.create_time_series(
336
633
  f'{self.label_full}|relative_losses', self.relative_losses
@@ -344,7 +641,7 @@ class TransmissionModel(ComponentModel):
344
641
  def __init__(self, model: SystemModel, element: Transmission):
345
642
  super().__init__(model, element)
346
643
  self.element: Transmission = element
347
- self.on_off: Optional[OnOffModel] = None
644
+ self.on_off: OnOffModel | None = None
348
645
 
349
646
  def do_modeling(self):
350
647
  """Initiates all FlowModels"""
@@ -402,8 +699,8 @@ class LinearConverterModel(ComponentModel):
402
699
  def __init__(self, model: SystemModel, element: LinearConverter):
403
700
  super().__init__(model, element)
404
701
  self.element: LinearConverter = element
405
- self.on_off: Optional[OnOffModel] = None
406
- self.piecewise_conversion: Optional[PiecewiseConversion] = None
702
+ self.on_off: OnOffModel | None = None
703
+ self.piecewise_conversion: PiecewiseModel | None = None
407
704
 
408
705
  def do_modeling(self):
409
706
  super().do_modeling()
@@ -416,8 +713,8 @@ class LinearConverterModel(ComponentModel):
416
713
  # für alle linearen Gleichungen:
417
714
  for i, conv_factors in enumerate(self.element.conversion_factors):
418
715
  used_flows = set([self.element.flows[flow_label] for flow_label in conv_factors])
419
- used_inputs: Set = all_input_flows & used_flows
420
- used_outputs: Set = all_output_flows & used_flows
716
+ used_inputs: set[Flow] = all_input_flows & used_flows
717
+ used_outputs: set[Flow] = all_output_flows & used_flows
421
718
 
422
719
  self.add(
423
720
  self._model.add_constraints(
@@ -452,9 +749,9 @@ class StorageModel(ComponentModel):
452
749
  def __init__(self, model: SystemModel, element: Storage):
453
750
  super().__init__(model, element)
454
751
  self.element: Storage = element
455
- self.charge_state: Optional[linopy.Variable] = None
456
- self.netto_discharge: Optional[linopy.Variable] = None
457
- self._investment: Optional[InvestmentModel] = None
752
+ self.charge_state: linopy.Variable | None = None
753
+ self.netto_discharge: linopy.Variable | None = None
754
+ self._investment: InvestmentModel | None = None
458
755
 
459
756
  def do_modeling(self):
460
757
  super().do_modeling()
@@ -557,7 +854,7 @@ class StorageModel(ComponentModel):
557
854
  )
558
855
 
559
856
  @property
560
- def absolute_charge_state_bounds(self) -> Tuple[NumericData, NumericData]:
857
+ def absolute_charge_state_bounds(self) -> tuple[NumericData, NumericData]:
561
858
  relative_lower_bound, relative_upper_bound = self.relative_charge_state_bounds
562
859
  if not isinstance(self.element.capacity_in_flow_hours, InvestParameters):
563
860
  return (
@@ -571,7 +868,7 @@ class StorageModel(ComponentModel):
571
868
  )
572
869
 
573
870
  @property
574
- def relative_charge_state_bounds(self) -> Tuple[NumericData, NumericData]:
871
+ def relative_charge_state_bounds(self) -> tuple[NumericData, NumericData]:
575
872
  return (
576
873
  self.element.relative_minimum_charge_state.active_data,
577
874
  self.element.relative_maximum_charge_state.active_data,
@@ -581,32 +878,105 @@ class StorageModel(ComponentModel):
581
878
  @register_class_for_io
582
879
  class SourceAndSink(Component):
583
880
  """
584
- class for source (output-flow) and sink (input-flow) in one commponent
881
+ A SourceAndSink combines both supply and demand capabilities in a single component.
882
+
883
+ SourceAndSink components can both consume AND provide energy or material flows
884
+ from and to the system, making them ideal for modeling markets, (simple) storage facilities,
885
+ or bidirectional grid connections where buying and selling occur at the same location.
886
+
887
+ Args:
888
+ label: The label of the Element. Used to identify it in the FlowSystem.
889
+ inputs: Input-flows into the SourceAndSink representing consumption/demand side.
890
+ outputs: Output-flows from the SourceAndSink representing supply/generation side.
891
+ prevent_simultaneous_flow_rates: If True, prevents simultaneous input and output
892
+ flows. This enforces that the component operates either as a source OR sink
893
+ at any given time, but not both simultaneously. Default is True.
894
+ meta_data: Used to store additional information about the Element. Not used
895
+ internally but saved in results. Only use Python native types.
896
+
897
+ Examples:
898
+ Electricity market connection (buy/sell to grid):
899
+
900
+ ```python
901
+ electricity_market = SourceAndSink(
902
+ label='grid_connection',
903
+ inputs=[electricity_purchase], # Buy from grid
904
+ outputs=[electricity_sale], # Sell to grid
905
+ prevent_simultaneous_flow_rates=True, # Can't buy and sell simultaneously
906
+ )
907
+ ```
908
+
909
+ Natural gas storage facility:
910
+
911
+ ```python
912
+ gas_storage_facility = SourceAndSink(
913
+ label='underground_gas_storage',
914
+ inputs=[gas_injection_flow], # Inject gas into storage
915
+ outputs=[gas_withdrawal_flow], # Withdraw gas from storage
916
+ prevent_simultaneous_flow_rates=True, # Injection or withdrawal, not both
917
+ )
918
+ ```
919
+
920
+ District heating network connection:
921
+
922
+ ```python
923
+ dh_connection = SourceAndSink(
924
+ label='district_heating_tie',
925
+ inputs=[heat_purchase_flow], # Purchase heat from network
926
+ outputs=[heat_sale_flow], # Sell excess heat to network
927
+ prevent_simultaneous_flow_rates=False, # May allow simultaneous flows
928
+ )
929
+ ```
930
+
931
+ Industrial waste heat exchange:
932
+
933
+ ```python
934
+ waste_heat_exchange = SourceAndSink(
935
+ label='industrial_heat_hub',
936
+ inputs=[
937
+ waste_heat_input_a, # Receive waste heat from process A
938
+ waste_heat_input_b, # Receive waste heat from process B
939
+ ],
940
+ outputs=[
941
+ useful_heat_supply_c, # Supply heat to process C
942
+ useful_heat_supply_d, # Supply heat to process D
943
+ ],
944
+ prevent_simultaneous_flow_rates=False, # Multiple simultaneous flows allowed
945
+ )
946
+ ```
947
+
948
+ Note:
949
+ When prevent_simultaneous_flow_rates is True, binary variables are created to
950
+ ensure mutually exclusive operation between input and output flows, which
951
+ increases computational complexity but reflects realistic market or storage
952
+ operation constraints.
953
+
954
+ SourceAndSink is particularly useful for modeling:
955
+ - Energy markets with bidirectional trading
956
+ - Storage facilities with injection/withdrawal operations
957
+ - Grid tie points with import/export capabilities
958
+ - Waste exchange networks with multiple participants
959
+
960
+ Deprecated:
961
+ The deprecated `sink` and `source` kwargs are accepted for compatibility but will be removed in future releases.
585
962
  """
586
963
 
587
964
  def __init__(
588
965
  self,
589
966
  label: str,
590
- inputs: List[Flow] = None,
591
- outputs: List[Flow] = None,
967
+ inputs: list[Flow] | None = None,
968
+ outputs: list[Flow] | None = None,
592
969
  prevent_simultaneous_flow_rates: bool = True,
593
- meta_data: Optional[Dict] = None,
970
+ meta_data: dict | None = None,
594
971
  **kwargs,
595
972
  ):
596
- """
597
- Args:
598
- label: The label of the Element. Used to identify it in the FlowSystem
599
- outputs: output-flows of this component
600
- inputs: input-flows of this component
601
- prevent_simultaneous_flow_rates: If True, inflow and outflow can not be active simultaniously.
602
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
603
- """
604
973
  source = kwargs.pop('source', None)
605
974
  sink = kwargs.pop('sink', None)
606
975
  prevent_simultaneous_sink_and_source = kwargs.pop('prevent_simultaneous_sink_and_source', None)
607
976
  if source is not None:
608
- warnings.deprecated(
977
+ warnings.warn(
609
978
  'The use of the source argument is deprecated. Use the outputs argument instead.',
979
+ DeprecationWarning,
610
980
  stacklevel=2,
611
981
  )
612
982
  if outputs is not None:
@@ -614,17 +984,19 @@ class SourceAndSink(Component):
614
984
  outputs = [source]
615
985
 
616
986
  if sink is not None:
617
- warnings.deprecated(
618
- 'The use of the sink argument is deprecated. Use the outputs argument instead.',
987
+ warnings.warn(
988
+ 'The use of the sink argument is deprecated. Use the inputs argument instead.',
989
+ DeprecationWarning,
619
990
  stacklevel=2,
620
991
  )
621
992
  if inputs is not None:
622
- raise ValueError('Either sink or outputs can be specified, but not both.')
993
+ raise ValueError('Either sink or inputs can be specified, but not both.')
623
994
  inputs = [sink]
624
995
 
625
996
  if prevent_simultaneous_sink_and_source is not None:
626
- warnings.deprecated(
997
+ warnings.warn(
627
998
  'The use of the prevent_simultaneous_sink_and_source argument is deprecated. Use the prevent_simultaneous_flow_rates argument instead.',
999
+ DeprecationWarning,
628
1000
  stacklevel=2,
629
1001
  )
630
1002
  prevent_simultaneous_flow_rates = prevent_simultaneous_sink_and_source
@@ -633,7 +1005,7 @@ class SourceAndSink(Component):
633
1005
  label,
634
1006
  inputs=inputs,
635
1007
  outputs=outputs,
636
- prevent_simultaneous_flows=inputs + outputs if prevent_simultaneous_flow_rates is True else None,
1008
+ prevent_simultaneous_flows=(inputs or []) + (outputs or []) if prevent_simultaneous_flow_rates else None,
637
1009
  meta_data=meta_data,
638
1010
  )
639
1011
  self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
@@ -650,7 +1022,7 @@ class SourceAndSink(Component):
650
1022
  @property
651
1023
  def sink(self) -> Flow:
652
1024
  warnings.warn(
653
- 'The sink property is deprecated. Use the outputs property instead.',
1025
+ 'The sink property is deprecated. Use the inputs property instead.',
654
1026
  DeprecationWarning,
655
1027
  stacklevel=2,
656
1028
  )
@@ -668,20 +1040,88 @@ class SourceAndSink(Component):
668
1040
 
669
1041
  @register_class_for_io
670
1042
  class Source(Component):
1043
+ """
1044
+ A Source generates or provides energy or material flows into the system.
1045
+
1046
+ Sources represent supply points like power plants, fuel suppliers, renewable
1047
+ energy sources, or any system boundary where flows originate. They provide
1048
+ unlimited supply capability subject to flow constraints, demand patterns and effects.
1049
+
1050
+ Args:
1051
+ label: The label of the Element. Used to identify it in the FlowSystem.
1052
+ outputs: Output-flows from the source. Can be single flow or list of flows
1053
+ for sources providing multiple commodities or services.
1054
+ meta_data: Used to store additional information about the Element. Not used
1055
+ internally but saved in results. Only use Python native types.
1056
+ prevent_simultaneous_flow_rates: If True, only one output flow can be active
1057
+ at a time. Useful for modeling mutually exclusive supply options. Default is False.
1058
+
1059
+ Examples:
1060
+ Simple electricity grid connection:
1061
+
1062
+ ```python
1063
+ grid_source = Source(label='electrical_grid', outputs=[grid_electricity_flow])
1064
+ ```
1065
+
1066
+ Natural gas supply with cost and capacity constraints:
1067
+
1068
+ ```python
1069
+ gas_supply = Source(
1070
+ label='gas_network',
1071
+ outputs=[
1072
+ Flow(
1073
+ label='natural_gas_flow',
1074
+ bus=gas_bus,
1075
+ size=1000, # Maximum 1000 kW supply capacity
1076
+ effects_per_flow_hour={'cost': 0.04}, # €0.04/kWh gas cost
1077
+ )
1078
+ ],
1079
+ )
1080
+ ```
1081
+
1082
+ Multi-fuel power plant with switching constraints:
1083
+
1084
+ ```python
1085
+ multi_fuel_plant = Source(
1086
+ label='flexible_generator',
1087
+ outputs=[coal_electricity, gas_electricity, biomass_electricity],
1088
+ prevent_simultaneous_flow_rates=True, # Can only use one fuel at a time
1089
+ )
1090
+ ```
1091
+
1092
+ Renewable energy source with investment optimization:
1093
+
1094
+ ```python
1095
+ solar_farm = Source(
1096
+ label='solar_pv',
1097
+ outputs=[
1098
+ Flow(
1099
+ label='solar_power',
1100
+ bus=electricity_bus,
1101
+ size=InvestParameters(
1102
+ minimum_size=0,
1103
+ maximum_size=50000, # Up to 50 MW
1104
+ specific_effects={'cost': 800}, # €800/kW installed
1105
+ fix_effects={'cost': 100000}, # €100k development costs
1106
+ ),
1107
+ fixed_relative_profile=solar_profile, # Hourly generation profile
1108
+ )
1109
+ ],
1110
+ )
1111
+ ```
1112
+
1113
+ Deprecated:
1114
+ The deprecated `source` kwarg is accepted for compatibility but will be removed in future releases.
1115
+ """
1116
+
671
1117
  def __init__(
672
1118
  self,
673
1119
  label: str,
674
- outputs: List[Flow] = None,
675
- meta_data: Optional[Dict] = None,
1120
+ outputs: list[Flow] | None = None,
1121
+ meta_data: dict | None = None,
676
1122
  prevent_simultaneous_flow_rates: bool = False,
677
1123
  **kwargs,
678
1124
  ):
679
- """
680
- Args:
681
- label: The label of the Element. Used to identify it in the FlowSystem
682
- outputs: output-flows of source
683
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
684
- """
685
1125
  source = kwargs.pop('source', None)
686
1126
  if source is not None:
687
1127
  warnings.warn(
@@ -713,29 +1153,112 @@ class Source(Component):
713
1153
 
714
1154
  @register_class_for_io
715
1155
  class Sink(Component):
1156
+ """
1157
+ A Sink consumes energy or material flows from the system.
1158
+
1159
+ Sinks represent demand points like electrical loads, heat demands, material
1160
+ consumption, or any system boundary where flows terminate. They provide
1161
+ unlimited consumption capability subject to flow constraints, demand patterns and effects.
1162
+
1163
+ Args:
1164
+ label: The label of the Element. Used to identify it in the FlowSystem.
1165
+ inputs: Input-flows into the sink. Can be single flow or list of flows
1166
+ for sinks consuming multiple commodities or services.
1167
+ meta_data: Used to store additional information about the Element. Not used
1168
+ internally but saved in results. Only use Python native types.
1169
+ prevent_simultaneous_flow_rates: If True, only one input flow can be active
1170
+ at a time. Useful for modeling mutually exclusive consumption options. Default is False.
1171
+
1172
+ Examples:
1173
+ Simple electrical demand:
1174
+
1175
+ ```python
1176
+ electrical_load = Sink(label='building_load', inputs=[electricity_demand_flow])
1177
+ ```
1178
+
1179
+ Heat demand with time-varying profile:
1180
+
1181
+ ```python
1182
+ heat_demand = Sink(
1183
+ label='district_heating_load',
1184
+ inputs=[
1185
+ Flow(
1186
+ label='heat_consumption',
1187
+ bus=heat_bus,
1188
+ fixed_relative_profile=hourly_heat_profile, # Demand profile
1189
+ size=2000, # Peak demand of 2000 kW
1190
+ )
1191
+ ],
1192
+ )
1193
+ ```
1194
+
1195
+ Multi-energy building with switching capabilities:
1196
+
1197
+ ```python
1198
+ flexible_building = Sink(
1199
+ label='smart_building',
1200
+ inputs=[electricity_heating, gas_heating, heat_pump_heating],
1201
+ prevent_simultaneous_flow_rates=True, # Can only use one heating mode
1202
+ )
1203
+ ```
1204
+
1205
+ Industrial process with variable demand:
1206
+
1207
+ ```python
1208
+ factory_load = Sink(
1209
+ label='manufacturing_plant',
1210
+ inputs=[
1211
+ Flow(
1212
+ label='electricity_process',
1213
+ bus=electricity_bus,
1214
+ size=5000, # Base electrical load
1215
+ effects_per_flow_hour={'cost': -0.1}, # Value of service (negative cost)
1216
+ ),
1217
+ Flow(
1218
+ label='steam_process',
1219
+ bus=steam_bus,
1220
+ size=3000, # Process steam demand
1221
+ fixed_relative_profile=production_schedule,
1222
+ ),
1223
+ ],
1224
+ )
1225
+ ```
1226
+
1227
+ Deprecated:
1228
+ The deprecated `sink` kwarg is accepted for compatibility but will be removed in future releases.
1229
+ """
1230
+
716
1231
  def __init__(
717
1232
  self,
718
1233
  label: str,
719
- inputs: List[Flow] = None,
720
- meta_data: Optional[Dict] = None,
1234
+ inputs: list[Flow] | None = None,
1235
+ meta_data: dict | None = None,
721
1236
  prevent_simultaneous_flow_rates: bool = False,
722
1237
  **kwargs,
723
1238
  ):
724
1239
  """
725
- Args:
726
- label: The label of the Element. Used to identify it in the FlowSystem
727
- inputs: output-flows of source
728
- meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
1240
+ Initialize a Sink (consumes flow from the system).
1241
+
1242
+ Supports legacy `sink=` keyword for backward compatibility (deprecated): if `sink` is provided it is used as the single input flow and a DeprecationWarning is issued; specifying both `inputs` and `sink` raises ValueError.
1243
+
1244
+ Parameters:
1245
+ label (str): Unique element label.
1246
+ inputs (list[Flow], optional): Input flows for the sink.
1247
+ meta_data (dict, optional): Arbitrary metadata attached to the element.
1248
+ prevent_simultaneous_flow_rates (bool, optional): If True, prevents simultaneous nonzero flow rates across the element's inputs by wiring that restriction into the base Component setup.
1249
+
1250
+ Note:
1251
+ The deprecated `sink` kwarg is accepted for compatibility but will be removed in future releases.
729
1252
  """
730
1253
  sink = kwargs.pop('sink', None)
731
1254
  if sink is not None:
732
1255
  warnings.warn(
733
- 'The use of the sink argument is deprecated. Use the outputs argument instead.',
1256
+ 'The use of the sink argument is deprecated. Use the inputs argument instead.',
734
1257
  DeprecationWarning,
735
1258
  stacklevel=2,
736
1259
  )
737
1260
  if inputs is not None:
738
- raise ValueError('Either sink or outputs can be specified, but not both.')
1261
+ raise ValueError('Either sink or inputs can be specified, but not both.')
739
1262
  inputs = [sink]
740
1263
 
741
1264
  self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
@@ -749,7 +1272,7 @@ class Sink(Component):
749
1272
  @property
750
1273
  def sink(self) -> Flow:
751
1274
  warnings.warn(
752
- 'The sink property is deprecated. Use the outputs property instead.',
1275
+ 'The sink property is deprecated. Use the inputs property instead.',
753
1276
  DeprecationWarning,
754
1277
  stacklevel=2,
755
1278
  )