<#Include SYSTEM "title.xml">
Introduction
Overview over this manual Chapter is concerned with the technical details of installing and running this package. Chapter answers the question why and how the &GAP; functionality concerning a sparse matrix type and gaussian algorithms was extended. The following chapters are concerned with the workings of the sparse matrix type () and sparse Gaussian algorithms (). Included is a documented list of the most important methods and functions needed to work with sparse matrices and the algorithms provided by the &Gauss; package. Anyone interested in source code should just check out the files in the gap/pkg/Gauss/gap/ folder (&see; Appendix ).
<#Include SYSTEM "install.xml"/>
Extending Gauss Functionality
The need for extended functionality &GAP; has a lot of functionality for row echelon forms of matrices. These can be called by SemiEchelonForm and similar commands. All of these work for the &GAP; matrix type over fields. However, these algorithms are not capable of computing a reduced row echelon form (RREF) of a matrix, there is no way to "Gauss upwards". While this is not neccessary for things like Rank or Kernel computations, this was one in a number of missing features important for the development of the &GAP; package &homalg; by M. Barakat .

Parallel to this development I worked on &SCO; , a package for creating simplicial sets and computing the cohomology of orbifolds, based on the paper "Simplicial Cohomology of Orbifolds" by I. Moerdijk and D. A. Pronk . Very early on it became clear that the cohomology matrices (with entries in &ZZ; or finite quotients of &ZZ;) would grow exponentially in size with the cohomology degree. At one point in time, for example, a 50651 x 1133693 matrix had to be handled.

It should be quite clear that there was a need for a sparse matrix data type and corresponding Gaussian algorithms. After an unfruitful search for a computer algebra system capable of this task, the &Gauss; package was born - to provide not only the missing RREF algorithms, but also support a new data type, enabling &GAP; to handle sparse matrices of almost arbritrary size.

I am proud to tell you that, thanks to optimizing the algorithms for matrices over GF(2), it was possible to compute the GF(2)-Rank of the matrix mentioned above in less than 20 minutes with a memory usage of about 3 GB.
The applications of the &Gauss; package algorithms Please refer to to find out more about the &homalg; project and its related packages. Most of the motivation for the algorithms in the &Gauss; package can be found there. If you are interested in this project, you might also want to check out my &GaussForHomalg; package, which, just as &RingsForHomalg; does for external Rings, serves as the connection between &homalg; and &Gauss;. By allowing &homalg; to delegate computational tasks to &Gauss; this small package extends &homalg;'s capabilities to dense and sparse matrices over fields and rings of the form &ZZ; / \langle p^n \rangle.

For those unfamiliar with the &homalg; project let me explain a couple of points. As outlined in by D. Robertz and M. Barakat homological computations can be reduced to three basic tasks:

Computing a row basis of a module (BasisOfRowModule). Reducing a module with a basis (DecideZeroRows). Compute the relations between module elements (SyzygiesGeneratorsOfRows). In addition to these tasks only relatively easy tools for matrix manipulation are needed, ranging from addition and multiplication to finding the zero rows in a matrix. However, to reduce the need for communication it might be helpful to supply &homalg; with some more advanced procedures.

While the above tasks can be quite difficult when, for example, working in noncommutative polynomial rings, in the &Gauss; case they can all be done as long as you can compute a Reduced Row Echelon Form. This is clear for BasisOfRowModule, as the rows of the RREF of the matrix are already a basis of the module. is used to compute RREFs, based on the &GAP; internal method SemiEchelonMat for Row Echelon Forms.

Lets look at the second point, the basic function DecideZeroRows: When you face the task of reducing a module A with a given basis B, you can compute the RREF of the following block matrix:

Id $\begin{array}{ccc} 1&\\ &\ddots&\\ &&1\\ \end{array}$ A
0 B

By computing the RREF (notice how important "Gaussing upwards" is here) A is reduced with B. However, the left side of the matrix just serves the single purpose of tricking the Gaussian algorithms into doing what we want. Therefore, it was a logical step to implement , which does the same thing but without needing unneccessary columns.

Note: When, much later, it became clear that it was important to compute the transformation matrices of the reduction, was born, similar to . This corresponds to the &homalg; procedure DecideZeroRowsEffectively.

