低内存消耗c++特征解算器
low RAM consuming c++ eigen solver
我是新手在 c++编程,但我有一个任务来计算特征值和特征向量(标准特征问题Ax=lx对称矩阵(和厄米特))对于非常大的矩阵的大小:二项式(L,L/2)其中L约为18-22。现在我正在一台有7.7 GB可用内存的机器上测试它,但最终我将能够访问具有64GB内存的PC。
我从 lapack++ 开始。在一开始,我的项目假设只解决对称实矩阵的这个问题。
这个库很棒。非常快和小的RAM消耗。它可以选择计算特征向量并将其放入输入矩阵A中以节省内存。它的工作原理!我认为 lapack++ 特征求解器可以处理厄米矩阵,但由于未知的原因(也许我做错了什么)。我的项目已经发展,我应该也能够计算厄米矩阵的这个问题。
所以我尝试将library更改为Armadillo library。它工作得很好,但它不像 lapack++ 那样好,它将mat A
替换为所有eigenvec
,但当然支持厄米矩阵。
L=14的一些统计量
-
lapack++ RAM 126MB时间7.9s特征值+特征向量
-
Armadillo RAM 216MB时间12s特征值
-
Armadillo RAM 396MB时间15s特征值+特征向量
我们计算一下:double
变量约为8B。我的矩阵有大小binomial(14,7) = 3432,所以在理想情况下,它应该有3432^2*8/1024^2 = 89 MB。
我的问题是:是否有可能修改或增强Armadillo来做一个很好的技巧 lapack++ ?Armadillo使用LAPACK
和BLAS
例程。或者有人可以推荐使用其他库来解决这个问题的另一种方法?
注。我的矩阵非常稀疏。它有大约2 *二项(L,L/2)非零元素。我试着用CSC格式的SuperLU
来计算这个,但它不是很有效,对于L=14 -> RAM 185MB,但时间为135秒。
Lapackpp和Armadillo都依赖于Lapack来计算复矩阵的特征值和特征向量。Lapack库提供了对复杂厄米矩阵执行这些操作的不同方法。
-
函数
zgeev()
不关心矩阵是否是厄米矩阵。这个函数由Lapackpp库在函数LaEigSolve
中为类型为LaGenMatComplex
的矩阵调用。Armadillo库的eig_gen()
函数调用这个函数 -
函数
zheev()
专用于复厄米矩阵。首先调用ZHETRD将厄米矩阵化简为三对角线形式。根据是否需要特征向量,然后使用QR算法计算特征值和特征向量(如果需要)。如果选择了方法std
, Armadillo库的eig_sym()
函数将调用此函数 -
如果不需要特征向量,函数
zheevd()
与zheev()
做同样的事情。否则,它会使用分治算法(参见zstedc()
)。如果选择了方法dc
, Armadillo库的eig_sym()
函数将调用该函数。由于分治法被证明对于大矩阵更快,所以它现在是默认方法。
zheevr()
或zheevx
)。如果你想保持密集的矩阵格式,你也可以尝试Eigen库的ComplexEigenSolver
。
下面是一个使用Lapack库的C包装器LAPACKE
的c++小测试。由g++ main.cpp -o main2 -L /home/...../lapack-3.5.0 -llapacke -llapack -lblas
#include <iostream>
#include <complex>
#include <ctime>
#include <cstring>
#include "lapacke.h"
#undef complex
using namespace std;
int main()
{
//int n = 3432;
int n = 600;
std::complex<double> *matrix=new std::complex<double>[n*n];
memset(matrix, 0, n*n*sizeof(std::complex<double>));
std::complex<double> *matrix2=new std::complex<double>[n*n];
memset(matrix2, 0, n*n*sizeof(std::complex<double>));
std::complex<double> *matrix3=new std::complex<double>[n*n];
memset(matrix3, 0, n*n*sizeof(std::complex<double>));
std::complex<double> *matrix4=new std::complex<double>[n*n];
memset(matrix4, 0, n*n*sizeof(std::complex<double>));
for(int i=0;i<n;i++){
matrix[i*n+i]=42;
matrix2[i*n+i]=42;
matrix3[i*n+i]=42;
matrix4[i*n+i]=42;
}
for(int i=0;i<n-1;i++){
matrix[i*n+(i+1)]=20;
matrix2[i*n+(i+1)]=20;
matrix3[i*n+(i+1)]=20;
matrix4[i*n+(i+1)]=20;
matrix[(i+1)*n+i]=20;
matrix2[(i+1)*n+i]=20;
matrix3[(i+1)*n+i]=20;
matrix4[(i+1)*n+i]=20;
}
double* w=new double[n];//eigenvalues
//the lapack function zheev
clock_t t;
t = clock();
LAPACKE_zheev(LAPACK_COL_MAJOR,'V','U', n,reinterpret_cast< __complex__ double*>(matrix), n, w);
t = clock() - t;
cout<<"zheev : "<<((float)t)/CLOCKS_PER_SEC<<" seconds"<<endl;
cout<<"largest eigenvalue="<<w[n-1]<<endl;
std::complex<double> *wc=new std::complex<double>[n];
std::complex<double> *vl=new std::complex<double>[n*n];
std::complex<double> *vr=new std::complex<double>[n*n];
t = clock();
LAPACKE_zgeev(LAPACK_COL_MAJOR,'V','V', n,reinterpret_cast< __complex__ double*>(matrix2), n, reinterpret_cast< __complex__ double*>(wc),reinterpret_cast< __complex__ double*>(vl),n,reinterpret_cast< __complex__ double*>(vr),n);
t = clock() - t;
cout<<"zgeev : "<<((float)t)/CLOCKS_PER_SEC<<" seconds"<<endl;
cout<<"largest eigenvalue="<<wc[0]<<endl;
t = clock();
LAPACKE_zheevd(LAPACK_COL_MAJOR,'V','U', n,reinterpret_cast< __complex__ double*>(matrix3), n, w);
t = clock() - t;
cout<<"zheevd : "<<((float)t)/CLOCKS_PER_SEC<<" seconds"<<endl;
cout<<"largest eigenvalue="<<w[n-1]<<endl;
t = clock();
LAPACKE_zheevd(LAPACK_COL_MAJOR,'N','U', n,reinterpret_cast< __complex__ double*>(matrix4), n, w);
t = clock() - t;
cout<<"zheevd (no vector) : "<<((float)t)/CLOCKS_PER_SEC<<" seconds"<<endl;
cout<<"largest eigenvalue="<<w[n-1]<<endl;
delete[] w;
delete[] wc;
delete[] vl;
delete[] vr;
delete[] matrix;
delete[] matrix2;
return 0;
}
我的计算机的输出是:
zheev : 2.79 seconds
largest eigenvalue=81.9995
zgeev : 10.74 seconds
largest eigenvalue=(77.8421,0)
zheevd : 0.44 seconds
largest eigenvalue=81.9995
zheevd (no vector) : 0.02 seconds
largest eigenvalue=81.9995
这些测试可以通过使用Armadillo库来执行。直接调用Lapack库可能会允许您获得一些内存,但是Lapack的包装器在这方面也是有效的。
真正的问题是你是需要所有的特征向量,所有的特征值还是只需要最大的特征值。因为在最后一种情况下有非常有效的方法。看看Arnoldi/Lanczos迭代算法。如果矩阵是空间的,则可能获得巨大的内存增益,因为只执行矩阵-向量乘积:没有必要保持密集格式。这是在SlepC库中完成的,它利用了Petsc的空间矩阵格式。下面是一个可以作为起点的Slepc示例:
如果有人有同样的问题,我在未来有一些提示后,我找到了解决方案(感谢所有张贴的一些答案或线索!)。
在英特尔网站上,你可以找到一些用Fortran和c写的很好的例子。例如厄米特征值问题例程zheev():https://software.intel.com/sites/products/documentation/doclib/mkl_sa/11/mkl_lapack_examples/zheev_ex.c.htm
要使它在c++中工作,你应该在代码中编辑一些行:
在原型函数声明中,对所有函数做类似的声明:extern void zheev( ... )
更改为extern "C" {void zheev( ... )}
更改调用lapack的函数,添加_
符号,例如:zheev( ... )
到zheev_( ... )
(通过替换使其在代码中全部可用,但我不知道为什么它可以工作。我通过做一些实验弄明白了。
您可以选择将printf
函数转换为标准流std::cout
,并将包含的标头stdio.h
替换为iostream
。
编译运行命令:g++ test.cpp -o test -llapack -lgfortran -lm -Wno-write-strings
关于最后一个选项-Wno-write-strings
,我现在不知道它在做什么,但可能有问题英特尔的例子,当他们把字符串而不是字符调用函数zheev( "Vectors", "Lower", ... )
- 如何声明特征矩阵,然后通过嵌套循环初始化它
- 特征::矩阵<双精度,1,3> 结构类型函数中的返回类型函数
- 有没有一种方法可以通过"typedef"为重新定义的基本类型定义特征和强制转换运算符
- 特征命名访问向量段
- 将特征矩阵的向量设置为0
- 特征:模板函数中矩阵的平面图
- basic_string的前导/尾部不区分空格的特征
- 特征 3 类的模板专用化
- 特征 c++:复矩阵的面积双曲正切(atanh)
- C++ 中的特征向量计算
- 根据C++标准的定义实现"is_similar"类型特征
- C++类型特征,以查看是否可以<uint32_t>对类型"K"的任何变量调用"static_cast(k)"
- 有没有办法找到特征矩阵系数的中值?
- 如何将高维数据映射到特征类型?
- 将平面阵列重塑为复杂的特征类型
- 特征 LLT 模块给出不正确的结果?
- 特征模板化函数和维度
- 特征稀疏向量:求最大系数
- 特征 3.3.x:如何在所有行中操作 lamba?
- 如何将向量断言到特征矩阵