linalg API
The linalg package computes gradients and Jacobians of functions on
immutable vectors from
Luna-Flow/linear-algebra. Each
driver evaluates the function on vectors of dual numbers, one input
coordinate at a time. The method is explained in the
linalg design.
Source: src/linalg/linalg.mbt.
Importing
import {
"Luna-Flow/autodiff/linalg",
"Luna-Flow/linear-algebra/immut" @la,
}
The examples use @linalg for this package and @la for
linear-algebra/immut. @linalg.Vector and @linalg.Matrix are the same
types as @la.Vector and @la.Matrix.
Function shapes and preconditions
The drivers take the function first and the point second:
| Driver | f | Result |
|---|---|---|
gradient | (Vector[Dual[T]]) -> Dual[T] | |
value_and_gradient | (Vector[Dual[T]]) -> Dual[T] | |
jacobian | (Vector[Dual[T]]) -> Vector[Dual[T]] | |
value_and_jacobian | (Vector[Dual[T]]) -> Vector[Dual[T]] |
All four require:
faccepts a vector of length =x.length()and reads only indices below ;- a vector-valued
freturns the same length on every call; ftreats every captured value as a constant (Dual::constant).
The drivers do not validate shapes. A violation aborts with an index error or, if a later call returns a longer vector, silently ignores the extra entries.
Gradients
gradient
Computes the gradient of a scalar function.
pub fn[T : @luna-generic.One + @luna-generic.Zero] gradient((@immut.Vector[@dual.Dual[T]]) -> @dual.Dual[T], @immut.Vector[T]) -> @immut.Vector[T]
For each it calls f once with coordinate seeded as
Dual::new(x[i], 1) and every other coordinate as Dual::constant(x[j]),
and stores the tangent of the result as component . Cost:
evaluations of f on dual numbers. For the result is empty and f
is not called.
value_and_gradient
Returns and .
pub fn[T : @luna-generic.One + @luna-generic.Zero] value_and_gradient((@immut.Vector[@dual.Dual[T]]) -> @dual.Dual[T], @immut.Vector[T]) -> (T, @immut.Vector[T])
The value comes from one extra call with all tangents zero, so the cost is evaluations.
test "gradient of x0^2 + x0 x1" {
let x = @la.Vector::from_array([2.0, 3.0])
let f = (v : @la.Vector[@autodiff.Dual[Double]]) => v[0] * v[0] + v[0] * v[1]
assert_eq(@linalg.gradient(f, x), @la.Vector::from_array([7.0, 2.0]))
let (value, grad) = @linalg.value_and_gradient(f, x)
assert_eq(value, 10.0)
assert_eq(grad, @la.Vector::from_array([7.0, 2.0]))
}
Jacobians
jacobian
Computes the Jacobian matrix of a vector-valued function, with one row per output and one column per input:
pub fn[T : @luna-generic.One + @luna-generic.Zero] jacobian((@immut.Vector[@dual.Dual[T]]) -> @immut.Vector[@dual.Dual[T]], @immut.Vector[T]) -> @immut.Matrix[T]
It first calls f with all tangents zero to learn , then once per input
with coordinate seeded; the output tangents of call form
column . Cost: evaluations. The result has row() and
col() .
value_and_jacobian
Returns and .
pub fn[T : @luna-generic.One + @luna-generic.Zero] value_and_jacobian((@immut.Vector[@dual.Dual[T]]) -> @immut.Vector[@dual.Dual[T]], @immut.Vector[T]) -> (@immut.Vector[T], @immut.Matrix[T])
The value comes from its own call with zero tangents, and the Jacobian from
jacobian, so the cost is evaluations.
test "jacobian of (x0 + x1, x0 x1, x0^2)" {
let x = @la.Vector::from_array([2.0, 3.0])
let f = (v : @la.Vector[@autodiff.Dual[Double]]) => {
@la.Vector::from_array([v[0] + v[1], v[0] * v[1], v[0] * v[0]])
}
let (value, j) = @linalg.value_and_jacobian(f, x)
assert_eq(value, @la.Vector::from_array([5.0, 6.0, 4.0]))
assert_eq(j.row(), 3)
assert_eq(j.col(), 2)
assert_eq(j[1][0], 3.0) // d(x0 x1)/dx0 = x1
assert_eq(j[2][1], 0.0) // d(x0^2)/dx1
}
Re-exported types
Dual
The dual-number type; see the dual API.
pub using @dual {type Dual}
Vector
The immutable vector of linear-algebra.
pub using @immut {type Vector}
Matrix
The immutable matrix of linear-algebra.
pub using @immut {type Matrix}