diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee157a756..a3949339f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,17 +18,17 @@ jobs: include: - rust: 1.64.0 # MSRV - rust: stable - features: unstable quickcheck + features: unstable quickcheck rayon test_all: --all - rust: beta test_all: --all - rust: nightly - features: unstable quickcheck + features: unstable quickcheck rayon test_all: --all bench: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -58,7 +58,7 @@ jobs: rustfmt: rustfmt steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -72,7 +72,7 @@ jobs: miri: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: miri diff --git a/Cargo.toml b/Cargo.toml index 9880629c8..009bcb918 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,10 @@ [package] name = "petgraph" -version = "0.6.4" +version = "0.6.5" readme = "README.md" license = "MIT OR Apache-2.0" -authors = [ -"bluss", -"mitchmindtree", -] +authors = ["bluss", "mitchmindtree"] description = "Graph data structure library. Provides graph types and graph algorithms." documentation = "https://docs.rs/petgraph/" @@ -21,7 +18,7 @@ rust-version = "1.64" edition = "2018" [package.metadata.docs.rs] -features = ["serde-1", "quickcheck"] +features = ["rayon", "serde-1", "quickcheck"] [package.metadata.release] no-dev-version = true @@ -42,18 +39,29 @@ indexmap = "2.0" quickcheck = { optional = true, version = "0.8", default-features = false } serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } +rayon = { version = "1.5.3", optional = true } [dev-dependencies] bincode = "1.3.3" defmac = "0.2.1" -itertools = { version = "0.11.0", default-features = false } +itertools = { version = "0.12.1", default-features = false } odds = { version = "0.4.0" } rand = "0.5.5" +ahash = "0.7.2" +fxhash = "0.2.1" [features] +rayon = ["dep:rayon", "indexmap/rayon"] # feature flags for testing use only -all = ["unstable", "quickcheck", "matrix_graph", "stable_graph", "graphmap"] +all = [ + "unstable", + "quickcheck", + "matrix_graph", + "stable_graph", + "graphmap", + "rayon", +] default = ["graphmap", "stable_graph", "matrix_graph"] generate = [] # For unstable features diff --git a/README.md b/README.md index 0f345b678..4eba84681 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ +![](assets/graphosaurus-512.png) + # petgraph Graph data structure library. Please read the [API documentation here][]. Supports Rust 1.64 and later. -[![build_status][]](https://github.com/petgraph/petgraph/actions) [![crates][]](https://crates.io/crates/petgraph) [![gitter][]](https://gitter.im/petgraph-rs/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Crates.io][crates-badge]][crates-url] +[![docs.rs][docsrs-badge]][docsrs-url] +![MSRV][msrv-badge] +[![Discord chat][discord-badge]][discord-url] +[![build_status][]](https://github.com/petgraph/petgraph/actions) Crate feature flags: @@ -13,12 +19,18 @@ Crate feature flags: - `matrix_graph` (default) enable `MatrixGraph`. - `serde-1` (optional) enable serialization for `Graph, StableGraph, GraphMap` using serde 1.0. Requires Rust version as required by serde. +- `rayon` (optional) enable parallel iterators for the underlying data in `GraphMap`. Requires Rust version as required by Rayon. ## Recent Changes See [RELEASES][] for a list of changes. The minimum supported rust version will only change on major releases. +## Logo + +The mascot is named "Sir Paul Rustory Graphosaurus" (close friends call him Paul). +The logo has been created by the talented Aren. + ## License Dual-licensed to be compatible with the Rust project. @@ -28,8 +40,13 @@ Licensed under the Apache License, Version 2.0 , at your option. This file may not be copied, modified, or distributed except according to those terms. - [API documentation here]: https://docs.rs/petgraph/ - [build_status]: https://github.com/petgraph/petgraph/workflows/Continuous%20integration/badge.svg?branch=master - [crates]: https://img.shields.io/crates/v/petgraph - [gitter]: https://badges.gitter.im/petgraph-rs/community.svg - [RELEASES]: RELEASES.rst +[API documentation here]: https://docs.rs/petgraph/ +[build_status]: https://github.com/petgraph/petgraph/workflows/Continuous%20integration/badge.svg?branch=master +[docsrs-badge]: https://img.shields.io/docsrs/petgraph +[docsrs-url]: https://docs.rs/petgraph/latest/petgraph/ +[crates-badge]: https://img.shields.io/crates/v/petgraph.svg +[crates-url]: https://crates.io/crates/petgraph +[discord-badge]: https://img.shields.io/discord/1166289348384280616?logo=discord&style=flat +[discord-url]: https://discord.gg/n2tc79tJ4e +[msrv-badge]: https://img.shields.io/badge/rustc-1.64+-blue.svg +[RELEASES]: RELEASES.rst diff --git a/RELEASES.rst b/RELEASES.rst index db9a698cb..29b00a9f4 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -1,3 +1,29 @@ +Version 0.6.5 (2024-05-06) +========================== + +- Add rayon support for ``GraphMap`` (`#573`_, `#615`_) +- Add ``Topo::with_initials`` method (`#585`_) +- Add logo to the project (`#598`_) +- Add Ford-Fulkerson algorithm (`#640`_) +- Update ``itertools`` to 0.12.1 (`#628`_) +- Update ``GraphMap`` to allow custom hash functions (`#623`_) +- Fix documentation (`#630`_) +- Fix clippy warnings (`#627`_) +- (internal) Fix remove old ``copyclone`` macro (`#601`_) +- (internal) Move minimum spanning tree into own module (`#624`_) + +.. _`#573`: https://github.com/petgraph/petgraph/pull/573 +.. _`#615`: https://github.com/petgraph/petgraph/pull/615 +.. _`#585`: https://github.com/petgraph/petgraph/pull/585 +.. _`#598`: https://github.com/petgraph/petgraph/pull/598 +.. _`#640`: https://github.com/petgraph/petgraph/pull/640 +.. _`#628`: https://github.com/petgraph/petgraph/pull/628 +.. _`#623`: https://github.com/petgraph/petgraph/pull/623 +.. _`#630`: https://github.com/petgraph/petgraph/pull/630 +.. _`#627`: https://github.com/petgraph/petgraph/pull/627 +.. _`#601`: https://github.com/petgraph/petgraph/pull/601 +.. _`#624`: https://github.com/petgraph/petgraph/pull/624 + Version 0.6.4 (2023-08-21) ========================== diff --git a/assets/LICENSE.md b/assets/LICENSE.md new file mode 100644 index 000000000..ba81e5248 --- /dev/null +++ b/assets/LICENSE.md @@ -0,0 +1,7 @@ +Graphosaurus (c) by the petgraph project. + +Graphosaurus is licensed under a +Creative Commons Attribution-ShareAlike 4.0 International License. + +You should have received a copy of the license along with this +work. If not, see . \ No newline at end of file diff --git a/assets/graphosaurus-128.png b/assets/graphosaurus-128.png new file mode 100644 index 000000000..47def8cc6 Binary files /dev/null and b/assets/graphosaurus-128.png differ diff --git a/assets/graphosaurus-256.png b/assets/graphosaurus-256.png new file mode 100644 index 000000000..4c0de862d Binary files /dev/null and b/assets/graphosaurus-256.png differ diff --git a/assets/graphosaurus-512.png b/assets/graphosaurus-512.png new file mode 100644 index 000000000..d49c163ec Binary files /dev/null and b/assets/graphosaurus-512.png differ diff --git a/assets/graphosaurus-64.png b/assets/graphosaurus-64.png new file mode 100644 index 000000000..7d93a736d Binary files /dev/null and b/assets/graphosaurus-64.png differ diff --git a/assets/graphosaurus.svg b/assets/graphosaurus.svg new file mode 100644 index 000000000..abb789765 --- /dev/null +++ b/assets/graphosaurus.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/pridosaurus-128.png b/assets/pridosaurus-128.png new file mode 100644 index 000000000..860c349b4 Binary files /dev/null and b/assets/pridosaurus-128.png differ diff --git a/assets/pridosaurus-256.png b/assets/pridosaurus-256.png new file mode 100644 index 000000000..0bfd8341f Binary files /dev/null and b/assets/pridosaurus-256.png differ diff --git a/assets/pridosaurus-512.png b/assets/pridosaurus-512.png new file mode 100644 index 000000000..6da035534 Binary files /dev/null and b/assets/pridosaurus-512.png differ diff --git a/assets/pridosaurus-64.png b/assets/pridosaurus-64.png new file mode 100644 index 000000000..f126a4000 Binary files /dev/null and b/assets/pridosaurus-64.png differ diff --git a/assets/pridosaurus.svg b/assets/pridosaurus.svg new file mode 100644 index 000000000..9db1da537 --- /dev/null +++ b/assets/pridosaurus.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/benches/bellman_ford.rs b/benches/bellman_ford.rs index 9a91a3f13..faed79503 100644 --- a/benches/bellman_ford.rs +++ b/benches/bellman_ford.rs @@ -13,7 +13,7 @@ use petgraph::algo::{bellman_ford, find_negative_cycle}; fn bellman_ford_bench(bench: &mut Bencher) { static NODE_COUNT: usize = 100; let mut g = Graph::new(); - let nodes: Vec> = (0..NODE_COUNT).into_iter().map(|i| g.add_node(i)).collect(); + let nodes: Vec> = (0..NODE_COUNT).map(|i| g.add_node(i)).collect(); for i in 0..NODE_COUNT { let n1 = nodes[i]; let neighbour_count = i % 8 + 3; @@ -25,7 +25,7 @@ fn bellman_ford_bench(bench: &mut Bencher) { if n1 != n2 { distance -= 1.0 } - g.add_edge(n1, n2, distance as f64); + g.add_edge(n1, n2, distance); } } @@ -38,7 +38,7 @@ fn bellman_ford_bench(bench: &mut Bencher) { fn find_negative_cycle_bench(bench: &mut Bencher) { static NODE_COUNT: usize = 100; let mut g = Graph::new(); - let nodes: Vec> = (0..NODE_COUNT).into_iter().map(|i| g.add_node(i)).collect(); + let nodes: Vec> = (0..NODE_COUNT).map(|i| g.add_node(i)).collect(); for i in 0..NODE_COUNT { let n1 = nodes[i]; let neighbour_count = i % 8 + 3; diff --git a/benches/dijkstra.rs b/benches/dijkstra.rs index 7901983ff..65cc9079c 100644 --- a/benches/dijkstra.rs +++ b/benches/dijkstra.rs @@ -13,7 +13,7 @@ use petgraph::algo::dijkstra; fn dijkstra_bench(bench: &mut Bencher) { static NODE_COUNT: usize = 10_000; let mut g = Graph::new_undirected(); - let nodes: Vec> = (0..NODE_COUNT).into_iter().map(|i| g.add_node(i)).collect(); + let nodes: Vec> = (0..NODE_COUNT).map(|i| g.add_node(i)).collect(); for i in 0..NODE_COUNT { let n1 = nodes[i]; let neighbour_count = i % 8 + 3; diff --git a/benches/floyd_warshall.rs b/benches/floyd_warshall.rs index e76b97896..a192e3c80 100644 --- a/benches/floyd_warshall.rs +++ b/benches/floyd_warshall.rs @@ -13,7 +13,7 @@ use petgraph::algo::floyd_warshall; fn floyd_warshall_bench(bench: &mut Bencher) { static NODE_COUNT: usize = 100; let mut g = Graph::new_undirected(); - let nodes: Vec> = (0..NODE_COUNT).into_iter().map(|i| g.add_node(i)).collect(); + let nodes: Vec> = (0..NODE_COUNT).map(|i| g.add_node(i)).collect(); for i in 0..NODE_COUNT { let n1 = nodes[i]; let neighbour_count = i % 8 + 3; diff --git a/benches/ford_fulkerson.rs b/benches/ford_fulkerson.rs new file mode 100644 index 000000000..ca0271134 --- /dev/null +++ b/benches/ford_fulkerson.rs @@ -0,0 +1,24 @@ +#![feature(test)] +extern crate petgraph; +extern crate test; + +use petgraph::algo::ford_fulkerson; +use petgraph::prelude::{Graph, NodeIndex}; +use test::Bencher; + +#[bench] +fn ford_fulkerson_bench(bench: &mut Bencher) { + static NODE_COUNT: usize = 1_000; + let mut g: Graph = Graph::new(); + let nodes: Vec> = (0..NODE_COUNT).map(|i| g.add_node(i)).collect(); + for i in 0..NODE_COUNT - 1 { + g.add_edge(nodes[i], nodes[i + 1], 1); + } + bench.iter(|| { + let _flow = ford_fulkerson( + &g, + NodeIndex::from(0), + NodeIndex::from(g.node_count() as u32 - 1), + ); + }); +} diff --git a/benches/graphmap.rs b/benches/graphmap.rs new file mode 100644 index 000000000..d9f7c1a02 --- /dev/null +++ b/benches/graphmap.rs @@ -0,0 +1,94 @@ +#![feature(test)] +#![cfg(feature = "rayon")] + +extern crate petgraph; +extern crate test; + +use petgraph::prelude::*; +use rayon::iter::ParallelIterator; +use std::hash::BuildHasher; +use test::Bencher; + +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +struct MyStruct { + u: String, + v: String, + w: String, +} + +fn test_nodes() -> Vec { + let mut nodes = vec![]; + for i in 0..2500 { + nodes.push(MyStruct { + u: format!("X {}", i), + v: format!("Y {} Y", i), + w: format!("{}Z", i), + }); + } + + nodes +} + +fn test_graph( + data: &Vec, +) -> GraphMap<&MyStruct, usize, Directed, H> { + let mut gr = GraphMap::new(); + + for i in 0..2500 { + gr.add_node(&data[i]); + } + + for i in 0..1_000 { + for j in 999..2000 { + gr.add_edge(&data[i], &data[j], i * j); + } + } + + gr +} + +macro_rules! test_case_with_hasher { + ($name:ident, $hasher:path) => { + #[bench] + fn $name(bench: &mut Bencher) { + let data = test_nodes(); + let gr = test_graph::<$hasher>(&data); + bench.iter(|| { + let mut sources = vec![]; + for n in gr.nodes() { + for (src, _, e) in gr.edges_directed(n, Direction::Outgoing) { + if *e == 500 { + sources.push(src.clone()); + } + } + } + }); + } + }; +} + +test_case_with_hasher!(graphmap_serial_bench, std::hash::RandomState); +test_case_with_hasher!(graphmap_serial_bench_fxhash, fxhash::FxBuildHasher); +test_case_with_hasher!(graphmap_serial_bench_ahash, ahash::RandomState); + +#[bench] +fn graphmap_parallel_bench(bench: &mut Bencher) { + let data = test_nodes(); + let gr = test_graph::(&data); + bench.iter(|| { + let sources: Vec = gr + .par_nodes() + .map(|n| { + let mut sources = vec![]; + for (src, _, e) in gr.edges_directed(n, Direction::Outgoing) { + if *e == 500 { + sources.push(src.clone()); + } + } + + sources + }) + .flatten() + .collect(); + }); +} diff --git a/benches/k_shortest_path.rs b/benches/k_shortest_path.rs index 47f4413ba..52effeae1 100644 --- a/benches/k_shortest_path.rs +++ b/benches/k_shortest_path.rs @@ -13,7 +13,7 @@ use petgraph::algo::k_shortest_path; fn k_shortest_path_bench(bench: &mut Bencher) { static NODE_COUNT: usize = 10_000; let mut g = Graph::new_undirected(); - let nodes: Vec> = (0..NODE_COUNT).into_iter().map(|i| g.add_node(i)).collect(); + let nodes: Vec> = (0..NODE_COUNT).map(|i| g.add_node(i)).collect(); for i in 0..NODE_COUNT { let n1 = nodes[i]; let neighbour_count = i % 8 + 3; diff --git a/benches/matrix_graph.rs b/benches/matrix_graph.rs index 108db3726..3d59f1b09 100644 --- a/benches/matrix_graph.rs +++ b/benches/matrix_graph.rs @@ -44,7 +44,7 @@ fn add_5_edges_for_each_of_100_nodes(b: &mut test::Bencher) { let edges_to_add: Vec<_> = nodes .iter() .enumerate() - .map(|(i, &node)| { + .flat_map(|(i, &node)| { let edges: Vec<_> = (0..5) .map(|j| (i + j + 1) % nodes.len()) .map(|j| (node, nodes[j])) @@ -52,7 +52,6 @@ fn add_5_edges_for_each_of_100_nodes(b: &mut test::Bencher) { edges }) - .flatten() .collect(); b.iter(|| { diff --git a/benches/min_spanning_tree.rs b/benches/min_spanning_tree.rs new file mode 100644 index 000000000..ba274ed55 --- /dev/null +++ b/benches/min_spanning_tree.rs @@ -0,0 +1,60 @@ +#![feature(test)] + +extern crate petgraph; +extern crate test; + +use test::Bencher; + +#[allow(dead_code)] +mod common; +use common::{digraph, ungraph}; + +use petgraph::algo::min_spanning_tree; + +#[bench] +fn min_spanning_tree_praust_undir_bench(bench: &mut Bencher) { + let a = ungraph().praust_a(); + let b = ungraph().praust_b(); + + bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); +} + +#[bench] +fn min_spanning_tree_praust_dir_bench(bench: &mut Bencher) { + let a = digraph().praust_a(); + let b = digraph().praust_b(); + + bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); +} + +#[bench] +fn min_spanning_tree_full_undir_bench(bench: &mut Bencher) { + let a = ungraph().full_a(); + let b = ungraph().full_b(); + + bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); +} + +#[bench] +fn min_spanning_tree_full_dir_bench(bench: &mut Bencher) { + let a = digraph().full_a(); + let b = digraph().full_b(); + + bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); +} + +#[bench] +fn min_spanning_tree_petersen_undir_bench(bench: &mut Bencher) { + let a = ungraph().petersen_a(); + let b = ungraph().petersen_b(); + + bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); +} + +#[bench] +fn min_spanning_tree_petersen_dir_bench(bench: &mut Bencher) { + let a = digraph().petersen_a(); + let b = digraph().petersen_b(); + + bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); +} diff --git a/benches/page_rank.rs b/benches/page_rank.rs new file mode 100644 index 000000000..a7a6bbaf7 --- /dev/null +++ b/benches/page_rank.rs @@ -0,0 +1,36 @@ +#![feature(test)] +extern crate petgraph; +extern crate test; + +use test::Bencher; + +use petgraph::algo::page_rank; + +#[allow(dead_code)] +mod common; + +use common::directed_fan; + +#[cfg(feature = "rayon")] +use petgraph::algo::page_rank::parallel_page_rank; +#[cfg(feature = "rayon")] +use rayon::prelude::*; + +#[bench] +fn page_rank_bench(bench: &mut Bencher) { + static NODE_COUNT: usize = 500; + let g = directed_fan(NODE_COUNT); + bench.iter(|| { + let _ranks = page_rank(&g, 0.6_f64, 10); + }); +} + +#[bench] +#[cfg(feature = "rayon")] +fn par_page_rank_bench(bench: &mut Bencher) { + static NODE_COUNT: usize = 2_000; + let g = directed_fan(NODE_COUNT); + bench.iter(|| { + let _ranks = parallel_page_rank(&g, 0.6_f64, 100, None); + }); +} diff --git a/benches/unionfind.rs b/benches/unionfind.rs index dd4446a52..4c9608025 100644 --- a/benches/unionfind.rs +++ b/benches/unionfind.rs @@ -9,7 +9,7 @@ use test::Bencher; mod common; use common::*; -use petgraph::algo::{connected_components, is_cyclic_undirected, min_spanning_tree}; +use petgraph::algo::{connected_components, is_cyclic_undirected}; #[bench] fn connected_components_praust_undir_bench(bench: &mut Bencher) { @@ -106,51 +106,3 @@ fn is_cyclic_undirected_petersen_dir_bench(bench: &mut Bencher) { bench.iter(|| (is_cyclic_undirected(&a), is_cyclic_undirected(&b))); } - -#[bench] -fn min_spanning_tree_praust_undir_bench(bench: &mut Bencher) { - let a = ungraph().praust_a(); - let b = ungraph().praust_b(); - - bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); -} - -#[bench] -fn min_spanning_tree_praust_dir_bench(bench: &mut Bencher) { - let a = digraph().praust_a(); - let b = digraph().praust_b(); - - bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); -} - -#[bench] -fn min_spanning_tree_full_undir_bench(bench: &mut Bencher) { - let a = ungraph().full_a(); - let b = ungraph().full_b(); - - bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); -} - -#[bench] -fn min_spanning_tree_full_dir_bench(bench: &mut Bencher) { - let a = digraph().full_a(); - let b = digraph().full_b(); - - bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); -} - -#[bench] -fn min_spanning_tree_petersen_undir_bench(bench: &mut Bencher) { - let a = ungraph().petersen_a(); - let b = ungraph().petersen_b(); - - bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); -} - -#[bench] -fn min_spanning_tree_petersen_dir_bench(bench: &mut Bencher) { - let a = digraph().petersen_a(); - let b = digraph().petersen_b(); - - bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); -} diff --git a/src/adj.rs b/src/adj.rs index 2b9135321..3ea3f574c 100644 --- a/src/adj.rs +++ b/src/adj.rs @@ -71,10 +71,7 @@ pub struct EdgeReference<'a, E, Ix: IndexType> { impl<'a, E, Ix: IndexType> Copy for EdgeReference<'a, E, Ix> {} impl<'a, E, Ix: IndexType> Clone for EdgeReference<'a, E, Ix> { fn clone(&self) -> Self { - EdgeReference { - id: self.id, - edge: self.edge, - } + *self } } diff --git a/src/algo/dominators.rs b/src/algo/dominators.rs index db6436ae4..a7e1a5f9a 100644 --- a/src/algo/dominators.rs +++ b/src/algo/dominators.rs @@ -132,7 +132,7 @@ where type Item = N; fn next(&mut self) -> Option { - while let Some(next) = self.iter.next() { + for next in self.iter.by_ref() { if next.1 == &self.node { return Some(*next.0); } @@ -157,7 +157,7 @@ const UNDEFINED: usize = ::std::usize::MAX; /// Cooper et al found it to be faster in practice on control flow graphs of up /// to ~30,000 vertices. /// -/// [0]: http://www.cs.rice.edu/~keith/EMBED/dom.pdf +/// [0]: http://www.hipersoft.rice.edu/grads/publications/dom14.pdf pub fn simple_fast(graph: G, root: G::NodeId) -> Dominators where G: IntoNeighbors + Visitable, @@ -267,7 +267,7 @@ where .map(|p| *node_to_post_order_idx.get(&p).unwrap()) .collect() }) - .unwrap_or_else(Vec::new) + .unwrap_or_default() }) .collect() } diff --git a/src/algo/ford_fulkerson.rs b/src/algo/ford_fulkerson.rs new file mode 100644 index 000000000..3b2127f33 --- /dev/null +++ b/src/algo/ford_fulkerson.rs @@ -0,0 +1,196 @@ +use std::{collections::VecDeque, ops::Sub}; + +use crate::{ + data::DataMap, + visit::{ + EdgeCount, EdgeIndexable, IntoEdges, IntoEdgesDirected, NodeCount, NodeIndexable, VisitMap, + Visitable, + }, +}; + +use super::{EdgeRef, PositiveMeasure}; +use crate::prelude::Direction; + +fn residual_capacity( + network: N, + edge: N::EdgeRef, + vertex: N::NodeId, + flow: N::EdgeWeight, +) -> N::EdgeWeight +where + N: NodeIndexable + IntoEdges, + N::EdgeWeight: Sub + PositiveMeasure, +{ + if vertex == edge.source() { + // backward edge + flow + } else if vertex == edge.target() { + // forward edge + return *edge.weight() - flow; + } else { + let end_point = NodeIndexable::to_index(&network, vertex); + panic!("Illegal endpoint {}", end_point); + } +} + +/// Gets the other endpoint of graph edge, if any, otherwise panics. +fn other_endpoint(network: N, edge: N::EdgeRef, vertex: N::NodeId) -> N::NodeId +where + N: NodeIndexable + IntoEdges, +{ + if vertex == edge.source() { + edge.target() + } else if vertex == edge.target() { + edge.source() + } else { + let end_point = NodeIndexable::to_index(&network, vertex); + panic!("Illegal endpoint {}", end_point); + } +} + +/// Tells whether there is an augmented path in the graph +fn has_augmented_path( + network: N, + source: N::NodeId, + destination: N::NodeId, + edge_to: &mut [Option], + flows: &[N::EdgeWeight], +) -> bool +where + N: NodeCount + IntoEdgesDirected + NodeIndexable + EdgeIndexable + Visitable, + N::EdgeWeight: Sub + PositiveMeasure, +{ + let mut visited = network.visit_map(); + let mut queue = VecDeque::new(); + visited.visit(source); + queue.push_back(source); + + while let Some(vertex) = queue.pop_front() { + let out_edges = network.edges_directed(vertex, Direction::Outgoing); + let in_edges = network.edges_directed(vertex, Direction::Incoming); + for edge in out_edges.chain(in_edges) { + let next = other_endpoint(&network, edge, vertex); + let edge_index: usize = EdgeIndexable::to_index(&network, edge.id()); + let residual_cap = residual_capacity(&network, edge, next, flows[edge_index]); + if !visited.is_visited(&next) && (residual_cap > N::EdgeWeight::zero()) { + visited.visit(next); + edge_to[NodeIndexable::to_index(&network, next)] = Some(edge); + if destination == next { + return true; + } + queue.push_back(next); + } + } + } + false +} + +fn adjust_residual_flow( + network: N, + edge: N::EdgeRef, + vertex: N::NodeId, + flow: N::EdgeWeight, + delta: N::EdgeWeight, +) -> N::EdgeWeight +where + N: NodeIndexable + IntoEdges, + N::EdgeWeight: Sub + PositiveMeasure, +{ + if vertex == edge.source() { + // backward edge + flow - delta + } else if vertex == edge.target() { + // forward edge + flow + delta + } else { + let end_point = NodeIndexable::to_index(&network, vertex); + panic!("Illegal endpoint {}", end_point); + } +} + +/// \[Generic\] Ford-Fulkerson algorithm. +/// +/// Computes the [maximum flow][ff] of a weighted directed graph. +/// +/// If it terminates, it returns the maximum flow and also the computed edge flows. +/// +/// [ff]: https://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm +/// +/// # Example +/// ```rust +/// use petgraph::Graph; +/// use petgraph::algo::ford_fulkerson; +/// // Example from CLRS book +/// let mut graph = Graph::::new(); +/// let source = graph.add_node(0); +/// let _ = graph.add_node(1); +/// let _ = graph.add_node(2); +/// let _ = graph.add_node(3); +/// let _ = graph.add_node(4); +/// let destination = graph.add_node(5); +/// graph.extend_with_edges(&[ +/// (0, 1, 16), +/// (0, 2, 13), +/// (1, 2, 10), +/// (1, 3, 12), +/// (2, 1, 4), +/// (2, 4, 14), +/// (3, 2, 9), +/// (3, 5, 20), +/// (4, 3, 7), +/// (4, 5, 4), +/// ]); +/// let (max_flow, _) = ford_fulkerson(&graph, source, destination); +/// assert_eq!(23, max_flow); +/// ``` +pub fn ford_fulkerson( + network: N, + source: N::NodeId, + destination: N::NodeId, +) -> (N::EdgeWeight, Vec) +where + N: NodeCount + + EdgeCount + + IntoEdgesDirected + + EdgeIndexable + + NodeIndexable + + DataMap + + Visitable, + N::EdgeWeight: Sub + PositiveMeasure, +{ + let mut edge_to = vec![None; network.node_count()]; + let mut flows = vec![N::EdgeWeight::zero(); network.edge_count()]; + let mut max_flow = N::EdgeWeight::zero(); + while has_augmented_path(&network, source, destination, &mut edge_to, &flows) { + let mut path_flow = N::EdgeWeight::max(); + + // Find the bottleneck capacity of the path + let mut vertex = destination; + let mut vertex_index = NodeIndexable::to_index(&network, vertex); + while let Some(edge) = edge_to[vertex_index] { + let edge_index = EdgeIndexable::to_index(&network, edge.id()); + let residual_capacity = residual_capacity(&network, edge, vertex, flows[edge_index]); + // Minimum between the current path flow and the residual capacity. + path_flow = if path_flow > residual_capacity { + residual_capacity + } else { + path_flow + }; + vertex = other_endpoint(&network, edge, vertex); + vertex_index = NodeIndexable::to_index(&network, vertex); + } + + // Update the flow of each edge along the path + let mut vertex = destination; + let mut vertex_index = NodeIndexable::to_index(&network, vertex); + while let Some(edge) = edge_to[vertex_index] { + let edge_index = EdgeIndexable::to_index(&network, edge.id()); + flows[edge_index] = + adjust_residual_flow(&network, edge, vertex, flows[edge_index], path_flow); + vertex = other_endpoint(&network, edge, vertex); + vertex_index = NodeIndexable::to_index(&network, vertex); + } + max_flow = max_flow + path_flow; + } + (max_flow, flows) +} diff --git a/src/algo/isomorphism.rs b/src/algo/isomorphism.rs index 69da7fcfd..b0d5f1805 100644 --- a/src/algo/isomorphism.rs +++ b/src/algo/isomorphism.rs @@ -766,7 +766,7 @@ mod matching { // We hardcode n! values into an array that accounts for architectures // with smaller usizes to get our upper bound. - let upper_bounds: Vec> = vec![ + let upper_bounds: Vec> = [ 1u64, 1, 2, diff --git a/src/algo/matching.rs b/src/algo/matching.rs index cf06e44fd..ff9146fc1 100644 --- a/src/algo/matching.rs +++ b/src/algo/matching.rs @@ -494,8 +494,8 @@ fn find_join( graph: &G, edge: G::EdgeRef, mate: &[Option], - label: &mut Vec>, - first_inner: &mut Vec, + label: &mut [Label], + first_inner: &mut [usize], mut visitor: F, ) where G: IntoEdges + NodeIndexable + Visitable, diff --git a/src/algo/min_spanning_tree.rs b/src/algo/min_spanning_tree.rs new file mode 100644 index 000000000..19cefcf51 --- /dev/null +++ b/src/algo/min_spanning_tree.rs @@ -0,0 +1,117 @@ +//! Minimum Spanning Tree algorithms. + +use std::collections::{BinaryHeap, HashMap}; + +use crate::prelude::*; + +use crate::data::Element; +use crate::scored::MinScored; +use crate::unionfind::UnionFind; +use crate::visit::{Data, IntoNodeReferences, NodeRef}; +use crate::visit::{IntoEdgeReferences, NodeIndexable}; + +/// \[Generic\] Compute a *minimum spanning tree* of a graph. +/// +/// The input graph is treated as if undirected. +/// +/// Using Kruskal's algorithm with runtime **O(|E| log |E|)**. We actually +/// return a minimum spanning forest, i.e. a minimum spanning tree for each connected +/// component of the graph. +/// +/// The resulting graph has all the vertices of the input graph (with identical node indices), +/// and **|V| - c** edges, where **c** is the number of connected components in `g`. +/// +/// Use `from_elements` to create a graph from the resulting iterator. +pub fn min_spanning_tree(g: G) -> MinSpanningTree +where + G::NodeWeight: Clone, + G::EdgeWeight: Clone + PartialOrd, + G: IntoNodeReferences + IntoEdgeReferences + NodeIndexable, +{ + // Initially each vertex is its own disjoint subgraph, track the connectedness + // of the pre-MST with a union & find datastructure. + let subgraphs = UnionFind::new(g.node_bound()); + + let edges = g.edge_references(); + let mut sort_edges = BinaryHeap::with_capacity(edges.size_hint().0); + for edge in edges { + sort_edges.push(MinScored( + edge.weight().clone(), + (edge.source(), edge.target()), + )); + } + + MinSpanningTree { + graph: g, + node_ids: Some(g.node_references()), + subgraphs, + sort_edges, + node_map: HashMap::new(), + node_count: 0, + } +} + +/// An iterator producing a minimum spanning forest of a graph. +#[derive(Debug, Clone)] +pub struct MinSpanningTree +where + G: Data + IntoNodeReferences, +{ + graph: G, + node_ids: Option, + subgraphs: UnionFind, + #[allow(clippy::type_complexity)] + sort_edges: BinaryHeap>, + node_map: HashMap, + node_count: usize, +} + +impl Iterator for MinSpanningTree +where + G: IntoNodeReferences + NodeIndexable, + G::NodeWeight: Clone, + G::EdgeWeight: PartialOrd, +{ + type Item = Element; + + fn next(&mut self) -> Option { + let g = self.graph; + if let Some(ref mut iter) = self.node_ids { + if let Some(node) = iter.next() { + self.node_map.insert(g.to_index(node.id()), self.node_count); + self.node_count += 1; + return Some(Element::Node { + weight: node.weight().clone(), + }); + } + } + self.node_ids = None; + + // Kruskal's algorithm. + // Algorithm is this: + // + // 1. Create a pre-MST with all the vertices and no edges. + // 2. Repeat: + // + // a. Remove the shortest edge from the original graph. + // b. If the edge connects two disjoint trees in the pre-MST, + // add the edge. + while let Some(MinScored(score, (a, b))) = self.sort_edges.pop() { + // check if the edge would connect two disjoint parts + let (a_index, b_index) = (g.to_index(a), g.to_index(b)); + if self.subgraphs.union(a_index, b_index) { + let (&a_order, &b_order) = + match (self.node_map.get(&a_index), self.node_map.get(&b_index)) { + (Some(a_id), Some(b_id)) => (a_id, b_id), + _ => panic!("Edge references unknown node"), + }; + return Some(Element::Edge { + source: a_order, + target: b_order, + weight: score, + }); + } + } + None + } +} diff --git a/src/algo/mod.rs b/src/algo/mod.rs index 67e36245f..6c0a408eb 100644 --- a/src/algo/mod.rs +++ b/src/algo/mod.rs @@ -10,13 +10,15 @@ pub mod dijkstra; pub mod dominators; pub mod feedback_arc_set; pub mod floyd_warshall; +pub mod ford_fulkerson; pub mod isomorphism; pub mod k_shortest_path; pub mod matching; +pub mod min_spanning_tree; +pub mod page_rank; pub mod simple_paths; pub mod tred; -use std::collections::{BinaryHeap, HashMap}; use std::num::NonZeroUsize; use crate::prelude::*; @@ -28,22 +30,22 @@ use super::visit::{ IntoNodeIdentifiers, NodeCompactIndexable, NodeIndexable, Reversed, VisitMap, Visitable, }; use super::EdgeType; -use crate::data::Element; -use crate::scored::MinScored; use crate::visit::Walker; -use crate::visit::{Data, IntoNodeReferences, NodeRef}; pub use astar::astar; pub use bellman_ford::{bellman_ford, find_negative_cycle}; pub use dijkstra::dijkstra; pub use feedback_arc_set::greedy_feedback_arc_set; pub use floyd_warshall::floyd_warshall; +pub use ford_fulkerson::ford_fulkerson; pub use isomorphism::{ is_isomorphic, is_isomorphic_matching, is_isomorphic_subgraph, is_isomorphic_subgraph_matching, subgraph_isomorphisms_iter, }; pub use k_shortest_path::k_shortest_path; pub use matching::{greedy_matching, maximum_matching, Matching}; +pub use min_spanning_tree::min_spanning_tree; +pub use page_rank::page_rank; pub use simple_paths::all_simple_paths; /// \[Generic\] Return the number of connected components of the graph. @@ -635,112 +637,6 @@ where condensed } -/// \[Generic\] Compute a *minimum spanning tree* of a graph. -/// -/// The input graph is treated as if undirected. -/// -/// Using Kruskal's algorithm with runtime **O(|E| log |E|)**. We actually -/// return a minimum spanning forest, i.e. a minimum spanning tree for each connected -/// component of the graph. -/// -/// The resulting graph has all the vertices of the input graph (with identical node indices), -/// and **|V| - c** edges, where **c** is the number of connected components in `g`. -/// -/// Use `from_elements` to create a graph from the resulting iterator. -pub fn min_spanning_tree(g: G) -> MinSpanningTree -where - G::NodeWeight: Clone, - G::EdgeWeight: Clone + PartialOrd, - G: IntoNodeReferences + IntoEdgeReferences + NodeIndexable, -{ - // Initially each vertex is its own disjoint subgraph, track the connectedness - // of the pre-MST with a union & find datastructure. - let subgraphs = UnionFind::new(g.node_bound()); - - let edges = g.edge_references(); - let mut sort_edges = BinaryHeap::with_capacity(edges.size_hint().0); - for edge in edges { - sort_edges.push(MinScored( - edge.weight().clone(), - (edge.source(), edge.target()), - )); - } - - MinSpanningTree { - graph: g, - node_ids: Some(g.node_references()), - subgraphs, - sort_edges, - node_map: HashMap::new(), - node_count: 0, - } -} - -/// An iterator producing a minimum spanning forest of a graph. -#[derive(Debug, Clone)] -pub struct MinSpanningTree -where - G: Data + IntoNodeReferences, -{ - graph: G, - node_ids: Option, - subgraphs: UnionFind, - #[allow(clippy::type_complexity)] - sort_edges: BinaryHeap>, - node_map: HashMap, - node_count: usize, -} - -impl Iterator for MinSpanningTree -where - G: IntoNodeReferences + NodeIndexable, - G::NodeWeight: Clone, - G::EdgeWeight: PartialOrd, -{ - type Item = Element; - - fn next(&mut self) -> Option { - let g = self.graph; - if let Some(ref mut iter) = self.node_ids { - if let Some(node) = iter.next() { - self.node_map.insert(g.to_index(node.id()), self.node_count); - self.node_count += 1; - return Some(Element::Node { - weight: node.weight().clone(), - }); - } - } - self.node_ids = None; - - // Kruskal's algorithm. - // Algorithm is this: - // - // 1. Create a pre-MST with all the vertices and no edges. - // 2. Repeat: - // - // a. Remove the shortest edge from the original graph. - // b. If the edge connects two disjoint trees in the pre-MST, - // add the edge. - while let Some(MinScored(score, (a, b))) = self.sort_edges.pop() { - // check if the edge would connect two disjoint parts - let (a_index, b_index) = (g.to_index(a), g.to_index(b)); - if self.subgraphs.union(a_index, b_index) { - let (&a_order, &b_order) = - match (self.node_map.get(&a_index), self.node_map.get(&b_index)) { - (Some(a_id), Some(b_id)) => (a_id, b_id), - _ => panic!("Edge references unknown node"), - }; - return Some(Element::Edge { - source: a_order, - target: b_order, - weight: score, - }); - } - } - None - } -} - /// An algorithm error: a cycle was found in the graph. #[derive(Clone, Debug, PartialEq)] pub struct Cycle(N); @@ -901,3 +797,69 @@ macro_rules! impl_bounded_measure_float( ); impl_bounded_measure_float!(f32, f64); + +/// A floating-point measure that can be computed from `usize` +/// and with a default measure of proximity. +pub trait UnitMeasure: + Measure + + std::ops::Sub + + std::ops::Mul + + std::ops::Div + + std::iter::Sum +{ + fn zero() -> Self; + fn one() -> Self; + fn from_usize(nb: usize) -> Self; + fn default_tol() -> Self; +} + +macro_rules! impl_unit_measure( + ( $( $t:ident ),* )=> { + $( + impl UnitMeasure for $t { + fn zero() -> Self { + 0 as $t + } + fn one() -> Self { + 1 as $t + } + + fn from_usize(nb: usize) -> Self { + nb as $t + } + + fn default_tol() -> Self { + 1e-6 as $t + } + + } + + )* + } +); +impl_unit_measure!(f32, f64); + +/// Some measure of positive numbers, assuming positive +/// float-pointing numbers +pub trait PositiveMeasure: Measure + Copy { + fn zero() -> Self; + fn max() -> Self; +} + +macro_rules! impl_positive_measure( + ( $( $t:ident ),* )=> { + $( + impl PositiveMeasure for $t { + fn zero() -> Self { + 0 as $t + } + fn max() -> Self { + std::$t::MAX + } + } + + )* + } +); + +impl_positive_measure!(u8, u16, u32, u64, u128, usize, f32, f64); diff --git a/src/algo/page_rank.rs b/src/algo/page_rank.rs new file mode 100644 index 000000000..b829e5301 --- /dev/null +++ b/src/algo/page_rank.rs @@ -0,0 +1,185 @@ +use crate::visit::{EdgeRef, IntoEdges, NodeCount, NodeIndexable}; + +#[cfg(feature = "rayon")] +use rayon::prelude::*; + +use super::UnitMeasure; +/// \[Generic\] Page Rank algorithm. +/// +/// Computes the ranks of every node in a graph using the [Page Rank algorithm][pr]. +/// +/// Returns a `Vec` container mapping each node index to its rank. +/// +/// # Panics +/// The damping factor should be a number of type `f32` or `f64` between 0 and 1 (0 and 1 included). Otherwise, it panics. +/// +/// # Complexity +/// Time complexity is **O(N|V|²|E|)**. +/// Space complexity is **O(|V| + |E|)** +/// where **N** is the number of iterations, **|V|** the number of vertices (i.e nodes) and **|E|** the number of edges. +/// +/// [pr]: https://en.wikipedia.org/wiki/PageRank +/// +/// # Example +/// ```rust +/// use petgraph::Graph; +/// use petgraph::algo::page_rank; +/// let mut g: Graph<(), usize> = Graph::new(); +/// assert_eq!(page_rank(&g, 0.5_f64, 1), vec![]); // empty graphs have no node ranks. +/// let a = g.add_node(()); +/// let b = g.add_node(()); +/// let c = g.add_node(()); +/// let d = g.add_node(()); +/// let e = g.add_node(()); +/// g.extend_with_edges(&[(0, 1), (0, 3), (1, 2), (1, 3)]); +/// // With the following dot representation. +/// //digraph { +/// // 0 [ label = "()" ] +/// // 1 [ label = "()" ] +/// // 2 [ label = "()" ] +/// // 3 [ label = "()" ] +/// // 4 [ label = "()" ] +/// // 0 -> 1 [ label = "0.0" ] +/// // 0 -> 3 [ label = "0.0" ] +/// // 1 -> 2 [ label = "0.0" ] +/// // 1 -> 3 [ label = "0.0" ] +/// //} +/// let damping_factor = 0.7_f32; +/// let number_iterations = 10; +/// let output_ranks = page_rank(&g, damping_factor, number_iterations); +/// let expected_ranks = vec![0.14685437, 0.20267677, 0.22389607, 0.27971846, 0.14685437]; +/// assert_eq!(expected_ranks, output_ranks); +/// ``` +pub fn page_rank(graph: G, damping_factor: D, nb_iter: usize) -> Vec +where + G: NodeCount + IntoEdges + NodeIndexable, + D: UnitMeasure + Copy, +{ + let node_count = graph.node_count(); + if node_count == 0 { + return vec![]; + } + assert!( + D::zero() <= damping_factor && damping_factor <= D::one(), + "Damping factor should be between 0 et 1." + ); + let nb = D::from_usize(node_count); + let mut ranks = vec![D::one() / nb; node_count]; + let nodeix = |i| graph.from_index(i); + let out_degrees: Vec = (0..node_count) + .map(|i| graph.edges(nodeix(i)).map(|_| D::one()).sum::()) + .collect(); + + for _ in 0..nb_iter { + let pi = (0..node_count) + .enumerate() + .map(|(v, _)| { + ranks + .iter() + .enumerate() + .map(|(w, r)| { + let mut w_out_edges = graph.edges(nodeix(w)); + if w_out_edges.any(|e| e.target() == nodeix(v)) { + damping_factor * *r / out_degrees[w] + } else if out_degrees[w] == D::zero() { + damping_factor * *r / nb // stochastic matrix condition + } else { + (D::one() - damping_factor) * *r / nb // random jumps + } + }) + .sum::() + }) + .collect::>(); + let sum = pi.iter().copied().sum::(); + ranks = pi.iter().map(|r| *r / sum).collect::>(); + } + ranks +} + +#[allow(dead_code)] +fn out_edges_info(graph: G, index_w: usize, index_v: usize) -> (D, bool) +where + G: NodeCount + IntoEdges + NodeIndexable + std::marker::Sync, + D: UnitMeasure + Copy + std::marker::Send + std::marker::Sync, +{ + let node_w = graph.from_index(index_w); + let node_v = graph.from_index(index_v); + let mut out_edges = graph.edges(node_w); + let mut out_edge = out_edges.next(); + let mut out_degree = D::zero(); + let mut flag_points_to = false; + while let Some(edge) = out_edge { + out_degree = out_degree + D::one(); + if edge.target() == node_v { + flag_points_to = true; + } + out_edge = out_edges.next(); + } + (out_degree, flag_points_to) +} +/// \[Generic\] Parallel Page Rank algorithm. +/// +/// See [`page_rank`]. +#[cfg(feature = "rayon")] +pub fn parallel_page_rank( + graph: G, + damping_factor: D, + nb_iter: usize, + tol: Option, +) -> Vec +where + G: NodeCount + IntoEdges + NodeIndexable + std::marker::Sync, + D: UnitMeasure + Copy + std::marker::Send + std::marker::Sync, +{ + let node_count = graph.node_count(); + if node_count == 0 { + return vec![]; + } + assert!( + D::zero() <= damping_factor && damping_factor <= D::one(), + "Damping factor should be between 0 et 1." + ); + let mut tolerance = D::default_tol(); + if let Some(_tol) = tol { + tolerance = _tol; + } + let nb = D::from_usize(node_count); + let mut ranks: Vec = (0..node_count) + .into_par_iter() + .map(|i| D::one() / nb) + .collect(); + for _ in 0..nb_iter { + let pi = (0..node_count) + .into_par_iter() + .map(|v| { + ranks + .iter() + .enumerate() + .map(|(w, r)| { + let (out_deg, w_points_to_v) = out_edges_info(graph, w, v); + if w_points_to_v { + damping_factor * *r / out_deg + } else if out_deg == D::zero() { + damping_factor * *r / nb // stochastic matrix condition + } else { + (D::one() - damping_factor) * *r / nb // random jumps + } + }) + .sum::() + }) + .collect::>(); + let sum = pi.par_iter().map(|score| *score).sum::(); + let new_ranks = pi.par_iter().map(|r| *r / sum).collect::>(); + let squared_norm_2 = new_ranks + .par_iter() + .zip(&ranks) + .map(|(new, old)| (*new - *old) * (*new - *old)) + .sum::(); + if squared_norm_2 <= tolerance { + return ranks; + } else { + ranks = new_ranks; + } + } + ranks +} diff --git a/src/csr.rs b/src/csr.rs index ae3a8d450..f9f7446cb 100644 --- a/src/csr.rs +++ b/src/csr.rs @@ -344,7 +344,7 @@ where .row .get(a.index() + 1) .cloned() - .unwrap_or_else(|| self.column.len()); + .unwrap_or(self.column.len()); index..end } diff --git a/src/graph_impl/mod.rs b/src/graph_impl/mod.rs index d0be47337..a5ce807ef 100644 --- a/src/graph_impl/mod.rs +++ b/src/graph_impl/mod.rs @@ -29,6 +29,8 @@ pub type DefaultIx = u32; /// Trait for the unsigned integer type used for node and edge indices. /// +/// # Safety +/// /// Marked `unsafe` because: the trait must faithfully preserve /// and convert index values. pub unsafe trait IndexType: Copy + Default + Hash + Ord + fmt::Debug + 'static { @@ -1733,13 +1735,10 @@ where type Item = EdgeReference<'a, E, Ix>; fn next(&mut self) -> Option> { - while let Some(edge) = self.edges.next() { - if edge.node[1] == self.target_node { - return Some(edge); - } - } - - None + let target_node = self.target_node; + self.edges + .by_ref() + .find(|&edge| edge.node[1] == target_node) } fn size_hint(&self) -> (usize, Option) { let (_, upper) = self.edges.size_hint(); diff --git a/src/graphmap.rs b/src/graphmap.rs index c140205c6..9e1bdd36f 100644 --- a/src/graphmap.rs +++ b/src/graphmap.rs @@ -5,11 +5,12 @@ use indexmap::map::Keys; use indexmap::map::{Iter as IndexMapIter, IterMut as IndexMapIterMut}; use indexmap::IndexMap; use std::cmp::Ordering; +use std::collections::hash_map::RandomState; use std::collections::HashSet; use std::fmt; -use std::hash::{self, Hash}; +use std::hash::{self, BuildHasher, Hash}; +use std::iter::Copied; use std::iter::FromIterator; -use std::iter::{Cloned, DoubleEndedIterator}; use std::marker::PhantomData; use std::mem; use std::ops::{Deref, Index, IndexMut}; @@ -22,6 +23,11 @@ use crate::graph::Graph; use crate::visit; use crate::IntoWeightedEdge; +#[cfg(feature = "rayon")] +use indexmap::map::rayon::{ParIter, ParIterMut, ParKeys}; +#[cfg(feature = "rayon")] +use rayon::prelude::*; + /// A `GraphMap` with undirected edges. /// /// For example, an edge between *1* and *2* is equivalent to an edge between @@ -58,13 +64,18 @@ pub type DiGraphMap = GraphMap; /// /// Depends on crate feature `graphmap` (default). #[derive(Clone)] -pub struct GraphMap { - nodes: IndexMap>, - edges: IndexMap<(N, N), E>, +pub struct GraphMap +where + S: BuildHasher, +{ + nodes: IndexMap, S>, + edges: IndexMap<(N, N), E, S>, ty: PhantomData, } -impl fmt::Debug for GraphMap { +impl fmt::Debug + for GraphMap +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.nodes.fmt(f) } @@ -117,33 +128,35 @@ impl PartialEq for CompactDirection { } #[cfg(feature = "serde-1")] -impl serde::Serialize for GraphMap +impl serde::Serialize for GraphMap where Ty: EdgeType, N: NodeTrait + serde::Serialize, E: serde::Serialize, - GraphMap: Clone, + S: BuildHasher, + Self: Clone, { /// Serializes the given `GraphMap` into the same format as the standard /// `Graph`. Needs feature `serde-1`. /// /// Note: the graph has to be `Clone` for this to work. - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: Ser) -> Result where - S: serde::Serializer, + Ser: serde::Serializer, { - let cloned_graph: GraphMap = GraphMap::clone(self); + let cloned_graph: GraphMap = GraphMap::clone(self); let equivalent_graph: Graph = cloned_graph.into_graph(); equivalent_graph.serialize(serializer) } } #[cfg(feature = "serde-1")] -impl<'de, N, E, Ty> serde::Deserialize<'de> for GraphMap +impl<'de, N, E, Ty, S> serde::Deserialize<'de> for GraphMap where Ty: EdgeType, N: NodeTrait + serde::Deserialize<'de>, E: Clone + serde::Deserialize<'de>, + S: BuildHasher + Default, { /// Deserializes into a new `GraphMap` from the same format as the standard /// `Graph`. Needs feature `serde-1`. @@ -161,21 +174,40 @@ where } } -impl GraphMap +impl GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { /// Create a new `GraphMap` - pub fn new() -> Self { + pub fn new() -> Self + where + S: Default, + { Self::default() } /// Create a new `GraphMap` with estimated capacity. - pub fn with_capacity(nodes: usize, edges: usize) -> Self { - GraphMap { - nodes: IndexMap::with_capacity(nodes), - edges: IndexMap::with_capacity(edges), + pub fn with_capacity(nodes: usize, edges: usize) -> Self + where + S: Default, + { + Self { + nodes: IndexMap::with_capacity_and_hasher(nodes, S::default()), + edges: IndexMap::with_capacity_and_hasher(edges, S::default()), + ty: PhantomData, + } + } + + /// Create a new `GraphMap` with estimated capacity, and specified hasher. + pub fn with_capacity_and_hasher(nodes: usize, edges: usize, hasher: S) -> Self + where + S: Clone, + { + Self { + nodes: IndexMap::with_capacity_and_hasher(nodes, hasher.clone()), + edges: IndexMap::with_capacity_and_hasher(edges, hasher), ty: PhantomData, } } @@ -223,6 +255,7 @@ where where I: IntoIterator, I::Item: IntoWeightedEdge, + S: Default, { Self::from_iter(iterable) } @@ -245,7 +278,7 @@ where /// Add node `n` to the graph. pub fn add_node(&mut self, n: N) -> N { - self.nodes.entry(n).or_insert(Vec::new()); + self.nodes.entry(n).or_default(); n } @@ -367,7 +400,7 @@ where } else { exist1 }; - let weight = self.edges.remove(&Self::edge_key(a, b)); + let weight = self.edges.swap_remove(&Self::edge_key(a, b)); debug_assert!(exist1 == exist2 && exist1 == weight.is_some()); weight } @@ -380,9 +413,22 @@ where /// Return an iterator over the nodes of the graph. /// /// Iterator element type is `N`. - pub fn nodes(&self) -> Nodes { + pub fn nodes(&self) -> Nodes<'_, N> { Nodes { - iter: self.nodes.keys().cloned(), + iter: self.nodes.keys().copied(), + } + } + + /// Return a parallel iterator over the nodes of the graph. + /// + /// Iterator element type is `N`. + #[cfg(feature = "rayon")] + pub fn par_nodes(&self) -> ParNodes<'_, N> + where + N: Send + Sync, + { + ParNodes { + iter: self.nodes.par_keys(), } } @@ -433,7 +479,7 @@ where /// /// Produces an empty iterator if the node doesn't exist.
/// Iterator element type is `(N, N, &E)`. - pub fn edges(&self, a: N) -> Edges { + pub fn edges(&self, a: N) -> Edges { Edges { from: a, iter: self.neighbors(a), @@ -453,7 +499,7 @@ where /// /// Produces an empty iterator if the node doesn't exist.
/// Iterator element type is `(N, N, &E)`. - pub fn edges_directed(&self, a: N, dir: Direction) -> EdgesDirected { + pub fn edges_directed(&self, a: N, dir: Direction) -> EdgesDirected { EdgesDirected { from: a, iter: self.neighbors_directed(a, dir), @@ -495,6 +541,38 @@ where } } + /// Return a parallel iterator over all edges of the graph with their weight in arbitrary + /// order. + /// + /// Iterator element type is `(N, N, &E)` + #[cfg(feature = "rayon")] + pub fn par_all_edges(&self) -> ParAllEdges + where + N: Send + Sync, + E: Sync, + { + ParAllEdges { + inner: self.edges.par_iter(), + ty: PhantomData, + } + } + + /// Return a parallel iterator over all edges of the graph in arbitrary order, with a mutable + /// reference to their weight. + /// + /// Iterator element type is `(N, N, &mut E)` + #[cfg(feature = "rayon")] + pub fn par_all_edges_mut(&mut self) -> ParAllEdgesMut + where + N: Send + Sync, + E: Send, + { + ParAllEdgesMut { + inner: self.edges.par_iter_mut(), + ty: PhantomData, + } + } + /// Return a `Graph` that corresponds to this `GraphMap`. /// /// 1. Note that node and edge indices in the `Graph` have nothing in common @@ -516,8 +594,8 @@ where gr.add_node(node); } for ((a, b), edge_weight) in self.edges { - let (ai, _, _) = self.nodes.get_full(&a).unwrap(); - let (bi, _, _) = self.nodes.get_full(&b).unwrap(); + let ai = self.nodes.get_index_of(&a).unwrap(); + let bi = self.nodes.get_index_of(&b).unwrap(); gr.add_edge(node_index(ai), node_index(bi), edge_weight); } gr @@ -534,8 +612,9 @@ where where Ix: crate::graph::IndexType, E: Clone, + S: Default, { - let mut new_graph: GraphMap = + let mut new_graph: GraphMap = GraphMap::with_capacity(graph.node_count(), graph.edge_count()); for node in graph.raw_nodes() { @@ -556,11 +635,12 @@ where } /// Create a new `GraphMap` from an iterable of edges. -impl FromIterator for GraphMap +impl FromIterator for GraphMap where Item: IntoWeightedEdge, N: NodeTrait, Ty: EdgeType, + S: BuildHasher + Default, { fn from_iter(iterable: I) -> Self where @@ -577,11 +657,12 @@ where /// Extend the graph from an iterable of edges. /// /// Nodes are inserted automatically to match the edges. -impl Extend for GraphMap +impl Extend for GraphMap where Item: IntoWeightedEdge, N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { fn extend(&mut self, iterable: I) where @@ -603,7 +684,7 @@ iterator_wrap! { #[derive(Debug, Clone)] struct Nodes <'a, N> where { N: 'a + NodeTrait } item: N, - iter: Cloned>>, + iter: Copied>>, } #[derive(Debug, Clone)] @@ -687,21 +768,23 @@ where } #[derive(Debug, Clone)] -pub struct Edges<'a, N, E: 'a, Ty> +pub struct Edges<'a, N, E: 'a, Ty, S = RandomState> where N: 'a + NodeTrait, Ty: EdgeType, + S: BuildHasher, { from: N, - edges: &'a IndexMap<(N, N), E>, + edges: &'a IndexMap<(N, N), E, S>, iter: Neighbors<'a, N, Ty>, } -impl<'a, N, E, Ty> Iterator for Edges<'a, N, E, Ty> +impl<'a, N, E, Ty, S> Iterator for Edges<'a, N, E, Ty, S> where N: 'a + NodeTrait, E: 'a, Ty: EdgeType, + S: BuildHasher, { type Item = (N, N, &'a E); fn next(&mut self) -> Option { @@ -719,22 +802,24 @@ where } #[derive(Debug, Clone)] -pub struct EdgesDirected<'a, N, E: 'a, Ty> +pub struct EdgesDirected<'a, N, E: 'a, Ty, S = RandomState> where N: 'a + NodeTrait, Ty: EdgeType, + S: BuildHasher, { from: N, dir: Direction, - edges: &'a IndexMap<(N, N), E>, + edges: &'a IndexMap<(N, N), E, S>, iter: NeighborsDirected<'a, N, Ty>, } -impl<'a, N, E, Ty> Iterator for EdgesDirected<'a, N, E, Ty> +impl<'a, N, E, Ty, S> Iterator for EdgesDirected<'a, N, E, Ty, S> where N: 'a + NodeTrait, E: 'a, Ty: EdgeType, + S: BuildHasher, { type Item = (N, N, &'a E); fn next(&mut self) -> Option { @@ -864,10 +949,11 @@ where } /// Index `GraphMap` by node pairs to access edge weights. -impl Index<(N, N)> for GraphMap +impl Index<(N, N)> for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { type Output = E; fn index(&self, index: (N, N)) -> &E { @@ -878,10 +964,11 @@ where } /// Index `GraphMap` by node pairs to access edge weights. -impl IndexMut<(N, N)> for GraphMap +impl IndexMut<(N, N)> for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { fn index_mut(&mut self, index: (N, N)) -> &mut E { let index = Self::edge_key(index.0, index.1); @@ -891,10 +978,11 @@ where } /// Create a new empty `GraphMap`. -impl Default for GraphMap +impl Default for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher + Default, { fn default() -> Self { GraphMap::with_capacity(0, 0) @@ -1014,27 +1102,30 @@ where } } -impl visit::GraphBase for GraphMap +impl visit::GraphBase for GraphMap where N: Copy + PartialEq, + S: BuildHasher, { type NodeId = N; type EdgeId = (N, N); } -impl visit::Data for GraphMap +impl visit::Data for GraphMap where N: Copy + PartialEq, Ty: EdgeType, + S: BuildHasher, { type NodeWeight = N; type EdgeWeight = E; } -impl visit::Visitable for GraphMap +impl visit::Visitable for GraphMap where N: Copy + Ord + Hash, Ty: EdgeType, + S: BuildHasher, { type Map = HashSet; fn visit_map(&self) -> HashSet { @@ -1045,18 +1136,20 @@ where } } -impl visit::GraphProp for GraphMap +impl visit::GraphProp for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { type EdgeType = Ty; } -impl<'a, N, E, Ty> visit::IntoNodeReferences for &'a GraphMap +impl<'a, N, E, Ty, S> visit::IntoNodeReferences for &'a GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { type NodeRef = (N, &'a N); type NodeReferences = NodeReferences<'a, N, E, Ty>; @@ -1069,10 +1162,11 @@ where } } -impl<'a, N, E: 'a, Ty> visit::IntoNodeIdentifiers for &'a GraphMap +impl<'a, N, E: 'a, Ty, S> visit::IntoNodeIdentifiers for &'a GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { type NodeIdentifiers = NodeIdentifiers<'a, N, E, Ty>; @@ -1085,27 +1179,28 @@ where } } -impl visit::NodeCount for GraphMap +impl visit::NodeCount for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { fn node_count(&self) -> usize { (*self).node_count() } } -impl visit::NodeIndexable for GraphMap +impl visit::NodeIndexable for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { fn node_bound(&self) -> usize { self.node_count() } fn to_index(&self, ix: Self::NodeId) -> usize { - let (i, _, _) = self.nodes.get_full(&ix).unwrap(); - i + self.nodes.get_index_of(&ix).unwrap() } fn from_index(&self, ix: usize) -> Self::NodeId { assert!( @@ -1118,17 +1213,19 @@ where } } -impl visit::NodeCompactIndexable for GraphMap +impl visit::NodeCompactIndexable for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { } -impl<'a, N: 'a, E, Ty> visit::IntoNeighbors for &'a GraphMap +impl<'a, N: 'a, E, Ty, S> visit::IntoNeighbors for &'a GraphMap where N: Copy + Ord + Hash, Ty: EdgeType, + S: BuildHasher, { type Neighbors = Neighbors<'a, N, Ty>; fn neighbors(self, n: Self::NodeId) -> Self::Neighbors { @@ -1136,10 +1233,11 @@ where } } -impl<'a, N: 'a, E, Ty> visit::IntoNeighborsDirected for &'a GraphMap +impl<'a, N: 'a, E, Ty, S> visit::IntoNeighborsDirected for &'a GraphMap where N: Copy + Ord + Hash, Ty: EdgeType, + S: BuildHasher, { type NeighborsDirected = NeighborsDirected<'a, N, Ty>; fn neighbors_directed(self, n: N, dir: Direction) -> Self::NeighborsDirected { @@ -1147,18 +1245,18 @@ where } } -impl visit::EdgeIndexable for GraphMap +impl visit::EdgeIndexable for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { fn edge_bound(&self) -> usize { self.edge_count() } fn to_index(&self, ix: Self::EdgeId) -> usize { - let (i, _, _) = self.edges.get_full(&ix).unwrap(); - i + self.edges.get_index_of(&ix).unwrap() } fn from_index(&self, ix: usize) -> Self::EdgeId { @@ -1172,32 +1270,35 @@ where } } -impl<'a, N: 'a, E: 'a, Ty> visit::IntoEdges for &'a GraphMap +impl<'a, N: 'a, E: 'a, Ty, S> visit::IntoEdges for &'a GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { - type Edges = Edges<'a, N, E, Ty>; + type Edges = Edges<'a, N, E, Ty, S>; fn edges(self, a: Self::NodeId) -> Self::Edges { self.edges(a) } } -impl<'a, N: 'a, E: 'a, Ty> visit::IntoEdgesDirected for &'a GraphMap +impl<'a, N: 'a, E: 'a, Ty, S> visit::IntoEdgesDirected for &'a GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { - type EdgesDirected = EdgesDirected<'a, N, E, Ty>; + type EdgesDirected = EdgesDirected<'a, N, E, Ty, S>; fn edges_directed(self, a: Self::NodeId, dir: Direction) -> Self::EdgesDirected { self.edges_directed(a, dir) } } -impl<'a, N: 'a, E: 'a, Ty> visit::IntoEdgeReferences for &'a GraphMap +impl<'a, N: 'a, E: 'a, Ty, S> visit::IntoEdgeReferences for &'a GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { type EdgeRef = (N, N, &'a E); type EdgeReferences = AllEdges<'a, N, E, Ty>; @@ -1206,10 +1307,11 @@ where } } -impl visit::EdgeCount for GraphMap +impl visit::EdgeCount for GraphMap where N: NodeTrait, Ty: EdgeType, + S: BuildHasher, { #[inline] fn edge_count(&self) -> usize { @@ -1218,10 +1320,11 @@ where } /// The `GraphMap` keeps an adjacency matrix internally. -impl visit::GetAdjacencyMatrix for GraphMap +impl visit::GetAdjacencyMatrix for GraphMap where N: Copy + Ord + Hash, Ty: EdgeType, + S: BuildHasher, { type AdjMatrix = (); #[inline] @@ -1231,3 +1334,171 @@ where self.contains_edge(a, b) } } + +/// A [ParallelIterator] over this graph's nodes. +#[cfg(feature = "rayon")] +pub struct ParNodes<'a, N> +where + N: NodeTrait + Send + Sync, +{ + iter: ParKeys<'a, N, Vec<(N, CompactDirection)>>, +} + +#[cfg(feature = "rayon")] +impl<'a, N> ParallelIterator for ParNodes<'a, N> +where + N: NodeTrait + Send + Sync, +{ + type Item = N; + + fn drive_unindexed(self, consumer: C) -> C::Result + where + C: rayon::iter::plumbing::UnindexedConsumer, + { + self.iter.copied().drive_unindexed(consumer) + } + + fn opt_len(&self) -> Option { + self.iter.opt_len() + } +} + +#[cfg(feature = "rayon")] +impl<'a, N> IndexedParallelIterator for ParNodes<'a, N> +where + N: NodeTrait + Send + Sync, +{ + fn drive(self, consumer: C) -> C::Result + where + C: rayon::iter::plumbing::Consumer, + { + self.iter.copied().drive(consumer) + } + + fn len(&self) -> usize { + self.iter.len() + } + + fn with_producer(self, callback: CB) -> CB::Output + where + CB: rayon::iter::plumbing::ProducerCallback, + { + self.iter.copied().with_producer(callback) + } +} + +/// A [ParallelIterator] over this graph's edges. +#[cfg(feature = "rayon")] +pub struct ParAllEdges<'a, N, E, Ty> +where + N: NodeTrait + Send + Sync, + E: Sync, +{ + inner: ParIter<'a, (N, N), E>, + ty: PhantomData, +} + +#[cfg(feature = "rayon")] +impl<'a, N, E, Ty> ParallelIterator for ParAllEdges<'a, N, E, Ty> +where + N: NodeTrait + Send + Sync, + E: Sync, +{ + type Item = (N, N, &'a E); + + fn drive_unindexed(self, c: C) -> C::Result + where + C: rayon::iter::plumbing::UnindexedConsumer, + { + self.inner.map(|(&(a, b), v)| (a, b, v)).drive_unindexed(c) + } + + fn opt_len(&self) -> Option { + self.inner.opt_len() + } +} + +#[cfg(feature = "rayon")] +impl<'a, N, E, Ty> IndexedParallelIterator for ParAllEdges<'a, N, E, Ty> +where + N: NodeTrait + Send + Sync, + E: Sync, +{ + fn drive(self, consumer: C) -> C::Result + where + C: rayon::iter::plumbing::Consumer, + { + self.inner.map(|(&(a, b), v)| (a, b, v)).drive(consumer) + } + + fn len(&self) -> usize { + self.inner.len() + } + + fn with_producer(self, callback: CB) -> CB::Output + where + CB: rayon::iter::plumbing::ProducerCallback, + { + self.inner + .map(|(&(a, b), v)| (a, b, v)) + .with_producer(callback) + } +} + +/// A [ParallelIterator] over this graph's edges by mutable reference. +#[cfg(feature = "rayon")] +pub struct ParAllEdgesMut<'a, N, E: 'a, Ty> +where + N: NodeTrait + Send + Sync, + E: Send, +{ + inner: ParIterMut<'a, (N, N), E>, + ty: PhantomData, +} + +#[cfg(feature = "rayon")] +impl<'a, N, E, Ty> ParallelIterator for ParAllEdgesMut<'a, N, E, Ty> +where + N: NodeTrait + Send + Sync, + E: Send, +{ + type Item = (N, N, &'a mut E); + + fn drive_unindexed(self, c: C) -> C::Result + where + C: rayon::iter::plumbing::UnindexedConsumer, + { + self.inner.map(|(&(a, b), v)| (a, b, v)).drive_unindexed(c) + } + + fn opt_len(&self) -> Option { + self.inner.opt_len() + } +} + +#[cfg(feature = "rayon")] +impl<'a, N, E, Ty> IndexedParallelIterator for ParAllEdgesMut<'a, N, E, Ty> +where + N: NodeTrait + Send + Sync, + E: Send, +{ + fn drive(self, consumer: C) -> C::Result + where + C: rayon::iter::plumbing::Consumer, + { + self.inner.map(|(&(a, b), v)| (a, b, v)).drive(consumer) + } + + fn len(&self) -> usize { + self.inner.len() + } + + fn with_producer(self, callback: CB) -> CB::Output + where + CB: rayon::iter::plumbing::ProducerCallback, + { + self.inner + .map(|(&(a, b), v)| (a, b, v)) + .with_producer(callback) + } +} diff --git a/src/iter_format.rs b/src/iter_format.rs index f6ffd6aff..841c08655 100644 --- a/src/iter_format.rs +++ b/src/iter_format.rs @@ -6,7 +6,7 @@ use std::fmt; /// Format the iterator like a map pub struct DebugMap(pub F); -impl<'a, F, I, K, V> fmt::Debug for DebugMap +impl fmt::Debug for DebugMap where F: Fn() -> I, I: IntoIterator, diff --git a/src/lib.rs b/src/lib.rs index e7be95e90..c34c4eac8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,20 +170,9 @@ pub mod graph { #[cfg(feature = "stable_graph")] pub use crate::graph_impl::stable_graph; -macro_rules! copyclone { - ($name:ident) => { - impl Clone for $name { - #[inline] - fn clone(&self) -> Self { - *self - } - } - }; -} - // Index into the NodeIndex and EdgeIndex arrays /// Edge direction. -#[derive(Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] #[repr(usize)] pub enum Direction { /// An `Outgoing` edge is an outward edge *from* the current node. @@ -192,8 +181,6 @@ pub enum Direction { Incoming = 1, } -copyclone!(Direction); - impl Direction { /// Return the opposite `Direction`. #[inline] @@ -215,14 +202,12 @@ impl Direction { pub use crate::Direction as EdgeDirection; /// Marker type for a directed graph. -#[derive(Copy, Debug)] +#[derive(Clone, Copy, Debug)] pub enum Directed {} -copyclone!(Directed); /// Marker type for an undirected graph. -#[derive(Copy, Debug)] +#[derive(Clone, Copy, Debug)] pub enum Undirected {} -copyclone!(Undirected); /// A graph's edge type determines whether it has directed edges or not. pub trait EdgeType { diff --git a/src/visit/traversal.rs b/src/visit/traversal.rs index 88782fa24..4840843b9 100644 --- a/src/visit/traversal.rs +++ b/src/visit/traversal.rs @@ -344,6 +344,23 @@ where topo } + /// Create a new `Topo` with initial nodes. + /// + /// Nodes with incoming edges are ignored. + pub fn with_initials(graph: G, initials: I) -> Self + where + G: IntoNeighborsDirected + Visitable, + I: IntoIterator, + { + Topo { + tovisit: initials + .into_iter() + .filter(|&n| graph.neighbors_directed(n, Incoming).next().is_none()) + .collect(), + ordered: graph.visit_map(), + } + } + fn extend_with_initials(&mut self, g: G) where G: IntoNodeIdentifiers + IntoNeighborsDirected, diff --git a/tests/floyd_warshall.rs b/tests/floyd_warshall.rs index 6e6ab68a2..117d9b488 100644 --- a/tests/floyd_warshall.rs +++ b/tests/floyd_warshall.rs @@ -100,7 +100,7 @@ fn floyd_warshall_uniform_weight() { .iter() .cloned() .collect(); - let res = floyd_warshall(&graph, |_| 1 as i32).unwrap(); + let res = floyd_warshall(&graph, |_| 1_i32).unwrap(); let nodes = [a, b, c, d, e, f, g, h]; for node1 in &nodes { diff --git a/tests/ford_fulkerson.rs b/tests/ford_fulkerson.rs new file mode 100644 index 000000000..0b7881d2e --- /dev/null +++ b/tests/ford_fulkerson.rs @@ -0,0 +1,121 @@ +use petgraph::algo::ford_fulkerson; +use petgraph::prelude::Graph; + +#[test] +fn test_ford_fulkerson() { + // Example from https://downey.io/blog/max-flow-ford-fulkerson-algorithm-explanation/ + let mut graph = Graph::::new(); + let source = graph.add_node(0); + let _ = graph.add_node(1); + let _ = graph.add_node(2); + let destination = graph.add_node(3); + graph.extend_with_edges(&[(0, 1, 3), (0, 2, 2), (1, 2, 5), (1, 3, 2), (2, 3, 3)]); + let (max_flow, _) = ford_fulkerson(&graph, source, destination); + assert_eq!(5, max_flow); + + // Example from https://brilliant.org/wiki/ford-fulkerson-algorithm/ + let mut graph = Graph::::new(); + let source = graph.add_node(0); + let _ = graph.add_node(1); + let _ = graph.add_node(2); + let _ = graph.add_node(3); + let _ = graph.add_node(4); + let destination = graph.add_node(5); + graph.extend_with_edges(&[ + (0, 1, 4.), + (0, 2, 3.), + (1, 3, 4.), + (2, 4, 6.), + (3, 2, 3.), + (3, 5, 2.), + (4, 5, 6.), + ]); + let (max_flow, _) = ford_fulkerson(&graph, source, destination); + assert_eq!(7.0, max_flow); + + // Example from https://cp-algorithms.com/graph/edmonds_karp.html + let mut graph = Graph::::new(); + let source = graph.add_node(0); + let _ = graph.add_node(1); + let _ = graph.add_node(2); + let _ = graph.add_node(3); + let _ = graph.add_node(4); + let destination = graph.add_node(5); + graph.extend_with_edges(&[ + (0, 1, 7.), + (0, 2, 4.), + (1, 3, 5.), + (1, 4, 3.), + (2, 1, 3.), + (2, 4, 2.), + (3, 5, 8.), + (4, 3, 3.), + (4, 5, 5.), + ]); + let (max_flow, _) = ford_fulkerson(&graph, source, destination); + assert_eq!(10.0, max_flow); + + // Example from https://www.programiz.com/dsa/ford-fulkerson-algorithm (corrected: result not 6 but 5) + let mut graph = Graph::::new(); + let source = graph.add_node(0); + let _ = graph.add_node(1); + let _ = graph.add_node(2); + let _ = graph.add_node(3); + let _ = graph.add_node(4); + let destination = graph.add_node(5); + graph.extend_with_edges(&[ + (0, 1, 8.), + (0, 2, 3.), + (1, 3, 9.), + (2, 3, 7.), + (2, 4, 4.), + (3, 5, 2.), + (4, 5, 5.), + ]); + let (max_flow, _) = ford_fulkerson(&graph, source, destination); + assert_eq!(5.0, max_flow); + + let mut graph = Graph::::new(); + let source = graph.add_node(0); + let _ = graph.add_node(1); + let _ = graph.add_node(2); + let _ = graph.add_node(3); + let _ = graph.add_node(4); + let destination = graph.add_node(5); + graph.extend_with_edges(&[ + (0, 1, 16), + (0, 2, 13), + (1, 2, 10), + (1, 3, 12), + (2, 1, 4), + (2, 4, 14), + (3, 2, 9), + (3, 5, 20), + (4, 3, 7), + (4, 5, 4), + ]); + let (max_flow, _) = ford_fulkerson(&graph, source, destination); + assert_eq!(23, max_flow); + + // Example taken from https://medium.com/@jithmisha/solving-the-maximum-flow-problem-with-ford-fulkerson-method-3fccc2883dc7 + let mut graph = Graph::::new(); + let source = graph.add_node(0); + let _ = graph.add_node(1); + let _ = graph.add_node(2); + let _ = graph.add_node(3); + let _ = graph.add_node(4); + let destination = graph.add_node(5); + graph.extend_with_edges(&[ + (0, 1, 10), + (0, 2, 10), + (1, 2, 2), + (1, 3, 4), + (1, 4, 8), + (2, 4, 9), + (3, 5, 10), + (4, 3, 6), + (4, 5, 10), + ]); + let (max_flow, _) = ford_fulkerson(&graph, source, destination); + assert_eq!(19, max_flow); +} diff --git a/tests/graph.rs b/tests/graph.rs index 91a9a43ab..de943b47e 100644 --- a/tests/graph.rs +++ b/tests/graph.rs @@ -10,7 +10,7 @@ use petgraph as pg; use petgraph::algo::{ dominators, has_path_connecting, is_bipartite_undirected, is_cyclic_undirected, - is_isomorphic_matching, min_spanning_tree, + is_isomorphic_matching, }; use petgraph::graph::node_index as n; @@ -165,64 +165,6 @@ fn bfs() { assert_eq!(bfs.next(&gr), None); } -#[test] -fn mst() { - use petgraph::data::FromElements; - - let mut gr = Graph::<_, _>::new(); - let a = gr.add_node("A"); - let b = gr.add_node("B"); - let c = gr.add_node("C"); - let d = gr.add_node("D"); - let e = gr.add_node("E"); - let f = gr.add_node("F"); - let g = gr.add_node("G"); - gr.add_edge(a, b, 7.); - gr.add_edge(a, d, 5.); - gr.add_edge(d, b, 9.); - gr.add_edge(b, c, 8.); - gr.add_edge(b, e, 7.); - gr.add_edge(c, e, 5.); - gr.add_edge(d, e, 15.); - gr.add_edge(d, f, 6.); - gr.add_edge(f, e, 8.); - gr.add_edge(f, g, 11.); - gr.add_edge(e, g, 9.); - - // add a disjoint part - let h = gr.add_node("H"); - let i = gr.add_node("I"); - let j = gr.add_node("J"); - gr.add_edge(h, i, 1.); - gr.add_edge(h, j, 3.); - gr.add_edge(i, j, 1.); - - println!("{}", Dot::new(&gr)); - - let mst = UnGraph::from_elements(min_spanning_tree(&gr)); - - println!("{}", Dot::new(&mst)); - println!("{:?}", Dot::new(&mst)); - println!("MST is:\n{:#?}", mst); - assert!(mst.node_count() == gr.node_count()); - // |E| = |N| - 2 because there are two disconnected components. - assert!(mst.edge_count() == gr.node_count() - 2); - - // check the exact edges are there - assert!(mst.find_edge(a, b).is_some()); - assert!(mst.find_edge(a, d).is_some()); - assert!(mst.find_edge(b, e).is_some()); - assert!(mst.find_edge(e, c).is_some()); - assert!(mst.find_edge(e, g).is_some()); - assert!(mst.find_edge(d, f).is_some()); - - assert!(mst.find_edge(h, i).is_some()); - assert!(mst.find_edge(i, j).is_some()); - - assert!(mst.find_edge(d, b).is_none()); - assert!(mst.find_edge(b, c).is_none()); -} - #[test] fn selfloop() { let mut gr = Graph::new(); @@ -1536,6 +1478,30 @@ fn toposort_generic() { println!("{:?}", gr); assert_is_topo_order(&gr, &order); + { + order.clear(); + let init_nodes = gr.node_identifiers().filter(|n| { + gr.neighbors_directed(*n, Direction::Incoming) + .next() + .is_none() + }); + let mut topo = Topo::with_initials(&gr, init_nodes); + while let Some(nx) = topo.next(&gr) { + order.push(nx); + } + assert_is_topo_order(&gr, &order); + } + + { + // test `with_initials` API using nodes with incoming edges + order.clear(); + let mut topo = Topo::with_initials(&gr, gr.node_identifiers()); + while let Some(nx) = topo.next(&gr) { + order.push(nx); + } + assert_is_topo_order(&gr, &order); + } + { order.clear(); let mut topo = Topo::new(&gr); @@ -1800,7 +1766,7 @@ where G::NodeId: PartialEq, { // self loops count twice - let original_node = node.clone(); + let original_node = node; let mut degree = 0; for v in g.neighbors(node) { degree += if v == original_node { 2 } else { 1 }; @@ -2175,7 +2141,7 @@ fn filtered_post_order() { Graph::from_edges(&[(0, 2), (1, 2), (0, 3), (1, 4), (2, 4), (4, 5), (3, 5)]); // map reachable nodes let mut dfs = Dfs::new(&gr, n(0)); - while let Some(_) = dfs.next(&gr) {} + while dfs.next(&gr).is_some() {} let map = dfs.discovered; gr.add_edge(n(0), n(1), ()); @@ -2293,7 +2259,7 @@ fn test_edge_filtered() { assert_eq!(connected_components(&positive_edges), 2); let mut dfs = DfsPostOrder::new(&positive_edges, n(0)); - while let Some(_) = dfs.next(&positive_edges) {} + while dfs.next(&positive_edges).is_some() {} let n = n::; for node in &[n(0), n(1), n(2)] { diff --git a/tests/graphmap.rs b/tests/graphmap.rs index cc023258a..2b15a88d4 100644 --- a/tests/graphmap.rs +++ b/tests/graphmap.rs @@ -168,7 +168,7 @@ fn dfs() { { let mut cnt = 0; let mut dfs = Dfs::new(&gr, h); - while let Some(_) = dfs.next(&gr) { + while dfs.next(&gr).is_some() { cnt += 1; } assert_eq!(cnt, 4); @@ -176,7 +176,7 @@ fn dfs() { { let mut cnt = 0; let mut dfs = Dfs::new(&gr, z); - while let Some(_) = dfs.next(&gr) { + while dfs.next(&gr).is_some() { cnt += 1; } assert_eq!(cnt, 1); @@ -250,10 +250,10 @@ fn graphmap_directed() { // Add reverse edges -- ok! assert!(gr.add_edge(e, d, ()).is_none()); // duplicate edge - no - assert!(!gr.add_edge(a, b, ()).is_none()); + assert!(gr.add_edge(a, b, ()).is_some()); // duplicate self loop - no - assert!(!gr.add_edge(b, b, ()).is_none()); + assert!(gr.add_edge(b, b, ()).is_some()); println!("{:#?}", gr); } @@ -398,3 +398,46 @@ fn self_loops_can_be_removed() { assert_eq!(graph.neighbors_directed((), Outgoing).next(), None); assert_eq!(graph.neighbors_directed((), Incoming).next(), None); } + +#[test] +#[cfg(feature = "rayon")] +fn test_parallel_iterator() { + use rayon::prelude::*; + let mut gr: DiGraphMap = DiGraphMap::new(); + + for i in 0..1000 { + gr.add_node(i); + } + + let serial_sum: u32 = gr.nodes().sum(); + let parallel_sum: u32 = gr.par_nodes().sum(); + assert_eq!(serial_sum, parallel_sum); + + gr.par_nodes() + .enumerate() + .for_each(|(i, n)| assert_eq!(i as u32, n)); + + for i in 0..1000 { + gr.add_edge(i / 2, i, i + i / 2); + } + + let serial_sum: u32 = gr.all_edges().map(|(.., &e)| e).sum(); + let parallel_sum: u32 = gr.par_all_edges().map(|(.., &e)| e).sum(); + assert_eq!(serial_sum, parallel_sum); + + gr.par_all_edges_mut().for_each(|(n1, n2, e)| *e -= n1 + n2); + gr.all_edges().for_each(|(.., &e)| assert_eq!(e, 0)); +} + +#[test] +fn test_alternative_hasher() { + let mut gr: GraphMap<&str, u32, Directed, fxhash::FxBuildHasher> = GraphMap::new(); + gr.add_node("abc"); + gr.add_node("def"); + gr.add_node("ghi"); + + gr.add_edge("abc", "def", 1); + + assert!(gr.contains_edge("abc", "def")); + assert!(!gr.contains_edge("abc", "ghi")); +} diff --git a/tests/min_spanning_tree.rs b/tests/min_spanning_tree.rs new file mode 100644 index 000000000..de2cf2681 --- /dev/null +++ b/tests/min_spanning_tree.rs @@ -0,0 +1,59 @@ +use petgraph::{algo::min_spanning_tree, dot::Dot, graph::UnGraph, Graph}; + +#[test] +fn mst() { + use petgraph::data::FromElements; + + let mut gr = Graph::<_, _>::new(); + let a = gr.add_node("A"); + let b = gr.add_node("B"); + let c = gr.add_node("C"); + let d = gr.add_node("D"); + let e = gr.add_node("E"); + let f = gr.add_node("F"); + let g = gr.add_node("G"); + gr.add_edge(a, b, 7.); + gr.add_edge(a, d, 5.); + gr.add_edge(d, b, 9.); + gr.add_edge(b, c, 8.); + gr.add_edge(b, e, 7.); + gr.add_edge(c, e, 5.); + gr.add_edge(d, e, 15.); + gr.add_edge(d, f, 6.); + gr.add_edge(f, e, 8.); + gr.add_edge(f, g, 11.); + gr.add_edge(e, g, 9.); + + // add a disjoint part + let h = gr.add_node("H"); + let i = gr.add_node("I"); + let j = gr.add_node("J"); + gr.add_edge(h, i, 1.); + gr.add_edge(h, j, 3.); + gr.add_edge(i, j, 1.); + + println!("{}", Dot::new(&gr)); + + let mst = UnGraph::from_elements(min_spanning_tree(&gr)); + + println!("{}", Dot::new(&mst)); + println!("{:?}", Dot::new(&mst)); + println!("MST is:\n{:#?}", mst); + assert!(mst.node_count() == gr.node_count()); + // |E| = |N| - 2 because there are two disconnected components. + assert!(mst.edge_count() == gr.node_count() - 2); + + // check the exact edges are there + assert!(mst.find_edge(a, b).is_some()); + assert!(mst.find_edge(a, d).is_some()); + assert!(mst.find_edge(b, e).is_some()); + assert!(mst.find_edge(e, c).is_some()); + assert!(mst.find_edge(e, g).is_some()); + assert!(mst.find_edge(d, f).is_some()); + + assert!(mst.find_edge(h, i).is_some()); + assert!(mst.find_edge(i, j).is_some()); + + assert!(mst.find_edge(d, b).is_none()); + assert!(mst.find_edge(b, c).is_none()); +} diff --git a/tests/page_rank.rs b/tests/page_rank.rs new file mode 100644 index 000000000..224415eb0 --- /dev/null +++ b/tests/page_rank.rs @@ -0,0 +1,83 @@ +use petgraph::{algo::page_rank, Graph}; + +#[cfg(feature = "rayon")] +use petgraph::algo::page_rank::parallel_page_rank; + +fn graph_example() -> Graph { + // Taken and adapted from https://github.com/neo4j-labs/graph?tab=readme-ov-file#how-to-run-algorithms + let mut graph = Graph::<_, f32>::new(); + graph.add_node("A".to_owned()); + graph.add_node("B".to_owned()); + graph.add_node("C".to_owned()); + graph.add_node("D".to_owned()); + graph.add_node("E".to_owned()); + graph.add_node("F".to_owned()); + graph.add_node("G".to_owned()); + graph.add_node("H".to_owned()); + graph.add_node("I".to_owned()); + graph.add_node("J".to_owned()); + graph.add_node("K".to_owned()); + graph.add_node("L".to_owned()); + graph.add_node("M".to_owned()); + graph.extend_with_edges(&[ + (1, 2), // B->C + (2, 1), // C->B + (4, 0), // D->A + (4, 1), // D->B + (5, 4), // E->D + (5, 1), // E->B + (5, 6), // E->F + (6, 1), // F->B + (6, 5), // F->E + (7, 1), // G->B + (7, 5), // F->E + (8, 1), // G->B + (8, 5), // G->E + (9, 1), // H->B + (9, 5), // H->E + (10, 1), // I->B + (10, 5), // I->E + (11, 5), // J->B + (12, 5), // K->B + ]); + graph +} + +fn expected_ranks() -> Vec { + vec![ + 0.029228685, + 0.38176042, + 0.3410649, + 0.014170233, + 0.035662483, + 0.077429585, + 0.035662483, + 0.014170233, + 0.014170233, + 0.014170233, + 0.014170233, + 0.014170233, + 0.014170233, + ] +} + +#[test] +fn test_page_rank() { + let graph = graph_example(); + let output_ranks = page_rank(&graph, 0.85_f32, 100); + assert_eq!(expected_ranks(), output_ranks); +} + +#[test] +#[cfg(feature = "rayon")] + +fn test_par_page_rank() { + let graph = graph_example(); + let output_ranks = parallel_page_rank(&graph, 0.85_f32, 100, Some(1e-12)); + assert!(!expected_ranks() + .iter() + .zip(output_ranks) + .any(|(expected, computed)| ((expected - computed).abs() > 1e-6) + || computed.is_nan() + || expected.is_nan())); +} diff --git a/tests/quickcheck.rs b/tests/quickcheck.rs index aedc49873..3b9ae6919 100644 --- a/tests/quickcheck.rs +++ b/tests/quickcheck.rs @@ -23,10 +23,10 @@ use quickcheck::{Arbitrary, Gen}; use rand::Rng; use petgraph::algo::{ - bellman_ford, condensation, dijkstra, find_negative_cycle, floyd_warshall, + bellman_ford, condensation, dijkstra, find_negative_cycle, floyd_warshall, ford_fulkerson, greedy_feedback_arc_set, greedy_matching, is_cyclic_directed, is_cyclic_undirected, is_isomorphic, is_isomorphic_matching, k_shortest_path, kosaraju_scc, maximum_matching, - min_spanning_tree, tarjan_scc, toposort, Matching, + min_spanning_tree, page_rank, tarjan_scc, toposort, Matching, }; use petgraph::data::FromElements; use petgraph::dot::{Config, Dot}; @@ -35,7 +35,7 @@ use petgraph::graphmap::NodeTrait; use petgraph::operator::complement; use petgraph::prelude::*; use petgraph::visit::{ - EdgeFiltered, EdgeRef, IntoEdgeReferences, IntoEdges, IntoNeighbors, IntoNodeIdentifiers, + EdgeFiltered, EdgeIndexable, IntoEdgeReferences, IntoEdges, IntoNeighbors, IntoNodeIdentifiers, IntoNodeReferences, NodeCount, NodeIndexable, Reversed, Topo, VisitMap, Visitable, }; use petgraph::EdgeType; @@ -727,6 +727,35 @@ fn full_topo_generic() { return false; } } + + { + order.clear(); + let init_nodes = gr.node_identifiers().filter(|n| { + gr.neighbors_directed(n.clone(), Direction::Incoming) + .next() + .is_none() + }); + let mut topo = Topo::with_initials(&gr, init_nodes); + while let Some(nx) = topo.next(&gr) { + order.push(nx); + } + if !is_topo_order(&gr, &order) { + println!("{:?}", gr); + return false; + } + } + + { + order.clear(); + let mut topo = Topo::with_initials(&gr, gr.node_identifiers()); + while let Some(nx) = topo.next(&gr) { + order.push(nx); + } + if !is_topo_order(&gr, &order) { + println!("{:?}", gr); + return false; + } + } true } quickcheck::quickcheck(prop_generic as fn(_) -> bool); @@ -1283,3 +1312,67 @@ quickcheck! { true } } + +quickcheck! { + // The ranks are probabilities, + // as such they are positive and they should sum up to 1. + fn test_page_rank_proba(gr: Graph<(), f32>) -> bool { + if gr.node_count() == 0 { + return true; + } + let tol = 1e-10; + let ranks: Vec = page_rank(&gr, 0.85_f64, 5); + let at_least_one_neg_rank = ranks.iter().any(|rank| *rank < 0.); + let not_sumup_to_one = (ranks.iter().sum::() - 1.).abs() > tol; + if at_least_one_neg_rank | not_sumup_to_one{ + return false; + } + true + } +} + +fn sum_flows( + gr: &Graph, + flows: &[F], + node: NodeIndex, + dir: Direction, +) -> F { + gr.edges_directed(node, dir) + .map(|edge| flows[EdgeIndexable::to_index(&gr, edge.id())]) + .sum::() +} + +quickcheck! { + // 1. (Capacity) + // The flows should be <= capacities + // 2. (Flow conservation) + // For every internal node (i.e a node different from the + // source node and the destination (or sink) node), the sum + // of incoming flows (i.e flows of incoming edges) is equal + // to the sum of the outgoing flows (i.e flows of outgoing edges). + // 3. (Maximum flow) + // It is equal to the sum of the destination node incoming flows and + // also the sum of the outgoing flows of the source node. + fn test_ford_fulkerson_flows(gr: Graph) -> bool { + if gr.node_count() <= 1 || gr.edge_count() == 0 { + return true; + } + let source = NodeIndex::from(0); + let destination = NodeIndex::from(gr.node_count() as u32 / 2); + let (max_flow, flows) = ford_fulkerson(&gr, source, destination); + let capacity_constraint = flows + .iter() + .enumerate() + .all(|(ix, flow)| flow <= gr.edge_weight(EdgeIndexable::from_index(&gr, ix)).unwrap()); + let flow_conservation_constraint = (0..gr.node_count()).all(|ix| { + let node = NodeIndexable::from_index(&gr, ix); + if (node != source) && (node != destination){ + sum_flows(&gr, &flows, node, Direction::Outgoing) + == sum_flows(&gr, &flows, node, Direction::Incoming) + } else {true} + }); + let max_flow_constaint = (sum_flows(&gr, &flows, source, Direction::Outgoing) == max_flow) + && (sum_flows(&gr, &flows, destination, Direction::Incoming) == max_flow); + return capacity_constraint && flow_conservation_constraint && max_flow_constaint; + } +}