# Jordan Canonical Forms
#
#   by Shin-ichiro Hara
#
# Version 1.00 (2001.11.01)
require "algebra/matrix-algebra"

module Algebra
  class MatrixAlgebra
    def jordan_form
      Algebra::JordanForm.decompose(self).first
    end
    def jordan_form_info
      Algebra::JordanForm.decompose(self)
    end
  end

  class JordanForm
    def initialize(body)
      @body = body
    end

    def to_matrix(ring = Integer)
      mat = nil
      @body.each do |x, n|
	if mat
	  mat = mat.dsum(jordan_block_u(ring, x, n))
	else
	  mat = jordan_block_u(ring, x, n)
	end
      end
      mat
    end

    alias to_matrix_u to_matrix

    def to_matrix_l
      to_matrix_u.transpose
    end

    def jordan_block_u(ring, x, n)
      a = Algebra.SquareMatrix(ring, n)
      a.matrix{|i, j|
	if i == j
	  x
	elsif i == j-1
	  ring.unity
	else
	  ring.zero
	end
      }
    end

    def jordan_block_l(ring, x, n)
      jordan_block_u(ring, x, n).transpose
    end

    def self.construct(elem_divs, facts, field, pfield)
      a = []
      elem_divs.each do |e|
	facts.each do |f, n|
	  f = f.convert_to(pfield)
	  g = e.convert_to(pfield)
	  raise "insufficient decomposition" unless f.deg == 1
	  k = 0
	  while true
	    q, r = g.divmod f
	    break unless r.zero?
	    g = q
	    k += 1
	  end
	  a.push [-f[0], k] if k > 0
	end
      end
      Algebra::JordanForm.new(a).to_matrix(field)
    end
    
    def self.decompose(m)
      base_ring = m.ground
      base_pring = Algebra.Polynomial(base_ring, "x")
      base_alg = Algebra.SquareMatrix(base_pring, m.size)

      me = m._char_matrix(base_alg).to_quint.e_diagonalize
      elem_divs = me.body.diag
      
      facts = Algebra::ElementaryDivisor.factorize(elem_divs)
      dl = facts.last
      field, modulus, facts, roots, elms = dl.pi.decompose(dl)
      
      pfield = Algebra.Polynomial(field, "x")
      pfm_alg = Algebra.SquareMatrix(pfield, m.size)
      
      jm = Algebra::JordanForm.construct(elem_divs, facts, field, pfield) 
      jme = jm._char_matrix(pfm_alg).to_quint.e_diagonalize
      
      mebody = pfm_alg.convert_from(me.body)
      meright = pfm_alg.convert_from(me.right)
      meleft = pfm_alg.convert_from(me.left)
      
      sR = (meright * jme.righti).i2o.evaluateR(jm)
      tL = (jme.lefti * meleft).i2o.evaluateL(jm)
      
      [jm, tL, sR, field, modulus]
    end
  end
end

if $0 == __FILE__
  j = Algebra::JordanForm.new([[2, 3], [-1, 2]])
  j.to_matrix.display
end
