MyraMath
Tutorial for /sparse routines

Overview

The /sparse folder supplies containers, ranges and algorithms for matrices that are sparse, in the sense that most of their entries are zero-valued and not explicitly stored. The chief containers are Pattern and SparseMatrix, which store their contents in a compressed-sparse-column (CSC) format. CSC is compact and contiguous, which leads to reasonably efficient algorithms for e.g. sparse matrix-vector multiplication. However, the rigidity of this data structure also implies that Pattern and SparseMatrix are basically immutable, in that their nonzero entries must be explicitly provided at construction-time and you can never add any more. This is a drag for casual usage, so some helper classes (PatternBuilder, SparseMatrixBuilder) that do allow random access/mutable stencils are provided.

Following similar design idioms that were laid out in /dense, SparseMatrix has an associated interface type (SparseMatrixRange) which all of the algorithms in /sparse operate upon. Like MatrixRange, SparseMatrixRange provides chainable methods for arbitrary row/column slicing into shallow subviews. A new concept in /sparse is SparseMatrixIterator, which provides fine grained access for one-by-one traversals over all the structural nonzeroes in a SparseMatrixRange. SparseMatrixRange also provides the means for getting appropriately formatted "external data" into MyraMath routines.

Below are links to the sections of this page:

PatternBuilder and SparseMatrixBuilder

A Pattern is used to specify the nonzero stencil of a SparseMatrix, but the rigidity of these data structures makes both of them essentially immutable. Helper classes, called PatternBuilder and SparseMatrixBuilder, are provided to assist in their construction. PatternBuilder's are constructed empty, then populated with a variety of .insert() statements. The code excerpt below (tutorial/sparse/pattern.cpp) visualizes the process of constructing a Pattern using a PatternBuilder. Neither Pattern nor PatternBuilder carry any numeric data, only symbolic nonzeroes.

3 
7 
8 #include <tests/myratest.h>
9 
10 #include <iostream>
11 
12 using namespace myra;
13 
14 ADD_TEST("sparse_pattern","[sparse]") // int main()
15  {
16  // Make empty pattern of size 15x10
17  PatternBuilder P(15,10);
18  std::cout << "Insert a single nonzero:" << std::endl;
19  P.insert(6,3);
20  std::cout << "P = " << P << std::endl;
21  std::cout << "Insert multiple nonzeros on a row:" << std::endl;
22  P.insert_row(12,{1,3,4});
23  std::cout << "P = " << P << std::endl;
24  std::cout << "Insert multiple nonzeros on a column:" << std::endl;
25  P.insert_column({8,12,13,14},9);
26  std::cout << "P = " << P << std::endl;
27  std::cout << "Insert tensor product of nonzeros:" << std::endl;
28  P.insert_tensor({1,3,4,5},{5,6,8});
29  std::cout << "P = " << P << std::endl;
30  std::cout << "Insert symmetric tensor product of nonzeros:" << std::endl;
31  P.insert_tensor({1,2,3});
32  std::cout << "P = " << P << std::endl;
33  auto A = SparseMatrix<double>::fill(P.make_Pattern(),1.0);
34  A(6,3) = 2.0;
35  std::cout << "A = " << A << std::endl;
36  std::cout << "A.make_Matrix() = " << A.make_Matrix() << std::endl;
37  }
static SparseMatrix< Number > fill(const PatternRange &P, Number c)
Generates a SparseMatrix with Pattern P, filled with constant c.
Definition: SparseMatrix.cpp:521
Builder class for a sparse nonzero pattern, used in reordering/symbolic analysis. ...
General purpose compressed-sparse-column (CSC) container.
Definition: syntax.dox:1
General purpose dense matrix container, O(i*j) storage.
Like Pattern, but easier to populate via insert()/erase() methods.
Definition: PatternBuilder.h:31
Container class for a sparse nonzero pattern, used in reordering/symbolic analysis.
Interface class for representing subranges of contiguous int&#39;s.
Insert a single nonzero:
P = size 15 by 10 PatternBuilder:
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - x - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
Insert multiple nonzeros on a row:
P = size 15 by 10 PatternBuilder:
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - x - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - x - x x - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
Insert multiple nonzeros on a column:
P = size 15 by 10 PatternBuilder:
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - x - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - x ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - x - x x - - - - x ]
[ - - - - - - - - - x ]
[ - - - - - - - - - x ]
Insert tensor product of nonzeros:
P = size 15 by 10 PatternBuilder:
[ - - - - - - - - - - ]
[ - - - - - x x - x - ]
[ - - - - - - - - - - ]
[ - - - - - x x - x - ]
[ - - - - - x x - x - ]
[ - - - - - x x - x - ]
[ - - - x - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - x ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - x - x x - - - - x ]
[ - - - - - - - - - x ]
[ - - - - - - - - - x ]
Insert symmetric tensor product of nonzeros:
P = size 15 by 10 PatternBuilder:
[ - - - - - - - - - - ]
[ - x x x - x x - x - ]
[ - x x x - - - - - - ]
[ - x x x - x x - x - ]
[ - - - - - x x - x - ]
[ - - - - - x x - x - ]
[ - - - x - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - x ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
[ - x - x x - - - - x ]
[ - - - - - - - - - x ]
[ - - - - - - - - - x ]
A = size 15 by 10 SparseMatrix of double (29 nz):
A(:,j0) = 0 nz [ ]
A(:,j1) = 4 nz [ i1,1 i2,1 i3,1 i12,1 ]
A(:,j2) = 3 nz [ i1,1 i2,1 i3,1 ]
A(:,j3) = 5 nz [ i1,1 i2,1 i3,1 i6,2 i12,1 ]
A(:,j4) = 1 nz [ i12,1 ]
A(:,j5) = 4 nz [ i1,1 i3,1 i4,1 i5,1 ]
A(:,j6) = 4 nz [ i1,1 i3,1 i4,1 i5,1 ]
A(:,j7) = 0 nz [ ]
A(:,j8) = 4 nz [ i1,1 i3,1 i4,1 i5,1 ]
A(:,j9) = 4 nz [ i8,1 i12,1 i13,1 i14,1 ]
A.make_Matrix() = size 15 by 10 Matrix of double:
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 1 1 1 0 1 1 0 1 0 ]
[ 0 1 1 1 0 0 0 0 0 0 ]
[ 0 1 1 1 0 1 1 0 1 0 ]
[ 0 0 0 0 0 1 1 0 1 0 ]
[ 0 0 0 0 0 1 1 0 1 0 ]
[ 0 0 0 2 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 0 0 0 1 ]
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 1 0 1 1 0 0 0 0 1 ]
[ 0 0 0 0 0 0 0 0 0 1 ]
[ 0 0 0 0 0 0 0 0 0 1 ]

