MPI Project Set 2

Project 4

A “token ring” is a circular messaging system. Think of a relay, with a message or “baton” being passed from one runner to the next, but around a circle so that the last “runner” passes it back to the first. Write a program that implements this with MPI. Hint: separate the ranks into root and everybody else. You may wish to use MPI_ANY_TAG and MPI_ANY_SOURCE.

Example Solutions

C++

#include <iostream>
#include <mpi.h>

using namespace std;

int main(int argc, char **argv)
{
    int nProcs;
    int rank;
    int baton = 42;
    MPI_Status status;
    double startTime;
    double stopTime;

    MPI_Init(&argc, &argv);

    MPI_Comm_size(MPI_COMM_WORLD, &nProcs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (nProcs > 1)
    {
        if (rank == 0) {
            startTime = MPI_Wtime();
            MPI_Send(&baton, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
            MPI_Recv(&baton, 1, MPI_INT, MPI_ANY_SOURCE,
            MPI_ANY_TAG, MPI_COMM_WORLD, &status);
            cout<<"Process "<<rank<<" has the baton.\n";
            stopTime = MPI_Wtime();
            cout<<"Elapsed time to pass a token around a ring of size "<<nProcs<<" was "<<stopTime-startTime<<"seconds\n";
            fflush(stdout);
        } else {
            MPI_Recv(&baton, 1, MPI_INT, MPI_ANY_SOURCE,
                                  MPI_ANY_TAG, MPI_COMM_WORLD, &status);
            cout<<"Process "<<rank<<" has the baton\n";
            fflush(stdout);
            MPI_Send(&baton, 1, MPI_INT, (rank + 1) % nProcs,
                               0, MPI_COMM_WORLD);
        }
    }

    MPI_Finalize();
    return 0;
}

Fortran

program ring
use mpi

implicit none

integer           :: baton

integer           :: my_rank,  npes
integer, parameter:: tag = 0
integer           :: err, errcode
integer           :: i
double precision  :: start, runtime

integer, dimension(MPI_STATUS_SIZE) :: status

call MPI_INIT(err)
call MPI_COMM_RANK(MPI_COMM_WORLD, my_rank, err)
call MPI_COMM_SIZE(MPI_COMM_WORLD, npes, err)

if ( my_rank .eq. 0 ) then
    baton = 42
endif

if ( npes .eq. 1 ) then
   print *, 'Rank ',my_rank, 'has the baton ', baton
   call MPI_Finalize(err)
   stop
endif

if ( my_rank .eq. 0 ) then
   start = MPI_Wtime()
   call MPI_Send(baton,1,MPI_INTEGER,1,tag,MPI_COMM_WORLD,err)
   call MPI_Recv(baton,1,MPI_INTEGER,MPI_ANY_SOURCE,MPI_ANY_TAG,               &
                         MPI_COMM_WORLD,status,err)
   print *, 'Rank ',my_rank, 'has the baton ', baton
   call flush(6)
   runtime = MPI_Wtime()-start
   write(*,'(a,f0.4,a)') 'Total elapsed time was ', runtime, ' seconds'
   call flush(6)

else
     call MPI_Recv(baton,1,MPI_INTEGER,MPI_ANY_SOURCE,MPI_ANY_TAG,             &
                           MPI_COMM_WORLD,status,err)
     call MPI_Send(baton,1,MPI_INTEGER,mod(my_rank+1,npes),tag,                &
                           MPI_COMM_WORLD,err)
     write(*,*) 'Rank ',my_rank, 'has the baton ', baton
     call flush(6)

endif

call MPI_Finalize(err)

end program ring


Python

from mpi4py import MPI
import numpy as np
import sys

comm=MPI.COMM_WORLD
my_rank=comm.Get_rank()
npes   =comm.Get_size()

if my_rank==0:
   baton=42*np.array([1],dtype='int')
else:
   baton=np.zeros((1,),dtype='int')

if npes==1:
   print('Rank ',my_rank,'has the baton ',baton)
   MPI.Finalize()
   sys.exit()

if my_rank==0:
   start=MPI.Wtime()
   comm.Send([baton,MPI.INT],1,0)
   comm.Recv([baton,MPI.INT],MPI.ANY_SOURCE,MPI.ANY_TAG)
   print('Rank ',my_rank,'has the baton ',baton)
   runtime=MPI.Wtime()-start
   print('Total elapsed time was %.4f seconds'%runtime)
else:
   comm.Recv([baton,MPI.INT],MPI.ANY_SOURCE,MPI.ANY_TAG)
   comm.Send([baton,MPI.INT],(my_rank+1)%npes,0)
   print('Rank ',my_rank,'has the baton ',baton)

Project 5

Write a program in which each process determines a unique partner to exchange messages. One way to do this is to use

if rank < npes//2:
partner=npes//2 + rank
else
partner=rank-npes//2

where the // indicates integer division (no fractional part).

Each tasks sends its rank to its partner. Each task receives the partner’s rank. Print the message received when done. You may assume that the number of processes is even, but check that this is the case before proceeding.

Hints: do not overwrite the receiver’s rank. As always, Python programmers should take care that NumPy arrays are declared with the correct type.

Example Solutions

C++

#include <iostream>
#include "mpi.h"

using namespace std;

int main(int argc, char *argv[]) {
    int rank, nprocs;
    MPI_Status status;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if ( nprocs%2 != 0 ) {
	cout<<"This program runs only on an even number of processes\n";
	MPI_Finalize();
	exit(1);
    }

    int message;

    int partner;
    int half=nprocs/2;
    if ( rank < half ) {
        partner=half+rank;
    }
    else {
	partner=rank-half;
    }

    if ( rank < half ) {
	MPI_Recv(&message,1,MPI_INT,partner,0,MPI_COMM_WORLD,&status);
	MPI_Send(&rank,1,MPI_INT,partner,0,MPI_COMM_WORLD);
    }
    else { 
	MPI_Send(&rank,1,MPI_INT,partner,0,MPI_COMM_WORLD);
	MPI_Recv(&message,1,MPI_INT,partner,0,MPI_COMM_WORLD,&status);
    }

    cout<<rank<<" "<<message<<"\n";

    MPI_Finalize();

    exit(0);
}


Fortran

program send_receive
use mpi
implicit none

integer ::  rank, partner, nprocs, ierr
integer, dimension(MPI_STATUS_SIZE) :: status
integer :: half, message

   call MPI_Init(ierr)
   call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr);
   call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr);

    if ( mod(nprocs,2) /= 0 ) then
        write(*,*) "This program runs only on an even number of processes"
        call MPI_Finalize(ierr)
        stop
    endif

    half = nprocs/2
    if ( rank < half ) then
        partner= half+rank
    else 
        partner=rank-half
    endif

    if ( rank < nprocs/2 ) then
        call MPI_Recv(message,1,MPI_INTEGER,partner,0,MPI_COMM_WORLD,status,ierr);
        call MPI_Send(rank,1,MPI_INTEGER,partner,0,MPI_COMM_WORLD,ierr)
    else 
        call MPI_Send(rank,1,MPI_INTEGER,partner,0,MPI_COMM_WORLD,ierr)
        call MPI_Recv(message,1,MPI_INTEGER,partner,0,MPI_COMM_WORLD,status,ierr)
    endif

    write(*,*) rank, message

    call MPI_Finalize(ierr)

