原です。

ちょっと間が空いてしまいました。すいません。Integer成分の
determinantの話の続きです。

In [ruby-dev:27700]
>けいじゅ@いしつかです.

>>でも、quoを使った方が現状よりは(クレームの頻度の点で)かなりまし
>>じゃないですかね。実際にdetを計算したいのはFloatの行列が多いらしい
>>し。
>
>ましなのは理解しますが, Rationalをrequireするよりましな理由がわかりま
>せんが?

私が誤解しているのか、どうも石塚さんの話がぴんとこないのです
が、、、私は、現行ではRationalをrequireするだけでなく、更に
各成分をRational化してdetに放り込まなくてはならないのが問題
だと思うのです。

>det計算中Rationalをrequireする場合,
>すべてがIntegerの場合, 結果は必ずInitegerになり,
>行列中1要素でもFloatが入っている場合, 結果は必ずFloatになり, 
>Rationalになることはありません.

今回問題になっているのは、整数行列の行列式はどうあつかうべき
か、ですよね?

>ですので, (内部の計算がどうなっているかは別として), それぞれの
>クラスに応じた自然な結果が出ることが必ず保証できます.

細かい話ですが、整数行列の成分をRational(x, 1)で全て有理数
化してdetを計算させると、結果はRationalですよね。mathn.rbを
導入するとIntegerですけど。

>あと, パフォーマンスのことを気にするなら, 最初っから要素は
>Floatにすべきだと思います.

私がquoを使った方がいいと思うのは、整数行列のdetを計算したと
きとんでもない値が出るのを防げるからという理由で、ただその一
点に尽きます。

それに対して石塚さんはrational.rbをrequireして各成分、
Rational()でラップしてくれ、と主張しているわけですよね。
私はそれは面倒すぎるのではないかと、そして今後も忘れて
意図しない結果に悩む人が大勢出るのではないかと思います。
それならちょっとかっこ悪くても「quoでFloat」でいいのでは
ないでしょうか。

現行をquo式にしたメリットデメリットまとめると、

--------------------------------------------------------------
成分               | 現行               | quo式               
-------------------+--------------------+---------------------
全てInteger        | NG                 | 戻値:Float          
                   |                    | 誤差が気になることも
-------------------+--------------------+---------------------
全てInteger        | NG                 | 戻値:Rational       
rationa.rb導入のみ |                    | 遅い                
-------------------+--------------------+---------------------
全てRational       | 戻値:Rational      | 戻値:Rational       
                   | 遅さは気にならない | 遅さは気にならない  
-------------------+--------------------+---------------------
全てFloat          | 戻値:Float         | 戻値:Float          
                   | 誤差は気にならない | 誤差は気にならない  
-------------------+--------------------+---------------------

となります。「遅さは気にならない」とは、Rational計算である
のが明白なので遅いことは覚悟が出来ている、、という意味です。
:-) 
quo式は、rational.rbをrequireしてるか否かで結果が違うのが確
かに気持ちが悪いのですが、それ以上にとにかく現行の"NG"は良く
ないのではないか。

quo式で困るとしたら、rational.rbをrequireしている状態で、整
数行列のdetをFloatで高速に欲しい人(いるのか?)ですが、その
人には各成分Float()してからquo式detを計算してもらうことにし
ます。

どうでしょう。


更にもう一つ提案なんですが、整数行列のdetの計算には、互除法
を使ったそこそこ速いのがあります:

  def determinant_e
    size = row_size - 1
    a = to_a

    det = 1
    0.upto(size) do |k|
      (k + 1).upto(size) do |i|
        until a[i][k].zero?
          q = a[k][k] / a[i][k]
          k.upto(size) do |j|
            a[k][j] -= a[i][j] * q
          end
          a[k], a[i] = a[i], a[k]
          det *= -1
        end
      end
      if a[k][k].zero?
        return 0
      else
        det *= a[k][k]
      end
    end
    det
  end

これは、整数行列をRatinal()してから、現行detを計算するより
かなり速いです。次は15次行列の整数成分の行列式を5回計算させ
たのにかかった時間です。

determinant         : 4.278sec.
determinant_e       : 0.329sec.

上段が各成分をRational()して現行のdetによる計算です。
拡張ライブラリのrational.soを使っても1.4秒でした。

このdeterminant_eの面白いのは、IntegerとRationalの混合した
成分を持つ行列でも正しく動作する所です。まとめると、

------------------------------------------------------------
成分               | quo式                | determinant_e
-------------------+----------------------+-----------------
全てInteger        | 戻値:Float           | 戻値:Integer    
                   | 誤差が気になることも | やや遅い        
-------------------+----------------------+-----------------
全てInteger        | 戻値:Rational        | 戻値:Integer    
rationa.rb導入のみ | 遅い                 | やや遅い        
-------------------+----------------------+-----------------
全てRational       | 戻値:Rational        | 戻値:Rational   
                   | 気にならない遅さ     | 気にならない遅さ
-------------------+----------------------+-----------------
全てFloat          | 戻値:Float           | NG              
                   | 気にならない誤差     |                 
