1use petgraph::EdgeType;
7use petgraph::graph::NodeIndex;
8use petgraph::stable_graph::StableGraph;
9
10#[derive(Debug, Clone, Copy)]
12pub struct StdpConfig {
13 pub a_plus: f64,
15 pub a_minus: f64,
17 pub tau_plus: f64,
19 pub tau_minus: f64,
21}
22
23impl Default for StdpConfig {
24 fn default() -> Self {
25 Self {
26 a_plus: 0.01,
27 a_minus: 0.005,
28 tau_plus: 5.0,
29 tau_minus: 5.0,
30 }
31 }
32}
33
34pub type TimedActivation = (NodeIndex, f64, u64);
36
37pub fn stdp_update<N, Ty: EdgeType>(
64 graph: &mut StableGraph<N, f64, Ty>,
65 activations: &[TimedActivation],
66 config: &StdpConfig,
67) -> usize {
68 let mut modified = 0;
69 for i in 0..activations.len() {
70 let j_start = if Ty::is_directed() { 0 } else { i + 1 };
71 for j in j_start..activations.len() {
72 if i == j {
73 continue;
74 }
75 let (ni, _si, ti) = activations[i];
76 let (nj, _sj, tj) = activations[j];
77 if ti == tj {
78 continue;
79 }
80
81 let dt = (tj as f64) - (ti as f64);
84 let delta_w = if dt > 0.0 {
85 config.a_plus * (-dt / config.tau_plus).exp()
86 } else {
87 -config.a_minus * (dt / config.tau_minus).exp()
88 };
89
90 if let Some(idx) = graph.find_edge(ni, nj) {
91 if let Some(w) = graph.edge_weight_mut(idx) {
92 *w += delta_w;
93 modified += 1;
94 }
95 } else if delta_w > 0.0 {
96 graph.add_edge(ni, nj, delta_w);
97 modified += 1;
98 }
99
100 if Ty::is_directed() {
102 let delta_w_rev = -delta_w; if let Some(idx) = graph.find_edge(nj, ni) {
104 if let Some(w) = graph.edge_weight_mut(idx) {
105 *w += delta_w_rev;
106 modified += 1;
107 }
108 } else if delta_w_rev > 0.0 {
109 graph.add_edge(nj, ni, delta_w_rev);
110 modified += 1;
111 }
112 }
113 }
114 }
115 modified
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use petgraph::stable_graph::{StableDiGraph, StableUnGraph};
122
123 #[test]
124 fn causal_pair_strengthened() {
125 let mut g = StableDiGraph::<(), f64>::new();
126 let a = g.add_node(());
127 let b = g.add_node(());
128 g.add_edge(a, b, 0.5);
129
130 stdp_update(&mut g, &[(a, 1.0, 1), (b, 1.0, 2)], &StdpConfig::default());
132 assert!(*g.edge_weight(0.into()).unwrap() > 0.5);
133 }
134
135 #[test]
136 fn anti_causal_pair_weakened() {
137 let mut g = StableDiGraph::<(), f64>::new();
138 let a = g.add_node(());
139 let b = g.add_node(());
140 g.add_edge(a, b, 0.5);
141
142 stdp_update(&mut g, &[(a, 1.0, 3), (b, 1.0, 1)], &StdpConfig::default());
144 assert!(*g.edge_weight(0.into()).unwrap() < 0.5);
145 }
146
147 #[test]
148 fn strength_depends_on_time_difference() {
149 let config = StdpConfig::default();
150
151 let mut g1 = StableDiGraph::<(), f64>::new();
153 let a1 = g1.add_node(());
154 let b1 = g1.add_node(());
155 g1.add_edge(a1, b1, 0.5);
156 stdp_update(&mut g1, &[(a1, 1.0, 1), (b1, 1.0, 2)], &config);
157 let w_close = *g1.edge_weight(0.into()).unwrap();
158
159 let mut g2 = StableDiGraph::<(), f64>::new();
161 let a2 = g2.add_node(());
162 let b2 = g2.add_node(());
163 g2.add_edge(a2, b2, 0.5);
164 stdp_update(&mut g2, &[(a2, 1.0, 1), (b2, 1.0, 10)], &config);
165 let w_far = *g2.edge_weight(0.into()).unwrap();
166
167 assert!(w_close > w_far);
168 }
169
170 #[test]
171 fn beyond_tau_window_minimal_effect() {
172 let config = StdpConfig {
173 tau_plus: 2.0,
174 ..StdpConfig::default()
175 };
176
177 let mut g = StableDiGraph::<(), f64>::new();
178 let a = g.add_node(());
179 let b = g.add_node(());
180 g.add_edge(a, b, 0.5);
181
182 stdp_update(&mut g, &[(a, 1.0, 1), (b, 1.0, 21)], &config);
184 let w = *g.edge_weight(0.into()).unwrap();
185 assert!((w - 0.5).abs() < 0.0001);
186 }
187
188 #[test]
189 fn same_tick_no_change() {
190 let mut g = StableDiGraph::<(), f64>::new();
191 let a = g.add_node(());
192 let b = g.add_node(());
193 g.add_edge(a, b, 0.5);
194
195 let modified = stdp_update(&mut g, &[(a, 1.0, 5), (b, 1.0, 5)], &StdpConfig::default());
196 assert_eq!(modified, 0);
197 assert_eq!(*g.edge_weight(0.into()).unwrap(), 0.5);
198 }
199
200 #[test]
201 fn undirected_stdp() {
202 let mut g = StableUnGraph::<(), f64>::with_capacity(0, 0);
203 let a = g.add_node(());
204 let b = g.add_node(());
205 g.add_edge(a, b, 0.5);
206
207 let modified = stdp_update(&mut g, &[(a, 1.0, 1), (b, 1.0, 3)], &StdpConfig::default());
209 assert_eq!(modified, 1);
210 assert!(*g.edge_weight(0.into()).unwrap() > 0.5);
211 }
212}