[Fortranのはじめ方] Part11: 多次元配列とスライス

Fortran

はじめに

これまでのステップでは、Fortranの基本的なデータ型や制御構文、そして1次元配列について学んできました。今回は、さらに複雑なデータを扱うための強力な機能、「多次元配列」と「配列スライス」について学びましょう!

科学技術計算では、行列や3次元空間のデータなど、多次元的なデータを扱う場面が多くあります。Fortranは、このような多次元配列を効率的に扱うための機能が充実しています。さらに、「スライス」という機能を使えば、配列の一部を簡単に取り出すことができます。🚀

このステップをマスターすれば、より実践的なプログラミングが可能になりますよ!💪

多次元配列の宣言方法

多次元配列の宣言は、1次元配列の宣言と似ています。`DIMENSION`属性を使うか、変数名の後ろに各次元の大きさをカンマ区切りで指定します。Fortranでは最大7次元までの配列を宣言できます。

宣言方法:

  • 型, DIMENSION(大きさ1, 大きさ2, ...) :: 配列名
  • 型 :: 配列名(大きさ1, 大きさ2, ...)

Fortranの配列のインデックスは、デフォルトでは1から始まります。これはC言語など他の言語(0から始まることが多い)と異なる点なので注意しましょう。もちろん、範囲を指定して宣言することも可能です。

例:2次元配列(行列)の宣言

! 3行4列の実数型2次元配列 matrix を宣言
REAL, DIMENSION(3, 4) :: matrix
! 上と同じ宣言 (変数名の後ろに大きさを指定)
REAL :: matrix(3, 4)

! インデックスを 0 から始めたい場合
INTEGER, DIMENSION(0:2, 0:3) :: matrix_zero_based ! 3行4列

例:3次元配列の宣言

! 2x3x4 の整数型3次元配列 tensor を宣言
INTEGER, DIMENSION(2, 3, 4) :: tensor
! 上と同じ宣言
INTEGER :: tensor(2, 3, 4)

注意点: Fortranの多次元配列は列優先 (Column Major) でメモリに格納されます。これは、最初の次元のインデックスが最も速く変化するように格納されることを意味します。例えば、`matrix(3, 4)` の要素はメモリ上では `matrix(1,1), matrix(2,1), matrix(3,1), matrix(1,2), …` の順に並びます。これは行優先 (Row Major) のC言語などとは逆なので、他言語と連携する際には注意が必要です。

多次元配列の要素へのアクセス

多次元配列の特定の要素にアクセスするには、各次元のインデックスをカンマで区切って指定します。

PROGRAM access_multi_dim
  IMPLICIT NONE
  REAL, DIMENSION(3, 4) :: matrix
  INTEGER :: i, j

  ! 配列要素に値を代入 (例: i行j列に i*10 + j を代入)
  DO i = 1, 3
    DO j = 1, 4
      matrix(i, j) = REAL(i * 10 + j)
    END DO
  END DO

  ! 特定の要素を出力 (例: 2行3列目)
  PRINT *, 'matrix(2, 3) = ', matrix(2, 3) ! 出力: matrix(2, 3) = 23.0000000

  ! 全要素を出力
  PRINT *, '--- Matrix Elements ---'
  DO i = 1, 3
    ! WRITE文とフォーマット指定で整形して出力
    WRITE(*, '(4F6.1)') (matrix(i, j), j = 1, 4)
  END DO
  ! 出力例:
  ! --- Matrix Elements ---
  !   11.0  12.0  13.0  14.0
  !   21.0  22.0  23.0  24.0
  !   31.0  32.0  33.0  34.0

END PROGRAM access_multi_dim

配列スライス:配列の一部を切り取る 🔪

配列スライスは、配列の一部を簡単に取り出すための非常に便利な機能です。コロン (`:`) を使って範囲を指定します。

基本的な構文: 配列名(開始インデックス : 終了インデックス : ステップ)

  • 開始インデックス: 省略すると、その次元の下限インデックスになります。
  • 終了インデックス: 省略すると、その次元の上限インデックスになります。
  • ステップ: 省略すると、1になります。要素を1つ飛ばしで取得する場合は2を指定します。逆順にする場合は負の値を指定できます。
  • 特定の次元全体を指定する場合は、単に `:` と書きます。

例:1次元配列のスライス

PROGRAM slice_1d
  IMPLICIT NONE
  INTEGER, DIMENSION(10) :: vec = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  INTEGER, DIMENSION(:), ALLOCATABLE :: sub_vec

  PRINT *, 'Original Vector:', vec

  ! 3番目から7番目の要素
  sub_vec = vec(3:7)
  PRINT *, 'vec(3:7)  :', sub_vec ! 出力: [3, 4, 5, 6, 7]

  ! 最初から5番目までの要素
  sub_vec = vec(:5)
  PRINT *, 'vec(:5)   :', sub_vec ! 出力: [1, 2, 3, 4, 5]

  ! 6番目から最後まで
  sub_vec = vec(6:)
  PRINT *, 'vec(6:)   :', sub_vec ! 出力: [6, 7, 8, 9, 10]

  ! 全要素 (コピー)
  sub_vec = vec(:)
  PRINT *, 'vec(:)    :', sub_vec ! 出力: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  ! 奇数番目の要素 (ステップ指定)
  sub_vec = vec(1:10:2) ! vec(::2) と同じ
  PRINT *, 'vec(1:10:2):', sub_vec ! 出力: [1, 3, 5, 7, 9]

  ! 逆順 (ステップ指定)
  sub_vec = vec(10:1:-1) ! vec(::-1) と同じ
  PRINT *, 'vec(::-1)  :', sub_vec ! 出力: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

