演算子の機能を確認するために簡単なプログラムを書いてみます。ここで利用するベ クトルと行列のクラス・インターフェースはこちらをご覧ください。 これらは、ヘッダファイル( matrix17.h )、関数定義プログラム( matrix17.cpp ) にまとめてあります。
3.9.1 ベクトルと行列の演算
次のプログラムは、ベクトル演算子の使用法を示しています。
| 
// lst03_19.cpp
// ベクトルの演算をテストする
//matrix17.cppと一緒にコンパイルする
#include <iostream.h>
#include "matrix17.h"
int main()
{
    Vector x(4), y(4);
    for(int i = 0; i < x.getSize(); i++){
        y[i] = 0.2 * (i+1);
        y[i] = 0.1  * (i+1);
    }
    //x, yの内容をプリントする
    cout << "x: " << x <<  "y: " << y << endl;
    //ベクトルの和
    cout << " x + y : " <<  ( x + y );
    //ベクトルの差
    cout << " x - y : " <<  ( x - y ) << endl;
    // ベクトルの内積
    cout << " x * y = " <<  x * y  << endl << endl;
    // 等値演算子
    if( x == ( 2 * y ) ) cout << "x と ( 2 * y ) は等しい。\n" << endl;
    // -x
    cout << "-x: " << -x << endl;
    // ベクトルを3倍する
    cout << "3.0 * x : " << 3.0 * x;
    cout << "y * 3.0 : " << y * 3.0 << endl;
    y *= 2.0;
    cout << " y *= 2.0 の結果:" << endl << y;
    x /= 2.0;
    cout << " x /= 2.0 の結果:" << endl << x << endl;
    // ベクトルを規格化する
    cout << "xを規格化: " << x.normalize();
    cout << "xのノルム: " << x.norm() << endl;
    return 0;
} | 
[実行結果] ソースプログラム ( lst03_19.cpp )
| x: 2.000000e-01 4.000000e-01 6.000000e-01 8.000000e-01 y: 1.000000e-01 2.000000e-01 3.000000e-01 4.000000e-01 x + y : 3.000000e-01 6.000000e-01 9.000000e-01 1.200000e+00 x - y : 1.000000e-01 2.000000e-01 3.000000e-01 4.000000e-01 x * y = 6.000000e-01 x と ( 2 * y ) は等しい。 -x: -2.000000e-01 -4.000000e-01 -6.000000e-01 -8.000000e-01 3.0 * x : 6.000000e-01 1.200000e+00 1.800000e+00 2.400000e+00 y * 3.0 : 3.000000e-01 6.000000e-01 9.000000e-01 1.200000e+00 y *= 2.0 の結果: 2.000000e-01 4.000000e-01 6.000000e-01 8.000000e-01 x /= 2.0 の結果: 1.000000e-01 2.000000e-01 3.000000e-01 4.000000e-01 xを規格化: 1.825742e-01 3.651484e-01 5.477226e-01 7.302967e-01 xのノルム: 1.000000e+00 | 
同じ次元の2つのベクトル・オブジェクトに対する算術演算には、次の3つがあり、 x + y; // 和 x - y; // 差 x * y; // 内積 またベクトル・オブジェクトと実数(浮動小数点数)との演算には、 x * 3.0; // 実数倍 3.0 * x; // 実数倍 x / 2.0; // 割り算 -x; // マイナスのベクトル がサポートされています。これらはオブジェクト自体は変更されません。オブジェクト が変更される算術演算には、 x += y; // x = x + y x -= y; // x = x - y x *= 2.0 // x = 2 * x x /= 2.0 // x = x / 2 が利用できます。 次のプログラムは、行列の演算子の使い方を示します。
| 
// lst03_20.cpp
// 行列の和・差・積
//matrix17.cppと一緒にコンパイルする
#include <iostream.h>
#include <iomanip.h>
#include "matrix17.h"
int main()
{
    Matrix a(3,3), b(3,3);
    int i,j;
    for(i = 0; i < a.getRow(); i++)
        for(j = 0; j < a.getCol(); j++){
            a[i][j] = 2.0 * (i+1) + 0.1*(j+1);
            b[i][j] = (i+1) + 0.2*(j+1);
        }
    cout << "行列 a: \n" << a << endl;
    cout << "行列 b: \n" << b << endl;
    cout << "行列の和 a + b: " << endl << ( a + b ) << endl;
    cout << "行列の差 a - b: " << endl << ( a - b ) << endl;
    a.setSize(2,3);
    b.setSize(3,2);
    for(i = 0; i < a.getRow(); i++)
        for(j = 0; j < a.getCol(); j++)
            a[i][j] = i+j+1;
    cout << "行列 a: \n" << a << endl;
    for(i = 0; i < b.getRow(); i++)
        for(j = 0; j < b.getCol(); j++)
            b[i][j] = i+j+1;
    cout << "行列 b: \n" << b << endl;
    Matrix c = a * b;
    cout << "行列の積 c = a * b: \n" << c << endl;
    c.setSize(3,3);
    c = b * a;
    cout << "行列の積 c = b * a: \n" << c << endl;
    return 0;
} | 
| 行列 a: 2.100000e+00 2.200000e+00 2.300000e+00 4.100000e+00 4.200000e+00 4.300000e+00 6.100000e+00 6.200000e+00 6.300000e+00 行列 b: 1.200000e+00 1.400000e+00 1.600000e+00 2.200000e+00 2.400000e+00 2.600000e+00 3.200000e+00 3.400000e+00 3.600000e+00 行列の和 a + b: 3.300000e+00 3.600000e+00 3.900000e+00 6.300000e+00 6.600000e+00 6.900000e+00 9.300000e+00 9.600000e+00 9.900000e+00 行列の差 a - b: 9.000000e-01 8.000000e-01 7.000000e-01 1.900000e+00 1.800000e+00 1.700000e+00 2.900000e+00 2.800000e+00 2.700000e+00 行列 a: 1.000000e+00 2.000000e+00 3.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 行列 b: 1.000000e+00 2.000000e+00 2.000000e+00 3.000000e+00 3.000000e+00 4.000000e+00 行列の積 c = a * b: 1.400000e+01 2.000000e+01 2.000000e+01 2.900000e+01 行列の積 c = b * a: 5.000000e+00 8.000000e+00 1.100000e+01 8.000000e+00 1.300000e+01 1.800000e+01 1.100000e+01 1.800000e+01 2.500000e+01 | 
行列オブジェクト同士の算術演算は和と差 a + b; a - b; があり、これらは行数と列数が等しい2つの行列に対して定義されています。また、 (L×M)の行列aと(M×N)の行列bの積は c = a * b; と表現できて、演算結果を代入した行列cは(L×N)行列でなければなりません。間 違った型同士の演算を行うと、呼び出された演算子関数はエラーメッセージを表示して プログラムを終了させます。 この様な単純な機能確認プログラムでは退屈なので、少しばかり教養的な話題を取り 上げます。以下では、行列とベクトルの積および行列と行列の積を使ったプログラムを 扱います。
3.9.2 固有値方程式
 n行n列の正方行列Aに対して、次の方程式を固有値方程式といいます。
 
