ruby-fftw3 と fftw3.gem のインターフェースの違いに注意

FFT(Fast Fourier Transform) をするための FFTW3 というライブラリを Ruby から利用するための ruby-fftw3 というライブラリがあります。少し仕事で FFT を使って実験するためにこのライブラリを利用してみてはまったので書き残しておきます。

まず ruby-fftw3 は http://ruby.gfd-dennou.org/products/ruby-fftw3/ で公開されています。作者は Takeshi Horinouchi さんと GFD電脳倶楽部。ここで公開されているのは .tar.gz 形式のパッケージで自分でビルドしてインストールするものです。

gem 化されていないのかなと探してみると fftw3 という gem パッケージがありました。これは Evan Weaver さんが gem 化したもので、githubリポジトリはなぜか別の人が持ってるみたいです ( https://github.com/fauna/fftw3 )。けど homepage は Not Found...*1

そこで fftw3.gem をインストールして、こちらのマニュアルを参照して実験していたところ、2次元FFTを試しているとどうも結果がおかしいのです。どうもY方向のフーリエ変換しか実行されていないようになります。

そこで gem パッケージの中身をみてみるとどうも ruby-fftw3 のパッケージから変更されている点がありました。

  1. NumRu::FFTW3 というクラス名(NumRu モジュールにネストしている)から FFTW3 に変更。これはすぐわかったのでマニュアルが古いだけかと思って対応してました。
  2. NumRu::FFTW3.fft は FFTW3.fft_raw というメソッド名に変更されていて、FFTW3.fft と FFTW3.ifft というメソッドは fft_raw のラッパになっている

元々の fft メソッドは 第2引数に変換方向(1: 逆フーリエ変換、 -1: フーリエ変換typo じゃないです。1 が逆向き)を指定することになっているのですが、これを FTTW3.fft(ary) は raw_ffw(ary, -1), FFTW3.ifft(ary) は raw_fft(ary, 1) のように変換方向の指定を追加して呼ぶようにしていました。

フーリエ変換と逆フーリエ変換は係数が違うだけで、元々の FFTW3 の関数も同じ引数を受け取るのでそれを反映したのでしょうけど、1 or -1 を渡すのもいまいちだし -1 が正方向で 1 が逆方向とかトラップすぎます。なので fftw3.gem のラッパ定義は気持ちはよくわかるのですが、同じメソッド名にしてしまったことと、誤った使い方をしてもエラーにならない(fft は第3引数以降にフーリエ変換する次数を指定できます)ためにはまりやすくなっています。

na = NArray.float(126, 126)
...
fc = FFTW3.fft(na, -1)  # => FFTW3.fft_raw(na, -1, -1) になっている
                        #    次数の -1 は最後から1番目の次数の指定なので
                        #    ここでは2次元の NArray なので 1 の指定と同じ
nc = FFTW3.fft(fc, 1)   # => FFTW3.fft_raw(fc, -1, 1) になる

fftw3.gem を使っている場合は正しくは以下のようにしないといけません。

fc = FFTW3.fft(na)      # 全次元でフーリエ変換
nc = FFTW3.ifft(na)     # 全次元でフーリエ逆変換

まあそもそもクラスの名前空間が別なのでそこで気がつくべきだったんですが、せっかく同じライブラリを gem 化したものなのに使い方が違うのはちょっと残念ですね。 ruby-fftw3 の作者に連絡してみようかと思ったのですがメールアドレスがわからず。

ちなみに本家 ruby-fftw3 は今年の 4月に version 0.4 をリリースしています。変更は LICENSE.txt と require "narray" が不要になったという点だけで、fftw3.gem は 0.3 で既に上記のラッパを定義しているところで require "narray" しているので実用上は古くなっているところはありません。

*1:この gem 化は本家は感知しているんでしょうか?