連立一次方程式の解法10

 連立一次方程式の解法の続きで、今回は、線形システムの反復解法です。
ヤコビ法、ガウス・ザイデル法のプログラム例です、この計算方法についての詳細は、webで検索すれば、沢山検索出来ますのでそちらを参照して下さい。
反復計算で、解に収束するには、条件があるので実際の連立方程式の計算にはあまり使用されないようです。

ヤコビ法

 左記は、3元の場合でヤコビ法による計算手順で初期値としてXnにゼロ与えて、反復解法を行う計算式です。
但し、正解に近づくためには条件があります。

ガウス・ザイデル法

 ヤコビ法を改良したものでガウス・ザイデル法です。
ヤコビ法より、速く収束します。
正解に近づくためには条件が存在しますが条件はヤコビ法よりは、かなり緩くなります。


収束条件

 収束する条件は、各行の非対角の絶対値の和より対角の絶対値が大きいことです。
必ず収束するのは、全ての行が対角の絶対値が大きいことですが、一か所の行が条件を満たしていると、収束をする場合があります。

 ガウス・ザイデル法の場合、最終行が条件を満たしている場合、他の行が条件を満たしていなくても、絶対値の比が 1 に近いほど収束する確率が高くなります。
当然最終行は絶対値の比が 1 より大きくなるのですが、この値が大きい程他の行の値は小さくても良くなります。
大きいのが最終行で無くても、収束しますが、1行目については、大きくしても効果は余りありません。
 条件を満たすようにする為には、行の移動、列の移動を行います。

 1行しか条件を満たしていない場合、現在は収束するかどうか解りません。
特に、解けない値なのか、収束しないだけなのか判別出来ないのが大きな問題です。
しかし、単純な計算の繰り返しなので、何らかの判別方法があると思われますが、つきとめている人はいない様です。
必ず解を求めることが出来る、LU分解や、消去法があるので、反復法でも計算が出来ると言う事なのでしょう。

 左図は今回のプログラムの実行画面です。
通常はガウス・ザイデル法で計算され、
ヤコビ法のチェックボックスにチェックを入れるとヤコビ法で計算されます。

解法出来るサンプル
 ヤコビ法            E 
 ガウス・ザイデル法      A,B,E
 消去法(ピボット操作有り)  A,B,C,E

サンプルDは、解法出来ない(答えが無い)値のサンプルです。

 プログラムには、前のプログラムを流用したので、結果の検定が残っていますが、解法出来ない値の場合、収束しないので、検定は無意味となっています。


プログラム

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, Vcl.Grids, system.UITypes,
  Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    BitBtn1: TBitBtn;
    Memo1: TMemo;
    StringGrid1: TStringGrid;
    StringGrid2: TStringGrid;
    LabeledEdit1: TLabeledEdit;
    BitBtn2: TBitBtn;
    RadioGroup1: TRadioGroup;
    LabeledEdit2: TLabeledEdit;
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    procedure BitBtn1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private 宣言 }
    function Jacobi: byte;
    function Gauss_Seidel: byte;
    procedure GridSet(Sin, Colin: integer);    // グリッドサイズ変更
    procedure testset;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  Nltest : integer = 4;             // テストデーター行列数
                                    // テストデーター
  mattestA : array[0..3] of array[0..3] of extended = ((8, 16, 24, 32),
                                                       (2,  7, 12, 17),
                                                       (6, 17, 32, 59),
                                                       (7, 22, 46,105));
  mattestB : array[0..3] of array[0..3] of extended = ((8, 16, 24,  32),
                                                       (2,  7, 12,  17),
                                                       (2, 17, 32,   0),
                                                       (7,  0,  0, 105));
  mattestC : array[0..3] of array[0..3] of extended = (( 8, 34, 24,  32),
                                                       ( 2,  7, 32,  0),
                                                       (24, 17, 46, 105),
                                                       ( 7,  0,  0,  32));
  mattestD : array[0..3] of array[0..3] of extended = ((8, 16, 24,   0),
                                                       (2,  7, 12,   0),
                                                       (2, 17, 32,   0),
                                                       (7,  0,  0, 105));
  mattestE : array[0..3] of array[0..3] of extended = ((8,  0,  0,  32),
                                                       (0, 17, 12,  17),
                                                       (6, 17, 32,   0),
                                                       (7,  0,  0, 105));
  btestA    : array[0..3] of extended = (160, 70, 198, 291);
  btestB    : array[0..3] of extended = (150, 60, 10, 40);

  Nl : integer;                       // 行列数
  a  : array of array of extended;
  b  : array of extended;           // b行列
  x  : array of extended;           // x行列
  loop : integer;

  allowable_errorr : extended;
  epsilon : extended;

