# 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))
require_relative "matrix/version"
module ExceptionForMatrix # :nodoc:
class ErrDimensionMismatch < StandardError
def initialize(val = nil)
super("Dimension mismatch")
class ErrNotRegular < StandardError
def initialize(val = nil)
super("Not Regular Matrix")
class ErrOperationNotDefined < StandardError
super("Operation(#{vals[0]}) can't be defined: #{vals[1]} op #{vals[2]}")
class ErrOperationNotImplemented < StandardError
super("Sorry, Operation(#{vals[0]}) not implemented: #{vals[1]} op #{vals[2]}")
# 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.
alias_method :unit, :identity
alias_method :I, :identity
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
# Matrix.combine(*matrices) { |*elements| ... }
# 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))
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]}
# combine(*other_matrices) { |*elements| ... }
# Creates new matrix by combining with <i>other_matrices</i> entrywise,
# x = Matrix[[6, 6], [4, 4]]
# y = Matrix[[1, 2], [3, 4]]
# x.combine(y) {|a, b| a - b} # => Matrix[[5, 4], [1, 0]]
def combine(*matrices, &block)
Matrix.combine(self, *matrices, &block)
# Matrix.new is private; use ::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
private 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]
# matrix[range, range] = matrix/element
# matrix[range, integer] = vector/column_matrix/element
# matrix[integer, range] = vector/row_matrix/element
# matrix[integer, integer] = element
# Set element or elements of matrix.
raise FrozenError, "can't modify frozen Matrix" if frozen?
rows = check_range(i, :row) or row = check_int(i, :row)
columns = check_range(j, :column) or column = check_int(j, :column)
set_row_and_col_range(rows, columns, v)
set_row_range(rows, column, v)
set_col_range(row, columns, v)
set_value(row, column, v)
private :set_element, :set_component
private def check_range(val, direction)
return unless val.is_a?(Range)
count = direction == :row ? row_count : column_count
CoercionHelper.check_range(val, count, direction)
private def check_int(val, direction)
count = direction == :row ? row_count : column_count
CoercionHelper.check_int(val, count, direction)
private def set_value(row, col, value)
raise ErrDimensionMismatch, "Expected a a value, got a #{value.class}" if value.respond_to?(:to_matrix)
private def set_row_and_col_range(row_range, col_range, value)
if row_range.size != value.row_count || col_range.size != value.column_count
raise ErrDimensionMismatch, [
'Expected a Matrix of dimensions',
"#{row_range.size}x#{col_range.size}",
"#{value.row_count}x#{value.column_count}",
source = value.instance_variable_get :@rows
row_range.each_with_index do |row, i|
@rows[row][col_range] = source[i]
elsif value.is_a?(Vector)
raise ErrDimensionMismatch, 'Expected a Matrix or a value, got a Vector'
value_to_set = Array.new(col_range.size, value)
@rows[i][col_range] = value_to_set
private def set_row_range(row_range, col, value)
raise ErrDimensionMismatch unless row_range.size == value.size
set_column_vector(row_range, col, value)
elsif value.is_a?(Matrix)
raise ErrDimensionMismatch unless value.column_count == 1
raise ErrDimensionMismatch unless row_range.size == value.size
set_column_vector(row_range, col, value)
@rows[row_range].each{|e| e[col] = value }
private def set_column_vector(row_range, col, value)
value.each_with_index do |e, index|
r = row_range.begin + index
private def set_col_range(row, col_range, value)
value = if value.is_a?(Vector)
elsif value.is_a?(Matrix)
raise ErrDimensionMismatch unless value.row_count == 1
Array.new(col_range.size, value)
raise ErrDimensionMismatch unless col_range.size == value.size
@rows[row][col_range] = value
# 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.
# 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