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

SEE ALSO

Sidef::Types::Array::Array, Sidef::Types::Array::Vector