1use feagi_structures::genomic::cortical_area::CorticalID;
11use feagi_structures::genomic::{
12 validate_instance_fields, ModulatorKind, ModulatorValidationError,
13};
14use serde::{Deserialize, Serialize};
15use serde_json::{json, Value};
16use std::collections::HashMap;
17
18use crate::types::{EvoError, EvoResult};
19
20#[derive(Debug, Clone, PartialEq)]
22pub struct ModulatorInstance {
23 pub kind: ModulatorKind,
24 pub magnitude_percent: f32,
25 pub effect_duration_bursts: u16,
26 pub rest_bursts: u16,
27 pub graded: bool,
28 pub full_scale_potential: Option<f32>,
29 pub driver_cortical_id: CorticalID,
30}
31
32impl ModulatorInstance {
33 pub fn validate(&self) -> Result<(), ModulatorValidationError> {
35 validate_instance_fields(
36 self.magnitude_percent,
37 self.effect_duration_bursts,
38 self.graded,
39 self.full_scale_potential,
40 )
41 }
42
43 pub fn to_json(&self) -> Value {
45 let mut object = serde_json::Map::new();
46 object.insert("type".to_string(), json!(self.kind.as_str()));
47 object.insert(
48 "magnitude_percent".to_string(),
49 json!(self.magnitude_percent),
50 );
51 object.insert(
52 "effect_duration_bursts".to_string(),
53 json!(self.effect_duration_bursts),
54 );
55 object.insert("rest_bursts".to_string(), json!(self.rest_bursts));
56 object.insert("graded".to_string(), json!(self.graded));
57 if let Some(scale) = self.full_scale_potential {
58 object.insert("full_scale_potential".to_string(), json!(scale));
59 }
60 object.insert(
61 "driver_cortical_id".to_string(),
62 json!(self.driver_cortical_id.as_base_64()),
63 );
64 Value::Object(object)
65 }
66}
67
68#[derive(Debug, Clone, Default)]
70pub struct ModulatorRegistry {
71 modulators: HashMap<String, ModulatorInstance>,
72}
73
74impl ModulatorRegistry {
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn insert(&mut self, id: String, instance: ModulatorInstance) {
80 self.modulators.insert(id, instance);
81 }
82
83 pub fn get(&self, id: &str) -> Option<&ModulatorInstance> {
84 self.modulators.get(id)
85 }
86
87 pub fn get_mut(&mut self, id: &str) -> Option<&mut ModulatorInstance> {
88 self.modulators.get_mut(id)
89 }
90
91 pub fn contains(&self, id: &str) -> bool {
92 self.modulators.contains_key(id)
93 }
94
95 pub fn remove(&mut self, id: &str) -> Option<ModulatorInstance> {
96 self.modulators.remove(id)
97 }
98
99 pub fn rename(&mut self, old_id: &str, new_id: String) -> bool {
100 if let Some(instance) = self.modulators.remove(old_id) {
101 self.modulators.insert(new_id, instance);
102 true
103 } else {
104 false
105 }
106 }
107
108 pub fn len(&self) -> usize {
109 self.modulators.len()
110 }
111
112 pub fn is_empty(&self) -> bool {
113 self.modulators.is_empty()
114 }
115
116 pub fn ids(&self) -> Vec<String> {
117 let mut ids: Vec<String> = self.modulators.keys().cloned().collect();
118 ids.sort();
119 ids
120 }
121
122 pub fn iter(&self) -> impl Iterator<Item = (&String, &ModulatorInstance)> {
123 self.modulators.iter()
124 }
125
126 pub fn to_json(&self) -> Value {
128 let mut object = serde_json::Map::new();
129 let mut ids: Vec<&String> = self.modulators.keys().collect();
130 ids.sort();
131 for id in ids {
132 if let Some(instance) = self.modulators.get(id) {
133 object.insert(id.clone(), instance.to_json());
134 }
135 }
136 Value::Object(object)
137 }
138}
139
140pub fn parse_modulator_registry(value: Option<&Value>) -> EvoResult<ModulatorRegistry> {
142 let Some(value) = value else {
143 return Ok(ModulatorRegistry::new());
144 };
145 let Some(object) = value.as_object() else {
146 return Err(EvoError::InvalidGenome(
147 "modulators must be an object".to_string(),
148 ));
149 };
150 let mut registry = ModulatorRegistry::new();
151 for (id, body) in object {
152 registry.insert(id.clone(), parse_modulator_instance(id, body)?);
153 }
154 Ok(registry)
155}
156
157fn parse_modulator_instance(id: &str, body: &Value) -> EvoResult<ModulatorInstance> {
158 let object = body
159 .as_object()
160 .ok_or_else(|| EvoError::InvalidGenome(format!("modulator '{id}' must be an object")))?;
161 let type_name = object
162 .get("type")
163 .and_then(|v| v.as_str())
164 .ok_or_else(|| EvoError::InvalidGenome(format!("modulator '{id}' is missing type")))?;
165 let kind = ModulatorKind::parse(type_name)
166 .map_err(|err| EvoError::InvalidGenome(format!("modulator '{id}': {err}")))?;
167 let magnitude_percent = object
168 .get("magnitude_percent")
169 .and_then(|v| v.as_f64())
170 .ok_or_else(|| {
171 EvoError::InvalidGenome(format!("modulator '{id}' is missing magnitude_percent"))
172 })? as f32;
173 let effect_duration_bursts = object
174 .get("effect_duration_bursts")
175 .and_then(|v| v.as_u64())
176 .ok_or_else(|| {
177 EvoError::InvalidGenome(format!(
178 "modulator '{id}' is missing effect_duration_bursts"
179 ))
180 })?;
181 if effect_duration_bursts > u16::MAX as u64 {
182 return Err(EvoError::InvalidGenome(format!(
183 "modulator '{id}' effect_duration_bursts exceeds u16"
184 )));
185 }
186 let rest_bursts = object
187 .get("rest_bursts")
188 .and_then(|v| v.as_u64())
189 .unwrap_or(0);
190 if rest_bursts > u16::MAX as u64 {
191 return Err(EvoError::InvalidGenome(format!(
192 "modulator '{id}' rest_bursts exceeds u16"
193 )));
194 }
195 let graded = object
196 .get("graded")
197 .and_then(|v| v.as_bool())
198 .unwrap_or(false);
199 let full_scale_potential = object
200 .get("full_scale_potential")
201 .and_then(|v| v.as_f64())
202 .map(|v| v as f32);
203 let driver = object
204 .get("driver_cortical_id")
205 .and_then(|v| v.as_str())
206 .ok_or_else(|| {
207 EvoError::InvalidGenome(format!("modulator '{id}' is missing driver_cortical_id"))
208 })?;
209 let driver_cortical_id = CorticalID::try_from_base_64(driver)
210 .map_err(|err| EvoError::InvalidGenome(format!("modulator '{id}' driver id: {err}")))?;
211 let instance = ModulatorInstance {
212 kind,
213 magnitude_percent,
214 effect_duration_bursts: effect_duration_bursts as u16,
215 rest_bursts: rest_bursts as u16,
216 graded,
217 full_scale_potential,
218 driver_cortical_id,
219 };
220 instance
221 .validate()
222 .map_err(|err| EvoError::InvalidGenome(format!("modulator '{id}': {err}")))?;
223 Ok(instance)
224}
225
226pub fn driver_locked_properties(instance: &ModulatorInstance) -> HashMap<String, Value> {
228 let mut properties = HashMap::new();
229 properties.insert("refractory_period".to_string(), json!(0));
230 properties.insert("spike_train".to_string(), json!(true));
231 properties.insert(
232 "consecutive_fire_cnt_max".to_string(),
233 json!(instance.effect_duration_bursts),
234 );
235 properties.insert(
236 "consecutive_fire_limit".to_string(),
237 json!(instance.effect_duration_bursts),
238 );
239 properties.insert("snooze_length".to_string(), json!(instance.rest_bursts));
240 properties.insert("snooze_period".to_string(), json!(instance.rest_bursts));
241 properties.insert("mp_driven_psp".to_string(), json!(instance.graded));
242 properties.insert("cortical_group".to_string(), json!("MODULATOR"));
243 if let Some(scale) = instance.full_scale_potential {
244 properties.insert("full_scale_potential".to_string(), json!(scale));
245 }
246 properties
247}
248
249pub fn driver_locked_property_names() -> &'static [&'static str] {
251 &[
252 "refractory_period",
253 "spike_train",
254 "consecutive_fire_cnt_max",
255 "consecutive_fire_limit",
256 "neuron_consecutive_fire_count",
257 "consecutive_fire_count",
258 "snooze_length",
259 "snooze_period",
260 "mp_driven_psp",
261 ]
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct ModulatorWrite {
267 #[serde(rename = "type")]
268 pub kind: String,
269 pub magnitude_percent: f32,
270 pub effect_duration_bursts: u16,
271 pub rest_bursts: u16,
272 #[serde(default)]
273 pub graded: bool,
274 #[serde(default)]
275 pub full_scale_potential: Option<f32>,
276}
277
278impl ModulatorWrite {
279 pub fn into_instance(self, driver_cortical_id: CorticalID) -> EvoResult<ModulatorInstance> {
280 let kind = ModulatorKind::parse(&self.kind).map_err(EvoError::InvalidGenome)?;
281 let instance = ModulatorInstance {
282 kind,
283 magnitude_percent: self.magnitude_percent,
284 effect_duration_bursts: self.effect_duration_bursts,
285 rest_bursts: self.rest_bursts,
286 graded: self.graded,
287 full_scale_potential: self.full_scale_potential,
288 driver_cortical_id,
289 };
290 instance
291 .validate()
292 .map_err(|err| EvoError::InvalidGenome(err.0))?;
293 Ok(instance)
294 }
295}