Provided by: python-mpi4py-doc_3.0.3-4build2_all bug

NAME

       mpi4py - MPI for Python

       Author Lisandro Dalcin

       Contact
              dalcinl@gmail.com

       Web Site
              https://bitbucket.org/mpi4py/mpi4py

       Date   February 28, 2020

   Abstract
       This  document  describes  the  MPI  for Python package.  MPI for Python provides bindings of the Message
       Passing Interface (MPI) standard for the Python programming language,  allowing  any  Python  program  to
       exploit multiple processors.

       This  package  is  constructed  on  top  of  the MPI-1/2/3 specifications and provides an object oriented
       interface which resembles the MPI-2 C++  bindings.  It  supports  point-to-point  (sends,  receives)  and
       collective  (broadcasts,  scatters,  gathers)  communications  of any picklable Python object, as well as
       optimized communications of Python object exposing the single-segment  buffer  interface  (NumPy  arrays,
       builtin bytes/string/array objects)

INTRODUCTION

       Over  the  last  years,  high  performance  computing  has  become  an  affordable  resource to many more
       researchers in the scientific community than ever before. The conjunction of quality open source software
       and  commodity  hardware  strongly influenced the now widespread popularity of Beowulf class clusters and
       cluster of workstations.

       Among many parallel computational models, message-passing has  proven  to  be  an  effective  one.   This
       paradigm  is  specially  suited  for (but not limited to) distributed memory architectures and is used in
       today’s most demanding scientific and engineering application related to  modeling,  simulation,  design,
       and  signal processing.  However, portable message-passing parallel programming used to be a nightmare in
       the past because of the many incompatible options developers were faced to.  Fortunately, this  situation
       definitely changed after the MPI Forum released its standard specification.

       High  performance  computing  is  traditionally  associated  with  software  development  using  compiled
       languages. However, in typical applications programs, only a small part  of  the  code  is  time-critical
       enough  to  require  the  efficiency  of compiled languages. The rest of the code is generally related to
       memory management, error handling, input/output, and user interaction, and those  are  usually  the  most
       error  prone  and  time-consuming  lines  of  code  to  write and debug in the whole development process.
       Interpreted high-level languages can be really advantageous for this kind of tasks.

       For  implementing  general-purpose  numerical  computations,  MATLAB  [1]  is  the  dominant  interpreted
       programming  language.  In  the  open  source  side, Octave and Scilab are well known, freely distributed
       software packages providing compatibility with the MATLAB language. In this  work,  we  present  MPI  for
       Python,  a  new package enabling applications to exploit multiple processors using standard MPI “look and
       feel” in Python scripts.

       [1]  MATLAB is a registered trademark of The MathWorks, Inc.

   What is MPI?
       MPI, [mpi-using] [mpi-ref] the Message Passing Interface, is a standardized and portable  message-passing
       system  designed to function on a wide variety of parallel computers. The standard defines the syntax and
       semantics of library routines and allows  users  to  write  portable  programs  in  the  main  scientific
       programming languages (Fortran, C, or C++).

       Since  its  release,  the  MPI  specification  [mpi-std1]  [mpi-std2] has become the leading standard for
       message-passing libraries  for  parallel  computers.   Implementations  are  available  from  vendors  of
       high-performance  computers  and from well known open source projects like MPICH [mpi-mpich] and Open MPI
       [mpi-openmpi].

   What is Python?
       Python is a modern, easy to learn, powerful  programming  language.  It  has  efficient  high-level  data
       structures  and  a  simple  but effective approach to object-oriented programming with dynamic typing and
       dynamic binding. It supports modules and packages, which encourages program modularity  and  code  reuse.
       Python’s  elegant  syntax,  together with its interpreted nature, make it an ideal language for scripting
       and rapid application development in many areas on most platforms.

       The Python interpreter and the extensive standard library are available in source or binary form  without
       charge  for  all major platforms, and can be freely distributed. It is easily extended with new functions
       and data types implemented in C or C++. Python is also suitable as an extension language for customizable
       applications.

       Python  is  an  ideal candidate for writing the higher-level parts of large-scale scientific applications
       [Hinsen97] and driving simulations in parallel architectures [Beazley97] like clusters of PC’s or  SMP’s.
       Python  codes are quickly developed, easily maintained, and can achieve a high degree of integration with
       other libraries written in compiled languages.

   Related Projects
       As this work started and evolved, some ideas were borrowed from well known MPI and  Python  related  open
       source projects from the Internet.

       • OOMPI

         • It has not relation with Python, but is an excellent object oriented approach to MPI.

         • It is a C++ class library specification layered on top of the C bindings that encapsulates MPI into a
           functional class hierarchy.

         • It provides a flexible and intuitive interface by adding some abstractions, like Ports and  Messages,
           which enrich and simplify the syntax.

       • Pypar

         • Its interface is rather minimal. There is no support for communicators or process topologies.

         • It  does  not  require  the  Python  interpreter  to  be  modified or recompiled, but does not permit
           interactive parallel runs.

         • General (picklable) Python objects of any type can be communicated. There is good support for numeric
           arrays, practically full MPI bandwidth can be achieved.

       • pyMPI

         • It  rebuilds  the  Python interpreter providing a built-in module for message passing. It does permit
           interactive parallel runs, which are useful for learning and debugging.

         • It provides an interface suitable for basic parallel programing.   There  is  not  full  support  for
           defining new communicators or process topologies.

         • General  (picklable)  Python  objects  can  be  messaged between processors. There is not support for
           numeric arrays.

       • Scientific Python

         • It provides a collection of Python modules that are useful for scientific computing.

         • There is an interface to MPI and BSP (Bulk Synchronous Parallel programming).

         • The interface is simple but incomplete and does not resemble the MPI specification. There is  support
           for numeric arrays.

       Additionally,  we  would  like  to  mention  some  available  tools for scientific computing and software
       development with Python.

       • NumPy is a package that provides array manipulation and computational  capabilities  similar  to  those
         found  in  IDL,  MATLAB,  or Octave. Using NumPy, it is possible to write many efficient numerical data
         processing applications directly in Python without using any C, C++ or Fortran code.

       • SciPy is an open source library of scientific tools for Python,  gathering  a  variety  of  high  level
         science  and  engineering  modules  together  as a single package. It includes modules for graphics and
         plotting,  optimization,  integration,  special  functions,  signal  and  image   processing,   genetic
         algorithms, ODE solvers, and others.

       • Cython  is a language that makes writing C extensions for the Python language as easy as Python itself.
         The Cython language is very close to the Python language, but Cython additionally  supports  calling  C
         functions and declaring C types on variables and class attributes. This allows the compiler to generate
         very efficient C code from Cython code. This makes Cython the ideal language for wrapping for  external
         C libraries, and for fast C modules that speed up the execution of Python code.

       • SWIG  is  a  software  development  tool  that connects programs written in C and C++ with a variety of
         high-level programming languages like Perl, Tcl/Tk, Ruby and Python. Issuing header files  to  SWIG  is
         the simplest approach to interfacing C/C++ libraries from a Python module.

       [mpi-std1]
            MPI  Forum.  MPI:  A  Message  Passing  Interface  Standard.  International Journal of Supercomputer
            Applications, volume 8, number 3-4, pages 159-416, 1994.

       [mpi-std2]
            MPI Forum. MPI: A Message Passing Interface  Standard.   High  Performance  Computing  Applications,
            volume 12, number 1-2, pages 1-299, 1998.

       [mpi-using]
            William  Gropp, Ewing Lusk, and Anthony Skjellum.  Using MPI: portable parallel programming with the
            message-passing interface.  MIT Press, 1994.

       [mpi-ref]
            Mark Snir, Steve Otto, Steven Huss-Lederman, David Walker, and Jack Dongarra.  MPI  -  The  Complete
            Reference, volume 1, The MPI Core.  MIT Press, 2nd. edition, 1998.

       [mpi-mpich]
            W. Gropp, E. Lusk, N. Doss, and A. Skjellum.  A high-performance, portable implementation of the MPI
            message passing interface standard.  Parallel Computing, 22(6):789-828, September 1996.

       [mpi-openmpi]
            Edgar Gabriel, Graham E. Fagg, George Bosilca, Thara Angskun, Jack J. Dongarra, Jeffrey M.  Squyres,
            Vishal  Sahay,  Prabhanjan  Kambadur,  Brian  Barrett,  Andrew Lumsdaine, Ralph H. Castain, David J.
            Daniel, Richard L. Graham, and Timothy S. Woodall. Open MPI: Goals, Concept, and Design  of  a  Next
            Generation MPI Implementation. In Proceedings, 11th European PVM/MPI Users’ Group Meeting, Budapest,
            Hungary, September 2004.

       [Hinsen97]
            Konrad Hinsen.  The Molecular Modelling Toolkit: a case study of a large scientific  application  in
            Python.   In  Proceedings  of  the  6th International Python Conference, pages 29-35, San Jose, Ca.,
            October 1997.

       [Beazley97]
            David M. Beazley and Peter S. Lomdahl.  Feeding a large-scale physics  application  to  Python.   In
            Proceedings of the 6th International Python Conference, pages 21-29, San Jose, Ca., October 1997.