ここで、定数λを行列Aの固有値、n次元ベクトルxを固有値λに属する固有ベクトル と呼び、これらを求めることを固有値方程式を解くといいます。この式は行列に固有ベ クトルを掛けると、自身に比例するベクトル、すなわち固有ベクトルのスカラー倍(固 有値倍)が得られることを意味します。 この意味を具体的に見ることにしましょう。例として、次の2行2列の行列
 
を取り上げます。この行列に、ベクトル (x,y)を掛けると (x -0.5*y, -0.5*x + y)が 得られます。これを、平面上の点 (x,y)が行列によって点 (x -0.5*y, -0.5*x + y)に 移される写像と見なすことができます。色々な点に対して、元の点から移った点へ矢印 を引いて描いてみると、次のような図形が得られます。
 ソースプログラム
( lst03_21.cpp )
 ソースプログラム
( lst03_21.cpp )
図を見ると矢印の変化には、2つの流れがあります。引き伸ばしと圧縮です。引き伸ば しは、直線「 y = -x 」に沿って起こり、この直線の近くの点はこの直線に沿って原点 から引き離されています。一方、圧縮は直線「 y = x」に沿って、原点に向かって押し 込もうとしています。 すべての点は、これら引き伸ばしと圧縮の2つの作用の組み合わせで移動しています。 そして、この2つの直線上の点はすべてその直線上に移ります。すると、点(1,1)は 直線「 y = x」上の点であり、また点(1, −1)は直線「 y = -x 」上の点ですから、
 
 
となります。これはまさに固有値方程式です。この行列の固有値は、1/2 と 3/2で、 それぞれに属する固有ベクトルは(1、1)と(1、−1)となります。 つまり固有ベクトルとは、「引き伸ばしと圧縮」をする方向のベクトルという意味を 持ちます。そして、その伸縮の比率が固有値です。
この性質を利用することで、固有値と固有ベクトルを求めることができます。アイデ アは、こうです。あるベクトルから初めて、このベクトルを繰り返し行列に掛けて行く と得られるベクトルは固有ベクトルの1つに近づいて行くであろう。これは、べき乗法 といわれるもので、次のように述べられます。
 
