Skip to content
rbotafogo edited this page Dec 30, 2014 · 4 revisions

This version of MDArray introduces class MDMatrix. MDMatrix is a matrix class wrapping many linear algebra methods from Parallel Colt (see below). MDMatrix support only the following types: i) int; ii) long; iii) float and iv) double.

Differently from other libraries, in which matrix is a subclass of array, MDMatrix is a twin class of MDArray. MDMatrix is a twin class of MDArray as every time an MDMatrix is instantiated, an MDArray class is also instantiated. In reality, there is only one backing store that can be viewed by either MDMatrix or MDArray.

Creation of MDMatrix follows the same API as MDArray API. For instance, creating a double square matrix of size 5 x 5 can be done by:

matrix = MDMatrix.double([5, 5], [2.0, 0.0, 8.0, 6.0, 0.0,\
                                  1.0, 6.0, 0.0, 1.0, 7.0,\
                                  5.0, 0.0, 7.0, 4.0, 0.0,\
                                  7.0, 0.0, 8.0, 5.0, 0.0,\
                                  0.0, 10.0, 0.0, 0.0, 7.0])

Creating an int matrix filled with zero can be done by:

matrix = MDMatrix.int([4, 3])

MDMatrix also supports creation based on methods such as fromfunction, linspace, init_with, arange, typed_arange and ones:

array = MDArray.typed_arange("double", 0, 15)
array = MDMatrix.fromfunction("double", [4, 4]) { |x, y| x + y }

An MDMatrix can also be created from an MDArray as follows:

d2 = MDArray.typed_arange("double", 0, 15)
double_matrix = MDMatrix.from_mdarray(d2)

An MDMatrix can only be created from MDArrays of one, two or three dimensions. However, one can take a view from an MDArray to create an MDMatrix, as long as the view is at most three dimensional:

# Instantiate an MDArray and shape it with 4 dimensions
> d1 = MDArray.typed_arange("double", 0, 420)
> d1.reshape!([5, 4, 3, 7])
# slice the array, getting a three dimensional array and from that, make a matrix
> matrix = MDMatrix.from_mdarray(d1.slice(0, 0))
# get a region from the array, with the first two dimensions of size 0, reduce it to a
# size two array and then build a two dimensional matrix
> matrix = MDMatrix.from_mdarray(d1.region(:spec => "0:0, 0:0, 0:2, 0:6").reduce)

printing the above two dimensional matrix gives us:

> matrix.print
3 x 7 matrix
0,00000 1,00000 2,00000 3,00000 4,00000 5,00000 6,00000
7,00000 8,00000 9,00000 10,0000 11,0000 12,0000 13,0000
14,0000 15,0000 16,0000 17,0000 18,0000 19,0000 20,0000

Every MDMatrix instance has a twin MDArray instance that uses the same backing store. This allows the user to treat the data as either a matrix or an array and use methods either from matrix or array. The above matrix can be printed as an array:

> matrix.mdarray.print
[[0.00 1.00 2.00 3.00 4.00 5.00 6.00]
 [7.00 8.00 9.00 10.00 11.00 12.00 13.00]
 [14.00 15.00 16.00 17.00 18.00 19.00 20.00]]

With an MDMatrix, many linear algebra methods can be easily calculated:

# basic operations on matrix can be done, such as, ‘+’, ‘-‘, ´*’, ‘/’
# make a 4 x 4 matrix and fill it with ´double´ 2.5
> a = MDMatrix.double([4, 4])
> a.fill(2.5)
# make a 4 x 4 matrix ´b´ from a given function (block)
> b = MDMatrix.fromfunction("double", [4, 4]) { |x, y| x + y }
# add both matrices
> c = a + b
# multiply by scalar
> c = a * 2
# divide two matrices.  Matrix ´b´ has to be non-singular, otherwise an exception is
# raised.
# generate a non-singular matrix from a given matrix
> b.generate_non_singular!
> c = a / b

Linear algebra methods:

# create a matrix with the given data
> pos = MDArray.double([3, 3], [4, 12, -16, 12, 37, -43, -16, -43, 98])
> matrix = MDMatrix.from_mdarray(pos)
# Cholesky decomposition from wikipedia example
> chol = matrix.chol
> assert_equal(2, chol[0, 0])
> assert_equal(6, chol[1, 0])
> assert_equal(-8, chol[2, 0])
> assert_equal(5, chol[2, 1])
> assert_equal(3, chol[2, 2])

All other linear algebra methods are called the same way.

require 'rubygems'
require "test/unit"
require 'shoulda'

require 'mdarray'


