1use std::collections::HashMap;
13
14use crate::types::{BduError, BduResult, Position};
15
16pub use feagi_structures::genomic::cortical_area::{
18 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalID,
19};
20
21pub trait CorticalAreaExt {
23 fn with_properties(self, properties: HashMap<String, serde_json::Value>) -> Self;
25
26 fn add_property(self, key: String, value: serde_json::Value) -> Self;
28
29 fn add_property_mut(&mut self, key: String, value: serde_json::Value);
31
32 fn contains_position(&self, pos: (i32, i32, i32)) -> bool;
34
35 fn to_relative_position(&self, pos: (i32, i32, i32)) -> BduResult<Position>;
37
38 fn to_absolute_position(&self, rel_pos: Position) -> BduResult<(i32, i32, i32)>;
40
41 fn neurons_per_voxel(&self) -> u32;
43
44 fn refractory_period(&self) -> u16;
46
47 fn snooze_period(&self) -> u16;
49
50 fn leak_coefficient(&self) -> f32;
52
53 fn firing_threshold(&self) -> f32;
55
56 fn firing_threshold_limit(&self) -> f32;
58
59 fn get_u32_property(&self, key: &str, default: u32) -> u32;
61
62 fn get_u16_property(&self, key: &str, default: u16) -> u16;
64
65 fn get_f32_property(&self, key: &str, default: f32) -> f32;
68
69 fn get_f64_property(&self, key: &str, default: f64) -> f64;
72
73 fn get_bool_property(&self, key: &str, default: bool) -> bool;
75
76 fn is_input_area(&self) -> bool;
78
79 fn is_output_area(&self) -> bool;
81
82 fn get_cortical_group(&self) -> Option<String>;
84
85 fn visible(&self) -> bool;
87
88 fn sub_group(&self) -> Option<String>;
90
91 fn plasticity_constant(&self) -> f32;
93
94 fn postsynaptic_current(&self) -> f32;
96
97 fn psp_uniform_distribution(&self) -> bool;
100
101 fn degeneration(&self) -> f32;
103
104 fn burst_engine_active(&self) -> bool;
106
107 fn firing_threshold_increment(&self) -> f32;
109
110 fn firing_threshold_increment_x(&self) -> f32;
112
113 fn firing_threshold_increment_y(&self) -> f32;
115
116 fn firing_threshold_increment_z(&self) -> f32;
118
119 fn consecutive_fire_count(&self) -> u32;
121
122 fn leak_variability(&self) -> f32;
124
125 fn neuron_excitability(&self) -> f32;
127
128 fn postsynaptic_current_max(&self) -> f32;
130
131 fn mp_charge_accumulation(&self) -> bool;
133
134 fn mp_driven_psp(&self) -> bool;
136
137 fn init_lifespan(&self) -> u32;
139
140 fn lifespan_growth_rate(&self) -> f32;
142
143 fn longterm_mem_threshold(&self) -> u32;
145}
146
147impl CorticalAreaExt for CorticalArea {
148 fn with_properties(mut self, properties: HashMap<String, serde_json::Value>) -> Self {
149 self.properties = properties;
150 self
151 }
152
153 fn add_property(mut self, key: String, value: serde_json::Value) -> Self {
154 self.properties.insert(key, value);
155 self
156 }
157
158 fn add_property_mut(&mut self, key: String, value: serde_json::Value) {
159 self.properties.insert(key, value);
160 }
161
162 fn contains_position(&self, pos: (i32, i32, i32)) -> bool {
163 let (x, y, z) = pos;
164 let ox = self.position.x;
165 let oy = self.position.y;
166 let oz = self.position.z;
167
168 x >= ox
169 && y >= oy
170 && z >= oz
171 && x < ox + self.dimensions.width as i32
172 && y < oy + self.dimensions.height as i32
173 && z < oz + self.dimensions.depth as i32
174 }
175
176 fn to_relative_position(&self, pos: (i32, i32, i32)) -> BduResult<Position> {
177 if !self.contains_position(pos) {
178 return Err(BduError::OutOfBounds {
179 pos: (pos.0 as u32, pos.1 as u32, pos.2 as u32),
180 dims: (
181 self.dimensions.width as usize,
182 self.dimensions.height as usize,
183 self.dimensions.depth as usize,
184 ),
185 });
186 }
187
188 let ox = self.position.x;
189 let oy = self.position.y;
190 let oz = self.position.z;
191 Ok((
192 (pos.0 - ox) as u32,
193 (pos.1 - oy) as u32,
194 (pos.2 - oz) as u32,
195 ))
196 }
197
198 fn to_absolute_position(&self, rel_pos: Position) -> BduResult<(i32, i32, i32)> {
199 if !self.dimensions.contains(rel_pos) {
200 return Err(BduError::OutOfBounds {
201 pos: rel_pos,
202 dims: (
203 self.dimensions.width as usize,
204 self.dimensions.height as usize,
205 self.dimensions.depth as usize,
206 ),
207 });
208 }
209
210 let ox = self.position.x;
211 let oy = self.position.y;
212 let oz = self.position.z;
213 Ok((
214 ox + rel_pos.0 as i32,
215 oy + rel_pos.1 as i32,
216 oz + rel_pos.2 as i32,
217 ))
218 }
219
220 fn neurons_per_voxel(&self) -> u32 {
221 self.get_u32_property("neurons_per_voxel", 1)
222 }
223
224 fn refractory_period(&self) -> u16 {
225 self.get_u16_property("refractory_period", 0)
226 }
227
228 fn snooze_period(&self) -> u16 {
229 self.get_u16_property("snooze_period", 0)
230 }
231
232 fn leak_coefficient(&self) -> f32 {
233 self.get_f32_property("leak_coefficient", 0.0)
234 }
235
236 fn firing_threshold(&self) -> f32 {
237 self.get_f32_property("firing_threshold", 1.0)
238 }
239
240 fn get_u32_property(&self, key: &str, default: u32) -> u32 {
241 self.properties
242 .get(key)
243 .and_then(|v| v.as_u64())
244 .map(|v| v as u32)
245 .unwrap_or(default)
246 }
247
248 fn get_u16_property(&self, key: &str, default: u16) -> u16 {
249 self.properties
250 .get(key)
251 .and_then(|v| v.as_u64())
252 .map(|v| v as u16)
253 .unwrap_or(default)
254 }
255
256 fn get_f32_property(&self, key: &str, default: f32) -> f32 {
257 self.properties
258 .get(key)
259 .and_then(|v| v.as_f64())
260 .map(|v| v as f32)
261 .unwrap_or(default)
262 }
263
264 fn get_f64_property(&self, key: &str, default: f64) -> f64 {
265 self.properties
266 .get(key)
267 .and_then(|v| v.as_f64())
268 .unwrap_or(default)
269 }
270
271 fn get_bool_property(&self, key: &str, default: bool) -> bool {
272 self.properties
273 .get(key)
274 .and_then(|v| v.as_bool())
275 .unwrap_or(default)
276 }
277
278 fn is_input_area(&self) -> bool {
279 matches!(
280 self.cortical_type,
281 feagi_structures::genomic::cortical_area::CorticalAreaType::BrainInput(_)
282 )
283 }
284
285 fn is_output_area(&self) -> bool {
286 matches!(
287 self.cortical_type,
288 feagi_structures::genomic::cortical_area::CorticalAreaType::BrainOutput(_)
289 )
290 }
291
292 fn get_cortical_group(&self) -> Option<String> {
293 self.properties
294 .get("cortical_group")
295 .and_then(|v| v.as_str())
296 .map(|s| s.to_string())
297 .or_else(|| {
298 use feagi_structures::genomic::cortical_area::CorticalAreaType;
300 match self.cortical_type {
301 CorticalAreaType::BrainInput(_) => Some("IPU".to_string()),
302 CorticalAreaType::BrainOutput(_) => Some("OPU".to_string()),
303 CorticalAreaType::Memory(_) => Some("MEMORY".to_string()),
304 CorticalAreaType::Custom(_) => Some("CUSTOM".to_string()),
305 CorticalAreaType::Core(_) => Some("CORE".to_string()),
306 }
307 })
308 }
309
310 fn visible(&self) -> bool {
311 self.get_bool_property("visible", true)
312 }
313
314 fn sub_group(&self) -> Option<String> {
315 self.properties
316 .get("sub_group")
317 .and_then(|v| v.as_str())
318 .map(|s| s.to_string())
319 }
320
321 fn plasticity_constant(&self) -> f32 {
322 self.get_f32_property("plasticity_constant", 0.0)
323 }
324
325 fn postsynaptic_current(&self) -> f32 {
326 self.get_f32_property("postsynaptic_current", 1.0)
327 }
328
329 fn psp_uniform_distribution(&self) -> bool {
330 let default = matches!(
331 self.cortical_type,
332 feagi_structures::genomic::cortical_area::CorticalAreaType::Memory(_)
333 | feagi_structures::genomic::cortical_area::CorticalAreaType::Core(
334 CoreCorticalType::Power
335 )
336 );
337 self.get_bool_property("psp_uniform_distribution", default)
338 }
339
340 fn degeneration(&self) -> f32 {
341 self.get_f32_property("degeneration", 0.0)
342 }
343
344 fn burst_engine_active(&self) -> bool {
345 self.get_bool_property("burst_engine_active", false)
346 }
347
348 fn firing_threshold_increment(&self) -> f32 {
349 self.get_f32_property("firing_threshold_increment", 0.0)
350 }
351
352 fn firing_threshold_increment_x(&self) -> f32 {
353 self.get_f32_property("firing_threshold_increment_x", 0.0)
354 }
355
356 fn firing_threshold_increment_y(&self) -> f32 {
357 self.get_f32_property("firing_threshold_increment_y", 0.0)
358 }
359
360 fn firing_threshold_increment_z(&self) -> f32 {
361 self.get_f32_property("firing_threshold_increment_z", 0.0)
362 }
363
364 fn firing_threshold_limit(&self) -> f32 {
365 self.get_f32_property("firing_threshold_limit", 0.0)
366 }
367
368 fn consecutive_fire_count(&self) -> u32 {
369 self.get_u32_property("consecutive_fire_limit", 0)
370 }
371
372 fn leak_variability(&self) -> f32 {
373 self.get_f32_property("leak_variability", 0.0)
374 }
375
376 fn neuron_excitability(&self) -> f32 {
377 self.get_f32_property("neuron_excitability", 100.0)
378 }
379
380 fn postsynaptic_current_max(&self) -> f32 {
381 self.get_f32_property("postsynaptic_current_max", 0.0)
382 }
383
384 fn mp_charge_accumulation(&self) -> bool {
385 self.get_bool_property("mp_charge_accumulation", false)
386 }
387
388 fn mp_driven_psp(&self) -> bool {
389 self.get_bool_property("mp_driven_psp", false)
390 }
391
392 fn init_lifespan(&self) -> u32 {
393 self.get_u32_property("init_lifespan", 0)
394 }
395
396 fn lifespan_growth_rate(&self) -> f32 {
397 self.get_f32_property("lifespan_growth_rate", 0.0)
398 }
399
400 fn longterm_mem_threshold(&self) -> u32 {
401 self.get_u32_property("longterm_mem_threshold", 0)
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn test_contains_position() {
411 let cortical_id = CoreCorticalType::Power.to_cortical_id();
412 let cortical_type = cortical_id
413 .as_cortical_type()
414 .expect("Failed to get cortical type");
415 let dims = CorticalAreaDimensions::new(10, 10, 10).unwrap();
416 let area = CorticalArea::new(
417 cortical_id,
418 0,
419 "Test Area".to_string(),
420 dims,
421 (5, 5, 5).into(),
422 cortical_type,
423 )
424 .unwrap();
425
426 assert!(area.contains_position((5, 5, 5))); assert!(area.contains_position((14, 14, 14))); assert!(!area.contains_position((4, 5, 5))); assert!(!area.contains_position((15, 5, 5))); }
431
432 #[test]
433 fn test_position_conversion() {
434 let cortical_id = CoreCorticalType::Power.to_cortical_id();
435 let cortical_type = cortical_id
436 .as_cortical_type()
437 .expect("Failed to get cortical type");
438 let dims = CorticalAreaDimensions::new(10, 10, 10).unwrap();
439 let area = CorticalArea::new(
440 cortical_id,
441 0,
442 "Test Area".to_string(),
443 dims,
444 (100, 200, 300).into(),
445 cortical_type,
446 )
447 .unwrap();
448
449 let rel_pos = area.to_relative_position((105, 207, 308)).unwrap();
452 assert_eq!(rel_pos, (5, 7, 8));
453
454 let abs_pos = area.to_absolute_position(rel_pos).unwrap();
456 assert_eq!(abs_pos, (105, 207, 308));
457
458 let result = area.to_relative_position((99, 200, 300));
460 assert!(result.is_err());
461 }
462
463 #[test]
464 fn test_properties() {
465 let cortical_id = CoreCorticalType::Power.to_cortical_id();
466 let cortical_type = cortical_id
467 .as_cortical_type()
468 .expect("Failed to get cortical type");
469 let dims = CorticalAreaDimensions::new(10, 10, 10).unwrap();
470 let area = CorticalArea::new(
471 cortical_id,
472 0,
473 "Test".to_string(),
474 dims,
475 (0, 0, 0).into(),
476 cortical_type,
477 )
478 .unwrap()
479 .add_property("resolution".to_string(), serde_json::json!(128))
480 .add_property("modality".to_string(), serde_json::json!("visual"));
481
482 assert_eq!(
483 area.get_property("resolution"),
484 Some(&serde_json::json!(128))
485 );
486 assert_eq!(
487 area.get_property("modality"),
488 Some(&serde_json::json!("visual"))
489 );
490 assert_eq!(area.get_property("nonexistent"), None);
491 }
492
493 #[test]
494 fn test_power_defaults_zero_degeneration_and_psp_uniform() {
495 let cortical_id = CoreCorticalType::Power.to_cortical_id();
496 let cortical_type = cortical_id
497 .as_cortical_type()
498 .expect("Failed to get cortical type");
499 let dims = CorticalAreaDimensions::new(1, 1, 1).unwrap();
500 let area = CorticalArea::new(
501 cortical_id,
502 1,
503 "Brain_Power".to_string(),
504 dims,
505 (0, 0, -20).into(),
506 cortical_type,
507 )
508 .unwrap();
509
510 assert_eq!(area.degeneration(), 0.0);
511 assert!(
512 area.psp_uniform_distribution(),
513 "Power must default to PSP uniformity when the genome omits the flag"
514 );
515 }
516}