-
Reverse Mode Differentiation: Ideal for functions with many inputs and one output (think neural nets).
-
Foolproof API: Lifetimes and phantom types ensure correctness at compile time, at no cost to the API consumer.
-
Scoped Variables: Keep your variables around and reuse them across computations. This allows for effective variable storage in model structures, bounded by the variable scope's lifetime.
Here’s a quick example:
use auto::Tape;
fn main() {
// Create a new tape (Wengert list) to store the nodes of our computation
let mut tape = Tape::new();
// Define a scope to play around in
tape.scope(|guard| {
// Create a variable on the guard with value 2.0
let x = guard.var(2.0);
// Create another variable (implicitly on the guard) with dependence on `x`
let y = x.sin() + x.cos();
// After locking a guard, we can only spawn more subcomputations, or collapse into gradients
let grads = guard.lock().collapse().of(&y);
// Now we get the gradient of `y` wrt `x`
println!("Value: {}, dy/dx: {}", *y, grads[&x]);
});
}
In this snippet, we create a variable, compute a function, and automatically derive its gradient...