Matrix Basics
Matrices come in many shapes and sizes. When the matrix exhibits a definite structure, calculations can often be speeded up by several orders of magnitude. Storage requirements may also be significantly reduced. It is therefore useful to define different matrix types to take advantage of these improvements.
Numerics.NET includes classes for dense matrices, upper- and lower-triangular matrices, symmetric or Hermitian matrices, and sparse matrices.
The Matrix<T> class is an abstract base class that exposes the mathematical methods and properties of matrices of arbitrary types. It also contains a default implementation for these methods and properties. A series of derived classes implement specific types of matrices. These are listed in the table below.
Class | Description |
|---|---|
A standard dense matrix. All elements can take on any value. This is the most common type. | |
A matrix whose elements above or below the main diagonal are all zero. | |
A matrix whose elements are symmetric about the main diagonal. A symmetric matrix is equal to its transpose. | |
A complex matrix that is equal to its complex conjugate transpose. | |
A matrix whose only stored elements lie on the main diagonal. | |
A matrix whose only stored elements lie in diagonal bands around the main diagonal. | |
A matrix whose elements are mostly zero. Only the nonzero elements are stored. | |
A matrix that represents a range or slice of another matrix. |
The Matrix<T> class does not specify how matrix elements should be stored. This is left entirely up to the derived classes. The derived matrix types provide their own optimized implementations for many of the base methods and properties. Wherever possible, use is made of optimized implementations of the standard Basic Linear Algebra Subroutines (BLAS) and the Linear Algebra PACKage (LAPACK).
Some matrix types put restrictions on the elements that can be changed. For example, you can only independently modify elements in the stored triangle of a TriangularMatrix<T>. If you try to assign a value to a read-only element, an exception of type ComponentReadOnlyException is thrown. You can check whether an element is writable by calling the IsElementWritable(Int32, Int32) method with the row and column index of the element.
A DenseMatrix<T> can represent arbitrary values at every coordinate, but an instance may still be read-only. Storage representation and permission to write are separate concerns. Use IsElementWritable(Int32, Int32) to check a particular coordinate.
Creating matrices
Matrices are created using static factory methods in the Matrix class. The naming of these methods follows a consistent philosophy that clearly indicates what the method does with data:
CopyFrom methods create a new matrix by copying data from an existing source. The original data is duplicated, so changes to the matrix do not affect the source, and vice versa. Use these methods when you need an independent copy of the data.
Wrap methods create a matrix that uses existing data without copying. The matrix is a "view" over the original data. Changes to the matrix will affect the original data, and vice versa. Use these methods when you want to avoid copying data for performance reasons, or when you need multiple representations of the same underlying data.
Generative methods like Zeros, Identity, Random, and FromFunction create new matrices with elements generated according to a specific pattern or rule.
Structured creation methods like CreateSymmetric, CreateUpperTriangular, CreateLowerTriangular, and CreateBanded create matrices with specific structural properties. These methods also follow the CopyFrom/Wrap pattern where applicable (for example, CopyFromSymmetric and WrapSymmetric).
Get...View methods reinterpret existing matrix storage with a structural meaning, such as upper triangular, lower triangular, symmetric, or Hermitian. A view shares storage with its source. Changes through the view and changes through the source are visible through both objects for elements that are part of the view.
This naming scheme eliminates the need for confusing boolean parameters like createView or reuseValuesArray that were common in earlier versions. The method name itself tells you what will happen with the data. Legacy APIs that use names such as Extract... may still exist for compatibility, but new code should use the Get...View, CopyFrom..., and Wrap... APIs.
For detailed information on constructing specific matrix types, see the topics on dense matrices, symmetric matrices, triangular matrices, and Hermitian matrices.
Copying matrices
There are three ways to make a copy of a matrix.
The ShallowCopy() method creates another object without copying its element storage. It is not an independent snapshot. Writes can be visible through both objects when storage is shared; copy-on-write instances may detach on mutation. Use a full clone when independence is required.
The Clone() method returns an independent copy. Its default cloning mode is NonzeroElementsWritable. The representation may change to make each logically nonzero coordinate independently writable. For example, cloning a nonzero constant matrix can produce a dense matrix. Changes to the clone do not change the original.
The CloneData() method detaches an instance from shared element storage when its representation supports this operation. Successful detachment also clears copy-on-write and shared-writable-storage state. It does not make structurally constrained coordinates independently writable. To obtain an independent object whose every coordinate is writable, use Clone(CloningMethod) with AllElementsWritable.
// Create a vector, a shallow copy, and a clone:
Matrix m = Matrix.Create(2, 2);
m[1,1] = 2;
Matrix mShallowCopy = m.ShallowCopy();
Matrix mClone = m.Clone();
// This prints '2':
Console.WriteLine("m[1,1] = {0}", m[1,1]);
// Now change the value of this component.
m[1,1] = -1;
// Component changed. This prints '-1':
Console.WriteLine("m[1,1] = {0}", m[1,1]);
// Shallow copy changed, also. This prints '-1':
Console.WriteLine("mShallowCopy[1,1] = {0}", mShallowCopy[1,1]);
// Clone is left unchanged. This prints '2':
Console.WriteLine("mClone[1,1] = {0}", mClone[1,1]);The third way to copy a matrix is by calling the matrix's ToDenseMatrix method. This method returns a stand-alone Matrix<T> whose elements are copied from the original matrix. If this method is called on a DenseMatrix<T>, a new matrix is still created. In all cases, the elements of the new matrix are guaranteed to be stored in a contiguous block of memory.
You can copy the elements of a matrix to another, previously created matrix using the CopyTo method. Care should be taken that you don't try to modify elements that are read-only. Any such attempt will result in a ComponentReadOnlyException.
Unchecked element access
GetValueUnchecked(Int32, Int32) reads a logical matrix element without explicit bounds validation. The caller must supply valid row and column indices. Structured entries, including reflected elements and implied diagonal values, retain their logical meaning.
SetValueUnchecked(T, Int32, Int32) skips coordinate validation but still enforces writability and prepares copy-on-write storage. Prefer the indexer unless coordinates have already been validated. For missing-aware matrices, use IsMissing(Int32, Int32) to test whether an element is missing.