use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphResult, VertexId};
pub fn simplify(graph: &Graph, remove_multiple: bool, remove_loops: bool) -> IgraphResult<Graph> {
let n = graph.vcount();
let directed = graph.is_directed();
if !remove_multiple && !remove_loops {
return Ok(graph.clone());
}
let m = graph.ecount();
let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(m);
for eid in 0..m {
let eid_u32 = u32::try_from(eid)
.map_err(|_| crate::IgraphError::Internal("edge id exceeds u32::MAX"))?;
let (f, t) = graph.edge(eid_u32 as EdgeId)?;
if remove_loops && f == t {
continue;
}
edges.push((f, t));
}
if remove_multiple {
edges.sort_unstable();
edges.dedup();
}
let mut g = Graph::new(n, directed)?;
g.add_edges(edges)?;
Ok(g)
}
#[cfg(test)]
mod tests {
use super::*;
fn sorted_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
let m = u32::try_from(g.ecount()).expect("ecount fits in u32");
let mut v: Vec<_> = (0..m).map(|e| g.edge(e).unwrap()).collect();
v.sort_unstable();
v
}
#[test]
fn no_op_when_both_flags_false() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let s = simplify(&g, false, false).unwrap();
assert_eq!(s.vcount(), g.vcount());
assert_eq!(s.ecount(), g.ecount());
assert_eq!(sorted_edges(&s), sorted_edges(&g));
}
#[test]
fn removes_self_loops_only() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(1, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let s = simplify(&g, false, true).unwrap();
assert_eq!(s.ecount(), 2);
assert_eq!(sorted_edges(&s), vec![(0, 1), (0, 1)]);
}
#[test]
fn removes_multi_edges_only_undirected() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
g.add_edge(1, 2).unwrap();
let s = simplify(&g, true, false).unwrap();
assert_eq!(s.ecount(), 2);
assert_eq!(sorted_edges(&s), vec![(0, 1), (1, 2)]);
}
#[test]
fn removes_multi_edges_directed() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
g.add_edge(1, 2).unwrap();
let s = simplify(&g, true, false).unwrap();
assert!(s.is_directed());
assert_eq!(s.ecount(), 3);
assert_eq!(sorted_edges(&s), vec![(0, 1), (1, 0), (1, 2)]);
}
#[test]
fn removes_both_loops_and_multi() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let s = simplify(&g, true, true).unwrap();
assert_eq!(s.ecount(), 3);
assert_eq!(sorted_edges(&s), vec![(0, 1), (0, 2), (1, 2)]);
}
#[test]
fn idempotent_on_simple_graph() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let s = simplify(&g, true, true).unwrap();
assert_eq!(s.ecount(), g.ecount());
assert_eq!(sorted_edges(&s), sorted_edges(&g));
}
#[test]
fn empty_graph() {
let g = Graph::with_vertices(0);
let s = simplify(&g, true, true).unwrap();
assert_eq!(s.vcount(), 0);
assert_eq!(s.ecount(), 0);
}
#[test]
fn vertices_preserved_when_no_edges_remain() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(1, 1).unwrap();
let s = simplify(&g, true, true).unwrap();
assert_eq!(s.vcount(), 3);
assert_eq!(s.ecount(), 0);
}
#[test]
fn igraph_simplify_example_directed_5_parallel() {
let mut g = Graph::new(2, true).unwrap();
for _ in 0..5 {
g.add_edge(0, 1).unwrap();
}
let s = simplify(&g, true, true).unwrap();
assert_eq!(s.ecount(), 1);
assert_eq!(s.edge(0).unwrap(), (0, 1));
}
#[test]
fn igraph_simplify_example_directed_loops_and_multi() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(1, 1).unwrap();
g.add_edge(2, 2).unwrap();
g.add_edge(1, 2).unwrap();
let s = simplify(&g, true, true).unwrap();
assert_eq!(s.ecount(), 1);
assert_eq!(s.edge(0).unwrap(), (1, 2));
}
}