ExpertOp4Grid 0.3.2__tar.gz → 0.3.2.post3__tar.gz
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.
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/ExpertOp4Grid.egg-info/PKG-INFO +2 -2
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/PKG-INFO +2 -2
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/overflow_graph.py +46 -7
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/interactive_html.py +69 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_interactive_html.py +30 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_overflow_graph.py +118 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/setup.py +2 -2
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/ExpertOp4Grid.egg-info/SOURCES.txt +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/ExpertOp4Grid.egg-info/dependency_links.txt +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/ExpertOp4Grid.egg-info/entry_points.txt +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/ExpertOp4Grid.egg-info/not-zip-safe +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/ExpertOp4Grid.egg-info/requires.txt +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/ExpertOp4Grid.egg-info/top_level.txt +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/LICENSE +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/MANIFEST.in +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/README.md +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/Expert_rule_action_verification.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/__init__.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/__init__.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/alphadeesp.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/elements.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/__init__.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/constants.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/constrained_path.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/graph_consolidation.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/graph_utils.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/null_flow.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/null_flow_graph.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/power_flow_graph.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/shortest_paths.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/structured_overload_graph.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphsAndPaths.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/grid2op/Grid2opObservationLoader.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/grid2op/Grid2opSimulation.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/grid2op/__init__.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/grid2op/scoring.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/network.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/printer.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/pypownet/PypownetObservationLoader.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/pypownet/PypownetSimulation.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/pypownet/__init__.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/simulation.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/topo_applicator.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/topology_scorer.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/twin_nodes.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/expert_operator.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/main.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/config/config.ini +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/datetimes.csv +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/hazards.zip +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/imaps.csv +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/load_p.zip +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/load_p_forecasted.zip +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/load_q.zip +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/load_q_forecasted.zip +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/maintenance.zip +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/prod_p.zip +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/prod_p_forecasted.zip +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/prods_p.csv +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/chronics/a/simu_ids.csv +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/config.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/difficulty_levels.json +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/grid.json +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/grid_layout.json +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/ressources/parameters/l2rpn_2019/prods_charac.csv +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/__init__.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/alphadeesp_test.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/graphs_test_helpers.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/grid2op/__init__.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/grid2op/grid2op_grid_test.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/grid2op/test_integrations.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/pypownet/__init__.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/pypownet/grid_test.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/pypownet/test_integrations.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_alphadeesp_unit.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_cli.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_constrained_path.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_expert_op.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_expert_rules.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_graph_utils.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_graphs_package.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_null_flow.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_score_helpers.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_shortest_paths.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_topo_applicator.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/test_topology_scorer.py +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/docs/DESCRIPTION.rst +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/docs/DETAILS.rst +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/docs/GETTING_STARTED.rst +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/docs/INSTALL.rst +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/docs/README.rst +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/docs/index.rst +0 -0
- {expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/setup.cfg +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ExpertOp4Grid
|
|
3
|
-
Version: 0.3.2
|
|
3
|
+
Version: 0.3.2.post3
|
|
4
4
|
Summary: Expert analysis algorithm for solving overloads in a powergrid
|
|
5
5
|
Home-page: https://github.com/marota/ExpertOp4Grid/
|
|
6
|
-
Download-URL: https://github.com/marota/ExpertOp4Grid/archive/refs/tags/v0.3.2.tar.gz
|
|
6
|
+
Download-URL: https://github.com/marota/ExpertOp4Grid/archive/refs/tags/v0.3.2.post3.tar.gz
|
|
7
7
|
Author: Antoine Marot
|
|
8
8
|
Author-email: antoine.marot@rte-france.com
|
|
9
9
|
License: Mozilla Public License 2.0 (MPL 2.0)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ExpertOp4Grid
|
|
3
|
-
Version: 0.3.2
|
|
3
|
+
Version: 0.3.2.post3
|
|
4
4
|
Summary: Expert analysis algorithm for solving overloads in a powergrid
|
|
5
5
|
Home-page: https://github.com/marota/ExpertOp4Grid/
|
|
6
|
-
Download-URL: https://github.com/marota/ExpertOp4Grid/archive/refs/tags/v0.3.2.tar.gz
|
|
6
|
+
Download-URL: https://github.com/marota/ExpertOp4Grid/archive/refs/tags/v0.3.2.post3.tar.gz
|
|
7
7
|
Author: Antoine Marot
|
|
8
8
|
Author-email: antoine.marot@rte-france.com
|
|
9
9
|
License: Mozilla Public License 2.0 (MPL 2.0)
|
|
@@ -41,6 +41,7 @@ class OverFlowGraph(NullFlowGraphMixin, GraphConsolidationMixin, PowerFlowGraph)
|
|
|
41
41
|
df_overflow: pd.DataFrame,
|
|
42
42
|
layout: Optional[List[Tuple[float, float]]] = None,
|
|
43
43
|
float_precision: str = "%.2f",
|
|
44
|
+
extra_lines_to_cut: Optional[Iterable[int]] = None,
|
|
44
45
|
) -> None:
|
|
45
46
|
if "line_name" not in df_overflow.columns:
|
|
46
47
|
df_overflow["line_name"] = [
|
|
@@ -49,6 +50,16 @@ class OverFlowGraph(NullFlowGraphMixin, GraphConsolidationMixin, PowerFlowGraph)
|
|
|
49
50
|
]
|
|
50
51
|
|
|
51
52
|
self.df = df_overflow
|
|
53
|
+
# Subset of ``lines_to_cut`` that the caller wants the cut-analysis
|
|
54
|
+
# to treat like overloads (so they get the same black/constrained
|
|
55
|
+
# styling and feed the structured-overload graph the same way) but
|
|
56
|
+
# WITHOUT being classified as overloads in the viewer's
|
|
57
|
+
# ``Overloads`` layer / ``is_overload`` flag. Used by callers who
|
|
58
|
+
# want the recommender to find actions that prevent flow increase
|
|
59
|
+
# on otherwise-healthy lines (ExpertAgent's ``additionalLinesToCut``
|
|
60
|
+
# semantic). ``None`` / empty means "no extras" — every cut line
|
|
61
|
+
# is a true overload, preserving the legacy behaviour.
|
|
62
|
+
self.extra_lines_cut = set(extra_lines_to_cut or [])
|
|
52
63
|
super().__init__(topo, lines_to_cut, layout, float_precision)
|
|
53
64
|
|
|
54
65
|
def build_graph(self) -> None:
|
|
@@ -74,14 +85,23 @@ class OverFlowGraph(NullFlowGraphMixin, GraphConsolidationMixin, PowerFlowGraph)
|
|
|
74
85
|
)
|
|
75
86
|
|
|
76
87
|
cols = ("idx_or", "idx_ex", "delta_flows", "gray_edges", "line_name")
|
|
88
|
+
# Operator-selected extras must NOT be coloured black: black is the
|
|
89
|
+
# visual signal for "overload contingency line" used by both the
|
|
90
|
+
# ``Overloads`` layer and the structured-overload analyser. We
|
|
91
|
+
# therefore strip extras from the cut list passed to ``_edge_color``
|
|
92
|
+
# so they keep their natural flow polarity colour (coral / blue).
|
|
93
|
+
# They stay marked ``is_constrained`` and ``is_extra_cut`` so the
|
|
94
|
+
# downstream layers can still find them by flag.
|
|
95
|
+
cut_for_colour = [idx for idx in lines_to_cut if idx not in self.extra_lines_cut]
|
|
77
96
|
for i, (origin, extremity, reported_flow, gray_edge, line_name) in enumerate(
|
|
78
97
|
zip(*(self.df[c] for c in cols))):
|
|
79
98
|
self._add_overflow_edge(
|
|
80
99
|
g, origin, extremity, reported_flow, line_name,
|
|
81
|
-
color=self._edge_color(i, reported_flow, gray_edge,
|
|
100
|
+
color=self._edge_color(i, reported_flow, gray_edge, cut_for_colour),
|
|
82
101
|
scaling_factor=scaling_factor,
|
|
83
102
|
min_penwidth=min_penwidth,
|
|
84
|
-
is_constrained=(i in lines_to_cut)
|
|
103
|
+
is_constrained=(i in lines_to_cut),
|
|
104
|
+
is_extra_cut=(i in self.extra_lines_cut))
|
|
85
105
|
|
|
86
106
|
@staticmethod
|
|
87
107
|
def _edge_color(
|
|
@@ -105,6 +125,7 @@ class OverFlowGraph(NullFlowGraphMixin, GraphConsolidationMixin, PowerFlowGraph)
|
|
|
105
125
|
scaling_factor: float,
|
|
106
126
|
min_penwidth: float,
|
|
107
127
|
is_constrained: bool,
|
|
128
|
+
is_extra_cut: bool = False,
|
|
108
129
|
) -> None:
|
|
109
130
|
"""Add a single styled overflow edge to g."""
|
|
110
131
|
fp = self.float_precision
|
|
@@ -119,6 +140,12 @@ class OverFlowGraph(NullFlowGraphMixin, GraphConsolidationMixin, PowerFlowGraph)
|
|
|
119
140
|
}
|
|
120
141
|
if is_constrained:
|
|
121
142
|
attrs["constrained"] = True
|
|
143
|
+
if is_extra_cut:
|
|
144
|
+
# Operator-supplied extra cut — gets the black/constrained
|
|
145
|
+
# styling like a real overload but the viewer's "Overloads"
|
|
146
|
+
# layer must skip it. ``highlight_significant_line_loading``
|
|
147
|
+
# respects this flag when stamping ``is_overload``.
|
|
148
|
+
attrs["is_extra_cut"] = True
|
|
122
149
|
g.add_edge(origin, extremity, **attrs)
|
|
123
150
|
|
|
124
151
|
def keep_overloads_components(self) -> None:
|
|
@@ -191,6 +218,7 @@ class OverFlowGraph(NullFlowGraphMixin, GraphConsolidationMixin, PowerFlowGraph)
|
|
|
191
218
|
edge_names = nx.get_edge_attributes(self.g, "name")
|
|
192
219
|
edge_colors = nx.get_edge_attributes(self.g, "color")
|
|
193
220
|
edge_x_labels = nx.get_edge_attributes(self.g, "label")
|
|
221
|
+
edge_extra_cut = nx.get_edge_attributes(self.g, "is_extra_cut")
|
|
194
222
|
label_font_color = {edge: "black" for edge in edge_names.keys()}
|
|
195
223
|
color_label_highlight = "darkred"
|
|
196
224
|
|
|
@@ -204,18 +232,29 @@ class OverFlowGraph(NullFlowGraphMixin, GraphConsolidationMixin, PowerFlowGraph)
|
|
|
204
232
|
current_edge_color = edge_colors[edge]
|
|
205
233
|
before = dict_line_loading[edge_name]["before"]
|
|
206
234
|
after = dict_line_loading[edge_name]["after"]
|
|
235
|
+
is_extra = bool(edge_extra_cut.get(edge, False))
|
|
207
236
|
|
|
208
237
|
# Every entry in dict_line_loading is a monitored / low-
|
|
209
238
|
# margin line; the black ones are additionally overloads.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
# Operator-selected extras (``is_extra_cut``) are kept out
|
|
240
|
+
# of both flags so the viewer's ``Overloads`` and
|
|
241
|
+
# ``Low margin lines`` layers reflect the recommender's
|
|
242
|
+
# detected state, not user-supplied targets.
|
|
243
|
+
if not is_extra:
|
|
244
|
+
is_monitored_attrs[edge] = True
|
|
245
|
+
if current_edge_color == "black":
|
|
246
|
+
edge_x_labels[edge] = f'< {current_x_label} <BR/> <B>{before}%</B> → {after}%>'
|
|
247
|
+
is_overload_attrs[edge] = True
|
|
248
|
+
else:
|
|
249
|
+
edge_x_labels[edge] = f'< {current_x_label} <BR/> {before}% → <B>{after}%</B> >'
|
|
250
|
+
edge_colors[edge] = f'"{current_edge_color}:yellow:{current_edge_color}"'
|
|
214
251
|
else:
|
|
252
|
+
# Extras keep their natural flow colour; only the
|
|
253
|
+
# ``before → 0%`` annotation surfaces the cut so the
|
|
254
|
+
# operator sees how their choice materialises.
|
|
215
255
|
edge_x_labels[edge] = f'< {current_x_label} <BR/> {before}% → <B>{after}%</B> >'
|
|
216
256
|
|
|
217
257
|
label_font_color[edge] = color_label_highlight
|
|
218
|
-
edge_colors[edge] = f'"{current_edge_color}:yellow:{current_edge_color}"'
|
|
219
258
|
|
|
220
259
|
nx.set_edge_attributes(self.g, edge_x_labels, "label")
|
|
221
260
|
nx.set_edge_attributes(self.g, label_font_color, "fontcolor")
|
|
@@ -73,9 +73,34 @@ _SEMANTIC_LAYERS: List[Dict[str, str]] = [
|
|
|
73
73
|
{"key": "in_red_loop", "label": "Red-loop paths", "swatch": "red-loop", "scope": "both"},
|
|
74
74
|
{"key": "is_overload", "label": "Overloads", "swatch": "overload", "scope": "edge"},
|
|
75
75
|
{"key": "is_monitored", "label": "Low margin lines", "swatch": "monitored", "scope": "edge"},
|
|
76
|
+
# Operator-supplied extras (ExpertAgent's `additionalLinesToCut`):
|
|
77
|
+
# cut in the analysis like overloads but rendered with their
|
|
78
|
+
# natural flow colour and excluded from the Overloads /
|
|
79
|
+
# Low margin lines layers. Surfaced as a dedicated layer so the
|
|
80
|
+
# operator can still see how their choice materialised.
|
|
81
|
+
{"key": "is_extra_cut", "label": "Extra lines to prevent flow increase", "swatch": "extra-cut", "scope": "edge"},
|
|
76
82
|
{"key": "is_hub", "label": "Hubs", "swatch": "diamond", "scope": "node"},
|
|
77
83
|
]
|
|
78
84
|
|
|
85
|
+
# Threshold below which a node's ``value`` (prod − load, in MW) is
|
|
86
|
+
# treated as "no real prod/load here". Build-time conventions in the
|
|
87
|
+
# upstream simulators tag every node with ``prod_or_load="load"`` and
|
|
88
|
+
# ``value="0.0"`` even when no consumption exists, so a strict
|
|
89
|
+
# ``prod_or_load == "load"`` test would flood the layer with empty
|
|
90
|
+
# nodes. The 1 MW floor matches operator practice.
|
|
91
|
+
_PROD_LOAD_VALUE_FLOOR_MW = 1.0
|
|
92
|
+
|
|
93
|
+
# Per-kind config for the value-based node layers. Matched against the
|
|
94
|
+
# ``prod_or_load`` attribute set by ``build_nodes`` upstream
|
|
95
|
+
# (alphaDeesp/core/graphs/power_flow_graph.py and the simulator-specific
|
|
96
|
+
# build_nodes_v2 helpers). Each entry produces a single layer in the
|
|
97
|
+
# "Individual entities properties" section, populated only with the
|
|
98
|
+
# nodes whose absolute ``value`` clears ``_PROD_LOAD_VALUE_FLOOR_MW``.
|
|
99
|
+
_VALUE_NODE_LAYERS: List[Dict[str, str]] = [
|
|
100
|
+
{"key": "prod", "label": "Production nodes", "swatch": "prod-node"},
|
|
101
|
+
{"key": "load", "label": "Consumption nodes", "swatch": "load-node"},
|
|
102
|
+
]
|
|
103
|
+
|
|
79
104
|
# Per-layer-key section assignment. The JS renders one ``<h3>`` per
|
|
80
105
|
# section in the order the sections are first encountered.
|
|
81
106
|
_LAYER_SECTIONS: Dict[str, str] = {
|
|
@@ -85,10 +110,14 @@ _LAYER_SECTIONS: Dict[str, str] = {
|
|
|
85
110
|
# Individual entities properties — per-edge / per-node flags.
|
|
86
111
|
"semantic:is_overload": _SECTION_PROPERTIES,
|
|
87
112
|
"semantic:is_monitored": _SECTION_PROPERTIES,
|
|
113
|
+
"semantic:is_extra_cut": _SECTION_PROPERTIES,
|
|
88
114
|
"semantic:is_hub": _SECTION_PROPERTIES,
|
|
89
115
|
"style:dashed": _SECTION_PROPERTIES,
|
|
90
116
|
"style:dotted": _SECTION_PROPERTIES,
|
|
91
117
|
"style:tapered": _SECTION_PROPERTIES,
|
|
118
|
+
# Value-based node layers — see _VALUE_NODE_LAYERS / build_nodes.
|
|
119
|
+
"node:prod": _SECTION_PROPERTIES,
|
|
120
|
+
"node:load": _SECTION_PROPERTIES,
|
|
92
121
|
# Flow polarity buckets.
|
|
93
122
|
"color:coral": _SECTION_FLOWS,
|
|
94
123
|
"color:blue": _SECTION_FLOWS,
|
|
@@ -309,6 +338,40 @@ def _build_layer_index(
|
|
|
309
338
|
"edges": bucket["edges"],
|
|
310
339
|
})
|
|
311
340
|
|
|
341
|
+
# Value-based node layers (Production / Consumption). Built from
|
|
342
|
+
# the ``prod_or_load`` attribute upstream tagged on every node by
|
|
343
|
+
# ``build_nodes`` — see _VALUE_NODE_LAYERS. The white-coloured
|
|
344
|
+
# zero-balance nodes carry ``prod_or_load="load"`` AND
|
|
345
|
+
# ``value="0.0"`` upstream by convention; the 1 MW floor filters
|
|
346
|
+
# them out so the operator's "Consumption nodes" toggle doesn't
|
|
347
|
+
# also tag every passive substation.
|
|
348
|
+
if nodes:
|
|
349
|
+
value_buckets: Dict[str, List[str]] = {
|
|
350
|
+
cfg["key"]: [] for cfg in _VALUE_NODE_LAYERS
|
|
351
|
+
}
|
|
352
|
+
for n in nodes:
|
|
353
|
+
kind = n["attrs"].get("prod_or_load")
|
|
354
|
+
if kind not in value_buckets:
|
|
355
|
+
continue
|
|
356
|
+
try:
|
|
357
|
+
magnitude = abs(float(n["attrs"].get("value", "0")))
|
|
358
|
+
except (TypeError, ValueError):
|
|
359
|
+
continue
|
|
360
|
+
if magnitude < _PROD_LOAD_VALUE_FLOOR_MW:
|
|
361
|
+
continue
|
|
362
|
+
value_buckets[kind].append(n["name"])
|
|
363
|
+
for cfg in _VALUE_NODE_LAYERS:
|
|
364
|
+
ids = value_buckets[cfg["key"]]
|
|
365
|
+
if not ids:
|
|
366
|
+
continue
|
|
367
|
+
raw_layers.append({
|
|
368
|
+
"key": f"node:{cfg['key']}",
|
|
369
|
+
"label": cfg["label"],
|
|
370
|
+
"swatch": cfg["swatch"],
|
|
371
|
+
"nodes": ids,
|
|
372
|
+
"edges": [],
|
|
373
|
+
})
|
|
374
|
+
|
|
312
375
|
# Drop layers without a section assignment (e.g. ``color:black``,
|
|
313
376
|
# ``color:gray``, ``color:darkred`` — historically redundant
|
|
314
377
|
# buckets). Then group by section in the canonical order so the
|
|
@@ -637,6 +700,12 @@ const MODEL = __MODEL_JSON__;
|
|
|
637
700
|
if (swatch === 'constrained-path') return '<svg viewBox="0 0 14 6"><line x1="0" y1="3" x2="14" y2="3" stroke="black" stroke-width="2"/></svg>';
|
|
638
701
|
if (swatch === 'overload') return '<svg viewBox="0 0 14 6"><line x1="0" y1="3" x2="14" y2="3" stroke="black" stroke-width="2.5"/><line x1="0" y1="3" x2="14" y2="3" stroke="yellow" stroke-width="0.8"/></svg>';
|
|
639
702
|
if (swatch === 'monitored') return '<svg viewBox="0 0 14 6"><line x1="0" y1="3" x2="14" y2="3" stroke="coral" stroke-width="2.5"/><line x1="0" y1="3" x2="14" y2="3" stroke="yellow" stroke-width="0.8"/></svg>';
|
|
703
|
+
if (swatch === 'extra-cut') return '<svg viewBox="0 0 14 6"><line x1="0" y1="3" x2="14" y2="3" stroke="blue" stroke-width="2" stroke-dasharray="3 2"/></svg>';
|
|
704
|
+
// Match the upstream node fillcolors set in build_nodes:
|
|
705
|
+
// prod (prod_minus_load > 0) → coral
|
|
706
|
+
// load (prod_minus_load < 0) → lightblue
|
|
707
|
+
if (swatch === 'prod-node') return '<svg viewBox="0 0 10 10"><circle cx="5" cy="5" r="4" fill="coral" stroke="#444" stroke-width="0.6"/></svg>';
|
|
708
|
+
if (swatch === 'load-node') return '<svg viewBox="0 0 10 10"><circle cx="5" cy="5" r="4" fill="lightblue" stroke="#444" stroke-width="0.6"/></svg>';
|
|
640
709
|
return '';
|
|
641
710
|
}
|
|
642
711
|
function swatchStyle(swatch) {
|
|
@@ -167,6 +167,35 @@ def test_layer_index_emits_semantic_layers_from_source_flags():
|
|
|
167
167
|
assert set(by_key["semantic:is_monitored"]["nodes"]) == {"A", "B"}
|
|
168
168
|
|
|
169
169
|
|
|
170
|
+
def test_layer_index_emits_extra_cut_layer_with_endpoints():
|
|
171
|
+
"""``is_extra_cut`` is an edge-only semantic flag; like the other
|
|
172
|
+
edge-only layers it must include the endpoint nodes so the
|
|
173
|
+
substations stay visible when the operator ticks it on alone, and
|
|
174
|
+
the layer must be assigned to the "Properties" section."""
|
|
175
|
+
edges = [
|
|
176
|
+
{"id": "edge1", "source": "A", "target": "B",
|
|
177
|
+
"attrs": {"color": "blue", "is_extra_cut": "True"}},
|
|
178
|
+
{"id": "edge2", "source": "B", "target": "C",
|
|
179
|
+
"attrs": {"color": "coral"}},
|
|
180
|
+
]
|
|
181
|
+
nodes = [
|
|
182
|
+
{"name": "A", "attrs": {}},
|
|
183
|
+
{"name": "B", "attrs": {}},
|
|
184
|
+
{"name": "C", "attrs": {}},
|
|
185
|
+
]
|
|
186
|
+
layers = _build_layer_index(edges, nodes)
|
|
187
|
+
by_key = {l["key"]: l for l in layers}
|
|
188
|
+
|
|
189
|
+
assert "semantic:is_extra_cut" in by_key
|
|
190
|
+
layer = by_key["semantic:is_extra_cut"]
|
|
191
|
+
assert layer["edges"] == ["edge1"]
|
|
192
|
+
assert set(layer["nodes"]) == {"A", "B"}
|
|
193
|
+
assert layer["swatch"] == "extra-cut"
|
|
194
|
+
assert layer["label"] == "Extra lines to prevent flow increase"
|
|
195
|
+
# Section assignment matches the other per-entity property layers.
|
|
196
|
+
assert layer["section"] == "Individual entities properties"
|
|
197
|
+
|
|
198
|
+
|
|
170
199
|
def test_layer_index_skips_semantic_layer_when_no_match():
|
|
171
200
|
"""No noise: empty semantic buckets do NOT produce a layer entry."""
|
|
172
201
|
edges = [
|
|
@@ -177,6 +206,7 @@ def test_layer_index_skips_semantic_layer_when_no_match():
|
|
|
177
206
|
keys = {l["key"] for l in _build_layer_index(edges, nodes)}
|
|
178
207
|
assert "semantic:is_hub" not in keys
|
|
179
208
|
assert "semantic:in_red_loop" not in keys
|
|
209
|
+
assert "semantic:is_extra_cut" not in keys
|
|
180
210
|
assert "color:coral" in keys
|
|
181
211
|
|
|
182
212
|
|
|
@@ -352,6 +352,124 @@ class TestOverFlowGraphScaling:
|
|
|
352
352
|
assert penwidth == 1.5
|
|
353
353
|
|
|
354
354
|
|
|
355
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
356
|
+
# extra_lines_to_cut: operator-supplied extras keep their natural flow
|
|
357
|
+
# colour, are flagged ``is_extra_cut`` (alongside ``constrained``), and
|
|
358
|
+
# stay out of the ``is_overload`` / ``is_monitored`` semantic layers.
|
|
359
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _three_line_df():
|
|
363
|
+
"""L1 positive overload, L2 negative extra-cut, L3 healthy positive."""
|
|
364
|
+
return pd.DataFrame({
|
|
365
|
+
"idx_or": [0, 1, 2],
|
|
366
|
+
"idx_ex": [1, 2, 0],
|
|
367
|
+
"delta_flows": [1000.0, -100.0, 50.0],
|
|
368
|
+
"gray_edges": [False, False, False],
|
|
369
|
+
"line_name": ["L1", "L2", "L3"],
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _edge_by_name(g, name):
|
|
374
|
+
for u, v, k, data in g.edges(keys=True, data=True):
|
|
375
|
+
if data.get("name") == name:
|
|
376
|
+
return (u, v, k), data
|
|
377
|
+
raise AssertionError(f"edge {name!r} not found")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class TestExtraLinesToCut:
|
|
381
|
+
|
|
382
|
+
def test_default_extras_is_empty(self):
|
|
383
|
+
ofg = OverFlowGraph(_basic_topo(3), [0, 1], _three_line_df())
|
|
384
|
+
assert ofg.extra_lines_cut == set()
|
|
385
|
+
|
|
386
|
+
def test_extras_stored_as_set(self):
|
|
387
|
+
ofg = OverFlowGraph(
|
|
388
|
+
_basic_topo(3), [0, 1], _three_line_df(),
|
|
389
|
+
extra_lines_to_cut=[1, 1],
|
|
390
|
+
)
|
|
391
|
+
assert ofg.extra_lines_cut == {1}
|
|
392
|
+
|
|
393
|
+
def test_extras_keep_natural_flow_colour(self):
|
|
394
|
+
"""An extra-cut line never gets the black overload colour — it
|
|
395
|
+
keeps coral / blue based on its delta-flow polarity."""
|
|
396
|
+
ofg = OverFlowGraph(
|
|
397
|
+
_basic_topo(3), [0, 1], _three_line_df(),
|
|
398
|
+
extra_lines_to_cut=[1],
|
|
399
|
+
)
|
|
400
|
+
_, l1 = _edge_by_name(ofg.g, "L1") # in lines_to_cut, NOT extra
|
|
401
|
+
_, l2 = _edge_by_name(ofg.g, "L2") # in lines_to_cut AND extra
|
|
402
|
+
_, l3 = _edge_by_name(ofg.g, "L3") # not cut at all
|
|
403
|
+
assert l1["color"] == "black"
|
|
404
|
+
assert l2["color"] == "blue" # natural — delta_flows = -100
|
|
405
|
+
assert l3["color"] == "coral" # natural — delta_flows = +50
|
|
406
|
+
|
|
407
|
+
def test_extras_are_constrained_and_flagged(self):
|
|
408
|
+
ofg = OverFlowGraph(
|
|
409
|
+
_basic_topo(3), [0, 1], _three_line_df(),
|
|
410
|
+
extra_lines_to_cut=[1],
|
|
411
|
+
)
|
|
412
|
+
_, l1 = _edge_by_name(ofg.g, "L1")
|
|
413
|
+
_, l2 = _edge_by_name(ofg.g, "L2")
|
|
414
|
+
_, l3 = _edge_by_name(ofg.g, "L3")
|
|
415
|
+
# Real overload: constrained, not extra.
|
|
416
|
+
assert l1.get("constrained") is True
|
|
417
|
+
assert "is_extra_cut" not in l1
|
|
418
|
+
# Extra cut: both flags set so downstream layers can find it.
|
|
419
|
+
assert l2.get("constrained") is True
|
|
420
|
+
assert l2.get("is_extra_cut") is True
|
|
421
|
+
# Untouched line carries neither flag.
|
|
422
|
+
assert "constrained" not in l3
|
|
423
|
+
assert "is_extra_cut" not in l3
|
|
424
|
+
|
|
425
|
+
def test_extras_skipped_in_overload_and_monitored(self):
|
|
426
|
+
"""``highlight_significant_line_loading`` must not stamp
|
|
427
|
+
``is_overload`` / ``is_monitored`` on extras, must not yellow-tint
|
|
428
|
+
their colour, but should still annotate the edge label."""
|
|
429
|
+
ofg = OverFlowGraph(
|
|
430
|
+
_basic_topo(3), [0, 1], _three_line_df(),
|
|
431
|
+
extra_lines_to_cut=[1],
|
|
432
|
+
)
|
|
433
|
+
ofg.highlight_significant_line_loading({
|
|
434
|
+
"L1": {"before": 110, "after": 80},
|
|
435
|
+
"L2": {"before": 90, "after": 0},
|
|
436
|
+
"L3": {"before": 75, "after": 60},
|
|
437
|
+
})
|
|
438
|
+
_, l1 = _edge_by_name(ofg.g, "L1")
|
|
439
|
+
_, l2 = _edge_by_name(ofg.g, "L2")
|
|
440
|
+
_, l3 = _edge_by_name(ofg.g, "L3")
|
|
441
|
+
|
|
442
|
+
# L1 is a real overload: yellow-tinted, both flags set.
|
|
443
|
+
assert l1["color"] == '"black:yellow:black"'
|
|
444
|
+
assert l1.get("is_overload") is True
|
|
445
|
+
assert l1.get("is_monitored") is True
|
|
446
|
+
|
|
447
|
+
# L2 is the extra cut: keeps natural blue (no yellow tint), no
|
|
448
|
+
# is_overload / is_monitored, but the loading annotation still
|
|
449
|
+
# fires so the operator sees how their choice materialises.
|
|
450
|
+
assert l2["color"] == "blue"
|
|
451
|
+
assert l2.get("is_overload") is None
|
|
452
|
+
assert l2.get("is_monitored") is None
|
|
453
|
+
assert "90% → <B>0%</B>" in l2["label"]
|
|
454
|
+
|
|
455
|
+
# L3 is a low-margin line (not overload, not extra).
|
|
456
|
+
assert l3["color"] == '"coral:yellow:coral"'
|
|
457
|
+
assert l3.get("is_overload") is None
|
|
458
|
+
assert l3.get("is_monitored") is True
|
|
459
|
+
|
|
460
|
+
def test_legacy_behaviour_when_no_extras(self):
|
|
461
|
+
"""Without ``extra_lines_to_cut`` the contingency lines render
|
|
462
|
+
black and get tagged as overloads — the legacy contract."""
|
|
463
|
+
ofg = OverFlowGraph(_basic_topo(3), [0], _three_line_df())
|
|
464
|
+
ofg.highlight_significant_line_loading({
|
|
465
|
+
"L1": {"before": 110, "after": 80},
|
|
466
|
+
})
|
|
467
|
+
_, l1 = _edge_by_name(ofg.g, "L1")
|
|
468
|
+
assert l1["color"] == '"black:yellow:black"'
|
|
469
|
+
assert l1.get("is_overload") is True
|
|
470
|
+
assert l1.get("is_extra_cut") is None
|
|
471
|
+
|
|
472
|
+
|
|
355
473
|
# ──────────────────────────────────────────────────────────────────────
|
|
356
474
|
# detect_edges_to_keep (full method)
|
|
357
475
|
# ──────────────────────────────────────────────────────────────────────
|
|
@@ -28,7 +28,7 @@ pkgs = {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
setup(name='ExpertOp4Grid',
|
|
31
|
-
version='0.3.2',
|
|
31
|
+
version='0.3.2.post3',
|
|
32
32
|
description='Expert analysis algorithm for solving overloads in a powergrid',
|
|
33
33
|
long_description_content_type="text/markdown",
|
|
34
34
|
python_requires=">=3.9",
|
|
@@ -50,7 +50,7 @@ setup(name='ExpertOp4Grid',
|
|
|
50
50
|
author='Antoine Marot',
|
|
51
51
|
author_email='antoine.marot@rte-france.com',
|
|
52
52
|
url="https://github.com/marota/ExpertOp4Grid/",
|
|
53
|
-
download_url = 'https://github.com/marota/ExpertOp4Grid/archive/refs/tags/v0.3.2.tar.gz',
|
|
53
|
+
download_url = 'https://github.com/marota/ExpertOp4Grid/archive/refs/tags/v0.3.2.post3.tar.gz',
|
|
54
54
|
license='Mozilla Public License 2.0 (MPL 2.0)',
|
|
55
55
|
packages=setuptools.find_packages(),
|
|
56
56
|
extras_require=pkgs["extras"],
|
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/ExpertOp4Grid.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/Expert_rule_action_verification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/constrained_path.py
RENAMED
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/graph_consolidation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/graphs/power_flow_graph.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/grid2op/Grid2opSimulation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/core/pypownet/PypownetSimulation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/grid2op/grid2op_grid_test.py
RENAMED
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/grid2op/test_integrations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{expertop4grid-0.3.2 → expertop4grid-0.3.2.post3}/alphaDeesp/tests/pypownet/test_integrations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|