C ++でLAPACKを使い始める方法は?


10

私は計算科学が初めてで、統合、補間、c ++でのRK4、Numerovなどの基本的な方法をすでに学びましたが、最近、教授から、LAPACKを使用して行列に関連する問題を解決する方法を学ぶように求められました。たとえば、複雑な行列の固有値を見つけるようなものです。私はサードパーティのライブラリを使用したことがなく、ほとんど常に自分の関数を記述しています。私は数日間探し回っていましたが、素人向けのlapackガイドは見つかりませんでした。それらはすべて私が理解できない言葉で書かれており、すでに書かれた関数を使用することがなぜこれほど複雑になるのか、私にはわかりません。彼らはzgeev、dtrsvなどの言葉でいっぱいで、私は不満を感じています。私はこの疑似コードのようなものをコーディングしたいだけです:

#include <lapack:matrix>
int main(){
  LapackComplexMatrix A(n,n);
  for...
   for...
    cin>>A(i,j);
  cout<<LapackEigenValues(A);
  return 0;
}

ばかげているのか、アマチュアなのかわかりません。しかし、繰り返しますが、これはそれほど難しいことではありませんか?LAPACKとLAPACK ++のどちらを使用すればよいかわかりません。(私はc ++でコードを書いていて、PythonやFORTRANの知識がありません)、そしてそれらをインストールする方法。


おそらく次の例が役に立つでしょう:matrixprogramming.com/files/code/LAPACK
nukeguy

始めたばかりなら、ArrayFire github.com/arrayfire/arrayfireのような単純なライブラリを使用する方が簡単でしょう。C ++から直接呼び出すことができ、APIの方が簡単で、LAPACKが行うすべての操作を実行できると思います。
Vikram 2017年

この他の投稿では、ユーザーがLAPACKの紹介を容易にする非常に優れた構文を持つ独自のラッパーFLENSを提案しています。
Zythos、

LAPACK関数を直接呼び出すことは非常に退屈で、エラーが発生しやすくなります。Armadilloのように、はるかに使いやすいLAPACK用のユーザーフレンドリーなC ++ラッパーがいくつかあります。複雑な固有分解の具体的な使用例については、ユーザーフレンドリーなeig_gen()関数を参照してください。この関数は、このLAPACKの怪物、zheev(JOBZ、UPLO、N、A、LDA、W、WORK、LWORK、RWORK、INFO)をラップしています。そして、取得した固有値と固有ベクトルを標準表現に再フォーマットします。
hbrerkere、

回答:


18

私は他の回答の一部と一致しないと私はLAPACKを使用する方法を考え出すことと信じていると言っているつもりである科学技術計算の分野で重要。

ただし、LAPACKの使用には大きな学習曲線があります。これは非常に低いレベルで書かれているためです。それの不利な点は、それが非常に不可解であり、感覚に心地よくないということです。これの利点は、インターフェースが明確であり、基本的に変更されないことです。さらに、Intel Math Kernel LibraryなどのLAPACKの実装は非常に高速です。

私自身の目的のために、LAPACKサブルーチンをラップする独自の高レベルC ++クラスがあります。多くの科学図書館もLAPACKを使用しています。それらを使用する方が簡単な場合もありますが、私の意見では、その下にあるツールを理解することには多くの価値があります。そのために、私はLAPACKを使用してC ++で記述された小さな実用的な例を提供し、あなたが始められるようにしました。これは、liblapack3パッケージがインストールされているUbuntuで動作し、ビルドに必要な他のパッケージも含まれます。おそらくほとんどのLinuxディストリビューションで使用できますが、LAPACKのインストールとリンクはさまざまです。

これがファイルです test_lapack.cpp

#include <iostream>
#include <fstream>


using namespace std;

// dgeev_ is a symbol in the LAPACK library files
extern "C" {
extern int dgeev_(char*,char*,int*,double*,int*,double*, double*, double*, int*, double*, int*, double*, int*, int*);
}