// グリッドの設定
procedure TForm1.GridSet(Sin, Colin: integer);    // グリッドサイズ変更
var                                               // データー行の数は10迄
  H, W, i :integer;                               // それ以上はスクロールする
begin
  inc(Sin);
  inc(Colin);
  StringGrid1.ColCount := Colin;
  StringGrid1.RowCount := Sin;
  if Sin <= 11 then begin                         // ストリンググリッドの大きさ設定 固定行を含め11行又はそれ以下の場合
    H := (StringGrid1.DefaultRowHeight + 1) * Sin;
    StringGrid2.ScrollBars := ssNone;             // スクロールバー表示
  end
  else begin                                      // 固定行を含め11行より多い場合
    H := (StringGrid1.DefaultRowHeight + 1) * (10 + 1);
    StringGrid2.ScrollBars := ssVertical;         // スクロールバー表示
  end;
  if Colin <= 11 then begin                       // ストリンググリッドの大きさ設定 固定行を含め11行又はそれ以下の場合
    W := (StringGrid1.DefaultColWidth + 1) * Colin;
    StringGrid1.ScrollBars := ssNone;             // スクロールバーなし
  end
  else begin                                      // 固定行を含め11行より多い場合
    W := (StringGrid1.DefaultColWidth + 1) * (10 + 1);
    StringGrid1.ScrollBars := ssBoth;             // スクロールバー表示
  end;
  StringGrid1.ClientWidth := W;
  StringGrid1.ClientHeight := H;
  for i := 1 to Sin - 1 do StringGrid1.Cells[0,i] := 'a' + intTostr(i) + '*';
  for i := 1 to Sin - 1 do StringGrid1.Cells[i,0] := 'a*' + intTostr(i);
  StringGrid2.RowCount := Sin;
  StringGrid2.ClientHeight := H + 1;
  StringGrid2.ClientWidth := (StringGrid2.DefaultColWidth + 1) * 2;
  for i := 1 to Sin - 1 do StringGrid2.Cells[0, i] := intTostr(i);
  StringGrid2.Cells[1, 0] := 'b*=';
end;

// ヤコビ法
function TForm1.Jacobi: byte;
var
  y : array of extended;
  i, j, k, l : integer;
begin
  setlength(y, Nl);
  loop := 0;
  result := 0;
  for i := 0 to Nl - 1 do x[i] := 0;          // 初期値
  for k := 0 to Nl * 2000 do begin
    for i := 0 to Nl - 1 do begin
      y[i] := b[i];
      for j := 0 to Nl - 1 do
        if j <> i then y[i] := y[i] - a[i, j] * x[j];
      y[i] := y[i] / a[i, i];
    end;
    for l := 0 to Nl -1 do
      if abs(y[l] - x[l]) > epsilon * (1 + abs(x[l])) then break;   // 収束判定
    if l = Nl then begin
      result := 1;
      break;
    end;
    for i := 0 to Nl - 1 do x[i] := y[i];
    inc(loop);
  end;
end;

// ガウス・ザイデル法による連立方程式の解法
function TForm1.Gauss_Seidel: byte;
var
  s : extended;
  i, j, iter: integer;
