feagi_brain_development/spatial/
hash.rs1use ahash::AHashMap;
11use roaring::RoaringBitmap;
12use std::sync::{Arc, RwLock};
13
14use super::morton::{morton_encode_3d, morton_encode_region_3d};
15
16type NeuronMapKey = (String, u64);
18type CoordinateMapValue = (String, u32, u32, u32);
20
21pub struct MortonSpatialHash {
23 cortical_bitmaps: Arc<RwLock<AHashMap<String, RoaringBitmap>>>,
25
26 neuron_map: Arc<RwLock<AHashMap<NeuronMapKey, Vec<u64>>>>,
29
30 coordinate_map: Arc<RwLock<AHashMap<u64, CoordinateMapValue>>>,
32}
33
34impl MortonSpatialHash {
35 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 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 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 {
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 {
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 {
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 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 {
100 let bitmaps = self.cortical_bitmaps.read().unwrap();
101 let bitmap = bitmaps.get(cortical_area)?;
102 if !bitmap.contains(morton_code as u32) {
103 return None;
104 }
105 }
106
107 let neuron_map = self.neuron_map.read().unwrap();
109 let key = (cortical_area.to_string(), morton_code);
110 neuron_map
111 .get(&key)
112 .and_then(|neurons| neurons.first().copied())
113 }
114
115 pub fn get_neurons_at_coordinate(
117 &self,
118 cortical_area: &str,
119 x: u32,
120 y: u32,
121 z: u32,
122 ) -> Vec<u64> {
123 if x >= (1 << 21) || y >= (1 << 21) || z >= (1 << 21) {
124 return Vec::new();
125 }
126
127 let morton_code = morton_encode_3d(x, y, z);
128
129 {
131 let bitmaps = self.cortical_bitmaps.read().unwrap();
132 if let Some(bitmap) = bitmaps.get(cortical_area) {
133 if !bitmap.contains(morton_code as u32) {
134 return Vec::new();
135 }
136 } else {
137 return Vec::new();
138 }
139 }
140
141 let neuron_map = self.neuron_map.read().unwrap();
143 let key = (cortical_area.to_string(), morton_code);
144 neuron_map.get(&key).cloned().unwrap_or_default()
145 }
146
147 #[allow(clippy::too_many_arguments)]
149 pub fn get_neurons_in_region(
150 &self,
151 cortical_area: &str,
152 x1: u32,
153 y1: u32,
154 z1: u32,
155 x2: u32,
156 y2: u32,
157 z2: u32,
158 ) -> Vec<u64> {
159 let area_bitmap = {
161 let bitmaps = self.cortical_bitmaps.read().unwrap();
162 match bitmaps.get(cortical_area) {
163 Some(bitmap) => bitmap.clone(),
164 None => return Vec::new(),
165 }
166 };
167
168 let region_codes = morton_encode_region_3d(x1, y1, z1, x2, y2, z2);
170 let mut region_bitmap = RoaringBitmap::new();
171 for code in region_codes {
172 region_bitmap.insert(code as u32);
173 }
174
175 let intersection = &area_bitmap & ®ion_bitmap;
177
178 let neuron_map = self.neuron_map.read().unwrap();
180 let mut result = Vec::new();
181
182 for morton_code in intersection {
183 let key = (cortical_area.to_string(), morton_code as u64);
184 if let Some(neurons) = neuron_map.get(&key) {
185 result.extend(neurons);
186 }
187 }
188
189 result
190 }
191
192 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(String, u32, u32, u32)> {
194 let coord_map = self.coordinate_map.read().unwrap();
195 coord_map.get(&neuron_id).cloned()
196 }
197
198 pub fn remove_neuron(&self, neuron_id: u64) -> bool {
200 let position = {
202 let mut coord_map = self.coordinate_map.write().unwrap();
203 coord_map.remove(&neuron_id)
204 };
205
206 if let Some((area, x, y, z)) = position {
207 let morton_code = morton_encode_3d(x, y, z);
208
209 {
211 let mut neuron_map = self.neuron_map.write().unwrap();
212 let key = (area.clone(), morton_code);
213 if let Some(neurons) = neuron_map.get_mut(&key) {
214 neurons.retain(|&id| id != neuron_id);
215 if neurons.is_empty() {
216 neuron_map.remove(&key);
217 }
218 }
219 }
220
221 {
223 let neuron_map = self.neuron_map.read().unwrap();
224 let key = (area.clone(), morton_code);
225 if !neuron_map.contains_key(&key) {
226 let mut bitmaps = self.cortical_bitmaps.write().unwrap();
227 if let Some(bitmap) = bitmaps.get_mut(&area) {
228 bitmap.remove(morton_code as u32);
229 }
230 }
231 }
232
233 true
234 } else {
235 false
236 }
237 }
238
239 pub fn clear(&self) {
241 self.cortical_bitmaps.write().unwrap().clear();
242 self.neuron_map.write().unwrap().clear();
243 self.coordinate_map.write().unwrap().clear();
244 }
245
246 pub fn get_stats(&self) -> SpatialHashStats {
248 let bitmaps = self.cortical_bitmaps.read().unwrap();
249 let coord_map = self.coordinate_map.read().unwrap();
250
251 SpatialHashStats {
252 total_areas: bitmaps.len(),
253 total_neurons: coord_map.len(),
254 total_occupied_positions: bitmaps.values().map(|b| b.len() as usize).sum(),
255 }
256 }
257}
258
259impl Default for MortonSpatialHash {
260 fn default() -> Self {
261 Self::new()
262 }
263}
264
265#[derive(Debug, Clone)]
267pub struct SpatialHashStats {
268 pub total_areas: usize,
269 pub total_neurons: usize,
270 pub total_occupied_positions: usize,
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_add_and_get_neuron() {
279 let hash = MortonSpatialHash::new();
280
281 assert!(hash.add_neuron("v1".to_string(), 10, 20, 30, 1001));
282
283 let neuron = hash.get_neuron_at_coordinate("v1", 10, 20, 30);
284 assert_eq!(neuron, Some(1001));
285
286 let neurons = hash.get_neurons_at_coordinate("v1", 10, 20, 30);
287 assert_eq!(neurons, vec![1001]);
288 }
289
290 #[test]
291 fn test_multiple_neurons_same_position() {
292 let hash = MortonSpatialHash::new();
293
294 hash.add_neuron("v1".to_string(), 5, 5, 5, 100);
295 hash.add_neuron("v1".to_string(), 5, 5, 5, 101);
296 hash.add_neuron("v1".to_string(), 5, 5, 5, 102);
297
298 let neurons = hash.get_neurons_at_coordinate("v1", 5, 5, 5);
299 assert_eq!(neurons.len(), 3);
300 assert!(neurons.contains(&100));
301 assert!(neurons.contains(&101));
302 assert!(neurons.contains(&102));
303 }
304
305 #[test]
306 fn test_region_query() {
307 let hash = MortonSpatialHash::new();
308
309 for x in 0..10 {
311 for y in 0..10 {
312 for z in 0..10 {
313 let neuron_id = (x * 100 + y * 10 + z) as u64;
314 hash.add_neuron("v1".to_string(), x, y, z, neuron_id);
315 }
316 }
317 }
318
319 let neurons = hash.get_neurons_in_region("v1", 0, 0, 0, 1, 1, 1);
321 assert_eq!(neurons.len(), 8);
322 }
323
324 #[test]
325 fn test_get_neuron_position() {
326 let hash = MortonSpatialHash::new();
327
328 hash.add_neuron("v1".to_string(), 42, 84, 126, 999);
329
330 let pos = hash.get_neuron_position(999);
331 assert_eq!(pos, Some(("v1".to_string(), 42, 84, 126)));
332 }
333
334 #[test]
335 fn test_remove_neuron() {
336 let hash = MortonSpatialHash::new();
337
338 hash.add_neuron("v1".to_string(), 10, 20, 30, 1001);
339 assert!(hash.remove_neuron(1001));
340
341 let neuron = hash.get_neuron_at_coordinate("v1", 10, 20, 30);
342 assert_eq!(neuron, None);
343 }
344}