# MatrixAlgebra over Ring
#
#   by Shin-ichiro Hara
#
# Version 1.05 (2003.06.24)

require "algebra/algebraic-system"
require "algebra/gaussian-elimination"

module Enumerable
  def collecti
    a = []
    each_with_index do |x, i|
      a.push yield(x, i)
    end
    a
  end

  def sum(z)
    sum = z
    each do |i|
      sum += yield(i)
    end
    sum
  end
end

module Algebra
def MatrixAlgebra(ground, rsize, csize)
  MatrixAlgebra.create(ground, rsize, csize)
end

def SquareMatrix(ground, size)
  SquareMatrix.create(ground, size)
end

def Vector(ground, size)
  Vector.create(ground, size)
end

def Covector(ground, size)
  Covector.create(ground, size)
end
module_function :MatrixAlgebra, :SquareMatrix, :Vector, :Covector

class MatrixAlgebra
  extend AlgebraCreator
  include AlgebraBase
  include Enumerable
  auto_req_init
  auto_req :solve_eigen_value_problem, "algebra/linear-algebra"
  auto_req :diagonalize, "algebra/linear-algebra"
  auto_req :to_triplet, "algebra/matrix-algebra-triplet"
  auto_req :to_quint, "algebra/matrix-algebra-triplet"
  auto_req :e_inverse, "algebra/elementary-divisor"
  auto_req :elementary_divisor, "algebra/elementary-divisor"
  auto_req :e_diagonalize, "algebra/elementary-divisor"
  auto_req :e_diagonalize!, "algebra/elementary-divisor"
  autoload :JordanForm, "algebra/jordan-form"
  auto_req :jordan_form, "algebra/jordan-form"
  auto_req :jordan_form_info, "algebra/jordan-form"

  Matrices = {}

  def initialize(array, conv = false)
    raise "rsize error (#{array.size} for #{rsize})" if array.size != rsize
    array.each do |v|
      raise "csize error (#{v.size} for #{csize})" if v.size != csize
    end
    if conv
      @bone = array.collect{|cv| cv.collect{|x| 
          ground.regulate(x) or
            raise "initialize: unknown type #{x.class} (#{x})"
        }}
    else
      @bone = array
    end
  end

  def dup
#    matrix{|x| self[* x.collect{|y| y.dup}]}
#    matrix{|x| self[* x.collect{|y| y}]} #in 1.8.0, Integer can't be dup..
    matrix{|i, j| self[i, j]} #in 1.8.0, Integer can't be dup..
  end

  def replace(m)
    if m.is_a?(self.class) and sizes == m.sizes
      each_index do |i, j|
	@bone[i][j] = m[i, j]
      end
    else
      raise "Type Error #{m.class}"
    end
  end

  def simplify; matrix{|i, j| self[i, j].simplify}; end

  def self.matrices; Matrices; end

  def self.[](*a)
     new(a, true)
  end

  ############################################
  #
  # matrix type conversion
  #
  #############################################

  def self.*(otype)
    if csize != otype.rsize
      raise "type error (#{self} * #{otype}, #{csize}:#{otype.rsize})"
    end

    if ground.respond_to?(:wedge)
      g = ground.wedge otype.ground
    elsif ground <= Numeric
      g = otype.ground
    else
      raise "*: unkown type conversion (#{ground}) * (#{oground})"
    end

    if otype <= Vector
      Algebra::Vector.create(g, rsize)
    elsif self <= Covector
      Algebra::Covector.create(g, csize)
    elsif rsize == otype.csize
      SquareMatrix.create(g, rsize)
    else
      MatrixAlgebra.create(g, rsize, otype.csize)
    end
  end

#  def self.vector_type
#    Algebra::Vector.create(ground, rsize)
#  end

