Clean

CLAS - Clean Linear Algebra Subroutines version 0.7

Download

Version history

.tar.gz

.zip

CLAS provides basic operations for numerical linear algebra. Its name and organization is inspired by BLAS (Basic Linear Algebra Subroutines), which exist for many imperative languages such as C and FORTRAN.

CLAS is (much like BLAS) organized in three levels/modules of increasing complexity and some additional modules.
The levels are

Importing one level, all lower levels will be imported as well, plus SampleVec and SampleMat.

At this time the CLAS functions are provided for full matrices only, defined as

:: Vector :== {# Real}
:: Matrix :== {# .Vector}

Sparse storage schemes are being investigated and will be included at a later time.

With version 0.7 slicing is introduced in module slicing with instructive examples in slicingExamples.


CLAS

Clas1

function name types/remarks
(+), (-) vector addition, subtraction {# a} {# a} -> {# a}

Due to a recursive instantiation, this function can also be used for matrices and tensors.
An instance for a of type Real gives: Vector Vector -> Vector,
an instance for a of type Vector gives: Matrix Matrix -> Matrix
etc.
(*), (/) Hadamard product, division {# a} {# a} -> {# a}

Due to a recursive instantiation, this function can also be used for matrices and tensors.
An instance for a of type Real gives: Vector Vector -> Vector,
an instance for a of type Vector gives: Matrix Matrix -> Matrix
etc.

The Hadamard product/division is not a very common operation.
Do not confuse it with the dot product.
dot dot (inner) product

.Vector .Vector -> Real

nrm1, nrm2, nrmInf 1-norm, 2-norm, infinity-norm

.Vector -> Real

(.*) scalar product Real a -> a

If a is inferred to be of type Vector: Real Vector -> Vector,
if a is inferred to be of type Matrix: Real Matrix -> Matrix
etc.
(/.) scalar division a Real -> a

If a is inferred to be of type Vector: Vector Real -> Vector,
if a is inferred to be of type Matrix: Matrix Real -> Matrix
etc.
givens Givens rotation

Real Real -> (Real, Real)

Idea: zero out the lower component of a 2-by-1 vector by means of a rotation with angle t:

Input is a and b, output is the pair of c = cos t and s = sin t.

swap swaps two elements of a unique vector *{# a} .Int .Int -> .{# a}

Clas2

function name types/remarks
(**) matrix vector product a .Vector -> .Vector

An instance has been defined for type Matrix.
transpose matrix transposition .Matrix -> .Matrix
perVec permutes a vector {# Int} .Vector -> .Vector
forwardSubst forward substitution Matrix Vector -> *Vector

Idea: Solve a linear system with a lower left triangular system, where the diagonal elements are all one.

BUG in 0.5: Please erase "\ l.[0,0]" at the end of the second line in the function forwardSubst in Clas2.icl
backwardSubst backward substitution Matrix Vector -> *Vector

Idea: Solve a linear system with an upper right triangular system.

Clas3

function name types/remarks
(***) matrix matrix product a a -> a

An instance has been defined for type Matrix
lu LU decomposition

*Matrix -> *Matrix

Idea: Decompose A into a lower left triangular matrix L and an upper right triangular matrix R with ones on the diagonal:

A will hereby be overwritten by both L and U. The diagonal of the result matrix will contain the diagonal elements of L, as the diagonal elements of U are know to be one.

luPartPiv LU decomposition with partial pivoting *Matrix -> (*Matrix, *{# Int})

Idea: During the LU decomposition rows are permuted such that the diagonal (or pivoting) element is the largest within the column. These permutations are stored in an array of integers for later reference. Thus a permutation of A is overwritten by both L and U: PA = LU.

Remark: Due to the search for the pivoting element partial pivoting is more expensive, but applicable to a much larger class of problems.
solveLS solves a general linear system

*Matrix .Vector -> .Vector

Idea: Solve Ax=b for x, where A is a general nonsingular matrix, by means of LU decomposition, followed by forward and backward substitution. Solving a linear system is then

Solving triangular systems can easily be done by forward and backward substitution.
Input is A and b, output is x.

solvePartPiv solves a general linear system

*Matrix .Vector -> .Vector

Same idea. Additionally we take care of the permutation P in the following manner:

invert Gauss-Jordan algorithm to invert a general matrix *Matrix -> .Matrix

Idea: Efficiently invert a matrix (if this is really needed - it is not, if you want to solve a linear system!).

SampleVec

function name types/remarks
uniVec creates a unique copy of a vector .Vector -> *Vector
zeros, ones, one2n creates zero-vector, one-vector, a vector containing 1,...,n Int -> .Vector

returns the vector of a given length
ek k-th unit vector Int Int -> .Vector

returns the k-th unit vector (i. e. the k-th element of the vector is one, all others are zero) of given length n
prettyRowVector, prettyColumnVector vector output Vector -> String

SampleMat

function name types/remarks
uniMat creates a unique copy of a matrix .Matrix -> *Matrix
zeroMatrix creates zero-matrix Int Int -> .Matrix

returns matrix of the given size (n columns, m rows)
Ik unit matrix Int -> .Matrix

returns the unit matrix of given size n-by-n (square matrix)
prettyMatrix matrix output Matrix -> String


Slicing

What is a slice?

In general a slice is a rectangular sub-array of a multidimensional array, a matrix in our case. In FORTRAN 90 this is referred to as a section.

What can they be used for?

Working with slices of a matrix rather than with the whole matrix is useful for many algorithms that manipulate matrix elements. It can lead to very concise and elegant code.

How are they defined in Clean?

In general slices can be defined by stating a lower and an upper bound of the slice for each dimension.

We implement the type of a slice as a constructor followed by two lists of integers, one for each dimension of a matrix.

:: Sli =  Sli ![Int] ![Int]

The integers in the lists indicate the row/column numbers of a given matrix, that we want to treat as a slice.

For example within a matrix a, Sli [i .. (i+l)] [j .. (j+k)] refers to:

But besides continuous submatrices of a matrix we can define parts of a matrix less restrictively with lists. Lists allow us

Here is an example: Within a matrix a, Sli [1,7,5,5,13] [1,2,7,1] refers to

Besides Sli there are three more types defined for the following case: If one (or both) of the lists has got only one entry, the yielded slice is one-dimensional and thus some part of a row or of a column or just a single element:

:: Row =  Row Int ![Int]
:: Col =  Col ![Int] Int
:: Ele =  Ele Int Int

We have to distinguish these one-dimensional slices from the "proper" slices, as operations change depending upon the dimension of the slices involved. We will see how later.

How can I work with slices?

Typically slice operations involve two or three slices. An obvious way to carry out this kind of operation is to cut slices out of their respective matrices (i. e. to copy them). Then the operation itself is performed, resulting in a new matrix or vector, which then has to be embedded into a matrix again.

For large matrices with almost equally large slices this becomes prohibitively expensive in both execution time and storage requirements, due to copying. Therefore we have to exploit uniqueness typing for the matrix we want to embed the resulting slice in. Luckily there are many algorithms in which all involved slices stem from the same matrix. Therefore we provide (among others) two families of functions named sliceXX(X) which take two and three slices along with one or two functions (that define the operation on the elements) and a unique matrix. The Xs indicate which kind of slice is involved and determines the operation. For example

sliceSS :: !Sli (Real Real -> Real) !Sli *Matrix -> *Matrix

We can use sliceSS for instance to add two slices and write its result to the first slice. In MATLAB this can be programmed as

A = ...;
A(1:3,1:3) = A(1:3,1:3) + A(4:6,4:6);

In Clean using CLAS it looks like this:

a :: .Matrix
a = ...
Start = sliceSS (Sli [1 .. 3] [1 .. 3]) (+) (Sli [1 .. 3] [4 .. 6]) a

This function overwrites the elements of the first slice with the (elementwise) sum of both slices:

Another very typical application is the outer product of a column and a row slice being add to a slice. In MATLAB again this can be coded as

A(k+1:n,k+1:n) = A(k+1:n,k+1:n) + A(k+1:n,k) * A(k,k+1:n);

In Clean using CLAS this is:

a :: .Matrix
a = ...
Start = sliceSCR (Sli [inc k .. n] [inc k .. n]) (+) (Col [inc k .. n] k) (*) (Row k [inc k .. n]) a

And again this is the effect on the matrix:

Two issues need special attention:

Which slicing functions are available?

This family of functions performs operations of the kind

a <- a * (b o c),

where a,b and c are slices and (*) and (o) are operations on their elements.

Due to the absence of multiple type classes, we have to define a function for each feasible combination of slice types. Along with our four types of slices (Sli, Row, Col, Ele) we also accept the type Vector and the type Matrix to be able to work with external references as well. So there are actually 63 = 216 combinations of types to fill in X, Y and Z in the function sliceXYZ. However not all these combinations are sensible (If you think otherwise, feel free to fill in your definition). The following table shows all combinations we want to provide: The entry at the crossing of a row (the Y) and a column (the Z) is the type of the result (the X):

sliceXYZ Sli Row Col Ele Matrix Vector
Sli Sli   Sli Sli  
Row   Row Ele Row   Row
Col Sli Col Col   Col
Ele Sli Row Col Ele    
Matrix Sli       Sli  
Vector   Row Col     Sli, Row,
Col, Ele

Let us find the entry of the previous example of an outer product: sliceSCR, i. e. Sli, Col and Row. For Col and Row we find Sli, which is the result type. Changing the order to Row and Col we find Ele. So there is another function called sliceERC. It computes some kind of dot product between a Row and a Col and writes it to an Ele.

In the case of two vectors the first letter determines, how the vectors are interpreted.The type Vector does not carry the information of a vector is a column or a row vector. For instance if the two vectors are one row and one column vector, the result is a Sli (sliceSVV), two column vectors result in a Col (sliceCVV).

a <- a * b,

where a and b are slices and (*) is an operation on their elements. Following our convention, that the first slice is used for the result, types Matrix and Vector are not allowed in the first position. The following table shows all defined functions. Notice that the result matches the first slice type.

sliceXY Sli Row Col Ele Matrix Vector
Sli Sli   Sli Sli  
Row   Row   Row   Row
Col Col Col Col   Col
Ele Ele    

All slicing operations are defined in Slicing, along with sample programs (for Gaussian LU, Householder QR and Givens QR decomposition) in SampleSlice.

Slicing

function name types/remarks
sliceXYZ Please refer to the text for more information. !X (Real Real -> Real) !Y (Real Real -> Real) !Z *Matrix -> *Matrix
sliceXY !X (Real Real -> Real) !Y *Matrix -> *Matrix
sliceMipX .Matrix X *Matrix -> *Matrix
imbedX X .Matrix *Matrix -> *Matrix
X .Vector *Matrix -> *Matrix
cutX X .Matrix -> Vector
X .Matrix -> .Matrix
transposeSlice (Sli, .Matrix) -> (Sli, .Matrix)

SampleSlicing

function name types/remarks
sliceGauss LU decomposition *Matrix -> *Matrix
sliceCholesky Cholesky decomposition

*Matrix -> *Matrix

Idea: Decompose a symmetric and positive definite matrix A into

A = L LT,

where L is a lower left triangular matrix L with positive diagonal entries.
Only the lower left part of the matrix A will be accessed (the matrix has to be symmetric) and overwritten by L.

solveLSP solve a least square problem

*Matrix .Vector -> .Vector

Idea: Besides solving linear systems the solution of least square problems is very important. They have the form

They can always be solved by applying the Gauss Normal Equation, which yields

ATAx = ATb,

where (ATA) is symmetric, which is a necessary property to do e. g. a LU decomposition or a Cholesky decomposition (which is a special case of the LU decomposition, where A=LTL).
Rather than first calculating ATA explicitly and then decomposing it, we can much better compute the QR decomposition of A. Hereby Q is an orthogonal matrix (QTQ = I) and R is an upper right triangular matrix:

As the Cholesky decomposition of a matrix is unique, we can see that the triangular matrix R is one factor of the Cholesky decomposition of ATA.
Using forward and backward substitution we solve two triangular linear systems

The QR decomposition can be done in many ways, Householder reflections and Givens rotations are two of them.

qrHouse QR decomposition by means of Householder reflections

*Matrix -> *Matrix

Householder reflections zero out parts of a matrix in the following columnwise fashion:

From the elements of the matrix indicated with a star the Householder reflection is computed using the function house and applied to the matrix.

The function qrHouse overwrites the given matrix with R.

house Householder reflection

.Vector -> .(Vector, Real)

Idea: For each non zero vector one can find an orthogonal operation that zeros out all elements except for the first element.

This operation is called a Householder reflection. In can be expressed by a matrix P, which is readily given by the so called Householder vector and a scalar:

The function house returns the Householder vector and the scalar as a pair.

qrGivens QR decomposition by means of Givens rotations *Matrix -> *Matrix

Givens rotations zero out single elements of a matrix (cf. function givens). This can be used for the QR decomposition to obtain an upper right triangular matrix.

The function qrGivens overwrites the given matrix with R.