# frozen_string_literal: false
# An implementation of Matrix and Vector classes.
# See classes Matrix and Vector for documentation.
# Current Maintainer:: Marc-André Lafortune
# Original Author:: Keiju ISHITSUKA
# Original Documentation:: Gavin Sinclair (sourced from <i>Ruby in a Nutshell</i> (Matsumoto, O'Reilly))
module ExceptionForMatrix # :nodoc:
extend Exception2MessageMapper
def_e2message(TypeError, "wrong argument type %s (expected %s)")
def_e2message(ArgumentError, "Wrong # of arguments(%d for %d)")
def_exception("ErrDimensionMismatch", "\#{self.name} dimension mismatch")
def_exception("ErrNotRegular", "Not Regular Matrix")
def_exception("ErrOperationNotDefined", "Operation(%s) can\\'t be defined: %s op %s")
def_exception("ErrOperationNotImplemented", "Sorry, Operation(%s) not implemented: %s op %s")
# The +Matrix+ class represents a mathematical matrix. It provides methods for creating
# matrices, operating on them arithmetically and algebraically,
# and determining their mathematical properties such as trace, rank, inverse, determinant,
include ExceptionForMatrix
autoload :EigenvalueDecomposition, "matrix/eigenvalue_decomposition"
autoload :LUPDecomposition, "matrix/lup_decomposition"
private_class_method :new
# Creates a matrix where each argument is a row.
# Matrix[ [25, 93], [-1, 66] ]
# Creates a matrix where +rows+ is an array of arrays, each of which is a row
# of 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]])
def Matrix.rows(rows, copy = true)
rows = convert_to_array(rows, copy)
convert_to_array(row, copy)
size = (rows[0] || []).size
raise ErrDimensionMismatch, "row size differs (#{row.size} should be #{size})" unless row.size == size
# Creates a matrix using +columns+ as an array of column vectors.
# Matrix.columns([[25, 93], [-1, 66]])
def Matrix.columns(columns)
rows(columns, false).transpose
# Creates a matrix of size +row_count+ x +column_count+.
# It fills the values by calling the given block,
# passing the current row and column.
# Returns an enumerator if no block is given.
# m = Matrix.build(2, 4) {|row, col| col - row }
# => Matrix[[0, 1, 2, 3], [-1, 0, 1, 2]]
# m = Matrix.build(3) { rand }
# => a 3x3 matrix with random elements
def Matrix.build(row_count, column_count = row_count)
row_count = CoercionHelper.coerce_to_int(row_count)
column_count = CoercionHelper.coerce_to_int(column_count)
raise ArgumentError if row_count < 0 || column_count < 0
return to_enum :build, row_count, column_count unless block_given?
rows = Array.new(row_count) do |i|
Array.new(column_count) do |j|
# Creates a matrix where the diagonal elements are composed of +values+.
# Matrix.diagonal(9, 5, -3)
def Matrix.diagonal(*values)
return Matrix.empty if size == 0
rows = Array.new(size) {|j|
# Creates an +n+ by +n+ diagonal matrix where each diagonal element is
def Matrix.scalar(n, value)
diagonal(*Array.new(n, value))
# Creates an +n+ by +n+ identity matrix.
def Matrix.zero(row_count, column_count = row_count)
rows = Array.new(row_count){Array.new(column_count, 0)}
# Creates a single-row matrix where the values of that row are as given in
# Matrix.row_vector([4,5,6])
def Matrix.row_vector(row)
row = convert_to_array(row)
# Creates a single-column matrix where the values of that column are as given
# Matrix.column_vector([4,5,6])
def Matrix.column_vector(column)
column = convert_to_array(column)
new [column].transpose, 1
# Creates a empty matrix of +row_count+ x +column_count+.
# At least one of +row_count+ or +column_count+ must be 0.
# n == Matrix.columns([ [], [], [] ])
# => Matrix[[0, 0, 0], [0, 0, 0]]
def Matrix.empty(row_count = 0, column_count = 0)
raise ArgumentError, "One size must be 0" if column_count != 0 && row_count != 0
raise ArgumentError, "Negative size" if column_count < 0 || row_count < 0
new([[]]*row_count, column_count)
# Create a matrix by stacking matrices vertically
# x = Matrix[[1, 2], [3, 4]]
# y = Matrix[[5, 6], [7, 8]]
# Matrix.vstack(x, y) # => Matrix[[1, 2], [3, 4], [5, 6], [7, 8]]
def Matrix.vstack(x, *matrices)
x = CoercionHelper.coerce_to_matrix(x)
result = x.send(:rows).map(&:dup)
m = CoercionHelper.coerce_to_matrix(m)
if m.column_count != x.column_count
raise ErrDimensionMismatch, "The given matrices must have #{x.column_count} columns, but one has #{m.column_count}"
result.concat(m.send(:rows))
new result, x.column_count
# Create a matrix by stacking matrices horizontally
# x = Matrix[[1, 2], [3, 4]]
# y = Matrix[[5, 6], [7, 8]]
# Matrix.hstack(x, y) # => Matrix[[1, 2, 5, 6], [3, 4, 7, 8]]
def Matrix.hstack(x, *matrices)
x = CoercionHelper.coerce_to_matrix(x)
result = x.send(:rows).map(&:dup)
total_column_count = x.column_count
m = CoercionHelper.coerce_to_matrix(m)
if m.row_count != x.row_count
raise ErrDimensionMismatch, "The given matrices must have #{x.row_count} rows, but one has #{m.row_count}"
result.each_with_index do |row, i|
row.concat m.send(:rows)[i]
total_column_count += m.column_count
new result, total_column_count
# Create a matrix by combining matrices entrywise, using the given block
# x = Matrix[[6, 6], [4, 4]]
# y = Matrix[[1, 2], [3, 4]]
# Matrix.combine(x, y) {|a, b| a - b} # => Matrix[[5, 4], [1, 0]]
def Matrix.combine(*matrices)
return to_enum(__method__, *matrices) unless block_given?
return Matrix.empty if matrices.empty?
matrices.map!(&CoercionHelper.method(:coerce_to_matrix))
Matrix.Raise ErrDimensionMismatch unless x.row_count == m.row_count && x.column_count == m.column_count
rows = Array.new(x.row_count) do |i|
Array.new(x.column_count) do |j|
yield matrices.map{|m| m[i,j]}
def combine(*matrices, &block)
Matrix.combine(self, *matrices, &block)
# Matrix.new is private; use Matrix.rows, columns, [], etc... to create.
def initialize(rows, column_count = rows[0].size)
# No checking is done at this point. rows must be an Array of Arrays.
# column_count must be the size of the first row, if there is one,
# otherwise it *must* be specified and can be any integer >= 0
@column_count = column_count
def new_matrix(rows, column_count = rows[0].size) # :nodoc:
self.class.send(:new, rows, column_count) # bypass privacy of Matrix.new
# Returns element (+i+,+j+) of the matrix. That is: row +i+, column +j+.
@rows.fetch(i){return nil}[j]
private :[]=, :set_element, :set_component
# Returns the number of rows.
alias_method :row_size, :row_count
# Returns the number of columns.
attr_reader :column_count
alias_method :column_size, :column_count
# 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.
def row(i, &block) # :yield: e
@rows.fetch(i){return self}.each(&block)
Vector.elements(@rows.fetch(i){return nil})
# 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
def column(j) # :yield: e
return self if j >= column_count || j < -column_count
return nil if j >= column_count || j < -column_count
col = Array.new(row_count) {|i|
Vector.elements(col, false)
# 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 }
def collect(&block) # :yield: e
return to_enum(:collect) unless block_given?
rows = @rows.collect{|row| row.collect(&block)}
new_matrix rows, column_count
# Yields all elements of the matrix, starting with those of the first row,
# or returns an Enumerator if no block given.
# Elements can be restricted by passing an argument:
# * :all (default): yields all elements
# * :diagonal: yields only elements on the diagonal
# * :off_diagonal: yields all elements except on the diagonal
# * :lower: yields only elements on or below the diagonal
# * :strict_lower: yields only elements below the diagonal
# * :strict_upper: yields only elements above the diagonal
# * :upper: yields only elements on or above the diagonal
# Matrix[ [1,2], [3,4] ].each { |e| puts e }
# # => prints the numbers 1 to 4
# Matrix[ [1,2], [3,4] ].each(:strict_lower).to_a # => [3]
def each(which = :all) # :yield: e
return to_enum :each, which unless block_given?
@rows.each_with_index do |row, row_index|
yield row.fetch(row_index){return self}
@rows.each_with_index do |row, row_index|
column_count.times do |col_index|
yield row[col_index] unless row_index == col_index
@rows.each_with_index do |row, row_index|
0.upto([row_index, last].min) do |col_index|
@rows.each_with_index do |row, row_index|
[row_index, column_count].min.times do |col_index|
@rows.each_with_index do |row, row_index|
(row_index+1).upto(last) do |col_index|
@rows.each_with_index do |row, row_index|
row_index.upto(last) do |col_index|
raise ArgumentError, "expected #{which.inspect} to be one of :all, :diagonal, :off_diagonal, :lower, :strict_lower, :strict_upper or :upper"
# Same as #each, but the row index and column index in addition to the element
# Matrix[ [1,2], [3,4] ].each_with_index do |e, row, col|
# puts "#{e} at #{row}, #{col}"
def each_with_index(which = :all) # :yield: e, row, column
return to_enum :each_with_index, which unless block_given?
@rows.each_with_index do |row, row_index|
row.each_with_index do |e, col_index|
yield e, row_index, col_index
@rows.each_with_index do |row, row_index|
yield row.fetch(row_index){return self}, row_index, row_index
@rows.each_with_index do |row, row_index|
column_count.times do |col_index|
yield row[col_index], row_index, col_index unless row_index == col_index
@rows.each_with_index do |row, row_index|
0.upto([row_index, last].min) do |col_index|
yield row[col_index], row_index, col_index
@rows.each_with_index do |row, row_index|
[row_index, column_count].min.times do |col_index|
yield row[col_index], row_index, col_index
@rows.each_with_index do |row, row_index|
(row_index+1).upto(last) do |col_index|
yield row[col_index], row_index, col_index
@rows.each_with_index do |row, row_index|
row_index.upto(last) do |col_index|
yield row[col_index], row_index, col_index
raise ArgumentError, "expected #{which.inspect} to be one of :all, :diagonal, :off_diagonal, :lower, :strict_lower, :strict_upper or :upper"