spread: simultaneous wavefront instead of independent BFS

All seeds emit at once. At each hop, activations from all sources
sum at each node, and the combined map propagates on the next hop.
Nodes where multiple wavefronts overlap get reinforced and radiate
stronger — natural interference patterns.

Lower default min_activation threshold (×0.1) since individual
contributions are smaller in additive mode.

Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-03-09 01:35:27 -04:00
parent c13a9da81c
commit 05c7d55949

View file

@ -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<String, f64> = HashMap::new();
let mut queue: VecDeque<(String, f64, u32)> = VecDeque::new();
// Initialize wavefront from all seeds
let mut frontier: HashMap<String, f64> = 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<String, f64> = 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();