dual API

dual パッケージは Dual[T]、すなわち ε2=0\varepsilon^2 = 0 を満たす双対数 a+bεa + b\varepsilon を、その算術、Luna Flow のトレイトインスタンス、初等関数の微分規則とともに所有しています。このリポジトリの他のすべてのパッケージは、この型を再エクスポートするか利用します。規則の背後にある数学は dual の設計 にあります。

ソース:src/dual/dual.mbt と src/dual/extends.mbt。

インポート

import {
  "Luna-Flow/autodiff/dual",
}

ほとんどのプログラムは代わりにルートパッケージ Luna-Flow/autodiff をインポートします。これは Dual を @autodiff.Dual として再エクスポートしています(autodiff API を参照)。このページの例はルートのエイリアスを使います。

型

Dual

Dual[T] はプライマル値と 1 階の接成分を格納します。

pub struct Dual[T] {
  value : T
  tangent : T
} derive(Eq, @debug.Debug)

組 (value, tangent) は、aa = value、bb = tangent として a+bεa + b\varepsilon を表します。計算を Dual::variable(x) から始めると、すべての中間結果の接成分は、その中間値の x に関する導関数になります。フィールドはパッケージ外からは読み取り専用です。値は以下のコンストラクタで構築してください。

derive(Eq) は両成分を比較するため、値が同じでも接成分が異なる 2 つの双対数は等しくありません。derive(Debug) はレコード形式 { value: …, tangent: … } を出力し、debug_inspect と assert_eq がこれを使います。

Dual[T] は意図的に Compare、Field、MulGroup、Inverse、Show のインスタンスを持ちません。設計上の判断 を参照してください。

構築とアクセス

Dual::new

明示的な値と接成分から a+bεa + b\varepsilon を構築します。

pub fn[T] Dual::new(T, T) -> Dual[T]

11 以外の方向をシードするときに使います。たとえば方向微分 ∇f(x)⋅v\nabla f(x) \cdot v では、各入力座標に接成分 viv_i を与えます。

Dual::constant

接成分 0 で値を埋め込みます:c↦c+0εc \mapsto c + 0\varepsilon。

pub fn[T : @luna-generic.Zero] Dual::constant(T) -> Dual[T]

定数は微分変数に依存しないので、その導関数は 00 です。微分される計算に入るすべてのリテラルやキャプチャされた値に使ってください。

Dual::variable

接成分 1 で微分変数を埋め込みます:x↦x+1εx \mapsto x + 1\varepsilon。

pub fn[T : @luna-generic.One] Dual::variable(T) -> Dual[T]

Dual::value

プライマル成分 aa を返します。

pub fn[T] Dual::value(Dual[T]) -> T

Dual::tangent

接成分 bb を返します。

pub fn[T] Dual::tangent(Dual[T]) -> T
test "construct and read dual numbers" {
  let x = @autodiff.Dual::new(2.0, 3.0)
  assert_eq(x.value(), 2.0)
  assert_eq(x.tangent(), 3.0)
  let c : @autodiff.Dual[Double] = @autodiff.Dual::constant(5.0)
  assert_eq(c.tangent(), 0.0)
  let v : @autodiff.Dual[Double] = @autodiff.Dual::variable(5.0)
  assert_eq(v.tangent(), 1.0)
}

Dual::zero

加法単位元 0+0ε0 + 0\varepsilon を返します。

pub fn[T : @luna-generic.Zero] Dual::zero() -> Dual[T]

これは Zero インスタンスの昇格されたメソッドです。

Dual::one

乗法単位元 1+0ε1 + 0\varepsilon を返します。

pub fn[T : @luna-generic.One + @luna-generic.Zero] Dual::one() -> Dual[T]

one は定数なので、その接成分は 0 です。Dual::variable(1) ではありません。これは One インスタンスの昇格されたメソッドです。

算術

4 つの演算子は和・差・積・商の規則を実装します。演算子としても、昇格されたメソッドとしても利用できます。

項目演算子規則T の境界
Dual::addx + y(a+bε)+(c+dε)=(a+c)+(b+d)ε(a+b\varepsilon)+(c+d\varepsilon) = (a+c) + (b+d)\varepsilonAdd
Dual::subx - y(a−c)+(b−d)ε(a-c) + (b-d)\varepsilonSub
Dual::neg-x−a−bε-a - b\varepsilonNeg
Dual::mulx * yac+(ad+cb)εac + (ad + cb)\varepsilonAdd + Mul
Dual::divx / yac+bc−adc2ε\dfrac{a}{c} + \dfrac{bc - ad}{c^2}\varepsilonDiv + Sub + Mul

Dual::add

成分ごとに加算します。

