3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Fortran】配列メモ

Last updated at Posted at 2019-06-26

コンパイラはifort.
単語帳.毎回検索するのが面倒なので転載多め.元URLあり.
自前の検証メモも.

配列の形状

大きさ0に割り付けられる

allocate_0.f90
program main
    implicit none
    integer, allocatable :: a(:)
    allocate(a(0)); a(:) = 1 ! エラーは発生しない
    write(*, *) a            ! 何も表示されない
    write(*, *) allocated(a) ! T
    write(*, *) a(0)         ! forrtl: severe (174): SIGSEGV, segmentation fault occurred
end program main

大きさ0の配列・未割り付けの配列のsizeは0.

size_0.f90
program main
    implicit none
    integer, allocatable :: a(:)
    allocate(a(3)); a(:) = 1
    write(*, *) size(a) ! 3
    deallocate(a)

    allocate(a(0)); a(:) = 1
    write(*, *) size(a) ! 0
    deallocate(a)

    write(*, *) size(a) ! 0
end program main

これらの性質を用いると,allocatableな配列を返すサブルーチン+そのsizeで繰り返し処理,という場面で便利.

手続きに渡される配列の添え字の下限値は引き継がれない

p.90
サブルーチンや関数で引数の配列を(:)と指定した場合,
元々の添え字の下限値 (lbound)は引き継がれず,1が仮定される.
添え字の下限値も渡したい場合は,
下限値は引数として渡す + 上限値はそれとsizeから計算

反対に,手続きの中で割り付けられた配列の上限下限は引き継がれる

module mymod
    implicit none
contains

subroutine mysub(arr)
    integer, intent(out), allocatable :: arr(:,:)
    allocate(arr(-3:2,-1:4), source=0)
end subroutine mysub

end module mymod

program main
    use mymod, only: mysub
    implicit none
    integer, allocatable :: arr(:,:)
    call mysub(arr)
    write(*, *) lbound(arr, 1), ubound(arr, 1) ! -3 2
    write(*, *) lbound(arr, 2), ubound(arr, 2) ! -1 4
end program main

配列間の代入

2つの配列の間で次元の上限・下限が異なっていても(:)なら値を渡せる

program main
    implicit none
    integer a(1:3), b(4:6)
    integer i
    a(:) = 1
    b(:) = 4
    b(:) = a(:)
    write(*, *) a ! 4 4 4

    b(:) = 4
    do i = 1, 3
        b(i) = a(i)
    enddo
    write(*, *) b ! 1 1 1
end program main

大きさの異なる配列間の代入

小配列に大配列を代入 (小配列 = 大配列): 大配列の値がで上書き
大配列に小配列を代入 (大配列 = 小配列): 大配列の残りの要素は保持されない場合がある
以下の例では

  • 大配列(:) = 小配列(:): 残りの要素は0に
  • 大配列   = 小配列  : 残りの要素は保持 (5)

という結果だったが,特に前者の結果はこれらを実行する順番でも変化した
(例: b = a, init_array, b(:) = a(:)の順に実行すると値は保持された).
明示的に値を保持するためには,小さい方のサイズに合わせるなどした方が無難?

program main
    implicit none
    integer, allocatable :: a(:), b(:)
    integer :: asize, bsize, n
    asize = 2
    bsize = 3

    call init_array
    ! a 1 2
    ! b 3 4 5
    call print_array

    n = min(size(a), size(b))
    b(:) = a(:) ! b 1 2 0
!    b    = a   ! b 1 2 5
!    b(:n) = a(:n) ! b 1 2 5

! 小 = 大は同じ結果
!    a(:) = b(:)
!    a    = b
!    a(:n) = b(:n)
    call print_array
    ! a 3 4
    ! b 3 4 5

contains

    subroutine init_array
        integer :: i
        if (allocated(a)) deallocate(a)
        if (allocated(b)) deallocate(b)
        allocate(a(asize), source=[(i, i = 1, asize)])
        allocate(b(bsize), source=[(i, i = asize+1, asize+bsize)])
    end subroutine init_array

    subroutine print_array
        integer :: i
        write(*, '(a,*(1x,i0))') 'a', (a(i), i = 1, asize)
        write(*, '(a,*(1x,i0))') 'b', (b(i), i = 1, bsize)
    end subroutine print_array
end program main

組み込み手続き

他の配列に形状・値を渡すallocate(source=)