The last few lines created a SparseMatrix from the resulting PatternBuilder (with 1's for most of the numeric values), then examined the contents in a dense format. Separating the symbolic fill from the numeric fill is commonplace in finite element modeling, because the symbolic fill is cheap to compute and can be used to load balance (and synchronize) the more expensive numeric fill in parallel environments.

SparseMatrixBuilder can also be used to construct a SparseMatrix, but unlike Pattern and PatternBuilder it can hold numeric data too. It is constructed empty and then populated using "function call" syntax A(i,j)=value for each nonzero. When finished, a SparseMatrix can be extracted by calling the .make_SparseMatrix() method. The code excerpt below (tutorial/sparse/builder.cpp) uses a SparseMatrixBuilder to produce the exact same SparseMatrix as before:

4 
5 #include <tests/myratest.h>
6 
7 #include <iostream>
8 
9 using namespace myra;
10 
11 ADD_TEST("sparse_builder","[sparse]") // int main()
12  {
13  // Construct empty SparseMatrixBuilder.
15  // Populate every (i,j) entry as desired.
16  B(6,3) = 2.0;
17  for (int j : {1,3,4})
18  B(12,j) = 1.0;
19  for (int i : {8,12,13,14})
20  B(i,9) = 1.0;
21  for (int i : {1,3,4,5})
22  for (int j : {5,6,8})
23  B(i,j) = 1.0;
24  for (int i : {1,2,3})
25  for (int j : {1,2,3})
26  B(i,j) = 1.0;
27  // Bake into SparseMatrix, examine result.
28  SparseMatrix<double> A = B.make_SparseMatrix();
29  std::cout << "A = " << A.make_Matrix() << std::endl;
30  }
General purpose compressed-sparse-column (CSC) container.
Definition: syntax.dox:1
General purpose dense matrix container, O(i*j) storage.
Like SparseMatrix, but easier to populate via random access (i,j) operator.
Definition: SparseMatrix.h:32
Convenience type for building SparseMatrix&#39;s, allow random access without fussing with upfront constr...
Stores an IxJ matrix A in compressed sparse column format.
Definition: bothcat.h:23
Matrix< Number > make_Matrix() const
Accumulates *this onto a Matrix<Number>.
Definition: SparseMatrix.cpp:581
A = size 15 by 10 Matrix of double:
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 1 1 1 0 1 1 0 1 0 ]
[ 0 1 1 1 0 0 0 0 0 0 ]
[ 0 1 1 1 0 1 1 0 1 0 ]
[ 0 0 0 0 0 1 1 0 1 0 ]
[ 0 0 0 0 0 1 1 0 1 0 ]
[ 0 0 0 2 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 0 0 0 1 ]
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 0 0 0 0 ]
[ 0 1 0 1 1 0 0 0 0 1 ]
[ 0 0 0 0 0 0 0 0 0 1 ]
[ 0 0 0 0 0 0 0 0 0 1 ]

Filling a finite element system directly into a SparseMatrixBuilder is easier than the two phase serial-symbolic/parallel-numeric scheme outlined previously, but would be a strictly serial process. In both cases, PatternBuilder and SparseMatrixBuilder are playing similar roles as "precursor" ingredients for SparseMatrix and the choice between them is largely a matter of taste.

SparseMatrix and SparseMatrixRange

