第4回 ベクトル・複素数の演算(1)

今回はベクトル・複素数の扱い方の実習を行う.
複素数については,フーリエ変換の基礎となる部分なので,しっかりと理解しておく必要がある.

ベクトルとは,力や速度など,2つ以上の数の組み合わせで一つの物理量などを表す概念である.
一般的には,「大きさ」と「向き」を表す場合に用いられる.
これに対して,温度や圧力など,一つの値のみで表す量を「スカラー」と言う.

ベクトル量の例

スカラー量の例

複素数はベクトルとは異なる概念であるが,複数の実数(実部と虚部)からなり,一般的に
z = re + j im
と表され,プログラミングとしてはベクトルと似た扱いが可能である.
ここで,reを複素数の「実部, real part」,imを複素数の「虚部, imaginary part」という.
虚数単位は,数学では i で示すが,工学の分野では j が使われることも多い.

さて,Cの文法規則中には,組み込み変数型として「ベクトル」や「複素数」の概念があるわけではない.
したがって,ベクトルの各成分は配列として,また,複素数は実部と虚部の2つの実数の組み合わせとして扱う必要がある.
C言語でこのようなデータ形式を扱うためには,いままでの情報処理科目等で学んだ「配列」や「構造体」を使えば簡単に表現することができる.

ベクトルの表現方法

ベクトルは複数の同一の型の成分が複数まとまったものである.
例えば,二次元座標であれば ( x, y ) の2つの実数,三次元の速度ベクトルは ( u, v, w ) の三つの実数からなる.
もちろん,成分が複素数であるベクトルも考えられる.

したがって,ベクトルをプログラム中で表現するには,配列または構造体を使う方法が考えられる.

例1:配列でベクトル (u, v, w)を表現

double Vec[3];     /* Vec[0]がu, Vec[1]がv, Vec[2]がw  */


例2:構造体でベクトル (u, v, w)を表現

struct Vec
{
    double u;
    double v;
    double w;
};

複素数の表現方法

複素数も同様に,例えば以下のようなパターンが考えられる.

例1:配列で複素数を表現

double z[2];     /* z[0]が実部, z[1]が虚部 */



例2:構造体で複素数を表現

struct Complex
{
    double re;      /*  実部  */
    double im;      /*  虚部  */
};

となる.
どちらも有効な方法であるが,それぞれ一長一短がある.

配列使用の長所

構造体使用の長所

である.
情報処理実習2では,練習も兼ねて,ベクトルは配列で,複素数は構造体を用いてプログラミングを行う.


ベクトルの計算

課題1

以下の手順に従って,プログラミングを行ってみよう.
いずれも計算結果が画面で確認できるようにすること.

まず,実数2つの配列からなるVec型を定義する.
(要素数は後から変更する可能性あり)


const int dim = 2;
typedef double Vec[dim];

