λ Haskell λ で導関数を実装
今回はHaskellを用い、速度関数及び加速関数を題に導関数の実装を紹介します。
速度関数
速度関数の
数学なら:
つまり、
velocity dt x t = (x(t + dt / 2) - x(t - dt / 2)) / dt
ご覧の通り、極限を真似る一番簡単な方法ですからdt
を指定します。上記を数学に再訳すと
ただし、数学的微分と違って、この時点でdt
を小さくするのではなく、大きくするときの方は精密度が高いです(手頃なためGHCiという対話環境を使います):
ghci> velocity dt x t = (x(t + dt / 2) - x(t - dt / 2)) / dt
-- 15秒たてば速度はなーんだ?
-- 当たり前ですけど、x(t) = 30*tだと速度は常に30
-- でも、あれ?結果は少し違います
ghci> velocity 0.001 (\t -> 30*t) 15
30.000000000086402
ghci> velocity 0.01 (\t -> 30*t) 15
30.00000000000682
-- dtを大きくすると正解が出る
ghci> velocity 0.1 (\t -> 30*t) 15
30.0
ghci> velocity 1 (\t -> 30*t) 15
30.0
期待に反してdt
を大きくすればするほど正解に近づきます。なぜこうなるのかといいますと、使っている関数(\t -> 30*t
)が単純すぎるからです。速度は常に等しいため、計測する間隔をどんなに伸ばしたり縮めたりしても平均速度は変わりません。
これに対して、間隔を短くすると浮動小数点数の限界に達し、精密度が下がってしまいます。
では、速度ができるだけ頻繁に変わる例を考えましょう。例えば、くねくねとした図表を持っている正弦関数なら良いでしょう。
正弦関数の導関数は余弦関数ですので、
ghci> velocity 1 sin 0
0.958851077208406
ghci> velocity 0.1 sin 0
0.9995833854135666
ghci> velocity 0.01 sin 0
0.9999958333385416
ghci> velocity 0.001 sin 0
0.9999999583333338
ghci> velocity 0.0001 sin 0
0.9999999995833334
ghci> velocity 0.00001 sin 0
0.9999999999958332
今回はdt
を小さくすると精密度がちゃんと上がってくれます。
加速と2階導関数
もちろん、微分ができると、速度だけでなく加速も計算できます。acceleration
の実装は上記のvelocity
と全く同じですが、名前だけ変えて多少わかりやすくしましょう:
-- 導関数
ghci> acceleration dt v t = (v(t + dt / 2) - v(t - dt / 2)) / dt
-- 速度関数
ghci> v t = 2 * sin t + 30
ghci> acceleration 0.000001 v 0
2.0000000020559128 -- 悪くはない
OK。次は位置関数を渡し、加速を返す2階導関数を作りたいです。そのために、まず積文法を使って2階導関数に渡すべき位置関数を準備します:
Haskellでは:p t = -2 * cos t + 30 * t
後は、導関数を自分の入力にして2階導関数を作り出します。位置関数の2階導関数ですので一応ddx
と名付けましょう:
-- 元の導関数
ghci> dx dt x t = (x(t + dt / 2) - x(t - dt / 2)) / dt
-- 2階導関数
ghci> ddx dt x t = dx dt (dx dt x) t
最後に、上記のp
を渡せば完成:
ghci> ddx 0.0001 p 0
1.9999999656405976
正解です。ただ、今回のdt
は割と大きいにしないと浮動小数点数の精密度の限界を超えてしまいますので、ご注意ください。
おまけ:2階導関数の簡略化
念のため、2階導関数の式を簡略化し、dt
の精密度を上げることはできないか検討してみます。
簡略化はこちら:
Haskellに戻ると:
ghci> ddx2 dt x t = (x(t+dt)+x(t-dt)-2*x(t))/dt**2
ghci> ddx2 0.0001 p 0
1.999999987845058
dt
をこれ以上小さくすると精密度が下がりますので、前と比べると結果は微妙に正解に近づいたぐらいですね。
Discussion