メモの日々


2011年11月02日(水) [長年日記]

[c][math] LAPACKを使って行列の固有値計算

線形代数計算ライブラリのLAPACKを使ったのでちょっとだけメモ。

LAPACKを説明したサイトは色々あるけど、数値計算マニュアル:線型代数 (FAQ)がよくまとまっていてよかった。日本計算工学会誌「計算工学」からリンクされている「BLAS, LAPACKチュートリアル パート1」(Vol16,No.2)、「BLAS, LAPACKチュートリアル パート2」(Vol.16,No.3)も参考になる。

実対称行列の固有値と固有ベクトルを計算してくれる関数 dsyev_() を使ったサンプルを書いてみた。関数の戻り値が何を表すのかが不明。

#include <cmath>
#include <iostream>
#include <stdexcept>
#include <vector>

using std::vector;

// f2c.h, clapack.h から必要な宣言を抜粋
extern "C" {
    typedef long int integer;
    typedef double doublereal;

    int dsyev_(
            char *jobz,
            char *uplo,
            integer *n,
            doublereal *a,
            integer *lda,
            doublereal *w,
            doublereal *work,
            integer *lwork,
            integer *info);
}

/**
 * 実対称行列の固有値/固有ベクトルを計算する
 *
 * @param matrix 計算対象の行列。上三角部分だけが設定されていればよい。
 *               matrixの内容は破壊される(固有ベクトルが設定される)。
 *
 * @return matrixの全固有値からなるvectorと、全固有ベクトルのvector。
 *         戻り値の0番目のvectorが全固有値、
 *         戻り値の1番目のvectorが0番目の固有値に対する固有ベクトル、
 *         戻り値の2番目のvectorが1番目の固有値に対する固有ベクトル、
 *         ...
 */
std::vector<std::vector<double> > eigen(std::vector<double>& matrix) {
    if (matrix.empty()) return vector<vector<double> >();

    char jobz = 'V';                // 固有ベクトルも計算する
    char uplo ='U';                 // 上三角部分を使用。ただし列優先。
    integer n = static_cast<integer>(std::sqrt(matrix.size())); // matrixの行数
    integer lda = n;                // matrixのleading dimension
    vector<doublereal> work(n * 3); // dsyev_()の作業領域
    integer lwork = static_cast<integer>(work.size());          // workのサイズ
    integer info = 0;               // dsyev_()の処理結果

    vector<double> eigenvalues(n);
    dsyev_( &jobz,
            &uplo,
            &n,
            &matrix[0],
            &lda,
            &eigenvalues[0],
            &work[0],
            &lwork,
            &info);

    if (info) {
        throw std::runtime_error("error: dsyev_()");
    }

    vector<vector<double> > result(n + 1, vector<double>(n));
    result[0] = eigenvalues;

    // LAPACKは列優先
    for (integer col = 0; col < n; ++col) {
        for (integer row = 0; row < n; ++row) {
            result[col + 1][row] = matrix[col * n + row];
        }
    }
    return result;
}

int main() {
    vector<double> m;
    m.push_back(0);
    m.push_back(1); // ここの値は使われない
    m.push_back(1);
    m.push_back(-1);

    vector<vector<double> > eigens = eigen(m);

    if (eigens.empty()) {
        throw std::runtime_error("BUG: no eivenvalue.");
    }

    std::cout << "eigenvalues:";
    for (std::size_t i = 0; i < eigens[0].size(); ++i) {
        std::cout << " " << eigens[0][i];
    }
    std::cout << "\n";

    for (std::size_t i = 1; i < eigens.size(); ++i) {
        std::cout << "eigenvector" << i << ":";
        for (std::size_t j = 0; j < eigens[i].size(); ++j) {
            std::cout << " " << eigens[i][j];
        }
        std::cout << "\n";
    }
}