mpi並列化楽々化計画 in Fortran: mpi_allgathervを使いこなす

Fortran Advent Calendar 2019です。

mpi_allgathervを使う方法について、オブジェクト指向を使って述べます。
モジュールにしたので気軽に呼べるようになりました。

## バージョン
gcc version 9.2.0 (Homebrew GCC 9.2.0_2) 

# はじめに
MPIでループを並列化したい！ということはよくあると思います。
例えば

```fortran
program main
    implicit none
    integer::N
    real(8),allocatable::vec_a(:)
    integer::i

    N = 11
    allocate(vec_a(N))
    do i=1,N
        vec_a(i) = i
    end do
    write(*,*) vec_a
end program 
```
このdoループを並列計算したい、ということを考えます。

# ループ数が並列数で割り切れる場合
並列数をnprocsとして、Nが並列数で割り切れる場合、

```fortran
program main
    implicit none
    include "mpif.h"
    integer::N
    real(8),allocatable::vec_a(:)
    real(8),allocatable::vec_a_temp(:)
    integer::i,ista,iend,ierr,myrank,nprocs,nbun,count


    call mpi_init(ierr)


    call mpi_comm_rank(mpi_comm_world,myrank,ierr)
    call mpi_comm_size(mpi_comm_world,nprocs,ierr)

    N = 10
    nbun = N/nprocs
    ista = myrank*nbun + 1
    iend = ista + nbun -1
    allocate(vec_a(N))
    allocate(vec_a_temp(nbun))

    count = 0
    do i=ista,iend
        count = count + 1
        vec_a_temp(count) = i
    end do
    call mpi_allgather(vec_a_temp,nbun,mpi_double_precision,vec_a,nbun,mpi_double_precision,MPI_COMM_WORLD,ierr)   
    if (myrank == 0) then
        write(*,*) vec_a
    end if

    call mpi_finalize(ierr)

end program 
```
でできます。ここで、mpi_allgatherというMPIのサブルーチンを呼んでいます。
これは、長さN/nprocsのvec_a_tempという配列をnprocs分積み上げて、長さNのvec_aという配列を作るサブルーチンです。
ここで、それぞれのMPIプロセスでループの数が同じ場合には配列の長さN/nprocsは各プロセスで同じです。しかし、並列数で割り切れない場合には、この方法は使えません。

# ループ数が並列数で割り切れない場合
ループ数が並列数で割り切れない場合、mpi_allgathervというサブルーチンを使います。しかし、いろいろ引数があって面倒です。
参考URL:
http://nkl.cc.u-tokyo.ac.jp/11e/03-MPI/reportS1.pdf
そこで、オブジェクト指向を使って煩雑な手続きを見えないようにしてしまいましょう。
まず、classとして、gatherv typeを作ります。

```fortran
    type gatherv
        integer::nprocs
        integer::myrank
        integer::N
        integer,allocatable::rcounts(:)
        integer,allocatable::displs(:)
        integer::count
        
        contains
        procedure:: allgatherv_dble =>  allgatherv_dble
        procedure:: allgatherv_complex =>  allgatherv_complex
        procedure:: get_start
        procedure:: get_end
    end type gatherv
```
ここに、MPI並列で必要そうな情報を入れておきます。

次に、このtypeを使うためのコンストラクタ

```fortran
    type(gatherv) function init_gatherv(N) result(result)
        implicit none
        integer,intent(in)::N
        integer::ierr,nprocs,myrank,i
        integer::count,m
        
        call mpi_comm_rank(mpi_comm_world,myrank,ierr)
        call mpi_comm_size(mpi_comm_world,nprocs,ierr)
        result%nprocs = nprocs
        result%myrank = myrank
        result%N = N
        allocate(result%rcounts(1:nprocs))
        allocate(result%displs(1:nprocs+1))

        count = 0
        m = mod(N,nprocs) !N*nprocs + m
        count = (N-m)/nprocs
        if (mod(myrank-1+nprocs,nprocs)+1 <= m) then 
            count = count + 1
        end if
        result%count = count
        call mpi_allgather(count,1,mpi_integer,result%rcounts,1,mpi_integer,mpi_comm_world,ierr)

        result%displs(1) = 0
        do i=1,nprocs
            result%displs(i+1) = result%displs(i) + result%rcounts(i)
        end do
        
        return
    end function init_gatherv
```
を作ります。MPI並列数をnprocs、自分自身のプロセス番号をmyrankとしています。
また、mpi_allgathervで必要なrcountsとdisplsを作成しています。
ここでは、まずループ数Nと並列数nprocsのあまりmを計算し、割り切れる数(N-m)/nprocsを求めます。全てのプロセスが少なくともこの数だけループを回すことになります。残りはあまりのmですが、残りのm回ループを各プロセスに割り振っています。myrank=0は何かと他の処理をする可能性があるためループが一番少なくなるようにしました。
例えば、m=5でnprocsが10の場合、myrankは0,1,2,3,...,9までですが、残りループ数m=5はmyrank=1,2,3,4,5の5つのプロセスが担当することになります。つまり、N=105,nprocs=10なら、あまりは5で、myrank=0は10回、myrank=1から5までが11回、残りは10回ループを回します。
これで、それぞれのプロセスにおけるベクトルの長さは、10,11,11,11,11,11,10,10,10,10となります。