#  def self.covector_type
#    Algebra::Covector.create(ground, csize)
#  end

  def self.minor
    if self <= SquareMatrix
      superclass.create(ground, size-1)
    elsif self <= MatrixAlgebra
      superclass.create(ground, rsize-1, csize-1)
    else
      raise "minor: unknown type #{self.class}"
    end
  end

  def self.dsum(otype)
    #unless ground == otype.ground
    #  raise "type error #{ground}.dsum #{otype.ground}"
    #end
    if self <= SquareMatrix and otype <= SquareMatrix
      superclass.create(ground, size + otype.size)
    else
      superclass.create(ground, rsize + otype.rsize, csize + otype.csize)
    end
  end

  def self.dsum_r(otype); raise "not implemented"; end

  def self.dsum_c(otype); raise "not implemented"; end

  ################################


#  def [](i, j = nil)
#    j ? @bone[i][j] : @bone[i]
#  end

  def [](i, j)
    @bone[i][j]
  end

  def []=(i, j, x)
    @bone[i][j] = x
  end

  def rows
    (0...rsize).collect{|i| row(i)}
  end

  def row(i)
    raise "size error" unless 0 <= i && i < rsize
    @bone[i].clone
  end

  def row!(i) # column!(j) is not defined!
    raise "size error" unless 0 <= i && i < rsize
    @bone[i]
  end

  def set_row(i, array)
    raise "size error" unless 0 <= i && i < rsize
    raise "size error" unless csize == array.size
    @bone[i] = array
    self
  end

  def columns
    (0...csize).collect{|j| column(j)}
  end

  def vectors
#    vc = self.class.vector_type
    vc = Algebra::Vector.create(ground, rsize)
    columns.collect{|v| vc[*v]}
  end

  def column(j)
    raise "size error" unless 0 <= j && j < csize
    (0...rsize).collect{|i| @bone[i][j]}
  end

  def set_column(j, array)
    raise "size error" unless 0 <= j && j < csize
    raise "size error" unless rsize == array.size
    (0...rsize).each{|i| @bone[i][j] = array[i]}
    self
  end


  def sizes; self.class.sizes; end
  def rsize; self.class.rsize; end
  def csize; self.class.csize; end

  def self.create(ground, rsize, csize)
    if klass = matrices[[ground, rsize, csize]] #flyweight
      klass
    else
      klass = super(ground)
      klass.sysvar(:sizes, [rsize, csize])
      klass.sysvar(:rsize, rsize)
      klass.sysvar(:csize, csize)
      matrices[[ground, rsize, csize]] = klass
      klass
    end
  end

  def self.transpose
    if sizes == [csize, rsize]
      self
    else
      superclass.create(ground, csize, rsize)
    end
  end

  def to_s
    @bone.inspect
  end

  def display(out = $stdout)
    @bone.each do |col|
#      out << col.inspect << "\n"
      first = true
      col.each do |x|
	if first
	  first = false
	else
	  out << ", "
	end
	out << sprintf("%3s", x)
      end
      out << "\n"
    end
    out
  end

  def display_by_latex(out = $stdout)
#    out << "\\left(\n"
#    out "\\begin{array}{" + "c" * @bone[0].size + "}\n"
    @bone.each do |col|
#      out << col.inspect << "\n"
      first = true
      col.each do |x|
	if first
	  first = false
	else
	  out << " & "
	end
	out << sprintf("%3s", x)
      end
      out << "\\\\\n"
    end
#    out << "\\end{array}"
#    out << "\\right)"
    out
  end

  def latex(env = "pmatrix")
    s = ""
    s << "\\begin{#{env}}\n" if env
    s << display_by_latex("")
    s << "\\end{#{env}}\n" if env
    s
  end

  def inspect
    "#{self.class}#{to_s}"
  end

  #check entries
  def types
    matrix{|i, j| self[i, j].class}
  end

  def self.zero
