NAME
Sidef::Types::Array::Matrix - Mathematical matrix operations in Sidef
DESCRIPTION
This class implements a mathematical matrix type with support for standard linear algebra operations, including addition, subtraction, multiplication, division, inversion, determinants, and solving systems of linear equations. It's a subclass of Sidef::Types::Array::Array: a matrix is just an array of rows, each row itself an array of elements.
For "mul", "div", "mod", and to some extent "pow", the other operand being a matrix rather than a plain scalar changes the kind of operation performed, not just applies it per element -- see "MATRIX-SPECIFIC ARITHMETIC" below.
Several class-level constructors ("rows", "columns", "diagonal", "anti_diagonal") double as their own instance-level counterpart with a related but different meaning depending on whether they're called on the Matrix class itself or on an existing matrix -- see their individual entries.
SYNOPSIS
var A = Matrix(
[2, -3, 1],
[1, -2, -2],
[3, -4, 1],
)
var B = Matrix(
[9, -3, -2],
[3, -1, 7],
[2, -4, -8],
)
say(A + B) # matrix addition
say(A - B) # matrix subtraction
say(A * B) # matrix multiplication
say(A / B) # A * B^-1
say(A + 42) # matrix-scalar addition
say(A * 42) # matrix-scalar multiplication
say(A ** 20) # matrix exponentiation
say(A ** -1) # matrix inverse: A^-1
say(A ** -2) # (A^2)^-1
say(B.det) # matrix determinant
say(B.solve([1, 2, 3])) # solve a system of linear equations
# Create special matrices
var identity = Matrix.I(3) # 3x3 identity matrix
var zeros = Matrix.zero(2, 3) # 2x3 zero matrix
var random = Matrix.rand(3, 3) # 3x3 random matrix
# Matrix properties and operations
say(A.transpose) # transpose of A
say(A.row_count) # number of rows
say(A.col_count) # number of columns
say(A.is_square) # check if square matrix
say(A.diagonal) # extract diagonal elements
INHERITS
Inherits methods from Sidef::Types::Array::Array.
CONSTRUCTION AND SPECIAL MATRICES
new
Matrix(*rows)
Matrix.new(*rows)
Creates a new matrix from rows, where each element of rows is itself an array representing one row. Note that this constructor always blesses into the literal Sidef::Types::Array::Matrix package itself, regardless of the invocant -- it is not polymorphic.
say(Matrix.new([1, 2], [3, 4])) #=> Matrix([1, 2], [3, 4])
say(Matrix([1, 2], [3, 4])) # same as above
Aliases: call
identity
Matrix.identity(n)
Matrix.I(n)
Returns the nx<n> identity matrix (1s on the main diagonal, 0s elsewhere).
say(Matrix.I(3)) #=> Matrix([1, 0, 0], [0, 1, 0], [0, 0, 1])
Aliases: I
zero
Matrix.zero(n, m=n)
Returns an nx<m> matrix with every element set to 0. If m is omitted, returns a square nx<n> matrix.
say(Matrix.zero(2, 3)) #=> Matrix([0, 0, 0], [0, 0, 0])
say(Matrix.zero(2)) #=> Matrix([0, 0], [0, 0])
rand
Matrix.rand(n, m=n)
Returns an nx<m> matrix with every element set to an independent random value between 0 and 1. If m is omitted, returns a square nx<n> matrix.
say(Matrix.rand(2, 3)) # a 2x3 matrix of random values
scalar
Matrix.scalar(n, value)
Returns the nx<n> scalar matrix with value on the main diagonal and 0 elsewhere.
say(Matrix.scalar(3, 5)) #=> Matrix([5, 0, 0], [0, 5, 0], [0, 0, 5])
row_vector
Matrix.row_vector(*values)
Creates a 1xn matrix (a single row) from values.
say(Matrix.row_vector(1, 2, 3)) #=> Matrix([1, 2, 3])
column_vector
Matrix.column_vector(*values)
Creates an nx1 matrix (a single column) from values.
say(Matrix.column_vector(1, 2, 3)) #=> Matrix([1], [2], [3])
Aliases: col_vector
build
Matrix.build(n, m, block)
Matrix.build(n, block)
Returns an nx<m> matrix where the element at row i, column j is block(i, j). If only n and a block are given (no m), builds a square nx<n> matrix instead.
say(Matrix.build(3, 3, {|i, j| i + j })) #=> Matrix([0, 1, 2], [1, 2, 3], [2, 3, 4])
say(Matrix.build(3, {|i, j| i + j })) # same as above (square shorthand)
ELEMENT-WISE ARITHMETIC
These operators are always genuinely per-element, whether the other operand is a plain scalar (applied to every element) or another matrix (combined element-by-element with the corresponding entry).
add
self.add(other)
self + other
Adds other to every element (scalar), or adds corresponding elements together (matrix).
say(Matrix([1, 2], [3, 4]) + Matrix([5, 6], [7, 8])) #=> Matrix([6, 8], [10, 12])
say(Matrix([1, 2], [3, 4]) + 10) #=> Matrix([11, 12], [13, 14])
Aliases: operator +
sub
self.sub(other)
self - other
Subtracts other from every element (scalar), or subtracts corresponding elements (matrix).
say(Matrix([5, 6], [7, 8]) - Matrix([1, 2], [3, 4])) #=> Matrix([4, 4], [4, 4])
say(Matrix([5, 6], [7, 8]) - 5) #=> Matrix([0, 1], [2, 3])
Aliases: operator -
and
self.and(other)
self & other
Bitwise ANDs every element with other (scalar), or ANDs corresponding elements together (matrix).
say(Matrix([12, 8], [15, 7]) & 3) #=> Matrix([0, 0], [3, 3])
Aliases: operator &
or
self.or(other)
self | other
Bitwise ORs every element with other (scalar), or ORs corresponding elements together (matrix).
say(Matrix([8, 4], [2, 1]) | 1) #=> Matrix([9, 5], [3, 1])
Aliases: operator |
xor
self.xor(other)
self ^ other
Bitwise XORs every element with other (scalar), or XORs corresponding elements together (matrix).
say(Matrix([12, 8], [15, 7]) ^ 5) #=> Matrix([9, 13], [10, 2])
Aliases: operator ^
UNARY TRANSFORMATIONS
neg
self.neg
Returns a new matrix with every element negated.
say(Matrix([1, -2], [3, -4]).neg) #=> Matrix([-1, 2], [-3, 4])
abs
self.abs
Returns a new matrix with the absolute value of every element.
say(Matrix([-1, 2], [-3, -4]).abs) #=> Matrix([1, 2], [3, 4])
floor
self.floor
Returns a new matrix with every element rounded down to the nearest integer.
say(Matrix([1.7, 2.3], [3.8, 4.2]).floor) #=> Matrix([1, 2], [3, 4])
ceil
self.ceil
Returns a new matrix with every element rounded up to the nearest integer.
say(Matrix([1.2, 2.7], [3.1, 4.9]).ceil) #=> Matrix([2, 3], [4, 5])
MATRIX-SPECIFIC ARITHMETIC
mul
self.mul(other)
self * other
If other is a scalar, scales every element by it. If other is a matrix, returns the true matrix product of self and other (standard row-by-column multiplication) -- not an element-wise product.
say(Matrix([1, 2], [3, 4]) * 10) #=> Matrix([10, 20], [30, 40])
say(Matrix([1, 2], [3, 4]) * Matrix([5, 6], [7, 8])) #=> Matrix([19, 22], [43, 50])
Aliases: operator *
div
self.div(other)
self / other
If other is a scalar, divides every element by it. If other is a matrix, returns self * other.invert (self multiplied by the inverse of other) -- not an element-wise division.
say(Matrix([2, 4], [6, 8]) / 2) #=> Matrix([1, 2], [3, 4])
Aliases: operator /, operator ÷
mod
self.mod(other)
self % other
If other is a scalar, takes every element modulo it. If other is a matrix, returns self - other * (self / other).floor -- the matrix analogue of "modulo via floored division" (requires other to be square and invertible).
say(Matrix([5, 8], [10, 15]) % 3) #=> Matrix([2, 2], [1, 0])
Aliases: operator %
pow
self.pow(n)
self ** n
Returns self raised to the integer power n, computed by repeated squaring. n = 0 gives the identity matrix; a negative n is handled by computing the corresponding positive power first and inverting the result at the end (except n = -1, which returns "invert" directly, without a multiplication step).
say(Matrix([1, 1], [1, 0]) ** 5) # Fibonacci matrix, 5th power
say(Matrix([1, 2], [3, 4]) ** 0) #=> Matrix([1, 0], [0, 1])
say(Matrix([1, 2], [3, 4]) ** -1) # same as .invert
Aliases: operator **
STRUCTURE AND LAYOUT
transpose
self.transpose
Returns the transpose of the matrix (rows and columns swapped).
say(Matrix([1, 2, 3], [4, 5, 6]).transpose) #=> Matrix([1, 4], [2, 5], [3, 6])
Aliases: t, not
horizontal_flip
self.horizontal_flip
Returns a new matrix with every row reversed (a left-right mirror).
say(Matrix([1, 2, 3], [4, 5, 6]).horizontal_flip) #=> Matrix([3, 2, 1], [6, 5, 4])
vertical_flip
self.vertical_flip
Returns a new matrix with the order of its rows reversed (a top-bottom mirror).
say(Matrix([1, 2], [3, 4], [5, 6]).vertical_flip) #=> Matrix([5, 6], [3, 4], [1, 2])
flip
self.flip
Returns a new matrix that is both "horizontal_flip" and "vertical_flip" applied together -- a full 180-degree rotation, not the same thing as "vertical_flip" alone.
say(Matrix([1, 2], [3, 4], [5, 6]).flip) #=> Matrix([6, 5], [4, 3], [2, 1])
concat
self.concat(other)
If other is a matrix, returns a new matrix with each row of other appended to the end of the corresponding row of self (side-by-side concatenation). If the two matrices have different numbers of rows, only the shorter one's row count is used -- extra rows in the longer matrix are silently dropped. If other is a scalar, it's instead appended as one new extra column, with that same value in every row.
say(Matrix([1, 2], [3, 4]).concat(Matrix([5], [6]))) #=> Matrix([1, 2, 5], [3, 4, 6])
say(Matrix([1, 2], [3, 4]).concat(0)) #=> Matrix([1, 2, 0], [3, 4, 0])
set_row
self.set_row(k, row)
Replaces row k (0-indexed) of the matrix in place with row, and returns self.
var m = Matrix([1, 2], [3, 4])
m.set_row(0, [5, 6])
say(m) #=> Matrix([5, 6], [3, 4])
set_column
self.set_column(k, col)
Replaces column k (0-indexed) of the matrix in place with the values from col, and returns self.
var m = Matrix([1, 2], [3, 4])
m.set_column(0, [5, 6])
say(m) #=> Matrix([5, 2], [6, 4])
Aliases: set_col
ROWS, COLUMNS, AND DIAGONALS
is_square
self.is_square
Returns true if the matrix has the same number of rows as its first row has columns (an empty, 0x0 matrix counts as square), otherwise false. This does not verify that every row is actually the same length.
say(Matrix([1, 2], [3, 4]).is_square) #=> true
say(Matrix([1, 2, 3], [4, 5, 6]).is_square) #=> false
size
self.size
Returns the matrix's dimensions as two plain Number values, (rows, columns) -- not wrapped in a single Array. An empty matrix returns (0, 0).
var (rows, cols) = Matrix([1, 2, 3], [4, 5, 6]).size
say(rows) #=> 2
say(cols) #=> 3
row
self.row(n)
Returns row n (0-indexed) of the matrix, as a plain Array.
say(Matrix([1, 2, 3], [4, 5, 6]).row(0)) #=> [1, 2, 3]
Aliases: get_row
column
self.column(n)
Returns column n (0-indexed) of the matrix, as a plain Array.
say(Matrix([1, 2, 3], [4, 5, 6]).column(1)) #=> [2, 5]
Aliases: col, get_column
rows
self.rows
Matrix.rows(*rows)
Called on an existing matrix (any arguments given are ignored), returns every row as a plain array of arrays -- the same thing "to_array" returns. Called on the Matrix class itself, instead builds a new matrix out of rows, exactly like "new" does.
say(Matrix([1, 2], [3, 4]).rows) #=> [[1, 2], [3, 4]]
say(Matrix.rows([1, 2], [3, 4])) #=> Matrix([1, 2], [3, 4])
Aliases: from_rows
columns
self.columns
Matrix.columns(*columns)
Called on an existing matrix (any arguments given are ignored), returns every column as a plain array of arrays (i.e. self.transpose.to_array). Called on the Matrix class itself, instead builds a new matrix where each of columns becomes a column (rather than a row) of the result. If the given column arrays aren't all the same length, the shorter ones are padded with nil up to the longest one's length.
say(Matrix([1, 2], [3, 4]).columns) #=> [[1, 3], [2, 4]]
say(Matrix.columns([1, 2], [3, 4])) #=> Matrix([1, 3], [2, 4])
Aliases: cols, from_cols, from_columns
diagonal
self.diagonal
Matrix.diagonal(*values)
Called on an existing matrix, returns the elements on the main diagonal (top-left to bottom-right) as a plain Array. Called on the Matrix class itself, instead builds a new square matrix with values on the main diagonal and 0 elsewhere.
say(Matrix([1, 2, 3], [4, 5, 6], [7, 8, 9]).diagonal) #=> [1, 5, 9]
say(Matrix.diagonal(1, 2, 3)) #=> Matrix([1, 0, 0], [0, 2, 0], [0, 0, 3])
anti_diagonal
self.anti_diagonal
Matrix.anti_diagonal(*values)
Called on an existing matrix, returns the elements on the anti-diagonal (top-right to bottom-left) as a plain Array. Called on the Matrix class itself, instead builds a new square matrix with values placed along the anti-diagonal and 0 elsewhere.
say(Matrix([1, 2, 3], [4, 5, 6], [7, 8, 9]).anti_diagonal) #=> [3, 5, 7]
say(Matrix.anti_diagonal(1, 2, 3)) #=> Matrix([0, 0, 1], [0, 2, 0], [3, 0, 0])
vector_rows
self.vector_rows
Returns an Array of Vector objects, one per row of the matrix.
say(Matrix([1, 2], [3, 4]).vector_rows) #=> [Vector(1, 2), Vector(3, 4)]
Aliases: vec_rows
vector_columns
self.vector_columns
Returns an Array of Vector objects, one per column of the matrix.
say(Matrix([1, 2], [3, 4]).vector_columns) #=> [Vector(1, 3), Vector(2, 4)]
Aliases: vec_cols, vec_columns
row_count
self.row_count
Returns the number of rows in the matrix.
say(Matrix([1, 2, 3], [4, 5, 6]).row_count) #=> 2
Aliases: row_len, row_size
column_count
self.column_count
Returns the number of columns in the matrix (based on the first row's length; 0 for an empty matrix).
say(Matrix([1, 2, 3], [4, 5, 6]).column_count) #=> 3
Aliases: column_len, column_size, col_size, col_count, col_len
LINEAR ALGEBRA
determinant
self.determinant
Returns the determinant of the (square) matrix, computed via LU(P) decomposition.
say(Matrix([1, 2], [3, 4]).determinant) #=> -2
Aliases: det
det_bareiss
self.det_bareiss
Returns the determinant of the (square) matrix using the Bareiss algorithm, a fraction-free variant well suited to integer matrices.
say(Matrix([2, 3], [4, 5]).det_bareiss) #=> -2
invert
self.invert
Returns the inverse of the (square, invertible) matrix, via LU(P) decomposition and forward/back substitution.
say(Matrix([1, 2], [3, 4]).invert) #=> Matrix([-2, 1], [1.5, -0.5])
Aliases: inv, inverse
invmod
self.invmod(mod)
Returns "invert"'s result with every element reduced modulo mod.
say(Matrix([2, 1], [1, 1]).invmod(5)) #=> Matrix([1, 4], [4, 2])
powmod
self.powmod(n, mod)
Returns self raised to the integer power n, with every intermediate result reduced modulo mod along the way (modular matrix exponentiation, e.g. for computing large Fibonacci numbers via the Fibonacci matrix).
say(Matrix([1, 1], [1, 0]).powmod(10, 1000))
solve
self.solve(vector)
Solves the linear system self . x = vector for x, via LU(P) decomposition and forward/back substitution, and returns the solution as a plain Array.
say(Matrix([3, 2], [1, 2]).solve([7, 5])) #=> [1, 2]
gauss_jordan_solve
self.gauss_jordan_solve(vector)
Solves the linear system self . x = vector for x using Gauss-Jordan elimination, and returns the solution as a plain Array.
say(Matrix([2, 1], [1, 3]).gauss_jordan_solve([5, 6])) #=> [1.8, 1.4]
gauss_jordan_invert
self.gauss_jordan_invert
Returns the inverse of the (square, invertible) matrix, computed via Gauss-Jordan elimination instead of LU decomposition.
say(Matrix([1, 2], [3, 4]).gauss_jordan_invert) #=> Matrix([-2, 1], [1.5, -0.5])
rref
self.rref
Returns the reduced row echelon form of the matrix, via Gaussian elimination.
say(Matrix([2, 1, -1], [1, 3, 2]).rref)
Aliases: reduced_row_echelon_form
AGGREGATION
sum
self.sum(block)
Flattens every element of the matrix into a single list and returns the sum, or the sum of block applied to each element if given.
say(Matrix([1, 2], [3, 4]).sum {|x| x * x }) #=> 30
Aliases: sum_by
prod
self.prod(block)
Flattens every element of the matrix into a single list and returns the product, or the product of block applied to each element if given.
say(Matrix([1, 2], [3, 4]).prod {|x| x + 1 }) #=> 120
Aliases: prod_by
CONVERSION
to_array
self.to_array
Returns the matrix as a plain array of arrays (one array per row).
say(Matrix([1, 2], [3, 4]).to_array) #=> [[1, 2], [3, 4]]
Aliases: to_a
dump
self.dump
Returns a multi-line String representation of the matrix, with each row on its own indented line.
say(Matrix([1, 2], [3, 4]).dump)
# Matrix(
# [1, 2],
# [3, 4]
# )
Aliases: to_s, to_str
EXAMPLES
Creating matrices
var A = Matrix([1, 2], [3, 4])
var I = Matrix.I(3) # identity matrix
var Z = Matrix.zero(2, 3) # zero matrix
var R = Matrix.rand(3, 3) # random matrix
var S = Matrix.scalar(3, 5) # scalar matrix
var col = Matrix.column_vector(1, 2, 3)
var row = Matrix.row_vector(1, 2, 3)
var M = Matrix.build(3, 3, {|i, j| i * j })
var D = Matrix.diagonal(1, 2, 3) # class-level: builds a diagonal matrix
Arithmetic
var A = Matrix([1, 2], [3, 4])
var B = Matrix([5, 6], [7, 8])
say(A + B) # element-wise addition
say(A - B) # element-wise subtraction
say(A * B) # true matrix multiplication
say(A / B) # A * B.invert
say(A * 10) # scalar scaling
say(A + 5) # scalar shift
say(A ** 2) # A * A
say(A ** -1) # A.invert
Linear algebra
var A = Matrix([2, 1], [1, 3])
var b = [5, 6]
say(A.solve(b)) #=> [1.8, 1.4]
say(A.det) #=> 5
say(A.invert)
say(A.transpose)
Layout transformations
var M = Matrix([1, 2, 3], [4, 5, 6], [7, 8, 9])
say(M.vertical_flip) # rows reversed
say(M.horizontal_flip) # each row reversed
say(M.flip) # both -- a 180-degree rotation
say(M.diagonal) #=> [1, 5, 9]
say(M.anti_diagonal) #=> [3, 5, 7]
Modular matrix arithmetic
var A = Matrix([1, 2], [3, 4])
say(A.invmod(7)) # modular inverse
say(A.powmod(10, 100)) # modular exponentiation
REFERENCES
The LU(P) decomposition used internally by "determinant", "invert", "invmod", and "solve" is translated (with minor tweaks) from the C code example at https://en.wikipedia.org/wiki/LU_decomposition#C_code_examples.