flixopt 3.1.0__py3-none-any.whl → 3.2.0__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.
- flixopt/aggregation.py +13 -4
- flixopt/calculation.py +2 -3
- flixopt/color_processing.py +261 -0
- flixopt/components.py +12 -10
- flixopt/config.py +59 -4
- flixopt/effects.py +11 -13
- flixopt/flow_system.py +5 -3
- flixopt/interface.py +2 -1
- flixopt/io.py +239 -22
- flixopt/plotting.py +583 -789
- flixopt/results.py +475 -70
- flixopt/structure.py +4 -9
- {flixopt-3.1.0.dist-info → flixopt-3.2.0.dist-info}/METADATA +2 -2
- flixopt-3.2.0.dist-info/RECORD +26 -0
- flixopt/utils.py +0 -86
- flixopt-3.1.0.dist-info/RECORD +0 -26
- {flixopt-3.1.0.dist-info → flixopt-3.2.0.dist-info}/WHEEL +0 -0
- {flixopt-3.1.0.dist-info → flixopt-3.2.0.dist-info}/licenses/LICENSE +0 -0
- {flixopt-3.1.0.dist-info → flixopt-3.2.0.dist-info}/top_level.txt +0 -0
flixopt/aggregation.py
CHANGED
|
@@ -20,7 +20,9 @@ try:
|
|
|
20
20
|
except ImportError:
|
|
21
21
|
TSAM_AVAILABLE = False
|
|
22
22
|
|
|
23
|
+
from .color_processing import process_colors
|
|
23
24
|
from .components import Storage
|
|
25
|
+
from .config import CONFIG
|
|
24
26
|
from .structure import (
|
|
25
27
|
FlowSystemModel,
|
|
26
28
|
Submodel,
|
|
@@ -141,7 +143,7 @@ class Aggregation:
|
|
|
141
143
|
def use_extreme_periods(self):
|
|
142
144
|
return self.time_series_for_high_peaks or self.time_series_for_low_peaks
|
|
143
145
|
|
|
144
|
-
def plot(self, colormap: str =
|
|
146
|
+
def plot(self, colormap: str | None = None, show: bool = True, save: pathlib.Path | None = None) -> go.Figure:
|
|
145
147
|
from . import plotting
|
|
146
148
|
|
|
147
149
|
df_org = self.original_data.copy().rename(
|
|
@@ -150,13 +152,20 @@ class Aggregation:
|
|
|
150
152
|
df_agg = self.aggregated_data.copy().rename(
|
|
151
153
|
columns={col: f'Aggregated - {col}' for col in self.aggregated_data.columns}
|
|
152
154
|
)
|
|
153
|
-
|
|
155
|
+
colors = list(
|
|
156
|
+
process_colors(colormap or CONFIG.Plotting.default_qualitative_colorscale, list(df_org.columns)).values()
|
|
157
|
+
)
|
|
158
|
+
fig = plotting.with_plotly(df_org.to_xarray(), 'line', colors=colors, xlabel='Time in h')
|
|
154
159
|
for trace in fig.data:
|
|
155
160
|
trace.update(dict(line=dict(dash='dash')))
|
|
156
|
-
|
|
161
|
+
fig2 = plotting.with_plotly(df_agg.to_xarray(), 'line', colors=colors, xlabel='Time in h')
|
|
162
|
+
for trace in fig2.data:
|
|
163
|
+
fig.add_trace(trace)
|
|
157
164
|
|
|
158
165
|
fig.update_layout(
|
|
159
|
-
title='Original vs Aggregated Data (original = ---)',
|
|
166
|
+
title='Original vs Aggregated Data (original = ---)',
|
|
167
|
+
xaxis_title='Time in h',
|
|
168
|
+
yaxis_title='Value',
|
|
160
169
|
)
|
|
161
170
|
|
|
162
171
|
plotting.export_figure(
|
flixopt/calculation.py
CHANGED
|
@@ -22,7 +22,6 @@ import numpy as np
|
|
|
22
22
|
import yaml
|
|
23
23
|
|
|
24
24
|
from . import io as fx_io
|
|
25
|
-
from . import utils as utils
|
|
26
25
|
from .aggregation import Aggregation, AggregationModel, AggregationParameters
|
|
27
26
|
from .components import Storage
|
|
28
27
|
from .config import CONFIG
|
|
@@ -144,7 +143,7 @@ class Calculation:
|
|
|
144
143
|
],
|
|
145
144
|
}
|
|
146
145
|
|
|
147
|
-
return
|
|
146
|
+
return fx_io.round_nested_floats(main_results)
|
|
148
147
|
|
|
149
148
|
@property
|
|
150
149
|
def summary(self):
|
|
@@ -253,7 +252,7 @@ class FullCalculation(Calculation):
|
|
|
253
252
|
logger.info(
|
|
254
253
|
f'{" Main Results ":#^80}\n'
|
|
255
254
|
+ yaml.dump(
|
|
256
|
-
|
|
255
|
+
self.main_results,
|
|
257
256
|
default_flow_style=False,
|
|
258
257
|
sort_keys=False,
|
|
259
258
|
allow_unicode=True,
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Simplified color handling for visualization.
|
|
2
|
+
|
|
3
|
+
This module provides clean color processing that transforms various input formats
|
|
4
|
+
into a label-to-color mapping dictionary, without needing to know about the plotting engine.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
import matplotlib.colors as mcolors
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
import plotly.express as px
|
|
14
|
+
from plotly.exceptions import PlotlyError
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger('flixopt')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _rgb_string_to_hex(color: str) -> str:
|
|
20
|
+
"""Convert Plotly RGB/RGBA string format to hex.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
color: Color in format 'rgb(R, G, B)', 'rgba(R, G, B, A)' or already in hex
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Color in hex format '#RRGGBB'
|
|
27
|
+
"""
|
|
28
|
+
color = color.strip()
|
|
29
|
+
|
|
30
|
+
# If already hex, return as-is
|
|
31
|
+
if color.startswith('#'):
|
|
32
|
+
return color
|
|
33
|
+
|
|
34
|
+
# Try to parse rgb() or rgba()
|
|
35
|
+
try:
|
|
36
|
+
if color.startswith('rgb('):
|
|
37
|
+
# Extract RGB values from 'rgb(R, G, B)' format
|
|
38
|
+
rgb_str = color[4:-1] # Remove 'rgb(' and ')'
|
|
39
|
+
elif color.startswith('rgba('):
|
|
40
|
+
# Extract RGBA values from 'rgba(R, G, B, A)' format
|
|
41
|
+
rgb_str = color[5:-1] # Remove 'rgba(' and ')'
|
|
42
|
+
else:
|
|
43
|
+
return color
|
|
44
|
+
|
|
45
|
+
# Split on commas and parse first three components
|
|
46
|
+
components = rgb_str.split(',')
|
|
47
|
+
if len(components) < 3:
|
|
48
|
+
return color
|
|
49
|
+
|
|
50
|
+
# Parse and clamp the first three components
|
|
51
|
+
r = max(0, min(255, int(round(float(components[0].strip())))))
|
|
52
|
+
g = max(0, min(255, int(round(float(components[1].strip())))))
|
|
53
|
+
b = max(0, min(255, int(round(float(components[2].strip())))))
|
|
54
|
+
|
|
55
|
+
return f'#{r:02x}{g:02x}{b:02x}'
|
|
56
|
+
except (ValueError, IndexError):
|
|
57
|
+
# If parsing fails, return original
|
|
58
|
+
return color
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def process_colors(
|
|
62
|
+
colors: None | str | list[str] | dict[str, str],
|
|
63
|
+
labels: list[str],
|
|
64
|
+
default_colorscale: str = 'turbo',
|
|
65
|
+
) -> dict[str, str]:
|
|
66
|
+
"""Process color input and return a label-to-color mapping.
|
|
67
|
+
|
|
68
|
+
This function takes flexible color input and always returns a dictionary
|
|
69
|
+
mapping each label to a specific color string. The plotting engine can then
|
|
70
|
+
use this mapping as needed.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
colors: Color specification in one of four formats:
|
|
74
|
+
- None: Use the default colorscale
|
|
75
|
+
- str: Name of a colorscale (e.g., 'turbo', 'plasma', 'Set1', 'portland')
|
|
76
|
+
- list[str]: List of color strings (hex, named colors, etc.)
|
|
77
|
+
- dict[str, str]: Direct label-to-color mapping
|
|
78
|
+
labels: List of labels that need colors assigned
|
|
79
|
+
default_colorscale: Fallback colorscale name if requested scale not found
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Dictionary mapping each label to a color string
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
>>> # Using None - applies default colorscale
|
|
86
|
+
>>> process_colors(None, ['A', 'B', 'C'])
|
|
87
|
+
{'A': '#0d0887', 'B': '#7e03a8', 'C': '#cc4778'}
|
|
88
|
+
|
|
89
|
+
>>> # Using a colorscale name
|
|
90
|
+
>>> process_colors('plasma', ['A', 'B', 'C'])
|
|
91
|
+
{'A': '#0d0887', 'B': '#7e03a8', 'C': '#cc4778'}
|
|
92
|
+
|
|
93
|
+
>>> # Using a list of colors
|
|
94
|
+
>>> process_colors(['red', 'blue', 'green'], ['A', 'B', 'C'])
|
|
95
|
+
{'A': 'red', 'B': 'blue', 'C': 'green'}
|
|
96
|
+
|
|
97
|
+
>>> # Using a pre-made mapping
|
|
98
|
+
>>> process_colors({'A': 'red', 'B': 'blue'}, ['A', 'B', 'C'])
|
|
99
|
+
{'A': 'red', 'B': 'blue', 'C': '#0d0887'} # C gets color from default scale
|
|
100
|
+
"""
|
|
101
|
+
if not labels:
|
|
102
|
+
return {}
|
|
103
|
+
|
|
104
|
+
# Case 1: Already a mapping dictionary
|
|
105
|
+
if isinstance(colors, dict):
|
|
106
|
+
return _fill_missing_colors(colors, labels, default_colorscale)
|
|
107
|
+
|
|
108
|
+
# Case 2: None or colorscale name (string)
|
|
109
|
+
if colors is None or isinstance(colors, str):
|
|
110
|
+
colorscale_name = colors if colors is not None else default_colorscale
|
|
111
|
+
color_list = _get_colors_from_scale(colorscale_name, len(labels), default_colorscale)
|
|
112
|
+
return dict(zip(labels, color_list, strict=False))
|
|
113
|
+
|
|
114
|
+
# Case 3: List of colors
|
|
115
|
+
if isinstance(colors, list):
|
|
116
|
+
if len(colors) == 0:
|
|
117
|
+
logger.warning(f'Empty color list provided. Using {default_colorscale} instead.')
|
|
118
|
+
color_list = _get_colors_from_scale(default_colorscale, len(labels), default_colorscale)
|
|
119
|
+
return dict(zip(labels, color_list, strict=False))
|
|
120
|
+
|
|
121
|
+
if len(colors) < len(labels):
|
|
122
|
+
logger.debug(
|
|
123
|
+
f'Not enough colors provided ({len(colors)}) for all labels ({len(labels)}). Colors will cycle.'
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Cycle through colors if we don't have enough
|
|
127
|
+
return {label: colors[i % len(colors)] for i, label in enumerate(labels)}
|
|
128
|
+
|
|
129
|
+
raise TypeError(f'colors must be None, str, list, or dict, got {type(colors)}')
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _fill_missing_colors(
|
|
133
|
+
color_mapping: dict[str, str],
|
|
134
|
+
labels: list[str],
|
|
135
|
+
default_colorscale: str,
|
|
136
|
+
) -> dict[str, str]:
|
|
137
|
+
"""Fill in missing labels in a color mapping using a colorscale.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
color_mapping: Partial label-to-color mapping
|
|
141
|
+
labels: All labels that need colors
|
|
142
|
+
default_colorscale: Colorscale to use for missing labels
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Complete label-to-color mapping
|
|
146
|
+
"""
|
|
147
|
+
missing_labels = [label for label in labels if label not in color_mapping]
|
|
148
|
+
|
|
149
|
+
if not missing_labels:
|
|
150
|
+
return color_mapping.copy()
|
|
151
|
+
|
|
152
|
+
# Log warning about missing labels
|
|
153
|
+
logger.debug(f'Labels missing colors: {missing_labels}. Using {default_colorscale} for these.')
|
|
154
|
+
|
|
155
|
+
# Get colors for missing labels
|
|
156
|
+
missing_colors = _get_colors_from_scale(default_colorscale, len(missing_labels), default_colorscale)
|
|
157
|
+
|
|
158
|
+
# Combine existing and new colors
|
|
159
|
+
result = color_mapping.copy()
|
|
160
|
+
result.update(dict(zip(missing_labels, missing_colors, strict=False)))
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _get_colors_from_scale(
|
|
165
|
+
colorscale_name: str,
|
|
166
|
+
num_colors: int,
|
|
167
|
+
fallback_scale: str,
|
|
168
|
+
) -> list[str]:
|
|
169
|
+
"""Extract a list of colors from a named colorscale.
|
|
170
|
+
|
|
171
|
+
Tries to get colors from the named scale (Plotly first, then Matplotlib),
|
|
172
|
+
falls back to the fallback scale if not found.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
colorscale_name: Name of the colorscale to try
|
|
176
|
+
num_colors: Number of colors needed
|
|
177
|
+
fallback_scale: Fallback colorscale name if first fails
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of color strings (hex format)
|
|
181
|
+
"""
|
|
182
|
+
# Try to get the requested colorscale
|
|
183
|
+
colors = _try_get_colorscale(colorscale_name, num_colors)
|
|
184
|
+
|
|
185
|
+
if colors is not None:
|
|
186
|
+
return colors
|
|
187
|
+
|
|
188
|
+
# Fallback to default
|
|
189
|
+
logger.warning(f"Colorscale '{colorscale_name}' not found. Using '{fallback_scale}' instead.")
|
|
190
|
+
|
|
191
|
+
colors = _try_get_colorscale(fallback_scale, num_colors)
|
|
192
|
+
|
|
193
|
+
if colors is not None:
|
|
194
|
+
return colors
|
|
195
|
+
|
|
196
|
+
# Ultimate fallback: just use basic colors
|
|
197
|
+
logger.warning(f"Fallback colorscale '{fallback_scale}' also not found. Using basic colors.")
|
|
198
|
+
basic_colors = [
|
|
199
|
+
'#1f77b4',
|
|
200
|
+
'#ff7f0e',
|
|
201
|
+
'#2ca02c',
|
|
202
|
+
'#d62728',
|
|
203
|
+
'#9467bd',
|
|
204
|
+
'#8c564b',
|
|
205
|
+
'#e377c2',
|
|
206
|
+
'#7f7f7f',
|
|
207
|
+
'#bcbd22',
|
|
208
|
+
'#17becf',
|
|
209
|
+
]
|
|
210
|
+
return [basic_colors[i % len(basic_colors)] for i in range(num_colors)]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _try_get_colorscale(colorscale_name: str, num_colors: int) -> list[str] | None:
|
|
214
|
+
"""Try to get colors from Plotly or Matplotlib colorscales.
|
|
215
|
+
|
|
216
|
+
Tries Plotly colorscales first (both qualitative and sequential),
|
|
217
|
+
then falls back to Matplotlib colorscales.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
colorscale_name: Name of the colorscale
|
|
221
|
+
num_colors: Number of colors needed
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
List of color strings (hex format) if successful, None if colorscale not found
|
|
225
|
+
"""
|
|
226
|
+
# First try Plotly qualitative (discrete) color sequences
|
|
227
|
+
colorscale_title = colorscale_name.title()
|
|
228
|
+
if hasattr(px.colors.qualitative, colorscale_title):
|
|
229
|
+
color_list = getattr(px.colors.qualitative, colorscale_title)
|
|
230
|
+
# Convert to hex format for matplotlib compatibility
|
|
231
|
+
return [_rgb_string_to_hex(color_list[i % len(color_list)]) for i in range(num_colors)]
|
|
232
|
+
|
|
233
|
+
# Then try Plotly sequential/continuous colorscales
|
|
234
|
+
try:
|
|
235
|
+
colorscale = px.colors.get_colorscale(colorscale_name)
|
|
236
|
+
# Sample evenly from the colorscale
|
|
237
|
+
if num_colors == 1:
|
|
238
|
+
sample_points = [0.5]
|
|
239
|
+
else:
|
|
240
|
+
sample_points = [i / (num_colors - 1) for i in range(num_colors)]
|
|
241
|
+
colors = px.colors.sample_colorscale(colorscale, sample_points)
|
|
242
|
+
# Convert to hex format for matplotlib compatibility
|
|
243
|
+
return [_rgb_string_to_hex(c) for c in colors]
|
|
244
|
+
except (PlotlyError, ValueError):
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
# Finally try Matplotlib colorscales
|
|
248
|
+
try:
|
|
249
|
+
cmap = plt.get_cmap(colorscale_name)
|
|
250
|
+
|
|
251
|
+
# Sample evenly from the colorscale
|
|
252
|
+
if num_colors == 1:
|
|
253
|
+
colors = [cmap(0.5)]
|
|
254
|
+
else:
|
|
255
|
+
colors = [cmap(i / (num_colors - 1)) for i in range(num_colors)]
|
|
256
|
+
|
|
257
|
+
# Convert RGBA tuples to hex strings
|
|
258
|
+
return [mcolors.rgb2hex(color[:3]) for color in colors]
|
|
259
|
+
|
|
260
|
+
except (ValueError, KeyError):
|
|
261
|
+
return None
|
flixopt/components.py
CHANGED
|
@@ -1304,16 +1304,18 @@ class Sink(Component):
|
|
|
1304
1304
|
prevent_simultaneous_flow_rates: bool = False,
|
|
1305
1305
|
**kwargs,
|
|
1306
1306
|
):
|
|
1307
|
-
"""
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1307
|
+
"""Initialize a Sink (consumes flow from the system).
|
|
1308
|
+
|
|
1309
|
+
Supports legacy `sink=` keyword for backward compatibility (deprecated): if `sink` is provided
|
|
1310
|
+
it is used as the single input flow and a DeprecationWarning is issued; specifying both
|
|
1311
|
+
`inputs` and `sink` raises ValueError.
|
|
1312
|
+
|
|
1313
|
+
Args:
|
|
1314
|
+
label: Unique element label.
|
|
1315
|
+
inputs: Input flows for the sink.
|
|
1316
|
+
meta_data: Arbitrary metadata attached to the element.
|
|
1317
|
+
prevent_simultaneous_flow_rates: If True, prevents simultaneous nonzero flow rates
|
|
1318
|
+
across the element's inputs by wiring that restriction into the base Component setup.
|
|
1317
1319
|
|
|
1318
1320
|
Note:
|
|
1319
1321
|
The deprecated `sink` kwarg is accepted for compatibility but will be removed in future releases.
|
flixopt/config.py
CHANGED
|
@@ -8,7 +8,6 @@ from pathlib import Path
|
|
|
8
8
|
from types import MappingProxyType
|
|
9
9
|
from typing import Literal
|
|
10
10
|
|
|
11
|
-
import yaml
|
|
12
11
|
from rich.console import Console
|
|
13
12
|
from rich.logging import RichHandler
|
|
14
13
|
from rich.style import Style
|
|
@@ -54,6 +53,16 @@ _DEFAULTS = MappingProxyType(
|
|
|
54
53
|
'big_binary_bound': 100_000,
|
|
55
54
|
}
|
|
56
55
|
),
|
|
56
|
+
'plotting': MappingProxyType(
|
|
57
|
+
{
|
|
58
|
+
'default_show': True,
|
|
59
|
+
'default_engine': 'plotly',
|
|
60
|
+
'default_dpi': 300,
|
|
61
|
+
'default_facet_cols': 3,
|
|
62
|
+
'default_sequential_colorscale': 'turbo',
|
|
63
|
+
'default_qualitative_colorscale': 'plotly',
|
|
64
|
+
}
|
|
65
|
+
),
|
|
57
66
|
}
|
|
58
67
|
)
|
|
59
68
|
|
|
@@ -185,6 +194,42 @@ class CONFIG:
|
|
|
185
194
|
epsilon: float = _DEFAULTS['modeling']['epsilon']
|
|
186
195
|
big_binary_bound: int = _DEFAULTS['modeling']['big_binary_bound']
|
|
187
196
|
|
|
197
|
+
class Plotting:
|
|
198
|
+
"""Plotting configuration.
|
|
199
|
+
|
|
200
|
+
Configure backends via environment variables:
|
|
201
|
+
- Matplotlib: Set `MPLBACKEND` environment variable (e.g., 'Agg', 'TkAgg')
|
|
202
|
+
- Plotly: Set `PLOTLY_RENDERER` or use `plotly.io.renderers.default`
|
|
203
|
+
|
|
204
|
+
Attributes:
|
|
205
|
+
default_show: Default value for the `show` parameter in plot methods.
|
|
206
|
+
default_engine: Default plotting engine.
|
|
207
|
+
default_dpi: Default DPI for saved plots.
|
|
208
|
+
default_facet_cols: Default number of columns for faceted plots.
|
|
209
|
+
default_sequential_colorscale: Default colorscale for heatmaps and continuous data.
|
|
210
|
+
default_qualitative_colorscale: Default colormap for categorical plots (bar/line/area charts).
|
|
211
|
+
|
|
212
|
+
Examples:
|
|
213
|
+
```python
|
|
214
|
+
# Set consistent theming
|
|
215
|
+
CONFIG.Plotting.plotly_template = 'plotly_dark'
|
|
216
|
+
CONFIG.apply()
|
|
217
|
+
|
|
218
|
+
# Configure default export and color settings
|
|
219
|
+
CONFIG.Plotting.default_dpi = 600
|
|
220
|
+
CONFIG.Plotting.default_sequential_colorscale = 'plasma'
|
|
221
|
+
CONFIG.Plotting.default_qualitative_colorscale = 'Dark24'
|
|
222
|
+
CONFIG.apply()
|
|
223
|
+
```
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
default_show: bool = _DEFAULTS['plotting']['default_show']
|
|
227
|
+
default_engine: Literal['plotly', 'matplotlib'] = _DEFAULTS['plotting']['default_engine']
|
|
228
|
+
default_dpi: int = _DEFAULTS['plotting']['default_dpi']
|
|
229
|
+
default_facet_cols: int = _DEFAULTS['plotting']['default_facet_cols']
|
|
230
|
+
default_sequential_colorscale: str = _DEFAULTS['plotting']['default_sequential_colorscale']
|
|
231
|
+
default_qualitative_colorscale: str = _DEFAULTS['plotting']['default_qualitative_colorscale']
|
|
232
|
+
|
|
188
233
|
config_name: str = _DEFAULTS['config_name']
|
|
189
234
|
|
|
190
235
|
@classmethod
|
|
@@ -253,13 +298,15 @@ class CONFIG:
|
|
|
253
298
|
Raises:
|
|
254
299
|
FileNotFoundError: If the config file does not exist.
|
|
255
300
|
"""
|
|
301
|
+
# Import here to avoid circular import
|
|
302
|
+
from . import io as fx_io
|
|
303
|
+
|
|
256
304
|
config_path = Path(config_file)
|
|
257
305
|
if not config_path.exists():
|
|
258
306
|
raise FileNotFoundError(f'Config file not found: {config_file}')
|
|
259
307
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
cls._apply_config_dict(config_dict)
|
|
308
|
+
config_dict = fx_io.load_yaml(config_path)
|
|
309
|
+
cls._apply_config_dict(config_dict)
|
|
263
310
|
|
|
264
311
|
cls.apply()
|
|
265
312
|
|
|
@@ -319,6 +366,14 @@ class CONFIG:
|
|
|
319
366
|
'epsilon': cls.Modeling.epsilon,
|
|
320
367
|
'big_binary_bound': cls.Modeling.big_binary_bound,
|
|
321
368
|
},
|
|
369
|
+
'plotting': {
|
|
370
|
+
'default_show': cls.Plotting.default_show,
|
|
371
|
+
'default_engine': cls.Plotting.default_engine,
|
|
372
|
+
'default_dpi': cls.Plotting.default_dpi,
|
|
373
|
+
'default_facet_cols': cls.Plotting.default_facet_cols,
|
|
374
|
+
'default_sequential_colorscale': cls.Plotting.default_sequential_colorscale,
|
|
375
|
+
'default_qualitative_colorscale': cls.Plotting.default_qualitative_colorscale,
|
|
376
|
+
},
|
|
322
377
|
}
|
|
323
378
|
|
|
324
379
|
|
flixopt/effects.py
CHANGED
|
@@ -480,19 +480,17 @@ class EffectCollection:
|
|
|
480
480
|
def create_effect_values_dict(
|
|
481
481
|
self, effect_values_user: PeriodicEffectsUser | TemporalEffectsUser
|
|
482
482
|
) -> dict[str, Scalar | TemporalDataUser] | None:
|
|
483
|
-
"""
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
Returns
|
|
494
|
-
-------
|
|
495
|
-
dict or None
|
|
483
|
+
"""Converts effect values into a dictionary. If a scalar is provided, it is associated with a default effect type.
|
|
484
|
+
|
|
485
|
+
Examples:
|
|
486
|
+
```python
|
|
487
|
+
effect_values_user = 20 -> {'<standard_effect_label>': 20}
|
|
488
|
+
effect_values_user = {None: 20} -> {'<standard_effect_label>': 20}
|
|
489
|
+
effect_values_user = None -> None
|
|
490
|
+
effect_values_user = {'effect1': 20, 'effect2': 0.3} -> {'effect1': 20, 'effect2': 0.3}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
496
494
|
A dictionary keyed by effect label, or None if input is None.
|
|
497
495
|
Note: a standard effect must be defined when passing scalars or None labels.
|
|
498
496
|
"""
|
flixopt/flow_system.py
CHANGED
|
@@ -4,7 +4,6 @@ This module contains the FlowSystem class, which is used to collect instances of
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
import json
|
|
8
7
|
import logging
|
|
9
8
|
import warnings
|
|
10
9
|
from typing import TYPE_CHECKING, Any, Literal, Optional
|
|
@@ -13,6 +12,7 @@ import numpy as np
|
|
|
13
12
|
import pandas as pd
|
|
14
13
|
import xarray as xr
|
|
15
14
|
|
|
15
|
+
from .config import CONFIG
|
|
16
16
|
from .core import (
|
|
17
17
|
ConversionError,
|
|
18
18
|
DataConverter,
|
|
@@ -484,7 +484,7 @@ class FlowSystem(Interface):
|
|
|
484
484
|
| list[
|
|
485
485
|
Literal['nodes', 'edges', 'layout', 'interaction', 'manipulation', 'physics', 'selection', 'renderer']
|
|
486
486
|
] = True,
|
|
487
|
-
show: bool =
|
|
487
|
+
show: bool | None = None,
|
|
488
488
|
) -> pyvis.network.Network | None:
|
|
489
489
|
"""
|
|
490
490
|
Visualizes the network structure of a FlowSystem using PyVis, saving it as an interactive HTML file.
|
|
@@ -514,7 +514,9 @@ class FlowSystem(Interface):
|
|
|
514
514
|
from . import plotting
|
|
515
515
|
|
|
516
516
|
node_infos, edge_infos = self.network_infos()
|
|
517
|
-
return plotting.plot_network(
|
|
517
|
+
return plotting.plot_network(
|
|
518
|
+
node_infos, edge_infos, path, controls, show if show is not None else CONFIG.Plotting.default_show
|
|
519
|
+
)
|
|
518
520
|
|
|
519
521
|
def start_network_app(self):
|
|
520
522
|
"""Visualizes the network structure of a FlowSystem using Dash, Cytoscape, and networkx.
|
flixopt/interface.py
CHANGED
|
@@ -712,6 +712,8 @@ class InvestParameters(Interface):
|
|
|
712
712
|
Combinable with effects_of_investment and effects_of_investment_per_size.
|
|
713
713
|
effects_of_retirement: Costs incurred if NOT investing (demolition, penalties).
|
|
714
714
|
Dict: {'effect_name': value}.
|
|
715
|
+
linked_periods: Describes which periods are linked. 1 means linked, 0 means size=0. None means no linked periods.
|
|
716
|
+
For convenience, pass a tuple containing the first and last period (2025, 2039), linking them and those in between
|
|
715
717
|
|
|
716
718
|
Deprecated Args:
|
|
717
719
|
fix_effects: **Deprecated**. Use `effects_of_investment` instead.
|
|
@@ -724,7 +726,6 @@ class InvestParameters(Interface):
|
|
|
724
726
|
Will be removed in version 4.0.
|
|
725
727
|
optional: DEPRECATED. Use `mandatory` instead. Opposite of `mandatory`.
|
|
726
728
|
Will be removed in version 4.0.
|
|
727
|
-
linked_periods: Describes which periods are linked. 1 means linked, 0 means size=0. None means no linked periods.
|
|
728
729
|
|
|
729
730
|
Cost Annualization Requirements:
|
|
730
731
|
All cost values must be properly weighted to match the optimization model's time horizon.
|