end program


Python

import sys
import numpy as np
from mpi4py import MPI

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

if nprocs%2 != 0:
    print("This program works only for an even number of processes.")
    sys.exit()

message=np.zeros(1,dtype='int')
rank_val=rank*np.ones(1,dtype='int')

half = nprocs//2
if rank < half:
    partner=half+rank
else:
    partner=rank-half

if rank<half:
    comm.Recv([message,MPI.INT],source=partner)
    comm.Send([rank_val,MPI.INT],dest=partner)
else:
    comm.Send([rank_val,MPI.INT],dest=partner)
    comm.Recv([message,MPI.INT],source=partner)

print(rank,message)

Project 6

A common pattern in parallel programming is the manager-worker structure. A “manager” process distributes work to “worker” processes. Sometimes manager code is separated by if rank==0 (the manager should nearly always be rank 0) statements, while the other ranks execute the “worker” code. Sometimes the manager spawns distinct worker processes, but that requires using the more advanced MPI spawn capability. In this project we will use a single code. Usually the manager distributes work to the workers, which return results; the manager then hands more work to those processes that are ready, until all is completed. For this example the workers will do only one round of work.

Starting from the stub for your language, complete the send and receive calls.

Starting Codes

C++

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <random>
#include <cmath>
#include <vector>
#include "mpi.h"


using namespace std;

int random_int(int n,int m) {
    //quick and stupid way, using full C++ machinery is better but complex
    return n+rand()%m;
}

double do_work() {
    //hardcoded bounds for convenience
    int nsteps=random_int(10000,30000);
    float result=0.;
    for (int i=0; i<nsteps; ++i) { 
        result+=i;
    }
    return result;
}

