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