SELECTORS = {all: true, diagonal: true, off_diagonal: true, lower: true, strict_lower: true, strict_upper: true, upper: true}.freeze
# index(value, selector = :all) -> [row, column]
# index(selector = :all){ block } -> [row, column]
# index(selector = :all) -> an_enumerator
# The index method is specialized to return the index as [row, column]
# It also accepts an optional +selector+ argument, see #each for details.
# Matrix[ [1,2], [3,4] ].index(&:even?) # => [0, 1]
# Matrix[ [1,1], [1,1] ].index(1, :strict_lower) # => [1, 0]
raise ArgumentError, "wrong number of arguments(#{args.size} for 0-2)" if args.size > 2
which = (args.size == 2 || SELECTORS.include?(args.last)) ? args.pop : :all
return to_enum :find_index, which, *args unless block_given? || args.size == 1
each_with_index(which) do |e, row_index, col_index|
return row_index, col_index if e == value
each_with_index(which) do |e, row_index, col_index|
return row_index, col_index if yield e
alias_method :find_index, :index
# Returns a section of the matrix. The parameters are either:
# * start_row, nrows, start_col, ncols; OR
# Matrix.diagonal(9, 5, -3).minor(0..1, 0..2)
# Like Array#[], negative indices count backward from the end of the
# row or column (-1 is the last element). Returns nil if the starting
# row or column is greater than row_count or column_count respectively.
row_range, col_range = param
from_row = row_range.first
from_row += row_count if from_row < 0
to_row += row_count if to_row < 0
to_row += 1 unless row_range.exclude_end?
size_row = to_row - from_row
from_col = col_range.first
from_col += column_count if from_col < 0
to_col += column_count if to_col < 0
to_col += 1 unless col_range.exclude_end?
size_col = to_col - from_col
from_row, size_row, from_col, size_col = param
return nil if size_row < 0 || size_col < 0
from_row += row_count if from_row < 0
from_col += column_count if from_col < 0
raise ArgumentError, param.inspect
return nil if from_row > row_count || from_col > column_count || from_row < 0 || from_col < 0
rows = @rows[from_row, size_row].collect{|row|
new_matrix rows, [column_count - from_col, size_col].min
# Returns the submatrix obtained by deleting the specified row and column.
# Matrix.diagonal(9, 5, -3, 4).first_minor(1, 2)
def first_minor(row, column)
raise RuntimeError, "first_minor of empty matrix is not defined" if empty?
unless 0 <= row && row < row_count
raise ArgumentError, "invalid row (#{row.inspect} for 0..#{row_count - 1})"
unless 0 <= column && column < column_count
raise ArgumentError, "invalid column (#{column.inspect} for 0..#{column_count - 1})"
new_matrix arrays, column_count - 1
# Returns the (row, column) cofactor which is obtained by multiplying
# the first minor by (-1)**(row + column).
# Matrix.diagonal(9, 5, -3, 4).cofactor(1, 1)
def cofactor(row, column)
raise RuntimeError, "cofactor of empty matrix is not defined" if empty?
Matrix.Raise ErrDimensionMismatch unless square?
det_of_minor = first_minor(row, column).determinant
det_of_minor * (-1) ** (row + column)
# Returns the adjugate of the matrix.
# Matrix[ [7,6],[3,9] ].adjugate
Matrix.Raise ErrDimensionMismatch unless square?
Matrix.build(row_count, column_count) do |row, column|
# Returns the Laplace expansion along given row or column.
# Matrix[[7,6], [3,9]].laplace_expansion(column: 1)
# Matrix[[Vector[1, 0], Vector[0, 1]], [2, 3]].laplace_expansion(row: 0)
def laplace_expansion(row: nil, column: nil)
if !num || (row && column)
raise ArgumentError, "exactly one the row or column arguments must be specified"
Matrix.Raise ErrDimensionMismatch unless square?
raise RuntimeError, "laplace_expansion of empty matrix is not defined" if empty?
unless 0 <= num && num < row_count
raise ArgumentError, "invalid num (#{num.inspect} for 0..#{row_count - 1})"
send(row ? :row : :column, num).map.with_index { |e, k|
e * cofactor(*(row ? [num, k] : [k,num]))
alias_method :cofactor_expansion, :laplace_expansion
# TESTING -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Returns +true+ if this is a diagonal matrix.
# Raises an error if matrix is not square.
Matrix.Raise ErrDimensionMismatch unless square?
each(:off_diagonal).all?(&:zero?)
# Returns +true+ if this is an empty matrix, i.e. if the number of rows
# or the number of columns is 0.
column_count == 0 || row_count == 0
# Returns +true+ if this is an hermitian matrix.
# Raises an error if matrix is not square.
Matrix.Raise ErrDimensionMismatch unless square?
each_with_index(:upper).all? do |e, row, col|
# Returns +true+ if this is a lower triangular matrix.
each(:strict_upper).all?(&:zero?)
# Returns +true+ if this is a normal matrix.
# Raises an error if matrix is not square.
Matrix.Raise ErrDimensionMismatch unless square?
rows.each_with_index do |row_i, i|
rows.each_with_index do |row_j, j|
rows.each_with_index do |row_k, k|
s += row_i[k] * row_j[k].conj - row_k[i].conj * row_k[j]
return false unless s == 0
# Returns +true+ if this is an orthogonal matrix
# Raises an error if matrix is not square.
Matrix.Raise ErrDimensionMismatch unless square?
rows.each_with_index do |row, i|
column_count.times do |j|
return false unless s == (i == j ? 1 : 0)
# Returns +true+ if this is a permutation matrix
# Raises an error if matrix is not square.
Matrix.Raise ErrDimensionMismatch unless square?
cols = Array.new(column_count)
rows.each_with_index do |row, i|
row.each_with_index do |e, j|
return false if found || cols[j]
return false unless found
# Returns +true+ if all entries of the matrix are real.
# Returns +true+ if this is a regular (i.e. non-singular) matrix.
# Returns +true+ if this is a singular matrix.
# Returns +true+ if this is a square matrix.
column_count == row_count
# Returns +true+ if this is a symmetric matrix.
# Raises an error if matrix is not square.
Matrix.Raise ErrDimensionMismatch unless square?
each_with_index(:strict_upper) do |e, row, col|
return false if e != rows[col][row]
# Returns +true+ if this is a unitary matrix
# Raises an error if matrix is not square.
Matrix.Raise ErrDimensionMismatch unless square?
rows.each_with_index do |row, i|
column_count.times do |j|
s += row[k].conj * rows[k][j]
return false unless s == (i == j ? 1 : 0)
# Returns +true+ if this is an upper triangular matrix.
each(:strict_lower).all?(&:zero?)
# Returns +true+ if this is a matrix with only zero elements
# OBJECT METHODS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Returns +true+ if and only if the two matrices contain equal elements.
return false unless Matrix === other &&
column_count == other.column_count # necessary for empty matrices
return false unless Matrix === other &&
column_count == other.column_count # necessary for empty matrices
# Returns a clone of the matrix, so that the contents of each do not reference
# There should be no good reason to do this since Matrices are immutable.
new_matrix @rows.map(&:dup), column_count
# Returns a hash-code for the matrix.
# ARITHMETIC -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Matrix[[2,4], [6,8]] * Matrix.identity(2)
def *(m) # m is matrix or vector or number
rows = @rows.collect {|row|
return new_matrix rows, column_count
m = self.class.column_vector(m)
Matrix.Raise ErrDimensionMismatch if column_count != m.row_count
rows = Array.new(row_count) {|i|
Array.new(m.column_count) {|j|
(0 ... column_count).inject(0) do |vij, k|
vij + self[i, k] * m[k, j]
return new_matrix rows, m.column_count
return apply_through_coercion(m, __method__)
# Matrix.scalar(2,5) + Matrix[[1,0], [-4,7]]
Matrix.Raise ErrOperationNotDefined, "+", self.class, m.class
m = self.class.column_vector(m)
return apply_through_coercion(m, __method__)
Matrix.Raise ErrDimensionMismatch unless row_count == m.row_count && column_count == m.column_count
rows = Array.new(row_count) {|i|
Array.new(column_count) {|j|
new_matrix rows, column_count
# Matrix[[1,5], [4,2]] - Matrix[[9,3], [-4,1]]
Matrix.Raise ErrOperationNotDefined, "-", self.class, m.class
m = self.class.column_vector(m)
return apply_through_coercion(m, __method__)
Matrix.Raise ErrDimensionMismatch unless row_count == m.row_count && column_count == m.column_count
rows = Array.new(row_count) {|i|
Array.new(column_count) {|j|
new_matrix rows, column_count
# Matrix division (multiplication by the inverse).
# Matrix[[7,6], [3,9]] / Matrix[[2,9], [3,1]]
rows = @rows.collect {|row|
row.collect {|e| e / other }
return new_matrix rows, column_count
return self * other.inverse
return apply_through_coercion(other, __method__)
# Matrix[[1,2], [3,4]].hadamard_product(Matrix[[1,2], [3,2]])
alias_method :entrywise_product, :hadamard_product
# Returns the inverse of the matrix.
# Matrix[[-1, -1], [0, -1]].inverse
Matrix.Raise ErrDimensionMismatch unless square?