From d4b9b56c256d70c2b4a0b87af379fb3d5cbe2389 Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Mon, 20 Jan 2025 20:19:28 +0530 Subject: [PATCH 1/9] Add method to retrieve incoming edge indices for a node --- src/digraph.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/digraph.rs b/src/digraph.rs index 7afc6315a..e4a3cd55c 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -1842,6 +1842,29 @@ impl PyDiGraph { } } + /// Return the list of incoming edge indices to a provided node + /// + /// This method will return the incoming edges of the provided + /// ``node``. + /// + /// :param int node: The node index to get incoming edges from. If + /// this node index is not present in the graph this method will + /// return an empty list and not error. + /// + /// :returns: A list of the incoming edge indices to a node in the graph + /// :rtype: EdgeIndices + #[pyo3(text_signature = "(self, node, /)")] + pub fn in_edge_indices(&self, node: usize) -> EdgeIndices { + let node_index = NodeIndex::new(node); + EdgeIndices { + edges: self + .graph + .edges_directed(node_index, petgraph::Direction::Incoming) + .map(|e| e.id().index()) + .collect(), + } + } + /// Return the index map of edges incident to a provided node /// /// By default this method will only return the outgoing edges of From 0758aa8c2b228c73b151924af53fb7f8d05dfab6 Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Mon, 20 Jan 2025 20:22:15 +0530 Subject: [PATCH 2/9] Add type annotations in stub file --- rustworkx/rustworkx.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 2bc3c8a77..ff7a1a9b9 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -1423,6 +1423,7 @@ class PyDiGraph(Generic[_S, _T]): def in_edges(self, node: int, /) -> WeightedEdgeList[_T]: ... def incident_edge_index_map(self, node: int, /, all_edges: bool = ...) -> EdgeIndexMap: ... def incident_edges(self, node: int, /, all_edges: bool = ...) -> EdgeIndices: ... + def in_edge_indices(self, node: int, /) -> EdgeIndices: ... def insert_node_on_in_edges(self, node: int, ref_node: int, /) -> None: ... def insert_node_on_in_edges_multiple(self, node: int, ref_nodes: Sequence[int], /) -> None: ... def insert_node_on_out_edges(self, node: int, ref_node: int, /) -> None: ... From f76891de7e6ab8d8d215abe41e1c12a7229c4017 Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Mon, 20 Jan 2025 20:22:59 +0530 Subject: [PATCH 3/9] Add tests for in_edge_indices --- tests/digraph/test_edges.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/digraph/test_edges.py b/tests/digraph/test_edges.py index d2275a6de..fa4960007 100644 --- a/tests/digraph/test_edges.py +++ b/tests/digraph/test_edges.py @@ -813,6 +813,23 @@ def test_incident_edges_all_edges(self): res = graph.incident_edges(node_d, all_edges=True) self.assertEqual([2, 1], res) + def test_in_edge_indices(self): + graph = rustworkx.PyDiGraph() + node_a = graph.add_node(0) + node_b = graph.add_node(1) + node_c = graph.add_node("c") + node_d = graph.add_node("d") + graph.add_edge(node_a, node_c, "edge a") + graph.add_edge(node_b, node_d, "edge b") + graph.add_edge(node_d, node_c, "edge c") + res = graph.in_edge_indices(node_c) + self.assertEqual([2, 0], res) + + def test_in_edge_indices_invalid_node(self): + graph = rustworkx.PyDiGraph() + res = graph.in_edge_indices(0) + self.assertEqual([], res) + def test_incident_edge_index_map(self): graph = rustworkx.PyDiGraph() node_a = graph.add_node(0) From 140fe608fd99312975d8b481f34ea7ec24c7dd9a Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Thu, 23 Jan 2025 14:55:55 +0530 Subject: [PATCH 4/9] Add method to get outgoing edge indices for a node --- src/digraph.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/digraph.rs b/src/digraph.rs index e4a3cd55c..b4015fbcb 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -1856,10 +1856,35 @@ impl PyDiGraph { #[pyo3(text_signature = "(self, node, /)")] pub fn in_edge_indices(&self, node: usize) -> EdgeIndices { let node_index = NodeIndex::new(node); + let dir = petgraph::Direction::Incoming; EdgeIndices { edges: self .graph - .edges_directed(node_index, petgraph::Direction::Incoming) + .edges_directed(node_index, dir) + .map(|e| e.id().index()) + .collect(), + } + } + + /// Return the list of outgoing edge indices from a provided node + /// + /// This method will return the outgoing edges of the provided + /// ``node``. + /// + /// :param int node: The node index to get outgoing edges from. If + /// this node index is not present in the graph this method will + /// return an empty list and not error. + /// + /// :returns: A list of the outgoing edge indices from a node in the graph + /// :rtype: EdgeIndices + #[pyo3(text_signature = "(self, node, /)")] + pub fn out_edge_indices(&self, node: usize) -> EdgeIndices { + let node_index = NodeIndex::new(node); + let dir = petgraph::Direction::Outgoing; + EdgeIndices { + edges: self + .graph + .edges_directed(node_index, dir) .map(|e| e.id().index()) .collect(), } From 84d8599409d1e6ba849ce66cbabeb05032f10d2d Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Thu, 23 Jan 2025 14:57:42 +0530 Subject: [PATCH 5/9] Add in_edge_indices and out_edge_indices methods in PyGraph --- src/graph.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/graph.rs b/src/graph.rs index adc72f512..19a1bbc10 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -552,6 +552,52 @@ impl PyGraph { } } + /// Return the list of edge indices incident to a provided node. + /// + /// This method returns the indices of all edges connected to the provided + /// ``node``. In undirected graphs, all edges connected to the node are + /// returned as there is no distinction between incoming and outgoing edges. + /// + /// :param int node: The node index to get incident edges from. If + /// this node index is not present in the graph this method will + /// return an empty list and not error. + /// + /// :returns: A list of the edge indices incident to the node + /// :rtype: EdgeIndices + #[pyo3(text_signature = "(self, node, /)")] + pub fn in_edge_indices(&self, node: usize) -> EdgeIndices { + EdgeIndices { + edges: self + .graph + .edges(NodeIndex::new(node)) + .map(|e| e.id().index()) + .collect(), + } + } + + /// Return the list of edge indices incident to a provided node. + /// + /// This method returns the indices of all edges connected to the provided + /// ``node``. In undirected graphs, all edges connected to the node are + /// returned as there is no distinction between incoming and outgoing edges. + /// + /// :param int node: The node index to get incident edges from. If + /// this node index is not present in the graph this method will + /// return an empty list and not error. + /// + /// :returns: A list of the edge indices incident to the node + /// :rtype: EdgeIndices + #[pyo3(text_signature = "(self, node, /)")] + pub fn out_edge_indices(&self, node: usize) -> EdgeIndices { + EdgeIndices { + edges: self + .graph + .edges(NodeIndex::new(node)) + .map(|e| e.id().index()) + .collect(), + } + } + /// Return the index map of edges incident to a provided node /// /// :param int node: The node index to get incident edges from. If From 797b492daa6e797ea4bd119605181eae3af92ddf Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Thu, 23 Jan 2025 14:58:43 +0530 Subject: [PATCH 6/9] update stub file --- rustworkx/rustworkx.pyi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index ff7a1a9b9..6dfed3b87 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -1257,6 +1257,7 @@ class PyGraph(Generic[_S, _T]): def in_edges(self, node: int, /) -> WeightedEdgeList[_T]: ... def incident_edge_index_map(self, node: int, /) -> EdgeIndexMap: ... def incident_edges(self, node: int, /) -> EdgeIndices: ... + def in_edge_indices(self, node: int, /) -> EdgeIndices: ... def neighbors(self, node: int, /) -> NodeIndices: ... def node_indexes(self) -> NodeIndices: ... def node_indices(self) -> NodeIndices: ... @@ -1264,6 +1265,7 @@ class PyGraph(Generic[_S, _T]): def num_edges(self) -> int: ... def num_nodes(self) -> int: ... def out_edges(self, node: int, /) -> WeightedEdgeList[_T]: ... + def out_edge_indices(self, node: int, /) -> EdgeIndices: ... @staticmethod def read_edge_list( path: str, @@ -1440,6 +1442,7 @@ class PyDiGraph(Generic[_S, _T]): def num_nodes(self) -> int: ... def out_degree(self, node: int, /) -> int: ... def out_edges(self, node: int, /) -> WeightedEdgeList[_T]: ... + def out_edge_indices(self, node: int, /) -> EdgeIndices: ... def predecessor_indices(self, node: int, /) -> NodeIndices: ... def predecessors(self, node: int, /) -> list[_S]: ... @staticmethod From 71f5df58f1df699c955867e4cd929ab33d3c99cf Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Thu, 23 Jan 2025 15:01:05 +0530 Subject: [PATCH 7/9] Add tests for out_edge_indices and in_edge_indices --- tests/digraph/test_edges.py | 23 ++++++++++++++++++++--- tests/graph/test_edges.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/tests/digraph/test_edges.py b/tests/digraph/test_edges.py index fa4960007..604fba039 100644 --- a/tests/digraph/test_edges.py +++ b/tests/digraph/test_edges.py @@ -819,17 +819,34 @@ def test_in_edge_indices(self): node_b = graph.add_node(1) node_c = graph.add_node("c") node_d = graph.add_node("d") - graph.add_edge(node_a, node_c, "edge a") + edge_ac = graph.add_edge(node_a, node_c, "edge a") graph.add_edge(node_b, node_d, "edge b") - graph.add_edge(node_d, node_c, "edge c") + edge_dc = graph.add_edge(node_d, node_c, "edge c") res = graph.in_edge_indices(node_c) - self.assertEqual([2, 0], res) + self.assertEqual([edge_dc, edge_ac], res) def test_in_edge_indices_invalid_node(self): graph = rustworkx.PyDiGraph() res = graph.in_edge_indices(0) self.assertEqual([], res) + def test_out_edge_indices(self): + graph = rustworkx.PyDiGraph() + node_a = graph.add_node(0) + node_b = graph.add_node(1) + node_c = graph.add_node("c") + node_d = graph.add_node("d") + edge_ab = graph.add_edge(node_a, node_b, "edge a") + edge_ac = graph.add_edge(node_a, node_c, "edge b") + graph.add_edge(node_c, node_d, "edge c") + res = graph.out_edge_indices(node_a) + self.assertEqual([edge_ac, edge_ab], res) + + def test_out_edge_indices_invalid_node(self): + graph = rustworkx.PyDiGraph() + res = graph.out_edge_indices(0) + self.assertEqual([], res) + def test_incident_edge_index_map(self): graph = rustworkx.PyDiGraph() node_a = graph.add_node(0) diff --git a/tests/graph/test_edges.py b/tests/graph/test_edges.py index 483488832..1ce441031 100644 --- a/tests/graph/test_edges.py +++ b/tests/graph/test_edges.py @@ -526,6 +526,40 @@ def test_incident_edges_invalid_node(self): res = graph.incident_edges(42) self.assertEqual([], res) + def test_in_edge_indices(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node(0) + node_b = graph.add_node(1) + node_c = graph.add_node("c") + node_d = graph.add_node("d") + graph.add_edge(node_a, node_c, "edge a") + graph.add_edge(node_b, node_d, "edge_b") + graph.add_edge(node_c, node_d, "edge c") + res = graph.in_edge_indices(node_d) + self.assertEqual({1, 2}, set(res)) + + def test_in_edge_indices_invalid_node(self): + graph = rustworkx.PyGraph() + res = graph.in_edge_indices(0) + self.assertEqual([], res) + + def test_out_edge_indices(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node(0) + node_b = graph.add_node(1) + node_c = graph.add_node("c") + node_d = graph.add_node("d") + graph.add_edge(node_a, node_c, "edge a") + graph.add_edge(node_b, node_d, "edge_b") + graph.add_edge(node_c, node_d, "edge c") + res = graph.out_edge_indices(node_d) + self.assertEqual({1, 2}, set(res)) + + def test_out_edge_indices_invalid_node(self): + graph = rustworkx.PyGraph() + res = graph.out_edge_indices(0) + self.assertEqual([], res) + def test_incident_edge_index_map(self): graph = rustworkx.PyGraph() node_a = graph.add_node(0) From e0443cd9c0eb6ab2d8a213a423e0bf60d51cc58f Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Thu, 23 Jan 2025 15:01:29 +0530 Subject: [PATCH 8/9] Add release notes for in_edge_indices and out_edge_indices functions --- .../notes/add-in-out-edge-indices-248c5f63c716f1e5.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 releasenotes/notes/add-in-out-edge-indices-248c5f63c716f1e5.yaml diff --git a/releasenotes/notes/add-in-out-edge-indices-248c5f63c716f1e5.yaml b/releasenotes/notes/add-in-out-edge-indices-248c5f63c716f1e5.yaml new file mode 100644 index 000000000..11630deee --- /dev/null +++ b/releasenotes/notes/add-in-out-edge-indices-248c5f63c716f1e5.yaml @@ -0,0 +1,7 @@ +features: + - | + Added two new functions, :func:`~rustworkx.in_edge_indices` and + :func:`~rustworkx.out_edge_indices`, to the ``rustworkx.PyDiGraph`` class. These functions return the indices of all incoming and outgoing edges for a given node in a directed graph, respectively. + - | + Added two new functions, :func:`~rustworkx.in_edge_indices` and + :func:`~rustworkx.out_edge_indices`, to the ``rustworkx.PyGraph`` class. As ``PyGraph`` is an undirected graph, both functions return the indices of all edges connected to the given node. This maintains API consistency with the directed graph implementation. From 5ae194d2bc53abd52cb334749d55ddcd8d3dccdc Mon Sep 17 00:00:00 2001 From: ThisuraGallage Date: Thu, 23 Jan 2025 15:25:28 +0530 Subject: [PATCH 9/9] Change releasenote name --- ...ml => add-in-out-edge-indices-functions-fc1ce122ff761ad6.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename releasenotes/notes/{add-in-out-edge-indices-248c5f63c716f1e5.yaml => add-in-out-edge-indices-functions-fc1ce122ff761ad6.yaml} (100%) diff --git a/releasenotes/notes/add-in-out-edge-indices-248c5f63c716f1e5.yaml b/releasenotes/notes/add-in-out-edge-indices-functions-fc1ce122ff761ad6.yaml similarity index 100% rename from releasenotes/notes/add-in-out-edge-indices-248c5f63c716f1e5.yaml rename to releasenotes/notes/add-in-out-edge-indices-functions-fc1ce122ff761ad6.yaml