6 ๋ถ
๐๏ธ Rust์ ๋ฏธ๋ถํ๊ธฐ 02: ๊ธฐํธ ๋ฏธ๋ถ
๐ Automatic Differentiation Series
๐ ์์น์ ๋ฏธ๋ถ์ ํ๊ณ
์ ๋ฒ ํฌ์คํธ์์ ์์น์ ๋ฏธ๋ถ์ ์ฌ๋ฌ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํ๋ ๊ฒ์ ๋ค๋ค๋ณด์๋๋ฐ, ์ด๋ ์ จ๋์? ์๋ง, ์ฝ๋ฉ์ ๋ํ ์กฐ๊ธ์ ์ง์๋ง ์์ผ๋ฉด ์คํ๋ ค ๊ณ ๋ฑํ๊ต๋์ ๋ฏธ๋ถ๋ณด๋ค ํจ์ฌ ์ฝ๊ฒ ๋๊ปด์ง์ จ์ ๊ฒ๋๋ค. ์ ํฌ๊ฐ ์ฌ์ฉํ ๊ฒ์ด๋ผ๊ณ ๋ ๊ทธ์ ๋ํจ์์ ์ ์์ ๋ฐ๋ผ ํจ์์ ๊ฐ ๊ตฌ๊ฐ ๊ฐ์ ๋์ ํ ๊ฒ์ด ์ ๋ถ์๋๋ฐ, ์ด๋ฅผ ์ฝ๋๋ก ๋ํ๋ด๋ฉด ๊ฒฐ๊ตญ ๋ค์์ ์ฝ๋์ ์ง๋์ง ์์ต๋๋ค.
# Python
def differentiation(f, x, h=1e-06):
return (f(x + h) - f(x)) / h
๋๋จธ์ง๋ ์ด๋ฅผ ๊ฐ์ฒด์งํฅ์ ์ผ๋ก ๊ตฌํํ๊ฑฐ๋, ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ๊ตฌํํ๊ฑฐ๋ ์ ๋๋ฆญ ํ๋ก๊ทธ๋๋ฐ์ ๋์ ํ๋ ๋ฑ์ ๊ตฌํ๋ฐฉ๋ฒ์ ์ฐจ์ด์ผ ๋ฟ์ด์์ต๋๋ค. ์ด๋ ๊ฒ ์์น์ ๋ฏธ๋ถ ๋ฐฉ๋ฒ์ ๊ต์ฅํ ๊ฐ๋จํ ๊ตฌํ๊ณผ ์์ฒญ ๋น ๋ฅธ ๊ณ์ฐ์๋๋ฅผ ๊ฐ์ ธ์ ๋๊ตฌ๋ ์ฝ๊ฒ ๋ฏธ๋ถ์ ํ ์ ์๊ฒ ํด์ฃผ์์ต๋๋ค๋ง, ์ค์ฐจ๊ฐ ํ์ฐ์ ์ผ๋ก ๋ฐ์ํ๊ฒ ๋๋ ๋จ์ ์ด ์์์ต๋๋ค. ๋ฐ๋ผ์ ์ค์ฐจ์ ํฌ๊ฒ ๋ฏผ๊ฐํ์ง ์์ ๋ฌธ์ ๋, Step ์๊ฐ ์ ์ด์ ์ค์ฐจ๊ฐ ํฌ๊ฒ ์์ด์ง ์๋ ๋ฏธ๋ถ๋ฐฉ์ ์์ ํธ๋ ๊ฒฝ์ฐ์ ์ถฉ๋ถํ์ง๋ง, ์ค์ฐจ์ ๋ฏผ๊ฐํ๊ฑฐ๋ Step ์๊ฐ ๋ง์์ ์ค์ฐจ๊ฐ ์์ฌ ์ ์๋ฏธํ ์ฐจ์ด๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฏธ๋ถ๋ฐฉ์ ์์ ๊ฒฝ์ฐ์ ํฐ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํ ์ ์์ต๋๋ค. ๋ํ์ ์ธ ์์๋ก “๋ก๋ ์ฆ์ ๋๋น"๊ฐ ์์ต๋๋ค.
๐ฆ ๋ก๋ ์ฆ์ ๋๋น
์๋์๋ ๋ก๋ ์ฆ๋ ๊ฑธ์ถํ ์ํ์๋ก, ํนํ ์นด์ค์ค ์ด๋ก ์ ์ ๊ตฌ์๋ก ์ ๋ช ํ์ ๋ถ์ ๋๋ค. ๊ทธ๋ 1963๋ ์ ๋๊ธฐ ๋๋ฅ์ ๊ฐ๋จํ ์ํ์ ๋ชจํ์ ๋ง๋ค์๋๋ฐ, ์ด ๋ชจ๋ธ์ ๋ค์์ 3๊ฐ์ ์๋ฏธ๋ถ๋ฐฉ์ ์์ผ๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค.
$$ \begin{align} \frac{dx}{dt} &= \sigma(y-x) \\ \frac{dy}{dt} &= x (\rho - z) - y \\ \frac{dz}{dt} &= xy - \beta z \end{align} $$
๋ถ๋ช ์์ฃผ ๊ฐ๋จํ ๋ฏธ๋ถ๋ฐฉ์ ์์ธ๋ฐ, ๋๋๊ฒ๋ ์์ฃผ ๋ณต์กํ ํํ์ ํด๊ฐ ๋์ถ๋ฉ๋๋ค. ์ด๋์ ๋ํ์ ์ธ ํด์ ํํ๊ฐ ์์์ ์ฒจ๋ถํ ๊ทธ๋ฆผ์ ๋๋ค. ์ด ์์คํ ์ ๊ต์ฅํ ์๋ฏผํ๋ฐ, ๋งค๊ฐ๋ณ์์ ๊ฐ์ ์กฐ๊ธ ๋ฐ๊พธ๊ฑฐ๋ ํน์ Step size๋ฅผ ์กฐ๊ธ๋ง ๋ฐ๊ฟ๋ ํด์ ํํ๋ ์์ธกํ ์ ์๋ ํํ๋ก, ๊ทธ๊ฒ๋ ๊ต์ฅํ ํ๊ฒฉ์ ์ผ๋ก ๋ณํ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด ์์ ๊ทธ๋ฆผ์ ์ค์ผ๋ฌ(Euler) ๋ฐฉ๋ฒ์ด๋ผ๋ ์์น์ ๋ฏธ๋ถ๋ฐฉ์ ์ ํด๋ฒ ์ค ํ๋๋ก ํ์๋๋ฐ, ์์ ๋ชจ๋ ์กฐ๊ฑด์ ๋์ผํ๊ฒ ๋๊ณ ๋ฐฉ๋ฒ๋ง ๋ฃฝ๊ฒ-์ฟ ํ(Runge-Kutta 4th order) ๋ฐฉ๋ฒ์ผ๋ก ๋ฐ๊พธ๋ฉด ๋ค์์ ๊ทธ๋ฆผ์ด ๋์ต๋๋ค.
์ค๋ก์ง ๋ฐฉ๋ฒ๋ง ๋ฐ๊พธ์์ ๋ฟ์ธ๋ฐ ๊ฒฐ๊ณผ๊ฐ ์๋นํ ๋ง์ด ๋ค๋ฅธ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๋ฌผ๋ก ์ด ๊ฒฝ์ฐ์๋ ์ ์ฒด์ ์ธ ํํ๋ ๋ฐ๋์ง ์์ง๋ง, ํน์ ๋งค๊ฐ๋ณ์ ์ฃผ๋ณ์์๋ ์์ ํํ ์ ์ฒด๊ฐ ๊ธ๊ฒฉํ๊ฒ ๋ณํ๋๋ ๊ฒฝ์ฐ๋ ๋ฐ์ํฉ๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ์๋ ์ค์ฐจ๊ฐ ํ์ฐ์ ์ผ๋ก ๋ฐ์ํ๋ ์์น์ ๋ฏธ๋ถ ๋ฐฉ๋ฒ์ด ์ ํฉํ์ง ์์ต๋๋ค. ๊ทธ๋ ๋ค๋ฉด ์ด๋ฐ ๊ฒฝ์ฐ์๋ ์ด๋ป๊ฒ ํ์ด์ผ ํ ๊น์?
๐ฌ๐ท ๊ธฐํธ์ ๋ฏธ๋ถ (Symbolic Differentiation)
์ธ๊ฐ์ ์ ์ ํ ๊ต์ก๋ง ๋ฐ๋๋ค๋ฉด ๋ฏธ๋ถ์ ์๋ฌด๋ฐ ์ค์ฐจ์์ด ๊ณ์ฐํด๋ผ ์ ์์ต๋๋ค. (๋ฌผ๋ก , ๊ณ์ฐ์ค์๋ก ์ธํ ์ค์ฐจ๋ ์ข ์ข ๋ฐ์ํฉ๋๋ค.) ์๋ฅผ ๋ค์ด ๋ค์ ํจ์์ ๋ํจ์๋ฅผ ์๊ฐํด๋ด ์๋ค.
$$ y = x^2 $$
์ด์ ๊ธ์์ ๋ค๋ฃจ์๋ค์ํผ ์์น์ ๋ฏธ๋ถ ๊ตฌํ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. ๋๋ฌด ๋๊ฐ์ผ๋ฉด ์ฌ์ฌํ๋ ์์ฆ ๊ฐ๊ด๋ฐ๋ ์์น ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ธ Julia๋ก ํํํด๋ณด๊ฒ ์ต๋๋ค.
# Julia
function df(x, f, h=1e-06)
return (f(x+h) - f(x)) / h
end
# Derivative
dx2(x) = df(x, x -> x^2)
# Print
println(dx2(1)) # 2.0000009999243673
์ด๋ฒ์ ๊ณ ๋ฑํ์์ด ํธ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ด ์๋ค. (๋ํ๋ฏผ๊ตญ ๊ณ ๋ฑํ๊ต 2ํ๋ ์ํ2 ๊ณผ์ ์ ์ด์ํ ํ์์ด๋ผ๊ณ ๊ฐ์ ํฉ๋๋ค.)
$$ \begin{align} \frac{d}{dx}(x^2) &= \lim_{h \rightarrow 0} \frac{(x+h)^2 - x^2}{h} \\ &= \lim_{h\rightarrow 0} \frac{2hx + h^2}{h} \\ &= 2x \end{align} $$
์ฌ๊ธฐ์ 1์ ๋์ ํ๋ฉด ์ ํํ 2๊ฐ ๋์ต๋๋ค. ์์์ ์์น์ ๋ฏธ๋ถ์ ๊ฒฐ๊ณผ์ ๋ฌ๋ฆฌ ์ค์ฐจ๋ ํฌํจ๋์ง ์์์ต๋๋ค. ๋ฏธ๋ถ์ ๋ฐฐ์ด ์ฌ๋์ผ ๊ฒฝ์ฐ, ์ ํ์ด๋ ์ ํ ์ด๋ ค์ด ํ์ด๊ฐ ์๋๋๋ค. ๊ท์น๋ง ์ ์งํจ๋ค๋ฉด ๋ค๋ฅธ ํจ์๋ค์ ๋ฏธ๋ถํ ๋์๋ ํฐ ์ด๋ ค์์ ์์ ๊ฒ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ปดํจํฐ์๊ฒ ๊ท์น์ ๊ฐ๋ฅด์น๋ฉด ์ด๋จ๊น์? ์ด๋ป๊ฒ ๊ฐ๋ฅด์น๋๊ฐ ๊ด๊ฑด์ด๊ฒ ์ง๋ง ์ผ๋จ ๊ฐ๋ฅด์น ์ ์๋ค๋ฉด ์ค์ฐจ์๋ ์๋ฒฝํ ๋ฏธ๋ถ์ ์ปดํจํฐ๋ก ๊ตฌํํ ์ ์์ ๊ฒ์ ๋๋ค. ๋คํํ๋ ์ฌ๋๋ค์ ์ด๋ฏธ ๊ทธ๊ฒ์ ๊ตฌํํ์๊ณ ์ด๋ฅผ CAS(Computer Algebra System)๋ผ ๋ถ๋ฆ ๋๋ค.
๋ํ์ ์ธ CAS๋ก๋ Mathematica, Matlab, Maple ๋ฑ์ ์์ ์ฉ ํ๋ก๊ทธ๋จ๋ค๊ณผ Python์ผ๋ก ๊ตฌํ๋ Sympy, Sagemath ๋ฑ์ ๋ฌด๋ฃ ํ๋ก๊ทธ๋จ ํน์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์ต๋๋ค. CAS๋ ์ค์ ๋ก ์ธ๊ฐ์ด ํ๋ ๊ฒ์ฒ๋ผ ๋ฏธ๋ถ, ์ ๋ถ, ๋์ ๋ฟ ์๋๋ผ ์ฌ์ง์ด ๋ฏธ๋ถ๊ธฐํ ๋ฑ์ ๊ณ ๊ธ ์ํ ๋ฌธ์ ๊น์ง๋ ํ์ด๋ผ ์ ์์ต๋๋ค.
์๋๋ sagemath๋ฅผ ์ด์ฉํ ๊ฐ๋จํ ๋ํจ์ ๊ตฌํ์ ๋๋ค.
var('x') # ๋ณ์๋ฅผ ์ ์ธํฉ๋๋ค.
f(x) = x^2 # ํจ์๋ฅผ ์ ์ธํฉ๋๋ค.
df = diff(f, x) # ๋ํจ์๋ฅผ ๊ณ์ฐํฉ๋๋ค.
print(df(1)) # 2
์ ํํ ๋ฟ๋ง ์๋๋ผ ๊ฐ๋จํ๊ธฐ๊น์ง ํ๋ ๋ ์ด์ ์์น์ ๋ฏธ๋ถ์ ๊ณ ์งํ ์ด์ ๋ ์์ด๋ณด์ ๋๋ค. ํ์ง๋ง ์ด๋ ๊ฒ ์์ฒญ๋ CAS์๋ ์น๋ช ์ ์ธ ๋จ์ ์ด ์กด์ฌํฉ๋๋ค. ๋ฐ๋ก ์๋์ ๋๋ค. ๊ธฐํธ์ ๋ฏธ๋ถ ์์ฒด๋ ๊ณ์ฐ ์๋๊ฐ ๋น ๋ฅผ ์ ์์ง๋ง ๊ทธ๊ฒ์ ์์น ๊ฐ๋ค์ ๋์ ํ ๋ ํ์ ํ๊ฒ ์๋ ์ ํ๊ฐ ์ผ์ด๋ฉ๋๋ค. ์๋๋ ๊ฐ๋จํ ๋ฏธ๋ถ ๊ณ์ฐ์ ํฌ๊ธฐ๊ฐ ํฐ ๋ฐฐ์ด ๊ฐ์ ๋์ ํ์ฌ ์ฑ๋ฅ์ ์ธก์ ํ ๊ฒฐ๊ณผ์ ๋๋ค. Peroxide๋ Rust์ ์์น๊ณ์ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ด๋ฆ์ด๋ฉฐ ํ์ ๋ค๋ฃฐ ์๋๋ฏธ๋ถ ์๊ณ ๋ฆฌ์ฆ์ ์ ์ฉํ์ฌ ๊ณ์ฐ์ ์ํํ์๊ณ , numpy๋ Python์ ์ ๋ช ํ ์์น๊ณ์ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์์น์ ๋ฏธ๋ถ์ผ๋ก ๊ณ์ฐํ์์ต๋๋ค. ๋ง์ง๋ง์ผ๋ก Sagemath๋ ๊ธฐํธ์ ๋ฏธ๋ถ์ผ๋ก ๊ณ์ฐ ํ ์์น ๊ฐ์ ๋์ ํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ตฌํ์ต๋๋ค.
๋ฌผ๋ก ์ด๋ค ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ๋์ง์ ๋ฐ๋ผ ์ค์ ์์น ๊ณ์ฐ์์์ ๊ฒฐ๊ณผ๋ ์กฐ๊ธ ๋ค๋ฅผ ์ ์์ต๋๋ค. ๋ค์์ Julia ์ธ์ด ํ์์ ์ค์ํ Benchmark ๊ฒฐ๊ณผ์ ๋๋ค.
๊ทธ๋ฆผ์ ๋ณด๋ฉด Matlab์ ์คํ์์ค ๊ฒฉ์ธ Octave๋ ์์ธ๋ก ์น๋๋ผ๋ Mathematica๊ฐ ์๊ฐ๋ณด๋จ ๋๋ฆฌ์ง ์์์ ์ ์ ์์ต๋๋ค. (๊ทธ๋๋ C๋ณด๋ค ๊ฑฐ์ 10~100๋ฐฐ ๋๋ฆฌ๊ธด ํ์ง๋ง์.) Mathematica๋ ํ๋ ฌ ๊ณ์ฐ์ BLAS๋ฅผ ์ด์ฉํ๊ณ ๊ฐ๊ฐ์ง ํ์ํ ์์น ๊ณ์ฐ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ๊ธฐ์ ํน์ ๊ณ์ฐ๋ค์ ์ฌ์ง์ด numpy๋ฅผ ์ด์ฉํ Python๋ณด๋ค ๋น ๋ฅด๊ธฐ๊น์ง ํฉ๋๋ค. ๋ค๋ง, Mathematica์์๋ ๊ธฐํธ์ ๋ฏธ๋ถ๊ณผ ์์น์ ์ธ ์ฐ์ฐ์ ์๋ก ์ค๊ฐ๋์๋ ์ญ์๋ ํฐ ์๋์ ํ๊ฐ ํ์ฐ์ ์ผ๋ก ๋ฐ์ํฉ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ๊ท๋ชจ๊ฐ ํฐ ๋ฏธ๋ถ ๊ณ์ฐ์ ๋ํด์๋ ์ด๋ป๊ฒ ์ ๊ทผํด์ผํ ๊น์? ์๋ ์ ํ๋ฅผ ๊ณ ๋ คํ์ฌ ์์น์ ๋ฏธ๋ถ์ผ๋ก ๊ตฌํํ์๋ ๊ท๋ชจ๊ฐ ์ปค์ ์ค์ฐจ๋ ๊ทธ๋งํผ ๋ง์ด ์์ผํ ๊ณ , ์ ํ๋๋ฅผ ๊ณ ๋ คํ์ฌ ๊ธฐํธ์ ๋ฏธ๋ถ์ ๊ณ ๋ คํ์๋ ๊ต์ฅํ ์ค๋ ์์ผ์ด ๊ฑธ๋ฆด ๊ฒ์ ๋ปํฉ๋๋ค. ์ฌ์ง์ด ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ก ๊ฒ์ฐ ๋์ค์ ๋ค์ด๋ ์๋ ์์ต๋๋ค. ๋คํํ๋ ๋ฏธ๋ถ์ ํํด์๋ ๊ฑฐ์ ์๋ฒฝํ ํด๋ต์ด ์กด์ฌํฉ๋๋ค. ์ด์ ๋ํด์๋ ๋ค์ ํฌ์คํธ์์ ๋ค๋ฃจ๋๋ก ํ๊ฒ ์ต๋๋ค.
๐ ๋ถ๋ก
A. ๋ก๋ ์ฆ ๋๋น ์ฝ๋
์์์ ์ฒจ๋ถํ ๋ก๋ ์ฆ ๋๋น ๊ทธ๋ฆผ๋ค์ Rust์ ์์น ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Peroxide๋ฅผ ์ด์ฉํ์ฌ ๊ณ์ฐํ์์ต๋๋ค. ์์ค์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
extern crate peroxide;
use peroxide::fuga::*;
fn main() -> Result<(), Box<dyn Error>> {
// =========================================
// Declare ODE
// =========================================
let mut ex_test = ExplicitODE::new(butterfly);
let init_state: State<f64> = State::new(
0.0,
vec![10.0, 1.0, 1.0],
vec![0.0, 0.0, 0.0],
);
ex_test
.set_initial_condition(init_state)
.set_method(ExMethod::Euler)
.set_step_size(0.01f64)
.set_times(10000);
let mut ex_test2 = ex_test.clone();
ex_test2.set_method(ExMethod::RK4);
// =========================================
// Save results
// =========================================
let results = ex_test.integrate();
let results2 = ex_test2.integrate();
let mut df_euler = DataFrame::from_matrix(results);
df_euler.set_header(vec!["t", "x", "y", "z"]);
df_euler.print();
let mut df_rk4 = DataFrame::from_matrix(results2);
df_rk4.set_header(vec!["t", "x", "y", "z"]);
df_rk4.print();
df_euler.write_nc("data/euler.nc")?;
df_rk4.write_nc("data/rk4.nc")?;
Ok(())
}
fn butterfly(st: &mut State<f64>, _: &NoEnv) {
let x = &st.value;
let dx = &mut st.deriv;
dx[0] = 10f64 * (x[1] - x[0]);
dx[1] = 28f64 * x[0] - x[1] - x[0] * x[2];
dx[2] = -8f64/3f64 * x[2] + x[0] * x[1];
}
์ดํ์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์์ ๊ทธ๋ฆผ์ ๊ทธ๋ฆฌ๋ ๊ฒ์ Python์ผ๋ก ์์ฑํ์์ต๋๋ค. ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
from netCDF4 import Dataset
import matplotlib.pyplot as plt
# Import netCDF file
ncfile1 = './data/euler.nc'
data1 = Dataset(ncfile1)
var1 = data1.variables
ncfile2 = './data/rk4.nc'
data2 = Dataset(ncfile2)
var2 = data2.variables
# Use latex
plt.rc('text', usetex=True)
plt.rc('font', family='serif')
# Prepare Plot
plt.figure(figsize=(10,6), dpi=300)
plt.title(r"Lorenz Butterfly (Euler)", fontsize=16)
plt.xlabel(r'$x$', fontsize=14)
plt.ylabel(r'$z$', fontsize=14)
# Prepare Data to Plot
x1 = var1['x'][:]
z1 = var1['z'][:]
# Plot with Legends
plt.plot(x1, z1, label=r'Lorenz (Euler)')
# Other options
plt.legend(fontsize=12)
plt.grid()
plt.savefig("euler.png", dpi=300)
# Prepare Plot
plt.figure(figsize=(10,6), dpi=300)
plt.title(r"Lorenz Butterfly (RK4)", fontsize=16)
plt.xlabel(r'$x$', fontsize=14)
plt.ylabel(r'$z$', fontsize=14)
# Prepare Data to Plot
x2 = var2['x'][:]
z2 = var2['z'][:]
# Plot with Legends
plt.plot(x2, z2, label=r'Lorenz (RK4)')
# Other options
plt.legend(fontsize=12)
plt.grid()
plt.savefig("rk4.png", dpi=300)
์ด์ธ์ ์์ธํ ์ฌํญ์ Peroxide Gallery์ ๋์์์ผ๋ ์ฐธ๊ณ ํ์๋ฉด ๋ฉ๋๋ค.