diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ae80b1d9..98b234e03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,11 +31,12 @@ you for an overview of our simplified source tree: │ │ └── more_pure_rust_code.rs ``` -#### Module exports in `lib.rs` +#### Exporting new functions -To add new functions, you will need to export them in `lib.rs`. `lib.rs` will -import functions defined in Rust modules (see the next section), and export -them to Python using `m.add_wrapped(wrap_pyfunction!(your_new_function))?;` +To add new functions, you will need to export them in the +`export_rustworkx_functions!` statement in the Rust file you are editing. +If the function is not added to that statement, the Rust compiler +will complain about dead-code and Python will not find the function. #### Adding and changing functions in modules @@ -66,7 +67,6 @@ pub fn your_new_function( > __NOTE:__ If you create a new `your_module.rs`, remember to declare and import it in `lib.rs`: > ```rust > mod your_module; -> use your_module::*; > ``` #### Module directories: when a single file is not enough diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..f860bcef0 --- /dev/null +++ b/build.rs @@ -0,0 +1,93 @@ +use std::collections::BTreeSet; +use std::env; +use std::fs; +use std::io::Read; +use std::io::Write; +use std::path; + +fn main() { + let src_dir_path = env::var("CARGO_MANIFEST_DIR").unwrap(); + let src_dir_path = format!("{}/src/", src_dir_path); + let out_dir = fs::read_dir(src_dir_path).expect("could not read src/ directory"); + + let mut modules = BTreeSet::new(); + for entry in out_dir { + let entry = entry.expect("could not read entry"); + let path = entry.path(); + // Top-level .rs files + if path.is_file() { + let file_name = path.to_str().unwrap(); + if file_name.ends_with(".rs") && file_name != "lib.rs" { + modules.insert(file_name.to_string()); + } + } + if path.is_dir() { + let sub_dir = fs::read_dir(path).expect("could not read subdirectory"); + for entry in sub_dir { + let entry = entry.expect("could not read entry"); + let path = entry.path(); + if path.is_file() { + let file_name = path.to_str().unwrap(); + if file_name.ends_with("mod.rs") { + let module_name = file_name.to_string(); + modules.insert(module_name); + } + } + } + } + } + + // Create the generated file with the modules + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = path::PathBuf::from(out_dir).join("generated_include_rustworkx_modules.rs"); + + // Create the file and write the contents to it + let mut f = fs::File::create(dest_path).unwrap(); + + let mut rustworkx_modules = BTreeSet::new(); + + for path in modules { + let mut file = fs::File::open(path.clone()) + .expect("could not open file to check if it declares a rustworkx module"); + let mut content = String::new(); + file.read_to_string(&mut content) + .expect("could not read contents of the file"); + if content.contains("export_rustworkx_functions!") { + rustworkx_modules.insert(module_name_from_file_name(path.clone())); + } + } + + writeln!(f, "fn register_rustworkx_everything(m: &pyo3::Bound) -> pyo3::prelude::PyResult<()>").expect("could not write function signature"); + writeln!(f, "{{").expect("could not write function body"); + + for module in rustworkx_modules { + writeln!(f, "{}::register_rustworkx_functions(m)?;", module.clone()) + .expect("could not write to file"); + } + writeln!(f, "Ok(())").expect("could not write function body"); + writeln!(f, "}}").expect("could not write function body"); +} + +fn module_name_from_file_name(filename: String) -> String { + if filename.ends_with("mod.rs") { + let parent = path::Path::new(&filename) + .parent() + .expect("could not get parent directory"); + let module_name = parent + .file_name() + .expect("could not get file name") + .to_str() + .expect("could not convert to string"); + return module_name.to_string(); + } + let module_name = path::Path::new(&filename) + .file_name() + .expect("could not get file name") + .to_str() + .expect("could not convert to string"); + return module_name + .to_string() + .strip_suffix(".rs") + .expect("could not strip suffix") + .to_string(); +} diff --git a/src/bisimulation.rs b/src/bisimulation.rs index 4a0525f38..1a10ede90 100644 --- a/src/bisimulation.rs +++ b/src/bisimulation.rs @@ -8,10 +8,12 @@ use hashbrown::hash_map::Entry; use hashbrown::{HashMap, HashSet}; use crate::iterators::{IndexPartitionBlock, RelationalCoarsestPartition}; -use crate::{digraph, Directed, StablePyGraph}; +use crate::{digraph, export_rustworkx_functions, Directed, StablePyGraph}; use petgraph::graph; use petgraph::Direction::{Incoming, Outgoing}; +export_rustworkx_functions!(digraph_maximum_bisimulation); + type Block = Vec; type Counterimage = HashMap>; type NodeToBlockVec = Vec>>; diff --git a/src/cartesian_product.rs b/src/cartesian_product.rs index d938859f5..37525e8cc 100644 --- a/src/cartesian_product.rs +++ b/src/cartesian_product.rs @@ -11,7 +11,7 @@ // under the License. use crate::iterators::ProductNodeMap; -use crate::{digraph, graph, StablePyGraph}; +use crate::{digraph, export_rustworkx_functions, graph, StablePyGraph}; use hashbrown::HashMap; @@ -21,6 +21,8 @@ use petgraph::{algo, EdgeType}; use pyo3::prelude::*; use pyo3::Python; +export_rustworkx_functions!(graph_cartesian_product, digraph_cartesian_product); + fn cartesian_product( py: Python, first: &StablePyGraph, diff --git a/src/centrality.rs b/src/centrality.rs index ca055cec3..b5ab15edb 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -15,6 +15,7 @@ use std::convert::TryFrom; use crate::digraph; +use crate::export_rustworkx_functions; use crate::graph; use crate::iterators::{CentralityMapping, EdgeCentralityMapping}; use crate::CostFn; @@ -29,6 +30,19 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use rustworkx_core::centrality; +export_rustworkx_functions!( + graph_betweenness_centrality, + digraph_betweenness_centrality, + graph_closeness_centrality, + digraph_closeness_centrality, + graph_edge_betweenness_centrality, + digraph_edge_betweenness_centrality, + graph_eigenvector_centrality, + digraph_eigenvector_centrality, + graph_katz_centrality, + digraph_katz_centrality +); + /// Compute the betweenness centrality of all nodes in a PyGraph. /// /// Betweenness centrality of a node :math:`v` is the sum of the diff --git a/src/coloring.rs b/src/coloring.rs index 25b96ebd3..12f4f626e 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -11,7 +11,7 @@ // under the License. use crate::GraphNotBipartite; -use crate::{digraph, graph, EdgeIndex, NodeIndex}; +use crate::{digraph, export_rustworkx_functions, graph, EdgeIndex, NodeIndex}; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -27,6 +27,15 @@ use rustworkx_core::coloring::{ pub use rustworkx_core::coloring::ColoringStrategy as ColoringStrategyCore; +export_rustworkx_functions!( + graph_greedy_color, + graph_misra_gries_edge_color, + graph_greedy_edge_color, + graph_bipartite_edge_color, + graph_two_color, + digraph_two_color +); + /// Greedy coloring strategies available for `graph_greedy_color` /// /// .. list-table:: Strategy description diff --git a/src/connectivity/mod.rs b/src/connectivity/mod.rs index 353a57fe2..9c59b90f9 100644 --- a/src/connectivity/mod.rs +++ b/src/connectivity/mod.rs @@ -17,7 +17,8 @@ mod johnson_simple_cycles; mod subgraphs; use super::{ - digraph, get_edge_iter_with_weights, graph, score, weight_callable, InvalidNode, NullGraph, + digraph, export_rustworkx_functions, get_edge_iter_with_weights, graph, score, weight_callable, + InvalidNode, NullGraph, }; use hashbrown::{HashMap, HashSet}; @@ -46,6 +47,43 @@ use rustworkx_core::coloring::two_color; use rustworkx_core::connectivity; use rustworkx_core::dag_algo::longest_path; +export_rustworkx_functions!( + cycle_basis, + simple_cycles, + strongly_connected_components, + digraph_find_cycle, + number_connected_components, + connected_components, + node_connected_component, + is_connected, + number_weakly_connected_components, + weakly_connected_components, + is_weakly_connected, + is_semi_connected, + digraph_adjacency_matrix, + graph_adjacency_matrix, + graph_complement, + digraph_complement, + digraph_is_bipartite, + graph_is_bipartite, + graph_isolates, + digraph_isolates, + chain_decomposition, + biconnected_components, + bridges, + articulation_points, + stoer_wagner_min_cut, + graph_all_simple_paths, + digraph_all_simple_paths, + graph_all_pairs_all_simple_paths, + digraph_all_pairs_all_simple_paths, + graph_longest_simple_path, + digraph_longest_simple_path, + graph_core_number, + digraph_core_number, + connected_subgraphs +); + /// Return a list of cycles which form a basis for cycles of a given PyGraph /// /// A basis for cycles of a graph is a minimal collection of diff --git a/src/dag_algo/mod.rs b/src/dag_algo/mod.rs index 769582ce1..918cebacf 100644 --- a/src/dag_algo/mod.rs +++ b/src/dag_algo/mod.rs @@ -17,7 +17,9 @@ use rustworkx_core::dag_algo::layers as core_layers; use rustworkx_core::dictmap::InitWithHasher; use super::iterators::NodeIndices; -use crate::{digraph, DAGHasCycle, InvalidNode, RxPyResult, StablePyGraph}; +use crate::{ + digraph, export_rustworkx_functions, DAGHasCycle, InvalidNode, RxPyResult, StablePyGraph, +}; use rustworkx_core::dag_algo::collect_bicolor_runs as core_collect_bicolor_runs; use rustworkx_core::dag_algo::collect_runs as core_collect_runs; @@ -37,6 +39,21 @@ use petgraph::visit::NodeIndexable; use num_traits::{Num, Zero}; +export_rustworkx_functions!( + dag_longest_path, + dag_longest_path_length, + dag_weighted_longest_path, + dag_weighted_longest_path_length, + is_directed_acyclic_graph, + transitive_reduction, + topological_sort, + topological_generations, + lexicographical_topological_sort, + collect_runs, + collect_bicolor_runs, + layers +); + /// Calculate the longest path in a directed acyclic graph (DAG). /// /// This function interfaces with the Python `PyDiGraph` object to compute the longest path diff --git a/src/graphml.rs b/src/graphml.rs index 6211b25ce..c9b210d07 100644 --- a/src/graphml.rs +++ b/src/graphml.rs @@ -33,7 +33,9 @@ use pyo3::exceptions::PyException; use pyo3::prelude::*; use pyo3::PyErr; -use crate::{digraph::PyDiGraph, graph::PyGraph, StablePyGraph}; +use crate::{digraph::PyDiGraph, export_rustworkx_functions, graph::PyGraph, StablePyGraph}; + +export_rustworkx_functions!(read_graphml); pub enum Error { Xml(String), diff --git a/src/isomorphism/mod.rs b/src/isomorphism/mod.rs index 6e48f1f10..9f0f860b8 100644 --- a/src/isomorphism/mod.rs +++ b/src/isomorphism/mod.rs @@ -14,13 +14,22 @@ mod vf2; -use crate::{digraph, graph}; +use crate::{digraph, export_rustworkx_functions, graph}; use std::cmp::Ordering; use pyo3::prelude::*; use pyo3::Python; +export_rustworkx_functions!( + graph_is_isomorphic, + digraph_is_isomorphic, + graph_is_subgraph_isomorphic, + digraph_is_subgraph_isomorphic, + digraph_vf2_mapping, + graph_vf2_mapping +); + /// Determine if 2 directed graphs are isomorphic /// /// This checks if 2 graphs are isomorphic both structurally and also diff --git a/src/json/mod.rs b/src/json/mod.rs index 2ad2e8b6b..9a2a3825e 100644 --- a/src/json/mod.rs +++ b/src/json/mod.rs @@ -15,12 +15,19 @@ mod node_link_data; use std::fs::File; use std::io::BufReader; -use crate::{digraph, graph, JSONDeserializationError, StablePyGraph}; +use crate::{digraph, export_rustworkx_functions, graph, JSONDeserializationError, StablePyGraph}; use petgraph::{algo, Directed, Undirected}; use pyo3::prelude::*; use pyo3::Python; +export_rustworkx_functions!( + digraph_node_link_json, + graph_node_link_json, + from_node_link_json_file, + parse_node_link_json +); + /// Parse a node-link format JSON file to generate a graph /// /// :param path str: The path to the JSON file to load diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 802207b1c..a9fdf615f 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -17,7 +17,7 @@ mod shell; mod spiral; mod spring; -use crate::{digraph, graph}; +use crate::{digraph, export_rustworkx_functions, graph}; use spring::Point; use hashbrown::{HashMap, HashSet}; @@ -27,6 +27,21 @@ use pyo3::Python; use crate::iterators::Pos2DMapping; +export_rustworkx_functions!( + graph_random_layout, + digraph_random_layout, + graph_bipartite_layout, + digraph_bipartite_layout, + graph_circular_layout, + digraph_circular_layout, + graph_shell_layout, + digraph_shell_layout, + graph_spiral_layout, + digraph_spiral_layout, + graph_spring_layout, + digraph_spring_layout +); + /// Position nodes using Fruchterman-Reingold force-directed algorithm. /// /// The algorithm simulates a force-directed representation of the network diff --git a/src/lib.rs b/src/lib.rs index 79f183462..4bb07bab5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,31 +41,6 @@ mod traversal; mod tree; mod union; -use bisimulation::*; -use cartesian_product::*; -use centrality::*; -use coloring::*; -use connectivity::*; -use dag_algo::*; -use graphml::*; -use isomorphism::*; -use json::*; -use layout::*; -use line_graph::*; -use link_analysis::*; - -use matching::*; -use planar::*; -use random_graph::*; -use shortest_path::*; -use steiner_tree::*; -use tensor_product::*; -use token_swapper::*; -use transitivity::*; -use traversal::*; -use tree::*; -use union::*; - use hashbrown::HashMap; use numpy::Complex64; @@ -74,7 +49,6 @@ use pyo3::exceptions::PyException; use pyo3::exceptions::PyValueError; use pyo3::import_exception; use pyo3::prelude::*; -use pyo3::wrap_pyfunction; use pyo3::wrap_pymodule; use pyo3::Python; @@ -405,6 +379,13 @@ create_exception!(rustworkx, FailedToConverge, PyException); // Graph is not bipartite create_exception!(rustworkx, GraphNotBipartite, PyException); +// auto-generated register_rustworkx_everything function. +// it registers nearly all functions +include!(concat!( + env!("OUT_DIR"), + "/generated_include_rustworkx_modules.rs" +)); + #[pymodule] fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { m.add("__version__", env!("CARGO_PKG_VERSION"))?; @@ -436,188 +417,9 @@ fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { "JSONDeserializationError", py.get_type_bound::(), )?; - m.add_wrapped(wrap_pyfunction!(bfs_successors))?; - m.add_wrapped(wrap_pyfunction!(bfs_predecessors))?; - m.add_wrapped(wrap_pyfunction!(graph_bfs_search))?; - m.add_wrapped(wrap_pyfunction!(digraph_bfs_search))?; - m.add_wrapped(wrap_pyfunction!(graph_dijkstra_search))?; - m.add_wrapped(wrap_pyfunction!(digraph_dijkstra_search))?; - m.add_wrapped(wrap_pyfunction!(dag_longest_path))?; - m.add_wrapped(wrap_pyfunction!(dag_longest_path_length))?; - m.add_wrapped(wrap_pyfunction!(dag_weighted_longest_path))?; - m.add_wrapped(wrap_pyfunction!(dag_weighted_longest_path_length))?; - m.add_wrapped(wrap_pyfunction!(transitive_reduction))?; - m.add_wrapped(wrap_pyfunction!(number_connected_components))?; - m.add_wrapped(wrap_pyfunction!(connected_components))?; - m.add_wrapped(wrap_pyfunction!(is_connected))?; - m.add_wrapped(wrap_pyfunction!(node_connected_component))?; - m.add_wrapped(wrap_pyfunction!(number_weakly_connected_components))?; - m.add_wrapped(wrap_pyfunction!(weakly_connected_components))?; - m.add_wrapped(wrap_pyfunction!(is_weakly_connected))?; - m.add_wrapped(wrap_pyfunction!(is_semi_connected))?; - m.add_wrapped(wrap_pyfunction!(is_directed_acyclic_graph))?; - m.add_wrapped(wrap_pyfunction!(digraph_is_isomorphic))?; - m.add_wrapped(wrap_pyfunction!(graph_is_isomorphic))?; - m.add_wrapped(wrap_pyfunction!(digraph_is_subgraph_isomorphic))?; - m.add_wrapped(wrap_pyfunction!(graph_is_subgraph_isomorphic))?; - m.add_wrapped(wrap_pyfunction!(digraph_vf2_mapping))?; - m.add_wrapped(wrap_pyfunction!(graph_vf2_mapping))?; - m.add_wrapped(wrap_pyfunction!(digraph_union))?; - m.add_wrapped(wrap_pyfunction!(graph_union))?; - m.add_wrapped(wrap_pyfunction!(digraph_maximum_bisimulation))?; - m.add_wrapped(wrap_pyfunction!(digraph_cartesian_product))?; - m.add_wrapped(wrap_pyfunction!(graph_cartesian_product))?; - m.add_wrapped(wrap_pyfunction!(topological_sort))?; - m.add_wrapped(wrap_pyfunction!(topological_generations))?; - m.add_wrapped(wrap_pyfunction!(descendants))?; - m.add_wrapped(wrap_pyfunction!(ancestors))?; - m.add_wrapped(wrap_pyfunction!(lexicographical_topological_sort))?; - m.add_wrapped(wrap_pyfunction!(graph_floyd_warshall))?; - m.add_wrapped(wrap_pyfunction!(digraph_floyd_warshall))?; - m.add_wrapped(wrap_pyfunction!(graph_floyd_warshall_numpy))?; - m.add_wrapped(wrap_pyfunction!(digraph_floyd_warshall_numpy))?; - m.add_wrapped(wrap_pyfunction!( - graph_floyd_warshall_successor_and_distance - ))?; - m.add_wrapped(wrap_pyfunction!( - digraph_floyd_warshall_successor_and_distance - ))?; - m.add_wrapped(wrap_pyfunction!(collect_runs))?; - m.add_wrapped(wrap_pyfunction!(collect_bicolor_runs))?; - m.add_wrapped(wrap_pyfunction!(layers))?; - m.add_wrapped(wrap_pyfunction!(graph_distance_matrix))?; - m.add_wrapped(wrap_pyfunction!(digraph_distance_matrix))?; - m.add_wrapped(wrap_pyfunction!(digraph_adjacency_matrix))?; - m.add_wrapped(wrap_pyfunction!(graph_adjacency_matrix))?; - m.add_wrapped(wrap_pyfunction!(graph_all_pairs_all_simple_paths))?; - m.add_wrapped(wrap_pyfunction!(digraph_all_pairs_all_simple_paths))?; - m.add_wrapped(wrap_pyfunction!(graph_longest_simple_path))?; - m.add_wrapped(wrap_pyfunction!(digraph_longest_simple_path))?; - m.add_wrapped(wrap_pyfunction!(graph_all_simple_paths))?; - m.add_wrapped(wrap_pyfunction!(digraph_all_simple_paths))?; - m.add_wrapped(wrap_pyfunction!(graph_dijkstra_shortest_paths))?; - m.add_wrapped(wrap_pyfunction!(digraph_dijkstra_shortest_paths))?; - m.add_wrapped(wrap_pyfunction!(graph_all_shortest_paths))?; - m.add_wrapped(wrap_pyfunction!(digraph_all_shortest_paths))?; - m.add_wrapped(wrap_pyfunction!(graph_has_path))?; - m.add_wrapped(wrap_pyfunction!(digraph_has_path))?; - m.add_wrapped(wrap_pyfunction!(graph_dijkstra_shortest_path_lengths))?; - m.add_wrapped(wrap_pyfunction!(digraph_dijkstra_shortest_path_lengths))?; - m.add_wrapped(wrap_pyfunction!(graph_bellman_ford_shortest_paths))?; - m.add_wrapped(wrap_pyfunction!(digraph_bellman_ford_shortest_paths))?; - m.add_wrapped(wrap_pyfunction!(graph_bellman_ford_shortest_path_lengths))?; - m.add_wrapped(wrap_pyfunction!(digraph_bellman_ford_shortest_path_lengths))?; - m.add_wrapped(wrap_pyfunction!(negative_edge_cycle))?; - m.add_wrapped(wrap_pyfunction!(find_negative_cycle))?; - m.add_wrapped(wrap_pyfunction!(digraph_all_pairs_dijkstra_path_lengths))?; - m.add_wrapped(wrap_pyfunction!(digraph_all_pairs_dijkstra_shortest_paths))?; - m.add_wrapped(wrap_pyfunction!(graph_all_pairs_dijkstra_path_lengths))?; - m.add_wrapped(wrap_pyfunction!(graph_all_pairs_dijkstra_shortest_paths))?; - m.add_wrapped(wrap_pyfunction!( - digraph_all_pairs_bellman_ford_path_lengths - ))?; - m.add_wrapped(wrap_pyfunction!( - digraph_all_pairs_bellman_ford_shortest_paths - ))?; - m.add_wrapped(wrap_pyfunction!(graph_all_pairs_bellman_ford_path_lengths))?; - m.add_wrapped(wrap_pyfunction!( - graph_all_pairs_bellman_ford_shortest_paths - ))?; - m.add_wrapped(wrap_pyfunction!(graph_betweenness_centrality))?; - m.add_wrapped(wrap_pyfunction!(digraph_betweenness_centrality))?; - m.add_wrapped(wrap_pyfunction!(graph_closeness_centrality))?; - m.add_wrapped(wrap_pyfunction!(digraph_closeness_centrality))?; - m.add_wrapped(wrap_pyfunction!(graph_edge_betweenness_centrality))?; - m.add_wrapped(wrap_pyfunction!(digraph_edge_betweenness_centrality))?; - m.add_wrapped(wrap_pyfunction!(graph_eigenvector_centrality))?; - m.add_wrapped(wrap_pyfunction!(digraph_eigenvector_centrality))?; - m.add_wrapped(wrap_pyfunction!(graph_katz_centrality))?; - m.add_wrapped(wrap_pyfunction!(digraph_katz_centrality))?; - m.add_wrapped(wrap_pyfunction!(graph_astar_shortest_path))?; - m.add_wrapped(wrap_pyfunction!(digraph_astar_shortest_path))?; - m.add_wrapped(wrap_pyfunction!(graph_greedy_color))?; - m.add_wrapped(wrap_pyfunction!(graph_misra_gries_edge_color))?; - m.add_wrapped(wrap_pyfunction!(graph_greedy_edge_color))?; - m.add_wrapped(wrap_pyfunction!(graph_bipartite_edge_color))?; - m.add_wrapped(wrap_pyfunction!(graph_two_color))?; - m.add_wrapped(wrap_pyfunction!(digraph_two_color))?; - m.add_wrapped(wrap_pyfunction!(graph_is_bipartite))?; - m.add_wrapped(wrap_pyfunction!(digraph_is_bipartite))?; - m.add_wrapped(wrap_pyfunction!(graph_line_graph))?; - m.add_wrapped(wrap_pyfunction!(graph_tensor_product))?; - m.add_wrapped(wrap_pyfunction!(digraph_tensor_product))?; - m.add_wrapped(wrap_pyfunction!(directed_gnp_random_graph))?; - m.add_wrapped(wrap_pyfunction!(undirected_gnp_random_graph))?; - m.add_wrapped(wrap_pyfunction!(directed_gnm_random_graph))?; - m.add_wrapped(wrap_pyfunction!(undirected_gnm_random_graph))?; - m.add_wrapped(wrap_pyfunction!(undirected_sbm_random_graph))?; - m.add_wrapped(wrap_pyfunction!(directed_sbm_random_graph))?; - m.add_wrapped(wrap_pyfunction!(random_geometric_graph))?; - m.add_wrapped(wrap_pyfunction!(hyperbolic_random_graph))?; - m.add_wrapped(wrap_pyfunction!(barabasi_albert_graph))?; - m.add_wrapped(wrap_pyfunction!(directed_barabasi_albert_graph))?; - m.add_wrapped(wrap_pyfunction!(directed_random_bipartite_graph))?; - m.add_wrapped(wrap_pyfunction!(undirected_random_bipartite_graph))?; - m.add_wrapped(wrap_pyfunction!(cycle_basis))?; - m.add_wrapped(wrap_pyfunction!(simple_cycles))?; - m.add_wrapped(wrap_pyfunction!(strongly_connected_components))?; - m.add_wrapped(wrap_pyfunction!(digraph_dfs_edges))?; - m.add_wrapped(wrap_pyfunction!(graph_dfs_edges))?; - m.add_wrapped(wrap_pyfunction!(digraph_find_cycle))?; - m.add_wrapped(wrap_pyfunction!(digraph_k_shortest_path_lengths))?; - m.add_wrapped(wrap_pyfunction!(graph_k_shortest_path_lengths))?; - m.add_wrapped(wrap_pyfunction!(is_matching))?; - m.add_wrapped(wrap_pyfunction!(is_maximal_matching))?; - m.add_wrapped(wrap_pyfunction!(max_weight_matching))?; - m.add_wrapped(wrap_pyfunction!(minimum_spanning_edges))?; - m.add_wrapped(wrap_pyfunction!(minimum_spanning_tree))?; - m.add_wrapped(wrap_pyfunction!(graph_transitivity))?; - m.add_wrapped(wrap_pyfunction!(digraph_transitivity))?; - m.add_wrapped(wrap_pyfunction!(graph_token_swapper))?; - m.add_wrapped(wrap_pyfunction!(graph_core_number))?; - m.add_wrapped(wrap_pyfunction!(digraph_core_number))?; - m.add_wrapped(wrap_pyfunction!(graph_complement))?; - m.add_wrapped(wrap_pyfunction!(digraph_complement))?; - m.add_wrapped(wrap_pyfunction!(graph_random_layout))?; - m.add_wrapped(wrap_pyfunction!(digraph_random_layout))?; - m.add_wrapped(wrap_pyfunction!(graph_bipartite_layout))?; - m.add_wrapped(wrap_pyfunction!(digraph_bipartite_layout))?; - m.add_wrapped(wrap_pyfunction!(graph_circular_layout))?; - m.add_wrapped(wrap_pyfunction!(digraph_circular_layout))?; - m.add_wrapped(wrap_pyfunction!(graph_shell_layout))?; - m.add_wrapped(wrap_pyfunction!(digraph_shell_layout))?; - m.add_wrapped(wrap_pyfunction!(graph_spiral_layout))?; - m.add_wrapped(wrap_pyfunction!(digraph_spiral_layout))?; - m.add_wrapped(wrap_pyfunction!(graph_spring_layout))?; - m.add_wrapped(wrap_pyfunction!(digraph_spring_layout))?; - m.add_wrapped(wrap_pyfunction!(digraph_num_shortest_paths_unweighted))?; - m.add_wrapped(wrap_pyfunction!(graph_num_shortest_paths_unweighted))?; - m.add_wrapped(wrap_pyfunction!( - digraph_unweighted_average_shortest_path_length - ))?; - m.add_wrapped(wrap_pyfunction!( - graph_unweighted_average_shortest_path_length - ))?; - m.add_wrapped(wrap_pyfunction!(metric_closure))?; - m.add_wrapped(wrap_pyfunction!(stoer_wagner_min_cut))?; - m.add_wrapped(wrap_pyfunction!(steiner_tree::steiner_tree))?; - m.add_wrapped(wrap_pyfunction!(digraph_dfs_search))?; - m.add_wrapped(wrap_pyfunction!(graph_dfs_search))?; - m.add_wrapped(wrap_pyfunction!(articulation_points))?; - m.add_wrapped(wrap_pyfunction!(bridges))?; - m.add_wrapped(wrap_pyfunction!(biconnected_components))?; - m.add_wrapped(wrap_pyfunction!(chain_decomposition))?; - m.add_wrapped(wrap_pyfunction!(graph_isolates))?; - m.add_wrapped(wrap_pyfunction!(digraph_isolates))?; - m.add_wrapped(wrap_pyfunction!(connected_subgraphs))?; - m.add_wrapped(wrap_pyfunction!(is_planar))?; - m.add_wrapped(wrap_pyfunction!(read_graphml))?; - m.add_wrapped(wrap_pyfunction!(digraph_node_link_json))?; - m.add_wrapped(wrap_pyfunction!(graph_node_link_json))?; - m.add_wrapped(wrap_pyfunction!(from_node_link_json_file))?; - m.add_wrapped(wrap_pyfunction!(parse_node_link_json))?; - m.add_wrapped(wrap_pyfunction!(pagerank))?; - m.add_wrapped(wrap_pyfunction!(hits))?; + + register_rustworkx_everything(m)?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -644,7 +446,20 @@ fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; - m.add_class::()?; + m.add_class::()?; m.add_wrapped(wrap_pymodule!(generators::generators))?; Ok(()) } + +#[macro_export] +macro_rules! export_rustworkx_functions { + ($($v:ident),*) => { + + pub fn register_rustworkx_functions(m: &pyo3::Bound) -> pyo3::prelude::PyResult<()> { + $( + m.add_wrapped(pyo3::wrap_pyfunction!($v))?; + )* + Ok(()) + } + } +} diff --git a/src/line_graph.rs b/src/line_graph.rs index 38f3ee474..18ad7be3e 100644 --- a/src/line_graph.rs +++ b/src/line_graph.rs @@ -10,7 +10,7 @@ // License for the specific language governing permissions and limitations // under the License. -use crate::{graph, StablePyGraph}; +use crate::{export_rustworkx_functions, graph, StablePyGraph}; use hashbrown::HashMap; @@ -23,6 +23,8 @@ use rustworkx_core::line_graph::line_graph; use pyo3::prelude::*; use pyo3::Python; +export_rustworkx_functions!(graph_line_graph); + /// Constructs the line graph of a :class:`~.PyGraph` object. /// /// The line graph `L(G)` of a graph `G` represents the adjacencies between edges of G. diff --git a/src/link_analysis.rs b/src/link_analysis.rs index c43e3db8e..44632f006 100644 --- a/src/link_analysis.rs +++ b/src/link_analysis.rs @@ -18,7 +18,7 @@ use pyo3::Python; use crate::digraph::PyDiGraph; use crate::iterators::CentralityMapping; -use crate::{weight_callable, FailedToConverge}; +use crate::{export_rustworkx_functions, weight_callable, FailedToConverge}; use hashbrown::HashMap; use ndarray::prelude::*; @@ -29,6 +29,8 @@ use petgraph::visit::NodeIndexable; use rustworkx_core::dictmap::*; use sprs::{CsMat, TriMat}; +export_rustworkx_functions!(pagerank, hits); + /// Computes the PageRank of the nodes in a :class:`~PyDiGraph`. /// /// For details on the PageRank, refer to: diff --git a/src/matching/mod.rs b/src/matching/mod.rs index af9740ad9..01ab90b4b 100644 --- a/src/matching/mod.rs +++ b/src/matching/mod.rs @@ -22,8 +22,11 @@ use petgraph::graph::NodeIndex; use petgraph::prelude::*; use petgraph::visit::IntoEdgeReferences; +use crate::export_rustworkx_functions; use crate::weight_callable; +export_rustworkx_functions!(is_matching, is_maximal_matching, max_weight_matching); + /// Compute a maximum-weighted matching for a :class:`~rustworkx.PyGraph` /// /// A matching is a subset of edges in which no node occurs more than once. diff --git a/src/planar/mod.rs b/src/planar/mod.rs index 2683c871a..685d5afe8 100644 --- a/src/planar/mod.rs +++ b/src/planar/mod.rs @@ -10,11 +10,14 @@ // License for the specific language governing permissions and limitations // under the License. +use crate::export_rustworkx_functions; use crate::graph::PyGraph; use rustworkx_core::planar; use pyo3::prelude::*; +export_rustworkx_functions!(is_planar); + /// Check if an undirected graph is planar. /// /// A graph is planar iff it can be drawn in a plane without any edge diff --git a/src/random_graph.rs b/src/random_graph.rs index 66451dc22..8ccb8d08d 100644 --- a/src/random_graph.rs +++ b/src/random_graph.rs @@ -12,7 +12,7 @@ #![allow(clippy::float_cmp)] -use crate::{digraph, graph, StablePyGraph}; +use crate::{digraph, export_rustworkx_functions, graph, StablePyGraph}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; @@ -31,6 +31,21 @@ use rand_pcg::Pcg64; use rustworkx_core::generators as core_generators; +export_rustworkx_functions!( + directed_gnp_random_graph, + undirected_gnp_random_graph, + directed_gnm_random_graph, + undirected_gnm_random_graph, + undirected_sbm_random_graph, + directed_sbm_random_graph, + random_geometric_graph, + hyperbolic_random_graph, + barabasi_albert_graph, + directed_barabasi_albert_graph, + directed_random_bipartite_graph, + undirected_random_bipartite_graph +); + /// Return a :math:`G_{np}` directed random graph, also known as an /// Erdős-Rényi graph or a binomial graph. /// diff --git a/src/shortest_path/mod.rs b/src/shortest_path/mod.rs index 1b9970327..28c870bfd 100644 --- a/src/shortest_path/mod.rs +++ b/src/shortest_path/mod.rs @@ -19,7 +19,10 @@ mod num_shortest_path; use std::convert::TryFrom; -use crate::{digraph, edge_weights_from_callable, graph, CostFn, NegativeCycle, NoPathFound}; +use crate::{ + digraph, edge_weights_from_callable, export_rustworkx_functions, graph, CostFn, NegativeCycle, + NoPathFound, +}; use pyo3::prelude::*; use pyo3::Python; @@ -43,6 +46,47 @@ use crate::iterators::{ PathLengthMapping, PathMapping, }; +export_rustworkx_functions!( + graph_dijkstra_shortest_paths, + digraph_dijkstra_shortest_paths, + graph_all_shortest_paths, + digraph_all_shortest_paths, + graph_dijkstra_shortest_path_lengths, + digraph_dijkstra_shortest_path_lengths, + graph_bellman_ford_shortest_paths, + digraph_bellman_ford_shortest_paths, + graph_bellman_ford_shortest_path_lengths, + digraph_bellman_ford_shortest_path_lengths, + graph_floyd_warshall, + digraph_floyd_warshall, + graph_floyd_warshall_numpy, + digraph_floyd_warshall_numpy, + graph_floyd_warshall_successor_and_distance, + digraph_floyd_warshall_successor_and_distance, + digraph_all_pairs_bellman_ford_path_lengths, + digraph_all_pairs_bellman_ford_shortest_paths, + graph_all_pairs_bellman_ford_path_lengths, + graph_all_pairs_bellman_ford_shortest_paths, + digraph_all_pairs_dijkstra_path_lengths, + digraph_all_pairs_dijkstra_shortest_paths, + graph_all_pairs_dijkstra_path_lengths, + graph_all_pairs_dijkstra_shortest_paths, + graph_astar_shortest_path, + digraph_astar_shortest_path, + negative_edge_cycle, + find_negative_cycle, + digraph_k_shortest_path_lengths, + graph_k_shortest_path_lengths, + digraph_num_shortest_paths_unweighted, + graph_num_shortest_paths_unweighted, + digraph_unweighted_average_shortest_path_length, + graph_unweighted_average_shortest_path_length, + digraph_has_path, + graph_has_path, + graph_distance_matrix, + digraph_distance_matrix +); + /// Find the shortest path from a node /// /// This function will generate the shortest path from a source node using diff --git a/src/steiner_tree.rs b/src/steiner_tree.rs index 57819b992..abffdcc34 100644 --- a/src/steiner_tree.rs +++ b/src/steiner_tree.rs @@ -22,11 +22,13 @@ use pyo3::Python; use petgraph::stable_graph::{EdgeIndex, EdgeReference, NodeIndex}; use petgraph::visit::{EdgeRef, IntoEdgeReferences}; -use crate::{graph, is_valid_weight}; +use crate::{export_rustworkx_functions, graph, is_valid_weight}; use rustworkx_core::steiner_tree::metric_closure as core_metric_closure; use rustworkx_core::steiner_tree::steiner_tree as core_steiner_tree; +export_rustworkx_functions!(metric_closure, steiner_tree); + /// Return the metric closure of a graph /// /// The metric closure of a graph is the complete graph in which each edge is diff --git a/src/tensor_product.rs b/src/tensor_product.rs index 134cb9b8b..1b2c68f36 100644 --- a/src/tensor_product.rs +++ b/src/tensor_product.rs @@ -11,7 +11,7 @@ // under the License. use crate::iterators::ProductNodeMap; -use crate::{digraph, graph, StablePyGraph}; +use crate::{digraph, export_rustworkx_functions, graph, StablePyGraph}; use hashbrown::HashMap; @@ -21,6 +21,8 @@ use petgraph::{algo, EdgeType}; use pyo3::prelude::*; use pyo3::Python; +export_rustworkx_functions!(graph_tensor_product, digraph_tensor_product); + fn tensor_product( py: Python, first: &StablePyGraph, diff --git a/src/token_swapper.rs b/src/token_swapper.rs index a2ee68e21..f3335ab68 100644 --- a/src/token_swapper.rs +++ b/src/token_swapper.rs @@ -10,6 +10,7 @@ // License for the specific language governing permissions and limitations // under the License. +use crate::export_rustworkx_functions; use crate::graph; use crate::iterators::EdgeList; use crate::InvalidMapping; @@ -19,6 +20,8 @@ use petgraph::graph::NodeIndex; use pyo3::prelude::*; use rustworkx_core::token_swapper; +export_rustworkx_functions!(graph_token_swapper); + /// This module performs an approximately optimal Token Swapping algorithm /// Supports partial mappings (i.e. not-permutations) for graphs with missing tokens. /// diff --git a/src/transitivity.rs b/src/transitivity.rs index 6bcb25f94..576e126db 100644 --- a/src/transitivity.rs +++ b/src/transitivity.rs @@ -10,7 +10,7 @@ // License for the specific language governing permissions and limitations // under the License. -use super::{digraph, graph}; +use super::{digraph, export_rustworkx_functions, graph}; use hashbrown::HashSet; use pyo3::prelude::*; @@ -18,6 +18,8 @@ use pyo3::prelude::*; use petgraph::graph::NodeIndex; use rayon::prelude::*; +export_rustworkx_functions!(digraph_transitivity, graph_transitivity); + fn _graph_triangles(graph: &graph::PyGraph, node: usize) -> (usize, usize) { let mut triangles: usize = 0; diff --git a/src/traversal/mod.rs b/src/traversal/mod.rs index 2d8531b48..201537669 100644 --- a/src/traversal/mod.rs +++ b/src/traversal/mod.rs @@ -24,7 +24,7 @@ use rustworkx_core::traversal::{ descendants as core_descendants, dfs_edges, dijkstra_search, }; -use super::{digraph, graph, iterators, CostFn}; +use super::{digraph, export_rustworkx_functions, graph, iterators, CostFn}; use std::convert::TryFrom; @@ -38,6 +38,21 @@ use petgraph::graph::NodeIndex; use crate::iterators::EdgeList; +export_rustworkx_functions!( + digraph_dfs_edges, + graph_dfs_edges, + digraph_bfs_search, + graph_bfs_search, + digraph_dfs_search, + graph_dfs_search, + digraph_dijkstra_search, + graph_dijkstra_search, + bfs_successors, + bfs_predecessors, + descendants, + ancestors +); + /// Get an edge list of the tree edges from a depth-first traversal /// /// The pseudo-code for the DFS algorithm is listed below. The output diff --git a/src/tree.rs b/src/tree.rs index b9426346c..2f69a917c 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -12,7 +12,7 @@ use std::cmp::Ordering; -use super::{graph, weight_callable}; +use super::{export_rustworkx_functions, graph, weight_callable}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; @@ -27,6 +27,8 @@ use rayon::prelude::*; use crate::iterators::WeightedEdgeList; +export_rustworkx_functions!(minimum_spanning_edges, minimum_spanning_tree); + /// Find the edges in the minimum spanning tree or forest of a graph /// using Kruskal's algorithm. /// diff --git a/src/union.rs b/src/union.rs index f095cd017..3b67a1371 100644 --- a/src/union.rs +++ b/src/union.rs @@ -10,7 +10,7 @@ // License for the specific language governing permissions and limitations // under the License. -use crate::{digraph, find_node_by_weight, graph, StablePyGraph}; +use crate::{digraph, export_rustworkx_functions, find_node_by_weight, graph, StablePyGraph}; use petgraph::stable_graph::NodeIndex; use petgraph::visit::{EdgeRef, IntoEdgeReferences, NodeIndexable}; @@ -19,6 +19,8 @@ use petgraph::{algo, EdgeType}; use pyo3::prelude::*; use pyo3::Python; +export_rustworkx_functions!(graph_union, digraph_union); + #[derive(Copy, Clone)] enum Entry { Merged(T),