Skip to content

Commit

Permalink
Add Bidirectional parameters for directed graph generators (#189)
Browse files Browse the repository at this point in the history
Allows users to pass optional parameter 'bidirectional' to add edges in both directions
For directed_star_graph generator if 'bidirectional' is set to 'True', the 'inward' parameter is ignored.

Fixes #184

* Added Bidirectional parameters for directed graph generators

* Sets bidirectional parameter as false by default using pyfunction macro

* Refactoring
  • Loading branch information
darknight009 authored Nov 3, 2020
1 parent a0a809d commit f0d5254
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 11 deletions.
51 changes: 40 additions & 11 deletions src/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ where
/// :param list weights: A list of node weights, the first element in the list
/// will be the center node of the cycle graph. If both ``num_node`` and
/// ``weights`` are set this will be ignored and ``weights`` will be used.
/// :param bool bidirectional: Adds edges in both directions between two nodes
/// if set to ``True``. Default value is ``False``
///
/// :returns: The generated cycle graph
/// :rtype: PyDiGraph
Expand Down Expand Up @@ -68,12 +70,13 @@ where
/// os.remove(tmp_path)
/// image
///
#[pyfunction]
#[text_signature = "(/, num_nodes=None, weights=None)"]
#[pyfunction(bidirectional = "false")]
#[text_signature = "(/, num_nodes=None, weights=None, bidirectional=False)"]
pub fn directed_cycle_graph(
py: Python,
num_nodes: Option<usize>,
weights: Option<Vec<PyObject>>,
bidirectional: bool,
) -> PyResult<digraph::PyDiGraph> {
let mut graph = StableDiGraph::<PyObject, PyObject>::default();
if weights.is_none() && num_nodes.is_none() {
Expand Down Expand Up @@ -101,13 +104,21 @@ pub fn directed_cycle_graph(
};
for (node_a, node_b) in pairwise(nodes) {
match node_a {
Some(node_a) => graph.add_edge(node_a, node_b, py.None()),
Some(node_a) => {
if bidirectional {
graph.add_edge(node_b, node_a, py.None());
}
graph.add_edge(node_a, node_b, py.None());
}
None => continue,
};
}
let last_node_index = NodeIndex::new(node_len - 1);
let first_node_index = NodeIndex::new(0);
graph.add_edge(last_node_index, first_node_index, py.None());
if bidirectional {
graph.add_edge(first_node_index, last_node_index, py.None());
}
Ok(digraph::PyDiGraph {
graph,
node_removed: false,
Expand Down Expand Up @@ -206,6 +217,8 @@ pub fn cycle_graph(
/// :param list weights: A list of node weights, the first element in the list
/// will be the center node of the path graph. If both ``num_node`` and
/// ``weights`` are set this will be ignored and ``weights`` will be used.
/// :param bool bidirectional: Adds edges in both directions between two nodes
/// if set to ``True``. Default value is ``False``
///
/// :returns: The generated path graph
/// :rtype: PyDiGraph
Expand Down Expand Up @@ -234,12 +247,13 @@ pub fn cycle_graph(
/// os.remove(tmp_path)
/// image
///
#[pyfunction]
#[text_signature = "(/, num_nodes=None, weights=None)"]
#[pyfunction(bidirectional = "false")]
#[text_signature = "(/, num_nodes=None, weights=None, bidirectional=False)"]
pub fn directed_path_graph(
py: Python,
num_nodes: Option<usize>,
weights: Option<Vec<PyObject>>,
bidirectional: bool,
) -> PyResult<digraph::PyDiGraph> {
let mut graph = StableDiGraph::<PyObject, PyObject>::default();
if weights.is_none() && num_nodes.is_none() {
Expand All @@ -262,7 +276,14 @@ pub fn directed_path_graph(
};
for (node_a, node_b) in pairwise(nodes) {
match node_a {
Some(node_a) => graph.add_edge(node_a, node_b, py.None()),
Some(node_a) => {
if bidirectional {
graph.add_edge(node_a, node_b, py.None());
graph.add_edge(node_b, node_a, py.None());
} else {
graph.add_edge(node_a, node_b, py.None());
}
}
None => continue,
};
}
Expand Down Expand Up @@ -356,8 +377,11 @@ pub fn path_graph(
/// :param list weights: A list of node weights, the first element in the list
/// will be the center node of the star graph. If both ``num_node`` and
/// ``weights`` are set this will be ignored and ``weights`` will be used.
/// :param bool inward: if set ``True`` the nodes will be directed towards the
/// center node
/// :param bool bidirectional: Adds edges in both directions between two nodes
/// if set to ``True``. Default value is ``False``.
/// :param bool inward: If set ``True`` the nodes will be directed towards the
/// center node. This parameter is ignored if ``bidirectional`` is set to
/// ``True``.
///
/// :returns: The generated star graph
/// :rtype: PyDiGraph
Expand Down Expand Up @@ -409,13 +433,14 @@ pub fn path_graph(
/// os.remove(tmp_path)
/// image
///
#[pyfunction(inward = "false")]
#[text_signature = "(/, num_nodes=None, weights=None, inward=False)"]
#[pyfunction(inward = "false", bidirectional = "false")]
#[text_signature = "(/, num_nodes=None, weights=None, inward=False, bidirectional=False)"]
pub fn directed_star_graph(
py: Python,
num_nodes: Option<usize>,
weights: Option<Vec<PyObject>>,
inward: bool,
bidirectional: bool,
) -> PyResult<digraph::PyDiGraph> {
let mut graph = StableDiGraph::<PyObject, PyObject>::default();
if weights.is_none() && num_nodes.is_none() {
Expand All @@ -437,7 +462,11 @@ pub fn directed_star_graph(
.collect(),
};
for node in nodes[1..].iter() {
if inward {
//Add edges in both directions if bidirection is True
if bidirectional {
graph.add_edge(*node, nodes[0], py.None());
graph.add_edge(nodes[0], *node, py.None());
} else if inward {
graph.add_edge(*node, nodes[0], py.None());
} else {
graph.add_edge(nodes[0], *node, py.None());
Expand Down
13 changes: 13 additions & 0 deletions tests/generators/test_cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ def test_directed_cycle_graph_weights(self):
self.assertEqual(graph.out_edges(i), [(i, i + 1, None)])
self.assertEqual(graph.out_edges(19), [(19, 0, None)])

def test_directed_cycle_graph_bidirectional(self):
graph = retworkx.generators.directed_cycle_graph(
20, bidirectional=True)
self.assertEqual(graph.out_edges(0), [(0, 19, None), (0, 1, None)])
self.assertEqual(graph.in_edges(0), [(19, 0, None), (1, 0, None)])
for i in range(1, 19):
self.assertEqual(
graph.out_edges(i), [(i, i + 1, None), (i, i - 1, None)])
self.assertEqual(
graph.in_edges(i), [(i + 1, i, None), (i - 1, i, None)])
self.assertEqual(graph.out_edges(19), [(19, 0, None), (19, 18, None)])
self.assertEqual(graph.in_edges(19), [(0, 19, None), (18, 19, None)])

def test_cycle_directed_no_weights_or_num(self):
with self.assertRaises(IndexError):
retworkx.generators.directed_cycle_graph()
Expand Down
13 changes: 13 additions & 0 deletions tests/generators/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ def test_directed_path_graph_weights(self):
self.assertEqual(graph.out_edges(i), [(i, i + 1, None)])
self.assertEqual(graph.out_edges(19), [])

def test_directed_path_graph_bidirectional(self):
graph = retworkx.generators.directed_path_graph(
20, bidirectional=True)
self.assertEqual(graph.out_edges(0), [(0, 1, None)])
self.assertEqual(graph.in_edges(0), [(1, 0, None)])
for i in range(1, 19):
self.assertEqual(
graph.out_edges(i), [(i, i + 1, None), (i, i - 1, None)])
self.assertEqual(
graph.in_edges(i), [(i + 1, i, None), (i - 1, i, None)])
self.assertEqual(graph.out_edges(19), [(19, 18, None)])
self.assertEqual(graph.in_edges(19), [(18, 19, None)])

def test_path_directed_no_weights_or_num(self):
with self.assertRaises(IndexError):
retworkx.generators.directed_path_graph()
Expand Down
37 changes: 37 additions & 0 deletions tests/generators/test_star.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,43 @@ def test_directed_star_graph_weights(self):
expected_edges = sorted([(0, i, None) for i in range(1, 20)])
self.assertEqual(sorted(graph.out_edges(0)), expected_edges)

def test_directed_star_graph_bidirectional(self):
graph = retworkx.generators.directed_star_graph(
20, bidirectional=True)
outw = []
inw = []
for i in range(1, 20):
outw.append((0, i, None))
inw.append((i, 0, None))
self.assertEqual(graph.out_edges(i), [(i, 0, None)])
self.assertEqual(graph.in_edges(i), [(0, i, None)])
self.assertEqual(graph.out_edges(0), outw[::-1])
self.assertEqual(graph.in_edges(0), inw[::-1])

def test_directed_star_graph_bidirectional_inward(self):
graph = retworkx.generators.directed_star_graph(
20, bidirectional=True, inward=True)
outw = []
inw = []
for i in range(1, 20):
outw.append((0, i, None))
inw.append((i, 0, None))
self.assertEqual(graph.out_edges(i), [(i, 0, None)])
self.assertEqual(graph.in_edges(i), [(0, i, None)])
self.assertEqual(graph.out_edges(0), outw[::-1])
self.assertEqual(graph.in_edges(0), inw[::-1])
graph = retworkx.generators.directed_star_graph(
20, bidirectional=True, inward=False)
outw = []
inw = []
for i in range(1, 20):
outw.append((0, i, None))
inw.append((i, 0, None))
self.assertEqual(graph.out_edges(i), [(i, 0, None)])
self.assertEqual(graph.in_edges(i), [(0, i, None)])
self.assertEqual(graph.out_edges(0), outw[::-1])
self.assertEqual(graph.in_edges(0), inw[::-1])

def test_star_directed_graph_weights_inward(self):
graph = retworkx.generators.directed_star_graph(
weights=list(range(20)), inward=True)
Expand Down

0 comments on commit f0d5254

Please sign in to comment.