int main(int argc, char** argv){

  // check for an argument
  if (argc<2){
    cout << "Usage: " << argv[0] << " " << " filename" << endl;
    return -1;
  }

  int n,m;
  double *data;

  // read in a text file that contains a real matrix stored in column major format
  // but read it into row major format
  ifstream fin(argv[1]);
  if (!fin.is_open()){
    cout << "Failed to open " << argv[1] << endl;
    return -1;
  }
  fin >> n >> m;  // n is the number of rows, m the number of columns
  data = new double[n*m];
  for (int i=0;i<n;i++){
    for (int j=0;j<m;j++){
      fin >> data[j*n+i];
    }
  }
  if (fin.fail() || fin.eof()){
    cout << "Error while reading " << argv[1] << endl;
    return -1;
  }
  fin.close();

  // check that matrix is square
  if (n != m){
    cout << "Matrix is not square" <<endl;
    return -1;
  }

  // allocate data
  char Nchar='N';
  double *eigReal=new double[n];
  double *eigImag=new double[n];
  double *vl,*vr;
  int one=1;
  int lwork=6*n;
  double *work=new double[lwork];
  int info;

  // calculate eigenvalues using the DGEEV subroutine
  dgeev_(&Nchar,&Nchar,&n,data,&n,eigReal,eigImag,
        vl,&one,vr,&one,
        work,&lwork,&info);


  // check for errors
  if (info!=0){
    cout << "Error: dgeev returned error code " << info << endl;
    return -1;
  }

  // output eigenvalues to stdout
  cout << "--- Eigenvalues ---" << endl;
  for (int i=0;i<n;i++){
    cout << "( " << eigReal[i] << " , " << eigImag[i] << " )\n";
  }
  cout << endl;

  // deallocate
  delete [] data;
  delete [] eigReal;
  delete [] eigImag;
  delete [] work;


  return 0;
}

これはコマンドラインを使用して構築できます

g++ -o test_lapack test_lapack.cpp -llapack

これにより、という名前の実行可能ファイルが生成されtest_lapackます。テキスト入力ファイルを読み取るように設定しました。これmatrix.txtは3x3マトリックスを含むという名前のファイルです。

3 3
-1.0 -8.0  0.0
-1.0  1.0 -5.0
 3.0  0.0  2.0

プログラムを実行するには、単に次のように入力します

./test_lapack matrix.txt

コマンドラインで、出力は

--- Eigenvalues ---
( 6.15484 , 0 )
( -2.07742 , 3.50095 )
( -2.07742 , -3.50095 )

コメント:

  • LAPACKの命名方式に惑わされているようです。簡単な説明はこちらです。
  • DGEEVサブルーチンのインターフェースはこちらです。そこでの議論の説明を、私がここで行ったことと比較できるはずです。
  • extern "C"上部のセクションと、アンダースコアをに追加したことに注意してくださいdgeev_。これは、ライブラリがFortranで作成およびビルドされたため、リンク時にシンボルを一致させるために必要です。これはコンパイラとシステムに依存するため、Windowsで使用する場合はすべて変更する必要があります。
  • LAPACKへCインターフェースの使用を提案する人もいます。彼らは正しいかもしれませんが、私はいつもこのようにしてきました。

3
あなたが探しているものの多くは、いくつかのクイックグーラゲで見つけることができます。たぶん、何を検索すればいいのかわからないかもしれません。NetlibはLAPACKの管理人です。ドキュメントはここにありますこのページには、LAPACKの主な機能の便利な表があります。重要なものには、(1)連立方程式の解法、(2)固有値問題、(3)特異値分解、(4)QR分解があります。DGEEVのマニュアルを理解しましたか?
LedHead 2017年

1
それらはすべて同じものに対する異なるインターフェースです。LAPACKはオリジナルです。これはFortranで記述されているため、使用するには、ゲームをプレイして、C / C ++からのクロスコンパイルを機能させる必要があります。私はLAPACKEを使用したことがありませんが、このクロスコンパイルビジネスを回避するLAPACK上のかなり薄いCラッパーのようですが、それでもかなり低レベルです。LAPACK ++はさらに高いレベルのC ++ラッパーであるように見えますが、それはもうサポートされていないと思います(私が間違っていれば誰かが私を修正します)。
LedHead 2017年

1
特定のコードコレクションについては知りません。しかし、LAPACKサブルーチン名のいずれかをググると、StackExchangeサイトの1つで古い質問が必ず見つかります。
LedHead 2017年

1
@AlirezaHashemiちなみに、WORK配列を用意する必要があるのは、原則としてLAPACKがそのサブルーチン内にメモリを割り当てないためです。LAPACKを使用している場合は、メモリを大量に使用している可能性が高く、メモリの割り当てにはコストがかかるため、呼び出し元のルーチンがメモリ割り当てを担当することは理にかなっています。DGEEVは中間量を格納するためにメモリを必要とするため、その作業スペースをそれに提供する必要があります。
LedHead 2017年

