use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone)]
pub struct SubgraphFromEdgesResult {
pub graph: Graph,
pub vertex_map: Vec<u32>,
pub vertex_invmap: Vec<VertexId>,
pub edge_map: Vec<u32>,
}
pub fn subgraph_from_edges(
graph: &Graph,
eids: &[u32],
delete_vertices: bool,
) -> IgraphResult<SubgraphFromEdgesResult> {
let n = graph.vcount();
let m = graph.ecount();
let mut edge_set: Vec<bool> = vec![false; m];
for &eid in eids {
if eid as usize >= m {
return Err(IgraphError::InvalidArgument(format!(
"subgraph_from_edges: edge ID {eid} out of range (graph has {m} edges)"
)));
}
edge_set[eid as usize] = true;
}
let mut vertex_remain: Vec<bool> = if delete_vertices {
vec![false; n as usize]
} else {
vec![true; n as usize]
};
let mut retained_edges: Vec<(u32, u32, u32)> = Vec::new(); for (eid_usize, &keep) in edge_set.iter().enumerate() {
if keep {
#[allow(clippy::cast_possible_truncation)]
let eid = eid_usize as u32;
let (from, to) = graph.edge(eid)?;
retained_edges.push((from, to, eid));
if delete_vertices {
vertex_remain[from as usize] = true;
vertex_remain[to as usize] = true;
}
}
}
let mut vertex_map: Vec<u32> = vec![u32::MAX; n as usize];
let mut vertex_invmap: Vec<VertexId> = Vec::new();
let mut new_vid: u32 = 0;
for vid in 0..n {
if vertex_remain[vid as usize] {
vertex_map[vid as usize] = new_vid;
vertex_invmap.push(vid);
new_vid = new_vid.checked_add(1).ok_or(IgraphError::Internal(
"subgraph_from_edges: vertex count overflow",
))?;
}
}
let new_vcount = new_vid;
let mut result_graph = if graph.is_directed() {
Graph::new(new_vcount, true)?
} else {
Graph::with_vertices(new_vcount)
};
let mut edge_map: Vec<u32> = Vec::with_capacity(retained_edges.len());
for (from, to, old_eid) in &retained_edges {
let new_from = vertex_map[*from as usize];
let new_to = vertex_map[*to as usize];
result_graph.add_edge(new_from, new_to)?;
edge_map.push(*old_eid);
}
Ok(SubgraphFromEdgesResult {
graph: result_graph,
vertex_map,
vertex_invmap,
edge_map,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_undirected() {
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 r = subgraph_from_edges(&g, &[0, 2, 4], true).unwrap();
assert_eq!(r.graph.ecount(), 3);
assert_eq!(r.graph.vcount(), 5);
assert_eq!(r.edge_map, vec![0, 2, 4]);
}
#[test]
fn delete_isolated_vertices() {
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 r = subgraph_from_edges(&g, &[0], true).unwrap();
assert_eq!(r.graph.ecount(), 1);
assert_eq!(r.graph.vcount(), 2);
assert_eq!(r.vertex_invmap, vec![0, 1]);
}
#[test]
fn keep_all_vertices() {
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 r = subgraph_from_edges(&g, &[0], false).unwrap();
assert_eq!(r.graph.ecount(), 1);
assert_eq!(r.graph.vcount(), 5);
for i in 0..5u32 {
assert_eq!(r.vertex_map[i as usize], i);
}
}
#[test]
fn empty_edge_set() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let r = subgraph_from_edges(&g, &[], true).unwrap();
assert_eq!(r.graph.ecount(), 0);
assert_eq!(r.graph.vcount(), 0);
assert!(r.vertex_invmap.is_empty());
}
#[test]
fn empty_edge_set_keep_vertices() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
let r = subgraph_from_edges(&g, &[], false).unwrap();
assert_eq!(r.graph.ecount(), 0);
assert_eq!(r.graph.vcount(), 3);
}
#[test]
fn duplicate_edge_ids() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
let r = subgraph_from_edges(&g, &[0, 0, 1, 1, 0], true).unwrap();
assert_eq!(r.graph.ecount(), 2);
assert_eq!(r.graph.vcount(), 3);
}
#[test]
fn edge_id_out_of_range() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
let err = subgraph_from_edges(&g, &[5], true).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn directed_graph() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(2, 3).unwrap(); g.add_edge(3, 0).unwrap();
let r = subgraph_from_edges(&g, &[0, 2], true).unwrap();
assert!(r.graph.is_directed());
assert_eq!(r.graph.ecount(), 2);
assert_eq!(r.graph.vcount(), 4);
}
#[test]
fn directed_delete_vertices() {
let mut g = Graph::new(5, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(2, 3).unwrap(); g.add_edge(3, 4).unwrap();
let r = subgraph_from_edges(&g, &[1], true).unwrap();
assert_eq!(r.graph.vcount(), 2);
assert_eq!(r.graph.ecount(), 1);
assert_eq!(r.vertex_invmap, vec![2, 3]);
assert_eq!(r.vertex_map[2], 0);
assert_eq!(r.vertex_map[3], 1);
}
#[test]
fn vertex_map_consistency() {
let mut g = Graph::with_vertices(6);
g.add_edge(1, 3).unwrap(); g.add_edge(3, 5).unwrap();
let r = subgraph_from_edges(&g, &[0, 1], true).unwrap();
assert_eq!(r.graph.vcount(), 3);
assert_eq!(r.vertex_invmap, vec![1, 3, 5]);
assert_eq!(r.vertex_map[1], 0);
assert_eq!(r.vertex_map[3], 1);
assert_eq!(r.vertex_map[5], 2);
assert_eq!(r.vertex_map[0], u32::MAX);
assert_eq!(r.vertex_map[2], u32::MAX);
assert_eq!(r.vertex_map[4], u32::MAX);
}
#[test]
fn all_edges_selected() {
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 r = subgraph_from_edges(&g, &[0, 1, 2], true).unwrap();
assert_eq!(r.graph.vcount(), 4);
assert_eq!(r.graph.ecount(), 3);
}
#[test]
fn self_loop_edge() {
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 r = subgraph_from_edges(&g, &[0], true).unwrap();
assert_eq!(r.graph.vcount(), 1);
assert_eq!(r.graph.ecount(), 1);
assert_eq!(r.vertex_invmap, vec![0]);
}
#[test]
fn edge_map_ordering() {
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 r = subgraph_from_edges(&g, &[2, 0], true).unwrap();
assert_eq!(r.edge_map, vec![0, 2]);
}
}