The listing below (tutorial/sparse/range.cpp) generates a test SparseMatrix and experiments with various SparseMatrixRange slicing operations. SparseMatrixRange supports the same suite of slices as the MatrixRange from /dense. Namely: window() for general slices, top(), bottom(), row(), rows() for row slicing, and left(), right(), column(), columns() for column slicing.

7 
9 
10 #include <tests/myratest.h>
11 
12 #include <iostream>
13 
14 using namespace myra;
15 
16 // For sake of example, the connectivity of laplacian2(4,4) is:
17 //
18 // 0---4---8--12
19 // | | | |
20 // 1---5---9--13
21 // | | | |
22 // 2---6--10--14
23 // | | | |
24 // 3---7--11--15
25 
26 ADD_TEST("sparse_range","[sparse]") // int main()
27  {
28  // Make a Matrix A, a 2D graph laplacian over a 4x4 structured grid ..
29  SparseMatrix<double> A = laplacian2<double>(4,4);
30  // .. inspect all of A ..
31  std::cout << "A = " << A << std::endl;
32  std::cout << "A.pattern() = " << A.pattern() << std::endl;
33  // .. inspect various "views" of A ..
34  std::cout << "A.left(10) = " << A.left(10).pattern() << std::endl;
35  std::cout << "A.top(5) = " << A.top(5).pattern() << std::endl;
36  std::cout << "A.bottom(6).right(6) = " << A.bottom(6).right(6).pattern() << std::endl;
37  // .. modify A through a range, try zeroing an entry ..
38  A.top(2) *= 2.0;
39  A(3,3) = 0.0;
40  std::cout << "A(3,3) = 0 .. " << A << std::endl;
41  // .. to get rid of a structural zero, have to explicitly use a Builder ..
43  B.erase(3,3);
44  A = B.make_SparseMatrix();
45  std::cout << "A.erase(3,3) .. " << A << std::endl;
46  }
Builder class for a sparse nonzero pattern, used in reordering/symbolic analysis. ...
General purpose compressed-sparse-column (CSC) container.
CSparseMatrixRange< Number > left(int j) const
Returns a SparseMatrixRange over the j leftmost columns, this(:,0:j)
Definition: SparseMatrix.cpp:417
Definition: syntax.dox:1
PatternRange pattern() const
Returns the nonzero Pattern over all of A(:,:)
Definition: SparseMatrix.cpp:221
General purpose dense matrix container, O(i*j) storage.
CSparseMatrixRange< Number > bottom(int i) const
Returns a SparseMatrixRange over the i bottommost rows, this(I-i:I,:)
Definition: SparseMatrix.cpp:379
Range/Iterator types associated with Pattern.
CSparseMatrixRange< Number > top(int i) const
Returns a SparseMatrixRange over the i topmost rows, this(0:i,:)
Definition: SparseMatrix.cpp:361
Container class for a sparse nonzero pattern, used in reordering/symbolic analysis.
Like SparseMatrix, but easier to populate via random access (i,j) operator.
Definition: SparseMatrix.h:32
Convenience type for building SparseMatrix&#39;s, allow random access without fussing with upfront constr...
Stores an IxJ matrix A in compressed sparse column format.
Definition: bothcat.h:23
Helper routines for reordering/filling 2D structured grids. Used by many unit tests.
A = size 16 by 16 SparseMatrix of double (64 nz):
A(:,j0) = 3 nz [ i0,3 i1,-1 i4,-1 ]
A(:,j1) = 4 nz [ i0,-1 i1,4 i2,-1 i5,-1 ]
A(:,j2) = 4 nz [ i1,-1 i2,4 i3,-1 i6,-1 ]
A(:,j3) = 3 nz [ i2,-1 i3,3 i7,-1 ]
A(:,j4) = 4 nz [ i0,-1 i4,4 i5,-1 i8,-1 ]
A(:,j5) = 5 nz [ i1,-1 i4,-1 i5,5 i6,-1 i9,-1 ]
A(:,j6) = 5 nz [ i2,-1 i5,-1 i6,5 i7,-1 i10,-1 ]
A(:,j7) = 4 nz [ i3,-1 i6,-1 i7,4 i11,-1 ]
A(:,j8) = 4 nz [ i4,-1 i8,4 i9,-1 i12,-1 ]
A(:,j9) = 5 nz [ i5,-1 i8,-1 i9,5 i10,-1 i13,-1 ]
A(:,j10) = 5 nz [ i6,-1 i9,-1 i10,5 i11,-1 i14,-1 ]
A(:,j11) = 4 nz [ i7,-1 i10,-1 i11,4 i15,-1 ]
A(:,j12) = 3 nz [ i8,-1 i12,3 i13,-1 ]
A(:,j13) = 4 nz [ i9,-1 i12,-1 i13,4 i14,-1 ]
A(:,j14) = 4 nz [ i10,-1 i13,-1 i14,4 i15,-1 ]
A(:,j15) = 3 nz [ i11,-1 i14,-1 i15,3 ]
A.pattern() = size 16 by 16 Pattern:
[ x x - - x - - - - - - - - - - - ]
[ x x x - - x - - - - - - - - - - ]
[ - x x x - - x - - - - - - - - - ]
[ - - x x - - - x - - - - - - - - ]
[ x - - - x x - - x - - - - - - - ]
[ - x - - x x x - - x - - - - - - ]
[ - - x - - x x x - - x - - - - - ]
[ - - - x - - x x - - - x - - - - ]
[ - - - - x - - - x x - - x - - - ]
[ - - - - - x - - x x x - - x - - ]
[ - - - - - - x - - x x x - - x - ]
[ - - - - - - - x - - x x - - - x ]
[ - - - - - - - - x - - - x x - - ]
[ - - - - - - - - - x - - x x x - ]
[ - - - - - - - - - - x - - x x x ]
[ - - - - - - - - - - - x - - x x ]
A.left(10) = size 16 by 10 Pattern:
[sliced]
[ x x - - x - - - - - ]
[ x x x - - x - - - - ]
[ - x x x - - x - - - ]
[ - - x x - - - x - - ]
[ x - - - x x - - x - ]
[ - x - - x x x - - x ]
[ - - x - - x x x - - ]
[ - - - x - - x x - - ]
[ - - - - x - - - x x ]
[ - - - - - x - - x x ]
[ - - - - - - x - - x ]
[ - - - - - - - x - - ]
[ - - - - - - - - x - ]
[ - - - - - - - - - x ]
[ - - - - - - - - - - ]
[ - - - - - - - - - - ]
A.top(5) = size 5 by 16 Pattern:
[sliced]
[ x x - - x - - - - - - - - - - - ]
[ x x x - - x - - - - - - - - - - ]
[ - x x x - - x - - - - - - - - - ]
[ - - x x - - - x - - - - - - - - ]
[ x - - - x x - - x - - - - - - - ]
A.bottom(6).right(6) = size 6 by 6 Pattern:
[sliced]
[ x x - - x - ]
[ x x - - - x ]
[ - - x x - - ]
[ - - x x x - ]
[ x - - x x x ]
[ - x - - x x ]
A(3,3) = 0 .. size 16 by 16 SparseMatrix of double (64 nz):
A(:,j0) = 3 nz [ i0,6 i1,-2 i4,-1 ]
A(:,j1) = 4 nz [ i0,-2 i1,8 i2,-1 i5,-1 ]
A(:,j2) = 4 nz [ i1,-2 i2,4 i3,-1 i6,-1 ]
A(:,j3) = 3 nz [ i2,-1 i3,0 i7,-1 ]
A(:,j4) = 4 nz [ i0,-2 i4,4 i5,-1 i8,-1 ]
A(:,j5) = 5 nz [ i1,-2 i4,-1 i5,5 i6,-1 i9,-1 ]
A(:,j6) = 5 nz [ i2,-1 i5,-1 i6,5 i7,-1 i10,-1 ]
A(:,j7) = 4 nz [ i3,-1 i6,-1 i7,4 i11,-1 ]
A(:,j8) = 4 nz [ i4,-1 i8,4 i9,-1 i12,-1 ]
A(:,j9) = 5 nz [ i5,-1 i8,-1 i9,5 i10,-1 i13,-1 ]
A(:,j10) = 5 nz [ i6,-1 i9,-1 i10,5 i11,-1 i14,-1 ]
A(:,j11) = 4 nz [ i7,-1 i10,-1 i11,4 i15,-1 ]
A(:,j12) = 3 nz [ i8,-1 i12,3 i13,-1 ]
A(:,j13) = 4 nz [ i9,-1 i12,-1 i13,4 i14,-1 ]
A(:,j14) = 4 nz [ i10,-1 i13,-1 i14,4 i15,-1 ]
A(:,j15) = 3 nz [ i11,-1 i14,-1 i15,3 ]
A.erase(3,3) .. size 16 by 16 SparseMatrix of double (63 nz):
A(:,j0) = 3 nz [ i0,6 i1,-2 i4,-1 ]
A(:,j1) = 4 nz [ i0,-2 i1,8 i2,-1 i5,-1 ]
A(:,j2) = 4 nz [ i1,-2 i2,4 i3,-1 i6,-1 ]
A(:,j3) = 2 nz [ i2,-1 i7,-1 ]
A(:,j4) = 4 nz [ i0,-2 i4,4 i5,-1 i8,-1 ]
A(:,j5) = 5 nz [ i1,-2 i4,-1 i5,5 i6,-1 i9,-1 ]
A(:,j6) = 5 nz [ i2,-1 i5,-1 i6,5 i7,-1 i10,-1 ]
A(:,j7) = 4 nz [ i3,-1 i6,-1 i7,4 i11,-1 ]
A(:,j8) = 4 nz [ i4,-1 i8,4 i9,-1 i12,-1 ]
A(:,j9) = 5 nz [ i5,-1 i8,-1 i9,5 i10,-1 i13,-1 ]
A(:,j10) = 5 nz [ i6,-1 i9,-1 i10,5 i11,-1 i14,-1 ]
A(:,j11) = 4 nz [ i7,-1 i10,-1 i11,4 i15,-1 ]
A(:,j12) = 3 nz [ i8,-1 i12,3 i13,-1 ]
A(:,j13) = 4 nz [ i9,-1 i12,-1 i13,4 i14,-1 ]
A(:,j14) = 4 nz [ i10,-1 i13,-1 i14,4 i15,-1 ]
A(:,j15) = 3 nz [ i11,-1 i14,-1 i15,3 ]

