# Gussian Elimination Algorism
#
#   by Shin-ichiro Hara
#
# Version 1.1 (2001.09.12)

module Algebra
module GaussianElimination
#ElementaryOpeartion
  def sswap_r!(i, j)
    a, b = row!(i), row!(j)
    set_row(i, b).set_row(j, a)
  end

  def swap_r!(i, j)
    a, b = row(i), row(j)
    set_row(i, b).set_row(j, a)
  end

  def swap_r(i, j)
    dup.swap_r!(i, j)
  end

  def swap_c!(i, j)
    a, b = column(i), column(j)
    set_column(i, b).set_column(j, a)
  end

  def swap_c(i, j)
    dup.swap_c!(i, j)
  end

  def multiply_r!(i, c)
    set_row(i, row(i).collect{|x| x * c})
  end

  def multiply_r(i, c)
    dup.multiply_r!(i, c)
  end

  def multiply_c!(j, c)
    set_column(j, column(j).collect{|x| x * c})
  end

  def multiply_c(j, c)
    dup.multiply_c!(j, c)
  end

  def divide_r!(i, c)
    set_row(i, row(i).collect{|x| x / c})
  end

  def divide_r(i, c)
    dup.divide_r!(i, c)
  end

  def divide_c!(j, c)
    set_column(j, column(j).collect{|x| x / c})
  end

  def divide_c(j, c)
    dup.divide_c!(j, c)
  end

  def mix_r!(i, j, c = nil)
    row_i = row(i)
    row_j = row(j)
    set_row(i, (0...csize).collect{|k|
	      row_i[k] + (c ? row_j[k]*c : row_j[k])}
	    )
  end

  def mix_r(i, j, c = nil)
    dup.mix_r!(i, j, c)
  end

  def mix_c!(i, j, c = nil)
    column_i = column(i)
    column_j = column(j)
    set_column(i, (0...rsize).collect{|k|
		 column_i[k] + (c ? column_j[k]*c : column_j[k])}
	       )
  end

  def mix_c(i, j, c = nil)
    dup.mix_c!(i, j, c)
  end

  def left_eliminate!
    inv = Algebra.SquareMatrix(ground, rsize).unity
    k = ground.unity
    pi = 0
    each_j do |j|
      if i = (pi...rsize).find{|i| !self[i, j].zero?}
#	  pivots.push j
	if i != pi
	  swap_r!(pi, i); inv.swap_r!(pi, i)
	  k = -k
	end
	c = ground.unity / self[pi, j] # this lets the entries be in ground
	multiply_r!(pi, c); inv.multiply_r!(pi, c)
	k = k * c
	each_i do |i0|
	  next if i0 == pi
	  d = self[i0, j]# / self[pi, j]
	  mix_r!(i0, pi, -d); inv.mix_r!(i0, pi, -d)
	end
	pi += 1
      end
    end
    [inv, k]
  end

  def _normalize!(pi, j, inv)
    c = ground.unity
    e = self[pi, j]
    if !e.unity? && e.unit?
      c /= e
      multiply_r!(pi, c); inv.multiply_r!(pi, c)
    end
    c
  end
  private :_normalize!

  def left_eliminate_euclidian!
    inv = Algebra.SquareMatrix(ground, rsize).unity
    k = ground.unity
    pi = 0
    each_j do |j|
      if i = (pi...rsize).find{|i| !self[i, j].zero?}
#	  pivots.push j
	if i != pi
	  swap_r!(pi, i); inv.swap_r!(pi, i)
	  k = -k
	end
	k *= _normalize!(pi, j, inv)
	(pi+1...rsize).each do |i0|
#	each_i do |i0|;  next if i0 == pi
	  d = self[i0, j] / self[pi, j]
	  mix_r!(i0, pi, -d); inv.mix_r!(i0, pi, -d)
	  if !self[i0, j].zero? && i0 > pi
	    swap_r!(i0, pi); inv.swap_r!(i0, pi);
	    k = -k
	    k *= _normalize!(pi, j, inv)
	    redo
	  end
	end
	
	pi += 1
      end
    end
    [inv, k]
  end

  def left_inverse
    dup.left_eliminate![0]
  end

  def left_sweep
    m = dup
    m.left_eliminate!
    m
  end

  def step_matrix?
    pivots = []
    i = j = 0
    each_j do |j|
      if !self[i, j].zero?
	0.upto rsize - 1 do |i0|
	  return nil if i0 != i && !self[i0, j].zero?
	end
	pivots << j
	return pivots if (i += 1) >= rsize
      else
	(i+1).upto rsize - 1 do |i0|
	  return nil if i0 != i && !self[i0, j].zero?
	end
      end
    end
    pivots
  end

  def kernel_basis
    if pivots =  step_matrix?
      m = self
    else
      m = left_sweep
      pivots = m.step_matrix?
    end
    basis = []

    v = Algebra.Vector(ground, csize)
    (0...csize).each do |i|
      unless pivots.include? i
	base = v.vector{|j|
	  if j == i
	    ground.unity
	  elsif k = pivots.index(j)
	    -m[k, i]
	  else
	    ground.zero
	  end
	}
	basis.push base
      end
    end
    basis
  end
