Skip to main content

do_memory_mcp/patterns/predictive/
kdtree.rs

1//! # Predictive Analysis Module
2//!
3//! Provides forecasting models, anomaly detection, and causal inference capabilities
4//! using advanced algorithms from augurs and deep_causality.
5
6#[allow(clippy::cast_precision_loss)]
7#[allow(clippy::cast_possible_wrap)]
8#[allow(clippy::cast_sign_loss)]
9use serde::{Deserialize, Serialize};
10
11/// DBSCAN Anomaly Detection Implementation
12/// Density-adaptive DBSCAN with KD-tree spatial indexing for efficient
13/// real-time streaming anomaly detection in memory patterns.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Point {
16    /// Point ID for tracking
17    pub id: usize,
18    /// Time series values
19    pub values: Vec<f64>,
20    /// Semantic embedding (optional, for enhanced clustering)
21    pub embedding: Option<Vec<f32>>,
22    /// Temporal position
23    pub timestamp: f64,
24    /// Pre-calculated features for clustering
25    pub features: Vec<f64>,
26}
27
28impl Point {
29    /// Create a new point from time series data
30    pub fn new(id: usize, values: &[f64], embedding: Option<Vec<f32>>, timestamp: f64) -> Self {
31        let features = Self::extract_features(values);
32        Self {
33            id,
34            values: values.to_vec(),
35            embedding,
36            timestamp,
37            features,
38        }
39    }
40
41    /// Extract features from time series values for clustering
42    fn extract_features(values: &[f64]) -> Vec<f64> {
43        if values.len() < 3 {
44            return vec![0.0; 8]; // Default feature vector
45        }
46
47        let mean: f64 = values.iter().sum::<f64>() / values.len() as f64;
48        let variance: f64 =
49            values.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
50        let std_dev = variance.sqrt();
51
52        let mut features = Vec::with_capacity(8);
53
54        // Statistical features
55        features.push(mean);
56        features.push(std_dev);
57
58        // Trend features
59        #[allow(clippy::cast_precision_loss)]
60        let trend = if let (Some(&first_val), Some(&last_val)) = (values.first(), values.last()) {
61            (last_val - first_val) / (values.len() - 1) as f64
62        } else {
63            0.0
64        };
65        features.push(trend);
66
67        // Volatility (rolling standard deviation)
68        let volatility = if values.len() > 3 {
69            let window = values.len().min(5);
70            let mut rolling_std = Vec::new();
71            for i in (window - 1)..values.len() {
72                let start = i.saturating_sub(window - 1);
73                let window_data = &values[start..=i];
74                let window_mean: f64 = window_data.iter().sum::<f64>() / window_data.len() as f64;
75                let window_var: f64 = window_data
76                    .iter()
77                    .map(|&x| (x - window_mean).powi(2))
78                    .sum::<f64>()
79                    / window_data.len() as f64;
80                rolling_std.push(window_var.sqrt());
81            }
82            rolling_std.iter().sum::<f64>() / rolling_std.len() as f64
83        } else {
84            std_dev
85        };
86        features.push(volatility);
87
88        // Skewness
89        let skewness = if std_dev > 0.0 {
90            values
91                .iter()
92                .map(|&x| ((x - mean) / std_dev).powi(3))
93                .sum::<f64>()
94                / values.len() as f64
95        } else {
96            0.0
97        };
98        features.push(skewness);
99
100        // Kurtosis
101        let kurtosis = if std_dev > 0.0 {
102            values
103                .iter()
104                .map(|&x| ((x - mean) / std_dev).powi(4))
105                .sum::<f64>()
106                / values.len() as f64
107                - 3.0
108        } else {
109            0.0
110        };
111        features.push(kurtosis);
112
113        // Autocorrelation (lag 1)
114        let autocorr = if values.len() > 1 {
115            let mut numerator = 0.0;
116            let mut denom_x = 0.0;
117            let mut denom_y = 0.0;
118            for i in 1..values.len() {
119                numerator += (values[i - 1] - mean) * (values[i] - mean);
120                denom_x += (values[i - 1] - mean).powi(2);
121                denom_y += (values[i] - mean).powi(2);
122            }
123            if denom_x > 0.0 && denom_y > 0.0 {
124                numerator / (denom_x.sqrt() * denom_y.sqrt())
125            } else {
126                0.0
127            }
128        } else {
129            0.0
130        };
131        features.push(autocorr);
132
133        // Recent change (last 3 points trend)
134        let recent_change = if values.len() >= 3 {
135            let recent_mean: f64 = values.iter().rev().take(3).sum::<f64>() / 3.0;
136            (recent_mean - mean) / std_dev.max(1e-6)
137        } else {
138            0.0
139        };
140        features.push(recent_change);
141
142        features
143    }
144}
145
146/// KD-tree node for spatial indexing
147#[derive(Debug)]
148struct KDNode {
149    point: Point,
150    axis: usize,
151    left: Option<Box<KDNode>>,
152    right: Option<Box<KDNode>>,
153}
154
155impl KDNode {
156    fn new(point: Point, axis: usize) -> Self {
157        Self {
158            point,
159            axis,
160            left: None,
161            right: None,
162        }
163    }
164
165    /// Insert a new point into the KD-tree
166    fn insert(&mut self, point: Point) {
167        let axis = self.axis;
168        let comparison = if point.features.len() > axis {
169            point.features[axis] < self.point.features[axis]
170        } else {
171            point.id < self.point.id
172        };
173
174        if comparison {
175            if let Some(left) = &mut self.left {
176                left.insert(point);
177            } else {
178                self.left = Some(Box::new(KDNode::new(
179                    point,
180                    (axis + 1) % self.point.features.len(),
181                )));
182            }
183        } else if let Some(right) = &mut self.right {
184            right.insert(point);
185        } else {
186            self.right = Some(Box::new(KDNode::new(
187                point,
188                (axis + 1) % self.point.features.len(),
189            )));
190        }
191    }
192
193    /// Find neighbors within distance epsilon
194    fn range_query(&self, epsilon: f64, center: &[f64], results: &mut Vec<Point>) {
195        // Calculate distance to this node
196        let distance = calculate_distance(&self.point.features, center);
197        if distance <= epsilon {
198            results.push(self.point.clone());
199        }
200
201        // Recurse to children
202        let axis = self.axis;
203        if self.point.features.len() > axis {
204            let plane_distance = if center.len() > axis {
205                (center[axis] - self.point.features[axis]).abs()
206            } else {
207                0.0
208            };
209
210            if plane_distance <= epsilon {
211                if let Some(left) = &self.left {
212                    left.range_query(epsilon, center, results);
213                }
214                if let Some(right) = &self.right {
215                    right.range_query(epsilon, center, results);
216                }
217            } else {
218                // Only search the side that could contain points
219                if center.len() > axis && center[axis] < self.point.features[axis] {
220                    if let Some(left) = &self.left {
221                        left.range_query(epsilon, center, results);
222                    }
223                } else if let Some(right) = &self.right {
224                    right.range_query(epsilon, center, results);
225                }
226            }
227        }
228    }
229}
230
231/// KD-tree spatial index for efficient neighbor queries
232#[derive(Debug)]
233#[allow(dead_code)]
234pub struct KDTree {
235    root: Option<Box<KDNode>>,
236    max_depth: usize,
237}
238
239impl KDTree {
240    pub(crate) fn new() -> Self {
241        Self {
242            root: None,
243            max_depth: 10,
244        }
245    }
246
247    /// Build KD-tree from points
248    pub fn build(points: &[Point]) -> Self {
249        if points.is_empty() {
250            return Self::new();
251        }
252
253        let mut tree = Self::new();
254        let axis = 0; // Start with first dimension
255
256        if let Some(root_point) = points.first() {
257            tree.root = Some(Box::new(KDNode::new(root_point.clone(), axis)));
258
259            for point in points.iter().skip(1) {
260                if let Some(root) = tree.root.as_mut() {
261                    root.insert(point.clone());
262                }
263            }
264        }
265
266        tree
267    }
268
269    /// Find neighbors within distance epsilon
270    pub fn find_neighbors(&self, center: &[f64], epsilon: f64) -> Vec<Point> {
271        let mut results = Vec::new();
272        if let Some(root) = &self.root {
273            root.range_query(epsilon, center, &mut results);
274        }
275        results
276    }
277}
278
279/// Calculate Euclidean distance between two feature vectors
280pub fn calculate_distance(a: &[f64], b: &[f64]) -> f64 {
281    let mut sum = 0.0;
282    for (x, y) in a.iter().zip(b.iter()) {
283        let diff = x - y;
284        sum += diff * diff;
285    }
286    sum.sqrt()
287}