Linear Algebra and Tensors in Haskell

in

But hey, at least we get to use some fancy math terms like eigenvectors and singular values while coding.

Before anything else: what is linear algebra? Well, it’s the study of vector spaces and their transformations using matrices (which are just arrays with fancy names). And in Haskell, we can use libraries like `linear` or `vector-space` to perform these operations. But let’s not get too technical instead, let’s focus on how to actually write some code!

Let’s say you have a matrix (which is just a list of lists) and want to find its determinant. Here’s an example using `linear`:

-- Importing the necessary library for matrix operations
import Linear.Matrix

-- Defining a function to convert a list of lists to a matrix
fromLists :: [[Double]] -> Matrix Double
fromLists = fromRows . map fromIntegral

-- Defining a function to calculate the determinant of a matrix
determinant :: Matrix a => Matrix a -> Maybe (a, Int)
-- Checking if the matrix is a 1x1 matrix, in which case the determinant is just the single element
determinant m | n == 1    = Just $ product (map head (transpose (take n (rows m))))
-- For larger matrices, we use the following algorithm
               | otherwise = do
                   -- Calculating the determinant using the determinant' function
                   let det' = determinant' m
                   -- Calculating the sign of the determinant using the (-1)^n-1 formula
                       sign' = (-1)^(n-1)*product [i `mod` 2 | i <- [0..length (head m)]]
                   -- Returning the determinant and the size of the matrix as a tuple
                   return $ (det', length (head m))
               where n = rows m -- Defining n as the number of rows in the matrix
                     -- Defining a helper function to calculate the determinant of a submatrix
                     determinant' :: Matrix a => Matrix a -> Maybe (a, Int)
                     determinant' m = do
                         -- Calculating the submatrix using the matrixSubstitution function
                         let submatrix = matrixSubstitution m 0
                         -- Calculating the determinant of the submatrix using the determinant function
                             det = determinant submatrix
                         -- Calculating the sign of the determinant using the (-1)^n-1 formula
                             sign = (-1)^(n-1)*product [i `mod` 2 | i <- [0..length (head m)]]
                         -- Returning the determinant and the size of the submatrix as a tuple
                         return $ (det, length (head submatrix))

Wow, that’s a lot of code! But let me break it down for you. First, we define a helper function called `fromLists` which converts our input list-of-lists into a matrix using the `linear` library. Then, we have two functions: `determinant` and `determinant’`. The former is just a wrapper around the latter, while the latter does most of the heavy lifting.

The `determinant’` function uses recursion to calculate the determinant using Laplace expansion along the first row (which is why we need to keep track of the sign). We also use another helper function called `matrixSubstitution` which removes a given row and column from our matrix.

Now, tensors! Tensors are just fancy arrays with multiple dimensions think of them as matrices that can have more than two dimensions (which is why they’re sometimes called n-dimensional arrays). In Haskell, we can use the `vector-space` library to work with tensors.

Let’s say you want to calculate the dot product between two 3D vectors using `vector-space`. Here’s an example:

-- Import the VectorSpace library to work with tensors
import VectorSpace

-- Define a function to convert a list of Doubles into a Vector
fromList :: [Double] -> V Double
-- Use the fromLists function from the VectorSpace library to convert the list into a Vector
fromList = fromLists . map fromIntegral

-- Define a function to calculate the dot product between two Vectors
dotProduct :: (Vector v, Num a) => V v a -> V v a -> Maybe (a, Int)
-- Check if the Vectors have a dimension of 1
dotProduct x y | n == 1    = Just $ product (zipWith (*) (take n x) (take n y))
-- If the Vectors have a dimension greater than 1, use the dotProduct' function
               | otherwise = do
                   let dot' = dotProduct' x y
                   -- Calculate the sign of the dot product using the (-1)^(n-1) formula
                       sign' = (-1)^(n-1)*product [i `mod` 2 | i <- [0..length (head x)]]
                   -- Return the dot product and the length of the first Vector
                   return $ (dot', length (head x))
               where n = dim x
                     -- Define a helper function to calculate the dot product recursively
                     dotProduct' :: forall v. (Vector v, Num a) => V v a -> V v a -> Maybe (a, Int)
                     dotProduct' x y = do
                         -- Use the vectorSubstitution function to get the sub-vectors of x and y
                         let subVectors = vectorSubstitution x 0
                         -- Calculate the dot product of the sub-vectors
                             dot = dotProduct' subVectors (vectorSubstitution y 0)
                         -- Calculate the sign of the dot product using the (-1)^(n-1) formula
                             sign = (-1)^(n-1)*product [i `mod` 2 | i <- [0..length (head x)]]
                         -- Return the dot product and the length of the sub-vectors
                         return $ (dot, length (head subVectors))

Again, we have a helper function called `fromList` which converts our input list into a vector using the `vector-space` library. Then, we define two functions: `dotProduct` and `dotProduct’`. The former is just a wrapper around the latter, while the latter does most of the heavy lifting.

The `dotProduct’` function uses recursion to calculate the dot product between our vectors using Laplace expansion along the first dimension (which is why we need to keep track of the sign). We also use another helper function called `vectorSubstitution` which removes a given vector from our space.

And that’s it! You now know how to perform linear algebra and tensor operations in Haskell using fancy math terms like determinants and dot products. Who needs sliced bread when you have this kind of excitement?

SICORPS