#-------------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------------

should "do basic matrix algebra" do

  a = MDMatrix.double([4, 4])
  a.fill(2.5)

  b = MDMatrix.fromfunction("double", [4, 4]) { |x, y| x + y }

  c = a + b
  b.reset_traversal
  c.each do |val|
    assert_equal(2.5 + b.next, val)
  end
  c = a * 2
  a.reset_traversal
  c.each do |val|
    assert_equal(a.next * 2, val)
  end

  c = 2 * a
  a.reset_traversal
  c.each do |val|
    assert_equal(a.next * 2, val)
  end

  c = a - 2
  a.reset_traversal
  c.each do |val|
    assert_equal(a.next - 2, val)
  end

  c = 2 - a
  a.reset_traversal
  c.each do |val|
    assert_equal(2 - a.next, val)
  end

  # Need to test/implement matrix division
  assert_raise ( RuntimeError ) { c = a / a }
  assert_raise ( RuntimeError ) { c = a / b }

  p "Matrix division"
  b.generate_non_singular!
  c = a / b
  c.print
  printf("\n\n")

end

#-------------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------------

should "get and set values for double Matrix" do

  a = MDMatrix.double([4, 4])
  a[0, 0] = 1
  assert_equal(1, a[0, 0])
  assert_equal(0.0, a[0, 1])

  a.fill(2.5)
  assert_equal(2.5, a[3, 3])

  b = MDMatrix.double([4, 4])
  b.fill(a)
  assert_equal(2.5, b[1, 3])

  # fill the matrix with the value of a Proc evaluation.  The argument to the 
  # Proc is the content of the array at the given index, i.e, x = b[i] for all i.
  func = Proc.new { |x| x ** 2 }
  b.fill(func)
  assert_equal(2.5 ** 2, b[0, 3])
  assert_equal(2.5 ** 2, b[1, 1])

  # fill the Matrix with the value of method apply from a given class.
  # In general this solution is more efficient than the above solution with
  # Proc.  
  class DoubleFunc
    def self.apply(x)
      x/2
    end
  end

  b.fill(DoubleFunc)
  assert_equal(3.125, b[2,0])

  # defines a class with a method apply with two arguments
  class DoubleFunc2
    def self.apply(x, y)
      (x + y) ** 2
    end
  end
  
  # fill array a with the value ot the result of a function to each cell; 
  # x[row,col] = function(x[row,col],y[row,col]).
  tmp = a[0,1]
  a.fill(b, DoubleFunc2)
  assert_equal((tmp + b[0, 1]) ** 2, a[0, 1]) 

  tens = MDMatrix.init_with("double", [5, 3], 10.0)
  assert_equal(10.0, tens[2, 2])

  typed_arange = MDMatrix.typed_arange("double", 0, 20, 2)
  assert_equal(0, typed_arange[0])
  assert_equal(2, typed_arange[1])
  assert_equal(4, typed_arange[2])

  typed_arange.reshape!([5, 2])

  val = typed_arange.reduce(Proc.new { |x, y| x + y }, Proc.new { |x| x })
  assert_equal(90, val)

  val = typed_arange.reduce(Proc.new { |x, y| x + y }, Proc.new { |x| x * x})
  assert_equal(1140, val)

  val = typed_arange.reduce(Proc.new { |x, y| x + y }, Proc.new { |x| x },
                            Proc.new { |x| x > 8 })
  assert_equal(70, val)

  linspace = MDMatrix.linspace("double", 0, 10, 50)
  assert_equal(0.20408163265306123, linspace[1])
  assert_qual(1.0204081632653061, linspace[5])

  # set the value of all cells that are bigger than 5 to 1.0
  linspace.fill_cond(Proc.new { |x| x > 5 }, 1.0)
  assert_equal(1.0, linspace[25])
  assert_equal(1.0, linspace[27])
  assert_not_equal(1.0, linspace[24])

  # set the value of all cells that are smaller than 5 to the square value
  linspace.fill_cond(Proc.new { |x| x < 5 }, Proc.new { |x| x * x })
  assert_equal(0.20408163265306123 ** 2, linspace[1])
  assert_equal(1.0204081632653061 ** 2, linspace[5])
  
  ones = MDMatrix.ones("double", [3, 5])
  assert_equal(1.0, ones[2, 1])

  arange = MDMatrix.arange(0, 10)
  assert_equal(0, arange[0])
  assert_equal(1, arange[1])

end

#-------------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------------

