If the number of rows and columns in a matrix are equal, then we term the matrix as a square matrix. We can easily generate a simple square matrix of size by using the repeat
function to repeat a single element as follows:
(defn square-mat "Creates a square matrix of size n x n whose elements are all e" [n e] (let [repeater #(repeat n %)] (matrix (-> e repeater repeater))))
In the preceding example, we define a closure to repeat a value n times, which is shown as the repeater
. We then use the thread macro (->
) to pass the element e
through the closure twice, and finally apply the matrix
function to the result of the thread macro. We can extend this definition to allow us to specify the matrix implementation to be used for the generated matrix; this is done as follows:
(defn square-mat "Creates a square matrix of size n x n whose elements are all e. Accepts an option argument for the matrix implementation." [n e & {:keys [implementation] :or {implementation :persistent-vector}}] (let [repeater #(repeat n %)] (matrix implementation (-> e repeater repeater))))
The square-mat
function is defined as one that accepts optional keyword arguments, which specify the matrix implementation of the generated matrix. We specify the default :persistent-vector
implementation of core.matrix as the default value for the :implementation
keyword.
Now, we can use this function to create square matrices and optionally specify the matrix implementation when required:
user> (square-mat 2 1) [[1 1] [1 1]] user> (square-mat 2 1 :implementation :clatrix) A 2x2 matrix ------------- 1.00e+00 1.00e+00 1.00e+00 1.00e+00
A special type of matrix that's used frequently is the identity matrix. An identity matrix is a square matrix whose diagonal elements are 1 and all the other elements are 0. We formally define an identity matrix as follows:
We can implement a function to create an identity matrix using the cl/map-indexed
function that we previously mentioned, as shown in the following code snippet. We first create a square matrix init
of size by using the previously defined square-mat
function, and then map all the diagonal elements to 1
using cl/map-indexed
:
(defn id-mat "Creates an identity matrix of n x n size" [n] (let [init (square-mat :clatrix n 0) identity-f (fn [i j n] (if (= i j) 1 n))] (cl/map-indexed identity-f init)))
The core.matrix library also has its own version of this function, named identity-matrix
:
user> (id-mat 5) A 5x5 matrix ------------- 1.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 1.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 1.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 1.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 1.00e+00 user> (pm (identity-matrix 5)) [[1.000 0.000 0.000 0.000 0.000] [0.000 1.000 0.000 0.000 0.000] [0.000 0.000 1.000 0.000 0.000] [0.000 0.000 0.000 1.000 0.000] [0.000 0.000 0.000 0.000 1.000]] nil
Another common scenario that we will encounter is the need to generate a matrix with random data. Let's implement the following function to generate a random matrix, just like the previously defined square-mat
function, using the rand-int
function. Note that the rand-int
function accepts a single argument n
, and returns a random integer between 0
and n
:
(defn rand-square-mat "Generates a random matrix of size n x n" [n] ;; this won't work (matrix (repeat n (repeat n (rand-int 100)))))
But this function produces a matrix whose elements are all single random numbers, which is not very useful. For example, if we call the rand-square-mat
function with any integer as its parameter, then it returns a matrix with a single distinct random number, as shown in the following code snippet:
user> (rand-square-mat 4) [[94 94] [94 94] [94 94] [94 94]]
Instead, we should map each element of the square matrix generated by the square-mat
function using the rand-int
function, to generate a random number for each element. Unfortunately, cl/map
only works with matrices created by the clatrix library, but we can easily replicate this behavior in Clojure using a lazy sequence, as returned by the repeatedly
function. Note that the repeatedly
function accepts the length of a lazily generated sequence and a function to be used as a generator for this sequence as arguments. Thus, we can implement functions to generate random matrices using the clatrix and core.matrix libraries as follows:
(defn rand-square-clmat "Generates a random clatrix matrix of size n x n" [n] (cl/map rand-int (square-mat :clatrix n 100))) (defn rand-square-mat "Generates a random matrix of size n x n" [n] (matrix (repeatedly n #(map rand-int (repeat n 100)))))
This implementation works as expected, and each element of the new matrix is now an independently generated random number. We can verify this in the REPL by calling the following modified rand-square-mat
function:
user> (pm (rand-square-mat 4)) [[97.000 35.000 69.000 69.000] [50.000 93.000 26.000 4.000] [27.000 14.000 69.000 30.000] [68.000 73.000 0.0007 3.000]] nil user> (rand-square-clmat 4) A 4x4 matrix ------------- 5.30e+01 5.00e+00 3.00e+00 6.40e+01 6.20e+01 1.10e+01 4.10e+01 4.20e+01 4.30e+01 1.00e+00 3.80e+01 4.70e+01 3.00e+00 8.10e+01 1.00e+01 2.00e+01
We can also generate a matrix of random elements using the cl/rnorm
function from the clatrix library. This function generates a matrix of normally distributed random elements with optionally specified mean and standard deviations. The matrix is normally distributed in the sense that all the elements are distributed evenly around the specified mean value with a spread specified by the standard deviation. Thus, a low standard deviation produces a set of values that are almost equal to the mean.
The cl/rnorm
function has several overloads. Let's examine a couple of them in the REPL:
user> (cl/rnorm 10 25 10 10) A 10x10 matrix --------------- -1.25e-01 5.02e+01 -5.20e+01 . 5.07e+01 2.92e+01 2.18e+01 -2.13e+01 3.13e+01 -2.05e+01 . -8.84e+00 2.58e+01 8.61e+00 4.32e+01 3.35e+00 2.78e+01 . -8.48e+00 4.18e+01 3.94e+01 ... 1.43e+01 -6.74e+00 2.62e+01 . -2.06e+01 8.14e+00 -2.69e+01 user> (cl/rnorm 5) A 5x1 matrix ------------- 1.18e+00 3.46e-01 -1.32e-01 3.13e-01 -8.26e-02 user> (cl/rnorm 3 4) A 3x4 matrix ------------- -4.61e-01 -1.81e+00 -6.68e-01 7.46e-01 1.87e+00 -7.76e-01 -1.33e+00 5.85e-01 1.06e+00 -3.54e-01 3.73e-01 -2.72e-02
In the preceding example, the first call specifies the mean, the standard deviation, and the number of rows and columns. The second call specifies a single argument n and produces a matrix of size . Lastly, the third call specifies the number of rows and columns of the matrix.
The core.matrix library also provides a compute-matrix
function to generate matrices, and will feel idiomatic to Clojure programmers. This function requires a vector that represents the size of the matrix, and a function that takes a number of arguments that is equal to the number of dimensions of the matrix. In fact, compute-matrix
is versatile enough to implement the generation of an identity matrix, as well as a matrix of randomly generated elements.
We can implement the following functions to create an identity matrix, as well as a matrix of random elements using the compute-matrix
function:
(defn id-computed-mat "Creates an identity matrix of size n x n using compute-matrix" [n] (compute-matrix [n n] #(if (= %1 %2) 1 0))) (defn rand-computed-mat "Creates an n x m matrix of random elements using compute-matrix" [n m] (compute-matrix [n m] (fn [i j] (rand-int 100))))