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 {
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 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 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 {
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 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 #[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 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 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 let intersection = &area_bitmap & ®ion_bitmap;
181
182 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 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 pub fn remove_neuron(&self, neuron_id: u64) -> bool {
204 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 {
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 {
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 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 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#[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 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 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}