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.
- docs/examples/00-Minimal Example.md +5 -0
- docs/examples/01-Basic Example.md +5 -0
- docs/examples/02-Complex Example.md +10 -0
- docs/examples/03-Calculation Modes.md +5 -0
- docs/examples/index.md +5 -0
- docs/faq/contribute.md +49 -0
- docs/faq/index.md +3 -0
- docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
- docs/images/architecture_flixOpt.png +0 -0
- docs/images/flixopt-icon.svg +1 -0
- docs/javascripts/mathjax.js +18 -0
- docs/release-notes/_template.txt +32 -0
- docs/release-notes/index.md +7 -0
- docs/release-notes/v2.0.0.md +93 -0
- docs/release-notes/v2.0.1.md +12 -0
- docs/user-guide/Mathematical Notation/Bus.md +33 -0
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +132 -0
- docs/user-guide/Mathematical Notation/Flow.md +26 -0
- docs/user-guide/Mathematical Notation/LinearConverter.md +21 -0
- docs/user-guide/Mathematical Notation/Piecewise.md +49 -0
- docs/user-guide/Mathematical Notation/Storage.md +44 -0
- docs/user-guide/Mathematical Notation/index.md +22 -0
- docs/user-guide/Mathematical Notation/others.md +3 -0
- docs/user-guide/index.md +124 -0
- {flixOpt → flixopt}/__init__.py +5 -2
- {flixOpt → flixopt}/aggregation.py +113 -140
- flixopt/calculation.py +455 -0
- {flixOpt → flixopt}/commons.py +7 -4
- flixopt/components.py +630 -0
- {flixOpt → flixopt}/config.py +9 -8
- {flixOpt → flixopt}/config.yaml +3 -3
- flixopt/core.py +970 -0
- flixopt/effects.py +386 -0
- flixopt/elements.py +534 -0
- flixopt/features.py +1042 -0
- flixopt/flow_system.py +409 -0
- flixopt/interface.py +265 -0
- flixopt/io.py +308 -0
- flixopt/linear_converters.py +331 -0
- flixopt/plotting.py +1340 -0
- flixopt/results.py +898 -0
- flixopt/solvers.py +77 -0
- flixopt/structure.py +630 -0
- flixopt/utils.py +62 -0
- flixopt-2.0.1.dist-info/METADATA +145 -0
- flixopt-2.0.1.dist-info/RECORD +57 -0
- {flixopt-1.0.12.dist-info → flixopt-2.0.1.dist-info}/WHEEL +1 -1
- flixopt-2.0.1.dist-info/top_level.txt +6 -0
- pics/architecture_flixOpt-pre2.0.0.png +0 -0
- pics/architecture_flixOpt.png +0 -0
- pics/flixopt-icon.svg +1 -0
- pics/pics.pptx +0 -0
- scripts/gen_ref_pages.py +54 -0
- site/release-notes/_template.txt +32 -0
- flixOpt/calculation.py +0 -629
- flixOpt/components.py +0 -614
- flixOpt/core.py +0 -182
- flixOpt/effects.py +0 -410
- flixOpt/elements.py +0 -489
- flixOpt/features.py +0 -942
- flixOpt/flow_system.py +0 -351
- flixOpt/interface.py +0 -203
- flixOpt/linear_converters.py +0 -325
- flixOpt/math_modeling.py +0 -1145
- flixOpt/plotting.py +0 -712
- flixOpt/results.py +0 -563
- flixOpt/solvers.py +0 -21
- flixOpt/structure.py +0 -733
- flixOpt/utils.py +0 -134
- flixopt-1.0.12.dist-info/METADATA +0 -174
- flixopt-1.0.12.dist-info/RECORD +0 -29
- flixopt-1.0.12.dist-info/top_level.txt +0 -3
- {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
|