The third procedure, SygygiesGeneratorsOfRows, is concerned with the relations between rows of a matrix, each row representing a module element. Over a field these relations are exactly the kernel of the matrix. One can easily see that this can be achieved by taking a matrix

A Id $\begin{array}{ccc} 1&\\ &\ddots&\\ &&1\\ \end{array}$

and computing its Row Echelon Form. Then the row relations are generated by the rows to the right of the zero rows of the REF. There are two problems with this approach: The computation diagonalizes the kernel, which might not be wanted, and, much worse, it does not work at all for rings with zero divisors. For example, the 1 \times 1 matrix [2 + 8&ZZ;] has a row relation [4 + 8&ZZ;] which would not have been found by this method.

Approaching this problem led to the method , which additionally computes the transformation matrix T, such that RREF = T \cdot M. Similar to SemiEchelonMatTransformation, T is split up into the rows needed to create the basis vectors of the RREF, and the relations that led to zero rows. Focussing on the computations over fields, it was an easy step to write , which terminates after the REF and returns the kernel generators.

The syzygy computation over &ZZ; / \langle p^n \rangle was solved by carefully keeping track of basis vectors with a zero-divising head. If, for v = (0,\ldots,0,h,*,\ldots,*), h \neq 0, there exists g \neq 0 such that g \cdot h = 0, the vector g \cdot v is regarded as an additional row vector which has to be reduced and can be reduced with. After some more work this allowed for the implementation of for matrices over &ZZ; / \langle p^n \rangle.

This concludes the explanation of the so-called basic tasks &Gauss; has to handle when called by &homalg; to do matrix calculations. Here is a tabular overview of the current capabilities of &Gauss; (p is a prime, n \in &NN;):

Matrix Type: Dense Dense Sparse Sparse Sparse
Base Ring: Field &ZZ; / \langle p^n \rangle Field GF(2) &ZZ; / \langle p^n \rangle
RankMat &GAP; n.a. + ++ n.a.
EchelonMat + - + ++ +
EchelonMatTransf. + - + ++ +
ReduceMat + - + ++ +
ReduceMatTransf. + - + ++ +
KernelMat + - + ++ +

As you can see, the development of hermite algorithms was not continued for dense matrices. There are two reasons for that: &GAP; already has very good algorithms for &ZZ;, and for small matrices the disadvantage of computing over &ZZ;, potentially leading to coefficient explosion, is marginal.
The Sparse Matrix Data Type
The inner workings of &Gauss; sparse matrices When doing any kind of computation there is a constant conflict between memory load and speed. On the one hand, memory usage is bounded by the total available memory, on the other hand, computation time should also not exceed certain proportions. Memory usage and CPU time are generally inversely proportional, because the computer needs more time to perform operations on a compactified data structure. The idea of sparse matrices mirrors exactly the need for less memory load, therefore it is natural that sparse algorithms take more time than dense ones. However, if the matrix is sufficiently large and sparse at the same time, sparse algorithms can easily be faster than dense ones while maintaining minimal memory load.

It should be noted that, although matrices that appear naturally in homological algebra are almost always sparse, they do not have to stay sparse under (R)REF algorithms, especially when the computation is concerned with transformation matrices. Therefore, in a perfect world there should be ways implemented to not only find out which data structure to use, but also at what point to convert from one to the other. This was, however, not the aim of the &Gauss; package and is just one of many points in which this package could be optimized or extended. Take a look at this matrix M:

0 0 2 9 0
0 5 0 0 0
0 0 0 1 0

The matrix M carries the same information as the following table, if and only if you know how many rows and columns the matrix has. There is also the matter of the base ring, but this is not important for now:

(i,j)Entry
(1,3)2
(1,4)9
(2,2)5
(3,4)1

This table relates each index tuple to its nonzero entry, all other matrix entries are defined to be zero. This only works for known dimensions of the matrix, otherwise trailing zero rows and columns could get lost (notice how the table gives no hint about the existence of a 5th column). To convert the above table into a sparse data structure, one could list the table entries in this way:

[ [ 1, 3, 2 ], [ 1, 4, 9 ], [ 2, 2, 5 ], [ 3, 4, 1 ] ]

