【Fortran】配列メモ

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


# 配列の形状

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

``` Fortran: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．

``` Fortran: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`から計算



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

```Fortran
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つの配列の間で次元の上限・下限が異なっていても(:)なら値を渡せる

``` Fortran
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(:)`の順に実行すると値は保持された)．
明示的に値を保持するためには，小さい方のサイズに合わせるなどした方が無難？

```Fortran
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)`と同じ

https://qiita.com/implicit_none/items/08f894bc851c29b40b58

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

``` Fortran: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
```


#### 構造体にも使える

``` Fortran: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
```


### 構造体の配列の解放

https://www.nag-j.co.jp/fortran/fortran2003/Fortran2003_3_3.html




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

上記を実行すると，

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

`move_alloc`を使うと配列の延長をする際のメモリコピーの回数が減る
= 実行速度が上がる!!
[nag: 3.5 MOVE_ALLOC組込みサブルーチン](https://www.nag-j.co.jp/fortran/fortran2003/Fortran2003_3_5.html)

この情報は[@Authns](https://qiita.com/Authns)さんから教えて頂きました．ありがとうございました！

```Fortran
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

```Fortran
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．

```Fortran
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`はこのような性質を持つ組み込み関数．

```Fortran
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
```