=encoding utf8

=head1 NAME

Sidef::Types::Array::Matrix - Mathematical matrix operations in Sidef

=head1 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 L<Sidef::Types::Array::Array>: a matrix is just an array of rows, each row itself an array of elements.

For L</mul>, L</div>, L</mod>, and to some extent L</pow>, the other operand being a matrix rather than a plain scalar changes the I<kind> of operation performed, not just applies it per element -- see L</MATRIX-SPECIFIC ARITHMETIC> below.

Several class-level constructors (L</rows>, L</columns>, L</diagonal>, L</anti_diagonal>) double as their own instance-level counterpart with a related but different meaning depending on whether they're called on the C<Matrix> class itself or on an existing matrix -- see their individual entries.

=head1 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

=head1 INHERITS

Inherits methods from L<Sidef::Types::Array::Array>.

=head1 CONSTRUCTION AND SPECIAL MATRICES

=head2 new

    Matrix(*rows)
    Matrix.new(*rows)

Creates a new matrix from C<rows>, where each element of C<rows> is itself an array representing one row. Note that this constructor always blesses into the literal C<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: I<call>

=cut

=head2 identity

    Matrix.identity(n)
    Matrix.I(n)

Returns the C<n>x<n> identity matrix (C<1>s on the main diagonal, C<0>s elsewhere).

    say(Matrix.I(3))   #=> Matrix([1, 0, 0], [0, 1, 0], [0, 0, 1])

Aliases: I<I>

=cut

=head2 zero

    Matrix.zero(n, m=n)

Returns an C<n>x<m> matrix with every element set to C<0>. If C<m> is omitted, returns a square C<n>x<n> matrix.

    say(Matrix.zero(2, 3))   #=> Matrix([0, 0, 0], [0, 0, 0])
    say(Matrix.zero(2))      #=> Matrix([0, 0], [0, 0])

=cut

=head2 rand

    Matrix.rand(n, m=n)

Returns an C<n>x<m> matrix with every element set to an independent random value between C<0> and C<1>. If C<m> is omitted, returns a square C<n>x<n> matrix.

    say(Matrix.rand(2, 3))   # a 2x3 matrix of random values

=cut

=head2 scalar

    Matrix.scalar(n, value)

Returns the C<n>x<n> scalar matrix with C<value> on the main diagonal and C<0> elsewhere.

    say(Matrix.scalar(3, 5))   #=> Matrix([5, 0, 0], [0, 5, 0], [0, 0, 5])

=cut

=head2 row_vector

    Matrix.row_vector(*values)

Creates a 1xn matrix (a single row) from C<values>.

    say(Matrix.row_vector(1, 2, 3))   #=> Matrix([1, 2, 3])

=cut

=head2 column_vector

    Matrix.column_vector(*values)

Creates an nx1 matrix (a single column) from C<values>.

    say(Matrix.column_vector(1, 2, 3))   #=> Matrix([1], [2], [3])

Aliases: I<col_vector>

=cut

=head2 build

    Matrix.build(n, m, block)
    Matrix.build(n, block)

Returns an C<n>x<m> matrix where the element at row C<i>, column C<j> is C<block(i, j)>. If only C<n> and a block are given (no C<m>), builds a square C<n>x<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)

=cut

=head1 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).

=head2 add

    self.add(other)
    self + other

Adds C<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 C<+>

=cut

=head2 sub

    self.sub(other)
    self - other

Subtracts C<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 C<->

=cut

=head2 and

    self.and(other)
    self & other

Bitwise ANDs every element with C<other> (scalar), or ANDs corresponding elements together (matrix).

    say(Matrix([12, 8], [15, 7]) & 3)   #=> Matrix([0, 0], [3, 3])

Aliases: operator C<&>

=cut

=head2 or

    self.or(other)
    self | other

Bitwise ORs every element with C<other> (scalar), or ORs corresponding elements together (matrix).

    say(Matrix([8, 4], [2, 1]) | 1)   #=> Matrix([9, 5], [3, 1])

Aliases: operator C<|>

=cut

=head2 xor

    self.xor(other)
    self ^ other

Bitwise XORs every element with C<other> (scalar), or XORs corresponding elements together (matrix).

    say(Matrix([12, 8], [15, 7]) ^ 5)   #=> Matrix([9, 13], [10, 2])

Aliases: operator C<^>

=cut

=head1 UNARY TRANSFORMATIONS

=head2 neg

    self.neg

