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

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

module Algebra
module InnerProductSpace
  def inner_product(other)
    ip = defined?(ground) ? ground.zero : first.zero
    each_with_index do |x, i|
      ip += x * other[i]
    end
    ip
  end
  
  def inner_product_complex(other)
    inner_product(other.conjugate)
  end

  def norm2
    inner_product(self)
  end
end

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
    type.collect_column{|j| orth_basis[j]}
  end
end

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

class Vector < MatrixAlgebra
  include InnerProductSpace
end

class CoVector < MatrixAlgebra
  include InnerProductSpace
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
  
  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
    s = Algebra.SquareMatrix(mdf, size)
    r = s.collect_column{|i| evectors[i]}
    
    [mdf, proots, r, evalues, elms, evectors, espaces, chp, facts]
  end
end
end

if __FILE__ == $0
end