-------------------+----------------------+-----------------

そこで提案なんですが、Matrix のdetはquo式にし、と同時に
determinant_e(det_iという名前もいいですが)も定義しておいて、

  【整数行列を扱う場合の注意】
    整数行列の行列式を計算させるときは、Matrix#det_iを使いましょ
    う。Matrix#detは、rational.rbをrequireしていない状態でFloat、
    requireしている状態でRationalを返します。

と、マニュアルに記述するのです。

どうでしょう?

ちなみに、determinant_eは[ruby-math:625]のコードをシンプルに
したものです。他にも幾つかバリエーションを試してみたのですが、
結局[ruby-math:625]のコードが速いのでこちらの方がいいかもし
れません。

テストスクリプトを載せます。

#!/usr/local/bin/ruby
require "matrix"
require "rational"

class Matrix
  def determinant_e
    size = row_size - 1
    a = to_a

    det = 1
    0.upto(size) do |k|
      (k + 1).upto(size) do |i|
        until a[i][k].zero?
          q = a[k][k] / a[i][k]
          k.upto(size) do |j|
            a[k][j] -= a[i][j] * q
          end
          a[k], a[i] = a[i], a[k]
          det *= -1
        end
      end
      if a[k][k].zero?
        return 0
      else
        det *= a[k][k]
      end
    end
    det
  end

  alias det_e determinant_e

  #[ruby-math:625]
  def determinant_625
    return 0 unless square?
    
    size = row_size - 1
    a = to_a
    
    det = 1
    k = 0
    begin 
      if a[k][k].zero?
        i = k
        begin
          return 0 if (i += 1) > size
        end while a[i][k].zero?
        a[i], a[k] = a[k], a[i]
        det *= -1
      end
      (k + 1).upto(size) do |i|
        q = a[i][k] / a[k][k]
        k.upto(size) do |j|
          a[i][j] -= a[k][j] * q
        end
        unless a[i][k].zero?
          a[i], a[k] = a[k], a[i]
          det *= -1
          redo
        end
      end
      det *= a[k][k]
    end while (k += 1) <= size
    det
  end
end

class Test
  attr_reader :value
  def initialize(name)
    @name = name
    @value = nil
    @time = 0.0
  end

  def stime(v = nil)
    t = Time.now
    @value = yield
    @time += Time.now - t
    if v && v != @value
      puts "Test Failed: #{@value} != #{v}"
      exit
    end
  end

  def to_s
    sprintf("%-30s: %3.3fsec.", @name, @time)
  end
end

def test(dim, num, range = 100)
  tr = Test.new("determinant(rational)")
  te = Test.new("determinant_e(int)")
  t1 = Test.new("determinant_e(int & rational)")
  tm = Test.new("determinant_625(int)")
  tf = Test.new("determinant(float)")

  num.times do
    a = (0...dim).map{(0...dim).map{rand(2*range+1)-range}}
    m = Matrix[*a]
    mr = Matrix[*a.map{|b| b.map{|c| Rational(c, 1)}}]
    m1 = Matrix[*a.map{|b| b.map{|c|
          rand(2).zero? ? Rational(c, 1) : c}}]
    mf = Matrix[*a.map{|b| b.map{|c| Float(c)}}]

        
    tr.stime{Matrix[*mr].det}
    te.stime(tr.value){Matrix[*m].determinant_e}
    t1.stime(tr.value){Matrix[*m1].determinant_e}
    tm.stime(tr.value){Matrix[*m].determinant_625}
    tf.stime(){Matrix[*mf].determinant}
  end

  puts "--- #{dim}-dim, #{num}times ---"
  puts tr, te, t1, tm, tf
  puts
end

test(5, 500)
test(10, 20)
test(15, 5)
test(20, 2)


以下、テストの結果:

--- 5-dim, 500times ---
determinant(rational)         : 4.503sec.
determinant(rational)         : 0.647sec. (with rational.so)
determinant_e(int)            : 1.158sec.
determinant_e(int & rational) : 7.313sec.
determinant_625(int)          : 0.703sec.
determinant(float)            : 0.193sec.

--- 10-dim, 20times ---
determinant(rational)         : 3.058sec.
determinant(rational)         : 0.786sec. (with rational.so)
determinant_e(int)            : 0.421sec.
determinant_e(int & rational) : 4.223sec.
determinant_625(int)          : 0.251sec.
determinant(float)            : 0.077sec.

--- 15-dim, 5times ---
determinant(rational)         : 4.278sec.
determinant(rational)         : 1.390sec. (with rational.so)
determinant_e(int)            : 0.329sec.
determinant_e(int & rational) : 5.347sec.
determinant_625(int)          : 0.238sec.
determinant(float)            : 0.027sec.

--- 20-dim, 2times ---
determinant(rational)         : 5.384sec.
determinant(rational)         : 2.208sec. (with rational.so)
determinant_e(int)            : 0.884sec.
determinant_e(int & rational) : 6.511sec.
determinant_625(int)          : 0.766sec.
determinant(float)            : 0.024sec.