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

Added ladder graph generators and test_ladder #705

Open
wants to merge 2 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
88 changes: 88 additions & 0 deletions src/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2568,6 +2568,93 @@ pub fn directed_complete_graph(
directed_mesh_graph(py, num_nodes, weights, multigraph)
}

/// Generate a ladder graph
///
/// :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.
/// :param bool multigraph: When set to False the output
/// :class:`~rustworkx.PyGraph` object will not be a multigraph and
/// won't allow parallel edges to be added. Instead
/// calls which would create a parallel edge will update the existing edge.
///
/// :returns: The generated ladder graph
/// :rtype: PyGraph
/// :raises IndexError: If neither ``num_nodes`` or ``weights`` are specified
///
/// .. jupyter-execute::
///
/// import rustworkx.generators
/// from rustworkx.visualization import mpl_draw
///
/// graph = rustworkx.generators.ladder_graph(5)
/// mpl_draw(graph)
///
#[pyfunction(multigraph = true)]
#[pyo3(text_signature = "(/, num_nodes=None, weights=None, multigraph=True")]
pub fn ladder_graph(
py: Python,
num_nodes: Option<usize>,
weights: Option<Vec<PyObject>>,
multigraph: bool,
) -> PyResult<graph::PyGraph> {
if weights.is_none() && num_nodes.is_none() {
return Err(PyIndexError::new_err(
"num_nodes and weights list not specified",
));
} else if num_nodes.is_none() && weights.as_ref().unwrap().len() % 2 == 1 {
return Err(PyIndexError::new_err(
"length of weights must be even numbers",
));
}
let node_len = if weights.is_none() {
num_nodes.unwrap()
} else {
weights.as_ref().unwrap().len() / 2
};

if node_len == 0 {
return Ok(graph::PyGraph {
graph: StablePyGraph::<Undirected>::default(),
node_removed: false,
multigraph,
attrs: py.None(),
});
}
let num_edges = 3 * node_len - 2;
let mut graph = StablePyGraph::<Undirected>::with_capacity(node_len * 2, num_edges);
match weights {
Some(weights) => {
for weight in weights {
graph.add_node(weight);
}
}
None => {
(0..(node_len * 2)).for_each(|_| {
graph.add_node(py.None());
});
}
};

for rail_a in 0..node_len - 1 {
graph.add_edge(NodeIndex::new(rail_a), NodeIndex::new(rail_a+1), py.None());
let rail_b = rail_a + node_len;
graph.add_edge(NodeIndex::new(rail_b), NodeIndex::new(rail_b+1), py.None());
}
for rung_a in 0..node_len {
graph.add_edge(NodeIndex::new(rung_a), NodeIndex::new(rung_a+node_len), py.None());
}

Ok(graph::PyGraph {
graph,
node_removed: false,
multigraph,
attrs: py.None(),
})
Comment on lines +2603 to +2655
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if weights.is_none() && num_nodes.is_none() {
return Err(PyIndexError::new_err(
"num_nodes and weights list not specified",
));
} else if num_nodes.is_none() && weights.as_ref().unwrap().len() % 2 == 1 {
return Err(PyIndexError::new_err(
"length of weights must be even numbers",
));
}
let node_len = if weights.is_none() {
num_nodes.unwrap()
} else {
weights.as_ref().unwrap().len() / 2
};
if node_len == 0 {
return Ok(graph::PyGraph {
graph: StablePyGraph::<Undirected>::default(),
node_removed: false,
multigraph,
attrs: py.None(),
});
}
let num_edges = 3 * node_len - 2;
let mut graph = StablePyGraph::<Undirected>::with_capacity(node_len * 2, num_edges);
match weights {
Some(weights) => {
for weight in weights {
graph.add_node(weight);
}
}
None => {
(0..(node_len * 2)).for_each(|_| {
graph.add_node(py.None());
});
}
};
for rail_a in 0..node_len - 1 {
graph.add_edge(NodeIndex::new(rail_a), NodeIndex::new(rail_a+1), py.None());
let rail_b = rail_a + node_len;
graph.add_edge(NodeIndex::new(rail_b), NodeIndex::new(rail_b+1), py.None());
}
for rung_a in 0..node_len {
graph.add_edge(NodeIndex::new(rung_a), NodeIndex::new(rung_a+node_len), py.None());
}
Ok(graph::PyGraph {
graph,
node_removed: false,
multigraph,
attrs: py.None(),
})
grid_graph(py, num_nodes, 2, weights, multigraph)

The syntax might not be correct, but the ladder graph is equivalent to a n x 2 grid graph hence we probably want to reuse most of the other code

}

#[pymodule]
pub fn generators(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(cycle_graph))?;
Expand Down Expand Up @@ -2596,5 +2683,6 @@ pub fn generators(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(directed_empty_graph))?;
m.add_wrapped(wrap_pyfunction!(complete_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_complete_graph))?;
m.add_wrapped(wrap_pyfunction!(ladder_graph))?;
Ok(())
}
26 changes: 26 additions & 0 deletions tests/rustworkx_tests/generators/test_ladder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Licensed under the Apache License,

import unittest

import rustworkx


class TestLadderGraph(unittest.TestCase):
def test_ladder_graph(self):
graph = rustworkx.generators.ladder_graph(20)
self.assertEqual(len(graph), 40)
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(len(graph.edges()), 58)

def test_ladder_graph_weights(self):
graph = rustworkx.generators.ladder_graph(weights=list(range(40)))
self.assertEqual(len(graph), 40)
self.assertEqual([x for x in range(40)], graph.nodes())
self.assertEqual(len(graph.edges()), 58)
Comment on lines +12 to +18
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these 2 tests it'd be good to add some assertion on the structure of the edges too. For example, if I built a circuit like:

from rustworkx import rx
graph = rx.PyGraph()
graph.add_nodes_from(range(40))
graph.add_edges_from_no_data([(0, 1)*50)

We should try to make the tests so they assert the structure of the created graph is correct and if it were to change by mistake on a future change to the code the test will fail if it's no longer a ladder graph.


def test_ladder_no_weights_or_num(self):
with self.assertRaises(IndexError):
rustworkx.generators.ladder_graph()

def test_zero_length_ladder_graph(self):
graph = rustworkx.generators.ladder_graph(0)
self.assertEqual(0, len(graph))