Accessing Rows and Columns
Matrices are two-dimensional tables. Numerics.NET includes classes that allow you to work with the rows and columns of a matrix as vectors. In addition, these classes allow you to easily navigate through a matrix.
Accessing individual elements
The Matrix<T> class has an Item[Int32, Int32] property. The first index is the zero-based row index. The second index is the zero-based column index. In languages that don't support indexers, you can use the indexed Item property. If the index is greater than or equal to the number of rows or columns, an exception of type ArgumentOutOfRangeException is thrown.
var m = Matrix.Create(2, 2, (i, j) => 11 + 10 * i + j);
// m -> [[ 11 12 ]
// [ 21 22 ]]
m[1, 1] = 99;
// m -> [[ 11 12 ]
// [ 21 99 ]]
Accessing rows and columns
There are three ways to access the rows or columns of a matrix: through indexers, through row and column getter methods, and through enumerators.
Indexer access
The indexer property is overloaded to allow for direct indexed access to complete or partial rows or columns. The indexers are similar to the indexers for vectors. For row access, the first index is the row index. The second index can be any value that is valid for a vector indexer. The special range All lets you access an entire row or column without having to specify any details about the range.
var m = Matrix.Create(3, 3, (i, j) => 11 + 10 * i + j);
// m -> [[ 11 12 13 ]
// [ 21 22 23 ]
// [ 31 32 33 ]]
var row1 = m[0, Range.All];
// row1 -> [ 11 12 13 ]
var column1 = m[Range.All, 1];
// column1 -> [ 12 22 32 ]
var row2 = m[1, new Range(1, 2)];
// row2 -> [ 22 23 ]
var row3 = m[1, new Range(0, 2, 2)];
// row3 -> [ 21 23 ]
The first example retrieves the entire first row of the matrix m. The second example retrieves the entire second column. The third example retrieves only the last two elements of the second row. The final example retrieves the first and last element of the second row.
The indexers can be assigned to as well. In this case, he elements in the range specified by the index are set to the corresponding elements of the right-hand side. This is illustrated in the next example:
m[1, new Range(1, 2)] = Vector.Create(88, 99);
// m -> [[ 11 12 13 ]
// [ 21 88 99 ]
// [ 31 32 33 ]]
m[1, new Range(0, 2, 2)] = Vector.Create(77, 66);
// m -> [[ 11 12 13 ]
// [ 77 88 66 ]
// [ 31 32 33 ]]
m[Range.All, 1] = Vector.Create(1, 2, 3);
// m -> [[ 11 1 13 ]
// [ 77 2 66 ]
// [ 31 3 33 ]]
Boolean masks and predicates may also be used to select a subset of the elements of the row or column. Once again, you can both get and set the elements:
var m = Matrix.Create(3, 3, (i, j) => Elementary.Pow(i, j));
// m -> [[ 1 1 1 ]
// [ 1 2 4 ]
// [ 1 3 9 ]]
var mask1 = Vector.Create(false, true, true);
var row1 = m[1, mask1];
// row1 -> [ 4 9 ]
var column1 = m[x => x > 1.0, 1];
// column1 -> [ 2 3 ]
m[1, mask1] = Vector.Create(4.0, 7.0);
// m -> [[ 1 1 1 ]
// [ 1 4 7 ]
// [ 1 3 9 ]]
Row and Column slices
The GetRow and GetColumn methods provide the same functionality as indexed range access, but add the option to specify the intent.
GetRow always takes the row index as its first argument. If no further arguments are supplied, the entire row is returned. A partial row can be returned by providing the column index of the first and last index in the new vector, and optionally a stride. You can also pass a Range structure as the only second argument. GetColumn has similar overloads. The first argument is always the column index. The remaining arguments, if present, specify the range of row indexes. The following code sample performs the same operations as the indexed range sample above, but uses GetRow and GetColumn:
var m = Matrix.Create(3, 3, (i, j) => 11 + 10 * i + j, ArrayMutability.Immutable);
// m -> [[ 11 12 13 ]
// [ 21 22 23 ]
// [ 31 32 33 ]]
var row1 = m.GetRow(1, 1, 2, Intent.WritableCopy);
// row1 -> [ 22, 23 ] and writable
// Note that this would fail since m is immutable:
// m[1, 2] = 99;
// But we can change the row vector:
row1[0] = 99;
// row1 -> [ 99, 23 ]
// m is unchanged.
var row2 = m.GetRow(1, new Range(0, 2, 2));
// row2 -> [ 21 23 ]
var column1 = m.GetColumn(1);
// column1 -> [ 12 22 32 ]
Note that the matrix m is immutable, so its rows and columns are always immutable as well. In the example, we make a writable copy of a row, and the elements of this vector can be changed without changing the elements of m.
Enumeration
Matrix operations often involve performing some action on each of the rows or columns of a matrix. The Rows and Columns properties return the rows and columns as collections that can be iterated over.
Enumerating rows and columns is often more efficient than getting each row or column in a loop because only one vector may be allocated
The following example shows a method that calculates the sum of the absolute values of a matrix.
double SumOfAbsoluteValues(Matrix<double> a)
{
double sum = 0;
foreach (var column in a.Columns)
sum += column.OneNorm();
return sum;
}
The one-norm of a vector equals the sum of the absolute values of its elements. All we have to do to obtain the sum of the absolute values of a matrix, then, is to sum the one-norms of all the rows or columns. Since most algorithms are optimized for matrices stored in column major order, it is recommended to iterate over the columns rather than the rows whenever possible.
Accessing diagonals
The GetDiagonal method returns a vector whose elements are the diagonal of a matrix. The method has an optional argument that specifies the index of the diagonal. A value of zero indicates the main diagonal. A value greater than zero indicates a super-diagonal. A value less than zero indicates a sub-diagonal. The default is to return the main diagonal.
As with the GetRow and GetRow methods, an Intent value can be passed as the last argument.
Accessing sub-matrices
Sometimes it may be useful to treat part of a matrix as if it were a matrix in its own right. There are two ways of achieving this.
Indexed range access
The indexer for the Matrix<T> class has several overloads that lets you extract a part of a matrix. Both arguments of the indexer are Range structures. The first specifies the range of rows to include. The second argument specifies the range of columns. Both arguments may have the special value All, which indicates that the entire column or row should be included. Some examples:
var m = Matrix.CreateRandom(10, 10);
// Extract the 2nd to the 5th row of m:
var m1 = m[new Range(1, 4), Range.All];
// Extract the odd columns:
var m2 = m[Range.All, new Range(1, 10, 2)];
// Extract the 4x4 leading sub-matrix of m:
var m3 = m[new Range(0, 3), new Range(0, 3)];
You can also assign to ranges. The following example sets the upper right and lower left blocks of a 10x10 matrix to the identity matrix:
var m = Matrix.Create<double>(10, 10);
var ones = Vector.CreateConstant(5, 1.0);
var identity5 = Matrix.CreateDiagonal(ones);
m[new Range(0, 4), new Range(5, 9)] = identity5;
m[new Range(5, 9), new Range(0, 4)] = identity5;
We first create a 5x5 identity matrix by calling the CreateDiagonal method. We then assign this matrix to the selected blocks of m.
Matrix slices
The same results can be achieved with the GetSubmatrix method, which is somewhat more flexible. Like the GetRow and GetColumn methods mentioned earlier, you can specify the intended use of the sub-matrix. You can also apply a transposition operation to the matrix.
The ranges can be specified using either Range structures or explicit start and end indices. An optional argument lets you specify the transpose operation to apply, if any. The earlier examples can be expressed using the GetSubmatrix method as follows:
var m = Matrix.CreateRandom(10, 10);
// Extract the 2nd to the 5th row of m.
// Start and end columns are supplied manually.
var m1 = m.GetSubmatrix(1, 4, 0, 9);
// Extract the odd columns:
// Here we need to supply the transpose parameter.
var m2 = m.GetSubmatrix(0, 9, 1, 1, 10, 2, TransposeOperation.None);
// Extract the 4x4 leading sub-matrix of m.
// And let's get its transpose, just because we can:
var m3 = m.GetSubmatrix(0, 3, 1, 0, 3, 1,
TransposeOperation.Transpose);
Notice how in the last sample, the transpose of the sub-matrix was returned. You can still assign to sub-matrices using the CopyTo method:
var m = Matrix.Create<double>(10, 10);
var ones = Vector.CreateConstant(5, 1.0);
var identity5 = Matrix.CreateDiagonal(ones);
identity5.CopyTo(m.GetSubmatrix(0, 4, 5, 9));
identity5.CopyTo(m.GetSubmatrix(5, 9, 0, 4));