修正Cholesky分解

[] [] []


修正Cholesky分解のアルゴリズム

A=RDR

変数を節約するため対角rii=diとする。さらに作業領域として下三角rji=sij (i<j) と使ってもよい。

【外積形式】

/* Aの上三角をRに入れる */

for i=1 to n

  for j=1 to n

     if(i<=j) rij = aij

     if(i>j )  rij = 0

 

/* Rの計算 */

for k=1 to n

  for i=k+1 to n

     for j=i to n

         rij = rij - rki* rkj/rkk

  for j=k+1 to n

    rkj = rkj/rkk

【内積形式、行ごと】

/* Aの上三角をRに入れる */

for i=1 to n

  for j=1 to n

     if(i==j) rij = aij

     if(i<j)  sij = aij

     if(i>j)  rij = 0

/* Rの計算 */

for k=1 to n

    for i=1 to k-1

        rkk = rkk - rik* sik

    for j=k+1 to n

        for i=1 to k-1

            skj = skj - rik* sij

        rkj = skj/rkk

【内積形式、列ごと】

/* Aの上三角をRに入れる */

for i=1 to n

  for j=1 to n

     if(i==j) rij = aij

     if(i<j)  sij = aij

     if(i>j)  rij = 0

/* Rの計算 */

for k=1 to n

    for i=1 to k-1

        for j=1 to i-1

            sik = sik - rji* sjk

        rik = sik/rii

        rkk = rkk - rik* sik

連立1次方程式Ax=b を解くには

/* Ry=byについて解く(前進消去) */

for i=1 to n

   tmp = 0

   for j=1 to i-1

      tmp = tmp + rji* yj

   yi = bi - tmp

/* Rx=D-1yxについて解く(後退代入) */

for i=n to 1

   tmp = 0

   for j=i+1 to n

      tmp = tmp + rij xj

   xi = yi / rii - tmp

正定値のエルミート行列Aを三角行列に分解するアルゴリズムを導く。

Aの対角要素aii はエルミート行列だから実数であり

さらに正定値だからaii>0であることに注意。

まずnn列のA11列目とそれ以外に分けて次のように書く

対角要素a11の下を消去すると

さらにa11の右側も消去できて、A3つの行列に分解できる。

ここで

このn-1n-1列の行列も正定値のエルミート行列になっている。なぜなら

の右辺は明らかに正定値のエルミート行列だからである。

したがって同様に分解を続ければ、対角行列Dと対角要素が1である下三角Rと上三角Rに分解される。これを修正コレスキー分解という

A=RDR

具体的には、次の手順になる。ただし変数を節約するためrii=di とする。

Aの上三角を作業領域Rに入れる。

・ステップk (k=1 to n)において

 k+1行目から下のr を求める

  rij = rij - rki* rkj/rkk (i=k+1 to n, j=i to n)

  (LU分解のj=k+1 to nに比べて半分で済む)

 k行目のrを求める

  rkj = rkj/rkk (i=k+1 to n)

【外積形式】

/* Aの上三角をRに入れる */

for i=1 to n

    for j=1 to i-1

        rij = 0

    for j=i to n

        rij = aij

/* Rの計算 */

for k=1 to n

    for i=k+1 to n

        for j=i to n

            rij = rij - rki* rkj/rkk

    for j=k+1 to n

        rkj = rkj/rkk

◇◇

上記のアルゴリズムは外積aaを使うので外積形式とよばれるが、内積形式のアルゴリズムもある

アルゴリズムを導く方針は、A=RDRと分解できたとして要素ごとに式を書き下し、これをd,rについて解く。

簡単のためn=3の場合を書き下すと

d,rについて解く手順として、行ごとか列ごとかの2とおりがある。

◇◇◇

行ごとに解くと

1行目 d1=a11 , r12=a12/d1 , r13=a13/d1

2行目 d2=a22 - d1 r12*r12 , r23=(a23 - d1 r12*r13)/d2

3行目 d3=a33 - d1 r 13*r13 - d2 r23*r23

この手順を一般化すると次のようになる。ただし変数を節約するためrii=di とする。

Aの上三角を作業領域Rに入れる。

・ステップk (k=1 to n)において

k行目のrkk を求める

