class Matrix
def *(m) # m is matrix or vector or number
6 8
=> 2 4
Matrix[[2,4], [6,8]] * Matrix.identity(2)
Matrix multiplication.
def *(m) # m is matrix or vector or number case(m) when Numeric rows = @rows.collect {|row| row.collect {|e| e * m } } return new_matrix(rows, column_size) when Vector m = self.class.column_vector(m) r = self * m return r.column(0) when Matrix Matrix.Raise ErrDimensionMismatch if column_size != m.row_size rows = Array.new(row_size) {|i| Array.new(m.column_size) {|j| (0 ... column_size).inject(0) do |vij, k| vij + self[i, k] * m[k, j] end } } return new_matrix(rows, m.column_size) else return apply_through_coercion(m, __method__) end end
def **(other)
48 99
=> 67 96
Matrix[[7,6], [3,9]] ** 2
Non integer exponents will be handled by diagonalizing the matrix.
Equivalent to multiplying the matrix by itself N times.
Matrix exponentiation.
def **(other) case other when Integer x = self if other <= 0 x = self.inverse return self.class.identity(self.column_size) if other == 0 other = -other end z = nil loop do z = z ? z * x : x if other[0] == 1 return z if (other >>= 1).zero? x *= x end when Numeric v, d, v_inv = eigensystem v * self.class.diagonal(*d.each(:diagonal).map{|e| e ** other}) * v_inv else Matrix.Raise ErrOperationNotDefined, "**", self.class, other.class end end
def +(m)
-4 12
=> 6 0
Matrix.scalar(2,5) + Matrix[[1,0], [-4,7]]
Matrix addition.
def +(m) case m when Numeric Matrix.Raise ErrOperationNotDefined, "+", self.class, m.class when Vector m = self.class.column_vector(m) when Matrix else return apply_through_coercion(m, __method__) end Matrix.Raise ErrDimensionMismatch unless row_size == m.row_size and column_size == m.column_size rows = Array.new(row_size) {|i| Array.new(column_size) {|j| self[i, j] + m[i, j] } } new_matrix rows, column_size end
def -(m)
8 1
=> -8 2
Matrix[[1,5], [4,2]] - Matrix[[9,3], [-4,1]]
Matrix subtraction.
def -(m) case m when Numeric Matrix.Raise ErrOperationNotDefined, "-", self.class, m.class when Vector m = self.class.column_vector(m) when Matrix else return apply_through_coercion(m, __method__) end Matrix.Raise ErrDimensionMismatch unless row_size == m.row_size and column_size == m.column_size rows = Array.new(row_size) {|i| Array.new(column_size) {|j| self[i, j] - m[i, j] } } new_matrix rows, column_size end
def /(other)
-3 -6
=> -7 1
Matrix[[7,6], [3,9]] / Matrix[[2,9], [3,1]]
Matrix division (multiplication by the inverse).
def /(other) case other when Numeric rows = @rows.collect {|row| row.collect {|e| e / other } } return new_matrix(rows, column_size) when Matrix return self * other.inverse else return apply_through_coercion(other, __method__) end end
def ==(other)
Returns +true+ if and only if the two matrices contain equal elements.
def ==(other) return false unless Matrix === other && column_size == other.column_size # necessary for empty matrices rows == other.rows end
def [](i, j)
Returns element (+i+,+j+) of the matrix. That is: row +i+, column +j+.
def [](i, j) @rows.fetch(i){return nil}[j] end
def []=(i, j, v)
def []=(i, j, v) @rows[i][j] = v end
def clone
There should be no good reason to do this since Matrices are immutable.
identical objects.
Returns a clone of the matrix, so that the contents of each do not reference
def clone new_matrix @rows.map(&:dup), column_size end
def coerce(other)
See also Numeric#coerce.
type between the two operands of the operator.
numeric operations: it is intended to find a compatible common
This coercion mechanism is used by Ruby to handle mixed-type
The coerce method provides support for Ruby type coercion.
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
def collect(&block) # :yield: e
9 16
=> 1 4
Matrix[ [1,2], [3,4] ].collect { |e| e**2 }
elements of the matrix.
Returns a matrix that is the result of iteration of the given block over all
def collect(&block) # :yield: e return to_enum(:collect) unless block_given? rows = @rows.collect{|row| row.collect(&block)} new_matrix rows, column_size end
def column(j) # :yield: e
iterated.
like an array). When a block is given, the elements of that vector are
Returns column vector number +j+ of the matrix as a Vector (starting at 0
def column(j) # :yield: e if block_given? return self if j >= column_size || j < -column_size row_size.times do |i| yield @rows[i][j] end self else return nil if j >= column_size || j < -column_size col = Array.new(row_size) {|i| @rows[i][j] } Vector.elements(col, false) end end
def column_vectors
Returns an array of the column vectors of the matrix. See Vector.
def column_vectors Array.new(column_size) {|i| column(i) } end
def conjugate
1 2 3
=> 1-2i -i 0
Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]].conjugate
1 2 3
=> 1+2i i 0
Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]]
Returns the conjugate of the matrix.
def conjugate collect(&:conjugate) end
def determinant
=> 45
Matrix[[7,6], [3,9]].determinant
Consider using exact types like Rational or BigDecimal instead.
because of their lack of precision.
Beware that using Float values can yield erroneous results
Returns the determinant of the matrix.
def determinant Matrix.Raise ErrDimensionMismatch unless square? m = @rows case row_size # Up to 4x4, give result using Laplacian expansion by minors. # This will typically be faster, as well as giving good results # in case of Floats when 0 +1 when 1 + m[0][0] when 2 + m[0][0] * m[1][1] - m[0][1] * m[1][0] when 3 m0, m1, m2 = m + m0[0] * m1[1] * m2[2] - m0[0] * m1[2] * m2[1] \ - m0[1] * m1[0] * m2[2] + m0[1] * m1[2] * m2[0] \ + m0[2] * m1[0] * m2[1] - m0[2] * m1[1] * m2[0] when 4 m0, m1, m2, m3 = m + m0[0] * m1[1] * m2[2] * m3[3] - m0[0] * m1[1] * m2[3] * m3[2] \ - m0[0] * m1[2] * m2[1] * m3[3] + m0[0] * m1[2] * m2[3] * m3[1] \ + m0[0] * m1[3] * m2[1] * m3[2] - m0[0] * m1[3] * m2[2] * m3[1] \ - m0[1] * m1[0] * m2[2] * m3[3] + m0[1] * m1[0] * m2[3] * m3[2] \ + m0[1] * m1[2] * m2[0] * m3[3] - m0[1] * m1[2] * m2[3] * m3[0] \ - m0[1] * m1[3] * m2[0] * m3[2] + m0[1] * m1[3] * m2[2] * m3[0] \ + m0[2] * m1[0] * m2[1] * m3[3] - m0[2] * m1[0] * m2[3] * m3[1] \ - m0[2] * m1[1] * m2[0] * m3[3] + m0[2] * m1[1] * m2[3] * m3[0] \ + m0[2] * m1[3] * m2[0] * m3[1] - m0[2] * m1[3] * m2[1] * m3[0] \ - m0[3] * m1[0] * m2[1] * m3[2] + m0[3] * m1[0] * m2[2] * m3[1] \ + m0[3] * m1[1] * m2[0] * m3[2] - m0[3] * m1[1] * m2[2] * m3[0] \ - m0[3] * m1[2] * m2[0] * m3[1] + m0[3] * m1[2] * m2[1] * m3[0] else # For bigger matrices, use an efficient and general algorithm. # Currently, we use the Gauss-Bareiss algorithm determinant_bareiss end end
def determinant_bareiss
intermediate results with better precision.
with smaller bignums (if any), while a matrix of Float will usually have
A matrix of Integers will have thus intermediate results that are also Integers,
Intermediate results are fraction free and of lower complexity.
It has the same computational cost order O(n^3) as standard Gaussian elimination.
Bareiss' multistep integer-preserving gaussian elimination.
Returns the determinant of the matrix, using
Private. Use Matrix#determinant
def determinant_bareiss size = row_size last = size - 1 a = to_a no_pivot = Proc.new{ return 0 } sign = +1 pivot = 1 size.times do |k| previous_pivot = pivot if (pivot = a[k][k]) == 0 switch = (k+1 ... size).find(no_pivot) {|row| a[row][k] != 0 } a[switch], a[k] = a[k], a[switch] pivot = a[k][k] sign = -sign end (k+1).upto(last) do |i| ai = a[i] (k+1).upto(last) do |j| ai[j] = (pivot * ai[j] - ai[k] * a[k][j]) / previous_pivot end end end sign * pivot end
def determinant_e
deprecated; use Matrix#determinant
def determinant_e warn "#{caller(1)[0]}: warning: Matrix#determinant_e is deprecated; use #determinant" rank end
def diagonal?
Raises an error if matrix is not square.
Returns +true+ is this is a diagonal matrix.
def diagonal? Matrix.Raise ErrDimensionMismatch unless square? each(:off_diagonal).all?(&:zero?) end
def each(which = :all) # :yield: e
Matrix[ [1,2], [3,4] ].each(:strict_lower).to_a # => [3]
# => prints the numbers 1 to 4
Matrix[ [1,2], [3,4] ].each { |e| puts e }
* :upper: yields only elements on or above the diagonal
* :strict_upper: yields only elements above the diagonal
* :strict_lower: yields only elements below the diagonal
* :lower: yields only elements on or below the diagonal
* :off_diagonal: yields all elements except on the diagonal
* :diagonal: yields only elements on the diagonal
* :all (default): yields all elements
Elements can be restricted by passing an argument:
or returns an Enumerator is no block given.
Yields all elements of the matrix, starting with those of the first row,
def each(which = :all) # :yield: e return to_enum(:each, which) unless block_given? last = column_size - 1 case which when :all block = Proc.new @rows.each do |row| row.each(&block) end when :diagonal @rows.each_with_index do |row, row_index| yield row.fetch(row_index){return self} end when :off_diagonal @rows.each_with_index do |row, row_index| column_size.times do |col_index| yield row[col_index] unless row_index == col_index end end when :lower @rows.each_with_index do |row, row_index| 0.upto([row_index, last].min) do |col_index| yield row[col_index] end end when :strict_lower @rows.each_with_index do |row, row_index| [row_index, column_size].min.times do |col_index| yield row[col_index] end end when :strict_upper @rows.each_with_index do |row, row_index| (row_index+1).upto(last) do |col_index| yield row[col_index] end end when :upper @rows.each_with_index do |row, row_index| row_index.upto(last) do |col_index| yield row[col_index] end end else Matrix.Raise ArgumentError, "expected #{which.inspect} to be one of :all, :diagonal, :off_diagonal, :lower, :strict_lower, :strict_upper or :upper" end self end
def each_with_index(which = :all) # :yield: e, row, column
# 4 at 1, 1
# 3 at 1, 0
# 2 at 0, 1
# 1 at 0, 0
# => Prints:
end
puts "#{e} at #{row}, #{col}"
Matrix[ [1,2], [3,4] ].each_with_index do |e, row, col|
Same as #each, but the row index and column index in addition to the element
def each_with_index(which = :all) # :yield: e, row, column return to_enum(:each_with_index, which) unless block_given? last = column_size - 1 case which when :all @rows.each_with_index do |row, row_index| row.each_with_index do |e, col_index| yield e, row_index, col_index end end when :diagonal @rows.each_with_index do |row, row_index| yield row.fetch(row_index){return self}, row_index, row_index end when :off_diagonal @rows.each_with_index do |row, row_index| column_size.times do |col_index| yield row[col_index], row_index, col_index unless row_index == col_index end end when :lower @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 end end when :strict_lower @rows.each_with_index do |row, row_index| [row_index, column_size].min.times do |col_index| yield row[col_index], row_index, col_index end end when :strict_upper @rows.each_with_index do |row, row_index| (row_index+1).upto(last) do |col_index| yield row[col_index], row_index, col_index end end when :upper @rows.each_with_index do |row, row_index| row_index.upto(last) do |col_index| yield row[col_index], row_index, col_index end end else Matrix.Raise ArgumentError, "expected #{which.inspect} to be one of :all, :diagonal, :off_diagonal, :lower, :strict_lower, :strict_upper or :upper" end self end
def eigensystem
(v * d * v_inv).round(5) == m # => true
v.inv == v_inv # => true
d.diagonal? # => true
v, d, v_inv = m.eigensystem
m = Matrix[[1, 2], [3, 4]]
Returns the Eigensystem of the matrix; see +EigenvalueDecomposition+.
def eigensystem EigenvalueDecomposition.new(self) end
def elements_to_f
def elements_to_f warn "#{caller(1)[0]}: warning: Matrix#elements_to_f is deprecated, use map(&:to_f)" map(&:to_f) end
def elements_to_i
def elements_to_i warn "#{caller(1)[0]}: warning: Matrix#elements_to_i is deprecated, use map(&:to_i)" map(&:to_i) end
def elements_to_r
def elements_to_r warn "#{caller(1)[0]}: warning: Matrix#elements_to_r is deprecated, use map(&:to_r)" map(&:to_r) end
def empty?
or the number of columns is 0.
Returns +true+ if this is an empty matrix, i.e. if the number of rows
def empty? column_size == 0 || row_size == 0 end
def eql?(other)
def eql?(other) return false unless Matrix === other && column_size == other.column_size # necessary for empty matrices rows.eql? other.rows end
def hash
Returns a hash-code for the matrix.
def hash @rows.hash end
def hermitian?
def hermitian? Matrix.Raise ErrDimensionMismatch unless square? each_with_index(:strict_upper).all? do |e, row, col| e == rows[col][row].conj end end if 42.respond_to?(:conj)
def imaginary
0 0 0
=> 2i i 0
Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]].imaginary
1 2 3
=> 1+2i i 0
Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]]
Returns the imaginary part of the matrix.
def imaginary collect(&:imaginary) end
def index(*args)
Matrix[ [1,1], [1,1] ].index(1, :strict_lower) # => [1, 0]
Matrix[ [1,2], [3,4] ].index(&:even?) # => [0, 1]
It also accepts an optional +selector+ argument, see #each for details.
The index method is specialized to return the index as [row, column]
index(selector = :all) -> an_enumerator
index(selector = :all){ block } -> [row, column]
index(value, selector = :all) -> [row, column]
:call-seq:
def index(*args) 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 if args.size == 1 value = args.first each_with_index(which) do |e, row_index, col_index| return row_index, col_index if e == value end else each_with_index(which) do |e, row_index, col_index| return row_index, col_index if yield e end end nil end
def initialize(rows, column_size = rows[0].size)
Matrix.new is private; use Matrix.rows, columns, [], etc... to create.
def initialize(rows, column_size = rows[0].size) # No checking is done at this point. rows must be an Array of Arrays. # column_size must be the size of the first row, if there is one, # otherwise it *must* be specified and can be any integer >= 0 @rows = rows @column_size = column_size end
def inspect
Overrides Object#inspect
def inspect if empty? "#{self.class}.empty(#{row_size}, #{column_size})" else "#{self.class}#{@rows.inspect}" end end
def inverse
0 -1
=> -1 1
Matrix[[-1, -1], [0, -1]].inverse
Returns the inverse of the matrix.
def inverse Matrix.Raise ErrDimensionMismatch unless square? self.class.I(row_size).send(:inverse_from, self) end
def inverse_from(src) # :nodoc:
def inverse_from(src) # :nodoc: last = row_size - 1 a = src.to_a 0.upto(last) do |k| i = k akk = a[k][k].abs (k+1).upto(last) 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] 0.upto(last) do |ii| next if ii == k q = a[ii][k].quo(akk) a[ii][k] = 0 (k + 1).upto(last) do |j| a[ii][j] -= a[k][j] * q end 0.upto(last) do |j| @rows[ii][j] -= @rows[k][j] * q end end (k+1).upto(last) do |j| a[k][j] = a[k][j].quo(akk) end 0.upto(last) do |j| @rows[k][j] = @rows[k][j].quo(akk) end end self end
def lower_triangular?
Returns +true+ is this is a lower triangular matrix.
def lower_triangular? each(:strict_upper).all?(&:zero?) end
def lup
a.lup.solve([2, 5]) # => Vector[(1/1), (1/2)]
l * u == a * p # => true
p.permutation? # => true
u.upper_triangular? # => true
l.lower_triangular? # => true
l, u, p = a.lup
a = Matrix[[1, 2], [3, 4]]
Returns the LUP decomposition of the matrix; see +LUPDecomposition+.
def lup LUPDecomposition.new(self) end
def minor(*param)
row or column is greater than row_size or column_size respectively.
row or column (-1 is the last element). Returns nil if the starting
Like Array#[], negative indices count backward from the end of the
0 5 0
=> 9 0 0
Matrix.diagonal(9, 5, -3).minor(0..1, 0..2)
* row_range, col_range
* start_row, nrows, start_col, ncols; OR
Returns a section of the matrix. The parameters are either:
def minor(*param) case param.size when 2 row_range, col_range = param from_row = row_range.first from_row += row_size if from_row < 0 to_row = row_range.end to_row += row_size 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_size if from_col < 0 to_col = col_range.end to_col += column_size if to_col < 0 to_col += 1 unless col_range.exclude_end? size_col = to_col - from_col when 4 from_row, size_row, from_col, size_col = param return nil if size_row < 0 || size_col < 0 from_row += row_size if from_row < 0 from_col += column_size if from_col < 0 else Matrix.Raise ArgumentError, param.inspect end return nil if from_row > row_size || from_col > column_size || from_row < 0 || from_col < 0 rows = @rows[from_row, size_row].collect{|row| row[from_col, size_col] } new_matrix rows, [column_size - from_col, size_col].min end
def new_matrix(rows, column_size = rows[0].size) # :nodoc:
def new_matrix(rows, column_size = rows[0].size) # :nodoc: self.class.send(:new, rows, column_size) # bypass privacy of Matrix.new end
def normal?
Raises an error if matrix is not square.
Returns +true+ is this is a normal matrix.
def normal? Matrix.Raise ErrDimensionMismatch unless square? rows.each_with_index do |row_i, i| rows.each_with_index do |row_j, j| s = 0 rows.each_with_index do |row_k, k| s += row_i[k] * row_j[k].conj - row_k[i].conj * row_k[j] end return false unless s == 0 end end true end if 42.respond_to?(:conj)
def orthogonal?
Raises an error if matrix is not square.
Returns +true+ is this is an orthogonal matrix
def orthogonal? Matrix.Raise ErrDimensionMismatch unless square? rows.each_with_index do |row, i| column_size.times do |j| s = 0 row_size.times do |k| s += row[k] * rows[k][j] end return false unless s == (i == j ? 1 : 0) end end true end
def permutation?
Raises an error if matrix is not square.
Returns +true+ is this is a permutation matrix
def permutation? Matrix.Raise ErrDimensionMismatch unless square? cols = Array.new(column_size) rows.each_with_index do |row, i| found = false row.each_with_index do |e, j| if e == 1 return false if found || cols[j] found = cols[j] = true elsif e != 0 return false end end return false unless found end true end
def rank
=> 2
Matrix[[7,6], [3,9]].rank
Consider using exact types like Rational or BigDecimal instead.
because of their lack of precision.
Beware that using Float values can yield erroneous results
Returns the rank of the matrix.
def rank # We currently use Bareiss' multistep integer-preserving gaussian elimination # (see comments on determinant) a = to_a last_column = column_size - 1 last_row = row_size - 1 pivot_row = 0 previous_pivot = 1 0.upto(last_column) do |k| switch_row = (pivot_row .. last_row).find {|row| a[row][k] != 0 } if switch_row a[switch_row], a[pivot_row] = a[pivot_row], a[switch_row] unless pivot_row == switch_row pivot = a[pivot_row][k] (pivot_row+1).upto(last_row) do |i| ai = a[i] (k+1).upto(last_column) do |j| ai[j] = (pivot * ai[j] - ai[k] * a[pivot_row][j]) / previous_pivot end end pivot_row += 1 previous_pivot = pivot end end pivot_row end
def rank_e
deprecated; use Matrix#rank
def rank_e warn "#{caller(1)[0]}: warning: Matrix#rank_e is deprecated; use #rank" rank end
def real
1 2 3
=> 1 0 0
Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]].real
1 2 3
=> 1+2i i 0
Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]]
Returns the real part of the matrix.
def real collect(&:real) end
def real?
Returns +true+ if all entries of the matrix are real.
def real? all?(&:real?) end
def rect
m.rect == [m.real, m.imag] # ==> true for all matrices m
parts of the matrix
Returns an array containing matrices corresponding to the real and imaginary
def rect [real, imag] end
def regular?
Returns +true+ if this is a regular (i.e. non-singular) matrix.
def regular? not singular? end
def round(ndigits=0)
(see Float#round)
Returns a matrix with entries rounded to the given precision
def round(ndigits=0) map{|e| e.round(ndigits)} end
def row(i, &block) # :yield: e
an array). When a block is given, the elements of that vector are iterated.
Returns row vector number +i+ of the matrix as a Vector (starting at 0 like
def row(i, &block) # :yield: e if block_given? @rows.fetch(i){return self}.each(&block) self else Vector.elements(@rows.fetch(i){return nil}) end end
def row_size
Returns the number of rows.
def row_size @rows.size end
def row_vectors
Returns an array of the row vectors of the matrix. See Vector.
def row_vectors Array.new(row_size) {|i| row(i) } end
def singular?
Returns +true+ is this is a singular matrix.
def singular? determinant == 0 end
def square?
Returns +true+ is this is a square matrix.
def square? column_size == row_size end
def symmetric?
Raises an error if matrix is not square.
Returns +true+ is this is a symmetric matrix.
def symmetric? Matrix.Raise ErrDimensionMismatch unless square? each_with_index(:strict_upper) do |e, row, col| return false if e != rows[col][row] end true end
def to_a
Returns an array of arrays that describe the rows of the matrix.
def to_a @rows.collect(&:dup) end
def to_s
Overrides Object#to_s
def to_s if empty? "#{self.class}.empty(#{row_size}, #{column_size})" else "#{self.class}[" + @rows.collect{|row| "[" + row.collect{|e| e.to_s}.join(", ") + "]" }.join(", ")+"]" end end
def trace
=> 16
Matrix[[7,6], [3,9]].trace
Returns the trace (sum of diagonal elements) of the matrix.
def trace Matrix.Raise ErrDimensionMismatch unless square? (0...column_size).inject(0) do |tr, i| tr + @rows[i][i] end end
def transpose
2 4 6
=> 1 3 5
Matrix[[1,2], [3,4], [5,6]].transpose
5 6
3 4
=> 1 2
Matrix[[1,2], [3,4], [5,6]]
Returns the transpose of the matrix.
def transpose return self.class.empty(column_size, 0) if row_size.zero? new_matrix @rows.transpose, row_size end
def unitary?
Raises an error if matrix is not square.
Returns +true+ is this is a unitary matrix
def unitary? Matrix.Raise ErrDimensionMismatch unless square? rows.each_with_index do |row, i| column_size.times do |j| s = 0 row_size.times do |k| s += row[k].conj * rows[k][j] end return false unless s == (i == j ? 1 : 0) end end true end if 42.respond_to?(:conj)
def upper_triangular?
Returns +true+ is this is an upper triangular matrix.
def upper_triangular? each(:strict_lower).all?(&:zero?) end
def zero?
Returns +true+ is this is a matrix with only zero elements
def zero? all?(&:zero?) end