プリント基板設計・シミュレーション

TOP > アポロレポート > コラム > 第67回 プログラミングについて『連立1次方程式を解こう その1』
コラム
2024/04/25

第67回 プログラミングについて『連立1次方程式を解こう その1』

アポロレポート

 連立1次方程式といえば中学生の頃に学校で習ったような記憶があります。プログラムの世界でも分野にもよりますが、連立1次方程式を解くことは、決して避けることはできないものの1つです。特に、自由曲線を扱うもの、力学的な解析を行なうもの、電気回路の解析を行なうものなどでは当たり前のように使用されています。
 私はどちらかと言えば、電気系の分野のプログラムを書くことが多いのですが、電気回路の解析はこれまでの自分の範疇ではなかったのでもっぱら自由曲線を扱うときぐらいにしか使用していないのが正直なところです。

 電気回路を勉強した方なら誰でもが知っているキルヒホッフの法則というものがあって、回路に流れる電流を求めるときに非常に役に立ってくれます。

   ┌───┐ ┌───┐
  ┌┤ R1 ├┬┤ R2 ├┐
  │└───┘│└───┘│
  │  ← ┌┴┐ ←  │
  │    │R│    │+
  │↓I1↑│ │↓I2↑○E
  │    │3│    │ー
  │    └┬┘    │
  │  →  │  →  │
  └─────┴─────┘

 上の図で、抵抗が3本(R1,R2,R3)と電源(E)の回路に流れる電流を求めたいときにこのキルヒホッフの法則を使用します。この法則の内容はともかくとして、上の図で、R1ーR3のループ部分を流れる電流をI1と仮定し、R2ーR3ーEのループを流れる電流をI2と仮定します。そうすると、次のような連立1次方程式を作ることができます。

 R1*I1+R3*(I1ーI2)  =0
 R2*I2+R3*(I2ーI1)ーE=0

この連立1次方程式を変形して、

 R1*I1+R3*I1ーR3*I2=0
 R2*I2+R3*I2ーR3*I1=E

さらに、

 (R1+R3)*I1   ーR3   *I2=0
   ーR3   *I1+(R2+R3)*I2=E

となります。人間ならばここでさらに式を変形して、I1とI2を求めるのですが、計算機はこのままだと非常に解を求めるのは面倒ですので、この式を行列表現してみます。

 ┌           ┐┌  ┐ ┌ ┐
 │R1+R3、  ーR3││I1│ │0│
 │           ││  │=│ │
 │  ーR3、R2+R3││I2│ │E│
 └           ┘└  ┘ └ ┘

するとこのように表現することができます。実際にはI1とI2以外には数値が入りますので、後は数値の計算をすることになります。例えば、R1を1、R2を2、R3を3、Eを10としてみると、

 ┌       ┐┌  ┐ ┌  ┐
 │1+3、 ー3││I1│ │ 0│
 │       ││  │=│  │
 │ ー3、2+3││I2│ │10│
 └       ┘└  ┘ └  ┘

となり、

 ┌     ┐┌  ┐ ┌  ┐
 │ 4、ー3││I1│ │ 0│    ー(式1)
 │     ││  │=│  │
 │ー3、 5││I2│ │10│    ー(式2)
 └     ┘└  ┘ └  ┘

となります。この左側の2X2の行列が

 ┌   ┐
 │1、0│
 │   │
 │0、1│
 └   ┘
となるようにこの行列を変形してみます。

 ┌        ┐┌  ┐ ┌  ┐
 │ 11/5、 0││I1│ │ 6│ ー(式3)
 │        ││  │=│  │
 │ー3   、 5││I2│ │10│ ー(式2)
 └        ┘└  ┘ └  ┘

式3は(式1)ー(式2)*(ー3)/5を計算したものです。次に式2ー式1*(ー3)/(11/5)を式4にします。

 ┌      ┐┌  ┐ ┌      ┐
 │11/5、0││I1│ │6     │ ー(式3)
 │      ││  │=│      │
 │0   、5││I2│ │200/11│ ー(式4)
 └      ┘└  ┘ └      ┘

次に式3/(11/5)を式5、式4/5を式6にします。

 ┌   ┐┌  ┐ ┌      ┐
 │1、0││I1│ │ 30/11│ ー(式5)
 │   ││  │=│      │
 │0、1││I2│ │ 40/11│ ー(式6)
 └   ┘└  ┘ └      ┘

結果として、

 I1= 30/11
 I2= 40/11

と求めることができます。

 プログラムで上の行列を計算するときには、そのままの形では計算し辛いので、

┌  ┐
│I1│
│  │
│I2│
└  ┘

を省略し、

 ┌     ┐ ┌  ┐
 │ 4、ー3│ │ 0│
 │     │=│  │
 │ー3、 5│ │10│
 └     ┘ └  ┘

とします。これでも行列が2つですので、

 ┌        ┐
 │ 4、ー3、 0│
 │        │
 │ー3、 5、10│
 └        ┘

