「Ruby Under a Microscope」を淡々と翻訳していくブログ

'http://patshaughnessy.net/ruby-under-a-microscope' これを翻訳していきます.

微積分や統計解析を快適に扱う言語(DSL) rubyで作ったヨ!

既にQiita に公開済みですが、このブログにマージして行きたいと思います.

(いずれQiita側は消したい)

Rubyで微積分や統計解析を快適に扱うDSL作ったヨ!↓

rubygems.org

github

バグ報告を頂ければ24時間以内に直します.

このDSLで一番大事にしたのは、

微積分や極限、対数や三角関数などを紙上で数学をする感覚と同じ感覚で取り扱える事.

わざわざRubyでやる必要、あったの...?(;゜0゜)

僕がこのDSLをRubyで作ったのにはいくつか訳があります.

  • Rubyが好き
  • MATLABが苦手
  • Rubyが好き
  • mathmaticaが苦手
  • Rubyが好き
  • pythonが苦手
  • Rubyが好き ...

とにかくRubyが好きです.

少しマジレスすると僕の大好きなRubyは数学色が薄いのが少し悲しかったのです.

(蛇足ですが、この想いからrubyのMatrixクラスにシコシコcommit してます.

ex. https://github.com/ruby/ruby/pull/568

Matrixを成長させ組み合わせれば、線形微分方程式や各種統計解析など夢ヒロガリング)

ひとまず、ご覧になって雰囲気を掴んで頂くのが良いと思います.

雰囲気を掴む

関数の定義と微積分

require 'dydx' # gem install dydxなどよしなに
# ↓このincludeによって、DSL化します.
include Dydx

# 'x + log(y)' の様な入力を内部でDydx内のオブジェクトに変換するようにしています.

x + log(y)
=> #<Dydx::Algebra::Formula:0x007fc1fb0cec88 @f=:x, @g=#<Dydx::Algebra::Set::Log:0x007fc1fb0cf048 @x=:y>, @operator=:+>


# f(x, y)という関数を定義している(数学上の:=や=にあたるもの、ここだけsyntax悔しい
f(x, y) <= x + x*y + y

# 式の比較(式は内部で自動的に最適化される
f(x, y) == x * (1 + y) + y
=> true

# 部分代入
f(a, 2) == 3*a + 2
=> true

# こんなのも
f(1, a + b) == 1 + 2 * ( a + b )
=> true

f(1, 1)
=> 3

# f(x)をxで微分
d/dx(f(x, y)) == 1 + y
=> true

g(x) <= sin(x)

d/dx(g(x)) == cos(x)
=> true

# g(x)をxで[0,π/2]上で、定積分. 
S(g(x), dx)[0, pi/2]
=> 1.0

対数や三角関数、自然対数や極限も

f(z) <= log(z)
S(f(z), dz)[0,1]
=> -Infinity

d/dx(log(x)) == 1 / x
=> true

d/dx(sin(x)) == -cos(x)
=> true

d/dx(e ^ x) == e ^ x
=> true

# 正規分布の確率密度関数
f(x) <= (1.0 / ( ( 2.0 * pi ) ^ 0.5 ) ) * ( e ^ (- (x ^ 2) / 2) )
S(f(x), dx)[-oo, oo]
=> 1.0

まるで、紙面で数式を撫でている感覚

割とこの辺りの機構はすごいのではないかと思ってます.

f(x) <= x ^ 2

f(a + b).to_s
=> "( ( a + b ) ^ 2 )"

#↓この様な機構を持ち合わせている言語は少ないのではないだろうか.
g(a, b) <= f(a + b)

g(a, b).to_s
=> "( ( a + b ) ^ 2 )"

g(2, 2)
=> 16

( d/da(g(a, b)) ).to_s
=> "( 2 * ( a + b ) )"

# 式の最適化
((x * y) + (z * x)).to_s
=> "( x * ( y + z ) )"

((x ^ y) / (x ^ z)).to_s
=> "( x ^ ( y - z ) )"

(x + x).to_s
=> "( 2 * x )"

# 関数の再代入は出来ません.
f(x) <= x
f(z) <= z + z

# なぜならば、このDSLは紙面上で数式をいじる感覚を意識しているからです.
# おなじ紙面内で同名の関数を再定義する事は滅多にないでしょう.
# このような場合(紙面上でいう次の問題に取りかかるとき)は、#resetを使いましょう

f(x) <= x
reset
f(z) <= z + z

使い方、仕様

モジュール、クラス構成

Dydx
  |- Algebra(代数)...いわゆる抽象代数
  |      |- Set(集合)...Dydx内で扱う最小の元
  |      |   |- Num
  |      |   |- ....
  |      |
  |      |- Operator(演算子)...Set間の演算の定義、式の最適化など
  |      |   |- Interface
  |      |   |- ....
  |      |
  |      |- Formula(式)...2つのSetと演算子からなる
  |      |- inverse(逆元)...Setオブジェクトの逆元を取り扱う
  |
  |- Function(関数)1つのAlgebraと変数からなる.
  |- Delta(微分作用素)
  |- Integrand(積分作用素)

