pub use crate::algorithms::games::bipartite::BipartiteMode;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone)]
pub struct FullBipartite {
pub graph: Graph,
pub types: Vec<bool>,
}
pub fn full_bipartite(
n1: u32,
n2: u32,
directed: bool,
mode: BipartiteMode,
) -> IgraphResult<FullBipartite> {
let total = n1.checked_add(n2).ok_or_else(|| {
IgraphError::InvalidArgument(format!(
"full_bipartite: total vertex count {n1} + {n2} overflows u32"
))
})?;
if total == 0 {
let graph = Graph::new(0, directed)?;
return Ok(FullBipartite {
graph,
types: Vec::new(),
});
}
let n1_us = n1 as usize;
let n2_us = n2 as usize;
let base_edges = n1_us.checked_mul(n2_us).ok_or_else(|| {
IgraphError::InvalidArgument("full_bipartite: edge count overflows usize".to_string())
})?;
let num_edges = if directed && mode == BipartiteMode::All {
base_edges.checked_mul(2).ok_or_else(|| {
IgraphError::InvalidArgument(
"full_bipartite: mutual edge count overflows usize".to_string(),
)
})?
} else {
base_edges
};
let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(num_edges);
match mode {
BipartiteMode::Out | BipartiteMode::All if !directed => {
for i in 0..n1 {
for j in 0..n2 {
edges.push((i, n1 + j));
}
}
}
BipartiteMode::Out => {
for i in 0..n1 {
for j in 0..n2 {
edges.push((i, n1 + j));
}
}
}
BipartiteMode::In => {
for i in 0..n1 {
for j in 0..n2 {
edges.push((n1 + j, i));
}
}
}
BipartiteMode::All => {
for i in 0..n1 {
for j in 0..n2 {
edges.push((i, n1 + j));
edges.push((n1 + j, i));
}
}
}
}
let mut graph = Graph::new(total, directed)?;
graph.add_edges(edges)?;
let mut types = vec![false; total as usize];
for i in n1..total {
types[i as usize] = true;
}
Ok(FullBipartite { graph, types })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph() {
let r = full_bipartite(0, 0, false, BipartiteMode::All).unwrap();
assert_eq!(r.graph.vcount(), 0);
assert_eq!(r.graph.ecount(), 0);
assert!(r.types.is_empty());
}
#[test]
fn one_partition_empty() {
let r = full_bipartite(3, 0, false, BipartiteMode::All).unwrap();
assert_eq!(r.graph.vcount(), 3);
assert_eq!(r.graph.ecount(), 0);
assert_eq!(r.types, vec![false, false, false]);
}
#[test]
fn other_partition_empty() {
let r = full_bipartite(0, 4, false, BipartiteMode::All).unwrap();
assert_eq!(r.graph.vcount(), 4);
assert_eq!(r.graph.ecount(), 0);
assert_eq!(r.types, vec![true, true, true, true]);
}
#[test]
fn k23_undirected() {
let r = full_bipartite(2, 3, false, BipartiteMode::All).unwrap();
assert_eq!(r.graph.vcount(), 5);
assert_eq!(r.graph.ecount(), 6);
assert!(!r.graph.is_directed());
assert_eq!(r.types, vec![false, false, true, true, true]);
}
#[test]
fn k11_undirected() {
let r = full_bipartite(1, 1, false, BipartiteMode::All).unwrap();
assert_eq!(r.graph.vcount(), 2);
assert_eq!(r.graph.ecount(), 1);
assert_eq!(r.types, vec![false, true]);
}
#[test]
fn k33_undirected() {
let r = full_bipartite(3, 3, false, BipartiteMode::All).unwrap();
assert_eq!(r.graph.vcount(), 6);
assert_eq!(r.graph.ecount(), 9);
}
#[test]
fn directed_out() {
let r = full_bipartite(2, 3, true, BipartiteMode::Out).unwrap();
assert!(r.graph.is_directed());
assert_eq!(r.graph.ecount(), 6);
for eid in 0..r.graph.ecount() {
#[allow(clippy::cast_possible_truncation)]
let e = eid as u32;
let (s, t) = r.graph.edge(e).unwrap();
assert!(!r.types[s as usize], "source should be partition 1");
assert!(r.types[t as usize], "target should be partition 2");
}
}
#[test]
fn directed_in() {
let r = full_bipartite(2, 3, true, BipartiteMode::In).unwrap();
assert!(r.graph.is_directed());
assert_eq!(r.graph.ecount(), 6);
for eid in 0..r.graph.ecount() {
#[allow(clippy::cast_possible_truncation)]
let e = eid as u32;
let (s, t) = r.graph.edge(e).unwrap();
assert!(r.types[s as usize], "source should be partition 2");
assert!(!r.types[t as usize], "target should be partition 1");
}
}
#[test]
fn directed_mutual() {
let r = full_bipartite(2, 3, true, BipartiteMode::All).unwrap();
assert!(r.graph.is_directed());
assert_eq!(r.graph.ecount(), 12); }
#[test]
fn types_correctness() {
let r = full_bipartite(4, 2, false, BipartiteMode::All).unwrap();
assert_eq!(r.types, vec![false, false, false, false, true, true]);
}
#[test]
fn bipartite_structure_no_within_partition_edges() {
let r = full_bipartite(3, 4, false, BipartiteMode::All).unwrap();
for eid in 0..r.graph.ecount() {
#[allow(clippy::cast_possible_truncation)]
let e = eid as u32;
let (s, t) = r.graph.edge(e).unwrap();
assert_ne!(
r.types[s as usize], r.types[t as usize],
"edge ({s}, {t}) connects same partition"
);
}
}
#[test]
fn overflow_vertex_count_errors() {
let result = full_bipartite(u32::MAX, 1, false, BipartiteMode::All);
assert!(result.is_err());
}
}