However, this data structure would not be very efficient. Whenever you are interested in a row i of M (this happens all the time when performing Gaussian elimination) the whole list would have to be searched for 3-tuples of the form [ i, *, * ]. This is why I tried to manage the row index by putting the tuples into the corresponding list entry:

[ [ 3, 2 ], [ 4, 9 ] ],
[ [ 2, 5 ] ],
[ [ 4, 1 ] ] ]

As you can see, this looks fairly complicated. However, the same information can be stored in this form, which would become the final data structure for &Gauss; sparse matrices:

indices :=[ [ 3, 4 ],entries:=[ [ 2, 9 ],
[ 2 ], [ 5 ],
[ 4 ] ] [ 1 ] ]

Although now the number of rows is equal to the Length of both `indices' and `entries', it is still stored in the sparse matrix. Here is the full data structure (&see; ):

        DeclareRepresentation( "IsSparseMatrixRep",
             IsSparseMatrix, [ "nrows", "ncols", "indices", "entries", "ring" ] );

As you can see, the matrix stores its ring to be on the safe side. This is especially important for zero matrices, as there is no way to determine the base ring from the sparse matrix structure. For further information on sparse matrix construction and converting, refer to . A special case: GF(2)

        DeclareRepresentation( "IsSparseMatrixGF2Rep",
             IsSparseMatrix, [ "nrows", "ncols", "indices", "ring" ] );

Because the nonzero entries of a matrix over GF(2) are all "1", the entries of M are not stored at all. It is of course crucial that all operations and algorithms make 100% sure that all appearing zero entries are deleted from the `indices' as well as the `entries' list as they arise.
Methods and functions for sparse matrices <#Include Label="SparseMatrix"> <#Include Label="ConvertSparseMatrixToMatrix"> <#Include Label="CopyMat"> <#Include Label="GetEntry"> <#Include Label="SetEntry"> <#Include Label="AddToEntry"> <#Include Label="SparseZeroMatrix"> <#Include Label="SparseIdentityMatrix"> <#Include Label="TransposedSparseMat"> <#Include Label="CertainRows"> <#Include Label="CertainColumns"> <#Include Label="SparseUnionOfRows"> <#Include Label="SparseUnionOfColumns"> <#Include Label="SparseDiagMat"> <#Include Label="Nrows"> <#Include Label="Ncols"> <#Include Label="IndicesOfSparseMatrix"> <#Include Label="EntriesOfSparseMatrix"> <#Include Label="RingOfDefinition">
Gaussian Algorithms
A list of the available algorithms As decribed earlier, the main functions of &Gauss; are and , and , and, additionally . These are all documented in the next section, but of course rely on specific algorithms depending on the base ring of the matrix. These are not fully documented but it should be very easy to find out how they work based on the documentation of the main functions.

EchelonMat
Field: EchelonMatDestructive
Ring: HermiteMatDestructive
EchelonMatTransformation
Field: EchelonMatTransformationDestructive
Ring: HermiteMatTransformationDestructive
ReduceMat
Field: ReduceMatWithEchelonMat
Ring: ReduceMatWithHermiteMat
ReduceMatTransformation
Field: ReduceMatWithEchelonMatTransformation
Ring: ReduceMatWithHermiteMatTransformation
KernelMat
Field: KernelEchelonMatDestructive
Ring: KernelHermiteMatDestructive
Rank
Field (dense):Rank (&GAP; method)
Field (sparse):RankDestructive
GF(2) (sparse):RankOfIndicesListList
Ring: n.a.

Methods and Functions for &Gauss;ian algorithms <#Include Label="EchelonMat"> <#Include Label="EchelonMatTransformation"> <#Include Label="ReduceMat"> <#Include Label="ReduceMatTransformation"> <#Include Label="KernelMat"> <#Include Label="Rank">
An Overview of the &Gauss; package source code

The &Gauss; package files.
Filename Content
SparseMatrix.gi Definitions and methods for the sparse matrix type
SparseMatrixGF2.gi Special case GF(2): no matrix entries needed
GaussDense.gi Gaussian elmination for &GAP; matrices over fields
Sparse.gi Documentation and forking depending on the base ring
GaussSparse.gi Gaussian elimination for sparse matrices over fields
HermiteSparse.gi Hermite elimination for sparse matrices over &ZZ; / \langle p^n \rangle