k行目のrkj を求める

ここでriiで割ってから再びかけるのは無駄なので

中間変数sij=riirij を使うとよい

【内積形式,行ごと】

/* Aの上三角をRに入れる */

for i=1 to n

    for j=1 to i-1

        rij = 0

    rii = aii

    for j=i+1 to n

        sij = aij

/* Rの計算 */

for k=1 to n

    for i=1 to k-1

        rkk = rkk - rik* sik

    for j=k+1 to n

        for i=1 to k-1

            skj = skj - rik* sij

        rkj = skj/rkk

◇◇◇◇

列ごとに解くと

1列目 d1=a11

2列目 r12=a12/d1 ,d2=a22 - d1 r12*r12

3列目 r13=a13/d1 , r23=(a23 - d1 r12*r13)/d2 , d3=a33 - d1 r 13*r13 - d2 r23*r23

この手順を一般化すると次のようになる。ただし変数を節約するためrii=di とする。

Aの上三角を作業領域Rに入れる。

・ステップk (k=1 to n)において

k列目のrik を求める

k列目のrkk を求める

ここでrii で割ってから再びかけるのは無駄なので

中間変数sij=riirij を使うとよい

【内積形式,列ごと】

/* Aの上三角をRに入れる */

for i=1 to n

    for j=1 to i-1

        rij = 0

    rii = aii

    for j=i+1 to n

        sij = aij

/* Rの計算 */

for k=1 to n

    for i=1 to k-1

        for j=1 to i-1

            sik = sik - rji* sjk

        rik = sik/rii

         rkk = rkk - rik* sik

◇◇◇◇◇

上記のアルゴリズムを検証するために使ったCコードは次のとおり。変数sij は下三角rjiを使っている。

/* choleskey.cpp */

#include <iostream>

#include <complex>

using namespace std;

static const int N = 3;     // 行列のサイズ

typedef complex<float> cpx_t;      // 複素数型

 

/* 行列を表示するテンプレート */

template <int M,int N>

void print_matrix(cpx_t a[M][N]) {

    for(int i=0; i<M; i++) {

        cout << "    ";

        for(int j=0; j<N; j++) { cout << a[i][j] << "\t"; }

        cout << endl;

    }

}

/* ベクトルを表示するテンプレート */

template <int M>

void print_vector(cpx_t a[M]) {

    for(int i=0; i<M; i++) { cout << "    " << a[i] << endl; }

}

/***************************************************************

 * サブルーチン - 修正コレスキー分解,外積形式 (R)=(U_conj)(D)(U)

***************************************************************/

void cholesky_outer(cpx_t r[N][N]) {

    for(int k=0; k<N; k++) {

        for(int i=k+1; i<N; i++) {

            for(int j=i; j<N; j++) {

                r[i][j] -= conj(r[k][i]) * r[k][j] / r[k][k];

            }

        }

        for(int i=k+1; i<N; i++) {

            r[k][i] = r[k][i] / r[k][k];

        }

    }

}

/***************************************************************

 * サブルーチン - 修正コレスキー分解,内積形式,行ごと (R)=(U_conj)(D)(U)

***************************************************************/

void cholesky_inner_kj(cpx_t r[N][N]) {

    for(int k=0; k<N; k++) {

        for(int i=0; i<k; i++) {

            r[k][k] -= conj(r[i][k]) * r[k][i];

        }

        for(int j=k+1; j<N; j++) {

            for(int i=0; i<k; i++) {

                r[j][k] -= conj(r[i][k]) * r[j][i];

            }

            r[k][j] = r[j][k] / r[k][k];

        }

    }

}

/***************************************************************

 * サブルーチン - 修正コレスキー分解,内積形式,列ごと (R)=(U_conj)(D)(U)

 ***************************************************************/

void cholesky_inner_ik(cpx_t r[N][N]) {

    for(int k=1; k<N; k++) { // k=0のときは何もしないのでk=1から始める

        for(int i=0; i<k; i++) {

            for(int j=0; j<i; j++) {

                r[k][i] -= conj(r[j][i]) * r[k][j];

            }

            r[i][k] = r[k][i] / r[i][i];

            r[k][k] -= conj(r[i][k]) * r[k][i];

        }

    }

}