should "test 2d double matrix functions" do

  b = MDMatrix.double([3], [1.5, 1, 1.3])
  pos = MDArray.double([3, 3], [4, 12, -16, 12, 37, -43, -16, -43, 98])
  matrix = MDMatrix.from_mdarray(pos)

  # Cholesky decomposition from wikipedia example
  chol = matrix.chol
  assert_equal(2, chol[0, 0])
  assert_equal(6, chol[1, 0])
  assert_equal(-8, chol[2, 0])
  assert_equal(5, chol[2, 1])
  assert_equal(3, chol[2, 2])

  matrix = MDMatrix.double([2, 2], [2, 3, 2, 1])
  # Eigenvalue decomposition
  eig = matrix.eig
  p "eigen decomposition"
  p "eigenvalue matrix"
  eig[0].print
  printf("\n\n")
  p "imaginary parts of the eigenvalues"
  eig[1].print
  printf("\n\n")
  p "real parts of the eigenvalues"
  eig[2].print
  printf("\n\n")
  p "eigenvector matrix"
  eig[3].print
  printf("\n\n")

  lu = matrix.lu
  p "lu decomposition"
  p "is non singular: #{lu[0]}"
  p "determinant: #{lu[1]}"
  p "pivot vector: #{lu[2]}"
  p "lower triangular matrix"
  lu[3].print
  printf("\n\n")
  p "upper triangular matrix"
  lu[4].print
  printf("\n\n")

  # Returns the condition of matrix A, which is the ratio of largest to 
  # smallest singular value.
  p "condition of matrix"
  p matrix.cond

  # Solves the upper triangular system U*x=b;
  p "solving the equation by backward_solve"
  solve = lu[4].backward_solve(b)
  solve.print
  printf("\n\n")

  # Solves the lower triangular system U*x=b;
  p "solving the equation by forward_solve"
  solve = lu[3].forward_solve(b)
  solve.print
  printf("\n\n")

  qr = matrix.qr
  p "QR decomposition"
  p "Matrix has full rank: #{qr[0]}"
  p "Orthogonal factor Q:"
  qr[1].print
  printf("\n\n")
  p "Upper triangular factor, R"
  qr[2].print
  printf("\n\n")

  matrix = MDMatrix.double([5, 5], [2.0, 0.0, 8.0, 6.0, 0.0,\
                                    1.0, 6.0, 0.0, 1.0, 7.0,\
                                    5.0, 0.0, 7.0, 4.0, 0.0,\
                                    7.0, 0.0, 8.0, 5.0, 0.0,\
                                    0.0, 10.0, 0.0, 0.0, 7.0])

  svd = matrix.svd
  p "Singular value decomposition"
  p "operation success? #{svd[0]}" # 0 success; < 0 ith value is illegal; > 0 not converge
  p "cond: #{svd[1]}"
  p "norm2: #{svd[2]}"
  p "rank: #{svd[3]}"
  assert_equal(17.91837085874625, svd[4][0])
  assert_equal(15.17137188041607, svd[4][1])
  assert_equal(3.5640020352605677, svd[4][2])
  assert_equal(1.9842281528992616, svd[4][3])
  assert_equal(0.3495556671751232, svd[4][4])

  p "Diagonal matrix of singular values"
  # svd[5].print
  printf("\n\n")
  p "left singular vectors U"
  svd[6].print
  printf("\n\n")
  p "right singular vectors V"
  svd[7].print
  printf("\n\n")

  m = MDArray.typed_arange("double", 0, 16)
  m.reshape!([4, 4])
  matrix1 = MDMatrix.from_mdarray(m)
  # mat2 = matrix.chol
  matrix1.print
  printf("\n\n")

  p "Transposing the above matrix"
  matrix1.transpose.print
  printf("\n\n")

  p "getting regions from the above matrix"
  p "specification is '0:0, 1:3'"
  matrix1.region(:spec => "0:0, 1:3").print
  printf("\n\n")

  p "specification is '0:3:2, 1:3'"
  matrix1.region(:spec => "0:3:2, 1:3").print
  printf("\n\n")

  p "flipping dim 0 of the matrix"
  matrix1.flip(0).print
  printf("\n\n")

  m = MDArray.typed_arange("double", 16, 32)
  m.reshape!([4, 4])
  matrix2 = MDMatrix.from_mdarray(m)
  matrix2.print
  printf("\n\n")
  
  p "matrix multiplication of square matrices"
  result = matrix1 * matrix2
  result.print 
  printf("\n\n")

  p "matrix multiplication of rec matrices"
  array1 = MDMatrix.double([2, 3], [1, 2, 3, 4, 5, 6])
  array2 = MDMatrix.double([3, 2], [1, 2, 3, 4, 5, 6])
  mult = array1 * array2
  mult.print
  printf("\n\n")

  p "matrix multiplication of rec matrices passing alpha and beta parameters."
  p "C = alpha * A x B + beta*C"
  mult = array1.mult(array2, 2, 2)
  mult.print
  printf("\n\n")

  p "matrix multiplication of rec matrices passing alpha, beta and return (C) parameters."
  p "C = alpha * A x B + beta*C"
  result = MDMatrix.double([2, 2], [2, 2, 2, 2])
  mult = array1.mult(array2, 2, 2, false, false, result)
  mult.print
  printf("\n\n")

  p "matrix multiplication by vector"
  array1 = MDMatrix.double([2, 3], [1, 2, 3, 4, 5, 6])
  vector = MDMatrix.double([3], [4, 5, 6])
  mult = array1 * vector
  mult.print
  printf("\n\n")

  result = matrix1.kron(matrix2)
  p "Kronecker multiplication"
  result.print
  printf("\n\n")

  print "determinant is: #{result.det}"
  printf("\n\n")

  p "norm1"
  p result.norm1

  p "norm2"
  p result.norm2

  p "Returns the Frobenius norm of matrix A, which is Sqrt(Sum(A[i,j]^2))"
  p result.normF

  p "Returns the infinity norm of matrix A, which is the maximum absolute row sum."
  p result.norm_infinity

  power3 = result ** 3
  power3.print 
  printf("\n\n")

  p result.trace

  trap_lower = result.trapezoidal_lower
  trap_lower.print
  printf("\n\n")

  p result.vector_norm2

  result.normalize!
  result.print
  printf("\n\n")

  p "summing all values of result: #{result.sum}"

  result.mdarray.print

