This includes data sturctures and algorithms for working with sparse matrices using the new Yale sparse matrix format. The two main data structures are
The vector data structure is simply an array of n entries. Functions related to vectors include:
The first three functions initialize the vector data structure. These require that the vector has not been previously initialized, otherwise we would have a memory leak.
Initialize a vector u by allocating sufficient memory for n entries. The entries are not initialized, so during one initialization all the entries may be zero, during a subsequent initialization all the entries may appear to be random values.
Initialize a vector u by allocating sufficient memory for n entries. All the entries are initialized to the real value r; that is uj←r for 0≤j<n.
Initialize a vector u by allocating sufficient memory to match the dimension of the vector u and copy the entries of v over to u; that is uj←vj for 0≤j<n.
Deallocate the memory for the vector u.
This requires that the vector has been initalized; otherwise the pointer to the memory would be a wild pointer, and after the memory is deallocated, the pointer to the memory is set to the null pointer to avoid having a dangling pointer.
These all require that any vector arguments have been initalized; otherwise we have a wild pointer.
Return the entry uj where 0≤j<n.
Assign the jth entry of u to r; that is, assign uj←r where 0≤j<n.
Copy the entries of the vector v over to the entries of u; that is, assign uj←vj for 0≤j<n. The run time is O(n).
Assign all the entries in the vector u to the value \alaph; that is, assign uj←\alaph for 0≤j<n. The run time is O(n).
Also, the dimensions of the two vectors must be equal; that is dim(u)=dim(v).
Return the dimension n of the vector u. The run time is O(1).
The following are vector operations for
These operations require that the vector has been initalized; otherwise we have a wild pointer.
Perform scalar multiplication u←r⋅u; that is, assign uj←r⋅uj for 0≤j<n.
Add the scalar r onto each entry of the vecotr u; that is, assign uj←r⋅uj for 0≤j<n.
Add the entries of the vector v onto the entries of the vector u; that is, assign uj←uj+vj for 0≤j<n.
Add a scalar multiple of the vector v onto the vector u; that is, that is, assign uj←uj+r⋅vj for 0≤j<n.
These operations require that the vector has been initalized; otherwise we have a wild pointer.
Calculate the inner product of the two vectors u and v; that is, ⟨u,v⟩=u∙v=∑n−1j=0ujvj.
Project the vector u onto the vector v; that is, \projv(u)=⟨u,v⟩⟨v,v⟩v=u∙vv∙v⟩v, or uj←⟨u,v⟩⟨v,v⟩vj for 0≤j<n.
Find the perpendicular component of the vector u relative to the vector v; that is, ⊥v(u)=u−⟨u,v⟩⟨v,v⟩v=u−u∙vv∙v⟩v, or uj←uj−⟨u,v⟩⟨v,v⟩vj for 0≤j<n.
Calculate the norm of a vector u using one of the metrics
Note that there is no mean infinity error.
Normalize a non-zero vector by dividing it by its 1-norm, mean error, 2-norm, root mean squared error or infinity norm.
Note that we cannot normalize with respect to, for example, the two-norm squared, for in general
\left \| \frac{\bf u}{\|{\bf u}\|_2^2} \right \|_2^2 \ne 1.
This is because the two-norm squared and the mean squared error are not vector norms.
Calculate the distance between two vectors {\bf u} and {\bf v} using
The metrics available for measuring the distance between two vectors include
Note that there is no mean infinity error.
Given an array of N vectors, all of the same dimension n, perform the numerically stable variation of the Gram-Schmidt algorithm producing an array of orthogonal vectors (some of which may be the zero vector).
Overwite u with a random vector where the entries are either
Using vector_scalar_multiply and vector_scalar_add, vectors with uniform distributions on arbitrary intervals [a, b] or normally distributed with mean \mu and standard deviation \sigma can easily be created.
Print the vector {\bf u} as if it were input for one of the specified languages:
The format of each double-precision floating-point number is through the string format using printf formatting.
The sparse matrix data structure used is the new Yale format. For a square matrix A of dimension n, under the assumption that the diagonal is in general non-zero, one array of size n stores the diagonal entries. The maximum number of non-zero entries is N and the values are stored in lexicographical order (that is, (0,1), (0,2), ..., (0,n - 1), (1,0), (1,2), ..., (1,n - 1), ..., (n - 1, 0), ..., (n - 1, n - 2)) in an array of size N. Also, the column indices are stored in the same order in a second column-index array, but rather than storing another array of the corresponding row entries, instead, we have an array of n + 1 entries indicating where the first location an entry of row i would occur with the last entry being one beyond the last entry stored.
typedef struct { size_t dimension; size_t max_non_zero_off_diagonal_values; size_t *a_row_indices; size_t *a_column_indices; double *a_diagonal_values; double *a_off_diagonal_values; } sparse_matrix_t;Functions related to matrices include:
The first three functions initialize the vector data structure. These require that the vector has not been previously initialized, otherwise we would have a memory leak.
Initialize a matrix A by allocating sufficient memory for n diagonal entries and N off-diagonal entries. The diagonal values are not initialized, so during one initialization the entries may be zero, during a subsequent initialization all the entries may appear to be random values.
Initialize a matrix A by allocating sufficient memory for n diagonal entries and N off-diagonal entries. All the diagonal entries are initialized to the real value r; that is a_{i,i} \leftarrow r for 0 ≤ i < n.
Given an n-dimensional vector {bf u}, initialize a matrix A by allocating sufficient memory for n diagonal entries and N off-diagonal entries. The diagonal entries are assigned initialized to the corresponding entries in the vector {\bf u}; that is a_{i,i} \leftarrow u_i for 0 ≤ i < n.
Deallocate the memory for the matrix A.
This requires that the vector has been initalized; otherwise the pointer to the memory would be a wild pointer, and after the memory is deallocated, the pointer to the memory is set to the null pointer to avoid having a dangling pointer.
These all require that any matrix arguments have been initalized; otherwise we have a wild pointer.
Return the entry a_{i,j} where 0 ≤ i < n and 0 ≤ j < n.
Assign the (i,j)^{\text th} entry of A to r; that is, assign a_{i,j} \leftarrow r where 0 ≤ i < n and 0 ≤ j < n. This is potentially an O(N) operator and should only be used sparingly. If at all possibly, use the next function matrix_append.
Assign the (i,j)^{\text th} entry of A to r; that is, assign a_{i,j} \leftarrow r where 0 ≤ i < n and 0 ≤ j < n where it is required that this entry must be placed in the next available position in the data structure. For this to be true, if (i',j') is the location of any other non-zero off-diagonal entry, then either i > i' or if i = i', j > j'.
Copy the entries of the matrix B over to the entries of A; that is, assign a_{i,j} \leftarrow b_{i,j} for 0 ≤ j < n and 0 ≤ j < n.
The dimensions of both matrices must be equal, and the capacity for non-diagonal entries of A must be at least as large as the number of off-diagonal entries in B.
Copy the entries of the vector {\bf v} over to the diagonal entries of A; that is, assign a_{i,i} \leftarrow v_i for 0 ≤ i < n. All off-diagonal entries in A are set to zero.
This requires that the dimensions of A and {\bf v} are equal.
Assign all the diagonal entries in the matrix A to the value r; that is, assign a_{i,i} \leftarrow r for 0 ≤ i < n. All off-diagonal entries in A are set to zero.
Return the dimension of the matrix A. The run time is O(1).
Calculate the density of the matrix, meaning the number of non-zero entries over the total number of entries. This function checks the diagonal, and thus runs in O(n) time.
Return true if the matrix A is diagonal (meaning that a_{i,j} = 0 if i \neq j) and false otherwise. This runs in O(1) time.
Return true if the matrix A is upper triangular (meaning that a_{i,j} = 0 if i > j) and false otherwise. This runs in O(n) time.
Return true if the matrix A is lower triangular (meaning that a_{i,j} = 0 if i < j) and false otherwise. This runs in O(n) time.
Return true if the matrix A is symmetric (meaning that a_{i,j} = a_{j,k}) and false otherwise. This runs in O(n^3d^2) time.
Return true if the matrix A is skew-symmetric (meaning that a_{i,j} = -a_{j,i}) and false otherwise. This runs in O(n^3d^2) time.
Return true if the matrix A data structure appears to be consistent, and false otherwise. It ensures that the row indices are increasing, starting with 0, that the number of off-diagonal entries is less than the maximum allowed, that the columns within each row are linearly ordered, that all column indices are within the dimensions of the matrix, and that the off-diagonal entries are all non-zero.
This routine should be used if you are directly manipulating the sparse matrix data structure and need to confirm that any resulting data structure continues to be valid.
Matrix operations are generally in-place, meaning that the first argument is modified based on the subsequent arguments. The only exception is matrix-matrix addition, an operation that cannot be done in-place without O(n) additional memory.
Multiply each entry in the matrix A by the scalar \alpha; that is, a_{i,j} \leftarrow \alpha a_{i,j}. The run time is O(n^2d + n) unless \alpha = 0, in which case the run time is O(n).
Overwrite the matrix A with the sum B + C; that is, a_{i,j} = b_{i,j} + c_{i,j}. Using only local variables, it is not possible to perform this in place, and therefore a third matrix must be invovled.
Add the entries of the vector onto the diagonal of the matrix A; that is, let a_{i,i} \leftarrow a_{i,i} + v_i. The run time is O(n).
Add the scalar \alpha onto each entry of the diagonal of the matrix A; that is, let a_{i,i} \leftarrow a_{i,i} + \alpha. The run time is O(n).
Assign {\bf v} the product A{\bf u}; that is v_i = \sum_{j = 1}^n a_{i,j}u_j. The run time is O(dn^2).
Assign {\bf v} the product A^T{\bf u}; that is v_i = \sum_{j = 1}^n a_{j,i}u_j. The run time is O(dn^2).
Solve a diagonal system of linear equations A{\bf u} = {\bf b} for {\bf u} using direct calculation; that is u_i = \frac{b_i}{a_{i,i}}. The matrix A must be diagonal.
Solve a upper-triangular system of linear equations A{\bf u} = {\bf b} for {\bf u} using backward substitution; that is, u_i = \frac{b_i - \sum_j = {i + 1}^{n - 1} a_{i,j}u_j}{a_{i,i}} for i from n - 1 down to 0. The matrix A must be upper-triangular.
Solve a lower-triangular system of linear equations A{\bf u} = {\bf b} for {\bf u} using forward substitution; that is, u_i = \frac{b_i - \sum_j = {j = 0}^{i - 1} a_{i,j}u_j}{a_{i,i}} for i from 0 to n - 1. The matrix A must be lower-triangular.
Approximate a solution to the system of linear equations defined by A{\bf u} = {\bf b} for {\bf u} using the Jacobi method. The algorithm will iterate a maximum of max_iteration times, and halts when the difference between successive approximations is less than step_tolerance. The number of iterations is returned, and if the algorithm failed to converge, the return value will be max_iterations + 1.
Approximate a solution to the system of linear equations defined by A{\bf u} = {\bf b} for {\bf u} using the Gauss-Seidel method. The algorithm will iterate a maximum of max_iteration times, and halts when the difference between successive approximations is less than step_tolerance. The number of iterations is returned, and if the algorithm failed to converge, the return value will be max_iterations + 1.
Approximate a solution to the system of linear equations defined by A{\bf u} = {\bf b} for {\bf u} using the steepest-descent method. The algorithm will iterate a maximum of max_iteration times, and halts when the difference between successive approximations is less than step_tolerance. The number of iterations is returned, and if the algorithm failed to converge, the return value will be max_iterations + 1.
The matrix must be symmetric and positive definite.
Approximate a solution to the system of linear equations defined by A{\bf u} = {\bf b} for {\bf u} using the minimal-residual method. The algorithm will iterate a maximum of max_iteration times, and halts when the difference between successive approximations is less than step_tolerance. The number of iterations is returned, and if the algorithm failed to converge, the return value will be max_iterations + 1.
The matrix must be positive definite.
Approximate a solution to the system of linear equations defined by A{\bf u} = {\bf b} for {\bf u} using the residual-norm steepest-descent method. The algorithm will iterate a maximum of max_iteration times, and halts when the difference between successive approximations is less than step_tolerance. The number of iterations is returned, and if the algorithm failed to converge, the return value will be max_iterations + 1.
Calculate the LU-decomposition of the matrix A assuming it exists and a permutation matrix is not necessary. The matrices L and U are overwritten. The run-time is more strongly based on the density of the lower- and upper-triangular matrices L and U, respectively, for even if the matrix A is sparse, the matrices L and U may be dense.
These functions are designed to have a run time equal to the number of diagonal entries and the number non-zero entries which may be either dense or based on a specified density d, meaning O(dn^2 + n). In the case of symmetric and skew-symmetric matrices, additional work must be done, and therefore the run time becomes O(dn^2 + n^2). In all cases, only local variables are used—no additional arrays of memory are allocated.
The algorithm used to achieve the desired density uses an exponential distribution to create a poisson distribution of entries that are either zero or otherwise assigned a random value. Because a poisson distribution assumes some bins may contain more than one item, to achieve the desired distribtuion of non-zero entries, it is necessary to correct the density. Thus, these algorithms are necessarily slower for densities closer to 1.
Overwrite A with a random matrix that has a shape that is one of
The density of the entries is given by the argument density and this determines the density of entries off of the diagonal. The distribution on the diagonal entries is decided by:
The random values themselves are chosen from one of three distributions:
Overwrite A with a random diagonal matrix where the distribution of diagonal entries is decided by:
The random values themselves are chosen from one of three distributions:
Overwrite A with a random dense tridiagonal matrix, meaning that all entries off the diagonal, the super diagonal and the sub diagonal are zero. Additionally, the matrix may have a shape that is one of
The random values themselves are chosen from one of three distributions:
Overwrite A with a random band-diagonal matrix meaning that a_{i,j} = 0 if |i - j| > m. Additionally, the matrix may have a shape that is one of
The density of the entries on the band is given by the argument density and this determines the density of entries off of the diagonal. The distribution on the diagonal entries is decided by:
The random values themselves are chosen from one of three distributions:
In each of these examples, we will show the output for the matrix
A = \left( \begin{matrix} 0.000246 & 0.473772 & 0.000000 & 0.000000 & 0.000000 \\ 0.473772 & 0.134160 & -0.711116 & 0.000000 & 0.000000 \\ 0.000000 & -0.711116 & 1.105896 & 1.668291 & 0.000000 \\ 0.000000 & 0.000000 & 1.668291 & 1.214598 & 0.259607 \\ 0.000000 & 0.000000 & 0.000000 & 0.259607 & -0.902819 \end{matrix} \right ).
Print the matrix A as if it were input for one of the specified languages:
The format of each double-precision floating-point number is through the string format using printf formatting. The above matrix printed in the Matlab format with "% f" yields an output of
[ 0.000246 0.473772 0.000000 0.000000 0.000000 0.473772 0.134160 -0.711116 0.000000 0.000000 0.000000 -0.711116 1.105896 1.668291 0.000000 0.000000 0.000000 1.668291 1.214598 0.259607 0.000000 0.000000 0.000000 0.259607 -0.902819];
Print the sparse matrix data structure, including
The above matrix printed in this format with "%f" yields an output of
Dimension: 5 x 5 Off-diagonal entries: 8 (max 20) Diagonal entries: 0.000246 0.134160 1.105896 1.214598 -0.902819 Row indices: 0 1 3 5 7 8 Column indices: 1 0 2 1 3 2 4 3 Off-diagonal values: 0.473772 0.473772 -0.711116 -0.711116 1.668291 1.668291 0.259607 0.259607
Print density plot of a matrix where
The above matrix printed in this format yields an output of
x x - - - x x o - - | o x x - | | x x x | | | x o