elementary tutorial
This tutorial differentiates analytic code written against the
arithmetic traits. You will write a function once with Exponential,
Logarithmic and Trigonometric bounds, evaluate it on Double, and get
its derivative by running it on Dual[Double]. The rules behind it are in
the elementary design.
Quick start
moon add Luna-Flow/autodiff@0.2.0
import {
"Luna-Flow/autodiff/elementary",
}
fn[T : @elementary.Trigonometric + Mul] sin_squared(x : T) -> T {
let s = @elementary.Trigonometric::sin(x)
s * s
}
fn main {
let y = sin_squared(@elementary.Dual::variable(0.5))
println("sin^2(0.5) = \{y.value()}")
println("derivative = \{y.tangent()}")
}
sin^2(0.5) = 0.22984884706593015
derivative = 0.8414709848078965
The derivative is , here .
Everyday tasks
Logarithms and exponentials
fn[T : @elementary.Exponential + @elementary.Logarithmic + Mul] f(x : T) -> T {
@elementary.Exponential::exp(x) * @elementary.Logarithmic::ln(x)
}
fn main {
let y = f(@elementary.Dual::variable(1.0))
println("f(1) = \{y.value()}, f'(1) = \{y.tangent()}")
}
f(1) = 0, f'(1) = 2.718281828459045
, which is at .
Other bases
exp2, log2 and log10 include the base-change factors:
fn main {
let x = @elementary.Dual::variable(3.0)
println("d/dx 2^x at 3 = \{x.exp2().tangent()}")
println("d/dx log2 x at 3 = \{x.log2().tangent()}")
println("d/dx log10 x at 3 = \{x.log10().tangent()}")
}
d/dx 2^x at 3 = 5.545177444479562
d/dx log2 x at 3 = 0.48089834696298783
d/dx log10 x at 3 = 0.14476482730108392
Constants inside generic code
fn[T : @elementary.Trigonometric + @elementary.Constants + Mul] wave(t : T) -> T {
let tau : T = @elementary.Constants::tau()
@elementary.Trigonometric::sin(tau * t)
}
fn main {
let y = wave(@elementary.Dual::variable(0.0))
println("slope at 0 = \{y.tangent()}")
}
slope at 0 = 6.283185307179586
The constant has tangent zero, so only is differentiated.
The tangent and its poles
fn main {
for a in [0.0, 1.0, 1.5] {
let y = @elementary.Dual::variable(a).tan()
println("tan'(\{a}) = \{y.tangent()}")
}
}
tan'(0) = 1
tan'(1) = 3.425518820814759
tan'(1.5) = 199.8500445264925
The derivative grows without bound towards .
Going further
- Combine the analytic traits with
Ringfrom the root package orcoreto write full models; see the autodiff tutorial. - For a custom scalar, implement
Exponential,LogarithmicandTrigonometric;Dualof it then differentiates them with the same rules. - For domain-checked square roots, use
SqrtChecked, see the checked tutorial.
Common pitfalls
- Outside the domain you get NaN or infinity.
lnof a non-positive number andsqrtat zero are not checked. - The trait instances need more than the method. Calling
@elementary.Exponential::exponDual[T]needs the full instance bound (LogarithmicandIntegralHomomorphismonT); the methodx.exp()needs less. - No hyperbolic or inverse functions.
sinh,asin,atanand friends have no dual rule yet; compose them from the available functions if you need them.
Next steps
- The elementary API lists the traits and bounds.
- The dual API lists every rule as computed.
- arithmetic documents the traits.