Можно ли отправлять данные из программы Fortran на Python с использованием MPI?

Я работаю над инструментом моделирования волновых энергетических преобразователей, где мне нужно связать два пакета программного обеспечения друг с другом. Одна программа написана в Fortran, другая - на С++. Мне нужно отправить информацию из программы Fortran в программу на С++ на каждом временном шаге. Однако сначала данные должны обрабатываться на Python перед отправкой в ​​программу на С++. Я получил подсказку для использования MPI для передачи данных между программами.

Теперь я пытаюсь отправить простую строку из кода Fortran в Python, но код Python застрял в команде receive.

Мой форвардный код выглядит следующим образом:

      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 в обоих кодах.

Ответ 1

Если вы запустите как программу Fortran, так и Python в одном и том же задании MPI, вы должны использовать что-то вроде:

mpiexec -n 1 fortran_program : -n 1 python main.py

Программа Fortran станет рангами MPI 0, а программа Python будет ранга 1 MPI. Вы также можете запустить более одного из исполняемых файлов, например:

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).

Ответ 2

У вас, конечно, не может быть как источник, так и пункт назначения 0, когда обе программы разные. Вы говорите "от процесса 0 до обработки 0", но у вас явно есть два разных процесса! У одного из них есть несколько разных номеров рангов, но вы не показываете свою фактическую команду mpirun, поэтому трудно сказать, какой из них.

Чтобы уточнить: MPI_COM_WORLD является коммуникатором для всех процессов, выполняемых в вашем mpirun или эквиваленте. Вы должны оставить простую картину разума, что первый процесс Python - это ранг 0, первый процесс Fortran - это ранг 0, первый С++ - ранг 0...

Если вы делаете

mpirun -n 1 python main.py : -n 1 ./fortran_main : -n 1 ./c++_main

то в MPI_COMM_WORLD программа Python будет ранга 0, процесс Fortran будет ранг 1, а С++ будет ранг 2. Вы можете создавать коммуникаторы, локальные только для подмножества Python или для подмножества Fortran или С++, и вы будет иметь ранг 0 в каждом из них, но это будет нумерация в другом коммуникаторе, а не в MPI_COMM_WORLD.

Ответ 3

Процесс MPI может запускать процессы с помощью функции MPI_Comm_spawn(). В программе python это Функция - это метод коммуникатора: comm.Spawn(). См. учебник mpi4py для примера. Инициализированный процесс запускается в соответствии с исполняемым файлом, который может быть другой программой python, программой ac/С++/fortran или тем, что вы хотите. Затем, intercommunicator может быть объединить, чтобы определить внутрикоммутатор между основным процессом и порожденными, как это сделано в 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;
}

Позвольте получить символ из кода С++ и отправить целое число в программу 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:

#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

При работе с коммуникаторами немного больше, "процесс С++" может отправить сообщение непосредственно в "процесс fortran", даже не вовлекая мастер-процесс в сообщение.

Наконец, смешивание языков таким образом может показаться легким, но в долгосрочной перспективе это не может быть хорошим решением. В самом деле, вы можете столкнуться с проблемами, связанными с исполнением или поддерживать систему, может стать трудной (три языка...). Для части С++ Cython и F2PY может быть ценной альтернативой. В конце концов, Python немного похож на клей...

Ответ 4

Я бы не использовал MPI для этой цели (если явно не требуется параллельное выполнение кода). Если вы хотите подключить подпрограммы, написанные на языках Fortran, С++ и Python, я предлагаю написать (основную) часть соединения в Python при создании адаптеров для ваших программ Fortran и С++, чтобы импортировать их в Python. Затем вы можете управлять всеми вызовами функций в основной программе Python и отправлять данные по своему желанию.

Ознакомьтесь со следующими ссылками:

Запуск кода Fortran в Python

  • f2py @numpy: f2py теперь поставляется с numpy, позволяющим скомпилировать исходный код Fortran в байт-код Python.

Запуск кода на С++ в Python