Skip to main content

contree/tree/
mod.rs

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