checked tutorial
This tutorial differentiates code that can fail. You will call the checked division and square root on dual numbers, propagate their errors through a computation, and write generic checked code that runs on plain and dual numbers. The reasoning is in the checked design.
Quick start
moon add Luna-Flow/autodiff@0.2.0
import {
"Luna-Flow/autodiff/checked",
}
fn main {
let ctx = @checked.ArithmeticContext::new(53)
let x : @checked.Dual[Double] = @checked.Dual::variable(2.0)
let y : @checked.Dual[Double] = @checked.Dual::constant(8.0)
match y.div_checked(x, ctx) {
Ok(q) => println("8/x at 2: value \{q.value()}, derivative \{q.tangent()}")
Err(e) => println("failed: \{e.message}")
}
}
8/x at 2: value 4, derivative -2
Everyday tasks
Inspect the failure
fn main {
let ctx = @checked.ArithmeticContext::new(53)
let one : @checked.Dual[Double] = @checked.Dual::constant(1.0)
for c in [1.0, 0.0] {
match one.div_checked(@checked.Dual::variable(c), ctx) {
Ok(q) => println("1/x at \{c}: derivative \{q.tangent()}")
Err(e) => println("1/x at \{c}: \{e.message}")
}
}
}
1/x at 1: derivative -1
1/x at 0: division by zero
Chain checked steps
Each step returns a Result; stop at the first error:
fn norm_ratio(
x : @checked.Dual[Double],
y : @checked.Dual[Double],
ctx : @checked.ArithmeticContext,
) -> Result[@checked.Dual[Double], @checked.ArithmeticError] {
// sqrt(x^2 + y^2) / y
let r = (x * x + y * y).sqrt_checked(ctx)
match r {
Err(e) => Err(e)
Ok(r) => r.div_checked(y, ctx)
}
}
fn main {
let ctx = @checked.ArithmeticContext::new(53)
let y : @checked.Dual[Double] = @checked.Dual::constant(4.0)
match norm_ratio(@checked.Dual::variable(3.0), y, ctx) {
Ok(v) => println("value \{v.value()}, d/dx \{v.tangent()}")
Err(e) => println(e.message)
}
let zero : @checked.Dual[Double] = @checked.Dual::constant(0.0)
match norm_ratio(@checked.Dual::variable(0.0), zero, ctx) {
Ok(_) => println("unexpected")
Err(e) => println("at the origin: \{e.message}")
}
}
value 1.25, d/dx 0.15
at the origin: zero divided by zero is undefined
At the origin the square root is evaluated at , where it has no derivative, so the chain stops there.
Generic checked code
Bound the function by the checked traits; it then runs on Double and on
Dual[Double]:
fn[T : @checked.DivChecked + @checked.SqrtChecked] sqrt_ratio(
a : T,
b : T,
ctx : @checked.ArithmeticContext,
) -> Result[T, @checked.ArithmeticError] {
match @checked.DivChecked::div_checked(a, b, ctx) {
Err(e) => Err(e)
Ok(q) => @checked.SqrtChecked::sqrt_checked(q, ctx)
}
}
fn main {
let ctx = @checked.ArithmeticContext::new(53)
match sqrt_ratio(8.0, 2.0, ctx) {
Ok(v) => println("plain: \{v}")
Err(e) => println(e.message)
}
let a : @checked.Dual[Double] = @checked.Dual::variable(8.0)
let b : @checked.Dual[Double] = @checked.Dual::constant(2.0)
match sqrt_ratio(a, b, ctx) {
Ok(v) => println("dual: \{v.value()}, d/da \{v.tangent()}")
Err(e) => println(e.message)
}
}
plain: 2
dual: 2, d/da 0.125
Going further
ArithmeticContext::new(precision, rounding=…)builds the context for scalar types that use it;DoubleandFloatignore it.- Functions passed to
@autodiff.diffcan use the checked operations; the forward tutorial shows how to carry the error out of the closure. - A custom scalar type takes part by implementing
DivCheckedandSqrtChecked; its errors then appear unchanged in dual results.
Common pitfalls
sqrt_checkedfails at zero. The derivative does not exist there, even when the input is a constant.- Tiny divisors. underflows for , and the tangent division reports a division by zero.
- Unchecked operators stay unchecked.
x / yandx.sqrt()on dual numbers never return errors; use the_checkedforms.
Next steps
- The checked API lists the re-exported names.
- The dual API gives the exact error table.
- arithmetic documents the error model.