elementary 教程

本教程对针对 arithmetic trait 编写的解析代码求导。你将以 Exponential、Logarithmic 和 Trigonometric 为约束编写一次函数,在 Double 上对其求值,再通过在 Dual[Double] 上运行它得到导数。背后的规则见 elementary 设计。

快速上手

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

导数为 2sin⁡xcos⁡x=sin⁡2x2\sin x\cos x = \sin 2x,此处为 sin⁡1\sin 1。

日常任务

对数与指数

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

(exln⁡x)′=exln⁡x+ex/x(e^x \ln x)' = e^x \ln x + e^x/x,在 x=1x = 1 处为 ee。

其他底数

exp2、log2 和 log10 包含了换底因子:

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

泛型代码中的常数

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

常数 τ=2π\tau = 2\pi 的切向分量为零,因此只对 tt 求导。

正切函数及其极点

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

导数 1/cos⁡2a1/\cos^2 a 在趋近 π/2\pi/2 时无界增长。

深入学习

  • 将解析 trait 与根包或 core 中的 Ring 结合,即可编写完整的模型;见 autodiff 教程。
  • 对于自定义标量,实现 Exponential、Logarithmic 和 Trigonometric;之后它的 Dual 就会按相同规则对这些函数求导。
  • 需要检查定义域的平方根请使用 SqrtChecked,见 checked 教程。

常见陷阱

  • 超出定义域会得到 NaN 或无穷大。 非正数的 ln 和零处的 sqrt 都不做检查。
  • trait 实例需要的比方法更多。 在 Dual[T] 上调用 @elementary.Exponential::exp 需要完整的实例约束(T 上的 Logarithmic 和 IntegralHomomorphism);方法 x.exp() 需要的则更少。
  • 没有双曲函数或反函数。 sinh、asin、atan 等函数目前还没有对偶规则;如有需要,请用现有函数组合实现。

后续阅读