Francis Wright
Time-stamp: <2026-06-26 16:32:13 franc>
This package is currently under development and hence experimental.
It currently runs correctly on SBCL and PSL, but not on CSL revision 7366 or earlier. I hope this will be fixed soon!
A sparse matrix is a matrix in which most of the elements are zero. Common examples of sparse matrices are diagonal and band matrices. By contrast, if most of the elements are non-zero, the matrix is considered to be dense. Sparse matrices benefit from being stored using different data structures and manipulated using different algorithms from dense matrices. Whether it is more efficient to regard a matrix (or more likely a set of matrices) as dense or sparse is ill defined and probably depends on the context, so it may be determinable only by experiment, but it is reasonable to assume that in a sparse matrix no more than half the elements are nonzero. A common borderline case is triangular matrices. (Of course, a dense matrix can be treated as a sparse matrix, and vice versa, which is likely to be non-optimal but is useful for testing.)
The standard REDUCE MATRIX package implicitly assumes dense matrices. The SPARSEMATRIX package is a re-implementation of the MATRIX package to support sparse matrices. It uses hash-tables to store matrix elements, and the canonical form for a sparse matrix (henceforth referred to as sparse representation) is a LISP list of the form (<hash> <m> <n>), where <hash> is a hash-table, <m> is the number of rows (row dimension), and <n> is the number of columns (column dimension). Only nonzero elements should be stored in the hash-table and all missing elements are implicitly zero. By contrast, the canonical form for a dense matrix (henceforth referred to as dense representation) is a LISP list of rows of the form (<row_1> <row_2> ... <row_m>), where each <row_i> is a list of the matrix elements in the i-th row. The algorithms used in the SPARSEMATRIX package try to avoid accessing implicitly-zero (i.e. non-stored) matrix elements, whereas the algorithms used in the MATRIX package always run through all matrix elements. Results obtained using the MATRIX and SPARSEMATRIX packages should be identical (apart from memory use and time), and the test files compare the results of using them both.
In tests (on Steel Bank Common Lisp) using large sparse matrices (500×500 matrices with 1000 nonzero rational number elements – 0.4% density), the SPARSEMATRIX package is very much faster than the MATRIX package for addition and multiplication, and faster for inversion and determinant computation.
I wrote this package specifically to experiment with hash-table support in REDUCE on Common Lisp, and then ported it to PSL and CSL. The code naturally relies heavily on lexical scoping, which Common Lisp uses but Standard Lisp (hence PSL/CSL) does not, so I emulate it in PSL/CSL by declaring variables that should be lexically scoped to be fluid. I based the hash-table access on the functions provided by Common Lisp, which are different in PSL/CSL (and different between PSL and CSL), so I have implemented any missing Common Lisp functions that I need using what is available in PSL/CSL.
In the following description, the letters d and s represent variables to which matrices in respectively dense or sparse representation will be, or have been, assigned. Similarly, the letters i and j represent positive integers.
| Description | Dense matrix operation | Sparse matrix operation |
|---|---|---|
| Declaration | matrix d(i,j), ... |
sparse_matrix s(i,j), ... |
| Creation | d := mat(...) |
sparse_random_matrix s(i,j) |
In the following description, the letter m represents any valid matrix expression involving matrices using dense and/or sparse representation, and the letter s represents any valid matrix expression involving matrices using only sparse representation. (In other words, all standard REDUCE matrix operations are generic and accept any mixture of matrix types, but there are also special cases of some operators that accept only matrices in sparse representation, which are provided primarily to facilitate testing.)
| Description | Generic matrix operation |
|---|---|
| Element access | m(i,j) |
| Arithmetic | + - * / ^ |
| Inverse | m^(-1) |
| Dimensions | length m |
| Determinant | det m (or sparse_det s) |
| Eigenvectors | mateigen(m,id) (or sparse_mateigen(s,id)) |
| Transpose | tp m (or sparse_tp s) |
| Trace | trace m (or sparse_trace s) |
| Cofactors | cofactor(m,i,j) (or sparse_cofactor(s,i,j)) |
| Nullspace | nullspace m (or sparse_nullspace s) |
| Rank | rank m (or sparse_rank s) |
| Substitution | sub(<equations>, m) |
| Explicit mapping | map(1 + ~w, m) |
| Implicit mapping | <function> m |
| Density | matrix_density m |
Matrices in dense and sparse representation can be freely mixed in algebraic expressions, i.e. on either side of the binary operators + - * /. The type of an algebraic expression involving matrices in both representations currently defaults to dense, but can be changed by the operator sparse_matrix_auto_convert_type. If an argument is supplied then it must be one of the symbols dense, sparse or none, and the operator always returns the previous type. Hence, sparse_matrix_auto_convert_type(); just displays the current auto-conversion type, without changing it.
densifyconverts a matrix from sparse to dense representation, e.g.d := densify ssparsifyconverts a matrix from dense to sparse representation, e.g.s := sparsify d
Note that using explicit matrix type conversion in an expression disables implicit matrix type conversion for that expression.
If the switch sparse_matrix_dense_print is on, which it is by default, then small matrices in sparse representation are displayed the same as matrices in dense representation (mainly to facilitate testing), where small means having no more than the number of columns specified by the value of the (shared) variable sparse_matrix_dense_print_colmax, which is 10 by default.
See sparsematrix.rlg for examples of using the above SPARSEMATRIX versions of the MATRIX operators with small (dense) matrices and the files speed*.tst for some timed examples using large sparse matrices.
The first three of these predicates mirror predicates in the LINALG package.
sparse_matrix_p(cf.LINALGmatrixp)sparse_square_matrix_p(cf.LINALGsquarep)sparse_symmetric_matrix_p(cf.LINALGsymmetricp)sparse_skew_symmetric_matrix_psparse_hermitian_matrix_psparse_skew_hermitian_matrix_psparse_diagonal_matrix_psparse_upper_triangular_matrix_psparse_lower_triangular_matrix_psparse_identity_matrix_psparse_orthogonal_matrix_psparse_unitary_matrix_p
These predicates all take a single argument that can be anything. They return true if the argument evaluates to a sparse matrix with the property implied by the name of the predicate, and false otherwise. Currently, these predicates are exact and do not allow for numerical error in floating-point matrices! See sparsepredicates.rlg for examples of using the above predicates.
Some potentially useful facilities for working with sparse matrices modelled loosely on LINALG, the REDUCE Linear Algebra Package, by Matt Rebbeck.
sparse_identity_matrix(cf.LINALGmake_identity)sparse_band_matrix(cf.LINALGband_matrix, but with reversed arguments)
sparse_matrix_augment(cf.LINALGmatrix_augment)sparse_block_diagonal_matrix(cf.LINALGdiagonal)
sparse_select_columns(synonymsparse_augment_columns, cf.LINALGaugment_columns)sparse_remove_columns(cf.LINALGremove_columns)sparse_get_columns(cf.LINALGget_columns)
These operators are all more general than those in the LINALG package. The input matrices can use either sparse or dense representation, but the output always uses sparse representation. Arguments specifying column indices can be a sequence of integers, integer lists or integer intervals, which can be freely intermixed. Column indices can be negative, meaning count from the right, and intervals can be descending. The operator sparse_select_columns allows columns to be duplicated. See sparselinalg.rlg for examples of using the above SPARSEMATRIX versions of the LINALG operators.
There is already a REDUCE package called SPARSE to support sparse matrices, written by Stephen Scowcroft in 1995 at the Konrad-Zuse-Zentrum für Informationstechnik Berlin. It uses a different data structure to represent a sparse matrix, namely a LISP list of the form (<vector> (spm <m> <n>)), where <vector> is a vector of rows of the form [nil <row_1> <row_2> ... <row_m>] and each <row_i> is a list of the matrix elements in the i-th row, each represented as a dotted pair, of the form ((nil) (j_1 . val_1) (j_2 . val_2) ...). The nil elements are to allow for the fact that REDUCE indexes vectors and lists from 0, whereas matrix indices conventionally start from 1. Note that the SPARSEMATRIX and SPARSE packages are completely incompatible: use one of the other!
In tests using large sparse matrices (500×500 matrices with 1000 nonzero rational number elements – 0.4% density), the SPARSE and SPARSEMATRIX packages are broadly comparable in speed. However, the SPARSE package has a number of issues:
- Computing the determinant of a sparse 50*50 integer matrix with 100 nonzero elements fails with heap overflow.
- Inverses can only be computed for numerical matrices. Raising a matrix to the power 0 produces the inverse. Non-positive powers of matrices in products fail.
- The REDUCE operators
map,sub,cofactorandnullspaceare not supported. The aggregate property (that appropriate operators automatically map over a data structure) is not supported. - If matrices in sparse and dense representation are both used in an expression then the result appears always to use sparse representation. I think that the result should usually be a dense matrix [BUT THIS NEEDS CHECKING]!
- The
mateigenoperator is implemented only asspmateigen, rather than overloadingmateigen.
- Check predicates apply substitutions.
- Better argument checking.
- More efficient maphash-and on Common Lisp.
- Overall efficiency review -- avoid repeated simplifications, etc.
- Check timings on PSL and CSL.
- Support for special matrices -- triangular, symmetric, etc. -- via access functions.
- More operators from
LINALGpackage (maybe), e.g. row analogues of column manipulations, sub_matrix (i.e. both row nd column manipulation) - More operators from
SPARSEpackage (maybe). - Operators from
NORMFORMpackage (maybe).