sc_neurocore_engine/neurons/rate/
mcculloch_pitts.rs1#[derive(Clone, Debug)]
11pub struct McCullochPittsNeuron {
12 pub theta: i32,
13}
14
15impl McCullochPittsNeuron {
16 pub fn new(theta: i32) -> Result<Self, String> {
18 if theta <= 0 {
19 return Err("theta must be a positive signed 32-bit integer".into());
20 }
21 Ok(Self { theta })
22 }
23
24 pub fn validate(&self) -> Result<(), String> {
26 if self.theta <= 0 {
27 return Err("theta must be a positive signed 32-bit integer".into());
28 }
29 Ok(())
30 }
31
32 pub fn try_step(&self, excitatory_count: i32, inhibitory_active: bool) -> Result<i32, String> {
34 self.validate()?;
35 if excitatory_count < 0 {
36 return Err("excitatory_count must be a non-negative signed 32-bit integer".into());
37 }
38 Ok(i32::from(
39 !inhibitory_active && excitatory_count >= self.theta,
40 ))
41 }
42}
43impl Default for McCullochPittsNeuron {
44 fn default() -> Self {
45 Self::new(1).expect("the default McCulloch-Pitts threshold is valid")
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn mcp_threshold() {
55 let n = McCullochPittsNeuron::default();
56 assert_eq!(n.try_step(2, false), Ok(1));
57 assert_eq!(n.try_step(0, false), Ok(0));
58 }
59
60 #[test]
61 fn mcp_below_threshold() {
62 let n = McCullochPittsNeuron::default();
63 assert_eq!(n.try_step(0, false), Ok(0));
64 }
65
66 #[test]
67 fn mcp_absolute_inhibition_vetoes_maximum_excitation() {
68 let n = McCullochPittsNeuron::default();
69 assert_eq!(n.try_step(i32::MAX, true), Ok(0));
70 }
71
72 #[test]
73 fn mcp_theta_two_is_and() {
74 let n = McCullochPittsNeuron::new(2).unwrap();
75 assert_eq!(n.try_step(0, false), Ok(0));
76 assert_eq!(n.try_step(1, false), Ok(0));
77 assert_eq!(n.try_step(2, false), Ok(1));
78 }
79
80 #[test]
81 fn mcp_rejects_non_positive_thresholds() {
82 assert!(McCullochPittsNeuron::new(0).is_err());
83 assert!(McCullochPittsNeuron::new(-1).is_err());
84 }
85
86 #[test]
87 fn mcp_rejects_negative_excitation() {
88 let n = McCullochPittsNeuron::default();
89 assert!(n.try_step(-1, false).is_err());
90 }
91
92 #[test]
93 fn mcp_revalidates_public_threshold_mutation() {
94 let n = McCullochPittsNeuron { theta: 0 };
95 assert!(n.try_step(1, false).is_err());
96 }
97
98 #[test]
99 fn mcp_is_stateless_across_history() {
100 let n = McCullochPittsNeuron::new(2).unwrap();
101 let outputs: Vec<i32> = [2, 0, 2, 0]
102 .into_iter()
103 .map(|count| n.try_step(count, false).unwrap())
104 .collect();
105 assert_eq!(outputs, vec![1, 0, 1, 0]);
106 }
107}