Backpropagation - Entrenar - Training & Optimization Library
Description: A comprehensive guide to building neural network training systems with autograd, optimizers, LoRA/QLoRA, and quantization using EXTREME TDD methodology

# 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;
        }
    }
    }