#    new((0...rsize).collect{(0...csize).collect{ground.zero}})
    matrix{ground.zero}
  end
    
  def self.regulate(x)
    case x
    when Vector, Covector, MatrixAlgebra #,SquareMatrix
      x
    else
      nil
    end
  end

  def coerce(other)#MatrixAlgebra
    if x = ground.regulate(other)
      [Algebra::Scalar.new(x), self]
    else
      super
    end
  end

  def each(&b)
    @bone.each(&b)
  end

  def self.each_index
    (0...rsize).each do |i|
      (0...csize).each do |j|
	yield i, j
      end
    end
  end

  class << self
    alias each_ij each_index
  end

  def each_index(&b)
    self.class.each_index(&b)
  end

  alias each_ij each_index

  def each_i
    (0...rsize).each do |i|
      yield i
    end
  end
  
  def each_j
    (0...csize).each do |j|
      yield j
    end
  end

  def each_row
    (0...rsize).each do |i|
      yield row(i)
    end
  end

  def each_column
    (0...rsize).each do |i|
      yield column(i)
    end
  end
  
  def ==(other)
    super{ |o|
      rais "type error" unless sizes == o.sizes
      each_index do |i, j|
	return false unless self[i, j] == o[i, j]
      end
      true
    }
  end

  def self.collect_ij(m = rsize, n = csize)
    (0...m).collect do |i|
      (0...n).collect do |j|
	yield(i, j)
      end
    end
  end
  
  def collect_ij(*x, &b); self.class.collect_ij(*x, &b); end

  def self.matrix(m = rsize, n = csize)
    new(collect_ij(m, n) {|i, j| yield i, j})
  end

  def matrix(m = rsize, n = csize)
    self.class.matrix(m, n){|i, j| yield i, j}
  end

  def minor(i, j)
    self.class.minor.matrix{|a, b|
      self[a < i ? a : a+1, b < j ? b : b+1]
    }
  end

  def cofactor(i, j)
    minor(i, j).determinant * (-1)**(i+j)
  end

  def cofactor_matrix
    self.transpose.matrix{|i, j| cofactor(j, i)}
  end

  alias adjoint cofactor_matrix
  
  def self.collect_row(m = rsize)
    new((0...m).collect{|i| yield i})
  end

  def collect_row(m = rsize)
    self.class.collect_row(m){|i| yield i}
  end

  def self.collect_column(n = csize)
    columns = (0...n).collect{|j| yield j}
    matrix{|i, j| columns[j][i]}
  end

  def collect_column(n = csize)
    self.class.collect_columns(n){|j| yield j}
  end
  
  def +(other)
    super{ |o|
      raise "type error" unless sizes == o.sizes
      matrix{|i, j| self[i, j] + o[i, j]}
    }
  end

  def -(other)
    super{ |o|
      raise "type error" unless sizes == o.sizes
      matrix{|i, j| self[i, j] - o[i, j]}
    }
  end

  def old_mul(other)
    #without regulation
    case other
    when ground, Numeric # 1.is_a?(Rational) is false
      matrix{|i, j| self[i, j]*other}
    else
      super{|o|
        msize = csize
        raise "type error" unless msize == o.rsize
        (self.class * o.class).matrix(rsize, o.csize){|i, j|
          (0...msize).sum(ground.zero){|k| self[i, k] * o[k, j]}
        }
      }
    end
  end

  def *(other)
    #without regulation!
    if other.is_a?(ground) || other.is_a?(Numeric)#1.is_a?(Rational) is false
      matrix{|i, j| self[i, j]*other}
    elsif self.is_a?(other.ground)
      other.matrix{|i, j| self*other[i, j]}
    else
      super{|o|
        msize = csize
        raise "type error" unless msize == o.rsize
        (self.class * o.class).matrix(rsize, o.csize){|i, j|
          (0...msize).sum(ground.zero){|k| self[i, k] * o[k, j]}
        }
      }
    end
  end

  def /(other)
    case other
    when ground, Numeric
      matrix{|i, j| self[i, j] / other}
    else
      raise "(/): unknown type #{other.class}"
    end
  end

  def map
    matrix{|i, j| yield(self[i, j])}
  end

  def transpose
    self.class.transpose.matrix(csize, rsize){|i, j| self[j, i]}
  end

  alias t transpose

  def dsum(other)
    mytype = self.class.dsum(other.class)
    mytype.matrix{|i, j|
      if i < rsize
	if j < csize
	  self[i, j]
	else
	  other.ground.zero
	end
      else
	if j < csize
	  ground.zero
	else
	  other[i - rsize, j - csize]
	end
      end
    }
  end

  def to_ary
    to_a
  end

  def flatten
    #'to_a' is defined in Enumerable
    to_a.flatten
  end

  def diag
    ed = []
    size = rsize < csize ? rsize : csize
    (0...size).each do |i|
      break if self[i, i].zero?
      ed.push self[i, i]
      end
    ed
  end

  def convert_to(mat_alg)
    mat_alg.matrix{|i, j| mat_alg.ground.regulate(self[i, j])}
  end

  def self.convert_from(m)
    matrix{|i, j| m[i, j].convert_to(ground)}
  end

  def self.**(m)
    m.convert_to(self)
  end
