flixopt 2.1.4__py3-none-any.whl → 2.1.6__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 +0 -1
- flixopt/components.py +130 -21
- flixopt/flow_system.py +53 -0
- flixopt/network_app.py +612 -0
- {flixopt-2.1.4.dist-info → flixopt-2.1.6.dist-info}/METADATA +23 -9
- {flixopt-2.1.4.dist-info → flixopt-2.1.6.dist-info}/RECORD +9 -8
- {flixopt-2.1.4.dist-info → flixopt-2.1.6.dist-info}/WHEEL +0 -0
- {flixopt-2.1.4.dist-info → flixopt-2.1.6.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.4.dist-info → flixopt-2.1.6.dist-info}/top_level.txt +0 -0
flixopt/aggregation.py
CHANGED
flixopt/components.py
CHANGED
|
@@ -3,6 +3,7 @@ This module contains the basic components of the flixopt framework.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
import warnings
|
|
6
7
|
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Set, Tuple, Union
|
|
7
8
|
|
|
8
9
|
import linopy
|
|
@@ -586,52 +587,160 @@ class SourceAndSink(Component):
|
|
|
586
587
|
def __init__(
|
|
587
588
|
self,
|
|
588
589
|
label: str,
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
590
|
+
inputs: List[Flow] = None,
|
|
591
|
+
outputs: List[Flow] = None,
|
|
592
|
+
prevent_simultaneous_flow_rates: bool = True,
|
|
592
593
|
meta_data: Optional[Dict] = None,
|
|
594
|
+
**kwargs,
|
|
593
595
|
):
|
|
594
596
|
"""
|
|
595
597
|
Args:
|
|
596
598
|
label: The label of the Element. Used to identify it in the FlowSystem
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
599
|
+
outputs: output-flows of this component
|
|
600
|
+
inputs: input-flows of this component
|
|
601
|
+
prevent_simultaneous_flow_rates: If True, inflow and outflow can not be active simultaniously.
|
|
600
602
|
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
601
603
|
"""
|
|
604
|
+
source = kwargs.pop('source', None)
|
|
605
|
+
sink = kwargs.pop('sink', None)
|
|
606
|
+
prevent_simultaneous_sink_and_source = kwargs.pop('prevent_simultaneous_sink_and_source', None)
|
|
607
|
+
if source is not None:
|
|
608
|
+
warnings.deprecated(
|
|
609
|
+
'The use of the source argument is deprecated. Use the outputs argument instead.',
|
|
610
|
+
stacklevel=2,
|
|
611
|
+
)
|
|
612
|
+
if outputs is not None:
|
|
613
|
+
raise ValueError('Either source or outputs can be specified, but not both.')
|
|
614
|
+
outputs = [source]
|
|
615
|
+
|
|
616
|
+
if sink is not None:
|
|
617
|
+
warnings.deprecated(
|
|
618
|
+
'The use of the sink argument is deprecated. Use the outputs argument instead.',
|
|
619
|
+
stacklevel=2,
|
|
620
|
+
)
|
|
621
|
+
if inputs is not None:
|
|
622
|
+
raise ValueError('Either sink or outputs can be specified, but not both.')
|
|
623
|
+
inputs = [sink]
|
|
624
|
+
|
|
625
|
+
if prevent_simultaneous_sink_and_source is not None:
|
|
626
|
+
warnings.deprecated(
|
|
627
|
+
'The use of the prevent_simultaneous_sink_and_source argument is deprecated. Use the prevent_simultaneous_flow_rates argument instead.',
|
|
628
|
+
stacklevel=2,
|
|
629
|
+
)
|
|
630
|
+
prevent_simultaneous_flow_rates = prevent_simultaneous_sink_and_source
|
|
631
|
+
|
|
602
632
|
super().__init__(
|
|
603
633
|
label,
|
|
604
|
-
inputs=
|
|
605
|
-
outputs=
|
|
606
|
-
prevent_simultaneous_flows=
|
|
634
|
+
inputs=inputs,
|
|
635
|
+
outputs=outputs,
|
|
636
|
+
prevent_simultaneous_flows=inputs + outputs if prevent_simultaneous_flow_rates is True else None,
|
|
607
637
|
meta_data=meta_data,
|
|
608
638
|
)
|
|
609
|
-
self.
|
|
610
|
-
|
|
611
|
-
|
|
639
|
+
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
640
|
+
|
|
641
|
+
@property
|
|
642
|
+
def source(self) -> Flow:
|
|
643
|
+
warnings.warn(
|
|
644
|
+
'The source property is deprecated. Use the outputs property instead.',
|
|
645
|
+
DeprecationWarning,
|
|
646
|
+
stacklevel=2,
|
|
647
|
+
)
|
|
648
|
+
return self.outputs[0]
|
|
649
|
+
|
|
650
|
+
@property
|
|
651
|
+
def sink(self) -> Flow:
|
|
652
|
+
warnings.warn(
|
|
653
|
+
'The sink property is deprecated. Use the outputs property instead.',
|
|
654
|
+
DeprecationWarning,
|
|
655
|
+
stacklevel=2,
|
|
656
|
+
)
|
|
657
|
+
return self.inputs[0]
|
|
658
|
+
|
|
659
|
+
@property
|
|
660
|
+
def prevent_simultaneous_sink_and_source(self) -> bool:
|
|
661
|
+
warnings.warn(
|
|
662
|
+
'The prevent_simultaneous_sink_and_source property is deprecated. Use the prevent_simultaneous_flow_rates property instead.',
|
|
663
|
+
DeprecationWarning,
|
|
664
|
+
stacklevel=2,
|
|
665
|
+
)
|
|
666
|
+
return self.prevent_simultaneous_flow_rates
|
|
612
667
|
|
|
613
668
|
|
|
614
669
|
@register_class_for_io
|
|
615
670
|
class Source(Component):
|
|
616
|
-
def __init__(
|
|
671
|
+
def __init__(
|
|
672
|
+
self,
|
|
673
|
+
label: str,
|
|
674
|
+
outputs: List[Flow] = None,
|
|
675
|
+
meta_data: Optional[Dict] = None,
|
|
676
|
+
prevent_simultaneous_flow_rates: bool = False,
|
|
677
|
+
**kwargs
|
|
678
|
+
):
|
|
617
679
|
"""
|
|
618
680
|
Args:
|
|
619
681
|
label: The label of the Element. Used to identify it in the FlowSystem
|
|
620
|
-
|
|
682
|
+
outputs: output-flows of source
|
|
621
683
|
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
622
684
|
"""
|
|
623
|
-
|
|
624
|
-
|
|
685
|
+
source = kwargs.pop('source', None)
|
|
686
|
+
if source is not None:
|
|
687
|
+
warnings.warn(
|
|
688
|
+
'The use of the source argument is deprecated. Use the outputs argument instead.',
|
|
689
|
+
DeprecationWarning,
|
|
690
|
+
stacklevel=2,
|
|
691
|
+
)
|
|
692
|
+
if outputs is not None:
|
|
693
|
+
raise ValueError('Either source or outputs can be specified, but not both.')
|
|
694
|
+
outputs = [source]
|
|
695
|
+
|
|
696
|
+
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
697
|
+
super().__init__(label, outputs=outputs, meta_data=meta_data, prevent_simultaneous_flows=outputs if prevent_simultaneous_flow_rates else None)
|
|
698
|
+
|
|
699
|
+
@property
|
|
700
|
+
def source(self) -> Flow:
|
|
701
|
+
warnings.warn(
|
|
702
|
+
'The source property is deprecated. Use the outputs property instead.',
|
|
703
|
+
DeprecationWarning,
|
|
704
|
+
stacklevel=2,
|
|
705
|
+
)
|
|
706
|
+
return self.outputs[0]
|
|
625
707
|
|
|
626
708
|
|
|
627
709
|
@register_class_for_io
|
|
628
710
|
class Sink(Component):
|
|
629
|
-
def __init__(
|
|
711
|
+
def __init__(
|
|
712
|
+
self,
|
|
713
|
+
label: str,
|
|
714
|
+
inputs: List[Flow] = None,
|
|
715
|
+
meta_data: Optional[Dict] = None,
|
|
716
|
+
prevent_simultaneous_flow_rates: bool = False,
|
|
717
|
+
**kwargs
|
|
718
|
+
):
|
|
630
719
|
"""
|
|
631
720
|
Args:
|
|
632
721
|
label: The label of the Element. Used to identify it in the FlowSystem
|
|
633
|
-
|
|
634
|
-
|
|
722
|
+
inputs: output-flows of source
|
|
723
|
+
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
635
724
|
"""
|
|
636
|
-
|
|
637
|
-
|
|
725
|
+
sink = kwargs.pop('sink', None)
|
|
726
|
+
if sink is not None:
|
|
727
|
+
warnings.warn(
|
|
728
|
+
'The use of the sink argument is deprecated. Use the outputs argument instead.',
|
|
729
|
+
DeprecationWarning,
|
|
730
|
+
stacklevel=2,
|
|
731
|
+
)
|
|
732
|
+
if inputs is not None:
|
|
733
|
+
raise ValueError('Either sink or outputs can be specified, but not both.')
|
|
734
|
+
inputs = [sink]
|
|
735
|
+
|
|
736
|
+
self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
|
|
737
|
+
super().__init__(label, inputs=inputs, meta_data=meta_data, prevent_simultaneous_flows=inputs if prevent_simultaneous_flow_rates else None)
|
|
738
|
+
|
|
739
|
+
@property
|
|
740
|
+
def sink(self) -> Flow:
|
|
741
|
+
warnings.warn(
|
|
742
|
+
'The sink property is deprecated. Use the outputs property instead.',
|
|
743
|
+
DeprecationWarning,
|
|
744
|
+
stacklevel=2,
|
|
745
|
+
)
|
|
746
|
+
return self.inputs[0]
|
flixopt/flow_system.py
CHANGED
|
@@ -61,6 +61,8 @@ class FlowSystem:
|
|
|
61
61
|
|
|
62
62
|
self._connected = False
|
|
63
63
|
|
|
64
|
+
self._network_app = None
|
|
65
|
+
|
|
64
66
|
@classmethod
|
|
65
67
|
def from_dataset(cls, ds: xr.Dataset):
|
|
66
68
|
timesteps_extra = pd.DatetimeIndex(ds.attrs['timesteps_extra'], name='time')
|
|
@@ -241,6 +243,57 @@ class FlowSystem:
|
|
|
241
243
|
node_infos, edge_infos = self.network_infos()
|
|
242
244
|
return plotting.plot_network(node_infos, edge_infos, path, controls, show)
|
|
243
245
|
|
|
246
|
+
def start_network_app(self):
|
|
247
|
+
"""Visualizes the network structure of a FlowSystem using Dash, Cytoscape, and networkx.
|
|
248
|
+
Requires optional dependencies: dash, dash-cytoscape, networkx, werkzeug.
|
|
249
|
+
"""
|
|
250
|
+
from .network_app import DASH_CYTOSCAPE_AVAILABLE, VISUALIZATION_ERROR, flow_graph, shownetwork
|
|
251
|
+
|
|
252
|
+
warnings.warn(
|
|
253
|
+
'The network visualization is still experimental and might change in the future.',
|
|
254
|
+
stacklevel=2,
|
|
255
|
+
category=UserWarning,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if not DASH_CYTOSCAPE_AVAILABLE:
|
|
259
|
+
raise ImportError(
|
|
260
|
+
f"Network visualization requires optional dependencies. "
|
|
261
|
+
f"Install with: pip install flixopt[viz], flixopt[full] or pip install dash dash_cytoscape networkx werkzeug. "
|
|
262
|
+
f"Original error: {VISUALIZATION_ERROR}"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if not self._connected:
|
|
266
|
+
self._connect_network()
|
|
267
|
+
|
|
268
|
+
if self._network_app is not None:
|
|
269
|
+
logger.warning('The network app is already running. Restarting it.')
|
|
270
|
+
self.stop_network_app()
|
|
271
|
+
|
|
272
|
+
self._network_app = shownetwork(flow_graph(self))
|
|
273
|
+
|
|
274
|
+
def stop_network_app(self):
|
|
275
|
+
"""Stop the network visualization server."""
|
|
276
|
+
from .network_app import DASH_CYTOSCAPE_AVAILABLE, VISUALIZATION_ERROR
|
|
277
|
+
if not DASH_CYTOSCAPE_AVAILABLE:
|
|
278
|
+
raise ImportError(
|
|
279
|
+
f'Network visualization requires optional dependencies. '
|
|
280
|
+
f'Install with: pip install flixopt[viz]. '
|
|
281
|
+
f'Original error: {VISUALIZATION_ERROR}'
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if self._network_app is None:
|
|
285
|
+
logger.warning('No network app is currently running. Cant stop it')
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
logger.info('Stopping network visualization server...')
|
|
290
|
+
self._network_app.server_instance.shutdown()
|
|
291
|
+
logger.info('Network visualization stopped.')
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.error(f'Failed to stop the network visualization app: {e}')
|
|
294
|
+
finally:
|
|
295
|
+
self._network_app = None
|
|
296
|
+
|
|
244
297
|
def network_infos(self) -> Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, str]]]:
|
|
245
298
|
if not self._connected:
|
|
246
299
|
self._connect_network()
|
flixopt/network_app.py
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import socket
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Any, Dict, List
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import dash_cytoscape as cyto
|
|
9
|
+
import dash_daq as daq
|
|
10
|
+
import networkx
|
|
11
|
+
from dash import Dash, Input, Output, State, callback_context, dcc, html, no_update
|
|
12
|
+
from werkzeug.serving import make_server
|
|
13
|
+
|
|
14
|
+
DASH_CYTOSCAPE_AVAILABLE = True
|
|
15
|
+
VISUALIZATION_ERROR = None
|
|
16
|
+
except ImportError as e:
|
|
17
|
+
DASH_CYTOSCAPE_AVAILABLE = False
|
|
18
|
+
VISUALIZATION_ERROR = str(e)
|
|
19
|
+
|
|
20
|
+
from .components import LinearConverter, Sink, Source, SourceAndSink, Storage
|
|
21
|
+
from .elements import Bus, Component, Flow
|
|
22
|
+
from .flow_system import FlowSystem
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger('flixopt')
|
|
25
|
+
|
|
26
|
+
# Configuration class for better organization
|
|
27
|
+
class VisualizationConfig:
|
|
28
|
+
"""Configuration constants for the visualization"""
|
|
29
|
+
|
|
30
|
+
DEFAULT_COLORS = {
|
|
31
|
+
'Bus': '#7F8C8D',
|
|
32
|
+
'Source': '#F1C40F',
|
|
33
|
+
'Sink': '#F1C40F',
|
|
34
|
+
'Storage': '#2980B9',
|
|
35
|
+
'Converter': '#D35400',
|
|
36
|
+
'Other': '#27AE60',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
COLOR_PRESETS = {
|
|
40
|
+
'Default': DEFAULT_COLORS,
|
|
41
|
+
'Vibrant': {
|
|
42
|
+
'Bus': '#FF6B6B', 'Source': '#4ECDC4', 'Sink': '#45B7D1',
|
|
43
|
+
'Storage': '#96CEB4', 'Converter': '#FFEAA7', 'Other': '#DDA0DD',
|
|
44
|
+
},
|
|
45
|
+
'Dark': {
|
|
46
|
+
'Bus': '#2C3E50', 'Source': '#34495E', 'Sink': '#7F8C8D',
|
|
47
|
+
'Storage': '#95A5A6', 'Converter': '#BDC3C7', 'Other': '#ECF0F1',
|
|
48
|
+
},
|
|
49
|
+
'Pastel': {
|
|
50
|
+
'Bus': '#FFB3BA', 'Source': '#BAFFC9', 'Sink': '#BAE1FF',
|
|
51
|
+
'Storage': '#FFFFBA', 'Converter': '#FFDFBA', 'Other': '#E0BBE4',
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
DEFAULT_STYLESHEET = [
|
|
56
|
+
{
|
|
57
|
+
'selector': 'node',
|
|
58
|
+
'style': {
|
|
59
|
+
'content': 'data(label)',
|
|
60
|
+
'background-color': 'data(color)',
|
|
61
|
+
'font-size': 10,
|
|
62
|
+
'color': 'white',
|
|
63
|
+
'text-valign': 'center',
|
|
64
|
+
'text-halign': 'center',
|
|
65
|
+
'width': '90px',
|
|
66
|
+
'height': '70px',
|
|
67
|
+
'shape': 'data(shape)',
|
|
68
|
+
'text-outline-color': 'black',
|
|
69
|
+
'text-outline-width': 0.5,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
'selector': '[shape = "custom-source"]',
|
|
74
|
+
'style': {
|
|
75
|
+
'shape': 'polygon',
|
|
76
|
+
'shape-polygon-points': '-0.5 0.5, 0.5 0.5, 1 -0.5, -1 -0.5',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
'selector': '[shape = "custom-sink"]',
|
|
81
|
+
'style': {
|
|
82
|
+
'shape': 'polygon',
|
|
83
|
+
'shape-polygon-points': '-0.5 -0.5, 0.5 -0.5, 1 0.5, -1 0.5',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
'selector': 'edge',
|
|
88
|
+
'style': {
|
|
89
|
+
'curve-style': 'straight',
|
|
90
|
+
'width': 2,
|
|
91
|
+
'line-color': 'gray',
|
|
92
|
+
'target-arrow-color': 'gray',
|
|
93
|
+
'target-arrow-shape': 'triangle',
|
|
94
|
+
'arrow-scale': 2,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
def flow_graph(flow_system: FlowSystem) -> networkx.DiGraph:
|
|
100
|
+
"""Convert FlowSystem to NetworkX graph - simplified and more robust"""
|
|
101
|
+
nodes = list(flow_system.components.values()) + list(flow_system.buses.values())
|
|
102
|
+
edges = list(flow_system.flows.values())
|
|
103
|
+
|
|
104
|
+
def get_element_type(element):
|
|
105
|
+
"""Determine element type for coloring"""
|
|
106
|
+
if isinstance(element, Bus):
|
|
107
|
+
return 'Bus'
|
|
108
|
+
elif isinstance(element, Source):
|
|
109
|
+
return 'Source'
|
|
110
|
+
elif isinstance(element, (Sink, SourceAndSink)):
|
|
111
|
+
return 'Sink'
|
|
112
|
+
elif isinstance(element, Storage):
|
|
113
|
+
return 'Storage'
|
|
114
|
+
elif isinstance(element, LinearConverter):
|
|
115
|
+
return 'Converter'
|
|
116
|
+
else:
|
|
117
|
+
return 'Other'
|
|
118
|
+
|
|
119
|
+
def get_shape(element):
|
|
120
|
+
"""Determine node shape"""
|
|
121
|
+
if isinstance(element, Bus):
|
|
122
|
+
return 'ellipse'
|
|
123
|
+
elif isinstance(element, Source):
|
|
124
|
+
return 'custom-source'
|
|
125
|
+
elif isinstance(element, (Sink, SourceAndSink)):
|
|
126
|
+
return 'custom-sink'
|
|
127
|
+
else:
|
|
128
|
+
return 'rectangle'
|
|
129
|
+
|
|
130
|
+
graph = networkx.DiGraph()
|
|
131
|
+
|
|
132
|
+
# Add nodes with attributes
|
|
133
|
+
for node in nodes:
|
|
134
|
+
graph.add_node(
|
|
135
|
+
node.label_full,
|
|
136
|
+
color=VisualizationConfig.DEFAULT_COLORS[get_element_type(node)],
|
|
137
|
+
shape=get_shape(node),
|
|
138
|
+
element_type=get_element_type(node),
|
|
139
|
+
parameters=str(node),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Add edges
|
|
143
|
+
for edge in edges:
|
|
144
|
+
try:
|
|
145
|
+
graph.add_edge(
|
|
146
|
+
u_of_edge=edge.bus if edge.is_input_in_component else edge.component,
|
|
147
|
+
v_of_edge=edge.component if edge.is_input_in_component else edge.bus,
|
|
148
|
+
label=edge.label_full,
|
|
149
|
+
parameters=edge.__str__().replace(')', '\n)'),
|
|
150
|
+
)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.error(f"Failed to add edge {edge}: {e}")
|
|
153
|
+
|
|
154
|
+
return graph
|
|
155
|
+
|
|
156
|
+
def make_cytoscape_elements(graph: networkx.DiGraph) -> List[Dict[str, Any]]:
|
|
157
|
+
"""Convert NetworkX graph to Cytoscape elements"""
|
|
158
|
+
elements = []
|
|
159
|
+
|
|
160
|
+
# Add nodes
|
|
161
|
+
for node_id in graph.nodes():
|
|
162
|
+
node_data = graph.nodes[node_id]
|
|
163
|
+
elements.append({
|
|
164
|
+
'data': {
|
|
165
|
+
'id': node_id,
|
|
166
|
+
'label': node_id,
|
|
167
|
+
'color': node_data.get('color', '#7F8C8D'),
|
|
168
|
+
'shape': node_data.get('shape', 'rectangle'),
|
|
169
|
+
'element_type': node_data.get('element_type', 'Other'),
|
|
170
|
+
'parameters': node_data.get('parameters', ''),
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
# Add edges
|
|
175
|
+
for u, v in graph.edges():
|
|
176
|
+
edge_data = graph.edges[u, v]
|
|
177
|
+
elements.append({
|
|
178
|
+
'data': {
|
|
179
|
+
'source': u,
|
|
180
|
+
'target': v,
|
|
181
|
+
'id': f'{u}-{v}',
|
|
182
|
+
'label': edge_data.get('label', ''),
|
|
183
|
+
'parameters': edge_data.get('parameters', ''),
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
return elements
|
|
188
|
+
|
|
189
|
+
def create_color_picker_input(label: str, input_id: str, default_color: str):
|
|
190
|
+
"""Create a compact color picker with DAQ ColorPicker"""
|
|
191
|
+
return html.Div([
|
|
192
|
+
html.Label(label, style={
|
|
193
|
+
'color': 'white', 'font-size': '12px', 'margin-bottom': '5px',
|
|
194
|
+
'display': 'block'
|
|
195
|
+
}),
|
|
196
|
+
daq.ColorPicker(
|
|
197
|
+
id=input_id,
|
|
198
|
+
label="",
|
|
199
|
+
value={'hex': default_color},
|
|
200
|
+
size=200,
|
|
201
|
+
theme={'dark': True},
|
|
202
|
+
style={'margin-bottom': '10px'}
|
|
203
|
+
),
|
|
204
|
+
])
|
|
205
|
+
|
|
206
|
+
def create_style_section(title: str, children: List):
|
|
207
|
+
"""Create a collapsible section for organizing controls"""
|
|
208
|
+
return html.Div([
|
|
209
|
+
html.H4(title, style={
|
|
210
|
+
'color': 'white', 'margin-bottom': '10px',
|
|
211
|
+
'border-bottom': '2px solid #3498DB', 'padding-bottom': '5px',
|
|
212
|
+
}),
|
|
213
|
+
html.Div(children, style={'margin-bottom': '20px'}),
|
|
214
|
+
])
|
|
215
|
+
|
|
216
|
+
def create_sidebar():
|
|
217
|
+
"""Create the main sidebar with improved organization"""
|
|
218
|
+
return html.Div([
|
|
219
|
+
html.Div([
|
|
220
|
+
html.H3('Style Controls', style={
|
|
221
|
+
'color': 'white', 'margin-bottom': '20px', 'text-align': 'center',
|
|
222
|
+
'border-bottom': '3px solid #9B59B6', 'padding-bottom': '10px',
|
|
223
|
+
}),
|
|
224
|
+
|
|
225
|
+
# Layout Section
|
|
226
|
+
create_style_section('Layout', [
|
|
227
|
+
dcc.Dropdown(
|
|
228
|
+
id='layout-dropdown',
|
|
229
|
+
options=[
|
|
230
|
+
{'label': 'Klay (horizontal)', 'value': 'klay'},
|
|
231
|
+
{'label': 'Dagre (vertical)', 'value': 'dagre'},
|
|
232
|
+
{'label': 'Breadthfirst', 'value': 'breadthfirst'},
|
|
233
|
+
{'label': 'Cose (force-directed)', 'value': 'cose'},
|
|
234
|
+
{'label': 'Grid', 'value': 'grid'},
|
|
235
|
+
{'label': 'Circle', 'value': 'circle'},
|
|
236
|
+
],
|
|
237
|
+
value='klay',
|
|
238
|
+
clearable=False,
|
|
239
|
+
style={'width': '100%'},
|
|
240
|
+
),
|
|
241
|
+
]),
|
|
242
|
+
|
|
243
|
+
# Color Scheme Section
|
|
244
|
+
create_style_section('Color Scheme', [
|
|
245
|
+
dcc.Dropdown(
|
|
246
|
+
id='color-scheme-dropdown',
|
|
247
|
+
options=[{'label': k, 'value': k} for k in VisualizationConfig.COLOR_PRESETS.keys()],
|
|
248
|
+
value='Default',
|
|
249
|
+
style={'width': '100%', 'margin-bottom': '10px'},
|
|
250
|
+
),
|
|
251
|
+
]),
|
|
252
|
+
|
|
253
|
+
# Color Pickers Section
|
|
254
|
+
create_style_section('Custom Colors', [
|
|
255
|
+
create_color_picker_input('Bus', 'bus-color-picker', '#7F8C8D'),
|
|
256
|
+
create_color_picker_input('Source', 'source-color-picker', '#F1C40F'),
|
|
257
|
+
create_color_picker_input('Sink', 'sink-color-picker', '#F1C40F'),
|
|
258
|
+
create_color_picker_input('Storage', 'storage-color-picker', '#2980B9'),
|
|
259
|
+
create_color_picker_input('Converter', 'converter-color-picker', '#D35400'),
|
|
260
|
+
create_color_picker_input('Edge', 'edge-color-picker', '#808080'),
|
|
261
|
+
]),
|
|
262
|
+
|
|
263
|
+
# Node Settings
|
|
264
|
+
create_style_section('Node Settings', [
|
|
265
|
+
html.Label('Size', style={'color': 'white', 'font-size': '12px'}),
|
|
266
|
+
dcc.Slider(
|
|
267
|
+
id='node-size-slider', min=50, max=150, step=10, value=90,
|
|
268
|
+
marks={i: {'label': str(i), 'style': {'color': 'white', 'font-size': '10px'}}
|
|
269
|
+
for i in range(50, 151, 25)},
|
|
270
|
+
tooltip={'placement': 'bottom', 'always_visible': True},
|
|
271
|
+
),
|
|
272
|
+
html.Br(),
|
|
273
|
+
html.Label('Font Size', style={'color': 'white', 'font-size': '12px'}),
|
|
274
|
+
dcc.Slider(
|
|
275
|
+
id='font-size-slider', min=8, max=20, step=1, value=10,
|
|
276
|
+
marks={i: {'label': str(i), 'style': {'color': 'white', 'font-size': '10px'}}
|
|
277
|
+
for i in range(8, 21, 2)},
|
|
278
|
+
tooltip={'placement': 'bottom', 'always_visible': True},
|
|
279
|
+
),
|
|
280
|
+
]),
|
|
281
|
+
|
|
282
|
+
# Reset Button
|
|
283
|
+
html.Div([
|
|
284
|
+
html.Button('Reset to Defaults', id='reset-btn', n_clicks=0, style={
|
|
285
|
+
'width': '100%', 'background-color': '#E74C3C', 'color': 'white',
|
|
286
|
+
'border': 'none', 'padding': '10px', 'border-radius': '5px',
|
|
287
|
+
'cursor': 'pointer', 'margin-top': '20px',
|
|
288
|
+
}),
|
|
289
|
+
]),
|
|
290
|
+
], id='sidebar-content', style={
|
|
291
|
+
'width': '280px', 'height': '100vh', 'background-color': '#2C3E50',
|
|
292
|
+
'padding': '20px', 'position': 'fixed', 'left': '0', 'top': '0',
|
|
293
|
+
'overflow-y': 'auto', 'border-right': '3px solid #34495E',
|
|
294
|
+
'box-shadow': '2px 0 5px rgba(0,0,0,0.1)', 'z-index': '999',
|
|
295
|
+
'transform': 'translateX(-100%)', 'transition': 'transform 0.3s ease',
|
|
296
|
+
})
|
|
297
|
+
])
|
|
298
|
+
|
|
299
|
+
def shownetwork(graph: networkx.DiGraph):
|
|
300
|
+
"""Main function to create and run the network visualization"""
|
|
301
|
+
if not DASH_CYTOSCAPE_AVAILABLE:
|
|
302
|
+
raise ImportError(f"Required packages not available: {VISUALIZATION_ERROR}")
|
|
303
|
+
|
|
304
|
+
app = Dash(__name__, suppress_callback_exceptions=True)
|
|
305
|
+
|
|
306
|
+
# Load extra layouts
|
|
307
|
+
cyto.load_extra_layouts()
|
|
308
|
+
|
|
309
|
+
# Create initial elements
|
|
310
|
+
elements = make_cytoscape_elements(graph)
|
|
311
|
+
|
|
312
|
+
# App Layout
|
|
313
|
+
app.layout = html.Div([
|
|
314
|
+
# Toggle button
|
|
315
|
+
html.Button('☰', id='toggle-sidebar', n_clicks=0, style={
|
|
316
|
+
'position': 'fixed', 'top': '20px', 'left': '20px', 'z-index': '1000',
|
|
317
|
+
'background-color': '#3498DB', 'color': 'white', 'border': 'none',
|
|
318
|
+
'padding': '10px 15px', 'border-radius': '5px', 'cursor': 'pointer',
|
|
319
|
+
'font-size': '18px', 'box-shadow': '0 2px 5px rgba(0,0,0,0.3)',
|
|
320
|
+
}),
|
|
321
|
+
|
|
322
|
+
# Data storage
|
|
323
|
+
dcc.Store(id='elements-store', data=elements),
|
|
324
|
+
|
|
325
|
+
# Sidebar
|
|
326
|
+
create_sidebar(),
|
|
327
|
+
|
|
328
|
+
# Main content
|
|
329
|
+
html.Div([
|
|
330
|
+
# Header
|
|
331
|
+
html.Div([
|
|
332
|
+
html.H2('Network Visualization', style={
|
|
333
|
+
'color': 'white', 'margin': '0', 'text-align': 'center'
|
|
334
|
+
}),
|
|
335
|
+
html.Button('Export PNG', id='export-btn', n_clicks=0, style={
|
|
336
|
+
'position': 'absolute', 'right': '20px', 'top': '15px',
|
|
337
|
+
'background-color': '#27AE60', 'color': 'white', 'border': 'none',
|
|
338
|
+
'padding': '10px 20px', 'border-radius': '5px', 'cursor': 'pointer',
|
|
339
|
+
}),
|
|
340
|
+
], style={
|
|
341
|
+
'background-color': '#34495E', 'padding': '15px 20px',
|
|
342
|
+
'position': 'relative', 'border-bottom': '2px solid #3498DB',
|
|
343
|
+
}),
|
|
344
|
+
|
|
345
|
+
# Cytoscape graph
|
|
346
|
+
cyto.Cytoscape(
|
|
347
|
+
id='cytoscape',
|
|
348
|
+
layout={'name': 'klay'},
|
|
349
|
+
style={'width': '100%', 'height': '70vh'},
|
|
350
|
+
elements=elements,
|
|
351
|
+
stylesheet=VisualizationConfig.DEFAULT_STYLESHEET,
|
|
352
|
+
),
|
|
353
|
+
|
|
354
|
+
# Info panel
|
|
355
|
+
html.Div([
|
|
356
|
+
html.H4('Element Information', style={
|
|
357
|
+
'color': 'white', 'margin': '0 0 10px 0',
|
|
358
|
+
'border-bottom': '2px solid #3498DB', 'padding-bottom': '5px',
|
|
359
|
+
}),
|
|
360
|
+
html.Div(id='info-panel', children=[
|
|
361
|
+
html.P('Click on a node or edge to see details.',
|
|
362
|
+
style={'color': '#95A5A6', 'font-style': 'italic'})
|
|
363
|
+
]),
|
|
364
|
+
], style={
|
|
365
|
+
'background-color': '#2C3E50', 'padding': '15px',
|
|
366
|
+
'height': '25vh', 'overflow-y': 'auto',
|
|
367
|
+
'border-top': '2px solid #34495E',
|
|
368
|
+
}),
|
|
369
|
+
], id='main-content', style={
|
|
370
|
+
'margin-left': '0', 'background-color': '#1A252F',
|
|
371
|
+
'min-height': '100vh', 'transition': 'margin-left 0.3s ease',
|
|
372
|
+
}),
|
|
373
|
+
])
|
|
374
|
+
|
|
375
|
+
# Callbacks
|
|
376
|
+
@app.callback(
|
|
377
|
+
[Output('sidebar-content', 'style'), Output('main-content', 'style')],
|
|
378
|
+
[Input('toggle-sidebar', 'n_clicks')]
|
|
379
|
+
)
|
|
380
|
+
def toggle_sidebar(n_clicks):
|
|
381
|
+
is_open = (n_clicks or 0) % 2 == 1
|
|
382
|
+
sidebar_transform = 'translateX(0)' if is_open else 'translateX(-100%)'
|
|
383
|
+
main_margin = '280px' if is_open else '0'
|
|
384
|
+
|
|
385
|
+
sidebar_style = {
|
|
386
|
+
'width': '280px', 'height': '100vh', 'background-color': '#2C3E50',
|
|
387
|
+
'padding': '20px', 'position': 'fixed', 'left': '0', 'top': '0',
|
|
388
|
+
'overflow-y': 'auto', 'border-right': '3px solid #34495E',
|
|
389
|
+
'box-shadow': '2px 0 5px rgba(0,0,0,0.1)', 'z-index': '999',
|
|
390
|
+
'transform': sidebar_transform, 'transition': 'transform 0.3s ease',
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
main_style = {
|
|
394
|
+
'margin-left': main_margin, 'background-color': '#1A252F',
|
|
395
|
+
'min-height': '100vh', 'transition': 'margin-left 0.3s ease',
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return sidebar_style, main_style
|
|
399
|
+
|
|
400
|
+
@app.callback(
|
|
401
|
+
[Output('cytoscape', 'elements'), Output('cytoscape', 'stylesheet')],
|
|
402
|
+
[
|
|
403
|
+
Input('color-scheme-dropdown', 'value'),
|
|
404
|
+
Input('bus-color-picker', 'value'),
|
|
405
|
+
Input('source-color-picker', 'value'),
|
|
406
|
+
Input('sink-color-picker', 'value'),
|
|
407
|
+
Input('storage-color-picker', 'value'),
|
|
408
|
+
Input('converter-color-picker', 'value'),
|
|
409
|
+
Input('edge-color-picker', 'value'),
|
|
410
|
+
Input('node-size-slider', 'value'),
|
|
411
|
+
Input('font-size-slider', 'value'),
|
|
412
|
+
],
|
|
413
|
+
[State('elements-store', 'data')]
|
|
414
|
+
)
|
|
415
|
+
def update_visualization(color_scheme, bus_color, source_color, sink_color,
|
|
416
|
+
storage_color, converter_color, edge_color,
|
|
417
|
+
node_size, font_size, stored_elements):
|
|
418
|
+
if not stored_elements:
|
|
419
|
+
return no_update, no_update
|
|
420
|
+
|
|
421
|
+
# Determine colors to use
|
|
422
|
+
if any(picker for picker in [bus_color, source_color, sink_color,
|
|
423
|
+
storage_color, converter_color, edge_color]):
|
|
424
|
+
# Use custom colors from pickers
|
|
425
|
+
colors = {
|
|
426
|
+
'Bus': bus_color.get('hex') if bus_color else '#7F8C8D',
|
|
427
|
+
'Source': source_color.get('hex') if source_color else '#F1C40F',
|
|
428
|
+
'Sink': sink_color.get('hex') if sink_color else '#F1C40F',
|
|
429
|
+
'Storage': storage_color.get('hex') if storage_color else '#2980B9',
|
|
430
|
+
'Converter': converter_color.get('hex') if converter_color else '#D35400',
|
|
431
|
+
'Other': '#27AE60',
|
|
432
|
+
}
|
|
433
|
+
else:
|
|
434
|
+
# Use preset scheme
|
|
435
|
+
colors = VisualizationConfig.COLOR_PRESETS.get(color_scheme,
|
|
436
|
+
VisualizationConfig.DEFAULT_COLORS)
|
|
437
|
+
|
|
438
|
+
# Update element colors
|
|
439
|
+
updated_elements = []
|
|
440
|
+
for element in stored_elements:
|
|
441
|
+
if 'data' in element and 'element_type' in element['data']:
|
|
442
|
+
element_copy = element.copy()
|
|
443
|
+
element_copy['data'] = element['data'].copy()
|
|
444
|
+
element_type = element_copy['data']['element_type']
|
|
445
|
+
if element_type in colors:
|
|
446
|
+
element_copy['data']['color'] = colors[element_type]
|
|
447
|
+
updated_elements.append(element_copy)
|
|
448
|
+
else:
|
|
449
|
+
updated_elements.append(element)
|
|
450
|
+
|
|
451
|
+
# Create stylesheet
|
|
452
|
+
edge_color_hex = edge_color.get('hex') if edge_color else 'gray'
|
|
453
|
+
stylesheet = [
|
|
454
|
+
{
|
|
455
|
+
'selector': 'node',
|
|
456
|
+
'style': {
|
|
457
|
+
'content': 'data(label)',
|
|
458
|
+
'background-color': 'data(color)',
|
|
459
|
+
'font-size': font_size or 10,
|
|
460
|
+
'color': 'white',
|
|
461
|
+
'text-valign': 'center',
|
|
462
|
+
'text-halign': 'center',
|
|
463
|
+
'width': f'{node_size or 90}px',
|
|
464
|
+
'height': f'{int((node_size or 90) * 0.8)}px',
|
|
465
|
+
'shape': 'data(shape)',
|
|
466
|
+
'text-outline-color': 'black',
|
|
467
|
+
'text-outline-width': 0.5,
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
'selector': '[shape = "custom-source"]',
|
|
472
|
+
'style': {
|
|
473
|
+
'shape': 'polygon',
|
|
474
|
+
'shape-polygon-points': '-0.5 0.5, 0.5 0.5, 1 -0.5, -1 -0.5',
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
'selector': '[shape = "custom-sink"]',
|
|
479
|
+
'style': {
|
|
480
|
+
'shape': 'polygon',
|
|
481
|
+
'shape-polygon-points': '-0.5 -0.5, 0.5 -0.5, 1 0.5, -1 0.5',
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
'selector': 'edge',
|
|
486
|
+
'style': {
|
|
487
|
+
'curve-style': 'straight',
|
|
488
|
+
'width': 2,
|
|
489
|
+
'line-color': edge_color_hex,
|
|
490
|
+
'target-arrow-color': edge_color_hex,
|
|
491
|
+
'target-arrow-shape': 'triangle',
|
|
492
|
+
'arrow-scale': 2,
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
]
|
|
496
|
+
|
|
497
|
+
return updated_elements, stylesheet
|
|
498
|
+
|
|
499
|
+
@app.callback(
|
|
500
|
+
Output('info-panel', 'children'),
|
|
501
|
+
[Input('cytoscape', 'tapNodeData'), Input('cytoscape', 'tapEdgeData')]
|
|
502
|
+
)
|
|
503
|
+
def display_element_info(node_data, edge_data):
|
|
504
|
+
ctx = callback_context
|
|
505
|
+
if not ctx.triggered:
|
|
506
|
+
return [html.P('Click on a node or edge to see details.',
|
|
507
|
+
style={'color': '#95A5A6', 'font-style': 'italic'})]
|
|
508
|
+
|
|
509
|
+
# Determine what was clicked
|
|
510
|
+
if ctx.triggered[0]['prop_id'] == 'cytoscape.tapNodeData' and node_data:
|
|
511
|
+
return [
|
|
512
|
+
html.H5(f"Node: {node_data.get('label', 'Unknown')}",
|
|
513
|
+
style={'color': 'white', 'margin-bottom': '10px'}),
|
|
514
|
+
html.P(f"Type: {node_data.get('element_type', 'Unknown')}",
|
|
515
|
+
style={'color': '#BDC3C7'}),
|
|
516
|
+
html.Pre(node_data.get('parameters', 'No parameters'),
|
|
517
|
+
style={'color': '#BDC3C7', 'font-size': '11px',
|
|
518
|
+
'white-space': 'pre-wrap'})
|
|
519
|
+
]
|
|
520
|
+
elif ctx.triggered[0]['prop_id'] == 'cytoscape.tapEdgeData' and edge_data:
|
|
521
|
+
return [
|
|
522
|
+
html.H5(f"Edge: {edge_data.get('label', 'Unknown')}",
|
|
523
|
+
style={'color': 'white', 'margin-bottom': '10px'}),
|
|
524
|
+
html.P(f"{edge_data.get('source', '')} → {edge_data.get('target', '')}",
|
|
525
|
+
style={'color': '#E67E22'}),
|
|
526
|
+
html.Pre(edge_data.get('parameters', 'No parameters'),
|
|
527
|
+
style={'color': '#BDC3C7', 'font-size': '11px',
|
|
528
|
+
'white-space': 'pre-wrap'})
|
|
529
|
+
]
|
|
530
|
+
|
|
531
|
+
return [html.P('Click on a node or edge to see details.',
|
|
532
|
+
style={'color': '#95A5A6', 'font-style': 'italic'})]
|
|
533
|
+
|
|
534
|
+
@app.callback(
|
|
535
|
+
Output('cytoscape', 'layout'),
|
|
536
|
+
Input('layout-dropdown', 'value')
|
|
537
|
+
)
|
|
538
|
+
def update_layout(selected_layout):
|
|
539
|
+
return {'name': selected_layout}
|
|
540
|
+
|
|
541
|
+
# Reset callback
|
|
542
|
+
@app.callback(
|
|
543
|
+
[
|
|
544
|
+
Output('color-scheme-dropdown', 'value'),
|
|
545
|
+
Output('bus-color-picker', 'value'),
|
|
546
|
+
Output('source-color-picker', 'value'),
|
|
547
|
+
Output('sink-color-picker', 'value'),
|
|
548
|
+
Output('storage-color-picker', 'value'),
|
|
549
|
+
Output('converter-color-picker', 'value'),
|
|
550
|
+
Output('edge-color-picker', 'value'),
|
|
551
|
+
Output('node-size-slider', 'value'),
|
|
552
|
+
Output('font-size-slider', 'value'),
|
|
553
|
+
Output('layout-dropdown', 'value'),
|
|
554
|
+
],
|
|
555
|
+
[Input('reset-btn', 'n_clicks')]
|
|
556
|
+
)
|
|
557
|
+
def reset_controls(n_clicks):
|
|
558
|
+
if n_clicks and n_clicks > 0:
|
|
559
|
+
return (
|
|
560
|
+
'Default', # color scheme
|
|
561
|
+
{'hex': '#7F8C8D'}, # bus
|
|
562
|
+
{'hex': '#F1C40F'}, # source
|
|
563
|
+
{'hex': '#F1C40F'}, # sink
|
|
564
|
+
{'hex': '#2980B9'}, # storage
|
|
565
|
+
{'hex': '#D35400'}, # converter
|
|
566
|
+
{'hex': '#808080'}, # edge
|
|
567
|
+
90, # node size
|
|
568
|
+
10, # font size
|
|
569
|
+
'klay', # layout
|
|
570
|
+
)
|
|
571
|
+
return no_update
|
|
572
|
+
|
|
573
|
+
# Export functionality
|
|
574
|
+
app.clientside_callback(
|
|
575
|
+
"""
|
|
576
|
+
function(n_clicks) {
|
|
577
|
+
if (n_clicks > 0 && window.cy) {
|
|
578
|
+
var png64 = window.cy.png({scale: 3, full: true});
|
|
579
|
+
var a = document.createElement('a');
|
|
580
|
+
a.href = png64;
|
|
581
|
+
a.download = 'network_visualization.png';
|
|
582
|
+
a.click();
|
|
583
|
+
}
|
|
584
|
+
return 'Export PNG';
|
|
585
|
+
}
|
|
586
|
+
""",
|
|
587
|
+
Output('export-btn', 'children'),
|
|
588
|
+
Input('export-btn', 'n_clicks'),
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# Start server
|
|
592
|
+
def find_free_port(start_port=8050, end_port=8100):
|
|
593
|
+
for port in range(start_port, end_port):
|
|
594
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
595
|
+
if s.connect_ex(('localhost', port)) != 0:
|
|
596
|
+
return port
|
|
597
|
+
raise Exception('No free port found')
|
|
598
|
+
|
|
599
|
+
port = find_free_port()
|
|
600
|
+
server = make_server('127.0.0.1', port, app.server)
|
|
601
|
+
|
|
602
|
+
# Start server in background thread
|
|
603
|
+
server_thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
604
|
+
server_thread.start()
|
|
605
|
+
|
|
606
|
+
print(f'Network visualization started on http://127.0.0.1:{port}/')
|
|
607
|
+
|
|
608
|
+
# Store server reference for cleanup
|
|
609
|
+
app.server_instance = server
|
|
610
|
+
app.port = port
|
|
611
|
+
|
|
612
|
+
return app
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flixopt
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.6
|
|
4
4
|
Summary: Vector based energy and material flow optimization framework in Python.
|
|
5
5
|
Author-email: "Chair of Building Energy Systems and Heat Supply, TU Dresden" <peter.stange@tu-dresden.de>, Felix Bumann <felixbumann387@gmail.com>, Felix Panitz <baumbude@googlemail.com>, Peter Stange <peter.stange@tu-dresden.de>
|
|
6
6
|
Maintainer-email: Felix Bumann <felixbumann387@gmail.com>, Peter Stange <peter.stange@tu-dresden.de>
|
|
@@ -18,13 +18,12 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Intended Audience :: Developers
|
|
19
19
|
Classifier: Intended Audience :: Science/Research
|
|
20
20
|
Classifier: Topic :: Scientific/Engineering
|
|
21
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
22
21
|
Requires-Python: >=3.10
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
License-File: LICENSE
|
|
25
24
|
Requires-Dist: numpy<3,>=1.21.5
|
|
26
25
|
Requires-Dist: pandas<3,>=2.0.0
|
|
27
|
-
Requires-Dist: linopy<0.6
|
|
26
|
+
Requires-Dist: linopy<0.5.6,>=0.5.1
|
|
28
27
|
Requires-Dist: netcdf4<2,>=1.6.1
|
|
29
28
|
Requires-Dist: PyYAML<7,>=6.0.0
|
|
30
29
|
Requires-Dist: rich>=13.0.0
|
|
@@ -32,6 +31,22 @@ Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
|
32
31
|
Requires-Dist: highspy>=1.5.3
|
|
33
32
|
Requires-Dist: matplotlib<4.0.0,>=3.5.2
|
|
34
33
|
Requires-Dist: plotly<6.0.0,>=5.15.0
|
|
34
|
+
Provides-Extra: network-viz
|
|
35
|
+
Requires-Dist: dash>=3.0.0; extra == "network-viz"
|
|
36
|
+
Requires-Dist: dash-cytoscape>=1.0.0; extra == "network-viz"
|
|
37
|
+
Requires-Dist: dash-daq>=0.6.0; extra == "network-viz"
|
|
38
|
+
Requires-Dist: networkx>=3.0.0; extra == "network-viz"
|
|
39
|
+
Requires-Dist: werkzeug>=3.0.0; extra == "network-viz"
|
|
40
|
+
Provides-Extra: full
|
|
41
|
+
Requires-Dist: pyvis==0.3.1; extra == "full"
|
|
42
|
+
Requires-Dist: tsam<3.0.0,>=2.3.1; extra == "full"
|
|
43
|
+
Requires-Dist: scipy<2.0.0,>=1.15.1; extra == "full"
|
|
44
|
+
Requires-Dist: gurobipy>=10.0.0; extra == "full"
|
|
45
|
+
Requires-Dist: dash>=3.0.0; extra == "full"
|
|
46
|
+
Requires-Dist: dash-cytoscape>=1.0.0; extra == "full"
|
|
47
|
+
Requires-Dist: dash-daq>=0.6.0; extra == "full"
|
|
48
|
+
Requires-Dist: networkx>=3.0.0; extra == "full"
|
|
49
|
+
Requires-Dist: werkzeug>=3.0.0; extra == "full"
|
|
35
50
|
Provides-Extra: dev
|
|
36
51
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
37
52
|
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
@@ -39,12 +54,11 @@ Requires-Dist: pyvis==0.3.1; extra == "dev"
|
|
|
39
54
|
Requires-Dist: tsam<3.0.0,>=2.3.1; extra == "dev"
|
|
40
55
|
Requires-Dist: scipy<2.0.0,>=1.15.1; extra == "dev"
|
|
41
56
|
Requires-Dist: gurobipy>=10.0.0; extra == "dev"
|
|
42
|
-
|
|
43
|
-
Requires-Dist:
|
|
44
|
-
Requires-Dist:
|
|
45
|
-
Requires-Dist:
|
|
46
|
-
Requires-Dist:
|
|
47
|
-
Requires-Dist: gurobipy>=10.0.0; extra == "full"
|
|
57
|
+
Requires-Dist: dash>=3.0.0; extra == "dev"
|
|
58
|
+
Requires-Dist: dash-cytoscape>=1.0.0; extra == "dev"
|
|
59
|
+
Requires-Dist: dash-daq>=0.6.0; extra == "dev"
|
|
60
|
+
Requires-Dist: networkx>=3.0.0; extra == "dev"
|
|
61
|
+
Requires-Dist: werkzeug>=3.0.0; extra == "dev"
|
|
48
62
|
Provides-Extra: docs
|
|
49
63
|
Requires-Dist: mkdocs-material<10,>=9.0.0; extra == "docs"
|
|
50
64
|
Requires-Dist: mkdocstrings-python>=1.0.0; extra == "docs"
|
|
@@ -19,26 +19,27 @@ docs/user-guide/Mathematical Notation/Storage.md,sha256=PeNzk77i-81VX8I5r3zen3ka
|
|
|
19
19
|
docs/user-guide/Mathematical Notation/index.md,sha256=gkglBsoARhgvppXN9PgdJF33sCSnwGY7MtKDtCC32bE,1255
|
|
20
20
|
docs/user-guide/Mathematical Notation/others.md,sha256=wOUsfspAoSNTMlTNipeQ8ohoVVX2S-eI3dmlzqqrbR8,47
|
|
21
21
|
flixopt/__init__.py,sha256=F49OK5QLUnMGmsaKQ-G0dXsVuKr9Ow_pjM4KMSNZ918,614
|
|
22
|
-
flixopt/aggregation.py,sha256=
|
|
22
|
+
flixopt/aggregation.py,sha256=xgQu2U5YEbtdDAEMjWiuP9uo_KjhzC95VNmY4ZcSX3I,16939
|
|
23
23
|
flixopt/calculation.py,sha256=1Hs9dc6eqdJoHT6Dd3NlwdRORFO2vKdKx38o95FPxJE,20016
|
|
24
24
|
flixopt/commons.py,sha256=ZNlUN1z-h9OGHPo-s-n5OLlJaoPZKVGcAdRyGKpMk4M,1256
|
|
25
|
-
flixopt/components.py,sha256=
|
|
25
|
+
flixopt/components.py,sha256=4M1WR4JasMXiOUo4JpJfgPuMY5H0RbazhTAXAf8IWI0,33177
|
|
26
26
|
flixopt/config.py,sha256=Kt8QYk7hX5qHcQUtfgjM862C6SQr4K2lDvtk_LLER8Y,9085
|
|
27
27
|
flixopt/config.yaml,sha256=imzAnnhcJhIfKNTTXFB5Td7Pvk5ARn5j720k-oGGRug,392
|
|
28
28
|
flixopt/core.py,sha256=dBdAzA3khIe64aVGpPj3G5PzOr7RdGdSymnV3xWgaR8,38083
|
|
29
29
|
flixopt/effects.py,sha256=TKpUfUo0wbX5y5HS9U8HcDNOiygg0R7k9V3TM0G6uL4,16650
|
|
30
30
|
flixopt/elements.py,sha256=9P2uB3twwADf48Gx1xCluE-ZJCkzw0X7tYrtKEQOjk8,26932
|
|
31
31
|
flixopt/features.py,sha256=sEtdj7BpaYS9a0XdhRUtdDFXWLdaGABRXdi5JOoLPb0,43919
|
|
32
|
-
flixopt/flow_system.py,sha256=
|
|
32
|
+
flixopt/flow_system.py,sha256=x7wgbBhftIarB7w1lNzSkDY6b1IYzg6O7rexsAym1es,19606
|
|
33
33
|
flixopt/interface.py,sha256=uXf6Z29OfHpIRsS1-oZZ6SSuy8FLe13FjtqzHPqzzQE,12088
|
|
34
34
|
flixopt/io.py,sha256=2QKdtu2-mkzSGBIqHtUcF9UaG32nq9qcIRxZghf1hLw,11284
|
|
35
35
|
flixopt/linear_converters.py,sha256=ej5V_ML_3m1k9HbDnuey6pHEpQtguYkxBXHxWyE9sq0,10936
|
|
36
|
+
flixopt/network_app.py,sha256=Xyb4iLm13BFlIPhkL6RE2x8n5jgAbXMiN8mTMr-_-nM,23459
|
|
36
37
|
flixopt/plotting.py,sha256=wUwBSQxxwy1uui-mi2hgj6h__O6EvxCnocIbX0ewpMk,54111
|
|
37
38
|
flixopt/results.py,sha256=GKSZmz0GCuJwspTC8Ot6MOKinvy_mhnDXCafb_f7uVY,35161
|
|
38
39
|
flixopt/solvers.py,sha256=k1bSoiXec3asWED70-erXkgtpn2C8KRBfSZj0FLviSM,2436
|
|
39
40
|
flixopt/structure.py,sha256=QS0IFBHzdEMKYTgd6uosudhoDD4X0JcdF7LlS-XRacs,26295
|
|
40
41
|
flixopt/utils.py,sha256=f-_vFDvvG27-c_VMpzkv3lb79Yny4rvoSmemushbzhU,1687
|
|
41
|
-
flixopt-2.1.
|
|
42
|
+
flixopt-2.1.6.dist-info/licenses/LICENSE,sha256=HKsZnbrM_3Rvnr_u9cWSG90cBsj5_slaqI_z_qcxnGI,1118
|
|
42
43
|
pics/architecture_flixOpt-pre2.0.0.png,sha256=9RWSA3vys588aadr2437zor-_-xBTQNQ0bAf8xGcu5g,70605
|
|
43
44
|
pics/architecture_flixOpt.png,sha256=KjN1bJwESbkHmTW7UsJ7dZyiKZlTO7Dx20dg8KlR1HU,260219
|
|
44
45
|
pics/flixOpt_plotting.jpg,sha256=zn7ZPAtXm5eRTxtOj86e4-PPhHpCar1jqGh7vMBgQGY,518862
|
|
@@ -47,7 +48,7 @@ pics/pics.pptx,sha256=ImWeGGvjtWJ6BGruipsnZYmWtHj5sWdbw1NSFePbkC8,683344
|
|
|
47
48
|
scripts/extract_release_notes.py,sha256=0v8B9c6VXz55PJ0I08W7FdxGp7jEY5NOkrqMY6MNdYU,1249
|
|
48
49
|
scripts/gen_ref_pages.py,sha256=AYRtXyz78x5I_Hn0oRtGVbTxgLLj2QNyRX6vWRefPjc,1960
|
|
49
50
|
tests/ressources/Zeitreihen2020.csv,sha256=kbsDTKZS0iUsNZAS7m3DohzZI_OHHWe44s3GwLvcTLw,1918412
|
|
50
|
-
flixopt-2.1.
|
|
51
|
-
flixopt-2.1.
|
|
52
|
-
flixopt-2.1.
|
|
53
|
-
flixopt-2.1.
|
|
51
|
+
flixopt-2.1.6.dist-info/METADATA,sha256=drw11yScM5qaROW3pGcs_6S55msL0DiC3uzzppHDwpQ,8019
|
|
52
|
+
flixopt-2.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
53
|
+
flixopt-2.1.6.dist-info/top_level.txt,sha256=DEuo4R1z7GmEp5R3pjbQEJbaPRjKHFvNX2ceiBnVOL0,32
|
|
54
|
+
flixopt-2.1.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|