OVERVIEW

       MPI  for  Python  provides  an  object oriented approach to message passing which grounds on the standard
       MPI-2 C++ bindings. The interface was designed with focus in translating  MPI  syntax  and  semantics  of
       standard  MPI-2 bindings for C++ to Python. Any user of the standard C/C++ MPI bindings should be able to
       use this module without need of learning a new interface.

   Communicating Python Objects and Array Data
       The Python standard library supports different mechanisms for data persistence. Many of them rely on disk
       storage, but pickling and marshaling can also work with memory buffers.

       The  pickle modules provide user-extensible facilities to serialize general Python objects using ASCII or
       binary formats. The marshal module provides facilities to  serialize  built-in  Python  objects  using  a
       binary format specific to Python, but independent of machine architecture issues.

       MPI  for  Python  can  communicate  any  built-in  or  user-defined Python object taking advantage of the
       features provided by the pickle  module.  These  facilities  will  be  routinely  used  to  build  binary
       representations  of  objects to communicate (at sending processes), and restoring them back (at receiving
       processes).

       Although simple and general, the  serialization  approach  (i.e.,  pickling  and  unpickling)  previously
       discussed imposes important overheads in memory as well as processor usage, especially in the scenario of
       objects with large memory footprints being communicated. Pickling general Python  objects,  ranging  from
       primitive  or  container built-in types to user-defined classes, necessarily requires computer resources.
       Processing is also needed for dispatching the appropriate serialization method (that depends on the  type
       of  the object) and doing the actual packing. Additional memory is always needed, and if its total amount
       is not known a priori, many reallocations can occur.  Indeed, in the case of large numeric  arrays,  this
       is  certainly unacceptable and precludes communication of objects occupying half or more of the available
       memory resources.

       MPI for Python supports direct communication of any object exporting the single-segment buffer interface.
       This  interface is a standard Python mechanism provided by some types (e.g., strings and numeric arrays),
       allowing access in the C side to a contiguous memory buffer (i.e., address  and  length)  containing  the
       relevant  data.  This  feature,  in  conjunction  with  the  capability  of constructing user-defined MPI
       datatypes describing complicated memory layouts, enables the implementation of many algorithms  involving
       multidimensional  numeric  arrays  (e.g.,  image  processing,  fast Fourier transforms, finite difference
       schemes on structured Cartesian grids) directly in Python, with negligible overhead, and almost  as  fast
       as compiled Fortran, C, or C++ codes.

   Communicators
       In  MPI  for  Python,  MPI.Comm  is  the base class of communicators. The MPI.Intracomm and MPI.Intercomm
       classes are sublcasses of the MPI.Comm class.  The MPI.Comm.Is_inter() method  (and  MPI.Comm.Is_intra(),
       provided  for  convenience but not part of the MPI specification) is defined for communicator objects and
       can be used to determine the particular communicator class.

       The two predefined intracommunicator instances are  available:  MPI.COMM_SELF  and  MPI.COMM_WORLD.  From
       them, new communicators can be created as needed.

       The  number of processes in a communicator and the calling process rank can be respectively obtained with
       methods MPI.Comm.Get_size() and MPI.Comm.Get_rank(). The associated process group can be retrieved from a
       communicator  by  calling  the  MPI.Comm.Get_group()  method,  which returns an instance of the MPI.Group
       class. Set operations with MPI.Group  objects  like  like  MPI.Group.Union(),  MPI.Group.Intersect()  and
       MPI.Group.Difference()  are  fully  supported,  as  well  as the creation of new communicators from these
       groups using MPI.Comm.Create() and MPI.Comm.Create_group().

       New communicator instances can be obtained with the MPI.Comm.Clone(), MPI.Comm.Dup() and MPI.Comm.Split()
       methods, as well methods MPI.Intracomm.Create_intercomm() and MPI.Intercomm.Merge().

       Virtual  topologies (MPI.Cartcomm, MPI.Graphcomm and MPI.Distgraphcomm classes, which are specializations
       of the MPI.Intracomm class) are fully supported. New instances can  be  obtained  from  intracommunicator
       instances with factory methods MPI.Intracomm.Create_cart() and MPI.Intracomm.Create_graph().

   Point-to-Point Communications
       Point  to  point  communication  is  a  fundamental capability of message passing systems. This mechanism
       enables the transmission of data between a pair of processes, one side sending, the other receiving.

       MPI provides a set of send and receive functions  allowing  the  communication  of  typed  data  with  an
       associated tag.  The type information enables the conversion of data representation from one architecture
       to  another  in  the  case  of  heterogeneous  computing  environments;  additionally,  it   allows   the
       representation  of  non-contiguous data layouts and user-defined datatypes, thus avoiding the overhead of
       (otherwise unavoidable) packing/unpacking operations. The tag information allows selectivity of  messages
       at the receiving end.

   Blocking Communications
       MPI  provides basic send and receive functions that are blocking.  These functions block the caller until
       the data buffers involved in the communication can be safely reused by the application program.

       In MPI for Python, the MPI.Comm.Send(), MPI.Comm.Recv() and MPI.Comm.Sendrecv() methods  of  communicator
       objects provide support for blocking point-to-point communications within MPI.Intracomm and MPI.Intercomm
       instances. These methods can communicate memory buffers. The  variants  MPI.Comm.send(),  MPI.Comm.recv()
       and MPI.Comm.sendrecv() can communicate general Python objects.

   Nonblocking Communications
       On many systems, performance can be significantly increased by overlapping communication and computation.
       This is particularly true on systems where communication can be executed autonomously by an  intelligent,
       dedicated communication controller.

       MPI provides nonblocking send and receive functions. They allow the possible overlap of communication and
       computation.  Non-blocking communication always come in two parts: posting  functions,  which  begin  the
       requested  operation;  and  test-for-completion  functions, which allow to discover whether the requested
       operation has completed.

       In MPI  for  Python,  the  MPI.Comm.Isend()  and  MPI.Comm.Irecv()  methods  initiate  send  and  receive
       operations,  respectively.  These methods return a MPI.Request instance, uniquely identifying the started
       operation.   Its  completion  can  be  managed  using  the  MPI.Request.Test(),  MPI.Request.Wait()   and
       MPI.Request.Cancel()  methods.  The  management  of  MPI.Request  objects  and  associated memory buffers
       involved in communication requires a careful, rather  low-level  coordination.  Users  must  ensure  that
       objects  exposing  their  memory  buffers are not accessed at the Python level while they are involved in
       nonblocking message-passing operations.

   Persistent Communications
       Often a communication with the same argument list is repeatedly executed within an inner  loop.  In  such
       cases,  communication  can  be  further optimized by using persistent communication, a particular case of
       nonblocking communication allowing the reduction of the  overhead  between  processes  and  communication
       controllers.  Furthermore  ,  this  kind  of  optimization  can  also  alleviate the extra call overheads
       associated to interpreted, dynamic languages like Python.

       In MPI for Python, the MPI.Comm.Send_init() and MPI.Comm.Recv_init() methods create  persistent  requests
       for  a  send  and  receive operation, respectively.  These methods return an instance of the MPI.Prequest
       class, a subclass of the MPI.Request class. The actual communication can be effectively started using the
       MPI.Prequest.Start() method, and its completion can be managed as previously described.

   Collective Communications
       Collective  communications  allow  the  transmittal  of  data  between  multiple  processes  of  a  group
       simultaneously. The syntax and semantics  of  collective  functions  is  consistent  with  point-to-point
       communication.  Collective  functions  communicate  typed  data,  but  messages  are  not  paired with an
       associated tag; selectivity of messages  is  implied  in  the  calling  order.  Additionally,  collective
       functions come in blocking versions only.

       The more commonly used collective communication operations are the following.

       • Barrier synchronization across all group members.

       • Global communication functions

         • Broadcast data from one member to all members of a group.

         • Gather data from all members to one member of a group.

         • Scatter data from one member to all members of a group.

       • Global reduction operations such as sum, maximum, minimum, etc.

       In MPI for Python, the MPI.Comm.Bcast(), MPI.Comm.Scatter(), MPI.Comm.Gather(), MPI.Comm.Allgather(), and
       MPI.Comm.Alltoall() MPI.Comm.Alltoallw() methods provide support for collective communications of  memory
       buffers.    The    lower-case    variants    MPI.Comm.bcast(),   MPI.Comm.scatter(),   MPI.Comm.gather(),
       MPI.Comm.allgather() and MPI.Comm.alltoall() can communicate general Python objects.  The vector variants
       (which   can   communicate   different   amounts   of   data   to   each   process)  MPI.Comm.Scatterv(),
       MPI.Comm.Gatherv(),  MPI.Comm.Allgatherv(),  MPI.Comm.Alltoallv()  and  MPI.Comm.Alltoallw()   are   also
       supported, they can only communicate objects exposing memory buffers.

       Global   reduction   operations   on   memory  buffers  are  accessible  through  the  MPI.Comm.Reduce(),
       MPI.Comm.Reduce_scatter, MPI.Comm.Allreduce(), MPI.Intracomm.Scan() and  MPI.Intracomm.Exscan()  methods.
       The    lower-case    variants    MPI.Comm.reduce(),    MPI.Comm.allreduce(),   MPI.Intracomm.scan()   and
       MPI.Intracomm.exscan() can communicate general Python objects; however,  the  actual  required  reduction
       computations  are  performed  sequentially  at some process. All the predefined (i.e., MPI.SUM, MPI.PROD,
       MPI.MAX, etc.)  reduction operations can be applied.

   Dynamic Process Management
       In the context of the MPI-1 specification, a parallel application is static; that is, no processes can be
       added  to  or  deleted from a running application after it has been started. Fortunately, this limitation
       was addressed in MPI-2. The new  specification  added  a  process  management  model  providing  a  basic
       interface between an application and external resources and process managers.

       This  MPI-2  extension  can  be  really  useful,  especially  for sequential applications built on top of
       parallel modules, or parallel applications with a client/server model. The MPI-2 process model provides a
       mechanism  to  create  new  processes  and  establish  communication  between  them  and the existing MPI
       application.  It  also  provides  mechanisms  to  establish  communication  between  two   existing   MPI
       applications, even when one did not start the other.

       In  MPI  for  Python,  new independent process groups can be created by calling the MPI.Intracomm.Spawn()
       method within an intracommunicator.  This call returns a new intercommunicator  (i.e.,  an  MPI.Intercomm
       instance)   at   the   parent   process  group.  The  child  process  group  can  retrieve  the  matching
       intercommunicator  by  calling  the  MPI.Comm.Get_parent()  class  method.  At   each   side,   the   new
       intercommunicator  can be used to perform point to point and collective communications between the parent
       and child groups of processes.

       Alternatively, disjoint groups of processes can establish communication using a  client/server  approach.
       Any   server  application  must  first  call  the  MPI.Open_port()  function  to  open  a  port  and  the
       MPI.Publish_name() function to publish a provided  service,  and  next  call  the  MPI.Intracomm.Accept()
       method.   Any  client  applications  can  first find a published service by calling the MPI.Lookup_name()
       function,  which  returns  the  port  where  a   server   can   be   contacted;   and   next   call   the
       MPI.Intracomm.Connect() method. Both MPI.Intracomm.Accept() and MPI.Intracomm.Connect() methods return an
       MPI.Intercomm instance. When connection between client/server processes is no longer needed, all of  them
       must  cooperatively  call  the  MPI.Comm.Disconnect()  method.  Additionally,  server applications should
       release resources by calling the MPI.Unpublish_name() and MPI.Close_port() functions.

   One-Sided Communications
       One-sided communications (also called Remote Memory Access, RMA) supplements the  traditional  two-sided,
       send/receive  based  MPI  communication  model  with  a  one-sided,  put/get  based  interface. One-sided
       communication that can take advantage  of  the  capabilities  of  highly  specialized  network  hardware.
       Additionally,  this  extension  lowers  latency  and  software  overhead  in applications written using a
       shared-memory-like paradigm.

       The MPI specification revolves around the use of objects called windows; they intuitively specify regions
       of  a process’s memory that have been made available for remote read and write operations.  The published
       memory blocks can be accessed through three functions for put (remote  send),  get  (remote  write),  and
       accumulate  (remote  update or reduction) data items. A much larger number of functions support different
       synchronization styles; the semantics of these synchronization operations are fairly complex.

       In MPI for Python, one-sided operations are available by using instances of the MPI.Win class. New window
       objects  are  created  by  calling the MPI.Win.Create() method at all processes within a communicator and
       specifying a memory buffer . When a window instance is no longer needed, the MPI.Win.Free() method should
       be called.

       The three one-sided MPI operations for remote write, read and reduction are available through calling the
       methods MPI.Win.Put(), MPI.Win.Get(), and MPI.Win.Accumulate() respectively within a Win instance.  These
       methods  need  an  integer  rank  identifying  the target process and an integer offset relative the base
       address of the remote memory block being accessed.

       The one-sided operations read, write, and reduction are implicitly nonblocking, and must be  synchronized
       by  using  two  primary  modes.   Active  target  synchronization requires the origin process to call the
       MPI.Win.Start() and MPI.Win.Complete() methods at the origin process, and target  process  cooperates  by
       calling the MPI.Win.Post() and MPI.Win.Wait() methods. There is also a collective variant provided by the
       MPI.Win.Fence() method. Passive target synchronization is more lenient, only the origin process calls the
       MPI.Win.Lock()  and  MPI.Win.Unlock()  methods.  Locks  are used to protect remote accesses to the locked
       remote window and to protect local load/store accesses to a locked local window.

   Parallel Input/Output
       The POSIX standard provides a model of a widely portable file system. However,  the  optimization  needed
       for  parallel  input/output cannot be achieved with this generic interface. In order to ensure efficiency
       and scalability, the  underlying  parallel  input/output  system  must  provide  a  high-level  interface
       supporting  partitioning  of  file  data  among  processes and a collective interface supporting complete
       transfers  of  global  data  structures  between  process  memories  and  files.  Additionally,   further
       efficiencies  can  be  gained  via  support  for asynchronous input/output, strided accesses to data, and
       control over physical file layout on storage devices. This scenario motivated the inclusion in the  MPI-2
       standard of a custom interface in order to support more elaborated parallel input/output operations.

       The  MPI specification for parallel input/output revolves around the use objects called files. As defined
       by MPI, files are not just contiguous byte streams. Instead, they are regarded as ordered collections  of
       typed  data  items.  MPI  supports  sequential  or  random  access  to  any  integral set of these items.
       Furthermore, files are opened collectively by a group of processes.

       The common patterns for accessing a shared file (broadcast, scatter, gather, reduction) is  expressed  by
       using  user-defined  datatypes.   Compared to the communication patterns of point-to-point and collective
       communications, this approach has the advantage of added  flexibility  and  expressiveness.  Data  access
       operations  (read  and  write)  are  defined  for different kinds of positioning (using explicit offsets,
       individual file pointers, and shared file pointers), coordination (non-collective  and  collective),  and
       synchronism (blocking, nonblocking, and split collective with begin/end phases).

       In MPI for Python, all MPI input/output operations are performed through instances of the MPI.File class.
       File handles are obtained by calling the MPI.File.Open() method at all processes  within  a  communicator
       and  providing  a  file name and the intended access mode.  After use, they must be closed by calling the
       MPI.File.Close() method.  Files even can be deleted by calling method MPI.File.Delete().

       After creation, files are typically associated with a per-process view. The view defines the current  set
       of  data  visible  and  accessible from an open file as an ordered set of elementary datatypes. This data
       layout can be set and queried with the MPI.File.Set_view() and MPI.File.Get_view() methods respectively.

       Actual input/output operations are achieved by many methods combining read and write calls with different
       behavior  regarding  positioning,  coordination, and synchronism. Summing up, MPI for Python provides the
       thirty (30) methods defined in MPI-2 for reading from or writing to files using explicit offsets or  file
       pointers (individual or shared), in blocking or nonblocking and collective or noncollective versions.

   Environmental Management
   Initialization and Exit
       Module  functions  MPI.Init()  or  MPI.Init_thread()  and  MPI.Finalize()  provide MPI initialization and
       finalization respectively. Module  functions  MPI.Is_initialized()  and  MPI.Is_finalized()  provide  the
       respective tests for initialization and finalization.

       NOTE:
          MPI_Init()  or  MPI_Init_thread()  is  actually  called when you import the MPI module from the mpi4py
          package,  but  only  if  MPI  is  not  already  initialized.  In  such  case,  calling  MPI.Init()  or
          MPI.Init_thread()  from  Python is expected to generate an MPI error, and in turn an exception will be
          raised.

       NOTE:
          MPI_Finalize() is registered (by using Python C/API  function  Py_AtExit())  for  being  automatically
          called when Python processes exit, but only if mpi4py actually initialized MPI. Therefore, there is no
          need to call MPI.Finalize() from Python to ensure MPI finalization.

   Implementation Information
       • The MPI version  number  can  be  retrieved  from  module  function  MPI.Get_version().  It  returns  a
         two-integer tuple (version,subversion).

       • The MPI.Get_processor_name() function can be used to access the processor name.

       • The  values  of predefined attributes attached to the world communicator can be obtained by calling the
         MPI.Comm.Get_attr() method within the MPI.COMM_WORLD instance.

   Timers
       MPI timer functionalities are available through the MPI.Wtime() and MPI.Wtick() functions.

   Error Handling
       In order facilitate handle sharing with other Python modules interfacing  MPI-based  parallel  libraries,
       the  predefined  MPI  error  handlers  MPI.ERRORS_RETURN  and MPI.ERRORS_ARE_FATAL can be assigned to and
       retrieved from communicators, windows and files using  methods  MPI.{Comm|Win|File}.Set_errhandler()  and
       MPI.{Comm|Win|File}.Get_errhandler().

       When  the predefined error handler MPI.ERRORS_RETURN is set, errors returned from MPI calls within Python
       code will raise an instance of the exception class MPI.Exception, which is a  subclass  of  the  standard
       Python exception RuntimeError.

       NOTE:
          After  import,  mpi4py  overrides  the  default MPI rules governing inheritance of error handlers. The
          MPI.ERRORS_RETURN  error  handler  is  set  in  the  predefined   MPI.COMM_SELF   and   MPI.COMM_WORLD
          communicators,  as  well as any new MPI.Comm, MPI.Win, or MPI.File instance created through mpi4py. If
          you  ever  pass  such  handles  to  C/C++/Fortran  library  code,  it  is  recommended  to   set   the
          MPI.ERRORS_ARE_FATAL error handler on them to ensure MPI errors do not pass silently.

       WARNING:
          Importing  with from mpi4py.MPI import * will cause a name clashing with the standard Python Exception
          base class.

