Skip to content

Commit

Permalink
Merge branch 'main' into move_random
Browse files Browse the repository at this point in the history
  • Loading branch information
enavarro51 committed May 17, 2023
2 parents c237a86 + 57f873f commit cebf689
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
[![Zenodo](https://img.shields.io/badge/Zenodo-10.5281%2Fzenodo.5879859-blue)](https://doi.org/10.5281/zenodo.5879859)

- You can see the full rendered docs at:
<https://qiskit.org/documentation/rustworkx/dev>
<https://qiskit.org/ecosystem/rustworkx/dev>

|:warning:| The retworkx project has been renamed to **rustworkx**. The use of the
retworkx package will still work for the time being but starting in the 1.0.0
Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@
redirects[f"stubs/{source_str}"] = f"../apiref/{source_str}"

if os.getenv("RETWORKX_LEGACY_DOCS", None) is not None:
redirects["*"] = "https://qiskit.org/documentation/rustworkx/$source.html"
html_baseurl = "https://qiskit.org/documentation/rustworkx/"
redirects["*"] = "https://qiskit.org/ecosystem/rustworkx/$source.html"
html_baseurl = "https://qiskit.org/ecosystem/rustworkx/"


# Version extensions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
features:
- |
Added a new method, :meth:`~.PyDiGraph.make_symmetric`, to the
:class:`~.PyDiGraph` class. This method is used to make all the edges
in the graph symmetric (there is a reverse edge in the graph for each edge).
For example:
.. jupyter-execute::
import rustworkx as rx
from rustworkx.visualization import graphviz_draw
graph = rx.generators.directed_path_graph(5, bidirectional=False)
graph.make_symmetric()
graphviz_draw(graph)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
:meth:`rustworkx.PyGraph.add_edge` and :meth:`rustworkx.PyDiGraph.add_edge` and now raises an
``IndexError`` when one of the nodes does not exist in the graph. Previously, it caused the Python
interpreter to exit with a ``PanicException``
5 changes: 2 additions & 3 deletions rustworkx-core/src/centrality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ mod test_eigenvector_centrality {

macro_rules! assert_almost_equal {
($x:expr, $y:expr, $d:expr) => {
if !($x - $y < $d || $y - $x < $d) {
if ($x - $y).abs() >= $d {
panic!("{} != {} within delta of {}", $x, $y, $d);
}
};
Expand Down Expand Up @@ -753,8 +753,7 @@ mod test_eigenvector_centrality {
let output: Result<Option<Vec<f64>>> = eigenvector_centrality(&g, |_| Ok(2.), None, None);
let result = output.unwrap().unwrap();
let expected_values: Vec<f64> = vec![
0.25368793, 0.19576478, 0.32817092, 0.40430835, 0.48199885, 0.15724483, 0.51346196,
0.32475403,
0.2140437, 0.2009269, 0.1036383, 0.0972886, 0.3113323, 0.4891686, 0.4420605, 0.6016448,
];
for i in 0..8 {
assert_almost_equal!(expected_values[i], result[i], 1e-4);
Expand Down
2 changes: 1 addition & 1 deletion rustworkx-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
//! The release notes for rustworkx-core are included as part of the rustworkx
//! documentation which is hosted at:
//!
//! <https://qiskit.org/documentation/rustworkx/release_notes.html>
//! <https://qiskit.org/ecosystem/rustworkx/release_notes.html>

use std::convert::Infallible;

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def readme():
project_urls={
"Bug Tracker": "https://github.com/Qiskit/rustworkx/issues",
"Source Code": "https://github.com/Qiskit/rustworkx",
"Documentation": "https://qiskit.org/documentation/rustworkx",
"Documentation": "https://qiskit.org/ecosystem/rustworkx/",
},
rust_extensions=RUST_EXTENSIONS,
include_package_data=True,
Expand Down
27 changes: 26 additions & 1 deletion src/coloring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,38 @@ use petgraph::visit::NodeCount;

use rayon::prelude::*;

/// Color a PyGraph using a largest_first strategy greedy graph coloring.
/// Color a :class:`~.PyGraph` object using a greedy graph coloring algorithm.
///
/// This function uses a `largest-first` strategy as described in [1]_ and colors
/// the nodes with higher degree first.
///
/// .. note::
///
/// The coloring problem is NP-hard and this is a heuristic algorithm which
/// may not return an optimal solution.
///
/// :param PyGraph: The input PyGraph object to color
///
/// :returns: A dictionary where keys are node indices and the value is
/// the color
/// :rtype: dict
///
/// .. jupyter-execute::
///
/// import rustworkx as rx
/// from rustworkx.visualization import mpl_draw
///
/// graph = rx.generators.generalized_petersen_graph(5, 2)
/// coloring = rx.graph_greedy_color(graph)
/// colors = [coloring[node] for node in graph.node_indices()]
///
/// # Draw colored graph
/// layout = rx.shell_layout(graph, nlist=[[0, 1, 2, 3, 4],[6, 7, 8, 9, 5]])
/// mpl_draw(graph, node_color=colors, pos=layout)
///
///
/// .. [1] Adrian Kosowski, and Krzysztof Manuszewski, Classical Coloring of Graphs,
/// Graph Colorings, 2-19, 2004. ISBN 0-8218-3458-4.
#[pyfunction]
#[pyo3(text_signature = "(graph, /)")]
pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult<PyObject> {
Expand Down
2 changes: 1 addition & 1 deletion src/connectivity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ pub fn graph_complement(py: Python, graph: &graph::PyGraph) -> PyResult<graph::P
|| !complement_graph.has_edge(node_a.index(), node_b.index()))
{
// avoid creating parallel edges in multigraph
complement_graph.add_edge(node_a.index(), node_b.index(), py.None());
complement_graph.graph.add_edge(node_a, node_b, py.None());
}
}
}
Expand Down
50 changes: 44 additions & 6 deletions src/digraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,11 @@ impl PyDiGraph {
pub fn add_edge(&mut self, parent: usize, child: usize, edge: PyObject) -> PyResult<usize> {
let p_index = NodeIndex::new(parent);
let c_index = NodeIndex::new(child);
if !self.graph.contains_node(p_index) || !self.graph.contains_node(c_index) {
return Err(PyIndexError::new_err(
"One of the endpoints of the edge does not exist in graph",
));
}
let out_index = self._add_edge(p_index, c_index, edge)?;
Ok(out_index)
}
Expand All @@ -1103,9 +1108,7 @@ impl PyDiGraph {
) -> PyResult<Vec<usize>> {
let mut out_list: Vec<usize> = Vec::with_capacity(obj_list.len());
for obj in obj_list {
let p_index = NodeIndex::new(obj.0);
let c_index = NodeIndex::new(obj.1);
let edge = self._add_edge(p_index, c_index, obj.2)?;
let edge = self.add_edge(obj.0, obj.1, obj.2)?;
out_list.push(edge);
}
Ok(out_list)
Expand All @@ -1129,9 +1132,7 @@ impl PyDiGraph {
) -> PyResult<Vec<usize>> {
let mut out_list: Vec<usize> = Vec::with_capacity(obj_list.len());
for obj in obj_list {
let p_index = NodeIndex::new(obj.0);
let c_index = NodeIndex::new(obj.1);
let edge = self._add_edge(p_index, c_index, py.None())?;
let edge = self.add_edge(obj.0, obj.1, py.None())?;
out_list.push(edge);
}
Ok(out_list)
Expand Down Expand Up @@ -2711,6 +2712,43 @@ impl PyDiGraph {
edges.is_empty()
}

/// Make edges in graph symmetric
///
/// This function iterates over all the edges in the graph, adding for each
/// edge the reversed edge, unless one is already present. Note the edge insertion
/// is not fixed and the edge indices are not guaranteed to be consistent
/// between executions of this method on identical graphs.
///
/// :param callable edge_payload: This optional argument takes in a callable which will
/// be passed a single positional argument the data payload for an edge that will
/// have a reverse copied in the graph. The returned value from this callable will
/// be used as the data payload for the new edge created. If this is not specified
/// then by default the data payload will be copied when the reverse edge is added.
/// If there are parallel edges, then one of the edges (typically the one with the lower
/// index, but this is not a guarantee) will be copied.
pub fn make_symmetric(
&mut self,
py: Python,
edge_payload_fn: Option<PyObject>,
) -> PyResult<()> {
let edges: HashMap<[NodeIndex; 2], EdgeIndex> = self
.graph
.edge_references()
.map(|edge| ([edge.source(), edge.target()], edge.id()))
.collect();
for ([edge_source, edge_target], edge_index) in edges.iter() {
if !edges.contains_key(&[*edge_target, *edge_source]) {
let forward_weight = self.graph.edge_weight(*edge_index).unwrap();
let weight: PyObject = match edge_payload_fn.as_ref() {
Some(callback) => callback.call1(py, (forward_weight,))?,
None => forward_weight.clone_ref(py),
};
self._add_edge(*edge_target, *edge_source, weight)?;
}
}
Ok(())
}

/// Generate a new PyGraph object from this graph
///
/// This will create a new :class:`~rustworkx.PyGraph` object from this
Expand Down
30 changes: 17 additions & 13 deletions src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -847,10 +847,15 @@ impl PyGraph {
/// of an existing edge with ``multigraph=False``) edge.
/// :rtype: int
#[pyo3(text_signature = "(self, node_a, node_b, edge, /)")]
pub fn add_edge(&mut self, node_a: usize, node_b: usize, edge: PyObject) -> usize {
pub fn add_edge(&mut self, node_a: usize, node_b: usize, edge: PyObject) -> PyResult<usize> {
let p_index = NodeIndex::new(node_a);
let c_index = NodeIndex::new(node_b);
self._add_edge(p_index, c_index, edge)
if !self.graph.contains_node(p_index) || !self.graph.contains_node(c_index) {
return Err(PyIndexError::new_err(
"One of the endpoints of the edge does not exist in graph",
));
}
Ok(self._add_edge(p_index, c_index, edge))
}

/// Add new edges to the graph.
Expand All @@ -869,14 +874,15 @@ impl PyGraph {
/// :returns: A list of int indices of the newly created edges
/// :rtype: list
#[pyo3(text_signature = "(self, obj_list, /)")]
pub fn add_edges_from(&mut self, obj_list: Vec<(usize, usize, PyObject)>) -> EdgeIndices {
pub fn add_edges_from(
&mut self,
obj_list: Vec<(usize, usize, PyObject)>,
) -> PyResult<EdgeIndices> {
let mut out_list: Vec<usize> = Vec::with_capacity(obj_list.len());
for obj in obj_list {
let p_index = NodeIndex::new(obj.0);
let c_index = NodeIndex::new(obj.1);
out_list.push(self._add_edge(p_index, c_index, obj.2));
out_list.push(self.add_edge(obj.0, obj.1, obj.2)?);
}
EdgeIndices { edges: out_list }
Ok(EdgeIndices { edges: out_list })
}

/// Add new edges to the graph without python data.
Expand All @@ -898,14 +904,12 @@ impl PyGraph {
&mut self,
py: Python,
obj_list: Vec<(usize, usize)>,
) -> EdgeIndices {
) -> PyResult<EdgeIndices> {
let mut out_list: Vec<usize> = Vec::with_capacity(obj_list.len());
for obj in obj_list {
let p_index = NodeIndex::new(obj.0);
let c_index = NodeIndex::new(obj.1);
out_list.push(self._add_edge(p_index, c_index, py.None()));
out_list.push(self.add_edge(obj.0, obj.1, py.None())?);
}
EdgeIndices { edges: out_list }
Ok(EdgeIndices { edges: out_list })
}

/// Extend graph from an edge list
Expand Down Expand Up @@ -1703,7 +1707,7 @@ impl PyGraph {
}

for (source, weight) in edges {
self.add_edge(source.index(), node_index.index(), weight);
self.add_edge(source.index(), node_index.index(), weight)?;
}

Ok(node_index.index())
Expand Down
2 changes: 1 addition & 1 deletion src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub fn minimum_spanning_tree(
.edges
.iter()
{
spanning_tree.add_edge(edge.0, edge.1, edge.2.clone_ref(py));
spanning_tree.add_edge(edge.0, edge.1, edge.2.clone_ref(py))?;
}

Ok(spanning_tree)
Expand Down
17 changes: 16 additions & 1 deletion tests/rustworkx_tests/digraph/test_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,21 @@ def test_extend_from_weighted_edge_list(self):
self.assertEqual(len(graph), 4)
self.assertEqual(["a", "b", "c", "d", "e"], graph.edges())

def test_add_edge_non_existent(self):
g = rustworkx.PyDiGraph()
with self.assertRaises(IndexError):
g.add_edge(2, 3, None)

def test_add_edges_from_non_existent(self):
g = rustworkx.PyDiGraph()
with self.assertRaises(IndexError):
g.add_edges_from([(2, 3, 5)])

def test_add_edges_from_no_data_non_existent(self):
g = rustworkx.PyDiGraph()
with self.assertRaises(IndexError):
g.add_edges_from_no_data([(2, 3)])

def test_reverse_graph(self):
graph = rustworkx.PyDiGraph()
graph.add_nodes_from([i for i in range(4)])
Expand All @@ -978,7 +993,7 @@ def test_reverse_graph(self):
self.assertEqual([(1, 0), (2, 1), (2, 0), (3, 2), (3, 0)], graph.edge_list())

def test_reverse_large_graph(self):
LARGE_AMOUNT_OF_NODES = 10000000
LARGE_AMOUNT_OF_NODES = 1000000

graph = rustworkx.PyDiGraph()
graph.add_nodes_from(range(LARGE_AMOUNT_OF_NODES))
Expand Down
Loading

0 comments on commit cebf689

Please sign in to comment.