int main(int argc, char **argv) {

    int nprocs, rank;
    MPI_Status status;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    //Don't use rand and srand if good random numbers are needed
    unsigned int seed = (unsigned) time(NULL);
    //taken from some University course site
    unsigned int my_seed = (seed & 0xFFFFFFF0) | (rank + 1);
    srand(my_seed);

    vector<float> results(nprocs);

    int done=0;
    float result;
    int sender;

    if (rank==0) {
        for (int i=1; i<nprocs; ++i) {
            MPI_Recv(
	    sender=
	    results[sender]=result;
            done=1;
            MPI_Send(&done,1,MPI_INT,sender,0,MPI_COMM_WORLD);
	}
    } else {
        for(int n=1; n<nprocs; ++n) {
            if (rank==n) {
                result=do_work();
                MPI_Send(
                MPI_Recv(
	    }
	}
    }

    float total=0;
    if (rank==0) {
	for (int i=1; i<nprocs; ++i ) {
            total+=results[i];
	}
        cout << "The final result is "<<total<<"\n";
    }

    MPI_Finalize();

}

Fortran

module Random
  implicit none
  ! Comment out one or the other when the module is incorporated into a code.

  ! Single precision
  integer, parameter   :: rk = kind(1.0)

  ! Double precision
  !integer, parameter   :: rk = kind(1.0d0)

contains

  subroutine get_random_seed(seed)
    ! To use this subroutine, seed must be declared allocatable in the
    ! calling unit.
    integer, dimension(:), allocatable, intent(out)  :: seed
    integer                                          :: isize

    call random_seed(size=isize)
    if (.not. allocated(seed)) allocate(seed(isize))
    call random_seed(get=seed)

  end subroutine get_random_seed

  subroutine set_random_seed(seed)
    ! Sets all elements of the seed array
    integer, optional, intent(in)       :: seed

    integer                             :: isize
    integer, dimension(:), allocatable  :: iseed
    integer, dimension(8)               :: idate
    integer, parameter                  :: default_seed=2345678

    call get_random_seed(iseed)

    if ( .not. present(seed) ) then
       call date_and_time(values=idate)
       ! idate(8) contains millisecond
       if ( all(iseed .ne. 0) ) then
          iseed = iseed * (idate(8))
       else
          iseed = default_seed * (idate(8))
       endif
    else
       iseed=int(seed)
    endif

    call random_seed(put=iseed)

  end subroutine set_random_seed

  function urand(lb,ub,seed)
    ! Returns a uniformly-distributed random number in the range lb to ub.
    real(rk)                        :: urand
    real(rk), optional, intent(in)  :: lb,ub
    real(rk), optional, intent(in)  :: seed

    integer                         :: iseed
    real(rk)                        :: rnd
    real(rk)                        :: lower,upper

    if ( present(seed) ) then
       iseed=int(seed)
       call set_random_seed(iseed)
    endif

    if ( present(lb) ) then
       lower=lb
    else
       lower=0.0_rk
    endif

    if ( present(ub) ) then
       upper = ub
    else
       upper = 1.0_rk
    endif

    call random_number(rnd)
    urand = lower+(upper-lower)*rnd

    return
  end function urand

  function randint(n,m)
    ! Returns a random integer between n and m.
    integer                 :: randint
    integer, intent(in)     :: n,m

    randint=ceiling(urand(real(n-1,rk),real(m,rk)))

  end function randint

end module random

program manager_worker
use random
use mpi

   integer :: i, n

   integer :: nprocs, rank, sender, ierr
   integer, dimension(MPI_STATUS_SIZE) :: status;

   integer ::  done=0
   real    ::  my_result, total=0.
   real, dimension(:), allocatable :: results
   integer, dimension(:), allocatable :: seed

   interface
        function do_work() result(my_result)
        use random
        real :: my_result
      end function do_work
   end interface

   call MPI_Init(ierr)
   call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr)
   call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)

   allocate(results(nprocs))
   results=0.

   ! The topic of handling random-number generation in parallel programs is
   ! an issue in applied mathematics, and beyond our scope here. We just
   ! use the rank to spread out the seeds some.
   call get_random_seed(seed)
   seed=seed*(rank+1)
   call set_random_seed(seed(1))

   if (rank==0) then
       do i=1,nprocs-1
          call MPI_Recv(
          sender=
          results(sender)=my_result
          done=1;
          call MPI_Send(done,1,MPI_INTEGER,sender,0,MPI_COMM_WORLD,ierr)
       enddo
   else 
       do n=1,nprocs-1
          if (rank==n) then
             my_result=do_work();
             call MPI_Send(my_result,1,MPI_REAL,0,rank,MPI_COMM_WORLD,ierr)
             call MPI_Recv(done,1,MPI_INTEGER,0,MPI_ANY_TAG,MPI_COMM_WORLD,status,ierr)
          endif
       enddo
   endif

   total=sum(results)
   if (rank==0) then
       write(*,*) "The final result is",total
   endif

   call MPI_Finalize(ierr)

end program

function do_work() result(my_result)
    use random
    real :: my_result
    integer nsteps
    !hardcoded bounds for convenience
    nsteps=randint(10000,30000);

    my_result=0.
    do i=1,nsteps
        my_result=my_result+i;
    enddo
end function

Python

import sys
import numpy as np
from mpi4py import MPI

def do_work():
    nsteps=rng.integers(low=10000, high=30000)
    result=0
    for i in range(nsteps):
        result+=i
    return np.array([result],dtype='float')

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

status=MPI.Status()

# The topic of handling random-number generation in parallel programs is 
# an issue in applied mathematics, and beyond our scope here. This should 
# generally space out the seeds a bit.
# rng is global
rng = np.random.default_rng()

done=np.array([0])
results=np.zeros(nprocs)

if rank==0:
    result=np.empty(1)
    for i in range(1,nprocs):
        comm.Recv([result,MPI.DOUBLE],source=MPI.ANY_SOURCE,tag=MPI.ANY_TAG,status=status)
        sender=status.Get_source()
        results[sender]=result
        done[:]=1
        comm.Send([done,MPI.INT],dest=sender)
else: 
    for n in range(1,nprocs):
        if (rank==n):
            result=do_work()
            comm.Send([result,MPI.DOUBLE],dest=0,tag=rank)
            comm.Recv([done,MPI.INT],source=0,status=status)

total=np.sum(results)
if rank==0:
    print(f"The final result is {total:e}")

#Disconnect all processes from communicator
comm.Disconnect()

Example Solutions

C++

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <random>
#include <cmath>
#include <vector>
#include "mpi.h"


using namespace std;

int random_int(int n,int m) {
    //quick and stupid way, using full C++ machinery is better but complex
    return n+rand()%m;
}

double do_work() {
    //hardcoded bounds for convenience
    int nsteps=random_int(10000,30000);
    float result=0.;
    for (int i=0; i<nsteps; ++i) { 
        result+=i;
    }
    return result;
}

int main(int argc, char **argv) {

    int nprocs, rank;
    MPI_Status status;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    //Don't use rand and srand if good random numbers are needed
    unsigned int seed = (unsigned) time(NULL);
    //taken from some University course site
    unsigned int my_seed = (seed & 0xFFFFFFF0) | (rank + 1);
    srand(my_seed);

    vector<float> results(nprocs);

    int done=0;
    float result;
    int sender;

    if (rank==0) {
        for (int i=1; i<nprocs; ++i) {
            MPI_Recv(&result,1,MPI_FLOAT,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,&status);
	    sender=status.MPI_SOURCE;
	    results[sender]=result;
            done=1;
            MPI_Send(&done,1,MPI_INT,sender,0,MPI_COMM_WORLD);
	}
    } else {
        for(int n=1; n<nprocs; ++n) {
            if (rank==n) {
                result=do_work();
                MPI_Send(&result,1,MPI_FLOAT,0,rank,MPI_COMM_WORLD);
                MPI_Recv(&done,1,MPI_INT,0,MPI_ANY_TAG,MPI_COMM_WORLD,&status);
	    }
	}
    }

    float total=0;
    if (rank==0) {
	for (int i=1; i<nprocs; ++i ) {
            total+=results[i];
	}
        cout << "The final result is "<<total<<"\n";
    }

    MPI_Finalize();

}

Fortran

module Random
  implicit none
  ! Comment out one or the other when the module is incorporated into a code.

  ! Single precision
  integer, parameter   :: rk = kind(1.0)

  ! Double precision
  !integer, parameter   :: rk = kind(1.0d0)

contains

  subroutine get_random_seed(seed)
    ! To use this subroutine, seed must be declared allocatable in the
    ! calling unit.
    integer, dimension(:), allocatable, intent(out)  :: seed
    integer                                          :: isize

    call random_seed(size=isize)
    if (.not. allocated(seed)) allocate(seed(isize))
    call random_seed(get=seed)

  end subroutine get_random_seed

  subroutine set_random_seed(seed)
    ! Sets all elements of the seed array
    integer, optional, intent(in)       :: seed

    integer                             :: isize
    integer, dimension(:), allocatable  :: iseed
    integer, dimension(8)               :: idate
    integer, parameter                  :: default_seed=2345678

    call get_random_seed(iseed)

    if ( .not. present(seed) ) then
       call date_and_time(values=idate)
       ! idate(8) contains millisecond
       if ( all(iseed .ne. 0) ) then
          iseed = iseed * (idate(8))
       else
          iseed = default_seed * (idate(8))
       endif
    else
       iseed=int(seed)
    endif

    call random_seed(put=iseed)

  end subroutine set_random_seed

  function urand(lb,ub,seed)
    ! Returns a uniformly-distributed random number in the range lb to ub.
    real(rk)                        :: urand
    real(rk), optional, intent(in)  :: lb,ub
    real(rk), optional, intent(in)  :: seed

    integer                         :: iseed
    real(rk)                        :: rnd
    real(rk)                        :: lower,upper

    if ( present(seed) ) then
       iseed=int(seed)
       call set_random_seed(iseed)
    endif

    if ( present(lb) ) then
       lower=lb
    else
       lower=0.0_rk
    endif

    if ( present(ub) ) then
       upper = ub
    else
       upper = 1.0_rk
    endif

    call random_number(rnd)
    urand = lower+(upper-lower)*rnd

    return
  end function urand

  function randint(n,m)
    ! Returns a random integer between n and m.
    integer                 :: randint
    integer, intent(in)     :: n,m

    randint=ceiling(urand(real(n-1,rk),real(m,rk)))

  end function randint

end module random

program manager_worker
use random
use mpi

   integer :: i, n

   integer :: nprocs, rank, sender, ierr
   integer, dimension(MPI_STATUS_SIZE) :: status;

   integer ::  done=0
   real    ::  my_result, total=0.
   real, dimension(:), allocatable :: results
   integer, dimension(:), allocatable :: seed

   interface
        function do_work() result(my_result)
        use random
        real :: my_result
      end function do_work
   end interface

   call MPI_Init(ierr)
   call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr)
   call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)

   allocate(results(nprocs))
   results=0.

   ! The topic of handling random-number generation in parallel programs is
   ! an issue in applied mathematics, and beyond our scope here. We just
   ! use the rank to spread out the seeds some.
   call get_random_seed(seed)
   seed=seed*(rank+1)
   call set_random_seed(seed(1))

   if (rank==0) then
       do i=1,nprocs-1
          call MPI_Recv(my_result,1,MPI_REAL,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,status,ierr);
          sender=status(MPI_SOURCE)
          results(sender)=my_result
          done=1;
          call MPI_Send(done,1,MPI_INTEGER,sender,0,MPI_COMM_WORLD,ierr)
       enddo
   else 
       do n=1,nprocs-1
          if (rank==n) then
             my_result=do_work();
             call MPI_Send(my_result,1,MPI_REAL,0,rank,MPI_COMM_WORLD,ierr)
             call MPI_Recv(done,1,MPI_INTEGER,0,MPI_ANY_TAG,MPI_COMM_WORLD,status,ierr)
          endif
       enddo
   endif

   total=sum(results)
   if (rank==0) then
       write(*,*) "The final result is",total
   endif

   call MPI_Finalize(ierr)

