anomstream_core/forest/mod.rs
1//! Forest aggregate root.
2//!
3//! - [`point_store::PointStore`] — a refcounted ring buffer that
4//! holds the canonical copy of every point currently referenced by
5//! any tree. Trees see it through the
6//! [`crate::tree::PointAccessor`] trait.
7//! - [`random_cut_forest::RandomCutForest`] — orchestrates `N`
8//! `(RandomCutTree, ReservoirSampler)` pairs sharing the
9//! [`point_store::PointStore`].
10//! - [`ForestSnapshot`] — minimal read-only health + capacity view,
11//! exposed so downstream crates (`anomstream-triage`, any external
12//! calibrator / SOC dashboard) can consume forest state without
13//! reaching into the reservoir-level internals.
14
15pub mod point_store;
16pub mod random_cut_forest;
17
18pub use point_store::PointStore;
19pub use random_cut_forest::RandomCutForest;
20
21/// Read-only snapshot view of a forest's capacity + health.
22///
23/// Lives in `anomstream-core` so any consumer — including the
24/// downstream `anomstream-triage` crate that hosts SAGE, Platt,
25/// `AlertClusterer`, `FeedbackStore` — can introspect a forest
26/// (`RandomCutForest` or `ThresholdedForest`) without needing
27/// access to the reservoir internals (`point_store()`, `trees()`).
28///
29/// The contract is intentionally tiny; calibration / triage
30/// pipelines typically need only sizing + progress information,
31/// not tree-level data.
32///
33/// Implemented by:
34/// - [`RandomCutForest<D>`]
35/// - [`crate::thresholded::ThresholdedForest<D>`] (delegates to
36/// its inner forest)
37///
38/// # Examples
39///
40/// ```
41/// use anomstream_core::{ForestBuilder, ForestSnapshot};
42///
43/// let forest = ForestBuilder::<4>::new()
44/// .num_trees(50)
45/// .sample_size(64)
46/// .seed(42)
47/// .build()
48/// .unwrap();
49/// assert_eq!(forest.snapshot_num_trees(), 50);
50/// assert_eq!(forest.snapshot_dimension(), 4);
51/// assert_eq!(forest.snapshot_updates_seen(), 0);
52/// ```
53pub trait ForestSnapshot {
54 /// Number of trees in the forest.
55 fn snapshot_num_trees(&self) -> usize;
56 /// Per-tree reservoir capacity.
57 fn snapshot_sample_size(&self) -> usize;
58 /// Per-point compile-time dimensionality.
59 fn snapshot_dimension(&self) -> usize;
60 /// Live points currently referenced by at least one tree.
61 fn snapshot_live_points(&self) -> usize;
62 /// Total `update` calls observed since construction.
63 fn snapshot_updates_seen(&self) -> u64;
64 /// Pessimistic upper bound on the forest's memory footprint in
65 /// bytes (point store + tree arenas + samplers + RNGs).
66 fn snapshot_memory_estimate(&self) -> usize;
67}
68
69impl<const D: usize> ForestSnapshot for RandomCutForest<D> {
70 fn snapshot_num_trees(&self) -> usize {
71 self.num_trees()
72 }
73 fn snapshot_sample_size(&self) -> usize {
74 self.sample_size()
75 }
76 fn snapshot_dimension(&self) -> usize {
77 self.dimension()
78 }
79 fn snapshot_live_points(&self) -> usize {
80 self.point_store().live_count()
81 }
82 fn snapshot_updates_seen(&self) -> u64 {
83 self.updates_seen()
84 }
85 fn snapshot_memory_estimate(&self) -> usize {
86 self.memory_estimate()
87 }
88}