Returns a new matrix with every element negated.

    say(Matrix([1, -2], [3, -4]).neg)   #=> Matrix([-1, 2], [-3, 4])

=cut

=head2 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])

=cut

=head2 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])

=cut

=head2 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])

=cut

=head1 MATRIX-SPECIFIC ARITHMETIC

=head2 mul

    self.mul(other)
    self * other

If C<other> is a scalar, scales every element by it. If C<other> is a matrix, returns the true B<matrix product> of C<self> and C<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 C<*>

=cut

=head2 div

    self.div(other)
    self / other

If C<other> is a scalar, divides every element by it. If C<other> is a matrix, returns C<self * other.invert> (C<self> multiplied by the inverse of C<other>) -- not an element-wise division.

    say(Matrix([2, 4], [6, 8]) / 2)   #=> Matrix([1, 2], [3, 4])

Aliases: operator C<E<sol>>, operator C<÷>

=cut

=head2 mod

    self.mod(other)
    self % other

If C<other> is a scalar, takes every element modulo it. If C<other> is a matrix, returns C<self - other * (self / other).floor> -- the matrix analogue of "modulo via floored division" (requires C<other> to be square and invertible).

    say(Matrix([5, 8], [10, 15]) % 3)   #=> Matrix([2, 2], [1, 0])

Aliases: operator C<%>

=cut

=head2 pow

    self.pow(n)
    self ** n

Returns C<self> raised to the integer power C<n>, computed by repeated squaring. C<n = 0> gives the identity matrix; a negative C<n> is handled by computing the corresponding positive power first and inverting the result at the end (except C<n = -1>, which returns L</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 C<**>

=cut

=head1 STRUCTURE AND LAYOUT

=head2 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: I<t>, I<not>

=cut

=head2 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])

=cut

=head2 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])

=cut

=head2 flip

    self.flip

Returns a new matrix that is both L</horizontal_flip> and L</vertical_flip> applied together -- a full 180-degree rotation, I<not> the same thing as L</vertical_flip> alone.

    say(Matrix([1, 2], [3, 4], [5, 6]).flip)   #=> Matrix([6, 5], [4, 3], [2, 1])

=cut

=head2 concat

    self.concat(other)

If C<other> is a matrix, returns a new matrix with each row of C<other> appended to the end of the corresponding row of C<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 C<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])

=cut

=head2 set_row

    self.set_row(k, row)

Replaces row C<k> (0-indexed) of the matrix in place with C<row>, and returns C<self>.

    var m = Matrix([1, 2], [3, 4])
    m.set_row(0, [5, 6])
    say(m)   #=> Matrix([5, 6], [3, 4])

=cut

=head2 set_column

    self.set_column(k, col)

Replaces column C<k> (0-indexed) of the matrix in place with the values from C<col>, and returns C<self>.

    var m = Matrix([1, 2], [3, 4])
    m.set_column(0, [5, 6])
    say(m)   #=> Matrix([5, 2], [6, 4])

Aliases: I<set_col>

=cut

=head1 ROWS, COLUMNS, AND DIAGONALS

=head2 is_square

    self.is_square

Returns C<true> if the matrix has the same number of rows as its first row has columns (an empty, 0x0 matrix counts as square), otherwise C<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

=cut

=head2 size

    self.size

Returns the matrix's dimensions as two plain C<Number> values, C<(rows, columns)> -- not wrapped in a single C<Array>. An empty matrix returns C<(0, 0)>.

    var (rows, cols) = Matrix([1, 2, 3], [4, 5, 6]).size
    say(rows)   #=> 2
    say(cols)   #=> 3

=cut

=head2 row

    self.row(n)

Returns row C<n> (0-indexed) of the matrix, as a plain C<Array>.

    say(Matrix([1, 2, 3], [4, 5, 6]).row(0))   #=> [1, 2, 3]

Aliases: I<get_row>

=cut

=head2 column

    self.column(n)

Returns column C<n> (0-indexed) of the matrix, as a plain C<Array>.

    say(Matrix([1, 2, 3], [4, 5, 6]).column(1))   #=> [2, 5]

Aliases: I<col>, I<get_column>

=cut

=head2 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 L</to_array> returns. Called on the C<Matrix> class itself, instead builds a I<new> matrix out of C<rows>, exactly like L</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: I<from_rows>

=cut