この方法は、絶対値最大の固有値と固有ベクトルしか求めることができません。これ は決して実用的とはいえませんが、行列とベクトルの掛け算を繰り返すだけの簡単なも のであり、行列の面白い性質を示しています。例題を1つ考えましょう。次の行列
 
は2と1(重解)の2つの固有値を持ちます。べき乗法を使って大きい方の固有値とそ の固有ベクトルを求めてみましょう。
| 
// lst03_22.cpp
// べき乗法 (Power method)
//matrix17.cppと一緒にコンパイルする
#include <iostream.h>
#include "matrix17.h"
const int MAX_ITERATION = 100; // 繰り返しの上限
int power(const Matrix &, Vector &, double &);
int main()
{
    cout << "べき乗法 (Power method)" << endl << endl;
    // 行列
    // この行列の固有値は:2,1(重解)
    double mat[3][3] = {{  3.0,  1.0, -2.0},
                         { -6.0, -2.0,  6.0},
                         { -2.0, -1.0,  3.0}};
    Matrix a(3,3);// Matrixクラスのオブジェクトを生成する
    // 行列の読み込み
    for(int i = 0; i < a.getRow(); i++)
        for(int j = 0; j < a.getCol(); j++)
            a[i][j] = mat[i][j];
    cout << "行列:" << endl << a << endl;
    // べき乗法
    double lambda;// 最大固有値
    Vector v(3);  // 固有ベクトル
    int status = power(a, v, lambda);
    if(!status){
        cout << endl << "最大固有値:" << lambda << endl;
        cout << "固有ベクトル:" << v << endl;
    }
    return 0;
}
/*
* ========================
* べき乗法 (Power method)
* ========================
*  (入力)
*        a:Matrix型の行列(変更されない)
*        v:Vector型の求める固有ベクトル
*        λ:求める絶対値が最大の固有値
*  (出力)
*        v:固有ベクトル(ベクトルvに上書きして返す)
*        λ:λに上書きして返す
*  (戻り値)
*        0:成功
*        1:行列とベクトルの型が一致しない
*        2:初期ベクトルが見つからない
*        3:固有ベクトルがヌルベクトルになった
*        4:収束しなかった
*/
int power(const Matrix &a, Vector &v, double &lambda)
{
    // 行列とベクトルの型チェック
    int size = v.getSize();
    if( ( size != a.getRow() ) || ( size != a.getCol()) ){
        cout << "行列とベクトルの型が一致しません。" << endl;
        return (1);
    }
    // 初期ベクトルの設定
    Vector v_old(size);// ベクトルはゼロで初期化
    for(int i = 0; i < size; i++){
        v_old[i] = 1.0;
        v = a * v_old;
        if( v.norm() > NEARLY_ZERO ) break;
        v_old[i] = 0.0;
    }
    if( v.norm() < 1.0) return (2);// 初期ベクトルが見つからない
    int k = 1;
    while(k < MAX_ITERATION){
        v = a * v_old;     // 固有ベクトル
        lambda = v * v_old;// 最大固有値
        if( v.norm() < NEARLY_ZERO ) return (3); // ヌルベクトル
        v.normalize(); // 固有ベクトルを規格化
        if( v == v_old ){
            cout << "繰り返し数: " << k << endl;
            return (0); // 正常終了(収束した)
        }
        v_old = v;
        ++k;
    }
    return (4); // 収束しない
}
 | 
| 
べき乗法 (Power method) 
 行列: 
    3.000000e+00   1.000000e+00  -2.000000e+00 
   -6.000000e+00  -2.000000e+00   6.000000e+00 
   -2.000000e+00  -1.000000e+00   3.000000e+00 
 繰り返し数: 50 
 最大固有値:2.000000e+00 
 固有ベクトル:   3.015114e-01  -9.045340e-01  -3.015113e-01  | 
