處理和解決稀疏問題的各種模塊,總結如下:
模組 | 頭文件 | 內容 |
---|---|---|
SparseCore |
#include <Eigen / SparseCore>
|
SparseMatrix and SparseVector classes, matrix assembly, basic sparse linear algebra (including sparse triangular solvers) |
SparseCholesky |
#include <Eigen / SparseCholesky>
|
Direct sparse LLT and LDLT Cholesky factorization to solve sparse self-adjoint positive definite problems |
SparseLU |
#include <Eigen / SparseLU>
|
Sparse LU factorization to 解決通用的square sparse systems |
SparseQR |
#include <Eigen / SparseQR>
|
Sparse QR factorization解決稀疏線性最小二乘問題 |
IterativeLinearSolvers | #include<Eigen/IterativeLinearSolvers> |
迭代求解器用於求解大規模通用的線性square problems (including self-adjoint positive definite problems) |
Sparse | #include <Eigen/Sparse> | 包括以上所有模塊 |
稀疏矩陣格式
在許多應用中(例如,有限元方法),通常需要處理非常大的矩陣,這樣的矩陣只有幾個係數不等於零。在這種情況下,通過使用僅存儲非零元素,可以減少內存消耗並提高性能。這樣的矩陣稱爲稀疏矩陣。
稀疏矩陣類
SparseMatrix類是Eigen稀疏模塊的稀疏矩陣主要表示形式。它提供了高性能以及低內存使用率。
It implements a more versatile variant of the widely-used Compressed Column (or Row) Storage scheme.
SparseMatrix包含了4個精簡的數組:
Values:
儲非零元素。InnerIndices:
存儲非零元素的行/列索引.OuterStarts:
存儲每一列/行第一個非零元素在上面兩個數組中的索引。InnerNNZs:
存儲每一列/行中非零元素的數量。Inner在列優先矩陣中表示一個列,在行優先矩陣中表示一個行。Outer表示另一個方向。
在一個示例中可以更好地解釋此存儲方案。以下矩陣
0 3 0 0 0
22 0 0 0 17
7 5 0 1 0
0 0 0 0 0
0 0 14 0 8
and one of its possible sparse, column major representation:
Values: | 22 | 7 | _ | 3 | 5 | 14 | _ | _ | 1 | _ | 17 | 8 |
InnerIndices: | 1 | 2 | _ | 0 | 2 | 4 | _ | _ | 2 | _ | 1 | 4 |
OuterStarts: | 0 | 3 | 5 | 8 | 10 | 12 |
InnerNNZs: | 2 | 2 | 1 | 1 | 2 |
Currently the elements of a given inner vector are guaranteed to be always sorted by increasing inner indices. The "_"
indicates available free space to quickly insert new elements. Assuming no reallocation is needed, the insertion of a random element is therefore in O(nnz_j) where nnz_j is the number of nonzeros of the respective inner vector. On the other hand, inserting elements with increasing inner indices in a given inner vector is much more efficient since this only requires to increase the respective InnerNNZs
entry that is a O(1) operation.
本徵運算的結果始終會生成壓縮的稀疏矩陣。另一方面,在SparseMatrix中插入新元素會將其稍後轉換爲非壓縮模式。
如果中間沒有留未佔用的空間,就是壓縮模式。 對應於Compressed Column (or Row) Storage Schemes (CCS or CRS)。
任何SparseMatrix都可以通過SparseMatrix::makeCompressed() 方法變成稀疏模式。此時,InnerNNZs相對於OuterStarts而言,就是多餘的,因爲InnerNNZs[j] = OuterStarts[j+1]-OuterStarts[j]。因此SparseMatrix::makeCompressed()會釋放InnerNNZ存儲空間。
Eigen的操作總是返回壓縮模式的稀疏矩陣。當向一個SparseMatrix插入新元素時,會變成非壓縮模式。
SparseVector是SparseMatrix的一個特例,只有 Values 和 InnerIndices數組。沒有壓縮和非壓縮的區別。
這是以前以壓縮模式表示的矩陣:
Values: 22 7 3 5 14 1 17 8
InnerIndices: 1 2 0 2 4 2 1 4
輸出:
OuterStarts: 0 2 4 5 6 8
A SparseVector is a special case of a SparseMatrix where only the Values
and InnerIndices
arrays are stored. There is no notion of compressed/uncompressed mode for a SparseVector.
第一個例子
#include <Eigen/Sparse>
#include <vector>
#include <iostream>
typedef Eigen::SparseMatrix<double> SpMat;
typedef Eigen::Triplet<double> T;
void buildProblem(std::vector<T>& coefficients, Eigen::VectorXd& b, int n);
void saveAsBitmap(const Eigen::VectorXd& x, int n, const char* filename);
int main(int argc, char** argv)
{
if(argc!=2) {
std::cerr << "Error: expected one and only one argument.\n";
return -1;
}
int n = 300; // size of the image
int m = n*n; // number of unknows (=number of pixels)
// Assembly:
std::vector<T> coefficients;
Eigen::VectorXd b(m);
buildProblem(coefficients, b, n);
SpMat A(m,m);
A.setFromTriplets(coefficients.begin(), coefficients.end());
// Solving:
Eigen::SimplicialCholesky<SpMat> chol(A);
Eigen::VectorXd x = chol.solve(b);
// Export the result to a file:
saveAsBitmap(x, n, argv[1]);
return 0;
}
void buildProblem(std::vector<T>& coefficients, Eigen::VectorXd& b, int n)
{
b.setZero();
Eigen::ArrayXd boundary = Eigen::ArrayXd::LinSpaced(n, 0,M_PI).sin().pow(2);
for(int j=0; j<n; ++j)
{
for(int i=0; i<n; ++i)
{
int id = i+j*n;
insertCoefficient(id, i-1,j, -1, coefficients, b, boundary);
insertCoefficient(id, i+1,j, -1, coefficients, b, boundary);
insertCoefficient(id, i,j-1, -1, coefficients, b, boundary);
insertCoefficient(id, i,j+1, -1, coefficients, b, boundary);
insertCoefficient(id, i,j, 4, coefficients, b, boundary);
}
}
}
void saveAsBitmap(const Eigen::VectorXd& x, int n, const char* filename)
{
Eigen::Array<unsigned char,Eigen::Dynamic,Eigen::Dynamic> bits =
(x*255).cast<unsigned char>();
QImage img(bits.data(), n,n,QImage::Format_Indexed8);
img.setColorCount(256);
for(int i=0;i<256;i++) img.setColor(i,qRgb(i,i,i));
img.save(filename);
}
在此示例中,我們首先定義元素爲double類型的稀疏矩陣SparseMatrix<double>
;
和類型相同的三元組列表Triplet<double>
。
A triplet is a simple object representing a non-zero entry as the triplet: row
index, column
index, value
.
SparseMatrix類
矩陣和矢量特性
SparseMatrix類和SparseVector類有3個模板參數:元素類型,存儲順序,(ColMajor(默認)或RowMajor)和內索引類型(默認int)。
對於密集Matrix類,構造函數的參數是對象的大小。
SparseMatrix <std::complex <float>> mat(1000,2000); //聲明一個複雜的<float>的1000x2000列主壓縮稀疏矩陣
SparseMatrix <double,RowMajor>墊(1000,2000); //聲明一個double的1000x2000行主壓縮稀疏矩陣
SparseVector <std::complex <float>> vec(1000); //聲明大小爲1000的complex <float>的列稀疏向量
SparseVector <double,RowMajor> vec(1000); //聲明大小爲1000的double的行稀疏向量
迭代所有的非零元素
使用coeffRef(i,j)函數可以隨機訪問稀疏對象的元素,但是用到的二叉搜索比較浪費時間。
大多數情況下,我們僅需迭代所有的非零元素。這時,可以先迭代外維度,然後迭代當前內維度向量的非零元素。非零元素的訪問順序和存儲順序相同?
SparseMatrix<double> mat(rows,cols);
for (int k=0; k<mat.outerSize(); ++k)
for (SparseMatrix<double>::InnerIterator it(mat,k); it; ++it)
{
it.value();
it.row(); // row index
it.col(); // col index (here it is equal to k)
it.index(); // inner index, here it is equal to it.row()
}
SparseVector<double> vec(size);
for (SparseVector<double>::InnerIterator it(vec); it; ++it)
{
it.value(); // == vec[ it.index() ]
it.index();
}
For a writable expression, 可以使用valueRef()函數修改參考值。如果稀疏矩陣或向量的類型取決於模板參數,則typename
需要關鍵字來指示InnerIterator
表示類型;有關詳細信息,請參見C ++中的template和typename關鍵字。
填充稀疏矩陣
由於稀疏矩陣的特殊存儲格式,添加新的非零元素時需要特別注意。例如,隨機插入一個非零元素的複雜度是O(nnz),nnz表示非零元素的個數。
最簡單的保證性能的創建稀疏矩陣的方法是,先創建一個三元組列表,然後轉換成SparseMatrix。如下所示:
typedef Eigen::Triplet<double> T;
std::vector<T> tripletList;
tripletList.reserve(estimation_of_entries);
for(...)
{
// ...
tripletList.push_back(T(i,j,v_ij));
}
SparseMatrixType mat(rows,cols);
mat.setFromTriplets(tripletList.begin(), tripletList.end());
// mat is ready to go!
該std::vector
三胞胎的可能包含以任意順序的元素,甚至可能包含將由setFromTriplets來概括重複元素()。見稀疏矩陣:: setFromTriplets()函數和類三聯的更多細節。
The std::vector
of triplets 可能包含以任意順序的元素, and might even contain duplicated elements that will be summed up by setFromTriplets().
See the SparseMatrix::setFromTriplets() function and class Triplet for more details.
但是,在某些情況下,直接將非零元素插入到目標矩陣的方法更加高效,節省內存。如下所示:
1: SparseMatrix<double> mat(rows,cols); // default is column major
2: mat.reserve(VectorXi::Constant(cols,6));
3: for each i,j such that v_ij != 0
4: mat.insert(i,j) = v_ij; // alternative: mat.coeffRef(i,j) += v_ij;
5: mat.makeCompressed(); // optional
第2行,我們爲每列保留6個非零的空間。在許多情況下,可以很容易地預先知道每列或每行的非零數。
If it varies significantly for each inner vector, then it is possible to specify a reserve size for each inner vector by providing a vector object with an operator[](int j) returning the reserve size of the j-th
inner vector (e.g., via a VectorXi or std::vector<int>).如果只能獲得每個內部向量的非零數目的粗略估計,則強烈建議過高估計而不是相反。
如果省略此行,則第一次插入新元素將爲每個內部向量保留2個元素的空間。
第4行執行排序插入。在此示例中,理想的情況是is not full and contains non-zeros whose inner-indices are smaller than i
.In this case, this operation boils down to trivial O(1) operation.
調用insert(i,j)時,元素i
j必須不存在,otherwise use the coeffRef(i,j) method that will allow to, e.g., accumulate values. 如果該元素尚不存在,則此方法首先執行二進制搜索,最後調用insert(i,j)。它比insert()更靈活,但代價也更高。
第5行抑制了剩餘的空白空間並將矩陣轉換爲壓縮的列存儲。
支持的操作和函數
由於其特殊的存儲格式,稀疏矩陣無法提供與密集矩陣相同級別的靈活性。
Eigen的稀疏矩陣模塊僅僅實現了密集矩陣API的一個子集。
在下文中,sm表示稀疏矩陣,sv表示稀疏向量,dm表示密集矩陣,而dv表示密集向量。
稀疏矩陣支持大多數的逐元素的一元操作符和二元操作符。
sm1.real() sm1.imag() -sm1 0.5*sm1
sm1+sm2 sm1-sm2 sm1.cwiseProduct(sm2)
sm4 = sm1 + sm2 + sm3;
,sm1,sm2,sm3必須都是行優先或列優先的;sm4沒有要求。$A^T+A$
時,必須先將前一項生成一個兼容順序的臨時矩陣,然後再計算。如下所示:
SparseMatrix<double> A, B;
B = SparseMatrix<double>(A.transpose()) + A;
sm2 = sm1.cwiseProduct(dm1);
dm2 = sm1 + dm1;
dm2 = dm1-sm1;
dm2 = sm1 + dm1
寫,而是這樣寫:
dm2 = dm1;
dm2 + = sm1;
稀疏矩陣也支持轉置,但沒有原地計算的轉置函數transposeInPlace()。
sm1 = sm2.transpose();
sm1 = sm2.adjoint();
矩陣乘法
Eigen supports various kind of sparse matrix products which are summarize below:
- 稀疏矩陣和密集矩陣乘法:
dv2 = sm1 * dv1;
dm2 = dm1 * sm1.adjoint();
dm2 = 2. * sm1 * dm1;
- 對稱稀疏矩陣和密集矩陣乘法:
dm2 = sm1.selfadjointView<>() * dm1; // if all coefficients of A are stored
dm2 = A.selfadjointView<Upper>() * dm1; // if only the upper part of A is stored
dm2 = A.selfadjointView<Lower>() * dm1; // if only the lower part of A is stored
稀疏矩陣間的乘法
dm2 = sm1.selfadjointView<>() * dm1; // if all coefficients of A are stored
dm2 = A.selfadjointView<Upper>() * dm1; // if only the upper part of A is stored
dm2 = A.selfadjointView<Lower>() * dm1; // if only the lower part of A is stored
稀疏矩陣之間的乘法有兩種算法。稀疏矩陣之間的乘法有兩種算法。默認的方法是一種保守的方法,保存可能出現的零。如下所示:
sm3 = (sm1 * sm2).pruned(); // removes numerical zeros
sm3 = (sm1 * sm2).pruned(ref); // removes elements much smaller than ref
sm3 = (sm1 * sm2).pruned(ref,epsilon); // removes elements smaller than ref*epsilon
- 排列 稀疏矩陣也可以進行排列操作。
PermutationMatrix<Dynamic,Dynamic> P = ...;
sm2 = P * sm1;
sm2 = sm1 * P.inverse();
sm2 = sm1.transpose() * P;
塊操作
- 關於讀操作,稀疏矩陣支持類似於密集矩陣相同的接口來訪問塊,列,行。
- 但由於性能的原因,稀疏子矩陣的寫操作非常受限。當前僅列(列)優先稀疏矩陣的連續列(或行)可寫。此外,在編譯時必須知道這些信息。
- SparseMatrix類的寫操作API如下所示:
SparseMatrix<double,ColMajor> sm1;
sm1.col(j) = ...;
sm1.leftCols(ncols) = ...;
sm1.middleCols(j,ncols) = ...;
sm1.rightCols(ncols) = ...;
SparseMatrix<double,RowMajor> sm2;
sm2.row(i) = ...;
sm2.topRows(nrows) = ...;
sm2.middleRows(i,nrows) = ...;
sm2.bottomRows(nrows) = ...;
稀疏矩陣的2個方法SparseMatrixBase::innerVector() 和 SparseMatrixBase::innerVectors(),當稀疏矩陣是列優先存儲方式時,這兩個方法綁定到col/middleCols方法,當稀疏矩陣是行優先存儲方式時,這兩個方法綁定到row/middleRows方法。
三角和selfadjoint
triangularView()函數可以用於解決矩陣的而三角部分,給定右邊密集矩陣求解三角**。
dm2 = sm1.triangularView<Lower>(dm1);
dv2 = sm1.transpose().triangularView<Upper>(dv1);
dm2 = sm1.triangularView<Lower>(dm1);
dv2 = sm1.transpose().triangularView<Upper>(dv1);
- optimized sparse-dense matrix products:
dm2 = sm1.selfadjointView<>() * dm1; // if all coefficients of A are stored
dm2 = A.selfadjointView<Upper>() * dm1; // if only the upper part of A is stored
dm2 = A.selfadjointView<Lower>() * dm1; // if only the lower part of A is stored
- 複製三角部分
sm2 = sm1.selfadjointView<Upper>();
// makes a full selfadjoint matrix from the upper triangular part
sm2.selfadjointView<Lower>() = sm1.selfadjointView<Upper>();
// copies the upper triangular part to the lower triangular part
- 對稱排列
PermutationMatrix<Dynamic,Dynamic> P = ...;
sm2 = A.selfadjointView<Upper>().twistedBy(P);
// compute P S P' from the upper triangular part of A, and make it a full matrix
sm2.selfadjointView<Lower>() = A.selfadjointView<Lower>().twistedBy(P);
// compute P S P' from the lower triangular part of A, and then only compute the lower part