TUTORIAL

       WARNING:
          Under construction. Contributions very welcome!

       MPI for Python supports convenient, pickle-based communication of generic Python object as well as  fast,
       near C-speed, direct array data communication of buffer-provider objects (e.g., NumPy arrays).

       • Communication of generic Python objects

         You  have  to use all-lowercase methods (of the Comm class), like send(), recv(), bcast(). An object to
         be sent is passed as a paramenter to the communication call, and the  received  object  is  simply  the
         return value.

         The  isend()  and  irecv() methods return Request instances; completion of these methods can be managed
         using the test() and wait() methods of the Request class.

         The recv() and irecv() methods may be passed a buffer object that can be  repeatedly  used  to  receive
         messages avoiding internal memory allocation. This buffer must be sufficiently large to accommodate the
         transmitted messages; hence, any buffer passed to recv() or irecv() must be at least  as  long  as  the
         pickled data transmitted to the receiver.

         Collective  calls like scatter(), gather(), allgather(), alltoall() expect a single value or a sequence
         of Comm.size elements at the root or all process. They return a  single  value,  a  list  of  Comm.size
         elements, or None.

       • Communication of buffer-like objects

         You  have  to  use  method  names  starting with an upper-case letter (of the Comm class), like Send(),
         Recv(), Bcast(), Scatter(), Gather().

         In general, buffer arguments to these calls must be explicitly specified by using a 2/3-list/tuple like
         [data,  MPI.DOUBLE],  or  [data,  count, MPI.DOUBLE] (the former one uses the byte-size of data and the
         extent of the MPI datatype to define count).

         For vector collectives communication operations like Scatterv() and  Gatherv(),  buffer  arguments  are
         specified as [data, count, displ, datatype], where count and displ are sequences of integral values.

         Automatic  MPI  datatype  discovery  for NumPy arrays and PEP-3118 buffers is supported, but limited to
         basic C types (all C/C99-native signed/unsigned integral types and single/double precision real/complex
         floating  types)  and  availability of matching datatypes in the underlying MPI implementation. In this
         case, the buffer-provider object can be passed directly  as  a  buffer  argument,  the  count  and  MPI
         datatype will be inferred.

   Running Python scripts with MPI
       Most MPI programs can be run with the command mpiexec. In practice, running Python programs looks like:

          $ mpiexec -n 4 python script.py

       to run the program with 4 processors.

   Point-to-Point Communication
       • Python objects (pickle under the hood):

            from mpi4py import MPI

            comm = MPI.COMM_WORLD
            rank = comm.Get_rank()

            if rank == 0:
                data = {'a': 7, 'b': 3.14}
                comm.send(data, dest=1, tag=11)
            elif rank == 1:
                data = comm.recv(source=0, tag=11)

       • Python objects with non-blocking communication:

            from mpi4py import MPI

            comm = MPI.COMM_WORLD
            rank = comm.Get_rank()

            if rank == 0:
                data = {'a': 7, 'b': 3.14}
                req = comm.isend(data, dest=1, tag=11)
                req.wait()
            elif rank == 1:
                req = comm.irecv(source=0, tag=11)
                data = req.wait()

       • NumPy arrays (the fast way!):

            from mpi4py import MPI
            import numpy

            comm = MPI.COMM_WORLD
            rank = comm.Get_rank()

            # passing MPI datatypes explicitly
            if rank == 0:
                data = numpy.arange(1000, dtype='i')
                comm.Send([data, MPI.INT], dest=1, tag=77)
            elif rank == 1:
                data = numpy.empty(1000, dtype='i')
                comm.Recv([data, MPI.INT], source=0, tag=77)

            # automatic MPI datatype discovery
            if rank == 0:
                data = numpy.arange(100, dtype=numpy.float64)
                comm.Send(data, dest=1, tag=13)
            elif rank == 1:
                data = numpy.empty(100, dtype=numpy.float64)
                comm.Recv(data, source=0, tag=13)

   Collective Communication
       • Broadcasting a Python dictionary:

            from mpi4py import MPI

            comm = MPI.COMM_WORLD
            rank = comm.Get_rank()

            if rank == 0:
                data = {'key1' : [7, 2.72, 2+3j],
                        'key2' : ( 'abc', 'xyz')}
            else:
                data = None
            data = comm.bcast(data, root=0)

       • Scattering Python objects:

            from mpi4py import MPI

            comm = MPI.COMM_WORLD
            size = comm.Get_size()
            rank = comm.Get_rank()

            if rank == 0:
                data = [(i+1)**2 for i in range(size)]
            else:
                data = None
            data = comm.scatter(data, root=0)
            assert data == (rank+1)**2

       • Gathering Python objects:

            from mpi4py import MPI

            comm = MPI.COMM_WORLD
            size = comm.Get_size()
            rank = comm.Get_rank()

            data = (rank+1)**2
            data = comm.gather(data, root=0)
            if rank == 0:
                for i in range(size):
                    assert data[i] == (i+1)**2
            else:
                assert data is None

       • Broadcasting a NumPy array:

            from mpi4py import MPI
            import numpy as np

            comm = MPI.COMM_WORLD
            rank = comm.Get_rank()

            if rank == 0:
                data = np.arange(100, dtype='i')
            else:
                data = np.empty(100, dtype='i')
            comm.Bcast(data, root=0)
            for i in range(100):
                assert data[i] == i

       • Scattering NumPy arrays:

            from mpi4py import MPI
            import numpy as np

            comm = MPI.COMM_WORLD
            size = comm.Get_size()
            rank = comm.Get_rank()

            sendbuf = None
            if rank == 0:
                sendbuf = np.empty([size, 100], dtype='i')
                sendbuf.T[:,:] = range(size)
            recvbuf = np.empty(100, dtype='i')
            comm.Scatter(sendbuf, recvbuf, root=0)
            assert np.allclose(recvbuf, rank)

       • Gathering NumPy arrays:

            from mpi4py import MPI
            import numpy as np

            comm = MPI.COMM_WORLD
            size = comm.Get_size()
            rank = comm.Get_rank()

            sendbuf = np.zeros(100, dtype='i') + rank
            recvbuf = None
            if rank == 0:
                recvbuf = np.empty([size, 100], dtype='i')
            comm.Gather(sendbuf, recvbuf, root=0)
            if rank == 0:
                for i in range(size):
                    assert np.allclose(recvbuf[i,:], i)

       • Parallel matrix-vector product:

            from mpi4py import MPI
            import numpy

            def matvec(comm, A, x):
                m = A.shape[0] # local rows
                p = comm.Get_size()
                xg = numpy.zeros(m*p, dtype='d')
                comm.Allgather([x,  MPI.DOUBLE],
                               [xg, MPI.DOUBLE])
                y = numpy.dot(A, xg)
                return y

   MPI-IO
       • Collective I/O with NumPy arrays:

            from mpi4py import MPI
            import numpy as np

            amode = MPI.MODE_WRONLY|MPI.MODE_CREATE
            comm = MPI.COMM_WORLD
            fh = MPI.File.Open(comm, "./datafile.contig", amode)

            buffer = np.empty(10, dtype=np.int)
            buffer[:] = comm.Get_rank()

            offset = comm.Get_rank()*buffer.nbytes
            fh.Write_at_all(offset, buffer)

            fh.Close()

       • Non-contiguous Collective I/O with NumPy arrays and datatypes:

            from mpi4py import MPI
            import numpy as np

            comm = MPI.COMM_WORLD
            rank = comm.Get_rank()
            size = comm.Get_size()

            amode = MPI.MODE_WRONLY|MPI.MODE_CREATE
            fh = MPI.File.Open(comm, "./datafile.noncontig", amode)

            item_count = 10

            buffer = np.empty(item_count, dtype='i')
            buffer[:] = rank

            filetype = MPI.INT.Create_vector(item_count, 1, size)
            filetype.Commit()

            displacement = MPI.INT.Get_size()*rank
            fh.Set_view(displacement, filetype=filetype)

            fh.Write_all(buffer)
            filetype.Free()
            fh.Close()

   Dynamic Process Management
       • Compute Pi - Master (or parent, or client) side:

            #!/usr/bin/env python
            from mpi4py import MPI
            import numpy
            import sys

            comm = MPI.COMM_SELF.Spawn(sys.executable,
                                       args=['cpi.py'],
                                       maxprocs=5)

            N = numpy.array(100, 'i')
            comm.Bcast([N, MPI.INT], root=MPI.ROOT)
            PI = numpy.array(0.0, 'd')
            comm.Reduce(None, [PI, MPI.DOUBLE],
                        op=MPI.SUM, root=MPI.ROOT)
            print(PI)

            comm.Disconnect()

       • Compute Pi - Worker (or child, or server) side:

            #!/usr/bin/env python
            from mpi4py import MPI
            import numpy

            comm = MPI.Comm.Get_parent()
            size = comm.Get_size()
            rank = comm.Get_rank()

            N = numpy.array(0, dtype='i')
            comm.Bcast([N, MPI.INT], root=0)
            h = 1.0 / N; s = 0.0
            for i in range(rank, N, size):
                x = h * (i + 0.5)
                s += 4.0 / (1.0 + x**2)
            PI = numpy.array(s * h, dtype='d')
            comm.Reduce([PI, MPI.DOUBLE], None,
                        op=MPI.SUM, root=0)

            comm.Disconnect()

   Wrapping with SWIG
       • C source:

            /* file: helloworld.c */
            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);
            }

       • SWIG interface file:

            // file: helloworld.i
            %module helloworld
            %{
            #include <mpi.h>
            #include "helloworld.c"
            }%

            %include mpi4py/mpi4py.i
            %mpi4py_typemap(Comm, MPI_Comm);
            void sayhello(MPI_Comm comm);

       • Try it in the Python prompt:

            >>> from mpi4py import MPI
            >>> import helloworld
            >>> helloworld.sayhello(MPI.COMM_WORLD)
            Hello, World! I am process 0 of 1.

   Wrapping with F2Py
       • Fortran 90 source:

            ! file: helloworld.f90
            subroutine sayhello(comm)
              use mpi
              implicit none
              integer :: comm, rank, size, ierr
              call MPI_Comm_size(comm, size, ierr)
              call MPI_Comm_rank(comm, rank, ierr)
              print *, 'Hello, World! I am process ',rank,' of ',size,'.'
            end subroutine sayhello

       • Compiling example using f2py

            $ f2py -c --f90exec=mpif90 helloworld.f90 -m helloworld

       • Try it in the Python prompt:

            >>> from mpi4py import MPI
            >>> import helloworld
            >>> fcomm = MPI.COMM_WORLD.py2f()
            >>> helloworld.sayhello(fcomm)
            Hello, World! I am process 0 of 1.

