use crate::core::cache::CachedProperty;
use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphResult};
pub fn has_loop(graph: &Graph) -> IgraphResult<bool> {
if let Some(v) = graph.cache_get(CachedProperty::HasLoop) {
return Ok(v);
}
let m = u32::try_from(graph.ecount())
.map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
for e in 0..m {
let (u, v) = graph.edge(e as EdgeId)?;
if u == v {
graph.cache_set(CachedProperty::HasLoop, true);
return Ok(true);
}
}
graph.cache_set(CachedProperty::HasLoop, false);
Ok(false)
}
pub fn has_multiple(graph: &Graph) -> IgraphResult<bool> {
if let Some(v) = graph.cache_get(CachedProperty::HasMulti) {
return Ok(v);
}
let m = u32::try_from(graph.ecount())
.map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
if m < 2 {
graph.cache_set(CachedProperty::HasMulti, false);
return Ok(false);
}
let mut pairs: Vec<(u32, u32)> = Vec::with_capacity(m as usize);
for e in 0..m {
pairs.push(graph.edge(e as EdgeId)?);
}
pairs.sort_unstable();
for i in 1..pairs.len() {
if pairs[i] == pairs[i - 1] {
graph.cache_set(CachedProperty::HasMulti, true);
return Ok(true);
}
}
graph.cache_set(CachedProperty::HasMulti, false);
Ok(false)
}
pub fn is_loop(graph: &Graph) -> IgraphResult<Vec<bool>> {
let m = u32::try_from(graph.ecount())
.map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
let mut out = Vec::with_capacity(m as usize);
for e in 0..m {
let (u, v) = graph.edge(e as EdgeId)?;
out.push(u == v);
}
Ok(out)
}
pub fn is_multiple(graph: &Graph) -> IgraphResult<Vec<bool>> {
let m = u32::try_from(graph.ecount())
.map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
let m_us = m as usize;
if m_us == 0 {
return Ok(Vec::new());
}
let mut pairs: Vec<((u32, u32), u32)> = Vec::with_capacity(m_us);
for e in 0..m {
pairs.push((graph.edge(e as EdgeId)?, e));
}
pairs.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
let mut out = vec![false; m_us];
let mut i = 0usize;
while i < m_us {
let mut j = i + 1;
while j < m_us && pairs[j].0 == pairs[i].0 {
j += 1;
}
for entry in &pairs[i + 1..j] {
out[entry.1 as usize] = true;
}
i = j;
}
Ok(out)
}
pub fn count_loops(graph: &Graph) -> IgraphResult<usize> {
let m = u32::try_from(graph.ecount())
.map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
let mut count = 0usize;
for e in 0..m {
let (u, v) = graph.edge(e as EdgeId)?;
if u == v {
count += 1;
}
}
Ok(count)
}
pub fn count_multiple(graph: &Graph) -> IgraphResult<Vec<usize>> {
let m = u32::try_from(graph.ecount())
.map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
let m_us = m as usize;
if m_us == 0 {
return Ok(Vec::new());
}
let mut pairs: Vec<((u32, u32), u32)> = Vec::with_capacity(m_us);
for e in 0..m {
pairs.push((graph.edge(e as EdgeId)?, e));
}
pairs.sort_unstable_by_key(|p| p.0);
let mut out = vec![0usize; m_us];
let mut i = 0usize;
while i < m_us {
let mut j = i + 1;
while j < m_us && pairs[j].0 == pairs[i].0 {
j += 1;
}
let group_size = j - i;
for entry in &pairs[i..j] {
out[entry.1 as usize] = group_size;
}
i = j;
}
Ok(out)
}
pub fn count_multiple_1(graph: &Graph, eid: EdgeId) -> IgraphResult<usize> {
let m = graph.ecount();
if (eid as usize) >= m {
return Err(crate::IgraphError::InvalidArgument(format!(
"count_multiple_1: edge id {eid} out of range (ecount={m})"
)));
}
let (from, to) = graph.edge(eid)?;
let neighbors = graph.neighbors(from)?;
let mut count = neighbors.iter().filter(|&&nb| nb == to).count();
if !graph.is_directed() && from == to {
count /= 2;
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_has_no_loop() {
let g = Graph::with_vertices(0);
assert!(!has_loop(&g).unwrap());
}
#[test]
fn empty_graph_has_no_multi() {
let g = Graph::with_vertices(0);
assert!(!has_multiple(&g).unwrap());
}
#[test]
fn no_edge_graph_has_neither() {
let g = Graph::with_vertices(5);
assert!(!has_loop(&g).unwrap());
assert!(!has_multiple(&g).unwrap());
}
#[test]
fn path_has_neither() {
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();
assert!(!has_loop(&g).unwrap());
assert!(!has_multiple(&g).unwrap());
}
#[test]
fn detects_self_loop() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap();
assert!(has_loop(&g).unwrap());
assert!(!has_multiple(&g).unwrap());
}
#[test]
fn detects_parallel_undirected() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
assert!(!has_loop(&g).unwrap());
assert!(has_multiple(&g).unwrap());
}
#[test]
fn detects_parallel_directed() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert!(has_multiple(&g).unwrap());
}
#[test]
fn directed_mutual_pair_not_parallel() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
assert!(!has_multiple(&g).unwrap());
}
#[test]
fn duplicate_self_loops_count_as_parallel() {
let mut g = Graph::with_vertices(1);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 0).unwrap();
assert!(has_loop(&g).unwrap());
assert!(has_multiple(&g).unwrap());
}
#[test]
fn is_loop_per_edge_marks_self_loops_only() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 2).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(is_loop(&g).unwrap(), vec![false, true, false]);
}
#[test]
fn is_loop_empty_graph() {
let g = Graph::with_vertices(0);
assert!(is_loop(&g).unwrap().is_empty());
}
#[test]
fn is_multiple_per_edge_marks_only_duplicates_after_first() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(is_multiple(&g).unwrap(), vec![false, true, false]);
}
#[test]
fn is_multiple_directed_mutual_pair_not_multiple() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
assert_eq!(is_multiple(&g).unwrap(), vec![false, false]);
}
#[test]
fn is_multiple_three_copies_first_canonical_only() {
let mut g = Graph::with_vertices(2);
for _ in 0..3 {
g.add_edge(0, 1).unwrap();
}
assert_eq!(is_multiple(&g).unwrap(), vec![false, true, true]);
}
#[test]
fn is_multiple_empty_graph() {
let g = Graph::with_vertices(0);
assert!(is_multiple(&g).unwrap().is_empty());
}
#[test]
fn matches_is_simple_negation_for_simple_graphs() {
let mut g = Graph::with_vertices(4);
for u in 0..4u32 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
assert!(!has_loop(&g).unwrap());
assert!(!has_multiple(&g).unwrap());
assert!(crate::is_simple(&g).unwrap());
}
#[test]
fn count_loops_empty_graph() {
let g = Graph::with_vertices(0);
assert_eq!(count_loops(&g).unwrap(), 0);
}
#[test]
fn count_loops_no_loops() {
let mut g = Graph::with_vertices(4);
for (u, v) in [(0, 1), (1, 2), (2, 3)] {
g.add_edge(u, v).unwrap();
}
assert_eq!(count_loops(&g).unwrap(), 0);
}
#[test]
fn count_loops_counts_each_self_loop() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 1).unwrap();
g.add_edge(2, 2).unwrap();
g.add_edge(2, 2).unwrap();
assert_eq!(count_loops(&g).unwrap(), 3);
}
#[test]
fn count_loops_directed_self_loops() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(2, 2).unwrap();
assert_eq!(count_loops(&g).unwrap(), 2);
}
#[test]
fn count_multiple_empty_graph() {
let g = Graph::with_vertices(0);
assert!(count_multiple(&g).unwrap().is_empty());
}
#[test]
fn count_multiple_simple_graph_all_ones() {
let mut g = Graph::with_vertices(4);
for (u, v) in [(0, 1), (1, 2), (2, 3)] {
g.add_edge(u, v).unwrap();
}
assert_eq!(count_multiple(&g).unwrap(), vec![1, 1, 1]);
}
#[test]
fn count_multiple_groups_parallel_undirected() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(count_multiple(&g).unwrap(), vec![2, 2, 1]);
}
#[test]
fn count_multiple_directed_mutual_pair_distinct() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
assert_eq!(count_multiple(&g).unwrap(), vec![1, 1]);
}
#[test]
fn count_multiple_three_parallel_copies() {
let mut g = Graph::with_vertices(2);
for _ in 0..3 {
g.add_edge(0, 1).unwrap();
}
assert_eq!(count_multiple(&g).unwrap(), vec![3, 3, 3]);
}
#[test]
fn count_multiple_self_loops_grouped_per_vertex() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(2, 2).unwrap();
assert_eq!(count_multiple(&g).unwrap(), vec![2, 2, 1]);
}
#[test]
fn count_multiple_mixed_graph() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(0, 1).unwrap(); g.add_edge(3, 3).unwrap(); g.add_edge(2, 3).unwrap(); assert_eq!(count_multiple(&g).unwrap(), vec![2, 1, 2, 1, 1]);
}
#[test]
fn count_multiple_length_matches_ecount() {
let mut g = Graph::with_vertices(5);
for (u, v) in [(0, 1), (1, 2), (2, 3), (3, 4), (0, 4)] {
g.add_edge(u, v).unwrap();
}
assert_eq!(count_multiple(&g).unwrap().len(), g.ecount());
}
#[test]
fn count_multiple_consistent_with_is_multiple_and_has_multiple() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let mults = count_multiple(&g).unwrap();
let is_mult = is_multiple(&g).unwrap();
let has_mult_via_count = mults.iter().any(|&m| m > 1);
assert_eq!(has_mult_via_count, has_multiple(&g).unwrap());
for (e, &flag) in is_mult.iter().enumerate() {
if flag {
assert!(mults[e] > 1, "is_multiple but mult = {}", mults[e]);
}
}
}
#[test]
fn count_loops_consistent_with_is_loop() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 2).unwrap();
g.add_edge(1, 2).unwrap();
let n_loops = count_loops(&g).unwrap();
let is_l = is_loop(&g).unwrap();
assert_eq!(n_loops, is_l.iter().filter(|&&b| b).count());
}
#[test]
fn count_multiple_1_simple() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(count_multiple_1(&g, 0).unwrap(), 1);
assert_eq!(count_multiple_1(&g, 1).unwrap(), 1);
}
#[test]
fn count_multiple_1_parallel() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(count_multiple_1(&g, 0).unwrap(), 3);
assert_eq!(count_multiple_1(&g, 1).unwrap(), 3);
assert_eq!(count_multiple_1(&g, 2).unwrap(), 3);
}
#[test]
fn count_multiple_1_self_loop() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(count_multiple_1(&g, 0).unwrap(), 2);
assert_eq!(count_multiple_1(&g, 1).unwrap(), 2);
assert_eq!(count_multiple_1(&g, 2).unwrap(), 1);
}
#[test]
fn count_multiple_1_out_of_range() {
let g = Graph::with_vertices(3);
assert!(count_multiple_1(&g, 5).is_err());
}
#[test]
fn count_multiple_1_consistent_with_count_multiple() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 3).unwrap();
let all = count_multiple(&g).unwrap();
#[allow(clippy::cast_possible_truncation)]
let ecount = g.ecount() as u32;
for eid in 0..ecount {
assert_eq!(
count_multiple_1(&g, eid).unwrap(),
all[eid as usize],
"mismatch at edge {eid}"
);
}
}
}