use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn create_bipartite(
types: &[bool],
edges: &[(VertexId, VertexId)],
directed: bool,
) -> IgraphResult<Graph> {
let n = u32::try_from(types.len()).map_err(|_| {
IgraphError::InvalidArgument("create_bipartite: vertex count exceeds u32::MAX".to_string())
})?;
for (i, &(from, to)) in edges.iter().enumerate() {
if from >= n {
return Err(IgraphError::InvalidArgument(format!(
"create_bipartite: edge {i} source vertex {from} out of range (n={n})"
)));
}
if to >= n {
return Err(IgraphError::InvalidArgument(format!(
"create_bipartite: edge {i} target vertex {to} out of range (n={n})"
)));
}
let t_from = types[from as usize];
let t_to = types[to as usize];
if t_from == t_to {
return Err(IgraphError::InvalidArgument(format!(
"create_bipartite: edge {i} ({from}, {to}) connects vertices of the same type"
)));
}
}
let mut graph = Graph::new(n, directed)?;
if !edges.is_empty() {
graph.add_edges(edges.to_vec())?;
}
Ok(graph)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph() {
let g = create_bipartite(&[], &[], false).unwrap();
assert_eq!(g.vcount(), 0);
assert_eq!(g.ecount(), 0);
}
#[test]
fn no_edges() {
let types = vec![false, true, false, true];
let g = create_bipartite(&types, &[], false).unwrap();
assert_eq!(g.vcount(), 4);
assert_eq!(g.ecount(), 0);
}
#[test]
fn k22_undirected() {
let types = vec![false, false, true, true];
let edges = vec![(0, 2), (0, 3), (1, 2), (1, 3)];
let g = create_bipartite(&types, &edges, false).unwrap();
assert_eq!(g.vcount(), 4);
assert_eq!(g.ecount(), 4);
assert!(!g.is_directed());
}
#[test]
fn k22_directed() {
let types = vec![false, false, true, true];
let edges = vec![(0, 2), (0, 3), (1, 2), (1, 3)];
let g = create_bipartite(&types, &edges, true).unwrap();
assert_eq!(g.vcount(), 4);
assert_eq!(g.ecount(), 4);
assert!(g.is_directed());
}
#[test]
fn rejects_same_type_edge_both_false() {
let types = vec![false, false, true];
let edges = vec![(0, 1)];
let result = create_bipartite(&types, &edges, false);
assert!(result.is_err());
}
#[test]
fn rejects_same_type_edge_both_true() {
let types = vec![false, true, true];
let edges = vec![(1, 2)];
let result = create_bipartite(&types, &edges, false);
assert!(result.is_err());
}
#[test]
fn rejects_out_of_range_source() {
let types = vec![false, true];
let edges = vec![(5, 1)];
let result = create_bipartite(&types, &edges, false);
assert!(result.is_err());
}
#[test]
fn rejects_out_of_range_target() {
let types = vec![false, true];
let edges = vec![(0, 5)];
let result = create_bipartite(&types, &edges, false);
assert!(result.is_err());
}
#[test]
fn single_edge() {
let types = vec![false, true];
let edges = vec![(0, 1)];
let g = create_bipartite(&types, &edges, false).unwrap();
assert_eq!(g.vcount(), 2);
assert_eq!(g.ecount(), 1);
}
#[test]
fn mixed_type_ordering() {
let types = vec![true, false, true, false];
let edges = vec![(0, 1), (0, 3), (2, 1), (2, 3)];
let g = create_bipartite(&types, &edges, false).unwrap();
assert_eq!(g.vcount(), 4);
assert_eq!(g.ecount(), 4);
}
#[test]
fn valid_directed_reverse_edges() {
let types = vec![false, true];
let edges = vec![(1, 0)]; let g = create_bipartite(&types, &edges, true).unwrap();
assert_eq!(g.ecount(), 1);
}
}