1
とった。そして、zgeevを使用して複素行列の固有値を計算する最初のコードを正常に記述しました。そして、すでにもっとや​​っています!ありがとう!
Alireza

7

私は通常、質問に答えるのではなく、何をすべきかを人々に話すのを拒否しますが、この場合は例外を設けます。

LapackはFORTRANで書かれており、APIは非常にFORTRANに似ています。LapackへのC APIがあり、インターフェースが少し苦痛を軽減しますが、C ++からLapackを使用することは決して楽しい経験ではありません。

また、そこに呼ばれるC ++行列のクラスライブラリである固有 LAPACKの機能の多くを持って、より良いLAPACKの実装に匹敵する計算性能を提供し、C ++から使用することは非常に便利です。特に、ここでは、Eigenを使用してサンプルコードを作成する方法を示します。

#include <iostream>
using std::cout;
using std::endl;

#include <Eigen/Eigenvalues>

int main()
{
  const int n = 4;
  Eigen::MatrixXd a(n, n);
  a <<
    0.35, 0.45, -0.14, -0.17,
    0.09, 0.07, -0.54, 0.35,
    -0.44, -0.33, -0.03, 0.17,
    0.25, -0.32, -0.13, 0.11;
  Eigen::EigenSolver<Eigen::MatrixXd> es;
  es.compute(a);
  Eigen::VectorXcd ev = es.eigenvalues();
  cout << ev << endl;
}

この固有値問題の例は、ラパック関数のテストケースです dgeev。この問題のdgeev例のFORTRANコードと結果を表示し て、独自の比較を行うことができます。


回答・説明ありがとうございます!このライブラリを試して、自分のニーズに最も適したライブラリを選択します。
Alireza

ああ、オーバーロードoperator,!実際に行われたことは見たことがない:-)
Wolfgang Bangerth 2017年

1
実際、そのoperator,オーバーロードは最初に現れるよりも興味深い/優れています。行列の初期化に使用されます。行列を初期化するエントリは、スカラー定数にすることができますが、以前に定義された行列またはサブ行列にすることもできます。非常にMATLABに似ています。私のC ++プログラミング能力が自分自身を洗練する何かを実装するのに十分良かったと思います;-)
Bill Greene

7

上記と同じように別の答えがあります。

Armadillo C ++線形代数ライブラリを調べてください。

長所:

  1. 関数の構文は高レベルです(MATLABの構文に似ています)。つまり、DGESVmumbo-jumboはありませんX = solve( A, B )奇妙に見えるLAPACK関数名の背後には理由がありますが ...)。
  2. さまざまな行列分解(LU、QR、固有値、SVD、コレスキーなど)を実装します。
  3. 適切に使用すると高速です。
  4. それは十分に文書化されています
  5. スパース行列をサポートしています(これらについては後で詳しく説明します)。
  6. 最適化されたパフォーマンスのために、それを超最適化されたBLAS / LAPACKライ​​ブラリーにリンクできます。

これは、@ BillGreeneのコードがArmadilloでどのように見えるかです。

#include <iostream>
#include <armadillo>

using namespace std;
using namespace arma;

int main()
{
   const int k = 4;
   mat A = zeros<mat>(k,k) // mat == Mat<double>

   // with the << operator...
   A <<
    0.35 << 0.45 << -0.14 << -0.17 << endr
    0.09 << 0.07 << -0.54 << 0.35  << endr
    -0.44 << -0.33 << -0.03 << 0.17 << endr
    0.25 << -0.32 << -0.13 << 0.11 << endr;

   // but using an initializer list is faster
   A = { {0.35, 0.45, -0.14, -0.17}, 
         {0.09, 0.07, -0.54, 0.35}, 
         {-0.44, -0.33, -0.03, 0.17}, 
         {0.25, -0.32, -0.13, 0.11} };

   cx_vec eigval; // eigenvalues may well be complex
   cx_mat eigvec;

   // eigenvalue decomposition for general dense matrices
   eig_gen(eigval, eigvec, A);

   std::cout << eigval << std::endl;

   return 0;
}

回答・説明ありがとうございます!このライブラリを試して、自分のニーズに最も適したライブラリを選択します。
Alireza
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.