flixopt 1.0.12__py3-none-any.whl → 2.0.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.

Files changed (73) hide show
  1. docs/examples/00-Minimal Example.md +5 -0
  2. docs/examples/01-Basic Example.md +5 -0
  3. docs/examples/02-Complex Example.md +10 -0
  4. docs/examples/03-Calculation Modes.md +5 -0
  5. docs/examples/index.md +5 -0
  6. docs/faq/contribute.md +49 -0
  7. docs/faq/index.md +3 -0
  8. docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
  9. docs/images/architecture_flixOpt.png +0 -0
  10. docs/images/flixopt-icon.svg +1 -0
  11. docs/javascripts/mathjax.js +18 -0
  12. docs/release-notes/_template.txt +32 -0
  13. docs/release-notes/index.md +7 -0
  14. docs/release-notes/v2.0.0.md +93 -0
  15. docs/release-notes/v2.0.1.md +12 -0
  16. docs/user-guide/Mathematical Notation/Bus.md +33 -0
  17. docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +132 -0
  18. docs/user-guide/Mathematical Notation/Flow.md +26 -0
  19. docs/user-guide/Mathematical Notation/LinearConverter.md +21 -0
  20. docs/user-guide/Mathematical Notation/Piecewise.md +49 -0
  21. docs/user-guide/Mathematical Notation/Storage.md +44 -0
  22. docs/user-guide/Mathematical Notation/index.md +22 -0
  23. docs/user-guide/Mathematical Notation/others.md +3 -0
  24. docs/user-guide/index.md +124 -0
  25. {flixOpt → flixopt}/__init__.py +5 -2
  26. {flixOpt → flixopt}/aggregation.py +113 -140
  27. flixopt/calculation.py +455 -0
  28. {flixOpt → flixopt}/commons.py +7 -4
  29. flixopt/components.py +630 -0
  30. {flixOpt → flixopt}/config.py +9 -8
  31. {flixOpt → flixopt}/config.yaml +3 -3
  32. flixopt/core.py +970 -0
  33. flixopt/effects.py +386 -0
  34. flixopt/elements.py +534 -0
  35. flixopt/features.py +1042 -0
  36. flixopt/flow_system.py +409 -0
  37. flixopt/interface.py +265 -0
  38. flixopt/io.py +308 -0
  39. flixopt/linear_converters.py +331 -0
  40. flixopt/plotting.py +1340 -0
  41. flixopt/results.py +898 -0
  42. flixopt/solvers.py +77 -0
  43. flixopt/structure.py +630 -0
  44. flixopt/utils.py +62 -0
  45. flixopt-2.0.1.dist-info/METADATA +145 -0
  46. flixopt-2.0.1.dist-info/RECORD +57 -0
  47. {flixopt-1.0.12.dist-info → flixopt-2.0.1.dist-info}/WHEEL +1 -1
  48. flixopt-2.0.1.dist-info/top_level.txt +6 -0
  49. pics/architecture_flixOpt-pre2.0.0.png +0 -0
  50. pics/architecture_flixOpt.png +0 -0
  51. pics/flixopt-icon.svg +1 -0
  52. pics/pics.pptx +0 -0
  53. scripts/gen_ref_pages.py +54 -0
  54. site/release-notes/_template.txt +32 -0
  55. flixOpt/calculation.py +0 -629
  56. flixOpt/components.py +0 -614
  57. flixOpt/core.py +0 -182
  58. flixOpt/effects.py +0 -410
  59. flixOpt/elements.py +0 -489
  60. flixOpt/features.py +0 -942
  61. flixOpt/flow_system.py +0 -351
  62. flixOpt/interface.py +0 -203
  63. flixOpt/linear_converters.py +0 -325
  64. flixOpt/math_modeling.py +0 -1145
  65. flixOpt/plotting.py +0 -712
  66. flixOpt/results.py +0 -563
  67. flixOpt/solvers.py +0 -21
  68. flixOpt/structure.py +0 -733
  69. flixOpt/utils.py +0 -134
  70. flixopt-1.0.12.dist-info/METADATA +0 -174
  71. flixopt-1.0.12.dist-info/RECORD +0 -29
  72. flixopt-1.0.12.dist-info/top_level.txt +0 -3
  73. {flixopt-1.0.12.dist-info → flixopt-2.0.1.dist-info/licenses}/LICENSE +0 -0
