Skip to content

Fortran チートシート

科学技術計算と数値計算のパイオニア言語。

01

基本とプログラム構造

プログラム構造とHello World

すべてのFortranプログラムは'program NAME'で始まり、'end program NAME'で終わる。'implicit none'はモダンFortranで必須 — すべての変数の明示的な宣言を強制(これがないと、Fortranはi-nで始まる変数を整数、それ以外を実数とする暗黙の型付けを使用し、これはバグの大きな原因)。'contains'ブロックが実行可能コードを内部プロシージャ(プログラム内で定義されたサブルーチン/関数)から分離。コメントは'!'で始まる。自由ソース形式(Fortran 90+)は.f90拡張子を使用、列は関係ない。gfortran/ifortでコンパイル。

fortran
program hello
  ! A complete Fortran program structure
  implicit none
  ! declarations go here
  integer :: status = 0

  print *, "Hello, World!"   ! list-directed output to stdout

  ! executable statements
  call do_work(status)

  print *, "Exit status: ", status
contains
  subroutine do_work(st)
    integer, intent(out) :: st
    st = 0
    print *, "Working..."
  end subroutine do_work
end program hello

! Compile: gfortran hello.f90 -o hello
! Run:     ./hello

変数と組み込み型

Fortranには5つの組み込み型:integer、real、complex、character、logicalがある。'kind'が精度/サイズを選択 — 64ビットにはkind=8を使用(または移植性のためselected_real_kind/iso_fortran_envを使用)。実数リテラルにはkind接尾辞が必要:3.14_8(3.14だけではなく)。double precisionはreal(kind=8)のレガシー構文。複素数リテラルは(real, imag)形式。論理値は.true. / .false.(ドット付き)。文字列はlen=:とallocatableで宣言しない限り固定'len'を持つ(遅延長、Fortran 2003+)。暗黙の精度損失を避けるため常に一致するkind接尾辞で初期化。

fortran
program variables
  implicit none
  ! Integer types
  integer :: count = 0
  integer(kind=8) :: big = 9223372036854775807_8   ! 64-bit
  ! Real types
  real :: x = 3.14              ! default (often 32-bit)
  real(kind=8) :: y = 2.718281828459045_8   ! double precision
  double precision :: z = 1.0d0
  ! Complex
  complex :: c = (1.0, 2.0)    ! 1 + 2i
  complex(kind=8) :: cw = (1.0_8, 2.0_8)
  ! Character
  character(len=20) :: name = "Alice"
  character(len=:), allocatable :: flexible   ! deferred length
  ! Logical
  logical :: flag = .true.
  ! Print all
  print *, count, big
  print *, x, y, z
  print *, c, cw
  print *, name, flag
end program variables

定数とパラメータ

定数は'parameter'属性を使用し、宣言時に初期化必須。変更不可 — コンパイラが最適化とインライン化可能。慣習でSCREAMING_SNAKE_CASEを使用。character(*)は'初期化子から長さを取る'を意味(文字列定数に便利)。パラメータは配列サイズ、物理定数、列挙風整数コードに一般的に使用。Fortran 2003+には適切なENUM型もあるがパラメータ整数が慣用的な選択。パラメータは配列次元宣言や他の定数式コンテキストで使用可能。

fortran
program constants
  implicit none
  ! Named constants via 'parameter' attribute
  integer, parameter :: MAX_SIZE = 100
  real, parameter :: PI = 3.14159265
  real, parameter :: E = 2.718281828
  character(*), parameter :: APP_NAME = "MyApp"
  ! Using parameters
  real :: arr(MAX_SIZE)
  arr = 0.0
  print *, APP_NAME, " size=", MAX_SIZE
  print *, "Circumference: ", 2.0 * PI * 5.0

  ! Enum-like via parameter
  integer, parameter :: SUNDAY = 1, MONDAY = 2, TUESDAY = 3
  integer :: day = MONDAY
  print *, "Day code: ", day
end program constants

演算子と式

Fortran演算子:算術(+ - * / **)、**はべき乗(Fortranに固有)。整数除算はゼロ方向に切り捨て — 真の除算にはreal(a)/bを使用。2つの関係構文:モダン(< > == /= <= >=)とレガシー(.lt. .gt. .eq. .ne. .le. .ge.)。論理:.and. .or. .not. .eqv.(同値).neqv.(排他的論理和)。文字列連結は//を使用、trim()が後続スペースを削除(Fortranは固定長文字列をスペースでパディング)。mod vs modulo:modは切り捨て除算の符号に従い、moduloは床除算に従う — 負のオペランドで異なる。

fortran
program operators
  implicit none
  integer :: a = 17, b = 5
  real :: x = 2.0
  ! Arithmetic
  print *, a + b, a - b, a * b    ! 22 12 85
  print *, a / b                   ! 3 (integer division!)
  print *, real(a) / b             ! 3.4 (cast to real)
  print *, a ** 2                  ! 289 (exponentiation)
  print *, mod(a, b)               ! 2 (modulo)
  print *, modulo(a, b)            ! 2 (differs for negatives)
  ! Relational (both forms work)
  print *, a > b, a < b            ! T F
  print *, a .gt. b, a .lt. b      ! T F (old form)
  print *, a == b, a /= b          ! F T
  ! Logical
  print *, (a > 0) .and. (b > 0)   ! T
  print *, (a > 0) .or. (b < 0)    ! T
  print *, .not. (a > 0)           ! F
  print *, (a > 0) .eqv. (b > 0)   ! T (equivalence)
  ! String concatenation
  character(10) :: s1 = "Hello", s2 = "World"
  print *, trim(s1) // " " // trim(s2)   ! Hello World
end program operators

組み込み関数と数学

Fortranには豊富な組み込み(ビルトイン)関数がある。数学:abs、sqrt、exp、log(自然)、log10、sin/cos/tan/asin/acos/atan/atan2、sinh/cosh/tanh。丸め:int(切り捨て)、nint(最近接)、floor、ceiling。変換:real()、int()、cmplx()。問い合わせ:size、shape、huge(最大値)、tiny(最小正値)、kind。すべての三角関数はラジアンを取る。atan2(y, x)は正しい象限の角度を返す(atanと異なる)。範囲制限のチェックにhuge/tinyを使用。組み込み関数は要素的 — 配列上で自動的に要素ごとに動作。

fortran
program intrinsics
  implicit none
  real :: x = -3.7, y = 2.5
  ! Math functions
  print *, abs(x)         ! 3.7
  print *, sqrt(2.0)      ! 1.414...
  print *, exp(1.0)       ! 2.718... (e^x)
  print *, log(2.0)       ! 0.693... (natural log)
  print *, log10(1000.0)  ! 3.0
  print *, sin(3.14159/2) ! 1.0
  print *, cos(0.0)       ! 1.0
  print *, atan2(1.0,1.0) ! 0.785... (pi/4)
  ! Rounding
  print *, int(x)         ! -3 (truncate toward zero)
  print *, nint(x)        ! -4 (nearest integer)
  print *, floor(x)       ! -4 (toward -inf)
  print *, ceiling(x)     ! -3 (toward +inf)
  print *, abs(x), max(x, y), min(x, y)   ! 3.7 2.5 -3.7
  ! Type conversion
  print *, real(5)        ! 5.0
  print *, int(3.9)       ! 3
  ! Inquiry
  real :: arr(10)
  print *, size(arr)      ! 10
  print *, huge(1)        ! 2147483647
  print *, tiny(1.0)      ! smallest positive real
end program intrinsics
02

制御フロー

If...Then...Else

ブロックIF:'if (cond) then ... else if (cond) then ... else ... end if'。各分岐には'then'が必要(最後のelseを除く)。論理IFは1行:'if (cond) statement'('then'/'end if'なし)。条件は関係演算子(< > == /= <= >=または.lt. .gt. .eq. .ne. .le. .ge.)と.and. .or. .not.の組み合わせ。'stop'がプログラムを終了(オプションでメッセージ/コード付き)。算術IF(if (x) label1, label2, label3)はFortran 2018で削除 — 絶対に使用しない。宣言されていない変数をキャッチするため常に'implicit none'を使用。

fortran
program if_demo
  implicit none
  integer :: score = 85
  character(1) :: grade

  ! Multi-branch if/else if/else
  if (score >= 90) then
    grade = 'A'
  else if (score >= 80) then
    grade = 'B'
  else if (score >= 70) then
    grade = 'C'
  else if (score >= 60) then
    grade = 'D'
  else
    grade = 'F'
  end if

  print *, "Score ", score, " -> Grade ", grade

  ! Logical if (single statement, no 'then')
  if (score < 0 .or. score > 100) stop "Invalid score"

  ! Arithmetic if (OBSOLETE - avoid)
  ! if (x) 10, 20, 30   ! jump to label based on sign
end program if_demo

Select Case(Switch)

select caseはFortranのswitch文。ケースは単一値(case (3))、リスト(case (1, 3, 5))、または範囲(case (4:5)は4から5を含む)。case defaultがフォールバック。Cと異なり、フォールスルーなし — 各分岐は独立で1つのみ実行。整数、文字、論理型で動作(実数は不可)。文字範囲はASCII順序('A':'Z')。浮動小数点比較にはif/elseを使用。select caseは整数/文字ディスパッチに長いif/else ifチェーンより効率的(コンパイラがジャンプテーブルを使用可能)。

fortran
program case_demo
  implicit none
  integer :: day = 3
  character(1) :: ch = 'A'
  character(10) :: day_name

  ! Integer select case
  select case (day)
  case (1)
    day_name = "Monday"
  case (2)
    day_name = "Tuesday"
  case (3)
    day_name = "Wednesday"
  case (4:5)
    day_name = "Thu/Fri"
  case (6:7)
    day_name = "Weekend"
  case default
    day_name = "Invalid"
  end select
  print *, day_name

  ! Character select case (case-insensitive via pre-upper)
  select case (ch)
  case ('A':'Z')
    print *, "Uppercase letter"
  case ('a':'z')
    print *, "Lowercase letter"
  case ('0':'9')
    print *, "Digit"
  case default
    print *, "Other"
  end select

  ! Logical select case
  select case (day > 5)
  case (.true.)
    print *, "Weekend!"
  case (.false.)
    print *, "Weekday"
  end select
end program case_demo

Doループ(カウント)

カウントDOループ:'do var = start, end, step'(stepはデフォルト1)。ループはvar <= end(正のstep)またはvar >= end(負のstep)の間実行。varは各反復後にインクリメント。暗黙do構文[(expr, var=start,end)]は配列初期化とI/Oリストに強力。名前付きループ(outer: do ... end do outer)はcycle/exitを特定のネストレベルにターゲット可能。ループ変数は自動的に定義、Fortranではループ後に最終値を保持。ループ本体内でループ変数を変更しない。

fortran
program do_loops
  implicit none
  integer :: i, j, total

  ! Basic counted loop: do var = start, end [, step]
  do i = 1, 5
    print *, i          ! 1 2 3 4 5
  end do

  ! With step
  do i = 10, 1, -1      ! countdown
    print *, i
  end do

  do i = 0, 100, 25     ! 0 25 50 75 100
    print *, i
  end do

  ! Implied-do (inline, for array init / I/O)
  integer :: arr(5) = [(i**2, i=1,5)]   ! 1 4 9 16 25
  print *, arr
  print *, (i, i=1,3)                   ! 1 2 3

  ! Nested loops with labels (for cycle/exit targeting)
  total = 0
  outer: do i = 1, 3
    inner: do j = 1, 3
      total = total + i*j
    end do inner
  end do outer
  print *, "Total: ", total
end program do_loops

Do Whileと無限ループ

do while (cond) ... end doは事前テストループ(各反復前に条件チェック、ゼロ回実行の可能性あり)。事後テストビヘイビアにはdo ... if (cond) exit ... end doを使用。裸の'do ... end do'は無限ループ — exit文が必須(そうでなければ無限)。'exit'は最内ループ(または名前付きループ)を脱出。名前付きループ(factorial_loop:)でexitが外側ループをターゲット可能。反復回数が未知で条件に依存する場合はdo whileを、回数が事前に分かっている場合はカウントdoを使用。

fortran
program while_demo
  implicit none
  integer :: n, count
  real :: x, sum

  ! do while: pre-test loop
  n = 1024
  count = 0
  do while (n > 1)
    n = n / 2
    count = count + 1
  end do
  print *, "log2(1024) = ", count   ! 10

  ! Infinite loop with exit
  sum = 0.0
  do
    read(*, *) x
    if (x < 0) exit          ! leave loop
    sum = sum + x
  end do
  print *, "Sum: ", sum

  ! do ... end do with conditional exit
  n = 1
  factorial_loop: do
    if (n > 10) exit factorial_loop
    print *, n, factorial(n)
    n = n + 1
  end do factorial_loop

contains
  recursive function factorial(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n <= 1) then
      f = 1
    else
      f = n * factorial(n-1)
    end if
  end function factorial
end program while_demo

Cycle、Exitとループ制御

cycleは現在の反復の残りをスキップし次へジャンプ(C/Pythonの'continue'のようなもの)。exitはループから完全に脱出('break'のようなもの)。デフォルトで最内ループをターゲットするが、名前付きループ(search: do ... end do search)で外側ループをターゲット可能:'exit search'または'cycle search'。これはネストしたループをクリーンに脱出するのに不可欠。フィルタリング(不要な反復をスキップ)にcycle、早期終了(検索発見、エラー検出)にexitを使用。名前付きループでネストした制御フローを明示的で読みやすく。

fortran
program loop_control
  implicit none
  integer :: i, j

  ! cycle: skip to next iteration (like 'continue' in C)
  do i = 1, 10
    if (mod(i, 2) == 0) cycle   ! skip even numbers
    print *, i                  ! 1 3 5 7 9
  end do

  ! exit: break out of loop (like 'break' in C)
  do i = 1, 100
    if (i * i > 50) then
      print *, "Stopped at i=", i
      exit
    end if
  end do

  ! Named loops: cycle/exit can target outer loops
  search: do i = 1, 5
    do j = 1, 5
      if (i + j == 7) then
        print *, "Found: ", i, "+", j, "= 7"
        exit search             ! break out of OUTER loop
      end if
    end do
  end do search

  ! Early exit from a search
  integer :: arr(10) = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
  do i = 1, size(arr)
    if (arr(i) == 9) then
      print *, "Found 9 at index ", i
      exit
    end if
  end do
end program loop_control
03

配列とベクトル操作

配列宣言と初期化

Fortran配列はデフォルトで1インデックス(下限 = 1)だが、カスタム下限を指定可能:a(0:4)はインデックス0..4を持つ。多次元配列は(rows, cols)順序 — 列優先ストレージ(最初のインデックスがメモリ内で最も速く変化)。配列コンストラクタ[1,2,3]または暗黙do[(expr, i=start,end)]で初期化。reshapeが1Dリストから多次元配列を埋める。size()が総要素数を返す、lbound/uboundが下限/上限を返す。shape()が形状を1D配列として返す。配列は'全体配列' — 明示的ループなしで代入と操作が可能。

fortran
program array_decl
  implicit none
  ! Declaration with dimension
  integer :: a(5)                  ! 1D, indices 1..5
  integer :: b(0:4)                ! 1D, indices 0..4 (custom lower bound)
  real :: c(3, 4)                  ! 2D, 3 rows x 4 cols
  real, dimension(10) :: d         ! using dimension attribute

  ! Initialization at declaration
  integer :: x(5) = [1, 2, 3, 4, 5]
  integer :: y(5) = [(i*2, i=1,5)] ! implied-do: 2 4 6 8 10
  integer :: z(5) = 0              ! all zeros
  real :: m(2,2) = reshape([1,2,3,4], [2,2])

  ! Allocation later
  print *, size(x), lbound(x), ubound(x)   ! 5 1 5
  print *, size(c, dim=1)                  ! 3 (rows)
  print *, size(c, dim=2)                  ! 4 (cols)

  ! Array of characters
  character(10) :: names(3) = ["Alice", "Bob", "Carol"]
  print *, names(2)                        ! Bob
end program array_decl

配列セクションとベクトル添字

配列セクション(スライシング)はa(start:end:stride)構文を使用 — すべての部分はオプション。strideは負も可能(逆順)。ベクトル添字でインデックス配列による収集/分散が可能:a(idx)は[a(idx(1)), a(idx(2)), ...]を返す。セクションは代入可能:a(2:4) = [99,98,97]。'where'構文は配列レベルの条件付き代入(numpy whereのようなもの)。Fortranの配列操作はベクトル化 — 要素ごとの操作に明示的ループ不要。これがFortranの数値コードのキラーフィーチャー:クリーンで数学的な構文をコンパイラが自動ベクトル化。

fortran
program array_sections
  implicit none
  integer :: a(10) = [(i, i=1,10)]
  integer :: b(5)
  integer :: idx(3) = [2, 5, 7]

  ! Array sections (slicing): a(start:end[:stride])
  print *, a(3:7)         ! 3 4 5 6 7
  print *, a(1:10:2)      ! 1 3 5 7 9 (stride 2)
  print *, a(:5)          ! 1 2 3 4 5 (start defaults to 1)
  print *, a(6:)          ! 6 7 8 9 10 (end defaults to ubound)
  print *, a(::3)         ! 1 4 7 10
  print *, a(10:1:-1)     ! 10 9 8 ... 1 (reverse)

  ! Vector subscript (gather)
  b = a(idx)              ! b = [a(2), a(5), a(7)] = [2, 5, 7]
  print *, b

  ! Assigning to a section
  a(2:4) = [99, 98, 97]
  print *, a(1:5)         ! 1 99 98 97 5

  ! 2D sections
  real :: m(4,4) = 0.0
  m(2:3, 2:3) = 1.0       ! set 2x2 sub-block
  print *, m(2,2), m(3,3) ! 1.0 1.0

  ! Where construct (masked array assignment)
  where (a > 50) a = 0    ! set elements > 50 to 0
end program array_sections

多次元配列と行列

Fortranは配列を列優先で保存(最初のインデックスがメモリ内で最も速く変化)— Cと逆。reshapeは列優先順序で埋めるので、reshape([1,2,3,4],[2,2])は[[1,3],[2,4]]を与える。matmul(A,B)は真の行列乗算(線形代数)、A*Bは要素ごと(アダマール)— 同じではない!transpose(A)が転置を返す。還元:sum、product、maxval、minval、maxloc、minloc、count — すべてdim=で1軸に沿って還元をサポート。高性能には、キャッシュフレンドリーにするため列優先順序でループを書く(最初のインデックスで最内ループ)。

fortran
program matrices
  implicit none
  real :: A(3,3), B(3,3), C(3,3)
  integer :: i, j

  ! Initialize with implied-do
  A = reshape([(real(i), i=1,9)], [3,3])
  ! A = 1 4 7
  !     2 5 8
  !     3 6 9   (column-major fill!)

  ! Identity matrix
  B = 0.0
  do i = 1, 3
    B(i,i) = 1.0
  end do

  ! Matrix multiplication (intrinsic)
  C = matmul(A, B)        ! A * I = A
  print *, C(1,1), C(2,2) ! 1.0 5.0

  ! Transpose
  print *, transpose(A)(1,:)   ! 1 2 3 (first row of A^T)

  ! Element-wise operations
  C = A + B               ! element-wise add
  C = A * 2.0             ! scalar multiply
  C = A * B               ! element-wise (NOT matmul!)

  ! Array reduction along a dimension
  print *, sum(A, dim=1)   ! column sums: 6 15 24
  print *, sum(A, dim=2)   ! row sums: 12 15 18
  print *, maxval(A)       ! 9.0
  print *, maxloc(A)       ! 3 3 (location of max)

  ! Reshape
  integer :: flat(6) = [1,2,3,4,5,6]
  integer :: mat(2,3)
  mat = reshape(flat, [2,3])
end program matrices

割り付け可能配列(動的)

allocatable配列はFortranのモダンな動的メモリの方法 — ポインタより安全(メモリリークなし、スコープ終了時に自動解放)。allocatable属性と遅延形状(:、(:,:)など)で宣言。allocate()にstat=でエラーをキャッチ(常にチェック!)。deallocate()で明示的に解放。Fortran 2003+は代入時の自動再割り当てをサポート:flex = [flex, 4]が配列を拡大。allocated()が現在割り付けされているかチェック。割り付け可能配列はコンパイラが追跡して自動解放するためポインタより推奨 — リークなし、ダングリングポインタなし。

