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

Add binomial tree graph generator #299

Merged
merged 40 commits into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
127f685
Merge pull request #2 from Qiskit/master
nahumsa Apr 1, 2021
75bc722
Add binomial tree graph
nahumsa Apr 1, 2021
5ff3f47
update documentation
nahumsa Apr 1, 2021
aff705d
add release notes
nahumsa Apr 1, 2021
0a6749c
lint
nahumsa Apr 1, 2021
4b5241f
lint
nahumsa Apr 1, 2021
af129b1
lint
nahumsa Apr 1, 2021
5eb6b56
fix docs
nahumsa Apr 1, 2021
c7a6891
remove option for order
nahumsa Apr 1, 2021
3a52c15
change order to u32
nahumsa Apr 8, 2021
d9e9bb8
clippy
nahumsa Apr 8, 2021
56e6beb
Merge branch 'master' into binomial-tree-graph
mtreinish Apr 9, 2021
2741051
add directed binomial tree graph
nahumsa Apr 26, 2021
e2c9a4d
Merge branch 'binomial-tree-graph' of https://github.com/nahumsa/retw…
nahumsa Apr 26, 2021
199ee00
lint
nahumsa Apr 26, 2021
1a2e8fd
fix positional argument
nahumsa May 4, 2021
764f487
updated test
nahumsa May 4, 2021
590e824
updated tests
nahumsa May 4, 2021
4fa210f
add case mismatch weights
nahumsa May 4, 2021
9639284
Merge branch 'binomial-tree-graph' of https://github.com/nahumsa/retw…
nahumsa May 4, 2021
769b04b
add edgelist assertion
nahumsa May 4, 2021
7f8f78a
lint
nahumsa May 4, 2021
d336599
add find_edge before adding edge
nahumsa May 7, 2021
7449f7c
add bidirectional tests
nahumsa May 7, 2021
43158ee
clippy
nahumsa May 7, 2021
ee8017e
lint
nahumsa May 8, 2021
d598afd
lint
nahumsa May 8, 2021
ed271f4
Merge branch 'main' into binomial-tree-graph
nahumsa May 8, 2021
23e4e5e
run black
nahumsa May 8, 2021
e37eda2
Merge remote-tracking branch 'upstream/main' into binomial-tree-graph
nahumsa May 18, 2021
7aa3d3c
fix merge leftover
nahumsa May 18, 2021
f977eaf
fix lint
nahumsa May 18, 2021
0fef932
add newline release notes
nahumsa May 18, 2021
c33983f
Merge branch 'main' into binomial-tree-graph
nahumsa May 20, 2021
7b3238a
add `.is_none()` assertion
nahumsa May 21, 2021
534d49a
`order` required arg
nahumsa May 21, 2021
ad0d838
`order` required arg
nahumsa May 21, 2021
c5fe7d2
Use `.is_none()` instead of `== None` everywhere
mtreinish May 21, 2021
fb2c810
Merge branch 'main' into binomial-tree-graph
mtreinish May 21, 2021
fd1707a
Run cargo fmt
mtreinish May 21, 2021
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
1 change: 1 addition & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Generators
retworkx.generators.directed_mesh_graph
retworkx.generators.grid_graph
retworkx.generators.directed_grid_graph
retworkx.generators.binomial_tree_graph
retworkx.generators.hexagonal_lattice_graph
retworkx.generators.directed_hexagonal_lattice_graph

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
features:
- |
Added a new generator for constructing a binomial tree graph (:func:`retworkx.generators.binomial_tree_graph`).
243 changes: 243 additions & 0 deletions src/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use std::iter;
use petgraph::algo;
use petgraph::graph::NodeIndex;
use petgraph::stable_graph::{StableDiGraph, StableUnGraph};
use petgraph::visit::{EdgeRef, IntoEdgeReferences};

use pyo3::exceptions::PyIndexError;
use pyo3::prelude::*;
Expand Down Expand Up @@ -983,6 +984,246 @@ pub fn directed_grid_graph(
})
}