MPI4PY.FUTURES

       New in version 3.0.0.

       This  package  provides a high-level interface for asynchronously executing callables on a pool of worker
       processes using MPI for inter-process communication.

   concurrent.futures
       The mpi4py.futures package is  based  on  concurrent.futures  from  the  Python  standard  library.  More
       precisely, mpi4py.futures provides the MPIPoolExecutor class as a concrete implementation of the abstract
       class Executor.  The submit() interface schedules a callable to be executed asynchronously and returns  a
       Future  object  representing the execution of the callable.  Future instances can be queried for the call
       result or exception. Sets of Future instances can be passed to the wait() and as_completed() functions.

       NOTE:
          The concurrent.futures package was introduced in Python  3.2.  A  backport  targeting  Python  2.7  is
          available  on  PyPI.  The mpi4py.futures package uses concurrent.futures if available, either from the
          Python 3 standard library or the Python 2.7 backport if installed. Otherwise,  mpi4py.futures  uses  a
          bundled copy of core functionality backported from Python 3.5 to work with Python 2.7.

       SEE ALSO:

          Module concurrent.futures
                 Documentation of the concurrent.futures standard module.

   MPIPoolExecutor
       The  MPIPoolExecutor  class  uses  a pool of MPI processes to execute calls asynchronously. By performing
       computations in separate processes, it allows to side-step the Global Interpreter  Lock  but  also  means
       that  only  picklable  objects  can  be  executed and returned. The __main__ module must be importable by
       worker processes, thus MPIPoolExecutor instances may not work in the interactive interpreter.

       MPIPoolExecutor takes advantage of the dynamic  process  management  features  introduced  in  the  MPI-2
       standard.  In  particular,  the MPI.Intracomm.Spawn() method of MPI.COMM_SELF() is used in the master (or
       parent) process to spawn new worker (or child) processes running a Python interpreter. The master process
       uses  a  separate  thread  (one for each MPIPoolExecutor instance) to communicate back and forth with the
       workers.  The worker processes serve the execution of tasks in the main (and only) thread until they  are
       signaled for completion.

       NOTE:
          The  worker  processes  must  import  the main script in order to unpickle any callable defined in the
          __main__ module and submitted from the master process. Furthermore, the callables may need  access  to
          other  global  variables.  At  the  worker processes,:mod:mpi4py.futures executes the main script code
          (using the runpy module) under the __worker__ namespace to define the __main__  module.  The  __main__
          and  __worker__  modules  are added to sys.modules (both at the master and worker processes) to ensure
          proper pickling and unpickling.

       WARNING:
          During the initial  import  phase  at  the  workers,  the  main  script  cannot  create  and  use  new
          MPIPoolExecutor  instances.  Otherwise,  each  worker  would  attempt  to spawn a new pool of workers,
          leading to infinite recursion. mpi4py.futures detects such recursive attempts to spawn new workers and
          aborts  the  MPI execution environment. As the main script code is run under the __worker__ namespace,
          the easiest way to avoid spawn recursion is using the idiom if __name__ == '__main__': ... in the main
          script.

       class mpi4py.futures.MPIPoolExecutor(max_workers=None, **kwargs)
              An  Executor  subclass  that  executes  calls  asynchronously  using a pool of at most max_workers
              processes.   If  max_workers  is  None  or  not  given,  its  value   is   determined   from   the
              MPI4PY_MAX_WORKERS  environment  variable  if  set,  or  the MPI universe size if set, otherwise a
              single worker process is spawned.  If max_workers is lower than or equal to 0, then  a  ValueError
              will be raised.

              Other parameters:

              • python_exe:  Path to the Python interpreter executable used to spawn worker processes, otherwise
                sys.executable is used.

              • python_args: list or iterable  with  additional  command  line  flags  to  pass  to  the  Python
                executable.  Command  line  flags  determined  from inspection of sys.flags, sys.warnoptions and
                sys._xoptions in are passed unconditionally.

              • mpi_info: dict or iterable yielding (key, value) pairs.  These (key,  value)  pairs  are  passed
                (through  an  MPI.Info object) to the MPI.Intracomm.Spawn() call used to spawn worker processes.
                This mechanism allows telling the MPI runtime system where and how to start the processes. Check
                the  documentation of the backend MPI implementation about the set of keys it interprets and the
                corresponding format for values.

              • globals: dict or iterable yielding (name, value) pairs to initialize the main  module  namespace
                in worker processes.

              • main:  If  set  to False, do not import the __main__ module in worker processes. Setting main to
                False prevents worker processes from accessing definitions in the parent __main__ namespace.

              • path: list or iterable with paths to append to sys.path in worker processes to extend the module
                search path.

              • wdir:  Path  to  set  the  current  working  directory in worker processes using os.chdir(). The
                initial working directory is set by the MPI implementation. Quality MPI  implementations  should
                honor a wdir info key passed through mpi_info, although such feature is not mandatory.

              • env:  dict  or  iterable  yielding  (name,  value)  pairs  with  environment variables to update
                os.environ in worker processes.  The initial environment is set by the MPI  implementation.  MPI
                implementations may allow setting the initial environment through mpi_info, however such feature
                is not required nor recommended by the MPI standard.

              submit(func, *args, **kwargs)
                     Schedule the callable, func, to be executed as func(*args, **kwargs) and returns  a  Future
                     object representing the execution of the callable.

                        executor = MPIPoolExecutor(max_workers=1)
                        future = executor.submit(pow, 321, 1234)
                        print(future.result())

              map(func, *iterables, timeout=None, chunksize=1, **kwargs)
                     Equivalent  to  map(func,  *iterables)  except  func is executed asynchronously and several
                     calls to func may be made concurrently, out-of-order, in separate processes.  The  returned
                     iterator raises a TimeoutError if __next__() is called and the result isn’t available after
                     timeout seconds from the original call to map().  timeout can be an int  or  a  float.   If
                     timeout  is not specified or None, there is no limit to the wait time.  If a call raises an
                     exception, then that exception will  be  raised  when  its  value  is  retrieved  from  the
                     iterator.  This method chops iterables into a number of chunks which it submits to the pool
                     as separate tasks. The (approximate) size of these  chunks  can  be  specified  by  setting
                     chunksize to a positive integer. For very long iterables, using a large value for chunksize
                     can significantly improve performance compared to the default size of one. By default,  the
                     returned  iterator yields results in-order, waiting for successive tasks to complete . This
                     behavior can be changed by passing the keyword argument unordered as True, then the  result
                     iterator will yield a result as soon as any of the tasks complete.

                        executor = MPIPoolExecutor(max_workers=3)
                        for result in executor.map(pow, [2]*32, range(32)):
                            print(result)

              starmap(func, iterable, timeout=None, chunksize=1, **kwargs)
                     Equivalent  to  itertools.starmap(func,  iterable).  Used  instead  of  map() when argument
                     parameters are already grouped in  tuples  from  a  single  iterable  (the  data  has  been
                     “pre-zipped”). map(func, *iterable) is equivalent to starmap(func, zip(*iterable)).

                        executor = MPIPoolExecutor(max_workers=3)
                        iterable = ((2, n) for n in range(32))
                        for result in executor.starmap(pow, iterable):
                            print(result)

              shutdown(wait=True)
                     Signal  the  executor that it should free any resources that it is using when the currently
                     pending futures are done executing.  Calls to submit() and map() made after shutdown() will
                     raise RuntimeError.

                     If  wait  is  True  then this method will not return until all the pending futures are done
                     executing and the resources associated with the executor have been freed.  If wait is False
                     then  this  method  will  return immediately and the resources associated with the executor
                     will be freed when all pending futures are done executing.   Regardless  of  the  value  of
                     wait, the entire Python program will not exit until all pending futures are done executing.

                     You  can  avoid  having to call this method explicitly if you use the with statement, which
                     will shutdown the executor instance (waiting as if shutdown() were called with wait set  to
                     True).

                        import time
                        with MPIPoolExecutor(max_workers=1) as executor:
                            future = executor.submit(time.sleep, 2)
                        assert future.done()

              bootup(wait=True)
                     Signal  the executor that it should allocate eagerly any required resources (in particular,
                     MPI worker processes). If wait is True, then bootup() will not return  until  the  executor
                     resources  are  ready to process submissions.  Resources are automatically allocated in the
                     first call to submit(), thus calling bootup() explicitly is seldom needed.

       NOTE:
          As the master process uses a separate thread to  perform  MPI  communication  with  the  workers,  the
          backend  MPI  implementation should provide support for MPI.THREAD_MULTIPLE. However, some popular MPI
          implementations do not support yet concurrent MPI calls from multiple threads. Additionally, users may
          decide  to  initialize MPI with a lower level of thread support. If the level of thread support in the
          backend MPI is less than MPI.THREAD_MULTIPLE, mpi4py.futures will use a global lock to  serialize  MPI
          calls.  If  the level of thread support is less than MPI.THREAD_SERIALIZED, mpi4py.futures will emit a
          RuntimeWarning.

       WARNING:
          If the level of thread support in the backend MPI is  less  than  MPI.THREAD_SERIALIZED  (i.e,  it  is
          either MPI.THREAD_SINGLE or MPI.THREAD_FUNNELED), in theory mpi4py.futures cannot be used. Rather than
          raising an exception, mpi4py.futures emits a warning and takes a “cross-fingers” attitude to  continue
          execution in the hope that serializing MPI calls with a global lock will actually work.

   MPICommExecutor
       Legacy  MPI-1  implementations  (as well as some vendor MPI-2 implementations) do not support the dynamic
       process management features introduced in the MPI-2 standard.  Additionally,  job  schedulers  and  batch
       systems  in  supercomputing  facilities  may  pose  additional  complications  to  applications using the
       MPI_Comm_spawn() routine.

       With these issues in mind, mpi4py.futures  supports  an  additonal,  more  traditional,  SPMD-like  usage
       pattern  requiring  MPI-1  calls  only.  Python  applications  are started the usual way, e.g., using the
       mpiexec command. Python code should make a collective call to  the  MPICommExecutor  context  manager  to
       partition  the  set  of  MPI processes within a MPI communicator in one master processes and many workers
       processes. The master process gets access to an MPIPoolExecutor instance to submit tasks. Meanwhile,  the
       worker  process  follow  a  different  execution path and team-up to execute the tasks submitted from the
       master.

       Besides alleviating the lack of dynamic process managment features  in  legacy  MPI-1  or  partial  MPI-2
       implementations,  the  MPICommExecutor  context  manager  may  be  useful  in  classic  MPI-based  Python
       applications willing to take advantage of the simple, task-based, master/worker approach available in the
       mpi4py.futures package.

       class mpi4py.futures.MPICommExecutor(comm=None, root=0)
              Context  manager  for  MPIPoolExecutor. This context manager splits a MPI (intra)communicator comm
              (defaults to MPI.COMM_WORLD if not provided or None) in two disjoint sets: a single master process
              (with rank root in comm) and the remaining worker processes. These sets are then connected through
              an intercommunicator.  The target of the with statement  is  assigned  either  an  MPIPoolExecutor
              instance (at the master) or None (at the workers).

                 from mpi4py import MPI
                 from mpi4py.futures import MPICommExecutor

                 with MPICommExecutor(MPI.COMM_WORLD, root=0) as executor:
                     if executor is not None:
                        future = executor.submit(abs, -42)
                        assert future.result() == 42
                        answer = set(executor.map(abs, [-42, 42]))
                        assert answer == {42}

       WARNING:
          If  MPICommExecutor  is  passed  a  communicator  of size one (e.g., MPI.COMM_SELF), then the executor
          instace assigned to the target of the with statement will execute all  submitted  tasks  in  a  single
          worker  thread, thus ensuring that task execution still progress asynchronously. However, the GIL will
          prevent the main and worker threads from running concurrently in multicore processors.  Moreover,  the
          thread  context switching may harm noticeably the performance of CPU-bound tasks. In case of I/O-bound
          tasks, the GIL is not usually an issue, however, as a single worker thread is used,  it  progress  one
          task  at  a  time.  We advice against using MPICommExecutor with communicators of size one and suggest
          refactoring your code to use instead a ThreadPoolExecutor.

   Command line
       Recalling the issues related to the lack of  support  for  dynamic  process  managment  features  in  MPI
       implementations,  mpi4py.futures  supports  an  alternative  usage pattern where Python code (either from
       scripts, modules, or zip files) is run under command  line  control  of  the  mpi4py.futures  package  by
       passing  -m  mpi4py.futures  to  the python executable.  The mpi4py.futures invocation should be passed a
       pyfile path  to  a  script  (or  a  zipfile/directory  containing  a  __main__.py  file).   Additionally,
       mpi4py.futures  accepts  -m mod to execute a module named mod, -c cmd to execute a command string cmd, or
       even - to read commands from standard input (sys.stdin).  Summarizing, mpi4py.futures can be  invoked  in
       the following ways:

       • $ mpiexec -n numprocs python -m mpi4py.futures pyfile [arg] ...$ mpiexec -n numprocs python -m mpi4py.futures -m mod [arg] ...$ mpiexec -n numprocs python -m mpi4py.futures -c cmd [arg] ...$ mpiexec -n numprocs python -m mpi4py.futures - [arg] ...

       Before  starting  the  main  script  execution,  mpi4py.futures  splits MPI.COMM_WORLD in one master (the
       process with rank 0 in MPI.COMM_WORLD) and 16 workers and connect them through an MPI  intercommunicator.
       Afterwards,  the  master  process  proceeds  with the execution of the user script code, which eventually
       creates MPIPoolExecutor instances to submit tasks. Meanwhile, the worker  processes  follow  a  different
       execution  path  to  serve the master.  Upon successful termination of the main script at the master, the
       entire MPI execution environment exists gracefully. In case  of  any  unhandled  exception  in  the  main
       script,  the  master  process calls MPI.COMM_WORLD.Abort(1) to prevent deadlocks and force termination of
       entire MPI execution environment.

       WARNING:
          Running scripts under command  line  control  of  mpi4py.futures  is  quite  similar  to  executing  a
          single-process  application  that  spawn  additional  workers  as  required.  However, there is a very
          important difference users should be aware of. All MPIPoolExecutor instances  created  at  the  master
          will  share  the  pool of workers. Tasks submitted at the master from many different executors will be
          scheduled for execution in random order as soon as a worker is idle. Any executor  can  easily  starve
          all  the  workers  (e.g., by calling MPIPoolExecutor.map() with long iterables). If that ever happens,
          submissions from other executors will not be serviced until free workers are available.

       SEE ALSO:

          python:using-on-cmdline
                 Documentation on Python command line interface.

   Examples
       The following julia.py script computes the Julia set and dumps an image to disk in binary PGM format. The
       code starts by importing MPIPoolExecutor from the mpi4py.futures package. Next, some global constants and
       functions implement the computation of the Julia set. The computations are protected with the standard if
       __name__  ==  '__main__':...  idiom.  The image is computed by whole scanlines submitting all these tasks
       at once using the map method. The result iterator  yields  scanlines  in-order  as  the  tasks  complete.
       Finally, each scanline is dumped to disk.

       julia.py

          from mpi4py.futures import MPIPoolExecutor

          x0, x1, w = -2.0, +2.0, 640*2
          y0, y1, h = -1.5, +1.5, 480*2
          dx = (x1 - x0) / w
          dy = (y1 - y0) / h

          c = complex(0, 0.65)

          def julia(x, y):
              z = complex(x, y)
              n = 255
              while abs(z) < 3 and n > 1:
                  z = z**2 + c
                  n -= 1
              return n

          def julia_line(k):
              line = bytearray(w)
              y = y1 - k * dy
              for j in range(w):
                  x = x0 + j * dx
                  line[j] = julia(x, y)
              return line

          if __name__ == '__main__':

              with MPIPoolExecutor() as executor:
                  image = executor.map(julia_line, range(h))
                  with open('julia.pgm', 'wb') as f:
                      f.write(b'P5 %d %d %d\n' % (w, h, 255))
                      for line in image:
                          f.write(line)

       The  recommended  way  to  execute the script is using the mpiexec command specifying one MPI process and
       (optional but recommended) the desired MPI universe size [1].

          $ mpiexec -n 1 -usize 17 python julia.py

       The mpiexec command launches a single MPI  process  (the  master)  running  the  Python  interpreter  and
       executing  the  main  script.  When  required,  mpi4py.futures  spawns  16  additional MPI processes (the
       children) to dynamically allocate the pool of workers. The master submits tasks to the children and waits
       for  the  results.  The  children  receive incoming tasks, execute them, and send back the results to the
       master.

       Alternatively, users may decide to execute the script in a more traditional way, that  is,  all  the  MPI
       process  are started at once. The user script is run under command line control of mpi4py.futures passing
       the -m flag to the python executable.

          $ mpiexec -n 17 python -m mpi4py.futures julia.py

       As explained previously, the 17 processes are partitioned in  one  master  and  16  workers.  The  master
       process executes the main script while the workers execute the tasks submitted from the master.

       [1]  This   mpiexec   invocation   example   using   the   -usize   flag   (alternatively,   setting  the
            MPIEXEC_UNIVERSE_SIZE environment variable) assumes the  backend  MPI  implementation  is  an  MPICH
            derivative  using  the  Hydra process manager. In the Open MPI implementation, the MPI universe size
            can be specified by setting the OMPI_UNIVERSE_SIZE  environment  variable  to  a  positive  integer.
            Check  the  documentation  of  your  actual  MPI  implementation and/or batch system for the ways to
            specify the desired MPI universe size.

