| #![cfg(feature = "quickcheck")] |
| #[macro_use] |
| extern crate quickcheck; |
| extern crate petgraph; |
| extern crate rand; |
| #[macro_use] |
| extern crate defmac; |
| |
| extern crate itertools; |
| extern crate odds; |
| |
| mod utils; |
| |
| use utils::{Small, Tournament}; |
| |
| use odds::prelude::*; |
| use std::collections::HashSet; |
| use std::hash::Hash; |
| |
| use itertools::assert_equal; |
| use itertools::cloned; |
| use quickcheck::{Arbitrary, Gen}; |
| use rand::Rng; |
| |
| use petgraph::algo::{ |
| 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, page_rank, tarjan_scc, toposort, Matching, |
| }; |
| use petgraph::data::FromElements; |
| use petgraph::dot::{Config, Dot}; |
| use petgraph::graph::{edge_index, node_index, IndexType}; |
| use petgraph::graphmap::NodeTrait; |
| use petgraph::operator::complement; |
| use petgraph::prelude::*; |
| use petgraph::visit::{ |
| EdgeFiltered, EdgeIndexable, IntoEdgeReferences, IntoEdges, IntoNeighbors, IntoNodeIdentifiers, |
| IntoNodeReferences, NodeCount, NodeIndexable, Reversed, Topo, VisitMap, Visitable, |
| }; |
| use petgraph::EdgeType; |
| |
| fn mst_graph<N, E, Ty, Ix>(g: &Graph<N, E, Ty, Ix>) -> Graph<N, E, Undirected, Ix> |
| where |
| Ty: EdgeType, |
| Ix: IndexType, |
| N: Clone, |
| E: Clone + PartialOrd, |
| { |
| Graph::from_elements(min_spanning_tree(&g)) |
| } |
| |
| use std::fmt; |
| |
| quickcheck! { |
| fn mst_directed(g: Small<Graph<(), u32>>) -> bool { |
| // filter out isolated nodes |
| let no_singles = g.filter_map( |
| |nx, w| g.neighbors_undirected(nx).next().map(|_| w), |
| |_, w| Some(w)); |
| for i in no_singles.node_indices() { |
| assert!(no_singles.neighbors_undirected(i).count() > 0); |
| } |
| assert_eq!(no_singles.edge_count(), g.edge_count()); |
| let mst = mst_graph(&no_singles); |
| assert!(!is_cyclic_undirected(&mst)); |
| true |
| } |
| } |
| |
| quickcheck! { |
| fn mst_undirected(g: Graph<(), u32, Undirected>) -> bool { |
| // filter out isolated nodes |
| let no_singles = g.filter_map( |
| |nx, w| g.neighbors_undirected(nx).next().map(|_| w), |
| |_, w| Some(w)); |
| for i in no_singles.node_indices() { |
| assert!(no_singles.neighbors_undirected(i).count() > 0); |
| } |
| assert_eq!(no_singles.edge_count(), g.edge_count()); |
| let mst = mst_graph(&no_singles); |
| assert!(!is_cyclic_undirected(&mst)); |
| true |
| } |
| } |
| |
| quickcheck! { |
| fn reverse_undirected(g: Small<UnGraph<(), ()>>) -> bool { |
| let mut h = (*g).clone(); |
| h.reverse(); |
| is_isomorphic(&*g, &h) |
| } |
| } |
| |
| fn assert_graph_consistent<N, E, Ty, Ix>(g: &Graph<N, E, Ty, Ix>) |
| where |
| Ty: EdgeType, |
| Ix: IndexType, |
| { |
| assert_eq!(g.node_count(), g.node_indices().count()); |
| assert_eq!(g.edge_count(), g.edge_indices().count()); |
| for edge in g.raw_edges() { |
| assert!( |
| g.find_edge(edge.source(), edge.target()).is_some(), |
| "Edge not in graph! {:?} to {:?}", |
| edge.source(), |
| edge.target() |
| ); |
| } |
| } |
| |
| #[test] |
| fn reverse_directed() { |
| fn prop<Ty: EdgeType>(mut g: Graph<(), (), Ty>) -> bool { |
| let node_outdegrees = g |
| .node_indices() |
| .map(|i| g.neighbors_directed(i, Outgoing).count()) |
| .collect::<Vec<_>>(); |
| let node_indegrees = g |
| .node_indices() |
| .map(|i| g.neighbors_directed(i, Incoming).count()) |
| .collect::<Vec<_>>(); |
| |
| g.reverse(); |
| let new_outdegrees = g |
| .node_indices() |
| .map(|i| g.neighbors_directed(i, Outgoing).count()) |
| .collect::<Vec<_>>(); |
| let new_indegrees = g |
| .node_indices() |
| .map(|i| g.neighbors_directed(i, Incoming).count()) |
| .collect::<Vec<_>>(); |
| assert_eq!(node_outdegrees, new_indegrees); |
| assert_eq!(node_indegrees, new_outdegrees); |
| assert_graph_consistent(&g); |
| true |
| } |
| quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>) -> bool); |
| } |
| |
| #[test] |
| fn graph_retain_nodes() { |
| fn prop<Ty: EdgeType>(mut g: Graph<i32, i32, Ty>) -> bool { |
| // Remove all negative nodes, these should be randomly spread |
| let og = g.clone(); |
| let nodes = g.node_count(); |
| let num_negs = g.raw_nodes().iter().filter(|n| n.weight < 0).count(); |
| let mut removed = 0; |
| g.retain_nodes(|g, i| { |
| let keep = g[i] >= 0; |
| if !keep { |
| removed += 1; |
| } |
| keep |
| }); |
| let num_negs_post = g.raw_nodes().iter().filter(|n| n.weight < 0).count(); |
| let num_pos_post = g.raw_nodes().iter().filter(|n| n.weight >= 0).count(); |
| assert_eq!(num_negs_post, 0); |
| assert_eq!(removed, num_negs); |
| assert_eq!(num_negs + g.node_count(), nodes); |
| assert_eq!(num_pos_post, g.node_count()); |
| |
| // check against filter_map |
| let filtered = og.filter_map( |
| |_, w| if *w >= 0 { Some(*w) } else { None }, |
| |_, w| Some(*w), |
| ); |
| assert_eq!(g.node_count(), filtered.node_count()); |
| /* |
| println!("Iso of graph with nodes={}, edges={}", |
| g.node_count(), g.edge_count()); |
| */ |
| assert!(is_isomorphic_matching( |
| &filtered, |
| &g, |
| PartialEq::eq, |
| PartialEq::eq |
| )); |
| |
| true |
| } |
| quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>) -> bool); |
| quickcheck::quickcheck(prop as fn(Graph<_, _, Undirected>) -> bool); |
| } |
| |
| #[test] |
| fn graph_retain_edges() { |
| fn prop<Ty: EdgeType>(mut g: Graph<(), i32, Ty>) -> bool { |
| // Remove all negative edges, these should be randomly spread |
| let og = g.clone(); |
| let edges = g.edge_count(); |
| let num_negs = g.raw_edges().iter().filter(|n| n.weight < 0).count(); |
| let mut removed = 0; |
| g.retain_edges(|g, i| { |
| let keep = g[i] >= 0; |
| if !keep { |
| removed += 1; |
| } |
| keep |
| }); |
| let num_negs_post = g.raw_edges().iter().filter(|n| n.weight < 0).count(); |
| let num_pos_post = g.raw_edges().iter().filter(|n| n.weight >= 0).count(); |
| assert_eq!(num_negs_post, 0); |
| assert_eq!(removed, num_negs); |
| assert_eq!(num_negs + g.edge_count(), edges); |
| assert_eq!(num_pos_post, g.edge_count()); |
| if og.edge_count() < 30 { |
| // check against filter_map |
| let filtered = og.filter_map( |
| |_, w| Some(*w), |
| |_, w| if *w >= 0 { Some(*w) } else { None }, |
| ); |
| assert_eq!(g.node_count(), filtered.node_count()); |
| assert!(is_isomorphic(&filtered, &g)); |
| } |
| true |
| } |
| quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>) -> bool); |
| quickcheck::quickcheck(prop as fn(Graph<_, _, Undirected>) -> bool); |
| } |
| |
| #[test] |
| fn stable_graph_retain_edges() { |
| fn prop<Ty: EdgeType>(mut g: StableGraph<(), i32, Ty>) -> bool { |
| // Remove all negative edges, these should be randomly spread |
| let og = g.clone(); |
| let edges = g.edge_count(); |
| let num_negs = g.edge_references().filter(|n| *n.weight() < 0).count(); |
| let mut removed = 0; |
| g.retain_edges(|g, i| { |
| let keep = g[i] >= 0; |
| if !keep { |
| removed += 1; |
| } |
| keep |
| }); |
| let num_negs_post = g.edge_references().filter(|n| *n.weight() < 0).count(); |
| let num_pos_post = g.edge_references().filter(|n| *n.weight() >= 0).count(); |
| assert_eq!(num_negs_post, 0); |
| assert_eq!(removed, num_negs); |
| assert_eq!(num_negs + g.edge_count(), edges); |
| assert_eq!(num_pos_post, g.edge_count()); |
| if og.edge_count() < 30 { |
| // check against filter_map |
| let filtered = og.filter_map( |
| |_, w| Some(*w), |
| |_, w| if *w >= 0 { Some(*w) } else { None }, |
| ); |
| assert_eq!(g.node_count(), filtered.node_count()); |
| } |
| true |
| } |
| quickcheck::quickcheck(prop as fn(StableGraph<_, _, Directed>) -> bool); |
| quickcheck::quickcheck(prop as fn(StableGraph<_, _, Undirected>) -> bool); |
| } |
| |
| #[test] |
| fn isomorphism_1() { |
| // using small weights so that duplicates are likely |
| fn prop<Ty: EdgeType>(g: Small<Graph<i8, i8, Ty>>) -> bool { |
| let mut rng = rand::thread_rng(); |
| // several trials of different isomorphisms of the same graph |
| // mapping of node indices |
| let mut map = g.node_indices().collect::<Vec<_>>(); |
| let mut ng = Graph::<_, _, Ty>::with_capacity(g.node_count(), g.edge_count()); |
| for _ in 0..1 { |
| rng.shuffle(&mut map); |
| ng.clear(); |
| |
| for _ in g.node_indices() { |
| ng.add_node(0); |
| } |
| // Assign node weights |
| for i in g.node_indices() { |
| ng[map[i.index()]] = g[i]; |
| } |
| // Add edges |
| for i in g.edge_indices() { |
| let (s, t) = g.edge_endpoints(i).unwrap(); |
| ng.add_edge(map[s.index()], map[t.index()], g[i]); |
| } |
| if g.node_count() < 20 && g.edge_count() < 50 { |
| assert!(is_isomorphic(&*g, &ng)); |
| } |
| assert!(is_isomorphic_matching( |
| &*g, |
| &ng, |
| PartialEq::eq, |
| PartialEq::eq |
| )); |
| } |
| true |
| } |
| quickcheck::quickcheck(prop::<Undirected> as fn(_) -> bool); |
| quickcheck::quickcheck(prop::<Directed> as fn(_) -> bool); |
| } |
| |
| #[test] |
| fn isomorphism_modify() { |
| // using small weights so that duplicates are likely |
| fn prop<Ty: EdgeType>(g: Small<Graph<i16, i8, Ty>>, node: u8, edge: u8) -> bool { |
| println!("graph {:#?}", g); |
| let mut ng = (*g).clone(); |
| let i = node_index(node as usize); |
| let j = edge_index(edge as usize); |
| if i.index() < g.node_count() { |
| ng[i] = (g[i] == 0) as i16; |
| } |
| if j.index() < g.edge_count() { |
| ng[j] = (g[j] == 0) as i8; |
| } |
| if i.index() < g.node_count() || j.index() < g.edge_count() { |
| assert!(!is_isomorphic_matching( |
| &*g, |
| &ng, |
| PartialEq::eq, |
| PartialEq::eq |
| )); |
| } else { |
| assert!(is_isomorphic_matching( |
| &*g, |
| &ng, |
| PartialEq::eq, |
| PartialEq::eq |
| )); |
| } |
| true |
| } |
| quickcheck::quickcheck(prop::<Undirected> as fn(_, _, _) -> bool); |
| quickcheck::quickcheck(prop::<Directed> as fn(_, _, _) -> bool); |
| } |
| |
| #[test] |
| fn graph_remove_edge() { |
| fn prop<Ty: EdgeType>(mut g: Graph<(), (), Ty>, a: u8, b: u8) -> bool { |
| let a = node_index(a as usize); |
| let b = node_index(b as usize); |
| let edge = g.find_edge(a, b); |
| if !g.is_directed() { |
| assert_eq!(edge.is_some(), g.find_edge(b, a).is_some()); |
| } |
| if let Some(ex) = edge { |
| assert!(g.remove_edge(ex).is_some()); |
| } |
| assert_graph_consistent(&g); |
| assert!(g.find_edge(a, b).is_none()); |
| assert!(g.neighbors(a).find(|x| *x == b).is_none()); |
| if !g.is_directed() { |
| assert!(g.neighbors(b).find(|x| *x == a).is_none()); |
| } |
| true |
| } |
| quickcheck::quickcheck(prop as fn(Graph<_, _, Undirected>, _, _) -> bool); |
| quickcheck::quickcheck(prop as fn(Graph<_, _, Directed>, _, _) -> bool); |
| } |
| |
| #[cfg(feature = "stable_graph")] |
| #[test] |
| fn stable_graph_remove_edge() { |
| fn prop<Ty: EdgeType>(mut g: StableGraph<(), (), Ty>, a: u8, b: u8) -> bool { |
| let a = node_index(a as usize); |
| let b = node_index(b as usize); |
| let edge = g.find_edge(a, b); |
| if !g.is_directed() { |
| assert_eq!(edge.is_some(), g.find_edge(b, a).is_some()); |
| } |
| if let Some(ex) = edge { |
| assert!(g.remove_edge(ex).is_some()); |
| } |
| //assert_graph_consistent(&g); |
| assert!(g.find_edge(a, b).is_none()); |
| assert!(g.neighbors(a).find(|x| *x == b).is_none()); |
| if !g.is_directed() { |
| assert!(g.find_edge(b, a).is_none()); |
| assert!(g.neighbors(b).find(|x| *x == a).is_none()); |
| } |
| true |
| } |
| quickcheck::quickcheck(prop as fn(StableGraph<_, _, Undirected>, _, _) -> bool); |
| quickcheck::quickcheck(prop as fn(StableGraph<_, _, Directed>, _, _) -> bool); |
| } |
| |
| #[cfg(feature = "stable_graph")] |
| #[test] |
| fn stable_graph_add_remove_edges() { |
| fn prop<Ty: EdgeType>(mut g: StableGraph<(), (), Ty>, edges: Vec<(u8, u8)>) -> bool { |
| for &(a, b) in &edges { |
| let a = node_index(a as usize); |
| let b = node_index(b as usize); |
| let edge = g.find_edge(a, b); |
| |
| if edge.is_none() && g.contains_node(a) && g.contains_node(b) { |
| let _index = g.add_edge(a, b, ()); |
| continue; |
| } |
| |
| if !g.is_directed() { |
| assert_eq!(edge.is_some(), g.find_edge(b, a).is_some()); |
| } |
| if let Some(ex) = edge { |
| assert!(g.remove_edge(ex).is_some()); |
| } |
| //assert_graph_consistent(&g); |
| assert!( |
| g.find_edge(a, b).is_none(), |
| "failed to remove edge {:?} from graph {:?}", |
| (a, b), |
| g |
| ); |
| assert!(g.neighbors(a).find(|x| *x == b).is_none()); |
| if !g.is_directed() { |
| assert!(g.find_edge(b, a).is_none()); |
| assert!(g.neighbors(b).find(|x| *x == a).is_none()); |
| } |
| } |
| true |
| } |
| quickcheck::quickcheck(prop as fn(StableGraph<_, _, Undirected>, _) -> bool); |
| quickcheck::quickcheck(prop as fn(StableGraph<_, _, Directed>, _) -> bool); |
| } |
| |
| fn assert_graphmap_consistent<N, E, Ty>(g: &GraphMap<N, E, Ty>) |
| where |
| Ty: EdgeType, |
| N: NodeTrait + fmt::Debug, |
| { |
| for (a, b, _weight) in g.all_edges() { |
| assert!( |
| g.contains_edge(a, b), |
| "Edge not in graph! {:?} to {:?}", |
| a, |
| b |
| ); |
| assert!( |
| g.neighbors(a).find(|x| *x == b).is_some(), |
| "Edge {:?} not in neighbor list for {:?}", |
| (a, b), |
| a |
| ); |
| if !g.is_directed() { |
| assert!( |
| g.neighbors(b).find(|x| *x == a).is_some(), |
| "Edge {:?} not in neighbor list for {:?}", |
| (b, a), |
| b |
| ); |
| } |
| } |
| } |
| |
| #[test] |
| fn graphmap_remove() { |
| fn prop<Ty: EdgeType>(mut g: GraphMap<i8, (), Ty>, a: i8, b: i8) -> bool { |
| //if g.edge_count() > 20 { return true; } |
| assert_graphmap_consistent(&g); |
| let contains = g.contains_edge(a, b); |
| if !g.is_directed() { |
| assert_eq!(contains, g.contains_edge(b, a)); |
| } |
| assert_eq!(g.remove_edge(a, b).is_some(), contains); |
| assert!(!g.contains_edge(a, b) && g.neighbors(a).find(|x| *x == b).is_none()); |
| //(g.is_directed() || g.neighbors(b).find(|x| *x == a).is_none())); |
| assert!(g.remove_edge(a, b).is_none()); |
| assert_graphmap_consistent(&g); |
| true |
| } |
| quickcheck::quickcheck(prop as fn(DiGraphMap<_, _>, _, _) -> bool); |
| quickcheck::quickcheck(prop as fn(UnGraphMap<_, _>, _, _) -> bool); |
| } |
| |
| #[test] |
| fn graphmap_add_remove() { |
| fn prop(mut g: UnGraphMap<i8, ()>, a: i8, b: i8) -> bool { |
| assert_eq!(g.contains_edge(a, b), g.add_edge(a, b, ()).is_some()); |
| g.remove_edge(a, b); |
| !g.contains_edge(a, b) |
| && g.neighbors(a).find(|x| *x == b).is_none() |
| && g.neighbors(b).find(|x| *x == a).is_none() |
| } |
| quickcheck::quickcheck(prop as fn(_, _, _) -> bool); |
| } |
| |
| fn sort_sccs<T: Ord>(v: &mut [Vec<T>]) { |
| for scc in &mut *v { |
| scc.sort(); |
| } |
| v.sort(); |
| } |
| |
| quickcheck! { |
| fn graph_sccs(g: Graph<(), ()>) -> bool { |
| let mut sccs = kosaraju_scc(&g); |
| let mut tsccs = tarjan_scc(&g); |
| sort_sccs(&mut sccs); |
| sort_sccs(&mut tsccs); |
| if sccs != tsccs { |
| println!("{:?}", |
| Dot::with_config(&g, &[Config::EdgeNoLabel, |
| Config::NodeIndexLabel])); |
| println!("Sccs {:?}", sccs); |
| println!("Sccs (Tarjan) {:?}", tsccs); |
| return false; |
| } |
| true |
| } |
| } |
| |
| quickcheck! { |
| fn kosaraju_scc_is_topo_sort(g: Graph<(), ()>) -> bool { |
| let tsccs = kosaraju_scc(&g); |
| let firsts = tsccs.iter().rev().map(|v| v[0]).collect::<Vec<_>>(); |
| subset_is_topo_order(&g, &firsts) |
| } |
| } |
| |
| quickcheck! { |
| fn tarjan_scc_is_topo_sort(g: Graph<(), ()>) -> bool { |
| let tsccs = tarjan_scc(&g); |
| let firsts = tsccs.iter().rev().map(|v| v[0]).collect::<Vec<_>>(); |
| subset_is_topo_order(&g, &firsts) |
| } |
| } |
| |
| quickcheck! { |
| // Reversed edges gives the same sccs (when sorted) |
| fn graph_reverse_sccs(g: Graph<(), ()>) -> bool { |
| let mut sccs = kosaraju_scc(&g); |
| let mut tsccs = kosaraju_scc(Reversed(&g)); |
| sort_sccs(&mut sccs); |
| sort_sccs(&mut tsccs); |
| if sccs != tsccs { |
| println!("{:?}", |
| Dot::with_config(&g, &[Config::EdgeNoLabel, |
| Config::NodeIndexLabel])); |
| println!("Sccs {:?}", sccs); |
| println!("Sccs (Reversed) {:?}", tsccs); |
| return false; |
| } |
| true |
| } |
| } |
| |
| quickcheck! { |
| // Reversed edges gives the same sccs (when sorted) |
| fn graphmap_reverse_sccs(g: DiGraphMap<u16, ()>) -> bool { |
| let mut sccs = kosaraju_scc(&g); |
| let mut tsccs = kosaraju_scc(Reversed(&g)); |
| sort_sccs(&mut sccs); |
| sort_sccs(&mut tsccs); |
| if sccs != tsccs { |
| println!("{:?}", |
| Dot::with_config(&g, &[Config::EdgeNoLabel, |
| Config::NodeIndexLabel])); |
| println!("Sccs {:?}", sccs); |
| println!("Sccs (Reversed) {:?}", tsccs); |
| return false; |
| } |
| true |
| } |
| } |
| |
| #[test] |
| fn graph_condensation_acyclic() { |
| fn prop(g: Graph<(), ()>) -> bool { |
| !is_cyclic_directed(&condensation(g, /* make_acyclic */ true)) |
| } |
| quickcheck::quickcheck(prop as fn(_) -> bool); |
| } |
| |
| #[derive(Debug, Clone)] |
| struct DAG<N: Default + Clone + Send + 'static>(Graph<N, ()>); |
| |
| impl<N: Default + Clone + Send + 'static> Arbitrary for DAG<N> { |
| fn arbitrary<G: Gen>(g: &mut G) -> Self { |
| let nodes = usize::arbitrary(g); |
| if nodes == 0 { |
| return DAG(Graph::with_capacity(0, 0)); |
| } |
| let split = g.gen_range(0., 1.); |
| let max_width = f64::sqrt(nodes as f64) as usize; |
| let tall = (max_width as f64 * split) as usize; |
| let fat = max_width - tall; |
| |
| let edge_prob = 1. - (1. - g.gen_range(0., 1.)) * (1. - g.gen_range(0., 1.)); |
| let edges = ((nodes as f64).powi(2) * edge_prob) as usize; |
| let mut gr = Graph::with_capacity(nodes, edges); |
| let mut nodes = 0; |
| for _ in 0..tall { |
| let cur_nodes = g.gen_range(0, fat); |
| for _ in 0..cur_nodes { |
| gr.add_node(N::default()); |
| } |
| for j in 0..nodes { |
| for k in 0..cur_nodes { |
| if g.gen_range(0., 1.) < edge_prob { |
| gr.add_edge(NodeIndex::new(j), NodeIndex::new(k + nodes), ()); |
| } |
| } |
| } |
| nodes += cur_nodes; |
| } |
| DAG(gr) |
| } |
| |
| // shrink the graph by splitting it in two by a very |
| // simple algorithm, just even and odd node indices |
| fn shrink(&self) -> Box<dyn Iterator<Item = Self>> { |
| let self_ = self.clone(); |
| Box::new((0..2).filter_map(move |x| { |
| let gr = self_.0.filter_map( |
| |i, w| { |
| if i.index() % 2 == x { |
| Some(w.clone()) |
| } else { |
| None |
| } |
| }, |
| |_, w| Some(w.clone()), |
| ); |
| // make sure we shrink |
| if gr.node_count() < self_.0.node_count() { |
| Some(DAG(gr)) |
| } else { |
| None |
| } |
| })) |
| } |
| } |
| |
| fn is_topo_order<N>(gr: &Graph<N, (), Directed>, order: &[NodeIndex]) -> bool { |
| if gr.node_count() != order.len() { |
| println!( |
| "Graph ({}) and count ({}) had different amount of nodes.", |
| gr.node_count(), |
| order.len() |
| ); |
| return false; |
| } |
| // check all the edges of the graph |
| for edge in gr.raw_edges() { |
| let a = edge.source(); |
| let b = edge.target(); |
| let ai = order.find(&a).unwrap(); |
| let bi = order.find(&b).unwrap(); |
| if ai >= bi { |
| println!("{:?} > {:?} ", a, b); |
| return false; |
| } |
| } |
| true |
| } |
| |
| fn subset_is_topo_order<N>(gr: &Graph<N, (), Directed>, order: &[NodeIndex]) -> bool { |
| if gr.node_count() < order.len() { |
| println!( |
| "Graph (len={}) had less nodes than order (len={})", |
| gr.node_count(), |
| order.len() |
| ); |
| return false; |
| } |
| // check all the edges of the graph |
| for edge in gr.raw_edges() { |
| let a = edge.source(); |
| let b = edge.target(); |
| if a == b { |
| continue; |
| } |
| // skip those that are not in the subset |
| let ai = match order.find(&a) { |
| Some(i) => i, |
| None => continue, |
| }; |
| let bi = match order.find(&b) { |
| Some(i) => i, |
| None => continue, |
| }; |
| if ai >= bi { |
| println!("{:?} > {:?} ", a, b); |
| return false; |
| } |
| } |
| true |
| } |
| |
| #[test] |
| fn full_topo() { |
| fn prop(DAG(gr): DAG<()>) -> bool { |
| let order = toposort(&gr, None).unwrap(); |
| is_topo_order(&gr, &order) |
| } |
| quickcheck::quickcheck(prop as fn(_) -> bool); |
| } |
| |
| #[test] |
| fn full_topo_generic() { |
| fn prop_generic(DAG(mut gr): DAG<usize>) -> bool { |
| assert!(!is_cyclic_directed(&gr)); |
| let mut index = 0; |
| let mut topo = Topo::new(&gr); |
| while let Some(nx) = topo.next(&gr) { |
| gr[nx] = index; |
| index += 1; |
| } |
| |
| let mut order = Vec::new(); |
| index = 0; |
| let mut topo = Topo::new(&gr); |
| while let Some(nx) = topo.next(&gr) { |
| order.push(nx); |
| assert_eq!(gr[nx], index); |
| index += 1; |
| } |
| if !is_topo_order(&gr, &order) { |
| println!("{:?}", gr); |
| return false; |
| } |
| |
| { |
| order.clear(); |
| let mut topo = Topo::new(&gr); |
| while let Some(nx) = topo.next(&gr) { |
| order.push(nx); |
| } |
| if !is_topo_order(&gr, &order) { |
| println!("{:?}", gr); |
| 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); |
| } |
| |
| quickcheck! { |
| // checks that the distances computed by dijkstra satisfy the triangle |
| // inequality. |
| fn dijkstra_triangle_ineq(g: Graph<u32, u32>, node: usize) -> bool { |
| if g.node_count() == 0 { |
| return true; |
| } |
| let v = node_index(node % g.node_count()); |
| let distances = dijkstra(&g, v, None, |e| *e.weight()); |
| for v2 in distances.keys() { |
| let dv2 = distances[v2]; |
| // triangle inequality: |
| // d(v,u) <= d(v,v2) + w(v2,u) |
| for edge in g.edges(*v2) { |
| let u = edge.target(); |
| let w = edge.weight(); |
| if distances.contains_key(&u) && distances[&u] > dv2 + w { |
| return false; |
| } |
| } |
| } |
| true |
| } |
| } |
| |
| quickcheck! { |
| // checks that the distances computed by k'th shortest path is always greater or equal compared to their dijkstra computation |
| fn k_shortest_path_(g: Graph<u32, u32>, node: usize) -> bool { |
| if g.node_count() == 0 { |
| return true; |
| } |
| let v = node_index(node % g.node_count()); |
| let second_best_distances = k_shortest_path(&g, v, None, 2, |e| *e.weight()); |
| let dijkstra_distances = dijkstra(&g, v, None, |e| *e.weight()); |
| for v in second_best_distances.keys() { |
| if second_best_distances[&v] < dijkstra_distances[&v] { |
| return false; |
| } |
| } |
| true |
| } |
| } |
| |
| quickcheck! { |
| // checks floyd_warshall against dijkstra results |
| fn floyd_warshall_(g: Graph<u32, u32>) -> bool { |
| if g.node_count() == 0 { |
| return true; |
| } |
| |
| let fw_res = floyd_warshall(&g, |e| *e.weight()).unwrap(); |
| |
| for node1 in g.node_identifiers() { |
| let dijkstra_res = dijkstra(&g, node1, None, |e| *e.weight()); |
| |
| for node2 in g.node_identifiers() { |
| // if dijkstra found a path then the results must be same |
| if let Some(distance) = dijkstra_res.get(&node2) { |
| let floyd_distance = fw_res.get(&(node1, node2)).unwrap(); |
| if distance != floyd_distance { |
| return false; |
| } |
| } else { |
| // if there are no path between two nodes then floyd_warshall will return maximum value possible |
| if *fw_res.get(&(node1, node2)).unwrap() != u32::MAX { |
| return false; |
| } |
| } |
| } |
| } |
| true |
| } |
| } |
| |
| quickcheck! { |
| // checks that the complement of the complement is the same as the input if the input does not contain self-loops |
| fn complement_(g: Graph<u32, u32>, _node: usize) -> bool { |
| if g.node_count() == 0 { |
| return true; |
| } |
| for x in g.node_indices() { |
| if g.contains_edge(x, x) { |
| return true; |
| } |
| } |
| let mut complement_graph: Graph<u32, u32> = Graph::new(); |
| let mut result: Graph<u32, u32> = Graph::new(); |
| complement(&g, &mut complement_graph, 0); |
| complement(&complement_graph, &mut result, 0); |
| |
| for x in g.node_indices() { |
| for y in g.node_indices() { |
| if g.contains_edge(x, y) != result.contains_edge(x, y){ |
| return false; |
| } |
| } |
| } |
| true |
| } |
| } |
| |
| fn set<I>(iter: I) -> HashSet<I::Item> |
| where |
| I: IntoIterator, |
| I::Item: Hash + Eq, |
| { |
| iter.into_iter().collect() |
| } |
| |
| quickcheck! { |
| fn dfs_visit(gr: Graph<(), ()>, node: usize) -> bool { |
| use petgraph::visit::{Visitable, VisitMap}; |
| use petgraph::visit::DfsEvent::*; |
| use petgraph::visit::{Time, depth_first_search}; |
| if gr.node_count() == 0 { |
| return true; |
| } |
| let start_node = node_index(node % gr.node_count()); |
| |
| let invalid_time = Time(!0); |
| let mut discover_time = vec![invalid_time; gr.node_count()]; |
| let mut finish_time = vec![invalid_time; gr.node_count()]; |
| let mut has_tree_edge = gr.visit_map(); |
| let mut edges = HashSet::new(); |
| depth_first_search(&gr, Some(start_node).into_iter().chain(gr.node_indices()), |
| |evt| { |
| match evt { |
| Discover(n, t) => discover_time[n.index()] = t, |
| Finish(n, t) => finish_time[n.index()] = t, |
| TreeEdge(u, v) => { |
| // v is an ancestor of u |
| assert!(has_tree_edge.visit(v), "Two tree edges to {:?}!", v); |
| assert!(discover_time[v.index()] == invalid_time); |
| assert!(discover_time[u.index()] != invalid_time); |
| assert!(finish_time[u.index()] == invalid_time); |
| edges.insert((u, v)); |
| } |
| BackEdge(u, v) => { |
| // u is an ancestor of v |
| assert!(discover_time[v.index()] != invalid_time); |
| assert!(finish_time[v.index()] == invalid_time); |
| edges.insert((u, v)); |
| } |
| CrossForwardEdge(u, v) => { |
| edges.insert((u, v)); |
| } |
| } |
| }); |
| assert!(discover_time.iter().all(|x| *x != invalid_time)); |
| assert!(finish_time.iter().all(|x| *x != invalid_time)); |
| assert_eq!(edges.len(), gr.edge_count()); |
| assert_eq!(edges, set(gr.edge_references().map(|e| (e.source(), e.target())))); |
| true |
| } |
| } |
| |
| quickcheck! { |
| fn test_bellman_ford(gr: Graph<(), f32>) -> bool { |
| let mut gr = gr; |
| for elt in gr.edge_weights_mut() { |
| *elt = elt.abs(); |
| } |
| if gr.node_count() == 0 { |
| return true; |
| } |
| for (i, start) in gr.node_indices().enumerate() { |
| if i >= 10 { break; } // testing all is too slow |
| bellman_ford(&gr, start).unwrap(); |
| } |
| true |
| } |
| } |
| |
| quickcheck! { |
| fn test_find_negative_cycle(gr: Graph<(), f32>) -> bool { |
| let gr = gr; |
| if gr.node_count() == 0 { |
| return true; |
| } |
| for (i, start) in gr.node_indices().enumerate() { |
| if i >= 10 { break; } // testing all is too slow |
| if let Some(path) = find_negative_cycle(&gr, start) { |
| assert!(path.len() >= 1); |
| } |
| } |
| true |
| } |
| } |
| |
| quickcheck! { |
| fn test_bellman_ford_undir(gr: Graph<(), f32, Undirected>) -> bool { |
| let mut gr = gr; |
| for elt in gr.edge_weights_mut() { |
| *elt = elt.abs(); |
| } |
| if gr.node_count() == 0 { |
| return true; |
| } |
| for (i, start) in gr.node_indices().enumerate() { |
| if i >= 10 { break; } // testing all is too slow |
| bellman_ford(&gr, start).unwrap(); |
| } |
| true |
| } |
| } |
| |
| defmac!(iter_eq a, b => a.eq(b)); |
| defmac!(nodes_eq ref a, ref b => a.node_references().eq(b.node_references())); |
| defmac!(edgew_eq ref a, ref b => a.edge_references().eq(b.edge_references())); |
| defmac!(edges_eq ref a, ref b => |
| iter_eq!( |
| a.edge_references().map(|e| (e.source(), e.target())), |
| b.edge_references().map(|e| (e.source(), e.target())))); |
| |
| quickcheck! { |
| fn test_di_from(gr1: DiGraph<i32, i32>) -> () { |
| let sgr = StableGraph::from(gr1.clone()); |
| let gr2 = Graph::from(sgr); |
| |
| assert!(nodes_eq!(&gr1, &gr2)); |
| assert!(edgew_eq!(&gr1, &gr2)); |
| assert!(edges_eq!(&gr1, &gr2)); |
| } |
| fn test_un_from(gr1: UnGraph<i32, i32>) -> () { |
| let sgr = StableGraph::from(gr1.clone()); |
| let gr2 = Graph::from(sgr); |
| |
| assert!(nodes_eq!(&gr1, &gr2)); |
| assert!(edgew_eq!(&gr1, &gr2)); |
| assert!(edges_eq!(&gr1, &gr2)); |
| } |
| |
| fn test_graph_from_stable_graph(gr1: StableDiGraph<usize, usize>) -> () { |
| let mut gr1 = gr1; |
| let gr2 = Graph::from(gr1.clone()); |
| |
| // renumber the stablegraph nodes and put the new index in the |
| // associated data |
| let mut index = 0; |
| for i in 0..gr1.node_bound() { |
| let ni = node_index(i); |
| if gr1.contains_node(ni) { |
| gr1[ni] = index; |
| index += 1; |
| } |
| } |
| if let Some(edge_bound) = gr1.edge_references().next_back() |
| .map(|ed| ed.id().index() + 1) |
| { |
| index = 0; |
| for i in 0..edge_bound { |
| let ni = edge_index(i); |
| if gr1.edge_weight(ni).is_some() { |
| gr1[ni] = index; |
| index += 1; |
| } |
| } |
| } |
| |
| assert_equal( |
| // Remap the stablegraph to compact indices |
| gr1.edge_references().map(|ed| (edge_index(*ed.weight()), gr1[ed.source()], gr1[ed.target()])), |
| gr2.edge_references().map(|ed| (ed.id(), ed.source().index(), ed.target().index())) |
| ); |
| } |
| |
| fn stable_di_graph_map_id(gr1: StableDiGraph<usize, usize>) -> () { |
| let gr2 = gr1.map(|_, &nw| nw, |_, &ew| ew); |
| assert!(nodes_eq!(&gr1, &gr2)); |
| assert!(edgew_eq!(&gr1, &gr2)); |
| assert!(edges_eq!(&gr1, &gr2)); |
| } |
| |
| fn stable_un_graph_map_id(gr1: StableUnGraph<usize, usize>) -> () { |
| let gr2 = gr1.map(|_, &nw| nw, |_, &ew| ew); |
| assert!(nodes_eq!(&gr1, &gr2)); |
| assert!(edgew_eq!(&gr1, &gr2)); |
| assert!(edges_eq!(&gr1, &gr2)); |
| } |
| |
| fn stable_di_graph_filter_map_id(gr1: StableDiGraph<usize, usize>) -> () { |
| let gr2 = gr1.filter_map(|_, &nw| Some(nw), |_, &ew| Some(ew)); |
| assert!(nodes_eq!(&gr1, &gr2)); |
| assert!(edgew_eq!(&gr1, &gr2)); |
| assert!(edges_eq!(&gr1, &gr2)); |
| } |
| |
| fn test_stable_un_graph_filter_map_id(gr1: StableUnGraph<usize, usize>) -> () { |
| let gr2 = gr1.filter_map(|_, &nw| Some(nw), |_, &ew| Some(ew)); |
| assert!(nodes_eq!(&gr1, &gr2)); |
| assert!(edgew_eq!(&gr1, &gr2)); |
| assert!(edges_eq!(&gr1, &gr2)); |
| } |
| |
| fn stable_di_graph_filter_map_remove(gr1: Small<StableDiGraph<i32, i32>>, |
| nodes: Vec<usize>, |
| edges: Vec<usize>) -> () |
| { |
| let gr2 = gr1.filter_map(|ix, &nw| { |
| if !nodes.contains(&ix.index()) { Some(nw) } else { None } |
| }, |
| |ix, &ew| { |
| if !edges.contains(&ix.index()) { Some(ew) } else { None } |
| }); |
| let check_nodes = &set(gr1.node_indices()) - &set(cloned(&nodes).map(node_index)); |
| let mut check_edges = &set(gr1.edge_indices()) - &set(cloned(&edges).map(edge_index)); |
| // remove all edges with endpoint in removed nodes |
| for edge in gr1.edge_references() { |
| if nodes.contains(&edge.source().index()) || |
| nodes.contains(&edge.target().index()) { |
| check_edges.remove(&edge.id()); |
| } |
| } |
| // assert maintained |
| for i in check_nodes { |
| assert_eq!(gr1[i], gr2[i]); |
| } |
| for i in check_edges { |
| assert_eq!(gr1[i], gr2[i]); |
| assert_eq!(gr1.edge_endpoints(i), gr2.edge_endpoints(i)); |
| } |
| |
| // assert removals |
| for i in nodes { |
| assert!(gr2.node_weight(node_index(i)).is_none()); |
| } |
| for i in edges { |
| assert!(gr2.edge_weight(edge_index(i)).is_none()); |
| } |
| } |
| } |
| |
| fn naive_closure_foreach<G, F>(g: G, mut f: F) |
| where |
| G: Visitable + IntoNeighbors + IntoNodeIdentifiers, |
| F: FnMut(G::NodeId, G::NodeId), |
| { |
| let mut dfs = Dfs::empty(&g); |
| for i in g.node_identifiers() { |
| dfs.reset(&g); |
| dfs.move_to(i); |
| while let Some(nx) = dfs.next(&g) { |
| if i != nx { |
| f(i, nx); |
| } |
| } |
| } |
| } |
| |
| fn naive_closure<G>(g: G) -> Vec<(G::NodeId, G::NodeId)> |
| where |
| G: Visitable + IntoNodeIdentifiers + IntoNeighbors, |
| { |
| let mut res = Vec::new(); |
| naive_closure_foreach(g, |a, b| res.push((a, b))); |
| res |
| } |
| |
| fn naive_closure_edgecount<G>(g: G) -> usize |
| where |
| G: Visitable + IntoNodeIdentifiers + IntoNeighbors, |
| { |
| let mut res = 0; |
| naive_closure_foreach(g, |_, _| res += 1); |
| res |
| } |
| |
| quickcheck! { |
| fn test_tred(g: DAG<()>) -> bool { |
| let acyclic = g.0; |
| println!("acyclic graph {:#?}", &acyclic); |
| let toposort = toposort(&acyclic, None).unwrap(); |
| println!("Toposort:"); |
| for (new, old) in toposort.iter().enumerate() { |
| println!("{} -> {}", old.index(), new); |
| } |
| let (toposorted, revtopo): (petgraph::adj::List<(), usize>, _) = |
| petgraph::algo::tred::dag_to_toposorted_adjacency_list(&acyclic, &toposort); |
| println!("checking revtopo"); |
| for (i, ix) in toposort.iter().enumerate() { |
| assert_eq!(i, revtopo[ix.index()]); |
| } |
| println!("toposorted adjacency list: {:#?}", &toposorted); |
| let (tred, tclos) = petgraph::algo::tred::dag_transitive_reduction_closure(&toposorted); |
| println!("tred: {:#?}", &tred); |
| println!("tclos: {:#?}", &tclos); |
| if tred.node_count() != tclos.node_count() { |
| println!("Different node count"); |
| return false; |
| } |
| if acyclic.node_count() != tclos.node_count() { |
| println!("Different node count from original graph"); |
| return false; |
| } |
| // check the closure |
| let mut clos_edges: Vec<(_, _)> = tclos.edge_references().map(|i| (i.source(), i.target())).collect(); |
| clos_edges.sort(); |
| let mut tred_closure = naive_closure(&tred); |
| tred_closure.sort(); |
| if tred_closure != clos_edges { |
| println!("tclos is not the transitive closure of tred"); |
| return false |
| } |
| // check the transitive reduction is a transitive reduction |
| for i in tred.edge_references() { |
| let filtered = EdgeFiltered::from_fn(&tred, |edge| { |
| edge.source() !=i.source() || edge.target() != i.target() |
| }); |
| let new = naive_closure_edgecount(&filtered); |
| if new >= clos_edges.len() { |
| println!("when removing ({} -> {}) the transitive closure does not shrink", |
| i.source().index(), i.target().index()); |
| return false |
| } |
| } |
| // check that the transitive reduction is included in the original graph |
| for i in tred.edge_references() { |
| if acyclic.find_edge(toposort[i.source().index()], toposort[i.target().index()]).is_none() { |
| println!("tred is not included in the original graph"); |
| return false |
| } |
| } |
| println!("ok!"); |
| true |
| } |
| } |
| |
| quickcheck! { |
| fn greedy_fas_remaining_graph_is_acyclic(g: StableDiGraph<(), ()>) -> bool { |
| let mut g = g; |
| let fas: Vec<EdgeIndex> = greedy_feedback_arc_set(&g).map(|e| e.id()).collect(); |
| |
| for edge_id in fas { |
| g.remove_edge(edge_id); |
| } |
| |
| !is_cyclic_directed(&g) |
| } |
| |
| /// Assert that the size of the feedback arc set of a tournament does not exceed |
| /// **|E| / 2 - |V| / 6** |
| fn greedy_fas_performance_within_bound(t: Tournament<(), ()>) -> bool { |
| let Tournament(g) = t; |
| |
| let expected_bound = if g.node_count() < 2 { |
| 0 |
| } else { |
| ((g.edge_count() as f64) / 2.0 - (g.node_count() as f64) / 6.0) as usize |
| }; |
| |
| let fas_size = greedy_feedback_arc_set(&g).count(); |
| |
| fas_size <= expected_bound |
| } |
| } |
| |
| fn is_valid_matching<G: NodeIndexable>(m: &Matching<G>) -> bool { |
| // A set of edges is a matching if no two edges from the matching share an |
| // endpoint. |
| for (s1, t1) in m.edges() { |
| for (s2, t2) in m.edges() { |
| if s1 == s2 && t1 == t2 { |
| continue; |
| } |
| |
| if s1 == s2 || s1 == t2 || t1 == s2 || t1 == t2 { |
| // Two edges share an endpoint. |
| return false; |
| } |
| } |
| } |
| |
| true |
| } |
| |
| fn is_maximum_matching<G: NodeIndexable + IntoEdges + IntoNodeIdentifiers + Visitable>( |
| g: G, |
| m: &Matching<G>, |
| ) -> bool { |
| // Berge's lemma: a matching is maximum iff there is no augmenting path (a |
| // path that starts and ends in unmatched vertices, and alternates between |
| // matched and unmatched edges). Thus if we find an augmenting path, the |
| // matching is not maximum. |
| // |
| // Start with an unmatched node and traverse the graph alternating matched |
| // and unmatched edges. If an unmatched node is found, then an augmenting |
| // path was found. |
| for unmatched in g.node_identifiers().filter(|u| !m.contains_node(*u)) { |
| let visited = &mut g.visit_map(); |
| let mut stack = Vec::new(); |
| |
| stack.push((unmatched, false)); |
| while let Some((u, do_matched_edges)) = stack.pop() { |
| if visited.visit(u) { |
| for e in g.edges(u) { |
| if e.source() == e.target() { |
| // Ignore self-loops. |
| continue; |
| } |
| |
| let is_matched = m.contains_edge(e.source(), e.target()); |
| |
| if do_matched_edges && is_matched || !do_matched_edges && !is_matched { |
| stack.push((e.target(), !do_matched_edges)); |
| |
| // Found another free node (other than the starting one) |
| // that is unmatched - an augmenting path. |
| if !is_matched && !m.contains_node(e.target()) && e.target() != unmatched { |
| return false; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| true |
| } |
| |
| fn is_perfect_matching<G: NodeCount + NodeIndexable>(g: G, m: &Matching<G>) -> bool { |
| // By definition. |
| g.node_count() % 2 == 0 && m.edges().count() == g.node_count() / 2 |
| } |
| |
| quickcheck! { |
| fn matching(g: Graph<(), (), Undirected>) -> bool { |
| let m1 = greedy_matching(&g); |
| let m2 = maximum_matching(&g); |
| |
| assert!(is_valid_matching(&m1), "greedy_matching returned an invalid matching"); |
| assert!(is_valid_matching(&m2), "maximum_matching returned an invalid matching"); |
| assert!(is_maximum_matching(&g, &m2), "maximum_matching returned a matching that is not maximum"); |
| assert_eq!(m1.is_perfect(), is_perfect_matching(&g, &m1), "greedy_matching incorrectly determined whether the matching is perfect"); |
| assert_eq!(m2.is_perfect(), is_perfect_matching(&g, &m2), "maximum_matching incorrectly determined whether the matching is perfect"); |
| |
| true |
| } |
| |
| fn matching_in_stable_graph(g: StableGraph<(), (), Undirected>) -> bool { |
| let m1 = greedy_matching(&g); |
| let m2 = maximum_matching(&g); |
| |
| assert!(is_valid_matching(&m1), "greedy_matching returned an invalid matching"); |
| assert!(is_valid_matching(&m2), "maximum_matching returned an invalid matching"); |
| assert!(is_maximum_matching(&g, &m2), "maximum_matching returned a matching that is not maximum"); |
| assert_eq!(m1.is_perfect(), is_perfect_matching(&g, &m1), "greedy_matching incorrectly determined whether the matching is perfect"); |
| assert_eq!(m2.is_perfect(), is_perfect_matching(&g, &m2), "maximum_matching incorrectly determined whether the matching is perfect"); |
| |
| 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<f64> = 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::<f64>() - 1.).abs() > tol; |
| if at_least_one_neg_rank | not_sumup_to_one{ |
| return false; |
| } |
| true |
| } |
| } |
| |
| fn sum_flows<N, F: std::iter::Sum + Copy>( |
| gr: &Graph<N, F>, |
| flows: &[F], |
| node: NodeIndex, |
| dir: Direction, |
| ) -> F { |
| gr.edges_directed(node, dir) |
| .map(|edge| flows[EdgeIndexable::to_index(&gr, edge.id())]) |
| .sum::<F>() |
| } |
| |
| 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<usize, u32>) -> 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; |
| } |
| } |