end

class MatrixAlgebra
  include GaussianElimination
end

class SquareMatrix < MatrixAlgebra
  def inverse; left_inverse; end
  def inverse_euclidian; left_inverse_euclidian; end

  def determinant_by_elimination_old
    m = dup
    inv, k = m.left_eliminate!
    s = ground.unity
    (0...size).each do |i|
      s *= m[i, i]
    end
    s / k
  end

  def determinant_by_elimination_euclidian_old
    m = dup
    inv, k = m.left_eliminate_euclidian!
    s = ground.unity
    (0...size).each do |i|
      s *= m[i, i]
    end
    s / k
  end
  
  #ground ring must be a field
  def determinant_by_elimination
    m = dup
    det = ground.unity
    each_j do |d|
      if i = (d...size).find{|i| !m[i, d].zero?}
	if i != d
	  m.sswap_r!(d, i)
	  det = -det
	end
	c = m[d, d]
	det *= c
	(d+1...size).each do |i0|
	  r = m.row!(i0)
	  q = ground.unity * m[i0, d] / c #this lets the entries be in ground
	  (d+1...size).each do |j0|
	    r[j0] -= q * m[d, j0]
	  end
	end
      else
	return ground.zero
      end
    end
    det
  end
  
  #ground ring must be an euclidian domain
  def determinant_by_elimination_euclidian#_new
    m = dup
    det = ground.unity
    each_j do |d|
      if i = (d...size).find{|i| !m[i, d].zero?}
	if i != d
	  m.sswap_r!(d, i)
	  det = -det
	end
	(d+1...size).each do |i0|
	  r = m.row!(i0)
	  q = m[i0, d] / m[d, d]
	  (d...size).each do |j0|; r[j0] -=  q * m[d, j0]; end
	  unless m[i0, d].zero?
	    m.sswap_r!(d, i0)
	    det = -det
	    redo
	  end
	end
	det *= m[d, d]
      else
	return ground.zero
      end
    end
    det
  end
  
  def determinant
    if ground.field?
      determinant_by_elimination
    elsif ground.euclidian?
      determinant_by_elimination_euclidian
    else
      determinant!
    end
  end

end
end

if $0 == __FILE__
  require "m-polynomial"
  require "polynomial"
  require "rational"
  require "matrix-algebra"
  class Rational# < Numeric
    def inspect; to_s; end
  end
#  include Algebra

  G = Algebra.MatrixAlgebra(Rational, 2, 2)
  a = G[[0, 0], [1, -1]]
  p a
  p a.kernel_basis

#  R = MPolynomial(Integer)
  R = Algebra.MPolynomial(Rational)
  a, b, c, d, e, f, g, h, i, j, k, l, x, y, z = R.vars("abcdefghijklxyz")
  M3 = Algebra.MatrixAlgebra(R, 3, 4)
  m = M3[
    [a, b, c, d],
    [e, f, g, h],
    [i, j, k, l]
  ]
  m.display
  m.swap_r(0, 2).display
#  m.set_column(1, [1,2,3]).display
#  puts
  m.swap_c(0, 2).display
  m.multiply_r(2, x).display
  m.multiply_c(0, x).display
  m.mix_r(0, 2, x).display
  m.mix_c(0, 2, x).display

  u, z = R.unity, R.zero
  require "residue-class-ring"
  F = Algebra.ResidueClassRing(Integer, 2)
#  M = SquareMatrix(Rational, 8)
  M = Algebra.SquareMatrix(F, 8)
  m = M[
    "10001101".scan(/./).collect{|x|x.to_i},
    "00000011".scan(/./).collect{|x|x.to_i},
    "01001011".scan(/./).collect{|x|x.to_i},
    "00001111".scan(/./).collect{|x|x.to_i},
    "00101011".scan(/./).collect{|x|x.to_i},
    "00001001".scan(/./).collect{|x|x.to_i},
    "00011011".scan(/./).collect{|x|x.to_i},
    "00000111".scan(/./).collect{|x|x.to_i}
  ]
  m = m - m.unity
  puts "m = "
  m.display
  m.kernel_basis.each do |b|
    puts b.type
    puts b
    #[1, 0, 0, 0, 0, 0, 0, 0]
    #[0, 1, 1, 1, 1, 1, 1, 0]
    #[0, 1, 1, 0, 1, 0, 0, 1]
  end
end