end

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

  def norm2_complex
    inner_product_complex(self)
  end
end

class Vector < MatrixAlgebra
  include InnerProductSpace

  Matrices = {}

  def initialize(array, conv = false)
    @bone0 = array
    super(array.collect{|x| [x]}, conv)
  end

  def each
    @bone0.each do |x|
      yield x
    end
  end
  
  def size; self.class.size; end
  
  #def to_a
  #  column(0)
  #end

  def [](*x)
    case x.size
    when 1
      super(x[0], 0)
    when 2
      super(*x)
    else
      raise "size of index be 1 or 2"
    end
  end
  
  def self.create(ground, n)
    klass = super(ground, n, 1)
    klass.sysvar(:size, n)
    klass
  end
  
  def transpose
    Algebra::Covector.create(ground, size).new(to_a)
  end
  
  def self.vector(conv = false, &b)
    new((0...size).collect(&b), conv)
  end

  def self.[](*a)
    vector(true){|i| a[i]}
  end
  
  def self.matrix(r = size, s = 1)
    vector{|i| yield(i, 0)}
  end
  
  def inspect
    @bone0.inspect
  end
  
  def to_s
    @bone0.inspect
  end
end

class Covector < MatrixAlgebra
  include InnerProductSpace

  Matrices = {}

  def initialize(array, conv = false)
    @bone0 = array
    super([array], conv)
  end

  def each
    @bone0.each do |x|
      yield x
    end
  end
  
  def size; self.class.size; end
  
  #    def to_a
  #      row(0)
  #    end
  
  def [](*x)
    case x.size
    when 1; super(0, x[0])
    when 2; super(*x)
    else
      raise "size of index be 1 or 2"
    end
  end
  
  def self.create(ground, n)
    klass = super(ground, 1, n)
    klass.sysvar(:size, n)
    klass
  end
  
  def transpose
    Algebra::Vector.create(ground, size).new(to_a)
  end
  
  def self.[](*a)
    covector(true){|i| a[i]}
  end
  
  def self.covector(conv = false, &b)
    new((0...size).collect(&b), true)
  end

  def self.matrix(r = 1, s = size)
    covector{|j| yield(0, j)}
  end

  def inspect
    @bone0.inspect
  end
  
  def to_s
    super
  end
end
  
class SquareMatrix < MatrixAlgebra
  Matrices = {}
  
  def self.unity
    matrix{|i, j| i == j ? ground.unity : ground.zero }
  end
  
  def self.const(x)
    matrix{|i, j| i == j ? ground.regulate(x) : ground.zero }
  end
  
  def const(x)
    self.class.const(x)
  end
  
  def self.matrices
    Matrices
  end
  
  def initialzie(array, conv = false)
    super
  end
  
  def self.create(ground, n, m = nil)
    if m && n != m
      raise "type error to create SquareMatrix"
    end
    klass = super(ground, n, n)
    klass.sysvar(:size, n)
    klass
  end
  
  def size; self.class.size; end
  
  def self.regulate(x)
    if x.is_a? superclass #SquareMatrix
      x
    elsif y = ground.regulate(x)
      const(y)
    else
      super
    end
  end

  def self.determinant(aa)
    r = create(Integer, aa.size)
    m = r.new(aa)
    m.determinant
  end

  class << self
    alias det determinant
  end

  def /(other)
    #It might be better to use regulate ...
    case other
