LoginSignup
kiritorisen0192
@kiritorisen0192

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

LU分解を用いた逆行列の求め方

お世話になっております。

C++でLU分解と連立一次方程式の解を求める関数SLE_bu_LUを利用して、逆行列の求める関数InvMatを作成中です。

「行列Bのj列目の列ベクトルをbj とすれば,行列Cのj列目の列ベクトル
xj は連立1次方程式 Axj = bj の解として求められる.これが各列について成立するので,
求めたx1, x2, x3,・・・・, xmを各列としてC=[x1 x2 x3 ・・・・ xm] を計算することがで
きる。その際,係数行列Aが同一の連立1次方程式をm 組解いて行列C が得られること
がわかる。従って,初めに1回だけAを LU 分解しておいて,前進代入と後退代入のプロセ
スをm回行うことによって行列C が求められる」
と議題には書かれており、Cを求める逆行列、Bを単位行列としてプログラミングを書いていたのですが、
関数InvMatではCとBのm列成分を関数SLE by LUに引数として渡すという方針であっていますでしょうか?
(そうなるとm回関数SLE_by_LUを呼び出し、m回LU分解してしまうので間違っていると思いました...)

関数SLE_bu_LUは正しく動き、追加した関数は一番下のInvMatのみでここが怪しいと思います。

よろしくお願いします。


#include <iostream> 
#include <iomanip> 
using namespace std; 
const int    N = 4;                    /*  連立方程式の元数      */ 
void SLE_by_LU(double *,double [][N],double *); // LU 分解により連立1次方程式 
                                                 // を解く関数のプロトタイプ宣言 
void InvMat(double [][N],double [][N]);         //逆行列を求める関数のプロトタイプ宣言
int main()  
{ 
    double   x[N]; 
   /*    入力データ (演習問題 2.1 の場合)  */ 
   double a[N][N]={{1.0,2.0,-12.0,8.0},{5.0,4.0,7.0,-2.0}, 
                   {-3.0,7.0,9.0,5.0},{6.0,-12.0,-9.0,3.0}}; 
   double ai[N][N]={}; //逆行列用
    double b[N]={27.0, 4.0,11.0,49.0}; 



SLE_by_LU(x,a,b);  // LU 分解により連立1次方程式を解く関数の呼び出し 


   /*   データ出力     */ 
   for(int i=0; i<N; i++)  {cout << 'x' << i+1 << '=' << setw(12) << 
                             fixed << setprecision(9) << x[i] << endl;
   }
   InvMat(a,ai);//逆行列の計算関数呼び出し

 return 0; 
}
/******  LU 分解により連立1次方程式を解く関数プログラム  **********/ 
/*      入力 : a[N][N]=係数行列,b[N]=右辺ベクトル,N=元数     */ 
/*      出力 : x[N]=方程式の解                                 */ 
/******************************************************************/ 


void SLE_by_LU(double x[],double a[][N],double b[]) 
{    int  i, j, k; 
     double  l[N][N], u[N][N], y[N];                    // LU 分解用配列 
     double  sigma;                                     // ∑演算用作業変数 
     /*      LU 分解     */ 
     for(j=0; j<N; j++) u[0][j]=a[0][j];                // ステップ 1.1 
     for(j=1; j<N; j++) l[j][0]=a[j][0]/u[0][0];        // ステップ 1.2 
     for(k=1; k<N; k++)                                 // ステップ 2 の反復 
     { 
        for(j=k; j<N; j++)                              // ステップ 2.1 
        { 
           u[k][j]=a[k][j]; 
           for(i=0; i<k; i++)  u[k][j]-=l[k][i]*u[i][j]; 
       } 
       for(j=k+1; j<N; j++)                            // ステップ 2.2 
       { 
          sigma=a[j][k]; 
          for(i=0; i<k; i++)  sigma-=l[j][i]*u[i][k]; 
          l[j][k]=sigma/u[k][k]; 
       } 
    } 

    /*     前進代入     */ 
    for(i=0; i<N; i++)                                 // i=1,2,・・・,n 
    { 
       y[i]=b[i];                                      // yi ← bi 
       for(j=0; j<i; j++)  y[i]-=l[i][j]*y[j];         // yi=bi-∑lijyj 
    } 

    /*     後退代入     */ 
    for(k=N-1; k>=0; k--) 
    { 
       sigma=y[k]; 
       for(j=k+1; j<N; j++) sigma-=u[k][j]*x[j]; 
       x[k]=sigma/u[k][k]; 
    } 
} 
void InvMat(double a[][N], double ai[][N]) //A 配列a[][N]に格納 逆行列AI 配列ai[][N]に格納
{//AC=BでBが単位行列Eの時、Cは逆行列AIとなる

 double array[N][N];//単位行列格納
 double array_m[N];//単位行列のm列目格納
 double ai_m[N];//AIのm列目格納
 for(int i = 0; i <N; i++){//単位行列を作る
        for(int j = 0; j <N; j++){
            if(i == j )
                {array[i][j] = 1.0;}
            else
                {array[i][j] = 0.0;}
        } 
    }

    //以下A.14より

for(int i=0;i<N;i++){//m列目処理
  for(int j=0;j<N;j++){
  array_m[i]=array[j][i];//m番目の単位行列の列成分
    ai_m[i]=ai[j][i];//m番目のAI行列の列成分
 SLE_by_LU(ai_m,a,array_m);
 cout<<ai[i][j];
  }
}
}
0