fortran
program alloc_demo
  implicit none
  integer, allocatable :: arr(:), matrix(:,:)
  integer :: n, m, i, stat

  ! Get size from user
  print *, "Enter size:"
  read(*, *) n
  m = n * 2

  ! Allocate
  allocate(arr(n), matrix(n, m), stat=stat)
  if (stat /= 0) then
    print *, "Allocation failed!"
    stop 1
  end if

  ! Use the arrays
  arr = [(i, i=1, n)]
  matrix = 0.0
  do i = 1, n
    matrix(i, :) = i
  end do

  print *, size(arr), size(matrix, dim=2)
  print *, allocated(arr)   ! T

  ! Deallocate (or let it auto-deallocate at scope exit)
  deallocate(arr, matrix)
  print *, allocated(arr)   ! F

  ! Automatic reallocation on assignment (Fortran 2003)
  integer, allocatable :: flex(:)
  flex = [1, 2, 3]          ! auto-allocates to size 3
  flex = [flex, 4, 5]       ! reallocates to size 5: 1 2 3 4 5
  print *, flex
  deallocate(flex)
end program alloc_demo

配列組み込み関数

Fortranの配列組み込み関数はその超能力。還元:sum、product、maxval、minval、maxloc(最大のインデックス)、minloc、count(trueのカウント)、any(存在)、all(すべて)。すべてmask=で条件付き還元をサポート。pack()がmaskがtrueの要素を収集(numpy compressのようなもの)、unpack()が分散。cshift/eoshiftが配列を回転(循環 vs エンドオフ)。merge(a, b, mask)が要素ごとの選択。注意:Fortranには組み込みのソートなし — 自分で書く必要がある(またはライブラリを使用)。これらの組み込み関数は要素的でベクトル化可能、Fortranコードをクリーンかつ高速にする。

fortran
program array_funcs
  implicit none
  integer :: a(5) = [3, 1, 4, 1, 5, 9, 2, 6]
  ! Wait, let's fix size
  integer :: b(8) = [3, 1, 4, 1, 5, 9, 2, 6]
  integer :: c(5) = [10, 20, 30, 40, 50]
  logical :: mask(5) = [.true., .false., .true., .false., .true.]

  ! Inquiry
  print *, size(b)        ! 8
  print *, shape(b)       ! 8
  print *, lbound(b), ubound(b)   ! 1 8

  ! Reductions
  print *, sum(b)         ! 31
  print *, product(c)     ! 120000000
  print *, maxval(b), minval(b)   ! 9 1
  print *, maxloc(b)      ! 6 (index of max)
  print *, minloc(b)      ! 2 (index of min, first occurrence)
  print *, count(b > 3)   ! 5 (number of true elements)
  print *, any(b > 8)     ! T (at least one)
  print *, all(b > 0)     ! T (all of them)

  ! With mask
  print *, sum(b, mask=b > 3)     ! sum of elements > 3
  print *, pack(b, b > 3)         ! compact array of elements > 3
  print *, unpack([1,2], mask, 0) ! spread values per mask

  ! Manipulation
  print *, cshift(b, 2)   ! circular shift left by 2
  print *, eoshift(b, 2)  ! end-off shift left by 2 (fills 0)
  print *, merge(b, c, mask)  ! element-wise: b where mask true, else c

  ! Sorting (Fortran 2003+)
  integer :: sorted(8)
  sorted = b
  call sort_array(sorted)   ! custom sort (no built-in sort)
end program array_funcs
04

文字列と文字処理

文字宣言と長さ

Fortran文字列はデフォルトで固定長 — より短い文字列は宣言された長さまでスペースでパディング。character(N)またはcharacter(len=N)が長さNを宣言。character(*)はコンテキストから長さを取る(パラメータ初期化子またはダミー引数)。character(:), allocatableが遅延長動的文字列を有効化(Fortran 2003+)— 文字列は代入時に再割り当て。len()が宣言された長さを返す、len_trim()が後続スペースなしの長さを返す。trim()が後続スペースなしの文字列を返す(ただし結果はコンテキスト内でまだ固定長)。可変長テキスト処理には割り付け可能な遅延長文字列を使用。

fortran
program char_decl
  implicit none
  ! Fixed-length strings
  character(10) :: s1 = "Hello"
  character(len=20) :: s2 = "World"
  character(20) :: s3          ! len= keyword optional

  ! Deferred-length (allocatable) - Fortran 2003+
  character(:), allocatable :: flex
  flex = "Dynamic"             ! len=7
  flex = "Now longer string"   ! reallocates to len=18
  print *, len(flex)           ! 18

  ! Array of strings
  character(15) :: names(3) = ["Alice", "Bob", "Carol"]

  ! Single character
  character(1) :: ch = 'A'
  character :: ch2 = 'B'       ! len=1 default

  print *, s1                  ! "Hello     " (padded to 10)
  print *, trim(s1)            ! "Hello" (no padding)
  print *, len(s1), len_trim(s1)   ! 10 5
  print *, names(2)            ! "Bob" (padded)
end program char_decl

文字列連結と操作

文字列連結は//演算子を使用。repeat(s, n)が文字列をn回繰り返す。部分文字列はs(start:end)を使用 — 1インデックス、両端を含む(Pythonと異なる)。s(8:)は位置8から終わりまで、s(:5)は開始から位置5までを意味。index(s, sub)がsubの最初の出現位置を返す(見つからない場合は0、大文字小文字区別)。scan(s, set)がセット内の最初の文字の位置を返す、verify(s, set)がセット内にない最初の文字を返す。adjustl/adjustrが先頭/後続スペースをシフト。Fortran文字列はCのようにnull終端ではない — 長さは別途追跡。

fortran
program string_ops
  implicit none
  character(20) :: first = "John", last = "Doe"
  character(50) :: full

  ! Concatenation with //
  full = first // " " // last   ! "John Doe"
  print *, trim(full)

  ! Repeat
  print *, repeat("-", 30)      ! 30 dashes

  ! Substring (1-indexed, inclusive)
  character(20) :: s = "Hello, World!"
  print *, s(1:5)               ! "Hello"
  print *, s(8:12)              ! "World"
  print *, s(8:)                ! "World!"  (to end)
  print *, s(:5)                ! "Hello"   (from start)

  ! Index (find substring)
  print *, index(s, "World")    ! 8 (position, 0 if not found)
  print *, index(s, "world")    ! 0 (case-sensitive)
  print *, scan(s, "aeiou")     ! 2 (first vowel position)
  print *, verify(s, "abcdefg") ! 1 (first char NOT in set)

  ! Length
  print *, len_trim(s)          ! 13
  print *, adjustl(s)           ! left-justify
  print *, adjustr(s)           ! right-justify
end program string_ops

組み込み文字列関数

iachar(c)が文字のASCIIコードを返す、achar(i)が逆。(ichar/charはプロセッサ依存 — 移植性のためiachar/acharを推奨)。Fortranには組み込みのケース変換なし — iachar/acharで手動で行う(A-Zは65-90、a-zは97-122、差は32)。辞書式比較:lge/lgt/lle/llt(辞書的に大/小)が異なる長さの文字列を優雅に処理。内部I/O(ファイル代わりに文字列へ読み書き)が文字列と数値間の変換の慣用的な方法:read(str, *) numとwrite(str, fmt) num。I0フォーマットが最小幅整数(パディングなし)を与える。

fortran
program string_funcs
  implicit none
  character(20) :: s = "Hello World"
  integer :: i
  character(1) :: ch

  ! Character code conversion
  print *, iachar('A')          ! 65 (ASCII code)
  print *, achar(66)            ! 'B' (from ASCII code)
  print *, ichar('A')           ! processor-dependent (use iachar for ASCII)

  ! Case conversion (manual - no built-in)
  do i = 1, len_trim(s)
    ch = s(i:i)
    if (ch >= 'A' .and. ch <= 'Z') then
      s(i:i) = achar(iachar(ch) + 32)   ! to lowercase
    end if
  end do
  print *, s                    ! "hello world"

  ! Comparison
  print *, lge("apple", "banana")   ! F (lexicographic >=)
  print *, lgt("zebra", "apple")    ! T
  print *, lle("abc", "abcd")       ! T (<=)
  print *, llt("abc", "abd")        ! T (<)

  ! String to number conversion (internal read)
  character(20) :: num_str = "3.14159"
  real :: pi_val
  read(num_str, *) pi_val
  print *, pi_val * 2           ! 6.28318

  ! Number to string (internal write)
  character(20) :: out_str
  integer :: n = 42
  write(out_str, '(I0)') n      ! I0 = minimal-width integer
  print *, "Number: " // trim(out_str)
end program string_funcs

文字列フォーマット

フォーマット指定は文字列内にある:'(I5, F8.2, A)'。I=整数、F=固定小数点実数、E=指数、ES=科学(仮数1-10)、A=文字、X=スペース、/=改行。幅が最初(I5 = 幅5)、次にオプションの.mで最小桁(I5.3)。I0は最小幅(パディングなし)を意味。ESが適切な科学表記(1.23E+6)を与える、E(0.12E+7)と対照的。繰り返し:3I4 = 3つの整数それぞれ幅4。フォーマット内の文字列リテラル:'("text")'。フォーマットは文字列変数、*(リスト指向、コンパイラが選択)、またはラベル(文番号)のいずれか。クリーンな出力には整数にI0、実数にFまたはESを推奨。

fortran
program format_demo
  implicit none
  integer :: n = 42
  real :: pi = 3.14159265
  real :: big = 1234567.89
  character(20) :: name = "Alice"

  ! Format specifiers
  ! Iw     - integer, width w
  ! Iw.m   - integer, width w, at least m digits
  ! Fw.d   - fixed-point real, width w, d decimals
  ! Ew.d   - exponential, width w, d decimals
  ! Aw     - character, width w
  ! A      - character, default width
  ! nX     - n spaces
  ! /      - newline

  write(*, '(I5)') n            ! "   42"
  write(*, '(I5.3)') n          ! "  042"
  write(*, '(I0)') n            ! "42" (minimal)
  write(*, '(F10.4)') pi        ! "    3.1416"
  write(*, '(E12.4)') big       ! " 0.1235E+07"
  write(*, '(ES12.4)') big      ! " 1.2346E+06" (scientific)
  write(*, '(A10)') name        ! "     Alice"
  write(*, '(A, I3)') "n=", n   ! "n= 42"

  ! Multiple items
  write(*, '(A, I3, A, F8.4)') "n=", n, " pi=", pi

  ! Repeated format: 3I4 = three integers width 4
  write(*, '(3I4)') 1, 2, 3     ! "   1   2   3"

  ! Newline and spacing
  write(*, '("Name: ", A, /, "Age: ", I3)') name, n

  ! List-directed (default format)
  print *, name, n, pi
end program format_demo

解析とトークン化

Fortranには組み込みのsplit/tokenizeなし — index()と部分文字列で手動で書く必要がある。パターン:indexで区切り文字を見つけ、部分文字列でトークンを抽出、区切り文字を越えて進め、繰り返す。key=valueペアには、indexで'='を見つけ、キー(前)と値(後)に分割。trim()が後続スペースを削除、adjustl()が先頭スペースを削除。堅牢な解析には、空のトークンと空白も処理。あるいは、構造化データにフォーマット指定子付きの内部読み取りを使用、または文字列をファイルのように読み取り。split()のようなライブラリは一部のFortranフレームワークに存在するが標準ではない。

fortran
program parse_demo
  implicit none
  character(100) :: line = "name=Bob,age=30,city=NYC"
  character(50) :: token
  integer :: pos, start, end

  ! Split by comma
  start = 1
  do
    end = index(line(start:), ",")
    if (end == 0) then
      ! last token
      token = line(start:)
      call process(trim(token))
      exit
    end if
    token = line(start:start+end-2)
    call process(trim(token))
    start = start + end
  end do

contains
  subroutine process(t)
    character(*), intent(in) :: t
    integer :: eq_pos
    eq_pos = index(t, "=")
    if (eq_pos > 0) then
      print *, "Key: ", trim(t(:eq_pos-1)), &
               " Value: ", trim(t(eq_pos+1:))
    end if
  end subroutine process
end program parse_demo

! Output:
! Key: name Value: Bob
! Key: age Value: 30
! Key: city Value: NYC
05

プロシージャ:関数とサブルーチン

関数

関数は値を返し式で使用される(数学関数のようなもの)。モダン構文:'function name(args) result(var)' — result変数が返されるもの。intent(in)が読み取り専用引数をマーク(コンパイラが強制)。関数はスカラーまたは配列を返せる(出力のサイズにsize()を使用)。関数はPURE(副作用なし)であるべき — 関数内でグローバル状態を変更したりI/Oを行わない。内部プロシージャ('contains'ブロック内)はホストの変数にアクセス可能(ホスト結合)。外部プロシージャには、シグネチャを指定するinterfaceブロックを使用。

fortran
program func_demo
  implicit none
  ! Function declared in interface or as external
  print *, add(3, 4)         ! 7
  print *, square(5)         ! 25
  print *, distance(0.0, 0.0, 3.0, 4.0)   ! 5.0

contains
  ! Basic function with result clause
  function add(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c
    c = a + b
  end function add

  ! Function with result named same as function (legacy)
  function square(x) result(y)
    integer, intent(in) :: x
    integer :: y
    y = x * x
  end function square

  ! Real function
  function distance(x1, y1, x2, y2) result(d)
    real, intent(in) :: x1, y1, x2, y2
    real :: d
    d = sqrt((x2-x1)**2 + (y2-y1)**2)
  end function distance

  ! Array-valued function
  function reverse(arr) result(rev)
    integer, intent(in) :: arr(:)
    integer :: rev(size(arr))
    integer :: i, n
    n = size(arr)
    do i = 1, n
      rev(i) = arr(n-i+1)
    end do
  end function reverse
end program func_demo

サブルーチンとIntent

サブルーチンは'call'で呼び出され値を返さない — 引数をインプレースで変更。サブルーチンを使用する場合:(1) 複数の引数を変更必要、(2) 操作が'計算'ではなく'コマンド'、(3) 配列値の結果を返すのが厄介。intent属性が引数の方向を文書化し強制:intent(in) = 読み取り専用(代入するとコンパイルエラー)、intent(out) = 書き込み専用(入場時未定義、戻り前に設定必要)、intent(inout) = 読み書き。常にintentを指定 — バグをキャッチし最適化を可能に。サブルーチンは渡された配列を変更可能(連続していればコピーなし)。

fortran
program sub_demo
  implicit none
  integer :: x = 10, y = 20
  real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]

  ! Subroutines are called with 'call'
  call swap(x, y)
  print *, x, y              ! 20 10

  call scale_array(arr, 2.0)
  print *, arr               ! 2 4 6 8 10

  call fill_zero(arr)
  print *, arr               ! 0 0 0 0 0

contains
  subroutine swap(a, b)
    integer, intent(inout) :: a, b   ! read AND write
    integer :: tmp
    tmp = a
    a = b
    b = tmp
  end subroutine swap

  subroutine scale_array(a, factor)
    real, intent(inout) :: a(:)
    real, intent(in) :: factor
    a = a * factor           ! whole-array operation
  end subroutine scale_array

  subroutine fill_zero(a)
    real, intent(out) :: a(:)        ! write-only (output)
    a = 0.0
  end subroutine fill_zero
end program sub_demo

PureとElemental関数

pure関数は副作用なし:I/Oなし、グローバル変数の変更なし、stopなし、他のpureプロシージャのみ呼び出し可能。コンパイラ最適化(並列化、共通部分式除去)を可能にし、一部のコンテキスト(DO CONCURRENTなど)で必須。elemental関数はスカラー用に書かれるが配列上で自動的に要素ごとに動作 — 一度書き、両方に使用。pure elementalが両方を組み合わせ。真に数学関数(副作用なし)の関数にはpureを使用。操作が配列に要素ごとに自然に適用される場合(数学関数、変換)にはelementalを使用。コンパイラは配列上のelemental呼び出しを自動ベクトル化可能。

