anomstream_core/tree/node.rs
1//! Tree node algebraic type and packed `NodeRef` reference.
2//!
3//! [`NodeRef`] packs an internal-vs-leaf discriminator and a slot
4//! index into a single `u32`. The high bit (`1 << 31`) marks leaves;
5//! the low 31 bits hold the slot index in the corresponding storage
6//! arena owned by [`NodeStore`].
7//!
8//! [`NodeStore`]: crate::tree::NodeStore
9
10use crate::domain::{BoundingBox, Cut};
11
12/// Packed reference to a tree node.
13///
14/// The high bit discriminates internal (`0`) from leaf (`1`) so node
15/// identity is a single 4-byte field — useful for cache-friendly
16/// storage in [`NodeStore`].
17///
18/// [`NodeStore`]: crate::tree::NodeStore
19///
20/// # Examples
21///
22/// ```
23/// use anomstream_core::ForestBuilder;
24///
25/// let forest = ForestBuilder::<2>::new()
26/// .num_trees(50)
27/// .sample_size(8)
28/// .seed(1)
29/// .build()
30/// .unwrap();
31/// for (tree, _, _) in forest.trees() {
32/// assert!(tree.root().is_none()); // empty forest has no root yet
33/// }
34/// ```
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct NodeRef(u32);
38
39impl NodeRef {
40 /// Bit set on a leaf reference.
41 pub(crate) const LEAF_BIT: u32 = 1 << 31;
42 /// Mask covering the slot-index bits.
43 pub(crate) const INDEX_MASK: u32 = !Self::LEAF_BIT;
44 /// Largest representable slot index (`(1 << 31) − 1`).
45 pub const MAX_INDEX: u32 = Self::INDEX_MASK;
46
47 /// Build an internal reference from a slot index.
48 ///
49 /// # Panics
50 ///
51 /// Panics in debug builds when `idx > MAX_INDEX`. Internal callers
52 /// always size-check first via the store's capacity.
53 #[must_use]
54 pub(crate) fn internal(idx: u32) -> Self {
55 debug_assert!(idx <= Self::MAX_INDEX, "internal index overflow");
56 Self(idx)
57 }
58
59 /// Build a leaf reference from a slot index.
60 ///
61 /// # Panics
62 ///
63 /// Panics in debug builds when `idx > MAX_INDEX`. Internal callers
64 /// always size-check first via the store's capacity.
65 #[must_use]
66 pub(crate) fn leaf(idx: u32) -> Self {
67 debug_assert!(idx <= Self::MAX_INDEX, "leaf index overflow");
68 Self(idx | Self::LEAF_BIT)
69 }
70
71 /// Whether this reference points to a leaf.
72 #[must_use]
73 #[inline]
74 pub fn is_leaf(self) -> bool {
75 self.0 & Self::LEAF_BIT != 0
76 }
77
78 /// Whether this reference points to an internal node.
79 #[must_use]
80 #[inline]
81 pub fn is_internal(self) -> bool {
82 !self.is_leaf()
83 }
84
85 /// Slot index in the corresponding storage arena.
86 #[must_use]
87 #[inline]
88 pub fn index(self) -> usize {
89 (self.0 & Self::INDEX_MASK) as usize
90 }
91
92 /// Raw packed `u32` representation. Used by [`NodeStore`] for
93 /// child-equality comparisons during sibling lookup.
94 ///
95 /// [`NodeStore`]: crate::tree::NodeStore
96 #[must_use]
97 #[inline]
98 pub(crate) fn raw(self) -> u32 {
99 self.0
100 }
101}
102
103/// Raw internal-node record. Lives inline in the
104/// [`crate::NodeStore`] internal arena — one entry per live
105/// internal node. The bounding box is embedded inline so tree
106/// traversal stays cache-resident.
107#[derive(Debug, Clone, PartialEq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109pub struct InternalData<const D: usize> {
110 /// The hyperplane partitioning the subtree.
111 pub cut: Cut,
112 /// Cached union bounding box of the subtree.
113 pub bbox: BoundingBox<D>,
114 /// Left child (`point[cut.dim] <= cut.value`).
115 pub left: NodeRef,
116 /// Right child (`point[cut.dim] > cut.value`).
117 pub right: NodeRef,
118 /// Parent reference (`None` only at the root).
119 pub parent: Option<NodeRef>,
120 /// Number of leaf descendants.
121 pub mass: u64,
122}
123
124/// Raw leaf-node record. Lives inline in the [`crate::NodeStore`]
125/// leaf arena — one entry per live leaf. Kept small (no bounding
126/// box, no cut) so the leaf arena fits many entries per cache line.
127/// Before the v4 split the leaf arena stored the full `Node<D>`
128/// enum with its internal-variant shape, wasting ~300 bytes per
129/// leaf at `D = 16`.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
132pub struct LeafData {
133 /// Index into the forest point store.
134 pub point_idx: usize,
135 /// Parent reference (`None` only when the tree contains a
136 /// single leaf at the root).
137 pub parent: Option<NodeRef>,
138 /// Number of stored copies of this point. Always `>= 1`.
139 pub mass: u64,
140}
141
142/// Zero-copy immutable view of a tree node. Returned by
143/// [`crate::NodeStore::view`] — pattern-match to branch on
144/// internal-vs-leaf without cloning the underlying record.
145#[derive(Debug)]
146pub enum NodeView<'a, const D: usize> {
147 /// Reference to an internal node's record.
148 Internal(&'a InternalData<D>),
149 /// Reference to a leaf node's record.
150 Leaf(&'a LeafData),
151}
152
153impl<const D: usize> NodeView<'_, D> {
154 /// Mass of the node (leaf count for internals, copy count for
155 /// leaves).
156 #[must_use]
157 #[inline]
158 pub fn mass(&self) -> u64 {
159 match self {
160 Self::Internal(i) => i.mass,
161 Self::Leaf(l) => l.mass,
162 }
163 }
164
165 /// Parent reference (`None` only for the root).
166 #[must_use]
167 #[inline]
168 pub fn parent(&self) -> Option<NodeRef> {
169 match self {
170 Self::Internal(i) => i.parent,
171 Self::Leaf(l) => l.parent,
172 }
173 }
174
175 /// Whether this is an internal node.
176 #[must_use]
177 #[inline]
178 pub fn is_internal(&self) -> bool {
179 matches!(self, Self::Internal(_))
180 }
181
182 /// Whether this is a leaf.
183 #[must_use]
184 #[inline]
185 pub fn is_leaf(&self) -> bool {
186 matches!(self, Self::Leaf(_))
187 }
188}
189
190/// Zero-copy mutable view of a tree node. Mirrors [`NodeView`] but
191/// hands out `&mut` references for in-place field updates.
192#[derive(Debug)]
193pub enum NodeViewMut<'a, const D: usize> {
194 /// Mutable reference to an internal node's record.
195 Internal(&'a mut InternalData<D>),
196 /// Mutable reference to a leaf node's record.
197 Leaf(&'a mut LeafData),
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn ref_internal_round_trip() {
206 let r = NodeRef::internal(42);
207 assert!(r.is_internal());
208 assert!(!r.is_leaf());
209 assert_eq!(r.index(), 42);
210 }
211
212 #[test]
213 fn ref_leaf_round_trip() {
214 let r = NodeRef::leaf(7);
215 assert!(r.is_leaf());
216 assert!(!r.is_internal());
217 assert_eq!(r.index(), 7);
218 }
219
220 #[test]
221 fn ref_internal_and_leaf_with_same_index_differ() {
222 let i = NodeRef::internal(0);
223 let l = NodeRef::leaf(0);
224 assert_ne!(i, l);
225 assert_eq!(i.index(), l.index());
226 }
227
228 #[test]
229 fn ref_max_index_round_trips() {
230 let r = NodeRef::internal(NodeRef::MAX_INDEX);
231 assert_eq!(r.index(), NodeRef::MAX_INDEX as usize);
232 assert!(r.is_internal());
233 }
234
235 #[test]
236 fn leaf_view_mass_and_parent() {
237 let data = LeafData {
238 point_idx: 0,
239 parent: Some(NodeRef::internal(5)),
240 mass: 3,
241 };
242 let view: NodeView<'_, 2> = NodeView::Leaf(&data);
243 assert_eq!(view.mass(), 3);
244 assert!(view.is_leaf());
245 assert!(!view.is_internal());
246 assert_eq!(view.parent(), Some(NodeRef::internal(5)));
247 }
248}