使い方、仕様

gem install dydx などでgemを持ってきて頂き、

dydxrequireしてinclude Dydx をして頂ければ、

各種メソッドの定義とFixnumSymbol、などのoverrideが行われ、DSL化します.

require 'dydx'
=> true
include Dydx
=> Object

# Dydxで予約語(eやf)になっていない一文字からなる小文字英数はsymbolとして扱われる.
x
=> :x

# symbol に対して演算を行うとFormula(式)クラスのオブジェクトが出来る.
x + x
=> #<Dydx::Algebra::Formula:0x007f83a9108a18 @f=#<Dydx::Algebra::Set::Num:0x007f83a911ac68 @n=2>, @g=:x, @operator=:*>

# 各種定数
e
=> #<Dydx::Algebra::Set::E:0x007f86d0905db8>

pi
=> #<Dydx::Algebra::Set::Pi:0x007f86d0986350>

oo
=> Infinity

Formulaオブジェクト

実数やシンボル、各種定数と演算子の組み合わせからなるFormulaオブジェクト.

このオブジェクトに微分演算や四則演算が定められています.

log(z) + x
=> #<Dydx::Algebra::Formula:0x007f86d0960088 @f=#<Dydx::Algebra::Set::Log:0x007f86d0960420 @f=:z>, @g=:x, @operator=:+>

# Formulaオブジェクトの微分メソッドを呼び出す
(log(z) + x).d(z)
=> #<Dydx::Algebra::Inverse:0x007f86d0949298 @x=:z, @operator=:*>

log(z).d(z).to_s
=> "( 1 / z )"

# この微分メソッドがDSLによってラッピングされて、こう書けるのだ.
d/dz(log(z) + x)
=> #<Dydx::Algebra::Inverse:0x007f86d091f5d8 @x=:z, @operator=:*>

# こんな書き方も出来る
$y = log(z) + x
dy/dz
=> #<Dydx::Algebra::Inverse:0x007f86d0905200 @x=:z, @operator=:*>

Functionオブジェクト

Formulaオブジェクトは代数的な数式でしかない.

x が変数なのか、定数なのかはFormulaオブジェクトは考えず識別子でしかない. (ex. 1/xは引数zの関数なのかもしれない)

Formulaオブジェクトに変数(引数)の情報を加えたものを、Functionオブジェクトと定めている.

Functionオブジェクトは#fメソッドによって定義される

#fメソッドは任意の数引数をとりそれを変数(引数)とするFunctionオブジェクトをnewする.

# Functionオブジェクトの実体はグローバル変数$fに格納されており、

# 以後f(3)などの呼び出しでここを参照する.

f(x) <= log(x)
=> #<Dydx::Function:0x007f86d08cd210 @vars=[:x], @algebra=#<Dydx::Algebra::Set::Log:0x007f86d08cd080 @f=:x>>

# Function#<= はFunctionオブジェクトにFormulaオブジェクトを紐づける事を意味している.

$f
=> #<Dydx::Function:0x007f86d08cd210 @vars=[:x], @algebra=#<Dydx::Algebra::Set::Log:0x007f86d08cd080 @f=:x>>

#f に対して関数を定義した時に以外の値を渡すとその値で評価してくれます.

# f(x)はFunctionオブジェクトだが、
f(x)
=> #<Dydx::Function:0x007f86d08cd210 @vars=[:x], @algebra=#<Dydx::Algebra::Set::Log:0x007f86d08cd080 @f=:x>>


# x以外を渡すと評価して返値を得れる.
f(2)
=> 0.6931471805599453

#fと同じ作用を持つものとして、#g, #hがあります.

微分積分いい気分

現在の仕様では、微分はFormulaとFunctionに作用でき、数値微分とシンボリックな微分に対応.

積分はFunctionにのみ作用でき、数値積分のみ.

(Risch-algorithmを使えば不定積分出来そう. http://en.wikipedia.org/wiki/Risch_algorithm )

# formulaオブジェクトの微分を直接呼び出しても良いし、
log(z).d(z).to_s
=> "( 1 / z )"

# Functionに微分演算子をかませてもいい
f(z) <= log(z)
d/dz(f(z))


# 積分は#Sメソッドに、Functionオブジェクト、積分をする引数(今は1つ)を指定し、
# [a,b]で積分を範囲を指定すれば良い
S(f(z), dz)[0,1]
=> -Infinity

# 数値積分はシンプソンの公式で近似しており、その精度n(分割幅)は[a, b, n] の用に指定可能

 S(f(z), dz)[1,2,200]
=> 0.38629436111951104

デフォルトは100(本当は積分範囲に応じて、最適化しないといけない..)

ふぅ. とりあえずこんなもんです.

もし暇なら使ってみてください!これからもガンガンアップデートしていきます!!!!!!!!!