The native output from std::cout << A for a SparseMatrix is a little cryptic, but basically it prints all the nonzero entries, displaying them one column at a time. Recall that in CSC format, A(:,j) is stored contiguously so accessing/printing A by column is cheap. The last few lines of this example show that:

For the record, removing nonzeroes from SparseMatrix's by roundtripping in and out of a SparseMatrixBuilder is discouraged because it leads to excessive data movement. For best performance, try to organize/separate your sparse calculations into (i) a "populate" phase using PatternBuilder and/or SparseMatrixBuilder (ii) a single call to construct a SparseMatrix (iii) a "calculate" phase where you feed it into non-modifying downstream algorithms (like iterative or direct solvers).

SparseMatrixIterator

SparseMatrixRange's provide coarse grained access to large collections of nonzeroes. For fine-grained access to single nonzeroes, using a SparseMatrixIterator is preferred. Note that this is a little different from how /dense Matrix's and MatrixRange's work. To visit every nonzero of a dense Matrix, you'd typically use a double for loop over all (i,j) indices. But for a SparseMatrix, this is awkward and asymptotically inefficient because not every (i,j) pair maps to an actual nonzero. Though SparseMatrix and SparseMatrixRange provide a .pointer(i,j) method that can be used to "probe" for the presence of a nonzero, it would be preferable to somehow restructure the loops to "jump directly" from one nonzero to the next. This is what SparseMatrixIterator does. The code excerpt below (tutorial/sparse/iterator.cpp) demonstrates its usage.