このfunctionはmodule内で

```fortran
    interface gatherv
        module procedure::init_gatherv
    end interface gatherv
```
としてgathervとして呼べるようにしておきます。

次に、ループを分割した時のそれぞれのプロセスでのiの最初と最後を指定する関数を作っておきます。

```fortran
    integer function get_start(self) result(ista)
        implicit none
        class(gatherv)::self
        ista = self%displs(self%myrank+1) + 1
        return
    end function get_start

    integer function get_end(self) result(iend)
        implicit none
        class(gatherv)::self
        iend = self%displs(self%myrank+1+1)
        return
    end function get_end  
```

そして、mpi_allgathervを呼ぶサブルーチンを

```fortran
    subroutine allgatherv_dble(self,v_ip,v) 
        implicit none
        class(gatherv)::self
        real(8),intent(in)::v_ip(:)
        real(8),intent(out)::v(:)
        integer::ierr
        call mpi_allgatherv(v_ip(1:self%count),self%count,mpi_double_precision,v, &
            self%rcounts,self%displs,mpi_double_precision,mpi_comm_world,ierr)
        return
    end subroutine allgatherv_dble

    subroutine allgatherv_complex(self,v_ip,v) 
        implicit none
        class(gatherv)::self
        complex(8),intent(in)::v_ip(:)
        complex(8),intent(out)::v(:)
        integer::ierr
        call mpi_allgatherv(v_ip(1:self%count),self%count,mpi_double_complex,v, &
            self%rcounts,self%displs,mpi_double_complex,mpi_comm_world,ierr)
    end subroutine allgatherv_complex  
```
と作ります。



全体のmoduleは以下の通りです

```fortran
module mpicalls
    implicit none
    include "mpif.h"
    private
    public::gatherv!,init_gatherv


    type gatherv
        integer::nprocs
        integer::myrank
        integer::N
        integer,allocatable::rcounts(:)
        integer,allocatable::displs(:)
        integer::count
        
        contains
        procedure:: allgatherv_dble =>  allgatherv_dble
        procedure:: allgatherv_complex =>  allgatherv_complex
        procedure:: get_start
        procedure:: get_end
    end type gatherv

    interface gatherv
        module procedure::init_gatherv
    end interface gatherv

    contains

    integer function get_start(self) result(ista)
        implicit none
        class(gatherv)::self
        ista = self%displs(self%myrank+1) + 1
        return
    end function get_start

    integer function get_end(self) result(iend)
        implicit none
        class(gatherv)::self
        iend = self%displs(self%myrank+1+1)
        return
    end function get_end   

    type(gatherv) function init_gatherv(N) result(result)
        implicit none
        integer,intent(in)::N
        integer::ierr,nprocs,myrank,i
        integer::count,m
        
        call mpi_comm_rank(mpi_comm_world,myrank,ierr)
        call mpi_comm_size(mpi_comm_world,nprocs,ierr)
        result%nprocs = nprocs
        result%myrank = myrank
        result%N = N
        allocate(result%rcounts(1:nprocs))
        allocate(result%displs(1:nprocs+1))

        count = 0
        m = mod(N,nprocs) !N*nprocs + m
        count = (N-m)/nprocs
        if (mod(myrank-1+nprocs,nprocs)+1 <= m) then 
            count = count + 1
        end if
        result%count = count
        call mpi_allgather(count,1,mpi_integer,result%rcounts,1,mpi_integer,mpi_comm_world,ierr)

        result%displs(1) = 0
        do i=1,nprocs
            result%displs(i+1) = result%displs(i) + result%rcounts(i)
        end do
        
        return
    end function init_gatherv

    subroutine allgatherv_dble(self,v_ip,v) 
        implicit none
        class(gatherv)::self
        real(8),intent(in)::v_ip(:)
        real(8),intent(out)::v(:)
        integer::ierr
        call mpi_allgatherv(v_ip(1:self%count),self%count,mpi_double_precision,v, &
            self%rcounts,self%displs,mpi_double_precision,mpi_comm_world,ierr)
        return
    end subroutine allgatherv_dble

    subroutine allgatherv_complex(self,v_ip,v) 
        implicit none
        class(gatherv)::self
        complex(8),intent(in)::v_ip(:)
        complex(8),intent(out)::v(:)
        integer::ierr
        call mpi_allgatherv(v_ip(1:self%count),self%count,mpi_double_complex,v, &
            self%rcounts,self%displs,mpi_double_complex,mpi_comm_world,ierr)
    end subroutine allgatherv_complex  


end module mpicalls
```

