作業中のメモ

よく「計算機」を使って作業をする.知らなかったことを中心にまとめるつもり.

ガウスの消去法(Fortran 自由形式)

Fortran90 の自由形式に関して

どうも,筆者です.何度同じ記事書くんだ?って感じだが,どうも自分が扱っていた Fortran の記述方法は古いらしい. 無料のコンパイラがあることを含めると,Fortran 90/95 の自由形式(free format)が主流らしい.Fortran 2003/2008 とかもあるらしい.

という事で,Fortran 90/95 の自由形式で記述する練習を兼ねてプログラムを書き直した.また,配列の使い方も何となく理解してきた.もっとも,同じプログラムを違う形式で書き換えているだけなので,動作は同じである.

プログラム

まず,ガウスの消去法を行う subroutine を示す.モジュールなるものを使ってみたりした.

! ====================
! Gaussian elimination
! ====================
!23456789|123456789|123456789|123456789|123456789|123456789|123456789|123456789|123456789|123456789
!        1         2         3         4         5         6         7         8         9
module mdlGaussElim
  implicit none
  private
  public :: GaussianElimination
  real(8), parameter :: eps_default = real(1e-10, 8)

  contains

  ! =================================
  ! ガウスの消去法
  !
  ! A: n × n の係数行列,A(row, col)
  ! b: n 次元右辺ベクトル,b(row)
  ! =================================
  subroutine GaussianElimination(A, b, mineps)
    ! 引数
    real(8), intent(inout) :: A(:, :), b(:)
    real(8), intent(in), optional :: mineps

    ! 局所変数
    integer(4) :: n, i, id, pivot
    real(8) :: val, diag, weight, use_eps
    n = size(A, 1)

    ! 許容誤差の確認
    use_eps = eps_default
    if (present(mineps)) then
      use_eps = mineps
    end if

    if (n /= size(A, 2)) then
      write(*, '("A is not square matrix")')
      return
    end if

    ! ================
    ! 部分ピボット選択
    ! ================
    do i = 1, n - 1
      id = maxloc(abs(A(i:n, i)), 1)

      ! 対角要素が最大でない場合
      if (id /= 1) then
        pivot = id + i - 1

        ! 入れ替えを行う
        do id = 1, n
          val = A(i, id)
          A(i, id) = A(pivot, id)
          A(pivot, id) = val
        end do
        val = b(i)
        b(i) = b(pivot)
        b(pivot) = val
      end if

      ! ========
      ! 前進消去
      ! ========
      diag = A(i, i)

      if (abs(diag) < use_eps) then
        write(*, '("Error: diagonal component is less than", G15.7)') use_eps
        return
      end if

      do id = i + 1, n
        weight = A(id, i) / diag
        A(id, i:n) = A(id, i:n) - weight * A(i, i:n)
        b(id) = b(id) - weight * b(i)
      end do
    end do

    ! ========
    ! 後退代入
    ! ========
    b(n) = b(n) / A(n, n)
    do i = n - 1, 1, -1
      id = i + 1
      val = b(i) - sum(A(i, id:n) * b(id:n))
      b(i) = val / A(i, i)
    end do

    return
  end subroutine GaussianElimination
end module mdlGaussElim

次に,main routine を示す.

! ============
! main routine
! ============
!23456789|123456789|123456789|123456789|123456789|123456789|123456789|123456789|123456789|123456789
!        1         2         3         4         5         6         7         8         9

program main
  use mdlGaussElim
  implicit none

  character(32) :: fmtstr, fname
  integer(4) :: n, i, ioerr
  real(8), allocatable :: A(:, :), b(:)

  ! ======================
  ! === 引数のチェック ===
  ! ======================
  if (iargc() /= 1) then
    call getarg(0, fname)
    write(*, '("Usage: ", a, "[data file name]")') fname
    stop
  else
    ! ======================
    ! === 引数の読み込み ===
    ! ======================
    call getarg(1, fname)
  end if

  ! ========================
  ! === ファイルオープン ===
  ! ========================
  open(10, file = fname, status = 'old', iostat = ioerr)

  if (ioerr /= 0) then
    write(*, '("Error: ", a, " cannot open")') fname
    stop
  end if

  ! ====================================
  ! === ファイルからデータを読み込む ===
  ! ====================================
  read(10, *) n

  allocate(A(1:n, 1:n), b(1:n))
  write(fmtstr, '("(", i0, "(E20.13), 3X, E20.13)")') n

  do i = 1, n
    read(10, *, iostat = ioerr) A(i, 1:n), b(i)

    if (ioerr /= 0) exit
  end do
  close(unit = 10)

  ! ============================
  ! === ガウスの消去法を行う ===
  ! ============================
  call GaussianElimination(A, b)

  ! ============
  ! === 出力 ===
  ! ============
  write(*, '(E20.13)') (b(i), i = 1, n)

  deallocate(A, b)

  stop
end program main

そして,最後に Makefile である.

FC := gfortran
FFLAG := -O2
PROG := gaussSolver
SRCS := gaussianElimination.f90 mainRoutine.f90

all: $(PROG)

$(PROG): $(SRCS)
  $(FC) -o $@ $(FFLAG) $^

.PHONY: clean
clean:
  rm -f $(PROG) *.mod

ファイル読み込みは,以前と同様で,最初に次元数,次に係数行列,右辺ベクトルという並びのものを用意する.