MPI4PY.RUN

       New in version 3.0.0.

       At import time, mpi4py initializes the MPI execution environment calling MPI_Init_thread()  and  installs
       an   exit  hook  to  automatically  call  MPI_Finalize()  just  before  the  Python  process  terminates.
       Additionally,  mpi4py  overrides  the  default   MPI.ERRORS_ARE_FATAL   error   handler   in   favor   of
       MPI.ERRORS_RETURN,  which  allows  translating  MPI  errors  in  Python exceptions. These departures from
       standard MPI behavior may be controversial, but are quite convenient within  the  highly  dynamic  Python
       programming  environment.  Third-party  code using mpi4py can just from mpi4py import MPI and perform MPI
       calls  without  the  tedious  initialization/finalization  handling.    MPI   errors,   once   translated
       automatically  to  Python  exceptions, can be dealt with the common tryexceptfinally clauses; unhandled
       MPI exceptions will print a traceback which helps in locating problems in source code.

       Unfortunately, the interplay  of  automatic  MPI  finalization  and  unhandled  exceptions  may  lead  to
       deadlocks.  In  unattended  runs, these deadlocks will drain the battery of your laptop, or burn precious
       allocation hours in your supercomputing facility.

       Consider the following snippet of Python code. Assume this code is stored in  a  standard  Python  script
       file and run with mpiexec in two or more processes.

          from mpi4py import MPI
          assert MPI.COMM_WORLD.Get_size() > 1
          rank = MPI.COMM_WORLD.Get_rank()
          if rank == 0:
              1/0
              MPI.COMM_WORLD.send(None, dest=1, tag=42)
          elif rank == 1:
              MPI.COMM_WORLD.recv(source=0, tag=42)

       Process 0 raises ZeroDivisionError exception before performing a send call to process 1. As the exception
       is not handled, the Python interpreter running in process 0 will proceed to exit  with  non-zero  status.
       However,  as  mpi4py  installed a finalizer hook to call MPI_Finalize() before exit, process 0 will block
       waiting for other processes to also enter the  MPI_Finalize()  call.  Meanwhile,  process  1  will  block
       waiting  for  a  message  to  arrive from process 0, thus never reaching to MPI_Finalize(). The whole MPI
       execution environment is irremediably in a deadlock state.

       To alleviate this issue, mpi4py offers a simple, alternative command line execution  mechanism  based  on
       using  the -m flag and implemented with the runpy module. To use this features, Python code should be run
       passing -m mpi4py in the command line invoking the Python interpreter. In case of  unhandled  exceptions,
       the  finalizer  hook  will call MPI_Abort() on the MPI_COMM_WORLD communicator, thus effectively aborting
       the MPI execution environment.

       WARNING:
          When a process is forced to abort, resources (e.g. open files) are not cleaned-up and  any  registered
          finalizers  (either  with  the  atexit  module,  the  Python C/API function Py_AtExit(), or even the C
          standard library function atexit()) will not be executed. Thus, aborting  execution  is  an  extremely
          impolite way of ensuring process termination. However, MPI provides no other mechanism to recover from
          a deadlock state.

   Interface options
       The use of -m mpi4py to execute Python code on the command line resembles that of the Python interpreter.

       • mpiexec -n numprocs python -m mpi4py pyfile [arg] ...mpiexec -n numprocs python -m mpi4py -m mod [arg] ...mpiexec -n numprocs python -m mpi4py -c cmd [arg] ...mpiexec -n numprocs python -m mpi4py - [arg] ...

       <pyfile>
              Execute the Python code contained in pyfile, which must be a filesystem path referring to either a
              Python  file,  a  directory  containing  a __main__.py file, or a zipfile containing a __main__.py
              file.

       -m <mod>
              Search sys.path for the named module mod and execute its contents.

       -c <cmd>
              Execute the Python code in the cmd string command.

       -      Read commands from standard input (sys.stdin).

       SEE ALSO:

          python:using-on-cmdline
                 Documentation on Python command line interface.