END PROGRAM slice_1d

例:2次元配列のスライス

PROGRAM slice_2d
  IMPLICIT NONE
  INTEGER, DIMENSION(3, 4) :: matrix
  INTEGER, DIMENSION(:), ALLOCATABLE :: row_vec, col_vec
  INTEGER, DIMENSION(:,:), ALLOCATABLE :: sub_matrix
  INTEGER :: i, j

  ! matrix を初期化 (11, 12, ... 34)
  DO i = 1, 3
    DO j = 1, 4
      matrix(i, j) = i * 10 + j
    END DO
  END DO

  PRINT *, 'Original Matrix:'
  DO i = 1, 3
    WRITE(*, '(4I5)') (matrix(i, j), j = 1, 4)
  END DO
  ! 出力例:
  ! Original Matrix:
  !    11   12   13   14
  !    21   22   23   24
  !    31   32   33   34

  ! 2行目全体を取得 (結果は1次元配列)
  row_vec = matrix(2, :) ! コロンは第2次元全体を示す
  PRINT *, 'Row 2: ', row_vec ! 出力: Row 2:           21          22          23          24

  ! 3列目全体を取得 (結果は1次元配列)
  col_vec = matrix(:, 3) ! コロンは第1次元全体を示す
  PRINT *, 'Column 3: ', col_vec ! 出力: Column 3:           13          23          33

  ! 部分行列 (2行目から3行目、1列目から2列目)
  sub_matrix = matrix(2:3, 1:2)
  PRINT *, 'Sub Matrix (2:3, 1:2):'
  DO i = 1, SIZE(sub_matrix, 1)
     WRITE(*, '(2I5)') (sub_matrix(i,j), j=1, SIZE(sub_matrix, 2))
  END DO
  ! 出力例:
  ! Sub Matrix (2:3, 1:2):
  !    21   22
  !    31   32

END PROGRAM slice_2d

このように、スライスを使うことで、ループを使わずに配列の一部を簡潔に表現し、操作することができます。これはコードの可読性を高め、記述を簡略化するのに役立ちます。✨

注意: 配列スライスで部分配列をサブルーチンや関数に渡す場合、Fortranコンパイラによっては一時的な配列コピーが生成されることがあります。非常に大きな配列の一部を頻繁に渡すようなケースでは、パフォーマンスに影響が出る可能性もゼロではありません。しかし、多くの場合、このオーバーヘッドは小さく、コードの簡潔さのメリットの方が大きいです。

多次元配列の操作例

多次元配列やスライスを使うと、配列全体や部分に対する演算も簡単に行えます。

PROGRAM array_operations
  IMPLICIT NONE
  REAL, DIMENSION(3, 4) :: a, b, c
  INTEGER :: i, j

  ! 配列 a を初期化 (1.0, 2.0, ...)
  DO i = 1, 3
    DO j = 1, 4
      a(i, j) = REAL((i - 1) * 4 + j)
    END DO
  END DO

  ! 配列全体にスカラ値を代入
  b = 0.0
  PRINT *, 'Matrix b (initialized to 0.0):'
  DO i = 1, 3
    WRITE(*, '(4F5.1)') (b(i, j), j = 1, 4)
  END DO

  ! 配列全体に対する演算 (要素ごと)
  c = a * 2.0 + 1.0 ! 全要素を2倍して1を足す
  PRINT *, 'Matrix c = a * 2.0 + 1.0:'
  DO i = 1, 3
    WRITE(*, '(4F5.1)') (c(i, j), j = 1, 4)
  END DO

  ! スライスを使った部分的な代入 (aの1行目をbの2行目にコピー)
  b(2, :) = a(1, :)
  PRINT *, 'Matrix b (after b(2,:) = a(1,:)):'
  DO i = 1, 3
    WRITE(*, '(4F5.1)') (b(i, j), j = 1, 4)
  END DO

  ! スライスを使った演算 (aの1列目と2列目を足してcの3列目に代入)
  c(:, 3) = a(:, 1) + a(:, 2)
  PRINT *, 'Matrix c (after c(:,3) = a(:,1) + a(:,2)):'
  DO i = 1, 3
    WRITE(*, '(4F5.1)') (c(i, j), j = 1, 4)
  END DO

END PROGRAM array_operations

配列全体やスライスを使った演算は、要素ごとにループを回して計算するよりもコードが簡潔になり、多くの場合コンパイラによって最適化され高速に実行されます。

まとめ

今回は、Fortranにおける多次元配列の宣言、要素アクセス、そして強力な配列スライス機能について学びました。

  • 多次元配列は DIMENSION(d1, d2, ...) または 変数名(d1, d2, ...) で宣言します。
  • 要素アクセスは 配列名(i1, i2, ...) で行います。
  • Fortranの配列は列優先で格納されます。
  • 配列スライス (start:end:step) を使うと、配列の一部を簡単に切り出して操作できます。
  • 配列全体やスライスに対する演算は、コードを簡潔にし、効率的な実行が期待できます。

これらの機能を使いこなすことで、行列演算やグリッドデータの処理など、科学技術計算で頻出する様々な問題をエレガントかつ効率的に記述できるようになります。🎉 次のステップでは、構造体(派生型)について学び、さらに複雑なデータを扱えるようになりましょう!

コメント

タイトルとURLをコピーしました