use crate::core::{Graph, IgraphError, IgraphResult};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CorenessMode {
All,
In,
Out,
}
pub fn coreness(graph: &Graph) -> IgraphResult<Vec<u32>> {
coreness_with_mode(graph, CorenessMode::All)
}
pub fn coreness_with_mode(graph: &Graph, mode: CorenessMode) -> IgraphResult<Vec<u32>> {
let n = graph.vcount();
let n_us = n as usize;
if n_us == 0 {
return Ok(Vec::new());
}
let eff_mode = if graph.is_directed() {
mode
} else {
CorenessMode::All
};
let mut cores = vec![0u32; n_us];
let mut max_deg: u32 = 0;
for v in 0..n {
let d = vertex_degree_in_mode(graph, v, eff_mode)?;
cores[v as usize] = d;
if d > max_deg {
max_deg = d;
}
}
let max_deg_us = max_deg as usize;
let mut bin = vec![0usize; max_deg_us + 1];
for &c in &cores {
bin[c as usize] += 1;
}
let mut start = 0usize;
for slot in bin.iter_mut().take(max_deg_us + 1) {
let count = *slot;
*slot = start;
start += count;
}
let mut vert = vec![0u32; n_us];
let mut pos = vec![0usize; n_us];
let mut bin_cursor = bin.clone();
for v in 0..n {
let c = cores[v as usize] as usize;
let p = bin_cursor[c];
pos[v as usize] = p;
vert[p] = v;
bin_cursor[c] += 1;
}
drop(bin_cursor);
for i in 0..n_us {
let v = vert[i];
let neis = peel_neighbors_in_mode(graph, v, eff_mode)?;
for u in neis {
if cores[u as usize] > cores[v as usize] {
let du = cores[u as usize] as usize;
let pu = pos[u as usize];
let pw = bin[du];
let w = vert[pw];
if u != w {
pos[u as usize] = pw;
pos[w as usize] = pu;
vert[pu] = w;
vert[pw] = u;
}
bin[du] += 1;
cores[u as usize] -= 1;
}
}
}
Ok(cores)
}
fn vertex_degree_in_mode(graph: &Graph, v: u32, mode: CorenessMode) -> IgraphResult<u32> {
let raw = match mode {
CorenessMode::All => graph.degree(v)?,
CorenessMode::Out => graph.out_neighbors_vec(v)?.len(),
CorenessMode::In => graph.in_neighbors_vec(v)?.len(),
};
u32::try_from(raw).map_err(|_| IgraphError::Internal("vertex degree overflows u32"))
}
fn peel_neighbors_in_mode(graph: &Graph, v: u32, mode: CorenessMode) -> IgraphResult<Vec<u32>> {
match mode {
CorenessMode::All => graph.neighbors(v),
CorenessMode::Out => graph.in_neighbors_vec(v),
CorenessMode::In => graph.out_neighbors_vec(v),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_returns_empty() {
let g = Graph::with_vertices(0);
assert_eq!(coreness(&g).unwrap(), Vec::<u32>::new());
}
#[test]
fn singleton_zero() {
let g = Graph::with_vertices(1);
assert_eq!(coreness(&g).unwrap(), vec![0]);
}
#[test]
fn isolated_vertices_all_zero() {
let g = Graph::with_vertices(5);
assert_eq!(coreness(&g).unwrap(), vec![0; 5]);
}
#[test]
fn single_edge_two_one_cores() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
assert_eq!(coreness(&g).unwrap(), vec![1, 1]);
}
#[test]
fn triangle_all_two_cores() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(0, 2).unwrap();
assert_eq!(coreness(&g).unwrap(), vec![2, 2, 2]);
}
#[test]
fn path_all_one_cores() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
assert_eq!(coreness(&g).unwrap(), vec![1; 5]);
}
#[test]
fn star_centre_vs_leaves() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
assert_eq!(coreness(&g).unwrap(), vec![1, 1, 1, 1]);
}
#[test]
fn k4_all_three_cores() {
let mut g = Graph::with_vertices(4);
for u in 0..4 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
assert_eq!(coreness(&g).unwrap(), vec![3, 3, 3, 3]);
}
#[test]
fn triangle_with_pendant_mixed_cores() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(2, 3).unwrap();
assert_eq!(coreness(&g).unwrap(), vec![2, 2, 2, 1]);
}
#[test]
fn two_components_independent() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(3, 4).unwrap();
assert_eq!(coreness(&g).unwrap(), vec![2, 2, 2, 1, 1]);
}
#[test]
fn self_loop_counts_twice() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let cores = coreness(&g).unwrap();
assert_eq!(cores[1], 1);
}
#[test]
fn directed_default_is_undirected_view_for_canonical_entry() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
assert_eq!(coreness(&g).unwrap(), vec![2, 2, 2]);
}
#[test]
fn directed_3_cycle_in_out_modes_match() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
assert_eq!(
coreness_with_mode(&g, CorenessMode::Out).unwrap(),
vec![1, 1, 1]
);
assert_eq!(
coreness_with_mode(&g, CorenessMode::In).unwrap(),
vec![1, 1, 1]
);
}
#[test]
fn directed_star_out_mode_drains_to_zero() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
assert_eq!(
coreness_with_mode(&g, CorenessMode::Out).unwrap(),
vec![0, 0, 0, 0]
);
}
#[test]
fn directed_star_in_mode_drains_to_zero() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
assert_eq!(
coreness_with_mode(&g, CorenessMode::In).unwrap(),
vec![0, 0, 0, 0]
);
}
#[test]
fn directed_complete_3_all_one_in_one_out() {
let mut g = Graph::new(3, true).unwrap();
for &(u, v) in &[(0u32, 1), (1, 0), (1, 2), (2, 1), (0, 2), (2, 0)] {
g.add_edge(u, v).unwrap();
}
assert_eq!(
coreness_with_mode(&g, CorenessMode::Out).unwrap(),
vec![2, 2, 2]
);
assert_eq!(
coreness_with_mode(&g, CorenessMode::In).unwrap(),
vec![2, 2, 2]
);
assert_eq!(
coreness_with_mode(&g, CorenessMode::All).unwrap(),
vec![4, 4, 4]
);
}
#[test]
fn undirected_modes_all_agree() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(0, 2).unwrap();
let a = coreness_with_mode(&g, CorenessMode::All).unwrap();
let i = coreness_with_mode(&g, CorenessMode::In).unwrap();
let o = coreness_with_mode(&g, CorenessMode::Out).unwrap();
assert_eq!(a, i);
assert_eq!(a, o);
assert_eq!(a, vec![2, 2, 2]);
}
#[test]
fn directed_chain_out_mode_descends() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
assert_eq!(
coreness_with_mode(&g, CorenessMode::Out).unwrap(),
vec![0, 0, 0, 0]
);
}
#[test]
fn coreness_bounded_by_max_degree() {
let mut g = Graph::with_vertices(6);
for &(u, v) in &[(0u32, 1), (1, 2), (2, 0), (2, 3), (3, 4), (4, 5), (5, 3)] {
g.add_edge(u, v).unwrap();
}
let cores = coreness(&g).unwrap();
for v in 0..g.vcount() {
let d = u32::try_from(g.degree(v).unwrap()).unwrap();
assert!(
cores[v as usize] <= d,
"vertex {}: coreness {} exceeds degree {}",
v,
cores[v as usize],
d
);
}
}
}