3 
4 #include <tests/myratest.h>
5 
6 #include <iostream>
7 #include <cmath>
8 
9 using namespace myra;
10 
11 ADD_TEST("sparse_iterator","[sparse]") // int main()
12  {
13  // Make a random SparseMatrix A of decimals.
14  auto A = SparseMatrix<double>::random(3,4,10);
15  A.transform( [&](double a){return round(a*10.0)/10.0;} );
16  std::cout << "A = " << A.make_Matrix() << std::endl;
17  // Examine every entry of A using a double for loop.
18  // (awkward and not recommended)
19  std::cout << "A (nested-for) = { ";
20  for (int i = 0; i < A.size().first; ++i)
21  for (int j = 0; j < A.size().second; ++j)
22  if (A.pointer(i,j))
23  std::cout << "(" << i << "," << j << ")=" << *A.pointer(i,j) << " ";
24  std::cout << "}" << std::endl;
25  // Examine every entry of A using a SparseMatrixIterator.
26  // (idiomatic and recommended)
27  std::cout << "A (iterator) = { ";
28  for (auto aij = A.begin(); aij != A.end(); ++aij)
29  std::cout << "(" << aij.i() << "," << aij.j() << ")=" << aij.value() << " ";
30  std::cout << "}" << std::endl;
31  }
CSparseMatrixIterator< Number > end() const
Returns iterators over all of A(:,:)
Definition: SparseMatrix.cpp:323
const Number * pointer(int i, int j) const
Returns pointer to A(i,j), returns nullptr if structural zero.
Definition: SparseMatrix.cpp:292
void transform(const Functor &f)
Overwrites every A(i,j) in this SparseMatrix with f(A(i,j)).
Definition: SparseMatrix.h:299
static SparseMatrix< Number > random(int I, int J, int N)
Generates a random SparseMatrix with size IxJ and (approximately) N nonzeros.
Definition: SparseMatrix.cpp:493
General purpose compressed-sparse-column (CSC) container.
Definition: syntax.dox:1
General purpose dense matrix container, O(i*j) storage.
std::pair< int, int > size() const
Size inspector.
Definition: SparseMatrix.cpp:201
CSparseMatrixIterator< Number > begin() const
Returns iterators over all of A(:,:)
Definition: SparseMatrix.cpp:321
Matrix< Number > make_Matrix() const
Accumulates *this onto a Matrix<Number>.
Definition: SparseMatrix.cpp:581
A = size 3 by 4 Matrix of double:
[ 0.7 0.1 -0.9 0 ]
[ -0.3 0 0 0 ]
[ 0.4 -0.3 0.7 -0.8 ]
A (nested-for) = { (0,0)=0.7 (0,1)=0.1 (0,2)=-0.9 (1,0)=-0.3 (2,0)=0.4 (2,1)=-0.3 (2,2)=0.7 (2,3)=-0.8 }
A (iterator) = { (0,0)=0.7 (1,0)=-0.3 (2,0)=0.4 (0,1)=0.1 (2,1)=-0.3 (0,2)=-0.9 (2,2)=0.7 (2,3)=-0.8 }

