use super::variable::Variable;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub(crate) enum Operation {
Leaf,
Add { a: Variable, b: Variable },
Sub { a: Variable, b: Variable },
Mul { a: Variable, b: Variable },
Div { a: Variable, b: Variable },
Pow { a: Variable, b: f64 },
Exp { a: Variable },
Log { a: Variable },
Sin { a: Variable },
Cos { a: Variable },
Tanh { a: Variable },
Relu { a: Variable },
Sigmoid { a: Variable },
}
#[derive(Debug)]
pub struct Tape {
next_id: usize,
operations: HashMap<usize, Operation>,
gradients: HashMap<usize, f64>,
}
impl Tape {
pub fn new() -> Self {
Self {
next_id: 0,
operations: HashMap::new(),
gradients: HashMap::new(),
}
}
pub fn var(&mut self, value: f64) -> Variable {
let id = self.next_id;
self.next_id += 1;
self.operations.insert(id, Operation::Leaf);
Variable::new(id, value)
}
pub(crate) fn record(&mut self, value: f64, op: Operation) -> Variable {
let id = self.next_id;
self.next_id += 1;
self.operations.insert(id, op);
Variable::new(id, value)
}
pub fn grad(&self, var: &Variable) -> f64 {
self.gradients.get(&var.id).copied().unwrap_or(0.0)
}
pub fn zero_grad(&mut self) {
self.gradients.clear();
}
pub fn backward(&mut self, output: &Variable) {
self.zero_grad();
self.gradients.insert(output.id, 1.0);
let mut sorted_ids: Vec<usize> = self.operations.keys().copied().collect();
sorted_ids.sort_by(|a, b| b.cmp(a));
for &id in &sorted_ids {
let grad = self.gradients.get(&id).copied().unwrap_or(0.0);
if grad == 0.0 {
continue;
}
if let Some(op) = self.operations.get(&id).cloned() {
self.backward_op(&op, grad);
}
}
}
fn backward_op(&mut self, op: &Operation, grad: f64) {
match op {
Operation::Leaf => {}
Operation::Add { a, b } => {
self.accumulate_grad(a.id, grad);
self.accumulate_grad(b.id, grad);
}
Operation::Sub { a, b } => {
self.accumulate_grad(a.id, grad);
self.accumulate_grad(b.id, -grad);
}
Operation::Mul { a, b } => {
self.accumulate_grad(a.id, grad * b.value());
self.accumulate_grad(b.id, grad * a.value());
}
Operation::Div { a, b } => {
let b_val = b.value();
self.accumulate_grad(a.id, grad / b_val);
self.accumulate_grad(b.id, -grad * a.value() / (b_val * b_val));
}
Operation::Pow { a, b } => {
let a_val = a.value();
self.accumulate_grad(a.id, grad * b * a_val.powf(b - 1.0));
}
Operation::Exp { a } => {
let a_val = a.value();
self.accumulate_grad(a.id, grad * a_val.exp());
}
Operation::Log { a } => {
let a_val = a.value();
self.accumulate_grad(a.id, grad / a_val);
}
Operation::Sin { a } => {
let a_val = a.value();
self.accumulate_grad(a.id, grad * a_val.cos());
}
Operation::Cos { a } => {
let a_val = a.value();
self.accumulate_grad(a.id, -grad * a_val.sin());
}
Operation::Tanh { a } => {
let a_val = a.value();
let tanh_val = a_val.tanh();
self.accumulate_grad(a.id, grad * (1.0 - tanh_val * tanh_val));
}
Operation::Relu { a } => {
let a_val = a.value();
self.accumulate_grad(a.id, if a_val > 0.0 { grad } else { 0.0 });
}
Operation::Sigmoid { a } => {
let a_val = a.value();
let sig = 1.0 / (1.0 + (-a_val).exp());
self.accumulate_grad(a.id, grad * sig * (1.0 - sig));
}
}
}
fn accumulate_grad(&mut self, id: usize, grad: f64) {
*self.gradients.entry(id).or_insert(0.0) += grad;
}
}
impl Default for Tape {
fn default() -> Self {
Self::new()
}
}