diff --git a/poc-memory/src/search.rs b/poc-memory/src/search.rs index a334643..ad867a9 100644 --- a/poc-memory/src/search.rs +++ b/poc-memory/src/search.rs @@ -177,7 +177,7 @@ fn run_spread( let store_params = store.params(); let max_hops = stage.param_u32("max_hops", store_params.max_hops); let edge_decay = stage.param_f64("edge_decay", store_params.edge_decay); - let min_activation = stage.param_f64("min_activation", store_params.min_activation); + let min_activation = stage.param_f64("min_activation", store_params.min_activation * 0.1); spreading_activation(seeds, graph, store, max_hops, edge_decay, min_activation) } @@ -632,6 +632,12 @@ fn run_manifold( results } +/// Simultaneous wavefront spreading activation. +/// +/// All seeds emit at once. At each hop, activations from all sources +/// sum at each node, and the combined activation map propagates on +/// the next hop. This creates interference patterns — nodes where +/// multiple wavefronts overlap get reinforced and radiate stronger. fn spreading_activation( seeds: &[(String, f64)], graph: &Graph, @@ -641,30 +647,35 @@ fn spreading_activation( min_activation: f64, ) -> Vec<(String, f64)> { let mut activation: HashMap = HashMap::new(); - let mut queue: VecDeque<(String, f64, u32)> = VecDeque::new(); + // Initialize wavefront from all seeds + let mut frontier: HashMap = HashMap::new(); for (key, act) in seeds { - let current = activation.entry(key.clone()).or_insert(0.0); - if *act > *current { - *current = *act; - queue.push_back((key.clone(), *act, 0)); - } + *frontier.entry(key.clone()).or_insert(0.0) += act; + *activation.entry(key.clone()).or_insert(0.0) += act; } - while let Some((key, act, depth)) = queue.pop_front() { - if depth >= max_hops { continue; } + // Propagate hop by hop — all sources simultaneously + for _hop in 0..max_hops { + let mut next_frontier: HashMap = HashMap::new(); - for (neighbor, strength) in graph.neighbors(&key) { - let neighbor_weight = store.node_weight(neighbor.as_str()); - let propagated = act * edge_decay * neighbor_weight * strength as f64; - if propagated < min_activation { continue; } + for (key, act) in &frontier { + for (neighbor, strength) in graph.neighbors(key) { + let neighbor_weight = store.node_weight(neighbor.as_str()); + let propagated = act * edge_decay * neighbor_weight * strength as f64; + if propagated < min_activation { continue; } - let current = activation.entry(neighbor.clone()).or_insert(0.0); - if propagated > *current { - *current = propagated; - queue.push_back((neighbor.clone(), propagated, depth + 1)); + *next_frontier.entry(neighbor.clone()).or_insert(0.0) += propagated; } } + + if next_frontier.is_empty() { break; } + + // Merge into total activation and advance frontier + for (key, act) in &next_frontier { + *activation.entry(key.clone()).or_insert(0.0) += act; + } + frontier = next_frontier; } let mut results: Vec<_> = activation.into_iter().collect();