みその計算物理学
ホーム はじめに リンク集
実対称行列の対角化の数値計算例
  • 実対称行列の対角化を行うプログラム例

    ここではある3次実対称行列の対角化を行う。

    #include <stdio.h>
    #include <math.h>
    
    #define N 3                     //実対象行列の次数
    #define PI 3.1415926535897932385                //円周率
    
    /*実対称行列とそれを対角化する直行行列を
    格納する配列を受け取り対角化を行う関数。*/
    void taikakuka(double a[N][N],double U[N][N]);  
    
    int main()
    {
            int i,j;
    //配列aには実対称行列を入れる。また結果の直行行列を入れる配列Uを作っておく。
            double U[N][N],a[N][N]={{0.0,1.0,-1.0},{1.0,0.0,1.0},{-1.0,1.0,0.0}};
    
            taikakuka(a,U);         //対角化関数の呼び出し
    
    //計算結果の対角行列の表示
            printf("対角行列は\n");
            for(i=0;i<N;i++) {
                    for(j=0;j<N;j++) {
                            printf("%f ",a[i][j]);
                    }
                    printf("\n");
            }
    //計算結果の直交行列の表示
            printf("直行行列は\n");
            for(i=0;i<N;i++) {
                    for(j=0;j<N;j++) {
                            printf("%f ",U[i][j]);
                    }
                    printf("\n");
            }
    
            return 0;
    }
    
    void taikakuka(double a[N][N],double U[N][N])
    {
            int i,j,l,m,p,q,count;
            double max,theta;
            double u[N][N],oldU[N][N],newa[N][N];
    
            FILE *output;
            output=fopen("output.dat","w");
    
    /*計算結果としてだされた直行行列を
    格納するための配列を単位行列に初期化しておく。*/
            for(p=0;p<N;p++) {
                    for(q=0;q<N;q++) {
                            if(p==q) U[p][q]=1.0;
                            else U[p][q]=0.0;
                    }
            }
    
            for(count=0;count<=10000;count++) {
    
    /*配列olduは新たな対角化計算を行う前に
    かけてきた直行行列を保持する。*/
                    for(p=0;p<N;p++) {
                            for(q=0;q<N;q++) {
                                    oldU[p][q]=U[p][q];
    /*対角化するときの個々の直交行列を入れる配列uの初期化。(単位行列にする。)*/
                                    if(p==q) u[p][q]=1.0;
                                    else u[p][q]=0.0;
                            }
                    }
    
    /*非対角要素の中から絶対値の最大のものを見つける*/
                    max=0.0;
                    for(p=0;p<N;p++) {
                            for(q=0;q<N;q++) {
                                    if(max<fabs(a[p][q]) && p!=q) {
                                            max=fabs(a[p][q]);
    /*その最大のものの成分の行と列にあたる数を記憶しておく。*/
                                            i=p;
                                            j=q;
                                    }
                            }
                    }
    /*先ほど選んだ最大のものが指定の値より小さければ対角化終了*/
                    if(fabs(a[i][j]) < 1.0e-10) {
                            break;
                    }
    /*条件によってシータの値を決める*/
                    if(a[i][i]==a[j][j]) theta=PI/4.0;
                    else theta=atan(-2*a[i][j]/(a[i][i]-a[j][j]))/2.0;
    
    /*ここでこのときに実対称行列にかける個々の直行行列uが決まるが
    特にここでの計算の意味はない。(する必要はない。)*/
                    u[i][j]=sin(theta);
                    u[j][i]=-sin(theta);
                    u[i][i]=u[j][j]=cos(theta);
    
    /*ここでいままで実対称行列にかけてきた直行行列を配列Uに入れる。*/
                    for(p=0;p<N;p++) {
                            U[p][i]=oldU[p][i]*cos(theta)-oldU[p][j]*sin(theta);
                            U[p][j]=oldU[p][i]*sin(theta)+oldU[p][j]*cos(theta);
                    }
    
    //対角化計算によってでた新たな実対称行列の成分を配列newaに入れる。
                    newa[i][i]=a[i][i]*cos(theta)*cos(theta)
                            +a[j][j]*sin(theta)*sin(theta)-2.0*a[i][j]*sin(theta)*cos(theta);
                    newa[j][j]=a[i][i]*sin(theta)*sin(theta)
                            +a[j][j]*cos(theta)*cos(theta)+2.0*a[i][j]*sin(theta)*cos(theta);
                    newa[i][j]=newa[j][i]=0.0;
                    for(l=0;l<N;l++) {
                            if(l!=i && l!=j) {
                                    newa[i][l]=a[i][l]*cos(theta)-a[j][l]*sin(theta);
                                    newa[l][i]=newa[i][l];
                                    newa[j][l]=a[i][l]*sin(theta)+a[j][l]*cos(theta);
                                    newa[l][j]=newa[j][l];
                            }
                    }
                    for(l=0;l<N;l++) {
                            for(m=0;m<N;m++) {
                                    if(l!=i && l!=j && m!=i && m!=j) newa[l][m]=a[l][m];
                            }
                    }
    
    //次の対角化計算を行う行列の成分を配列aへ上書きする。
                    for(p=0;p<N;p++) {
                            for(q=0;q<N;q++) {
                                    a[p][q]=newa[p][q];
                            }
                    }
    
                    if(count==10000) {
                            printf("対角化するためにはまだ作業を繰り返す必要があります.\n");
                    }
    
    //対角化の途中計算をファイルに記録する。
                    fprintf(output,"選択した非対角要素は %d 行 %d 列です。\n",i+1,j+1);
                    fprintf(output,"このときのシータの値は %f です。\n",theta);
                    fprintf(output,"また計算後の実対称行列は\n");
                    for(i=0;i<N;i++) {
                            for(j=0;j<N;j++) {
                                    fprintf(output,"%f ",newa[i][j]);
                            }
                            fprintf(output,"\n");
                    }
                    fprintf(output,"\nこのときまでに計算してきた直行行列は\n");
                    for(i=0;i<N;i++) {
                            for(j=0;j<N;j++) {
                                    fprintf(output,"%f ",U[i][j]);
                            }
                            fprintf(output,"\n");
                    }
                    fprintf(output,"\n\n");
            }
            
            fclose(output);
    }
    

    計算結果は

    対角行列は
    -2.000000 0.000000 0.000000
    0.000000 1.000000 0.000000
    0.000000 0.000000 1.000000
    直行行列は
    0.577350 0.707107 -0.408248
    -0.577350 0.707107 0.408248
    0.577350 0.000000 0.816497
    Press any key to continue

    である。よって固有値は縮退ありの -2 と 1 である。