begin
  loop := 0;
  result := 0;
  for i := 0 to Nl - 1 do x[i] := 0;          // 初期値
  for iter := 1 to Nl * 1000 do begin
    result := 1;
    for i := 0 to Nl - 1 do begin
      s := b[i];
      for j := 0 to i - 1 do  s := s - a[i, j] * x[j];
      for j := i + 1 to Nl - 1 do  s := s - a[i, j] * x[j];
      s := s / a[i, i];
      if (result = 1) and (abs(x[i] - s ) > epsilon * (1 + abs(s))) then result := 0; // 収束判定
      x[i] := s;
    end;
    inc(loop);
    if result = 1 then break;                 // 収束した場合ブレーク
  end;
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
const
  sp = ' ';
var
  i, j, ch : integer;
  maxb, minb, bc : extended;
  acmax : array of extended;
  acmin : array of extended;
  y : array of extended;
  LL, spl : string;
  sum : extended;
  k, n, h : integer;
begin
  setlength(a, Nl, Nl);
  setlength(b, Nl);
  setlength(y, Nl);
  setlength(x, Nl);
  setlength(acmax, Nl);
  setlength(acmin, Nl);

  // matAにグリッド1から値読み込み
  for i := 0 to Nl - 1 do
    for j := 0 to Nl - 1 do begin
      val(StringGrid1.Cells[i + 1,j + 1], a[j,i], ch);
      if ch <> 0 then begin
        MessageDlg('a' + intTostr(j + 1) + ',' + intTostr(i + 1) + ' の入力値の' + intTostr(ch) + '文字目に誤りが有ります。', mtInformation,[mbOk], 0);
        exit;
      end;
    end;
  // bにグリッド2から値読み込み
  for i := 0 to Nl - 1 do begin
    val(StringGrid2.Cells[1, i + 1], b[i], ch);
    if ch <> 0 then begin
      MessageDlg('b' + intTostr(i + 1) + 'の入力値の' + intTostr(ch) + '文字目に誤りが有ります。', mtInformation,[mbOk], 0);
      exit;
    end;
  end;
  // 検算許容誤差
  val(labelededit2.Text, allowable_errorr, ch);
  if ch <> 0 then begin
    MessageDlg('検算誤差許容値の入力値の' + intTostr(ch) + '文字目に誤りが有ります。', mtInformation,[mbOk], 0);
    exit;
  end;
  // 対角0の確認
  for i := 0 to Nl - 1 do begin
    if a[i, i] = 0 then begin
      MessageDlg('対角 a' + intTostr(i + 1) + ',' + intTostr(i + 1) + 'の値が0(ゼロ)です。', mtInformation,[mbOk], 0);
      exit;
    end;
  end;
  // A行列の最大値最小値検索
  for i := 0 to Nl -1 do begin
    acmax[i] := 1;
    acmin[i] := 1;
    if a[i, 0] <> 0 then acmax[i] := abs(a[i, 0]);
    if a[i, 0] <> 0 then acmin[i] := abs(a[i, 0]);
    for j := 1 to Nl - 1 do begin
      if (abs(a[i, j]) > acmax[i]) and (a[i, j] <> 0) then acmax[i] := abs(a[i, j]);
      if (abs(a[i, j]) < acmin[i]) and (a[i, j] <> 0) then acmin[i] := abs(a[i, j]);
    end;
  end;
  // B行列とA行列の比最小値と最大値検索
  maxb := abs(b[0]) / acmax[0];
  minb := abs(b[0]) / acmin[0];
  for i := 1 to Nl - 1 do begin
    if abs(b[i]) / acmax[i] > maxb then maxb := abs(b[i]) / acmax[i];
    if abs(b[i]) / acmin[i] < minb then minb := abs(b[i]) / acmin[i];
  end;
  // 検算結果の判定値設定
  bc := minb;
  if (minb = 0) and (maxb > 0) then
    if maxb >= 1 then bc := 1 / maxb
                 else bc := maxb;
  if (minb > 0) and (maxb > 0) then bc := minb / maxb;
  if (minb = 0) and (maxb = 0) then bc := 1;
  if checkbox1.Checked = false then
    if bc  < epsilon then begin
      MessageDlg('bの入力値の値が演算の範囲を超えています。', mtInformation,[mbOk], 0);
      memo1.Clear;
      exit;
    end;
  // 検算結果の判定値設定
  allowable_errorr := allowable_errorr * sqrt(bc);      // 誤差判定値

  memo1.Clear;

  // 連立方程式の解法  解法できない場合あり
  if checkbox2.Checked = true then j := Jacobi
                              else j := Gauss_Seidel;

  memo1.Lines.Append('Loop = ' + inttostr(loop));
  // 解法出来なかったら
  if j = 0 then begin
    MessageDlg('収束しません。', mtInformation,[mbOk], 0);
    exit;
  end;
  // 各行X値代入答えの差分計算
  for j := 0 to Nl - 1 do begin
    sum := 0;
    for i := 0 to Nl - 1 do
      sum := sum + a[j, i] * x[i];            // 行の値
    y[j] := b[j] - sum;                       // 差分
  end;

  // 各行の差分値表示
  memo1.Lines.Append('行 No                検算による差分');
  for  k := 0 to Nl - 1 do begin
    LL := 'No = ' + inttostr(k + 1);
    n := 25 - length(LL);
    spl := '';
    for j := 1 to n do spl := spl + sp;
    memo1.Lines.Append(LL + spl + floattostr(y[k]));
  end;

  // 差分判定
  h := 0;
  for k := 0 to Nl - 1 do begin
    if abs(b[k]) > allowable_errorr then sum := y[k] / b[k]
                                    else sum := y[k];
    if abs(sum) > allowable_errorr then inc(h);
  end;

  if checkbox1.Checked = false then begin
    if h = 0 then memo1.Lines.Append('判定   OK')
             else memo1.Lines.Append('判定   NG');
    if h > 0 then  exit;
  end;

  // xの値表示
  for k := 0 to Nl - 1 do
    memo1.Lines.Append('X' + inttostr(k + 1) + '=' + floatTostr(x[k]));
