Object
The Matrix class represents a mathematical matrix, and
provides methods for creating special-case matrices (zero, identity,
diagonal, singular, vector), operating on them arithmetically and
algebraically, and determining their mathematical properties (trace, rank,
inverse, determinant).
Note that although matrices should theoretically be rectangular, this is not enforced by the class.
Also note that the determinant of integer matrices may be incorrectly
calculated unless you also require 'mathn'. This may
be fixed in the future.
To create a matrix:
Matrix[*rows]
Matrix.[](*rows)
Matrix.rows(rows, copy = true)
Matrix.columns(columns)
Matrix.diagonal(*values)
Matrix.scalar(n, value)
Matrix.scalar(n, value)
Matrix.identity(n)
Matrix.unit(n)
Matrix.I(n)
Matrix.zero(n)
Matrix.row_vector(row)
Matrix.column_vector(column)
To access Matrix elements/columns/rows/submatrices/properties:
[](i, j)
#row_size
#column_size
#row(i)
#column(j)
#collect
#map
#minor(*param)
Properties of a matrix:
#regular?
#singular?
#square?
Matrix arithmetic:
*(m)
+(m)
-(m)
#/(m)
#inverse
#inv
**
Matrix functions:
#determinant
#det
#rank
#trace
#tr
#transpose
#t
Conversion to other data types:
#coerce(other)
#row_vectors
#column_vectors
#to_a
String representations:
#to_s
#inspect
Creates a matrix where each argument is a row.
Matrix[ [25, 93], [-1, 66] ]
=> 25 93
-1 66
# File matrix.rb, line 120
def Matrix.[](*rows)
new(:init_rows, rows, false)
end
Creates a single-column matrix where the values of that column are as given
in column.
Matrix.column_vector([4,5,6])
=> 4
5
6
# File matrix.rb, line 228
def Matrix.column_vector(column)
case column
when Vector
Matrix.columns([column.to_a])
when Array
Matrix.columns([column])
else
Matrix.columns([[column]])
end
end
Creates a matrix using columns as an array of column vectors.
Matrix.columns([[25, 93], [-1, 66]])
=> 25 -1
93 66
# File matrix.rb, line 142
def Matrix.columns(columns)
rows = (0 .. columns[0].size - 1).collect {|i|
(0 .. columns.size - 1).collect {|j|
columns[j][i]
}
}
Matrix.rows(rows, false)
end
Creates a matrix where the diagonal elements are composed of
values.
Matrix.diagonal(9, 5, -3)
=> 9 0 0
0 5 0
0 0 -3
# File matrix.rb, line 158
def Matrix.diagonal(*values)
size = values.size
rows = (0 .. size - 1).collect {|j|
row = Array.new(size).fill(0, 0, size)
row[j] = values[j]
row
}
rows(rows, false)
end
Creates an n by n identity matrix.
Matrix.identity(2)
=> 1 0
0 1
# File matrix.rb, line 185
def Matrix.identity(n)
Matrix.scalar(n, 1)
end
This method is used by the other methods that create matrices, and is of no use to general users.
# File matrix.rb, line 243
def initialize(init_method, *argv)
self.send(init_method, *argv)
end
Creates a single-row matrix where the values of that row are as given in
row.
Matrix.row_vector([4,5,6]) => 4 5 6
# File matrix.rb, line 209
def Matrix.row_vector(row)
case row
when Vector
Matrix.rows([row.to_a], false)
when Array
Matrix.rows([row.dup], false)
else
Matrix.rows([[row]], false)
end
end
Creates a matrix where rows is an array of arrays, each of
which is a row to the matrix. If the optional argument copy
is false, use the given arrays as the internal structure of the matrix
without copying.
Matrix.rows([[25, 93], [-1, 66]])
=> 25 93
-1 66
# File matrix.rb, line 131
def Matrix.rows(rows, copy = true)
new(:init_rows, rows, copy)
end
Matrix multiplication.
Matrix[[2,4], [6,8]] * Matrix.identity(2)
=> 2 4
6 8
# File matrix.rb, line 455
def *(m) # m is matrix or vector or number
case(m)
when Numeric
rows = @rows.collect {|row|
row.collect {|e|
e * m
}
}
return Matrix.rows(rows, false)
when Vector
m = Matrix.column_vector(m)
r = self * m
return r.column(0)
when Matrix
Matrix.Raise ErrDimensionMismatch if column_size != m.row_size
rows = (0 .. row_size - 1).collect {|i|
(0 .. m.column_size - 1).collect {|j|
vij = 0
0.upto(column_size - 1) do |k|
vij += self[i, k] * m[k, j]
end
vij
}
}
return Matrix.rows(rows, false)
else
x, y = m.coerce(self)
return x * y
end
end
Matrix exponentiation. Defined for integer powers only. Equivalent to multiplying the matrix by itself N times.
Matrix[[7,6], [3,9]] ** 2
=> 67 96
48 99
# File matrix.rb, line 633
def ** (other)
if other.kind_of?(Integer)
x = self
if other <= 0
x = self.inverse
return Matrix.identity(self.column_size) if other == 0
other = -other
end
z = x
n = other - 1
while n != 0
while (div, mod = n.divmod(2)
mod == 0)
x = x * x
n = div
end
z *= x
n -= 1
end
z
elsif other.kind_of?(Float) || defined?(Rational) && other.kind_of?(Rational)
Matrix.Raise ErrOperationNotDefined, "**"
else
Matrix.Raise ErrOperationNotDefined, "**"
end
end
Matrix addition.
Matrix.scalar(2,5) + Matrix[[1,0], [-4,7]]
=> 6 0
-4 12
# File matrix.rb, line 493
def +(m)
case m
when Numeric
Matrix.Raise ErrOperationNotDefined, "+"
when Vector
m = Matrix.column_vector(m)
when Matrix
else
x, y = m.coerce(self)
return x + y
end
Matrix.Raise ErrDimensionMismatch unless row_size == m.row_size and column_size == m.column_size
rows = (0 .. row_size - 1).collect {|i|
(0 .. column_size - 1).collect {|j|
self[i, j] + m[i, j]
}
}
Matrix.rows(rows, false)
end
Matrix subtraction.
Matrix[[1,5], [4,2]] - Matrix[[9,3], [-4,1]]
=> -8 2
8 1
# File matrix.rb, line 521
def -(m)
case m
when Numeric
Matrix.Raise ErrOperationNotDefined, "-"
when Vector
m = Matrix.column_vector(m)
when Matrix
else
x, y = m.coerce(self)
return x - y
end
Matrix.Raise ErrDimensionMismatch unless row_size == m.row_size and column_size == m.column_size
rows = (0 .. row_size - 1).collect {|i|
(0 .. column_size - 1).collect {|j|
self[i, j] - m[i, j]
}
}
Matrix.rows(rows, false)
end
Matrix division (multiplication by the inverse).
Matrix[[7,6], [3,9]] / Matrix[[2,9], [3,1]]
=> -7 1
-3 -6
# File matrix.rb, line 549
def /(other)
case other
when Numeric
rows = @rows.collect {|row|
row.collect {|e|
e / other
}
}
return Matrix.rows(rows, false)
when Matrix
return self * other.inverse
else
x, y = other.coerce(self)
return x / y
end
end
Returns true if and only if the two matrices contain equal
elements.
# File matrix.rb, line 401
def ==(other)
return false unless Matrix === other
other.compare_by_row_vectors(@rows)
end
Returns element (i,j) of the matrix. That is:
row i, column j.
# File matrix.rb, line 260
def [](i, j)
@rows[i][j]
end
Returns a clone of the matrix, so that the contents of each do not reference identical objects.
# File matrix.rb, line 428
def clone
Matrix.rows(@rows)
end
FIXME: describe coerce.
# File matrix.rb, line 891
def coerce(other)
case other
when Numeric
return Scalar.new(other), self
else
raise TypeError, "#{self.class} can't be coerced into #{other.class}"
end
end
Returns a matrix that is the result of iteration of the given block over all elements of the matrix.
Matrix[ [1,2], [3,4] ].collect { |e| e**2 }
=> 1 4
9 16
# File matrix.rb, line 329
def collect # :yield: e
rows = @rows.collect{|row| row.collect{|e| yield e}}
Matrix.rows(rows, false)
end
Returns column vector number j of the matrix as a Vector (starting at 0 like an array). When a block
is given, the elements of that vector are iterated.
# File matrix.rb, line 309
def column(j) # :yield: e
if block_given?
0.upto(row_size - 1) do |i|
yield @rows[i][j]
end
else
col = (0 .. row_size - 1).collect {|i|
@rows[i][j]
}
Vector.elements(col, false)
end
end
Returns the number of columns. Note that it is possible to construct a matrix with uneven columns (e.g. Matrix[ [1,2,3], [4,5] ]), but this is mathematically unsound. This method uses the first row to determine the result.
# File matrix.rb, line 286
def column_size
@rows[0].size
end
Returns an array of the column vectors of the matrix. See Vector.
# File matrix.rb, line 913
def column_vectors
columns = (0 .. column_size - 1).collect {|i|
column(i)
}
columns
end
Not really intended for general consumption.
# File matrix.rb, line 415
def compare_by_row_vectors(rows, comparison = :==)
return false unless @rows.size == rows.size
0.upto(@rows.size - 1) do |i|
return false unless @rows[i].send(comparison, rows[i])
end
true
end
Returns the determinant of the matrix. If the matrix is not square, the result is 0. This method's algorism is Gaussian elimination method and using Numeric#quo(). Beware that using Float values, with their usual lack of precision, can affect the value returned by this method. Use Rational values or #det_e instead if this is important to you.
Matrix[[7,6], [3,9]].determinant => 63.0
# File matrix.rb, line 674
def determinant
return 0 unless square?
size = row_size - 1
a = to_a
det = 1
k = 0
loop do
if (akk = a[k][k]) == 0
i = k
loop do
return 0 if (i += 1) > size
break unless a[i][k] == 0
end
a[i], a[k] = a[k], a[i]
akk = a[k][k]
det *= -1
end
for i in k + 1 .. size
q = a[i][k].quo(akk)
(k + 1).upto(size) do |j|
a[i][j] -= a[k][j] * q
end
end
det *= akk
break unless (k += 1) <= size
end
det
end
Returns the determinant of the matrix. If the matrix is not square, the result is 0. This method's algorism is Gaussian elimination method. This method uses Euclidean algorism. If all elements are integer, really exact value. But, if an element is a float, can't return exact value.
Matrix[[7,6], [3,9]].determinant => 63
# File matrix.rb, line 717
def determinant_e
return 0 unless square?
size = row_size - 1
a = to_a
det = 1
k = 0
loop do
if a[k][k].zero?
i = k
loop do
return 0 if (i += 1) > size
break unless a[i][k].zero?
end
a[i], a[k] = a[k], a[i]
det *= -1
end
for i in (k + 1)..size
q = a[i][k].quo(a[k][k])
k.upto(size) do |j|
a[i][j] -= a[k][j] * q
end
unless a[i][k].zero?
a[i], a[k] = a[k], a[i]
det *= -1
redo
end
end
det *= a[k][k]
break unless (k += 1) <= size
end
det
end
# File matrix.rb, line 927
def elements_to_f
collect{|e| e.to_f}
end
# File matrix.rb, line 931
def elements_to_i
collect{|e| e.to_i}
end
# File matrix.rb, line 935
def elements_to_r
collect{|e| e.to_r}
end
# File matrix.rb, line 406
def eql?(other)
return false unless Matrix === other
other.compare_by_row_vectors(@rows, :eql?)
end
Returns a hash-code for the matrix.
# File matrix.rb, line 435
def hash
value = 0
for row in @rows
for e in row
value ^= e.hash
end
end
return value
end
Overrides Object#inspect
# File matrix.rb, line 955
def inspect
"Matrix"+@rows.inspect
end
Returns the inverse of the matrix.
Matrix[[1, 2], [2, 1]].inverse
=> -1 1
0 -1
# File matrix.rb, line 572
def inverse
Matrix.Raise ErrDimensionMismatch unless square?
Matrix.I(row_size).inverse_from(self)
end
Not for public consumption?
# File matrix.rb, line 581
def inverse_from(src)
size = row_size - 1
a = src.to_a
for k in 0..size
i = k
akk = a[k][k].abs
((k+1)..size).each do |j|
v = a[j][k].abs
if v > akk
i = j
akk = v
end
end
Matrix.Raise ErrNotRegular if akk == 0
if i != k
a[i], a[k] = a[k], a[i]
@rows[i], @rows[k] = @rows[k], @rows[i]
end
akk = a[k][k]
for i in 0 .. size
next if i == k
q = a[i][k].quo(akk)
a[i][k] = 0
for j in (k + 1).. size
a[i][j] -= a[k][j] * q
end
for j in 0..size
@rows[i][j] -= @rows[k][j] * q
end
end
for j in (k + 1).. size
a[k][j] = a[k][j].quo(akk)
end
for j in 0..size
@rows[k][j] = @rows[k][j].quo(akk)
end
end
self
end
Returns a section of the matrix. The parameters are either:
start_row, nrows, start_col, ncols; OR
col_range, row_range
Matrix.diagonal(9, 5, -3).minor(0..1, 0..2)
=> 9 0 0
0 5 0
# File matrix.rb, line 344
def minor(*param)
case param.size
when 2
from_row = param[0].first
size_row = param[0].end - from_row
size_row += 1 unless param[0].exclude_end?
from_col = param[1].first
size_col = param[1].end - from_col
size_col += 1 unless param[1].exclude_end?
when 4
from_row = param[0]
size_row = param[1]
from_col = param[2]
size_col = param[3]
else
Matrix.Raise ArgumentError, param.inspect
end
rows = @rows[from_row, size_row].collect{|row|
row[from_col, size_col]
}
Matrix.rows(rows, false)
end
Returns the rank of the matrix. Beware that using Float values, probably return faild value. Use Rational values or #rank_e for getting exact result.
Matrix[[7,6], [3,9]].rank => 2
# File matrix.rb, line 762
def rank
if column_size > row_size
a = transpose.to_a
a_column_size = row_size
a_row_size = column_size
else
a = to_a
a_column_size = column_size
a_row_size = row_size
end
rank = 0
k = 0
begin
if (akk = a[k][k]) == 0
i = k
exists = true
loop do
if (i += 1) > a_row_size - 1
exists = false
break
end
break unless a[i][k] == 0
end
if exists
a[i], a[k] = a[k], a[i]
akk = a[k][k]
else
i = k
exists = true
loop do
if (i += 1) > a_column_size - 1
exists = false
break
end
break unless a[k][i] == 0
end
if exists
k.upto(a_row_size - 1) do |j|
a[j][k], a[j][i] = a[j][i], a[j][k]
end
akk = a[k][k]
else
next
end
end
end
for i in (k + 1)..(a_row_size - 1)
q = a[i][k].quo(akk)
for j in (k + 1)..(a_column_size - 1)
a[i][j] -= a[k][j] * q
end
end
rank += 1
end while (k += 1) <= a_column_size - 1
return rank
end
Returns the rank of the matrix. This method uses Euclidean algorism. If all elements are integer, really exact value. But, if an element is a float, can't return exact value.
Matrix[[7,6], [3,9]].rank => 2
# File matrix.rb, line 828
def rank_e
a = to_a
a_column_size = column_size
a_row_size = row_size
pi = 0
(0 ... a_column_size).each do |j|
if i = (pi ... a_row_size).find{|i0| !a[i0][j].zero?}
if i != pi
a[pi], a[i] = a[i], a[pi]
end
(pi + 1 ... a_row_size).each do |k|
q = a[k][j].quo(a[pi][j])
(pi ... a_column_size).each do |j0|
a[k][j0] -= q * a[pi][j0]
end
if k > pi && !a[k][j].zero?
a[k], a[pi] = a[pi], a[k]
redo
end
end
pi += 1
end
end
pi
end
Returns true if this is a regular matrix.
# File matrix.rb, line 375
def regular?
square? and rank == column_size
end
Returns row vector number i of the matrix as a Vector (starting at 0 like an array). When a block
is given, the elements of that vector are iterated.
# File matrix.rb, line 294
def row(i) # :yield: e
if block_given?
for e in @rows[i]
yield e
end
else
Vector.elements(@rows[i])
end
end
Returns the number of rows.
# File matrix.rb, line 276
def row_size
@rows.size
end
Returns an array of the row vectors of the matrix. See Vector.
# File matrix.rb, line 903
def row_vectors
rows = (0 .. row_size - 1).collect {|i|
row(i)
}
rows
end
Returns true is this is a singular (i.e. non-regular) matrix.
# File matrix.rb, line 382
def singular?
not regular?
end
Returns true is this is a square matrix. See note in #column_size about this being
unreliable, though.
# File matrix.rb, line 390
def square?
column_size == row_size
end
Returns an array of arrays that describe the rows of the matrix.
# File matrix.rb, line 923
def to_a
@rows.collect{|row| row.collect{|e| e}}
end
Overrides Object#to_s
# File matrix.rb, line 946
def to_s
"Matrix[" + @rows.collect{|row|
"[" + row.collect{|e| e.to_s}.join(", ") + "]"
}.join(", ")+"]"
end
Returns the trace (sum of diagonal elements) of the matrix.
Matrix[[7,6], [3,9]].trace => 16
# File matrix.rb, line 860
def trace
tr = 0
0.upto(column_size - 1) do |i|
tr += @rows[i][i]
end
tr
end
Returns the transpose of the matrix.
Matrix[[1,2], [3,4], [5,6]]
=> 1 2
3 4
5 6
Matrix[[1,2], [3,4], [5,6]].transpose
=> 1 3 5
2 4 6
# File matrix.rb, line 879
def transpose
Matrix.columns(@rows)
end