Skip to main content

feagi_brain_development/spatial/
hash.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Morton spatial hash implementation using Roaring bitmaps.
6
7High-performance spatial indexing for neuron positions.
8*/
9
10use ahash::AHashMap;
11use roaring::RoaringBitmap;
12use std::sync::{Arc, RwLock};
13
14use super::morton::{morton_encode_3d, morton_encode_region_3d};
15
16/// Type alias for neuron map key: (cortical_area, morton_code)
17type NeuronMapKey = (String, u64);
18/// Type alias for coordinate map value: (area, x, y, z)
19type CoordinateMapValue = (String, u32, u32, u32);
20
21/// Spatial hash system using Morton encoding + Roaring bitmaps
22pub struct MortonSpatialHash {
23    /// Per-cortical-area bitmaps of occupied positions
24    cortical_bitmaps: Arc<RwLock<AHashMap<String, RoaringBitmap>>>,
25
26    /// Map Morton code -> list of neuron IDs at that position
27    /// Key: (cortical_area, morton_code)
28    neuron_map: Arc<RwLock<AHashMap<NeuronMapKey, Vec<u64>>>>,
29
30    /// Reverse map: neuron_id -> (area, x, y, z)
31    coordinate_map: Arc<RwLock<AHashMap<u64, CoordinateMapValue>>>,
32}
33
34impl MortonSpatialHash {
35    /// Create a new spatial hash system
36    pub fn new() -> Self {
37        Self {
38            cortical_bitmaps: Arc::new(RwLock::new(AHashMap::new())),
39            neuron_map: Arc::new(RwLock::new(AHashMap::new())),
40            coordinate_map: Arc::new(RwLock::new(AHashMap::new())),
41        }
42    }
43
44    /// Add a neuron to the spatial hash
45    pub fn add_neuron(
46        &self,
47        cortical_area: String,
48        x: u32,
49        y: u32,
50        z: u32,
51        neuron_id: u64,
52    ) -> bool {
53        // Validate coordinates
54        if x >= (1 << 21) || y >= (1 << 21) || z >= (1 << 21) {
55            return false;
56        }
57
58        let morton_code = morton_encode_3d(x, y, z);
59
60        // Add to cortical bitmap
61        {
62            let mut bitmaps = self.cortical_bitmaps.write().unwrap();
63            bitmaps
64                .entry(cortical_area.clone())
65                .or_default()
66                .insert(morton_code as u32);
67        }
68
69        // Add to neuron map
70        {
71            let mut neuron_map = self.neuron_map.write().unwrap();
72            let key = (cortical_area.clone(), morton_code);
73            neuron_map.entry(key).or_default().push(neuron_id);
74        }
75
76        // Add to coordinate map
77        {
78            let mut coord_map = self.coordinate_map.write().unwrap();
79            coord_map.insert(neuron_id, (cortical_area, x, y, z));
80        }
81
82        true
83    }
84
85    /// Get first neuron at coordinate (or None)
86    pub fn get_neuron_at_coordinate(
87        &self,
88        cortical_area: &str,
89        x: u32,
90        y: u32,
91        z: u32,
92    ) -> Option<u64> {
93        if x >= (1 << 21) || y >= (1 << 21) || z >= (1 << 21) {
94            return None;
95        }
96
97        let morton_code = morton_encode_3d(x, y, z);
98
99        // Check if coordinate exists in bitmap
100        {
101            let bitmaps = self.cortical_bitmaps.read().unwrap();
102            if let Some(bitmap) = bitmaps.get(cortical_area) {
103                if !bitmap.contains(morton_code as u32) {
104                    return None;
105                }
106            } else {
107                return None;
108            }
109        }
110
111        // Get neuron IDs
112        let neuron_map = self.neuron_map.read().unwrap();
113        let key = (cortical_area.to_string(), morton_code);
114        neuron_map
115            .get(&key)
116            .and_then(|neurons| neurons.first().copied())
117    }
118
119    /// Get all neurons at coordinate
120    pub fn get_neurons_at_coordinate(
121        &self,
122        cortical_area: &str,
123        x: u32,
124        y: u32,
125        z: u32,
126    ) -> Vec<u64> {
127        if x >= (1 << 21) || y >= (1 << 21) || z >= (1 << 21) {
128            return Vec::new();
129        }
130
131        let morton_code = morton_encode_3d(x, y, z);
132
133        // Check bitmap first (fast)
134        {
135            let bitmaps = self.cortical_bitmaps.read().unwrap();
136            if let Some(bitmap) = bitmaps.get(cortical_area) {
137                if !bitmap.contains(morton_code as u32) {
138                    return Vec::new();
139                }
140            } else {
141                return Vec::new();
142            }
143        }
144
145        // Get neurons
146        let neuron_map = self.neuron_map.read().unwrap();
147        let key = (cortical_area.to_string(), morton_code);
148        neuron_map.get(&key).cloned().unwrap_or_default()
149    }
150
151    /// Get all neurons in a 3D region
152    #[allow(clippy::too_many_arguments)]
153    pub fn get_neurons_in_region(
154        &self,
155        cortical_area: &str,
156        x1: u32,
157        y1: u32,
158        z1: u32,
159        x2: u32,
160        y2: u32,
161        z2: u32,
162    ) -> Vec<u64> {
163        // Get area bitmap
164        let area_bitmap = {
165            let bitmaps = self.cortical_bitmaps.read().unwrap();
166            match bitmaps.get(cortical_area) {
167                Some(bitmap) => bitmap.clone(),
168                None => return Vec::new(),
169            }
170        };
171
172        // Create region bitmap
173        let region_codes = morton_encode_region_3d(x1, y1, z1, x2, y2, z2);
174        let mut region_bitmap = RoaringBitmap::new();
175        for code in region_codes {
176            region_bitmap.insert(code as u32);
177        }
178
179        // Fast intersection
180        let intersection = &area_bitmap & &region_bitmap;
181
182        // Collect neurons
183        let neuron_map = self.neuron_map.read().unwrap();
184        let mut result = Vec::new();
185
186        for morton_code in intersection {
187            let key = (cortical_area.to_string(), morton_code as u64);
188            if let Some(neurons) = neuron_map.get(&key) {
189                result.extend(neurons);
190            }
191        }
192
193        result
194    }
195
196    /// Get neuron's position
197    pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(String, u32, u32, u32)> {
198        let coord_map = self.coordinate_map.read().unwrap();
199        coord_map.get(&neuron_id).cloned()
200    }
201
202    /// Remove a neuron from the spatial hash
203    pub fn remove_neuron(&self, neuron_id: u64) -> bool {
204        // Get position
205        let position = {
206            let mut coord_map = self.coordinate_map.write().unwrap();
207            coord_map.remove(&neuron_id)
208        };
209
210        if let Some((area, x, y, z)) = position {
211            let morton_code = morton_encode_3d(x, y, z);
212
213            // Remove from neuron map
214            {
215                let mut neuron_map = self.neuron_map.write().unwrap();
216                let key = (area.clone(), morton_code);
217                if let Some(neurons) = neuron_map.get_mut(&key) {
218                    neurons.retain(|&id| id != neuron_id);
219                    if neurons.is_empty() {
220                        neuron_map.remove(&key);
221                    }
222                }
223            }
224
225            // If no more neurons at this position, remove from bitmap
226            {
227                let neuron_map = self.neuron_map.read().unwrap();
228                let key = (area.clone(), morton_code);
229                if !neuron_map.contains_key(&key) {
230                    let mut bitmaps = self.cortical_bitmaps.write().unwrap();
231                    if let Some(bitmap) = bitmaps.get_mut(&area) {
232                        bitmap.remove(morton_code as u32);
233                    }
234                }
235            }
236
237            true
238        } else {
239            false
240        }
241    }
242
243    /// Clear all data
244    pub fn clear(&self) {
245        self.cortical_bitmaps.write().unwrap().clear();
246        self.neuron_map.write().unwrap().clear();
247        self.coordinate_map.write().unwrap().clear();
248    }
249
250    /// Get statistics
251    pub fn get_stats(&self) -> SpatialHashStats {
252        let bitmaps = self.cortical_bitmaps.read().unwrap();
253        let coord_map = self.coordinate_map.read().unwrap();
254
255        SpatialHashStats {
256            total_areas: bitmaps.len(),
257            total_neurons: coord_map.len(),
258            total_occupied_positions: bitmaps.values().map(|b| b.len() as usize).sum(),
259        }
260    }
261}
262
263impl Default for MortonSpatialHash {
264    fn default() -> Self {
265        Self::new()
266    }
267}
268
269/// Statistics about the spatial hash
270#[derive(Debug, Clone)]
271pub struct SpatialHashStats {
272    pub total_areas: usize,
273    pub total_neurons: usize,
274    pub total_occupied_positions: usize,
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_add_and_get_neuron() {
283        let hash = MortonSpatialHash::new();
284
285        assert!(hash.add_neuron("v1".to_string(), 10, 20, 30, 1001));
286
287        let neuron = hash.get_neuron_at_coordinate("v1", 10, 20, 30);
288        assert_eq!(neuron, Some(1001));
289
290        let neurons = hash.get_neurons_at_coordinate("v1", 10, 20, 30);
291        assert_eq!(neurons, vec![1001]);
292    }
293
294    #[test]
295    fn test_multiple_neurons_same_position() {
296        let hash = MortonSpatialHash::new();
297
298        hash.add_neuron("v1".to_string(), 5, 5, 5, 100);
299        hash.add_neuron("v1".to_string(), 5, 5, 5, 101);
300        hash.add_neuron("v1".to_string(), 5, 5, 5, 102);
301
302        let neurons = hash.get_neurons_at_coordinate("v1", 5, 5, 5);
303        assert_eq!(neurons.len(), 3);
304        assert!(neurons.contains(&100));
305        assert!(neurons.contains(&101));
306        assert!(neurons.contains(&102));
307    }
308
309    #[test]
310    fn test_region_query() {
311        let hash = MortonSpatialHash::new();
312
313        // Add neurons in a 10x10x10 grid
314        for x in 0..10 {
315            for y in 0..10 {
316                for z in 0..10 {
317                    let neuron_id = (x * 100 + y * 10 + z) as u64;
318                    hash.add_neuron("v1".to_string(), x, y, z, neuron_id);
319                }
320            }
321        }
322
323        // Query a 2x2x2 subregion
324        let neurons = hash.get_neurons_in_region("v1", 0, 0, 0, 1, 1, 1);
325        assert_eq!(neurons.len(), 8);
326    }
327
328    #[test]
329    fn test_get_neuron_position() {
330        let hash = MortonSpatialHash::new();
331
332        hash.add_neuron("v1".to_string(), 42, 84, 126, 999);
333
334        let pos = hash.get_neuron_position(999);
335        assert_eq!(pos, Some(("v1".to_string(), 42, 84, 126)));
336    }
337
338    #[test]
339    fn test_remove_neuron() {
340        let hash = MortonSpatialHash::new();
341
342        hash.add_neuron("v1".to_string(), 10, 20, 30, 1001);
343        assert!(hash.remove_neuron(1001));
344
345        let neuron = hash.get_neuron_at_coordinate("v1", 10, 20, 30);
346        assert_eq!(neuron, None);
347    }
348}