flixOpt/components.py DELETED
@@ -1,614 +0,0 @@
1
- """
2
- This module contains the basic components of the flixOpt framework.
3
- """
4
-
5
- import logging
6
- from typing import Dict, List, Literal, Optional, Set, Tuple, Union
7
-
8
- import numpy as np
9
-
10
- from . import utils
11
- from .core import Numeric, Numeric_TS, Skalar, TimeSeries
12
- from .elements import Component, ComponentModel, Flow, _create_time_series
13
- from .features import InvestmentModel, MultipleSegmentsModel, OnOffModel
14
- from .interface import InvestParameters, OnOffParameters
15
- from .math_modeling import Equation, VariableTS
16
- from .structure import SystemModel, create_equation, create_variable
17
-
18
- logger = logging.getLogger('flixOpt')
19
-
20
-
21
- class LinearConverter(Component):
22
- """
23
- Converts one FLow into another via linear conversion factors
24
- """
25
-
26
- def __init__(
27
- self,
28
- label: str,
29
- inputs: List[Flow],
30
- outputs: List[Flow],
31
- on_off_parameters: OnOffParameters = None,
32
- conversion_factors: List[Dict[Flow, Numeric_TS]] = None,
33
- segmented_conversion_factors: Dict[Flow, List[Tuple[Numeric_TS, Numeric_TS]]] = None,
34
- meta_data: Optional[Dict] = None,
35
- ):
36
- """
37
- Parameters
38
- ----------
39
- label : str
40
- name.
41
- meta_data : Optional[Dict]
42
- used to store more information about the element. Is not used internally, but saved in the results
43
- inputs : input flows.
44
- outputs : output flows.
45
- on_off_parameters: Information about on and off states. See class OnOffParameters.
46
- conversion_factors : linear relation between flows.
47
- Either 'conversion_factors' or 'segmented_conversion_factors' can be used!
48
- example heat pump:
49
- segmented_conversion_factors : Segmented linear relation between flows.
50
- Each Flow gets a List of Segments assigned.
51
- If FLows need to be 0 (or Off), include a "Zero-Segment" "(0, 0)", or use on_off_parameters
52
- Either 'segmented_conversion_factors' or 'conversion_factors' can be used!
53
- --> "gaps" can be expressed by a segment not starting at the end of the prior segment : [(1,3), (4,5)]
54
- --> "points" can expressed as segment with same begin and end : [(3,3), (4,4)]
55
-
56
- """
57
- super().__init__(label, inputs, outputs, on_off_parameters, meta_data=meta_data)
58
- self.conversion_factors = conversion_factors or []
59
- self.segmented_conversion_factors = segmented_conversion_factors or {}
60
- self._plausibility_checks()
61
-
62
- def create_model(self) -> 'LinearConverterModel':
63
- self.model = LinearConverterModel(self)
64
- return self.model
65
-
66
- def _plausibility_checks(self) -> None:
67
- if not self.conversion_factors and not self.segmented_conversion_factors:
68
- raise Exception('Either conversion_factors or segmented_conversion_factors must be defined!')
69
- if self.conversion_factors and self.segmented_conversion_factors:
70
- raise Exception('Only one of conversion_factors or segmented_conversion_factors can be defined, not both!')
71
-
72
- if self.conversion_factors:
73
- if self.degrees_of_freedom <= 0:
74
- raise Exception(
75
- f'Too Many conversion_factors_specified. Care that you use less conversion_factors '
76
- f'then inputs + outputs!! With {len(self.inputs + self.outputs)} inputs and outputs, '
77
- f'use not more than {len(self.inputs + self.outputs) - 1} conversion_factors!'
78
- )
79
-
80
- for conversion_factor in self.conversion_factors:
81
- for flow in conversion_factor:
82
- if flow not in (self.inputs + self.outputs):
83
- raise Exception(
84
- f'{self.label}: Flow {flow.label} in conversion_factors is not in inputs/outputs'
85
- )
86
- if self.segmented_conversion_factors:
87
- for flow in self.inputs + self.outputs:
88
- if isinstance(flow.size, InvestParameters) and flow.size.fixed_size is None:
89
- raise Exception(
90
- f'segmented_conversion_factors (in {self.label_full}) and variable size '
91
- f'(in flow {flow.label_full}) do not make sense together!'
92
- )
93
-
94
- def transform_data(self):
95
- super().transform_data()
96
- if self.conversion_factors:
97
- self.conversion_factors = self._transform_conversion_factors()
98
- else:
99
- segmented_conversion_factors = {}
100
- for flow, segments in self.segmented_conversion_factors.items():
101
- segmented_conversion_factors[flow] = [
102
- (
103
- _create_time_series('Stuetzstelle', segment[0], self),
104
- _create_time_series('Stuetzstelle', segment[1], self),
105
- )
106
- for segment in segments
107
- ]
108
- self.segmented_conversion_factors = segmented_conversion_factors
109
-
110
- def _transform_conversion_factors(self) -> List[Dict[Flow, TimeSeries]]:
111
- """macht alle Faktoren, die nicht TimeSeries sind, zu TimeSeries"""
112
- list_of_conversion_factors = []
113
- for conversion_factor in self.conversion_factors:
114
- transformed_dict = {}
115
- for flow, values in conversion_factor.items():
116
- transformed_dict[flow] = _create_time_series(f'{flow.label}_factor', values, self)
117
- list_of_conversion_factors.append(transformed_dict)
118
- return list_of_conversion_factors
119
-
120
- @property
121
- def degrees_of_freedom(self):
122
- return len(self.inputs + self.outputs) - len(self.conversion_factors)
123
-
124
-
125
- class Storage(Component):
126
- """
127
- Klasse Storage
128
- """
129
-
130
- # TODO: Dabei fällt mir auf. Vielleicht sollte man mal überlegen, ob man für Ladeleistungen bereits in dem
131
- # jeweiligen Zeitschritt mit einem Verlust berücksichtigt. Zumindest für große Zeitschritte bzw. große Verluste
132
- # eventuell relevant.
133
- # -> Sprich: speicherverlust = charge_state(t) * relative_loss_per_hour * dt + 0.5 * Q_lade(t) * dt * relative_loss_per_hour * dt
134
- # -> müsste man aber auch für den sich ändernden Ladezustand berücksichtigten
135
-
136
- def __init__(
137
- self,
138
- label: str,
139
- charging: Flow,
140
- discharging: Flow,
141
- capacity_in_flow_hours: Union[Skalar, InvestParameters],
142
- relative_minimum_charge_state: Numeric = 0,
143
- relative_maximum_charge_state: Numeric = 1,
144
- initial_charge_state: Optional[Union[Skalar, Literal['lastValueOfSim']]] = 0,
145
- minimal_final_charge_state: Optional[Skalar] = None,
146
- maximal_final_charge_state: Optional[Skalar] = None,
147
- eta_charge: Numeric = 1,
148
- eta_discharge: Numeric = 1,
149
- relative_loss_per_hour: Numeric = 0,
150
- prevent_simultaneous_charge_and_discharge: bool = True,
151
- meta_data: Optional[Dict] = None,
152
- ):
153
- """
154
- constructor of storage
155
-
156
- Parameters
157
- ----------
158
- label : str
159
- description.
160
- meta_data : Optional[Dict]
161
- used to store more information about the element. Is not used internally, but saved in the results
162
- charging : Flow
163
- ingoing flow.
164
- discharging : Flow
165
- outgoing flow.
166
- capacity_in_flow_hours : Skalar or InvestParameter
167
- nominal capacity of the storage
168
- relative_minimum_charge_state : float or TS, optional
169
- minimum relative charge state. The default is 0.
170
- relative_maximum_charge_state : float or TS, optional
171
- maximum relative charge state. The default is 1.
172
- initial_charge_state : None, float (0...1), 'lastValueOfSim', optional
173
- storage charge_state at the beginning. The default is 0.
174
- float: defined charge_state at start of first timestep
175
- None: free to choose by optimizer
176
- 'lastValueOfSim': chargeState0 is equal to chargestate of last timestep ("closed simulation")
177
- minimal_final_charge_state : float or None, optional
178
- minimal value of chargeState at the end of timeseries.
179
- maximal_final_charge_state : float or None, optional
180
- maximal value of chargeState at the end of timeseries.
181
- eta_charge : float, optional
182
- efficiency factor of charging/loading. The default is 1.
183
- eta_discharge : TYPE, optional
184
- efficiency factor of uncharging/unloading. The default is 1.
185
- relative_loss_per_hour : float or TS. optional
186
- loss per chargeState-Unit per hour. The default is 0.
187
- prevent_simultaneous_charge_and_discharge : boolean, optional
188
- should simultaneously Loading and Unloading be avoided? (Attention, Performance maybe becomes worse with avoidInAndOutAtOnce=True). The default is True.
189
- """
190
- # TODO: fixed_relative_chargeState implementieren
191
- super().__init__(
192
- label,
193
- inputs=[charging],
194
- outputs=[discharging],
195
- prevent_simultaneous_flows=[charging, discharging] if prevent_simultaneous_charge_and_discharge else None,
196
- meta_data=meta_data,
197
- )
198
-
199
- self.charging = charging
200
- self.discharging = discharging
201
- self.capacity_in_flow_hours = capacity_in_flow_hours
202
- self.relative_minimum_charge_state: Numeric_TS = relative_minimum_charge_state
203
- self.relative_maximum_charge_state: Numeric_TS = relative_maximum_charge_state
204
-
205
- self.initial_charge_state = initial_charge_state
206
- self.minimal_final_charge_state = minimal_final_charge_state
207
- self.maximal_final_charge_state = maximal_final_charge_state
208
-
209
- self.eta_charge: Numeric_TS = eta_charge
210
- self.eta_discharge: Numeric_TS = eta_discharge
211
- self.relative_loss_per_hour: Numeric_TS = relative_loss_per_hour
212
-
213
- def create_model(self) -> 'StorageModel':
214
- self.model = StorageModel(self)
215
- return self.model
216
-
217
- def transform_data(self) -> None:
218
- super().transform_data()
219
- self.relative_minimum_charge_state = _create_time_series(
220
- 'relative_minimum_charge_state', self.relative_minimum_charge_state, self
221
- )
222
- self.relative_maximum_charge_state = _create_time_series(
223
- 'relative_maximum_charge_state', self.relative_maximum_charge_state, self
224
- )
225
- self.eta_charge = _create_time_series('eta_charge', self.eta_charge, self)
226
- self.eta_discharge = _create_time_series('eta_discharge', self.eta_discharge, self)
227
- self.relative_loss_per_hour = _create_time_series('relative_loss_per_hour', self.relative_loss_per_hour, self)
228
- if isinstance(self.capacity_in_flow_hours, InvestParameters):
229
- self.capacity_in_flow_hours.transform_data()
230
-
231
-
232
- class Transmission(Component):
233
- # TODO: automatic on-Value in Flows if loss_abs
234
- # TODO: loss_abs must be: investment_size * loss_abs_rel!!!
235
- # TODO: investmentsize only on 1 flow
236
- # TODO: automatic investArgs for both in-flows (or alternatively both out-flows!)
237
- # TODO: optional: capacities should be recognised for losses
238
-
239
- def __init__(
240
- self,
241
- label: str,
242
- in1: Flow,
243
- out1: Flow,
244
- in2: Optional[Flow] = None,
245
- out2: Optional[Flow] = None,
246
- relative_losses: Optional[Numeric_TS] = None,
247
- absolute_losses: Optional[Numeric_TS] = None,
248
- on_off_parameters: OnOffParameters = None,
249
- prevent_simultaneous_flows_in_both_directions: bool = True,
250
- ):
251
- """
252
- Initializes a Transmission component (Pipe, cable, ...) that models the flows between two sides
253
- with potential losses.
254
-
255
- Parameters
256
- ----------
257
- label : str
258
- The name of the transmission component.
259
- in1 : Flow
260
- The inflow at side A. Pass InvestmentParameters here.
261
- out1 : Flow
262
- The outflow at side B.
263
- in2 : Optional[Flow], optional
264
- The optional inflow at side B.
265
- If in1 got Investmentparameters, the size of this Flow will be equal to in1 (with no extra effects!)
266
- out2 : Optional[Flow], optional
267
- The optional outflow at side A.
268
- relative_losses : Optional[Numeric_TS], optional
269
- The relative loss between inflow and outflow, e.g., 0.02 for 2% loss.
270
- absolute_losses : Optional[Numeric_TS], optional
271
- The absolute loss, occur only when the Flow is on. Induces the creation of the ON-Variable
272
- on_off_parameters : OnOffParameters, optional
273
- Parameters defining the on/off behavior of the component.
274
- prevent_simultaneous_flows_in_both_directions : bool, default=True
275
- If True, prevents simultaneous flows in both directions.
276
- """
277
- super().__init__(
278
- label,
279
- inputs=[flow for flow in (in1, in2) if flow is not None],
280
- outputs=[flow for flow in (out1, out2) if flow is not None],
281
- on_off_parameters=on_off_parameters,
282
- prevent_simultaneous_flows=None
283
- if in2 is None or prevent_simultaneous_flows_in_both_directions is False
284
- else [in1, in2],
285
- )
286
- self.in1 = in1
287
- self.out1 = out1
288
- self.in2 = in2
289
- self.out2 = out2
290
-
291
- self.relative_losses = relative_losses
292
- self.absolute_losses = absolute_losses
293
-
294
- def _plausibility_checks(self):
295
- # check buses:
296
- if self.in2 is not None:
297
- assert self.in2.bus == self.out1.bus, (
298
- f'Output 1 and Input 2 do not start/end at the same Bus: {self.out1.bus=}, {self.in2.bus=}'
299
- )
300
- if self.out2 is not None:
301
- assert self.out2.bus == self.in1.bus, (
302
- f'Input 1 and Output 2 do not start/end at the same Bus: {self.in1.bus=}, {self.out2.bus=}'
303
- )
304
- # Check Investments
305
- for flow in [self.out1, self.in2, self.out2]:
306
- if flow is not None and isinstance(flow.size, InvestParameters):
307
- raise ValueError(
308
- 'Transmission currently does not support separate InvestParameters for Flows. '
309
- 'Please use Flow in1. The size of in2 is equal to in1. THis is handled internally'
310
- )
311
-
312
- def create_model(self) -> 'TransmissionModel':
313
- self.model = TransmissionModel(self)
314
- return self.model
315
-
316
- def transform_data(self) -> None:
317
- super().transform_data()
318
- self.relative_losses = _create_time_series('relative_losses', self.relative_losses, self)
319
- self.absolute_losses = _create_time_series('absolute_losses', self.absolute_losses, self)
320
-
321
-
322
- class TransmissionModel(ComponentModel):
323
- def __init__(self, element: Transmission):
324
- super().__init__(element)
325
- self.element: Transmission = element
326
- self._on: Optional[OnOffModel] = None
327
-
328
- def do_modeling(self, system_model: SystemModel):
329
- """Initiates all FlowModels"""
330
- # Force On Variable if absolute losses are present
331
- if (self.element.absolute_losses is not None) and np.any(self.element.absolute_losses.active_data != 0):
332
- for flow in self.element.inputs + self.element.outputs:
333
- if flow.on_off_parameters is None:
334
- flow.on_off_parameters = OnOffParameters()
335
-
336
- # Make sure either None or both in Flows have InvestParameters
337
- if self.element.in2 is not None:
338
- if isinstance(self.element.in1.size, InvestParameters) and not isinstance(
339
- self.element.in2.size, InvestParameters
340
- ):
341
- self.element.in2.size = InvestParameters(maximum_size=self.element.in1.size.maximum_size)
342
-
343
- super().do_modeling(system_model)
344
-
345
- # first direction
346
- self.create_transmission_equation('direction_1', self.element.in1, self.element.out1)
347
-
348
- # second direction:
349
- if self.element.in2 is not None:
350
- self.create_transmission_equation('direction_2', self.element.in2, self.element.out2)
351
-
352
- # equate size of both directions
353
- if isinstance(self.element.in1.size, InvestParameters) and self.element.in2 is not None:
354
- # eq: in1.size = in2.size
355
- eq_equal_size = create_equation('equal_size_in_both_directions', self, 'eq')
356
- eq_equal_size.add_summand(self.element.in1.model._investment.size, 1)
357
- eq_equal_size.add_summand(self.element.in2.model._investment.size, -1)
358
-
359
- def create_transmission_equation(self, name: str, in_flow: Flow, out_flow: Flow) -> Equation:
360
- """Creates an Equation for the Transmission efficiency and adds it to the model"""
361
- # first direction
362
- # eq: in(t)*(1-loss_rel(t)) = out(t) + on(t)*loss_abs(t)
363
- eq_transmission = create_equation(name, self, 'eq')
364
- efficiency = 1 if self.element.relative_losses is None else (1 - self.element.relative_losses.active_data)
365
- eq_transmission.add_summand(in_flow.model.flow_rate, efficiency)
366
- eq_transmission.add_summand(out_flow.model.flow_rate, -1)
367
- if self.element.absolute_losses is not None:
368
- eq_transmission.add_summand(in_flow.model._on.on, -1 * self.element.absolute_losses.active_data)
369
- return eq_transmission
370
-
371
-
372
- class LinearConverterModel(ComponentModel):
373
- def __init__(self, element: LinearConverter):
374
- super().__init__(element)
375
- self.element: LinearConverter = element
376
- self._on: Optional[OnOffModel] = None
377
-
378
- def do_modeling(self, system_model: SystemModel):
379
- super().do_modeling(system_model)
380
-
381
- # conversion_factors:
382
- if self.element.conversion_factors:
383
- all_input_flows = set(self.element.inputs)
384
- all_output_flows = set(self.element.outputs)
385
-
386
- # für alle linearen Gleichungen:
387
- for i, conversion_factor in enumerate(self.element.conversion_factors):
388
- # erstelle Gleichung für jedes t:
389
- # sum(inputs * factor) = sum(outputs * factor)
390
- # left = in1.flow_rate[t] * factor_in1[t] + in2.flow_rate[t] * factor_in2[t] + ...
391
- # right = out1.flow_rate[t] * factor_out1[t] + out2.flow_rate[t] * factor_out2[t] + ...
392
- # eq: left = right
393
- used_flows = set(conversion_factor.keys())
394
- used_inputs: Set = all_input_flows & used_flows
395
- used_outputs: Set = all_output_flows & used_flows
396
-
397
- eq_conversion = create_equation(f'conversion_{i}', self)
398
- for flow in used_inputs:
399
- factor = conversion_factor[flow].active_data
400
- eq_conversion.add_summand(flow.model.flow_rate, factor) # flow1.flow_rate[t] * factor[t]
401
- for flow in used_outputs:
402
- factor = conversion_factor[flow].active_data
403
- eq_conversion.add_summand(flow.model.flow_rate, -1 * factor) # output.val[t] * -1 * factor[t]
404
-
405
- eq_conversion.add_constant(0) # TODO: Is this necessary?
406
-
407
- # (linear) segments:
408
- else:
409
- # TODO: Improve Inclusion of OnOffParameters. Instead of creating a Binary in every flow, the binary could only be part of the Segment itself
410
- segments = {
411
- flow.model.flow_rate: [
412
- (ts1.active_data, ts2.active_data) for ts1, ts2 in self.element.segmented_conversion_factors[flow]
413
- ]
414
- for flow in self.element.inputs + self.element.outputs
415
- }
416
- linear_segments = MultipleSegmentsModel(
417
- self.element, segments, self._on.on if self._on is not None else None
418
- ) # TODO: Add Outside_segments Variable (On)
419
- linear_segments.do_modeling(system_model)
420
- self.sub_models.append(linear_segments)
421
-
422
-
423
- class StorageModel(ComponentModel):
424
- """Model of Storage"""
425
-
426
- # TODO: Add additional Timestep!!!
427
- def __init__(self, element: Storage):
428
- super().__init__(element)
429
- self.element: Storage = element
430
- self.charge_state: Optional[VariableTS] = None
431
- self.netto_discharge: Optional[VariableTS] = None
432
- self._investment: Optional[InvestmentModel] = None
433
-
434
- def do_modeling(self, system_model):
435
- super().do_modeling(system_model)
436
-
437
- lb, ub = self.absolute_charge_state_bounds
438
- self.charge_state = create_variable(
439
- 'charge_state', self, system_model.nr_of_time_steps + 1, lower_bound=lb, upper_bound=ub
440
- )
441
-
442
- self.netto_discharge = create_variable(
443
- 'netto_discharge', self, system_model.nr_of_time_steps, lower_bound=-np.inf
444
- ) # negative Werte zulässig!
445
-
446
- # netto_discharge:
447
- # eq: nettoFlow(t) - discharging(t) + charging(t) = 0
448
- eq_netto = create_equation('netto_discharge', self, eq_type='eq')
449
- eq_netto.add_summand(self.netto_discharge, 1)
450
- eq_netto.add_summand(self.element.charging.model.flow_rate, 1)
451
- eq_netto.add_summand(self.element.discharging.model.flow_rate, -1)
452
-
453
- indices_charge_state = range(system_model.indices.start, system_model.indices.stop + 1) # additional
454
-
455
- ############# Charge State Equation
456
- # charge_state(n+1)
457
- # + charge_state(n) * [relative_loss_per_hour * dt(n) - 1]
458
- # - charging(n) * eta_charge * dt(n)
459
- # + discharging(n) * 1 / eta_discharge * dt(n)
460
- # = 0
461
- eq_charge_state = create_equation('charge_state', self, eq_type='eq')
462
- eq_charge_state.add_summand(self.charge_state, 1, indices_charge_state[1:]) # 1:end
463
- eq_charge_state.add_summand(
464
- self.charge_state,
465
- (self.element.relative_loss_per_hour.active_data * system_model.dt_in_hours) - 1,
466
- indices_charge_state[:-1],
467
- ) # sprich 0 .. end-1 % nach letztem Zeitschritt gibt es noch einen weiteren Ladezustand!
468
- eq_charge_state.add_summand(
469
- self.element.charging.model.flow_rate, -1 * self.element.eta_charge.active_data * system_model.dt_in_hours
470
- )
471
- eq_charge_state.add_summand(
472
- self.element.discharging.model.flow_rate,
473
- 1 / self.element.eta_discharge.active_data * system_model.dt_in_hours,
474
- )
475
-
476
- if isinstance(self.element.capacity_in_flow_hours, InvestParameters):
477
- self._investment = InvestmentModel(
478
- self.element, self.element.capacity_in_flow_hours, self.charge_state, self.relative_charge_state_bounds
479
- )
480
- self.sub_models.append(self._investment)
481
- self._investment.do_modeling(system_model)
482
-
483
- # Initial charge state
484
- if self.element.initial_charge_state is not None:
485
- self._model_initial_and_final_charge_state(system_model)
486
-
487
- def _model_initial_and_final_charge_state(self, system_model):
488
- indices_charge_state = range(system_model.indices.start, system_model.indices.stop + 1) # additional
489
-
490
- if self.element.initial_charge_state is not None:
491
- eq_initial = create_equation('initial_charge_state', self, eq_type='eq')
492
- if utils.is_number(self.element.initial_charge_state):
493
- # eq: Q_Ladezustand(1) = Q_Ladezustand_Start;
494
- eq_initial.add_constant(self.element.initial_charge_state) # chargeState_0 !
495
- eq_initial.add_summand(self.charge_state, 1, system_model.indices[0])
496
- elif self.element.initial_charge_state == 'lastValueOfSim':
497
- # eq: Q_Ladezustand(1) - Q_Ladezustand(end) = 0;
498
- eq_initial.add_summand(self.charge_state, 1, system_model.indices[0])
499
- eq_initial.add_summand(self.charge_state, -1, system_model.indices[-1])
500
- else:
501
- raise Exception(f'initial_charge_state has undefined value: {self.element.initial_charge_state}')
502
- # TODO: Validation in Storage Class, not in Model
503
-
504
- ####################################
505
- # Final Charge State
506
- # 1: eq: Q_charge_state(end) <= Q_max
507
- if self.element.maximal_final_charge_state is not None:
508
- eq_max = create_equation('eq_final_charge_state_max', self, eq_type='ineq')
509
- eq_max.add_summand(self.charge_state, 1, indices_charge_state[-1])
510
- eq_max.add_constant(self.element.maximal_final_charge_state)
511
-
512
- # 2: eq: - Q_charge_state(end) <= - Q_min
513
- if self.element.minimal_final_charge_state is not None:
514
- eq_min = create_equation('eq_charge_state_end_min', self, eq_type='ineq')
515
- eq_min.add_summand(self.charge_state, -1, indices_charge_state[-1])
516
- eq_min.add_constant(-self.element.minimal_final_charge_state)
517
-
518
- @property
519
- def absolute_charge_state_bounds(self) -> Tuple[Numeric, Numeric]:
520
- relative_lower_bound, relative_upper_bound = self.relative_charge_state_bounds
521
- if not isinstance(self.element.capacity_in_flow_hours, InvestParameters):
522
- return (
523
- relative_lower_bound * self.element.capacity_in_flow_hours,
524
- relative_upper_bound * self.element.capacity_in_flow_hours,
525
- )
526
- else:
527
- return (
528
- relative_lower_bound * self.element.capacity_in_flow_hours.minimum_size,
529
- relative_upper_bound * self.element.capacity_in_flow_hours.maximum_size,
530
- )
531
-
532
- @property
533
- def relative_charge_state_bounds(self) -> Tuple[Numeric, Numeric]:
534
- return (
535
- self.element.relative_minimum_charge_state.active_data,
536
- self.element.relative_maximum_charge_state.active_data,
537
- )
538
-
539
-
540
- class SourceAndSink(Component):
541
- """
542
- class for source (output-flow) and sink (input-flow) in one commponent
543
- """
544
-
545
- # source : Flow
546
- # sink : Flow
547
-
548
- def __init__(
549
- self,
550
- label: str,
551
- source: Flow,
552
- sink: Flow,
553
- prevent_simultaneous_flows: bool = True,
554
- meta_data: Optional[Dict] = None,
555
- ):
556
- """
557
- Parameters
558
- ----------
559
- label : str
560
- name of sourceAndSink
561
- meta_data : Optional[Dict]
562
- used to store more information about the element. Is not used internally, but saved in the results
563
- source : Flow
564
- output-flow of this component
565
- sink : Flow
566
- input-flow of this component
567
- prevent_simultaneous_flows: boolean. Default ist True.
568
- True: inflow and outflow are not allowed to be both non-zero at same timestep.
569
- False: inflow and outflow are working independently.
570
-
571
- """
572
- super().__init__(
573
- label,
574
- inputs=[sink],
575
- outputs=[source],
576
- prevent_simultaneous_flows=[sink, source] if prevent_simultaneous_flows is True else None,
577
- meta_data=meta_data,
578
- )
579
- self.source = source
580
- self.sink = sink
581
-
582
-
583
- class Source(Component):
584
- def __init__(self, label: str, source: Flow, meta_data: Optional[Dict] = None):
585
- """
586
- Parameters
587
- ----------
588
- label : str
589
- name of source
590
- meta_data : Optional[Dict]
591
- used to store more information about the element. Is not used internally, but saved in the results
592
- source : Flow
593
- output-flow of source
594
- """
595
- super().__init__(label, outputs=[source], meta_data=meta_data)
596
- self.source = source
597
-
598
-
599
- class Sink(Component):
600
- def __init__(self, label: str, sink: Flow, meta_data: Optional[Dict] = None):
601
- """
602
- constructor of sink
603
-
604
- Parameters
605
- ----------
606
- label : str
607
- name of sink.
608
- meta_data : Optional[Dict]
609
- used to store more information about the element. Is not used internally, but saved in the results
610
- sink : Flow
611
- input-flow of sink
612
- """
613
- super().__init__(label, inputs=[sink], meta_data=meta_data)
614
- self.sink = sink