アルゴリズムの骨格は簡単で、行列とベクトルの掛け算を繰り返し、ベクトルが一定の 
値に収束するまで続けます。擬似コードは、 
    int k = 1; // 繰り返しの数 
    while(k < MAX_ITERATION){ 
        v = a * v_old;     // 固有ベクトルの近似値 
        最大固有値の近似値を計算 
        ベクトルvを規格化する 
        if( v == v_old ) 終了 
        v_old = v; // 更新 
        ++k; 
    } 
となります。 
 このプログラムでは50回繰り返していますが、収束の判定条件を緩めて、 
        if( v == v_old ) 
の代わりに、ベクトルの差のノルムがある値(たとえば 1.E-8)以下になるまで繰り返 
すようにすると、収束は速くなります。つまり、 
        Vector dv = v - v_old;// ベクトルの差 
        if( dv.norm() <  1.E-8 ) 
で置き換えます(この場合、24回で求まる)。 
3.9.4 有限 Markov連鎖
最後に、行列の掛け算を繰り返す例を取り上げます。サイコロを繰り返し振る場合の ように時間と共に変化する確率現象は確率過程(stchastic process)と呼ばれます。 わたしたちの周囲には、ランダムな現象が引き続いて起こるという例は数多くみられま す。確率過程は、この様な一連の現象の系列を扱う数学モデルで、自然科学、工学や経 済学などの広い分野で現象の解析に使われています。 とくに確率変数が有限の数で構成され (例えばサイコロの場合では1〜6の目の数) て、しかも時間の系列が離散的(つまり1回目、2回目...と数えられる)である確率 過程を有限マルコフ連鎖といいます。 有限マルコフ連鎖をなすシステムは、行列を使って記述することができます。これを 推移確率行列と呼び、次のような正方行列で表されます。
 
この行列は、n個の確率変数を持ち、各行ベクトルは状態間の推移確率を表します。 i行j列の要素は、現在の状態iから、次の時刻に状態jに移行する条件付き確率を 示します。従って、各行の値の合計は1(全確率)になります。このとき繰り返して起 こる確率現象は、この行列の掛け算を繰り返したものを調べることで、行き着く先での 状態の確率が計算できます。例えば次の行列は、推移確率行列の1つです。
 
 
| 
// lst03_23.cpp
// 確率過程(有限 Markov連鎖)
//matrix17.cppと一緒にコンパイルする
#include <iostream.h>
#include "matrix17.h"
const int MAX = 10000;    // 上限
int main()
{
    // 推移確率行列
    double mat[5][5] = {{ 0.5, 0.5, 0.0, 0.0, 0.0},
                         { 0.0, 0.5, 0.5, 0.0, 0.0},
                         { 0.0, 0.0, 0.5, 0.5, 0.0},
                         { 0.0, 0.0, 0.0, 0.5, 0.5},
                         { 0.5, 0.0, 0.0, 0.0, 0.5}};
    Matrix a(5,5);// Matrixクラスのオブジェクトを生成する
    // 行列の読み込み
    for(int i = 0; i < a.getRow(); i++)
        for(int j = 0; j < a.getCol(); j++)
            a[i][j] = mat[i][j];
    cout << "推移確率行列:" << endl << a << endl;
    int n = 1; // 試行数
    Matrix b = a;
    while( n < MAX ){
        ++n;
        Matrix c = b;
        b *= a;
        if( b == c ) break; // 収束
    }
    cout << n << "次推移確率行列:" << endl << b << endl;
    return 0;
}
 | 
| 
推移確率行列: 
    5.000000e-01   5.000000e-01   0.000000e+00   0.000000e+00   0.000000e+00 
    0.000000e+00   5.000000e-01   5.000000e-01   0.000000e+00   0.000000e+00 
    0.000000e+00   0.000000e+00   5.000000e-01   5.000000e-01   0.000000e+00 
    0.000000e+00   0.000000e+00   0.000000e+00   5.000000e-01   5.000000e-01 
    5.000000e-01   0.000000e+00   0.000000e+00   0.000000e+00   5.000000e-01 
169次推移確率行列: 
    2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01 
    2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01 
    2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01 
    2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01 
    2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01   2.000000e-01  | 
この様に、演算子を多重定義することで、クラスを利用するプログラムは直感的で簡単 になります。 次のページでは、ベクトルと行列のクラスの効率について検討します。
| 目次 | 前のページ | 次のページ | ページの先頭 |
Copyright(c) 1999 Yamada, K