と1つの行列にして計算します。

 これまでやってきたことをもう少し一般化するとn元の行列は、

 ┌            ┐
 │A00、A01、...A0n、V0│
 │A10、A11、...A1n、V1│
       :
       :
 │An0、An1、...Ann、Vn│
 └            ┘

という n*(n+1) の行列にして、A00 から Ann までの対角の成分が1それ以外が0になるように計算していきます。要するに単位行列にする訳です。

 これまで説明してきた行列の解法はガウス・ジョルダン法という方法で、プログラムも単純に記述することができます。

 ここからは、次の4x4の行列を解くことを考えてみましょう。

  ┌         ┐┌ ┐ ┌  ┐
  │2.0, 5.0, 8.0, 3.0││Xa│ │10.0│
  │4.0, 2.0, 3.0, 7.0││Xb│=│25.0│
  │8.0, 6.0, 9.0, 4.0││Xc│ │30.0│
  │9.0, 4.0, 3.0, 8.0││Xd│ │45.0│
  └         ┘└ ┘ └  ┘

先ほど説明したように、この行列を次のようにプログラム内で表現します。

 #define DIM 4

 double a[DIM][DIM+1]={
  2.0, 5.0, 8.0, 3.0, 10.0,
  4.0, 2.0, 3.0, 7.0, 25.0,
  8.0, 6.0, 9.0, 4.0, 30.0,
  9.0, 4.0, 3.0, 8.0, 45.0
 };

プログラムは次のように書くことができます。

 #define DIM 4

 double a[DIM][DIM+1]={
  2.0, 5.0, 8.0, 3.0, 10.0,
  4.0, 2.0, 3.0, 7.0, 25.0,
  8.0, 6.0, 9.0, 4.0, 30.0,
  9.0, 4.0, 3.0, 8.0, 45.0
 };

 main()
 {
  int  i,j,k;
  double t;
       /* 対角以外の要素を0にする */
  for(i=0;i<DIM;i++){
   for(j=0;j<DIM;j++){
    if(j==i)continue;

    t=a[j][i]/a[i][i];
    for(k=0;k<=DIM;k++)a[j][k]-=a[i][k]*t;
   }

   disp(a); /* 動作が分かりやすいように経過を表示 */
   getchar();
  }
               /* 対角の要素を1にする */
  for(i=0;i<DIM;i++){
   t=a[i][i];
   for(j=0;j<=DIM;j++)a[i][j]/=t;
  }

  disp(a);
 }
               /* 行列の表示 */
 disp(a)
 double a[DIM][DIM+1];
 {
  int i,j;

  for(i=0;i<DIM;i++){
   for(j=0;j<=DIM;j++)printf("%10.4f ",a[i][j]);
   printf("\n");
  }
 }

このプログラムを実行してみると、

 2.0000   5.0000   8.0000   3.0000  10.0000
 0.0000  -8.0000  -13.0000   1.0000   5.0000
 0.0000  -14.0000  -23.0000  -8.0000  -10.0000
 0.0000  -18.5000  -33.0000  -5.5000   0.0000
最初の列の対角以外の要素が0になっています。

 2.0000   0.0000  -0.1250   3.6250  13.1250
 0.0000  -8.0000  -13.0000   1.0000   5.0000
 0.0000   0.0000  -0.2500  -9.7500  -18.7500
 0.0000   0.0000  -2.9375  -7.8125  -11.5625
2番めの列の対角以外の要素が0になっています。

 2.0000   0.0000   0.0000   8.5000  22.5000
 0.0000  -8.0000   0.0000  508.0000  980.0000
 0.0000   0.0000  -0.2500  -9.7500  -18.7500
 0.0000   0.0000   0.0000  106.7500  208.7500
3番めの列の対角以外の要素が0になっています。

 2.0000   0.0000   0.0000  -0.0000   5.8782
 0.0000  -8.0000   0.0000   0.0000  -13.3958
 0.0000   0.0000  -0.2500   0.0000   0.3162
 0.0000   0.0000   0.0000  106.7500  208.7500
最後の列の対角以外の要素が0になっています。

 1.0000   0.0000   0.0000  -0.0000   2.9391
 0.0000   1.0000   0.0000  -0.0000   1.6745
 0.0000   0.0000   1.0000  -0.0000  -1.2646
 0.0000   0.0000   0.0000   1.0000   1.9555
対角の要素を1にしています。          ↑
                       これが解になる。

 以外に簡単なプログラムで解くことができましたが、このアルゴリズムには問題があります。すべての行を総当たりで計算しますので、元の数が多くなると計算回数が飛躍的に増えてしまうのです。単純に計算すると、対角以外の要素を0にするループでは、n元のときには n*n*(n+1) 回の計算を行ないますので、nが4のときには80回、nが5のときには 150回、nが100のときには101万回の計算を行なわなければならない訳です。

 続きはまた次回。


そのお悩み、
アポロ技研に話してみませんか?

アポロ技研でワンランク上の物創りへ!
そのお悩み、アポロ技研に話してみませんか?