CITATION

       If MPI for Python been significant to a project that leads to an academic publication, please acknowledge
       that fact by citing the project.

       • L.  Dalcin,  P.  Kler,  R. Paz, and A. Cosimo, Parallel Distributed Computing using Python, Advances in
         Water Resources, 34(9):1124-1139, 2011.  http://dx.doi.org/10.1016/j.advwatres.2011.04.013

       • L. Dalcin, R. Paz, M. Storti, and J.  D’Elia,  MPI  for  Python:  performance  improvements  and  MPI-2
         extensions,    Journal    of    Parallel    and    Distributed    Computing,    68(5):655-662,    2008.
         http://dx.doi.org/10.1016/j.jpdc.2007.09.005

       • L. Dalcin, R. Paz, and M. Storti, MPI for  Python,  Journal  of  Parallel  and  Distributed  Computing,
         65(9):1108-1115, 2005.  http://dx.doi.org/10.1016/j.jpdc.2005.03.010

INSTALLATION

   Requirements
       You need to have the following software properly installed in order to build MPI for Python:

       • A working MPI implementation, preferably supporting MPI-3 and built with shared/dynamic libraries.

         NOTE:
            If you want to build some MPI implementation from sources, check the instructions at building-mpi in
            the appendix.

       • Python 2.7, 3.3 or above.

         NOTE:
            Some MPI-1 implementations do require the actual command line arguments to be passed in  MPI_Init().
            In  this  case,  you will need to use a rebuilt, MPI-enabled, Python interpreter executable. MPI for
            Python has some support for alleviating you from this task. Check the instructions at python-mpi  in
            the appendix.

   Using pip or easy_install
       If  you  already  have  a  working  MPI  (either if you installed it from sources or by using a pre-built
       package from your favourite GNU/Linux distribution) and the mpicc compiler  wrapper  is  on  your  search
       path, you can use pip:

          $ [sudo] pip install mpi4py

       or alternatively setuptools easy_install (deprecated):

          $ [sudo] easy_install mpi4py

       NOTE:
          If  the  mpicc compiler wrapper is not on your search path (or if it has a different name) you can use
          env to pass the environment variable MPICC providing  the  full  path  to  the  MPI  compiler  wrapper
          executable:

              $ [sudo] env MPICC=/path/to/mpicc pip install mpi4py

              $ [sudo] env MPICC=/path/to/mpicc easy_install mpi4py

   Using distutils
       The  MPI  for  Python  package  is  available  for  download  at the project website generously hosted by
       Bitbucket. You can use curl or wget to get a release tarball.

       • Using curl:

            $ curl -O https://bitbucket.org/mpi4py/mpi4py/downloads/mpi4py-X.Y.tar.gz

       • Using wget:

            $ wget https://bitbucket.org/mpi4py/mpi4py/downloads/mpi4py-X.Y.tar.gz

       After unpacking the release tarball:

          $ tar -zxf mpi4py-X.Y.tar.gz
          $ cd mpi4py-X.Y

       the package is ready for building.

       MPI for Python uses a standard distutils-based build  system.  However,  some  distutils  commands  (like
       build) have additional options:

       --mpicc=
              Lets you specify a special location or name for the mpicc compiler wrapper.

       --mpi= Lets you pass a section with MPI configuration within a special configuration file.

       --configure
              Runs  exhaustive tests for checking about missing MPI types, constants, and functions. This option
              should be passed in order to build MPI for Python against  old  MPI-1  or  MPI-2  implementations,
              possibly providing a subset of MPI-3.

       If  you  use  a MPI implementation providing a mpicc compiler wrapper (e.g., MPICH, Open MPI), it will be
       used for compilation and linking. This is the preferred and easiest way of building MPI for Python.

       If mpicc is located somewhere in your search path, simply run the build command:

          $ python setup.py build

       If mpicc is not in your search path or the compiler wrapper has a different name, you can run  the  build
       command specifying its location:

          $ python setup.py build --mpicc=/where/you/have/mpicc

       Alternatively,  you can provide all the relevant information about your MPI implementation by editing the
       file called mpi.cfg. You can use the default section [mpi] or add a  new,  custom  section,  for  example
       [other_mpi]  (see  the  examples  provided  in  the  mpi.cfg  file  as a starting point to write your own
       section):

          [mpi]

          include_dirs         = /usr/local/mpi/include
          libraries            = mpi
          library_dirs         = /usr/local/mpi/lib
          runtime_library_dirs = /usr/local/mpi/lib

          [other_mpi]

          include_dirs         = /opt/mpi/include ...
          libraries            = mpi ...
          library_dirs         = /opt/mpi/lib ...
          runtime_library_dirs = /op/mpi/lib ...

          ...

       and then run the build command, perhaps specifying you custom configuration section:

          $ python setup.py build --mpi=other_mpi

       After building, the package is ready for install.

       If you have root privileges (either by log-in as the root user of by using sudo) and you want to  install
       MPI for Python in your system for all users, just do:

          $ python setup.py install

       The     previous     steps     will     install    the    mpi4py    package    at    standard    location
       prefix/lib/pythonX.X/site-packages.

       If you do not have root privileges or you want to install MPI for Python for your private use, just do:

          $ python setup.py install --user

   Testing
       To quickly test the installation:

          $ mpiexec -n 5 python -m mpi4py.bench helloworld
          Hello, World! I am process 0 of 5 on localhost.
          Hello, World! I am process 1 of 5 on localhost.
          Hello, World! I am process 2 of 5 on localhost.
          Hello, World! I am process 3 of 5 on localhost.
          Hello, World! I am process 4 of 5 on localhost.

       If you installed from source, issuing at the command line:

          $ mpiexec -n 5 python demo/helloworld.py

       or (in the case of ancient MPI-1 implementations):

          $ mpirun -np 5 python `pwd`/demo/helloworld.py

       will launch a five-process run of the Python interpreter and run the test script demo/helloworld.py  from
       the source distribution.

       You can also run all the unittest scripts:

          $ mpiexec -n 5 python test/runtests.py

       or, if you have nose unit testing framework installed:

          $ mpiexec -n 5 nosetests -w test

       or, if you have py.test unit testing framework installed:

          $ mpiexec -n 5 py.test test/