/// Generate an undirected binomial tree of order n recursively.
///
/// :param int order: Order of the binomial tree.
/// :param list weights: A list of node weights. If the number of weights is
/// less than 2**order extra nodes with with None will be appended.
/// :param bool multigraph: When set to False the output
/// :class:`~retworkx.PyGraph` object will not be 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: A binomial tree with 2^n vertices and 2^n - 1 edges.
/// :rtype: PyGraph
/// :raises IndexError: If the lenght of ``weights`` is greater that 2^n
///
/// .. jupyter-execute::
///
/// import os
/// import tempfile
///
/// import pydot
/// from PIL import Image
///
/// import retworkx.generators
///
/// graph = retworkx.generators.binomial_tree_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(multigraph = true)]
#[text_signature = "(order, /, weights=None, multigraph=True)"]
pub fn binomial_tree_graph(
py: Python,
order: u32,
weights: Option<Vec<PyObject>>,
multigraph: bool,
) -> PyResult<graph::PyGraph> {
let mut graph = StableUnGraph::<PyObject, PyObject>::default();

let num_nodes = usize::pow(2, order);

let nodes: Vec<NodeIndex> = match weights {
Some(weights) => {
let mut node_list: Vec<NodeIndex> = Vec::new();

let mut node_count = num_nodes;

if weights.len() > num_nodes {
return Err(PyIndexError::new_err(
"weights should be <= 2**order",
));
}

for weight in weights {
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
let index = graph.add_node(weight);
node_list.push(index);
node_count -= 1;
}

for _i in 0..node_count {
let index = graph.add_node(py.None());
node_list.push(index);
}

node_list
}

None => (0..num_nodes).map(|_| graph.add_node(py.None())).collect(),
};

let mut n = 1;

for _ in 0..order {
let edges: Vec<(NodeIndex, NodeIndex)> = graph
.edge_references()
.map(|e| (e.source(), e.target()))
.collect();

for (source, target) in edges {
let source_index = source.index();
let target_index = target.index();

graph.add_edge(
nodes[source_index + n],
nodes[target_index + n],
py.None(),
);
}

graph.add_edge(nodes[0], nodes[n], py.None());

n *= 2;
}

Ok(graph::PyGraph {
graph,
node_removed: false,
multigraph,
})
}

/// Generate an undirected binomial tree of order n recursively.
/// The edges propagate towards right and bottom direction if ``bidirectional`` is ``false``
///
/// :param int order: Order of the binomial tree.
/// :param list weights: A list of node weights. If the number of weights is
/// less than 2**order extra nodes with None will be appended.
/// :param bidirectional: A parameter to indicate if edges should exist in
/// both directions between nodes
///
/// :returns: A directed binomial tree with 2^n vertices and 2^n - 1 edges.
/// :rtype: PyDiGraph
/// :raises IndexError: If the lenght of ``weights`` is greater that 2^n
///
/// .. jupyter-execute::
///
/// import os
/// import tempfile
///
/// import pydot
/// from PIL import Image
///
/// import retworkx.generators
///
/// graph = retworkx.generators.directed_binomial_tree_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(bidirectional = "false")]
#[text_signature = "(order, /, weights=None, bidirectional=False)"]
pub fn directed_binomial_tree_graph(
py: Python,
order: u32,
weights: Option<Vec<PyObject>>,
bidirectional: bool,
) -> PyResult<digraph::PyDiGraph> {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason to not do a multigraph flag here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really, I just did this because all other directed generators have this pattern. Should we add multigraph on all of them?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I didn't realize it was missing as an option from all the digraph generators. I think then it's fine to exclude it here for now, but we should push a follow up PR and add a multigraph option to all the digraph generators.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I'll raise an issue and submit a PR for that

let mut graph = StableDiGraph::<PyObject, PyObject>::default();

let num_nodes = usize::pow(2, order);

let nodes: Vec<NodeIndex> = match weights {
Some(weights) => {
let mut node_list: Vec<NodeIndex> = Vec::new();
let mut node_count = num_nodes;

if weights.len() > num_nodes {
return Err(PyIndexError::new_err(
"weights should be <= 2**order",
));
}

for weight in weights {
let index = graph.add_node(weight);
node_list.push(index);
node_count -= 1;
}

for _i in 0..node_count {
let index = graph.add_node(py.None());
node_list.push(index);
}

node_list
}

None => (0..num_nodes).map(|_| graph.add_node(py.None())).collect(),
};

let mut n = 1;

for _ in 0..order {
let edges: Vec<(NodeIndex, NodeIndex)> = graph
.edge_references()
.map(|e| (e.source(), e.target()))
.collect();

for (source, target) in edges {
let source_index = source.index();
let target_index = target.index();

if graph.find_edge(nodes[source_index + n], nodes[target_index + n])
== None
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
{
graph.add_edge(
nodes[source_index + n],
nodes[target_index + n],
py.None(),
);
}

if bidirectional
&& graph
.find_edge(nodes[target_index + n], nodes[source_index + n])
== None
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
{
graph.add_edge(
nodes[target_index + n],
nodes[source_index + n],
py.None(),
);
}
}

if graph.find_edge(nodes[0], nodes[n]).is_none() {
graph.add_edge(nodes[0], nodes[n], py.None());
}

if bidirectional && graph.find_edge(nodes[n], nodes[0]) == None {
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
graph.add_edge(nodes[n], nodes[0], py.None());
}

n *= 2;
}

Ok(digraph::PyDiGraph {
graph,
node_removed: false,
check_cycle: false,
cycle_state: algo::DfsSpace::default(),
multigraph: true,
})
}

/// Generate an undirected hexagonal lattice graph.
///
/// :param int rows: The number of rows to generate the graph with.
Expand Down Expand Up @@ -1205,6 +1446,8 @@ pub fn generators(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(directed_mesh_graph))?;
m.add_wrapped(wrap_pyfunction!(grid_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_grid_graph))?;
m.add_wrapped(wrap_pyfunction!(binomial_tree_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_binomial_tree_graph))?;
m.add_wrapped(wrap_pyfunction!(hexagonal_lattice_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_hexagonal_lattice_graph))?;
Ok(())
Expand Down
Loading