Search code examples
pythoncmpicythonmpi4py

How to pass an MPI communicator from python to C via cython?


I am trying to wrap a C function taking an MPI_Comm communicator handle as a parameter via cython. As a result, I want to be able to call the function from python, passing it an mpi4py.MPI.Comm object. What I am wondering is, how to make the conversion from mpi4py.MPI.Comm to MPI_Comm.

To demonstrate, I use a simple "Hello World!"-type function:

helloworld.h:

#ifndef HELLOWORLD
#define HELLOWORLD
#include <mpi.h>

void sayhello(MPI_Comm comm);

#endif

helloworld.c:

#include <stdio.h>
#include "helloworld.h"

void sayhello(MPI_Comm comm){
    int size, rank;
    MPI_Comm_size(comm, &size);
    MPI_Comm_rank(comm, &rank);
    printf("Hello, World! "
           "I am process %d of %d.\n",
           rank, size);
}

I now want to call this function from python like this:

from_python.py:

import mpi4py
import helloworld_wrap

helloworld_wrap.py_sayhello(mpi4py.MPI.COMM_WORLD)

Meaning that mpirun -np 4 python2 from_python.py is supposed to give something like:

Hello, World! I am process 0 of 4.
Hello, World! I am process 1 of 4.
Hello, World! I am process 2 of 4.
Hello, World! I am process 3 of 4.

But if I try to achieve this via cython like this:

helloworld_wrap.pyx:

cimport mpi4py.MPI as MPI
cimport mpi4py.libmpi as libmpi

cdef extern from "helloworld.h":
   void sayhello(libmpi.MPI_Comm comm)

def py_sayhello(MPI.Comm comm):
    sayhello(comm)

and:

setup.py:

import os
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

mpi_compile_args = os.popen("mpicc --showme:compile").read().strip().split(' ')
mpi_link_args    = os.popen("mpicc --showme:link").read().strip().split(' ')

ext_modules=[
    Extension("helloworld_wrap",
              sources            = ["helloworld_wrap.pyx", "helloworld.c"],
              language           = 'c',
              extra_compile_args = mpi_compile_args,
              extra_link_args    = mpi_link_args,
          )
]

setup(
  name = "helloworld_wrap",
  cmdclass = {"build_ext": build_ext},
  ext_modules = ext_modules
)

I get the following error message:

helloworld_wrap.pyx:8:13: Cannot convert Python object to 'MPI_Comm'

indicating that mpi4py.MPI.Comm can't be converted into an MPI_Comm. So how can I convert the mpi4py.MPI.Comm into an MPI_Comm in order to get my wrapper to work?


Solution

  • The conversion is rather simple, as a mpi4py.MPI.Comm-object internally stores an MPI_Comm handle as member ob_mpi1. Therefore, if one changes the last line of helloworld_wrap.pyx to pass comm.ob_mpi instead of comm, the module compiles and works as intended:

    helloworld_wrap.pyx:

    cimport mpi4py.MPI as MPI
    cimport mpi4py.libmpi as libmpi
    
    cdef extern from "helloworld.h":
       void sayhello(libmpi.MPI_Comm comm)
    
    def py_sayhello(MPI.Comm comm):
        sayhello(comm.ob_mpi)
    

    Surprisingly, I did not find any documentation for this but only realized this when studying the sources of mpi4py.MPI.Comm. I am not sure, if this is the intended way to handle this, but I could not get it to work otherwise.


    1 In fact, most, if not all objects in mpi4py.MPI that model a corresponding MPI handle in C, hold a corresponding handle as member ob_mpi.