end program

function do_work() result(my_result)
    use random
    real :: my_result
    integer nsteps
    !hardcoded bounds for convenience
    nsteps=randint(10000,30000);

    my_result=0.
    do i=1,nsteps
        my_result=my_result+i;
    enddo
end function

Python

import sys
import numpy as np
from mpi4py import MPI

def do_work():
    nsteps=rng.integers(low=10000, high=30000)
    result=0
    for i in range(nsteps):
        result+=i
    return np.array([result],dtype='float')

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

status=MPI.Status()

# The topic of handling random-number generation in parallel programs is 
# an issue in applied mathematics, and beyond our scope here. This should 
# generally space out the seeds a bit.
# rng is global
rng = np.random.default_rng()

done=np.array([0])
results=np.zeros(nprocs)

if rank==0:
    result=np.empty(1)
    for i in range(1,nprocs):
        comm.Recv([result,MPI.DOUBLE],source=MPI.ANY_SOURCE,tag=MPI.ANY_TAG,status=status)
        sender=status.Get_source()
        results[sender]=result
        done[:]=1
        comm.Send([done,MPI.INT],dest=sender)
else: 
    for n in range(1,nprocs):
        if (rank==n):
            result=do_work()
            comm.Send([result,MPI.DOUBLE],dest=0,tag=rank)
            comm.Recv([done,MPI.INT],source=0,status=status)

total=np.sum(results)
if rank==0:
    print(f"The final result is {total:e}")

#Disconnect all processes from communicator
comm.Disconnect()

Previous
Next