It should come as little surprise that the iterator-based traversal reported the entries in column major order, because SparseMatrix is internally stored in CSC format. The iterator presents the nonzero through three accessor functions: the row/column indices i() and j(), and numeric value value() (which is actually mutable, if A is mutable). Many algorithms like sparse/frobenius.h, sparse/minmax.h, sparse/transpose.h, etc. are implemented in terms of SparseMatrixIterator's.

BLAS-like Routines

Compared to the dense BLAS, the sparse BLAS has (in this authors opinion) never really caught on. MyraMath does not bother to wrap or exploit any standard sparse BLAS routines, but instead provides a few routines that are BLAS-like in nature.

The first is a set of overloads in sparse/gemm.h to perform multiplication by a SparseMatrix. Like the dense gemm(), it performs a fused multiply-add update of the form C=alpha*op(A)*op(B)+beta*C for dense C. However, exactly one of its A or B operands is sparse. (So in effect it is either a sparse*dense multiply from the left, or a dense*sparse multiply from the right). This operation, applying a sparse matrix to a dense one, is the dominant calculation behind many iterative/krylov solution schemes. It is also required internally by MyraMath's direct solvers, for updating residuals inside iterative refinement routines. The following code excerpt (tutorial/sparse/gemm.cpp) demonstrates how to use the sparse version of gemm() and also checks it against the dense gemm() on an equivalent dense Matrix.

2 #include <myramath/sparse/gemm.h>
4 
6 #include <myramath/dense/gemm.h>
7 
8 #include <tests/myratest.h>
9 
10 #include <iostream>
11 
12 using namespace myra;
13 
14 ADD_TEST("sparse_gemm","[sparse]") // int main()
15  {
16  // Make sparse A and dense B, examine them.
17  SparseMatrix<double> A = laplacian2<double>(4,3);
18  Matrix<double> B(12,3);
19  for (int i = 0; i < 12; ++i)
20  for (int j = 0; j < 3; ++j)
21  B(i,j) = (i+1)*(j+1);
22  std::cout << "A = " << A.make_Matrix() << std::endl;
23  std::cout << "B = " << B << std::endl;
24  // Multiply A*B with sparse gemm(), and A.make_Matrix()*B with dense gemm()
25  Matrix<double> C1 = gemm(A,'N',B,'N');
26  Matrix<double> C2 = gemm(A.make_Matrix(),'N',B,'N');
27  // Examine results.
28  std::cout << "A*B (sparse) = " << C1 << std::endl;
29  std::cout << "A*B (dense) = " << C2 << std::endl;
30  }
31 
Tabulates an IxJ matrix. Allows random access, has column major layout to be compatible with BLAS/LAP...
Definition: bdsqr.h:20
Variety of routines for mixed dense*sparse or dense*sparse matrix multiplies. The dense*dense case is...
General purpose compressed-sparse-column (CSC) container.
Definition: syntax.dox:1
General purpose dense matrix container, O(i*j) storage.
Stores an IxJ matrix A in compressed sparse column format.
Definition: bothcat.h:23
Helper routines for reordering/filling 2D structured grids. Used by many unit tests.
Variety of routines all for dense Matrix*Matrix multiplies. Delegates to the BLAS.
Matrix< Number > make_Matrix() const
Accumulates *this onto a Matrix<Number>.
Definition: SparseMatrix.cpp:581
A = size 12 by 12 Matrix of double:
[ 3 -1 0 0 -1 0 0 0 0 0 0 0 ]
[ -1 4 -1 0 0 -1 0 0 0 0 0 0 ]
[ 0 -1 4 -1 0 0 -1 0 0 0 0 0 ]
[ 0 0 -1 3 0 0 0 -1 0 0 0 0 ]
[ -1 0 0 0 4 -1 0 0 -1 0 0 0 ]
[ 0 -1 0 0 -1 5 -1 0 0 -1 0 0 ]
[ 0 0 -1 0 0 -1 5 -1 0 0 -1 0 ]
[ 0 0 0 -1 0 0 -1 4 0 0 0 -1 ]
[ 0 0 0 0 -1 0 0 0 3 -1 0 0 ]
[ 0 0 0 0 0 -1 0 0 -1 4 -1 0 ]
[ 0 0 0 0 0 0 -1 0 0 -1 4 -1 ]
[ 0 0 0 0 0 0 0 -1 0 0 -1 3 ]
B = size 12 by 3 Matrix of double:
[ 1 2 3 ]
[ 2 4 6 ]
[ 3 6 9 ]
[ 4 8 12 ]
[ 5 10 15 ]
[ 6 12 18 ]
[ 7 14 21 ]
[ 8 16 24 ]
[ 9 18 27 ]
[ 10 20 30 ]
[ 11 22 33 ]
[ 12 24 36 ]
A*B (sparse) = size 12 by 3 Matrix of double:
[ -4 -8 -12 ]
[ -2 -4 -6 ]
[ -1 -2 -3 ]
[ 1 2 3 ]
[ 4 8 12 ]
[ 6 12 18 ]
[ 7 14 21 ]
[ 9 18 27 ]
[ 12 24 36 ]
[ 14 28 42 ]
[ 15 30 45 ]
[ 17 34 51 ]
A*B (dense) = size 12 by 3 Matrix of double:
[ -4 -8 -12 ]
[ -2 -4 -6 ]
[ -1 -2 -3 ]
[ 1 2 3 ]
[ 4 8 12 ]
[ 6 12 18 ]
[ 7 14 21 ]
[ 9 18 27 ]
[ 12 24 36 ]
[ 14 28 42 ]
[ 15 30 45 ]
[ 17 34 51 ]

