Можно ли отправлять данные из программы Fortran на Python с помощью MPI?
Я работаю над инструментом для моделирования преобразователей энергии волн, где мне нужно соединить два программных пакета друг с другом. Одна программа написана на Fortran, другая-на C++. Мне нужно отправлять информацию из программы Fortran в программу C++ на каждом временном шаге. Однако данные сначала должны быть обработаны в Python, прежде чем они будут отправлены в программу C++. Я получил совет использовать MPI для передачи данных между программами.
Теперь я пытаюсь отправить простой строка из кода Fortran в Python, но код Python застревает в команде receive.
мой код 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 receive и не завершается. Код Fortran завершает и правильно печатает сообщение" MPI FINALIZED".
Я не вижу, где я делаю что-то неправильно, сообщение отправляется из процесса 0 в процесс 0 с тегом 22 и использует MPI_COMM_WORLD
в обоих кодексах.
4 ответов
если вы хотите запустить программу Fortran и Python в одном задании MPI, вы должны использовать что-то вроде:
mpiexec -n 1 fortran_program : -n 1 python main.py
программа Fortran станет MPI rank 0, а программа Python будет MPI rank 1. Вы также можете запустить несколько исполняемых файлов, например:
mpiexec -n 2 fortran_program : -n 4 python main.py
ранги 0 и 1 будут из программы Fortran, ранги 2 до 5 - из Python one.
также обратите внимание, что comm.recv()
и другие методы связи в mpi4py, которые начинаются с маленькой буквы (comm.send()
, comm.irecv()
, etc.) используйте рассол под капотом и фактически работайте с сериализованными объектами Python. Это несовместимо с массивом символов, отправленным кодом Fortran. Вы должны использовать методы связи, которые начинаются с заглавной буквы (comm.Send()
, comm.Recv()
, etc.), которые работают с массивами 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).
вы, конечно, не можете иметь как источник, так и назначение 0, когда оба являются разными программами. Ты говоришь "от процесса 0 к процессу 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. Вы можете создать коммуникаторы, локальные только для подмножества Python или для подмножества Fortran или C++, и в каждом из них у вас будет ранг 0, но это будет нумерация в другом коммуникаторе, а не в MPI_COMM_WORLD.
процесс MPI может порождать процессы с помощью функции MPI_Comm_spawn()
. в программе python эта функция является методом коммуникатора:comm.Spawn()
. См.в mpi4py учебник для примера. порожденный процесс запускается в соответствии с исполняемым файлом, который может быть другой программой python, программой c/C++/fortran или чем угодно. затем intercommunicator может быть слил для определения intracommunicator между основным процессом и порожденными, как это выполняется в mpi4py: общение между порожденными процессами в результате главный процесс и породил процессы могут свободно общаться без каких-либо ограничений.
давайте представим пример 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()
код C, скомпилированный mpicc slave.c -o slavec -Wall
отправляет символ с помощью объединенного коммуникатор:
#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()
программа C++, скомпилированная mpiCC slave.cpp -o slavecpp -Wall
очень близко к C one:
#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;
}
наконец, программа Fortran, скомпилированная mpif90 slave.f90 -o slavef90 -Wall
получает целое число:
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++" смог послать сообщение сразу к "процессу fortran" , без даже включать мастер-процесс в общении.
наконец, смешивание языков таким образом может показаться легким, но это может быть не хорошим решением в долгосрочной перспективе. Действительно, Вы можете столкнуться с проблемами, связанными с выступлениями или поддержанием системы, может стать сложным(три языка...). Для части C++на Cython и F2PY может быть ценной альтернативой. Ведь Python немного похож на клей...
Я бы не использовал MPI для этой цели (если только явно не требуется параллельное выполнение кода). Если ваша цель-подключить подпрограммы, написанные на Fortran, C++ и Python, я предлагаю написать (основную) соединительную часть в Python, создавая адаптеры для ваших подпрограмм Fortran и C++, чтобы импортировать их в Python. Затем вы можете управлять всеми вызовами функций в основной программе Python и отправлять данные по своему усмотрению.
проверьте следующие ссылки:
запуск кода Fortran в Python
-
f2py @ numpy:
f2py
теперь корабли сnumpy
позволяет компилировать исходный код Fortran в байтовый код Python.