是否可以使用 MPI 将数据从 Fortran 程序发送到 Python?
Is it possible to send data from a Fortran program to Python using MPI?
我正在开发一种工具来模拟波能转换器,我需要将两个软件包相互耦合。一个程序是用 Fortran 编写的,另一个是用 C++ 编写的。我需要在每个时间步将信息从 Fortran 程序发送到 C++ 程序。但是,数据首先需要在 Python 中进行处理,然后才能发送到 C++ 程序。我收到了使用 MPI 在程序之间传输数据的提示。
我现在正尝试将一个简单的字符串从 Fortran 代码发送到 Python,但是 Python 代码卡在接收命令中。
我的 Fortran 代码如下所示:
USE GlobalVariables
USE MPI
IMPLICIT NONE
CHARACTER(LEN=10):: astring
INTEGER :: comm, rank, size, mpierr
! Initialize MPI on first timestep
IF(tstep .LT. 2) THEN
call MPI_INIT(mpierr)
ENDIF
! make string to send to python
astring = "TEST"
! MPI Test
call MPI_Comm_size(MPI_COMM_WORLD, size, mpierr)
call MPI_Comm_rank(MPI_COMM_WORLD, rank, mpierr)
! Send message to python
CALL MPI_SEND(astring, len(astring), MPI_CHARACTER, 0, 22, MPI_COMM_WORLD, mpierr)
print *, 'MPI MESSAGE SENT ', mpierr
! Initialize MPI on first timestep
IF(tstep .EQ. Nsteps-1) THEN
call MPI_FINALIZE(mpierr)
print *, 'MPI FINALIZED!'
ENDIF
我的 Python 代码如下:
from mpi4py import MPI
import numpy as np
import subprocess as sp
import os
# Start OW3D_SPH in the background and send MPI message
os.chdir('OW3D_run')
args = ['OceanWave3D_SPH','OW3D.inp']
pid = sp.Popen(args,shell=False)
os.chdir('..')
# Check if MPI is initialized
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
# Receive message from fortran
test = comm.recv(source=0, tag=22)
# Let the program end
output = pid.communicate()
with open('test.txt','w') as f:
f.write(test)
Python 代码永远不会通过 MPI 接收命令,也不会完成。 Fortran 代码确实完成并正确打印了 "MPI FINALIZED" 消息。
我看不出哪里做错了,消息从进程 0 发送到进程 0,带有标记 22,并在两个代码中使用 MPI_COMM_WORLD
。
我不会为此目的使用 MPI(除非明确要求并行执行代码)。如果您的目标是连接用 Fortran、C++ 和 Python 编写的例程,那么我建议在 Python 中编写(主要)连接部分,同时为您的 Fortran 和 C++ 例程创建适配器,以便将它们导入 Python。然后你可以管理主程序中的所有函数调用 Python 并根据需要发送数据。
查看以下链接:
运行 Python
中的 Fortran 代码
- f2py @ numpy:
f2py
现在随附 numpy
允许您将 Fortran 源代码编译为 Python 字节代码。
运行 Python
中的 C++ 代码
当源和目标是不同的程序时,您当然不能同时将源和目标都设置为 0。你说 "from process 0 to process 0" 但你显然有两个不同的过程!其中之一有一些不同的等级编号,但您没有显示实际的 mpirun
命令,所以很难说哪个是哪个。
澄清一下:MPI_COM_WORLD 是在您的 mpirun 或等效程序中执行的所有进程的通信器。你必须离开简单的思维画面,第一个 Python 进程是等级 0,第一个 Fortran 进程是等级 0,第一个 C++ 是等级 0...
如果你这样做
mpirun -n 1 python main.py : -n 1 ./fortran_main : -n 1 ./c++_main
然后在 MPI_COMM_WORLD 中,Python 程序将排在第 0 位,Fortran 进程将排在第 1 位,C++ 将排在第 2 位。您只能在 [=26 本地创建通信器=] 子集或 Fortran 子集或 C++ 子集,您将在每个子集中排名 0,但这将在不同的通信器中编号,而不是在 MPI_COMM_WORLD.
中
如果您要在同一个 MPI 作业中同时启动 Fortran 程序和 Python 程序,您必须使用类似的东西:
mpiexec -n 1 fortran_program : -n 1 python main.py
Fortran 程序将变为 MPI 级别 0,而 Python 程序将变为 MPI 级别 1。您还可以启动多个可执行文件,例如:
mpiexec -n 2 fortran_program : -n 4 python main.py
排名 0 和 1 将来自 Fortran 程序,排名 2 到 5 - 来自 Python。
另请注意,comm.recv()
和 mpi4py 中其他以小写字母开头的通信方法(comm.send()
、comm.irecv()
等)在后台使用 Pickle,并且实际使用序列化 Python 个对象。这与 Fortran 代码发送的字符数组不兼容。您必须使用以大写字母开头的通信方法(comm.Send()
、comm.Recv()
等),它们对 NumPy 数组进行操作并接收显式类型信息。不幸的是,我的 Python fu 很弱,我现在无法提供完整的工作示例,但 MPI 部分应该是这样的(未验证代码):
# Create an MPI status object
status = MPI.Status()
# Wait for a message without receiving it
comm.Probe(source=0, tag=22, status=status)
# Check the length of the message
nchars = status.Get_count(MPI.CHARACTER)
# Allocate a big enough data array of characters
data = np.empty(nchars, dtype='S')
# Receive the message
comm.Recv([data, MPI.CHARACTER], source=0, tag=22)
# Construct somehow the string out of the individual chars in "data"
在 Fortran 代码中,您必须指定目标等级 1(如果您是 运行 一个 Fortran 可执行文件和一个 Python 一个)。
MPI进程可以通过函数MPI_Comm_spawn()
. In a python program, this function is a method of the communicator: comm.Spawn()
. See the mpi4py tutorial for an example. The spawned process is ran according to an executable which could be another python program, a c/c++/fortran program or whatever you want. Then, an intercommunicator can be merged to define an intracommunicator between the master process and the spawned ones as performed in 生成进程,因此主进程和生成的进程可以不受任何限制地自由通信。
我们来介绍一个Python/c的例子。 Python 代码生成进程并接收一个字符:
from mpi4py import MPI
import sys
import numpy
'''
slavec is an executable built starting from slave.c
'''
# Spawing a process running an executable
# sub_comm is an MPI intercommunicator
sub_comm = MPI.COMM_SELF.Spawn('slavec', args=[], maxprocs=1)
# common_comm is an intracommunicator accross the python process and the spawned process. All kind sof collective communication (Bcast...) are now possible between the python process and the c process
common_comm=sub_comm.Merge(False)
#print 'parent in common_comm ', common_comm.Get_rank(), ' of ',common_comm.Get_size()
data = numpy.arange(1, dtype='int8')
common_comm.Recv([data, MPI.CHAR], source=1, tag=0)
print "Python received message from C:",data
# disconnecting the shared communicators is required to finalize the spawned process.
common_comm.Disconnect()
sub_comm.Disconnect()
mpicc slave.c -o slavec -Wall
编译的C代码使用合并的通信器发送字符:
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char *argv[])
{
int rank,size;
MPI_Comm parentcomm,intracomm;
MPI_Init( &argc, &argv );
//MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_get_parent( &parentcomm );
if (parentcomm == MPI_COMM_NULL){fprintf(stderr,"module1 : i'm supposed to be the spawned process!");exit(1);}
MPI_Intercomm_merge(parentcomm,1,&intracomm);
MPI_Comm_size(intracomm, &size);
MPI_Comm_rank(intracomm, &rank);
//printf("child had rank %d in communicator of size %d\n",rank,size);
char s= 42;
printf("sending message %d from C\n",s);
MPI_Send(&s,1,MPI_CHAR,0,0,intracomm);
MPI_Comm_disconnect(&intracomm); //disconnect after all communications
MPI_Comm_disconnect(&parentcomm);
MPI_Finalize();
return 0;
}
让我们从 C++ 代码接收一个字符并将一个整数发送到 Fortran 程序:
'''
slavecpp is an executable built starting from slave.cpp
'''
# Spawing a process running an executable
# sub_comm is an MPI intercommunicator
sub_comm = MPI.COMM_SELF.Spawn('slavecpp', args=[], maxprocs=1)
# common_comm is an intracommunicator accross the python process and the spawned process. All kind sof collective communication (Bcast...) are now possible between the python process and the c process
common_comm=sub_comm.Merge(False)
#print 'parent in common_comm ', common_comm.Get_rank(), ' of ',common_comm.Get_size()
data = numpy.arange(1, dtype='int8')
common_comm.Recv([data, MPI.CHAR], source=1, tag=0)
print "Python received message from C++:",data
# disconnecting the shared communicators is required to finalize the spawned process.
common_comm.Disconnect()
sub_comm.Disconnect()
'''
slavef90 is an executable built starting from slave.cpp
'''
# Spawing a process running an executable
# sub_comm is an MPI intercommunicator
sub_comm = MPI.COMM_SELF.Spawn('slavef90', args=[], maxprocs=1)
# common_comm is an intracommunicator accross the python process and the spawned process. All kind sof collective communication (Bcast...) are now possible between the python process and the c process
common_comm=sub_comm.Merge(False)
#print 'parent in common_comm ', common_comm.Get_rank(), ' of ',common_comm.Get_size()
data = numpy.arange(1, dtype='int32')
data[0]=42
print "Python sending message to fortran:",data
common_comm.Send([data, MPI.INT], dest=1, tag=0)
print "Python over"
# disconnecting the shared communicators is required to finalize the spawned process.
common_comm.Disconnect()
sub_comm.Disconnect()
mpiCC slave.cpp -o slavecpp -Wall
编译的C++程序与C程序非常接近:
#include <iostream>
#include <mpi.h>
#include <stdlib.h>
using namespace std;
int main(int argc,char *argv[])
{
int rank,size;
MPI_Comm parentcomm,intracomm;
MPI_Init( &argc, &argv );
//MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_get_parent( &parentcomm );
if (parentcomm == MPI_COMM_NULL){fprintf(stderr,"module1 : i'm supposed to be the spawned process!");exit(1);}
MPI_Intercomm_merge(parentcomm,1,&intracomm);
MPI_Comm_size(intracomm, &size);
MPI_Comm_rank(intracomm, &rank);
//cout<<"child had rank "<<rank<<" in communicator of size "<<size<<endl;
char s= 42;
cout<<"sending message "<<(int)s<<" from C++"<<endl;
MPI_Send(&s,1,MPI_CHAR,0,0,intracomm);
MPI_Comm_disconnect(&intracomm); //disconnect after all communications
MPI_Comm_disconnect(&parentcomm);
MPI_Finalize();
return 0;
}
最后mpif90 slave.f90 -o slavef90 -Wall
编译的Fortran程序收到整数:
program test
!
implicit none
!
include 'mpif.h'
!
integer :: ierr,s(1),stat(MPI_STATUS_SIZE)
integer :: parentcomm,intracomm
!
call MPI_INIT(ierr)
call MPI_COMM_GET_PARENT(parentcomm, ierr)
call MPI_INTERCOMM_MERGE(parentcomm, 1, intracomm, ierr)
call MPI_RECV(s, 1, MPI_INTEGER, 0, 0, intracomm,stat, ierr)
print*, 'fortran program received: ', s
call MPI_COMM_DISCONNECT(intracomm, ierr)
call MPI_COMM_DISCONNECT(parentcomm, ierr)
call MPI_FINALIZE(ierr)
endprogram test
在通信器上多做一些工作,"C++ process" 可以直接向 "fortran process" 发送消息,甚至不需要主进程参与通信。
最后,以这种方式混合语言看似容易,但从长远来看可能不是一个好的解决方案。事实上,您可能会面临与性能相关的问题,或者维护系统可能会变得困难(三种语言......)。对于 C++ 部分,Cython and F2PY can be a valuable alternative. After all, Python is a little bit like a glue...
我正在开发一种工具来模拟波能转换器,我需要将两个软件包相互耦合。一个程序是用 Fortran 编写的,另一个是用 C++ 编写的。我需要在每个时间步将信息从 Fortran 程序发送到 C++ 程序。但是,数据首先需要在 Python 中进行处理,然后才能发送到 C++ 程序。我收到了使用 MPI 在程序之间传输数据的提示。
我现在正尝试将一个简单的字符串从 Fortran 代码发送到 Python,但是 Python 代码卡在接收命令中。
我的 Fortran 代码如下所示:
USE GlobalVariables
USE MPI
IMPLICIT NONE
CHARACTER(LEN=10):: astring
INTEGER :: comm, rank, size, mpierr
! Initialize MPI on first timestep
IF(tstep .LT. 2) THEN
call MPI_INIT(mpierr)
ENDIF
! make string to send to python
astring = "TEST"
! MPI Test
call MPI_Comm_size(MPI_COMM_WORLD, size, mpierr)
call MPI_Comm_rank(MPI_COMM_WORLD, rank, mpierr)
! Send message to python
CALL MPI_SEND(astring, len(astring), MPI_CHARACTER, 0, 22, MPI_COMM_WORLD, mpierr)
print *, 'MPI MESSAGE SENT ', mpierr
! Initialize MPI on first timestep
IF(tstep .EQ. Nsteps-1) THEN
call MPI_FINALIZE(mpierr)
print *, 'MPI FINALIZED!'
ENDIF
我的 Python 代码如下:
from mpi4py import MPI
import numpy as np
import subprocess as sp
import os
# Start OW3D_SPH in the background and send MPI message
os.chdir('OW3D_run')
args = ['OceanWave3D_SPH','OW3D.inp']
pid = sp.Popen(args,shell=False)
os.chdir('..')
# Check if MPI is initialized
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
# Receive message from fortran
test = comm.recv(source=0, tag=22)
# Let the program end
output = pid.communicate()
with open('test.txt','w') as f:
f.write(test)
Python 代码永远不会通过 MPI 接收命令,也不会完成。 Fortran 代码确实完成并正确打印了 "MPI FINALIZED" 消息。
我看不出哪里做错了,消息从进程 0 发送到进程 0,带有标记 22,并在两个代码中使用 MPI_COMM_WORLD
。
我不会为此目的使用 MPI(除非明确要求并行执行代码)。如果您的目标是连接用 Fortran、C++ 和 Python 编写的例程,那么我建议在 Python 中编写(主要)连接部分,同时为您的 Fortran 和 C++ 例程创建适配器,以便将它们导入 Python。然后你可以管理主程序中的所有函数调用 Python 并根据需要发送数据。
查看以下链接:
运行 Python
中的 Fortran 代码- f2py @ numpy:
f2py
现在随附numpy
允许您将 Fortran 源代码编译为 Python 字节代码。
运行 Python
中的 C++ 代码当源和目标是不同的程序时,您当然不能同时将源和目标都设置为 0。你说 "from process 0 to process 0" 但你显然有两个不同的过程!其中之一有一些不同的等级编号,但您没有显示实际的 mpirun
命令,所以很难说哪个是哪个。
澄清一下:MPI_COM_WORLD 是在您的 mpirun 或等效程序中执行的所有进程的通信器。你必须离开简单的思维画面,第一个 Python 进程是等级 0,第一个 Fortran 进程是等级 0,第一个 C++ 是等级 0...
如果你这样做
mpirun -n 1 python main.py : -n 1 ./fortran_main : -n 1 ./c++_main
然后在 MPI_COMM_WORLD 中,Python 程序将排在第 0 位,Fortran 进程将排在第 1 位,C++ 将排在第 2 位。您只能在 [=26 本地创建通信器=] 子集或 Fortran 子集或 C++ 子集,您将在每个子集中排名 0,但这将在不同的通信器中编号,而不是在 MPI_COMM_WORLD.
中如果您要在同一个 MPI 作业中同时启动 Fortran 程序和 Python 程序,您必须使用类似的东西:
mpiexec -n 1 fortran_program : -n 1 python main.py
Fortran 程序将变为 MPI 级别 0,而 Python 程序将变为 MPI 级别 1。您还可以启动多个可执行文件,例如:
mpiexec -n 2 fortran_program : -n 4 python main.py
排名 0 和 1 将来自 Fortran 程序,排名 2 到 5 - 来自 Python。
另请注意,comm.recv()
和 mpi4py 中其他以小写字母开头的通信方法(comm.send()
、comm.irecv()
等)在后台使用 Pickle,并且实际使用序列化 Python 个对象。这与 Fortran 代码发送的字符数组不兼容。您必须使用以大写字母开头的通信方法(comm.Send()
、comm.Recv()
等),它们对 NumPy 数组进行操作并接收显式类型信息。不幸的是,我的 Python fu 很弱,我现在无法提供完整的工作示例,但 MPI 部分应该是这样的(未验证代码):
# Create an MPI status object
status = MPI.Status()
# Wait for a message without receiving it
comm.Probe(source=0, tag=22, status=status)
# Check the length of the message
nchars = status.Get_count(MPI.CHARACTER)
# Allocate a big enough data array of characters
data = np.empty(nchars, dtype='S')
# Receive the message
comm.Recv([data, MPI.CHARACTER], source=0, tag=22)
# Construct somehow the string out of the individual chars in "data"
在 Fortran 代码中,您必须指定目标等级 1(如果您是 运行 一个 Fortran 可执行文件和一个 Python 一个)。
MPI进程可以通过函数MPI_Comm_spawn()
. In a python program, this function is a method of the communicator: comm.Spawn()
. See the mpi4py tutorial for an example. The spawned process is ran according to an executable which could be another python program, a c/c++/fortran program or whatever you want. Then, an intercommunicator can be merged to define an intracommunicator between the master process and the spawned ones as performed in
我们来介绍一个Python/c的例子。 Python 代码生成进程并接收一个字符:
from mpi4py import MPI
import sys
import numpy
'''
slavec is an executable built starting from slave.c
'''
# Spawing a process running an executable
# sub_comm is an MPI intercommunicator
sub_comm = MPI.COMM_SELF.Spawn('slavec', args=[], maxprocs=1)
# common_comm is an intracommunicator accross the python process and the spawned process. All kind sof collective communication (Bcast...) are now possible between the python process and the c process
common_comm=sub_comm.Merge(False)
#print 'parent in common_comm ', common_comm.Get_rank(), ' of ',common_comm.Get_size()
data = numpy.arange(1, dtype='int8')
common_comm.Recv([data, MPI.CHAR], source=1, tag=0)
print "Python received message from C:",data
# disconnecting the shared communicators is required to finalize the spawned process.
common_comm.Disconnect()
sub_comm.Disconnect()
mpicc slave.c -o slavec -Wall
编译的C代码使用合并的通信器发送字符:
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char *argv[])
{
int rank,size;
MPI_Comm parentcomm,intracomm;
MPI_Init( &argc, &argv );
//MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_get_parent( &parentcomm );
if (parentcomm == MPI_COMM_NULL){fprintf(stderr,"module1 : i'm supposed to be the spawned process!");exit(1);}
MPI_Intercomm_merge(parentcomm,1,&intracomm);
MPI_Comm_size(intracomm, &size);
MPI_Comm_rank(intracomm, &rank);
//printf("child had rank %d in communicator of size %d\n",rank,size);
char s= 42;
printf("sending message %d from C\n",s);
MPI_Send(&s,1,MPI_CHAR,0,0,intracomm);
MPI_Comm_disconnect(&intracomm); //disconnect after all communications
MPI_Comm_disconnect(&parentcomm);
MPI_Finalize();
return 0;
}
让我们从 C++ 代码接收一个字符并将一个整数发送到 Fortran 程序:
'''
slavecpp is an executable built starting from slave.cpp
'''
# Spawing a process running an executable
# sub_comm is an MPI intercommunicator
sub_comm = MPI.COMM_SELF.Spawn('slavecpp', args=[], maxprocs=1)
# common_comm is an intracommunicator accross the python process and the spawned process. All kind sof collective communication (Bcast...) are now possible between the python process and the c process
common_comm=sub_comm.Merge(False)
#print 'parent in common_comm ', common_comm.Get_rank(), ' of ',common_comm.Get_size()
data = numpy.arange(1, dtype='int8')
common_comm.Recv([data, MPI.CHAR], source=1, tag=0)
print "Python received message from C++:",data
# disconnecting the shared communicators is required to finalize the spawned process.
common_comm.Disconnect()
sub_comm.Disconnect()
'''
slavef90 is an executable built starting from slave.cpp
'''
# Spawing a process running an executable
# sub_comm is an MPI intercommunicator
sub_comm = MPI.COMM_SELF.Spawn('slavef90', args=[], maxprocs=1)
# common_comm is an intracommunicator accross the python process and the spawned process. All kind sof collective communication (Bcast...) are now possible between the python process and the c process
common_comm=sub_comm.Merge(False)
#print 'parent in common_comm ', common_comm.Get_rank(), ' of ',common_comm.Get_size()
data = numpy.arange(1, dtype='int32')
data[0]=42
print "Python sending message to fortran:",data
common_comm.Send([data, MPI.INT], dest=1, tag=0)
print "Python over"
# disconnecting the shared communicators is required to finalize the spawned process.
common_comm.Disconnect()
sub_comm.Disconnect()
mpiCC slave.cpp -o slavecpp -Wall
编译的C++程序与C程序非常接近:
#include <iostream>
#include <mpi.h>
#include <stdlib.h>
using namespace std;
int main(int argc,char *argv[])
{
int rank,size;
MPI_Comm parentcomm,intracomm;
MPI_Init( &argc, &argv );
//MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_get_parent( &parentcomm );
if (parentcomm == MPI_COMM_NULL){fprintf(stderr,"module1 : i'm supposed to be the spawned process!");exit(1);}
MPI_Intercomm_merge(parentcomm,1,&intracomm);
MPI_Comm_size(intracomm, &size);
MPI_Comm_rank(intracomm, &rank);
//cout<<"child had rank "<<rank<<" in communicator of size "<<size<<endl;
char s= 42;
cout<<"sending message "<<(int)s<<" from C++"<<endl;
MPI_Send(&s,1,MPI_CHAR,0,0,intracomm);
MPI_Comm_disconnect(&intracomm); //disconnect after all communications
MPI_Comm_disconnect(&parentcomm);
MPI_Finalize();
return 0;
}
最后mpif90 slave.f90 -o slavef90 -Wall
编译的Fortran程序收到整数:
program test
!
implicit none
!
include 'mpif.h'
!
integer :: ierr,s(1),stat(MPI_STATUS_SIZE)
integer :: parentcomm,intracomm
!
call MPI_INIT(ierr)
call MPI_COMM_GET_PARENT(parentcomm, ierr)
call MPI_INTERCOMM_MERGE(parentcomm, 1, intracomm, ierr)
call MPI_RECV(s, 1, MPI_INTEGER, 0, 0, intracomm,stat, ierr)
print*, 'fortran program received: ', s
call MPI_COMM_DISCONNECT(intracomm, ierr)
call MPI_COMM_DISCONNECT(parentcomm, ierr)
call MPI_FINALIZE(ierr)
endprogram test
在通信器上多做一些工作,"C++ process" 可以直接向 "fortran process" 发送消息,甚至不需要主进程参与通信。
最后,以这种方式混合语言看似容易,但从长远来看可能不是一个好的解决方案。事实上,您可能会面临与性能相关的问题,或者维护系统可能会变得困难(三种语言......)。对于 C++ 部分,Cython and F2PY can be a valuable alternative. After all, Python is a little bit like a glue...