#    when ground, Numeric
#      matrix{|i, j| self[i, j] / other}
    when self.class.superclass #SquareMatrix
      self * other.inverse
    else
      super
    end
  end
  
  def determinant
    sum = ground.zero
    perm do |idx|
      product = ground.unity
      idx.each_with_index do |i, j|
	product *= self[i, j]
      end
      sum += product * sign(idx)
    end
    sum
  end
  
  alias determinant! determinant
  alias det determinant

  def inverse
    #gaussian-elimination.rb
    if ground.field? &&
        (!ground.respond_to?(:reducible) || ground.reducible)
      inverse_field
    else
      a = adjoint
      d = determinant
      unless d.unit?
        warn("#{a}/#{d} may not be done.")
      end
      a / d
    end
  end
  
  def sign(a)
    n = a.size
    a = a.dup
    b = (0...n).collect{|i| a.index(i)}
    s = 1
    (0...(n-1)).each do |i|
      if (j = a[i]) != i
	a[b[i]], b[j], s = j, b[i], -s
      end
    end
    s
  end
  
  def perm(n = size, stack = [])
    (0...n).each do |x|
      unless stack.include? x
	stack.push x
	if stack.size < n
	  perm(n, stack) do |y|; yield y; end
	else
	  yield stack
	end
	stack.pop
      end
    end
  end
  
  def char_polynomial0(obj = "x")
    require "algebra/polynomial"
    a = Algebra.Polynomial(ground, obj)
    k = Algebra.SquareMatrix(a, size)
    (k.unity * a.var - self).determinant
    #    (k.unity * a.var - k.matrix{|i, j| self[i,j]}).determinant
  end

  def _char_matrix(mop)
    # assume mop.ground.ground == self.ground
    mop.unity * mop.ground.var - self
  end

  def char_matrix(polyring)
    matrix_over_poly = Algebra.SquareMatrix(polyring, size)
    _char_matrix(matrix_over_poly)
  end

  def char_polynomial(polyring)
    matrix_over_poly = Algebra.SquareMatrix(polyring, size)
    _char_matrix(matrix_over_poly).determinant
  end
end

class Scalar # fake class for SCALAR * MATRIX
  def initialize(x)
    @value = x
  end
  
  def +(other)
    case other
    when Algebra::SquareMatrix
      other.const(@value) + other
    else
      raise "Fail: Scalar(#{@value}) + #{other}"
    end
  end
  
  def -(other)
    case other
    when Algebra::SquareMatrix
      other.const(@value) - other
    else
      raise "Fail: Scalar(#{@value}) - #{other}"
    end
  end
  
  def *(other)
    case other
    when Algebra::MatrixAlgebra
      other.matrix{|i, j| @value * other[i, j]}
    else
      raise "Fail: Scalar(#{@value}) * #{other}"
    end
  end
end
end

if $0 == __FILE__
#  include Algebra
  M33 = Algebra.MatrixAlgebra(Integer, 3, 3)
  M34 = Algebra.MatrixAlgebra(Integer, 3, 4)
  a33 = [
    [1, 1, 2],
    [3, 4, 5],
    [6, 7, 8]
  ]
  m0 = M33.new(a33)
  m0[0, 0] = 0
  m1 = M33[
    [10, 11, 12],
    [13, 14, 15],
    [16, 17, 18]]
  m2 = M34[[20, 21, 22, 23], [24, 25, 26, 27], [28, 29, 30, 31]]
  p m0 + m1
  p m0 * m0
  p( (m0 * m0).t )
  m = m0 * m2
  M43 = M34.transpose
  p M34.sizes
  p M43.sizes
  M44 = M43 * M34
  p m2.t * m2
  p m0.dsum(m1).display


  require "algebra/rational"

  M3 = Algebra.SquareMatrix(Rational, 3)
  m3 = M3[
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8]]

#  require "algebra/polynomial"
#  Px =  Algebra.Polynomial(Rational, "x")
#  require "algebra/polynomial"
#  p f = m3.char_polynomial(Px)
end


syntax highlighted by Code2HTML, v. 0.9.1