处理和解决稀疏问题的各种模块,总结如下:
模组 | 头文件 | 内容 |
---|---|---|
SparseCore |
#include
|
SparseMatrix and SparseVector classes, matrix assembly, basic sparse linear algebra (including sparse triangular solvers) |
SparseCholesky |
#include
|
Direct sparse LLT and LDLT Cholesky factorization to solve sparse self-adjoint positive definite problems |
SparseLU |
#include
|
Sparse LU factorization to 解决通用的square sparse systems |
SparseQR |
#include
|
Sparse QR factorization解决稀疏线性最小二乘问题 |
IterativeLinearSolvers | #include |
迭代求解器用于求解大规模通用的线性square problems (including self-adjoint positive definite problems) |
Sparse | #include |
包括以上所有模块 |
在许多应用中(例如,有限元方法),通常需要处理非常大的矩阵,这样的矩阵只有几个系数不等于零。在这种情况下,通过使用仅存储非零元素,可以减少内存消耗并提高性能。这样的矩阵称为稀疏矩阵。
稀疏矩阵类
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
#include
#include
typedef Eigen::SparseMatrix SpMat;
typedef Eigen::Triplet T;
void buildProblem(std::vector& 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 coefficients;
Eigen::VectorXd b(m);
buildProblem(coefficients, b, n);
SpMat A(m,m);
A.setFromTriplets(coefficients.begin(), coefficients.end());
// Solving:
Eigen::SimplicialCholesky 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& 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 bits =
(x*255).cast();
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
;
和类型相同的三元组列表Triplet
。
A triplet is a simple object representing a non-zero entry as the triplet: row
index, column
index, value
.
矩阵和矢量特性
SparseMatrix类和SparseVector类有3个模板参数:元素类型,存储顺序,(ColMajor(默认)或RowMajor)和内索引类型(默认int)。
对于密集Matrix类,构造函数的参数是对象的大小。
SparseMatrix > mat(1000,2000); //声明一个复杂的的1000x2000列主压缩稀疏矩阵
SparseMatrix 垫(1000,2000); //声明一个double的1000x2000行主压缩稀疏矩阵
SparseVector > vec(1000); //声明大小为1000的complex 的列稀疏向量
SparseVector vec(1000); //声明大小为1000的double的行稀疏向量
迭代所有的非零元素
使用coeffRef(i,j)函数可以随机访问稀疏对象的元素,但是用到的二叉搜索比较浪费时间。
大多数情况下,我们仅需迭代所有的非零元素。这时,可以先迭代外维度,然后迭代当前内维度向量的非零元素。非零元素的访问顺序和存储顺序相同?
SparseMatrix mat(rows,cols);
for (int k=0; k::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 vec(size);
for (SparseVector::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 T;
std::vector 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 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
如果省略此行,则第一次插入新元素将为每个内部向量保留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 A, B;
B = SparseMatrix(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() * dm1; // if only the upper part of A is stored
dm2 = A.selfadjointView() * dm1; // if only the lower part of A is stored
稀疏矩阵间的乘法
dm2 = sm1.selfadjointView<>() * dm1; // if all coefficients of A are stored
dm2 = A.selfadjointView() * dm1; // if only the upper part of A is stored
dm2 = A.selfadjointView() * 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 P = ...;
sm2 = P * sm1;
sm2 = sm1 * P.inverse();
sm2 = sm1.transpose() * P;
SparseMatrix sm1;
sm1.col(j) = ...;
sm1.leftCols(ncols) = ...;
sm1.middleCols(j,ncols) = ...;
sm1.rightCols(ncols) = ...;
SparseMatrix sm2;
sm2.row(i) = ...;
sm2.topRows(nrows) = ...;
sm2.middleRows(i,nrows) = ...;
sm2.bottomRows(nrows) = ...;
稀疏矩阵的2个方法SparseMatrixBase::innerVector() 和 SparseMatrixBase::innerVectors(),当稀疏矩阵是列优先存储方式时,这两个方法绑定到col/middleCols方法,当稀疏矩阵是行优先存储方式时,这两个方法绑定到row/middleRows方法。
triangularView()函数可以用于解决矩阵的而三角部分,给定右边密集矩阵求解三角**。
dm2 = sm1.triangularView(dm1);
dv2 = sm1.transpose().triangularView(dv1);
dm2 = sm1.triangularView(dm1);
dv2 = sm1.transpose().triangularView(dv1);
dm2 = sm1.selfadjointView<>() * dm1; // if all coefficients of A are stored
dm2 = A.selfadjointView() * dm1; // if only the upper part of A is stored
dm2 = A.selfadjointView() * dm1; // if only the lower part of A is stored
sm2 = sm1.selfadjointView();
// makes a full selfadjoint matrix from the upper triangular part
sm2.selfadjointView() = sm1.selfadjointView();
// copies the upper triangular part to the lower triangular part
PermutationMatrix P = ...;
sm2 = A.selfadjointView().twistedBy(P);
// compute P S P' from the upper triangular part of A, and make it a full matrix
sm2.selfadjointView() = A.selfadjointView().twistedBy(P);
// compute P S P' from the lower triangular part of A, and then only compute the lower part