離散フーリエ変換でRuby練習

◆Ruby入門

 今回はスクリプト系のオブジェクト指向言語であるRubyに入門してみたいと思います。この言語は、まつもとゆきひろ氏によって開発された国産言語として世界的に普及しています。

 ちょっとだけエンジニアらしく離散フーリエ変換とRubyを同時に味わってみたいと思います。

◆離散フーリエ変換って?

 離散フーリエ変換は以下のように定義されます。

 equ.1

 equ.2

 これをプログラムにするわけですが、今回はRubyの練習ですからプログラムのスピードなどはあんまり考慮しないで、なるべく式に忠実な実装を目指します。

 equ.1が離散フーリエ変換(DFT:Discrete Fourier Transform)と呼ばれ、f(n)が時間の関数でF(k)が周波数の関数になります。equ.1で時間から周波数に変換されます。equ.2は離散逆フーリエ変換(IDFT:Inverse Discrete Fourier Transform)と呼ばれ、DFTの逆演算になります。

◆プログラムにするには・・・

 このままではプログラムにできませんので、オイラーの公式を使って式を変換します。

 equ.3

 equ.3がオイラーの公式です。自然対数の底eが三角関数に変換できる不思議公式ですね。それから、f(n)とF(k)も複素数の関数ですから以下のようにばらして記述します。

 equ.4

 もうひとつ、筆者は電気人間なので虚数は i ではなくを j 使っています。equ.3とequ.4を使ってequ.1を変形します。

 equ.5

 同様にequ.2を変形します。

 equ.6

◆Rubyでコーディング

 オブジェクト指向言語なのでクラスを使った実装にします。まずは複素数クラスを定義します。

class Complex
  attr_accessor :re
  attr_accessor :im

  def initialize(re,im)
    @re=re
    @im=im
  end
	
  def +(a)
    Complex.new(@re+a.re,@im+a.im)
  end
	
  def *(a)
    if a.is_a? Complex
      return Complex.new(@re*a.re-@im*a.im,@re*a.im+@im*a.re)
    else
      return Complex.new(@re*a,@im*a)
    end
  end	
end

 クラスの属性は、実数と虚数の2つです。+と*の演算子を定義します。Rubyは変数を使うためにプロトタイプを宣言しなくても良い言語です。*演算子の定義をご覧ください。is_a?を使って変数の型をチェックしてから戻り値の計算を決定しています。こんな場合、C++やJavaではメソッドをオーバーロードするところですが、Rubyの場合そうはいきません。ちょっと気を使うところでもあります。

◆変換逆変換は似ている

 いよいよDFT,IDFTをコーディングする訳ですが、equ.5とequ.6は非常に似ていると思いませんか?先頭の1/Nの有無と虚数の符号が異なるだけなんです。だから、1つのメソッドで済ませちゃうことにします。

 フーリエ変換の対象は音のように時間的に変化するデータです。ここでは先ほど定義した複素数の列をクラス化しdftメソッドを定義します。dftメソッドの引数に正逆のフラグを渡すことで変換と逆変換を切り替えることにします。

class ComplexVector
  attr_accessor :elem
	
  def initialize(vals)
    @elem=[]
    vals.each{|e|
      @elem.push( Complex.new(e,0) )
    }
  end
	
  def dft(inv)
    ret=ComplexVector.new([])
    n=@elem.size
    a=Math::PI*2.0/n
    a=-a if inv 
    n.times{|i|
      ret.elem[i]=Complex.new(0,0)
      n.times{|j|
        ret.elem[i] = ret.elem[i] + 
          ( @elem[j] * Complex.new(Math.cos(a*i*j),-Math.sin(a*i*j)) )
      }
      ret.elem[i] = ret.elem[i] * (1.0/n) if inv
    }
    return ret
  end
end

 コンストラクタをご覧ください。ここでは実数の配列を引数で受け取り、複素数の配列であるelem属性に代入する処理を行っています。Rubyは配列とその要素にアクセスするためのループ表現が大変工夫された言語です。eachやtimesを使ってスマートなコーディングが可能です。

 オブジェクトを使うことによりプログラムが元の式からかけ離れないようにコーディングしました。equ.5とequ.6と上のコードを見比べてみてください。コードを読む時に一点注意する部分があります。equ.6で虚数(sin)の符号をマイナスにする処理が a=-a if inv となっています。三角関数の性質を利用した処理になっています。

◆実行してみる

 では実行してみましょう。実行のコードは以下の通りです。

s=ComplexVector.new([0,0,1,1,1,1,0,0])

f=s.dft(false)
s2=f.dft(true)

s.elem.each{|cmp|
  puts sprintf("%.5f %.5f",cmp.re,cmp.im)
}
puts
f.elem.each{|cmp|
  puts sprintf("%.5f %.5f",cmp.re,cmp.im)
}
puts
s2.elem.each{|cmp|
  puts sprintf("%.5f %.5f",cmp.re,cmp.im)
}

 0,0,1,1,1,1,0,0 のデータをDFTしてIDFTして元に戻ることを確認します。結果はこんな感じです。

0.00000 0.00000
0.00000 0.00000
1.00000 0.00000
1.00000 0.00000
1.00000 0.00000
1.00000 0.00000
0.00000 0.00000
0.00000 0.00000

4.00000 0.00000
-2.41421 -1.00000
0.00000 0.00000
0.41421 1.00000
0.00000 -0.00000
0.41421 -1.00000
-0.00000 0.00000
-2.41421 1.00000

-0.00000 -0.00000
0.00000 0.00000
1.00000 -0.00000
1.00000 0.00000
1.00000 -0.00000
1.00000 0.00000
0.00000 0.00000
0.00000 0.00000

◆最後に

 Rubyは楽しくコーディングするための工夫がふんだんに盛り込まれた大変強力な言語だと思います。スクリプト言語でありながら本格的なオブジェクト指向プログラミングができることや、ライブラリが充実していることに驚かされますね。

 

 

 


Latest update at 2007/10/08