Representing matrices

A matrix is simply a rectangular array of data arranged in rows and columns. Most programming languages, such as C# and Java, have direct support for rectangular arrays, while others, such as Clojure, use the heterogeneous array-of-arrays representation for rectangular arrays. Keep in mind that Clojure has no direct support for handling arrays, and an idiomatic Clojure code uses vectors to store and index an array of elements. As we will see later, a matrix is represented as a vector whose elements are the other vectors in Clojure.

Matrices also support several arithmetic operations, such as addition and multiplication, which constitute an important field of mathematics known as Linear Algebra. Almost every popular programming language has at least one linear algebra library. Clojure takes this a step ahead by letting us choose from several such libraries, all of which have a single standardized API interface that works with matrices.

The core.matrix library is a versatile Clojure library used to work with matrices. Core.matrix also contains a specification to handle matrices. An interesting fact about core.matrix is that while it provides a default implementation of this specification, it also supports multiple implementations. The core.matrix library is hosted and developed on GitHub at http://github.com/mikera/core.matrix.

Note

The core.matrix library can be added to a Leiningen project by adding the following dependency to the project.clj file:

[net.mikera/core.matrix "0.20.0"]

For the upcoming example, the namespace declaration should look similar to the following declaration:

(ns my-namespace
  (:use clojure.core.matrix))

Note that the use of :import to include library namespaces in Clojure is generally discouraged. Instead, aliased namespaces with the :require form are preferred. However, for the examples in the following section, we will use the preceding namespace declaration.

In Clojure, a matrix is simply a vector of vectors. This means that a matrix is represented as a vector whose elements are other vectors. A vector is an array of elements that takes near-constant time to retrieve an element, unlike a list that has linear lookup time. However, in the mathematical context of matrices, vectors are simply matrices with a single row or column.

To create a matrix from a vector of vectors, we use the following matrix function and pass a vector of vectors or a quoted list to it. Note that all the elements of the matrix are internally represented as a double data type (java.lang.Double) for added precision.

