C言語で学ぶ大学の数学 「複素数の四則演算 その2『忠実表現』」

この記事ではC言語で複素数の計算を、複素数体の忠実表現を利用して計算する方法について説明します。



1.環の表現・忠実表現とは


可換環 R の表現とは、ある線形空間 V の全ての自己準同型写像のなす環 End(V) への環準同型 R → End(V) のことです。

End(V) は、例えば V が実 n 次元ベクトル空間の場合は全ての成分が実数の n×n 行列の集合のなす環 M_n(ℝ) になります。

要するに環の各元に行列をうまく対応させる写像が表現だと思ってください。


一般に R と V に対して表現ρ: R → End(V) はいくつも存在します。

その中でも単射であるようなρを忠実であるとか、忠実表現(faithful representation)であるといいます。

忠実表現は環 R の情報を失うことなく行列に置き換えているため、色々と都合が良いです。


体の表現を考えるときは、体を可換環とみなして可換環の表現を考えます。

(注:より一般には、可換環 R に対し R 上の「代数(algebra)」A というものを考え、代数 A の表現 A→ End(V)を定義します。ただしこのとき、V は A と同じ環 R をスカラーに持つ必要があります。

代数とはベクトル空間に積構造を入れた概念です。環や体はその可換な部分環をスカラーとする代数であるとみなせます。

最初に述べた定義は可換環 R 自身が R 上の代数であるとみなすか、R をその部分環 S 上の代数とみなしています。)



2.複素数体の表現の例


では、複素数体の場合にどのような表現があるか例を挙げます。


例1.任意の複素数 c に c 倍写像を対応させる表現 e :ℂ→ End(ℂ)


これは V を複素1次元ベクトル空間 ℂにとっています。このときEnd(ℂ)の元は 1×1 行列、つまりただのスカラー倍写像です。

e(c) という写像は複素数 z に cz を対応させる写像です。

(この例自体は面白くありませんが、ℂを自明なLie bracket をもつ1次元リー代数だと思えば、この e は有限次元リー代数の忠実表現の例の一つでもあるので面白い、かもしれません)


例2.任意の複素数 x + iy (x, yは実数) に対し以下の実係数の2次正方行列

 [x, y]

 [-y, x]

(行ベクトルを並べて表示しています)

を対応させる写像 f: ℂ→ End(ℝ^2)


これが表現になっていることは、f(1) が単位行列であり、f(i) が二乗すると単位行列の-1 倍になっていて、f(x+iy) = x*f(1) + y*f(i) であることなどから証明できます。

この表現の優れている点は、任意の複素数に対応する行列が係数の行列であり、行列のサイズが小さく、そして忠実表現であることです。

係数が実数であるので、C言語で扱えます。

行列のサイズが小さければ小さいほど、計算時間は短くて済みます。これより小さい行列は1次正方行列だけですが、表現 ℂ→ End(ℝ) はどうやっても情報が失われてしまいます。


なので以下では例2の表現を使って複素数の計算をする方法を説明します。

また、この表現がどのようにして得られるのかはページの最後で解説します。



3.忠実表現を利用した複素数の扱い


上の表現を踏まえると、例えば 2+3i を格納するには引数が二つの配列変数を定義して


float a[2] = {2, 3};

float ma[2][2];

ma[0][0] = a[0];

ma[0][1] = a[1];

ma[1][0] = -a[1];

ma[1][1] = a[0];


とします。

或いは、 main 関数で変数に格納する際はあくまで


float a[2] = {2, 3};


としておき、ユーザ関数の中で


 float ma[2][2];

 ma[0][0] = a[0];

 ma[0][1] = a[1];

 ma[1][0] = -a[1];

 ma[1][1] = a[0];


とするのもいいかもしれません。

今回は前者の方法で変数を格納したときにユーザ関数をどのように定義するかについて説明することとします。



4.加法と減法


加法について、複素数 a, b に対し c = a + b を行列に置き換えて計算するユーザ関数は次のように定義できます。


void add_cpx(float ma[2][2], float mb[2][2], float mc[2][2]){

 int i,j;

 for(i=0; i<2; i++){

  for(j=0; j<2; j++){

   mc[i][j] = ma[i][j] + mb[i][j];

  }

 }

}


返り値を void としているので mc[0][0], mc[0][1], mc[1][0], mc[1][1] 全ての値を一度に変更しています。

行列に置き換えているので当然ですが、行列の加法を計算するのと同じです。

減法についても同様です。



5.乗法と除法


まずは乗法から説明します。

ma[][]とmb[][]の積をmc[][]に代入するには次のようにします。


void multi_cpx(float ma[2][2], float mb[2][2], float mc[2][2]){

 int i,j,k;

 for(i=0; i<2; i++){

  for(j=0; j<2; j++){

   mc[i][j] = 0;

   for(k=0; k<2; k++) mc[i][j] += ma[i][k]*mb[k][j];

  }

 }

}


このように、行列の積を計算しているのと同じです。


次に除法です。除法の計算のためにまずは逆数に対応する元を求めます。

複素数体で逆元をとることは f の像の逆行列を求めることに対応します。

2次正方行列の逆行列は公式として覚えられるほど簡単なので、公式を利用します。


void inverse_cpx(float ma[2][2]){

 float det = ma[0][0]*ma[1][1] - ma[0][1]*ma[1][0];

 float mb[2][2];

 int i,j;

 mb[0][0] = ma[1][1]/det;

 mb[0][1] = -ma[0][1]/det;

 mb[1][0] = -ma[1][0]/det;

 mb[1][1] = ma[0][0]/det;

 for(i=0; i<2; i++){

  for(j=0; j<2; j++){

   ma[i][j] = mb[i][j];

  }

 }

}