end

#-------------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------------

should "get and set values for float Matrix" do

  a = MDMatrix.float([4, 4])
  a[0, 0] = 1
  assert_equal(1, a[0, 0])
  assert_equal(0.0, a[0, 1])

  a.fill(2.5)
  assert_equal(2.5, a[3, 3])

  b = MDMatrix.float([4, 4])
  b.fill(a)
  assert_equal(2.5, b[1, 3])

  # fill the matrix with the value of a Proc evaluation.  The argument to the 
  # Proc is the content of the array at the given index, i.e, x = b[i] for all i.
  func = Proc.new { |x| x ** 2 }
  b.fill(func)
  assert_equal(6.25, b[0, 3])
  b.print
  printf("\n\n")

  p "defining function in a class"

  # fill the Matrix with the value of method apply from a given class.
  # In general this solution is more efficient than the above solution with
  # Proc.  
  class Func
    def self.apply(x)
      x/2
    end
  end

  b.fill(Func)
  assert_equal(3.125, b[2,0])
  b.print
  printf("\n\n")

  # defines a class with a method apply with two arguments
  class Func2
    def self.apply(x, y)
      (x + y) ** 2
    end
  end
  
  # fill array a with the value the result of a function to each cell; 
  # x[row,col] = function(x[row,col],y[row,col]).
  a.fill(b, Func2)
  a.print

  tens = MDMatrix.init_with("float", [5, 3], 10.0)
  tens.print
  printf("\n\n")

  typed_arange = MDMatrix.typed_arange("float", 0, 20, 2)
  typed_arange.print
  printf("\n\n")
  
  typed_arange.reshape!([5, 2])
  p "printing typed_arange"
  typed_arange.print
  printf("\n\n")

  p "reducing the value of typed_arange by summing all value"
  val = typed_arange.reduce(Proc.new { |x, y| x + y }, Proc.new { |x| x })
  p val
  assert_equal(90, val)

  p "reducing the value of typed_arange by summing the square of all value"
  val = typed_arange.reduce(Proc.new { |x, y| x + y }, Proc.new { |x| x * x})
  p val
  assert_equal(1140, val)

  p "reducing the value of typed_arange by summing all value larger than 8"
  val = typed_arange.reduce(Proc.new { |x, y| x + y }, Proc.new { |x| x },
                            Proc.new { |x| x > 8 })
  p val


  linspace = MDMatrix.linspace("float", 0, 10, 50)
  linspace.print
  printf("\n\n")

  # set the value of all cells that are bigger than 5 to 1.0
  linspace.fill_cond(Proc.new { |x| x > 5 }, 1.0)
  linspace.print
  printf("\n\n")

  # set the value of all cells that are smaller than 5 to the square value
  linspace.fill_cond(Proc.new { |x| x < 5 }, Proc.new { |x| x * x })
  linspace.print
  printf("\n\n")

  ones = MDMatrix.ones("float", [3, 5])
  ones.print
  printf("\n\n")