APPENDIX

   MPI-enabled Python interpreter
          WARNING:
              These  days it is no longer required to use the MPI-enabled Python interpreter in most cases, and,
              therefore, is not built by default anymore because it is too difficult to reliably build a  Python
              interpreter  across different distributions.  If you know that you still really need it, see below
              on how to use the build_exe and install_exe commands.

       Some MPI-1 implementations (notably, MPICH 1) do require the actual command line arguments to  be  passed
       at  the  time  MPI_Init()  is  called. In this case, you will need to use a re-built, MPI-enabled, Python
       interpreter binary executable. A basic implementation (targeting Python 2.X) of what is required is shown
       below:

          #include <Python.h>
          #include <mpi.h>

          int main(int argc, char *argv[])
          {
             int status, flag;
             MPI_Init(&argc, &argv);
             status = Py_Main(argc, argv);
             MPI_Finalized(&flag);
             if (!flag) MPI_Finalize();
             return status;
          }

       The  source code above is straightforward; compiling it should also be. However, the linking step is more
       tricky: special flags have to be passed to the linker depending on your platform. In order  to  alleviate
       you  for  such  low-level details, MPI for Python provides some pure-distutils based support to build and
       install an MPI-enabled Python interpreter executable:

          $ cd mpi4py-X.X.X
          $ python setup.py build_exe [--mpi=<name>|--mpicc=/path/to/mpicc]
          $ [sudo] python setup.py install_exe [--install-dir=$HOME/bin]

       After the above steps you should have the MPI-enabled interpreter installed  as  prefix/bin/pythonX.X-mpi
       (or  $HOME/bin/pythonX.X-mpi). Assuming that prefix/bin (or $HOME/bin) is listed on your PATH, you should
       be able to enter your MPI-enabled Python interactively, for example:

          $ python2.7-mpi
          Python 2.7.8 (default, Nov 10 2014, 08:19:18)
          [GCC 4.9.2 20141101 (Red Hat 4.9.2-1)] on linux2
          Type "help", "copyright", "credits" or "license" for more information.
          >>> import sys
          >>> sys.executable
          '/usr/bin/python2.7-mpi'
          >>>

   Building MPI from sources
       In the list below you have  some  executive  instructions  for  building  some  of  the  open-source  MPI
       implementations out there with support for shared/dynamic libraries on POSIX environments.

       • MPICH

            $ tar -zxf mpich-X.X.X.tar.gz
            $ cd mpich-X.X.X
            $ ./configure --enable-shared --prefix=/usr/local/mpich
            $ make
            $ make install

       • Open MPI

            $ tar -zxf openmpi-X.X.X tar.gz
            $ cd openmpi-X.X.X
            $ ./configure --prefix=/usr/local/openmpi
            $ make all
            $ make install

       • MPICH 1

            $ tar -zxf mpich-X.X.X.tar.gz
            $ cd mpich-X.X.X
            $ ./configure --enable-sharedlib --prefix=/usr/local/mpich1
            $ make
            $ make install

       Perhaps  you  will  need  to  set  the LD_LIBRARY_PATH environment variable (using export, setenv or what
       applies to your system) pointing to the directory containing the MPI  libraries  .  In  case  of  getting
       runtime  linking  errors  when  running  MPI programs, the following lines can be added to the user login
       shell script (.profile, .bashrc, etc.).

       • MPICH

            MPI_DIR=/usr/local/mpich
            export LD_LIBRARY_PATH=$MPI_DIR/lib:$LD_LIBRARY_PATH

       • Open MPI

            MPI_DIR=/usr/local/openmpi
            export LD_LIBRARY_PATH=$MPI_DIR/lib:$LD_LIBRARY_PATH

       • MPICH 1

            MPI_DIR=/usr/local/mpich1
            export LD_LIBRARY_PATH=$MPI_DIR/lib/shared:$LD_LIBRARY_PATH:
            export MPICH_USE_SHLIB=yes

         WARNING:
            MPICH 1 support  for  dynamic  libraries  is  not  completely  transparent.  Users  should  set  the
            environment  variable  MPICH_USE_SHLIB  to  yes in order to avoid link problems when using the mpicc
            compiler wrapper.

AUTHOR

       Lisandro Dalcin

COPYRIGHT

       2020, Lisandro Dalcin