pub fn[T : Add] Dual::add(Dual[T], Dual[T]) -> Dual[T]
pub impl[T : Add] Add for Dual[T]

Dual::sub

成分ごとに減算します。

pub fn[T : Sub] Dual::sub(Dual[T], Dual[T]) -> Dual[T]
pub impl[T : Sub] Sub for Dual[T]

Dual::neg

両成分の符号を反転します。

pub fn[T : Neg] Dual::neg(Dual[T]) -> Dual[T]
pub impl[T : Neg] Neg for Dual[T]

Dual::mul

積の規則で乗算します。

pub fn[T : Add + Mul] Dual::mul(Dual[T], Dual[T]) -> Dual[T]
pub impl[T : Add + Mul] Mul for Dual[T]

接成分は self.value * other.tangent + other.value * self.tangent、すなわち ad+cbad + cb として計算されます。数値型 T の乗算はいずれも可換なので、これは積の規則 ad+bcad + bc に等しくなります。

Dual::div

定義域チェックなしで、商の規則により除算します。

pub fn[T : Div + Sub + Mul] Dual::div(Dual[T], Dual[T]) -> Dual[T]
pub impl[T : Div + Sub + Mul] Div for Dual[T]

接成分は (self.tangent * other.value - self.value * other.tangent) / (other.value * other.value) です。除数の値が 0 の場合の結果は T の除算の結果に従います(Double では無限大または NaN)。代わりにエラーを得たい場合は Dual::div_checked を使ってください。

test "dual arithmetic" {
  let x = @autodiff.Dual::new(2.0, 3.0)
  let y = @autodiff.Dual::new(5.0, 7.0)
  assert_eq(x + y, @autodiff.Dual::new(7.0, 10.0))
  assert_eq(y - x, @autodiff.Dual::new(3.0, 4.0))
  assert_eq(-x, @autodiff.Dual::new(-2.0, -3.0))
  assert_eq(x * y, @autodiff.Dual::new(10.0, 29.0)) // 2*7 + 5*3
  assert_eq(y / x, @autodiff.Dual::new(2.5, -0.25)) // (7*2 - 5*3) / 4
}

Dual::equal

両成分が等しいかを判定します。

pub fn[T : Eq] Dual::equal(Dual[T], Dual[T]) -> Bool

これは導出された Eq インスタンスの昇格されたメソッドです。== を使うことを推奨します。

チェック付き演算

チェック付きの形式は、無限大や NaN を生成する代わりに Luna-Flow/arithmetic の Result[Dual[T], ArithmeticError] を返します。使用する型は checked API が再エクスポートしています。

Dual::div_checked

商の規則で除算し、不正な除算をエラーとして報告します。

pub fn[T : @arithmetic.DivChecked + Sub + Mul] Dual::div_checked(Dual[T], Dual[T], @arithmetic.ArithmeticContext) -> Result[Dual[T], @arithmetic.ArithmeticError]
pub impl[T : @arithmetic.DivChecked + Sub + Mul] @arithmetic.DivChecked for Dual[T]

まず T の div_checked で値 a/ca / c を計算し、次に 2 回目の div_checked 呼び出しで接成分 (bc−ad)/c2(bc - ad) / c^2 を計算します。最初のエラーはそのまま返されます。Double でのエラーは次のとおりです:

条件エラー
c=0c = 0 かつ a≠0a \ne 0is_division_by_zero()
a=0a = 0 かつ c=0c = 0is_domain_error()(ゼロをゼロで割る)
aa と cc がともに無限大is_domain_error()
c≠0c \ne 0 だが c⋅cc \cdot c がアンダーフローして 00 になる接成分の除算が失敗します。下記の落とし穴を参照してください

コンテキスト引数はそのまま T に渡されます。arithmetic の Double と Float のインスタンスはこれを無視します。

test "checked dual division" {
  let ctx = @autodiff.ArithmeticContext::new(53)
  let x = @autodiff.Dual::new(6.0, 2.0)
  let y = @autodiff.Dual::new(3.0, 1.0)
  match x.div_checked(y, ctx) {
    Ok(q) => assert_eq(q, @autodiff.Dual::new(2.0, 0.0))
    Err(_) => fail("unexpected error")
  }
  match x.div_checked(@autodiff.Dual::new(0.0, 1.0), ctx) {
    Ok(_) => fail("expected an error")
    Err(e) => assert_true(e.is_division_by_zero())
  }
}

Dual::sqrt_checked

規則 a+bε=a+b2aε\sqrt{a + b\varepsilon} = \sqrt a + \dfrac{b}{2\sqrt a}\varepsilon で平方根を取り、定義域エラーを報告します。

