Backpropagation
After simulation, propagate the result back up the tree.
Standard Backpropagation
#![allow(unused)] fn main() { fn backpropagate(&mut self, leaf_id: NodeId, reward: f32) { let mut current = Some(leaf_id); while let Some(node_id) = current { let node = self.tree.get_mut(node_id); node.visits += 1; node.total_reward += reward; current = node.parent; } } }
Reward Normalization
Normalize rewards to [0, 1] for consistent UCB calculations:
#![allow(unused)] fn main() { fn backpropagate_normalized(&mut self, leaf_id: NodeId, reward: f32) { // Track min/max for normalization self.stats.update_bounds(reward); let normalized = (reward - self.stats.min) / (self.stats.max - self.stats.min); self.backpropagate(leaf_id, normalized); } }
RAVE (Rapid Action Value Estimation)
Share statistics across sibling nodes:
#![allow(unused)] fn main() { fn backpropagate_rave(&mut self, path: &[NodeId], reward: f32) { let actions_taken: HashSet<_> = path.iter() .filter_map(|&id| self.tree.get(id).action.clone()) .collect(); for &node_id in path { let node = self.tree.get_mut(node_id); node.visits += 1; node.total_reward += reward; // Update AMAF statistics for siblings for sibling in self.tree.siblings(node_id) { if actions_taken.contains(&sibling.action) { sibling.amaf_visits += 1; sibling.amaf_reward += reward; } } } } }
Virtual Loss
For parallel MCTS, prevent thread collision:
#![allow(unused)] fn main() { fn apply_virtual_loss(&mut self, path: &[NodeId]) { for &node_id in path { let node = self.tree.get_mut(node_id); node.virtual_loss += 1; } } fn remove_virtual_loss(&mut self, path: &[NodeId], reward: f32) { for &node_id in path { let node = self.tree.get_mut(node_id); node.virtual_loss -= 1; node.visits += 1; node.total_reward += reward; } } }