fortran
program pure_demo
  implicit none
  real :: a(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
  real :: b(5)
  integer :: i

  ! Pure function: no side effects, no I/O
  b = square_arr(a)
  print *, b                 ! 1 4 9 16 25

  ! Elemental function: works on scalars AND arrays automatically
  b = cube(a)                ! applies cube() element-wise
  print *, b                 ! 1 8 27 64 125

  print *, cube(2.0)         ! also works on scalar: 8.0

contains
  ! Pure: no side effects, no I/O, no stop, only pure calls
  pure function square_arr(x) result(y)
    real, intent(in) :: x(:)
    real :: y(size(x))
    y = x * x
  end function square_arr

  ! Elemental: scalar signature, but works on arrays too
  elemental function cube(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x * x
  end function cube

  ! Pure elemental: both
  pure elemental double precision function sq(x) result(y)
    double precision, intent(in) :: x
    double precision :: y
    y = x * x
  end function sq
end program pure_demo

オプション引数とキーワード引数

optional引数で呼び出し側が省略可能。プロシージャ内でpresent(arg)を使用して引数が渡されたかチェック — 存在しないoptionalへのアクセスは未定義動作。キーワード引数(name="value")で任意の順序で引数を渡せ、呼び出しを自己文書化。キーワードを使用すると、後続のすべての引数もキーワードを使用必要。optional引数はシグネチャですべての必須引数の後に配置必要。デフォルト値はpresent()チェックで実装(Fortranには組み込みのデフォルト構文なし)。キーワード + optionalで柔軟なAPIを実現:呼び出し側は必要なもののみ指定。

fortran
program optional_demo
  implicit none
  ! All arguments after the first optional must also be optional
  print *, greet("Alice")                    ! Hello, Alice!
  print *, greet("Bob", "Hi")                ! Hi, Bob!
  print *, greet("Carol", greeting="Hey")    ! keyword argument
  print *, greet(greeting="Welcome", name="Dave")  ! all keywords

  ! With present() check
  call log_msg("Starting up")
  call log_msg("Error!", level=2)
  call log_msg("Debug info", level=0, file="debug.log")

contains
  function greet(name, greeting) result(msg)
    character(*), intent(in) :: name
    character(*), intent(in), optional :: greeting
    character(50) :: msg
    character(20) :: g

    if (present(greeting)) then
      g = greeting
    else
      g = "Hello"
    end if
    msg = trim(g) // ", " // name // "!"
  end function greet

  subroutine log_msg(message, level, file)
    character(*), intent(in) :: message
    integer, intent(in), optional :: level
    character(*), intent(in), optional :: file
    integer :: lvl
    lvl = 1
    if (present(level)) lvl = level
    print *, "[L", lvl, "] ", trim(message)
  end subroutine log_msg
end program optional_demo

内部プロシージャと再帰プロシージャ

内部プロシージャ('contains'内)はホスト結合を持つ — ホストプログラムの変数を読み取りおよび変更可能(クロージャのようなもの)。ホスト状態が必要なヘルパーに使用。再帰プロシージャは'recursive'接頭辞で宣言必要(Fortran 90/2003)、Fortran 2018は再帰をデフォルトに。相互再帰には、前方参照を宣言するinterfaceブロックを使用。再帰はエレガントだが遅い可能性(関数呼び出しオーバーヘッド)と危険(深い再帰でスタックオーバーフロー)。階乗/フィボナッチには反復版が高速で安全。自然に再帰的な問題(ツリートラバーサル、分割統治)には制限付き深さで再帰を使用。

fortran
program nested_demo
  implicit none
  integer :: counter = 0   ! host variable

  ! Internal procedures (in contains) access host variables
  call increment
  call increment
  print *, counter          ! 2

  ! Recursive function
  print *, factorial(5)     ! 120
  print *, fib(10)          ! 55

  ! Mutual recursion (needs forward declaration)
  print *, is_even(4)       ! T

contains
  subroutine increment
    counter = counter + 1   ! modifies host's counter
  end subroutine increment

  recursive function factorial(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n <= 1) then
      f = 1
    else
      f = n * factorial(n-1)
    end if
  end function factorial

  recursive function fib(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n < 2) then
      f = n
    else
      f = fib(n-1) + fib(n-2)
    end if
  end function fib

  ! Mutual recursion with interface
  recursive function is_even(n) result(r)
    integer, intent(in) :: n
    logical :: r
    interface
      recursive function is_odd(m) result(ro)
        integer, intent(in) :: m
        logical :: ro
      end function is_odd
    end interface
    if (n == 0) then
      r = .true.
    else
      r = is_odd(n-1)
    end if
  end function is_even
end program nested_demo
06

モジュールとカプセル化

モジュールの基本と使用

モジュールはFortranの主要なカプセル化メカニズム(common blocksと外部プロシージャを置き換え)。モジュールファイルは以下を含む:(1) 宣言(定数、変数、派生型)、(2) プロシージャを持つ'contains'ブロック。'use module_name'でインポート、'use module_name, only: x, y'は特定のエンティティのみインポート(推奨 — 名前空間汚染を回避)。モジュール変数は永続的(静的)でモジュールを使用するすべてのプロシージャで共有。モジュールは明示的インターフェースを提供(コンパイラが引数の型をチェック)、外部プロシージャと異なる。モジュールファイルを使用するファイルの前に常にコンパイル。モジュール内の'implicit none'はすべてのプロシージャに伝播。

fortran
! geometry.f90 - module file
module geometry
  implicit none
  private                      ! default: everything private
  public :: circle_area, circle_perimeter, PI

  ! Module-level constants (persistent)
  real, parameter :: PI = 3.14159265

contains
  function circle_area(r) result(a)
    real, intent(in) :: r
    real :: a
    a = PI * r * r
  end function circle_area

  function circle_perimeter(r) result(p)
    real, intent(in) :: r
    real :: p
    p = 2.0 * PI * r
  end function circle_perimeter
end module geometry

! main.f90 - using the module
program use_module
  use geometry, only: circle_area, circle_perimeter, PI
  implicit none
  real :: r = 5.0
  print *, "Area: ", circle_area(r)         ! 78.5398
  print *, "Perimeter: ", circle_perimeter(r) ! 31.4159
  print *, "PI: ", PI
end program use_module

! Compile: gfortran geometry.f90 main.f90 -o main

アクセス制御(Public/Private)

アクセス制御:'private'がエンティティをモジュール内部に、'public'がエクスポート。デフォルトはモジュールレベルで設定可能('private'で選択的に'public :: ...')— これがベストプラクティス(明示的インターフェース)。派生型のコンポーネントは型自体が公開されていてもプライベート可能 — 呼び出し側は型を使用できるが内部に直接アクセス不可、プロシージャ経由でなければならない。'save'がモジュール変数を永続化(呼び出し間で値を保持)— モジュール変数はデフォルトで保存。'final'がデストラクタを定義(オブジェクトがスコープを抜ける時に呼ばれる)。このカプセル化でプロシージャ経由で不変条件を強制する真のOOPを実現。

fortran
module bank_account
  implicit none
  private                       ! default: everything private
  ! Explicitly export:
  public :: account_t, deposit, withdraw, get_balance

  ! Derived type - can expose type but hide internals
  type :: account_t
    private                    ! components are private
    real :: balance = 0.0
    integer :: id = 0
  contains
    procedure :: balance => get_bal   ! type-bound procedure
    final :: cleanup                  ! destructor
  end type account_t

  ! Module-level counter (private, not exported)
  integer, save :: next_id = 1000

contains
  ! Constructor (factory function)
  function create_account(initial) result(acc)
    type(account_t) :: acc
    real, intent(in) :: initial
    acc%balance = initial
    acc%id = next_id
    next_id = next_id + 1
  end function create_account

  subroutine deposit(acc, amount)
    type(account_t), intent(inout) :: acc
    real, intent(in) :: amount
    acc%balance = acc%balance + amount
  end subroutine deposit

  function get_bal(acc) result(b)
    class(account_t), intent(in) :: acc
    real :: b
    b = acc%balance
  end function get_bal

  subroutine cleanup(acc)
    type(account_t) :: acc
    ! cleanup code (e.g., log closure)
  end subroutine cleanup
end module bank_account

モジュール内の派生型

モジュールで定義された派生型は以下を持てる:型束縛プロシージャ(procedure :: name => impl)、コンストラクタ(型名でオーバーロードされたinterface経由)、割り付け可能コンポーネント。型束縛プロシージャの'class(keyword)'がポリモーフィズムを有効化(実際の型はサブクラスの可能性)。型束縛プロシージャはobj%method(args)として呼ばれる — OOP構文。型名をinterfaceとしてオーバーロードで複数のコンストラクタを持てる(vector_from_array、vector_from_size)。割り付け可能コンポーネントは自動的に割り付け/解放。これがモダンFortran OOP:カプセル化、メソッド、コンストラクタ、ポリモーフィズム、すべてモジュールシステム内で。

fortran
module vector_mod
  implicit none
  private
  public :: vector_t, vector_add, vector_scale

  ! Derived type with type parameters (Fortran 2003)
  type :: vector_t
    real, allocatable :: data(:)
    integer :: length = 0
  contains
    procedure :: norm => vector_norm
    procedure :: print => vector_print
  end type vector_t

  ! Constructor interface (overloaded)
  interface vector_t
    procedure vector_from_array
    procedure vector_from_size
  end interface vector_t

contains
  function vector_from_array(arr) result(v)
    real, intent(in) :: arr(:)
    type(vector_t) :: v
    v%data = arr
    v%length = size(arr)
  end function

  function vector_from_size(n, fill) result(v)
    integer, intent(in) :: n
    real, intent(in) :: fill
    type(vector_t) :: v
    allocate(v%data(n))
    v%data = fill
    v%length = n
  end function

  function vector_norm(self) result(n)
    class(vector_t), intent(in) :: self
    real :: n
    n = sqrt(sum(self%data**2))
  end function

  subroutine vector_print(self)
    class(vector_t), intent(in) :: self
    print *, "Vector(len=", self%length, "): ", self%data
  end subroutine

  function vector_add(a, b) result(c)
    type(vector_t), intent(in) :: a, b
    type(vector_t) :: c
    c%data = a%data + b%data
    c%length = a%length
  end function

  function vector_scale(a, s) result(c)
    type(vector_t), intent(in) :: a
    real, intent(in) :: s
    type(vector_t) :: c
    c%data = a%data * s
    c%length = a%length
  end function
end module vector_mod

ジェネリックプロシージャとオーバーロード

ジェネリックinterfaceがアドホックポリモーフィズム(オーバーロード)を提供:1つの名前が引数の型に基づいて異なる特定のプロシージャにディスパッチ。'interface name / module procedure proc1, proc2 / end interface'ブロックがすべての特定のプロシージャをリスト。コンパイラがコンパイル時に引数の型/ランクでマッチするものを選択。ジェネリック内のすべての特定のプロシージャは異なるシグネチャ(引数の型で区別可能)を持つ必要 — そうでなければ曖昧。これがFortranがテンプレートなしで演算子/関数オーバーロードを行う方法。ジェネリックディスパッチはコンパイル時に解決(ランタイムオーバーヘッドなし)。型間で統一APIを提供するためにジェネリックを使用。

fortran
module generics_mod
  implicit none
  private
  public :: print_value, add

  ! Generic interface: one name, multiple specific procedures
  interface print_value
    module procedure print_int
    module procedure print_real
    module procedure print_str
    module procedure print_int_array
  end interface print_value

  interface add
    module procedure add_int
    module procedure add_real
    module procedure add_arrays
  end interface add

contains
  subroutine print_int(x)
    integer, intent(in) :: x
    print *, "Integer: ", x
  end subroutine

  subroutine print_real(x)
    real, intent(in) :: x
    print *, "Real: ", x
  end subroutine

  subroutine print_str(s)
    character(*), intent(in) :: s
    print *, "String: ", trim(s)
  end subroutine

  subroutine print_int_array(arr)
    integer, intent(in) :: arr(:)
    print *, "Array: ", arr
  end subroutine

  function add_int(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c
    c = a + b
  end function

  function add_real(a, b) result(c)
    real, intent(in) :: a, b
    real :: c
    c = a + b
  end function

  function add_arrays(a, b) result(c)
    real, intent(in) :: a(:), b(:)
    real :: c(size(a))
    c = a + b
  end function
end module generics_mod

program use_generics
  use generics_mod
  implicit none
  call print_value(42)              ! Integer: 42
  call print_value(3.14)            ! Real: 3.14
  call print_value("Hello")         ! String: Hello
  call print_value([1,2,3])         ! Array: 1 2 3
  print *, add(2, 3)                ! 5
  print *, add(2.5, 3.5)            ! 6.0
end program use_generics

演算子オーバーロード

演算子オーバーロードで派生型に対する+、-、*、/、==などの動作を定義可能。interface operator(+) / module procedure vec_add / end interfaceが+演算子を関数にバインド。二項演算子には、別々のプロシージャで両方の順序(vec*scalarとscalar*vec)をオーバーロード可能。assignment(=)が代入演算子をオーバーロード(プロシージャはintent(out) LHSとintent(in) RHSを持つサブルーチン)。これで数学的構文が可能:c = vec_add(a, b)の代わりにc = a + b。可読性が向上する数学的型(ベクトル、行列、複素数)に演算子オーバーロードを使用。自明でないセマンティクスにはオーバーロードを避ける。構造コンストラクタvec3(x,y,z)は派生型に組み込み。

fortran
module vec_ops
  implicit none
  private
  public :: vec3, operator(+), operator(*), assignment(=)

  type :: vec3
    real :: x, y, z
  end type vec3

  ! Overload the + operator for vec3 + vec3
  interface operator(+)
    module procedure vec_add
  end interface

  ! Overload * for vec3 * scalar and scalar * vec3
  interface operator(*)
    module procedure vec_scale_r
    module procedure vec_scale_l
    module procedure vec_dot
  end interface

  ! Overload = for array-to-vec assignment
  interface assignment(=)
    module procedure arr_to_vec
  end interface

contains
  function vec_add(a, b) result(c)
    type(vec3), intent(in) :: a, b
    type(vec3) :: c
    c = vec3(a%x+b%x, a%y+b%y, a%z+b%z)
  end function

  function vec_scale_r(v, s) result(r)
    type(vec3), intent(in) :: v
    real, intent(in) :: s
    type(vec3) :: r
    r = vec3(v%x*s, v%y*s, v%z*s)
  end function

  function vec_scale_l(s, v) result(r)
    real, intent(in) :: s
    type(vec3), intent(in) :: v
    type(vec3) :: r
    r = vec3(v%x*s, v%y*s, v%z*s)
  end function

  ! vec3 * vec3 = dot product (scalar)
  function vec_dot(a, b) result(d)
    type(vec3), intent(in) :: a, b
    real :: d
    d = a%x*b%x + a%y*b%y + a%z*b%z
  end function

  subroutine arr_to_vec(v, arr)
    type(vec3), intent(out) :: v
    real, intent(in) :: arr(3)
    v = vec3(arr(1), arr(2), arr(3))
  end subroutine
end module vec_ops

program use_ops
  use vec_ops
  implicit none
  type(vec3) :: a, b, c
  a = vec3(1.0, 2.0, 3.0)
  b = vec3(4.0, 5.0, 6.0)
  c = a + b              ! vec3 addition
  print *, c%x, c%y, c%z ! 5 7 9
  print *, a * 2.0       ! scale: 2 4 6
  print *, 3.0 * b       ! scale: 12 15 18
  print *, a * b         ! dot product: 32
end program use_ops
07

派生型(構造体)とOOP

派生型の定義

派生型はFortranの構造体(ユーザー定義の複合型)。'type :: Name ... end type Name'で定義。コンポーネントは%でアクセス(.ではない — それは複素数用)。構造コンストラクタ:Name(val1, val2)がインスタンスを作成。コンポーネントはデフォルト値を持てる(宣言内の= value)。型全体の代入はすべてのコンポーネントをコピー(割り付け可能コンポーネントにはディープコピー)。派生型の配列がサポート。派生型はFortran OOPの基盤(型束縛プロシージャ、継承、ポリモーフィズム付き)。コンポーネントアクセスに%を使用:obj%field、obj%method()。コンストラクタ構文Name(args)はinterfaceでオーバーライドしない限り自動。

fortran
program derived_types
  implicit none
  ! Basic derived type (struct)
  type :: Point
    real :: x, y
  end type Point

  ! Type with default initialization
  type :: Person
    character(20) :: name = "Unknown"
    integer :: age = 0
    logical :: active = .true.
  end type Person

  ! Declare and construct
  type(Point) :: p1, p2
  type(Person) :: alice, bob

  ! Structure constructor
  p1 = Point(3.0, 4.0)
  p2 = Point(0.0, 0.0)
  alice = Person("Alice", 30, .true.)
  bob = Person("Bob", 25)        ! uses default for 'active'

  ! Access components with %
  print *, p1%x, p1%y            ! 3.0 4.0
  print *, alice%name, alice%age ! Alice 30
  print *, bob%active            ! T (default)

  ! Modify components
  p1%x = p1%x + 1.0
  alice%age = 31

  ! Whole-type assignment (component-wise copy)
  p2 = p1
  print *, p2%x                  ! 4.0

  ! Array of derived types
  type(Point) :: points(3)
  points(1) = Point(1.0, 1.0)
  points(2) = Point(2.0, 2.0)
  points(3) = Point(3.0, 3.0)
  print *, points(2)%y           ! 2.0
end program derived_types

型コンポーネントとコンストラクタ

派生型は以下を持てる:割り付け可能コンポーネント(自動管理メモリ)、デフォルト初期化コンポーネント、型束縛プロシージャ(メソッド)、ファイナライザ(デストラクタ)。'interface TypeName / module procedure custom_init / end interface'が構造コンストラクタをカスタムファクトリ関数でオーバーロード。型束縛プロシージャの'class(ClassName)'(vs 'type(ClassName)')がポリモーフィズムを有効化(実際の型はサブクラスの可能性)。'final'プロシージャがオブジェクトのスコープ抜け時に実行(デストラクタ)— リソース解放に使用。割り付け可能コンポーネントはファイナライズ時に自動解放されるが、複雑なクリーンアップには明示的ファイナライザがクリア。block構文でコード途中で変数宣言可能(Fortran 2008)。

fortran
program type_components
  implicit none
  ! Type with various component kinds
  type :: Student
    integer :: id
    character(20) :: name
    real, allocatable :: grades(:)    ! allocatable component
    integer :: num_grades = 0
  contains
    procedure :: add_grade
    procedure :: average => student_avg
    final :: student_finalize
  end type Student

  ! Overloaded constructor
  interface Student
    module procedure student_init
  end interface Student

  type(Student) :: s

  ! Use custom constructor
  s = Student(101, "Alice")
  call s%add_grade(85.0)
  call s%add_grade(92.0)
  call s%add_grade(78.0)
  print *, s%average()    ! 85.0

contains
  function student_init(id, name) result(s)
    integer, intent(in) :: id
    character(*), intent(in) :: name
    type(Student) :: s
    s%id = id
    s%name = name
    s%num_grades = 0
  end function

  subroutine add_grade(self, g)
    class(Student), intent(inout) :: self
    real, intent(in) :: g
    integer :: n
    n = self%num_grades
    if (n == 0) then
      allocate(self%grades(1))
    else
      ! grow array
      block
        real, allocatable :: tmp(:)
        tmp = self%grades
        deallocate(self%grades)
        allocate(self%grades(n+1))
        self%grades(1:n) = tmp
      end block
    end if
    self%num_grades = n + 1
    self%grades(n+1) = g
  end subroutine

  function student_avg(self) result(avg)
    class(Student), intent(in) :: self
    real :: avg
    if (self%num_grades > 0) then
      avg = sum(self%grades) / self%num_grades
    else
      avg = 0.0
    end if
  end function

  subroutine student_finalize(self)
    type(Student) :: self
    if (allocated(self%grades)) deallocate(self%grades)
  end subroutine
end program type_components

型束縛プロシージャ(メソッド)

型束縛プロシージャはFortranのメソッド:'procedure :: method_name => implementation'。obj%method(args)として呼び出し — OOP構文。最初の引数は'self'(オブジェクト)、'class(TypeName)'(ポリモーフィック)または'type(TypeName)'(具象)として宣言。'class'は継承/ポリモーフィズムを許可、'type'は拡張不可な型用。'=> implementation'がメソッド名を特定のプロシージャにマップ(リネーム可能)。finalがデストラクタ。この例は自動拡大配列で動的スタックを実装。型束縛プロシージャが真のOOPを提供:カプセル化(データ + メソッド一緒)、メッセージパッシング構文(obj%method)、ポリモーフィズム(class経由)。将来の継承を有効化するため型束縛プロシージャには常にclass()を使用。

fortran
module stack_mod
  implicit none
  private
  public :: stack_t

  type :: stack_t
    integer, allocatable :: data(:)
    integer :: top = 0
    integer :: capacity = 0
  contains
    procedure :: push => stack_push
    procedure :: pop => stack_pop
    procedure :: peek => stack_peek
    procedure :: is_empty => stack_is_empty
    procedure :: size => stack_size
    procedure :: clear => stack_clear
    final :: stack_finalize
  end type stack_t

  interface stack_t
    module procedure stack_init
  end interface

contains
  function stack_init(initial_cap) result(s)
    integer, intent(in), optional :: initial_cap
    type(stack_t) :: s
    integer :: cap
    cap = 16
    if (present(initial_cap)) cap = initial_cap
    allocate(s%data(cap))
    s%capacity = cap
    s%top = 0
  end function

  subroutine stack_push(self, val)
    class(stack_t), intent(inout) :: self
    integer, intent(in) :: val
    if (self%top >= self%capacity) then
      ! grow
      block
        integer, allocatable :: tmp(:)
        tmp = self%data
        deallocate(self%data)
        allocate(self%data(self%capacity * 2))
        self%data(1:self%capacity) = tmp
        self%capacity = self%capacity * 2
      end block
    end if
    self%top = self%top + 1
    self%data(self%top) = val
  end subroutine

  function stack_pop(self) result(val)
    class(stack_t), intent(inout) :: self
    integer :: val
    if (self%top == 0) stop "Stack underflow"
    val = self%data(self%top)
    self%top = self%top - 1
  end function

  function stack_peek(self) result(val)
    class(stack_t), intent(in) :: self
    integer :: val
    val = self%data(self%top)
  end function

  logical function stack_is_empty(self) result(r)
    class(stack_t), intent(in) :: self
    r = (self%top == 0)
  end function

  integer function stack_size(self) result(n)
    class(stack_t), intent(in) :: self
    n = self%top
  end function

  subroutine stack_clear(self)
    class(stack_t), intent(inout) :: self
    self%top = 0
  end subroutine

  subroutine stack_finalize(self)
    type(stack_t) :: self
    if (allocated(self%data)) deallocate(self%data)
  end subroutine
end module stack_mod

program use_stack
  use stack_mod
  implicit none
  type(stack_t) :: s
  s = stack_t(8)         ! initial capacity 8
  call s%push(10)
  call s%push(20)
  call s%push(30)
  print *, s%size()      ! 3
  print *, s%pop()       ! 30
  print *, s%pop()       ! 20
  print *, s%is_empty()  ! F
end program use_stack

継承とポリモーフィズム

Fortran OOP:'type, extends(Parent) :: Child'がサブクラスを作成(継承)。'type, abstract :: Name'に'procedure(...), deferred :: method'で抽象基底を定義(Java抽象クラス / C++純粋仮想のようなもの)。'class(Base)'はポリモーフィック — 任意のサブクラスを保持可能。ポリモーフィックディスパッチ:obj%method()の呼び出しがサブクラスのオーバーライドを起動。'select type (var => expr) / type is (ConcreteType) / end select'がランタイム型チェック(ダウンキャスト)を行う。'allocate(TypeName::var)'で特定の具象型のポリモーフィックオブジェクトを作成。これが完全なOOP:継承、ポリモーフィズム、抽象型、ランタイムディスパッチ — Java/C++に匹敵。

fortran
module shapes
  implicit none
  private
  public :: shape, circle, rectangle, shape_ptr

  ! Base type (abstract)
  type, abstract :: shape
    character(20) :: name
  contains
    procedure(area_if), deferred :: area
    procedure(describe_if), deferred :: describe
    procedure :: get_name => shape_get_name
  end type shape

  ! Abstract interface (must be implemented by subclasses)
  abstract interface
    function area_if(self) result(a)
      import :: shape
      class(shape), intent(in) :: self
      real :: a
    end function area_if
    subroutine describe_if(self)
      import :: shape
      class(shape), intent(in) :: self
    end subroutine describe_if
  end interface

  ! Derived type: circle extends shape
  type, extends(shape) :: circle
    real :: radius
  contains
    procedure :: area => circle_area
    procedure :: describe => circle_describe
  end type circle

  ! Derived type: rectangle extends shape
  type, extends(shape) :: rectangle
    real :: width, height
  contains
    procedure :: area => rect_area
    procedure :: describe => rect_describe
  end type rectangle

  ! Class pointer for polymorphism
  type :: shape_ptr
    class(shape), allocatable :: ptr
  end type shape_ptr

contains
  function shape_get_name(self) result(n)
    class(shape), intent(in) :: self
    character(20) :: n
    n = self%name
  end function

  function circle_area(self) result(a)
    class(circle), intent(in) :: self
    real :: a
    a = 3.14159265 * self%radius**2
  end function

  subroutine circle_describe(self)
    class(circle), intent(in) :: self
    print *, "Circle '", trim(self%name), "' r=", self%radius
  end subroutine

  function rect_area(self) result(a)
    class(rectangle), intent(in) :: self
    real :: a
    a = self%width * self%height
  end function

  function rect_describe(self) result(s)
    class(rectangle), intent(in) :: self
    character(50) :: s
    write(s, '("Rectangle ", F6.2, "x", F6.2)') self%width, self%height
  end function
end module shapes

program use_polymorphism
  use shapes
  implicit none
  type(shape_ptr) :: shapes_arr(2)
  ! Allocate concrete types into polymorphic container
  allocate(circle::shapes_arr(1)%ptr)
  select type(s => shapes_arr(1)%ptr)
  type is (circle)
    s%radius = 5.0
    s%name = "C1"
  end select

  allocate(rectangle::shapes_arr(2)%ptr)
  select type(s => shapes_arr(2)%ptr)
  type is (rectangle)
    s%width = 3.0
    s%height = 4.0
    s%name = "R1"
  end select

  ! Polymorphic dispatch
  block
    integer :: i
    do i = 1, 2
      call shapes_arr(i)%ptr%describe
      print *, "Area: ", shapes_arr(i)%ptr%area()
    end do
  end block
end program use_polymorphism

ネストした型と型の配列

派生型はネスト可能(コンポジション):型は他の派生型のコンポーネントを持てる。ネストしたコンポーネントにチェーン%でアクセス:emp%home%city。構造コンストラクタは自然にネスト:Employee(id, name, Address(...), salary)。派生型の配列がサポート:type(Employee) :: emps(N)。単一コンポーネントの配列を抽出可能:emps(:)%idが整数配列を与える。割り付け可能コンポーネントで動的サイズコレクションを可能に(例:可変数の従業員を持つ部門)。このコンポジションモデルがFortranで複雑なデータ構造(ツリー、グラフ、リスト)を構築する基盤。明確なサブタイプ関係がない場合は継承(is-a)よりコンポジション(has-a)を使用。

fortran
program nested_types
  implicit none
  ! Nested derived types
  type :: Address
    character(50) :: street
    character(30) :: city
    character(10) :: zip
  end type Address

  type :: Employee
    integer :: id
    character(30) :: name
    type(Address) :: home      ! nested type
    real :: salary
  end type Employee

  type :: Department
    character(30) :: name
    type(Employee), allocatable :: employees(:)   ! array of types
    integer :: count = 0
  end type Department

  ! Construct with nested structure constructor
  type(Employee) :: emp
  emp = Employee(101, "Alice", &
        Address("123 Main St", "Springfield", "12345"), 75000.0)

  ! Access nested components
  print *, emp%name                      ! Alice
  print *, emp%home%city                 ! Springfield
  print *, emp%home%zip                  ! 12345

  ! Department with array of employees
  type(Department) :: dept
  dept%name = "Engineering"
  allocate(dept%employees(3))
  dept%employees(1) = emp
  dept%employees(2) = Employee(102, "Bob", &
                        Address("456 Oak Ave", "Shelbyville", "54321"), 68000.0)
  dept%count = 2

  ! Iterate over array of types
  block
    integer :: i
    do i = 1, dept%count
      print *, dept%employees(i)%id, trim(dept%employees(i)%name), &
               dept%employees(i)%home%city
    end do
  end block

  ! Array of derived type components
  print *, dept%employees(1:2)%id        ! array of id field
end program nested_types
08

ファイルI/Oとフォーマット

ファイルのオープンとクローズ

open()がファイルをユニット番号に接続。newunit=uでコンパイラに一意のユニットを選ばせる(競合を回避)— ハードコードされたユニット番号より常にこれを推奨。status:'old'(ファイルが存在必要)、'new'(存在してはいけない)、'replace'(削除 + 作成)、'scratch'(一時、クローズ時に自動削除)。action:'read'、'write'、'readwrite'。position:'rewind'(開始)、'append'(終わり)、'asis'(どこでも)。openとreadの後に常にiostatをチェック — 非ゼロはエラー(負 = EOF、正 = エラー)。iomsgが説明的なエラーメッセージを与える。close()が切断。堅牢なファイル処理には、常にiostatをチェックしエラーをグレースフルに処理。

fortran
program file_open
  implicit none
  integer :: u, ios
  character(100) :: msg

  ! newunit: compiler picks a unique unit number (Fortran 2008)
  ! status: 'old' (must exist), 'new' (must not exist),
  !         'replace' (overwrite), 'scratch' (temporary)
  ! action: 'read', 'write', 'readwrite'
  ! position: 'rewind', 'append', 'asis'
  open(newunit=u, file="data.txt", status="replace", &
       action="write", iostat=ios, iomsg=msg)
  if (ios /= 0) then
    print *, "Open failed: ", trim(msg)
    stop 1
  end if

  write(u, *) "First line"
  write(u, *) "Second line"
  close(u)

  ! Append to existing file
  open(newunit=u, file="data.txt", status="old", &
       action="write", position="append", iostat=ios)
  if (ios == 0) then
    write(u, *) "Appended line"
    close(u)
  end if

  ! Scratch file (auto-deleted on close)
  open(newunit=u, status="scratch", action="readwrite")
  write(u, *) "Temporary data"
  rewind(u)
  ! read back...
  close(u)   ! file disappears

  ! Reading with end-of-file detection
  open(newunit=u, file="data.txt", status="old", action="read")
  do
    read(u, '(A)', iostat=ios) msg
    if (ios /= 0) exit   ! EOF or error
    print *, trim(msg)
  end do
  close(u)
end program file_open

フォーマットI/O

フォーマット指定子がI/Oを制御:'(A, I0, F8.2)'。A=文字、I0=整数最小幅、F8.2=実数幅8小数2。write(unit, fmt)で書き込み、read(unit, fmt)で読み取り。unit=*はstdout/stdinを意味。ファイルにはopen()からのユニットを使用。リスト指向I/O(*)は柔軟:read(u, *) a, b, cがカンマ/スペース区切り値を自動的に読み取り。CSVには、値がカンマ区切りならリスト指向読み取りが動作。フォーマット文字列は再利用可能:'(3I4)'はI4を3回適用。データ型にフォーマットを常に一致 — 不一致フォーマットはランタイムエラーを引き起こす。テキスト+数値の混在には、文字列として読み後に解析、または明示的フォーマットを使用。

fortran
program formatted_io
  implicit none
  integer :: u, n = 42
  real :: pi = 3.14159265
  character(20) :: name = "Alice"

  ! Write formatted data to file
  open(newunit=u, file="output.txt", status="replace")
  write(u, '(A, I0)') "Count: ", n
  write(u, '(A, F8.4)') "Pi: ", pi
  write(u, '(A, A)') "Name: ", trim(name)
  write(u, '(3(I0, 1X))') 1, 2, 3   ! "1 2 3 "
  close(u)

  ! Read formatted data back
  open(newunit=u, file="output.txt", status="old", action="read")
  block
    character(100) :: line
    integer :: i
    do i = 1, 5
      read(u, '(A)') line
      print *, trim(line)
    end do
  end block
  close(u)

  ! Reading structured data
  open(newunit=u, file="data.csv", status="replace")
  write(u, '(I0, ",", I0, ",", I0)') 1, 10, 100
  write(u, '(I0, ",", I0, ",", I0)') 2, 20, 200
  close(u)

  ! Read back as numbers
  open(newunit=u, file="data.csv", status="old", action="read")
  block
    integer :: a, b, c
    ! List-directed read handles commas as separators
    read(u, *) a, b, c
    print *, a, b, c   ! 1 10 100
    read(u, *) a, b, c
    print *, a, b, c   ! 2 20 200
  end block
  close(u)
end program formatted_io

非フォーマット(バイナリ)I/O

非フォーマット(バイナリ)I/Oはフォーマット(テキスト)I/Oより高速でコンパクト — 文字列変換なし。form='unformatted'で有効化。access='stream'(Fortran 2003)がバイトストリームアクセスを与える(CファイルI/Oのようなもの、レコードマーカーなし)。access='sequential'(デフォルト)はレコードマーカーを使用(各書き込み/読み取りが長さ前置き付きのレコード)— Fortran内でポータブルだが他言語には非ポータブル。C/Pythonとの相互運用にはストリームアクセスを使用。バイナリファイルは人間が読めないが大きな数値データセットに理想的。データを正しく読み戻せるよう、データの前にメタデータ(配列サイズ、型情報)を常に書き込み。非フォーマットI/Oは完全な精度を保持(テキスト変換による丸めなし)。

fortran
program binary_io
  implicit none
  integer :: u, i
  real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
  real :: arr_in(5)
  integer :: n

  ! Write binary (unformatted) - faster, no format conversion
  open(newunit=u, file="data.bin", status="replace", &
       form="unformatted", access="stream")
  write(u) size(arr)        ! write the count first
  write(u) arr              ! write the whole array
  close(u)

  ! Read binary back
  open(newunit=u, file="data.bin", status="old", &
       form="unformatted", access="stream", action="read")
  read(u) n                 ! read the count
  print *, "Count: ", n
  read(u) arr_in            ! read the array
  print *, arr_in           ! 1 2 3 4 5
  close(u)

  ! Sequential unformatted (default) - with record markers
  open(newunit=u, file="data_seq.bin", status="replace", &
       form="unformatted")   ! access defaults to "sequential"
  do i = 1, 5
    write(u) i, arr(i)      ! each write is a "record"
  end do
  close(u)

  ! Read sequentially
  open(newunit=u, file="data_seq.bin", status="old", &
       form="unformatted", action="read")
  do
    read(u, iostat=i) n, arr_in(1)
    if (i /= 0) exit
    print *, n, arr_in(1)
  end do
  close(u)
end program binary_io

Namelist(グループ化I/O)

namelistが構造化テキストI/O用に変数をグループ化 — JSON/YAMLのようなものだがFortranネイティブ。'namelist /name/ var1, var2, ...'で定義。write(u, nml=name)が&NAME var=val, var=val, /形式で出力。read(u, nml=name)で解析し戻し。namelistは設定ファイルに最適:ユーザーがテキストファイルを編集、プログラムが読み取り。変数は宣言された値をデフォルトとして保持、ファイル内のもののみ上書き。フォーマットは寛容(空白非感受、オプションのカンマ)。namelistはすべての組み込み型と配列をサポート。iostatが解析エラーをキャッチ。これがカスタムパーサーを書かずにFortranプログラムを設定可能にする最も簡単な方法。

fortran
program namelist_demo
  implicit none
  ! Namelist groups variables for easy I/O
  integer :: max_iter = 100
  real :: tolerance = 1.0e-6
  logical :: verbose = .true.
  character(20) :: method = "newton"
  real :: params(3) = [0.1, 0.2, 0.3]

  ! Define a namelist group
  namelist /config/ max_iter, tolerance, verbose, method, params

  ! Write namelist to file
  block
    integer :: u
    open(newunit=u, file="config.nml", status="replace")
    write(u, nml=config)
    close(u)
  end block

  ! The file looks like:
  ! &CONFIG
  !  MAX_ITER=100,
  !  TOLERANCE=1.0000000E-06,
  !  VERBOSE=T,
  !  METHOD="newton",
  !  PARAMS=0.100000, 0.200000, 0.300000,
  ! /

  ! Read namelist from file (overrides defaults)
  block
    integer :: u, ios
    open(newunit=u, file="config.nml", status="old", action="read")
    read(u, nml=config, iostat=ios)
    close(u)
    if (ios /= 0) then
      print *, "Error reading config"
    else
      print *, "max_iter=", max_iter
      print *, "tolerance=", tolerance
      print *, "method=", trim(method)
      print *, "params=", params
    end if
  end block

  ! User can edit config.nml in a text editor
  ! then re-run to pick up new values
end program namelist_demo

内部ファイルとエラー処理

内部ファイルでファイル代わりに文字列から読み取り/文字列へ書き込み — Fortranのsprintf/sscanf。write(str, fmt)が文字列にフォーマット、read(str, fmt)が文字列から解析。これが文字列と数値間の変換の標準的な方法。エラー処理には常にiostatを使用:0 = 成功、負 = EOF、正 = エラー。レガシーのend=とerr=ラベルは動作するがiostatがクリーン(gotoなし)。堅牢な解析には、各読み取り後にiostatをチェック。内部I/Oは出力文字列の構築、ユーザー入力の解析、設定ファイル値の変換に最適。文字列が'内部ファイル'として機能 — 同じI/O文、ただし文字列宛先。

fortran
program internal_io
  implicit none
  character(100) :: buffer
  integer :: n = 42
  real :: x = 3.14159
  integer :: ios

  ! Internal write: format to a string (like sprintf)
  write(buffer, '(A, I0, A, F8.4)') "n=", n, " x=", x
  print *, trim(buffer)   ! n=42 x=  3.1416

  ! Internal read: parse from a string (like sscanf)
  character(50) :: input = "100 3.14 hello"
  integer :: a
  real :: b
  character(20) :: c
  read(input, *, iostat=ios) a, b, c
  if (ios == 0) print *, a, b, trim(c)   ! 100 3.14 hello

  ! Robust number parsing with error handling
  character(20) :: num_str = "3.14abc"
  real :: val
  read(num_str, *, iostat=ios) val
  if (ios /= 0) then
    print *, "Parse error: '", trim(num_str), "' is not a number"
  else
    print *, "Value: ", val
  end if

  ! End-of-file and error handling on file reads
  block
    integer :: u
    character(100) :: line
    open(newunit=u, file="data.txt", status="old", action="read")
    do
      read(u, '(A)', iostat=ios) line
      if (ios < 0) then
        print *, "End of file"
        exit
      else if (ios > 0) then
        print *, "Read error at line"
        exit
      end if
      print *, trim(line)
    end do
    close(u)
  end block

  ! err= and end= labels (legacy style)
  block
    integer :: u, val
    open(newunit=u, file="nums.txt", status="old", action="read")
    do
      read(u, *, end=100, err=200) val
      print *, val
    end do
100 print *, "Reached EOF"
    close(u)
    goto 300
200 print *, "Read error!"
    close(u)
300 continue
  end block
end program internal_io
09

数値計算

Kindパラメータと精度

Kindパラメータが精度/サイズを制御。モダンでポータブルな方法:iso_fortran_envを使用(int32/int64、real32/real64/real128)。レガシー:selected_real_kind(digits, exponent_range)。real32 ≈ 7有効桁、real64 ≈ 15桁(倍精度)、real128 ≈ 33桁(四倍精度)。重要:リテラルに常にkind接尾辞を付ける(3.14_dp、3.14ではなく)— そうでなければリテラルが単精度で解析されてから変換され、桁が失われる。precision()が有効桁を返す、range()が10進指数範囲を返す。epsilon()が機械イプシロン(最小識別増分)を与える。tiny/hugeが最小/最大を与える。科学計算にはデフォルトでreal64(倍精度)を使用。

fortran
program precision_demo
  use iso_fortran_env, only: int32, int64, real32, real64, real128
  implicit none
  ! Portable kind selection via iso_fortran_env
  integer(kind=int32) :: i32 = 100
  integer(kind=int64) :: i64 = 9223372036854775807_int64
  real(kind=real32) :: r32 = 3.14159265_real32   ! single (~7 digits)
  real(kind=real64) :: r64 = 3.14159265358979_real64  ! double (~15 digits)
  real(kind=real128) :: r128 = 3.14159265358979_real128 ! quad (~33 digits)

  ! Legacy: selected_real_kind (still works)
  integer, parameter :: dp = selected_real_kind(15, 307)  ! double
  integer, parameter :: sp = selected_real_kind(6, 37)    ! single
  real(kind=dp) :: pi = 3.14159265358979_dp

  print *, "real32 precision: ", precision(r32), " range: ", range(r32)
  print *, "real64 precision: ", precision(r64), " range: ", range(r64)
  print *, "real128 precision: ", precision(r128)

  ! Epsilon and tiny
  print *, "epsilon(r32): ", epsilon(r32)   ! ~1.19e-7
  print *, "epsilon(r64): ", epsilon(r64)   ! ~2.22e-16
  print *, "tiny(r64): ", tiny(r64)         ! smallest positive
  print *, "huge(i32): ", huge(i32)         ! 2147483647
  print *, "huge(i64): ", huge(i64)

  ! Always use _kind suffix on literals
  ! WRONG: real(kind=dp) :: x = 3.14  (loses precision!)
  ! RIGHT: real(kind=dp) :: x = 3.14_dp
end program precision_demo

線形代数(matmul、solve、BLAS)

Fortranには組み込みの線形代数がある:matmul(行列-行列/行列-ベクトル乗算)、dot_product、transpose。線形システム(Ax=b)の解法、固有値、SVDなどにはLAPACK(業界標準のFortranライブラリ)を使用:dgesvがAx=bを解く、dgesvdがSVD、dsyevが固有分解。-llapack -lblasでリンク。例は3x3システムの手動ガウス消去を示す — 実作業にはLAPACKを使用(より高速、ピボット付きでより正確、任意サイズを処理)。Fortranの列優先ストレージがLAPACKの期待にネイティブに一致(転置不要)。matmulは最適化されているが大きな行列にはBLAS dgemmがより高速。数値安定性のために常に条件数をチェック。

fortran
program linalg
  implicit none
  real(8) :: A(3,3), B(3,3), C(3,3)
  real(8) :: v(3), w(3), x(3)
  real(8) :: det
  integer :: i

  ! Initialize matrices
  A = reshape([1,2,3, 4,5,6, 7,8,10], [3,3])  ! column-major fill
  B = reshape([1,0,0, 0,1,0, 0,0,1], [3,3])   ! identity

  ! Matrix-matrix multiplication
  C = matmul(A, B)        ! A * I = A
  print *, "A * I = A? ", all(abs(C - A) < 1e-10)

  ! Matrix-vector multiplication
  v = [1.0, 2.0, 3.0]
  w = matmul(A, v)
  print *, "A * v = ", w    ! 14 32 53

  ! Dot product
  print *, "v . v = ", dot_product(v, v)   ! 14

  ! Outer product
  C = 0.0
  do i = 1, 3
    C(:,i) = v * v(i)
  end do

  ! Transpose
  C = transpose(A)

  ! Solve linear system Ax = b (need LAPACK or custom)
  ! Using LAPACK dgesv:
  !   call dgesv(n, nrhs, A, lda, ipiv, b, ldb, info)
  ! For demo, manual Gaussian elimination:
  x = solve_3x3(A, v)
  print *, "Solution: ", x

contains
  function solve_3x3(A, b) result(x)
    real(8), intent(inout) :: A(3,3)
    real(8), intent(in) :: b(3)
    real(8) :: x(3), M(3,4), factor
    integer :: k, j
    M(:,1:3) = A
    M(:,4) = b
    ! Forward elimination
    do k = 1, 2
      do i = k+1, 3
        factor = M(i,k) / M(k,k)
        M(i,:) = M(i,:) - factor * M(k,:)
      end do
    end do
    ! Back substitution
    x(3) = M(3,4) / M(3,3)
    x(2) = (M(2,4) - M(2,3)*x(3)) / M(2,2)
    x(1) = (M(1,4) - M(1,2)*x(2) - M(1,3)*x(3)) / M(1,1)
  end function
end program linalg

乱数

call random_number(x)がxを一様[0,1)実数で埋める — スカラーまたは配列で動作。call random_seed(size=n)がシードサイズを取得、random_seed(put=seed)が再現性のためシードを設定(テスト/デバッグに不可欠)。[a,b]の整数には:a + int(r * (b-a+1))。ガウス(正規)乱数には、Box-Muller変換(示されている)または極方法を使用。Fortranには組み込みの正規分布生成器なし — 実装するかライブラリを使用。モンテカルロシミュレーションには、再現性のためシードを設定し、多数の試行を実行。random_numberは暗号学的に安全ではない — セキュリティには暗号ライブラリを使用。並列コードでは、各イメージが異なるシードを必要。

fortran
program random_demo
  implicit none
  real :: r
  integer :: i, n = 10
  real :: arr(10)
  integer :: seed_size
  integer, allocatable :: seed(:)

  ! Initialize random seed (Fortran 2003: random_seed with no args
  ! uses a processor-dependent seed; for reproducibility, set it)
  call random_seed(size=seed_size)
  allocate(seed(seed_size))
  ! Set a fixed seed for reproducibility
  seed = [(12345 + i*6789, i=1, seed_size)]
  call random_seed(put=seed)

  ! Generate uniform [0,1) random reals
  call random_number(r)
  print *, "Single: ", r

  call random_number(arr)   ! fills entire array
  print *, "Array: ", arr

  ! Generate integers in [a, b]
  block
    integer :: a = 1, b = 6
    integer :: dice
    do i = 1, 5
      call random_number(r)
      dice = a + int(r * (b - a + 1))   ! [1, 6]
      print *, "Dice roll: ", dice
    end do
  end block

  ! Normal (Gaussian) via Box-Muller transform
  block
    real :: u1, u2, z1, z2
    integer :: j
    do j = 1, 5
      call random_number(u1)
      call random_number(u2)
      u1 = max(u1, 1e-10)   ! avoid log(0)
      z1 = sqrt(-2.0 * log(u1)) * cos(2.0 * 3.14159265 * u2)
      z2 = sqrt(-2.0 * log(u1)) * sin(2.0 * 3.14159265 * u2)
      print *, "Gaussian: ", z1, z2
    end do
  end block

  ! Monte Carlo: estimate pi
  block
    integer :: inside = 0, total = 100000
    real :: x, y
    do i = 1, total
      call random_number(x)
      call random_number(y)
      if (x*x + y*y <= 1.0) inside = inside + 1
    end do
    print *, "Pi estimate: ", 4.0 * real(inside) / total
  end block
end program random_demo

数値積分と根の探索

数値積分:シンプソン則は台形則より正確(O(h^4)誤差 vs O(h^2))。interfaceブロックを使用して関数を引数として渡す。根の探索:二分法はロバスト(符号が変われば常に収束)だが遅い(線形収束)、ニュートン法は高速(二次収束)だが微分が必要で発散する可能性。本番作業にはQUADPACK(積分)またはMINPACK(根の探索)を使用 — 実戦テスト済みのFortranライブラリ。関数を引数として渡す際にinterfaceブロックが不可欠 — コンパイラに関数のシグネチャを伝える。無限ループを避けるため常に最大反復回数を設定。関数値とステップサイズの両方の許容値で収束をチェック。

fortran
program numerical
  implicit none
  real(8) :: a, b, result
  integer :: n

  ! Numerical integration: Simpson's rule
  ! Integrate f(x) = x^2 from 0 to 2 (exact: 8/3 ≈ 2.6667)
  a = 0.0; b = 2.0; n = 1000
  result = simpson(f_sq, a, b, n)
  print *, "Integral of x^2 from 0 to 2: ", result   ! ~2.6667

  ! Integrate sin(x) from 0 to pi (exact: 2.0)
  result = simpson(f_sin, 0.0_8, 3.14159265358979_8, 1000)
  print *, "Integral of sin(x) from 0 to pi: ", result  ! ~2.0

  ! Root finding: bisection method
  ! Find root of f(x) = x^2 - 2 (i.e., sqrt(2) ≈ 1.4142)
  result = bisection(f_x2_minus_2, 0.0_8, 2.0_8, 1e-12_8)
  print *, "sqrt(2) = ", result   ! ~1.41421356

  ! Newton's method (needs derivative)
  result = newton(f_x2_minus_2, fp_x2_minus_2, 1.0_8, 1e-12_8)
  print *, "sqrt(2) via Newton: ", result

contains
  ! Function to integrate: x^2
  function f_sq(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = x * x
  end function

  function f_sin(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = sin(x)
  end function

  ! Simpson's rule: integral of f from a to b with n intervals
  function simpson(f, a, b, n) result(integral)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: a, b
    integer, intent(in) :: n
    real(8) :: integral, h, x
    integer :: i
    h = (b - a) / n
    integral = f(a) + f(b)
    do i = 1, n-1
      x = a + i * h
      if (mod(i, 2) == 0) then
        integral = integral + 2.0 * f(x)
      else
        integral = integral + 4.0 * f(x)
      end if
    end do
    integral = integral * h / 3.0
  end function

  function f_x2_minus_2(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = x*x - 2.0
  end function

  function fp_x2_minus_2(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = 2.0 * x
  end function

  ! Bisection: find root in [a, b] (f(a) and f(b) must have opposite signs)
  function bisection(f, a, b, tol) result(root)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: a, b, tol
    real(8) :: root, fa, fb, mid, fmid
    integer :: iter, maxiter = 100
    fa = f(a); fb = f(b)
    if (fa * fb > 0) stop "No sign change in interval"
    do iter = 1, maxiter
      mid = (a + b) / 2.0
      fmid = f(mid)
      if (abs(fmid) < tol .or. (b - a)/2.0 < tol) then
        root = mid
        return
      end if
      if (fa * fmid < 0) then
        b = mid; fb = fmid
      else
        a = mid; fa = fmid
      end if
    end do
    root = (a + b) / 2.0
  end function

  ! Newton's method: x_{n+1} = x_n - f(x)/f'(x)
  function newton(f, fp, x0, tol) result(root)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
      function fp(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: x0, tol
    real(8) :: root, x, fx, fpx
    integer :: iter, maxiter = 100
    x = x0
    do iter = 1, maxiter
      fx = f(x); fpx = fp(x)
      if (abs(fx) < tol) then
        root = x
        return
      end if
      x = x - fx / fpx
    end do
    root = x
  end function
end program numerical

IEEE算術と例外

ieee_arithmeticモジュール(Fortran 2003)がIEEE 754サポートを提供:無限大、NaN(非数)、シグナリング/クワイエットNaN、例外フラグ、丸めモード。NaNは自分自身を含む何とも決して等しくない — ieee_is_nan()でテスト。無限大はオーバーフローまたはゼロ除算の結果。例外フラグ(ieee_divide_by_zero、ieee_overflow、ieee_underflow、ieee_inexact、ieee_invalid)が例外が発生したか追跡 — ieee_get_flagでチェック、ieee_set_flagでクリア。ieee_set_halting_modeが例外でプログラムを停止するか制御。丸めモードが浮動小数点操作に影響。堅牢な数値コードに使用:NaN/Infを検出、例外をグレースフルに処理、精度を制御。注意:gfortranの-ffast-mathはIEEE準拠を破る(これらの機能に依存するコードには使用しない)。

fortran
program ieee_demo
  use ieee_arithmetic
  implicit none
  real :: a, b, c
  logical :: flag

  ! IEEE special values
  a = ieee_value(a, ieee_positive_inf)   ! +Infinity
  b = ieee_value(b, ieee_negative_inf)   ! -Infinity
  c = ieee_value(c, ieee_quiet_nan)      ! NaN

  print *, "Infinity: ", a                ! Infinity
  print *, "NaN: ", c                     ! NaN
  print *, "Inf > 1e30: ", a > 1e30       ! T
  print *, "NaN == NaN: ", c == c         ! F (NaN is never equal!)

  ! Check for special values
  print *, "is_nan(c): ", ieee_is_nan(c)         ! T
  print *, "is_finite(1.0): ", ieee_is_finite(1.0)  ! T
  print *, "is_inf(a): ", ieee_is_finite(a)      ! F

  ! Operations producing special values
  print *, "1.0/0.0: ", 1.0/0.0           ! Infinity (if -ffast-math off)
  print *, "0.0/0.0: ", 0.0/0.0           ! NaN
  print *, "sqrt(-1.0): ", sqrt(-1.0)     ! NaN

  ! IEEE exception flags
  call ieee_set_halting_mode(ieee_divide_by_zero, .false.)  ! don't halt
  b = 1.0 / 0.0   ! sets divide_by_zero flag, returns Inf
  call ieee_get_flag(ieee_divide_by_zero, flag)
  print *, "Divide by zero occurred: ", flag   ! T

  ! Check and clear flags
  call ieee_set_flag(ieee_all, .false.)   ! clear all flags
  b = 1.0 / 0.0
  call ieee_get_flag(ieee_divide_by_zero, flag)
  print *, "Flag after division: ", flag   ! T
  call ieee_set_flag(ieee_all, .false.)   ! clear

  ! Rounding modes
  call ieee_set_rounding_mode(ieee_nearest)   ! default
  call ieee_set_rounding_mode(ieee_down)      ! round toward -inf
  call ieee_set_rounding_mode(ieee_up)        ! round toward +inf
  call ieee_set_rounding_mode(ieee_to_zero)   ! truncate

  ! Comparing NaN-safe
  if (ieee_unordered(c, 1.0)) print *, "c is unordered (NaN)"
end program ieee_demo
10

Coarraysと並列

基本的なcoarray宣言

CoarraysはFortranの組み込み並列モデル(F2008)。各'イメージ'は並列プロセス。[*]接尾辞で宣言。this_image()がランクを返す、num_images()がカウント。x[k]でリモートアクセス。sync allがバリア。コンパイラ:gfortran(-fcoarray=lib付き)、ifort、Cray。

fortran
program coarray_hello
  use iso_fortran_env, only: real64
  implicit none
  real(real64) :: x[*]  ! coarray — one copy per image
  integer :: me

  me = this_image()
  x = real(me, real64)  ! local assignment

  call co_sum(x, result_image=1)  ! reduce sum to image 1
  if (me == 1) print *, 'Sum =', x

  sync all  ! barrier
end program

リモートアクセスと同期

[k]接尾辞でリモートcoarrayにアクセス — 一側通信。読み書きはsyncまで非ブロッキング。sync allがグローバルバリア、sync images([1,2])が特定のイメージを待つ。クリティカルセクション:critical...end criticalでlock/unlock。同期を一貫して順序付けでデッドロックを回避。

fortran
program remote_access
  implicit none
  integer :: val[*], neighbor
  integer :: me, n

  me = this_image()
  n = num_images()
  val = me * 10

  sync all  ! ensure all writes complete

  ! read from neighbor (circular)
  neighbor = merge(1, me + 1, me == n)
  print *, 'Image', me, 'sees neighbor', neighbor, 'value', val[neighbor]

  ! write to image 1 from all
  if (me /= 1) val[1] = val[me]
  sync all
  if (me == 1) print *, 'Image 1 received:', val
end program

集団操作

集団操作:co_sum、co_min、co_max、co_broadcast。これらはcoarray上で動作しイメージ間で還元/ブロードキャスト。result_imageが誰が答えを得るか指定(デフォルト:全員)。broadcast用にsource_image。他のイメージで集団結果を読む前に常に同期。sync付きの手動ループより高速。

fortran
program collectives
  use iso_fortran_env, only: real64
  implicit none
  real(real64) :: local_sum, global_sum[*]
  integer :: i

  local_sum = 0.0_real64
  do i = 1, 100
    local_sum = local_sum + real(i * this_image(), real64)
  end do

  global_sum = local_sum
  call co_sum(global_sum, result_image=1)
  if (this_image() == 1) print *, 'Total:', global_sum

  ! other collectives: co_min, co_max, co_broadcast
  call co_broadcast(global_sum, source_image=1)
end program

Coarray派生型

派生型はcoarrayにできる — すべてのコンポーネントがイメージごとに複製。p[k]%fieldでリモートコンポーネントにアクセス。coarray内の割り付け可能コンポーネントにはFortran 2018+が必要(一部のコンパイラはサポートしない可能性)。粒子の配列にはtype(particle), allocatable :: particles(:)[:]を使用。

fortran
program coarray_types
  implicit none
  type :: particle
    real :: x, y, z
    real :: mass
  end type
  type(particle) :: p[*]
  integer :: me

  me = this_image()
  p%mass = real(me)
  p%x = real(me) * 0.5

  sync all
  ! access component of remote coarray
  if (me == 1) print *, 'Image 2 mass:', p[2]%mass
end program

割り付け可能coarrayとチーム

割り付け可能coarrayは[*]接尾辞ですべてのイメージで同時に割り付け。チーム(F2018)がイメージを独立したグループに分割 — 各グループは独自のthis_image/num_imagesを持つ。form teamがチームを作成、change teamがスコープに入る。階層的並列処理に便利。コンパイラサポートは様々(ifort、gfortran 9+)。

fortran
program alloc_coarrays
  use iso_fortran_env, only: team_type
  implicit none
  integer, allocatable :: data(:)[:]
  type(team_type) :: odd_team, even_team
  integer :: me

  me = this_image()
  allocate(data(100)[*])  ! allocate on all images

  ! F2018 teams — split images into groups
  form team(merge(1, 2, mod(me, 2) == 1), odd_team, even_team)
  if (mod(me, 2) == 1) then
    change team(odd_team)
      ! this_image() and num_images() now refer to team
      data(this_image()) = me
    end team
  end if

  deallocate(data)
end program
11

Cとの相互運用

ISO_C_BINDINGの基本

iso_c_bindingがC互換kind(c_int、c_double、c_charなど)を提供。bind(C, name='...')がFortranをCに特定のシンボル名で公開。C文字列にはc_null_charターミネータが必要。interfaceブロックがC関数シグネチャを宣言。CとFortranを別々にコンパイル、一緒にリンク。

fortran
program c_interop
  use iso_c_binding, only: c_int, c_double, c_char, c_null_char
  implicit none
  interface
    subroutine c_print(msg) bind(C, name='c_print')
      import :: c_char
      character(kind=c_char), dimension(*) :: msg
    end subroutine
  end interface

  call c_print('Hello from Fortran' // c_null_char)
end program

! Corresponding C:
!   void c_print(const char* msg) { printf("%s\n", msg); }

Cへの配列渡し

配列はc_loc(Cポインタを取得)とc_ptr型で渡す。スカラーC引数には'value'属性を使用(参照ではなく値渡し)。Fortran配列は列優先、Cは行優先 — 2D配列を転置するか慣習を文書化。c_f_pointerがCポインタをFortranポインタに逆変換。

fortran
program array_interop
  use iso_c_binding, only: c_double, c_int, c_loc, c_f_pointer
  implicit none
  real(c_double), target :: arr(10)
  integer(c_int) :: n
  type(c_ptr) :: ptr

  arr = [(real(i), i=1,10)]
  ptr = c_loc(arr(1))  ! get C pointer

  ! call C function: void process(double* arr, int n);
  call process_c(ptr, size(arr, kind=c_int))

  interface
    subroutine process_c(arr, n) bind(C, name='process')
      import :: c_double, c_int, c_ptr
      type(c_ptr), value :: arr
      integer(c_int), value :: n
    end subroutine
  end interface
end program

C相互運用可能な型

bind(C)を持つ型はC互換メモリレイアウト — 構造体をCに渡すために必要。C互換kindのみ許可(デフォルトreal/integerなし)。割り付け可能/ポインタコンポーネントなし。固定長char配列がC文字列をエミュレート。順序が重要 — bind(C)なしでFortranがコンポーネントを並べ替える可能性。

fortran
module data_types
  use iso_c_binding, only: c_double, c_int, c_char
  implicit none

  type, bind(C) :: point
    real(c_double) :: x, y
    integer(c_int) :: id
  end type

  type, bind(C) :: string_holder
    character(kind=c_char, len=1) :: name(64)
  end type
end module

! C equivalent:
!   struct point { double x, y; int id; };
!   struct string_holder { char name[64]; };

CからFortranの呼び出し

bind(C, name='...')を持つサブルーチンはその名前でCから呼び出し可能。Cが値渡しするスカラーには'value'を使用、配列は参照渡し(valueなし)。関数結果はC互換スカラーでなければならない。コンパイラの名前マングリング(アンダースコア、ケース変更)を回避するためCバインディング名を使用。

fortran
! Fortran:
module fmod
  use iso_c_binding, only: c_double
  implicit none
contains
  subroutine square_array(arr, n) bind(C, name='square_array')
    integer, value :: n
    real(c_double), intent(inout) :: arr(n)
    arr = arr ** 2
  end subroutine
end module

! C caller:
!   extern void square_array(double* arr, int n);
!   double data[5] = {1, 2, 3, 4, 5};
!   square_array(data, 5);

C関数ポインタとコールバック

abstract interfaceがC関数シグネチャを宣言。procedure(iface)がマッチする関数を引数として受け入れる。C関数ポインタを直接渡す。c_funlocがFortranプロシージャのCアドレスを取得、c_f_procpointerがc_funptrをFortranプロシージャに逆変換。qsort風コールバックに便利。

fortran
module callbacks
  use iso_c_binding, only: c_funloc, c_funptr, c_int
  implicit none

  abstract interface
    function comparator(a, b) bind(C)
      import :: c_int
      integer(c_int), value :: a, b
      integer(c_int) :: comparator
    end function
  end interface

contains
  subroutine sort_with_c(arr, n, cmp) bind(C)
    integer(c_int), value :: n
    integer(c_int), intent(inout) :: arr(n)
    procedure(comparator) :: cmp
    ! ... use cmp(a, b) to compare ...
  end subroutine
end module

! C side:
!   int descending(int a, int b) { return b - a; }
!   sort_with_c(arr, n, descending);
12

オブジェクト指向(extends/final)

型拡張(継承)

型拡張 = 継承。'extends(parent)'がサブクラスを宣言。'class(T)'はポリモーフィック(Tまたは任意の拡張を受け入れる)、'type(T)'は正確。'abstract' + 'deferred' = 抽象メソッド(オーバーライド必須)。'contains'が型束縛プロシージャを導入。同じ名前でプロシージャを再宣言してオーバーライド。

fortran
module shapes
  implicit none
  type, abstract :: shape
    real :: x = 0, y = 0
  contains
    procedure(area_iface), deferred :: area
    procedure :: move => shape_move
  end type

  abstract interface
    function area_iface(this) result(a)
      import :: shape
      class(shape), intent(in) :: this
      real :: a
    end function
  end interface

  type, extends(shape) :: circle
    real :: radius
  contains
    procedure :: area => circle_area
  end type

contains
  subroutine shape_move(this, dx, dy)
    class(shape), intent(inout) :: this
    real, intent(in) :: dx, dy
    this%x = this%x + dx
    this%y = this%y + dy
  end subroutine

  function circle_area(this) result(a)
    class(circle), intent(in) :: this
    real :: a
    a = 3.14159 * this%radius ** 2
  end function
end module

ポリモーフィズムとSELECT TYPE

select typeがポリモーフィック変数でランタイム型判別を行う。'type is (T)'が正確な型にマッチ、'class is (T)'がTと拡張にマッチ。ブロック内で変数はマッチした型として扱われる(特定のコンポーネントにアクセス)。ポリモーフィック配列はclass(shape)経由で混合型を保持 — ただし割り付けは要素ごとに必要。

fortran
module polymorph
  use shapes, only: shape, circle, rectangle
  implicit none
contains
  subroutine describe(s)
    class(shape), intent(in) :: s
    select type(s)
    type is (circle)
      print *, 'Circle radius:', s%radius
    type is (rectangle)
      print *, 'Rectangle:', s%width, 'x', s%height
    class is (shape)
      print *, 'Some shape at:', s%x, s%y
    class default
      print *, 'Unknown type'
    end select
  end subroutine

  subroutine process_all(shapes)
    class(shape), intent(in) :: shapes(:)
    integer :: i
    do i = 1, size(shapes)
      call describe(shapes(i))
      print *, 'Area:', shapes(i)%area()
    end do
  end subroutine
end module

ファイナライザとデストラクタ

finalプロシージャは変数がスコープを抜ける時に自動的に実行 — C++デストラクタのようなもの。'final :: name'で定義。TYPE(classではなく)を取るサブルーチンでなければならない — ポリモーフィズムなし。1つの型に複数のファイナライザを持てる(ランクでオーバーロード)。ファイルクローズ、メモリ解放、リソース解放に使用。失敗/raise不可。

fortran
module resources
  implicit none
  type :: file_handle
    integer :: unit = -1
  contains
    final :: close_file
    procedure :: open
  end type
contains
  subroutine open(this, filename)
    class(file_handle), intent(inout) :: this
    character(*), intent(in) :: filename
    open(newunit=this%unit, file=filename, status='old')
  end subroutine

  subroutine close_file(this)
    type(file_handle), intent(inout) :: this
    if (this%unit /= -1) then
      close(this%unit)
      this%unit = -1
    end if
  end subroutine
end module

program demo
  use resources
  type(file_handle) :: f
  call f%open('data.txt')
  ! ... use file ...
end program  ! f goes out of scope -> close_file called automatically

コンストラクタと割り付け

型名を持つジェネリックinterfaceがカスタムコンストラクタとして機能 — デフォルト構造コンストラクタをオーバーロード。複数のプロシージャで異なる引数セットを許可。デフォルトコンストラクタ(point(x=..., y=...))はオーバーライドしない限り利用可能。ポリモーフィック割り付け:allocate(circle :: shape_var)がclass(shape)変数にcircleを作成。

fortran
module points
  implicit none
  type :: point
    real :: x, y
  contains
    procedure :: norm
  end type
  interface point
    procedure new_point
    procedure new_point_polar
  end interface
contains
  function new_point(x, y) result(p)
    real, intent(in) :: x, y
    type(point) :: p
    p%x = x
    p%y = y
  end function

  function new_point_polar(r, theta) result(p)
    real, intent(in) :: r, theta
    type(point) :: p
    p%x = r * cos(theta)
    p%y = r * sin(theta)
  end function

  function norm(this) result(n)
    class(point), intent(in) :: this
    real :: n
    n = sqrt(this%x**2 + this%y**2)
  end function
end module

program use_points
  use points
  type(point) :: a, b
  a = point(1.0, 2.0)         ! cartesian
  b = point(3.0, 0.5)         ! polar (same name, different args)
  print *, a%norm(), b%norm()
end program

抽象型とテンプレート

抽象型はインスタンス化不可 — 拡張のみ。遅延プロシージャは具象サブクラスでオーバーライド必須。class(*)は無制限ポリモーフィック — 任意の型を保持(回復にselect typeを使用)。このパターンが抽象基底クラスとインターフェースを実装。具象コンテナ(list、stack、queue)が拡張して遅延プロシージャを実装。

fortran
module container
  implicit none
  type, abstract :: container
  contains
    procedure(add_iface), deferred :: add
    procedure(size_iface), deferred :: size
    procedure :: is_empty => container_is_empty
  end type

  abstract interface
    subroutine add_iface(this, item)
      import :: container
      class(container), intent(inout) :: this
      class(*), intent(in) :: item
    end subroutine
    function size_iface(this) result(n)
      import :: container
      class(container), intent(in) :: this
      integer :: n
    end function
  end interface
contains
  function container_is_empty(this) result(e)
    class(container), intent(in) :: this
    logical :: e
    e = (this%size() == 0)
  end function
end module

! Concrete subclass implements add/size
! class(*) allows storing any type (unlimited polymorphic)
13

パラメータ化派生型

基本的なPDT宣言

パラメータ化派生型(PDT、F2003)はC++テンプレートのようなもの。'kind'パラメータはコンパイル時(インスタンスごとに固定)、'len'パラメータはランタイム(':'で遅延可能)。allocate(type(params) :: var)で遅延長型を割り付け。PDTでプリプロセッサのトリックなしの型安全なジェネリックコンテナを実現。

fortran
module pdt_types
  implicit none
  type :: matrix(k, n, m)
    integer, kind :: k = kind(1.0)
    integer, len :: n, m
    real(k) :: data(n, m)
  end type

  ! kind parameters: compile-time (like template params)
  ! len parameters: runtime (like allocatable dimensions)
end module

program use_pdt
  use pdt_types
  implicit none
  type(matrix(kind(1.0), 3, 3)) :: small  ! fixed 3x3
  type(matrix(kind(1.d0), :, :)), allocatable :: big  ! deferred

  small%data = 0.0
  allocate(matrix(kind(1.d0), 100, 100) :: big)
  big%data = 0.d0
  deallocate(big)
end program

kindパラメータ付きPDT

kindパラメータ化型で複数の精度で動作する1つの型を記述可能。関数戻り値の型はthis%kを使用してマッチ。プロシージャのclass(array_t(k=*))構文に注意 — kindパラメータ型に必須。コンパイラサポート:gfortran 9+、ifort。単精度/倍精度/四倍精度をサポートするライブラリに便利。

fortran
module typed_array
  implicit none
  type :: array_t(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:)
  contains
    procedure :: sum_values
  end type
contains
  function sum_values(this) result(s)
    class(array_t(k=*)), intent(in) :: this
    real(this%k) :: s
    s = sum(this%data)
  end function
end module

program test
  use typed_array
  type(array_t(kind(1.0)))  :: float_arr
  type(array_t(kind(1.d0))) :: double_arr

  allocate(float_arr%data(10))
  allocate(double_arr%data(10))
  float_arr%data = 1.0
  double_arr%data = 1.d0

  print *, float_arr%sum_values()
  print *, double_arr%sum_values()
end program

長さパラメータ付きPDT

長さパラメータ化型はサイズを型パラメータとして運ぶ。fstr(5)とfstr(6)は異なる型。プロシージャはfstr(*)で任意の長さを受け入れる。結果型は入力長に依存可能(cは長さa%n + b%nを持つ)。固定サイズバッファ、静的サイズ配列に便利。コンパイラサポートは限定的 — 徹底的にテスト。

fortran
module fixed_string
  implicit none
  type :: fstr(n)
    integer, len :: n
    character(len=n) :: str
  end type
contains
  function concat(a, b) result(c)
    type(fstr(*)), intent(in) :: a, b
    type(fstr(a%n + b%n)) :: c
    c%str = a%str // b%str
  end function
end module

program test
  use fixed_string
  type(fstr(5))  :: a = fstr(5)('Hello')
  type(fstr(6))  :: b = fstr(6)(' World')
  type(fstr(11)) :: c

  c = concat(a, b)
  print *, c%str
end program

PDT割り付け可能配列

PDTは割り付け可能コンポーネントを含める可能。kindパラメータはコンポーネント型に伝播(real(this%k))。move_allocが効率的に割り付けを転送(コピーなし)。プロシージャでclass(stack(k=*))を使用して任意のkindを受け入れる。割り付け可能コンポーネント付きPDTがジェネリック型付けと動的サイズ設定を組み合わせ。

fortran
module stack_t
  implicit none
  type :: stack(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:)
    integer :: top = 0
  contains
    procedure :: push
    procedure :: pop
    procedure :: is_empty
  end type
contains
  subroutine push(this, val)
    class(stack(k=*)), intent(inout) :: this
    real(this%k), intent(in) :: val
    if (this%top == size(this%data)) then
      block
        real(this%k), allocatable :: tmp(:)
        tmp = [this%data, val]
        call move_alloc(tmp, this%data)
      end block
    else
      this%top = this%top + 1
      this%data(this%top) = val
    end if
  end subroutine
end module

PDTの制限と回避策

PDTサポートは様々 — kindパラメータは広くサポート、長さパラメータはそうでない。ランタイムサイズ設定にはlenパラメータより割り付け可能コンポーネントを推奨。PDT配列(type(matrix(4,4)) :: arr(10))はすべてのコンパイラで動作しない可能性。ターゲットコンパイラでテスト。最大の移植性にはプリプロセッサ(#define)またはジェネリックinterfaceを使用。

fortran
! Limitations of PDTs:
! - Not all compilers fully support len parameters
! - I/O of PDTs may not work as expected
! - Some F2003 features (e.g., PDT arrays) have spotty support

! Workaround: use allocatable components instead of len params
module alt_matrix
  implicit none
  type :: matrix_alt(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:,:)
  end type
contains
  function make_matrix(k_, n, m) result(mat)
    integer, intent(in) :: n, m
    ! kind must be compile-time constant — can't pass dynamically
    ! workaround: separate constructors per kind
  end function
end module

! Best practice: use kind params (well-supported), avoid len params
! Use allocatable components for runtime sizing instead
14

サブモジュール

基本的なサブモジュール構造

サブモジュール(F2008)がinterfaceを実装から分離。モジュールがinterfaceを宣言、サブモジュールが本体を提供。サブモジュール本体の変更は依存関係の再コンパイルを引き起こさない — interfaceの変更のみ。'module procedure'で実装。コンパイルが高価な大規模ライブラリに最適。

fortran
! parent_module.f90
module math_lib
  implicit none
  interface
    module function integrate(f, a, b, n) result(s)
      abstract interface
        real function func_t(x)
          real, intent(in) :: x
        end function
      end interface
      procedure(func_t) :: f
      real, intent(in) :: a, b
      integer, intent(in) :: n
      real :: s
    end function
  end interface
end module

! sub_math.f90
submodule(math_lib) math_impl
  implicit none
contains
  module procedure integrate
    integer :: i
    real :: dx, x
    dx = (b - a) / n
    s = 0.5 * (f(a) + f(b))
    do i = 1, n-1
      x = a + i * dx
      s = s + f(x)
    end do
    s = s * dx
  end procedure
end submodule

モジュールごとの複数サブモジュール

モジュールは複数のサブモジュールを持てる — 実装をファイルに分割。各サブモジュールは'submodule(parent) name'で開始。同じ親モジュールのプロシージャは互いに呼び出し可能。これがインクリメンタルコンパイルを可能に:1つのサブモジュールを編集、それのみを再コンパイルしてリンク。非常に大きなモジュールに便利。

fortran
! big_lib.f90
module big_lib
  implicit none
  interface
    module subroutine sort(arr)
      real, intent(inout) :: arr(:)
    end subroutine
    module function mean(arr) result(m)
      real, intent(in) :: arr(:)
      real :: m
    end function
    module function stddev(arr) result(s)
      real, intent(in) :: arr(:)
      real :: s
    end function
  end interface
end module

! sub_sort.f90 — one submodule
submodule(big_lib) sort_impl
contains
  module procedure sort
    ! quicksort implementation
  end procedure
end submodule

! sub_stats.f90 — another submodule
submodule(big_lib) stats_impl
contains
  module procedure mean
    m = sum(arr) / size(arr)
  end procedure
  module procedure stddev
    block
      real :: mu
      mu = mean(arr)  ! can call sibling module procedures
      s = sqrt(sum((arr - mu)**2) / (size(arr) - 1))
    end block
  end procedure
end submodule

サブモジュール専用内部プロシージャ

サブモジュールは親モジュールのinterface経由で見えないプライベートヘルパープロシージャを含める可能。これで実装の詳細を隠しつつコロケーションを保持。'module procedure'実装のみが親経由でアクセス可能。ヘルパーは内部に留まる — モジュールのcontainsブロックにすべてを置くより良いカプセル化。

fortran
module sparse_ops
  implicit none
  interface
    module function sparse_multiply(a, b) result(c)
      ! ... sparse matrix types ...
      real, allocatable :: c(:,:)
    end function
  end interface
end module

submodule(sparse_ops) sparse_impl
  implicit none
contains
  module procedure sparse_multiply
    ! call helper — invisible to module users
    call validate_dimensions(a, b)
    c = multiply_kernel(a, b)
  end procedure

  ! private helper — not exposed via parent module
  subroutine validate_dimensions(a, b)
    ! ...
  end subroutine

  function multiply_kernel(a, b) result(c)
    ! ...
  end function
end submodule

モジュール変数の継承

サブモジュールはホスト結合経由で親モジュールの変数と型にアクセス可能。モジュールレベルの状態を読み取りおよび変更可能。これは実装ビヘイビアに影響する設定に便利。モジュール変数の変更は依然としてサブモジュール(と依存関係)の再コンパイルが必要。

fortran
module config
  implicit none
  integer :: verbosity = 0
  interface
    module subroutine log_message(msg)
      character(*), intent(in) :: msg
    end subroutine
  end interface
end module

submodule(config) config_impl
  implicit none
  ! submodule can access parent module's variables
contains
  module procedure log_message
    if (verbosity > 0) then
      print *, '[LOG]', msg
    end if
  end procedure
end submodule

! Usage:
!   use config
!   verbosity = 1
!   call log_message('Starting up')

サブモジュールのコンパイル利点

主な利点:サブモジュール内の実装変更は親モジュールを'use'するだけのコードの再コンパイルを引き起こさない。サブモジュール自体のみが再コンパイル。これが大規模Fortranプロジェクトのインクリメンタルビルドを劇的に高速化。頻繁に変更される実装をサブモジュールに再構成、安定したinterfaceは親に保持。

fortran
! Without submodules:
!   module big_mod
!     ... 5000 lines of implementation ...
!   end module
!   ! Any change -> recompile big_mod + all users

! With submodules:
!   module big_mod
!     ! just interfaces (~200 lines)
!   end module
!   submodule(big_mod) impl
!     ! 5000 lines of implementation
!   end submodule
!   ! Change impl body -> recompile only submodule
!   ! Change interface -> recompile everything (unavoidable)

! Build system example (Makefile):
!   big_mod.o: big_mod.f90
!   impl.o: impl.f90 big_mod.o
!   user.o: user.f90 big_mod.o  ! NOT impl.o
!   app: user.o impl.o big_mod.o
!   	$(FC) -o app user.o impl.o big_mod.o
15

IEEE浮動小数点

IEEE例外とフラグ

ieee_exceptionsがIEEE例外フラグ(overflow、underflow、divide_by_zero、invalid、inexact)へのアクセスを提供。ieee_get_flagが読み取り、ieee_set_flagがクリア。ieee_set_halting_modeが例外でプログラムを停止するか制御。ieee_arithmeticがieee_is_nan、ieee_is_finite、ieee_is_negativeなどを提供。重要な計算後に常にフラグをチェック。

fortran
program ieee_exceptions
  use ieee_exceptions
  use ieee_arithmetic, only: ieee_is_nan, ieee_is_finite
  implicit none
  real :: x, y
  logical :: overflow_flag

  call ieee_set_halting_mode(ieee_overflow, .false.)
  x = huge(x) * 10  ! overflow -> Inf, no halt

  call ieee_get_flag(ieee_overflow, overflow_flag)
  print *, 'Overflow occurred:', overflow_flag

  y = 0.0 / 0.0  ! NaN
  print *, 'x is finite:', ieee_is_finite(x)
  print *, 'y is NaN:', ieee_is_nan(y)

  call ieee_set_flag(ieee_all, .false.)  ! clear all flags
end program

NaNとInfの処理

InfとNaNはIEEE特殊値。NaN != NaN(テストにieee_is_nanを使用)。Infは算術を通じて伝播。NaNを生成する操作:0/0、Inf-Inf、0*Inf、sqrt(-1)。これらで停止するには-ffpe-trap=invalid,zero,overflowでコンパイル(デバッグ用)。本番コードは危険な操作後にNaNをチェックすべき。

fortran
program nan_inf
  use ieee_arithmetic, only: ieee_value, ieee_nan, &
                             ieee_positive_inf, ieee_negative_inf
  implicit none
  real :: pos_inf, neg_inf, nan_val

  pos_inf = ieee_value(0.0, ieee_positive_inf)
  neg_inf = ieee_value(0.0, ieee_negative_inf)
  nan_val = ieee_value(0.0, ieee_nan)

  print *, 'Inf - Inf =', pos_inf - pos_inf  ! NaN
  print *, '0 * Inf =', 0.0 * pos_inf         ! NaN
  print *, 'Inf > 1e30:', pos_inf > 1e30       ! T
  print *, 'NaN == NaN:', nan_val == nan_val   ! F (always!)

  ! generate via arithmetic
  pos_inf = 1.0 / 0.0  ! may halt without -fno-trapping-math
end program

丸めモード

IEEEは4つの丸めモードをサポート:最近接(デフォルト)、切り下げ、切り上げ、ゼロへ。ieee_set_rounding_modeが実行時に変更。区間演算に便利(切り上げ/切り下げで上限/下限を計算)。変更されるまで後続のすべてのFP操作に影響。完了時に最近接に戻す。一部のコンパイラは最近接を仮定して最適化 — 注意して使用。

fortran
program rounding_modes
  use ieee_arithmetic, only: ieee_set_rounding_mode, &
                             ieee_nearest, ieee_down, ieee_up, ieee_to_zero
  implicit none
  real :: x, y, result

  x = 1.0 / 3.0
  print *, 'Nearest:', x

  call ieee_set_rounding_mode(ieee_down)
  result = 1.0 / 3.0
  print *, 'Down:', result

  call ieee_set_rounding_mode(ieee_up)
  result = 1.0 / 3.0
  print *, 'Up:', result

  call ieee_set_rounding_mode(ieee_to_zero)
  result = 1.0 / 3.0
  print *, 'To zero:', result

  call ieee_set_rounding_mode(ieee_nearest)  ! restore default
end program

IEEE機能の問い合わせ

ieee_featuresとieee_arithmetic問い合わせ関数でコンパイラ/プラットフォームがサポートするIEEE機能をチェック可能。ieee_support_nan、ieee_support_inf、ieee_support_rounding、ieee_support_datatypeなど。ポータブルコードに便利 — 非IEEEシステムでグレースフルに劣化。ほとんどのモダンシステムはすべての機能をサポート。

fortran
program ieee_inquiry
  use ieee_features
  use ieee_arithmetic, only: ieee_support_nan, ieee_support_inf, &
                             ieee_support_rounding, ieee_nearest
  implicit none

  if (ieee_support_nan(1.0)) print *, 'NaN supported'
  if (ieee_support_inf(1.0)) print *, 'Inf supported'
  if (ieee_support_rounding(ieee_nearest, 1.0)) &
    print *, 'Nearest rounding supported'

  ! Check halting mode
  block
    use ieee_exceptions
    logical :: halting
    call ieee_get_halting_mode(ieee_overflow, halting)
    print *, 'Overflow halts:', halting
  end block
end program

FP環境の制御

セクションごとにFPビヘイビアを制御:危険なコードで停止を無効化、後にフラグをチェック、復元。ieee_allがすべての例外にマッチ。パターン:フラグをクリア、計算を実行、フラグをチェック、エラーを処理。本番には、フラグベースの検出より明示的チェック(ieee_is_nan)を推奨 — フラグは無関係なコードで設定される可能性。

fortran
program fp_env
  use ieee_arithmetic
  use ieee_exceptions
  implicit none
  real :: a, b, c

  ! Save/restore FP state
  block
    use iso_fortran_env, only: real32
    real(real32) :: saved_state
    ! (no portable save/restore in standard — use compiler intrinsics)
  end block

  ! Disable halting for a risky section
  call ieee_set_halting_mode(ieee_all, .false.)
  call ieee_set_flag(ieee_all, .false.)

  a = sqrt(-1.0)  ! NaN, no halt
  b = 1.0 / 0.0   ! Inf, no halt

  if (ieee_is_nan(a)) then
    print *, 'Got NaN, using fallback'
    a = 0.0
  end if

  ! Re-enable halting
  call ieee_set_halting_mode(ieee_divide_by_zero, .true.)
  call ieee_set_halting_mode(ieee_invalid, .true.)
end program
16

パフォーマンスと最適化

配列順序と連続性

Fortran配列は列優先:a(i,j)とa(i+1,j)はメモリ内で隣接。ループ順序が重要:最内ループが最初のインデックスを反復すべき。間違った順序はキャッシュミスを引き起こす — 10倍以上の減速。配列構文(sum、matmul)でコンパイラが最適化可能。最高パフォーマンスにはポインタではなく連続配列を使用。

fortran
program array_perf
  implicit none
  real, allocatable :: a(:,:), b(:,:)
  integer :: i, j, n
  real :: s

  n = 1000
  allocate(a(n,n), b(n,n))

  ! GOOD: column-major access (Fortran is column-major)
  do j = 1, n
    do i = 1, n
      s = s + a(i,j)
    end do
  end do

  ! BAD: row-major access (cache misses)
  do i = 1, n
    do j = 1, n
      s = s + a(i,j)
    end do
  end do

  ! BEST: array syntax (compiler optimizes)
  s = sum(a)
end program

Pureとelementalプロシージャ

pureプロシージャは副作用なし — コンパイラが最適化、並列化、呼び出し並べ替え可能。elementalプロシージャはスカラーと配列の両方で動作(自動ベクトル化)。pure elementalが両方を組み合わせ。数学関数にこれらを使用。制限:I/Oなし、グローバル変更なし、すべての入力にintent(in)。通常のプロシージャより良い最適化を有効化。

fortran
module math_ops
  implicit none
contains
  ! pure: no side effects, enables optimization
  pure function square(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x
  end function

  ! elemental: works on scalars and arrays element-wise
  pure elemental function clamp(x, lo, hi) result(y)
    real, intent(in) :: x, lo, hi
    real :: y
    y = max(lo, min(x, hi))
  end function
end module

program use_math
  use math_ops
  implicit none
  real :: arr(100), scalar

  arr = clamp(arr, 0.0, 1.0)  ! applies to each element
  scalar = clamp(1.5, 0.0, 1.0)  ! also works on scalar
  arr = square(arr)  ! elemental too (if declared)
end program

コンパイラ最適化フラグ

本番には-O2 -march=nativeで開始。-O3は役立つことも害することも — ベンチマーク。-OfastはIEEE準拠を破る(NaN/Infが重要なら使用しない)。-fltoがクロスファイルインライン化を有効化(コンパイル遅い、実行高速)。デバッグビルドでエラーをキャッチするには-fcheck=allと-ffpe-trapを使用。プロファイルガイド最適化(-fprofile-use)が5-15%のスピードアップを与える。

fortran
! gfortran optimization flags:
!   -O0   no optimization (debug)
!   -O1   basic optimization
!   -O2   standard optimization (recommended)
!   -O3   aggressive (may increase code size)
!   -Ofast  -O3 + -ffast-math (breaks IEEE compliance)
!   -march=native  use CPU's full instruction set
!   -flto  link-time optimization (cross-file inlining)
!   -fopenmp  enable OpenMP pragmas
!   -fprofile-generate/use  profile-guided optimization

! Example Makefile:
!   FC = gfortran
!   FFLAGS = -O2 -march=native -Wall -fcheck=all
!   FFLAGS_RELEASE = -O3 -march=native -flto -fno-trapping-math
!   FFLAGS_DEBUG = -O0 -g -fcheck=all -fbacktrace -ffpe-trap=invalid,zero

! ifort equivalents:
!   -O2, -O3, -xHost (= -march=native), -ipo (= -flto)

OpenMP並列処理

OpenMPが指令(!$omp)経由で並列処理を追加。'parallel do'が次のループを並列化。'reduction(+:s)'が合計を安全に処理。-fopenmp(gfortran)または-qopenmp(ifort)でコンパイル。OMP_NUM_THREADS環境変数でスレッド数を設定。独立した反復を持つCPUバウンドループに最適。偽共有と負荷不均衡に注意。

fortran
program openmp_demo
  use omp_lib
  implicit none
  real, allocatable :: a(:), b(:), c(:)
  integer :: i, n, nthreads

  n = 10000000
  allocate(a(n), b(n), c(n))
  a = 1.0; b = 2.0

  !$omp parallel
  if (omp_get_thread_num() == 0) then
    nthreads = omp_get_num_threads()
    print *, 'Threads:', nthreads
  end if
  !$omp end parallel

  !$omp parallel do
  do i = 1, n
    c(i) = a(i) + b(i)
  end do
  !$omp end parallel do

  ! reduction
  block
    real :: s
    s = 0.0
    !$omp parallel do reduction(+:s)
    do i = 1, n
      s = s + c(i)
    end do
    !$omp end parallel do
    print *, 'Sum:', s
  end block
end program

プロファイリングとホットスポット検出

system_clockがポータブルタイミングを与える。本格的なプロファイリングにはgprof(-pgでコンパイル)、perf(Linux)、またはIntel VTuneを使用。最適化前にプロファイル — 驚きが多い。ホットスポット(時間の80%を取る20%のコード)に集中。最内ループを最初に最適化。変更の前後に常にベンチマーク — 直感はしばしば間違っている。

fortran
! Compile with profiling:
!   gfortran -pg -O2 prog.f90 -o prog
!   ./prog  (generates gmon.out)
!   gprof prog gmon.out > profile.txt

! Or use perf (Linux):
!   perf record ./prog
!   perf report

! Or gprofng (modern):
!   gprofng collect app ./prog
!   gprofng analyze

! Manual timing:
program timing
  use iso_fortran_env, only: real64, int64
  implicit none
  integer(int64) :: start, finish, rate
  real(real64) :: elapsed
  real, allocatable :: a(:)
  integer :: i

  allocate(a(10000000))
  call system_clock(start, rate)
  do i = 1, 100
    a = a + 1.0
  end do
  call system_clock(finish)
  elapsed = real(finish - start, real64) / real(rate, real64)
  print *, 'Elapsed:', elapsed, 'sec'
end program
17

混合言語プログラミング

FortranからCライブラリの呼び出し

Cライブラリ関数をFortran interfaceモジュールでラップ。bind(C, name='...')で正確なCシンボルにリンク。ポインタを'value'付きのc_ptrとして渡す。関数ポインタ引数(qsortのコンパレータなど)にはabstract interfaceを使用。これでlibc、BLAS、システムコールを含む任意のC関数を呼び出し可能。

fortran
module c_glue
  use iso_c_binding
  implicit none
  interface
    function c_malloc(size) bind(C, name='malloc')
      import :: c_ptr, c_size_t
      integer(c_size_t), value :: size
      type(c_ptr) :: c_malloc
    end function

    subroutine c_free(ptr) bind(C, name='free')
      import :: c_ptr
      type(c_ptr), value :: ptr
    end subroutine

    subroutine c_qsort(base, nmemb, size, compar) bind(C, name='qsort')
      import :: c_ptr, c_size_t, c_int
      type(c_ptr), value :: base
      integer(c_size_t), value :: nmemb, size
      abstract interface
        function compar_fn(a, b) bind(C)
          import :: c_ptr
          type(c_ptr), value :: a, b
          integer(c_int) :: compar_fn
        end function
      end interface
      procedure(compar_fn) :: compar
    end subroutine
  end interface
end module

extern CによるC++相互運用

C++はシンボルを名前マングリングするので、C++関数をextern "C"ブロックでラップ。Fortran interfaceはその後マングリングされていない名前にバインド。-lstdc++(gfortran)でリンクまたはC++リンカを使用。これでC++ライブラリ(STL、Boost、Qt)をFortranから使用可能。C++クラスには、フラットなC APIラッパーを書き、Fortranから呼び出し。

fortran
! C++ side (wrapper.cpp):
!   extern "C" {
!     void cpp_process(double* data, int n) {
!       std::vector<double> v(data, data+n);
!       std::sort(v.begin(), v.end());
!       std::copy(v.begin(), v.end(), data);
!     }
!   }

! Fortran side:
module cpp_glue
  use iso_c_binding
  implicit none
  interface
    subroutine cpp_process(data, n) bind(C, name='cpp_process')
      import :: c_double, c_int
      real(c_double), intent(inout) :: data(*)
      integer(c_int), value :: n
    end subroutine
  end interface
end module

program use_cpp
  use cpp_glue
  implicit none
  real(c_double) :: arr(10) = [5.d0, 3.d0, 8.d0, 1.d0, 9.d0, &
                                2.d0, 7.d0, 4.d0, 6.d0, 0.d0]
  call cpp_process(arr, size(arr, kind=c_int))
  print *, arr
end program

! Build: gfortran use_cpp.f90 wrapper.cpp -lstdc++ -o app

f2pyによるPython相互運用

f2py(numpyの一部)がFortran用のPythonバインディングを自動生成。intent属性を読み取り、適切なPython/numpy interfaceを作成。intent(out)が戻り値になる。配列はnumpy配列にマップ(連続時ゼロコピー)。Pythonから呼ばれるパフォーマンスクリティカルな数値コードに最適。F2003+機能のサポートは限定的。

fortran
! Fortran module (myfuncs.f90):
module myfuncs
  implicit none
contains
  subroutine compute(arr, n, result)
    integer, intent(in) :: n
    real(8), intent(in) :: arr(n)
    real(8), intent(out) :: result
    result = sum(arr**2)
  end subroutine

  function fast_sin(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = sin(x)
  end function
end module

! Build Python extension:
!   f2py -c myfuncs.f90 -m myfuncs
!   (creates myfuncs.cpython-*.so)

! Python usage:
!   import numpy as np
!   import myfuncs
!   arr = np.array([1.0, 2.0, 3.0])
!   result = myfuncs.myfuncs.compute(arr)
!   y = myfuncs.myfuncs.fast_sin(1.5)

共有ライブラリと動的ロード

実行時ロードプラグインにはdlopen/dlsym(POSIX)またはLoadLibrary/GetProcAddress(Windows)を使用。iso_c_binding interfaceでラップ。c_f_procpointerでc_ptrをプロシージャポインタに変換。これでプラグインアーキテクチャが可能 — 実行時に異なる実装をロード。クロスプラットフォーム:OS固有ローダーに#ifdefを使用。

fortran
module dynamic_loader
  use iso_c_binding
  implicit none
  interface
    function dlopen(filename, flag) bind(C, name='dlopen')
      import :: c_ptr, c_char, c_int
      character(kind=c_char), dimension(*) :: filename
      integer(c_int), value :: flag
      type(c_ptr) :: dlopen
    end function

    function dlsym(handle, name) bind(C, name='dlsym')
      import :: c_ptr, c_char
      type(c_ptr), value :: handle
      character(kind=c_char), dimension(*) :: name
      type(c_ptr) :: dlsym
    end function

    function dlclose(handle) bind(C, name='dlclose')
      import :: c_ptr, c_int
      type(c_ptr), value :: handle
      integer(c_int) :: dlclose
    end function
  end interface
end module

program plugin
  use dynamic_loader
  implicit none
  type(c_ptr) :: lib, sym
  integer, parameter :: RTLD_NOW = 2

  lib = dlopen('./plugin.so' // c_null_char, RTLD_NOW)
  if (.not. c_associated(lib)) then
    print *, 'Failed to load library'
    stop 1
  end if

  sym = dlsym(lib, 'init' // c_null_char)
  ! call init via c_f_procpointer...
  print *, dlclose(lib)
end program

ビルドシステム統合

CMakeが混合言語ビルドをうまく処理。project()ですべての言語を宣言。言語ごとのフラグを設定。依存順にライブラリをリンク。CMakeがFortranモジュール依存関係を自動追跡(手動.mod順序付け不要)。C++標準ライブラリリンクにはプラットフォームごとに正しいフラグを使用。Fortranランタイムが必要な場合はFortranをメインリンカとして使用。

fortran
# CMakeLists.txt for mixed Fortran/C/C++ project:
cmake_minimum_required(VERSION 3.18)
project(mixed LANGUAGES Fortran C CXX)

set(CMAKE_Fortran_FLAGS "-O2 -fopenmp")
set(CMAKE_C_FLAGS "-O2")
set(CMAKE_CXX_FLAGS "-O2 -std=c++17")

add_library(fortran_lib STATIC mymod.f90)
add_library(c_lib STATIC helper.c)
add_library(cpp_lib STATIC wrapper.cpp)

# Mixed-language executable
add_executable(app main.f90)
target_link_libraries(app fortran_lib c_lib cpp_lib)

# Fortran needs C runtime
if(APPLE)
  target_link_libraries(app "-lc++")
else()
  target_link_libraries(app stdc++)
endif()

# Module dependency tracking (CMake handles automatically)
#   fortran_lib depends on its own .mod files
#   app depends on fortran_lib's .mod files
18

モダンI/O

Newunitと安全なファイル処理

newunit=がユニット番号の衝突を回避 — コンパイラが空き番号を選ぶ。open/read/writeの後に常にiostatをチェック — 非ゼロはエラー。iomsgが人間が読めるエラーメッセージを与える。action=('read'、'write'、'readwrite')が誤用を防止。status=('old'、'new'、'replace'、'scratch'、'unknown')がファイル作成を制御。

fortran
program safe_io
  implicit none
  integer :: u, ios
  character(256) :: msg

  ! newunit: compiler picks an unused unit number
  open(newunit=u, file='data.txt', status='old', action='read', &
       iostat=ios, iomsg=msg)
  if (ios /= 0) then
    print *, 'Open failed:', trim(msg)
    stop 1
  end if

  ! read with error handling
  read(u, *, iostat=ios, iomsg=msg) some_value
  if (ios /= 0) then
    print *, 'Read failed:', trim(msg)
  end if

  close(u)
contains
  integer function some_value()
    some_value = 0
  end function
end program

ストリームI/O(バイナリ)

ストリームアクセス(F2003)がCのようなバイト指向I/Oを与える — レコードマーカーなし。access='stream'で有効化。pos=が特定のバイトオフセットで読み書き。バイナリにはform='unformatted'。相互運用可能なバイナリファイルに最適(Fortran書き込みファイルをC/Pythonから読み取り)。デフォルトの順次アクセスはレコードマーカーを使用(非ポータブル)。

fortran
program stream_io
  implicit none
  integer :: u, i
  real, allocatable :: data(:)

  ! stream access = byte-oriented (like C fread/fwrite)
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='replace')

  data = [(real(i), i=1,100)]
  write(u) data  ! binary, no record markers
  close(u)

  ! read back
  allocate(data(100))
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='old', action='read')
  read(u) data
  close(u)

  ! position-based access
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='old')
  read(u, pos=21) data(1)  ! read 5th real (4 bytes each)
  close(u)
end program

派生型I/O

ユーザー定義派生型I/O(F2003)で型の読み書き方法を制御可能。特定のシグネチャを持つサブルーチンを定義し'generic :: write(formatted)'でバインド。DTフォーマットコードがそれをトリガー。カスタムシリアライズ(CSV、JSON風、バイナリ)を有効化。iotypeは'LISTDIRECTED'、'NAMELIST'、またはフォーマットI/O用'DT'。

fortran
module person_type
  implicit none
  type :: person
    character(20) :: name
    integer :: age
    real :: height
  end type
contains
  ! custom formatted I/O via DT format
  subroutine write_person(dtv, unit, iotype, v_list, iostat, iomsg)
    class(person), intent(in) :: dtv
    integer, intent(in) :: unit
    character(*), intent(in) :: iotype
    integer, intent(in) :: v_list(:)
    integer, intent(out) :: iostat
    character(*), intent(inout) :: iomsg
    write(unit, '(a,",",i0,",",f0.2)', iostat=iostat) &
      trim(dtv%name), dtv%age, dtv%height
  end subroutine
end module

program use_dtv
  use person_type
  implicit none
  type(person) :: p = person('Alice', 30, 5.7)

  ! DT format triggers custom writer
  print "(DT)", p  ! calls write_person
end program

Namelist I/O

namelistが変数グループ用の人間が読める名前ベースのI/Oを提供。形式:&group_name var=value, ... /。ファイル内の変数のみ更新 — 他は値を保持。設定ファイルに最適 — ユーザーがテキストを編集、パーサー不要。制限:一部のコンパイラでコメント不可、限定的な型サポート。

fortran
program namelist_demo
  implicit none
  integer :: n_iterations = 100
  real :: tolerance = 1.0e-6
  character(50) :: output_file = 'results.txt'
  logical :: verbose = .true.

  namelist /config/ n_iterations, tolerance, output_file, verbose

  ! write namelist
  open(newunit=u, file='config.nml', status='replace')
  write(u, nml=config)
  close(u)

  ! read namelist (only specified vars are updated)
  open(newunit=u, file='config.nml', status='old', action='read')
  read(u, nml=config)
  close(u)

  print *, 'Iterations:', n_iterations
end program

! config.nml format:
! &config
!   n_iterations = 500
!   tolerance = 1.0e-8
!   verbose = .false.
! /

非同期I/O

非同期I/O(F2003)がI/Oと計算をオーバーラップ。write(..., asynchronous='yes')が非ブロッキング操作を開始。wait(unit)が完了までブロック。大規模データセットに便利 — 次のチャンクを計算しながら書き込み開始。コンパイラサポートは様々。inquire(unit=u, pending=...)でステータスをチェック。パイプラインにはダブルバッファリングとペアに。

fortran
program async_io
  implicit none
  integer :: u1, u2, ios
  real, allocatable :: a(:), b(:)

  allocate(a(1000000), b(1000000))
  a = 1.0; b = 2.0

  open(newunit=u1, file='a.bin', access='stream', form='unformatted', &
       asynchronous='yes')
  open(newunit=u2, file='b.bin', access='stream', form='unformatted', &
       asynchronous='yes')

  ! non-blocking writes
  write(u1, asynchronous='yes') a
  write(u2, asynchronous='yes') b

  ! do other work while I/O proceeds...

  ! wait for completion
  wait(u1)
  wait(u2)

  close(u1); close(u2)
end program
19

デバッグとプロファイリング

コンパイル時デバッグフラグ

境界チェックに-fcheck=allを使用(オフバイワンエラーをキャッチ)。-ffpe-trapがNaN/Inf/オーバーフローで停止 — 数値コードに不可欠。-finit-real=nanが未初期化変数を可視化(NaNとして伝播)。-fbacktraceがクラッシュ時にスタックトレースを出力。常にこれらのフラグでデバッグ、本番ビルドでは削除(コードが遅くなる)。

fortran
! gfortran debug build:
!   gfortran -O0 -g -fcheck=all -fbacktrace -ffpe-trap=invalid,zero,overflow \
!            -Wall -Wextra -Wpedantic -finit-real=nan prog.f90

! Flag explanations:
!   -O0          no optimization (easier debugging)
!   -g           debug symbols (for gdb)
!   -fcheck=all  bounds, pointer, recursion checks
!   -fbacktrace  print stack trace on error
!   -ffpe-trap   halt on FP exceptions (invalid, zero, overflow)
!   -finit-real=nan  initialize reals to NaN (catch uninitialized)
!   -finit-integer=-9999  initialize integers to sentinel
!   -Wall -Wextra  more warnings

! Runtime error message:
!   At line 42 of file prog.f90
!   Fortran runtime error: Array bound out of bounds for dimension 1

iostatによるエラー処理

iostat:負 = EOF、ゼロ = 成功、正 = エラー。iomsgが詳細を与える。最初に文字列に読み、後に解析 — I/Oエラーと解析エラーを分離。役立つエラーメッセージのために行番号を追跡。エラーを常に明示的に処理 — サイレントな失敗はデバッグ困難。スクリプトに失敗を通知するには非ゼロコードでstopを使用。

fortran
program robust_io
  implicit none
  integer :: u, ios, line_num
  character(256) :: msg, line
  real :: value

  open(newunit=u, file='data.txt', status='old', action='read', &
       iostat=ios, iomsg=msg)
  if (ios /= 0) call error_exit('Open: ' // trim(msg))

  line_num = 0
  do
    line_num = line_num + 1
    read(u, '(a)', iostat=ios) line
    if (ios < 0) exit  ! EOF
    if (ios > 0) then
      print *, 'Read error at line', line_num
      cycle
    end if

    read(line, *, iostat=ios, iomsg=msg) value
    if (ios /= 0) then
      print *, 'Parse error at line', line_num, ':', trim(msg)
      cycle
    end if
    ! process value
  end do
  close(u)
contains
  subroutine error_exit(m)
    character(*), intent(in) :: m
    print *, 'ERROR:', m
    stop 1
  end subroutine
end program

Fortran用GDB

GDBはFortranをサポート:配列スライス、派生型コンポーネント、モジュールプロシージャ(名前形式:modname__procname)。最良のデバッグ体験には-g -O0を使用。'display'が各停止で変数を自動印刷 — ループ監視に便利。'info locals'がすべてのローカルを表示。モジュール変数には'print modname::varname'を使用。

fortran
! Compile: gfortran -g -O0 prog.f90 -o prog
! Start: gdb ./prog

! Common GDB commands for Fortran:
!   (gdb) break main           break at main
!   (gdb) break prog.f90:42     break at line 42
!   (gdb) break mymod__my_sub   break at subroutine (note double underscore)
!   (gdb) run                   start program
!   (gdb) next                  step over
!   (gdb) step                  step into
!   (gdb) print arr(5)          print array element
!   (gdb) print arr(1:10)       print array slice
!   (gdb) print mat(2,3)        print 2D array element
!   (gdb) print p%name          print derived type component
!   (gdb) display x             watch variable (auto-print on stop)
!   (gdb) backtrace             show call stack
!   (gdb) info locals           show all local variables
!   (gdb) continue              resume execution

gprofプロファイリング

gprofが実行中にプログラムカウンタをサンプリング。完全なプロファイルのためにすべてのソースファイルを-pgでコンパイル。フラットプロファイルが時間がどこで使われたかを表示、コールグラフが呼び出し階層を表示。'自己'時間が大きい関数に集中 — そこが最適化が役立つ場所。注意:-pgがタイミングを変更 — プロファイル風コードは本番で異なる動作の可能性。

fortran
! Compile with profiling:
!   gfortran -pg -O2 prog.f90 -o prog
! Run:
!   ./prog
!   (creates gmon.out)
! Analyze:
!   gprof prog gmon.out > profile.txt
!   gprof prog gmon.out | less

! profile.txt sections:
!   - Flat profile: time per function (self + cumulative)
!   - Call graph: who called whom, how many times
!   - Index: function cross-references

! Key columns:
!   %time  percentage of total time
!   cumulative  running total
!   self    seconds in function (excluding children)
!   calls   number of calls
!   self/call  average time per call (self)

! For multi-threaded: use gprofng (modern) or perf

パフォーマンスベンチマーク

適切にベンチマーク:最初にウォームアップ(キャッシュ効果)、複数回反復、最小値を取る(ノイズ最少)。count_rate付きのsystem_clockがウォール時間を与える。配列操作のスループット(要素/秒)を報告。同じマシンで同じフラグで実装を比較。注意:コンパイラが'未使用'の結果を最適化で削除する可能性 — 結果を使用(例:合計を印刷)してこれを防止。

fortran
program benchmark
  use iso_fortran_env, only: real64, int64
  implicit none
  integer, parameter :: n = 1000000
  integer, parameter :: niter = 100
  real(real64), allocatable :: a(:), b(:), c(:)
  integer(int64) :: start, finish, rate
  real(real64) :: t_start, t_end, min_time
  integer :: i, iter

  allocate(a(n), b(n), c(n))
  a = 1.0_real64; b = 2.0_real64

  ! warmup (cache, JIT-like effects)
  do i = 1, n
    c(i) = a(i) + b(i)
  end do

  min_time = huge(min_time)
  call system_clock(count_rate=rate)
  do iter = 1, niter
    call system_clock(start)
    do i = 1, n
      c(i) = a(i) + b(i)
    end do
    call system_clock(finish)
    t_start = real(start, real64) / rate
    t_end = real(finish, real64) / rate
    min_time = min(min_time, t_end - t_start)
  end do

  print *, 'Best time:', min_time * 1e6, 'us'
  print *, 'Throughput:', real(n) / min_time / 1e9, 'G elem/s'
end program
20

モダンFortran

自由形式

モダンFortran(90+)は自由形式を使用:列制限なし。コメントは!で開始。文は&で複数行にまたがれる。implicit noneが型安全のために必須。固定形式Fortran 77よりはるかに読みやすい。

fortran
program modern
    implicit none
    integer :: i
    do i = 1, 10
        print *, "Value:", i
    end do
end program modern
! Free form: no column restrictions
! Comments start with !

モジュール

モジュールが関連プロシージャとデータをグループ化。useがモジュールをインポート。containsがモジュールレベル宣言をプロシージャから分離。モジュールが明示的interfaceを提供、型チェックを有効化。外部プロシージャよりモジュールを推奨。

fortran
module math_utils
    implicit none
    contains
    function square(x) result(y)
        real, intent(in) :: x
        real :: y
        y = x * x
    end function square
end module math_utils

program test
    use math_utils
    print *, square(3.0)  ! 9.0
end program test

派生型

派生型はユーザー定義データ構造(構造体)。%がコンポーネントにアクセス。型束縛プロシージャがOOPを有効化。コンストラクタがインスタンスを作成。派生型はデフォルト値を持ち他の型を拡張可能(継承)。複雑なデータモデリングに使用。

fortran
type :: Point
    real :: x, y
end type Point
type(Point) :: p
p = Point(1.0, 2.0)  ! Constructor
p%x = 3.0  ! Component access
print *, p%x, p%y
! Type-bound procedures (OOP)
type :: Circle
    real :: radius
contains
    procedure :: area => circle_area
end type

Intent属性

intent(in)パラメータは読み取り専用(変更不可)。intent(out)は書き込み専用(プロシージャが設定)。intent(inout)は読み書き。コンパイラがintent違反をチェック。コードの明確さを向上し最適化を有効化。常にintentを指定。

fortran
subroutine process(input, output, inout)
    integer, intent(in) :: input    ! Read-only
    integer, intent(out) :: output  ! Write-only
    integer, intent(inout) :: inout ! Read-write
    output = input * 2
    inout = inout + 1
end subroutine

PureとElemental

pure関数は副作用なし(I/Oなし、変更可能な状態なし)。コンパイラが最適化可能。elemental関数はスカラーと配列の両方で自動的に動作。デフォルトでpure。数学的操作に理想的。並列実行を有効化。

fortran
pure function square(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x
end function
! Elemental: works on scalars and arrays
elemental function double_it(x) result(y)
    real, intent(in) :: x
    real :: y
    y = 2.0 * x
end function
! double_it([1,2,3]) returns [2,4,6]
21

配列

配列宣言

Fortranの配列はデフォルトで1から始まるインデックス。カスタム下限は(0:)で指定。列優先順序(最初のインデックスが最も速く変化)。allocatable配列はヒープ割り当てで、明示的に解放する必要がある。配列定数は[ ]を使用。Fortranの配列はディスクリプタによりCの配列より効率的。

fortran
! Static arrays
real :: a(10)           ! 1D, indices 1-10
real :: b(0:9)          ! 1D, indices 0-9
real :: c(3, 4)         ! 2D, 3 rows x 4 cols
! Allocatable (dynamic)
real, allocatable :: d(:)
allocate(d(100))        ! Allocate
deallocate(d)           ! Free
! Array constants
integer :: nums(5) = [1, 2, 3, 4, 5]

配列操作

Fortranは配列全体の操作をサポート:+, -, *, /, **。要素ごとの計算にループは不要。組み込み関数:sum, product, maxval, minval, any, all, count。ベクトル化によりループよりはるかに高速。これが数値計算におけるFortranの強み。

fortran
real :: a(5) = [1, 2, 3, 4, 5]
real :: b(5)
b = a * 2          ! Element-wise: [2,4,6,8,10]
b = a + 1          ! [2,3,4,5,6]
print *, sum(a)    ! 15
print *, maxval(a) ! 5
print *, any(a > 3)  ! .true.
print *, count(a > 2) ! 3

配列セクション

配列セクションは(start:end:stride)構文を使用。::はデフォルトを意味(1から末尾、ストライド1)。負のストライドは反転。多次元セクションは任意の次元で動作。セクションはプロシージャに渡せる。コピーせずにスライスする強力な機能。

fortran
real :: a(10) = [(i, i=1,10)]
print *, a(3:7)     ! Elements 3 to 7
print *, a(::2)     ! Every other: [1,3,5,7,9]
print *, a(2:8:2)   ! Stride 2: [2,4,6,8]
real :: m(3,3)
m(:, 2)             ! Second column
m(1, :)             ! First row

where構文

whereは配列レベルの条件付き代入。ベクトル化されたifのようなもの。elsewhereが偽の場合を処理。ベクトル化可能なためループより効率的。配列の要素ごとの条件付き操作に使用。

fortran
real :: a(5) = [1, -2, 3, -4, 5]
where (a > 0)
    a = a * 2       ! Double positives
elsewhere
    a = 0           ! Zero negatives
end where
! Result: [2, 0, 6, 0, 10]
! Equivalent to a loop with if

動的割り当て

allocatable配列は動的にサイズ変更可能。allocateが作成、deallocateが解放。Fortran 2003+はスコープ終了時に自動解放。allocated()で割り当て状態を確認。allocatable配列は代入時に自動再割り当て(Fortran 2003+)。Cのmalloc/freeよりはるかに安全。

fortran
real, allocatable :: matrix(:,:)
integer :: n
n = 100
allocate(matrix(n, n))
matrix = 0.0  ! Initialize all to 0
! ... use matrix ...
deallocate(matrix)
! Automatic deallocation at end of scope
! (Fortran 2003+)
22

I/O操作

書式付き出力

書式文字列が出力を制御。I5 = 整数幅5。F8.3 = 浮動小数点幅8、小数3桁。I0 = 最小幅。A = 文字列。X = 空白。/ = 改行。write(*,...)はprintと同じだがより柔軟。整列した出力に書式文字列を使用。

fortran
integer :: i = 42
real :: x = 3.14159
print "(I5, F8.3)", i, x    ! "   42    3.142"
print "(A, I0)", "Count=", i  ! "Count=42"
write(*, "(3F6.2)") 1.0, 2.0, 3.0
! Format specifiers:
! I: integer, F: float, E: exponential
! A: string, X: space, /: newline

ファイルI/O

openがファイルをユニットに接続。newunitが空きユニット番号を割り当て。status: old(存在必須)、new(存在不可)、replace。iostatはエラーやEOFで非ゼロを返す。クラッシュを避けるため常にiostatを確認。closeがファイルを切断。

fortran
integer :: unit, ios
open(newunit=unit, file="data.txt", status="old", action="read")
do
    read(unit, *, iostat=ios) value
    if (ios /= 0) exit
    print *, value
end do
close(unit)
! status: old, new, replace, scratch
! action: read, write, readwrite

ネームリスト

namelistがI/O用に変数をグループ化。ファイル形式は&config n=10, x=3.14 /。設定ファイルに便利。変数は任意の順序で指定可能。リストされた変数のみ読み書き。カスタム形式の解析よりはるかに簡単。

fortran
integer :: n = 10
real :: x = 3.14
namelist /config/ n, x
! Write namelist
open(1, file="config.nml")
write(1, nml=config)
close(1)
! Read namelist
open(1, file="config.nml")
read(1, nml=config)
close(1)

内部ファイル

内部ファイルは文字列をI/Oユニットとして使用。文字列へのwriteが値をテキストに変換。文字列からのreadがテキストを解析。型変換と書式設定に便利。trimが末尾の空白を削除。Cのsprintf/sscanfよりはるかにシンプル。

fortran
character(20) :: str
integer :: num = 42
! Integer to string
write(str, "(I0)") num
print *, trim(str)  ! "42"
! String to integer
read(str, *) num
! Internal files use character variables as units

バイナリI/O

非書式I/Oは生のバイナリデータを書き込み。テキストより高速でコンパクト。access="stream"でバイトレベルアクセス(Fortran 2008)。アーキテクチャ間でポータブルでない(エンディアン)。大規模な科学データセットに使用。書式付きI/Oは人間が読めるデータ用。

fortran
! Unformatted (binary) I/O
open(1, file="data.bin", form="unformatted", &
     access="stream")
write(1) array  ! No format, raw bytes
read(1) array2
close(1)
! Faster than formatted I/O
! Smaller file size
! Not portable across architectures
23

並列プログラミング

OpenMP

OpenMPはディレクティブでループを並列化。!$omp parallel doが反復をスレッド間に分散。private: 各スレッドが独自のコピーを持つ。reduction: 結果を結合。-fopenmpでコンパイル。数値コードを並列化する簡単な方法。

fortran
!$omp parallel do private(i) reduction(+:sum)
do i = 1, n
    sum = sum + a(i) * b(i)
end do
!$omp end parallel do
! Compile: gfortran -fopenmp program.f90
! Environment: OMP_NUM_THREADS=4

Coarrays

Coarrays(Fortran 2008)は組み込みの並列配列。各イメージ(プロセス)が独自のコピーを持つ。[N]が別のイメージのデータにアクセス。sync allがバリア。this_image()がイメージ番号を返す。言語に組み込みでライブラリ不要。

fortran
program coarray_example
    implicit none
    integer :: me[*]  ! Coarray: one per image
    me = this_image()
    sync all  ! Barrier
    if (this_image() == 1) then
        print *, "Image 2 has:", me[2]  ! Remote access
    end if
end program
! Compile: gfortran -fcoarray=single program.f90

MPIの基礎

MPI(Message Passing Interface)は分散並列処理の標準。mpi_init/finalizeが開始と終了。comm_rankがプロセスIDを取得。comm_sizeが総プロセス数を取得。Send/recvで通信。数千コアにスケール。クラスタに使用。

fortran
program mpi_example
    use mpi
    integer :: rank, size, ierr
    call mpi_init(ierr)
    call mpi_comm_rank(MPI_COMM_WORLD, rank, ierr)
    call mpi_comm_size(MPI_COMM_WORLD, size, ierr)
    print *, "I am rank", rank, "of", size
    call mpi_finalize(ierr)
end program
! Compile: mpifort program.f90

do concurrent

do concurrent(Fortran 2008)はループ反復が独立していることを示す。コンパイラが自動的に並列化可能。localがプライベート変数を宣言。OpenMPより安全:コンパイラが独立性を検証。恥ずかしいほど並列なループに使用。

fortran
do concurrent (i = 1:n) local(tmp)
    tmp = expensive_compute(a(i))
    b(i) = tmp * 2
end do
! Tells compiler iterations are independent
! Can be parallelized automatically
! local: private variable per iteration

リダクションパターン

リダクションは各スレッドの部分結果を結合。一般的:sum, product, max, min。各スレッドがローカルの部分結果を計算。ランタイムが最後にそれらを結合。データ競合を回避。並列数値アルゴリズムに不可欠。

fortran
!$omp parallel do reduction(+:total)
do i = 1, n
    total = total + a(i)
end do
!$omp end parallel do
! Common reductions: +, *, max, min, .and., .or.
! Each thread has a private copy
! Combined at the end
24

数値計算手法

線形代数

Fortranは組み込みの行列操作を持つ。matmulが行列を乗算。dot_productが内積を計算。transposeが転置。これらは高度に最適化されている(BLASレベル)。本番環境ではLAPACKを使用。Fortranは高性能数値計算の選択言語。

fortran
! Matrix multiplication
do i = 1, n
    do j = 1, n
        c(i,j) = sum(a(i,:) * b(:,j))
    end do
end do
! Or use matmul intrinsic
c = matmul(a, b)
! Dot product
dot = dot_product(a, b)
! Transpose
at = transpose(a)

ODEの求解

オイラー法は最もシンプルなODEソルバ:y(n+1) = y(n) + dt*f(t,y)。精度が必要な場合はRunge-Kutta(RK4)を使用。interfaceブロックが関数を引数として渡す。配列操作と性能のためFortranは科学計算に理想的。

fortran
! Euler method: dy/dt = f(t, y)
subroutine euler(f, t0, y0, dt, n, t, y)
    interface
        real function f(t, y)
            real, intent(in) :: t, y
        end function
    end interface
    real, intent(in) :: t0, y0, dt
    integer, intent(in) :: n
    real, intent(out) :: t(n+1), y(n+1)
    integer :: i
    t(1) = t0; y(1) = y0
    do i = 1, n
        t(i+1) = t(i) + dt
        y(i+1) = y(i) + dt * f(t(i), y(i))
    end do
end subroutine

乱数

random_numberが一様[0,1)の実数を生成。random_seedが生成器を初期化。整数の場合はスケールして変換。Box-Mullerが一様を正規分布に変換。本格的な作業にはライブラリを使用(例:Mersenne Twister)。再現性のため常にシードを設定。

fortran
call random_seed()  ! Seed from system
call random_number(x)  ! x in [0, 1)
! Array of randoms
real :: arr(100)
call random_number(arr)
! Integer in range [1, 6]
integer :: dice
call random_number(r)
dice = int(r * 6) + 1
! Normal distribution (Box-Muller)
call random_number(u1)
call random_number(u2)
z = sqrt(-2*log(u1)) * cos(2*PI*u2)

補間

線形補間は既知の点間の値を推定。区間を見つけてから補間。より滑らかな結果には3次スプライン補間を使用。Fortranの配列操作によりこれを簡潔に記述。外挿エラーを避けるため常に境界を確認。

fortran
function interp(x, xs, ys) result(y)
    real, intent(in) :: x, xs(:), ys(:)
    real :: y
    integer :: i
    ! Find interval
    i = 1
    do while (i < size(xs) .and. x > xs(i+1))
        i = i + 1
    end do
    ! Linear interpolation
    y = ys(i) + (ys(i+1) - ys(i)) * &
        (x - xs(i)) / (xs(i+1) - xs(i))
end function

数値積分

台形則が積分を近似:台形の和。より高精度:シンプソン則。高次元にはガウス求積を使用。性能のためFortranは数値積分に優れる。常に既知の解析解で検証。

fortran
! Trapezoidal rule
function trapezoid(f, a, b, n) result(integral)
    interface
        real function f(x)
            real, intent(in) :: x
        end function
    end interface
    real, intent(in) :: a, b
    integer, intent(in) :: n
    real :: integral, h
    integer :: i
    h = (b - a) / n
    integral = (f(a) + f(b)) / 2
    do i = 1, n-1
        integral = integral + f(a + i*h)
    end do
    integral = integral * h
end function
25

よくある落とし穴

1から始まるインデックス

Fortranの配列はデフォルトで1から始まるインデックス、C/Python(0から始まる)と異なる。コード移植時にoff-by-oneエラーを引き起こす。カスタム下限(0:9)が許可される。プロジェクト内で一貫性を保つ。-fcheck=boundsコンパイラフラグで配列境界をチェック。

fortran
! Fortran arrays are 1-based by default
real :: a(10)  ! Indices 1 to 10
! a(0) = 1.0  ! Error: out of bounds
a(1) = 1.0    ! OK
! Custom bounds
real :: b(0:9)  ! Indices 0 to 9
b(0) = 1.0    ! OK

暗黙の型付け

implicit noneがない場合、i-nで始まる変数は整数、それ以外は実数。これが微妙なバグを引き起こす(タイプミスが新しい変数を作成)。常にimplicit noneを使用。モダンFortran(2018+)は-fimplicit-noneでグローバルに設定可能。これはFortranの最も重要なベストプラクティス。

fortran
! BAD: implicit typing (Fortran 77 style)
program bad
    ! i-n start with integer by default
    i = 1      ! integer (implicit)
    x = 3.14   ! real (implicit)
end program
! GOOD: explicit typing
program good
    implicit none  ! Force explicit declaration
    integer :: i
    real :: x
end program

列優先順序

Fortranは配列を列優先で格納:m(1,1), m(2,1), m(3,1), m(1,2), ...列ごとのアクセスがキャッシュフレンドリ。間違ったループ順序はキャッシュミスを引き起こし10倍以上の遅延を生む。常にループ順序をメモリレイアウトに一致させる。C(行優先)の逆。

fortran
! Fortran is column-major (first index varies fastest)
real :: m(3, 3)
! Efficient: iterate over first index in inner loop
do j = 1, 3
    do i = 1, 3
        m(i, j) = 0.0  ! Cache-friendly
    end do
end do
! Inefficient: row-major access
do i = 1, 3
    do j = 1, 3
        m(i, j) = 0.0  ! Cache misses
    end do
end do

参照渡し

Fortranは引数を参照渡し(Cのポインタのようなもの)。intent(in)が指定されない限り、サブルーチンが呼び出し元の変数を変更可能。intentがないと意図しない変更がバグを引き起こす。常にintentを指定。intent(out)はプロシージャが値を設定することを示す。

fortran
subroutine modify(x)
    integer, intent(inout) :: x
    x = 99  ! Modifies the caller variable
end subroutine
! Fortran passes by reference by default
! intent(in) prevents modification
! Without intent, modification is allowed (dangerous)

浮動小数点精度

デフォルトのrealは単精度(約7桁)で、多くの場合不十分。科学計算には倍精度を使用。kind(1.0d0)またはselected_real_kind(15)で倍精度を定義。常にリテラルに接尾辞を付ける:3.14_dp。精度を混ぜると暗黙の切り捨てが発生。ポータブルなkindにはiso_fortran_envを使用。

fortran
! Single precision (default)
real :: x = 3.14159  ! ~7 digits
! Double precision
real(kind=8) :: y = 3.14159d0  ! ~15 digits
! Or use kind parameter
integer, parameter :: dp = kind(1.0d0)
real(dp) :: z = 3.14159_dp
! Always use _dp or d0 for double literals

Was this helpful?