There is also a set of overloads in sparse/trsm.h for solving triangular systems of the form op(A)*X=B or X*op(A)=B for sparse A, overwriting B with X. It still supports the same wide variety of options that dense trsm() supports: solving from either the 'L'eft or 'R'ight side, solving by either the 'U'pper or 'L'ower triangle of A, and applying a variety of modifiers (N,T,H,C) to A. The following code excerpt (tutorial/sparse/trsm.cpp) demonstrates how to use trsm() and also checks it against the dense trsm() on an equivalent dense Matrix.

3 #include <myramath/sparse/trsm.h>
4 
6 #include <myramath/dense/trsm.h>
7 #include <myramath/dense/tril.h>
8 #include <myramath/dense/gemm.h>
9 
10 #include <tests/myratest.h>
11 
12 #include <iostream>
13 
14 using namespace myra;
15 
16 ADD_TEST("sparse_trsm","[sparse]") // int main()
17  {
18  // Make sparse A and dense B, examine them.
19  SparseMatrix<double> A = laplacian2<double>(4,3);
20  Matrix<double> X(12,3);
21  for (int i = 0; i < 12; ++i)
22  for (int j = 0; j < 3; ++j)
23  X(i,j) = (i+1)*(j+1);
24  Matrix<double> B = tril(A.make_Matrix())*X;
25  std::cout << "A = " << A.make_Matrix() << std::endl;
26  std::cout << "B = " << B << std::endl;
27  // Solve tril(A)*X=B with sparse trsm(), and tril(A.make_Matrix()) with dense trsm()
28  Matrix<double> X1 = B;
29  trsm_inplace('L','L','N',A,X1);
30  Matrix<double> X2 = B;
31  trsm_inplace('L','L','N',A.make_Matrix(),X2);
32  // Examine results.
33  std::cout << "tril(A)\\B (sparse) = " << X1 << std::endl;
34  std::cout << "tril(A)\\B (dense) = " << X2 << std::endl;
35  }
Routines for backsolving by a triangular SparseMatrix.
Tabulates an IxJ matrix. Allows random access, has column major layout to be compatible with BLAS/LAP...
Definition: bdsqr.h:20
General purpose compressed-sparse-column (CSC) container.
Definition: syntax.dox:1
Routines for backsolving by a triangular Matrix or LowerMatrix.
Returns the lower triangle of a dense Matrix.
General purpose dense matrix container, O(i*j) storage.
Stores an IxJ matrix A in compressed sparse column format.
Definition: bothcat.h:23
Helper routines for reordering/filling 2D structured grids. Used by many unit tests.
Variety of routines all for dense Matrix*Matrix multiplies. Delegates to the BLAS.
Matrix< Number > make_Matrix() const
Accumulates *this onto a Matrix<Number>.
Definition: SparseMatrix.cpp:581
A = size 12 by 12 Matrix of double:
[ 3 -1 0 0 -1 0 0 0 0 0 0 0 ]
[ -1 4 -1 0 0 -1 0 0 0 0 0 0 ]
[ 0 -1 4 -1 0 0 -1 0 0 0 0 0 ]
[ 0 0 -1 3 0 0 0 -1 0 0 0 0 ]
[ -1 0 0 0 4 -1 0 0 -1 0 0 0 ]
[ 0 -1 0 0 -1 5 -1 0 0 -1 0 0 ]
[ 0 0 -1 0 0 -1 5 -1 0 0 -1 0 ]
[ 0 0 0 -1 0 0 -1 4 0 0 0 -1 ]
[ 0 0 0 0 -1 0 0 0 3 -1 0 0 ]
[ 0 0 0 0 0 -1 0 0 -1 4 -1 0 ]
[ 0 0 0 0 0 0 -1 0 0 -1 4 -1 ]
[ 0 0 0 0 0 0 0 -1 0 0 -1 3 ]
B = size 12 by 3 Matrix of double:
[ 3 6 9 ]
[ 7 14 21 ]
[ 10 20 30 ]
[ 9 18 27 ]
[ 19 38 57 ]
[ 23 46 69 ]
[ 26 52 78 ]
[ 21 42 63 ]
[ 22 44 66 ]
[ 25 50 75 ]
[ 27 54 81 ]
[ 17 34 51 ]
tril(A)\B (sparse) = size 12 by 3 Matrix of double:
[ 1 2 3 ]
[ 2 4 6 ]
[ 3 6 9 ]
[ 4 8 12 ]
[ 5 10 15 ]
[ 6 12 18 ]
[ 7 14 21 ]
[ 8 16 24 ]
[ 9 18 27 ]
[ 10 20 30 ]
[ 11 22 33 ]
[ 12 24 36 ]
tril(A)\B (dense) = size 12 by 3 Matrix of double:
[ 1 2 3 ]
[ 2 4 6 ]
[ 3 6 9 ]
[ 4 8 12 ]
[ 5 10 15 ]
[ 6 12 18 ]
[ 7 14 21 ]
[ 8 16 24 ]
[ 9 18 27 ]
[ 10 20 30 ]
[ 11 22 33 ]
[ 12 24 36 ]