# 使い方
使い方は、以下の通りです。

```fortran
program main
    use mpicalls
    implicit none
    include "mpif.h"
    integer::N
    type(gatherv)::g
    real(8),allocatable::vec_a(:)
    real(8),allocatable::vec_a_temp(:)
    integer::i,ista,iend,ierr,myrank,nprocs,nbun,count

    call mpi_init(ierr)
    N = 111

    allocate(vec_a(N))

    g = gatherv(N)
    allocate(vec_a_temp(g%count))

    count = 0
    do i=g%get_start(),g%get_end()
        count = count + 1
        vec_a_temp(count) = i
    end do
    call g%allgatherv_dble(vec_a_temp,vec_a)

    if (g%myrank == 0) then
        do i=1,N
            write(*,*) vec_a(i)
        end do
    end if


    call mpi_finalize(ierr)

end program
```
初期化として、`g = gatherv(N)`を呼び、`do i=g%get_start(),g%get_end()`でループを分割し、`call g%allgatherv_dble(vec_a_temp,vec_a)`で呼べば、それでOKです。

# 全体のコード
全体のコードを置いておきます。

```fortran
module mpicalls
    implicit none
    include "mpif.h"
    private
    public::gatherv!,init_gatherv


    type gatherv
        integer::nprocs
        integer::myrank
        integer::N
        integer,allocatable::rcounts(:)
        integer,allocatable::displs(:)
        integer::count
        
        contains
        procedure:: allgatherv_dble =>  allgatherv_dble
        procedure:: allgatherv_complex =>  allgatherv_complex
        procedure:: get_start
        procedure:: get_end
    end type gatherv

    interface gatherv
        module procedure::init_gatherv
    end interface gatherv

    contains

    integer function get_start(self) result(ista)
        implicit none
        class(gatherv)::self
        ista = self%displs(self%myrank+1) + 1
        return
    end function get_start

    integer function get_end(self) result(iend)
        implicit none
        class(gatherv)::self
        iend = self%displs(self%myrank+1+1)
        return
    end function get_end   

    type(gatherv) function init_gatherv(N) result(result)
        implicit none
        integer,intent(in)::N
        integer::ierr,nprocs,myrank,i
        integer::count,m
        
        call mpi_comm_rank(mpi_comm_world,myrank,ierr)
        call mpi_comm_size(mpi_comm_world,nprocs,ierr)
        result%nprocs = nprocs
        result%myrank = myrank
        result%N = N
        allocate(result%rcounts(1:nprocs))
        allocate(result%displs(1:nprocs+1))

        count = 0
        m = mod(N,nprocs) !N*nprocs + m
        count = (N-m)/nprocs
        if (mod(myrank-1+nprocs,nprocs)+1 <= m) then 
            count = count + 1
        end if
        result%count = count
        call mpi_allgather(count,1,mpi_integer,result%rcounts,1,mpi_integer,mpi_comm_world,ierr)

        result%displs(1) = 0
        do i=1,nprocs
            result%displs(i+1) = result%displs(i) + result%rcounts(i)
        end do
        
        return
    end function init_gatherv

    subroutine allgatherv_dble(self,v_ip,v) 
        implicit none
        class(gatherv)::self
        real(8),intent(in)::v_ip(:)
        real(8),intent(out)::v(:)
        integer::ierr
        call mpi_allgatherv(v_ip(1:self%count),self%count,mpi_double_precision,v, &
            self%rcounts,self%displs,mpi_double_precision,mpi_comm_world,ierr)
        return
    end subroutine allgatherv_dble

    subroutine allgatherv_complex(self,v_ip,v) 
        implicit none
        class(gatherv)::self
        complex(8),intent(in)::v_ip(:)
        complex(8),intent(out)::v(:)
        integer::ierr
        call mpi_allgatherv(v_ip(1:self%count),self%count,mpi_double_complex,v, &
            self%rcounts,self%displs,mpi_double_complex,mpi_comm_world,ierr)
    end subroutine allgatherv_complex  


end module mpicalls

program main
    use mpicalls
    implicit none
    include "mpif.h"
    integer::N
    type(gatherv)::g
    real(8),allocatable::vec_a(:)
    real(8),allocatable::vec_a_temp(:)
    integer::i,ista,iend,ierr,myrank,nprocs,nbun,count

    call mpi_init(ierr)
    N = 111

    allocate(vec_a(N))

    g = gatherv(N)
    allocate(vec_a_temp(g%count))

    count = 0
    do i=g%get_start(),g%get_end()
        count = count + 1
        vec_a_temp(count) = i
    end do
    call g%allgatherv_dble(vec_a_temp,vec_a)

    if (g%myrank == 0) then
        do i=1,N
            write(*,*) vec_a(i)
        end do
    end if


    call mpi_finalize(ierr)

end program
```