use crate::core::error::IgraphError;
use crate::core::{Graph, IgraphResult, VertexId};
#[derive(Debug, Clone)]
pub struct InducedSubgraphResult {
pub graph: Graph,
pub map: Vec<u32>,
pub invmap: Vec<VertexId>,
}
pub fn induced_subgraph(graph: &Graph, vids: &[VertexId]) -> IgraphResult<InducedSubgraphResult> {
let n = graph.vcount();
for &v in vids {
if v >= n {
return Err(IgraphError::InvalidArgument(format!(
"vertex id {v} is out of range [0, {n})"
)));
}
}
let mut map = vec![u32::MAX; n as usize];
let mut invmap: Vec<VertexId> = Vec::new();
let mut sorted_vids: Vec<VertexId> = vids.to_vec();
sorted_vids.sort_unstable();
sorted_vids.dedup();
let no_of_new_nodes = sorted_vids.len();
invmap.reserve(no_of_new_nodes);
for (new_id, &old_id) in sorted_vids.iter().enumerate() {
#[allow(clippy::cast_possible_truncation)]
let new_id_u32 = new_id as u32;
map[old_id as usize] = new_id_u32;
invmap.push(old_id);
}
let directed = graph.is_directed();
let mut new_edges: Vec<(VertexId, VertexId)> = Vec::new();
for (new_vid_idx, &old_vid) in invmap.iter().enumerate() {
#[allow(clippy::cast_possible_truncation)]
let new_vid = new_vid_idx as u32;
let incident = graph.incident(old_vid)?;
if directed {
for eid in incident {
let src = graph.edge_source(eid)?;
if src != old_vid {
continue;
}
let tgt = graph.edge_target(eid)?;
let new_tgt = map[tgt as usize];
if new_tgt == u32::MAX {
continue;
}
new_edges.push((new_vid, new_tgt));
}
} else {
let mut skip_loop = false;
for eid in incident {
let src = graph.edge_source(eid)?;
if src != old_vid {
continue;
}
let tgt = graph.edge_target(eid)?;
let new_tgt = map[tgt as usize];
if new_tgt == u32::MAX {
continue;
}
if new_vid == new_tgt {
skip_loop = !skip_loop;
if skip_loop {
continue;
}
}
new_edges.push((new_vid, new_tgt));
}
}
}
#[allow(clippy::cast_possible_truncation)]
let new_node_count = no_of_new_nodes as u32;
let mut result_graph = Graph::new(new_node_count, directed)?;
result_graph.add_edges(new_edges)?;
Ok(InducedSubgraphResult {
graph: result_graph,
map,
invmap,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_vids() {
let g = Graph::with_vertices(5);
let result = induced_subgraph(&g, &[]).unwrap();
assert_eq!(result.graph.vcount(), 0);
assert_eq!(result.graph.ecount(), 0);
assert!(result.invmap.is_empty());
}
#[test]
fn test_all_vertices() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let result = induced_subgraph(&g, &[0, 1, 2, 3]).unwrap();
assert_eq!(result.graph.vcount(), 4);
assert_eq!(result.graph.ecount(), 3);
assert_eq!(result.invmap, vec![0, 1, 2, 3]);
}
#[test]
fn test_partial_subgraph() {
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();
g.add_edge(0, 4).unwrap();
let result = induced_subgraph(&g, &[1, 2, 3]).unwrap();
assert_eq!(result.graph.vcount(), 3);
assert_eq!(result.graph.ecount(), 2);
assert_eq!(result.invmap, vec![1, 2, 3]);
assert_eq!(result.map[0], u32::MAX);
assert_eq!(result.map[1], 0);
assert_eq!(result.map[2], 1);
assert_eq!(result.map[3], 2);
assert_eq!(result.map[4], u32::MAX);
}
#[test]
fn test_duplicate_vids() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let result = induced_subgraph(&g, &[0, 1, 1, 0]).unwrap();
assert_eq!(result.graph.vcount(), 2);
assert_eq!(result.graph.ecount(), 1);
}
#[test]
fn test_unordered_vids() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let result = induced_subgraph(&g, &[3, 1, 0]).unwrap();
assert_eq!(result.graph.vcount(), 3);
assert_eq!(result.invmap, vec![0, 1, 3]);
assert_eq!(result.graph.ecount(), 1);
}
#[test]
fn test_invalid_vertex() {
let g = Graph::with_vertices(3);
let result = induced_subgraph(&g, &[0, 5]);
assert!(result.is_err());
}
#[test]
fn test_directed() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let result = induced_subgraph(&g, &[0, 1, 2]).unwrap();
assert_eq!(result.graph.vcount(), 3);
assert_eq!(result.graph.ecount(), 3); assert!(result.graph.is_directed());
}
#[test]
fn test_self_loop() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let result = induced_subgraph(&g, &[0, 1]).unwrap();
assert_eq!(result.graph.vcount(), 2);
assert_eq!(result.graph.ecount(), 2); }
#[test]
fn test_isolated_vertices() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let result = induced_subgraph(&g, &[1, 3, 4]).unwrap();
assert_eq!(result.graph.vcount(), 3);
assert_eq!(result.graph.ecount(), 0);
}
#[test]
fn test_single_vertex() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
let result = induced_subgraph(&g, &[1]).unwrap();
assert_eq!(result.graph.vcount(), 1);
assert_eq!(result.graph.ecount(), 0);
assert_eq!(result.invmap, vec![1]);
}
#[test]
fn test_complete_subgraph() {
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();
g.add_edge(1, 2).unwrap();
g.add_edge(1, 3).unwrap();
g.add_edge(2, 3).unwrap();
let result = induced_subgraph(&g, &[0, 1, 2]).unwrap();
assert_eq!(result.graph.vcount(), 3);
assert_eq!(result.graph.ecount(), 3); }
#[test]
fn test_directed_self_loop() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let result = induced_subgraph(&g, &[0, 1]).unwrap();
assert_eq!(result.graph.vcount(), 2);
assert_eq!(result.graph.ecount(), 2); }
#[test]
fn test_multi_edges() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
let result = induced_subgraph(&g, &[0, 1]).unwrap();
assert_eq!(result.graph.vcount(), 2);
assert_eq!(result.graph.ecount(), 2); }
#[test]
fn test_map_consistency() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 2).unwrap();
g.add_edge(2, 4).unwrap();
g.add_edge(4, 0).unwrap();
let result = induced_subgraph(&g, &[0, 2, 4]).unwrap();
#[allow(clippy::cast_possible_truncation)]
for (new_id, &old_id) in result.invmap.iter().enumerate() {
assert_eq!(result.map[old_id as usize], new_id as u32);
}
#[allow(clippy::cast_possible_truncation)]
for (old_id, &new_id) in result.map.iter().enumerate() {
if new_id != u32::MAX {
assert_eq!(result.invmap[new_id as usize], old_id as u32);
}
}
}
}