低RAM消耗c ++本征求解器

时间:2015-08-28 10:32:40

标签: c++ armadillo eigenvector eigenvalue lapack++

我是 C ++编程中的新手,但我有一项任务是计算对称矩阵(和hermitian)的特征值和特征向量(标准特征问题 Ax = lx ) ))对于非常大的矩阵矩阵:二项式(L,L / 2)其中 L 约为18-22。现在我在拥有大约7.7 GB RAM的机器上测试它,但最后我可以使用64GB RAM访问PC。

我已经开始使用 Lapack ++ 。一开始,我的项目假设只针对对称的实矩阵来解决这个问题。

这个图书馆很棒。非常快速和小RAM消耗。它可以选择计算特征向量并放入输入矩阵A以节省内存。有用!我认为 Lapack ++ eigensolver可以处理Hermitian矩阵,但它可能由于未知原因(可能我做错了)。我的项目已经发展,我应该能够计算Hermitian矩阵的这个问题。

所以我试图将库更改为 Armadillo库。它工作得很好,但它并不像 Lapack ++ 那样用mat A取代eigenvec,但当然支持Hermitian矩阵。

L = 14

的一些统计数据
  • Lapack ++ RAM 126MB时间7.9s特征值+特征向量

  • Armadillo RAM 216MB时间12s特征

  • Armadillo RAM 396MB时间15s特征值+特征向量

让我们做一些计算:double变量约为 8B 。我的矩阵有大小 二项式(14,7)= 3432 ,所以在理想情况下它应该 3432 ^ 2 * 8/1024 ^ 2 = 89 MB

我的问题是:是否可以修改或强制 Armadillo 做一个很好的技巧 Lapack ++ Armadillo 使用LAPACKBLAS例程。或者也许有人可以使用另一个库重新考虑另一种解决此问题的方法?

P.S .: 我的矩阵非常稀疏。它具有 2 *二项式(L,L / 2)非零元素。 我尝试用CSC格式的SuperLU来计算这个但是它不是非常有效,因为L = 14 - > RAM 185MB,但时间135s。

2 个答案:

答案 0 :(得分:4)

Lapackpp和Armadillo都依赖于Lapack来计算复矩阵的特征值和特征向量。 Lapack库为复杂的厄米特矩阵提供了不同的方法来执行这些操作。

  • 函数zgeev()并不关心矩阵是Hermitian。 Laplacepp库为函数LaEigSolve中类型为LaGenMatComplex的矩阵调用此函数。 Armadillo库的函数eig_gen()调用此函数。

  • 函数zheev()专用于复杂的Hermitian矩阵。它首先调用ZHETRD将Hermitian矩阵减少为三对角形式。根据是否需要特征向量,它然后使用QR algorithm来计算特征值和特征向量(如果需要)。如果选择方法std,Armadillo库的函数eig_sym()会调用此函数。

  • 如果不需要特征向量,函数zheevd()zheev()的作用相同。否则,它使用除法和conquert算法(见zstedc())。如果选择方法dc,则Armadillo库的函数eig_sym()会调用此函数。由于分裂和征服证明对于大型矩阵更快,它现在是默认方法。

Lapack库中提供了更多选项的功能。 (请参阅zheevr()zheevx)。如果你想保持密集矩阵格式,你也可以尝试使用特征库的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迭代算法。如果矩阵是sparce,则可能获得巨大的内存增益,因为只执行矩阵向量乘积:不需要保持密集格式。这是在SlepC库中完成的,它利用了Petsc的sparce矩阵格式。 Here is an example of Slepc可以作为起点。

答案 1 :(得分:1)

如果有人在将来遇到与我相同的问题,那么在我找到解决方案后会有一些提示(感谢所有发布了一些答案或线索!)。

在intel网站上,你可以找到一些用Fortran和C编写的很好的例子。例如,hermitian特征值问题例程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", ... ),我现在不知道它在做什么,但是当他们把字符串放入字符串而不是英特尔的例子时可能存在问题调用函数select sum(value) as vv, officer from bulb where officer='" +jTable1.getValueAt(i, 0) + "' && STR_TO_DATE(`startdate`,'%Y-%m-%d') BETWEEN '" + f2.getText() + "' AND '" + t2.getText() + "' group by carddtype