Skip to main content

rust_igraph/algorithms/constructors/
tree_from_parent_vector.rs

1//! Parent-vector tree/forest decoder (ALGO-CN-017).
2//!
3//! Counterpart of `igraph_tree_from_parent_vector()` in
4//! `references/igraph/src/constructors/trees.c:70-161`.
5//!
6//! A *parent vector* of length `n` represents a rooted forest on `n`
7//! vertices: `parents[v]` is the vertex id of `v`'s parent, or any
8//! negative value to mark `v` as a root (no parent). The decoder walks
9//! every vertex once and follows its parent chain, emitting one edge per
10//! `(v, parent[v])` link. Per-round colouring detects both self-loops
11//! (`parent[v] == v`) and longer cycles (`v → … → v`) in linear time.
12//!
13//! [`TreeMode`] is re-exported from [`crate::kary_tree`] and controls
14//! arc orientation:
15//!
16//! * [`TreeMode::Out`] — directed, every arc flows **parent → child**.
17//! * [`TreeMode::In`] — directed, every arc flows **child → parent**.
18//! * [`TreeMode::Undirected`] — undirected, raw `(child, parent)`
19//!   stored as canonical `(min, max)` by `Graph::add_edges`.
20//!
21//! The output is guaranteed to be a forest (or a tree, when there is
22//! exactly one root); validation rejects negative-or-large parent ids,
23//! self-loops, and longer cycles up-front.
24//!
25//! Time complexity: `O(n)` where `n = parents.len()`. Each vertex is
26//! visited exactly once across all rounds (once as `i`, once as the
27//! cascading `u`).
28
29use crate::algorithms::constructors::kary_tree::TreeMode;
30use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
31
32/// Decode a parent vector into the tree or forest it represents.
33///
34/// `parents[v]` is the vertex id of `v`'s parent. Any negative value
35/// marks `v` as a root. Roots produce no edge (singletons), so the
36/// emitted graph has between `0` and `n - 1` edges.
37///
38/// # Errors
39///
40/// * [`IgraphError::InvalidArgument`] — some non-negative `parents[v]`
41///   is `>= parents.len()`, or the chain starting at some `v` forms a
42///   self-loop or longer cycle, or `parents.len()` overflows [`u32`].
43///
44/// # Examples
45///
46/// ```
47/// use rust_igraph::{TreeMode, tree_from_parent_vector};
48///
49/// // Two roots (-1) produce a forest of two paths.
50/// let g = tree_from_parent_vector(&[-1, 0, 1, -1, 3], TreeMode::Out).unwrap();
51/// assert_eq!(g.vcount(), 5);
52/// assert_eq!(g.ecount(), 3);
53/// assert!(g.is_directed());
54/// ```
55pub 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    // 0 means "never seen"; round counter c starts at 1.
71    let mut seen: Vec<u32> = vec![0; n_usize];
72    // Per upstream, hint the allocator with 2*(n-1) for small graphs,
73    // but reserve only 2048 entries when n > 1024 so extracting a
74    // small subtree of a huge graph does not over-allocate. Our edge
75    // tuples carry 8 bytes each rather than two ints, so divide by 2
76    // when scaling to the (VertexId, VertexId) buffer.
77    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; // v is a root
97            }
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            // Bounds above guarantee 0 <= u_raw < n <= u32::MAX. The
104            // try_from cannot fail in practice; map_err keeps the path
105            // free of unwrap/expect per project conventions.
106            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; // u was seen in a previous round, stop traversal
131            }
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        // Acyclic when treated as undirected.
176        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        // From references/igraph/tests/unit/igraph_tree_from_parent_vector.out:
194        // parents = [4, 4, 1, -2, 3], OUT mode emits (parent, child).
195        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        // Expected ordered emission (matches upstream .out):
200        // (4,0) (3,4) (4,1) (1,2)
201        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        // Expected ordered emission (child, parent):
213        // (0,4) (4,3) (1,4) (2,1)
214        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        // After (min, max) canonicalisation by Graph::add_edges:
226        // (0,4) (3,4) (1,4) (1,2)
227        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        // [-1, 4, 1, -2, 3] — vertex 0 and vertex 3 are both roots.
236        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        // parents[0] = 0 ⇒ self-loop.
277        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        // 0 → 4 → 3 → 0 forms a 3-cycle.
292        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        // Vertex 0 is root, every other vertex's parent is 0.
311        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        // parents = [-1, 0, 1, 2, 3] ⇒ chain 0 — 1 — 2 — 3 — 4
325        // (undirected so we can read full degree via neighbors()).
326        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    /// Build a guaranteed-valid parent vector: every non-root entry
344    /// points to a strictly-smaller vertex id, which is a topological
345    /// order ⇒ cycle-free by construction.
346    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()); // vertex 0 is always a root
350            for i in 1..n {
351                // either a root (negative) or a smaller vertex id.
352                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            // ecount = number of non-root entries
383            let non_roots = parents.iter().filter(|&&p| p >= 0).count();
384            prop_assert_eq!(g.ecount(), non_roots);
385            // Acyclic (undirected sense): union-find without same-root collisions.
386            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; // bad >= 100 > n
448            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; // self-loop on vertex 0
456            let err = tree_from_parent_vector(&parents, TreeMode::Out).unwrap_err();
457            prop_assert!(matches!(err, IgraphError::InvalidArgument(_)));
458        }
459    }
460}