連立一次方程式の解法1

 連立一次方程式の解き方は色々あるようですが、Webで検索して取りあえず、LU分解による方法からプログラムを検討してみることにしました。
最初に参考にしたものは、[数学] LU分解法をプログラムで書いてみたです。

 連立方程式を
行列式で表し更に恒等式Ax=bで表しています。

 Aの行列を行列の対角の右上がゼロとなるLの行列と、左下がゼロとなるUの行列の積にします。



その後LUを使用して連立一次方程式を解きます。
詳細については、webで見てください。

 完全にLとUの行列に分けてしまう方法では、演算途中で、除算の分母にゼロが入らないようにするピボット操作が難しい為、除算の時分母にゼロが入った場合、単純に行の入れ替えを行って、ゼロが入らなくなるまで、行を入れ替えて計算を繰り返す方法をとってみました。
一定数以上繰り返しても答えが見つからない場合、答え無しとしました。
行列の大きさが大きい場合には、無理があります。

更に、演算誤差が大きいのか、除算の時、分母がゼロになる筈なのがゼロににらず、全く違った値になる為、検算し結果の判定をするようにしました。
bの値とかけ離れた値になるので、一目瞭然なのですが、一応プログラムに組み込んでみました。

プログラム

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;
    procedure BitBtn1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private 宣言 }
    function LUecomposition: byte;
    procedure GridSet(Sin, Colin: integer);    // グリッドサイズ変更
    procedure rowmatinc(k: 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));
  // このサンプル値はLU分解時対角がゼロとなるのですが、演算誤差によりゼロにならない値です。
  mattestC : 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));
  btestA    : array[0..3] of extended = (160, 70, 198, 291);
  btestB    : array[0..3] of extended = (150, 60, 10, 40);

  Nl : integer;                     // 行列数
  matA : array of array of extended;  // A行列配列
  bakA : array of array of extended;  // A行列配列

  matL : array of array of extended;  // L行列配列
  matU : array of array of extended;  // U行列配列
  lu   : array of array of extended;
  b    : array of extended;
  bakB : array of extended;
  ba   : array of extended;
  a    : array of array of extended;
  rowNo : array of integer;

  lc : integer;
  diagonal : 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;

// 行の入れ替え 対角にゼロが発生した時に行の入れ替えを行います。
procedure TForm1.rowmatinc(k: integer);
var
  ss : extended;
  i, m, p : integer;
begin
  p := k - 1;                    // p kの上の行
  if p < 0 then p := Nl + p;     // kが0一番上の時はpは3一番下
  for i := 0 to Nl - 1 do begin  // 上の行pと下の行k交換
    ss := matA[k, i];
    matA[k,i] := matA[p, i];
    matA[p, i] := ss;
  end;
  ss := b[k];
  b[k] := b[p];
  b[p] := ss;
  m := rowNo[k];                 // 交換行情報保存
  rowNo[k] := rowNo[p];
  rowNo[p] := m;
end;

// LU分解による連立方程式の解法
function TForm1.LUecomposition: byte;
const
  sp = ' ';
var
  i, j, k, n, h : integer;
  sum: extended;
  aa : array of array of extended;
  l, u, x, y: array of extended;
  LL, spl : string;