allocate(b, source=a)でaの形状・値をbにコピー

  • このとき元の配列aは変更なし

move_alloc(from=a, to=b)との違いで上記+aは解放

  • allocate(b, source=a); deallocate(a)と同じ

大きさ0の配列・未割り付けの配列にも使える

allocate_source_0.f90
program main
    implicit none
    integer, allocatable :: a(:), b(:)
    allocate(a(0)); a(:) = 0
    allocate(b, source=a)
    write(*, *) allocated(b), size(b) ! T 0

    deallocate(a, b)
    allocate(b, source=a)
    write(*, *) allocated(b), size(b) ! F 0
end program main

構造体にも使える

allocate_type.f90
program main
    implicit none
    type mycls1
        integer x, y
        character(len=8) t
    end type mycls1

    type mycls2
        integer, allocatable :: x(:), y(:)
        character(len=8) t
    end type mycls2

    type(mycls1), allocatable :: a(:), b(:)
    type(mycls2), allocatable :: c(:), d(:)
    type(mycls1) objmycls1
    type(mycls2) objmycls2
    integer i, n

    objmycls1%x = 1
    objmycls1%y = 2
    objmycls1%t = 'test'
    n = 3
    allocate(a(n)); a(:) = objmycls1
    allocate(b, source=a)
    do i = 1, n
        write(*, *) b(i)%x, b(i)%y, b(i)%t
    enddo

    allocate(objmycls2%x(2)); objmycls2%x(:) = 1
    allocate(objmycls2%y(2)); objmycls2%y(:) = 2
    objmycls2%t = 'test'
    allocate(c(n)); c(:) = objmycls2 ! x(:)とy(:)は勝手に割り付けられる
    do i = 1, n
        write(*, *) c(i)%x, c(i)%y, c(i)%t
    enddo
    allocate(d, source=c) ! x(:)とy(:)は勝手に割り付けられる
    do i = 1, n
        write(*, *) d(i)%x, d(i)%y, d(i)%t
    enddo
end program main

構造体の配列の解放

allocate(b, source=a)move_alloc(from=a, to=b)の違い

上記を実行すると,

  • bの配列の大きさ・値はどちらも同じだが,
  • bのあるメモリアドレス (メモリ番地) が異なる
    • allocate: baと異なるアドレスに
      • aも残っているので当たり前
    • move_alloc: baと同じアドレスに
      • 同時にadeallocateされるので,そこを自動で再利用してくれると思えば楽

move_allocを使うと配列の延長をする際のメモリコピーの回数が減る
= 実行速度が上がる!!
nag: 3.5 MOVE_ALLOC組込みサブルーチン

この情報は@Authnsさんから教えて頂きました.ありがとうございました!

program main
    implicit none
    integer, allocatable :: a(:), b(:)
    integer :: i, n

    n = 3
    allocate(a(n), source=[(i, i = 1, n)])
    call print_array(a(:))
    ! 1 2 3
    ! 7364736 7364740 7364744

    write(*, '(a)') 'allocate:'
    allocate(b, source=a)
    call print_array(b(:))
    deallocate(b)
    ! 1 2 3
    ! 7377472 7377476 7377480

    write(*, '(a)') 'move_alloc:'
    call move_alloc(from=a, to=b)
    call print_array(b(:))
    deallocate(b)
    ! 1 2 3
    ! 7364736 7364740 7364744

contains

    subroutine print_array(arr)
        integer, intent(in) :: arr(:)
        integer :: i, n
        n = size(arr)
        write(*, '(*(i0,1x))') (arr(i), i = 1, n)
        write(*, '(*(i0,1x))') (loc(arr(i)), i = 1, n)
    end subroutine print_array
end program main

move_allocの方が圧倒的に速い

以下のコードで実行時間を比較.
これが果たして同じコードなのかには自信がない
(1回のiterationの中でallocate, deallocateを2回ずつやっているつもり, これを1e5回)

配列の長さn allocate [sec] move_alloc [sec]
1e4 0.205400000000000 5.000000000000000E-004
1e5 2.50440000000000 5.000000000000000E-004
1e6 52.5643000000000 4.000000000000000E-004

2次元版 (n×n)

