Skip to main content

contree/tree/
mod.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4/// Why a tree could not be walked or did not hold its invariant.
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum TreeError {
7    /// The tree has no nodes: `fit` has not run, or it failed.
8    Empty,
9    /// A node breaks the leaf/internal invariant. See [`Tree::validate`].
10    Malformed { node: usize, reason: &'static str },
11    /// A node tests a feature the instance does not have.
12    FeatureOutOfRange {
13        node: usize,
14        feature: usize,
15        n_features: usize,
16    },
17    /// A batch whose length is not a whole number of rows.
18    RaggedInput { len: usize, n_features: usize },
19}
20
21impl fmt::Display for TreeError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            TreeError::Empty => write!(f, "the tree is empty"),
25            TreeError::Malformed { node, reason } => write!(f, "node {node}: {reason}"),
26            TreeError::FeatureOutOfRange {
27                node,
28                feature,
29                n_features,
30            } => write!(
31                f,
32                "node {node} tests feature {feature}, but the instance has {n_features}"
33            ),
34            TreeError::RaggedInput { len, n_features } => write!(
35                f,
36                "{len} values is not a whole number of rows of {n_features} features"
37            ),
38        }
39    }
40}
41
42impl std::error::Error for TreeError {}
43
44/// The content of a tree node.
45#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
46pub struct NodeInfos {
47    /// The feature tested by an internal node; `None` for a leaf.
48    pub feature: Option<usize>,
49    /// Misclassifications of the subtree rooted here.
50    pub error: usize,
51    /// The threshold of an internal node: instances with
52    /// `x[feature] <= split` go left.
53    pub split: Option<f64>,
54    /// The class predicted by a leaf (the majority class of its instances).
55    pub label: Option<usize>,
56}
57
58impl Default for NodeInfos {
59    fn default() -> Self {
60        NodeInfos::new()
61    }
62}
63
64impl NodeInfos {
65    pub fn new() -> NodeInfos {
66        NodeInfos {
67            feature: None,
68            error: usize::MAX,
69            split: None,
70            label: None,
71        }
72    }
73}
74
75/// A node of a [`Tree`] arena. Child indices of `0` mean "no child".
76#[derive(Copy, Clone, Serialize, Deserialize, Debug, Default)]
77pub struct TreeNode {
78    /// What the node tests or predicts.
79    pub value: NodeInfos,
80    /// The node's own index in the arena.
81    pub index: usize,
82    /// Arena index of the left child (`x[feature] <= split`).
83    pub left: usize,
84    /// Arena index of the right child.
85    pub right: usize,
86}
87
88impl TreeNode {
89    pub fn new(value: NodeInfos) -> TreeNode {
90        TreeNode {
91            value,
92            index: 0,
93            left: 0,
94            right: 0,
95        }
96    }
97}
98
99/// A binary decision tree stored as an arena of nodes, the root at index 0.
100#[derive(Clone, Serialize, Deserialize, Debug)]
101pub struct Tree {
102    tree: Vec<TreeNode>,
103}
104
105impl Default for Tree {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl Tree {
112    pub fn new() -> Self {
113        Tree { tree: Vec::new() }
114    }
115
116    pub fn with_capacity(capacity: usize) -> Self {
117        Tree {
118            tree: Vec::with_capacity(capacity),
119        }
120    }
121
122    pub fn is_empty(&self) -> bool {
123        self.tree.is_empty()
124    }
125
126    pub fn len(&self) -> usize {
127        self.tree.len()
128    }
129
130    /// Appends `node` as the left or right child of `parent` and returns its
131    /// index. The first node added becomes the root and `parent` is ignored.
132    pub fn add_node(&mut self, parent: usize, is_left: bool, mut node: TreeNode) -> usize {
133        node.index = self.tree.len();
134        self.tree.push(node);
135        let position = self.tree.len() - 1;
136        if position == 0 {
137            return position;
138        }
139        if let Some(parent_node) = self.tree.get_mut(parent) {
140            if is_left {
141                parent_node.left = position
142            } else {
143                parent_node.right = position
144            }
145        };
146        position
147    }
148
149    pub fn add_root(&mut self, root: TreeNode) -> usize {
150        self.add_node(0, false, root)
151    }
152
153    pub fn add_left_node(&mut self, parent: usize, node: TreeNode) -> usize {
154        self.add_node(parent, true, node)
155    }
156    pub fn add_right_node(&mut self, parent: usize, node: TreeNode) -> usize {
157        self.add_node(parent, false, node)
158    }
159
160    pub fn create_child(&mut self, parent: usize, left: bool) -> usize {
161        self.add_node(parent, left, TreeNode::default())
162    }
163
164    pub fn get_root_index(&self) -> usize {
165        0
166    }
167
168    pub fn get_node(&self, index: usize) -> Option<&TreeNode> {
169        self.tree.get(index)
170    }
171
172    pub fn get_node_mut(&mut self, index: usize) -> Option<&mut TreeNode> {
173        self.tree.get_mut(index)
174    }
175
176    pub fn get_left_child(&self, node: &TreeNode) -> Option<&TreeNode> {
177        if node.left == 0 {
178            None
179        } else {
180            self.tree.get(node.left)
181        }
182    }
183    pub fn get_left_child_mut(&mut self, node: &TreeNode) -> Option<&mut TreeNode> {
184        if node.left == 0 {
185            None
186        } else {
187            self.tree.get_mut(node.left)
188        }
189    }
190
191    pub fn get_right_child(&self, node: &TreeNode) -> Option<&TreeNode> {
192        if node.right == 0 {
193            None
194        } else {
195            self.tree.get(node.right)
196        }
197    }
198    pub fn get_right_child_mut(&mut self, node: &TreeNode) -> Option<&mut TreeNode> {
199        if node.right == 0 {
200            None
201        } else {
202            self.tree.get_mut(node.right)
203        }
204    }
205
206    /// A complete tree skeleton of the given depth, with empty nodes.
207    pub fn empty_tree(depth: usize) -> Tree {
208        let mut tree = Tree::new();
209        let value = NodeInfos::new();
210        let node = TreeNode::new(value);
211        let root = tree.add_root(node);
212        Self::build_tree_recurse(&mut tree, root, depth);
213        tree
214    }
215
216    fn build_tree_recurse(tree: &mut Tree, parent: usize, depth: usize) {
217        if depth == 0 {
218            if let Some(parent_node) = tree.get_node_mut(parent) {
219                parent_node.left = 0;
220                parent_node.right = 0;
221            }
222        } else {
223            let value = NodeInfos::new();
224            let node = TreeNode::new(value);
225            let left = tree.add_node(parent, true, node);
226            Self::build_tree_recurse(tree, left, depth - 1);
227            let node = TreeNode::new(value);
228            let right = tree.add_node(parent, false, node);
229            Self::build_tree_recurse(tree, right, depth - 1);
230        }
231    }
232
233    pub fn root_details(&self) -> NodeInfos {
234        self.get_node(self.get_root_index())
235            .map(|node| node.value)
236            .unwrap_or_default()
237    }
238
239    pub fn node_details(&self, index: usize) -> NodeInfos {
240        self.get_node(index)
241            .map(|node| node.value)
242            .unwrap_or_default()
243    }
244
245    pub fn root_error(&self) -> usize {
246        self.get_node(self.get_root_index())
247            .map(|node| node.value.error)
248            .unwrap_or(usize::MAX)
249    }
250
251    pub fn node_error(&self, index: usize) -> usize {
252        self.get_node(index)
253            .map(|node| node.value.error)
254            .unwrap_or(usize::MAX)
255    }
256
257    pub fn node_split(&self, index: usize) -> Option<f64> {
258        self.get_node(index)
259            .map(|node| node.value.split)
260            .unwrap_or(None)
261    }
262
263    pub fn root_label(&self) -> Option<usize> {
264        self.get_node(self.get_root_index())
265            .and_then(|node| node.value.label)
266    }
267
268    pub fn node_label(&self, index: usize) -> Option<usize> {
269        self.get_node(index).and_then(|node| node.value.label)
270    }
271
272    pub fn root_feature(&self) -> Option<usize> {
273        self.get_node(self.get_root_index())
274            .and_then(|node| node.value.feature)
275    }
276
277    pub fn root_split(&self) -> Option<f64> {
278        self.get_node(self.get_root_index())
279            .and_then(|node| node.value.split)
280    }
281
282    pub fn node_feature(&self, index: usize) -> Option<usize> {
283        self.get_node(index).and_then(|node| node.value.feature)
284    }
285
286    /// A builder that edits the node at `index`.
287    pub fn update_node(&mut self, index: usize) -> Option<NodeUpdater<'_>> {
288        self.get_node_mut(index).map(NodeUpdater::new)
289    }
290
291    pub fn update_root(&mut self) -> Option<NodeUpdater<'_>> {
292        self.get_node_mut(0).map(NodeUpdater::new)
293    }
294
295    /// `(left, right)` child indices of the node at `index`; `0` means none.
296    pub fn node_children(&self, index: usize) -> (usize, usize) {
297        self.get_node(index)
298            .map_or((0, 0), |node| (node.left, node.right))
299    }
300
301    /// Sets the `(error, label)` of the node at `index`.
302    pub fn update_leaf_node(&mut self, index: usize, error: (usize, usize)) -> &mut Self {
303        if let Some(updater) = self.update_node(index) {
304            updater.error(error.0).label(error.1);
305        }
306        self
307    }
308
309    /// Copies the subtree of `origin` rooted at `origin_index` onto the node at
310    /// `index`, creating or detaching children as needed.
311    pub fn update_subtree(&mut self, index: usize, origin: &Tree, origin_index: usize) {
312        let (left_index, right_index) = self.update_node(index).map_or((0, 0), |updater| {
313            updater
314                .value(origin.node_details(origin_index))
315                .get_children()
316        });
317
318        let (origin_left_index, origin_right_index) = origin.node_children(origin_index);
319
320        for (branch_value, (&source, mut dest)) in [origin_left_index, origin_right_index]
321            .iter()
322            .zip([left_index, right_index])
323            .enumerate()
324        {
325            if source > 0 {
326                if dest == 0 {
327                    dest = self.create_child(index, branch_value == 0);
328                }
329                self.update_subtree(dest, origin, source);
330            } else if dest != 0 {
331                // The source has no child here, but the destination (often a
332                // pre-allocated skeleton) still does: detach it.
333                self.detach_child(index, branch_value == 0);
334            }
335        }
336    }
337
338    fn detach_child(&mut self, index: usize, left: bool) {
339        if let Some(node) = self.get_node_mut(index) {
340            if left {
341                node.left = 0;
342            } else {
343                node.right = 0;
344            }
345        }
346    }
347
348    /// Collapses internal nodes that do not need their test: nodes marked as
349    /// leaves that still have children, and nodes whose two leaves predict
350    /// the same class.
351    pub fn clean_orphaned_nodes(&mut self) {
352        if self.is_empty() {
353            return;
354        }
355        self.cleanup_leaves(self.get_root_index())
356    }
357
358    fn cleanup_leaves(&mut self, index: usize) {
359        let (left, right) = self.node_children(index);
360        if left != 0 {
361            self.cleanup_leaves(left);
362        }
363        if right != 0 {
364            self.cleanup_leaves(right)
365        }
366
367        let has_children = left != 0 || right != 0;
368        if has_children && self.can_be_leaf(index) {
369            self.update_node(index).map(|updater| updater.leaf());
370            return;
371        }
372
373        // Two leaves that predict the same label make their parent's test
374        // pointless. Both labels must be set for the comparison to mean
375        // anything.
376        if has_children && self.is_leaf(left) && self.is_leaf(right) {
377            match (self.node_label(left), self.node_label(right)) {
378                (Some(left_label), Some(right_label)) if left_label == right_label => {
379                    self.update_node(index)
380                        .map(|updater| updater.label(left_label).leaf());
381                }
382                _ => {}
383            }
384        }
385    }
386
387    fn is_leaf(&self, index: usize) -> bool {
388        let (left, right) = self.node_children(index);
389        (left == 0) && (right == 0)
390    }
391
392    fn can_be_leaf(&self, index: usize) -> bool {
393        self.node_feature(index).is_none() && self.node_label(index).is_some()
394    }
395
396    /// The nodes of the arena, in insertion order. Index 0 is the root.
397    ///
398    /// Index 0 doubles as "no child": a child index of 0 means the node has no
399    /// child on that side.
400    pub fn nodes(&self) -> &[TreeNode] {
401        &self.tree
402    }
403
404    /// Rewrites the tree into its canonical form: a node is a leaf exactly
405    /// when its `feature` is `None`, and a leaf carries no split and no
406    /// children.
407    ///
408    /// The search marks leaves with `feature: Some(usize::MAX)` and the depth-2
409    /// solver leaves the children of its skeleton attached, so every tree goes
410    /// through this before it reaches a caller.
411    pub fn normalize_leaves(&mut self) {
412        if self.is_empty() {
413            return;
414        }
415        self.normalize_from(self.get_root_index());
416    }
417
418    fn normalize_from(&mut self, index: usize) {
419        let (feature, left, right) = match self.get_node(index) {
420            Some(node) => (node.value.feature, node.left, node.right),
421            None => return,
422        };
423
424        let is_leaf = match feature {
425            None => true,
426            Some(usize::MAX) => true,
427            Some(_) => left == 0 && right == 0,
428        };
429
430        if is_leaf {
431            if let Some(node) = self.get_node_mut(index) {
432                node.value.feature = None;
433                node.value.split = None;
434                node.left = 0;
435                node.right = 0;
436            }
437            return;
438        }
439
440        if left != 0 {
441            self.normalize_from(left);
442        }
443        if right != 0 {
444            self.normalize_from(right);
445        }
446    }
447
448    /// Checks the invariant `normalize_leaves` establishes.
449    ///
450    /// Every node is either a leaf (no feature, no split, no children, and a
451    /// label to predict) or an internal node with a feature, a finite split
452    /// threshold and two children.
453    pub fn validate(&self) -> Result<(), TreeError> {
454        if self.is_empty() {
455            return Err(TreeError::Empty);
456        }
457        self.validate_from(self.get_root_index(), 0)
458    }
459
460    fn validate_from(&self, index: usize, depth: usize) -> Result<(), TreeError> {
461        if depth > self.tree.len() {
462            return Err(TreeError::Malformed {
463                node: index,
464                reason: "the tree contains a cycle",
465            });
466        }
467        let node = self.get_node(index).ok_or(TreeError::Malformed {
468            node: index,
469            reason: "child index points outside the arena",
470        })?;
471        let (left, right) = (node.left, node.right);
472
473        match node.value.feature {
474            None => {
475                if left != 0 || right != 0 {
476                    return Err(TreeError::Malformed {
477                        node: index,
478                        reason: "a leaf must not have children",
479                    });
480                }
481                if node.value.split.is_some() {
482                    return Err(TreeError::Malformed {
483                        node: index,
484                        reason: "a leaf must not carry a split threshold",
485                    });
486                }
487                if node.value.label.is_none() {
488                    return Err(TreeError::Malformed {
489                        node: index,
490                        reason: "a leaf must carry a label",
491                    });
492                }
493                Ok(())
494            }
495            Some(_) => {
496                if left == 0 || right == 0 {
497                    return Err(TreeError::Malformed {
498                        node: index,
499                        reason: "an internal node must have two children",
500                    });
501                }
502                match node.value.split {
503                    Some(split) if split.is_finite() => {}
504                    _ => {
505                        return Err(TreeError::Malformed {
506                            node: index,
507                            reason: "an internal node must carry a finite split threshold",
508                        })
509                    }
510                }
511                self.validate_from(left, depth + 1)?;
512                self.validate_from(right, depth + 1)
513            }
514        }
515    }
516
517    /// Classifies one instance.
518    ///
519    /// An internal node sends an instance left when `x[feature] <= threshold`
520    /// and right otherwise, as in scikit-learn. This is also how the search
521    /// partitions the data.
522    pub fn predict_one(&self, x: &[f64]) -> Result<usize, TreeError> {
523        Ok(self.tree[self.leaf_for(x)?]
524            .value
525            .label
526            .expect("leaf_for returns a leaf, and every leaf has a label"))
527    }
528
529    /// The indices of the nodes an instance visits, root first, leaf last.
530    pub fn decision_path(&self, x: &[f64]) -> Result<Vec<usize>, TreeError> {
531        let mut path = Vec::new();
532        let mut index = self.first_node()?;
533        loop {
534            path.push(index);
535            match self.step(index, x)? {
536                Some(next) => index = next,
537                None => return Ok(path),
538            }
539        }
540    }
541
542    /// Classifies a batch. `rows` is row-major, `n_features` values per row.
543    pub fn predict(&self, rows: &[f64], n_features: usize) -> Result<Vec<usize>, TreeError> {
544        if n_features == 0 || rows.len() % n_features != 0 {
545            return Err(TreeError::RaggedInput {
546                len: rows.len(),
547                n_features,
548            });
549        }
550        rows.chunks_exact(n_features)
551            .map(|x| self.predict_one(x))
552            .collect()
553    }
554
555    fn first_node(&self) -> Result<usize, TreeError> {
556        if self.is_empty() {
557            return Err(TreeError::Empty);
558        }
559        Ok(self.get_root_index())
560    }
561
562    fn leaf_for(&self, x: &[f64]) -> Result<usize, TreeError> {
563        let mut index = self.first_node()?;
564        // The arena has one node per index, so a walk longer than that is a
565        // cycle rather than a very deep tree.
566        for _ in 0..=self.tree.len() {
567            match self.step(index, x)? {
568                Some(next) => index = next,
569                None => return Ok(index),
570            }
571        }
572        Err(TreeError::Malformed {
573            node: index,
574            reason: "the tree contains a cycle",
575        })
576    }
577
578    /// One step of the walk: `None` means `index` is a leaf.
579    fn step(&self, index: usize, x: &[f64]) -> Result<Option<usize>, TreeError> {
580        let node = self.get_node(index).ok_or(TreeError::Malformed {
581            node: index,
582            reason: "child index points outside the arena",
583        })?;
584
585        let Some(feature) = node.value.feature else {
586            if node.value.label.is_none() {
587                return Err(TreeError::Malformed {
588                    node: index,
589                    reason: "a leaf must carry a label",
590                });
591            }
592            return Ok(None);
593        };
594
595        if feature >= x.len() {
596            return Err(TreeError::FeatureOutOfRange {
597                node: index,
598                feature,
599                n_features: x.len(),
600            });
601        }
602        let Some(split) = node.value.split else {
603            return Err(TreeError::Malformed {
604                node: index,
605                reason: "an internal node must carry a split threshold",
606            });
607        };
608
609        let next = if x[feature] <= split {
610            node.left
611        } else {
612            node.right
613        };
614        if next == 0 {
615            return Err(TreeError::Malformed {
616                node: index,
617                reason: "an internal node must have two children",
618            });
619        }
620        Ok(Some(next))
621    }
622}
623
624/// One node per line, indented by depth, children after their parent.
625impl fmt::Display for Tree {
626    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
627        let mut stack: Vec<(usize, Option<&TreeNode>)> = Vec::new();
628        stack.push((0, self.get_node(self.get_root_index())));
629        while let Some((depth, node)) = stack.pop() {
630            if let Some(node) = node {
631                writeln!(f, "{}----{:?}", "    ".repeat(depth), node.value)?;
632                stack.push((depth + 1, self.get_right_child(node)));
633                stack.push((depth + 1, self.get_left_child(node)));
634            }
635        }
636        Ok(())
637    }
638}
639
640/// Chained setters for one node of a [`Tree`].
641pub struct NodeUpdater<'a> {
642    node: &'a mut TreeNode,
643}
644
645impl<'a> NodeUpdater<'a> {
646    pub fn new(node: &'a mut TreeNode) -> Self {
647        Self { node }
648    }
649
650    pub fn value(self, value: NodeInfos) -> Self {
651        self.node.value = value;
652        self
653    }
654
655    pub fn error(self, error: usize) -> Self {
656        self.node.value.error = error;
657        self
658    }
659
660    pub fn split(self, metric: f64) -> Self {
661        self.node.value.split = Some(metric);
662        self
663    }
664
665    pub fn label(self, output: usize) -> Self {
666        self.node.value.label = Some(output);
667        self
668    }
669
670    pub fn feature(self, test: usize) -> Self {
671        self.node.value.feature = Some(test);
672        self
673    }
674
675    /// Turns the node into a leaf: no test, no split, no children.
676    pub fn leaf(self) -> Self {
677        self.node.value.feature = None;
678        self.node.value.split = None;
679        self.node.left = 0;
680        self.node.right = 0;
681        self
682    }
683
684    pub fn get_children(&self) -> (usize, usize) {
685        (self.node.left, self.node.right)
686    }
687}