autodiff tutorial

This tutorial is the fastest route through the repository: with the single import Luna-Flow/autodiff you differentiate a function, write generic code against the re-exported traits, and handle checked failures. Each section links to the package tutorial that goes deeper.

Quick start

moon add Luna-Flow/autodiff@0.2.0
import {
  "Luna-Flow/autodiff",
}
fn main {
  let (value, slope) = @autodiff.value_and_diff(x => x * x.exp(), 1.0)
  println("f(1) = \{value}")
  println("f'(1) = \{slope}")
}
f(1) = 2.718281828459045
f'(1) = 5.43656365691809

f(x)=xexf(x) = x e^x has f′(x)=(1+x)exf'(x) = (1 + x)e^x, so f′(1)=2ef'(1) = 2e.

Everyday tasks

Differentiate a closure

fn main {
  let slope = @autodiff.diff(x => x.sin() * x.cos(), 0.0)
  println("d/dx sin x cos x at 0 = \{slope}")
}
d/dx sin x cos x at 0 = 1

Write generic code with the re-exported traits

All bounds come from the one import:

fn[T : @autodiff.Ring + @autodiff.Exponential + @autodiff.IntegralHomomorphism] softplus_like(
  x : T,
) -> T {
  let one : T = @autodiff.IntegralHomomorphism::from_integral(1)
  one + @autodiff.Exponential::exp(x)
}

fn main {
  println("value at 0: \{softplus_like(0.0)}")
  println("slope at 0: \{@autodiff.diff(softplus_like, 0.0)}")
}
value at 0: 2
slope at 0: 1

Use mathematical constants

Constants gives π\pi, τ\tau and ee as dual constants:

fn main {
  let area_slope = @autodiff.diff(
    r => {
      let pi : @autodiff.Dual[Double] = @autodiff.Constants::pi()
      pi * r * r
    },
    2.0,
  )
  println("d/dr pi r^2 at 2 = \{area_slope}")
}
d/dr pi r^2 at 2 = 12.566370614359172

Handle a checked failure

fn main {
  let ctx = @autodiff.ArithmeticContext::new(53)
  let x = @autodiff.Dual::variable(2.0)
  match @autodiff.DivChecked::div_checked(x, x - x, ctx) {
    Ok(_) => println("unexpected")
    Err(e) => println("division by zero: \{e.is_division_by_zero()}")
  }
}
division by zero: true

Going further

Common pitfalls

  • The root package has no gradients. Import Luna-Flow/autodiff/linalg for gradient and jacobian.
  • Re-exported traits are the original traits. An instance you write for @lg.Ring is the instance of @autodiff.Ring; do not implement both.
  • Literals need Dual::constant or from_integral inside differentiated code.

Next steps