pub fn[T : @arithmetic.SqrtChecked + @arithmetic.DivChecked + @luna-generic.IntegralHomomorphism + Mul] Dual::sqrt_checked(Dual[T], @arithmetic.ArithmeticContext) -> Result[Dual[T], @arithmetic.ArithmeticError]
pub impl[T : @arithmetic.SqrtChecked + @arithmetic.DivChecked + @luna-generic.IntegralHomomorphism + Mul] @arithmetic.SqrtChecked for Dual[T]

値は T の sqrt_checked(a)、接成分は div_checked(b, 2 * root) です。ここで 2 は IntegralHomomorphism::from_integral(2) から得られます。Double では:

条件エラー
a<0a < 0平方根からの is_domain_error()
a=0a = 0, b≠0b \ne 0接成分からの is_division_by_zero()
a=0a = 0, b=0b = 0接成分からの is_domain_error()(ゼロをゼロで割る)

したがって sqrt_checked は、定数入力であっても a=0a = 0 で失敗します。⋅\sqrt{\cdot} は 00 で微分可能ではなく、チェック付きの形式は接成分が 0 の場合を特別扱いしません。

test "checked dual square root" {
  let ctx = @autodiff.ArithmeticContext::new(53)
  match @autodiff.Dual::new(9.0, 6.0).sqrt_checked(ctx) {
    Ok(r) => assert_eq(r, @autodiff.Dual::new(3.0, 1.0))
    Err(_) => fail("unexpected error")
  }
  match @autodiff.Dual::new(-1.0, 1.0).sqrt_checked(ctx) {
    Ok(_) => fail("expected an error")
    Err(e) => assert_true(e.is_domain_error())
  }
}

初等関数

各初等関数は固有メソッドであると同時に、対応する arithmetic トレイトインスタンスのメソッドでもあります。そのため Sqrt、Exponential、Logarithmic、Trigonometric に対して書かれたジェネリックなコードは、変更なしで Dual[T] を通して微分できます。いずれも、下記の導関数を用いて規則 f(a+bε)=f(a)+f′(a) b εf(a + b\varepsilon) = f(a) + f'(a)\,b\,\varepsilon を適用します。いずれも定義域をチェックしません。定義域の外では結果は T が返すものになります(Double では NaN または無限大)。

項目値実際に計算される接成分導関数
Dual::sqrta\sqrt ab / (2 * sqrt(a))12a\frac{1}{2\sqrt a}
Dual::expeae^ab * exp(a)eae^a
Dual::exp22a2^ab * exp2(a) * ln(2)2aln⁡22^a \ln 2
Dual::lnln⁡a\ln ab / a1a\frac{1}{a}
Dual::log2log⁡2a\log_2 ab / (a * ln(2))1aln⁡2\frac{1}{a\ln 2}
Dual::log10log⁡10a\log_{10} ab / (a * ln(10))1aln⁡10\frac{1}{a\ln 10}
Dual::sinsin⁡a\sin ab * cos(a)cos⁡a\cos a
Dual::coscos⁡a\cos a-(b * sin(a))−sin⁡a-\sin a
Dual::tantan⁡a\tan ab / (cos(a) * cos(a))sec⁡2a\sec^2 a

定数 22 と 1010 は IntegralHomomorphism::from_integral から得られます。

Dual::sqrt

接成分 b/(2a)b / (2\sqrt a) を持つ平方根です。

pub fn[T : @arithmetic.Sqrt + @luna-generic.IntegralHomomorphism + Mul + Div] Dual::sqrt(Dual[T]) -> Dual[T]
pub impl[T : @arithmetic.Sqrt + @luna-generic.IntegralHomomorphism + Mul + Div] @arithmetic.Sqrt for Dual[T]

Dual::exp

接成分 b eab\,e^a を持つ指数関数です。値は 1 回だけ計算して再利用します。

pub fn[T : @arithmetic.Exponential + Mul] Dual::exp(Dual[T]) -> Dual[T]

Dual::exp2

接成分 b⋅2aln⁡2b \cdot 2^a \ln 2 を持つ底 2 の指数関数です。

pub fn[T : @arithmetic.Exponential + @arithmetic.Logarithmic + @luna-generic.IntegralHomomorphism + Mul] Dual::exp2(Dual[T]) -> Dual[T]
pub impl[T : @arithmetic.Exponential + @arithmetic.Logarithmic + @luna-generic.IntegralHomomorphism + Mul] @arithmetic.Exponential for Dual[T]

Exponential インスタンスは exp と exp2 の両方を提供するため、exp2 の境界が必要です。

Dual::ln

接成分 b/ab / a を持つ自然対数です。

pub fn[T : @arithmetic.Logarithmic + Div] Dual::ln(Dual[T]) -> Dual[T]

Dual::log2

