1use crate::algorithms::constructors::kary_tree::TreeMode;
30use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
31
32pub fn tree_from_parent_vector(parents: &[i64], mode: TreeMode) -> IgraphResult<Graph> {
56 let n_usize = parents.len();
57 let n = u32::try_from(n_usize).map_err(|_| {
58 IgraphError::InvalidArgument(
59 "tree_from_parent_vector: parents.len() overflows u32".to_string(),
60 )
61 })?;
62
63 let directed = !matches!(mode, TreeMode::Undirected);
64 let intree = !matches!(mode, TreeMode::Out);
65
66 if n == 0 {
67 return Graph::new(0, directed);
68 }
69
70 let mut seen: Vec<u32> = vec![0; n_usize];
72 let edge_cap = if n_usize > 1024 { 1024 } else { n_usize - 1 };
78 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(edge_cap);
79
80 let n_i64 = i64::from(n);
81 let mut c: u32 = 1;
82 for i in 0..n {
83 if seen[i as usize] != 0 {
84 c = c.checked_add(1).ok_or_else(|| {
85 IgraphError::InvalidArgument(
86 "tree_from_parent_vector: round counter overflows u32".to_string(),
87 )
88 })?;
89 continue;
90 }
91 let mut v = i;
92 loop {
93 seen[v as usize] = c;
94 let u_raw = parents[v as usize];
95 if u_raw < 0 {
96 break; }
98 if u_raw >= n_i64 {
99 return Err(IgraphError::InvalidArgument(format!(
100 "tree_from_parent_vector: invalid parent id {u_raw} for vertex {v} (must be < {n})",
101 )));
102 }
103 let u = u32::try_from(u_raw).map_err(|_| {
107 IgraphError::InvalidArgument(format!(
108 "tree_from_parent_vector: invalid parent id {u_raw} for vertex {v}",
109 ))
110 })?;
111
112 if intree {
113 edges.push((v, u));
114 } else {
115 edges.push((u, v));
116 }
117
118 if seen[u as usize] != 0 {
119 if seen[u as usize] == c {
120 return Err(IgraphError::InvalidArgument(if u == v {
121 format!(
122 "tree_from_parent_vector: self-loop at vertex {v} (parents[{v}] = {v})",
123 )
124 } else {
125 format!(
126 "tree_from_parent_vector: cycle detected reaching vertex {u} from vertex {v}",
127 )
128 }));
129 }
130 break; }
132
133 v = u;
134 }
135 c = c.checked_add(1).ok_or_else(|| {
136 IgraphError::InvalidArgument(
137 "tree_from_parent_vector: round counter overflows u32".to_string(),
138 )
139 })?;
140 }
141
142 let mut g = Graph::new(n, directed)?;
143 g.add_edges(edges)?;
144 Ok(g)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use std::collections::BTreeSet;
151
152 fn collect_edges_ordered(g: &Graph) -> Vec<(u32, u32)> {
153 let m = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
154 (0..m).map(|e| g.edge(e).expect("edge in range")).collect()
155 }
156
157 fn collect_edges_canonical(g: &Graph) -> BTreeSet<(u32, u32)> {
158 let m = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
159 (0..m)
160 .map(|e| g.edge(e).expect("edge"))
161 .map(|(a, b)| if a <= b { (a, b) } else { (b, a) })
162 .collect()
163 }
164
165 fn uf_find(parent: &mut [u32], mut node: u32) -> u32 {
166 while parent[node as usize] != node {
167 let grand = parent[parent[node as usize] as usize];
168 parent[node as usize] = grand;
169 node = grand;
170 }
171 node
172 }
173
174 fn is_forest(g: &Graph) -> bool {
175 let vcount = g.vcount();
177 let mut parent: Vec<u32> = (0..vcount).collect();
178 let m = u32::try_from(g.ecount()).expect("m fits u32");
179 for e in 0..m {
180 let (a, b) = g.edge(e).expect("edge");
181 let ra = uf_find(&mut parent, a);
182 let rb = uf_find(&mut parent, b);
183 if ra == rb {
184 return false;
185 }
186 parent[ra as usize] = rb;
187 }
188 true
189 }
190
191 #[test]
192 fn upstream_fixture_out_mode() {
193 let g = tree_from_parent_vector(&[4, 4, 1, -2, 3], TreeMode::Out).expect("ok");
196 assert_eq!(g.vcount(), 5);
197 assert_eq!(g.ecount(), 4);
198 assert!(g.is_directed());
199 let expected = vec![(4, 0), (3, 4), (4, 1), (1, 2)];
202 assert_eq!(collect_edges_ordered(&g), expected);
203 assert!(is_forest(&g));
204 }
205
206 #[test]
207 fn upstream_fixture_in_mode() {
208 let g = tree_from_parent_vector(&[4, 4, 1, -2, 3], TreeMode::In).expect("ok");
209 assert_eq!(g.vcount(), 5);
210 assert_eq!(g.ecount(), 4);
211 assert!(g.is_directed());
212 let expected = vec![(0, 4), (4, 3), (1, 4), (2, 1)];
215 assert_eq!(collect_edges_ordered(&g), expected);
216 assert!(is_forest(&g));
217 }
218
219 #[test]
220 fn upstream_fixture_undirected_mode() {
221 let g = tree_from_parent_vector(&[4, 4, 1, -2, 3], TreeMode::Undirected).expect("ok");
222 assert_eq!(g.vcount(), 5);
223 assert_eq!(g.ecount(), 4);
224 assert!(!g.is_directed());
225 let edges = collect_edges_canonical(&g);
228 let expected: BTreeSet<(u32, u32)> = [(0, 4), (3, 4), (1, 4), (1, 2)].into_iter().collect();
229 assert_eq!(edges, expected);
230 assert!(is_forest(&g));
231 }
232
233 #[test]
234 fn upstream_fixture_forest_two_roots() {
235 let g = tree_from_parent_vector(&[-1, 4, 1, -2, 3], TreeMode::Out).expect("ok");
237 assert_eq!(g.vcount(), 5);
238 assert_eq!(g.ecount(), 3);
239 assert!(g.is_directed());
240 let expected = vec![(4, 1), (3, 4), (1, 2)];
241 assert_eq!(collect_edges_ordered(&g), expected);
242 assert!(is_forest(&g));
243 }
244
245 #[test]
246 fn null_graph_empty_parents() {
247 let g = tree_from_parent_vector(&[], TreeMode::Out).expect("ok");
248 assert_eq!(g.vcount(), 0);
249 assert_eq!(g.ecount(), 0);
250 assert!(g.is_directed());
251 }
252
253 #[test]
254 fn edgeless_graph_all_roots() {
255 let g = tree_from_parent_vector(&[-1, -1, -1, -1, -1], TreeMode::Out).expect("ok");
256 assert_eq!(g.vcount(), 5);
257 assert_eq!(g.ecount(), 0);
258 }
259
260 #[test]
261 fn undirected_isolated_roots() {
262 let g = tree_from_parent_vector(&[-3, -1], TreeMode::Undirected).expect("ok");
263 assert_eq!(g.vcount(), 2);
264 assert_eq!(g.ecount(), 0);
265 assert!(!g.is_directed());
266 }
267
268 #[test]
269 fn invalid_parent_id_out_of_range() {
270 let err = tree_from_parent_vector(&[5, 4, 1, -2, 3], TreeMode::Out).unwrap_err();
271 assert!(matches!(err, IgraphError::InvalidArgument(_)));
272 }
273
274 #[test]
275 fn invalid_self_loop_rejected() {
276 let err = tree_from_parent_vector(&[0, 4, 1, -2, 3], TreeMode::Out).unwrap_err();
278 match err {
279 IgraphError::InvalidArgument(msg) => {
280 assert!(
281 msg.contains("self-loop"),
282 "expected self-loop msg, got {msg}"
283 );
284 }
285 other => panic!("expected InvalidArgument, got {other:?}"),
286 }
287 }
288
289 #[test]
290 fn invalid_longer_cycle_rejected() {
291 let err = tree_from_parent_vector(&[4, 4, 1, 0, 3], TreeMode::Out).unwrap_err();
293 match err {
294 IgraphError::InvalidArgument(msg) => {
295 assert!(msg.contains("cycle"), "expected cycle msg, got {msg}");
296 }
297 other => panic!("expected InvalidArgument, got {other:?}"),
298 }
299 }
300
301 #[test]
302 fn singleton_root() {
303 let g = tree_from_parent_vector(&[-1], TreeMode::Out).expect("ok");
304 assert_eq!(g.vcount(), 1);
305 assert_eq!(g.ecount(), 0);
306 }
307
308 #[test]
309 fn star_centred_on_zero() {
310 let g = tree_from_parent_vector(&[-1, 0, 0, 0, 0], TreeMode::Undirected).expect("ok");
312 assert_eq!(g.vcount(), 5);
313 assert_eq!(g.ecount(), 4);
314 let deg0 = g.neighbors(0).expect("neighbors").len();
315 assert_eq!(deg0, 4, "centre has degree 4");
316 for v in 1..5u32 {
317 let d = g.neighbors(v).expect("neighbors").len();
318 assert_eq!(d, 1, "leaf {v} has degree 1");
319 }
320 }
321
322 #[test]
323 fn chain_from_parent_vector() {
324 let g = tree_from_parent_vector(&[-1, 0, 1, 2, 3], TreeMode::Undirected).expect("ok");
327 assert_eq!(g.vcount(), 5);
328 assert_eq!(g.ecount(), 4);
329 for v in 0..5u32 {
330 let deg = g.neighbors(v).expect("neighbors").len();
331 let expected = if v == 0 || v == 4 { 1 } else { 2 };
332 assert_eq!(deg, expected, "vertex {v} degree");
333 }
334 }
335}
336
337#[cfg(all(test, feature = "proptest-harness"))]
338mod proptest_tests {
339 use super::*;
340 use proptest::prelude::*;
341 use std::collections::BTreeSet;
342
343 fn arb_valid_parents() -> impl Strategy<Value = Vec<i64>> {
347 (1usize..30).prop_flat_map(|n| {
348 let mut strategies: Vec<BoxedStrategy<i64>> = Vec::with_capacity(n);
349 strategies.push(Just(-1_i64).boxed()); for i in 1..n {
351 strategies
353 .push(prop_oneof![Just(-1_i64), (0i64..(i as i64)).prop_map(|p| p),].boxed());
354 }
355 strategies
356 })
357 }
358
359 fn arb_mode() -> impl Strategy<Value = TreeMode> {
360 prop_oneof![
361 Just(TreeMode::Out),
362 Just(TreeMode::In),
363 Just(TreeMode::Undirected),
364 ]
365 }
366
367 fn uf_find(parent: &mut [u32], mut node: u32) -> u32 {
368 while parent[node as usize] != node {
369 let grand = parent[parent[node as usize] as usize];
370 parent[node as usize] = grand;
371 node = grand;
372 }
373 node
374 }
375
376 proptest! {
377 #[test]
378 fn always_a_forest((parents, mode) in (arb_valid_parents(), arb_mode())) {
379 let g = tree_from_parent_vector(&parents, mode).unwrap();
380 let n = u32::try_from(parents.len()).unwrap();
381 prop_assert_eq!(g.vcount(), n);
382 let non_roots = parents.iter().filter(|&&p| p >= 0).count();
384 prop_assert_eq!(g.ecount(), non_roots);
385 let mut uf: Vec<u32> = (0..n).collect();
387 let m = u32::try_from(g.ecount()).unwrap();
388 for e in 0..m {
389 let (a, b) = g.edge(e).unwrap();
390 let ra = uf_find(&mut uf, a);
391 let rb = uf_find(&mut uf, b);
392 prop_assert_ne!(ra, rb, "found cycle in supposedly forest output");
393 uf[ra as usize] = rb;
394 }
395 }
396
397 #[test]
398 fn no_self_loops((parents, mode) in (arb_valid_parents(), arb_mode())) {
399 let g = tree_from_parent_vector(&parents, mode).unwrap();
400 let m = u32::try_from(g.ecount()).unwrap();
401 for e in 0..m {
402 let (a, b) = g.edge(e).unwrap();
403 prop_assert_ne!(a, b);
404 }
405 }
406
407 #[test]
408 fn no_duplicate_undirected_edges(parents in arb_valid_parents()) {
409 let g = tree_from_parent_vector(&parents, TreeMode::Undirected).unwrap();
410 let m = u32::try_from(g.ecount()).unwrap();
411 let mut seen: BTreeSet<(u32, u32)> = BTreeSet::new();
412 for e in 0..m {
413 let (a, b) = g.edge(e).unwrap();
414 let canon = if a <= b { (a, b) } else { (b, a) };
415 prop_assert!(seen.insert(canon), "duplicate edge {:?}", canon);
416 }
417 }
418
419 #[test]
420 fn directedness_matches_mode((parents, mode) in (arb_valid_parents(), arb_mode())) {
421 let g = tree_from_parent_vector(&parents, mode).unwrap();
422 match mode {
423 TreeMode::Out | TreeMode::In => prop_assert!(g.is_directed()),
424 TreeMode::Undirected => prop_assert!(!g.is_directed()),
425 }
426 }
427
428 #[test]
429 fn in_and_out_modes_reverse_each_other(parents in arb_valid_parents()) {
430 let g_out = tree_from_parent_vector(&parents, TreeMode::Out).unwrap();
431 let g_in = tree_from_parent_vector(&parents, TreeMode::In).unwrap();
432 prop_assert_eq!(g_out.ecount(), g_in.ecount());
433 let m = u32::try_from(g_out.ecount()).unwrap();
434 for e in 0..m {
435 let (a, b) = g_out.edge(e).unwrap();
436 let (c, d) = g_in.edge(e).unwrap();
437 prop_assert_eq!((a, b), (d, c), "edge {} not reversed", e);
438 }
439 }
440
441 #[test]
442 fn out_of_range_parent_errors(
443 n in 3u32..15,
444 bad in 100i64..200,
445 ) {
446 let mut parents = vec![-1_i64; n as usize];
447 parents[0] = bad; let err = tree_from_parent_vector(&parents, TreeMode::Out).unwrap_err();
449 prop_assert!(matches!(err, IgraphError::InvalidArgument(_)));
450 }
451
452 #[test]
453 fn self_loop_errors(n in 1u32..15) {
454 let mut parents = vec![-1_i64; n as usize];
455 parents[0] = 0; let err = tree_from_parent_vector(&parents, TreeMode::Out).unwrap_err();
457 prop_assert!(matches!(err, IgraphError::InvalidArgument(_)));
458 }
459 }
460}