use crate::{EvoError, EvoResult};
use serde_json::{json, Value};
use std::collections::{HashMap, HashSet};
use tracing::warn;
const PROPERTY_MAPPINGS: &[(&str, &str)] = &[
("_n_cnt-i", "per_voxel_neuron_cnt"),
("gd_vis-b", "visualization"),
("__name-t", "cortical_name"),
("rcordx-i", "relative_coordinate"),
("rcordy-i", "relative_coordinate"),
("rcordz-i", "relative_coordinate"),
("2dcorx-i", "2d_coordinate"),
("2dcory-i", "2d_coordinate"),
("___bbx-i", "block_boundaries"),
("___bby-i", "block_boundaries"),
("___bbz-i", "block_boundaries"),
("__rand-b", "location_generation_type"),
("synatt-f", "synapse_attractivity"),
("pstcr_-f", "postsynaptic_current"),
("pstcrm-f", "postsynaptic_current_max"),
("fire_t-f", "firing_threshold"),
("ftincx-f", "firing_threshold_increment_x"),
("ftincy-f", "firing_threshold_increment_y"),
("ftincz-f", "firing_threshold_increment_z"),
("fthlim-f", "firing_threshold_limit"),
("refrac-i", "refractory_period"),
("leak_c-f", "leak_coefficient"),
("leak_v-f", "leak_variability"),
("c_fr_c-i", "consecutive_fire_cnt_max"),
("snooze-f", "snooze_length"),
("_group-t", "group_id"),
("subgrp-t", "sub_group_id"),
("_group-t", "cortical_group"),
("dstmap-d", "cortical_mapping_dst"),
("hmlk-d", "rate_modulated_leak"),
("de_gen-f", "degeneration"),
("pspuni-b", "psp_uniform_distribution"),
("mp_acc-b", "mp_charge_accumulation"),
("mp_psp-b", "mp_driven_psp"),
("memory-b", "is_mem_type"),
("mem__t-i", "longterm_mem_threshold"),
("mem_gr-i", "lifespan_growth_rate"),
("mem_ls-i", "init_lifespan"),
("tmpdpt-i", "temporal_depth"),
("mplrn-b", "mp_learning_enabled"),
("excite-f", "neuron_excitability"),
("devcnt-i", "dev_count"),
("twinrf-t", "memory_twin_of"),
];
fn build_property_map() -> HashMap<String, String> {
PROPERTY_MAPPINGS
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
fn create_area_template() -> serde_json::Map<String, Value> {
let mut template = serde_json::Map::new();
template.insert("cortical_name".to_string(), json!(""));
template.insert("group_id".to_string(), json!("CUSTOM"));
template.insert("block_boundaries".to_string(), json!([1, 1, 1]));
template.insert("relative_coordinate".to_string(), json!([0, 0, 0]));
template.insert("2d_coordinate".to_string(), json!([0, 0]));
template.insert("cortical_mapping_dst".to_string(), json!({}));
template.insert("location_generation_type".to_string(), json!("sequential"));
template.insert("per_voxel_neuron_cnt".to_string(), json!(1));
template.insert("visualization".to_string(), json!(true));
template.insert("firing_threshold".to_string(), json!(1.0));
template.insert("refractory_period".to_string(), json!(0));
template.insert("leak_coefficient".to_string(), json!(0.0));
template.insert("neuron_excitability".to_string(), json!(1.0));
template.insert("postsynaptic_current".to_string(), json!(1.0));
template.insert("psp_uniform_distribution".to_string(), json!(false));
template
}
pub fn convert_flat_to_hierarchical_full(flat_genome: &Value) -> EvoResult<Value> {
let flat_blueprint = if let Some(bp) = flat_genome.get("blueprint") {
bp.as_object().ok_or_else(|| {
EvoError::InvalidGenome("Flat genome blueprint must be an object".to_string())
})?
} else {
return Err(EvoError::InvalidGenome(
"Flat genome missing blueprint section".to_string(),
));
};
let property_map = build_property_map();
let cortical_areas = extract_cortical_areas(flat_blueprint)?;
let visualization_overrides: HashMap<String, Value> =
if let Some(overrides_obj) = flat_genome.get("visualization_voxel_granularity_overrides") {
if let Some(overrides_map) = overrides_obj.as_object() {
overrides_map
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
} else {
HashMap::new()
}
} else {
HashMap::new()
};
let mut hierarchical_blueprint = serde_json::Map::new();
for cortical_id in &cortical_areas {
let mut area_data = create_area_template();
process_area_properties(cortical_id, flat_blueprint, &property_map, &mut area_data)?;
if let Some(override_value) = visualization_overrides.get(cortical_id) {
if let Some(properties) = area_data.get_mut("properties") {
if let Some(properties_obj) = properties.as_object_mut() {
properties_obj.insert(
"visualization_voxel_granularity".to_string(),
override_value.clone(),
);
}
}
}
hierarchical_blueprint.insert(cortical_id.clone(), Value::Object(area_data));
}
let mut hierarchical = serde_json::Map::new();
hierarchical.insert(
"blueprint".to_string(),
Value::Object(hierarchical_blueprint),
);
if let Some(morphologies) = flat_genome.get("neuron_morphologies") {
hierarchical.insert("neuron_morphologies".to_string(), morphologies.clone());
}
if let Some(physiology) = flat_genome.get("physiology") {
hierarchical.insert("physiology".to_string(), physiology.clone());
} else {
hierarchical.insert("physiology".to_string(), json!({}));
}
if let Some(stats) = flat_genome.get("stats") {
hierarchical.insert("stats".to_string(), stats.clone());
}
if let Some(signatures) = flat_genome.get("signatures") {
hierarchical.insert("signatures".to_string(), signatures.clone());
}
for field in &["genome_id", "genome_title", "version", "timestamp"] {
if let Some(value) = flat_genome.get(field) {
hierarchical.insert(field.to_string(), value.clone());
}
}
if let Some(br) = flat_genome.get("brain_regions") {
hierarchical.insert("brain_regions".to_string(), br.clone());
} else {
hierarchical.insert("brain_regions".to_string(), json!({}));
}
if let Some(root) = flat_genome.get("brain_regions_root") {
hierarchical.insert("brain_regions_root".to_string(), root.clone());
}
Ok(Value::Object(hierarchical))
}
fn extract_cortical_areas(
flat_blueprint: &serde_json::Map<String, Value>,
) -> EvoResult<HashSet<String>> {
let mut areas = HashSet::new();
for key in flat_blueprint.keys() {
if let Some(cortical_id) = parse_cortical_id(key) {
areas.insert(cortical_id);
}
}
Ok(areas)
}
fn parse_cortical_id(key: &str) -> Option<String> {
if !key.starts_with("_____10c-") {
return None;
}
let parts: Vec<&str> = key.split('-').collect();
if parts.len() >= 2 {
Some(parts[1].to_string())
} else {
None
}
}
fn process_area_properties(
cortical_id: &str,
flat_blueprint: &serde_json::Map<String, Value>,
property_map: &HashMap<String, String>,
area_data: &mut serde_json::Map<String, Value>,
) -> EvoResult<()> {
for (flat_key, flat_value) in flat_blueprint.iter() {
if let Some(key_area_id) = parse_cortical_id(flat_key) {
if key_area_id != cortical_id {
continue;
}
let parts: Vec<&str> = flat_key.split('-').collect();
if parts.len() < 3 {
continue;
}
let exon = parts[2..].join("-");
let exon_without_prefix = if parts.len() > 3 {
parts[3..].join("-")
} else {
exon.clone()
};
let lookup_key = if property_map.contains_key(&exon) {
&exon
} else if property_map.contains_key(&exon_without_prefix) {
&exon_without_prefix
} else {
continue;
};
let hierarchical_prop = &property_map[lookup_key];
match hierarchical_prop.as_str() {
"cortical_name" => {
area_data.insert(hierarchical_prop.clone(), flat_value.clone());
}
"location_generation_type" => {
let value = if flat_value.as_bool().unwrap_or(false) {
"random"
} else {
"sequential"
};
area_data.insert(hierarchical_prop.clone(), json!(value));
}
"cortical_mapping_dst" => {
process_dstmap(flat_value, area_data)?;
}
"block_boundaries" | "relative_coordinate" | "2d_coordinate" => {
process_coordinate_property(
flat_key,
flat_value,
hierarchical_prop,
area_data,
)?;
}
_ => {
area_data.insert(hierarchical_prop.clone(), flat_value.clone());
}
}
}
}
Ok(())
}
fn process_coordinate_property(
flat_key: &str,
flat_value: &Value,
prop_name: &str,
area_data: &mut serde_json::Map<String, Value>,
) -> EvoResult<()> {
const NULL_2D_JITTER_SPREAD: i32 = 30;
let axis_char = flat_key.chars().rev().nth(2).unwrap_or('x');
let index = match axis_char {
'x' => 0,
'y' => 1,
'z' => 2,
_ => return Ok(()),
};
if !area_data.contains_key(prop_name) {
let default_array = if prop_name == "2d_coordinate" {
json!([0, 0])
} else {
json!([0, 0, 0])
};
area_data.insert(prop_name.to_string(), default_array);
}
if flat_value.is_null() {
let cortical_id = flat_key.split('-').nth(1).unwrap_or("<unknown>");
if prop_name == "2d_coordinate" {
let mut h: u32 = 2166136261;
for b in cortical_id.as_bytes() {
h ^= *b as u32;
h = h.wrapping_mul(16777619);
}
let spread = NULL_2D_JITTER_SPREAD.max(0);
let jitter = if spread == 0 {
0
} else {
let span = (spread * 2 + 1) as u32;
let raw = if index == 0 { h } else { h.rotate_left(16) };
(raw % span) as i32 - spread
};
if let Some(arr) = area_data.get_mut(prop_name).and_then(|v| v.as_array_mut()) {
if index < arr.len() {
arr[index] = json!(jitter);
}
}
tracing::warn!(
target: "feagi-evo",
"⚠️ [GENOME-LOAD] Null 2D coordinate '{}' for cortical_id='{}' axis={} -> jitter={}",
flat_key,
cortical_id,
axis_char,
jitter
);
return Ok(());
}
tracing::warn!(
target: "feagi-evo",
"⚠️ [GENOME-LOAD] Null coordinate value for key '{}' ({} axis={}); defaulting to 0",
flat_key,
prop_name,
axis_char
);
return Ok(());
}
if let Some(arr) = area_data.get_mut(prop_name).and_then(|v| v.as_array_mut()) {
if index < arr.len() {
arr[index] = flat_value.clone();
}
}
if prop_name == "block_boundaries" {
if !area_data.contains_key("cortical_dimensions") {
area_data.insert("cortical_dimensions".to_string(), json!({}));
}
if let Some(dims) = area_data
.get_mut("cortical_dimensions")
.and_then(|v| v.as_object_mut())
{
let dim_name = match index {
0 => "width",
1 => "height",
2 => "depth",
_ => return Ok(()),
};
dims.insert(dim_name.to_string(), flat_value.clone());
}
} else if prop_name == "relative_coordinate" {
if !area_data.contains_key("coordinates_3d") {
area_data.insert("coordinates_3d".to_string(), json!({}));
}
if let Some(coords) = area_data
.get_mut("coordinates_3d")
.and_then(|v| v.as_object_mut())
{
let coord_name = match index {
0 => "x",
1 => "y",
2 => "z",
_ => return Ok(()),
};
coords.insert(coord_name.to_string(), flat_value.clone());
}
}
Ok(())
}
fn process_dstmap(
dstmap_value: &Value,
area_data: &mut serde_json::Map<String, Value>,
) -> EvoResult<()> {
let dstmap_obj = match dstmap_value.as_object() {
Some(obj) => obj,
None => return Ok(()), };
let mut hierarchical_dstmap = serde_json::Map::new();
for (destination_area, rules) in dstmap_obj {
let rules_array = match rules.as_array() {
Some(arr) => arr,
None => continue,
};
let mut converted_rules = Vec::new();
for rule in rules_array {
if let Some(rule_obj) = rule.as_object() {
if !rule_obj.contains_key("morphology_id")
|| !rule_obj.contains_key("postSynapticCurrent_multiplier")
|| !rule_obj.contains_key("plasticity_flag")
{
warn!(
target: "feagi-evo",
"Invalid dstmap rule object for destination {}: missing required keys",
destination_area
);
continue;
}
if rule_obj.get("plasticity_flag").and_then(|v| v.as_bool()) == Some(true) {
let required = [
"plasticity_constant",
"ltp_multiplier",
"ltd_multiplier",
"plasticity_window",
];
let missing: Vec<&str> = required
.iter()
.copied()
.filter(|k| !rule_obj.contains_key(*k))
.collect();
if !missing.is_empty() {
warn!(
target: "feagi-evo",
"Invalid plastic dstmap rule object for destination {}: missing keys {:?}",
destination_area,
missing
);
continue;
}
}
converted_rules.push(Value::Object(rule_obj.clone()));
continue;
}
let rule_array = match rule.as_array() {
Some(arr) => arr,
None => continue,
};
if rule_array.len() < 4 {
warn!(
target: "feagi-evo",
"Invalid mapping recipe format (need at least 4 elements): {:?}",
rule_array
);
continue;
}
let plasticity_constant = rule_array
.get(4)
.cloned()
.unwrap_or(serde_json::Value::Number(serde_json::Number::from(0)));
let ltp_multiplier = rule_array
.get(5)
.cloned()
.unwrap_or(serde_json::Value::Number(serde_json::Number::from(0)));
let ltd_multiplier = rule_array
.get(6)
.cloned()
.unwrap_or(serde_json::Value::Number(serde_json::Number::from(0)));
let plasticity_window = rule_array
.get(7)
.cloned()
.unwrap_or(serde_json::Value::Number(serde_json::Number::from(0)));
let synaptic_delay_bursts = rule_array
.get(8)
.cloned()
.unwrap_or(serde_json::Value::Number(serde_json::Number::from(1)));
let mut rule_dict = serde_json::Map::new();
rule_dict.insert("morphology_id".to_string(), rule_array[0].clone());
rule_dict.insert("morphology_scalar".to_string(), rule_array[1].clone());
rule_dict.insert(
"postSynapticCurrent_multiplier".to_string(),
rule_array[2].clone(),
);
rule_dict.insert("plasticity_flag".to_string(), rule_array[3].clone());
rule_dict.insert("plasticity_constant".to_string(), plasticity_constant);
rule_dict.insert("ltp_multiplier".to_string(), ltp_multiplier);
rule_dict.insert("ltd_multiplier".to_string(), ltd_multiplier);
rule_dict.insert("plasticity_window".to_string(), plasticity_window);
rule_dict.insert("synaptic_delay_bursts".to_string(), synaptic_delay_bursts);
converted_rules.push(Value::Object(rule_dict));
}
if !converted_rules.is_empty() {
hierarchical_dstmap.insert(destination_area.clone(), Value::Array(converted_rules));
}
}
area_data.insert(
"cortical_mapping_dst".to_string(),
Value::Object(hierarchical_dstmap),
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn test_property_map_completeness() {
let map = build_property_map();
let unique_flat_keys: HashSet<&str> = PROPERTY_MAPPINGS
.iter()
.map(|(flat_key, _)| *flat_key)
.collect();
assert_eq!(
map.len(),
unique_flat_keys.len(),
"lookup table size must match unique flat keys (duplicate flat keys keep last hierarchical target)"
);
assert!(map.contains_key("__name-t"));
assert!(map.contains_key("dstmap-d"));
assert!(map.contains_key("fire_t-f"));
assert!(map.contains_key("twinrf-t"));
}
#[test]
fn test_dstmap_parsing() {
let dstmap_flat = json!({
"dest_area": [
["block_to_block", 1, 1.0, true, 1, 1, 1, 4, 3],
["projector", 2, 0.5, false, 1, 1, 1, 1, 5]
]
});
let mut area_data = serde_json::Map::new();
process_dstmap(&dstmap_flat, &mut area_data).unwrap();
let dstmap = area_data.get("cortical_mapping_dst").unwrap();
let dest_rules = dstmap.get("dest_area").unwrap().as_array().unwrap();
assert_eq!(dest_rules.len(), 2);
assert_eq!(dest_rules[0]["morphology_id"], "block_to_block");
assert_eq!(dest_rules[0]["plasticity_constant"], 1);
assert_eq!(dest_rules[0]["plasticity_window"], 4);
assert_eq!(dest_rules[0]["synaptic_delay_bursts"], 3);
assert_eq!(dest_rules[1]["morphology_id"], "projector");
assert_eq!(dest_rules[1]["plasticity_constant"], 1);
assert_eq!(dest_rules[1]["plasticity_window"], 1);
assert_eq!(dest_rules[1]["synaptic_delay_bursts"], 5);
}
#[test]
fn test_dstmap_parsing_legacy_4_element_backward_compat() {
let dstmap_flat = json!({
"dest_area": [
["motor_backward", [1, 1, 1], 1, false],
["block_to_block", [1, 1, 1], 1, false]
]
});
let mut area_data = serde_json::Map::new();
process_dstmap(&dstmap_flat, &mut area_data).unwrap();
let dstmap = area_data.get("cortical_mapping_dst").unwrap();
let dest_rules = dstmap.get("dest_area").unwrap().as_array().unwrap();
assert_eq!(dest_rules.len(), 2);
assert_eq!(dest_rules[0]["morphology_id"], "motor_backward");
assert_eq!(dest_rules[0]["plasticity_flag"], false);
assert_eq!(dest_rules[0]["plasticity_constant"], 0);
assert_eq!(dest_rules[0]["ltp_multiplier"], 0);
assert_eq!(dest_rules[0]["ltd_multiplier"], 0);
assert_eq!(dest_rules[0]["plasticity_window"], 0);
assert_eq!(dest_rules[0]["synaptic_delay_bursts"], 1);
assert_eq!(dest_rules[1]["synaptic_delay_bursts"], 1);
}
#[test]
fn test_dstmap_parsing_object_rules_passthrough() {
let dstmap_flat = json!({
"dest_area": [
{
"morphology_id": "projector",
"morphology_scalar": [1, 1, 1],
"postSynapticCurrent_multiplier": 1,
"plasticity_flag": false
}
]
});
let mut area_data = serde_json::Map::new();
process_dstmap(&dstmap_flat, &mut area_data).unwrap();
let dstmap = area_data.get("cortical_mapping_dst").unwrap();
let dest_rules = dstmap.get("dest_area").unwrap().as_array().unwrap();
assert_eq!(dest_rules.len(), 1);
assert_eq!(dest_rules[0]["morphology_id"], "projector");
assert_eq!(dest_rules[0]["postSynapticCurrent_multiplier"], 1);
assert_eq!(dest_rules[0]["plasticity_flag"], false);
}
#[test]
fn test_null_coordinates_default_to_zero() {
let flat = json!({
"version": "2.0",
"blueprint": {
"_____10c-CIStra-cx-2dcorx-i": null,
"_____10c-CIStra-cx-2dcory-i": null,
"_____10c-CIStra-cx-rcordx-i": 10,
"_____10c-CIStra-cx-rcordy-i": null,
"_____10c-CIStra-cx-rcordz-i": -20,
"_____10c-CIStra-cx-___bbx-i": 1,
"_____10c-CIStra-cx-___bby-i": 1,
"_____10c-CIStra-cx-___bbz-i": 1,
"_____10c-CIStra-cx-__name-t": "train_forward"
},
"brain_regions": null,
"neuron_morphologies": {},
"physiology": {}
});
let hierarchical = convert_flat_to_hierarchical_full(&flat).unwrap();
let blueprint = hierarchical
.get("blueprint")
.and_then(|v| v.as_object())
.unwrap();
let area = blueprint.get("CIStra").and_then(|v| v.as_object()).unwrap();
let coords_2d = area.get("2d_coordinate").unwrap().as_array().unwrap();
assert_eq!(coords_2d.len(), 2);
assert!(coords_2d[0].as_i64().is_some());
assert!(coords_2d[1].as_i64().is_some());
assert_eq!(
area.get("relative_coordinate").unwrap(),
&json!([10, 0, -20])
);
}
}