end

#-------------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------------

should "test 2d float matrix functions" do

  b = MDMatrix.float([3], [1.5, 1, 1.3])

  pos = MDArray.float([3, 3], [2, -1, 0, -1, 2, -1, 0, -1, 2])
  matrix = MDMatrix.from_mdarray(pos)
  result = matrix.chol
  p "Cholesky decomposition"
  result.print
  printf("\n\n")

  eig = matrix.eig
  p "eigen decomposition"
  p "eigenvalue matrix"
  eig[0].print
  printf("\n\n")
  p "imaginary parts of the eigenvalues"
  eig[1].print
  printf("\n\n")
  p "real parts of the eigenvalues"
  eig[2].print
  printf("\n\n")
  p "eigenvector matrix"
  eig[3].print
  printf("\n\n")

  lu = matrix.lu
  p "lu decomposition"
  p "is non singular: #{lu[0]}"
  p "determinant: #{lu[1]}"
  p "pivot vector: #{lu[2]}"
  p "lower triangular matrix"
  lu[3].print
  printf("\n\n")
  p "upper triangular matrix"
  lu[4].print
  printf("\n\n")

  # Returns the condition of matrix A, which is the ratio of largest to 
  # smallest singular value.
  p "condition of matrix"
  p matrix.cond

  # Solves the upper triangular system U*x=b;
  p "solving the equation by backward_solve"
  solve = lu[4].backward_solve(b)
  solve.print
  printf("\n\n")

  # Solves the lower triangular system U*x=b;
  p "solving the equation by forward_solve"
  solve = lu[3].forward_solve(b)
  solve.print
  printf("\n\n")

  qr = matrix.qr
  p "QR decomposition"
  p "Matrix has full rank: #{qr[0]}"
  p "Orthogonal factor Q:"
  qr[1].print
  printf("\n\n")
  p "Upper triangular factor, R"
  qr[2].print
  printf("\n\n")

  svd = matrix.svd
  p "Singular value decomposition"
  p "operation success? #{svd[0]}" # 0 success; < 0 ith value is illegal; > 0 not converge
  p "cond: #{svd[1]}"
  p "norm2: #{svd[2]}"
  p "rank: #{svd[3]}"
  p "singular values"
  p svd[4]
  p "Diagonal matrix of singular values"
  # svd[5].print
  printf("\n\n")
  p "left singular vectors U"
  svd[6].print
  printf("\n\n")
  p "right singular vectors V"
  svd[7].print
  printf("\n\n")

  m = MDArray.typed_arange("float", 0, 16)
  m.reshape!([4, 4])
  matrix1 = MDMatrix.from_mdarray(m)
  # mat2 = matrix.chol
  matrix1.print
  printf("\n\n")

  p "Transposing the above matrix"
  matrix1.transpose.print
  printf("\n\n")

  p "getting regions from the above matrix"
  p "specification is '0:0, 1:3'"
  matrix1.region(:spec => "0:0, 1:3").print
  printf("\n\n")

  p "specification is '0:3:2, 1:3'"
  matrix1.region(:spec => "0:3:2, 1:3").print
  printf("\n\n")

  p "flipping dim 0 of the matrix"
  matrix1.flip(0).print
  printf("\n\n")

  m = MDArray.typed_arange("float", 16, 32)
  m.reshape!([4, 4])
  matrix2 = MDMatrix.from_mdarray(m)
  matrix2.print
  printf("\n\n")
  
  result = matrix1 * matrix2
  p "matrix multiplication"
  result.print 
  printf("\n\n")

  result = matrix1.kron(matrix2)
  p "Kronecker multiplication"
  result.print
  printf("\n\n")

  print "determinant is: #{result.det}"
  printf("\n\n")

  p "norm1"
  p result.norm1

  p "norm2"
  p result.norm2

  p "Returns the Frobenius norm of matrix A, which is Sqrt(Sum(A[i,j]^2))"
  p result.normF

  p "Returns the infinity norm of matrix A, which is the maximum absolute row sum."
  p result.norm_infinity

  power3 = result ** 3
  power3.print 
  printf("\n\n")

  p result.trace

  trap_lower = result.trapezoidal_lower
  trap_lower.print
  printf("\n\n")

  p result.vector_norm2

  result.normalize!
  result.print
  printf("\n\n")

  p "summing all values of result: #{result.sum}"

  result.mdarray.print

end
Clone this wiki locally