This routine is used internally for solving by the triangular factors that result from incomplete LU and incomplete Cholesky factorizations. Additional routines of note are (i) sparse/trmm.h, for multiplying by a triangular SparseMatrix (ii) sparse/symm.h, for multiplying by a symmetric SparseMatrix and (iii) sparse/hemm.h, for multiplying by a hermitian SparseMatrix.

Appendix: Working with external data

Like MatrixRange in /dense, SparseMatrixRange also serves as the interface class for injecting external data into /sparse algorithms. This data must be stored in compressed-sparse-column (CSC) format, which requires 3 flat arrays. They are:

SparseMatrixRange has a constructor that accepts pointers to these arrays (and some size metadata), the resulting range can be passed into any of the /sparse algorithms. The code excerpt below (tutorial/sparse/external.cpp) shows an example of passing external data into the sparse gemm() algorithm.

2 
4 #include <myramath/sparse/gemm.h>
5 
6 #include <tests/myratest.h>
7 
8 #include <iostream>
9 
10 using namespace myra;
11 
12 // Performs sparse matrix * dense matrix multiplication,
13 // where all operands are Range types encapsulating end
14 // user "external data" (mocked here by C-style arrays)
15 //
16 // A * B = C
17 //
18 // [ 5 3 . . 2 ] [ 1 6 ] [ 36 4 ]
19 // [ 1 -1 . . . ] [ 7 -8 ] [ -6 14 ]
20 // [ . . 4 -3 . ] * [ 3 2 ] = [ 24 -1 ]
21 // [ . 2 . 1 -4 ] [ -4 3 ] [ -10 -9 ]
22 // [ 1 . . 2 . ] [ 5 -1 ] [ -7 12 ]
23 
24 ADD_TEST("sparse_external","[sparse]") // int main()
25  {
26  // Encoding A in compressed sparse column (CSC) format.
27  int A_strides[6] = {0,3,6,7,10,12};
28  int A_indices[12] = {0,1,4,0,1,3,2,2,3,4,0,3};
29  double A_values [12] = {5,1,1,3,-1,2,4,-3,1,2,2,-4};
30  SparseMatrixRange<double> A(A_strides,A_indices,A_values,5,5);
31  // Encoding B in column major format.
32  double B_values[10] = {1,7,3,-4,5,6,-8,2,3,-1};
33  MatrixRange<double> B(B_values,5,2,5);
34  // The C operand is initially all zeros.
35  double C_values[10] = {0,0,0,0,0,0,0,0,0,0};
36  MatrixRange<double> C(C_values,5,2,5);
37  // Examine inputs.
38  std::cout << "A = " << A.make_Matrix() << std::endl;
39  std::cout << "B = " << B << std::endl;
40  // Multiply C=A*B, examine result.
41  gemm_inplace(C,A,'N',B,'N');
42  std::cout << "C = " << C << std::endl;
43  // Examine output C_values.
44  std::cout << "C_values = { ";
45  for (auto c : C_values)
46  std::cout << c << " ";
47  std::cout << "}" << std::endl;
48  }
Represents a mutable SparseMatrixRange.
Definition: conjugate.h:21
Variety of routines for mixed dense*sparse or dense*sparse matrix multiplies. The dense*dense case is...
Definition: syntax.dox:1
Represents a mutable MatrixRange.
Definition: conjugate.h:26
General purpose dense matrix container, O(i*j) storage.
Range/Iterator types associated with SparseMatrix.
Matrix< Number > make_Matrix() const
Accumulates *this onto a Matrix<Number>.
Definition: SparseMatrix.cpp:581
A = size 5 by 5 Matrix of double:
[ 5 3 0 0 2 ]
[ 1 -1 0 0 0 ]
[ 0 0 4 -3 0 ]
[ 0 2 0 1 -4 ]
[ 1 0 0 2 0 ]
B = size 5 by 2 Matrix of double:
[ 1 6 ]
[ 7 -8 ]
[ 3 2 ]
[ -4 3 ]
[ 5 -1 ]
C = size 5 by 2 Matrix of double:
[ 36 4 ]
[ -6 14 ]
[ 24 -1 ]
[ -10 -9 ]
[ -7 12 ]
C_values = { 36 -6 24 -10 -7 4 14 -1 -9 12 }

The same technique will be used to get external datasets into MyraMath's sparse direct solvers.

Continue to Tutorial for /multifrontal solvers (part 1), or go back to API Tutorials