2Answer

最初に気付いたこと

不勉強で、「LU分解を用いた逆行列の求め方」について知識を持たないのですが、以下のことは解りました。

  • 関数void InvMat(double a[][N], double ai[][N])において、aが入力で、aiが出力となるハズですが、空で渡されたaiから値を取り出す箇所はあっても、値を格納する箇所がありません。
    • 結果的に、aiは空っぽのまま変化しません。

さらに気付いたことと修正方針

  • InvMatでのSLE_by_LUの呼び出しは、aarray_mを受け取って、ai_mへ結果を返すはずですが、SLE_by_LUを呼ぶ前に、空のaiからai_mへ値を代入しています。
  • また、SLE_by_LUは、1列分の値をarray_mへセットしてから呼び出すはずなのに、列ごとに(未完成のままで)呼んでいます。
  • これらのことから、本来は、以下のように処理したいのだろうと推定しました。
    • 単位行列の1列分をarray_mへコピーする。
    • SLE_by_LUに渡してai_mを得る。
    • 得られたai_maiの1列にコピーする
  • つまり、「1列の取り出し(ループ) ⇒ 方程式を解く ⇒ 1列の格納(ループ)」となります。
  • その他に、以下を実施しました。
    • 書法を変えました。
    • コンソールへの出力を改修しました。

修正したコード

#include <iostream>
#include <iomanip>
using namespace std;

const int N = 4; //  連立方程式の元数

void SLE_by_LU (double *, double [][N], double *); // LU 分解により連立1次方程式を解く関数のプロトタイプ宣言
void InvMat (double [][N], double [][N]); //逆行列を求める関数のプロトタイプ宣言
void DumpArray (string, double *, int, int); // 行列をコンソールへ出力

int main () {
    double   x[N];
    // 入力データ (演習問題 2.1 の場合)
    double a[N][N] = {{1.0,2.0,-12.0,8.0},{5.0,4.0,7.0,-2.0},
                      {-3.0,7.0,9.0,5.0},{6.0,-12.0,-9.0,3.0}};
    double ai[N][N] = {}; //逆行列用
    double b[N] = {27.0, 4.0,11.0,49.0};

    SLE_by_LU (x, a, b); // LU 分解により連立1次方程式を解く関数の呼び出し
    InvMat (a, ai); //逆行列の計算関数呼び出し

    // データ出力
    DumpArray ("a", (double *)a, N, N);
    DumpArray ("b", (double *)b, 1, N);
    DumpArray ("x", (double *)x, 1, N);
    DumpArray ("ai", (double *)ai, N, N);

    return 0;
}

