From c05a583ea17855238c4d8cf65aa105fd025dee16 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Jan 2025 15:34:55 -0500 Subject: [PATCH] Bump PyO3 and rust-numpy to 0.23 (#1364) * Bump PyO3 and rust-numpy to 0.23 This commit bumps the version of pyo3 and rust-numpy used by qiskit to the latest release 0.23. The largest change by volume of code is the deprecation of all the *_bound() methods. These are just warnings but they would be fatal to our CI so it needs to be updated. The larger functional change that required updating the code is the change in the traits around converting to Python objects. As a side effect of this change it lets us unify the hashbrown versions installed because we can update indexmap. * Remove unused features section from Cargo.toml --- Cargo.lock | 97 ++++++++++++++++-------------- Cargo.toml | 12 +--- src/cartesian_product.rs | 23 ++++---- src/coloring.rs | 12 ++-- src/connectivity/mod.rs | 22 +++---- src/dag_algo/mod.rs | 17 +++--- src/digraph.rs | 118 ++++++++++++++++++------------------- src/generators.rs | 21 ++++--- src/graph.rs | 111 +++++++++++++++++----------------- src/graphml.rs | 51 +++++++++------- src/iterators.rs | 68 ++++++++++----------- src/json/mod.rs | 29 +++++---- src/json/node_link_data.rs | 5 +- src/lib.rs | 44 +++++--------- src/random_graph.rs | 13 ++-- src/shortest_path/mod.rs | 58 +++++++++--------- src/steiner_tree.rs | 5 +- src/tensor_product.rs | 27 +++++---- src/toposort.rs | 10 ++-- 19 files changed, 376 insertions(+), 367 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c106fed95..ebae6934f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,9 +23,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "autocfg" @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "either" @@ -107,6 +107,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "getrandom" version = "0.2.15" @@ -120,12 +126,13 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "ahash", "allocator-api2", + "equivalent", + "foldhash", "rayon", ] @@ -143,9 +150,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -178,15 +185,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "matrixmultiply" @@ -215,9 +222,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -311,9 +318,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e" +checksum = "b94caae805f998a07d33af06e6a3891e38556051b8045c615470a71590e13e78" dependencies = [ "libc", "ndarray", @@ -332,9 +339,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "petgraph" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b1374ec32450264534c67d1ccb5ca09818c4db8fd87cf97478d0df2fa44c65" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", "indexmap", @@ -342,15 +349,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "portable-atomic-util" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdd8420072e66d54a407b3316991fe946ce3ab1083a7f575b2463866624704d" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] @@ -377,18 +384,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" +checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" dependencies = [ "cfg-if", "hashbrown", @@ -408,9 +415,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" +checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" dependencies = [ "once_cell", "target-lexicon", @@ -418,9 +425,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" +checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" dependencies = [ "libc", "pyo3-build-config", @@ -428,9 +435,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" +checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -440,9 +447,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" +checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" dependencies = [ "heck", "proc-macro2", @@ -462,9 +469,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -547,9 +554,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustworkx" @@ -657,9 +664,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.82" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -674,9 +681,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unindent" diff --git a/Cargo.toml b/Cargo.toml index d3ff4aa052..68f51364f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,12 +26,12 @@ license = "Apache-2.0" [workspace.dependencies] ahash = "0.8.6" fixedbitset = "0.5.7" -hashbrown = { version = ">=0.13, <0.15", features = ["rayon"] } indexmap = { version = ">=1.9, <3", features = ["rayon"] } ndarray = { version = "0.16.1", features = ["rayon"] } num-traits = "0.2" -numpy = "0.22" petgraph = "0.7.0" +hashbrown = { version = ">=0.13, <0.16", features = ["rayon"] } +numpy = "0.23" rand = "0.8" rand_pcg = "0.3" rayon = "1.10" @@ -63,7 +63,7 @@ rustworkx-core = { path = "rustworkx-core", version = "=0.16.0" } flate2 = "1.0.35" [dependencies.pyo3] -version = "0.22.6" +version = "0.23" features = ["abi3-py39", "extension-module", "hashbrown", "num-bigint", "num-complex", "indexmap", "py-clone"] [dependencies.sprs] @@ -71,12 +71,6 @@ version = "^0.11" default-features = false features = ["multi_thread"] -[features] -default = [] -# TODO: remove this once PyO3 is updated to 0.23. Currently, this is a bug in PyO3 0.22 -# that leaks some of their features in a public macro. However, this was removed in 0.23. -gil-refs = ["pyo3/gil-refs"] - [profile.release] lto = 'fat' codegen-units = 1 diff --git a/src/cartesian_product.rs b/src/cartesian_product.rs index d938859f5c..3d211c9081 100644 --- a/src/cartesian_product.rs +++ b/src/cartesian_product.rs @@ -19,13 +19,14 @@ use petgraph::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences}; use petgraph::{algo, EdgeType}; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use pyo3::Python; fn cartesian_product( py: Python, first: &StablePyGraph, second: &StablePyGraph, -) -> (StablePyGraph, ProductNodeMap) { +) -> PyResult<(StablePyGraph, ProductNodeMap)> { let mut final_graph = StablePyGraph::::with_capacity( first.node_count() * second.node_count(), first.node_count() * second.edge_count() + first.edge_count() * second.node_count(), @@ -35,7 +36,7 @@ fn cartesian_product( for (x, weight_x) in first.node_references() { for (y, weight_y) in second.node_references() { - let n0 = final_graph.add_node((weight_x, weight_y).into_py(py)); + let n0 = final_graph.add_node((weight_x, weight_y).into_py_any(py)?); hash_nodes.insert((x, y), n0); } } @@ -65,7 +66,7 @@ fn cartesian_product( .collect(), }; - (final_graph, out_node_map) + Ok((final_graph, out_node_map)) } /// Return a new PyGraph by forming the cartesian product from two input @@ -104,10 +105,10 @@ pub fn graph_cartesian_product( py: Python, first: &graph::PyGraph, second: &graph::PyGraph, -) -> (graph::PyGraph, ProductNodeMap) { - let (out_graph, out_node_map) = cartesian_product(py, &first.graph, &second.graph); +) -> PyResult<(graph::PyGraph, ProductNodeMap)> { + let (out_graph, out_node_map) = cartesian_product(py, &first.graph, &second.graph)?; - ( + Ok(( graph::PyGraph { graph: out_graph, multigraph: true, @@ -115,7 +116,7 @@ pub fn graph_cartesian_product( attrs: py.None(), }, out_node_map, - ) + )) } /// Return a new PyDiGraph by forming the cartesian product from two input @@ -154,10 +155,10 @@ pub fn digraph_cartesian_product( py: Python, first: &digraph::PyDiGraph, second: &digraph::PyDiGraph, -) -> (digraph::PyDiGraph, ProductNodeMap) { - let (out_graph, out_node_map) = cartesian_product(py, &first.graph, &second.graph); +) -> PyResult<(digraph::PyDiGraph, ProductNodeMap)> { + let (out_graph, out_node_map) = cartesian_product(py, &first.graph, &second.graph)?; - ( + Ok(( digraph::PyDiGraph { graph: out_graph, cycle_state: algo::DfsSpace::default(), @@ -167,5 +168,5 @@ pub fn digraph_cartesian_product( attrs: py.None(), }, out_node_map, - ) + )) } diff --git a/src/coloring.rs b/src/coloring.rs index 25b96ebd36..50699bcdff 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -127,7 +127,7 @@ pub fn graph_greedy_color( greedy_node_color_with_coloring_strategy(&graph.graph, callback, inner_strategy)? } }; - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (node, color) in colors { out_dict.set_item(node.index(), color)?; } @@ -203,7 +203,7 @@ pub fn graph_greedy_edge_color( } }; - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (node, color) in colors { out_dict.set_item(node.index(), color)?; } @@ -237,7 +237,7 @@ pub fn graph_greedy_edge_color( #[pyo3(text_signature = "(graph, /)")] pub fn graph_misra_gries_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { let colors = misra_gries_edge_color(&graph.graph); - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (node, color) in colors { out_dict.set_item(node.index(), color)?; } @@ -258,7 +258,7 @@ pub fn graph_misra_gries_edge_color(py: Python, graph: &graph::PyGraph) -> PyRes pub fn graph_two_color(py: Python, graph: &graph::PyGraph) -> PyResult> { match two_color(&graph.graph) { Some(colors) => { - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (node, color) in colors { out_dict.set_item(node.index(), color)?; } @@ -282,7 +282,7 @@ pub fn graph_two_color(py: Python, graph: &graph::PyGraph) -> PyResult PyResult> { match two_color(&graph.graph) { Some(colors) => { - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (node, color) in colors { out_dict.set_item(node.index(), color)?; } @@ -318,7 +318,7 @@ pub fn graph_bipartite_edge_color(py: Python, graph: &graph::PyGraph) -> PyResul Ok(colors) => colors, Err(_) => return Err(GraphNotBipartite::new_err("Graph is not bipartite")), }; - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (node, color) in colors { out_dict.set_item(node.index(), color)?; } diff --git a/src/connectivity/mod.rs b/src/connectivity/mod.rs index 56839b2de6..8342f81b65 100644 --- a/src/connectivity/mod.rs +++ b/src/connectivity/mod.rs @@ -34,7 +34,7 @@ use pyo3::Python; use rayon::prelude::*; use ndarray::prelude::*; -use numpy::IntoPyArray; +use numpy::{IntoPyArray, PyArray2}; use crate::iterators::{ AllPairsMultiplePathMapping, BiconnectedComponents, Chains, EdgeList, NodeIndices, @@ -353,14 +353,14 @@ pub fn is_semi_connected(graph: &digraph::PyDiGraph) -> PyResult { signature=(graph, weight_fn=None, default_weight=1.0, null_value=0.0, parallel_edge="sum"), text_signature = "(graph, /, weight_fn=None, default_weight=1.0, null_value=0.0, parallel_edge=\"sum\")" )] -pub fn digraph_adjacency_matrix( - py: Python, +pub fn digraph_adjacency_matrix<'py>( + py: Python<'py>, graph: &digraph::PyDiGraph, weight_fn: Option, default_weight: f64, null_value: f64, parallel_edge: &str, -) -> PyResult { +) -> PyResult>> { let n = graph.node_count(); let mut matrix = Array2::::from_elem((n, n), null_value); let mut parallel_edge_count = HashMap::new(); @@ -398,7 +398,7 @@ pub fn digraph_adjacency_matrix( } } } - Ok(matrix.into_pyarray_bound(py).into()) + Ok(matrix.into_pyarray(py)) } /// Return the adjacency matrix for a PyGraph class @@ -438,14 +438,14 @@ pub fn digraph_adjacency_matrix( signature=(graph, weight_fn=None, default_weight=1.0, null_value=0.0, parallel_edge="sum"), text_signature = "(graph, /, weight_fn=None, default_weight=1.0, null_value=0.0, parallel_edge=\"sum\")" )] -pub fn graph_adjacency_matrix( - py: Python, +pub fn graph_adjacency_matrix<'py>( + py: Python<'py>, graph: &graph::PyGraph, weight_fn: Option, default_weight: f64, null_value: f64, parallel_edge: &str, -) -> PyResult { +) -> PyResult>> { let n = graph.node_count(); let mut matrix = Array2::::from_elem((n, n), null_value); let mut parallel_edge_count = HashMap::new(); @@ -491,7 +491,7 @@ pub fn graph_adjacency_matrix( } } } - Ok(matrix.into_pyarray_bound(py).into()) + Ok(matrix.into_pyarray(py)) } /// Compute the complement of an undirected graph. @@ -888,7 +888,7 @@ pub fn graph_longest_simple_path(graph: &graph::PyGraph) -> Option #[pyo3(text_signature = "(graph, /)")] pub fn graph_core_number(py: Python, graph: &graph::PyGraph) -> PyResult { let cores = connectivity::core_number(&graph.graph); - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (k, v) in cores { out_dict.set_item(k.index(), v)?; } @@ -914,7 +914,7 @@ pub fn graph_core_number(py: Python, graph: &graph::PyGraph) -> PyResult PyResult { let cores = connectivity::core_number(&graph.graph); - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (k, v) in cores { out_dict.set_item(k.index(), v)?; } diff --git a/src/dag_algo/mod.rs b/src/dag_algo/mod.rs index da5de0cb5b..36cff84982 100644 --- a/src/dag_algo/mod.rs +++ b/src/dag_algo/mod.rs @@ -28,6 +28,7 @@ use rustworkx_core::traversal::dfs_edges; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::PyList; +use pyo3::IntoPyObjectExt; use pyo3::Python; use petgraph::algo; @@ -313,7 +314,7 @@ pub fn layers( .collect(), ); if index_output { - let pylist = PyList::empty_bound(py); + let pylist = PyList::empty(py); for layer in result { match layer { Ok(layer) => pylist.append( @@ -327,7 +328,7 @@ pub fn layers( } Ok(pylist.into()) } else { - let pylist = PyList::empty_bound(py); + let pylist = PyList::empty(py); for layer in result { match layer { Ok(layer) => pylist.append( @@ -394,7 +395,7 @@ pub fn lexicographical_topological_sort( let initial: Option> = match initial { Some(initial) => { let mut initial_vec: Vec = Vec::new(); - for maybe_index in initial.iter()? { + for maybe_index in initial.try_iter()? { let node = NodeIndex::new(maybe_index?.extract::()?); initial_vec.push(node); } @@ -403,12 +404,12 @@ pub fn lexicographical_topological_sort( None => None, }; let out_list = core_lexico_topo_sort(&dag.graph, key_callable, reverse, initial.as_deref())?; - Ok(PyList::new_bound( + Ok(PyList::new( py, out_list .into_iter() .map(|node| dag.graph[node].clone_ref(py)), - ) + )? .into()) } @@ -524,8 +525,8 @@ pub fn collect_runs( // This is where a filter function error will be returned, otherwise Result is stripped away let py_run: Vec = run_result? .iter() - .map(|node| graph.graph.node_weight(*node).into_py(py)) - .collect(); + .map(|node| graph.graph.node_weight(*node).into_py_any(py)) + .collect::>>()?; result.push(py_run) } @@ -585,7 +586,7 @@ pub fn collect_bicolor_runs( .into_iter() .map(|node_index| { let node_weight = dag.node_weight(node_index).expect("Invalid NodeId"); - node_weight.into_py(py) + node_weight.into_py_any(py).unwrap() }) .collect() }) diff --git a/src/digraph.rs b/src/digraph.rs index adf9a768e9..7afc6315aa 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -32,6 +32,7 @@ use pyo3::exceptions::PyIndexError; use pyo3::gc::PyVisit; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyBool, PyDict, PyList, PyString, PyTuple, PyType}; +use pyo3::IntoPyObjectExt; use pyo3::PyTraverseError; use pyo3::Python; @@ -317,15 +318,18 @@ impl PyDiGraph { } } - fn __getnewargs_ex__<'py>(&self, py: Python<'py>) -> (Py, Bound<'py, PyDict>) { - ( - (self.check_cycle, self.multigraph, self.attrs.clone_ref(py)).into_py(py), + fn __getnewargs_ex__<'py>( + &self, + py: Python<'py>, + ) -> PyResult<(Bound<'py, PyTuple>, Bound<'py, PyDict>)> { + Ok(( + (self.check_cycle, self.multigraph, self.attrs.clone_ref(py)).into_pyobject(py)?, [ ("node_count_hint", self.graph.node_bound()), ("edge_count_hint", self.graph.edge_bound()), ] - .into_py_dict_bound(py), - ) + .into_py_dict(py)?, + )) } fn __getstate__(&self, py: Python) -> PyResult { @@ -335,7 +339,7 @@ impl PyDiGraph { // save nodes to a list along with its index for node_idx in self.graph.node_indices() { let node_data = self.graph.node_weight(node_idx).unwrap(); - nodes.push((node_idx.index(), node_data).to_object(py)); + nodes.push((node_idx.index(), node_data).into_py_any(py)?); } // edges are saved with none (deleted edges) instead of their index to save space @@ -344,15 +348,15 @@ impl PyDiGraph { let edge = match self.graph.edge_weight(idx) { Some(edge_w) => { let endpoints = self.graph.edge_endpoints(idx).unwrap(); - (endpoints.0.index(), endpoints.1.index(), edge_w).to_object(py) + (endpoints.0.index(), endpoints.1.index(), edge_w).into_py_any(py)? } None => py.None(), }; edges.push(edge); } - let out_dict = PyDict::new_bound(py); - let nodes_lst: PyObject = PyList::new_bound(py, nodes).into(); - let edges_lst: PyObject = PyList::new_bound(py, edges).into(); + let out_dict = PyDict::new(py); + let nodes_lst = PyList::new(py, nodes)?; + let edges_lst = PyList::new(py, edges)?; out_dict.set_item("nodes", nodes_lst)?; out_dict.set_item("edges", edges_lst)?; out_dict.set_item("nodes_removed", self.node_removed)?; @@ -1167,7 +1171,7 @@ impl PyDiGraph { ) -> PyResult<()> { let node_index = NodeIndex::new(node); let in_edges = { - let in_edges = PyDict::new_bound(py); + let in_edges = PyDict::new(py); for edge in self .graph .edges_directed(node_index, petgraph::Direction::Incoming) @@ -1187,14 +1191,14 @@ impl PyDiGraph { weight: edge.weight().clone_ref(py), nodes: vec![edge.source()], } - .into_py(py), + .into_pyobject(py)?, )? } } in_edges }; let out_edges = { - let out_edges = PyDict::new_bound(py); + let out_edges = PyDict::new(py); for edge in self .graph .edges_directed(node_index, petgraph::Direction::Outgoing) @@ -1214,7 +1218,7 @@ impl PyDiGraph { weight: edge.weight().clone_ref(py), nodes: vec![edge.target()], } - .into_py(py), + .into_pyobject(py)?, )? } } @@ -1283,7 +1287,7 @@ impl PyDiGraph { #[pyo3(text_signature = "(self, obj_list, /)")] pub fn add_edges_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult> { let mut out_list = Vec::new(); - for py_obj in obj_list.iter()? { + for py_obj in obj_list.try_iter()? { let obj = py_obj?.extract::<(usize, usize, PyObject)>()?; let edge = self.add_edge(obj.0, obj.1, obj.2)?; out_list.push(edge); @@ -1308,7 +1312,7 @@ impl PyDiGraph { obj_list: Bound<'_, PyAny>, ) -> PyResult> { let mut out_list = Vec::new(); - for py_obj in obj_list.iter()? { + for py_obj in obj_list.try_iter()? { let obj = py_obj?.extract::<(usize, usize)>()?; let edge = self.add_edge(obj.0, obj.1, py.None())?; out_list.push(edge); @@ -1331,7 +1335,7 @@ impl PyDiGraph { py: Python, edge_list: Bound<'_, PyAny>, ) -> PyResult<()> { - for py_obj in edge_list.iter()? { + for py_obj in edge_list.try_iter()? { let (source, target) = py_obj?.extract::<(usize, usize)>()?; let max_index = cmp::max(source, target); while max_index >= self.node_count() { @@ -1357,7 +1361,7 @@ impl PyDiGraph { py: Python, edge_list: Bound<'_, PyAny>, ) -> PyResult<()> { - for py_obj in edge_list.iter()? { + for py_obj in edge_list.try_iter()? { let (source, target, weight) = py_obj?.extract::<(usize, usize, PyObject)>()?; let max_index = cmp::max(source, target); while max_index >= self.node_count() { @@ -1508,7 +1512,7 @@ impl PyDiGraph { /// pair of nodes. #[pyo3(text_signature = "(self, index_list, /)")] pub fn remove_edges_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> { - for py_obj in index_list.iter()? { + for py_obj in index_list.try_iter()? { let (x, y) = py_obj?.extract::<(usize, usize)>()?; let (p_index, c_index) = (NodeIndex::new(x), NodeIndex::new(y)); let edge_index = match self.graph.find_edge(p_index, c_index) { @@ -1957,7 +1961,7 @@ impl PyDiGraph { #[pyo3(text_signature = "(self, obj_list, /)")] pub fn add_nodes_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult { let mut out_list = Vec::new(); - for py_obj in obj_list.iter()? { + for py_obj in obj_list.try_iter()? { let obj = py_obj?.extract::()?; out_list.push(self.graph.add_node(obj).index()); } @@ -1973,7 +1977,7 @@ impl PyDiGraph { /// graph. #[pyo3(text_signature = "(self, index_list, /)")] pub fn remove_nodes_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> { - for py_obj in index_list.iter()? { + for py_obj in index_list.try_iter()? { let node = py_obj?.extract::()?; self.remove_node(node)?; } @@ -2028,7 +2032,7 @@ impl PyDiGraph { ) -> PyResult<&PyObject> { let predicate_callable = |a: &PyObject| -> PyResult { let res = predicate.call1(py, (a,))?; - Ok(res.to_object(py)) + res.into_py_any(py) }; let index = NodeIndex::new(node); let dir = petgraph::Direction::Outgoing; @@ -2064,7 +2068,7 @@ impl PyDiGraph { ) -> PyResult<&PyObject> { let predicate_callable = |a: &PyObject| -> PyResult { let res = predicate.call1(py, (a,))?; - Ok(res.to_object(py)) + res.into_py_any(py) }; let index = NodeIndex::new(node); let dir = petgraph::Direction::Incoming; @@ -2135,14 +2139,14 @@ impl PyDiGraph { text_signature = "(self, /, node_attr=None, edge_attr=None, graph_attr=None, filename=None)", signature = (node_attr=None, edge_attr=None, graph_attr=None, filename=None) )] - pub fn to_dot( + pub fn to_dot<'py>( &self, - py: Python, + py: Python<'py>, node_attr: Option, edge_attr: Option, graph_attr: Option>, filename: Option, - ) -> PyResult> { + ) -> PyResult>> { match filename { Some(filename) => { let mut file = File::create(filename)?; @@ -2152,9 +2156,7 @@ impl PyDiGraph { None => { let mut file = Vec::::new(); build_dot(py, &self.graph, &mut file, graph_attr, node_attr, edge_attr)?; - Ok(Some( - PyString::new_bound(py, str::from_utf8(&file)?).to_object(py), - )) + Ok(Some(PyString::new(py, str::from_utf8(&file)?))) } } } @@ -2239,7 +2241,7 @@ impl PyDiGraph { src = match label_map.get(src_str) { Some(index) => *index, None => { - let index = out_graph.add_node(src_str.to_object(py)).index(); + let index = out_graph.add_node(src_str.into_py_any(py)?).index(); label_map.insert(src_str.to_string(), index); index } @@ -2247,7 +2249,7 @@ impl PyDiGraph { target = match label_map.get(target_str) { Some(index) => *index, None => { - let index = out_graph.add_node(target_str.to_object(py)).index(); + let index = out_graph.add_node(target_str.into_py_any(py)?).index(); label_map.insert(target_str.to_string(), index); index } @@ -2267,7 +2269,7 @@ impl PyDiGraph { Some(del) => pieces[2..].join(del), None => pieces[2..].join(&' '.to_string()), }; - PyString::new_bound(py, &weight_str).into() + PyString::new(py, &weight_str).into() } else { py.None() }; @@ -2376,7 +2378,7 @@ impl PyDiGraph { py: Python<'p>, matrix: PyReadonlyArray2<'p, f64>, null_value: f64, - ) -> PyDiGraph { + ) -> PyResult { _from_adjacency_matrix(py, matrix, null_value) } @@ -2412,7 +2414,7 @@ impl PyDiGraph { py: Python<'p>, matrix: PyReadonlyArray2<'p, Complex64>, null_value: Complex64, - ) -> PyDiGraph { + ) -> PyResult { _from_adjacency_matrix(py, matrix, null_value) } @@ -2516,7 +2518,7 @@ impl PyDiGraph { weight.clone_ref(py), )?; } - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (orig_node, new_node) in new_node_map.iter() { out_dict.set_item(orig_node.index(), new_node.index())?; } @@ -3209,7 +3211,7 @@ fn weight_transform_callable( match map_fn { Some(map_fn) => { let res = map_fn.call1(py, (value,))?; - Ok(res.to_object(py)) + res.into_py_any(py) } None => Ok(value.clone_ref(py)), } @@ -3219,48 +3221,44 @@ fn _from_adjacency_matrix<'p, T>( py: Python<'p>, matrix: PyReadonlyArray2<'p, T>, null_value: T, -) -> PyDiGraph +) -> PyResult where - T: Copy + std::cmp::PartialEq + numpy::Element + pyo3::ToPyObject + IsNan, + T: Copy + std::cmp::PartialEq + numpy::Element + pyo3::IntoPyObject<'p> + IsNan, { let array = matrix.as_array(); let shape = array.shape(); let mut out_graph = StablePyGraph::::new(); let _node_indices: Vec = (0..shape[0]) - .map(|node| out_graph.add_node(node.to_object(py))) - .collect(); - array - .axis_iter(Axis(0)) - .enumerate() - .for_each(|(index, row)| { - let source_index = NodeIndex::new(index); - for (target_index, elem) in row.iter().enumerate() { - if null_value.is_nan() { - if !elem.is_nan() { - out_graph.add_edge( - source_index, - NodeIndex::new(target_index), - elem.to_object(py), - ); - } - } else if *elem != null_value { + .map(|node| Ok(out_graph.add_node(node.into_py_any(py)?))) + .collect::>>()?; + for (index, row) in array.axis_iter(Axis(0)).enumerate() { + let source_index = NodeIndex::new(index); + for (target_index, elem) in row.iter().enumerate() { + if null_value.is_nan() { + if !elem.is_nan() { out_graph.add_edge( source_index, NodeIndex::new(target_index), - elem.to_object(py), + elem.into_py_any(py)?, ); } + } else if *elem != null_value { + out_graph.add_edge( + source_index, + NodeIndex::new(target_index), + elem.into_py_any(py)?, + ); } - }); - - PyDiGraph { + } + } + Ok(PyDiGraph { graph: out_graph, cycle_state: algo::DfsSpace::default(), check_cycle: false, node_removed: false, multigraph: true, attrs: py.None(), - } + }) } /// Simple wrapper newtype that lets us use `Py` pointers as hash keys with the equality defined by diff --git a/src/generators.rs b/src/generators.rs index 3db2765d33..ade905e2b3 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -17,6 +17,7 @@ use petgraph::Undirected; use pyo3::exceptions::{PyIndexError, PyOverflowError, PyValueError}; use pyo3::prelude::*; use pyo3::wrap_pyfunction; +use pyo3::IntoPyObjectExt; use pyo3::Python; use super::{digraph, graph, StablePyGraph}; @@ -1152,8 +1153,11 @@ pub fn hexagonal_lattice_graph( ) -> PyResult { let default_fn = || py.None(); let graph: StablePyGraph = if with_positions { - let node_position_fn = - |u: usize, v: usize| _hexagonal_lattice_node_position(u, v).to_object(py); + let node_position_fn = |u: usize, v: usize| { + _hexagonal_lattice_node_position(u, v) + .into_py_any(py) + .unwrap() + }; match core_generators::hexagonal_lattice_graph_weighted( rows, cols, @@ -1229,8 +1233,11 @@ pub fn directed_hexagonal_lattice_graph( ) -> PyResult { let default_fn = || py.None(); let graph: StablePyGraph = if with_positions { - let node_position_fn = - |u: usize, v: usize| _hexagonal_lattice_node_position(u, v).to_object(py); + let node_position_fn = |u: usize, v: usize| { + _hexagonal_lattice_node_position(u, v) + .into_py_any(py) + .unwrap() + }; match core_generators::hexagonal_lattice_graph_weighted( rows, cols, @@ -1754,10 +1761,10 @@ pub fn dorogovtsev_goltsev_mendes_graph(py: Python, n: usize) -> PyResult PyResult { let default_node_fn = |w: bool| match w { - true => "Mr. Hi".to_object(py), - false => "Officer".to_object(py), + true => "Mr. Hi".into_py_any(py).unwrap(), + false => "Officer".into_py_any(py).unwrap(), }; - let default_edge_fn = |w: usize| (w as f64).to_object(py); + let default_edge_fn = |w: usize| (w as f64).into_py_any(py).unwrap(); let graph: StablePyGraph = core_generators::karate_club_graph(default_node_fn, default_edge_fn); Ok(graph::PyGraph { diff --git a/src/graph.rs b/src/graph.rs index da2445c52c..adc72f5129 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -27,6 +27,7 @@ use pyo3::exceptions::PyIndexError; use pyo3::gc::PyVisit; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyBool, PyDict, PyList, PyString, PyTuple, PyType}; +use pyo3::IntoPyObjectExt; use pyo3::PyTraverseError; use pyo3::Python; @@ -211,15 +212,18 @@ impl PyGraph { } } - fn __getnewargs_ex__<'py>(&self, py: Python<'py>) -> (Py, Bound<'py, PyDict>) { - ( - (self.multigraph, self.attrs.clone_ref(py)).into_py(py), + fn __getnewargs_ex__<'py>( + &self, + py: Python<'py>, + ) -> PyResult<(Bound<'py, PyTuple>, Bound<'py, PyDict>)> { + Ok(( + (self.multigraph, self.attrs.clone_ref(py)).into_pyobject(py)?, [ ("node_count_hint", self.graph.node_bound()), ("edge_count_hint", self.graph.edge_bound()), ] - .into_py_dict_bound(py), - ) + .into_py_dict(py)?, + )) } fn __getstate__(&self, py: Python) -> PyResult { @@ -229,7 +233,7 @@ impl PyGraph { // save nodes to a list along with its index for node_idx in self.graph.node_indices() { let node_data = self.graph.node_weight(node_idx).unwrap(); - nodes.push((node_idx.index(), node_data).to_object(py)); + nodes.push((node_idx.index(), node_data).into_py_any(py)?); } // edges are saved with none (deleted edges) instead of their index to save space @@ -238,16 +242,16 @@ impl PyGraph { let edge = match self.graph.edge_weight(idx) { Some(edge_w) => { let endpoints = self.graph.edge_endpoints(idx).unwrap(); - (endpoints.0.index(), endpoints.1.index(), edge_w).to_object(py) + (endpoints.0.index(), endpoints.1.index(), edge_w).into_py_any(py)? } None => py.None(), }; edges.push(edge); } - let out_dict = PyDict::new_bound(py); - let nodes_lst: PyObject = PyList::new_bound(py, nodes).into(); - let edges_lst: PyObject = PyList::new_bound(py, edges).into(); + let out_dict = PyDict::new(py); + let nodes_lst: PyObject = PyList::new(py, nodes)?.into_any().unbind(); + let edges_lst: PyObject = PyList::new(py, edges)?.into_any().unbind(); out_dict.set_item("nodes", nodes_lst)?; out_dict.set_item("edges", edges_lst)?; out_dict.set_item("nodes_removed", self.node_removed)?; @@ -879,7 +883,7 @@ impl PyGraph { #[pyo3(text_signature = "(self, obj_list, /)")] pub fn add_edges_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult { let mut out_list = Vec::new(); - for py_obj in obj_list.iter()? { + for py_obj in obj_list.try_iter()? { let obj = py_obj?.extract::<(usize, usize, PyObject)>()?; out_list.push(self.add_edge(obj.0, obj.1, obj.2)?); } @@ -907,7 +911,7 @@ impl PyGraph { obj_list: Bound<'_, PyAny>, ) -> PyResult { let mut out_list: Vec = Vec::new(); - for py_obj in obj_list.iter()? { + for py_obj in obj_list.try_iter()? { let obj = py_obj?.extract::<(usize, usize)>()?; out_list.push(self.add_edge(obj.0, obj.1, py.None())?); } @@ -933,7 +937,7 @@ impl PyGraph { py: Python, edge_list: Bound<'_, PyAny>, ) -> PyResult<()> { - for py_obj in edge_list.iter()? { + for py_obj in edge_list.try_iter()? { let (source, target) = py_obj?.extract::<(usize, usize)>()?; let max_index = cmp::max(source, target); while max_index >= self.node_count() { @@ -967,7 +971,7 @@ impl PyGraph { py: Python, edge_list: Bound<'_, PyAny>, ) -> PyResult<()> { - for py_obj in edge_list.iter()? { + for py_obj in edge_list.try_iter()? { let (source, target, weight) = py_obj?.extract::<(usize, usize, PyObject)>()?; let max_index = cmp::max(source, target); while max_index >= self.node_count() { @@ -1024,7 +1028,7 @@ impl PyGraph { /// pair of nodes. #[pyo3(text_signature = "(self, index_list, /)")] pub fn remove_edges_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> { - for py_obj in index_list.iter()? { + for py_obj in index_list.try_iter()? { let (x, y) = py_obj?.extract::<(usize, usize)>()?; let (p_index, c_index) = (NodeIndex::new(x), NodeIndex::new(y)); let edge_index = match self.graph.find_edge(p_index, c_index) { @@ -1057,7 +1061,7 @@ impl PyGraph { #[pyo3(text_signature = "(self, obj_list, /)")] pub fn add_nodes_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult { let mut out_list = Vec::new(); - for py_obj in obj_list.iter()? { + for py_obj in obj_list.try_iter()? { let obj = py_obj?.extract::()?; out_list.push(self.graph.add_node(obj).index()); } @@ -1073,7 +1077,7 @@ impl PyGraph { /// graph #[pyo3(text_signature = "(self, index_list, /)")] pub fn remove_nodes_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> { - for py_obj in index_list.iter()? { + for py_obj in index_list.try_iter()? { let node = py_obj?.extract::()?; self.remove_node(node)?; } @@ -1258,14 +1262,14 @@ impl PyGraph { text_signature = "(self, /, node_attr=None, edge_attr=None, graph_attr=None, filename=None)", signature = (node_attr=None, edge_attr=None, graph_attr=None, filename=None) )] - pub fn to_dot( + pub fn to_dot<'py>( &self, - py: Python, + py: Python<'py>, node_attr: Option, edge_attr: Option, graph_attr: Option>, filename: Option, - ) -> PyResult> { + ) -> PyResult>> { match filename { Some(filename) => { let mut file = File::create(filename)?; @@ -1275,9 +1279,7 @@ impl PyGraph { None => { let mut file = Vec::::new(); build_dot(py, &self.graph, &mut file, graph_attr, node_attr, edge_attr)?; - Ok(Some( - PyString::new_bound(py, str::from_utf8(&file)?).to_object(py), - )) + Ok(Some(PyString::new(py, str::from_utf8(&file)?))) } } } @@ -1361,7 +1363,7 @@ impl PyGraph { src = match label_map.get(src_str) { Some(index) => *index, None => { - let index = out_graph.add_node(src_str.to_object(py)).index(); + let index = out_graph.add_node(src_str.into_py_any(py)?).index(); label_map.insert(src_str.to_string(), index); index } @@ -1369,7 +1371,7 @@ impl PyGraph { target = match label_map.get(target_str) { Some(index) => *index, None => { - let index = out_graph.add_node(target_str.to_object(py)).index(); + let index = out_graph.add_node(target_str.into_py_any(py)?).index(); label_map.insert(target_str.to_string(), index); index } @@ -1389,7 +1391,7 @@ impl PyGraph { Some(del) => pieces[2..].join(del), None => pieces[2..].join(&' '.to_string()), }; - PyString::new_bound(py, &weight_str).into() + PyString::new(py, &weight_str).into_any().unbind() } else { py.None() }; @@ -1496,7 +1498,7 @@ impl PyGraph { py: Python<'p>, matrix: PyReadonlyArray2<'p, f64>, null_value: f64, - ) -> PyGraph { + ) -> PyResult { _from_adjacency_matrix(py, matrix, null_value) } @@ -1533,7 +1535,7 @@ impl PyGraph { py: Python<'p>, matrix: PyReadonlyArray2<'p, Complex64>, null_value: Complex64, - ) -> PyGraph { + ) -> PyResult { _from_adjacency_matrix(py, matrix, null_value) } @@ -1643,7 +1645,7 @@ impl PyGraph { weight.clone_ref(py), ); } - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (orig_node, new_node) in new_node_map.iter() { out_dict.set_item(orig_node.index(), new_node.index())?; } @@ -2129,7 +2131,7 @@ fn weight_transform_callable( match map_fn { Some(map_fn) => { let res = map_fn.call1(py, (value,))?; - Ok(res.to_object(py)) + res.into_py_any(py) } None => Ok(value.clone_ref(py)), } @@ -2139,46 +2141,43 @@ fn _from_adjacency_matrix<'p, T>( py: Python<'p>, matrix: PyReadonlyArray2<'p, T>, null_value: T, -) -> PyGraph +) -> PyResult where - T: Copy + std::cmp::PartialEq + numpy::Element + pyo3::ToPyObject + IsNan, + T: Copy + std::cmp::PartialEq + numpy::Element + pyo3::IntoPyObject<'p> + IsNan, { let array = matrix.as_array(); let shape = array.shape(); let mut out_graph = StablePyGraph::::default(); let _node_indices: Vec = (0..shape[0]) - .map(|node| out_graph.add_node(node.to_object(py))) - .collect(); - array - .axis_iter(Axis(0)) - .enumerate() - .for_each(|(index, row)| { - let source_index = NodeIndex::new(index); - for (target_index, elem) in row.iter().enumerate() { - if target_index < index { - continue; - } - if null_value.is_nan() { - if !elem.is_nan() { - out_graph.add_edge( - source_index, - NodeIndex::new(target_index), - elem.to_object(py), - ); - } - } else if *elem != null_value { + .map(|node| Ok(out_graph.add_node(node.into_py_any(py)?))) + .collect::>>()?; + for (index, row) in array.axis_iter(Axis(0)).enumerate() { + let source_index = NodeIndex::new(index); + for (target_index, elem) in row.iter().enumerate() { + if target_index < index { + continue; + } + if null_value.is_nan() { + if !elem.is_nan() { out_graph.add_edge( source_index, NodeIndex::new(target_index), - elem.to_object(py), + elem.into_py_any(py)?, ); } + } else if *elem != null_value { + out_graph.add_edge( + source_index, + NodeIndex::new(target_index), + elem.into_py_any(py)?, + ); } - }); - PyGraph { + } + } + Ok(PyGraph { graph: out_graph, node_removed: false, multigraph: true, attrs: py.None(), - } + }) } diff --git a/src/graphml.rs b/src/graphml.rs index 89c71b79d3..e9c1bad4cd 100644 --- a/src/graphml.rs +++ b/src/graphml.rs @@ -35,6 +35,7 @@ use petgraph::{Directed, Undirected}; use pyo3::exceptions::PyException; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use pyo3::PyErr; use crate::{digraph::PyDiGraph, graph::PyGraph, StablePyGraph}; @@ -139,16 +140,20 @@ enum Value { UnDefined, } -impl IntoPy for Value { - fn into_py(self, py: Python) -> PyObject { +impl<'py> IntoPyObject<'py> for Value { + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { match self { - Value::Boolean(val) => val.into_py(py), - Value::Int(val) => val.into_py(py), - Value::Float(val) => val.into_py(py), - Value::Double(val) => val.into_py(py), - Value::String(val) => val.into_py(py), - Value::Long(val) => val.into_py(py), - Value::UnDefined => py.None(), + Value::Boolean(val) => val.into_pyobject(py)?.into_bound_py_any(py), + Value::Int(val) => Ok(val.into_pyobject(py)?.into_any()), + Value::Float(val) => Ok(val.into_pyobject(py)?.into_any()), + Value::Double(val) => Ok(val.into_pyobject(py)?.into_any()), + Value::String(val) => Ok(val.into_pyobject(py)?.into_any()), + Value::Long(val) => Ok(val.into_pyobject(py)?.into_any()), + Value::UnDefined => Ok(py.None().into_bound(py)), } } } @@ -263,8 +268,12 @@ impl Graph { } } -impl IntoPy for Graph { - fn into_py(self, py: Python) -> PyObject { +impl<'py> IntoPyObject<'py> for Graph { + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { macro_rules! make_graph { ($graph:ident) => { let mut mapping = HashMap::with_capacity(self.nodes.len()); @@ -274,7 +283,7 @@ impl IntoPy for Graph { // not by a hashable String. node.data .insert(String::from("id"), Value::String(node.id.clone())); - mapping.insert(node.id, $graph.add_node(node.data.into_py(py))); + mapping.insert(node.id, $graph.add_node(node.data.into_py_any(py)?)); } for mut edge in self.edges { @@ -286,7 +295,7 @@ impl IntoPy for Graph { if let Some(id) = edge.id { edge.data.insert(String::from("id"), Value::String(id)); } - $graph.add_edge(source, target, edge.data.into_py(py)); + $graph.add_edge(source, target, edge.data.into_py_any(py)?); } _ => { // We skip an edge if one of its endpoints was not added earlier in the graph. @@ -306,10 +315,10 @@ impl IntoPy for Graph { graph, node_removed: false, multigraph: true, - attrs: self.attributes.into_py(py), + attrs: self.attributes.into_py_any(py)?, }; - out.into_py(py) + Ok(out.into_pyobject(py)?.into_any()) } Direction::Directed => { let mut graph = @@ -322,10 +331,10 @@ impl IntoPy for Graph { check_cycle: false, node_removed: false, multigraph: true, - attrs: self.attributes.into_py(py), + attrs: self.attributes.into_py_any(py)?, }; - out.into_py(py) + Ok(out.into_pyobject(py)?.into_any()) } } } @@ -733,16 +742,16 @@ impl GraphML { /// :raises RuntimeError: when an error is encountered while parsing the GraphML file. #[pyfunction] #[pyo3(signature=(path, compression=None),text_signature = "(path, /, compression=None)")] -pub fn read_graphml( - py: Python, +pub fn read_graphml<'py>( + py: Python<'py>, path: &str, compression: Option, -) -> PyResult> { +) -> PyResult>> { let graphml = GraphML::from_file(path, &compression.unwrap_or_default())?; let mut out = Vec::new(); for graph in graphml.graphs { - out.push(graph.into_py(py)) + out.push(graph.into_pyobject(py)?) } Ok(out) diff --git a/src/iterators.rs b/src/iterators.rs index 34acb85693..a15cef94c4 100644 --- a/src/iterators.rs +++ b/src/iterators.rs @@ -51,6 +51,7 @@ use pyo3::gc::PyVisit; use pyo3::prelude::*; use pyo3::types::IntoPyDict; use pyo3::types::PySlice; +use pyo3::IntoPyObjectExt; use pyo3::PyTraverseError; macro_rules! last_type { @@ -295,7 +296,7 @@ where impl<'py, K, V> PyEq> for DictMap where - for<'p> K: PyEq + Clone + pyo3::ToPyObject, + for<'p> K: PyEq + Clone + pyo3::IntoPyObject<'py>, for<'p> V: PyEq>, { #[inline] @@ -304,7 +305,7 @@ where return Ok(false); } for (key, value) in self { - match other.get_item(key) { + match other.get_item(key.clone()) { Ok(other_raw) => { if !PyEq::eq(value, &other_raw, py)? { return Ok(false); @@ -432,14 +433,14 @@ impl<'py> FromPyObject<'py> for PySequenceIndex<'py> { } trait PyConvertToPyArray { - fn convert_to_pyarray(&self, py: Python) -> PyResult; + fn convert_to_pyarray<'py>(&self, py: Python<'py>) -> PyResult>; } macro_rules! py_convert_to_py_array_impl { ($($t:ty)*) => ($( impl PyConvertToPyArray for Vec<$t> { - fn convert_to_pyarray(&self, py: Python) -> PyResult { - Ok(self.clone().into_pyarray_bound(py).into()) + fn convert_to_pyarray<'py>(&self, py: Python<'py>) -> PyResult> { + Ok(self.clone().into_pyarray(py).into_any()) } } )*) @@ -448,9 +449,12 @@ macro_rules! py_convert_to_py_array_impl { macro_rules! py_convert_to_py_array_obj_impl { ($t:ty) => { impl PyConvertToPyArray for Vec<$t> { - fn convert_to_pyarray(&self, py: Python) -> PyResult { - let pyobj_vec: Vec = self.iter().map(|x| x.clone().into_py(py)).collect(); - Ok(pyobj_vec.into_pyarray_bound(py).into()) + fn convert_to_pyarray<'py>(&self, py: Python<'py>) -> PyResult> { + let pyobj_vec: Vec = self + .iter() + .map(|x| x.clone().into_py_any(py)) + .collect::>>()?; + Ok(pyobj_vec.into_pyarray(py).into_any()) } } }; @@ -462,7 +466,7 @@ py_convert_to_py_array_obj_impl! {EdgeList} py_convert_to_py_array_obj_impl! {(PyObject, Vec)} impl PyConvertToPyArray for Vec<(usize, usize)> { - fn convert_to_pyarray(&self, py: Python) -> PyResult { + fn convert_to_pyarray<'py>(&self, py: Python<'py>) -> PyResult> { let mut mat = Array2::::from_elem((self.len(), 2), 0); for (index, element) in self.iter().enumerate() { @@ -470,21 +474,21 @@ impl PyConvertToPyArray for Vec<(usize, usize)> { mat[[index, 1]] = element.1; } - Ok(mat.into_pyarray_bound(py).into()) + Ok(mat.into_pyarray(py).into_any()) } } impl PyConvertToPyArray for Vec<(usize, usize, PyObject)> { - fn convert_to_pyarray(&self, py: Python) -> PyResult { + fn convert_to_pyarray<'py>(&self, py: Python<'py>) -> PyResult> { let mut mat = Array2::::from_elem((self.len(), 3), py.None()); for (index, element) in self.iter().enumerate() { - mat[[index, 0]] = element.0.into_py(py); - mat[[index, 1]] = element.1.into_py(py); + mat[[index, 0]] = element.0.into_py_any(py)?; + mat[[index, 1]] = element.1.into_py_any(py)?; mat[[index, 2]] = element.2.clone(); } - Ok(mat.into_pyarray_bound(py).into()) + Ok(mat.into_pyarray(py).into_any()) } } @@ -588,7 +592,7 @@ macro_rules! custom_vec_iter_impl { cond = pos < indices.stop; } } - Ok(out_vec.into_py(py)) + out_vec.into_py_any(py) } PySequenceIndex::Int(idx) => { let len = self.$data.len() as isize; @@ -596,9 +600,9 @@ macro_rules! custom_vec_iter_impl { Err(PyIndexError::new_err(format!("Invalid index, {}", idx))) } else if idx < 0 { let len = self.$data.len(); - Ok(self.$data[len - idx.unsigned_abs()].clone().into_py(py)) + self.$data[len - idx.unsigned_abs()].clone().into_py_any(py) } else { - Ok(self.$data[idx as usize].clone().into_py(py)) + self.$data[idx as usize].clone().into_py_any(py) } } } @@ -619,12 +623,12 @@ macro_rules! custom_vec_iter_impl { } #[pyo3(signature = (dtype=None, copy=None))] - fn __array__( + fn __array__<'py>( &self, - py: Python, + py: Python<'py>, dtype: Option, copy: Option, - ) -> PyResult { + ) -> PyResult> { if copy == Some(false) { return Err(PyValueError::new_err( "A copy is needed to return an array from this object.", @@ -633,12 +637,10 @@ macro_rules! custom_vec_iter_impl { let res = self.$data.convert_to_pyarray(py)?; Ok(match dtype { Some(dtype) => { - let numpy_mod = py.import_bound("numpy")?; + let numpy_mod = py.import("numpy")?; let args = (res,); - let kwargs = [("dtype", dtype)].into_py_dict_bound(py); - numpy_mod - .call_method("asarray", args, Some(&kwargs))? - .into() + let kwargs = [("dtype", dtype)].into_py_dict(py)?; + numpy_mod.call_method("asarray", args, Some(&kwargs))? } None => res, }) @@ -664,14 +666,14 @@ macro_rules! custom_vec_iter_impl { #[pymethods] impl $iter { - fn __next__(&mut self, py: Python) -> Option> { + fn __next__<'py>(&mut self, py: Python<'py>) -> PyResult>> { let data = self.inner.as_ref().unwrap().borrow(py); if self.index < data.$data.len() { - let out = data.$data[self.index].clone().into_py(py); + let out = data.$data[self.index].clone().into_pyobject(py)?.into_any(); self.index += 1; - Some(out) + Ok(Some(out)) } else { - None + Ok(None) } } @@ -714,15 +716,15 @@ macro_rules! custom_vec_iter_impl { #[pymethods] impl $reversed { - fn __next__(&mut self, py: Python) -> Option> { + fn __next__(&mut self, py: Python) -> PyResult>> { let data = self.inner.as_ref().unwrap().borrow(py); let len = data.$data.len(); if self.index < len { - let out = data.$data[len - self.index - 1].clone().into_py(py); + let out = data.$data[len - self.index - 1].clone().into_py_any(py)?; self.index += 1; - Some(out) + Ok(Some(out)) } else { - None + Ok(None) } } diff --git a/src/json/mod.rs b/src/json/mod.rs index 2ad2e8b6bb..fabefb1891 100644 --- a/src/json/mod.rs +++ b/src/json/mod.rs @@ -19,6 +19,7 @@ use crate::{digraph, graph, JSONDeserializationError, StablePyGraph}; use petgraph::{algo, Directed, Undirected}; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use pyo3::Python; /// Parse a node-link format JSON file to generate a graph @@ -44,13 +45,13 @@ use pyo3::Python; /// :rtype: PyGraph | PyDiGraph #[pyfunction] #[pyo3(signature = (path, graph_attrs=None, node_attrs=None, edge_attrs=None))] -pub fn from_node_link_json_file( - py: Python, +pub fn from_node_link_json_file<'py>( + py: Python<'py>, path: &str, graph_attrs: Option, node_attrs: Option, edge_attrs: Option, -) -> PyResult { +) -> PyResult> { let file = File::open(path)?; let reader = BufReader::new(file); let graph: node_link_data::GraphInput = match serde_json::from_reader(reader) { @@ -65,7 +66,7 @@ pub fn from_node_link_json_file( let attrs: PyObject = match graph.attrs { Some(ref attrs) => match graph_attrs { Some(ref callback) => callback.call1(py, (attrs.clone(),))?, - None => attrs.to_object(py), + None => attrs.into_py_any(py)?, }, None => py.None(), }; @@ -83,7 +84,8 @@ pub fn from_node_link_json_file( multigraph, attrs, } - .into_py(py) + .into_pyobject(py)? + .into_any() } else { let mut inner_graph: StablePyGraph = StablePyGraph::with_capacity(graph.nodes.len(), graph.links.len()); @@ -95,7 +97,8 @@ pub fn from_node_link_json_file( multigraph, attrs, } - .into_py(py) + .into_pyobject(py)? + .into_any() }) } @@ -122,13 +125,13 @@ pub fn from_node_link_json_file( /// :rtype: PyGraph | PyDiGraph #[pyfunction] #[pyo3(signature = (data, graph_attrs=None, node_attrs=None, edge_attrs=None))] -pub fn parse_node_link_json( - py: Python, +pub fn parse_node_link_json<'py>( + py: Python<'py>, data: &str, graph_attrs: Option, node_attrs: Option, edge_attrs: Option, -) -> PyResult { +) -> PyResult> { let graph: node_link_data::GraphInput = match serde_json::from_str(data) { Ok(v) => v, Err(e) => { @@ -141,7 +144,7 @@ pub fn parse_node_link_json( let attrs: PyObject = match graph.attrs { Some(ref attrs) => match graph_attrs { Some(ref callback) => callback.call1(py, (attrs.clone(),))?, - None => attrs.to_object(py), + None => attrs.into_py_any(py)?, }, None => py.None(), }; @@ -158,7 +161,8 @@ pub fn parse_node_link_json( multigraph, attrs, } - .into_py(py) + .into_pyobject(py)? + .into_any() } else { let mut inner_graph: StablePyGraph = StablePyGraph::with_capacity(graph.nodes.len(), graph.links.len()); @@ -169,7 +173,8 @@ pub fn parse_node_link_json( multigraph, attrs, } - .into_py(py) + .into_pyobject(py)? + .into_any() }) } diff --git a/src/json/node_link_data.rs b/src/json/node_link_data.rs index 9a86b13db5..9763cc4898 100644 --- a/src/json/node_link_data.rs +++ b/src/json/node_link_data.rs @@ -18,6 +18,7 @@ use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use pyo3::Python; use petgraph::visit::EdgeRef; @@ -88,7 +89,7 @@ pub fn parse_node_link_data( let payload = match node.data { Some(data) => match node_attrs { Some(ref callback) => callback.call1(*py, (data,))?, - None => data.to_object(*py), + None => data.into_py_any(*py)?, }, None => py.None(), }; @@ -102,7 +103,7 @@ pub fn parse_node_link_data( let data = match edge.data { Some(data) => match edge_attrs { Some(ref callback) => callback.call1(*py, (data,))?, - None => data.to_object(*py), + None => data.into_py_any(*py)?, }, None => py.None(), }; diff --git a/src/lib.rs b/src/lib.rs index eb2ea200b0..b06b144b0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,6 +114,7 @@ use rustworkx_core::err::{ContractError, ContractSimpleError}; /// [PyErr] type will be converted to [RxPyErr] using the same /// mechanism, allowing Rustworkx core and PyO3 API usage to be /// intermixed within the same calling function. +#[derive(IntoPyObject)] pub struct RxPyErr { pyerr: PyErr, } @@ -159,12 +160,6 @@ impl From> for RxPyErr { } } -impl IntoPy for RxPyErr { - fn into_py(self, py: Python<'_>) -> PyObject { - self.pyerr.into_value(py).into() - } -} - impl From for PyErr { fn from(value: RxPyErr) -> Self { value.pyerr @@ -381,7 +376,7 @@ fn generic_class_getitem( key: &Bound<'_, PyAny>, ) -> PyResult { Python::with_gil(|py| -> PyResult { - let types_mod = py.import_bound("types")?; + let types_mod = py.import("types")?; let types_generic_alias = types_mod.getattr("GenericAlias")?; let args = (cls, key); let generic_alias = types_generic_alias.call1(args)?; @@ -423,33 +418,24 @@ create_exception!(rustworkx, GraphNotBipartite, PyException); #[pymodule] fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { m.add("__version__", env!("CARGO_PKG_VERSION"))?; - m.add("InvalidNode", py.get_type_bound::())?; - m.add("DAGWouldCycle", py.get_type_bound::())?; - m.add( - "NoEdgeBetweenNodes", - py.get_type_bound::(), - )?; - m.add("DAGHasCycle", py.get_type_bound::())?; - m.add( - "NoSuitableNeighbors", - py.get_type_bound::(), - )?; - m.add("NoPathFound", py.get_type_bound::())?; - m.add("InvalidMapping", py.get_type_bound::())?; - m.add("NullGraph", py.get_type_bound::())?; - m.add("NegativeCycle", py.get_type_bound::())?; + m.add("InvalidNode", py.get_type::())?; + m.add("DAGWouldCycle", py.get_type::())?; + m.add("NoEdgeBetweenNodes", py.get_type::())?; + m.add("DAGHasCycle", py.get_type::())?; + m.add("NoSuitableNeighbors", py.get_type::())?; + m.add("NoPathFound", py.get_type::())?; + m.add("InvalidMapping", py.get_type::())?; + m.add("NullGraph", py.get_type::())?; + m.add("NegativeCycle", py.get_type::())?; m.add( "JSONSerializationError", - py.get_type_bound::(), - )?; - m.add("FailedToConverge", py.get_type_bound::())?; - m.add( - "GraphNotBipartite", - py.get_type_bound::(), + py.get_type::(), )?; + m.add("FailedToConverge", py.get_type::())?; + m.add("GraphNotBipartite", py.get_type::())?; m.add( "JSONDeserializationError", - py.get_type_bound::(), + py.get_type::(), )?; m.add_wrapped(wrap_pyfunction!(bfs_successors))?; m.add_wrapped(wrap_pyfunction!(bfs_predecessors))?; diff --git a/src/random_graph.rs b/src/random_graph.rs index 66451dc222..f6a19e4250 100644 --- a/src/random_graph.rs +++ b/src/random_graph.rs @@ -17,6 +17,7 @@ use crate::{digraph, graph, StablePyGraph}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::PyDict; +use pyo3::IntoPyObjectExt; use pyo3::Python; use petgraph::algo; @@ -87,7 +88,7 @@ pub fn directed_gnp_random_graph( // in the python interface, we do it here. let nodes: Vec = graph.node_indices().collect(); for node in nodes.iter() { - graph[*node] = node.index().to_object(py); + graph[*node] = node.index().into_py_any(py)?; } Ok(digraph::PyDiGraph { graph, @@ -155,7 +156,7 @@ pub fn undirected_gnp_random_graph( // in the python interface, we do it here. let nodes: Vec = graph.node_indices().collect(); for node in nodes.iter() { - graph[*node] = node.index().to_object(py); + graph[*node] = node.index().into_py_any(py)?; } Ok(graph::PyGraph { graph, @@ -209,7 +210,7 @@ pub fn directed_gnm_random_graph( // in the python interface, we do it here. let nodes: Vec = graph.node_indices().collect(); for node in nodes.iter() { - graph[*node] = node.index().to_object(py); + graph[*node] = node.index().into_py_any(py)?; } Ok(digraph::PyDiGraph { graph, @@ -265,7 +266,7 @@ pub fn undirected_gnm_random_graph( // in the python interface, we do it here. let nodes: Vec = graph.node_indices().collect(); for node in nodes.iter() { - graph[*node] = node.index().to_object(py); + graph[*node] = node.index().into_py_any(py)?; } Ok(graph::PyGraph { graph, @@ -469,8 +470,8 @@ pub fn random_geometric_graph( } for pval in pos.iter() { - let pos_dict = PyDict::new_bound(py); - pos_dict.set_item("pos", pval.to_object(py))?; + let pos_dict = PyDict::new(py); + pos_dict.set_item("pos", pval.into_py_any(py)?)?; inner_graph.add_node(pos_dict.into()); } diff --git a/src/shortest_path/mod.rs b/src/shortest_path/mod.rs index c062945d2a..c4eea91124 100644 --- a/src/shortest_path/mod.rs +++ b/src/shortest_path/mod.rs @@ -31,7 +31,7 @@ use petgraph::visit::NodeCount; use pyo3::exceptions::PyIndexError; use pyo3::exceptions::PyValueError; -use numpy::IntoPyArray; +use numpy::{IntoPyArray, PyArray2}; use rustworkx_core::dictmap::*; use rustworkx_core::shortest_path::{ @@ -1067,13 +1067,13 @@ pub fn graph_floyd_warshall( signature=(graph, weight_fn=None, default_weight=1.0, parallel_threshold=300), text_signature = "(graph, /, weight_fn=None, default_weight=1.0, parallel_threshold=300)" )] -pub fn graph_floyd_warshall_numpy( - py: Python, +pub fn graph_floyd_warshall_numpy<'py>( + py: Python<'py>, graph: &graph::PyGraph, weight_fn: Option, default_weight: f64, parallel_threshold: usize, -) -> PyResult { +) -> PyResult>> { let (matrix, _) = floyd_warshall::floyd_warshall_numpy( py, &graph.graph, @@ -1083,9 +1083,11 @@ pub fn graph_floyd_warshall_numpy( false, parallel_threshold, )?; - Ok(matrix.into_pyarray_bound(py).into()) + Ok(matrix.into_pyarray(py)) } +type FloydWarshallReturn<'py> = (Bound<'py, PyArray2>, Bound<'py, PyArray2>); + /// Find all-pairs shortest path lengths using Floyd's algorithm /// /// Floyd's algorithm is used for finding shortest paths in dense graphs @@ -1140,13 +1142,13 @@ pub fn graph_floyd_warshall_numpy( signature=(graph, weight_fn=None, default_weight=1.0, parallel_threshold=300), text_signature = "(graph, /, weight_fn=None, default_weight=1.0, parallel_threshold=300)" )] -pub fn graph_floyd_warshall_successor_and_distance( - py: Python, +pub fn graph_floyd_warshall_successor_and_distance<'py>( + py: Python<'py>, graph: &graph::PyGraph, weight_fn: Option, default_weight: f64, parallel_threshold: usize, -) -> PyResult<(PyObject, PyObject)> { +) -> PyResult> { let (matrix, next) = floyd_warshall::floyd_warshall_numpy( py, &graph.graph, @@ -1156,10 +1158,7 @@ pub fn graph_floyd_warshall_successor_and_distance( true, parallel_threshold, )?; - Ok(( - matrix.into_pyarray_bound(py).into(), - next.unwrap().into_pyarray_bound(py).into(), - )) + Ok((matrix.into_pyarray(py), next.unwrap().into_pyarray(py))) } /// Find all-pairs shortest path lengths using Floyd's algorithm @@ -1202,14 +1201,14 @@ pub fn graph_floyd_warshall_successor_and_distance( signature=(graph, weight_fn=None, as_undirected=false, default_weight=1.0, parallel_threshold=300), text_signature = "(graph, /, weight_fn=None, as_undirected=False, default_weight=1.0, parallel_threshold=300)" )] -pub fn digraph_floyd_warshall_numpy( - py: Python, +pub fn digraph_floyd_warshall_numpy<'py>( + py: Python<'py>, graph: &digraph::PyDiGraph, weight_fn: Option, as_undirected: bool, default_weight: f64, parallel_threshold: usize, -) -> PyResult { +) -> PyResult>> { let (matrix, _) = floyd_warshall::floyd_warshall_numpy( py, &graph.graph, @@ -1219,7 +1218,7 @@ pub fn digraph_floyd_warshall_numpy( false, parallel_threshold, )?; - Ok(matrix.into_pyarray_bound(py).into()) + Ok(matrix.into_pyarray(py)) } /// Find all-pairs shortest path lengths using Floyd's algorithm @@ -1278,14 +1277,14 @@ pub fn digraph_floyd_warshall_numpy( signature=(graph, weight_fn=None, as_undirected=false, default_weight=1.0, parallel_threshold=300), text_signature = "(graph, /, weight_fn=None, as_undirected=False, default_weight=1.0, parallel_threshold=300)" )] -pub fn digraph_floyd_warshall_successor_and_distance( - py: Python, +pub fn digraph_floyd_warshall_successor_and_distance<'py>( + py: Python<'py>, graph: &digraph::PyDiGraph, weight_fn: Option, as_undirected: bool, default_weight: f64, parallel_threshold: usize, -) -> PyResult<(PyObject, PyObject)> { +) -> PyResult> { let (matrix, next) = floyd_warshall::floyd_warshall_numpy( py, &graph.graph, @@ -1295,10 +1294,7 @@ pub fn digraph_floyd_warshall_successor_and_distance( true, parallel_threshold, )?; - Ok(( - matrix.into_pyarray_bound(py).into(), - next.unwrap().into_pyarray_bound(py).into(), - )) + Ok((matrix.into_pyarray(py), next.unwrap().into_pyarray(py))) } /// Get the number of unweighted shortest paths from a source node @@ -1370,20 +1366,20 @@ pub fn graph_num_shortest_paths_unweighted( signature=(graph, parallel_threshold=300, as_undirected=false, null_value=0.0), text_signature = "(graph, /, parallel_threshold=300, as_undirected=False, null_value=0.0)" )] -pub fn digraph_distance_matrix( - py: Python, +pub fn digraph_distance_matrix<'py>( + py: Python<'py>, graph: &digraph::PyDiGraph, parallel_threshold: usize, as_undirected: bool, null_value: f64, -) -> PyObject { +) -> Bound<'py, PyArray2> { let matrix = distance_matrix::compute_distance_matrix( &graph.graph, parallel_threshold, as_undirected, null_value, ); - matrix.into_pyarray_bound(py).into() + matrix.into_pyarray(py) } /// Get the distance matrix for an undirected graph @@ -1412,19 +1408,19 @@ pub fn digraph_distance_matrix( signature=(graph, parallel_threshold=300, null_value=0.0), text_signature = "(graph, /, parallel_threshold=300, null_value=0.0)" )] -pub fn graph_distance_matrix( - py: Python, +pub fn graph_distance_matrix<'py>( + py: Python<'py>, graph: &graph::PyGraph, parallel_threshold: usize, null_value: f64, -) -> PyObject { +) -> Bound<'py, PyArray2> { let matrix = distance_matrix::compute_distance_matrix( &graph.graph, parallel_threshold, true, null_value, ); - matrix.into_pyarray_bound(py).into() + matrix.into_pyarray(py) } /// Return the average shortest path length for a :class:`~rustworkx.PyDiGraph` diff --git a/src/steiner_tree.rs b/src/steiner_tree.rs index a854dcc1db..86153129de 100644 --- a/src/steiner_tree.rs +++ b/src/steiner_tree.rs @@ -17,6 +17,7 @@ use rayon::prelude::*; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use pyo3::Python; use petgraph::stable_graph::{EdgeIndex, EdgeReference, NodeIndex}; @@ -63,7 +64,7 @@ pub fn metric_closure( out_graph.graph.add_edge( *result_graph.node_weight(source).unwrap(), *result_graph.node_weight(target).unwrap(), - weight.to_object(py), + weight.into_py_any(py)?, ); } Ok(out_graph) @@ -200,7 +201,7 @@ fn deduplicate_edges( let mut edges: Vec<(EdgeIndex, f64)> = Vec::with_capacity(edges_raw.len()); for edge in edges_raw { let res = weight_fn.call1(py, (&edge.1,))?; - let raw = res.to_object(py); + let raw = res.into_py_any(py)?; let weight = raw.extract(py)?; edges.push((edge.0, weight)); } diff --git a/src/tensor_product.rs b/src/tensor_product.rs index 134cb9b8ba..5d9ca45853 100644 --- a/src/tensor_product.rs +++ b/src/tensor_product.rs @@ -19,6 +19,7 @@ use petgraph::visit::{EdgeRef, IntoEdgeReferences}; use petgraph::{algo, EdgeType}; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use pyo3::Python; fn tensor_product( @@ -26,7 +27,7 @@ fn tensor_product( first: &StablePyGraph, second: &StablePyGraph, undirected: bool, -) -> (StablePyGraph, ProductNodeMap) { +) -> PyResult<(StablePyGraph, ProductNodeMap)> { let num_nodes = first.node_count() * second.node_count(); let mut num_edges = first.edge_count() * second.edge_count(); @@ -41,7 +42,7 @@ fn tensor_product( for y in second.node_indices() { let weight_x = &first[x]; let weight_y = &second[y]; - let n0 = final_graph.add_node((weight_x, weight_y).into_py(py)); + let n0 = final_graph.add_node((weight_x, weight_y).into_py_any(py)?); hash_nodes.insert((x, y), n0); } } @@ -63,7 +64,7 @@ fn tensor_product( edge_first.weight().clone_ref(py), edge_second.weight().clone_ref(py), ) - .into_py(py), + .into_py_any(py)?, ); } } @@ -91,7 +92,7 @@ fn tensor_product( edge_first.weight().clone_ref(py), edge_second.weight().clone_ref(py), ) - .into_py(py), + .into_py_any(py)?, ); } } @@ -104,7 +105,7 @@ fn tensor_product( .collect(), }; - (final_graph, out_node_map) + Ok((final_graph, out_node_map)) } /// Return a new PyGraph by forming the tensor product from two input @@ -142,10 +143,10 @@ pub fn graph_tensor_product( py: Python, first: &graph::PyGraph, second: &graph::PyGraph, -) -> (graph::PyGraph, ProductNodeMap) { - let (out_graph, out_node_map) = tensor_product(py, &first.graph, &second.graph, true); +) -> PyResult<(graph::PyGraph, ProductNodeMap)> { + let (out_graph, out_node_map) = tensor_product(py, &first.graph, &second.graph, true)?; - ( + Ok(( graph::PyGraph { graph: out_graph, multigraph: true, @@ -153,7 +154,7 @@ pub fn graph_tensor_product( attrs: py.None(), }, out_node_map, - ) + )) } /// Return a new PyDiGraph by forming the tensor product from two input @@ -191,10 +192,10 @@ pub fn digraph_tensor_product( py: Python, first: &digraph::PyDiGraph, second: &digraph::PyDiGraph, -) -> (digraph::PyDiGraph, ProductNodeMap) { - let (out_graph, out_node_map) = tensor_product(py, &first.graph, &second.graph, false); +) -> PyResult<(digraph::PyDiGraph, ProductNodeMap)> { + let (out_graph, out_node_map) = tensor_product(py, &first.graph, &second.graph, false)?; - ( + Ok(( digraph::PyDiGraph { graph: out_graph, cycle_state: algo::DfsSpace::default(), @@ -204,5 +205,5 @@ pub fn digraph_tensor_product( attrs: py.None(), }, out_node_map, - ) + )) } diff --git a/src/toposort.rs b/src/toposort.rs index cc518d814a..4bffab74b8 100644 --- a/src/toposort.rs +++ b/src/toposort.rs @@ -118,7 +118,7 @@ impl TopologicalSorter { let ready_nodes = if let Some(initial) = initial { let dag = &dag.borrow(py); initial - .iter()? + .try_iter()? .map(|maybe_index| { let node = NodeIndex::new(maybe_index?.extract::()?); // If we're using an initial set, it's possible that the user gave us an @@ -176,10 +176,10 @@ impl TopologicalSorter { /// /// :returns: A list of node indices of all the ready nodes. /// :rtype: List - fn get_ready<'py>(&mut self, py: Python<'py>) -> Bound<'py, PyList> { + fn get_ready<'py>(&mut self, py: Python<'py>) -> PyResult> { self.num_passed_out += self.ready_nodes.len(); if let Some(node2state) = self.node2state.as_mut() { - PyList::new_bound( + PyList::new( py, self.ready_nodes.drain(..).map(|nx| { node2state.insert(nx, NodeState::Ready); @@ -187,7 +187,7 @@ impl TopologicalSorter { }), ) } else { - PyList::new_bound(py, self.ready_nodes.drain(..).map(|nx| nx.index())) + PyList::new(py, self.ready_nodes.drain(..).map(|nx| nx.index())) } } @@ -215,7 +215,7 @@ impl TopologicalSorter { } Ok(()) } else { - for node in nodes.iter()? { + for node in nodes.try_iter()? { self.done_single(nodes.py(), NodeIndex::new(node?.extract()?))? } Ok(())