配列の大きさn allocate [sec] move_alloc [sec]
1e2 0.232900000000000 7.000000000000000E-004
1e3 61.9367000000000 5.999999999999999E-004
program main
    implicit none
    integer, allocatable :: a(:), b(:)
    integer :: i, n, itr, itr_max
    real(8) :: time
    integer :: t1, t2, t_rate, t_max, diff

    n = 1e6
    itr_max = 1e5
    allocate(a(n), source=[(i, i = 1, n)])
    write(*, '(a)') 'allocate:'
    call clock_start
    do itr = 1, itr_max
        allocate(b, source=a)
        deallocate(b)
        allocate(b, source=a)
        deallocate(b)
    enddo
    call clock_stop

    write(*, '(a)') 'move_alloc:'
    call clock_start
    do itr = 1, itr_max
        call move_alloc(from=a, to=b)
        call move_alloc(from=b, to=a)
    enddo
    call clock_stop

contains

    subroutine clock_start
        call system_clock(t1)
    end subroutine clock_start

    subroutine clock_stop
        call system_clock(t2, t_rate, t_max)
        if (t2 < t1) then
            diff = (t_max - t1) + t2 + 1
        else
            diff = t2 - t1
        endif
        write(*, *) diff / dble(t_rate)
    end subroutine clock_stop
end program main

その他文法

引数によって返す配列の大きさを変える関数

書き方は以下の通り.返り値にallocatable属性を付与するだけでOK.

function myfunc(nlen) result(arr)
    integer, intent(in)  :: nlen
    integer, allocatable :: arr(:)
    ! if (allocated(arr)) deallocate(arr) ! 不要 (あってもエラーにはならない)
    allocate(arr(nlen)); arr(:) = 1
end function myfunc

1次元の配列を引数の大きさに割り付け+全てに1を代入という関数.
これを呼び出す側では,

  • 返ってくる配列の大きさに予め割り付けなくてもOK
    • 違う大きさに割り付けられていても解放する必要なし
    • 仮に違う大きさに割り付けられていた場合,関数の返り値が優先(上書きされる)
  • 呼び出す側は(:)をつけない
    • 期待通りに割り付けられない
    • もし最初から割り付けられていた場合
      • 大きさが違ってもエラーにはならない
      • その一部の値を上書きする
      • それ以外は(以下の例だと)0に,元の値は保持されない
    • (:)は配列の形状は変えず,各要素の値だけを操作するという意味?
  • 1次元・長さ1の配列でもintegerには結果を取得できない

shapeはこのような性質を持つ組み込み関数.

subroutine test_alloc
    integer, allocatable :: arr(:)

    arr = myfunc(1)
    write(*, *) arr ! 1

    arr = myfunc(2)
    write(*, *) arr ! 1 1

    write(*, *) allocated(arr), size(arr) ! F 0
    arr = myfunc(1)
    write(*, *) sum(arr) ! 1
    write(*, *) allocated(arr), size(arr) ! T 1

    ! 違う大きさに割り付けられていても解放する必要なし
    arr = myfunc(2)
    write(*, *) sum(arr) ! 2
    write(*, *) allocated(arr), size(arr) ! T 2

    ! 違う大きさに割り付けられていた場合,関数の返り値が優先
    deallocate(arr)
    allocate(arr(3)); arr(3) = 0
    write(*, *) allocated(arr), size(arr) ! T 3
    arr = myfunc(2)
!    arr = myfunc(4)
    write(*, *) sum(arr) ! 2 (4, myfuncの引数を4にした場合)
    write(*, *) allocated(arr), size(arr) ! T 2 (4)
end subroutine test_alloc

! arr(:) = で呼び出す版
subroutine test_alloc
    integer, allocatable :: arr(:)

    arr(:) = myfunc(1)
    write(*, *) arr ! 何も表示されない
    write(*, *) allocated(arr), size(arr) ! F 0

    arr(:) = myfunc(2)
    write(*, *) arr ! 何も表示されない
    write(*, *) allocated(arr), size(arr) ! F 0

    allocate(arr(3)); arr(:) = 3
    arr(:) = myfunc(1)
    write(*, *) arr ! 1 0 0, 1 3 3ではない
    write(*, *) allocated(arr), size(arr) ! T 3
end subroutine test_alloc

! 配列の代わりにスカラーで呼び出そうとした版 (コンパイルエラー)
subroutine test_alloc
    integer :: scalar
    scalar = myfunc(1) ! error #6366: The shapes of the array expressions do not conform
    write(*, *) scalar
end subroutine test_alloc
3
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?