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/flow_system.py
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module contains the FlowSystem class, which is used to collect instances of many other classes by the end User.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import logging
|
|
7
|
-
import pathlib
|
|
8
|
-
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Tuple, Union
|
|
9
|
-
|
|
10
|
-
import numpy as np
|
|
11
|
-
|
|
12
|
-
from . import utils
|
|
13
|
-
from .core import TimeSeries
|
|
14
|
-
from .effects import Effect, EffectCollection
|
|
15
|
-
from .elements import Bus, Component, Flow
|
|
16
|
-
from .structure import Element, SystemModel, get_compact_representation, get_str_representation
|
|
17
|
-
|
|
18
|
-
if TYPE_CHECKING:
|
|
19
|
-
import pyvis
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger('flixOpt')
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class FlowSystem:
|
|
25
|
-
"""
|
|
26
|
-
A FlowSystem organizes the high level Elements (Components & Effects).
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
time_series: np.ndarray[np.datetime64],
|
|
32
|
-
last_time_step_hours: Optional[Union[int, float]] = None,
|
|
33
|
-
previous_dt_in_hours: Optional[Union[int, float, np.ndarray]] = None,
|
|
34
|
-
):
|
|
35
|
-
"""
|
|
36
|
-
Parameters
|
|
37
|
-
----------
|
|
38
|
-
time_series : np.ndarray of datetime64
|
|
39
|
-
timeseries of the data. Must be in datetime64 format. Don't use precisions below 'us'. !np.datetime64[ns]!
|
|
40
|
-
last_time_step_hours :
|
|
41
|
-
The duration of last time step.
|
|
42
|
-
Storages needs this time-duration for calculation of charge state
|
|
43
|
-
after last time step.
|
|
44
|
-
If None, then last time increment of time_series is used.
|
|
45
|
-
previous_dt_in_hours : Union[int, float, np.ndarray]
|
|
46
|
-
The duration of previous time steps.
|
|
47
|
-
If None, the first time increment of time_series is used.
|
|
48
|
-
This is needed to calculate previous durations (for example consecutive_on_hours).
|
|
49
|
-
If you use an array, take care that its long enough to cover all previous values!
|
|
50
|
-
"""
|
|
51
|
-
self.time_series = time_series if isinstance(time_series, np.ndarray) else np.array(time_series)
|
|
52
|
-
if self.time_series.dtype == np.dtype('datetime64[ns]'):
|
|
53
|
-
self.time_series = self.time_series.astype('datetime64[us]')
|
|
54
|
-
|
|
55
|
-
self.last_time_step_hours = (
|
|
56
|
-
self.time_series[-1] - self.time_series[-2] if last_time_step_hours is None else last_time_step_hours
|
|
57
|
-
)
|
|
58
|
-
self.time_series_with_end = np.append(self.time_series, self.time_series[-1] + self.last_time_step_hours)
|
|
59
|
-
self.previous_dt_in_hours: Union[int, float, np.ndarray] = (
|
|
60
|
-
((self.time_series[1] - self.time_series[0]) / np.timedelta64(1, 'h'))
|
|
61
|
-
if previous_dt_in_hours is None
|
|
62
|
-
else previous_dt_in_hours
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
utils.check_time_series('time series of FlowSystem', self.time_series_with_end)
|
|
66
|
-
|
|
67
|
-
# defaults:
|
|
68
|
-
self.components: Dict[str, Component] = {}
|
|
69
|
-
self.effect_collection: EffectCollection = EffectCollection('Effects') # Organizes Effects, Penalty & Objective
|
|
70
|
-
self.model: Optional[SystemModel] = None
|
|
71
|
-
|
|
72
|
-
def add_effects(self, *args: Effect) -> None:
|
|
73
|
-
for new_effect in list(args):
|
|
74
|
-
logger.info(f'Registered new Effect: {new_effect.label}')
|
|
75
|
-
self.effect_collection.add_effect(new_effect)
|
|
76
|
-
|
|
77
|
-
def add_components(self, *args: Component) -> None:
|
|
78
|
-
# Komponenten registrieren:
|
|
79
|
-
new_components = list(args)
|
|
80
|
-
for new_component in new_components:
|
|
81
|
-
logger.info(f'Registered new Component: {new_component.label}')
|
|
82
|
-
self._check_if_element_is_unique(new_component) # check if already exists:
|
|
83
|
-
new_component.register_component_in_flows() # Komponente in Flow registrieren
|
|
84
|
-
new_component.register_flows_in_bus() # Flows in Bus registrieren:
|
|
85
|
-
self.components[new_component.label] = new_component # Add to existing components
|
|
86
|
-
|
|
87
|
-
def add_elements(self, *args: Element) -> None:
|
|
88
|
-
"""
|
|
89
|
-
add all modeling elements, like storages, boilers, heatpumps, buses, ...
|
|
90
|
-
|
|
91
|
-
Parameters
|
|
92
|
-
----------
|
|
93
|
-
*args : childs of Element like Boiler, HeatPump, Bus,...
|
|
94
|
-
modeling Elements
|
|
95
|
-
|
|
96
|
-
"""
|
|
97
|
-
for new_element in list(args):
|
|
98
|
-
if isinstance(new_element, Component):
|
|
99
|
-
self.add_components(new_element)
|
|
100
|
-
elif isinstance(new_element, Effect):
|
|
101
|
-
self.add_effects(new_element)
|
|
102
|
-
else:
|
|
103
|
-
raise Exception('argument is not instance of a modeling Element (Element)')
|
|
104
|
-
|
|
105
|
-
def transform_data(self):
|
|
106
|
-
for element in self.all_elements.values():
|
|
107
|
-
element.transform_data()
|
|
108
|
-
|
|
109
|
-
def network_infos(self) -> Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, str]]]:
|
|
110
|
-
nodes = {
|
|
111
|
-
node.label_full: {
|
|
112
|
-
'label': node.label,
|
|
113
|
-
'class': 'Bus' if isinstance(node, Bus) else 'Component',
|
|
114
|
-
'infos': node.__str__(),
|
|
115
|
-
}
|
|
116
|
-
for node in list(self.components.values()) + list(self.buses.values())
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
edges = {
|
|
120
|
-
flow.label_full: {
|
|
121
|
-
'label': flow.label,
|
|
122
|
-
'start': flow.bus.label_full if flow.is_input_in_comp else flow.comp.label_full,
|
|
123
|
-
'end': flow.comp.label_full if flow.is_input_in_comp else flow.bus.label_full,
|
|
124
|
-
'infos': flow.__str__(),
|
|
125
|
-
}
|
|
126
|
-
for flow in self.flows.values()
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return nodes, edges
|
|
130
|
-
|
|
131
|
-
def infos(self, use_numpy=True, use_element_label=False) -> Dict:
|
|
132
|
-
infos = {
|
|
133
|
-
'Components': {
|
|
134
|
-
comp.label: comp.infos(use_numpy, use_element_label)
|
|
135
|
-
for comp in sorted(self.components.values(), key=lambda component: component.label.upper())
|
|
136
|
-
},
|
|
137
|
-
'Buses': {
|
|
138
|
-
bus.label: bus.infos(use_numpy, use_element_label)
|
|
139
|
-
for bus in sorted(self.buses.values(), key=lambda bus: bus.label.upper())
|
|
140
|
-
},
|
|
141
|
-
'Effects': {
|
|
142
|
-
effect.label: effect.infos(use_numpy, use_element_label)
|
|
143
|
-
for effect in sorted(self.effect_collection.effects.values(), key=lambda effect: effect.label.upper())
|
|
144
|
-
},
|
|
145
|
-
}
|
|
146
|
-
return infos
|
|
147
|
-
|
|
148
|
-
def to_json(self, path: Union[str, pathlib.Path]):
|
|
149
|
-
"""
|
|
150
|
-
Saves the flow system to a json file.
|
|
151
|
-
This not meant to be reloaded and recreate the object, but rather used to document or compare the object.
|
|
152
|
-
|
|
153
|
-
Parameters:
|
|
154
|
-
-----------
|
|
155
|
-
path : Union[str, pathlib.Path]
|
|
156
|
-
The path to the json file.
|
|
157
|
-
"""
|
|
158
|
-
data = get_compact_representation(self.infos(use_numpy=True, use_element_label=True))
|
|
159
|
-
with open(path, 'w', encoding='utf-8') as f:
|
|
160
|
-
json.dump(data, f, indent=4, ensure_ascii=False)
|
|
161
|
-
|
|
162
|
-
def visualize_network(
|
|
163
|
-
self,
|
|
164
|
-
path: Union[bool, str, pathlib.Path] = 'flow_system.html',
|
|
165
|
-
controls: Union[
|
|
166
|
-
bool,
|
|
167
|
-
List[
|
|
168
|
-
Literal['nodes', 'edges', 'layout', 'interaction', 'manipulation', 'physics', 'selection', 'renderer']
|
|
169
|
-
],
|
|
170
|
-
] = True,
|
|
171
|
-
show: bool = True,
|
|
172
|
-
) -> Optional['pyvis.network.Network']:
|
|
173
|
-
"""
|
|
174
|
-
Visualizes the network structure of a FlowSystem using PyVis, saving it as an interactive HTML file.
|
|
175
|
-
|
|
176
|
-
Parameters:
|
|
177
|
-
- path (Union[bool, str, pathlib.Path], default='flow_system.html'):
|
|
178
|
-
Path to save the HTML visualization.
|
|
179
|
-
- `False`: Visualization is created but not saved.
|
|
180
|
-
- `str` or `Path`: Specifies file path (default: 'flow_system.html').
|
|
181
|
-
|
|
182
|
-
- controls (Union[bool, List[str]], default=True):
|
|
183
|
-
UI controls to add to the visualization.
|
|
184
|
-
- `True`: Enables all available controls.
|
|
185
|
-
- `List`: Specify controls, e.g., ['nodes', 'layout'].
|
|
186
|
-
- Options: 'nodes', 'edges', 'layout', 'interaction', 'manipulation', 'physics', 'selection', 'renderer'.
|
|
187
|
-
|
|
188
|
-
- show (bool, default=True):
|
|
189
|
-
Whether to open the visualization in the web browser.
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
- Optional[pyvis.network.Network]: The `Network` instance representing the visualization, or `None` if `pyvis` is not installed.
|
|
193
|
-
|
|
194
|
-
Usage:
|
|
195
|
-
- Visualize and open the network with default options:
|
|
196
|
-
>>> self.visualize_network()
|
|
197
|
-
|
|
198
|
-
- Save the visualization without opening:
|
|
199
|
-
>>> self.visualize_network(show=False)
|
|
200
|
-
|
|
201
|
-
- Visualize with custom controls and path:
|
|
202
|
-
>>> self.visualize_network(path='output/custom_network.html', controls=['nodes', 'layout'])
|
|
203
|
-
|
|
204
|
-
Notes:
|
|
205
|
-
- This function requires `pyvis`. If not installed, the function prints a warning and returns `None`.
|
|
206
|
-
- Nodes are styled based on type (e.g., circles for buses, boxes for components) and annotated with node information.
|
|
207
|
-
"""
|
|
208
|
-
from . import plotting
|
|
209
|
-
|
|
210
|
-
node_infos, edge_infos = self.network_infos()
|
|
211
|
-
return plotting.visualize_network(node_infos, edge_infos, path, controls, show)
|
|
212
|
-
|
|
213
|
-
def _check_if_element_is_unique(self, element: Element) -> None:
|
|
214
|
-
"""
|
|
215
|
-
checks if element or label of element already exists in list
|
|
216
|
-
|
|
217
|
-
Parameters
|
|
218
|
-
----------
|
|
219
|
-
element : Element
|
|
220
|
-
new element to check
|
|
221
|
-
"""
|
|
222
|
-
if element in self.all_elements:
|
|
223
|
-
raise Exception(f'Element {element.label} already added to FlowSystem!')
|
|
224
|
-
# check if name is already used:
|
|
225
|
-
if element.label_full in self.all_elements:
|
|
226
|
-
raise Exception(f'Label of Element {element.label} already used in another element!')
|
|
227
|
-
|
|
228
|
-
def get_time_data_from_indices(
|
|
229
|
-
self, time_indices: Optional[Union[List[int], range]] = None
|
|
230
|
-
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.float64]:
|
|
231
|
-
"""
|
|
232
|
-
Computes time series data based on the provided time indices.
|
|
233
|
-
|
|
234
|
-
Args:
|
|
235
|
-
time_indices: A list of indices or a range object indicating which time steps to extract.
|
|
236
|
-
If None, the entire time series is used.
|
|
237
|
-
|
|
238
|
-
Returns:
|
|
239
|
-
A tuple containing:
|
|
240
|
-
- Extracted time series
|
|
241
|
-
- Time series with the "end time" appended
|
|
242
|
-
- Differences between consecutive timestamps in hours
|
|
243
|
-
- Total time in hours
|
|
244
|
-
"""
|
|
245
|
-
# If time_indices is None, use the full time series range
|
|
246
|
-
if time_indices is None:
|
|
247
|
-
time_indices = range(len(self.time_series))
|
|
248
|
-
|
|
249
|
-
# Extract the time series for the provided indices
|
|
250
|
-
time_series = self.time_series[time_indices]
|
|
251
|
-
|
|
252
|
-
# Ensure the next timestamp for end time is within bounds
|
|
253
|
-
last_index = time_indices[-1]
|
|
254
|
-
if last_index + 1 < len(self.time_series_with_end):
|
|
255
|
-
end_time = self.time_series_with_end[last_index + 1]
|
|
256
|
-
else:
|
|
257
|
-
raise IndexError(f"Index {last_index + 1} out of bounds for 'self.time_series_with_end'.")
|
|
258
|
-
|
|
259
|
-
# Append end time to the time series
|
|
260
|
-
time_series_with_end = np.append(time_series, end_time)
|
|
261
|
-
|
|
262
|
-
# Calculate time differences (time deltas) in hours
|
|
263
|
-
time_deltas = time_series_with_end[1:] - time_series_with_end[:-1]
|
|
264
|
-
dt_in_hours = time_deltas / np.timedelta64(1, 'h')
|
|
265
|
-
|
|
266
|
-
# Calculate the total time in hours
|
|
267
|
-
dt_in_hours_total = np.sum(dt_in_hours)
|
|
268
|
-
|
|
269
|
-
return time_series, time_series_with_end, dt_in_hours, dt_in_hours_total
|
|
270
|
-
|
|
271
|
-
def __repr__(self):
|
|
272
|
-
return f'<{self.__class__.__name__} with {len(self.components)} components and {len(self.effect_collection.effects)} effects>'
|
|
273
|
-
|
|
274
|
-
def __str__(self):
|
|
275
|
-
return get_str_representation(self.infos(use_numpy=True, use_element_label=True))
|
|
276
|
-
|
|
277
|
-
@property
|
|
278
|
-
def flows(self) -> Dict[str, Flow]:
|
|
279
|
-
set_of_flows = {flow for comp in self.components.values() for flow in comp.inputs + comp.outputs}
|
|
280
|
-
return {flow.label_full: flow for flow in set_of_flows}
|
|
281
|
-
|
|
282
|
-
@property
|
|
283
|
-
def buses(self) -> Dict[str, Bus]:
|
|
284
|
-
return {flow.bus.label: flow.bus for flow in self.flows.values()}
|
|
285
|
-
|
|
286
|
-
@property
|
|
287
|
-
def all_elements(self) -> Dict[str, Element]:
|
|
288
|
-
return {**self.components, **self.effect_collection.effects, **self.flows, **self.buses}
|
|
289
|
-
|
|
290
|
-
@property
|
|
291
|
-
def all_time_series(self) -> List[TimeSeries]:
|
|
292
|
-
return [ts for element in self.all_elements.values() for ts in element.used_time_series]
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def create_datetime_array(
|
|
296
|
-
start: str, steps: Optional[int] = None, freq: str = '1h', end: Optional[str] = None
|
|
297
|
-
) -> np.ndarray[np.datetime64]:
|
|
298
|
-
"""
|
|
299
|
-
Create a NumPy array with datetime64 values.
|
|
300
|
-
|
|
301
|
-
Parameters
|
|
302
|
-
----------
|
|
303
|
-
start : str
|
|
304
|
-
Start date in 'YYYY-MM-DD' format or a full timestamp (e.g., 'YYYY-MM-DD HH:MM').
|
|
305
|
-
steps : int, optional
|
|
306
|
-
Number of steps in the datetime array. If `end` is provided, `steps` is ignored.
|
|
307
|
-
freq : str, optional
|
|
308
|
-
Frequency for the datetime64 array. Supports flexible intervals:
|
|
309
|
-
- 'Y', 'M', 'W', 'D', 'h', 'm', 's' (e.g., '1h', '15m', '2h').
|
|
310
|
-
Defaults to 'h' (hourly).
|
|
311
|
-
end : str, optional
|
|
312
|
-
End date in 'YYYY-MM-DD' format or a full timestamp (e.g., 'YYYY-MM-DD HH:MM').
|
|
313
|
-
If provided, the function generates an array from `start` to `end` using `freq`.
|
|
314
|
-
|
|
315
|
-
Returns
|
|
316
|
-
-------
|
|
317
|
-
np.ndarray
|
|
318
|
-
NumPy array of datetime64 values.
|
|
319
|
-
|
|
320
|
-
Examples
|
|
321
|
-
--------
|
|
322
|
-
Create an array with 15-minute intervals:
|
|
323
|
-
>>> create_datetime_array('2023-01-01', steps=5, freq='15m')
|
|
324
|
-
array(['2023-01-01T00:00', '2023-01-01T00:15', '2023-01-01T00:30', ...], dtype='datetime64[m]')
|
|
325
|
-
|
|
326
|
-
Create 2-hour intervals:
|
|
327
|
-
>>> create_datetime_array('2023-01-01T00', steps=4, freq='2h')
|
|
328
|
-
array(['2023-01-01T00', '2023-01-01T02', '2023-01-01T04', ...], dtype='datetime64[h]')
|
|
329
|
-
|
|
330
|
-
Generate minute intervals until a specified end time:
|
|
331
|
-
>>> create_datetime_array('2023-01-01T00:00', end='2023-01-01T01:00', freq='m')
|
|
332
|
-
array(['2023-01-01T00:00', '2023-01-01T00:01', ..., '2023-01-01T00:59'], dtype='datetime64[m]')
|
|
333
|
-
"""
|
|
334
|
-
# Parse the frequency and interval
|
|
335
|
-
unit = freq[-1] # Get the time unit (e.g., 'h', 'm', 's')
|
|
336
|
-
interval = int(freq[:-1]) if freq[:-1].isdigit() else 1 # Default to interval=1 if not specified
|
|
337
|
-
step_size = np.timedelta64(interval, unit) # Create the timedelta step size
|
|
338
|
-
|
|
339
|
-
# Convert the start time to a datetime64 object
|
|
340
|
-
start_dt = np.datetime64(start)
|
|
341
|
-
|
|
342
|
-
# Generate the array based on the parameters
|
|
343
|
-
if end: # If `end` is specified, create a range from start to end
|
|
344
|
-
end_dt = np.datetime64(end)
|
|
345
|
-
return np.arange(start_dt, end_dt, step_size)
|
|
346
|
-
|
|
347
|
-
elif steps: # If `steps` is specified, create a range with the given number of steps
|
|
348
|
-
return np.array([start_dt + i * step_size for i in range(steps)], dtype='datetime64')
|
|
349
|
-
|
|
350
|
-
else: # If neither `steps` nor `end` is provided, raise an error
|
|
351
|
-
raise ValueError('Either `steps` or `end` must be provided.')
|
flixOpt/interface.py
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module contains classes to collect Parameters for the Investment and OnOff decisions.
|
|
3
|
-
These are tightly connected to features.py
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import logging
|
|
7
|
-
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
|
|
8
|
-
|
|
9
|
-
from .config import CONFIG
|
|
10
|
-
from .core import Numeric, Numeric_TS, Skalar
|
|
11
|
-
from .structure import Element, Interface
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from .effects import Effect, EffectTimeSeries, EffectValues, EffectValuesInvest
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger('flixOpt')
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class InvestParameters(Interface):
|
|
20
|
-
"""
|
|
21
|
-
collects arguments for invest-stuff
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(
|
|
25
|
-
self,
|
|
26
|
-
fixed_size: Optional[Union[int, float]] = None,
|
|
27
|
-
minimum_size: Union[int, float] = 0, # TODO: Use EPSILON?
|
|
28
|
-
maximum_size: Optional[Union[int, float]] = None,
|
|
29
|
-
optional: bool = True, # Investition ist weglassbar
|
|
30
|
-
fix_effects: Union[Dict, int, float] = None,
|
|
31
|
-
specific_effects: Union[Dict, int, float] = None, # costs per Flow-Unit/Storage-Size/...
|
|
32
|
-
effects_in_segments: Optional[
|
|
33
|
-
Tuple[List[Tuple[Skalar, Skalar]], Dict['Effect', List[Tuple[Skalar, Skalar]]]]
|
|
34
|
-
] = None,
|
|
35
|
-
divest_effects: Union[Dict, int, float] = None,
|
|
36
|
-
):
|
|
37
|
-
"""
|
|
38
|
-
Parameters
|
|
39
|
-
----------
|
|
40
|
-
fix_effects : None or scalar, optional
|
|
41
|
-
Fixed investment costs if invested.
|
|
42
|
-
(Attention: Annualize costs to chosen period!)
|
|
43
|
-
divest_effects : None or scalar, optional
|
|
44
|
-
Fixed divestment costs (if not invested, e.g., demolition costs or contractual penalty).
|
|
45
|
-
fixed_size : int, float, optional
|
|
46
|
-
Determines if the investment size is fixed.
|
|
47
|
-
optional : bool, optional
|
|
48
|
-
If True, investment is not forced.
|
|
49
|
-
specific_effects : scalar or Dict[Effect: Union[int, float, np.ndarray], optional
|
|
50
|
-
Specific costs, e.g., in €/kW_nominal or €/m²_nominal.
|
|
51
|
-
Example: {costs: 3, CO2: 0.3} with costs and CO2 representing an Object of class Effect
|
|
52
|
-
(Attention: Annualize costs to chosen period!)
|
|
53
|
-
effects_in_segments : list or List[ List[Union[int,float]], Dict[cEffecType: Union[List[Union[int,float]], optional
|
|
54
|
-
Linear relation in segments [invest_segments, cost_segments].
|
|
55
|
-
Example 1:
|
|
56
|
-
[ [5, 25, 25, 100], # size in kW
|
|
57
|
-
{costs: [50,250,250,800], # €
|
|
58
|
-
PE: [5, 25, 25, 100] # kWh_PrimaryEnergy
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
Example 2 (if only standard-effect):
|
|
62
|
-
[ [5, 25, 25, 100], # kW # size in kW
|
|
63
|
-
[50,250,250,800] # value for standart effect, typically €
|
|
64
|
-
] # €
|
|
65
|
-
(Attention: Annualize costs to chosen period!)
|
|
66
|
-
(Args 'specific_effects' and 'fix_effects' can be used in parallel to InvestsizeSegments)
|
|
67
|
-
minimum_size : scalar
|
|
68
|
-
Min nominal value (only if: size_is_fixed = False).
|
|
69
|
-
maximum_size : scalar, Optional
|
|
70
|
-
Max nominal value (only if: size_is_fixed = False).
|
|
71
|
-
"""
|
|
72
|
-
self.fix_effects: EffectValuesInvest = fix_effects or {}
|
|
73
|
-
self.divest_effects: EffectValuesInvest = divest_effects or {}
|
|
74
|
-
self.fixed_size = fixed_size
|
|
75
|
-
self.optional = optional
|
|
76
|
-
self.specific_effects: EffectValuesInvest = specific_effects or {}
|
|
77
|
-
self.effects_in_segments = effects_in_segments
|
|
78
|
-
self._minimum_size = minimum_size
|
|
79
|
-
self._maximum_size = maximum_size or CONFIG.modeling.BIG # default maximum
|
|
80
|
-
|
|
81
|
-
def transform_data(self):
|
|
82
|
-
from .effects import as_effect_dict
|
|
83
|
-
|
|
84
|
-
self.fix_effects = as_effect_dict(self.fix_effects)
|
|
85
|
-
self.divest_effects = as_effect_dict(self.divest_effects)
|
|
86
|
-
self.specific_effects = as_effect_dict(self.specific_effects)
|
|
87
|
-
|
|
88
|
-
@property
|
|
89
|
-
def minimum_size(self):
|
|
90
|
-
return self.fixed_size or self._minimum_size
|
|
91
|
-
|
|
92
|
-
@property
|
|
93
|
-
def maximum_size(self):
|
|
94
|
-
return self.fixed_size or self._maximum_size
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class OnOffParameters(Interface):
|
|
98
|
-
def __init__(
|
|
99
|
-
self,
|
|
100
|
-
effects_per_switch_on: Union[Dict, Numeric] = None,
|
|
101
|
-
effects_per_running_hour: Union[Dict, Numeric] = None,
|
|
102
|
-
on_hours_total_min: Optional[int] = None,
|
|
103
|
-
on_hours_total_max: Optional[int] = None,
|
|
104
|
-
consecutive_on_hours_min: Optional[Numeric] = None,
|
|
105
|
-
consecutive_on_hours_max: Optional[Numeric] = None,
|
|
106
|
-
consecutive_off_hours_min: Optional[Numeric] = None,
|
|
107
|
-
consecutive_off_hours_max: Optional[Numeric] = None,
|
|
108
|
-
switch_on_total_max: Optional[int] = None,
|
|
109
|
-
force_switch_on: bool = False,
|
|
110
|
-
):
|
|
111
|
-
"""
|
|
112
|
-
on_off_parameters class for modeling on and off state of an Element.
|
|
113
|
-
If no parameters are given, the default is to create a binary variable for the on state
|
|
114
|
-
without further constraints or effects and a variable for the total on hours.
|
|
115
|
-
|
|
116
|
-
Parameters
|
|
117
|
-
----------
|
|
118
|
-
effects_per_switch_on : scalar, array, TimeSeriesData, optional
|
|
119
|
-
cost of one switch from off (var_on=0) to on (var_on=1),
|
|
120
|
-
unit i.g. in Euro
|
|
121
|
-
effects_per_running_hour : scalar or TS, optional
|
|
122
|
-
costs for operating, i.g. in € per hour
|
|
123
|
-
on_hours_total_min : scalar, optional
|
|
124
|
-
min. overall sum of operating hours.
|
|
125
|
-
on_hours_total_max : scalar, optional
|
|
126
|
-
max. overall sum of operating hours.
|
|
127
|
-
consecutive_on_hours_min : scalar, optional
|
|
128
|
-
min sum of operating hours in one piece
|
|
129
|
-
(last on-time period of timeseries is not checked and can be shorter)
|
|
130
|
-
consecutive_on_hours_max : scalar, optional
|
|
131
|
-
max sum of operating hours in one piece
|
|
132
|
-
consecutive_off_hours_min : scalar, optional
|
|
133
|
-
min sum of non-operating hours in one piece
|
|
134
|
-
(last off-time period of timeseries is not checked and can be shorter)
|
|
135
|
-
consecutive_off_hours_max : scalar, optional
|
|
136
|
-
max sum of non-operating hours in one piece
|
|
137
|
-
switch_on_total_max : integer, optional
|
|
138
|
-
max nr of switchOn operations
|
|
139
|
-
force_switch_on : bool
|
|
140
|
-
force creation of switch on variable, even if there is no switch_on_total_max
|
|
141
|
-
"""
|
|
142
|
-
self.effects_per_switch_on: Union[EffectValues, EffectTimeSeries] = effects_per_switch_on or {}
|
|
143
|
-
self.effects_per_running_hour: Union[EffectValues, EffectTimeSeries] = effects_per_running_hour or {}
|
|
144
|
-
self.on_hours_total_min: Skalar = on_hours_total_min
|
|
145
|
-
self.on_hours_total_max: Skalar = on_hours_total_max
|
|
146
|
-
self.consecutive_on_hours_min: Numeric_TS = consecutive_on_hours_min
|
|
147
|
-
self.consecutive_on_hours_max: Numeric_TS = consecutive_on_hours_max
|
|
148
|
-
self.consecutive_off_hours_min: Numeric_TS = consecutive_off_hours_min
|
|
149
|
-
self.consecutive_off_hours_max: Numeric_TS = consecutive_off_hours_max
|
|
150
|
-
self.switch_on_total_max: Skalar = switch_on_total_max
|
|
151
|
-
self.force_switch_on: bool = force_switch_on
|
|
152
|
-
|
|
153
|
-
def transform_data(self, owner: 'Element'):
|
|
154
|
-
from .effects import effect_values_to_time_series
|
|
155
|
-
from .structure import _create_time_series
|
|
156
|
-
|
|
157
|
-
self.effects_per_switch_on = effect_values_to_time_series('per_switch_on', self.effects_per_switch_on, owner)
|
|
158
|
-
self.effects_per_running_hour = effect_values_to_time_series(
|
|
159
|
-
'per_running_hour', self.effects_per_running_hour, owner
|
|
160
|
-
)
|
|
161
|
-
self.consecutive_on_hours_min = _create_time_series(
|
|
162
|
-
'consecutive_on_hours_min', self.consecutive_on_hours_min, owner
|
|
163
|
-
)
|
|
164
|
-
self.consecutive_on_hours_max = _create_time_series(
|
|
165
|
-
'consecutive_on_hours_max', self.consecutive_on_hours_max, owner
|
|
166
|
-
)
|
|
167
|
-
self.consecutive_off_hours_min = _create_time_series(
|
|
168
|
-
'consecutive_off_hours_min', self.consecutive_off_hours_min, owner
|
|
169
|
-
)
|
|
170
|
-
self.consecutive_off_hours_max = _create_time_series(
|
|
171
|
-
'consecutive_off_hours_max', self.consecutive_off_hours_max, owner
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
@property
|
|
175
|
-
def use_off(self) -> bool:
|
|
176
|
-
"""Determines wether the OFF Variable is needed or not"""
|
|
177
|
-
return self.use_consecutive_off_hours
|
|
178
|
-
|
|
179
|
-
@property
|
|
180
|
-
def use_consecutive_on_hours(self) -> bool:
|
|
181
|
-
"""Determines wether a Variable for consecutive off hours is needed or not"""
|
|
182
|
-
return any(param is not None for param in [self.consecutive_on_hours_min, self.consecutive_on_hours_max])
|
|
183
|
-
|
|
184
|
-
@property
|
|
185
|
-
def use_consecutive_off_hours(self) -> bool:
|
|
186
|
-
"""Determines wether a Variable for consecutive off hours is needed or not"""
|
|
187
|
-
return any(param is not None for param in [self.consecutive_off_hours_min, self.consecutive_off_hours_max])
|
|
188
|
-
|
|
189
|
-
@property
|
|
190
|
-
def use_switch_on(self) -> bool:
|
|
191
|
-
"""Determines wether a Variable for SWITCH-ON is needed or not"""
|
|
192
|
-
return (
|
|
193
|
-
any(
|
|
194
|
-
param not in (None, {})
|
|
195
|
-
for param in [
|
|
196
|
-
self.effects_per_switch_on,
|
|
197
|
-
self.switch_on_total_max,
|
|
198
|
-
self.on_hours_total_min,
|
|
199
|
-
self.on_hours_total_max,
|
|
200
|
-
]
|
|
201
|
-
)
|
|
202
|
-
or self.force_switch_on
|
|
203
|
-
)
|