接成分 b/(aln⁡2)b / (a \ln 2) を持つ底 2 の対数です。

pub fn[T : @arithmetic.Logarithmic + @luna-generic.IntegralHomomorphism + Mul + Div] Dual::log2(Dual[T]) -> Dual[T]

Dual::log10

接成分 b/(aln⁡10)b / (a \ln 10) を持つ底 10 の対数です。

pub fn[T : @arithmetic.Logarithmic + @luna-generic.IntegralHomomorphism + Mul + Div] Dual::log10(Dual[T]) -> Dual[T]
pub impl[T : @arithmetic.Logarithmic + @luna-generic.IntegralHomomorphism + Mul + Div] @arithmetic.Logarithmic for Dual[T]

Logarithmic インスタンスは ln、log2、log10 を提供します。

Dual::sin

接成分 bcos⁡ab \cos a を持つ正弦関数です。

pub fn[T : @arithmetic.Trigonometric + Mul] Dual::sin(Dual[T]) -> Dual[T]

Dual::cos

接成分 −bsin⁡a-b \sin a を持つ余弦関数です。

pub fn[T : @arithmetic.Trigonometric + Mul + Neg] Dual::cos(Dual[T]) -> Dual[T]

Dual::tan

導関数 sec⁡2a\sec^2 a を持つ正接関数です。b/(cos⁡a⋅cos⁡a)b / (\cos a \cdot \cos a) として計算します。

pub fn[T : @arithmetic.Trigonometric + Mul + Div] Dual::tan(Dual[T]) -> Dual[T]
pub impl[T : @arithmetic.Trigonometric + Mul + Neg + Div] @arithmetic.Trigonometric for Dual[T]

Trigonometric インスタンスは sin、cos、tan を提供します。

test "elementary derivative rules" {
  let x = @autodiff.Dual::variable(0.5)
  assert_eq(x.sin().tangent(), @math.cos(0.5))
  assert_eq(x.exp().tangent(), @math.exp(0.5))
  assert_eq(x.ln().tangent(), 2.0)
  assert_eq(@autodiff.Dual::variable(4.0).sqrt().tangent(), 0.25)
}

トレイトインスタンス

Dual[T] は、代数 T[ε]/(ε2)T[\varepsilon]/(\varepsilon^2) が満たす Luna Flow の構造トレイトを、それぞれ T に対する対応する境界のもとで実装します。法則は dual の設計 で導出しています。

インスタンスT の境界意味
ZeroZerozero() は 0+0ε0 + 0\varepsilon
OneOne + Zeroone() は 1+0ε1 + 0\varepsilon
AddMonoidAddMonoid成分ごとの加算
AddGroupAddGroup成分ごとの符号反転
MulMonoidSemiring積の規則による乗算
SemiringSemiringT が半環なら T[ε]T[\varepsilon] も半環
RingRingT が環なら T[ε]T[\varepsilon] も環
NatHomomorphismNatHomomorphism + Zerofrom_nat(n) は Dual::constant(from_nat(n))
IntegralHomomorphismIntegralHomomorphism + Zerofrom_integral(n) は Dual::constant(from_integral(n))
@arithmetic.ConstantsConstants + Zeropi()、tau()、e() は定数
@arithmetic.DivCheckedDivChecked + Sub + MulDual::div_checked を参照
@arithmetic.SqrtCheckedDual::sqrt_checked を参照Dual::sqrt_checked を参照
@arithmetic.Sqrt, Exponential, Logarithmic, Trigonometric各メソッドを参照初等関数 を参照

整数と自然数の埋め込み、および定数は、微分変数に依存しないため接成分 0 を生成します。

fn[T : @autodiff.Ring + @autodiff.IntegralHomomorphism] three_x_squared(x : T) -> T {
  let three : T = @autodiff.IntegralHomomorphism::from_integral(3)
  three * x * x
}

test "generic code differentiates through the trait instances" {
  let y = three_x_squared(@autodiff.Dual::variable(2.0))
  assert_eq(y, @autodiff.Dual::new(12.0, 12.0))
}

非推奨

Dual は、かつて MoonBit がトレイトインスタンスから暗黙的に生成していたメソッド形式をいくつか残しています。これらはインターフェースファイルから隠されており、このパッケージの外で使うと警告が出ます。

非推奨のメソッド代替
x.not_equal(y)x != y
x.to_repr()Repr(x) または @debug.Debug::to_repr(x)
Dual::from_nat(n)Luna-Flow/luna-generic の NatHomomorphism::from_nat(n)
Dual::from_integral(n)@autodiff.IntegralHomomorphism::from_integral(n)
Dual::pi(), Dual::e(), Dual::tau()@autodiff.Constants::pi(), e(), tau()