end;

// テストデーターの選択設定
procedure TForm1.testset;
var
  i, j : integer;
begin
  GridSet(Nltest, Nltest);
  Nl := Nltest;
  for i := 1 to Nltest do
    for j := 1 to Nltest do begin
      case RadioGroup1.ItemIndex of
        0: StringGrid1.Cells[i,j] := floatTostr(mattestA[j-1, i-1]);
        1: StringGrid1.Cells[i,j] := floatTostr(mattestB[j-1, i-1]);
        2: StringGrid1.Cells[i,j] := floatTostr(mattestC[j-1, i-1]);
        3: StringGrid1.Cells[i,j] := floatTostr(mattestD[j-1, i-1]);
        4: StringGrid1.Cells[i,j] := floatTostr(mattestE[j-1, i-1]);
      end;
    end;
  for i := 1 to Nltest do begin
    case RadioGroup1.ItemIndex of
      0: StringGrid2.Cells[1,i]  := floatTostr(btestA[i-1]);
      1, 2, 3, 4: StringGrid2.Cells[1,i]  := floatTostr(btestB[i-1]);
    end;
  end;
  LabeledEdit1.Text := intTostr(Nltest);
  memo1.Clear;
end;

// グリッドサイズの変更
procedure TForm1.BitBtn2Click(Sender: TObject);
var
  ch : integer;
begin
  val(LabeledEdit1.Text, Nl, ch);
  if ch <> 0 then begin
    MessageDlg('N数の入力値の' + intTostr(ch) + '文字目に誤りが有ります。', mtInformation,[mbOk], 0);
    exit;
  end;
  GridSet(Nl, Nl);
end;

procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  testset;
end;

// 初期値の設定
procedure TForm1.FormCreate(Sender: TObject);
var
  a : extended;
begin
  testset;
  epsilon := 1;
  repeat
    epsilon := epsilon / 2;
    a := 1 - epsilon;
  until a = 1;
  epsilon := epsilon * 2;
end;

end.

download Gauss_Seidel.zip

  三角関数、逆三角関数、その他関数、 連立一次方程式の解法 に戻る