begin
  setlength(a, Nl, Nl);
  setlength(aa, Nl, Nl);

  setlength(l, Nl);
  setlength(u, Nl);
  setlength(x, Nl);
  setlength(y, Nl);

  // 行の入れ替えNo編集用にコピ// matデーターを編集用にコピー           matA, B
  for j := 0 to Nl - 1 do  begin
    ba[j ] := b[j];
    for k := 0 to Nl - 1 do a[j, k] := matA[j, k];
  end;

  memo1.Clear;

  // 編集用をバックアップににコピー
  for j := 0 to Nl - 1 do
    for k := 0 to Nl - 1 do
      aa[j, k] := a[j, k];

  // matUの対角を1に設定
  for i := 0 to Nl - 1 do
    for j := 0 to Nl - 1 do
      if i = j then begin
        matU[i, j] := 1;
        matL[i, j] := 0;
      end
      else begin
        matU[i, j] := 0;
        matL[i, j] := 0;
      end;

  // LU分解
  for i := 0 to Nl -1 do begin
    n := Nl - i - 1;
    // matA先頭の値をmatLの対角にセット
    matL[i, i] := a[0, 0];
    // MatLの作成
    for j := 0 to n - 1 do begin
      l[j] := a[j + 1, 0];
      matL[j + i + 1, i] := l[j];
    end;
    // 対角がゼロだったら終了
    if a[0, 0] = 0 then begin
      break;
    end;
    // matUの作成
    for j := 0 to n - 1 do begin
      u[j] := a[0, j + 1] / a[0, 0];
      matU[i, j + i + 1] := u[j];
    end;
    // luを求める
    for j := 0 to n - 1 do begin
      for k := 0 to n - 1 do begin
        lu[j, k] := l[j] * u[k];
      end;
    end;
    // aを求める
    for j := 0 to n - 1 do begin
      for k := 0 to n - 1 do begin
        a[j, k] := a[j + 1, k + 1] - lu[j, k];
      end;
    end;
  end;

  diagonal := Nl;

  // 対角にゼロが発生した場合演算を中止し'
  // 行の入れ替えを試みます
  if a[0, 0] = 0 then begin
    result := 1;
    diagonal := i;
    exit;
  end;

  // LU行列で連立方程式を解きます
  for i := 0 to Nl - 1 do begin
    sum := 0;
    for k := 0 to i - 1 do sum := sum + matL[i, k] * y[k];
    y[i] := (ba[i] - sum) / matL[i, i];
  end;
  // 後退代入
  for i := Nl - 1 downto 0 do begin
    sum := 0;
    for k := i + 1 to Nl - 1 do sum := sum + matU[i, k] * x[k];
    x[i] := y[i] - sum;
  end;

  // 各行代入答えの差分計算
  for j := 0 to Nl - 1 do begin
    sum := 0;
    for i := 0 to Nl - 1 do begin
      sum := sum + aa[j, i] * x[i];
    end;
    y[j] := ba[j] - sum;
  end;

  // 演算行No表示
  LL := '計算順 ';
  for i := 0 to Nl - 1 do begin
     if i < NL -1 then LL := LL + intTostr(rowNo[i] + 1) + ','
                  else LL := LL + intTostr(rowNo[i] + 1);
  end;
  memo1.Lines.Append(LL);

  // 行番号表示
  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;
    LL := LL + spl + floattostr(y[rowNo[k]]);
    memo1.Lines.Append(LL);
  end;

  // 判定
  h := 0;
  for k := 0 to Nl - 1 do begin
    if abs(ba[k]) > allowable_errorr then sum := y[k] / ba[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 begin
      result := 3;
      diagonal := 2;
      exit;
    end;
  end;

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

  result := 0;
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  i, j, ch, k : integer;
  maxb, minb, bc : extended;
  acmax : array of extended;
  acmin : array of extended;

  procedure sortdat;
  var
    k : integer;
  begin
    ch := 1;
    for k := 3 to Nl do ch := ch * k; // 最大ループ数計算 理論値の1/2
    // Lu分解連立方程式の解法  解法できない場合あり
    i := 0;
    j := 0;   // 解法できた場合 0  出来ない場合 1
    // Lu分解が正常に終了するまで繰り返す
    for k := 1 to ch do begin
      lc := Nl;
      repeat
        j := LUecomposition;
        if  j = 1 then begin        // 対角にゼロが発生
          dec(lc);
          inc(i);
          rowmatinc(lc);            // 行の移動
        end
        else lc := 0;
      until (lc <= 0) or (i >= ch);
      if (j = 0) or (i >= ch) then break;        // 対角値 0  か 解法で終了
    end;
  end;

begin
  setlength(matA, Nl, Nl);
  setlength(bakA, Nl, Nl);
  setlength(matL, Nl, Nl);
  setlength(b, Nl);
  setlength(bakb, Nl);
  setlength(ba, Nl);
  setlength(lu , Nl, Nl);
  setlength(matU, Nl, Nl);
  setlength(rowNo, 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],matA[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;
  // A行列の最大値最小値検索
  for i := 0 to Nl -1 do begin
    acmax[i] := 1;
    acmin[i] := 1;
    if matA[i, 0] <> 0 then acmax[i] := abs(matA[i, 0]);
    if matA[i, 0] <> 0 then acmin[i] := abs(matA[i, 0]);
    for j := 1 to Nl - 1 do begin
      if (abs(matA[i, j]) > acmax[i]) and (matA[i, j] <> 0) then acmax[i] := abs(matA[i, j]);
      if (abs(matA[i, j]) < acmin[i]) and (matA[i, j] <> 0) then acmin[i] := abs(matA[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;
  // bの値の範囲が演算桁数の範囲を越えているか判定
  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);      // 判定値

  for i := 0 to Nl -1 do begin        // 入力データーのバックアップ
    bakb[i] := b[i];
    for j := 0 to Nl - 1 do
      bakA[i, j] := matA[i, j];
  end;

  for i := 0 to Nl - 1 do rowNo[i] := i;

  sortdat;                           // 順方向計算
  if i >= ch then begin              // 順方向計算でNGだったら逆方向計算
    for k := 0 to Nl - 1 do begin
      rowNo[k] := Nl - 1 - k;
      b[rowNo[k]] := bakb[k];
      for j := 0 to Nl - 1 do
        matA[rowNo[k], j] := bakA[k, j];
    end;
    sortdat;
  end;

  // 解法出来なかったら
  if diagonal < Nl then begin
    MessageDlg('LU分解中に対角ゼロが発生しました、入力値を見直して下さい。', mtInformation,[mbOk], 0);
    exit;
  end;
  if j <> 0 then begin
    memo1.Clear;
    memo1.Lines.Append('答えを見つける事が出来ませんでした。');
  end;
  memo1.Lines.Append('再計算 Row= ' + intTostr(i));
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]);
      end;
    end;
  for i := 1 to Nltest do begin
    case radiogroup1.ItemIndex of
      0: StringGrid2.Cells[1,i]  := floatTostr(btestA[i-1]);
      1, 2 : 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 LU_ecomposition.zip

  三角関数、逆三角関数、その他関数 に戻る