use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BipartiteProjectionSize {
pub vcount1: u32,
pub ecount1: u32,
pub vcount2: u32,
pub ecount2: u32,
}
pub fn bipartite_projection_size(
graph: &Graph,
types: &[bool],
) -> IgraphResult<BipartiteProjectionSize> {
let n = graph.vcount();
let n_us = n as usize;
if types.len() != n_us {
return Err(IgraphError::InvalidArgument(format!(
"bipartite_projection_size: types length ({}) != vcount ({n})",
types.len()
)));
}
let adj = build_undirected_adj(graph, n_us)?;
let mut vc1: u32 = 0;
let mut ec1: u32 = 0;
let mut vc2: u32 = 0;
let mut ec2: u32 = 0;
let mut added: Vec<u32> = vec![0; n_us];
for i in 0..n_us {
if types[i] {
vc2 = vc2.saturating_add(1);
} else {
vc1 = vc1.saturating_add(1);
}
#[allow(clippy::cast_possible_truncation)]
let marker = (i as u32).wrapping_add(1);
for &nei in &adj[i] {
let nei_us = nei as usize;
if types[nei_us] == types[i] {
return Err(IgraphError::InvalidArgument(format!(
"bipartite_projection_size: non-bipartite edge found ({i}-{nei})"
)));
}
for &nb2 in &adj[nei_us] {
if nb2 as usize <= i {
continue;
}
if added[nb2 as usize] == marker {
continue;
}
added[nb2 as usize] = marker;
if types[i] {
ec2 = ec2.saturating_add(1);
} else {
ec1 = ec1.saturating_add(1);
}
}
}
}
Ok(BipartiteProjectionSize {
vcount1: vc1,
ecount1: ec1,
vcount2: vc2,
ecount2: ec2,
})
}
fn build_undirected_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
let m =
u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
for eid in 0..m {
let (from, to) = graph.edge(eid)?;
adj[from as usize].push(to);
if from != to {
adj[to as usize].push(from);
}
}
Ok(adj)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn k22() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(1, 3).unwrap();
let types = [false, false, true, true];
let sz = bipartite_projection_size(&g, &types).unwrap();
assert_eq!(sz.vcount1, 2);
assert_eq!(sz.ecount1, 1);
assert_eq!(sz.vcount2, 2);
assert_eq!(sz.ecount2, 1);
}
#[test]
fn k23() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
g.add_edge(0, 4).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(1, 3).unwrap();
g.add_edge(1, 4).unwrap();
let types = [false, false, true, true, true];
let sz = bipartite_projection_size(&g, &types).unwrap();
assert_eq!(sz.vcount1, 2);
assert_eq!(sz.ecount1, 1);
assert_eq!(sz.vcount2, 3);
assert_eq!(sz.ecount2, 3);
}
#[test]
fn star_bipartite() {
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();
let types = [false, true, true, true];
let sz = bipartite_projection_size(&g, &types).unwrap();
assert_eq!(sz.vcount1, 1);
assert_eq!(sz.ecount1, 0); assert_eq!(sz.vcount2, 3);
assert_eq!(sz.ecount2, 3); }
#[test]
fn no_edges() {
let g = Graph::with_vertices(4);
let types = [false, false, true, true];
let sz = bipartite_projection_size(&g, &types).unwrap();
assert_eq!(sz.vcount1, 2);
assert_eq!(sz.ecount1, 0);
assert_eq!(sz.vcount2, 2);
assert_eq!(sz.ecount2, 0);
}
#[test]
fn empty_graph() {
let g = Graph::with_vertices(0);
let types: [bool; 0] = [];
let sz = bipartite_projection_size(&g, &types).unwrap();
assert_eq!(sz.vcount1, 0);
assert_eq!(sz.ecount1, 0);
assert_eq!(sz.vcount2, 0);
assert_eq!(sz.ecount2, 0);
}
#[test]
fn types_mismatch() {
let g = Graph::with_vertices(3);
let types = [false, true];
assert!(bipartite_projection_size(&g, &types).is_err());
}
#[test]
fn non_bipartite_edge() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
let types = [false, false, true];
assert!(bipartite_projection_size(&g, &types).is_err());
}
#[test]
fn path_bipartite() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
let types = [false, true, false, true, false];
let sz = bipartite_projection_size(&g, &types).unwrap();
assert_eq!(sz.vcount1, 3); assert_eq!(sz.ecount1, 2); assert_eq!(sz.vcount2, 2); assert_eq!(sz.ecount2, 1); }
#[test]
fn directed_treated_as_undirected() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(3, 0).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 1).unwrap();
let types = [false, false, true, true];
let sz = bipartite_projection_size(&g, &types).unwrap();
assert_eq!(sz.vcount1, 2);
assert_eq!(sz.ecount1, 1);
assert_eq!(sz.vcount2, 2);
assert_eq!(sz.ecount2, 1);
}
}