use crate::core::error::IgraphError;
use crate::core::{Graph, IgraphResult, VertexId};
pub fn compose(g1: &Graph, g2: &Graph) -> IgraphResult<Graph> {
if g1.is_directed() != g2.is_directed() {
return Err(IgraphError::InvalidArgument(
"cannot compose directed and undirected graphs".to_string(),
));
}
let n1 = g1.vcount();
let n2 = g2.vcount();
let directed = g1.is_directed();
let n = n1.max(n2);
let adj1 = build_out_adjacency(g1, n1)?;
let adj2 = build_out_adjacency(g2, n2)?;
let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
for i in 0..n1 {
for &k in &adj1[i as usize] {
if k < n2 {
for &j in &adj2[k as usize] {
edges.push((i, j));
}
}
}
}
let mut result = Graph::new(n, directed)?;
result.add_edges(edges)?;
Ok(result)
}
fn build_out_adjacency(graph: &Graph, n: u32) -> IgraphResult<Vec<Vec<VertexId>>> {
let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n as usize];
let ecount = graph.ecount();
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let eid_u32 = eid as u32;
let (src, tgt) = graph.edge(eid_u32)?;
adj[src as usize].push(tgt);
if !graph.is_directed() && src != tgt {
adj[tgt as usize].push(src);
}
}
Ok(adj)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_directed_path_compose() {
let mut g1 = Graph::new(3, true).unwrap();
g1.add_edge(0, 1).unwrap();
g1.add_edge(1, 2).unwrap();
let mut g2 = Graph::new(3, true).unwrap();
g2.add_edge(0, 1).unwrap();
g2.add_edge(1, 2).unwrap();
let c = compose(&g1, &g2).unwrap();
assert_eq!(c.vcount(), 3);
assert_eq!(c.ecount(), 1);
assert_eq!(c.edge(0).unwrap(), (0, 2));
}
#[test]
fn test_identity_compose() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 0).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
g.add_edge(2, 1).unwrap();
let c = compose(&g, &g).unwrap();
assert_eq!(c.vcount(), 3);
assert_eq!(c.ecount(), 12);
}
#[test]
fn test_different_sizes() {
let mut g1 = Graph::new(3, true).unwrap();
g1.add_edge(0, 1).unwrap();
let mut g2 = Graph::new(5, true).unwrap();
g2.add_edge(1, 4).unwrap();
let c = compose(&g1, &g2).unwrap();
assert_eq!(c.vcount(), 5); assert_eq!(c.ecount(), 1);
assert_eq!(c.edge(0).unwrap(), (0, 4));
}
#[test]
fn test_undirected() {
let mut g1 = Graph::with_vertices(3);
g1.add_edge(0, 1).unwrap();
let mut g2 = Graph::with_vertices(3);
g2.add_edge(1, 2).unwrap();
let c = compose(&g1, &g2).unwrap();
assert_eq!(c.vcount(), 3);
assert_eq!(c.ecount(), 1);
}
#[test]
fn test_mixed_directedness_error() {
let g1 = Graph::new(3, true).unwrap();
let g2 = Graph::with_vertices(3);
assert!(compose(&g1, &g2).is_err());
}
#[test]
fn test_empty() {
let g1 = Graph::new(0, true).unwrap();
let g2 = Graph::new(0, true).unwrap();
let c = compose(&g1, &g2).unwrap();
assert_eq!(c.vcount(), 0);
assert_eq!(c.ecount(), 0);
}
#[test]
fn test_no_overlap() {
let mut g1 = Graph::new(4, true).unwrap();
g1.add_edge(0, 1).unwrap();
let mut g2 = Graph::new(4, true).unwrap();
g2.add_edge(2, 3).unwrap();
let c = compose(&g1, &g2).unwrap();
assert_eq!(c.vcount(), 4);
assert_eq!(c.ecount(), 0);
}
#[test]
fn test_self_loop_generation() {
let mut g1 = Graph::new(2, true).unwrap();
g1.add_edge(0, 1).unwrap();
let mut g2 = Graph::new(2, true).unwrap();
g2.add_edge(1, 0).unwrap();
let c = compose(&g1, &g2).unwrap();
assert_eq!(c.ecount(), 1);
assert_eq!(c.edge(0).unwrap(), (0, 0));
}
}