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/elements.py
DELETED
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module contains the basic elements of the flixOpt framework.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
from typing import Dict, List, Optional, Tuple, Union
|
|
7
|
-
|
|
8
|
-
import numpy as np
|
|
9
|
-
|
|
10
|
-
from .config import CONFIG
|
|
11
|
-
from .core import Numeric, Numeric_TS, Skalar
|
|
12
|
-
from .effects import EffectValues, effect_values_to_time_series
|
|
13
|
-
from .features import InvestmentModel, OnOffModel, PreventSimultaneousUsageModel
|
|
14
|
-
from .interface import InvestParameters, OnOffParameters
|
|
15
|
-
from .math_modeling import Variable, VariableTS
|
|
16
|
-
from .structure import (
|
|
17
|
-
Element,
|
|
18
|
-
ElementModel,
|
|
19
|
-
SystemModel,
|
|
20
|
-
_create_time_series,
|
|
21
|
-
copy_and_convert_datatypes,
|
|
22
|
-
create_equation,
|
|
23
|
-
create_variable,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
logger = logging.getLogger('flixOpt')
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class Component(Element):
|
|
30
|
-
"""
|
|
31
|
-
basic component class for all components
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
label: str,
|
|
37
|
-
inputs: Optional[List['Flow']] = None,
|
|
38
|
-
outputs: Optional[List['Flow']] = None,
|
|
39
|
-
on_off_parameters: Optional[OnOffParameters] = None,
|
|
40
|
-
prevent_simultaneous_flows: Optional[List['Flow']] = None,
|
|
41
|
-
meta_data: Optional[Dict] = None,
|
|
42
|
-
):
|
|
43
|
-
"""
|
|
44
|
-
Parameters
|
|
45
|
-
----------
|
|
46
|
-
label : str
|
|
47
|
-
name.
|
|
48
|
-
meta_data : Optional[Dict]
|
|
49
|
-
used to store more information about the element. Is not used internally, but saved in the results
|
|
50
|
-
inputs : input flows.
|
|
51
|
-
outputs : output flows.
|
|
52
|
-
on_off_parameters: Information about on and off state of Component.
|
|
53
|
-
Component is On/Off, if all connected Flows are On/Off.
|
|
54
|
-
Induces On-Variable in all FLows!
|
|
55
|
-
See class OnOffParameters.
|
|
56
|
-
prevent_simultaneous_flows: Define a Group of Flows. Only one them can be on at a time.
|
|
57
|
-
Induces On-Variable in all FLows!
|
|
58
|
-
"""
|
|
59
|
-
super().__init__(label, meta_data=meta_data)
|
|
60
|
-
self.inputs: List['Flow'] = inputs or []
|
|
61
|
-
self.outputs: List['Flow'] = outputs or []
|
|
62
|
-
self.on_off_parameters = on_off_parameters
|
|
63
|
-
self.prevent_simultaneous_flows: List['Flow'] = prevent_simultaneous_flows or []
|
|
64
|
-
|
|
65
|
-
self.flows: Dict[str, Flow] = {flow.label: flow for flow in self.inputs + self.outputs}
|
|
66
|
-
|
|
67
|
-
def create_model(self) -> 'ComponentModel':
|
|
68
|
-
self.model = ComponentModel(self)
|
|
69
|
-
return self.model
|
|
70
|
-
|
|
71
|
-
def transform_data(self) -> None:
|
|
72
|
-
if self.on_off_parameters is not None:
|
|
73
|
-
self.on_off_parameters.transform_data(self)
|
|
74
|
-
|
|
75
|
-
def register_component_in_flows(self) -> None:
|
|
76
|
-
for flow in self.inputs + self.outputs:
|
|
77
|
-
flow.comp = self
|
|
78
|
-
|
|
79
|
-
def register_flows_in_bus(self) -> None:
|
|
80
|
-
for flow in self.inputs:
|
|
81
|
-
flow.bus.add_output(flow)
|
|
82
|
-
for flow in self.outputs:
|
|
83
|
-
flow.bus.add_input(flow)
|
|
84
|
-
|
|
85
|
-
def infos(self, use_numpy=True, use_element_label=False) -> Dict:
|
|
86
|
-
infos = super().infos(use_numpy, use_element_label)
|
|
87
|
-
infos['inputs'] = [flow.infos(use_numpy, use_element_label) for flow in self.inputs]
|
|
88
|
-
infos['outputs'] = [flow.infos(use_numpy, use_element_label) for flow in self.outputs]
|
|
89
|
-
return infos
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class Bus(Element):
|
|
93
|
-
"""
|
|
94
|
-
realizing balance of all linked flows
|
|
95
|
-
(penalty flow is excess can be activated)
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
def __init__(
|
|
99
|
-
self, label: str, excess_penalty_per_flow_hour: Optional[Numeric_TS] = 1e5, meta_data: Optional[Dict] = None
|
|
100
|
-
):
|
|
101
|
-
"""
|
|
102
|
-
Parameters
|
|
103
|
-
----------
|
|
104
|
-
label : str
|
|
105
|
-
name.
|
|
106
|
-
meta_data : Optional[Dict]
|
|
107
|
-
used to store more information about the element. Is not used internally, but saved in the results
|
|
108
|
-
excess_penalty_per_flow_hour : none or scalar, array or TimeSeriesData
|
|
109
|
-
excess costs / penalty costs (bus balance compensation)
|
|
110
|
-
(none/ 0 -> no penalty). The default is 1e5.
|
|
111
|
-
(Take care: if you use a timeseries (no scalar), timeseries is aggregated if calculation_type = aggregated!)
|
|
112
|
-
"""
|
|
113
|
-
super().__init__(label, meta_data=meta_data)
|
|
114
|
-
self.excess_penalty_per_flow_hour = excess_penalty_per_flow_hour
|
|
115
|
-
self.inputs: List[Flow] = []
|
|
116
|
-
self.outputs: List[Flow] = []
|
|
117
|
-
|
|
118
|
-
def create_model(self) -> 'BusModel':
|
|
119
|
-
self.model = BusModel(self)
|
|
120
|
-
return self.model
|
|
121
|
-
|
|
122
|
-
def transform_data(self):
|
|
123
|
-
self.excess_penalty_per_flow_hour = _create_time_series(
|
|
124
|
-
'excess_penalty_per_flow_hour', self.excess_penalty_per_flow_hour, self
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
def add_input(self, flow) -> None:
|
|
128
|
-
flow: Flow
|
|
129
|
-
self.inputs.append(flow)
|
|
130
|
-
|
|
131
|
-
def add_output(self, flow) -> None:
|
|
132
|
-
flow: Flow
|
|
133
|
-
self.outputs.append(flow)
|
|
134
|
-
|
|
135
|
-
def _plausibility_checks(self) -> None:
|
|
136
|
-
if self.excess_penalty_per_flow_hour == 0:
|
|
137
|
-
logger.warning(f'In Bus {self.label}, the excess_penalty_per_flow_hour is 0. Use "None" or a value > 0.')
|
|
138
|
-
|
|
139
|
-
@property
|
|
140
|
-
def with_excess(self) -> bool:
|
|
141
|
-
return False if self.excess_penalty_per_flow_hour is None else True
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class Connection:
|
|
145
|
-
# input/output-dock (TODO:
|
|
146
|
-
# -> wäre cool, damit Komponenten auch auch ohne Knoten verbindbar
|
|
147
|
-
# input wären wie Flow,aber statt bus : connectsTo -> hier andere Connection oder aber Bus (dort keine Connection, weil nicht notwendig)
|
|
148
|
-
|
|
149
|
-
def __init__(self):
|
|
150
|
-
raise NotImplementedError()
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class Flow(Element):
|
|
154
|
-
"""
|
|
155
|
-
flows are inputs and outputs of components
|
|
156
|
-
"""
|
|
157
|
-
|
|
158
|
-
def __init__(
|
|
159
|
-
self,
|
|
160
|
-
label: str,
|
|
161
|
-
bus: Bus,
|
|
162
|
-
size: Union[Skalar, InvestParameters] = None,
|
|
163
|
-
fixed_relative_profile: Optional[Numeric_TS] = None,
|
|
164
|
-
relative_minimum: Numeric_TS = 0,
|
|
165
|
-
relative_maximum: Numeric_TS = 1,
|
|
166
|
-
effects_per_flow_hour: EffectValues = None,
|
|
167
|
-
on_off_parameters: Optional[OnOffParameters] = None,
|
|
168
|
-
flow_hours_total_max: Optional[Skalar] = None,
|
|
169
|
-
flow_hours_total_min: Optional[Skalar] = None,
|
|
170
|
-
load_factor_min: Optional[Skalar] = None,
|
|
171
|
-
load_factor_max: Optional[Skalar] = None,
|
|
172
|
-
previous_flow_rate: Optional[Numeric] = None,
|
|
173
|
-
meta_data: Optional[Dict] = None,
|
|
174
|
-
):
|
|
175
|
-
r"""
|
|
176
|
-
Parameters
|
|
177
|
-
----------
|
|
178
|
-
label : str
|
|
179
|
-
name of flow
|
|
180
|
-
meta_data : Optional[Dict]
|
|
181
|
-
used to store more information about the element. Is not used internally, but saved in the results
|
|
182
|
-
bus : Bus, optional
|
|
183
|
-
bus to which flow is linked
|
|
184
|
-
size : scalar, InvestmentParameters, optional
|
|
185
|
-
size of the flow. If InvestmentParameters is used, size is optimized.
|
|
186
|
-
If size is None, a default value is used.
|
|
187
|
-
relative_minimum : scalar, array, TimeSeriesData, optional
|
|
188
|
-
min value is relative_minimum multiplied by size
|
|
189
|
-
relative_maximum : scalar, array, TimeSeriesData, optional
|
|
190
|
-
max value is relative_maximum multiplied by size. If size = max then relative_maximum=1
|
|
191
|
-
load_factor_min : scalar, optional
|
|
192
|
-
minimal load factor general: avg Flow per nominalVal/investSize
|
|
193
|
-
(e.g. boiler, kW/kWh=h; solarthermal: kW/m²;
|
|
194
|
-
def: :math:`load\_factor:= sumFlowHours/ (nominal\_val \cdot \Delta t_{tot})`
|
|
195
|
-
load_factor_max : scalar, optional
|
|
196
|
-
maximal load factor (see minimal load factor)
|
|
197
|
-
effects_per_flow_hour : scalar, array, TimeSeriesData, optional
|
|
198
|
-
operational costs, costs per flow-"work"
|
|
199
|
-
on_off_parameters : OnOffParameters, optional
|
|
200
|
-
If present, flow can be "off", i.e. be zero (only relevant if relative_minimum > 0)
|
|
201
|
-
Therefore a binary var "on" is used. Further, several other restrictions and effects can be modeled
|
|
202
|
-
through this On/Off State (See OnOffParameters)
|
|
203
|
-
flow_hours_total_max : TYPE, optional
|
|
204
|
-
maximum flow-hours ("flow-work")
|
|
205
|
-
(if size is not const, maybe load_factor_max fits better for you!)
|
|
206
|
-
flow_hours_total_min : TYPE, optional
|
|
207
|
-
minimum flow-hours ("flow-work")
|
|
208
|
-
(if size is not const, maybe load_factor_min fits better for you!)
|
|
209
|
-
fixed_relative_profile : scalar, array, TimeSeriesData, optional
|
|
210
|
-
fixed relative values for flow (if given).
|
|
211
|
-
val(t) := fixed_relative_profile(t) * size(t)
|
|
212
|
-
With this value, the flow_rate is no opt-variable anymore;
|
|
213
|
-
(relative_minimum u. relative_maximum are making sense anymore)
|
|
214
|
-
used for fixed load profiles, i.g. heat demand, wind-power, solarthermal
|
|
215
|
-
If the load-profile is just an upper limit, use relative_maximum instead.
|
|
216
|
-
previous_flow_rate : scalar, array, optional
|
|
217
|
-
previous flow rate of the component.
|
|
218
|
-
"""
|
|
219
|
-
super().__init__(label, meta_data=meta_data)
|
|
220
|
-
self.size = size or CONFIG.modeling.BIG # Default size
|
|
221
|
-
self.relative_minimum = relative_minimum
|
|
222
|
-
self.relative_maximum = relative_maximum
|
|
223
|
-
self.fixed_relative_profile = fixed_relative_profile
|
|
224
|
-
|
|
225
|
-
self.load_factor_min = load_factor_min
|
|
226
|
-
self.load_factor_max = load_factor_max
|
|
227
|
-
# self.positive_gradient = TimeSeries('positive_gradient', positive_gradient, self)
|
|
228
|
-
self.effects_per_flow_hour = effects_per_flow_hour if effects_per_flow_hour is not None else {}
|
|
229
|
-
self.flow_hours_total_max = flow_hours_total_max
|
|
230
|
-
self.flow_hours_total_min = flow_hours_total_min
|
|
231
|
-
self.on_off_parameters = on_off_parameters
|
|
232
|
-
|
|
233
|
-
self.previous_flow_rate = previous_flow_rate
|
|
234
|
-
|
|
235
|
-
self.bus = bus
|
|
236
|
-
self.comp: Optional[Component] = None
|
|
237
|
-
|
|
238
|
-
self._plausibility_checks()
|
|
239
|
-
|
|
240
|
-
def create_model(self) -> 'FlowModel':
|
|
241
|
-
self.model = FlowModel(self)
|
|
242
|
-
return self.model
|
|
243
|
-
|
|
244
|
-
def transform_data(self):
|
|
245
|
-
self.relative_minimum = _create_time_series('relative_minimum', self.relative_minimum, self)
|
|
246
|
-
self.relative_maximum = _create_time_series('relative_maximum', self.relative_maximum, self)
|
|
247
|
-
self.fixed_relative_profile = _create_time_series('fixed_relative_profile', self.fixed_relative_profile, self)
|
|
248
|
-
self.effects_per_flow_hour = effect_values_to_time_series('per_flow_hour', self.effects_per_flow_hour, self)
|
|
249
|
-
if self.on_off_parameters is not None:
|
|
250
|
-
self.on_off_parameters.transform_data(self)
|
|
251
|
-
if isinstance(self.size, InvestParameters):
|
|
252
|
-
self.size.transform_data()
|
|
253
|
-
|
|
254
|
-
def infos(self, use_numpy=True, use_element_label=False) -> Dict:
|
|
255
|
-
infos = super().infos(use_numpy, use_element_label)
|
|
256
|
-
infos['is_input_in_component'] = self.is_input_in_comp
|
|
257
|
-
return infos
|
|
258
|
-
|
|
259
|
-
def _plausibility_checks(self) -> None:
|
|
260
|
-
# TODO: Incorporate into Variable? (Lower_bound can not be greater than upper bound
|
|
261
|
-
if np.any(self.relative_minimum > self.relative_maximum):
|
|
262
|
-
raise Exception(self.label_full + ': Take care, that relative_minimum <= relative_maximum!')
|
|
263
|
-
|
|
264
|
-
if (
|
|
265
|
-
self.size == CONFIG.modeling.BIG and self.fixed_relative_profile is not None
|
|
266
|
-
): # Default Size --> Most likely by accident
|
|
267
|
-
logger.warning(
|
|
268
|
-
f'Flow "{self.label}" has no size assigned, but a "fixed_relative_profile". '
|
|
269
|
-
f'The default size is {CONFIG.modeling.BIG}. As "flow_rate = size * fixed_relative_profile", '
|
|
270
|
-
f'the resulting flow_rate will be very high. To fix this, assign a size to the Flow {self}.'
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
@property
|
|
274
|
-
def label_full(self) -> str:
|
|
275
|
-
# Wenn im Erstellungsprozess comp noch nicht bekannt:
|
|
276
|
-
comp_label = 'unknownComp' if self.comp is None else self.comp.label
|
|
277
|
-
return f'{comp_label}__{self.label}' # z.B. für results_struct (deswegen auch _ statt . dazwischen)
|
|
278
|
-
|
|
279
|
-
@property # Richtung
|
|
280
|
-
def is_input_in_comp(self) -> bool:
|
|
281
|
-
return True if self in self.comp.inputs else False
|
|
282
|
-
|
|
283
|
-
@property
|
|
284
|
-
def size_is_fixed(self) -> bool:
|
|
285
|
-
# Wenn kein InvestParameters existiert --> True; Wenn Investparameter, den Wert davon nehmen
|
|
286
|
-
return False if (isinstance(self.size, InvestParameters) and self.size.fixed_size is None) else True
|
|
287
|
-
|
|
288
|
-
@property
|
|
289
|
-
def invest_is_optional(self) -> bool:
|
|
290
|
-
# Wenn kein InvestParameters existiert: # Investment ist nicht optional -> Keine Variable --> False
|
|
291
|
-
return False if (isinstance(self.size, InvestParameters) and not self.size.optional) else True
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
class FlowModel(ElementModel):
|
|
295
|
-
def __init__(self, element: Flow):
|
|
296
|
-
super().__init__(element)
|
|
297
|
-
self.element: Flow = element
|
|
298
|
-
self.flow_rate: Optional[VariableTS] = None
|
|
299
|
-
self.sum_flow_hours: Optional[Variable] = None
|
|
300
|
-
|
|
301
|
-
self._on: Optional[OnOffModel] = None
|
|
302
|
-
self._investment: Optional[InvestmentModel] = None
|
|
303
|
-
|
|
304
|
-
def do_modeling(self, system_model: SystemModel):
|
|
305
|
-
# eq relative_minimum(t) * size <= flow_rate(t) <= relative_maximum(t) * size
|
|
306
|
-
self.flow_rate = create_variable(
|
|
307
|
-
'flow_rate',
|
|
308
|
-
self,
|
|
309
|
-
system_model.nr_of_time_steps,
|
|
310
|
-
lower_bound=self.absolute_flow_rate_bounds[0] if self.element.on_off_parameters is None else 0,
|
|
311
|
-
upper_bound=self.absolute_flow_rate_bounds[1] if self.element.on_off_parameters is None else None,
|
|
312
|
-
previous_values=self.element.previous_flow_rate,
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
# OnOff
|
|
316
|
-
if self.element.on_off_parameters is not None:
|
|
317
|
-
self._on = OnOffModel(
|
|
318
|
-
self.element, self.element.on_off_parameters, [self.flow_rate], [self.absolute_flow_rate_bounds]
|
|
319
|
-
)
|
|
320
|
-
self._on.do_modeling(system_model)
|
|
321
|
-
self.sub_models.append(self._on)
|
|
322
|
-
|
|
323
|
-
# Investment
|
|
324
|
-
if isinstance(self.element.size, InvestParameters):
|
|
325
|
-
self._investment = InvestmentModel(
|
|
326
|
-
self.element,
|
|
327
|
-
self.element.size,
|
|
328
|
-
self.flow_rate,
|
|
329
|
-
self.relative_flow_rate_bounds,
|
|
330
|
-
fixed_relative_profile=(None
|
|
331
|
-
if self.element.fixed_relative_profile is None
|
|
332
|
-
else self.element.fixed_relative_profile.active_data),
|
|
333
|
-
on_variable=self._on.on if self._on is not None else None,
|
|
334
|
-
)
|
|
335
|
-
self._investment.do_modeling(system_model)
|
|
336
|
-
self.sub_models.append(self._investment)
|
|
337
|
-
|
|
338
|
-
# sumFLowHours
|
|
339
|
-
self.sum_flow_hours = create_variable(
|
|
340
|
-
'sumFlowHours',
|
|
341
|
-
self,
|
|
342
|
-
1,
|
|
343
|
-
lower_bound=self.element.flow_hours_total_min,
|
|
344
|
-
upper_bound=self.element.flow_hours_total_max,
|
|
345
|
-
)
|
|
346
|
-
eq_sum_flow_hours = create_equation('sumFlowHours', self, 'eq')
|
|
347
|
-
eq_sum_flow_hours.add_summand(self.flow_rate, system_model.dt_in_hours, as_sum=True)
|
|
348
|
-
eq_sum_flow_hours.add_summand(self.sum_flow_hours, -1)
|
|
349
|
-
|
|
350
|
-
# Load factor
|
|
351
|
-
self._create_bounds_for_load_factor(system_model)
|
|
352
|
-
|
|
353
|
-
# Shares
|
|
354
|
-
self._create_shares(system_model)
|
|
355
|
-
|
|
356
|
-
def _create_shares(self, system_model: SystemModel):
|
|
357
|
-
# Arbeitskosten:
|
|
358
|
-
if self.element.effects_per_flow_hour != {}:
|
|
359
|
-
system_model.effect_collection_model.add_share_to_operation(
|
|
360
|
-
name='effects_per_flow_hour',
|
|
361
|
-
element=self.element,
|
|
362
|
-
variable=self.flow_rate,
|
|
363
|
-
effect_values=self.element.effects_per_flow_hour,
|
|
364
|
-
factor=system_model.dt_in_hours,
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
def _create_bounds_for_load_factor(self, system_model: SystemModel):
|
|
368
|
-
# TODO: Add Variable load_factor for better evaluation?
|
|
369
|
-
|
|
370
|
-
# eq: var_sumFlowHours <= size * dt_tot * load_factor_max
|
|
371
|
-
if self.element.load_factor_max is not None:
|
|
372
|
-
flow_hours_per_size_max = system_model.dt_in_hours_total * self.element.load_factor_max
|
|
373
|
-
eq_load_factor_max = create_equation('load_factor_max', self, 'ineq')
|
|
374
|
-
eq_load_factor_max.add_summand(self.sum_flow_hours, 1)
|
|
375
|
-
# if investment:
|
|
376
|
-
if self._investment is not None:
|
|
377
|
-
eq_load_factor_max.add_summand(self._investment.size, -1 * flow_hours_per_size_max)
|
|
378
|
-
else:
|
|
379
|
-
eq_load_factor_max.add_constant(self.element.size * flow_hours_per_size_max)
|
|
380
|
-
|
|
381
|
-
# eq: size * sum(dt)* load_factor_min <= var_sumFlowHours
|
|
382
|
-
if self.element.load_factor_min is not None:
|
|
383
|
-
flow_hours_per_size_min = system_model.dt_in_hours_total * self.element.load_factor_min
|
|
384
|
-
eq_load_factor_min = create_equation('load_factor_min', self, 'ineq')
|
|
385
|
-
eq_load_factor_min.add_summand(self.sum_flow_hours, -1)
|
|
386
|
-
if self._investment is not None:
|
|
387
|
-
eq_load_factor_min.add_summand(self._investment.size, flow_hours_per_size_min)
|
|
388
|
-
else:
|
|
389
|
-
eq_load_factor_min.add_constant(-1 * self.element.size * flow_hours_per_size_min)
|
|
390
|
-
|
|
391
|
-
@property
|
|
392
|
-
def with_investment(self) -> bool:
|
|
393
|
-
"""Checks if the element's size is investment-driven."""
|
|
394
|
-
return isinstance(self.element.size, InvestParameters)
|
|
395
|
-
|
|
396
|
-
@property
|
|
397
|
-
def absolute_flow_rate_bounds(self) -> Tuple[Numeric, Numeric]:
|
|
398
|
-
"""Returns absolute flow rate bounds. Important for OnOffModel"""
|
|
399
|
-
rel_min, rel_max = self.relative_flow_rate_bounds
|
|
400
|
-
size = self.element.size
|
|
401
|
-
if not self.with_investment:
|
|
402
|
-
return rel_min * size, rel_max * size
|
|
403
|
-
if size.fixed_size is not None:
|
|
404
|
-
return rel_min * size.fixed_size, rel_max * size.fixed_size
|
|
405
|
-
return rel_min * size.minimum_size, rel_max * size.maximum_size
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
@property
|
|
409
|
-
def relative_flow_rate_bounds(self) -> Tuple[Numeric, Numeric]:
|
|
410
|
-
"""Returns relative flow rate bounds."""
|
|
411
|
-
fixed_profile = self.element.fixed_relative_profile
|
|
412
|
-
if fixed_profile is None:
|
|
413
|
-
return self.element.relative_minimum.active_data, self.element.relative_maximum.active_data
|
|
414
|
-
return fixed_profile.active_data, fixed_profile.active_data
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
class BusModel(ElementModel):
|
|
418
|
-
def __init__(self, element: Bus):
|
|
419
|
-
super().__init__(element)
|
|
420
|
-
self.element: Bus
|
|
421
|
-
self.excess_input: Optional[VariableTS] = None
|
|
422
|
-
self.excess_output: Optional[VariableTS] = None
|
|
423
|
-
|
|
424
|
-
def do_modeling(self, system_model: SystemModel) -> None:
|
|
425
|
-
self.element: Bus
|
|
426
|
-
# inputs = outputs
|
|
427
|
-
eq_bus_balance = create_equation('busBalance', self)
|
|
428
|
-
for flow in self.element.inputs:
|
|
429
|
-
eq_bus_balance.add_summand(flow.model.flow_rate, 1)
|
|
430
|
-
for flow in self.element.outputs:
|
|
431
|
-
eq_bus_balance.add_summand(flow.model.flow_rate, -1)
|
|
432
|
-
|
|
433
|
-
# Fehlerplus/-minus:
|
|
434
|
-
if self.element.with_excess:
|
|
435
|
-
excess_penalty = np.multiply(
|
|
436
|
-
system_model.dt_in_hours, self.element.excess_penalty_per_flow_hour.active_data
|
|
437
|
-
)
|
|
438
|
-
self.excess_input = create_variable('excess_input', self, system_model.nr_of_time_steps, lower_bound=0)
|
|
439
|
-
self.excess_output = create_variable('excess_output', self, system_model.nr_of_time_steps, lower_bound=0)
|
|
440
|
-
|
|
441
|
-
eq_bus_balance.add_summand(self.excess_output, -1)
|
|
442
|
-
eq_bus_balance.add_summand(self.excess_input, 1)
|
|
443
|
-
|
|
444
|
-
fx_collection = system_model.effect_collection_model
|
|
445
|
-
|
|
446
|
-
fx_collection.add_share_to_penalty(
|
|
447
|
-
f'{self.element.label_full}__excess_input', self.excess_input, excess_penalty
|
|
448
|
-
)
|
|
449
|
-
fx_collection.add_share_to_penalty(
|
|
450
|
-
f'{self.element.label_full}__excess_output', self.excess_output, excess_penalty
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
class ComponentModel(ElementModel):
|
|
455
|
-
def __init__(self, element: Component):
|
|
456
|
-
super().__init__(element)
|
|
457
|
-
self.element: Component = element
|
|
458
|
-
self._on: Optional[OnOffModel] = None
|
|
459
|
-
|
|
460
|
-
def do_modeling(self, system_model: SystemModel):
|
|
461
|
-
"""Initiates all FlowModels"""
|
|
462
|
-
all_flows = self.element.inputs + self.element.outputs
|
|
463
|
-
if self.element.on_off_parameters:
|
|
464
|
-
for flow in all_flows:
|
|
465
|
-
if flow.on_off_parameters is None:
|
|
466
|
-
flow.on_off_parameters = OnOffParameters()
|
|
467
|
-
|
|
468
|
-
if self.element.prevent_simultaneous_flows:
|
|
469
|
-
for flow in self.element.prevent_simultaneous_flows:
|
|
470
|
-
if flow.on_off_parameters is None:
|
|
471
|
-
flow.on_off_parameters = OnOffParameters()
|
|
472
|
-
|
|
473
|
-
self.sub_models.extend([flow.create_model() for flow in all_flows])
|
|
474
|
-
for sub_model in self.sub_models:
|
|
475
|
-
sub_model.do_modeling(system_model)
|
|
476
|
-
|
|
477
|
-
if self.element.on_off_parameters:
|
|
478
|
-
flow_rates: List[VariableTS] = [flow.model.flow_rate for flow in all_flows]
|
|
479
|
-
bounds: List[Tuple[Numeric, Numeric]] = [flow.model.absolute_flow_rate_bounds for flow in all_flows]
|
|
480
|
-
self._on = OnOffModel(self.element, self.element.on_off_parameters, flow_rates, bounds)
|
|
481
|
-
self.sub_models.append(self._on)
|
|
482
|
-
self._on.do_modeling(system_model)
|
|
483
|
-
|
|
484
|
-
if self.element.prevent_simultaneous_flows:
|
|
485
|
-
# Simultanious Useage --> Only One FLow is On at a time, but needs a Binary for every flow
|
|
486
|
-
on_variables = [flow.model._on.on for flow in self.element.prevent_simultaneous_flows]
|
|
487
|
-
simultaneous_use = PreventSimultaneousUsageModel(self.element, on_variables)
|
|
488
|
-
self.sub_models.append(simultaneous_use)
|
|
489
|
-
simultaneous_use.do_modeling(system_model)
|