/***************************************************************

 * サブルーチン - 前進消去 (U_conj)y=byについて解く

***************************************************************/

void forward_elimination(cpx_t r[N][N],cpx_t b[N],cpx_t y[N]) {

    cpx_t tmp;

    for(int i=0; i<N; i++) {

        tmp = 0;

        for(int j=0; j<i; j++) {

            tmp += conj(r[j][i]) * y[j];

        }

        y[i] = b[i] - tmp;

    }

}

/***************************************************************

 * サブルーチン - 後退代入 (D)(U)x=yxについて解く

***************************************************************/

void back_substitution(cpx_t r[N][N],cpx_t y[N],cpx_t x[N]) {

    cpx_t tmp;

    for(int i=N-1; i>=0; i--) {

        tmp = 0;

        for(int j=i+1; j<N; j++) {

            tmp += r[i][j] * x[j];

        }

        x[i] = y[i] / r[i][i] - tmp;

    }

}

/***************************************************************

 * メインルーチン - (A)x = bxについて解く

***************************************************************/

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

    cpx_t r[N][N];

    cpx_t x[N];

    cpx_t y[N];

    cpx_t Ax[N];

   

    /* 行列(A)bに値を設定 */

    cpx_t a[N][N] =

       { {  2   ,  0+1i,  1    }

       , {  0-1i,  3   ,  0-1i }

       , {  1   ,  0+1i,  4    } };

    cpx_t b[N] =

       { 1+1i

       , 2-1i

       , 3+1i };

    cout << "-- A=" << endl;

    print_matrix<N,N>(a);

    cout << "-- b=" << endl;

    print_vector<N>(b);

   

    /* 行列(R)(A)を代入 */

    for(int i=0; i<N; i++) {

        for(int j=0; j<N; j++) {

#ifdef CHOLESKY_OUTER

            // 外積形式のとき上三角に入れて,結果も上三角に出る,下三角は不使用

            r[i][j] = (i<=j)? a[i][j] : 999;

#else

            // 内積形式のとき下三角に入れて,結果は上三角に出る

            r[j][i] = (i<=j)? a[i][j] : 999;

#endif

        }

    }

    cout << "-- initial R=" << endl;

    print_matrix<N,N>(r);

   

    /* 修正コレスキー分解 (R)=(U_conj)(D)(U) */

#ifdef CHOLESKY_OUTER

    cholesky_outer(r);      // 外積形式

#elif defined CHOLESKY_INNER_KJ

    cholesky_inner_kj(r);   // 内積形式,行ごと

#else

    cholesky_inner_ik(r);   // 内積形式,列ごと

#endif

   

    /* 前進消去 (U_conj)y=byについて解く */

    forward_elimination(r,b,y);

   

    /* 後退代入 (D)(U)x=yxについて解く */

    back_substitution(r,y,x);

   

    /* 分解後の行列(R)xを表示 */

    cout << "-- R=" << endl;

    print_matrix<N,N>(r);

    cout << "-- x=" << endl;

    print_vector<N>(x);

   

    /* 検算 (A)x=b */

    for(int i=0; i<N; i++) {

        Ax[i] = 0;

        for(int j=0; j<N; j++) {

            Ax[i] += a[i][j] * x[j];

        }

    }

    cout << "-- Ax=" << endl;

    print_vector<N>(Ax);

}

CHOLESKY_INNER_KJの場合

-- A=

    (2,0)     (0,1)  (1,0) 

    (0,-1)    (3,0)  (0,-1)

    (1,0)     (0,1)  (4,0) 

-- p=

    (1,1)

    (2,-1)

    (3,1)

-- initial R=

    (2,0)     (999,0)       (999,0)      

    (0,1)     (3,0)  (999,0)      

    (1,0)     (0,-1) (4,0) 

-- R=

    (2,0)     (0,0.5)       (0.5,0)      

    (0,1)     (2.5,0)       (0,-0.2)     

    (1,0)     (0,-0.5)      (3.4,0)      

-- x=

    (0.117647,0.176471)

    (0.588235,-0.0588235)

    (0.705882,0.0588235)

-- Ax=

    (1,1)

    (2,-1)

    (3,1)


[] [] []