# Linear Algebra
#
#   by Shin-ichiro Hara
#
# Version 1.00 (2001.11.01)

require "algebra/matrix-algebra"
require "algebra/gaussian-elimination"
require "algebra/polynomial"
require "algebra/polynomial-factor"
require "algebra/algebraic-equation"
require "algebra/jordan-form"

module Algebra

module Orthogonalization
  def orthogonalize
    orth_basis = []
    vectors.each_with_index do |b, i|
      orth_basis.push b
      if i > 0
	orth_basis[0...i].each do |f|
	  orth_basis[i] -= b.inner_product(f) / f.norm2 * f
#	  orth_basis[i] = f.norm2*orth_basis[i] - b.inner_product(f)*f #also
	end
      end
    end
    self.class.collect_column{|j| orth_basis[j]}
  end
end

class MatrixAlgebra
  include Orthogonalization
  auto_req_init
  auto_req :e_diagonalize, "algebra/elementary-divisor"
  auto_req :elementary_divisor, "algebra/elementary-divisor"
end

class SquareMatrix < MatrixAlgebra
  def self.symmetric(*a)
    k = size*(size+1)/2
    raise "the size of #{a.inspect} must be <= #{k}" if a.size > k
    matrix{ |i, j|
      d = (i - j).abs
      a[(i < j ? i : j) + (2*size+1-d)*d/2 ] || ground.zero
    }
  end

  EigenSystem = Struct.new("EigenSystem",
                           :extfield,
                           :roots,
                           :tmatrix,
                           :evalues,
                           :addelms,
                           :evectors,
                           :espaces,
                           :chpoly,
                           :facts)

  def diagonalize
    chp = char_polynomial(Algebra.Polynomial(ground, "t"))

    facts = chp.factorize
    mdf, mods, fas, roots, elms = chp.decompose(facts)
    nu = Algebra.SquareMatrix(mdf, size)

    espaces = {}
    evalues = []
    evectors = []
    proots = []
    k = 0
    facts.each do |f, n|
      dim = f.deg
      if dim <= 0 # 0 might be occurred
	next
      elsif dim == 1
	evs =  [-f[0]/f[1]]
      else
	evs = roots[k, dim]#.reverse
	proots.push [f, evs]
      end
      k += dim

      evs.each do |evalue|
	ks = nu.const(evalue) - self
	kb = ks.kernel_basis
	espaces[evalue] = kb

	kb.each do |v|
	  evalues.push evalue
	  evectors.push v
	end
      end
    end

    raise "can't Diagonalize" if evalues.size < size
    ttype = Algebra.SquareMatrix(mdf, size)
    r = ttype.collect_column{|i| evectors[i]}
#    :extfield, :roots, :tmatrix, :evalues, :addelms, :evectors, 
#                   :espaces, :chpoly, :facts
    e = EigenSystem.new(mdf, proots, r, evalues, elms, evectors,
                    espaces, chp, facts)
    def e.to_ary; to_a; end
    e
  end

  def solve_eigen_value_problem
    puts "A = "; display; puts
    
    e = diagonalize
    puts "Charactoristic Poly.: #{e.chpoly} => #{e.facts}"; puts
    puts "Algebraic Numbers:"
    e.roots.each do |po, rs|
      puts "#{rs.join(', ')} : roots of #{po} == 0"
    end; puts
    
    puts "EigenSpaces: "
    e.evalues.uniq.each do |ev|
      puts "W_{#{ev}} = <#{e.espaces[ev].join(', ')}>"
    end; puts
    
    puts "Trans. Matrix:"
    puts "P ="
    e.tmatrix.display; puts
    
    puts "P^-1 * A * P = "; (e.tmatrix.inverse * self * e.tmatrix).display; puts
  end
end
end

if __FILE__ == $0
end