user> (matrix [[0 1 2] [3 4 5]])    ;; using a vector
[[0 1 2] [3 4 5]]
user> (matrix '((0 1 2) (3 4 5)))   ;; using a quoted list
[[0 1 2] [3 4 5]]

In the preceding example, the matrix has two rows and three columns, or is a 2 x 3 matrix to be more concise. It should be noted that when a matrix is represented by a vector of vectors, all the vectors that represent the individual rows of the matrix should have the same length.

The matrix that is created is printed as a vector, which is not the best way to visually represent it. We can use the pm function to print the matrix as follows:

user> (def A (matrix [[0 1 2] [3 4 5]]))
#'user/A
user> (pm A)
[[0.000 1.000 2.000]
 [3.000 4.000 5.000]]

Here, we define a matrix A, which is mathematically represented as follows. Note that the use of uppercase variable names is for illustration only, as all the Clojure variables are conventionally written in lowercase.

Representing matrices

The matrix A is composed of elements ai,j where i is the row index and j is the column index of the matrix. We can mathematically represent a matrix A using brackets as follows:

Representing matrices

We can use the matrix? function to check whether a symbol or variable is, in fact, a matrix. The matrix? function will return true for all the matrices that implement the core.matrix specification. Interestingly, the matrix? function will also return true for an ordinary vector of vectors.

The default implementation of core.matrix is written in pure Clojure, which does affect performance when handling large matrices. The core.matrix specification has two popular contrib implementations, namely vectorz-clj (http://github.com/mikera/vectorz-clj) that is implemented using pure Java and clatrix (http://github.com/tel/clatrix) that is implemented through native libraries. While there are several other libraries that implement the core.matrix specification, these two libraries are seen as the most mature ones.

Note

Clojure has three kinds of libraries, namely core, contrib, and third-party libraries. Core and contrib libraries are part of the standard Clojure library. The documentation for both the core and contrib libraries can be found at http://clojure.github.io/. The only difference between the core and contrib libraries is that the contrib libraries are not shipped with the Clojure language and have to be downloaded separately.

Third-party libraries can be developed by anyone and are made available via Clojars (https://clojars.org/). Leiningen supports all of the previous libraries and doesn't make much of a distinction between them.

The contrib libraries are often originally developed as third-party libraries. Interestingly, core.matrix was first developed as a third-party library and was later promoted to a contrib library.

The clatrix library uses the Basic Linear Algebra Subprograms (BLAS) specification to interface the native libraries that it uses. BLAS is also a stable specification of the linear algebra operations on matrices and vectors that are mostly used by native languages. In practice, clatrix performs significantly better than other implementations of core.matrix, and defines several utility functions used to work with matrices as well. You should note that matrices are treated as mutable objects by the clatrix library, as opposed to other implementations of the core.matrix specification that idiomatically treat a matrix as an immutable type.

For most of this chapter, we will use clatrix to represent and manipulate matrices. However, we can effectively reuse functions from core.matrix that perform matrix operations (such as addition and multiplication) on the matrices created through clatrix. The only difference is that instead of using the matrix function from the core.matrix namespace to create matrices, we should use the one defined in the clatrix library.

Note

The clatrix library can be added to a Leiningen project by adding the following dependency to the project.clj file:

[clatrix "0.3.0"]

For the upcoming example, the namespace declaration should look similar to the following declaration:

(ns my-namespace
  (:use clojure.core.matrix)
  (:require [clatrix.core :as cl]))

Keep in mind that we can use both the clatrix.core and clojure.core.matrix namespaces in the same source file, but a good practice would be to import both these namespaces into aliased namespaces to prevent naming conflicts.

We can create a matrix from the clatrix library using the following cl/matrix function. Note that clatrix produces a slightly different, yet more informative representation of the matrix than core.matrix. As mentioned earlier, the pm function can be used to print the matrix as a vector of vectors:

user> (def A (cl/matrix [[0 1 2] [3 4 5]]))
#'user/A
user> A
 A 2x3 matrix
 -------------
 0.00e+00  1.00e+00  2.00e+00 
 3.00e+00  4.00e+00  5.00e+00 
user> (pm A)
[[0.000 1.000 2.000]
 [3.000 4.000 5.000]]
nil

We can also use an overloaded version of the matrix function, which takes a matrix implementation name as the first parameter, and is followed by the usual definition of the matrix as a vector, to create a matrix. The implementation name is specified as a keyword. For example, the default persistent vector implementation is specified as :persistent-vector and the clatrix implementation is specified as :clatrix. We can call the matrix function by specifying this keyword argument to create matrices of different implementations, as shown in the following code. In the first call, we call the matrix function with the :persistent-vector keyword to specify the default persistent vector implementation. Similarly, we call the matrix function with the :clatrix keyword to create a clatrix implementation.

user> (matrix :persistent-vector [[1 2] [2 1]])
[[1 2] [2 1]]
user> (matrix :clatrix [[1 2] [2 1]])
 A 2x2 matrix
 -------------
 1.00e+00  2.00e+00 
 2.00e+00  1.00e+00

An interesting point is that the vectors of both vectors and numbers are treated as valid parameters for the matrix function by clatrix, which is different from how core.matrix handles it. For example, [0 1] produces a 2 x 1 matrix, while [[0 1]] produces a 1 x 2 matrix. The matrix function from core.matrix does not have this functionality and always expects a vector of vectors to be passed to it. However, calling the cl/matrix function with either [0 1] or [[0 1]] will create the following matrices without any error:

user> (cl/matrix [0 1])
 A 2x1 matrix
 -------------
 0.00e+00 
 1.00e+00 
user> (cl/matrix [[0 1]])
 A 1x2 matrix
 -------------
 0.00e+00  1.00e+00 

Analogous to the matrix? function, we can use the cl/clatrix? function to check whether a symbol or variable is a matrix from the clatrix library. While matrix? actually checks for an implementation of the core.matrix specification or protocol, the cl/clatrix? function checks for a specific type. If the cl/clatrix? function returns true for a particular variable, matrix? should return true as well; however, the converse of this axiom isn't true. If we call cl/clatrix? on a matrix created using the matrix function and not the cl/matrix function, it will return false; this is shown in the following code:

user> (def A (cl/matrix [[0 1]]))
#'user/A
user> (matrix? A)
true
user> (cl/clatrix? A)
true
user> (def B (matrix [[0 1]]))
#'user/B
user> (matrix? B)
true
user> (cl/clatrix? B)
false

Size is an important attribute of a matrix, and it often needs to be calculated. We can find the number of rows in a matrix using the row-count function. It's actually just the length of the vector composing a matrix, and thus, we can also use the standard count function to determine the row count of a matrix. Similarly, the column-count function returns the number of columns in a matrix. Considering the fact that a matrix comprises equally long vectors, the number of columns should be the length of any inner vector, or rather any row, of a matrix. We can check the return value of the count, row-count, and column-count functions on the following sample matrix in the REPL:

user> (count (cl/matrix [0 1 2]))
3
user> (row-count (cl/matrix [0 1 2]))
3
user> (column-count (cl/matrix [0 1 2]))
1

To retrieve an element from a matrix using its row and column indexes, use the following cl/get function. Apart from the matrix to perform the operation on, this function accepts two parameters as indexes to the matrix. Note that all elements are indexed relative to 0 in Clojure code, as opposed to the mathematical notation of treating 1 as the position of the first element in a matrix.

user> (def A (cl/matrix [[0 1 2] [3 4 5]]))
#'user/A
user> (cl/get A 1 1)
4.0
user> (cl/get A 3)
4.0

As shown in the preceding example, the cl/get function also has an alternate form where only a single index value is accepted as a function parameter. In this case, the elements are indexed through a row-first traversal. For example, (cl/get A 1) returns 3.0 and (cl/get A 3) returns 4.0. We can use the following cl/set function to change an element of a matrix. This function takes parameters similar to cl/get—a matrix, a row index, a column index, and lastly, the new element to be set in the specified position in the matrix. The cl/set function actually mutates or modifies the matrix it is supplied.

user> (pm A)
[[0.000 1.0002.000]
 [3.000 4.0005.000]]
nil
user> (cl/set A 1 2 0)
#<DoubleMatrix [0.000000, 1.000000, … , 0.000000]>
user> (pm A)
[[0.000 1.000 2.000]
 [3.000 4.000 0.000]]
nil

The clatrix library also provides two handy functions for functional composition: cl/map and cl/map-indexed. Both these functions accept a function and matrix as arguments and apply the passed function to each element in the matrix, in a manner that is similar to the standard map function. Also, both these functions return new matrices and do not mutate the matrix that they are supplied as parameters. Note that the function passed to cl/map-indexed should accept three arguments—the row index, the column index, and the element itself:

user> (cl/map-indexed 
      (fn [i j m] (* m 2)) A)
 A 2x3 matrix
 -------------
 0.00e+00  2.00e+00  4.00e+00 
 6.00e+00  8.00e+00  1.00e+01 
user> (pm (cl/map-indexed (fn [i j m] i) A))
[[0.000 0.000 0.000]
 [1.000 1.000 1.000]]
nil
user> (pm (cl/map-indexed (fn [i j m] j) A))
[[0.000 1.000 2.000]
 [0.000 1.000 2.000]]
nil
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.254.44