/******  LU 分解により連立1次方程式を解く関数プログラム  **********/
/*      入力 : a[N][N]=係数行列,b[N]=右辺ベクトル,N=元数     */
/*      出力 : x[N]=方程式の解                                 */
/******************************************************************/
void SLE_by_LU (double x[], double a[][N], double b[]) {
    double l[N][N], u[N][N], y[N];          // LU 分解用配列
    double sigma;                           // ∑演算用作業変数
    // LU 分解
    for (int j = 0; j < N; j++) {
        u[0][j] = a[0][j];                  // ステップ 1.1
    }
    for (int j = 1; j < N; j++) {
        l[j][0] = a[j][0] / u[0][0];        // ステップ 1.2
    }
    for (int k = 1; k < N; k++) {           // ステップ 2 の反復
        for (int j = k; j < N; j++) {       // ステップ 2.1
        u[k][j] = a[k][j];
            for (int i = 0; i < k; i++) {
                u[k][j] -= l[k][i] * u[i][j];
            }
        }
        for (int j = k + 1; j < N; j++) {   // ステップ 2.2
            sigma = a[j][k];
            for (int i = 0; i < k; i++) {
             sigma -= l[j][i] * u[i][k];
            }
            l[j][k] = sigma / u[k][k];
        }
    }
    // 前進代入
    for (int i = 0; i < N; i++) {           // i=1,2,・・・,n
        y[i] = b[i];                        // yi ← bi
        for (int j = 0; j < i; j++) {
            y[i] -= l[i][j] * y[j];         // yi=bi-∑lijyj
        }
    }
    // 後退代入
    for (int k = N - 1; k >= 0; k--) {
        sigma = y[k];
        for (int j = k + 1; j < N; j++) {
            sigma -= u[k][j] * x[j];
        }
        x[k] = sigma / u[k][k];
    }
}

//A 配列a[][N]に格納 逆行列AI 配列ai[][N]に格納
//AC=BでBが単位行列Eの時、Cは逆行列AIとなる
void InvMat (double a[][N], double ai[][N]) {
    double array[N][N]; //単位行列を作る
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            array[i][j] = (i == j) ? 1.0 : 0.0;
        }
    }
    //以下A.14より
    for (int i = 0; i < N; i++) { // m列目処理
        double array_m[N]; // 単位行列のm列目格納
        double ai_m[N]; // AIのm列目格納
        for (int j = 0; j < N; j++) {
            array_m[j] = array[j][i]; // m番目の単位行列の列成分
        }
        SLE_by_LU (ai_m, a, array_m);
        for (int j = 0; j < N; j++) {
            ai[j][i] = ai_m[j]; // m番目のAI行列の列成分
        }
    }
}


// 行列をコンソールへ出力
// 入力 タイトル、配列、行数、列数
void DumpArray (string title, double *array, int row, int col) {
    cout << title << endl;
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            printf ("%12.7lf", array[i * col + j]);
        }
        cout << endl;
    }
}

結果

アルゴリズムは理解していませんので、結果が正しいのかどうか不明ですが、先にご質問いただいていた掃き出し法と同じような結果になったようです。

a
   1.0000000   2.0000000 -12.0000000   8.0000000
   5.0000000   4.0000000   7.0000000  -2.0000000
  -3.0000000   7.0000000   9.0000000   5.0000000
   6.0000000 -12.0000000  -9.0000000   3.0000000
b
  27.0000000   4.0000000  11.0000000  49.0000000
x
   3.0374532  -2.0739015   1.0339819   5.0647666
ai
   0.0337079   0.1348315  -0.0224719   0.0374532
   0.0627569   0.0559057  -0.0337079  -0.0739015
  -0.0523431   0.0101398   0.0674157   0.0339819
   0.0265826  -0.0156207   0.1123596   0.0647666
1

Comments

  1. まさしく私が求めたかったアルゴリズム、コード、実行結果がこちらです...!!
    求めたった意図もくみ取って教えていただき、本当にありがとうございました。
    ずっと悩んでいたことが解消されてうれしいです。
    本当にありがとうございました(; ;)
  2. やはりInvMat関数ではfor文でn回SLE_by_LU関数を呼び出し、LU分解をn回行うことになり、1回だけのLU分解になりませんよね...難しい
  3. アルゴリズムを理解していないので、アレなのですが…
    `SLE_by_LU`は、1回にN行1列だけの処理を行うものですよね。
    N×N行列を処理するためには、N列分の繰り返しはどうしても必要になるのではないでしょうか?
  4. そうですよね...
    1回だけで済ませるとなると`SLE_by_LU`関数内で単位行列をつくり、LU分解後の計算部分(前進代入、後退代入)でn回計算させるのかなと考えてみました...

    ご回答いただいたものを参考に頑張ってみます!
  5. お力になれず申し訳ありません。
    どうか頑張ってください。
  6. いえいえい!本当にありがとうございました!!

