qualia_core_db/solvers/graph_opt/
spreading_activation.rs1use std::collections::HashMap;
12
13#[derive(Debug, Clone, Copy)]
15pub struct Edge {
16 pub from: usize,
17 pub to: usize,
18 pub weight: f64,
19}
20
21pub fn spreading_activation(
26 n_nodes: usize,
27 edges: &[Edge],
28 seeds: &[(usize, f64)],
29 decay: f64,
30 threshold: f64,
31 max_hops: usize,
32) -> Vec<f64> {
33 let mut total = vec![0.0; n_nodes];
34 if n_nodes == 0 {
35 return total;
36 }
37 let mut adj: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n_nodes];
39 for e in edges {
40 if e.from < n_nodes && e.to < n_nodes && e.weight > 0.0 {
41 adj[e.from].push((e.to, e.weight));
42 }
43 }
44 let mut current: HashMap<usize, f64> = HashMap::new();
46 for &(node, a) in seeds {
47 if node < n_nodes && a > 0.0 {
48 *current.entry(node).or_insert(0.0) += a;
49 total[node] += a;
50 }
51 }
52 let decay = decay.clamp(0.0, 1.0);
53 for _ in 0..max_hops {
54 if current.is_empty() {
55 break;
56 }
57 let mut next: HashMap<usize, f64> = HashMap::new();
58 for (&node, &a) in ¤t {
59 for &(to, w) in &adj[node] {
60 let contrib = a * w * decay;
61 if contrib > threshold {
62 *next.entry(to).or_insert(0.0) += contrib;
63 }
64 }
65 }
66 for (&node, &a) in &next {
67 total[node] += a;
68 }
69 current = next;
70 }
71 total
72}
73
74pub fn top_k(activation: &[f64], k: usize) -> Vec<usize> {
76 let mut idx: Vec<usize> = (0..activation.len()).collect();
77 idx.sort_by(|&a, &b| {
78 activation[b]
79 .partial_cmp(&activation[a])
80 .unwrap_or(core::cmp::Ordering::Equal)
81 });
82 idx.truncate(k.min(activation.len()));
83 idx
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 fn e(from: usize, to: usize, w: f64) -> Edge {
91 Edge {
92 from,
93 to,
94 weight: w,
95 }
96 }
97
98 #[test]
99 fn activation_decays_along_a_chain() {
100 let edges = [e(0, 1, 1.0), e(1, 2, 1.0), e(2, 3, 1.0)];
102 let act = spreading_activation(4, &edges, &[(0, 1.0)], 0.5, 1e-6, 10);
103 assert!(act[0] > act[1] && act[1] > act[2] && act[2] > act[3]);
105 assert!(act[3] > 0.0);
106 }
107
108 #[test]
109 fn closer_nodes_rank_higher() {
110 let edges = [e(0, 1, 1.0), e(0, 2, 1.0), e(0, 3, 1.0), e(1, 4, 1.0)];
112 let act = spreading_activation(5, &edges, &[(0, 1.0)], 0.6, 1e-9, 10);
113 assert!(act[1] > act[4]);
115 let ranking = top_k(&act, 3);
116 assert_eq!(ranking[0], 0); }
118
119 #[test]
120 fn weights_bias_the_spread() {
121 let edges = [e(0, 1, 0.9), e(0, 2, 0.1)];
123 let act = spreading_activation(3, &edges, &[(0, 1.0)], 1.0, 1e-9, 5);
124 assert!(act[1] > act[2]);
125 }
126
127 #[test]
128 fn empty_and_threshold() {
129 assert_eq!(spreading_activation(0, &[], &[], 0.5, 0.0, 5).len(), 0);
130 let edges = [e(0, 1, 0.01)];
132 let act = spreading_activation(2, &edges, &[(0, 1.0)], 0.5, 0.1, 5);
133 assert_eq!(act[1], 0.0);
134 }
135}