1use limbic_critic::ModulatorVector;
13use neuromod::{NeuroModulators, SpikingNetwork, StepError};
14
15#[inline]
20pub fn to_neuromodulators(v: &ModulatorVector) -> NeuroModulators {
21 NeuroModulators {
22 dopamine: v.dopamine,
23 serotonin: v.serotonin,
24 acetylcholine: v.acetylcholine,
25 norepinephrine: v.norepinephrine,
26 }
27}
28
29#[inline]
33pub fn from_neuromodulators(m: &NeuroModulators) -> ModulatorVector {
34 ModulatorVector {
35 dopamine: m.dopamine,
36 serotonin: m.serotonin,
37 acetylcholine: m.acetylcholine,
38 norepinephrine: m.norepinephrine,
39 }
40}
41
42#[inline]
47pub fn apply_modulator_vector(
48 network: &mut SpikingNetwork,
49 stimuli: &[f32],
50 vector: &ModulatorVector,
51) -> Result<Vec<usize>, StepError> {
52 network.step(stimuli, &to_neuromodulators(vector))
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use limbic_critic::{Environment, SimpleCritic, TDCritic};
59
60 #[test]
61 fn mapping_is_one_to_one() {
62 let v = ModulatorVector {
63 dopamine: 0.7,
64 serotonin: 0.3,
65 acetylcholine: 0.55,
66 norepinephrine: 0.2,
67 };
68 let m = to_neuromodulators(&v);
69 assert_eq!(m.dopamine, 0.7);
70 assert_eq!(m.serotonin, 0.3);
71 assert_eq!(m.acetylcholine, 0.55);
72 assert_eq!(m.norepinephrine, 0.2);
73 }
74
75 #[test]
76 fn roundtrip_preserves_fields() {
77 let original = ModulatorVector {
78 dopamine: 0.11,
79 serotonin: 0.22,
80 acetylcholine: 0.33,
81 norepinephrine: 0.44,
82 };
83 let roundtrip = from_neuromodulators(&to_neuromodulators(&original));
84 assert_eq!(roundtrip, original);
85 }
86
87 #[test]
88 fn defaults_map_to_defaults() {
89 let m = to_neuromodulators(&ModulatorVector::default());
90 assert_eq!(m, NeuroModulators::default());
91 let v = from_neuromodulators(&NeuroModulators::default());
92 assert_eq!(v, ModulatorVector::default());
93 }
94
95 struct ConstEnv {
96 objective: f32,
97 stress: f32,
98 volatility: f32,
99 surprise: f32,
100 }
101
102 impl Environment for ConstEnv {
103 fn objective(&self) -> f32 {
104 self.objective
105 }
106 fn stress(&self) -> f32 {
107 self.stress
108 }
109 fn volatility(&self) -> f32 {
110 self.volatility
111 }
112 fn surprise(&self) -> f32 {
113 self.surprise
114 }
115 }
116
117 #[test]
118 fn simple_critic_assess_maps_into_neuromodulators() {
119 let env = ConstEnv {
120 objective: 0.8,
121 stress: 0.4,
122 volatility: 0.25,
123 surprise: 0.6,
124 };
125 let vector = SimpleCritic::assess(&env);
126 let mods = to_neuromodulators(&vector);
127
128 assert_eq!(mods.dopamine, 0.8);
129 assert_eq!(mods.norepinephrine, 0.4);
130 assert_eq!(mods.serotonin, 0.25);
131 assert_eq!(mods.acetylcholine, 0.6);
132 }
133
134 #[test]
135 fn td_critic_assess_maps_into_neuromodulators() {
137 let env = ConstEnv {
138 objective: 0.5,
139 stress: 0.1,
140 volatility: 0.0,
141 surprise: 0.0,
142 };
143 let mut critic = TDCritic::new(0.1).expect("finite alpha in (0, 1]");
144 let vector = critic.assess(&env);
145 let mods = to_neuromodulators(&vector);
146
147 assert!((mods.dopamine - 0.05f32.tanh()).abs() < 1e-6);
149 assert!((mods.acetylcholine - 0.5f32.tanh()).abs() < 1e-6);
150 assert_eq!(mods.norepinephrine, 0.1);
151 assert_eq!(mods.serotonin, 0.0);
152 }
153
154 #[test]
155 fn apply_modulator_vector_steps_network() {
156 let mut network = SpikingNetwork::with_dimensions(4, 2, 8);
157 let stimuli = vec![0.25; 8];
158 let vector = ModulatorVector {
159 dopamine: 0.5,
160 serotonin: 0.1,
161 acetylcholine: 0.4,
162 norepinephrine: 0.2,
163 };
164 let spikes = apply_modulator_vector(&mut network, &stimuli, &vector)
165 .expect("step with bridged modulators");
166 let _ = spikes;
167 assert!((network.modulators.dopamine - 0.5).abs() < 1e-6);
168 assert!((network.modulators.norepinephrine - 0.2).abs() < 1e-6);
169 }
170}