連立一次方程式の解法2

 連立一次方程式の解法1の続きです。

 対角にゼロが入るかどうかをLU分解に先だって三角行列を作成して、行列の値から判別します。
対角にゼロが入らなければ、LU分解を行いますが、LU分解が出来ない場合は、LU分解が出来るまで、行の変更を行います。
今回の場合は、行の変更を、行の組み合わせ全てについて先に作成しておいて、順次組み合わせを変更して、解が求まるまで繰り返します。
解が求まらない場合は、全ての組み合わせの計算をするので解の無い方程式の行列式となります。

組み合わせの数は、行の数をnとすると、nの階乗となります。
5×5の場合で、24、6×6のの場合120もの組み合わせとなるのですが、PCでの実行なので、さほど時間は掛かりません。

 三角行列の行列式の値から対角にゼロが入るかどうか判別していますが、三角行列の作成時除算が入るので、 演算誤差によ数学的には行列式の値がゼロになる値が演算上必ずゼロになる保証は有りません。
LU分解時も同じなので、最終的には、検算による判別が必要ですが、此処のプログラムでは、最終判別を省略しています。

プログラム

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;
    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;
    function triangular_matrix(var z : integer): extended;
    procedure permutation;
  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, 16, 24,   0),
                                                       (2,  7, 12,   0),
                                                       (2, 17, 32,   0),
                                                       (7,  0,  0, 105));
  mattestD : array[0..3] of array[0..3] of extended = ((0,  16, 24,  32),
                                                       (2,  7, 12,  17),
                                                       (6, 17, 32,   0),
                                                       (7,  0,  0, 105));
  mattestE : array[0..3] of array[0..3] of extended = ((0,  0,  0,  32),
                                                       (0,  0, 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;                       // 行列数
  matA : 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;
  ba   : array of extended;
  a    : array of array of extended;
  perNo : array of array of integer;  // 全行No配列

  rowNo : array of integer;
  exchange : integer;

  diagonal : integer;


// グリッドの設定
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
  i : integer;
begin
  for i := 0 to Nl - 1 do rowNo[i] := perNo[k, i];
end;

// 行列式の値
// 単に対角にゼロがあるかどうかの確認
// 対角の計算値がゼロになのに演算誤差でゼロにならないときあり
function TForm1.triangular_matrix(var z : integer): extended;
var
  i, j, k, m, q: integer;
  buf, det: extended;
  c : array of array of extended;
  p : array of integer;
begin
  setlength(c, Nl, Nl);
  setlength(p, Nl);
  for i := 0 to Nl - 1 do begin
    p[i] := i;
    for j := 0 to Nl - 1 do
      c[i, j] := matA[i, j];
  end;
  z := 0;
  det := 1;
  // 三角行列の作成
  for i := 0 to Nl -1 do
    for j := 0 to Nl -1 do
      if i < J then begin
        if c[i, i] = 0 then begin
          for k := i + 1 to Nl - 1 do
            if c[k, i] <> 0 then begin
              det := -det;
              for m := 0 to Nl - 1 do begin
                buf := c[i,  m];
                c[i, m] := c[k, m];
                c[k, m] := buf;
              end;
              q := p[i];
              p[i] := p[k];
              p[k] := q;
            end;
        end
        else begin
          buf := c[j, i] / c[i, i];
          for k := 0 to Nl - 1 do c[j, k] := c[j, k] - c[i, k] * buf;
        end;
      end;

  // 対角行列の積
  for i := 0 to Nl - 1 do begin
    det := det * c[i, i];
    if c[i, i] = 0 then z := p[i] + 1;
  end;
  result := det;

  memo1.Lines.Append('行列式の値 ' + floattostr(det));
end;

// 全行No組み合わせ配列作成
procedure TForm1.permutation;
var
  v : array of integer;
  i, n : integer;

  procedure swap(var pa, pb : integer);
  var
    tmp : integer;
  begin
    tmp := pa;
    pa := pb;
    pb := tmp;
  end;

  procedure reverse(first, last: integer);
  var
    lam : integer;
  begin
    lam := last;
    dec(lam);
    while (first <> last) and (first <> lam) do begin
      swap(v[first], v[lam]);
      inc(first);
      last := lam;
      dec(lam);
    end;
  end;

  function next_permutation(first, last: integer): boolean;
  var
    i, j, k : integer;
  begin
    if first = last then begin
      result := false;
      exit;
    end;
    if first + 1 = last then begin
      result := false;
      exit;
    end;
    i := last - 1;
    while true do begin
      j := i;
      dec(i);
      if v[i] < v[j] then begin
        k := last;
        dec(k);
        while not(v[i] < v[k]) do dec(k);
        swap(v[i], v[k]);
        reverse(j,  last);
        result := true;
        exit;
      end;
      if i = first then begin
        reverse(j,  last);
        result := false;
        exit;
      end;
    end;
  end;

begin
  setlength(v, Nl);
  for i := 0 to Nl -1 do v[i] := i;
  n := 0;
  repeat
    for i := 0 to Nl - 1 do perNo[n, i] := v[i];
    inc(n);
  until not next_permutation(0, Nl);
end;


// LU分解による連立方程式の解法
function TForm1.LUecomposition: byte;
const
  sp = ' ';
var
  i, j, k, n : 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);


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

  // 編集用をバックアップににコピー
  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の対角にセット
    // 対角がゼロだったら終了
    if a[0, 0] = 0 then begin
      break;
    end;
    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;
    // 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;
    memo1.Lines.Append(LL + spl + floattostr(y[rowNo[k]]));
  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;
  LL : string;
begin
  setlength(matA, Nl, Nl);
  setlength(matL, Nl, Nl);
  setlength(b, Nl);
  setlength(ba, Nl);
  setlength(lu , Nl, Nl);
  setlength(matU, Nl, Nl);
  setlength(rowNo, 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;

  memo1.Clear;

  // 対角にゼロが有ったら終了
  if triangular_matrix(k) = 0 then begin
    MessageDlg('配列Aのaにゼロがあるか'
                + #13#10 + 'LU分解時ゼロが発生します。', mtInformation,[mbOk], 0);
    exit;
  end;


  k := 2;
  for i := 3 to NL do k := k * i;             // 行組み合わせ総数
  setlength(perNo, k, Nl);                    // 全行組み合わせ配列確保
  permutation;                                // 全行組み合わせ作成

  for i := 0 to Nl - 1 do rowNo[i] := i;      // 計算準初期化

  ch := k;                                    // 最大ループ数

  // Lu分解連立方程式の解法  解法できない場合あり
  exchange := 0;
  j := 0;   // 解法できた場合 0  出来ない場合 1  対角値0の場合 2
  // Lu分解が正常に終了するまで繰り返す
  // k = 0 は初期設定値なので1~
  for k := 1 to ch do begin
    j := LUecomposition;                      // LU分解
    if j = 1 then begin                      // 対角にゼロが発生
      rowmatinc(k);                           // 行の移動
      inc(exchange);
    end;
    if j = 0 then break;                      // 解法で終了
  end;
  // 解法出来なかったら
  if diagonal < Nl then
    MessageDlg('LU分解中に対角ゼロが発生しました、入力値を見直して下さい。', mtInformation,[mbOk], 0);
  memo1.Lines.Append('再計算 Row= ' + intTostr(exchange));
  if j = 1 then begin
    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);
  end;
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);
begin
  testset;
end;

end.

download LU_test.zip

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