以下の手順に従って,ベクトルを操作する各種関数を作成してみよう.

  1. ベクトルの内容を画面に表示するprintVec()関数を作成,main関数も作成して動作確認を行う.
    
    #include <stdio.h>
    
    const int dim = 2;          /* ベクトルの次元(=要素数), dimensionの略 */
    typedef double Vec[dim];    /* typedefにより別名を定義.Vecはdouble型がdim個 = dim 次元の配列 */
    
    void printVec(const Vec v)
    {
        printf("%lf , %lf\n", v[0], v[1]); /* %lfに関しては%fとも記述可能 */
    }
    
    int main()
    {
        Vec v1, v2;
    
        /* 以下の値は一例.いろいろと変化させよ.*/
        /* Vec型は,配列のtypedefなので,このような初期化が可能 */
        Vec v1 = { 1.0, 3.0}; /* 初期化,初期値代入は初めの宣言でも可能 */
        Vec v2 = {-2.0, 1.0}; /* 初期化,初期値代入は初めの宣言でも可能 */
            
        printVec(v1);
        printVec(v2);
    
        return 0;
    }
    

  2. 以下の参考に,ベクトルの加算を行う関数Vaddを作成せよ.作成した関数が正しく動作するか,動作確認すること.
    動作確認ができたら,ベクトルの減算を行う関数Vsubも作成せよ.
    
    void Vadd(Vec p, const Vec v1, const Vec v2)
    /*
     * p     : 計算結果を入れる.(アドレス参照であることに注意)
     * v1,v2 : 計算するベクトル.変更しないので,const 指定
     */
    {
        p[0] = ...
        p[1] = ...
    }
    
    int main()
    {
        Vec v1, v2, v3;
    
        ...
    
        Vadd(v3, v1, v2);  /* v3 = v1 + v2 の計算 */
        print(v3);         /* 計算結果の表示 */
        return 0;
    }
    
    
    実行例
    v1 : 1.000000 , 3.000000
    v2 : -2.000000 , 1.000000
    和 = -1.000000 , 4.000000
    差 = 3.000000 , 2.000000
    

  3. 次に,ベクトルの絶対値を計算する関数 double Vabs(const Vec v) およびプログラムを作成しよう.
    ベクトル x = (x1, x2, ... , xN) の絶対値は,
    |x| = (x12+x22+ ... + xN2)1/2
    と表される.各要素の二乗の和の平方根である.絶対値の二乗をノルムと呼ぶ.
    平方根の計算には sqrt()関数を用いる.要ヘッダーファイル:math.h

    実行例
    v1 : 1.000000 , 3.000000
    abs = 3.162278
    

  4. 二つのベクトルの内積を計算する関数 double inner_product(const Vec v1, const Vec v2) およびプログラムを作成しよう.
    ベクトル x(x1, x2, ... , xN) と y(y1, y2, ... , yN) の内積は
    x・y = x1y1 + x2y2 + ... + xNyN
    と表される.
    この計算では,各要素同士の「積」の「和」を計算するため,積和演算とも呼ばれる.
    2つのベクトルを同一のベクトルとすると,ノルムの計算ができる.
    (内積はスカラー積とも呼ばれる.これにたいして,外積はベクトル積と呼ばれる.)
    
    
    実行例
    v1 : 1.000000 , 3.000000
    v2 : -2.000000 , 1.000000
    内積 = 1.000000
    


上記,各関数および関数のテストコードは,単一のソースコードに順次まとめて記述すれば良い.
ソースファイル名を 153R000000-05-1.cpp とする.(000000の部分は各自の学生番号)


課題2

前問で作成した関数群を利用して,(正規化された)相互相関係数の計算を行ってみよう.

2つのベクトル xy の相互相関係数は,上記の内積および絶対値を組み合わせて,
R = x・y / (|x||y|)
と表すことができる.
相互相関は,信号の類似度合いを決定したり,画像中から特定のパターンを検索する処理に非常に広く用いられている.

相互相関係数は,2つのベクトルの類似度を表し,完全に一致するときは 1.0,全く異なる(=直交する)ときは 0.0,ベクトルの向きが真逆の場合は -1.0 となるような係数である.
相互相関係数は,幾何的には,2つのベクトルのなす角 θ の余弦 cosθを表す.
相互相関係数=0(即ち,内積=0)となる2ベクトルは,「直交する」という.( cosθ=0 即ち θ=90度であるため)

それでは,実際にベクトルの相互相関係数を計算してみよう.
以下の4本のベクトルの中から任意の2つのベクトルの相互相関係数を計算し,

  1. 一番似ているもの,即ち相互相関係数が最大となる組み合わせを画面に表示する.
  2. 同様に,相関係数の絶対値がゼロに最も近いも組み合わせを画面に表示する.

プログラムを作成せよ.
ここでは簡単のために,全ての組み合わせの相関係数を計算し,表として表示してみよう.
相関関数を計算する関数を double corr(const Vec v1, const Vec v2) とせよ.
上に示した式の通り,inner_product() および Vabs() を用いれば,簡単である.


const int dim = 8;
typedef double Vec[dim];

int main(void)
{
    Vec v1 = { 1, 2, 4,  5,  4,  1, -1, -2 };
    Vec v2 = { 3, 3, 4,  6,  3,  2,  1,  1 };
    Vec v3 = {-1, 2, 0,  2, -2, -1,  3,  1 };
    Vec v4 = { 2, 1, 3, -9,  2,  8,  2,  8 };

    ....

    return 0;
}

実行例.相関値のテーブルを全て計算.
この実行例では,printf関数の書式指定文字列に,%+2.2lfを用いている.(符号付,小数点第2位まで表示)

   | v1    v2     v3    v4
---+-------------------------
v1 | +1.00 +0.87 -0.02 -0.25
v2 | +0.87 +1.00 +0.24 -0.01
v3 | -0.02 +0.24 +1.00 -0.21
v4 | -0.25 -0.01 -0.21 +1.00

v1-4.png
図1:ベクトル v1-v4までの視覚的表示.

ソースコードを提出すること.ファイル名は,153R000000-05-2.cpp とする.