在上一篇中我们介绍了用 Boost.Python 包装 C++ 语言 MPI 程序以供 mpi4py 调用的方法,下面我们将介绍使用 f2py 包装 Fortran MPI 程序的方法。
f2py (Fortran to Python interface generator) 是 numpy 中自带的一个 Fortran 到 Python 的接口生成工具,支持 Fortran 77/90/95,可以使用它将 Fortran 程序包装成可供 Python 调用的扩展模块。感兴趣的读者可以参考其文档,这里不多作介绍。我们只会以简单的例子展示如何使用 f2py 包装 Fortran MPI 程序以供 mpi4py 调用。
假设我们有以下 Fortran 程序文件 helloworld.f90,其中定义了子例程 sayhello,其接受一个 MPI 通信子作为参数。
! helloworld.f90
!
! $ f2py --f90exec=mpif90 -m helloworld -c helloworld.f90
!
subroutine sayhello(comm)
use mpi
implicit none
integer :: comm
integer :: rank, size, nlen, ierr
character (len=MPI_MAX_PROCESSOR_NAME) :: pname
if (comm == MPI_COMM_NULL) then
print *, 'You passed MPI_COMM_NULL !!!'
return
end if
call MPI_Comm_rank(comm, rank, ierr)
call MPI_Comm_size(comm, size, ierr)
call MPI_Get_processor_name(pname, nlen, ierr)
print *, 'Hello, World!', &
' I am process ', rank, &
' of ', size, &
' on ', pname(1:nlen), '.'
end subroutine sayhello
! program main
! use mpi
! implicit none
! integer ierr
! call MPI_Init(ierr)
! call sayhello(MPI_COMM_WORLD)
! call MPI_Finalize(ierr)
! end program main
要将其编译成扩展模块 helloworld.so 以供 mpi4py 程序调用,我们可以使用类似于以下的命令一步到位:
$ f2py --f90exec=mpif90 -m helloworld -c helloworld.f90
其中 -m 指定模块名,-c 指明编译和重建扩展模块。
但是 f2py 文档中推荐使用以下两步走的方案:
首先通过以下命令产生一个 signature 文件 helloworld.pyf:
$ f2py -m helloworld -h helloworld.pyf helloworld.f90
生成的 signature 文件如下:
! -*- f90 -*-
! Note: the context of this file is case sensitive.
python module helloworld ! in
interface ! in :helloworld
subroutine sayhello(comm) ! in :helloworld:helloworld.f90
use mpi
integer :: comm
end subroutine sayhello
end interface
end python module helloworld
! This file was auto-generated with f2py (version:2).
! See http://cens.ioc.ee/projects/f2py2e/
可以根据需要手动编辑所产生的 signature 文件(一般情况下不用作任何编辑也能正常工作)。 作为一个简单的例子,我们在 integer :: comm
这一句中加上 intent(in)
,使 helloworld.pyf 变成如下:
! -*- f90 -*-
! Note: the context of this file is case sensitive.
python module helloworld ! in
interface ! in :helloworld
subroutine sayhello(comm) ! in :helloworld:helloworld.f90
use mpi
integer intent(in) :: comm
end subroutine sayhello
end interface
end python module helloworld
! This file was auto-generated with f2py (version:2).
! See http://cens.ioc.ee/projects/f2py2e/
然后使用以下命令生成扩展模块:
$ f2py --f90exec=mpif90 -c helloworld.pyf helloworld.f90
编译成功后会生成扩展模块 helloworld.so,然后就可以在我们的 mpi4py 程序中像使用其它 Python 模块一样导入该模块并调用该模块中定义的 sayhello 函数,可以向此函数传递一个 mpi4py 中定义的通信子,如 MPI.COMM_WORLD 或者其它通信子对象。例如使用以下的 test 例程:
# test.py
from mpi4py import MPI
import helloworld as hw
null = MPI.COMM_NULL
fnull = null.py2f()
hw.sayhello(fnull)
comm = MPI.COMM_WORLD
fcomm = comm.py2f()
hw.sayhello(fcomm)
try:
hw.sayhello(list())
except:
pass
else:
assert 0, "exception not raised"
执行结果如下:
$ mpiexec -n 4 python test.py
You passed MPI_COMM_NULL !!!
Hello, World! I am process 0 of 4 on node2.
You passed MPI_COMM_NULL !!!
Hello, World! I am process 1 of 4 on node2.
You passed MPI_COMM_NULL !!!
Hello, World! I am process 2 of 4 on node2.
You passed MPI_COMM_NULL !!!
Hello, World! I am process 3 of 4 on node2.
以上的过程直接编译出了扩展模块,如果想得到或看看扩展模块的源文件,可以使用如下命令:
$ f2py -m helloworld helloworld.f90
该命令会生成扩展模块源码文件 helloworldmodule.c,感兴趣的读者可以打开看看。当然如果你愿意的话也可以用一个 C 编译器手动地将此源文件编译成一个扩展模块。
为了方便,我们也可以编写如下 Makefile 以简化上述操作(注意其中使用了在上一篇中介绍的 python-config 文件):
# Makefile
.PHONY: default
default: build test clean
PYTHON = python
PYTHON_CONFIG = ${PYTHON} ./python-config
MPIF90 = mpif90
F2PY = f2py
SO = ${shell ${PYTHON_CONFIG} --extension-suffix}
.PHONY: build
build: helloworld${SO}
helloworld${SO}: helloworld.f90
${F2PY} --f90exec=${MPIF90} -m helloworld -c $<
MPIEXEC = mpiexec
NP_FLAG = -n
NP = 5
.PHONY: test
test: build
${MPIEXEC} ${NP_FLAG} ${NP} ${PYTHON} test.py
.PHONY: clean
clean:
${RM} helloworld${SO}
编译扩展库,执行程序及清理可以分别使用如下命令:
$ make build
$ make test
$ make clean
以上我们介绍了用 f2py 包装 Fortran 语言 MPI 程序以供 mpi4py 调用的方法,可以看到包装 C, C++,Fortran 等其它计算机语言的 MPI 程序供 mpi4py 调用是比较容易的,其实反过来将 mpi4py 程序嵌入其它计算机语言中也不难,在下一篇中我们将介绍在 C 语言程序中嵌入 mpi4py 程序的方法。