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/results.py
DELETED
|
@@ -1,563 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module contains the Results functionality of the flixOpt framework.
|
|
3
|
-
It provides high level functions to analyze the results of a calculation.
|
|
4
|
-
It leverages the plotting.py module to plot the results.
|
|
5
|
-
The results can also be analyzed without this module, as the results are stored in a widely supported format.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import datetime
|
|
9
|
-
import json
|
|
10
|
-
import logging
|
|
11
|
-
import pathlib
|
|
12
|
-
import timeit
|
|
13
|
-
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Tuple, Union
|
|
14
|
-
|
|
15
|
-
import numpy as np
|
|
16
|
-
import pandas as pd
|
|
17
|
-
import plotly
|
|
18
|
-
import yaml
|
|
19
|
-
|
|
20
|
-
from flixOpt import plotting, utils
|
|
21
|
-
|
|
22
|
-
if TYPE_CHECKING:
|
|
23
|
-
import matplotlib.pyplot as plt
|
|
24
|
-
import plotly.graph_objects as go
|
|
25
|
-
import pyvis
|
|
26
|
-
|
|
27
|
-
logger = logging.getLogger('flixOpt')
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class ElementResults:
|
|
31
|
-
def __init__(self, infos: Dict, results: Dict):
|
|
32
|
-
self.all_infos = infos
|
|
33
|
-
self.all_results = results
|
|
34
|
-
self.label = self.all_infos['label']
|
|
35
|
-
|
|
36
|
-
def __repr__(self):
|
|
37
|
-
return f'{self.__class__.__name__}({self.label})'
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def variables_flat(self) -> Dict[str, Union[int, float, np.ndarray]]:
|
|
41
|
-
return flatten_dict(self.all_results)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class CalculationResults:
|
|
45
|
-
def __init__(self, calculation_name: str, folder: str) -> None:
|
|
46
|
-
self.name = calculation_name
|
|
47
|
-
self.folder = pathlib.Path(folder)
|
|
48
|
-
self._path_infos = self.folder / f'{calculation_name}_infos.yaml'
|
|
49
|
-
self._path_data = self.folder / f'{calculation_name}_data.json'
|
|
50
|
-
self._path_results = self.folder / f'{calculation_name}_results.json'
|
|
51
|
-
|
|
52
|
-
start_time = timeit.default_timer()
|
|
53
|
-
with open(self._path_infos, 'rb') as f:
|
|
54
|
-
self.calculation_infos: Dict = yaml.safe_load(f)
|
|
55
|
-
logger.info(f'Loading Calculation Infos from .yaml took {(timeit.default_timer() - start_time):>8.2f} seconds')
|
|
56
|
-
|
|
57
|
-
start_time = timeit.default_timer()
|
|
58
|
-
with open(self._path_results, 'rb') as f:
|
|
59
|
-
self.all_results: Dict = json.load(f)
|
|
60
|
-
self.all_results = utils.convert_numeric_lists_to_arrays(self.all_results)
|
|
61
|
-
logger.info(f'Loading results from .json took {(timeit.default_timer() - start_time):>8.2f} seconds')
|
|
62
|
-
|
|
63
|
-
start_time = timeit.default_timer()
|
|
64
|
-
with open(self._path_data, 'rb') as f:
|
|
65
|
-
self.all_data: Dict = json.load(f)
|
|
66
|
-
self.all_data = utils.convert_numeric_lists_to_arrays(self.all_data)
|
|
67
|
-
logger.info(f'Loading data from .json took {(timeit.default_timer() - start_time):>8.2f} seconds')
|
|
68
|
-
|
|
69
|
-
self.component_results: Dict[str, ComponentResults] = {}
|
|
70
|
-
self.effect_results: Dict[str, EffectResults] = {}
|
|
71
|
-
self.bus_results: Dict[str, BusResults] = {}
|
|
72
|
-
|
|
73
|
-
self.time_with_end = np.array(
|
|
74
|
-
[datetime.datetime.fromisoformat(date) for date in self.all_results['Time']]
|
|
75
|
-
).astype('datetime64')
|
|
76
|
-
self.time = self.time_with_end[:-1]
|
|
77
|
-
self.time_intervals_in_hours = np.array(self.all_results['Time intervals in hours'])
|
|
78
|
-
|
|
79
|
-
self._construct_component_results()
|
|
80
|
-
self._construct_bus_results()
|
|
81
|
-
self._construct_effect_results()
|
|
82
|
-
|
|
83
|
-
def _construct_component_results(self):
|
|
84
|
-
comp_results = self.all_results['Components']
|
|
85
|
-
comp_infos = self.all_data['Components']
|
|
86
|
-
if not comp_results.keys() == comp_infos.keys():
|
|
87
|
-
logger.warning(f'Missing Component or mismatched keys: {comp_results.keys() ^ comp_infos.keys()}')
|
|
88
|
-
|
|
89
|
-
for key in comp_results.keys():
|
|
90
|
-
infos, results = comp_infos.get(key, {}), comp_results.get(key, {})
|
|
91
|
-
res = ComponentResults(infos, results)
|
|
92
|
-
self.component_results[res.label] = res
|
|
93
|
-
|
|
94
|
-
def _construct_effect_results(self):
|
|
95
|
-
effect_results = self.all_results['Effects']
|
|
96
|
-
effect_infos = self.all_data['Effects']
|
|
97
|
-
effect_infos['penalty'] = {'label': 'Penalty'}
|
|
98
|
-
if not effect_results.keys() == effect_infos.keys():
|
|
99
|
-
logger.warning(f'Missing Effect or mismatched keys: {effect_results.keys() ^ effect_infos.keys()}')
|
|
100
|
-
|
|
101
|
-
for key in effect_results.keys():
|
|
102
|
-
infos, results = effect_infos.get(key, {}), effect_results.get(key, {})
|
|
103
|
-
res = EffectResults(infos, results)
|
|
104
|
-
self.effect_results[res.label] = res
|
|
105
|
-
|
|
106
|
-
def _construct_bus_results(self):
|
|
107
|
-
"""This has to be called after _construct_component_results(), as its using the Flows from the Components"""
|
|
108
|
-
bus_results = self.all_results['Buses']
|
|
109
|
-
bus_infos = self.all_data['Buses']
|
|
110
|
-
if not bus_results.keys() == bus_infos.keys():
|
|
111
|
-
logger.warning(f'Missing Bus or mismatched keys: {bus_results.keys() ^ bus_infos.keys()}')
|
|
112
|
-
|
|
113
|
-
for bus_label in bus_results.keys():
|
|
114
|
-
infos, results = bus_infos.get(bus_label, {}), bus_results.get(bus_label, {})
|
|
115
|
-
inputs = [
|
|
116
|
-
flow
|
|
117
|
-
for flow in self.flow_results().values()
|
|
118
|
-
if bus_label == flow.bus_label and not flow.is_input_in_component
|
|
119
|
-
]
|
|
120
|
-
outputs = [
|
|
121
|
-
flow
|
|
122
|
-
for flow in self.flow_results().values()
|
|
123
|
-
if bus_label == flow.bus_label and flow.is_input_in_component
|
|
124
|
-
]
|
|
125
|
-
res = BusResults(infos, results, inputs, outputs)
|
|
126
|
-
self.bus_results[res.label] = res
|
|
127
|
-
|
|
128
|
-
def flow_results(self) -> Dict[str, 'FlowResults']:
|
|
129
|
-
return {
|
|
130
|
-
flow.label_full: flow for comp in self.component_results.values() for flow in comp.inputs + comp.outputs
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
def to_dataframe(
|
|
134
|
-
self,
|
|
135
|
-
label: str,
|
|
136
|
-
variable_name: str = 'flow_rate',
|
|
137
|
-
input_factor: Optional[Literal[1, -1]] = -1,
|
|
138
|
-
output_factor: Optional[Literal[1, -1]] = 1,
|
|
139
|
-
threshold: Optional[float] = 1e-5,
|
|
140
|
-
with_last_time_step: bool = True,
|
|
141
|
-
) -> pd.DataFrame:
|
|
142
|
-
"""
|
|
143
|
-
Convert results of a specified element to a DataFrame.
|
|
144
|
-
|
|
145
|
-
Parameters
|
|
146
|
-
----------
|
|
147
|
-
label : str
|
|
148
|
-
The label of the element (Component, Bus, or Flow) to retrieve data for.
|
|
149
|
-
variable_name : str, default='flow_rate'
|
|
150
|
-
The name of the variable to extract from the element's data.
|
|
151
|
-
input_factor : Optional[Literal[1, -1]], default=-1
|
|
152
|
-
Factor to apply to input values.
|
|
153
|
-
output_factor : Optional[Literal[1, -1]], default=1
|
|
154
|
-
Factor to apply to output values.
|
|
155
|
-
threshold : Optional[float], default=1e-5
|
|
156
|
-
Minimum absolute value for data inclusion in the DataFrame.
|
|
157
|
-
with_last_time_step : bool, default=True
|
|
158
|
-
Whether to include the last time step in the DataFrame index.
|
|
159
|
-
|
|
160
|
-
Returns
|
|
161
|
-
-------
|
|
162
|
-
pd.DataFrame
|
|
163
|
-
A DataFrame containing the specified variable's data with a datetime index.
|
|
164
|
-
Dataframe is empty (no index), if no values are left after filtering.
|
|
165
|
-
|
|
166
|
-
Raises
|
|
167
|
-
------
|
|
168
|
-
ValueError
|
|
169
|
-
If no data is found for the specified variable.
|
|
170
|
-
"""
|
|
171
|
-
|
|
172
|
-
comp_or_bus = {**self.component_results, **self.bus_results}.get(label, None)
|
|
173
|
-
flow = self.flow_results().get(label, None)
|
|
174
|
-
|
|
175
|
-
if comp_or_bus is not None and flow is not None:
|
|
176
|
-
raise Exception(f'{label=} matches both a Flow and a Component/Bus. That is an internal Error!')
|
|
177
|
-
elif comp_or_bus is not None:
|
|
178
|
-
df = comp_or_bus.to_dataframe(variable_name, input_factor, output_factor)
|
|
179
|
-
elif flow is not None:
|
|
180
|
-
df = flow.to_dataframe(variable_name)
|
|
181
|
-
else:
|
|
182
|
-
raise ValueError(f'No Element found with {label=}')
|
|
183
|
-
|
|
184
|
-
if threshold is not None:
|
|
185
|
-
df = df.loc[:, ((df > threshold) | (df < -1 * threshold)).any()] # Check if any value exceeds the threshold
|
|
186
|
-
if df.empty: # If no values are left, return an empty DataFrame
|
|
187
|
-
return df
|
|
188
|
-
|
|
189
|
-
if with_last_time_step:
|
|
190
|
-
if len(df) == len(self.time):
|
|
191
|
-
df.loc[len(df)] = df.iloc[-1]
|
|
192
|
-
df.index = self.time_with_end
|
|
193
|
-
elif len(df) == len(self.time_with_end):
|
|
194
|
-
df.index = self.time_with_end
|
|
195
|
-
else:
|
|
196
|
-
df.index = self.time
|
|
197
|
-
|
|
198
|
-
return df
|
|
199
|
-
|
|
200
|
-
def plot_operation(
|
|
201
|
-
self,
|
|
202
|
-
label: str,
|
|
203
|
-
mode: Literal['bar', 'line', 'area', 'heatmap'] = 'area',
|
|
204
|
-
variable_name: str = 'flow_rate',
|
|
205
|
-
heatmap_periods: Literal['YS', 'MS', 'W', 'D', 'h', '15min', 'min'] = 'D',
|
|
206
|
-
heatmap_steps_per_period: Literal['W', 'D', 'h', '15min', 'min'] = 'h',
|
|
207
|
-
colors: Union[str, List[str]] = 'viridis',
|
|
208
|
-
engine: Literal['plotly', 'matplotlib'] = 'plotly',
|
|
209
|
-
invert: bool = True,
|
|
210
|
-
show: bool = True,
|
|
211
|
-
save: bool = False,
|
|
212
|
-
path: Union[str, pathlib.Path, Literal['auto']] = 'auto',
|
|
213
|
-
) -> Union['go.Figure', Tuple['plt.Figure', 'plt.Axes']]:
|
|
214
|
-
"""
|
|
215
|
-
Plots the operation results for a specified Element using the chosen plotting engine and mode.
|
|
216
|
-
|
|
217
|
-
Parameters
|
|
218
|
-
----------
|
|
219
|
-
label : str
|
|
220
|
-
The label of the element to plot (e.g., a component or bus).
|
|
221
|
-
mode : {'bar', 'line', 'area', 'heatmap'}, default='area'
|
|
222
|
-
The type of plot to generate.
|
|
223
|
-
variable_name : str, default='flow_rate'
|
|
224
|
-
The variable to plot from the element's data.
|
|
225
|
-
heatmap_periods : {'YS', 'MS', 'W', 'D', 'h', '15min', 'min'}, default='D'
|
|
226
|
-
The period for heatmap plotting.
|
|
227
|
-
heatmap_steps_per_period : {'W', 'D', 'h', '15min', 'min'}, default='h'
|
|
228
|
-
The steps per period for heatmap plotting.
|
|
229
|
-
colors : str or List[str], default='viridis'
|
|
230
|
-
The colors or colorscale to use for the plot.
|
|
231
|
-
engine : {'plotly', 'matplotlib'}, default='plotly'
|
|
232
|
-
The plotting engine to use.
|
|
233
|
-
invert : bool, default=False
|
|
234
|
-
Whether to invert the input and output factors.
|
|
235
|
-
show : bool, default=True
|
|
236
|
-
Whether to display the plot immediately. (This includes saving the plot to file when engine='plotly')
|
|
237
|
-
save : bool, default=False
|
|
238
|
-
Whether to save the plot to a file.
|
|
239
|
-
path : Union[str, pathlib.Path, Literal['auto']], default='auto'
|
|
240
|
-
The path to save the plot to. If 'auto', the plot is saved to an automatically named file.
|
|
241
|
-
|
|
242
|
-
Returns
|
|
243
|
-
-------
|
|
244
|
-
Union[go.Figure, Tuple[plt.Figure, plt.Axes]]
|
|
245
|
-
The generated plot object, either a Plotly figure or a Matplotlib figure and axes.
|
|
246
|
-
|
|
247
|
-
Raises
|
|
248
|
-
------
|
|
249
|
-
ValueError
|
|
250
|
-
If an invalid engine or color configuration is provided for heatmap mode.
|
|
251
|
-
"""
|
|
252
|
-
|
|
253
|
-
if mode == 'heatmap' and not np.all(self.time_intervals_in_hours == self.time_intervals_in_hours[0]):
|
|
254
|
-
logger.warning(
|
|
255
|
-
'Heat map plotting with irregular time intervals in time series can lead to unwanted effects'
|
|
256
|
-
)
|
|
257
|
-
if mode == 'heatmap' and not isinstance(colors, str):
|
|
258
|
-
raise ValueError(
|
|
259
|
-
f'For a heatmap, you need to pass the colors as a valid name of a colormap, not {colors=}.'
|
|
260
|
-
f'Try "Turbo", "Hot", or "Viridis" instead.'
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
title = f'{variable_name.replace("_", " ").title()} of {label}'
|
|
264
|
-
if path == 'auto':
|
|
265
|
-
file_suffix = 'html' if engine == 'plotly' else 'png'
|
|
266
|
-
if mode == 'heatmap':
|
|
267
|
-
path = self.folder / f'{title} ({mode} {heatmap_periods}-{heatmap_steps_per_period}).{file_suffix}'
|
|
268
|
-
else:
|
|
269
|
-
path = self.folder / f'{title} ({mode}).{file_suffix}'
|
|
270
|
-
|
|
271
|
-
data = self.to_dataframe(
|
|
272
|
-
label, variable_name, input_factor=-1 if not invert else 1, output_factor=1 if not invert else -1
|
|
273
|
-
)
|
|
274
|
-
if mode == 'heatmap':
|
|
275
|
-
heatmap_data = plotting.heat_map_data_from_df(data, heatmap_periods, heatmap_steps_per_period, 'ffill')
|
|
276
|
-
|
|
277
|
-
if engine == 'plotly':
|
|
278
|
-
if mode == 'heatmap':
|
|
279
|
-
return plotting.heat_map_plotly(
|
|
280
|
-
heatmap_data, title=title, color_map=colors, show=show, save=save, path=path
|
|
281
|
-
)
|
|
282
|
-
else:
|
|
283
|
-
return plotting.with_plotly(
|
|
284
|
-
data=data, mode=mode, show=show, title=title, colors=colors, save=save, path=path
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
elif engine == 'matplotlib':
|
|
288
|
-
if mode == 'heatmap':
|
|
289
|
-
return plotting.heat_map_matplotlib(
|
|
290
|
-
heatmap_data, color_map=colors, show=show, path=path if save else None
|
|
291
|
-
)
|
|
292
|
-
else:
|
|
293
|
-
return plotting.with_matplotlib(
|
|
294
|
-
data=data, mode=mode, colors=colors, show=show, path=path if save else None
|
|
295
|
-
)
|
|
296
|
-
else:
|
|
297
|
-
raise ValueError(f'Unknown Engine: {engine=}')
|
|
298
|
-
|
|
299
|
-
def plot_storage(
|
|
300
|
-
self,
|
|
301
|
-
label: str,
|
|
302
|
-
variable_name: str = 'flow_rate',
|
|
303
|
-
mode: Literal['bar', 'line', 'area'] = 'area',
|
|
304
|
-
colors: Union[str, List[str]] = 'viridis',
|
|
305
|
-
invert: bool = True,
|
|
306
|
-
show: bool = True,
|
|
307
|
-
save: bool = False,
|
|
308
|
-
path: Union[str, pathlib.Path, Literal['auto']] = 'auto',
|
|
309
|
-
):
|
|
310
|
-
"""
|
|
311
|
-
Plots the storage operation results for a specified Storage Element, including its charge state.
|
|
312
|
-
|
|
313
|
-
Parameters
|
|
314
|
-
----------
|
|
315
|
-
label : str
|
|
316
|
-
The label of the Storage to plot
|
|
317
|
-
variable_name : str, default='flow_rate'
|
|
318
|
-
The variable to plot from the element's data.
|
|
319
|
-
mode : {'bar', 'line', 'area'}, default='area'
|
|
320
|
-
The type of plot to generate.
|
|
321
|
-
colors : str or List[str], default='viridis'
|
|
322
|
-
The colors or colorscale to use for the plot.
|
|
323
|
-
invert : bool, default=True
|
|
324
|
-
Whether to invert the input and output factors.
|
|
325
|
-
show : bool, default=True
|
|
326
|
-
Whether to display the plot immediately. (This includes saving the plot to file when engine='plotly')
|
|
327
|
-
save : bool, default=False
|
|
328
|
-
Whether to save the plot to a file.
|
|
329
|
-
path : Union[str, pathlib.Path, Literal['auto']], default='auto'
|
|
330
|
-
The path to save the plot to. If 'auto', the plot is saved to an automatically named file.
|
|
331
|
-
|
|
332
|
-
Returns
|
|
333
|
-
-------
|
|
334
|
-
plotly.graph_objs.Figure
|
|
335
|
-
The generated Plotly figure object with the storage operation plot.
|
|
336
|
-
"""
|
|
337
|
-
fig = self.plot_operation(
|
|
338
|
-
label, mode, variable_name, invert=invert, engine='plotly', show=False, colors=colors, save=False
|
|
339
|
-
)
|
|
340
|
-
fig.add_trace(
|
|
341
|
-
plotly.graph_objs.Scatter(
|
|
342
|
-
x=self.time_with_end,
|
|
343
|
-
y={**self.component_results, **self.bus_results}[label].variables['charge_state'],
|
|
344
|
-
mode='lines',
|
|
345
|
-
name='Charge State',
|
|
346
|
-
)
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
title = f'{variable_name.replace("_", " ").title()} and Charge State of {label}'
|
|
350
|
-
fig.update_layout(title=title)
|
|
351
|
-
|
|
352
|
-
if path == 'auto':
|
|
353
|
-
path = self.folder / f'{title} ({mode}).html'
|
|
354
|
-
path = path.as_posix()
|
|
355
|
-
if show:
|
|
356
|
-
plotly.offline.plot(fig, filename=path)
|
|
357
|
-
elif save: # If show, the file is saved anyway
|
|
358
|
-
fig.write_html(path)
|
|
359
|
-
|
|
360
|
-
return fig
|
|
361
|
-
|
|
362
|
-
def visualize_network(
|
|
363
|
-
self,
|
|
364
|
-
path: Union[bool, str, pathlib.Path] = 'results/network.html',
|
|
365
|
-
controls: Union[
|
|
366
|
-
bool,
|
|
367
|
-
List[
|
|
368
|
-
Literal['nodes', 'edges', 'layout', 'interaction', 'manipulation', 'physics', 'selection', 'renderer']
|
|
369
|
-
],
|
|
370
|
-
] = True,
|
|
371
|
-
show: bool = True,
|
|
372
|
-
) -> Optional['pyvis.network.Network']:
|
|
373
|
-
"""
|
|
374
|
-
Visualizes the network structure of a FlowSystem using PyVis, saving it as an interactive HTML file.
|
|
375
|
-
|
|
376
|
-
Parameters
|
|
377
|
-
----------
|
|
378
|
-
path : Union[bool, str, pathlib.Path], default='results/network.html'
|
|
379
|
-
Path to save the HTML visualization. If False, the visualization is created but not saved.
|
|
380
|
-
controls : Union[bool, List[str]], default=True
|
|
381
|
-
UI controls to add to the visualization. True enables all available controls, or specify a list of controls.
|
|
382
|
-
show : bool, default=True
|
|
383
|
-
Whether to open the visualization in the web browser.
|
|
384
|
-
|
|
385
|
-
Returns
|
|
386
|
-
-------
|
|
387
|
-
Optional[pyvis.network.Network]
|
|
388
|
-
The Network instance representing the visualization, or None if pyvis is not installed.
|
|
389
|
-
|
|
390
|
-
Notes
|
|
391
|
-
-----
|
|
392
|
-
This function requires pyvis. If not installed, the function prints a warning and returns None.
|
|
393
|
-
Nodes are styled based on type (e.g., circles for buses, boxes for components) and annotated with node information.
|
|
394
|
-
"""
|
|
395
|
-
from . import plotting
|
|
396
|
-
|
|
397
|
-
return plotting.visualize_network(
|
|
398
|
-
self.calculation_infos['Network']['Nodes'], self.calculation_infos['Network']['Edges'], path, controls, show
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
class FlowResults(ElementResults):
|
|
403
|
-
def __init__(self, infos: Dict, results: Dict, label_of_component: str) -> None:
|
|
404
|
-
super().__init__(infos, results)
|
|
405
|
-
self.is_input_in_component = self.all_infos['is_input_in_component']
|
|
406
|
-
self.component_label = label_of_component
|
|
407
|
-
self.bus_label = self.all_infos['bus']['label']
|
|
408
|
-
self.label_full = f'{label_of_component}__{self.label}'
|
|
409
|
-
self.variables = self.all_results
|
|
410
|
-
|
|
411
|
-
def to_dataframe(self, variable_name: str = 'flow_rate') -> pd.DataFrame:
|
|
412
|
-
return pd.DataFrame({variable_name: self.variables[variable_name]})
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
class ComponentResults(ElementResults):
|
|
416
|
-
def __init__(self, infos: Dict, results: Dict):
|
|
417
|
-
super().__init__(infos, results)
|
|
418
|
-
inputs, outputs = self._create_flow_results()
|
|
419
|
-
self.inputs: List[FlowResults] = inputs
|
|
420
|
-
self.outputs: List[FlowResults] = outputs
|
|
421
|
-
self.variables = {key: val for key, val in self.all_results.items() if key not in self.inputs + self.outputs}
|
|
422
|
-
|
|
423
|
-
def _create_flow_results(self) -> Tuple[List[FlowResults], List[FlowResults]]:
|
|
424
|
-
flow_infos = {flow['label']: flow for flow in self.all_infos['inputs'] + self.all_infos['outputs']}
|
|
425
|
-
flow_results = {flow_info['label']: self.all_results[flow_info['label']] for flow_info in flow_infos.values()}
|
|
426
|
-
flows = [
|
|
427
|
-
FlowResults(flow_info, flow_result, self.label)
|
|
428
|
-
for flow_info, flow_result in zip(flow_infos.values(), flow_results.values(), strict=False)
|
|
429
|
-
]
|
|
430
|
-
inputs = [flow for flow in flows if flow.is_input_in_component]
|
|
431
|
-
outputs = [flow for flow in flows if not flow.is_input_in_component]
|
|
432
|
-
return inputs, outputs
|
|
433
|
-
|
|
434
|
-
def to_dataframe(
|
|
435
|
-
self,
|
|
436
|
-
variable_name: str = 'flow_rate',
|
|
437
|
-
input_factor: Optional[Literal[1, -1]] = -1,
|
|
438
|
-
output_factor: Optional[Literal[1, -1]] = 1,
|
|
439
|
-
) -> pd.DataFrame:
|
|
440
|
-
inputs, outputs = {}, {}
|
|
441
|
-
if input_factor is not None:
|
|
442
|
-
inputs = {flow.label_full: (flow.variables[variable_name] * input_factor) for flow in self.inputs}
|
|
443
|
-
if output_factor is not None:
|
|
444
|
-
outputs = {flow.label_full: flow.variables[variable_name] * output_factor for flow in self.outputs}
|
|
445
|
-
|
|
446
|
-
return pd.DataFrame(data={**inputs, **outputs})
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
class BusResults(ElementResults):
|
|
450
|
-
def __init__(self, infos: Dict, results: Dict, inputs: List[FlowResults], outputs: List[FlowResults]):
|
|
451
|
-
super().__init__(infos, results)
|
|
452
|
-
self.inputs = inputs
|
|
453
|
-
self.outputs = outputs
|
|
454
|
-
self.variables = {key: val for key, val in self.all_results.items() if key not in self.inputs + self.outputs}
|
|
455
|
-
|
|
456
|
-
def to_dataframe(
|
|
457
|
-
self,
|
|
458
|
-
variable_name: str = 'flow_rate',
|
|
459
|
-
input_factor: Optional[Literal[1, -1]] = -1,
|
|
460
|
-
output_factor: Optional[Literal[1, -1]] = 1,
|
|
461
|
-
) -> pd.DataFrame:
|
|
462
|
-
inputs, outputs = {}, {}
|
|
463
|
-
if input_factor is not None:
|
|
464
|
-
inputs = {flow.label_full: (flow.variables[variable_name] * input_factor) for flow in self.inputs}
|
|
465
|
-
if 'excess_input' in self.variables:
|
|
466
|
-
inputs['Excess Input'] = self.variables['excess_input'] * input_factor
|
|
467
|
-
if output_factor is not None:
|
|
468
|
-
outputs = {flow.label_full: flow.variables[variable_name] * output_factor for flow in self.outputs}
|
|
469
|
-
if 'excess_output' in self.variables:
|
|
470
|
-
outputs['Excess Output'] = self.variables['excess_output'] * output_factor
|
|
471
|
-
|
|
472
|
-
return pd.DataFrame(data={**inputs, **outputs})
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
class EffectResults(ElementResults):
|
|
476
|
-
pass
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
def extract_single_result(
|
|
480
|
-
results_data: dict[str, Dict[str, Union[int, float, np.ndarray, dict]]], keys: List[str]
|
|
481
|
-
) -> Optional[Union[int, float, np.ndarray]]:
|
|
482
|
-
"""Goes through a nested dictionary with the given keys. Returns the value if found. Else returns None"""
|
|
483
|
-
for key in keys:
|
|
484
|
-
if isinstance(results_data, dict):
|
|
485
|
-
results_data = results_data.get(key, None)
|
|
486
|
-
else:
|
|
487
|
-
return None
|
|
488
|
-
return results_data
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
def extract_results(
|
|
492
|
-
results_data: dict[str, Dict[str, Union[int, float, np.ndarray, dict]]], keys: List[str], keep_none: bool = False
|
|
493
|
-
) -> Dict[str, Union[int, float, np.ndarray]]:
|
|
494
|
-
"""For each item in a dictionary, goes through its sub dictionaries.
|
|
495
|
-
Returns the value if found. Else returns None. If specified, removes all None values
|
|
496
|
-
"""
|
|
497
|
-
data = {kind: extract_single_result(results_data.get(kind, {}), keys) for kind in results_data.keys()}
|
|
498
|
-
if keep_none:
|
|
499
|
-
return data
|
|
500
|
-
else:
|
|
501
|
-
return {key: value for key, value in data.items() if value is not None}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
def flatten_dict(d, parent_key='', sep='__'):
|
|
505
|
-
"""
|
|
506
|
-
Recursively flattens a nested dictionary.
|
|
507
|
-
|
|
508
|
-
Parameters:
|
|
509
|
-
d (dict): The dictionary to flatten.
|
|
510
|
-
parent_key (str): The base key for the current recursion level.
|
|
511
|
-
sep (str): The separator to use when concatenating keys.
|
|
512
|
-
|
|
513
|
-
Returns:
|
|
514
|
-
dict: A flattened dictionary.
|
|
515
|
-
"""
|
|
516
|
-
items = []
|
|
517
|
-
for k, v in d.items():
|
|
518
|
-
new_key = f'{parent_key}{sep}{k}' if parent_key else k # Combine parent key and current key
|
|
519
|
-
if isinstance(v, dict): # If the value is a nested dictionary, recurse
|
|
520
|
-
items.extend(flatten_dict(v, new_key, sep=sep).items())
|
|
521
|
-
else: # Otherwise, just add the key-value pair
|
|
522
|
-
if new_key not in items:
|
|
523
|
-
items.append((new_key, v))
|
|
524
|
-
else:
|
|
525
|
-
for i in range(100000):
|
|
526
|
-
new_key = f'{new_key}_#{i}'
|
|
527
|
-
if new_key not in items:
|
|
528
|
-
items.append((new_key, v))
|
|
529
|
-
break
|
|
530
|
-
return dict(items)
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if __name__ == '__main__':
|
|
534
|
-
results = CalculationResults(
|
|
535
|
-
'Sim1', '/Users/felix/Documents/Dokumente - eigene/Neuer Ordner/flixOpt-Fork/examples/Ex02_complex/results'
|
|
536
|
-
)
|
|
537
|
-
|
|
538
|
-
results.to_dataframe('Kessel')
|
|
539
|
-
results.plot_flow_rate('Kessel__Q_fu', 'heatmap')
|
|
540
|
-
plotting.heat_map_plotly(
|
|
541
|
-
plotting.heat_map_data_from_df(
|
|
542
|
-
pd.DataFrame(results.component_results['Speicher'].variables['charge_state'], index=results.time_with_end),
|
|
543
|
-
periods='D',
|
|
544
|
-
steps_per_period='15min',
|
|
545
|
-
)
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
results.plot_operation('Fernwärme', 'area', engine='plotly')
|
|
549
|
-
fig = results.plot_operation('Fernwärme', 'area', engine='plotly')
|
|
550
|
-
fig = plotting.with_plotly(results.to_dataframe('Wärmelast'), 'line', fig=fig)
|
|
551
|
-
import plotly.offline
|
|
552
|
-
|
|
553
|
-
plotly.offline.plot(fig)
|
|
554
|
-
|
|
555
|
-
extract_results(results.all_results['Components'], ['Q_th', 'flow_rate'])
|
|
556
|
-
extract_single_result(results.all_results['Components'], ['Kessel', 'Q_th', 'flow_rate'])
|
|
557
|
-
|
|
558
|
-
fig = plotting.with_plotly(
|
|
559
|
-
pd.DataFrame(extract_results(results.all_results['Components'], ['OnOff', 'on']), index=results.time),
|
|
560
|
-
mode='bar',
|
|
561
|
-
)
|
|
562
|
-
fig.update_layout(barmode='group', bargap=0.2, bargroupgap=0.1)
|
|
563
|
-
plotly.offline.plot(fig)
|
flixOpt/solvers.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module contains the solvers of the flixOpt framework, making them available to the end user in a compact way.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from .math_modeling import (
|
|
6
|
-
CbcSolver,
|
|
7
|
-
CplexSolver,
|
|
8
|
-
GlpkSolver,
|
|
9
|
-
GurobiSolver,
|
|
10
|
-
HighsSolver,
|
|
11
|
-
Solver,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
__all__ = [
|
|
15
|
-
'Solver',
|
|
16
|
-
'HighsSolver',
|
|
17
|
-
'GurobiSolver',
|
|
18
|
-
'CbcSolver',
|
|
19
|
-
'CplexSolver',
|
|
20
|
-
'GlpkSolver',
|
|
21
|
-
]
|