├── .gitignore ├── 01-hello-world ├── 02-broadcast ├── 03-scatter-gather ├── 04-image-spectrogram ├── 04-image-spectrogram.job ├── 05-pseudo-whitening ├── 05-pseudo-whitening.job ├── 07-matrix-vector-product ├── 07-matrix-vector-product.job ├── 08-matrix-matrix-product.py ├── 09-task-pull.py ├── 10-task-pull-spawn.py ├── LICENSE ├── README.txt └── parutils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | hostfile 3 | -------------------------------------------------------------------------------- /01-hello-world: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | from mpi4py import MPI 5 | 6 | 7 | comm = MPI.COMM_WORLD 8 | 9 | print("Hello! I'm rank %d from %d running in total..." % (comm.rank, comm.size)) 10 | 11 | comm.Barrier() # wait for everybody to synchronize _here_ 12 | 13 | -------------------------------------------------------------------------------- /02-broadcast: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import division 4 | from __future__ import print_function 5 | 6 | import numpy as np 7 | from mpi4py import MPI 8 | 9 | from parutils import pprint 10 | 11 | comm = MPI.COMM_WORLD 12 | 13 | pprint("-"*78) 14 | pprint(" Running on %d cores" % comm.size) 15 | pprint("-"*78) 16 | 17 | comm.Barrier() 18 | 19 | # Prepare a vector of N=5 elements to be broadcasted... 20 | N = 5 21 | if comm.rank == 0: 22 | A = np.arange(N, dtype=np.float64) # rank 0 has proper data 23 | else: 24 | A = np.empty(N, dtype=np.float64) # all other just an empty array 25 | 26 | # Broadcast A from rank 0 to everybody 27 | comm.Bcast( [A, MPI.DOUBLE] ) 28 | 29 | # Everybody should now have the same... 30 | print("[%02d] %s" % (comm.rank, A)) 31 | -------------------------------------------------------------------------------- /03-scatter-gather: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import division 4 | from __future__ import print_function 5 | 6 | import numpy as np 7 | from mpi4py import MPI 8 | 9 | from parutils import pprint 10 | 11 | comm = MPI.COMM_WORLD 12 | 13 | pprint("-"*78) 14 | pprint(" Running on %d cores" % comm.size) 15 | pprint("-"*78) 16 | 17 | my_N = 4 18 | N = my_N * comm.size 19 | 20 | if comm.rank == 0: 21 | A = np.arange(N, dtype=np.float64) 22 | else: 23 | A = np.empty(N, dtype=np.float64) 24 | 25 | my_A = np.empty(my_N, dtype=np.float64) 26 | 27 | # Scatter data into my_A arrays 28 | comm.Scatter( [A, MPI.DOUBLE], [my_A, MPI.DOUBLE] ) 29 | 30 | pprint("After Scatter:") 31 | for r in range(comm.size): 32 | if comm.rank == r: 33 | print("[%d] %s" % (comm.rank, my_A)) 34 | comm.Barrier() 35 | 36 | # Everybody is multiplying by 2 37 | my_A *= 2 38 | 39 | # Allgather data into A again 40 | comm.Allgather( [my_A, MPI.DOUBLE], [A, MPI.DOUBLE] ) 41 | 42 | pprint("After Allgather:") 43 | for r in range(comm.size): 44 | if comm.rank == r: 45 | print("[%d] %s" % (comm.rank, A)) 46 | comm.Barrier() 47 | 48 | -------------------------------------------------------------------------------- /04-image-spectrogram: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | 5 | How to run: 6 | 7 | mpirun -np ./image-spectrogram 8 | 9 | 10 | This example computes the 2D-FFT of every image inside , 11 | Summs the absolute value of their spectrogram and finally displays 12 | the result in log-scale. 13 | """ 14 | 15 | from __future__ import division 16 | 17 | import sys 18 | import tables 19 | import pylab 20 | import numpy as np 21 | from numpy.fft import fft2, fftshift 22 | from mpi4py import MPI 23 | from parutils import pprint 24 | 25 | #============================================================================= 26 | # Main 27 | 28 | comm = MPI.COMM_WORLD 29 | 30 | in_fname = sys.argv[-1] 31 | 32 | try: 33 | h5in = tables.openFile(in_fname, 'r') 34 | except: 35 | pprint("Error: Could not open file %s" % in_fname) 36 | exit(1) 37 | 38 | images = h5in.root.images 39 | image_count, height, width = images.shape 40 | image_count = min(image_count, 800) 41 | 42 | pprint("============================================================================") 43 | pprint(" Running %d parallel MPI processes" % comm.size) 44 | pprint(" Reading images from '%s'" % in_fname) 45 | pprint(" Processing %d images of size %d x %d" % (image_count, width, height)) 46 | 47 | # Distribute workload so that each MPI process analyzes image number i, where 48 | # i % comm.size == comm.rank. 49 | # 50 | # For example if comm.size == 4: 51 | # rank 0: 0, 4, 8, ... 52 | # rank 1: 1, 5, 9, ... 53 | # rank 2: 2, 6, 10, ... 54 | # rank 3: 3, 7, 11, ... 55 | 56 | comm.Barrier() ### Start stopwatch ### 57 | t_start = MPI.Wtime() 58 | 59 | my_spec = np.zeros( (height, width) ) 60 | for i in range(comm.rank, image_count, comm.size): 61 | img = images[i] # Load image from HDF file 62 | img_ = fft2(img) # 2D FFT 63 | my_spec += np.abs(img_) # Sum absolute value into partial spectrogram 64 | 65 | my_spec /= image_count 66 | 67 | # Now reduce the partial spectrograms into *spec* by summing 68 | # them all together. The result is only avalable at rank 0. 69 | # If you want the result to be availabe on all processes, use 70 | # Allreduce(...) 71 | spec = np.zeros_like(my_spec) 72 | comm.Reduce( 73 | [my_spec, MPI.DOUBLE], 74 | [spec, MPI.DOUBLE], 75 | op=MPI.SUM, 76 | root=0 77 | ) 78 | 79 | comm.Barrier() 80 | t_diff = MPI.Wtime()-t_start ### Stop stopwatch ### 81 | 82 | h5in.close() 83 | 84 | pprint( 85 | " Analyzed %d images in %5.2f seconds: %4.2f images per second" % 86 | (image_count, t_diff, image_count/t_diff) 87 | ) 88 | pprint("============================================================================") 89 | 90 | # Now rank 0 outputs the resulting spectrogram. 91 | # Either onto the screen or into a image file. 92 | if comm.rank == 0: 93 | spec = fftshift(spec) 94 | pylab.imshow(np.log(spec)) 95 | pylab.show() 96 | 97 | comm.Barrier() 98 | 99 | -------------------------------------------------------------------------------- /04-image-spectrogram.job: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Join std- and error output 4 | #PBS -j oe 5 | 6 | # Preserve environment variables 7 | #PBS -V 8 | 9 | # Change into original workdir 10 | cd $PBS_O_WORKDIR 11 | 12 | mpirun -x PYTHONPATH ./image-spectrogram van-hateren-linear.h5 13 | 14 | -------------------------------------------------------------------------------- /05-pseudo-whitening: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | 5 | How to run: 6 | 7 | mpirun -np ./pseudo-whitening 8 | 9 | """ 10 | 11 | from __future__ import division 12 | 13 | import sys 14 | import tables 15 | import numpy as np 16 | from numpy.fft import fft2, ifft2 17 | from mpi4py import MPI 18 | from parutils import pprint 19 | 20 | #============================================================================= 21 | # Main 22 | 23 | comm = MPI.COMM_WORLD 24 | 25 | in_fname = sys.argv[-2] 26 | out_fname = sys.argv[-1] 27 | 28 | try: 29 | h5in = tables.openFile(in_fname, 'r') 30 | except: 31 | pprint("Error: Could not open file %s" % in_fname) 32 | exit(1) 33 | 34 | # 35 | images = h5in.root.images 36 | image_count, height, width = images.shape 37 | image_count = min(image_count, 200) 38 | 39 | pprint("============================================================================") 40 | pprint(" Running %d parallel MPI processes" % comm.size) 41 | pprint(" Reading images from '%s'" % in_fname) 42 | pprint(" Processing %d images of size %d x %d" % (image_count, width, height)) 43 | pprint(" Writing whitened images into '%s'" % out_fname) 44 | 45 | # Prepare convolution kernel in frequency space 46 | kernel_ = np.zeros((height, width)) 47 | 48 | # rank 0 needs buffer space to gather data 49 | if comm.rank == 0: 50 | gbuf = np.empty( (comm.size, height, width) ) 51 | else: 52 | gbuf = None 53 | 54 | # Distribute workload so that each MPI process processes image number i, where 55 | # i % comm.size == comm.rank. 56 | # 57 | # For example if comm.size == 4: 58 | # rank 0: 0, 4, 8, ... 59 | # rank 1: 1, 5, 9, ... 60 | # rank 2: 2, 6, 10, ... 61 | # rank 3: 3, 7, 11, ... 62 | # 63 | # Each process reads the image from the HDF file by itself. Sadly, python-tables 64 | # does not support parallel writes from multiple processes into the same HDF 65 | # file. So we have to serialize the write operation: Process 0 gathers all 66 | # whitened images and writes them. 67 | 68 | comm.Barrier() ### Start stopwatch ### 69 | t_start = MPI.Wtime() 70 | 71 | for i_base in range(0, image_count, comm.size): 72 | i = i_base + comm.rank 73 | # 74 | if i 0.01: 77 | pprint("!! Error: Wrong result!") 78 | 79 | pprint(" %d iterations of size %d in %5.2fs: %5.2f iterations per second" % 80 | (iter, size, t_diff, iter/t_diff) 81 | ) 82 | pprint("============================================================================") 83 | 84 | -------------------------------------------------------------------------------- /07-matrix-vector-product.job: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Join std- and error output 4 | #PBS -j oe 5 | 6 | # Preserve environment variables 7 | #PBS -V 8 | 9 | # Change into original workdir 10 | cd $PBS_O_WORKDIR 11 | 12 | mpirun -x PYTHONPATH ./matrix-vector-product 13 | 14 | -------------------------------------------------------------------------------- /08-matrix-matrix-product.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import division 4 | from __future__ import print_function 5 | 6 | import numpy as np 7 | from mpi4py import MPI 8 | from time import time 9 | 10 | #=============================================================================# 11 | 12 | my_N = 3000 13 | my_M = 3000 14 | 15 | #=============================================================================# 16 | 17 | NORTH = 0 18 | SOUTH = 1 19 | EAST = 2 20 | WEST = 3 21 | 22 | 23 | 24 | def pprint(string, comm=MPI.COMM_WORLD): 25 | if comm.rank == 0: 26 | print(string) 27 | 28 | 29 | if __name__ == "__main__": 30 | comm = MPI.COMM_WORLD 31 | 32 | mpi_rows = int(np.floor(np.sqrt(comm.size))) 33 | mpi_cols = comm.size // mpi_rows 34 | if mpi_rows*mpi_cols > comm.size: 35 | mpi_cols -= 1 36 | if mpi_rows*mpi_cols > comm.size: 37 | mpi_rows -= 1 38 | 39 | pprint("Creating a %d x %d processor grid..." % (mpi_rows, mpi_cols) ) 40 | 41 | ccomm = comm.Create_cart( (mpi_rows, mpi_cols), periods=(True, True), reorder=True) 42 | 43 | my_mpi_row, my_mpi_col = ccomm.Get_coords( ccomm.rank ) 44 | neigh = [0,0,0,0] 45 | 46 | neigh[NORTH], neigh[SOUTH] = ccomm.Shift(0, 1) 47 | neigh[EAST], neigh[WEST] = ccomm.Shift(1, 1) 48 | 49 | 50 | # Create matrices 51 | my_A = np.random.normal(size=(my_N, my_M)).astype(np.float32) 52 | my_B = np.random.normal(size=(my_N, my_M)).astype(np.float32) 53 | my_C = np.zeros_like(my_A) 54 | 55 | tile_A = my_A 56 | tile_B = my_B 57 | tile_A_ = np.empty_like(my_A) 58 | tile_B_ = np.empty_like(my_A) 59 | req = [None, None, None, None] 60 | 61 | t0 = time() 62 | for r in range(mpi_rows): 63 | req[EAST] = ccomm.Isend(tile_A , neigh[EAST]) 64 | req[WEST] = ccomm.Irecv(tile_A_, neigh[WEST]) 65 | req[SOUTH] = ccomm.Isend(tile_B , neigh[SOUTH]) 66 | req[NORTH] = ccomm.Irecv(tile_B_, neigh[NORTH]) 67 | 68 | #t0 = time() 69 | my_C += np.dot(tile_A, tile_B) 70 | #t1 = time() 71 | 72 | req[0].Waitall(req) 73 | #t2 = time() 74 | #print("Time computing %6.2f %6.2f" % (t1-t0, t2-t1)) 75 | comm.barrier() 76 | t_total = time()-t0 77 | 78 | t0 = time() 79 | np.dot(tile_A, tile_B) 80 | t_serial = time()-t0 81 | 82 | pprint(78*"=") 83 | pprint("Computed (serial) %d x %d x %d in %6.2f seconds" % (my_M, my_M, my_N, t_serial)) 84 | pprint(" ... expecting parallel computation to take %6.2f seconds" % (mpi_rows*mpi_rows*mpi_cols*t_serial / comm.size)) 85 | pprint("Computed (parallel) %d x %d x %d in %6.2f seconds" % (mpi_rows*my_M, mpi_rows*my_M, mpi_cols*my_N, t_total)) 86 | 87 | 88 | #print "[%d] (%d,%d): %s" % (comm.rank, my_mpi_row, my_mpi_col, neigh) 89 | 90 | comm.barrier() 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /09-task-pull.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Demonstrate the task-pull paradigm for high-throughput computing 3 | using mpi4py. Task pull is an efficient way to perform a large number of 4 | independent tasks when there are more tasks than processors, especially 5 | when the run times vary for each task. 6 | 7 | This code is over-commented for instructional purposes. 8 | 9 | This example was contributed by Craig Finch (cfinch@ieee.org). 10 | Inspired by http://math.acadiau.ca/ACMMaC/Rmpi/index.html 11 | """ 12 | from __future__ import print_function 13 | 14 | from mpi4py import MPI 15 | 16 | 17 | def enum(*sequential, **named): 18 | """Handy way to fake an enumerated type in Python 19 | http://stackoverflow.com/questions/36932/how-can-i-represent-an-enum-in-python 20 | """ 21 | enums = dict(zip(sequential, range(len(sequential))), **named) 22 | return type('Enum', (), enums) 23 | 24 | # Define MPI message tags 25 | tags = enum('READY', 'DONE', 'EXIT', 'START') 26 | 27 | # Initializations and preliminaries 28 | comm = MPI.COMM_WORLD # get MPI communicator object 29 | size = comm.size # total number of processes 30 | rank = comm.rank # rank of this process 31 | status = MPI.Status() # get MPI status object 32 | 33 | if rank == 0: 34 | # Master process executes code below 35 | tasks = range(2*size) 36 | task_index = 0 37 | num_workers = size - 1 38 | closed_workers = 0 39 | print("Master starting with %d workers" % num_workers) 40 | while closed_workers < num_workers: 41 | data = comm.recv(source=MPI.ANY_SOURCE, tag=MPI.ANY_TAG, status=status) 42 | source = status.Get_source() 43 | tag = status.Get_tag() 44 | if tag == tags.READY: 45 | # Worker is ready, so send it a task 46 | if task_index < len(tasks): 47 | comm.send(tasks[task_index], dest=source, tag=tags.START) 48 | print("Sending task %d to worker %d" % (task_index, source)) 49 | task_index += 1 50 | else: 51 | comm.send(None, dest=source, tag=tags.EXIT) 52 | elif tag == tags.DONE: 53 | results = data 54 | print("Got data from worker %d" % source) 55 | elif tag == tags.EXIT: 56 | print("Worker %d exited." % source) 57 | closed_workers += 1 58 | 59 | print("Master finishing") 60 | else: 61 | # Worker processes execute code below 62 | name = MPI.Get_processor_name() 63 | print("I am a worker with rank %d on %s." % (rank, name)) 64 | while True: 65 | comm.send(None, dest=0, tag=tags.READY) 66 | task = comm.recv(source=0, tag=MPI.ANY_TAG, status=status) 67 | tag = status.Get_tag() 68 | 69 | if tag == tags.START: 70 | # Do the work here 71 | result = task**2 72 | comm.send(result, dest=0, tag=tags.DONE) 73 | elif tag == tags.EXIT: 74 | break 75 | 76 | comm.send(None, dest=0, tag=tags.EXIT) 77 | 78 | -------------------------------------------------------------------------------- /10-task-pull-spawn.py: -------------------------------------------------------------------------------- 1 | """ A different implementation of task-pull with less communication and full 2 | use of resources (mainly-idle parent shares with worker). Sentinels are used in 3 | place of tags. Start parent with 'python ' rather than mpirun; 4 | parent will then spawn specified number of workers. Work is randomized to 5 | demonstrate dynamic allocation. Worker logs are collectively passed back to 6 | parent at the end in place of results. Comments and output are both 7 | deliberately excessive for instructional purposes. """ 8 | from __future__ import print_function 9 | from __future__ import division 10 | 11 | from mpi4py import MPI 12 | import random 13 | import time 14 | import sys 15 | 16 | n_workers = 10 17 | n_tasks = 50 18 | start_worker = 'worker' 19 | usage = 'Program should be started without argument' 20 | 21 | # Parent 22 | if len(sys.argv) == 1: 23 | 24 | # Start clock 25 | start = MPI.Wtime() 26 | 27 | # Random 1-9s tasks 28 | task_list = [random.randint(1, 9) for task in range(n_tasks)] 29 | total_time = sum(task_list) 30 | 31 | # Append stop sentinel for each worker 32 | msg_list = task_list + ([StopIteration] * n_workers) 33 | 34 | # Spawn workers 35 | comm = MPI.COMM_WORLD.Spawn( 36 | sys.executable, 37 | args=[sys.argv[0], start_worker], 38 | maxprocs=n_workers) 39 | 40 | # Reply to whoever asks until done 41 | status = MPI.Status() 42 | for position, msg in enumerate(msg_list): 43 | comm.recv(source=MPI.ANY_SOURCE, status=status) 44 | comm.send(obj=msg, dest=status.Get_source()) 45 | 46 | # Simple (loop position) progress bar 47 | percent = ((position + 1) * 100) // (n_tasks + n_workers) 48 | sys.stdout.write( 49 | '\rProgress: [%-50s] %3i%% ' % 50 | ('=' * (percent // 2), percent)) 51 | sys.stdout.flush() 52 | 53 | # Gather reports from workers 54 | reports = comm.gather(root=MPI.ROOT) 55 | 56 | # Print summary 57 | workers = 0; tasks = 0; time = 0 58 | print('\n\n Worker Tasks Time') 59 | print('-' * 26) 60 | for worker, report in enumerate(reports): 61 | print('%8i%8i%8i' % (worker, len(report), sum(report))) 62 | workers += 1; tasks += len(report); time += sum(report) 63 | print('-' * 26) 64 | print('%8i%8i%8i' % (workers, tasks, time)) 65 | 66 | # Check all in order 67 | assert workers == n_workers, 'Missing workers' 68 | assert tasks == n_tasks, 'Lost tasks' 69 | assert time == total_time, 'Output != assigned input' 70 | 71 | # Final statistics 72 | finish = MPI.Wtime() - start 73 | efficiency = (total_time * 100.) / (finish * n_workers) 74 | print('\nProcessed in %.2f secs' % finish) 75 | print('%.2f%% efficient' % efficiency) 76 | 77 | # Shutdown 78 | comm.Disconnect() 79 | 80 | # Worker 81 | elif sys.argv[1] == start_worker: 82 | 83 | # Connect to parent 84 | try: 85 | comm = MPI.Comm.Get_parent() 86 | rank = comm.Get_rank() 87 | except: 88 | raise ValueError('Could not connect to parent - ' + usage) 89 | 90 | # Ask for work until stop sentinel 91 | log = [] 92 | for task in iter(lambda: comm.sendrecv(dest=0), StopIteration): 93 | log.append(task) 94 | 95 | # Do work (or not!) 96 | time.sleep(task) 97 | 98 | # Collective report to parent 99 | comm.gather(sendobj=log, root=0) 100 | 101 | # Shutdown 102 | comm.Disconnect() 103 | 104 | # Catch 105 | else: 106 | raise ValueError(usage) 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jörg Bornschein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | 2 | === Dependencies === 3 | 4 | These programs depend on mpi4py (>= Version 1.0) 5 | 6 | The mpi4py documentation and installation instructions 7 | can be found at: 8 | 9 | http://mpi4py.scipy.org/ 10 | 11 | === How to run on a single (multi-core) host === 12 | 13 | Run it with 14 | 15 | mpirun -np 4 ./some-program 16 | 17 | where the number after "-np " is the number of parallel MPI 18 | processes to be started. 19 | 20 | 21 | === How to run on multiple hosts === 22 | 23 | If you want to run the program distributed over multiple hosts, 24 | you have to create a which looks like: 25 | 26 | -- hostfile -- 27 | host1 slots=4 28 | host2 slots=4 29 | host3 slots=4 30 | -------------- 31 | 32 | Where "slots=" specifies the number of parallel processes that should be 33 | started on that host. 34 | 35 | Run it with 36 | 37 | mpirun --hostfile ./some-program 38 | 39 | 40 | === Run on a cluster with the Torque Job scheduling system === 41 | 42 | There are two possibilities: 43 | 44 | a) Run interactively: 45 | 46 | Request an interactive session and allocate a number of processors/nodes for 47 | your session: 48 | 49 | $ qsub -I X -l nodes=4:ppn=4 50 | 51 | Where "-I" means you want to work interactively, "-X" requests grapical 52 | (X-Window) I/O -- (you can run arbitrary programs that open windows). The 53 | option "-l " specifies the resources you want to allocate. "-l nodes=4:ppn=4" 54 | requests four compute nodes with each having four processor cores 55 | [ppn =^ ProcessorsPerNode]. So in total you allocate 16 CPU cores. 56 | [The scheduler is free to run your job on two nodes having 8 CPU cores each] 57 | 58 | Once your interactive session is ready, you run 59 | 60 | $ mpirun ./your-program 61 | 62 | mpirun automatically knowns how many parallel processes have to be started and 63 | where they have to be started. 64 | 65 | b) Submit as non-interactive batch-job: 66 | 67 | Use 68 | 69 | $ qsub -l nodes=4:ppn=4 ./your-jobfile.job 70 | 71 | to submit jour job-file. Similar to the interactive case, "-l" again is used 72 | to request resources from the scheduling system. The job file usually is a 73 | simple shell script which specifies the commands to be run once your job 74 | starts. In addition, the jobfile can contain "#PBS " statements 75 | which are used to specify additional options which could have been specified 76 | in the "qsub" commandline. Please see "man qsub" for details. 77 | 78 | -------------------------------------------------------------------------------- /parutils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Some utility functions useful for MPI parallel programming 3 | """ 4 | from __future__ import print_function 5 | 6 | from mpi4py import MPI 7 | 8 | #============================================================================= 9 | # I/O Utilities 10 | 11 | def pprint(str="", end="\n", comm=MPI.COMM_WORLD): 12 | """Print for MPI parallel programs: Only rank 0 prints *str*.""" 13 | if comm.rank == 0: 14 | print(str+end, end=' ') 15 | 16 | --------------------------------------------------------------------------------