Skip to main content

rust_igraph/algorithms/operators/
subgraph_from_edges.rs

1//! Subgraph from edges (ALGO-OP-024).
2//!
3//! Counterpart of `igraph_subgraph_from_edges()` from
4//! `references/igraph/src/operators/subgraph.c`.
5//!
6//! Creates a subgraph containing only the specified edges (and optionally
7//! only the vertices incident to those edges).
8
9use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
10
11/// Result of a subgraph-from-edges extraction.
12#[derive(Debug, Clone)]
13pub struct SubgraphFromEdgesResult {
14    /// The resulting subgraph.
15    pub graph: Graph,
16    /// Mapping from old vertex IDs to new vertex IDs. Contains `u32::MAX`
17    /// for vertices not in the subgraph (only meaningful when
18    /// `delete_vertices` was `true`).
19    pub vertex_map: Vec<u32>,
20    /// Mapping from new vertex IDs to old vertex IDs.
21    pub vertex_invmap: Vec<VertexId>,
22    /// Mapping from new edge indices (positions in the result) to old edge IDs.
23    pub edge_map: Vec<u32>,
24}
25
26/// Create a subgraph containing only the specified edges.
27///
28/// Given a set of edge IDs, builds a new graph that keeps exactly those
29/// edges. When `delete_vertices` is `true`, vertices that have no
30/// incident edges in the selection are removed and IDs are renumbered.
31/// When `false`, all original vertices are kept.
32///
33/// Duplicate edge IDs are silently ignored. Edge ordering in the result
34/// follows the ascending order of original edge IDs.
35///
36/// # Arguments
37///
38/// * `graph` — the input graph.
39/// * `eids` — slice of edge IDs to retain.
40/// * `delete_vertices` — if `true`, remove isolated vertices.
41///
42/// # Errors
43///
44/// Returns `InvalidArgument` if any edge ID in `eids` is out of range.
45///
46/// # Examples
47///
48/// ```
49/// use rust_igraph::{Graph, subgraph_from_edges};
50///
51/// let mut g = Graph::with_vertices(5);
52/// g.add_edge(0, 1).unwrap(); // eid 0
53/// g.add_edge(1, 2).unwrap(); // eid 1
54/// g.add_edge(2, 3).unwrap(); // eid 2
55/// g.add_edge(3, 4).unwrap(); // eid 3
56///
57/// // Keep only edges 0 and 2: (0,1) and (2,3)
58/// let result = subgraph_from_edges(&g, &[0, 2], true).unwrap();
59/// assert_eq!(result.graph.ecount(), 2);
60/// assert_eq!(result.graph.vcount(), 4); // vertices 0,1,2,3 (4 removed)
61/// ```
62pub fn subgraph_from_edges(
63    graph: &Graph,
64    eids: &[u32],
65    delete_vertices: bool,
66) -> IgraphResult<SubgraphFromEdgesResult> {
67    let n = graph.vcount();
68    let m = graph.ecount();
69
70    // Validate edge IDs and collect them into a sorted, deduplicated set.
71    let mut edge_set: Vec<bool> = vec![false; m];
72    for &eid in eids {
73        if eid as usize >= m {
74            return Err(IgraphError::InvalidArgument(format!(
75                "subgraph_from_edges: edge ID {eid} out of range (graph has {m} edges)"
76            )));
77        }
78        edge_set[eid as usize] = true;
79    }
80
81    // Determine which vertices remain.
82    let mut vertex_remain: Vec<bool> = if delete_vertices {
83        vec![false; n as usize]
84    } else {
85        vec![true; n as usize]
86    };
87
88    // Collect retained edges (sorted by original ID) and mark incident vertices.
89    let mut retained_edges: Vec<(u32, u32, u32)> = Vec::new(); // (from, to, old_eid)
90    for (eid_usize, &keep) in edge_set.iter().enumerate() {
91        if keep {
92            #[allow(clippy::cast_possible_truncation)]
93            let eid = eid_usize as u32;
94            let (from, to) = graph.edge(eid)?;
95            retained_edges.push((from, to, eid));
96            if delete_vertices {
97                vertex_remain[from as usize] = true;
98                vertex_remain[to as usize] = true;
99            }
100        }
101    }
102
103    // Build vertex mapping.
104    let mut vertex_map: Vec<u32> = vec![u32::MAX; n as usize];
105    let mut vertex_invmap: Vec<VertexId> = Vec::new();
106    let mut new_vid: u32 = 0;
107    for vid in 0..n {
108        if vertex_remain[vid as usize] {
109            vertex_map[vid as usize] = new_vid;
110            vertex_invmap.push(vid);
111            new_vid = new_vid.checked_add(1).ok_or(IgraphError::Internal(
112                "subgraph_from_edges: vertex count overflow",
113            ))?;
114        }
115    }
116
117    let new_vcount = new_vid;
118
119    // Build the new graph.
120    let mut result_graph = if graph.is_directed() {
121        Graph::new(new_vcount, true)?
122    } else {
123        Graph::with_vertices(new_vcount)
124    };
125
126    let mut edge_map: Vec<u32> = Vec::with_capacity(retained_edges.len());
127    for (from, to, old_eid) in &retained_edges {
128        let new_from = vertex_map[*from as usize];
129        let new_to = vertex_map[*to as usize];
130        result_graph.add_edge(new_from, new_to)?;
131        edge_map.push(*old_eid);
132    }
133
134    Ok(SubgraphFromEdgesResult {
135        graph: result_graph,
136        vertex_map,
137        vertex_invmap,
138        edge_map,
139    })
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn basic_undirected() {
148        let mut g = Graph::with_vertices(5);
149        g.add_edge(0, 1).unwrap(); // 0
150        g.add_edge(1, 2).unwrap(); // 1
151        g.add_edge(2, 3).unwrap(); // 2
152        g.add_edge(3, 4).unwrap(); // 3
153        g.add_edge(0, 4).unwrap(); // 4
154
155        let r = subgraph_from_edges(&g, &[0, 2, 4], true).unwrap();
156        assert_eq!(r.graph.ecount(), 3);
157        // Vertices 0,1,2,3,4 are all incident to at least one kept edge
158        assert_eq!(r.graph.vcount(), 5);
159        assert_eq!(r.edge_map, vec![0, 2, 4]);
160    }
161
162    #[test]
163    fn delete_isolated_vertices() {
164        let mut g = Graph::with_vertices(5);
165        g.add_edge(0, 1).unwrap(); // 0
166        g.add_edge(1, 2).unwrap(); // 1
167        g.add_edge(2, 3).unwrap(); // 2
168        g.add_edge(3, 4).unwrap(); // 3
169
170        // Keep only edge 0: (0,1) — vertices 2,3,4 become isolated
171        let r = subgraph_from_edges(&g, &[0], true).unwrap();
172        assert_eq!(r.graph.ecount(), 1);
173        assert_eq!(r.graph.vcount(), 2);
174        assert_eq!(r.vertex_invmap, vec![0, 1]);
175    }
176
177    #[test]
178    fn keep_all_vertices() {
179        let mut g = Graph::with_vertices(5);
180        g.add_edge(0, 1).unwrap(); // 0
181        g.add_edge(1, 2).unwrap(); // 1
182        g.add_edge(2, 3).unwrap(); // 2
183        g.add_edge(3, 4).unwrap(); // 3
184
185        // Keep only edge 0, but don't delete vertices
186        let r = subgraph_from_edges(&g, &[0], false).unwrap();
187        assert_eq!(r.graph.ecount(), 1);
188        assert_eq!(r.graph.vcount(), 5);
189        // All vertices mapped to themselves
190        for i in 0..5u32 {
191            assert_eq!(r.vertex_map[i as usize], i);
192        }
193    }
194
195    #[test]
196    fn empty_edge_set() {
197        let mut g = Graph::with_vertices(3);
198        g.add_edge(0, 1).unwrap();
199        g.add_edge(1, 2).unwrap();
200
201        let r = subgraph_from_edges(&g, &[], true).unwrap();
202        assert_eq!(r.graph.ecount(), 0);
203        assert_eq!(r.graph.vcount(), 0);
204        assert!(r.vertex_invmap.is_empty());
205    }
206
207    #[test]
208    fn empty_edge_set_keep_vertices() {
209        let mut g = Graph::with_vertices(3);
210        g.add_edge(0, 1).unwrap();
211
212        let r = subgraph_from_edges(&g, &[], false).unwrap();
213        assert_eq!(r.graph.ecount(), 0);
214        assert_eq!(r.graph.vcount(), 3);
215    }
216
217    #[test]
218    fn duplicate_edge_ids() {
219        let mut g = Graph::with_vertices(3);
220        g.add_edge(0, 1).unwrap(); // 0
221        g.add_edge(1, 2).unwrap(); // 1
222
223        let r = subgraph_from_edges(&g, &[0, 0, 1, 1, 0], true).unwrap();
224        assert_eq!(r.graph.ecount(), 2);
225        assert_eq!(r.graph.vcount(), 3);
226    }
227
228    #[test]
229    fn edge_id_out_of_range() {
230        let mut g = Graph::with_vertices(3);
231        g.add_edge(0, 1).unwrap();
232
233        let err = subgraph_from_edges(&g, &[5], true).unwrap_err();
234        assert!(matches!(err, IgraphError::InvalidArgument(_)));
235    }
236
237    #[test]
238    fn directed_graph() {
239        let mut g = Graph::new(4, true).unwrap();
240        g.add_edge(0, 1).unwrap(); // 0
241        g.add_edge(1, 2).unwrap(); // 1
242        g.add_edge(2, 3).unwrap(); // 2
243        g.add_edge(3, 0).unwrap(); // 3
244
245        let r = subgraph_from_edges(&g, &[0, 2], true).unwrap();
246        assert!(r.graph.is_directed());
247        assert_eq!(r.graph.ecount(), 2);
248        // Vertices: 0,1 from edge 0; 2,3 from edge 2
249        assert_eq!(r.graph.vcount(), 4);
250    }
251
252    #[test]
253    fn directed_delete_vertices() {
254        let mut g = Graph::new(5, true).unwrap();
255        g.add_edge(0, 1).unwrap(); // 0
256        g.add_edge(2, 3).unwrap(); // 1
257        g.add_edge(3, 4).unwrap(); // 2
258
259        // Keep only edge 1: (2,3). Vertices 0,1,4 are isolated.
260        let r = subgraph_from_edges(&g, &[1], true).unwrap();
261        assert_eq!(r.graph.vcount(), 2);
262        assert_eq!(r.graph.ecount(), 1);
263        assert_eq!(r.vertex_invmap, vec![2, 3]);
264        // Check mapping
265        assert_eq!(r.vertex_map[2], 0);
266        assert_eq!(r.vertex_map[3], 1);
267    }
268
269    #[test]
270    fn vertex_map_consistency() {
271        let mut g = Graph::with_vertices(6);
272        g.add_edge(1, 3).unwrap(); // 0
273        g.add_edge(3, 5).unwrap(); // 1
274
275        let r = subgraph_from_edges(&g, &[0, 1], true).unwrap();
276        assert_eq!(r.graph.vcount(), 3);
277        assert_eq!(r.vertex_invmap, vec![1, 3, 5]);
278        assert_eq!(r.vertex_map[1], 0);
279        assert_eq!(r.vertex_map[3], 1);
280        assert_eq!(r.vertex_map[5], 2);
281        // Unmapped vertices
282        assert_eq!(r.vertex_map[0], u32::MAX);
283        assert_eq!(r.vertex_map[2], u32::MAX);
284        assert_eq!(r.vertex_map[4], u32::MAX);
285    }
286
287    #[test]
288    fn all_edges_selected() {
289        let mut g = Graph::with_vertices(4);
290        g.add_edge(0, 1).unwrap();
291        g.add_edge(1, 2).unwrap();
292        g.add_edge(2, 3).unwrap();
293
294        let r = subgraph_from_edges(&g, &[0, 1, 2], true).unwrap();
295        assert_eq!(r.graph.vcount(), 4);
296        assert_eq!(r.graph.ecount(), 3);
297    }
298
299    #[test]
300    fn self_loop_edge() {
301        let mut g = Graph::with_vertices(3);
302        g.add_edge(0, 0).unwrap(); // 0: self-loop
303        g.add_edge(0, 1).unwrap(); // 1
304        g.add_edge(1, 2).unwrap(); // 2
305
306        let r = subgraph_from_edges(&g, &[0], true).unwrap();
307        assert_eq!(r.graph.vcount(), 1);
308        assert_eq!(r.graph.ecount(), 1);
309        assert_eq!(r.vertex_invmap, vec![0]);
310    }
311
312    #[test]
313    fn edge_map_ordering() {
314        let mut g = Graph::with_vertices(4);
315        g.add_edge(0, 1).unwrap(); // 0
316        g.add_edge(1, 2).unwrap(); // 1
317        g.add_edge(2, 3).unwrap(); // 2
318
319        // Request in reverse order — result should still be sorted
320        let r = subgraph_from_edges(&g, &[2, 0], true).unwrap();
321        assert_eq!(r.edge_map, vec![0, 2]);
322    }
323}