defmodule ExAlgebra.Matrix do @moduledoc """ Functions that operate on matrices. Matrices are represented as lists of lists numbers, i.e. `[[number]]'. The rows are represented by the inner lists and the columns are represented by the elements of the inner lists. This means the length of the inner lists must all be the same. The input to these functions are not usually checked. For example, the number of rows and columns of a matrix. This is due to performance issues related to these operations. However, the library contains functions to help the user accomplish this task themselves. """ import :math, only: [pow: 2] alias ExAlgebra.Vector, as: Vector @doc """ Computes the rank of a matrix. Both the row rank and the column rank are returned as part of a map. ## Examples iex> ExAlgebra.Matrix.rank([[1, 2], [3, 4], [4, 3]]) %{rows: 3, columns: 2} """ @spec rank([[number]]) :: map def rank([first_row | _] = matrix) do %{rows: length(matrix), columns: length(first_row)} end @doc """ Computes the addition of two matrices. This is a new matrix with entries equal to the sum of the pair of matrices's corresponding entries. The input matrices should have the same rank: Since the time taken to compute the rank of a matrix increases by the product of its number of rows and number of columns we do not check the rank of the inputs before calculation. However, if the input matrices are of a different size, a clause match error will be thrown. ## Examples iex> ExAlgebra.Matrix.add([[1, 3, 1], [1, 0, 0]], [[0, 0, 5], [7, 5, 0]]) [[1, 3, 6], [8, 5, 0]] """ @spec add([[number]], [[number]]) :: [[number]] def add([], []), do: [] def add([a_first_row | a_remaining_rows], [b_first_row | b_remaining_rows]) do [Vector.add(a_first_row, b_first_row) | add(a_remaining_rows, b_remaining_rows)] end @doc """ Computes the subtraction of two matrices. This is a new matrix with entries equal to the difference of the pair of matrices's corresponding entries. The input matrices should have the same rank: Since the time taken to compute the rank of a matrix increases by the product of its number of rows and number of columns we do not check the rank of the inputs before calculation. However, if the input matrices are of a different size, a clause match error will be thrown. ## Examples iex> ExAlgebra.Matrix.subtract([[1, 3, 1], [1, 0, 0]], [[0, 0, 5], [7, 5, 0]]) [[1, 3, -4], [-6, -5, 0]] """ @spec subtract([[number]], [[number]]) :: [[number]] def subtract([], []), do: [] def subtract([a_first_row | a_remaining_rows], [b_first_row | b_remaining_rows]) do [Vector.subtract(a_first_row, b_first_row) | subtract(a_remaining_rows, b_remaining_rows)] end @doc """ Computes the multiple of a matrix by a scalar value. ## Examples iex> ExAlgebra.Matrix.scalar_multiply([[1, 3, 1], [1, 0, 0]] , 2.5) [[2.5, 7.5, 2.5], [2.5, 0.0, 0.0]] """ @spec scalar_multiply([[number]], number) :: [[number]] def scalar_multiply([], _scalar), do: [] def scalar_multiply(matrix, scalar) do matrix |> Enum.map(&Vector.scalar_multiply(&1, scalar)) end @doc """ Computes the transpose of a matrix. This is the matrix `At` built from the matrix `A` where the entries `Aij` have been mapped to `Aji`. ## Examples iex> ExAlgebra.Matrix.transpose([[1, 3, 1], [1, 0, 0]]) [[1, 1], [3, 0], [1, 0]] """ @spec transpose([[number]]) :: [[number]] def transpose(matrix) do matrix |> List.zip |> Enum.map(&Tuple.to_list(&1)) end @doc """ Computes the multiplication of two matrices. If the rank of matrix `A` is `n x m`, then the rank of matrix `B` must be `m x n`. Since the time taken to compute the rank of a matrix increases by the product of its number of rows and number of columns we do not check the rank of the inputs before calculation. However, if the input matrices are of a different size, a clause match error will be thrown. ## Examples iex> ExAlgebra.Matrix.multiply([[2, 3, 4], [1, 0, 0]], [[0, 1000], [1, 100], [0, 10]]) [[3, 2340], [0, 1000]] """ @spec multiply([[number]], [[number]]) :: [[number]] def multiply(matrix_one, matrix_two) do naive_multiply(matrix_one, transpose(matrix_two)) end @doc """ Returns the `(i, j)` submatrix of a matrix. This is the matrix with the row `i` and column `j` removed. ## Examples iex> ExAlgebra.Matrix.submatrix([[2, 3, 4], [1, 0, 0], [3, 4, 5]], 2, 3) [[2, 3], [3, 4]] """ @spec submatrix([[number]], number, number) :: [[number]] def submatrix(matrix, i, j) do matrix |> remove_row(i) |> remove_column(j) end @doc """ Removes the `ith` column of a matrix. ## Examples iex> ExAlgebra.Matrix.remove_column([[2, 3, 4], [1, 0, 0], [3, 4, 5]], 2) [[2, 4], [1, 0], [3, 5]] """ @spec remove_column([[number]], number) :: [[number]] def remove_column(matrix, column_to_remove) do matrix |> Enum.map(&(List.delete_at(&1, column_to_remove - 1))) end @doc """ Removes the `ith` row of a matrix. ## Examples iex> ExAlgebra.Matrix.remove_row([[2, 3, 4], [1, 0, 0], [3, 4, 5]], 2) [[2, 3, 4], [3, 4, 5]] """ @spec remove_row([[number]], number) :: [[number]] def remove_row(matrix, row_to_remove) do matrix |> List.delete_at(row_to_remove - 1) end @doc """ Computes the determinant of a matrix. This is computed by summing the cofactors of the matrix multiplied by corresponding elements of the first row. ## Examples iex> ExAlgebra.Matrix.det([[6, 1, 1], [4, -2, 5], [2, 8, 7]]) -306.0 """ @spec det([[number]]) :: number def det([[a]]), do: a def det([first_row | _] = matrix) do first_row |> Enum.with_index |> List.foldl(0, fn({row_element, row_index}, acc) -> acc + row_element * cofactor(matrix, 1, row_index + 1) end) end @doc """ Computes the `(i, j)` cofactor of a matrix. This is equal to the `(i, j)` minor of a matrix multiplied by `-1` raised to the power of `i + j`. ## Examples iex> ExAlgebra.Matrix.cofactor( [[2, 3, 4], [1, 0, 0], [3, 4, 5]], 1, 2) -5.0 """ @spec cofactor([[number]], number, number) :: number def cofactor(matrix, i, j), do: minor(matrix, i, j) * pow(-1, i + j) @doc """ Computes the `(i, j)` minor of a matrix. This is the determinant of a matrix whose `ith` row and `jth` column have been removed. ## Examples iex> ExAlgebra.Matrix.minor( [[2, 3, 4], [1, 0, 0], [3, 4, 5]], 1, 2) 5.0 """ @spec minor([[number]], number, number) :: number def minor(matrix, i, j), do: matrix |> submatrix(i, j) |> det @doc """ Computes the the trace of a matrix. This is the sum of the elements down the diagonal of a matrix. ## Examples iex> ExAlgebra.Matrix.trace([[6, 1, 1], [4, -2, 5], [2, 8, 7]]) 11 """ @spec trace([[number]]) :: number def trace(matrix) do matrix |> Enum.with_index |> Enum.map(fn({row, index}) -> Enum.at(row, index) end) |> Enum.sum end @spec naive_multiply([[number]], [[number]]) :: [[number]] defp naive_multiply(matrix_one, matrix_two) do matrix_one |> List.foldl([], fn(row, acc) -> acc ++ [matrix_two |> Enum.map(&Vector.dot(&1, row))] end) end end