Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Node/breaker topo AMPL export #833

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/dev-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ jobs:
cmake $GITHUB_WORKSPACE/cpp -DBUILD_PYPOWSYBL_JAVA=OFF -DPYPOWSYBL_JAVA_LIBRARY_DIR=$GITHUB_WORKSPACE/dist/lib -DPYPOWSYBL_JAVA_INCLUDE_DIR=$GITHUB_WORKSPACE/dist/include
cmake --build . --config Release

- name: Upload wheel
uses: actions/upload-artifact@v3
with:
name: pypowsybl-wheel-linux-${{ matrix.python.name }}
path: wheelhouse/*.whl

macos_windows_build:
name: Build ${{ matrix.config.name }} ${{ matrix.python.name }} wheel
runs-on: ${{ matrix.config.os }}
Expand Down Expand Up @@ -184,7 +190,12 @@ jobs:
unzip binaries.zip && mkdir build && cd build
cmake $GITHUB_WORKSPACE/cpp -DBUILD_PYPOWSYBL_JAVA=OFF -DPYPOWSYBL_JAVA_LIBRARY_DIR=$GITHUB_WORKSPACE/dist/lib -DPYPOWSYBL_JAVA_INCLUDE_DIR=$GITHUB_WORKSPACE/dist/include
cmake --build . --config Release



- name: Upload wheel
uses: actions/upload-artifact@v3
with:
name: pypowsybl-wheel-${{ matrix.config.name }}-${{ matrix.python.name }}
path: dist/*.whl



5 changes: 3 additions & 2 deletions java/src/main/java/com/powsybl/python/network/Dataframes.java
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,9 @@ private static List<NodeContext> getNodeBreakerViewNodes(VoltageLevel.NodeBreake
return nodes.stream().map(node -> {
Terminal terminal = nodeBreakerView.getTerminal(node);
if (terminal == null) {
return new NodeContext(node, null);
return new NodeContext(node, null, null);
} else {
return new NodeContext(node, terminal.getConnectable().getId());
return new NodeContext(node, terminal.getConnectable().getId(), terminal.getConnectable().getType());
}
}).collect(Collectors.toList());
}
Expand All @@ -322,6 +322,7 @@ private static DataframeMapper<VoltageLevel.NodeBreakerView, Void> createNodeBre
.itemsProvider(Dataframes::getNodeBreakerViewNodes)
.intsIndex("node", NodeContext::getNode)
.strings("connectable_id", node -> Objects.toString(node.getConnectableId(), ""))
.strings("connectable_type", node -> node.getConnectableType() != null ? node.getConnectableType().name() : "")
.build();
}

Expand Down
13 changes: 9 additions & 4 deletions java/src/main/java/com/powsybl/python/network/NodeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
*/
package com.powsybl.python.network;

import javax.annotation.Nullable;
import java.util.Objects;
import com.powsybl.iidm.network.IdentifiableType;

/**
* @author Etienne Lesot <etienne.lesot at rte-france.com>
*/
public class NodeContext {
private final int node;
private final String connectableId;
private final IdentifiableType connectableType;

public NodeContext(int node, @Nullable String connectableId) {
this.node = Objects.requireNonNull(node);
public NodeContext(int node, String connectableId, IdentifiableType connectableType) {
this.node = node;
this.connectableId = connectableId;
this.connectableType = connectableType;
}

public int getNode() {
Expand All @@ -29,4 +30,8 @@ public int getNode() {
public String getConnectableId() {
return connectableId;
}

public IdentifiableType getConnectableType() {
return connectableType;
}
}
50 changes: 47 additions & 3 deletions pypowsybl/network/impl/node_breaker_topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
#
from os import PathLike
from pathlib import Path
from typing import Union

import networkx as nx
from pandas import DataFrame
import networkx as _nx
import pypowsybl._pypowsybl as _pp
Expand All @@ -22,7 +27,7 @@ class NodeBreakerTopology:
def __init__(self, network_handle: _pp.JavaHandle, voltage_level_id: str):
self._internal_connections = create_data_frame_from_series_array(
_pp.get_node_breaker_view_internal_connections(network_handle, voltage_level_id))
self._switchs = create_data_frame_from_series_array(
self._switches = create_data_frame_from_series_array(
_pp.get_node_breaker_view_switches(network_handle, voltage_level_id))
self._nodes = create_data_frame_from_series_array(
_pp.get_node_breaker_view_nodes(network_handle, voltage_level_id))
Expand All @@ -32,7 +37,7 @@ def switches(self) -> DataFrame:
"""
The list of switches of the voltage level, together with their connection status, as a dataframe.
"""
return self._switchs
return self._switches

@property
def nodes(self) -> DataFrame:
Expand All @@ -55,6 +60,45 @@ def create_graph(self) -> _nx.Graph:
"""
graph = _nx.Graph()
graph.add_nodes_from(self._nodes.index.tolist())
graph.add_edges_from(self._switchs[['node1', 'node2']].values.tolist())
graph.add_edges_from(self._switches[['node1', 'node2']].values.tolist())
graph.add_edges_from(self._internal_connections[['node1', 'node2']].values.tolist())
return graph

def write_ampl(self, dir: Union[str, Path]) -> None:
"""
Write the node/breaker topology in a columned file format (with space as a separator) so that it is
easily readable by AMPL for running topology optimization.

3 files are being generated:
- topo_connections.txt: the list of node connections in the graph.
- topo_elements.txt: the elements ID and type associated to each of the nodes
- topo_valid.txt : the buses to which each of the nodes belong

Args:
dir: directory path to generate the 3 files
"""
dir_path = Path(dir)
with open(dir_path / 'topo_connections.txt', 'w') as file:
file.write("# switch_id node1 node2 open\n")
for i, row in enumerate(self.switches.itertuples(index=False)):
file.write(f"{i} {row[4]} {row[5]} {1 if row[2] else 0}\n")

with open(dir_path / 'topo_elements.txt', 'w') as file:
file.write("# node element_id element_type\n")
for node, row in self.nodes.iterrows():
file.write(f"{node} '{row['connectable_id']}' {row['connectable_type']}\n")

graph = nx.Graph()
graph.add_nodes_from(self.nodes.index.tolist())
topo_switches = self.switches
closed_topo_switches = topo_switches[~topo_switches['open']]
graph.add_edges_from(closed_topo_switches[['node1', 'node2']].values.tolist())
graph.add_edges_from(self.internal_connections[['node1', 'node2']].values.tolist())
components = list(nx.connected_components(graph))

i_topo = 0 # TODO will be used later on to generate multiple topologies
with open(dir_path / 'topo_valid.txt', 'w') as file:
file.write("# topo_id bus_id node\n")
for i_component, component in enumerate(components):
for node in component:
file.write(f"{i_topo} {i_component} {node}\n")
1 change: 1 addition & 0 deletions tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,7 @@ def test_node_breaker_view():
assert 0 == switches.loc['S4VL1_BBS_LINES3S4_DISCONNECTOR']['node1']
assert 5 == switches.loc['S4VL1_BBS_LINES3S4_DISCONNECTOR']['node2']
assert 7 == len(nodes)
assert 'BUSBAR_SECTION' == nodes.loc[0]['connectable_type']
assert topology.internal_connections.empty

with pytest.raises(PyPowsyblError) as exc:
Expand Down
Loading