From 023e0dc8d06268ba8507bf91c4e88ac23cfee3a2 Mon Sep 17 00:00:00 2001 From: Bhaavan Goel Date: Sun, 8 Nov 2020 19:08:06 +0530 Subject: [PATCH 1/6] Added Mesh and Grid graph generator --- docs/source/api.rst | 4 + src/generators.rs | 263 ++++++++++++++++++++++++++++++++++ tests/generators/test_mesh.py | 61 ++++++++ 3 files changed, 328 insertions(+) create mode 100644 tests/generators/test_mesh.py diff --git a/docs/source/api.rst b/docs/source/api.rst index f2922b2d8..7e00fef37 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -26,6 +26,10 @@ Generators retworkx.generators.directed_path_graph retworkx.generators.star_graph retworkx.generators.directed_star_graph + retworkx.generators.mesh_graph + retworkx.generators.directed_mesh_graph + retworkx.generators.grid_graph + retworkx.generators.directed_grid_graph Random Circuit Functions ------------------------ diff --git a/src/generators.rs b/src/generators.rs index 2d92f2fb8..a018ea6e1 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -551,6 +551,265 @@ pub fn star_graph( }) } +/// Generate an undirected mesh graph where every node is connected to every other +/// +/// :param int num_node: The number of nodes to generate the graph with. Node +/// weights will be None if this is specified. If both ``num_node`` and +/// ``weights`` are set this will be ignored and ``weights`` will be used. +/// :param list weights: A list of node weights. If both ``num_node`` and +/// ``weights`` are set this will be ignored and ``weights`` will be used. +/// +/// :returns: The generated mesh graph +/// :rtype: PyGraph +/// :raises IndexError: If neither ``num_nodes`` or ``weights`` are specified +/// +#[pyfunction] +#[text_signature = "(/, num_nodes=None, weights=None)"] +pub fn mesh_graph( + py: Python, + num_nodes: Option, + weights: Option>, +) -> PyResult { + let mut graph = StableUnGraph::::default(); + if weights.is_none() && num_nodes.is_none() { + return Err(PyIndexError::new_err( + "num_nodes and weights list not specified", + )); + } + let nodes: Vec = match weights { + Some(weights) => { + let mut node_list: Vec = Vec::new(); + for weight in weights { + let index = graph.add_node(weight); + node_list.push(index); + } + node_list + } + None => (0..num_nodes.unwrap()) + .map(|_| graph.add_node(py.None())) + .collect(), + }; + + let nodelen = nodes.len(); + for i in 0..nodelen - 1 { + for j in i + 1..nodelen { + graph.add_edge(nodes[i], nodes[j], py.None()); + } + } + Ok(graph::PyGraph { + graph, + node_removed: false, + }) +} + +/// Generate a directed mesh graph where every node is connected to every other +/// +/// :param int num_node: The number of nodes to generate the graph with. Node +/// weights will be None if this is specified. If both ``num_node`` and +/// ``weights`` are set this will be ignored and ``weights`` will be used. +/// :param list weights: A list of node weights. If both ``num_node`` and +/// ``weights`` are set this will be ignored and ``weights`` will be used. +/// +/// :returns: The generated mesh graph +/// :rtype: PyDiGraph +/// :raises IndexError: If neither ``num_nodes`` or ``weights`` are specified +/// +#[pyfunction] +#[text_signature = "(/, num_nodes=None, weights=None)"] +pub fn directed_mesh_graph( + py: Python, + num_nodes: Option, + weights: Option>, +) -> PyResult { + let mut graph = StableDiGraph::::default(); + if weights.is_none() && num_nodes.is_none() { + return Err(PyIndexError::new_err( + "num_nodes and weights list not specified", + )); + } + let nodes: Vec = match weights { + Some(weights) => { + let mut node_list: Vec = Vec::new(); + for weight in weights { + let index = graph.add_node(weight); + node_list.push(index); + } + node_list + } + None => (0..num_nodes.unwrap()) + .map(|_| graph.add_node(py.None())) + .collect(), + }; + let nodelen = nodes.len(); + for i in 0..nodelen - 1 { + for j in i + 1..nodelen { + graph.add_edge(nodes[i], nodes[j], py.None()); + graph.add_edge(nodes[j], nodes[i], py.None()); + } + } + Ok(digraph::PyDiGraph { + graph, + node_removed: false, + check_cycle: false, + cycle_state: algo::DfsSpace::default(), + }) +} + +/// Generate an undirected grid graph +/// +/// :param int rows: The number of rows to generate the graph with. Value +/// required +/// :param list cols: The number of rows to generate the graph with. Value +/// required +/// :param list weights: A list of node weights. +/// +/// :returns: The generated star graph +/// :rtype: PyGraph +/// :raises IndexError: If neither ``rows`` and ``cols`` or ``weights`` are +/// specified +/// +#[pyfunction] +#[text_signature = "(/, rows=None, cols=None, weights=None)"] +pub fn grid_graph( + py: Python, + rows: Option, + cols: Option, + weights: Option>, +) -> PyResult { + let mut graph = StableUnGraph::::default(); + if weights.is_none() && (rows.is_none() || cols.is_none()) { + return Err(PyIndexError::new_err( + "dimensions and weights list not specified", + )); + } + + let rowlen = rows.unwrap(); + let collen = cols.unwrap(); + let num_nodes = rowlen * collen; + let nodes: Vec = match weights { + Some(weights) => { + let mut node_list: Vec = Vec::new(); + for weight in weights { + let index = graph.add_node(weight); + node_list.push(index); + } + node_list + } + None => (0..num_nodes).map(|_| graph.add_node(py.None())).collect(), + }; + + for i in 0..rowlen { + for j in 0..collen { + if i + 1 < rowlen { + graph.add_edge( + nodes[i * collen + j], + nodes[(i + 1) * collen + j], + py.None(), + ); + } + if j + 1 < collen { + graph.add_edge( + nodes[i * collen + j], + nodes[i * collen + j + 1], + py.None(), + ); + } + } + } + Ok(graph::PyGraph { + graph, + node_removed: false, + }) +} + +/// Generate a directed grid graph. The edges propagate towards right and +/// bottom direction if ``bidirectional`` is ``false`` +/// +/// :param int rows: The number of rows to generate the graph with. Value +/// required +/// :param list cols: The number of rows to generate the graph with. Value +/// required +/// :param list weights: A list of node weights. +/// :param bidirectional: A parameter to indicate if edges should exist in +/// both directions between nodes +/// +/// :returns: The generated star graph +/// :rtype: PyDiGraph +/// :raises IndexError: If neither ``rows`` and ``cols`` or ``weights`` are +/// specified +/// +#[pyfunction(bidirectional = "false")] +#[text_signature = "(/, rows=None, cols=None, weights=None, bidirectional=False)"] +pub fn directed_grid_graph( + py: Python, + rows: Option, + cols: Option, + weights: Option>, + bidirectional: bool, +) -> PyResult { + let mut graph = StableDiGraph::::default(); + if weights.is_none() && (rows.is_none() || cols.is_none()) { + return Err(PyIndexError::new_err( + "dimensions and weights list not specified", + )); + } + + let rowlen = rows.unwrap(); + let collen = cols.unwrap(); + let num_nodes = rowlen * collen; + let nodes: Vec = match weights { + Some(weights) => { + let mut node_list: Vec = Vec::new(); + for weight in weights { + let index = graph.add_node(weight); + node_list.push(index); + } + node_list + } + None => (0..num_nodes).map(|_| graph.add_node(py.None())).collect(), + }; + + for i in 0..rowlen { + for j in 0..collen { + if i + 1 < rowlen { + graph.add_edge( + nodes[i * collen + j], + nodes[(i + 1) * collen + j], + py.None(), + ); + if bidirectional { + graph.add_edge( + nodes[(i + 1) * collen + j], + nodes[i * collen + j], + py.None(), + ); + } + } + + if j + 1 < collen { + graph.add_edge( + nodes[i * collen + j], + nodes[i * collen + j + 1], + py.None(), + ); + if bidirectional { + graph.add_edge( + nodes[i * collen + j + 1], + nodes[i * collen + j], + py.None(), + ); + } + } + } + } + Ok(digraph::PyDiGraph { + graph, + node_removed: false, + check_cycle: false, + cycle_state: algo::DfsSpace::default(), + }) +} + #[pymodule] pub fn generators(_py: Python, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(cycle_graph))?; @@ -559,5 +818,9 @@ pub fn generators(_py: Python, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(directed_path_graph))?; m.add_wrapped(wrap_pyfunction!(star_graph))?; m.add_wrapped(wrap_pyfunction!(directed_star_graph))?; + m.add_wrapped(wrap_pyfunction!(mesh_graph))?; + m.add_wrapped(wrap_pyfunction!(directed_mesh_graph))?; + m.add_wrapped(wrap_pyfunction!(grid_graph))?; + m.add_wrapped(wrap_pyfunction!(directed_grid_graph))?; Ok(()) } diff --git a/tests/generators/test_mesh.py b/tests/generators/test_mesh.py new file mode 100644 index 000000000..064370fc4 --- /dev/null +++ b/tests/generators/test_mesh.py @@ -0,0 +1,61 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + +import retworkx + + +class TestMeshGraph(unittest.TestCase): + + def test_directed_mesh_graph(self): + graph = retworkx.generators.directed_mesh_graph(20) + self.assertEqual(len(graph), 20) + self.assertEqual(len(graph.edges()), 380) + for i in range(20): + ls = [] + for j in range(19, -1, -1): + if i != j: + ls.append((i, j, None)) + self.assertEqual(graph.out_edges(i), ls) + + def test_directed_mesh_graph_weights(self): + graph = retworkx.generators.directed_mesh_graph( + weights=list(range(20))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)], graph.nodes()) + self.assertEqual(len(graph.edges()), 380) + for i in range(20): + ls = [] + for j in range(19, -1, -1): + if i != j: + ls.append((i, j, None)) + self.assertEqual(graph.out_edges(i), ls) + + def test_path_directed_no_weights_or_num(self): + with self.assertRaises(IndexError): + retworkx.generators.directed_mesh_graph() + + def test_mesh_graph(self): + graph = retworkx.generators.mesh_graph(20) + self.assertEqual(len(graph), 20) + self.assertEqual(len(graph.edges()), 190) + + def test_mesh_graph_weights(self): + graph = retworkx.generators.mesh_graph(weights=list(range(20))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)], graph.nodes()) + self.assertEqual(len(graph.edges()), 190) + + def test_mesh_no_weights_or_num(self): + with self.assertRaises(IndexError): + retworkx.generators.mesh_graph() From 96ed6603df8afb7ed5861f20c290ce2ecf99177e Mon Sep 17 00:00:00 2001 From: Bhaavan Goel Date: Thu, 12 Nov 2020 13:44:07 +0530 Subject: [PATCH 2/6] Updated grid_graph generator for 1D weights list --- src/generators.rs | 104 +++++++++++++++++++++----- tests/generators/test_grid.py | 133 ++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 20 deletions(-) create mode 100644 tests/generators/test_grid.py diff --git a/src/generators.rs b/src/generators.rs index a018ea6e1..67c4b42a9 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -655,17 +655,24 @@ pub fn directed_mesh_graph( }) } -/// Generate an undirected grid graph -/// -/// :param int rows: The number of rows to generate the graph with. Value -/// required -/// :param list cols: The number of rows to generate the graph with. Value -/// required -/// :param list weights: A list of node weights. +/// Generate an undirected grid graph. +/// +/// :param int rows: The number of rows to generate the graph with. +/// If specified, cols also need to be specified +/// :param list cols: The number of rows to generate the graph with. +/// If specified, rows also need to be specified. rows*cols +/// defines the number of nodes in the graph +/// :param list weights: A list of node weights. If rows and cols +/// are not specified, then a linear graph containing all the +/// values in weights list is created. +/// If number of nodes(rows*cols) is less than length of +/// weights list, the trailing weights are ignored. +/// If number of nodes(rows*cols) is greater than length of +/// weights list, extra nodes with None weight are appended /// /// :returns: The generated star graph /// :rtype: PyGraph -/// :raises IndexError: If neither ``rows`` and ``cols`` or ``weights`` are +/// :raises IndexError: If neither ``rows`` or ``cols`` and ``weights`` are /// specified /// #[pyfunction] @@ -683,15 +690,40 @@ pub fn grid_graph( )); } - let rowlen = rows.unwrap(); - let collen = cols.unwrap(); - let num_nodes = rowlen * collen; + let mut rowlen = match rows { + Some(rows) => rows, + None => 0, + }; + + let mut collen = match cols { + Some(cols) => cols, + None => 0, + }; + + let mut num_nodes = rowlen * collen; + let nodes: Vec = match weights { Some(weights) => { let mut node_list: Vec = Vec::new(); + if num_nodes < weights.len() && rowlen == 0 { + collen = weights.len(); + rowlen = 1; + num_nodes = collen; + } + + let mut node_cnt = num_nodes; + for weight in weights { + if node_cnt <= 0 { + break; + } let index = graph.add_node(weight); node_list.push(index); + node_cnt -= 1; + } + for _i in 0..node_cnt { + let index = graph.add_node(py.None()); + node_list.push(index); } node_list } @@ -725,17 +757,24 @@ pub fn grid_graph( /// Generate a directed grid graph. The edges propagate towards right and /// bottom direction if ``bidirectional`` is ``false`` /// -/// :param int rows: The number of rows to generate the graph with. Value -/// required -/// :param list cols: The number of rows to generate the graph with. Value -/// required -/// :param list weights: A list of node weights. +//// :param int rows: The number of rows to generate the graph with. +/// If specified, cols also need to be specified. +/// :param list cols: The number of rows to generate the graph with. +/// If specified, rows also need to be specified. rows*cols +/// defines the number of nodes in the graph. +/// :param list weights: A list of node weights. If rows and cols +/// are not specified, then a linear graph containing all the +/// values in weights list is created. +/// If number of nodes(rows*cols) is less than length of +/// weights list, the trailing weights are ignored. +/// If number of nodes(rows*cols) is greater than length of +/// weights list, extra nodes with None weight are appended. /// :param bidirectional: A parameter to indicate if edges should exist in /// both directions between nodes /// /// :returns: The generated star graph /// :rtype: PyDiGraph -/// :raises IndexError: If neither ``rows`` and ``cols`` or ``weights`` are +/// :raises IndexError: If neither ``rows`` or ``cols`` and ``weights`` are /// specified /// #[pyfunction(bidirectional = "false")] @@ -754,15 +793,40 @@ pub fn directed_grid_graph( )); } - let rowlen = rows.unwrap(); - let collen = cols.unwrap(); - let num_nodes = rowlen * collen; + let mut rowlen = match rows { + Some(rows) => rows, + None => 0, + }; + + let mut collen = match cols { + Some(cols) => cols, + None => 0, + }; + + let mut num_nodes = rowlen * collen; + let nodes: Vec = match weights { Some(weights) => { let mut node_list: Vec = Vec::new(); + if num_nodes < weights.len() && rowlen == 0 { + collen = weights.len(); + rowlen = 1; + num_nodes = collen; + } + + let mut node_cnt = num_nodes; + for weight in weights { + if node_cnt <= 0 { + break; + } let index = graph.add_node(weight); node_list.push(index); + node_cnt -= 1; + } + for _i in 0..node_cnt { + let index = graph.add_node(py.None()); + node_list.push(index); } node_list } diff --git a/tests/generators/test_grid.py b/tests/generators/test_grid.py new file mode 100644 index 000000000..3f239996e --- /dev/null +++ b/tests/generators/test_grid.py @@ -0,0 +1,133 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + +import retworkx + + +class TestGridGraph(unittest.TestCase): + + def test_directed_grid_graph_dimensions(self): + graph = retworkx.generators.directed_grid_graph(4, 5) + self.assertEqual(len(graph), 20) + self.assertEqual(len(graph.edges()), 31) + self.assertEqual(graph.out_edges(0), [(0, 1, None),(0, 5, None)]) + self.assertEqual(graph.out_edges(7), [(7, 8, None),(7, 12, None)]) + self.assertEqual(graph.out_edges(9), [(9, 14, None)]) + self.assertEqual(graph.out_edges(17), [(17, 18, None)]) + self.assertEqual(graph.out_edges(19), []) + self.assertEqual(graph.in_edges(0), []) + self.assertEqual(graph.in_edges(2), [(1, 2, None)]) + self.assertEqual(graph.in_edges(5), [(0, 5, None)]) + self.assertEqual(graph.in_edges(7), [(6, 7, None),(2, 7, None)]) + self.assertEqual(graph.in_edges(19), [(18, 19, None), (14, 19, None)]) + + def test_directed_grid_graph_weights(self): + graph = retworkx.generators.directed_grid_graph( + weights=list(range(20))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)], graph.nodes()) + self.assertEqual(len(graph.edges()), 19) + for i in range(19): + self.assertEqual(graph.out_edges(i), [(i, i+1, None)]) + self.assertEqual(graph.out_edges(19), []) + for i in range(1, 20): + self.assertEqual(graph.in_edges(i), [(i-1, i, None)]) + self.assertEqual(graph.in_edges(0), []) + + def test_directed_grid_graph_dimensions_weights(self): + graph = retworkx.generators.directed_grid_graph(4, 5, weights=list(range(20))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)], graph.nodes()) + self.assertEqual(len(graph.edges()), 31) + self.assertEqual(graph.out_edges(0), [(0, 1, None),(0, 5, None)]) + self.assertEqual(graph.out_edges(7), [(7, 8, None),(7, 12, None)]) + self.assertEqual(graph.out_edges(9), [(9, 14, None)]) + self.assertEqual(graph.out_edges(17), [(17, 18, None)]) + self.assertEqual(graph.out_edges(19), []) + self.assertEqual(graph.in_edges(0), []) + self.assertEqual(graph.in_edges(2), [(1, 2, None)]) + self.assertEqual(graph.in_edges(5), [(0, 5, None)]) + self.assertEqual(graph.in_edges(7), [(6, 7, None),(2, 7, None)]) + self.assertEqual(graph.in_edges(19), [(18, 19, None), (14, 19, None)]) + + def test_directed_grid_graph_more_dimensions_weights(self): + graph = retworkx.generators.directed_grid_graph(4, 5, weights=list(range(16))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(16)]+[None]*4, graph.nodes()) + self.assertEqual(len(graph.edges()), 31) + self.assertEqual(graph.out_edges(0), [(0, 1, None),(0, 5, None)]) + self.assertEqual(graph.out_edges(7), [(7, 8, None),(7, 12, None)]) + self.assertEqual(graph.out_edges(9), [(9, 14, None)]) + self.assertEqual(graph.out_edges(17), [(17, 18, None)]) + self.assertEqual(graph.out_edges(19), []) + self.assertEqual(graph.in_edges(0), []) + self.assertEqual(graph.in_edges(2), [(1, 2, None)]) + self.assertEqual(graph.in_edges(5), [(0, 5, None)]) + self.assertEqual(graph.in_edges(7), [(6, 7, None),(2, 7, None)]) + self.assertEqual(graph.in_edges(19), [(18, 19, None), (14, 19, None)]) + + def test_directed_grid_graph_less_dimensions_weights(self): + graph = retworkx.generators.directed_grid_graph(4, 5, weights=list(range(24))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)], graph.nodes()) + self.assertEqual(len(graph.edges()), 31) + self.assertEqual(graph.out_edges(0), [(0, 1, None),(0, 5, None)]) + self.assertEqual(graph.out_edges(7), [(7, 8, None),(7, 12, None)]) + self.assertEqual(graph.out_edges(9), [(9, 14, None)]) + self.assertEqual(graph.out_edges(17), [(17, 18, None)]) + self.assertEqual(graph.out_edges(19), []) + self.assertEqual(graph.in_edges(0), []) + self.assertEqual(graph.in_edges(2), [(1, 2, None)]) + self.assertEqual(graph.in_edges(5), [(0, 5, None)]) + self.assertEqual(graph.in_edges(7), [(6, 7, None),(2, 7, None)]) + self.assertEqual(graph.in_edges(19), [(18, 19, None), (14, 19, None)]) + + def test_grid_directed_no_weights_or_dim(self): + with self.assertRaises(IndexError): + retworkx.generators.directed_mesh_graph() + retworkx.generators.directed_mesh_graph(rows = 5, weights=[1,2,3,4,5]) + retworkx.generators.directed_mesh_graph(cols = 5, weights=[1,2,3,4,5]) + + def test_grid_graph_dimensions(self): + graph = retworkx.generators.grid_graph(4, 5) + self.assertEqual(len(graph), 20) + self.assertEqual(len(graph.edges()), 31) + + def test_grid_graph_weights(self): + graph = retworkx.generators.grid_graph(weights=list(range(20))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)], graph.nodes()) + self.assertEqual(len(graph.edges()), 19) + + def test_grid_graph_dimensions_weights(self): + graph = retworkx.generators.grid_graph(4, 5, weights=list(range(20))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)], graph.nodes()) + self.assertEqual(len(graph.edges()), 31) + + graph = retworkx.generators.grid_graph(4, 5, weights=list(range(16))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)] + [None]*4, graph.nodes()) + self.assertEqual(len(graph.edges()), 31) + + graph = retworkx.generators.grid_graph(4, 5, weights=list(range(24))) + self.assertEqual(len(graph), 20) + self.assertEqual([x for x in range(20)], graph.nodes()) + self.assertEqual(len(graph.edges()), 31) + + def test_grid_no_weights_or_dim(self): + with self.assertRaises(IndexError): + retworkx.generators.grid_graph() + retworkx.generators.grid_graph(rows = 5, weights=[1,2,3,4,5]) + retworkx.generators.grid_graph(cols = 5, weights=[1,2,3,4,5]) From 266298345dd72e2651e06ff28cdac80600bb6bd3 Mon Sep 17 00:00:00 2001 From: Bhaavan Goel Date: Thu, 12 Nov 2020 13:57:29 +0530 Subject: [PATCH 3/6] Lint refactoring --- src/generators.rs | 20 ++++++++-------- tests/generators/test_grid.py | 45 +++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/generators.rs b/src/generators.rs index 67c4b42a9..04be23e7b 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -660,11 +660,11 @@ pub fn directed_mesh_graph( /// :param int rows: The number of rows to generate the graph with. /// If specified, cols also need to be specified /// :param list cols: The number of rows to generate the graph with. -/// If specified, rows also need to be specified. rows*cols +/// If specified, rows also need to be specified. rows*cols /// defines the number of nodes in the graph -/// :param list weights: A list of node weights. If rows and cols -/// are not specified, then a linear graph containing all the -/// values in weights list is created. +/// :param list weights: A list of node weights. If rows and cols +/// are not specified, then a linear graph containing all the +/// values in weights list is created. /// If number of nodes(rows*cols) is less than length of /// weights list, the trailing weights are ignored. /// If number of nodes(rows*cols) is greater than length of @@ -712,7 +712,7 @@ pub fn grid_graph( } let mut node_cnt = num_nodes; - + for weight in weights { if node_cnt <= 0 { break; @@ -760,11 +760,11 @@ pub fn grid_graph( //// :param int rows: The number of rows to generate the graph with. /// If specified, cols also need to be specified. /// :param list cols: The number of rows to generate the graph with. -/// If specified, rows also need to be specified. rows*cols +/// If specified, rows also need to be specified. rows*cols /// defines the number of nodes in the graph. -/// :param list weights: A list of node weights. If rows and cols -/// are not specified, then a linear graph containing all the -/// values in weights list is created. +/// :param list weights: A list of node weights. If rows and cols +/// are not specified, then a linear graph containing all the +/// values in weights list is created. /// If number of nodes(rows*cols) is less than length of /// weights list, the trailing weights are ignored. /// If number of nodes(rows*cols) is greater than length of @@ -815,7 +815,7 @@ pub fn directed_grid_graph( } let mut node_cnt = num_nodes; - + for weight in weights { if node_cnt <= 0 { break; diff --git a/tests/generators/test_grid.py b/tests/generators/test_grid.py index 3f239996e..fe6cbf60f 100644 --- a/tests/generators/test_grid.py +++ b/tests/generators/test_grid.py @@ -21,15 +21,15 @@ def test_directed_grid_graph_dimensions(self): graph = retworkx.generators.directed_grid_graph(4, 5) self.assertEqual(len(graph), 20) self.assertEqual(len(graph.edges()), 31) - self.assertEqual(graph.out_edges(0), [(0, 1, None),(0, 5, None)]) - self.assertEqual(graph.out_edges(7), [(7, 8, None),(7, 12, None)]) + self.assertEqual(graph.out_edges(0), [(0, 1, None), (0, 5, None)]) + self.assertEqual(graph.out_edges(7), [(7, 8, None), (7, 12, None)]) self.assertEqual(graph.out_edges(9), [(9, 14, None)]) self.assertEqual(graph.out_edges(17), [(17, 18, None)]) self.assertEqual(graph.out_edges(19), []) self.assertEqual(graph.in_edges(0), []) self.assertEqual(graph.in_edges(2), [(1, 2, None)]) self.assertEqual(graph.in_edges(5), [(0, 5, None)]) - self.assertEqual(graph.in_edges(7), [(6, 7, None),(2, 7, None)]) + self.assertEqual(graph.in_edges(7), [(6, 7, None), (2, 7, None)]) self.assertEqual(graph.in_edges(19), [(18, 19, None), (14, 19, None)]) def test_directed_grid_graph_weights(self): @@ -46,58 +46,61 @@ def test_directed_grid_graph_weights(self): self.assertEqual(graph.in_edges(0), []) def test_directed_grid_graph_dimensions_weights(self): - graph = retworkx.generators.directed_grid_graph(4, 5, weights=list(range(20))) + graph = retworkx.generators.directed_grid_graph( + 4, 5, weights=list(range(20))) self.assertEqual(len(graph), 20) self.assertEqual([x for x in range(20)], graph.nodes()) self.assertEqual(len(graph.edges()), 31) - self.assertEqual(graph.out_edges(0), [(0, 1, None),(0, 5, None)]) - self.assertEqual(graph.out_edges(7), [(7, 8, None),(7, 12, None)]) + self.assertEqual(graph.out_edges(0), [(0, 1, None), (0, 5, None)]) + self.assertEqual(graph.out_edges(7), [(7, 8, None), (7, 12, None)]) self.assertEqual(graph.out_edges(9), [(9, 14, None)]) self.assertEqual(graph.out_edges(17), [(17, 18, None)]) self.assertEqual(graph.out_edges(19), []) self.assertEqual(graph.in_edges(0), []) self.assertEqual(graph.in_edges(2), [(1, 2, None)]) self.assertEqual(graph.in_edges(5), [(0, 5, None)]) - self.assertEqual(graph.in_edges(7), [(6, 7, None),(2, 7, None)]) + self.assertEqual(graph.in_edges(7), [(6, 7, None), (2, 7, None)]) self.assertEqual(graph.in_edges(19), [(18, 19, None), (14, 19, None)]) def test_directed_grid_graph_more_dimensions_weights(self): - graph = retworkx.generators.directed_grid_graph(4, 5, weights=list(range(16))) + graph = retworkx.generators.directed_grid_graph( + 4, 5, weights=list(range(16))) self.assertEqual(len(graph), 20) self.assertEqual([x for x in range(16)]+[None]*4, graph.nodes()) self.assertEqual(len(graph.edges()), 31) - self.assertEqual(graph.out_edges(0), [(0, 1, None),(0, 5, None)]) - self.assertEqual(graph.out_edges(7), [(7, 8, None),(7, 12, None)]) + self.assertEqual(graph.out_edges(0), [(0, 1, None), (0, 5, None)]) + self.assertEqual(graph.out_edges(7), [(7, 8, None), (7, 12, None)]) self.assertEqual(graph.out_edges(9), [(9, 14, None)]) self.assertEqual(graph.out_edges(17), [(17, 18, None)]) self.assertEqual(graph.out_edges(19), []) self.assertEqual(graph.in_edges(0), []) self.assertEqual(graph.in_edges(2), [(1, 2, None)]) self.assertEqual(graph.in_edges(5), [(0, 5, None)]) - self.assertEqual(graph.in_edges(7), [(6, 7, None),(2, 7, None)]) + self.assertEqual(graph.in_edges(7), [(6, 7, None), (2, 7, None)]) self.assertEqual(graph.in_edges(19), [(18, 19, None), (14, 19, None)]) def test_directed_grid_graph_less_dimensions_weights(self): - graph = retworkx.generators.directed_grid_graph(4, 5, weights=list(range(24))) + graph = retworkx.generators.directed_grid_graph( + 4, 5, weights=list(range(24))) self.assertEqual(len(graph), 20) self.assertEqual([x for x in range(20)], graph.nodes()) self.assertEqual(len(graph.edges()), 31) - self.assertEqual(graph.out_edges(0), [(0, 1, None),(0, 5, None)]) - self.assertEqual(graph.out_edges(7), [(7, 8, None),(7, 12, None)]) + self.assertEqual(graph.out_edges(0), [(0, 1, None), (0, 5, None)]) + self.assertEqual(graph.out_edges(7), [(7, 8, None), (7, 12, None)]) self.assertEqual(graph.out_edges(9), [(9, 14, None)]) self.assertEqual(graph.out_edges(17), [(17, 18, None)]) self.assertEqual(graph.out_edges(19), []) self.assertEqual(graph.in_edges(0), []) self.assertEqual(graph.in_edges(2), [(1, 2, None)]) self.assertEqual(graph.in_edges(5), [(0, 5, None)]) - self.assertEqual(graph.in_edges(7), [(6, 7, None),(2, 7, None)]) + self.assertEqual(graph.in_edges(7), [(6, 7, None), (2, 7, None)]) self.assertEqual(graph.in_edges(19), [(18, 19, None), (14, 19, None)]) def test_grid_directed_no_weights_or_dim(self): with self.assertRaises(IndexError): - retworkx.generators.directed_mesh_graph() - retworkx.generators.directed_mesh_graph(rows = 5, weights=[1,2,3,4,5]) - retworkx.generators.directed_mesh_graph(cols = 5, weights=[1,2,3,4,5]) + retworkx.generators.directed_grid_graph() + retworkx.generators.directed_grid_graph(rows=5, weights=[1]*5) + retworkx.generators.directed_grid_graph(cols=5, weights=[1]*5) def test_grid_graph_dimensions(self): graph = retworkx.generators.grid_graph(4, 5) @@ -118,7 +121,7 @@ def test_grid_graph_dimensions_weights(self): graph = retworkx.generators.grid_graph(4, 5, weights=list(range(16))) self.assertEqual(len(graph), 20) - self.assertEqual([x for x in range(20)] + [None]*4, graph.nodes()) + self.assertEqual([x for x in range(16)] + [None]*4, graph.nodes()) self.assertEqual(len(graph.edges()), 31) graph = retworkx.generators.grid_graph(4, 5, weights=list(range(24))) @@ -129,5 +132,5 @@ def test_grid_graph_dimensions_weights(self): def test_grid_no_weights_or_dim(self): with self.assertRaises(IndexError): retworkx.generators.grid_graph() - retworkx.generators.grid_graph(rows = 5, weights=[1,2,3,4,5]) - retworkx.generators.grid_graph(cols = 5, weights=[1,2,3,4,5]) + retworkx.generators.grid_graph(rows=5, weights=[1]*5) + retworkx.generators.grid_graph(cols=5, weights=[1]*5) From 58c983b58d21d146cf28f80a762f1d3dc504328d Mon Sep 17 00:00:00 2001 From: Bhaavan Goel Date: Thu, 12 Nov 2020 16:08:15 +0530 Subject: [PATCH 4/6] Minor refactoring --- tests/generators/test_grid.py | 16 ++++++++-------- tests/generators/test_mesh.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/generators/test_grid.py b/tests/generators/test_grid.py index fe6cbf60f..02eb59306 100644 --- a/tests/generators/test_grid.py +++ b/tests/generators/test_grid.py @@ -39,10 +39,10 @@ def test_directed_grid_graph_weights(self): self.assertEqual([x for x in range(20)], graph.nodes()) self.assertEqual(len(graph.edges()), 19) for i in range(19): - self.assertEqual(graph.out_edges(i), [(i, i+1, None)]) + self.assertEqual(graph.out_edges(i), [(i, i + 1, None)]) self.assertEqual(graph.out_edges(19), []) for i in range(1, 20): - self.assertEqual(graph.in_edges(i), [(i-1, i, None)]) + self.assertEqual(graph.in_edges(i), [(i - 1, i, None)]) self.assertEqual(graph.in_edges(0), []) def test_directed_grid_graph_dimensions_weights(self): @@ -66,7 +66,7 @@ def test_directed_grid_graph_more_dimensions_weights(self): graph = retworkx.generators.directed_grid_graph( 4, 5, weights=list(range(16))) self.assertEqual(len(graph), 20) - self.assertEqual([x for x in range(16)]+[None]*4, graph.nodes()) + self.assertEqual([x for x in range(16)] + [None] * 4, graph.nodes()) self.assertEqual(len(graph.edges()), 31) self.assertEqual(graph.out_edges(0), [(0, 1, None), (0, 5, None)]) self.assertEqual(graph.out_edges(7), [(7, 8, None), (7, 12, None)]) @@ -99,8 +99,8 @@ def test_directed_grid_graph_less_dimensions_weights(self): def test_grid_directed_no_weights_or_dim(self): with self.assertRaises(IndexError): retworkx.generators.directed_grid_graph() - retworkx.generators.directed_grid_graph(rows=5, weights=[1]*5) - retworkx.generators.directed_grid_graph(cols=5, weights=[1]*5) + retworkx.generators.directed_grid_graph(rows=5, weights=[1] * 5) + retworkx.generators.directed_grid_graph(cols=5, weights=[1] * 5) def test_grid_graph_dimensions(self): graph = retworkx.generators.grid_graph(4, 5) @@ -121,7 +121,7 @@ def test_grid_graph_dimensions_weights(self): graph = retworkx.generators.grid_graph(4, 5, weights=list(range(16))) self.assertEqual(len(graph), 20) - self.assertEqual([x for x in range(16)] + [None]*4, graph.nodes()) + self.assertEqual([x for x in range(16)] + [None] * 4, graph.nodes()) self.assertEqual(len(graph.edges()), 31) graph = retworkx.generators.grid_graph(4, 5, weights=list(range(24))) @@ -132,5 +132,5 @@ def test_grid_graph_dimensions_weights(self): def test_grid_no_weights_or_dim(self): with self.assertRaises(IndexError): retworkx.generators.grid_graph() - retworkx.generators.grid_graph(rows=5, weights=[1]*5) - retworkx.generators.grid_graph(cols=5, weights=[1]*5) + retworkx.generators.grid_graph(rows=5, weights=[1] * 5) + retworkx.generators.grid_graph(cols=5, weights=[1] * 5) diff --git a/tests/generators/test_mesh.py b/tests/generators/test_mesh.py index 064370fc4..dabd2463b 100644 --- a/tests/generators/test_mesh.py +++ b/tests/generators/test_mesh.py @@ -41,7 +41,7 @@ def test_directed_mesh_graph_weights(self): ls.append((i, j, None)) self.assertEqual(graph.out_edges(i), ls) - def test_path_directed_no_weights_or_num(self): + def test_mesh_directed_no_weights_or_num(self): with self.assertRaises(IndexError): retworkx.generators.directed_mesh_graph() From 78416eca752f37fc2ec0ee75a71bb07587ee0883 Mon Sep 17 00:00:00 2001 From: Bhaavan Goel Date: Thu, 12 Nov 2020 23:27:36 +0530 Subject: [PATCH 5/6] Added Graphviz examples for mesh and grid generators --- src/generators.rs | 112 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 102 insertions(+), 10 deletions(-) diff --git a/src/generators.rs b/src/generators.rs index 04be23e7b..48c8b964b 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -563,6 +563,29 @@ pub fn star_graph( /// :rtype: PyGraph /// :raises IndexError: If neither ``num_nodes`` or ``weights`` are specified /// +/// .. jupyter-execute:: +/// +/// import os +/// import tempfile +/// +/// import pydot +/// from PIL import Image +/// +/// import retworkx.generators +/// +/// graph = retworkx.generators.mesh_graph(4) +/// dot_str = graph.to_dot( +/// lambda node: dict( +/// color='black', fillcolor='lightblue', style='filled')) +/// dot = pydot.graph_from_dot_data(dot_str)[0] +/// +/// with tempfile.TemporaryDirectory() as tmpdirname: +/// tmp_path = os.path.join(tmpdirname, 'dag.png') +/// dot.write_png(tmp_path) +/// image = Image.open(tmp_path) +/// os.remove(tmp_path) +/// image +/// #[pyfunction] #[text_signature = "(/, num_nodes=None, weights=None)"] pub fn mesh_graph( @@ -614,6 +637,29 @@ pub fn mesh_graph( /// :rtype: PyDiGraph /// :raises IndexError: If neither ``num_nodes`` or ``weights`` are specified /// +/// .. jupyter-execute:: +/// +/// import os +/// import tempfile +/// +/// import pydot +/// from PIL import Image +/// +/// import retworkx.generators +/// +/// graph = retworkx.generators.directed_mesh_graph(4) +/// dot_str = graph.to_dot( +/// lambda node: dict( +/// color='black', fillcolor='lightblue', style='filled')) +/// dot = pydot.graph_from_dot_data(dot_str)[0] +/// +/// with tempfile.TemporaryDirectory() as tmpdirname: +/// tmp_path = os.path.join(tmpdirname, 'dag.png') +/// dot.write_png(tmp_path) +/// image = Image.open(tmp_path) +/// os.remove(tmp_path) +/// image +/// #[pyfunction] #[text_signature = "(/, num_nodes=None, weights=None)"] pub fn directed_mesh_graph( @@ -662,19 +708,42 @@ pub fn directed_mesh_graph( /// :param list cols: The number of rows to generate the graph with. /// If specified, rows also need to be specified. rows*cols /// defines the number of nodes in the graph -/// :param list weights: A list of node weights. If rows and cols -/// are not specified, then a linear graph containing all the -/// values in weights list is created. +/// :param list weights: A list of node weights. Nodes are filled row wise. +/// If rows and cols are not specified, then a linear graph containing +/// all the values in weights list is created. /// If number of nodes(rows*cols) is less than length of /// weights list, the trailing weights are ignored. /// If number of nodes(rows*cols) is greater than length of -/// weights list, extra nodes with None weight are appended +/// weights list, extra nodes with None weight are appended. /// -/// :returns: The generated star graph +/// :returns: The generated grid graph /// :rtype: PyGraph /// :raises IndexError: If neither ``rows`` or ``cols`` and ``weights`` are /// specified /// +/// .. jupyter-execute:: +/// +/// import os +/// import tempfile +/// +/// import pydot +/// from PIL import Image +/// +/// import retworkx.generators +/// +/// graph = retworkx.generators.grid_graph(2, 3) +/// dot_str = graph.to_dot( +/// lambda node: dict( +/// color='black', fillcolor='lightblue', style='filled')) +/// dot = pydot.graph_from_dot_data(dot_str)[0] +/// +/// with tempfile.TemporaryDirectory() as tmpdirname: +/// tmp_path = os.path.join(tmpdirname, 'dag.png') +/// dot.write_png(tmp_path) +/// image = Image.open(tmp_path) +/// os.remove(tmp_path) +/// image +/// #[pyfunction] #[text_signature = "(/, rows=None, cols=None, weights=None)"] pub fn grid_graph( @@ -757,14 +826,14 @@ pub fn grid_graph( /// Generate a directed grid graph. The edges propagate towards right and /// bottom direction if ``bidirectional`` is ``false`` /// -//// :param int rows: The number of rows to generate the graph with. +/// :param int rows: The number of rows to generate the graph with. /// If specified, cols also need to be specified. /// :param list cols: The number of rows to generate the graph with. /// If specified, rows also need to be specified. rows*cols /// defines the number of nodes in the graph. -/// :param list weights: A list of node weights. If rows and cols -/// are not specified, then a linear graph containing all the -/// values in weights list is created. +/// :param list weights: A list of node weights. Nodes are filled row wise. +/// If rows and cols are not specified, then a linear graph containing +/// all the values in weights list is created. /// If number of nodes(rows*cols) is less than length of /// weights list, the trailing weights are ignored. /// If number of nodes(rows*cols) is greater than length of @@ -772,11 +841,34 @@ pub fn grid_graph( /// :param bidirectional: A parameter to indicate if edges should exist in /// both directions between nodes /// -/// :returns: The generated star graph +/// :returns: The generated grid graph /// :rtype: PyDiGraph /// :raises IndexError: If neither ``rows`` or ``cols`` and ``weights`` are /// specified /// +/// .. jupyter-execute:: +/// +/// import os +/// import tempfile +/// +/// import pydot +/// from PIL import Image +/// +/// import retworkx.generators +/// +/// graph = retworkx.generators.directed_grid_graph(2, 3) +/// dot_str = graph.to_dot( +/// lambda node: dict( +/// color='black', fillcolor='lightblue', style='filled')) +/// dot = pydot.graph_from_dot_data(dot_str)[0] +/// +/// with tempfile.TemporaryDirectory() as tmpdirname: +/// tmp_path = os.path.join(tmpdirname, 'dag.png') +/// dot.write_png(tmp_path) +/// image = Image.open(tmp_path) +/// os.remove(tmp_path) +/// image +/// #[pyfunction(bidirectional = "false")] #[text_signature = "(/, rows=None, cols=None, weights=None, bidirectional=False)"] pub fn directed_grid_graph( From 3cac6e388c981ed730b0083d4d8b7e2945cf8f02 Mon Sep 17 00:00:00 2001 From: Bhaavan Goel Date: Fri, 13 Nov 2020 21:45:58 +0530 Subject: [PATCH 6/6] Minor refactors Fixes Rust Lint error where <0 check was implemented on a usize variable --- src/generators.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generators.rs b/src/generators.rs index 48c8b964b..0944db756 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -783,7 +783,7 @@ pub fn grid_graph( let mut node_cnt = num_nodes; for weight in weights { - if node_cnt <= 0 { + if node_cnt == 0 { break; } let index = graph.add_node(weight); @@ -909,7 +909,7 @@ pub fn directed_grid_graph( let mut node_cnt = num_nodes; for weight in weights { - if node_cnt <= 0 { + if node_cnt == 0 { break; } let index = graph.add_node(weight);