rust_igraph/algorithms/operators/
induced_subgraph.rs1use crate::core::error::IgraphError;
7use crate::core::{Graph, IgraphResult, VertexId};
8
9#[derive(Debug, Clone)]
11pub struct InducedSubgraphResult {
12 pub graph: Graph,
14 pub map: Vec<u32>,
17 pub invmap: Vec<VertexId>,
19}
20
21pub fn induced_subgraph(graph: &Graph, vids: &[VertexId]) -> IgraphResult<InducedSubgraphResult> {
58 let n = graph.vcount();
59
60 for &v in vids {
62 if v >= n {
63 return Err(IgraphError::InvalidArgument(format!(
64 "vertex id {v} is out of range [0, {n})"
65 )));
66 }
67 }
68
69 let mut map = vec![u32::MAX; n as usize];
71 let mut invmap: Vec<VertexId> = Vec::new();
72
73 let mut sorted_vids: Vec<VertexId> = vids.to_vec();
75 sorted_vids.sort_unstable();
76 sorted_vids.dedup();
77
78 let no_of_new_nodes = sorted_vids.len();
79 invmap.reserve(no_of_new_nodes);
80
81 for (new_id, &old_id) in sorted_vids.iter().enumerate() {
82 #[allow(clippy::cast_possible_truncation)]
84 let new_id_u32 = new_id as u32;
85 map[old_id as usize] = new_id_u32;
86 invmap.push(old_id);
87 }
88
89 let directed = graph.is_directed();
91 let mut new_edges: Vec<(VertexId, VertexId)> = Vec::new();
92
93 for (new_vid_idx, &old_vid) in invmap.iter().enumerate() {
94 #[allow(clippy::cast_possible_truncation)]
95 let new_vid = new_vid_idx as u32;
96 let incident = graph.incident(old_vid)?;
97
98 if directed {
99 for eid in incident {
100 let src = graph.edge_source(eid)?;
101 if src != old_vid {
102 continue;
103 }
104 let tgt = graph.edge_target(eid)?;
105 let new_tgt = map[tgt as usize];
106 if new_tgt == u32::MAX {
107 continue;
108 }
109 new_edges.push((new_vid, new_tgt));
110 }
111 } else {
112 let mut skip_loop = false;
117 for eid in incident {
118 let src = graph.edge_source(eid)?;
119 if src != old_vid {
120 continue;
121 }
122 let tgt = graph.edge_target(eid)?;
123 let new_tgt = map[tgt as usize];
124 if new_tgt == u32::MAX {
125 continue;
126 }
127 if new_vid == new_tgt {
128 skip_loop = !skip_loop;
129 if skip_loop {
130 continue;
131 }
132 }
133 new_edges.push((new_vid, new_tgt));
134 }
135 }
136 }
137
138 #[allow(clippy::cast_possible_truncation)]
140 let new_node_count = no_of_new_nodes as u32;
141 let mut result_graph = Graph::new(new_node_count, directed)?;
142 result_graph.add_edges(new_edges)?;
143
144 Ok(InducedSubgraphResult {
145 graph: result_graph,
146 map,
147 invmap,
148 })
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_empty_vids() {
157 let g = Graph::with_vertices(5);
158 let result = induced_subgraph(&g, &[]).unwrap();
159 assert_eq!(result.graph.vcount(), 0);
160 assert_eq!(result.graph.ecount(), 0);
161 assert!(result.invmap.is_empty());
162 }
163
164 #[test]
165 fn test_all_vertices() {
166 let mut g = Graph::with_vertices(4);
167 g.add_edge(0, 1).unwrap();
168 g.add_edge(1, 2).unwrap();
169 g.add_edge(2, 3).unwrap();
170 let result = induced_subgraph(&g, &[0, 1, 2, 3]).unwrap();
171 assert_eq!(result.graph.vcount(), 4);
172 assert_eq!(result.graph.ecount(), 3);
173 assert_eq!(result.invmap, vec![0, 1, 2, 3]);
174 }
175
176 #[test]
177 fn test_partial_subgraph() {
178 let mut g = Graph::with_vertices(5);
179 g.add_edge(0, 1).unwrap();
180 g.add_edge(1, 2).unwrap();
181 g.add_edge(2, 3).unwrap();
182 g.add_edge(3, 4).unwrap();
183 g.add_edge(0, 4).unwrap();
184
185 let result = induced_subgraph(&g, &[1, 2, 3]).unwrap();
186 assert_eq!(result.graph.vcount(), 3);
187 assert_eq!(result.graph.ecount(), 2);
188 assert_eq!(result.invmap, vec![1, 2, 3]);
189 assert_eq!(result.map[0], u32::MAX);
190 assert_eq!(result.map[1], 0);
191 assert_eq!(result.map[2], 1);
192 assert_eq!(result.map[3], 2);
193 assert_eq!(result.map[4], u32::MAX);
194 }
195
196 #[test]
197 fn test_duplicate_vids() {
198 let mut g = Graph::with_vertices(4);
199 g.add_edge(0, 1).unwrap();
200 g.add_edge(1, 2).unwrap();
201 let result = induced_subgraph(&g, &[0, 1, 1, 0]).unwrap();
202 assert_eq!(result.graph.vcount(), 2);
203 assert_eq!(result.graph.ecount(), 1);
204 }
205
206 #[test]
207 fn test_unordered_vids() {
208 let mut g = Graph::with_vertices(4);
209 g.add_edge(0, 1).unwrap();
210 g.add_edge(1, 2).unwrap();
211 g.add_edge(2, 3).unwrap();
212 let result = induced_subgraph(&g, &[3, 1, 0]).unwrap();
214 assert_eq!(result.graph.vcount(), 3);
215 assert_eq!(result.invmap, vec![0, 1, 3]);
216 assert_eq!(result.graph.ecount(), 1);
218 }
219
220 #[test]
221 fn test_invalid_vertex() {
222 let g = Graph::with_vertices(3);
223 let result = induced_subgraph(&g, &[0, 5]);
224 assert!(result.is_err());
225 }
226
227 #[test]
228 fn test_directed() {
229 let mut g = Graph::new(4, true).unwrap();
230 g.add_edge(0, 1).unwrap();
231 g.add_edge(1, 0).unwrap();
232 g.add_edge(1, 2).unwrap();
233 g.add_edge(2, 3).unwrap();
234
235 let result = induced_subgraph(&g, &[0, 1, 2]).unwrap();
236 assert_eq!(result.graph.vcount(), 3);
237 assert_eq!(result.graph.ecount(), 3); assert!(result.graph.is_directed());
239 }
240
241 #[test]
242 fn test_self_loop() {
243 let mut g = Graph::with_vertices(3);
244 g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap();
246 g.add_edge(1, 2).unwrap();
247
248 let result = induced_subgraph(&g, &[0, 1]).unwrap();
249 assert_eq!(result.graph.vcount(), 2);
250 assert_eq!(result.graph.ecount(), 2); }
252
253 #[test]
254 fn test_isolated_vertices() {
255 let mut g = Graph::with_vertices(5);
256 g.add_edge(0, 1).unwrap();
257 g.add_edge(2, 3).unwrap();
258
259 let result = induced_subgraph(&g, &[1, 3, 4]).unwrap();
261 assert_eq!(result.graph.vcount(), 3);
262 assert_eq!(result.graph.ecount(), 0);
263 }
264
265 #[test]
266 fn test_single_vertex() {
267 let mut g = Graph::with_vertices(3);
268 g.add_edge(0, 1).unwrap();
269 let result = induced_subgraph(&g, &[1]).unwrap();
270 assert_eq!(result.graph.vcount(), 1);
271 assert_eq!(result.graph.ecount(), 0);
272 assert_eq!(result.invmap, vec![1]);
273 }
274
275 #[test]
276 fn test_complete_subgraph() {
277 let mut g = Graph::with_vertices(4);
279 g.add_edge(0, 1).unwrap();
280 g.add_edge(0, 2).unwrap();
281 g.add_edge(0, 3).unwrap();
282 g.add_edge(1, 2).unwrap();
283 g.add_edge(1, 3).unwrap();
284 g.add_edge(2, 3).unwrap();
285
286 let result = induced_subgraph(&g, &[0, 1, 2]).unwrap();
287 assert_eq!(result.graph.vcount(), 3);
288 assert_eq!(result.graph.ecount(), 3); }
290
291 #[test]
292 fn test_directed_self_loop() {
293 let mut g = Graph::new(3, true).unwrap();
294 g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap();
296 g.add_edge(1, 2).unwrap();
297
298 let result = induced_subgraph(&g, &[0, 1]).unwrap();
299 assert_eq!(result.graph.vcount(), 2);
300 assert_eq!(result.graph.ecount(), 2); }
302
303 #[test]
304 fn test_multi_edges() {
305 let mut g = Graph::with_vertices(3);
306 g.add_edge(0, 1).unwrap();
307 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
309
310 let result = induced_subgraph(&g, &[0, 1]).unwrap();
311 assert_eq!(result.graph.vcount(), 2);
312 assert_eq!(result.graph.ecount(), 2); }
314
315 #[test]
316 fn test_map_consistency() {
317 let mut g = Graph::with_vertices(6);
318 g.add_edge(0, 2).unwrap();
319 g.add_edge(2, 4).unwrap();
320 g.add_edge(4, 0).unwrap();
321
322 let result = induced_subgraph(&g, &[0, 2, 4]).unwrap();
323 #[allow(clippy::cast_possible_truncation)]
325 for (new_id, &old_id) in result.invmap.iter().enumerate() {
326 assert_eq!(result.map[old_id as usize], new_id as u32);
327 }
328 #[allow(clippy::cast_possible_truncation)]
329 for (old_id, &new_id) in result.map.iter().enumerate() {
330 if new_id != u32::MAX {
331 assert_eq!(result.invmap[new_id as usize], old_id as u32);
332 }
333 }
334 }
335}