計算結果をmb[][]という変数に代入してから、それをma[][]に代入しています。


これを使って、ma[][]に対応する複素数をmb[][]に対応する複素数で割った値に対応する行列を mc[][]に代入するには


void div_cpx(float ma[2][2], float mb[2][2], float mc[2][2]){

 float md[2][2];

 int i,j;

 for(i=0; i<2; i++){

  for(j=0; j<2; j++){

   md[i][j] = mb[i][j];

  }

 }

 inverse_cpx(md);

 multi_cpx(ma, md, mc);

}


とします。上のやり方はmb[]を書き換えないためにmd[]という変数を用意して、そこにmb[]の値をコピーして逆数を求めています。



6.計算例


では、簡単な四則演算を上記の方法を使って計算してみます。前回と同じ例を使います。

 a = 2.0 + 3.0i

 b = -2.0 -1,0i

 c = 1.0 + 1.0i

 d = 3.0 + 5.0i

として

 e = (a + b)/c - d

を計算します。正しい答えは -2.0 - 4.0i です。


#include<stdio.h>


void add_cpx(float ma[2][2], float mb[2][2], float mc[2][2]){

(省略)

}


void sub_cpx(float ma[2][2], float mb[2][2], float mc[2][2]){

(省略)

}


void multi_cpx(float ma[2][2], float mb[2][2], float mc[2][2]){

(省略)

}


void inverse_cpx(float ma[2][2]){

(省略)

}


void div_cpx(float ma[2][2], float mb[2][2], float mc[2][2]){

(省略)

}


void input_matrix(float a[2], float ma[2][2]){

ma[0][0] = a[0];

ma[0][1] = a[1];

ma[1][0] = -a[1];

ma[1][1] = a[0];

}


int main(){

 float a[2] = {2.0, 3.0};

 float b[2] = {-2.0, -1.0};

 float c[2] = {1.0, 1.0};

 float d[2] = {3.0, 5.0};

 float ma[2][2], mb[2][2], mc[2][2], md[2][2];

 float me[2][2], mf1[2][2], mf2[2][2];

 input_matrix(a, ma);

 input_matrix(b, mb);

 input_matrix(c, mc);

 input_matrix(d, md);

 add_cpx(ma, mb, mf1);

 div_cpx(mf1, mc, mf2);

 sub_cpx(mf2, md, me);


 printf("answer is %f+%fi\n",me[0][0],me[0][1]);


 return 0;

}


上では説明しなかった input_matrix という関数がありますが、これはa[]に格納した複素数に対応する行列 ma[][]をとるための関数です。



7.忠実表現の構成


では最後に、複素数体の忠実表現 f がどのように得られるのか説明します。

一般に、代数 A の表現ρ:A → End(V)は A の基底がEnd(V)のどの元に対応するかを決めれば、他のAの元は基底の線形結合で書けるのでρの値が自動的に定まります。

A=ℂの場合は、1と虚数単位 i のとる値を決めれば、全ての複素数に対する値が定まります。


このうち1については、ρが環準同型であるということからρ(1)は V の恒等写像である必要があります。

行列環の元で言えばρ(1)は単位行列です。

そして i については、i^2 = -1 であるのでρ(i)の二乗が単位行列の-1倍である必要があります。

二乗すれば単位行列の-1 倍になる行列は、全ての固有値が i か -i になっています。

ここで、ρ(i)が実数係数の行列ということは、そのトレースも必ず実数です。

なのでρ(i)の固有値 i の個数と固有値 -i の個数は等しくなければありません。

2次正方行列の場合は、どちらの固有値も重複度が1でなければなりません。

よってρ(i)は


[i, 0]

[0, -i]


という行列と共役になっている必要があります。

つまり一般には -a^2 - b*c = 1 をみたす実数 a,b,cに対し


[a, b]

[c, -a]


という行列である必要があります。

実はρ(i)が上のような行列であれば、ρを線形に拡張することでρは表現になります。

例えば


[2, 1]

[-5, -2]


だと思えば、ρ(x + iy)は


[x +2y, y]

[-5y, x -2y]


に対応します。実際、input_matrix という関数を


void input_matrix(float a[2], float ma[2][2]){

 ma[0][0] = a[0] + 2*a[1];

 ma[0][1] = a[1];

 ma[1][0] = -5*a[1];

 ma[1][1] = a[0] - 2*a[1];

}


に置き換えれば、こちらの場合のρ(i)を使った忠実表現による複素数の計算が可能です。

ですが、その場合は計算結果の出力を


printf("answer is %f+%fi\n",(me[0][0]+me[1][1])/2, me[0][1]);


とするなど、工夫が必要です。

このような面倒を避けるには、ρ(i)を対角成分がともに0となるように


 [0, 1]

 [-1, 0]


ととるのが一番いいと思います。


  • Twitterで共有
  • Facebookで共有
  • はてなブックマークでブックマーク

作者を応援しよう!

ハートをクリックで、簡単に応援の気持ちを伝えられます。(ログインが必要です)

応援したユーザー

応援すると応援コメントも書けます

C言語で学ぶ大学の数学 アカイロモドキ @akairo_modoki

★で称える

この小説が面白かったら★をつけてください。おすすめレビューも書けます。

フォローしてこの作品の続きを読もう

この小説のおすすめレビューを見る

この小説のタグ