=head2 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. C<self.transpose.to_array>). Called on the C<Matrix> class itself, instead builds a I<new> matrix where each of C<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 C<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: I<cols>, I<from_cols>, I<from_columns>

=cut

=head2 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 C<Array>. Called on the C<Matrix> class itself, instead I<builds> a new square matrix with C<values> on the main diagonal and C<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])

=cut

=head2 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 C<Array>. Called on the C<Matrix> class itself, instead I<builds> a new square matrix with C<values> placed along the anti-diagonal and C<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])

=cut

=head2 vector_rows

    self.vector_rows

Returns an C<Array> of C<Vector> objects, one per row of the matrix.

    say(Matrix([1, 2], [3, 4]).vector_rows)   #=> [Vector(1, 2), Vector(3, 4)]

Aliases: I<vec_rows>

=cut

=head2 vector_columns

    self.vector_columns

Returns an C<Array> of C<Vector> objects, one per column of the matrix.

    say(Matrix([1, 2], [3, 4]).vector_columns)   #=> [Vector(1, 3), Vector(2, 4)]

Aliases: I<vec_cols>, I<vec_columns>

=cut

=head2 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: I<row_len>, I<row_size>

=cut

=head2 column_count

    self.column_count

Returns the number of columns in the matrix (based on the first row's length; C<0> for an empty matrix).

    say(Matrix([1, 2, 3], [4, 5, 6]).column_count)   #=> 3

Aliases: I<column_len>, I<column_size>, I<col_size>, I<col_count>, I<col_len>

=cut

=head1 LINEAR ALGEBRA

=head2 determinant

    self.determinant

Returns the determinant of the (square) matrix, computed via LU(P) decomposition.

    say(Matrix([1, 2], [3, 4]).determinant)   #=> -2

Aliases: I<det>

=cut

=head2 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

=cut

=head2 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: I<inv>, I<inverse>

=cut

=head2 invmod

    self.invmod(mod)

Returns L</invert>'s result with every element reduced modulo C<mod>.

    say(Matrix([2, 1], [1, 1]).invmod(5))   #=> Matrix([1, 4], [4, 2])

=cut

=head2 powmod

    self.powmod(n, mod)

Returns C<self> raised to the integer power C<n>, with every intermediate result reduced modulo C<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))

=cut

=head2 solve

    self.solve(vector)

Solves the linear system C<self . x = vector> for C<x>, via LU(P) decomposition and forward/back substitution, and returns the solution as a plain C<Array>.

    say(Matrix([3, 2], [1, 2]).solve([7, 5]))   #=> [1, 2]

=cut

=head2 gauss_jordan_solve

    self.gauss_jordan_solve(vector)

Solves the linear system C<self . x = vector> for C<x> using Gauss-Jordan elimination, and returns the solution as a plain C<Array>.

    say(Matrix([2, 1], [1, 3]).gauss_jordan_solve([5, 6]))   #=> [1.8, 1.4]

=cut

=head2 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])

=cut

=head2 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: I<reduced_row_echelon_form>

=cut

=head1 AGGREGATION

=head2 sum

    self.sum(block)

Flattens every element of the matrix into a single list and returns the sum, or the sum of C<block> applied to each element if given.

    say(Matrix([1, 2], [3, 4]).sum {|x| x * x })   #=> 30

Aliases: I<sum_by>

=cut

=head2 prod

    self.prod(block)

Flattens every element of the matrix into a single list and returns the product, or the product of C<block> applied to each element if given.

    say(Matrix([1, 2], [3, 4]).prod {|x| x + 1 })   #=> 120

Aliases: I<prod_by>

=cut

=head1 CONVERSION

=head2 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: I<to_a>

=cut

=head2 dump

    self.dump

Returns a multi-line C<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: I<to_s>, I<to_str>

=cut

=head1 EXAMPLES

=head2 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

=head2 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

=head2 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)

=head2 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]

=head2 Modular matrix arithmetic

    var A = Matrix([1, 2], [3, 4])

    say(A.invmod(7))          # modular inverse
    say(A.powmod(10, 100))    # modular exponentiation

=head1 REFERENCES

=over 4

=item * The LU(P) decomposition used internally by L</determinant>, L</invert>, L</invmod>, and L</solve> is translated (with minor tweaks) from the C code example at L<https://en.wikipedia.org/wiki/LU_decomposition#C_code_examples>.

=back

=head1 SEE ALSO

L<Sidef::Types::Array::Array>, L<Sidef::Types::Array::Vector>

=cut
