броненосец — низкий потребляющий ОЗУ c ++ собственный решатель

Я новичок в Программирование на C ++, но у меня есть задача вычислить собственные значения и собственные векторы (стандартная собственная проблема Ax = лк) для симметричных матриц (и эрмитовых)) для очень больших матриц размера: биномиальное (L, L / 2) где L около 18-22. Сейчас я тестирую его на машине с 7,7 ГБ оперативной памяти, но в итоге у меня будет доступ к ПК с 64 ГБ ОЗУ.

Я начал с Lapack ++. Вначале мой проект предполагает решить эту проблему только для симметричных вещественных матриц.

Эта библиотека была великолепна. Очень быстрый и маленький объем оперативной памяти. Он имеет возможность вычислить собственные векторы и поместить во входную матрицу A для экономии памяти. Оно работает! я думал так Lapack ++ eigensolver может работать с эрмитовой матрицей, но не может по неизвестной причине (может быть, я что-то не так делаю). Мой проект развился, и я должен быть в состоянии рассчитать эту проблему для эрмитовых матриц.

Поэтому я попытался изменить библиотеку на Библиотека Армадилло. Это работает хорошо, но это не так хорошо, как Lapack ++ который заменяет mat A со всем eigenvec, но, конечно, поддерживает эрмитовы матрицы.

Немного статистики для L = 14

  • Lapack ++ ОЗУ 126 МБ, время 7,9 с, собственные значения + собственные векторы

  • броненосец RAM 216MB время 12с собственное

  • броненосец ОЗУ 396 МБ, время 15с, собственные значения + собственные векторы

Давайте сделаем некоторые расчеты: double переменная о . Моя матрица имеет размер
бином (14,7) = 3432, так что в идеальном случае это должно иметь 3432 ^ 2 * 8/1024 ^ 2 = 89 МБ.

Мой вопрос: возможно ли изменить или сообщить броненосец сделать хороший трюк, как Lapack ++? броненосец использования LAPACK а также BLAS Подпрограммы. Или, может быть, кто-то может порекомендовать другой подход к этой проблеме, используя другую библиотеку?

P.S .:
Моя матрица действительно скудна. Это о 2 * бином (L, L / 2) ненулевые элементы.
Я пытался вычислить это с SuperLU в формате CSC, но это было не очень эффективно, для L = 14 -> RAM 185MB, но время 135 с.

3

Решение

И Лапакпп, и Армадилло полагаются на Лапака для вычисления собственных значений и собственных векторов комплексных матриц. Библиотека Лапака предоставляет различные способы выполнения этих операций для сложных эрмитовых матриц.

  • Функция zgeev() не заботится о том, чтобы матрица была эрмитовой. Эта функция вызывается библиотекой Lapackpp для матриц типа LaGenMatComplex в функции LaEigSolve. Функция eig_gen() из библиотеки Armadillo вызывает эту функцию.

  • Функция zheev() посвящен сложным эрмитовым матрицам. Сначала вызывается ZHETRD для приведения эрмитовой матрицы к трехдиагональной форме. В зависимости от того, нужны ли собственные векторы, он затем использует Алгоритм QR вычислить собственные значения и собственные векторы (при необходимости). Функция eig_sym() библиотеки Armadillo вызвать эту функцию, если метод std выбран.

  • Функция zheevd() делает то же самое, что и zheev() если собственные векторы не требуются. В противном случае он использует алгоритм «разделяй и властвуй» (см. zstedc()). Функция eig_sym() библиотеки Armadillo вызвать эту функцию, если метод dc выбран. Так как «разделяй и властвуй» оказались быстрее для больших матриц, теперь это метод по умолчанию.

Функции с большим количеством опций доступны в библиотеке Lapack. (увидеть zheevr() или же zheevx). Если вы хотите сохранить плотный матричный формат, вы также можете попробовать ComplexEigenSolver из собственной библиотеки.

Вот небольшой тест на C ++ с использованием оболочки C LAPACKE библиотеки Лапак. Составлено 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 также могут быть эффективны в этом аспекте.

Реальный вопрос заключается в том, нужны ли вам все собственные векторы, все собственные значения или только самые большие собственные значения. Потому что в последнем случае есть действительно эффективные методы. Посмотрите на Арнольди /Lanczos Итеративные алгоритмы. Огромный прирост памяти возможен, если матрица разрежена, поскольку выполняются только матрично-векторные произведения: нет необходимости сохранять плотный формат. Это то, что делается в библиотеке SlepC, которая использует форматы разреженной матрицы Petsc. Вот пример Slepc который может быть использован в качестве отправной точки.

4

Другие решения

Если у кого-то возникнет та же проблема, что и у меня в будущем, то после того, как я нашел решение, есть несколько советов (спасибо за все, что опубликовали некоторые ответы или подсказки!).

На сайте Intel вы можете найти несколько хороших примеров, написанных на фортране и 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( ... )}

Изменить функцию вызова вызывающего Лапака _ символ например: zheev( ... ) в zheev_( ... ) (сделать это для всего кода просто заменой, но я не знаю, почему это работает. Я понял это, проведя некоторые эксперименты.)

При желании вы можете конвертировать printf функция в стандартный поток std::cout и заменить включенные заголовки stdio.h в iostream,

Чтобы скомпилировать команду run, выполните следующие действия: g++ test.cpp -o test -llapack -lgfortran -lm -Wno-write-strings

О последнем варианте -Wno-write-stringsТеперь я не знаю, что он делает, но, вероятно, есть проблемы с примером Intel, когда они помещают строку, а не символ в вызывающую функцию. zheev( "Vectors", "Lower", ... )

1

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector