Skip to main content

feagi_brain_development/models/
cortical_area.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5CorticalArea business logic and extension methods.
6
7The core CorticalArea data structure is defined in feagi_data_structures.
8This module provides business logic methods for coordinate transformations
9and builder patterns.
10*/
11
12use std::collections::HashMap;
13
14use crate::types::{BduError, BduResult, Position};
15
16// Import core types from feagi_data_structures
17pub use feagi_structures::genomic::cortical_area::{
18    CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalID,
19};
20
21/// Extension trait providing business logic methods for CorticalArea
22pub trait CorticalAreaExt {
23    /// Create a cortical area with custom properties
24    fn with_properties(self, properties: HashMap<String, serde_json::Value>) -> Self;
25
26    /// Add a single property (builder pattern)
27    fn add_property(self, key: String, value: serde_json::Value) -> Self;
28
29    /// Add a single property in-place
30    fn add_property_mut(&mut self, key: String, value: serde_json::Value);
31
32    /// Check if a 3D position is within this area's bounds
33    fn contains_position(&self, pos: (i32, i32, i32)) -> bool;
34
35    /// Convert absolute brain position to relative position within this area
36    fn to_relative_position(&self, pos: (i32, i32, i32)) -> BduResult<Position>;
37
38    /// Convert relative position within area to absolute brain position
39    fn to_absolute_position(&self, rel_pos: Position) -> BduResult<(i32, i32, i32)>;
40
41    /// Get neurons_per_voxel from properties (defaults to 1)
42    fn neurons_per_voxel(&self) -> u32;
43
44    /// Get refractory_period from properties (defaults to 0)
45    fn refractory_period(&self) -> u16;
46
47    /// Get snooze_period from properties (defaults to 0)
48    fn snooze_period(&self) -> u16;
49
50    /// Get leak_coefficient from properties (defaults to 0.0)
51    fn leak_coefficient(&self) -> f32;
52
53    /// Get firing_threshold from properties (defaults to 1.0)
54    fn firing_threshold(&self) -> f32;
55
56    /// Get firing_threshold_limit from properties (defaults to 0.0 = no limit)
57    fn firing_threshold_limit(&self) -> f32;
58
59    /// Get property as u32 with default
60    fn get_u32_property(&self, key: &str, default: u32) -> u32;
61
62    /// Get property as u16 with default
63    fn get_u16_property(&self, key: &str, default: u16) -> u16;
64
65    /// Get property as f32 with default
66    fn get_f32_property(&self, key: &str, default: f32) -> f32;
67
68    /// Get property as bool with default
69    fn get_bool_property(&self, key: &str, default: bool) -> bool;
70
71    /// Check if this is an input area
72    fn is_input_area(&self) -> bool;
73
74    /// Check if this is an output area
75    fn is_output_area(&self) -> bool;
76
77    /// Get cortical group classification
78    fn get_cortical_group(&self) -> Option<String>;
79
80    /// Get visible flag from properties (defaults to true)
81    fn visible(&self) -> bool;
82
83    /// Get sub_group from properties
84    fn sub_group(&self) -> Option<String>;
85
86    /// Get plasticity_constant from properties
87    fn plasticity_constant(&self) -> f32;
88
89    /// Get postsynaptic_current from properties
90    fn postsynaptic_current(&self) -> f32;
91
92    /// Get psp_uniform_distribution from properties
93    fn psp_uniform_distribution(&self) -> bool;
94
95    /// Get degeneration from properties
96    fn degeneration(&self) -> f32;
97
98    /// Get burst_engine_active from properties
99    fn burst_engine_active(&self) -> bool;
100
101    /// Get firing_threshold_increment from properties
102    fn firing_threshold_increment(&self) -> f32;
103
104    /// Get firing_threshold_increment_x from properties
105    fn firing_threshold_increment_x(&self) -> f32;
106
107    /// Get firing_threshold_increment_y from properties
108    fn firing_threshold_increment_y(&self) -> f32;
109
110    /// Get firing_threshold_increment_z from properties
111    fn firing_threshold_increment_z(&self) -> f32;
112
113    /// Get consecutive_fire_count from properties
114    fn consecutive_fire_count(&self) -> u32;
115
116    /// Get leak_variability from properties
117    fn leak_variability(&self) -> f32;
118
119    /// Get neuron_excitability from properties
120    fn neuron_excitability(&self) -> f32;
121
122    /// Get postsynaptic_current_max from properties
123    fn postsynaptic_current_max(&self) -> f32;
124
125    /// Get mp_charge_accumulation from properties
126    fn mp_charge_accumulation(&self) -> bool;
127
128    /// Get mp_driven_psp from properties
129    fn mp_driven_psp(&self) -> bool;
130
131    /// Get init_lifespan from properties (memory parameter)
132    fn init_lifespan(&self) -> u32;
133
134    /// Get lifespan_growth_rate from properties (memory parameter)
135    fn lifespan_growth_rate(&self) -> f32;
136
137    /// Get longterm_mem_threshold from properties (memory parameter)
138    fn longterm_mem_threshold(&self) -> u32;
139}
140
141impl CorticalAreaExt for CorticalArea {
142    fn with_properties(mut self, properties: HashMap<String, serde_json::Value>) -> Self {
143        self.properties = properties;
144        self
145    }
146
147    fn add_property(mut self, key: String, value: serde_json::Value) -> Self {
148        self.properties.insert(key, value);
149        self
150    }
151
152    fn add_property_mut(&mut self, key: String, value: serde_json::Value) {
153        self.properties.insert(key, value);
154    }
155
156    fn contains_position(&self, pos: (i32, i32, i32)) -> bool {
157        let (x, y, z) = pos;
158        let ox = self.position.x;
159        let oy = self.position.y;
160        let oz = self.position.z;
161
162        x >= ox
163            && y >= oy
164            && z >= oz
165            && x < ox + self.dimensions.width as i32
166            && y < oy + self.dimensions.height as i32
167            && z < oz + self.dimensions.depth as i32
168    }
169
170    fn to_relative_position(&self, pos: (i32, i32, i32)) -> BduResult<Position> {
171        if !self.contains_position(pos) {
172            return Err(BduError::OutOfBounds {
173                pos: (pos.0 as u32, pos.1 as u32, pos.2 as u32),
174                dims: (
175                    self.dimensions.width as usize,
176                    self.dimensions.height as usize,
177                    self.dimensions.depth as usize,
178                ),
179            });
180        }
181
182        let ox = self.position.x;
183        let oy = self.position.y;
184        let oz = self.position.z;
185        Ok((
186            (pos.0 - ox) as u32,
187            (pos.1 - oy) as u32,
188            (pos.2 - oz) as u32,
189        ))
190    }
191
192    fn to_absolute_position(&self, rel_pos: Position) -> BduResult<(i32, i32, i32)> {
193        if !self.dimensions.contains(rel_pos) {
194            return Err(BduError::OutOfBounds {
195                pos: rel_pos,
196                dims: (
197                    self.dimensions.width as usize,
198                    self.dimensions.height as usize,
199                    self.dimensions.depth as usize,
200                ),
201            });
202        }
203
204        let ox = self.position.x;
205        let oy = self.position.y;
206        let oz = self.position.z;
207        Ok((
208            ox + rel_pos.0 as i32,
209            oy + rel_pos.1 as i32,
210            oz + rel_pos.2 as i32,
211        ))
212    }
213
214    fn neurons_per_voxel(&self) -> u32 {
215        self.get_u32_property("neurons_per_voxel", 1)
216    }
217
218    fn refractory_period(&self) -> u16 {
219        self.get_u16_property("refractory_period", 0)
220    }
221
222    fn snooze_period(&self) -> u16 {
223        self.get_u16_property("snooze_period", 0)
224    }
225
226    fn leak_coefficient(&self) -> f32 {
227        self.get_f32_property("leak_coefficient", 0.0)
228    }
229
230    fn firing_threshold(&self) -> f32 {
231        self.get_f32_property("firing_threshold", 1.0)
232    }
233
234    fn get_u32_property(&self, key: &str, default: u32) -> u32 {
235        self.properties
236            .get(key)
237            .and_then(|v| v.as_u64())
238            .map(|v| v as u32)
239            .unwrap_or(default)
240    }
241
242    fn get_u16_property(&self, key: &str, default: u16) -> u16 {
243        self.properties
244            .get(key)
245            .and_then(|v| v.as_u64())
246            .map(|v| v as u16)
247            .unwrap_or(default)
248    }
249
250    fn get_f32_property(&self, key: &str, default: f32) -> f32 {
251        self.properties
252            .get(key)
253            .and_then(|v| v.as_f64())
254            .map(|v| v as f32)
255            .unwrap_or(default)
256    }
257
258    fn get_bool_property(&self, key: &str, default: bool) -> bool {
259        self.properties
260            .get(key)
261            .and_then(|v| v.as_bool())
262            .unwrap_or(default)
263    }
264
265    fn is_input_area(&self) -> bool {
266        matches!(
267            self.cortical_type,
268            feagi_structures::genomic::cortical_area::CorticalAreaType::BrainInput(_)
269        )
270    }
271
272    fn is_output_area(&self) -> bool {
273        matches!(
274            self.cortical_type,
275            feagi_structures::genomic::cortical_area::CorticalAreaType::BrainOutput(_)
276        )
277    }
278
279    fn get_cortical_group(&self) -> Option<String> {
280        self.properties
281            .get("cortical_group")
282            .and_then(|v| v.as_str())
283            .map(|s| s.to_string())
284            .or_else(|| {
285                // Derive from cortical_type if not in properties
286                use feagi_structures::genomic::cortical_area::CorticalAreaType;
287                match self.cortical_type {
288                    CorticalAreaType::BrainInput(_) => Some("IPU".to_string()),
289                    CorticalAreaType::BrainOutput(_) => Some("OPU".to_string()),
290                    CorticalAreaType::Memory(_) => Some("MEMORY".to_string()),
291                    CorticalAreaType::Custom(_) => Some("CUSTOM".to_string()),
292                    CorticalAreaType::Core(_) => Some("CORE".to_string()),
293                }
294            })
295    }
296
297    fn visible(&self) -> bool {
298        self.get_bool_property("visible", true)
299    }
300
301    fn sub_group(&self) -> Option<String> {
302        self.properties
303            .get("sub_group")
304            .and_then(|v| v.as_str())
305            .map(|s| s.to_string())
306    }
307
308    fn plasticity_constant(&self) -> f32 {
309        self.get_f32_property("plasticity_constant", 0.0)
310    }
311
312    fn postsynaptic_current(&self) -> f32 {
313        self.get_f32_property("postsynaptic_current", 1.0)
314    }
315
316    fn psp_uniform_distribution(&self) -> bool {
317        self.get_bool_property("psp_uniform_distribution", false)
318    }
319
320    fn degeneration(&self) -> f32 {
321        self.get_f32_property("degeneration", 0.0)
322    }
323
324    fn burst_engine_active(&self) -> bool {
325        self.get_bool_property("burst_engine_active", false)
326    }
327
328    fn firing_threshold_increment(&self) -> f32 {
329        self.get_f32_property("firing_threshold_increment", 0.0)
330    }
331
332    fn firing_threshold_increment_x(&self) -> f32 {
333        self.get_f32_property("firing_threshold_increment_x", 0.0)
334    }
335
336    fn firing_threshold_increment_y(&self) -> f32 {
337        self.get_f32_property("firing_threshold_increment_y", 0.0)
338    }
339
340    fn firing_threshold_increment_z(&self) -> f32 {
341        self.get_f32_property("firing_threshold_increment_z", 0.0)
342    }
343
344    fn firing_threshold_limit(&self) -> f32 {
345        self.get_f32_property("firing_threshold_limit", 0.0)
346    }
347
348    fn consecutive_fire_count(&self) -> u32 {
349        self.get_u32_property("consecutive_fire_limit", 0)
350    }
351
352    fn leak_variability(&self) -> f32 {
353        self.get_f32_property("leak_variability", 0.0)
354    }
355
356    fn neuron_excitability(&self) -> f32 {
357        self.get_f32_property("neuron_excitability", 100.0)
358    }
359
360    fn postsynaptic_current_max(&self) -> f32 {
361        self.get_f32_property("postsynaptic_current_max", 0.0)
362    }
363
364    fn mp_charge_accumulation(&self) -> bool {
365        self.get_bool_property("mp_charge_accumulation", false)
366    }
367
368    fn mp_driven_psp(&self) -> bool {
369        self.get_bool_property("mp_driven_psp", false)
370    }
371
372    fn init_lifespan(&self) -> u32 {
373        self.get_u32_property("init_lifespan", 0)
374    }
375
376    fn lifespan_growth_rate(&self) -> f32 {
377        self.get_f32_property("lifespan_growth_rate", 0.0)
378    }
379
380    fn longterm_mem_threshold(&self) -> u32 {
381        self.get_u32_property("longterm_mem_threshold", 0)
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388
389    #[test]
390    fn test_contains_position() {
391        let cortical_id = CoreCorticalType::Power.to_cortical_id();
392        let cortical_type = cortical_id
393            .as_cortical_type()
394            .expect("Failed to get cortical type");
395        let dims = CorticalAreaDimensions::new(10, 10, 10).unwrap();
396        let area = CorticalArea::new(
397            cortical_id,
398            0,
399            "Test Area".to_string(),
400            dims,
401            (5, 5, 5).into(),
402            cortical_type,
403        )
404        .unwrap();
405
406        assert!(area.contains_position((5, 5, 5))); // Min corner
407        assert!(area.contains_position((14, 14, 14))); // Max corner
408        assert!(!area.contains_position((4, 5, 5))); // Outside (x too small)
409        assert!(!area.contains_position((15, 5, 5))); // Outside (x too large)
410    }
411
412    #[test]
413    fn test_position_conversion() {
414        let cortical_id = CoreCorticalType::Power.to_cortical_id();
415        let cortical_type = cortical_id
416            .as_cortical_type()
417            .expect("Failed to get cortical type");
418        let dims = CorticalAreaDimensions::new(10, 10, 10).unwrap();
419        let area = CorticalArea::new(
420            cortical_id,
421            0,
422            "Test Area".to_string(),
423            dims,
424            (100, 200, 300).into(),
425            cortical_type,
426        )
427        .unwrap();
428
429        // Area spans from (100,200,300) to (109,209,309)
430        // Absolute (105, 207, 308) should map to relative (5, 7, 8)
431        let rel_pos = area.to_relative_position((105, 207, 308)).unwrap();
432        assert_eq!(rel_pos, (5, 7, 8));
433
434        // Convert back
435        let abs_pos = area.to_absolute_position(rel_pos).unwrap();
436        assert_eq!(abs_pos, (105, 207, 308));
437
438        // Test out of bounds
439        let result = area.to_relative_position((99, 200, 300));
440        assert!(result.is_err());
441    }
442
443    #[test]
444    fn test_properties() {
445        let cortical_id = CoreCorticalType::Power.to_cortical_id();
446        let cortical_type = cortical_id
447            .as_cortical_type()
448            .expect("Failed to get cortical type");
449        let dims = CorticalAreaDimensions::new(10, 10, 10).unwrap();
450        let area = CorticalArea::new(
451            cortical_id,
452            0,
453            "Test".to_string(),
454            dims,
455            (0, 0, 0).into(),
456            cortical_type,
457        )
458        .unwrap()
459        .add_property("resolution".to_string(), serde_json::json!(128))
460        .add_property("modality".to_string(), serde_json::json!("visual"));
461
462        assert_eq!(
463            area.get_property("resolution"),
464            Some(&serde_json::json!(128))
465        );
466        assert_eq!(
467            area.get_property("modality"),
468            Some(&serde_json::json!("visual"))
469        );
470        assert_eq!(area.get_property("nonexistent"), None);
471    }
472}