LU分解とか理解しないままで、処理をまとめてみました。

コード
#include <iostream>
#include <iomanip>
using namespace std;

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

// 行列をコンソールへ出力
// 入力 : タイトル、行列
void DumpArray (string title, double array[N][N]) {
    cout << title << endl;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            printf ("%12.7lf", array[i][j]);
        }
        cout << endl;
    }
}

// LU分解により逆行列を求める
// 入力 : a[N][N]=元行列,N=元数
// 出力 : ai[N]=逆行列
void InvMat_by_LU (double ai[][N], double a[][N]) {
    double l[N][N], u[N][N];
    // LU 分解
    for (int j = 0; j < N; j++) {           // クリア
        for (int k = 0; k < N; k++) {
            u[j][k] = l[j][k] = 0.0;
        }
    }
    for (int j = 0; j < N; j++) {           // ステップ 1.1
        u[0][j] = a[0][j];
    }
    for (int j = 1; j < N; j++) {           // ステップ 1.2
        l[j][0] = a[j][0] / u[0][0];
    }
    for (int k = 1; k < N; k++) {           // ステップ 2
        for (int j = k; j < N; j++) {       // ステップ 2.1
            u[k][j] = a[k][j];
            for (int i = 0; i < k; i++) {
                u[k][j] -= l[k][i] * u[i][j];
            }
        }
        for (int j = k + 1; j < N; j++) {   // ステップ 2.2
            double sigma = a[j][k];
            for (int i = 0; i < k; i++) {
             sigma -= l[j][i] * u[i][k];
            }
            l[j][k] = sigma / u[k][k];
        }
    }
    double y[N][N];
    for (int i = 0; i < N; i++) { // 列の繰り返し
        // 前進代入
        for (int k = 0; k < N; k++) {           // k=1,2,・・・,n
            y[k][i] = (i == k) ? 1.0 : 0.0;        // yk ← I
            for (int j = 0; j < k; j++) {
                y[k][i] -= l[k][j] * y[j][i];         // yk=bk-∑lkjyj
            }
        }
        // 後退代入
        for (int k = N - 1; k >= 0; k--) {
            double sigma = y[k][i];
            for (int j = k + 1; j < N; j++) {
                sigma -= u[k][j] * ai[j][i];
            }
            ai[k][i] = sigma / u[k][k];
        }
    }
}

int main () {
    // 入力データ (演習問題 2.1 の場合)
    double a[N][N] = {{ 1.0,   2.0, -12.0,  8.0},
                      { 5.0,   4.0,   7.0, -2.0},
                      {-3.0,   7.0,   9.0,  5.0},
                      { 6.0, -12.0,  -9.0,  3.0}};
    double ai[N][N] = {}; //逆行列用
    // LU分解により逆行列を生成
    InvMat_by_LU (ai, a);
    // データ出力
    DumpArray ("a", a);
    DumpArray ("ai", ai);
    return 0;
}
結果
a
   1.0000000   2.0000000 -12.0000000   8.0000000
   5.0000000   4.0000000   7.0000000  -2.0000000
  -3.0000000   7.0000000   9.0000000   5.0000000
   6.0000000 -12.0000000  -9.0000000   3.0000000
ai
   0.0337079   0.1348315  -0.0224719   0.0374532
   0.0627569   0.0559057  -0.0337079  -0.0739015
  -0.0523431   0.0101398   0.0674157   0.0339819
   0.0265826  -0.0156207   0.1123596   0.0647666
1

Comments

  1. tetr4labさん
    次の日に組んでくださったのですね。ご丁寧にありがとうございます。
    なるほどSLU関数を丸々移すのですね...
    確かにそれが一番効率が良いです、勉強になります。
    これからもわからない事がありましたら、tetr4labさんにお聞きしたいです!!
    最後まで考えていただきありがとうございました...!!

Your answer might help someone💌