Calculating π using parallel Monte Carlo

The code below contains a program that computes the value of constant π = 3.1415… to single precision using the standard Monte Carlo technique (see e.g. Press et al. 1992, Numerical Recipes in C, section 7.6).

The parent process starts a user defined number of managed Dragon processes as workers executing the function pi_helper to parallelize the radius computation. The parent process sends two random numbers to the workers using an outgoing Dragon queue q_send and collects the radii from an incoming queue q_recv to compute the result. It also computes a residual delta with respect to the previous iteration. This is repeated until convergence is reached.

This example demonstrates two important paradigms of parallel programming with Dragon: How to execute Python processes using Dragon native and how to use Dragon native Queues to communicate objects between processes.

Listing 23 pi_demo.py: Calculate the value of π in parallel using Dragon native
  1""" Calculate pi in parallel using the standard Monte Carlo technique
  2    with the Dragon native interface.
  3
  4    NOTE: due to the missing Process object in Dragon native for version v0.3
  5    we are using the Multiprocessing process API here. This will change with
  6    v1.0
  7"""
  8
  9import sys
 10import os
 11import random
 12import pickle
 13import math
 14
 15import dragon
 16from dragon.native.queue import Queue
 17
 18from multiprocessing import Process as Process  # only for Dragon v0.3
 19
 20
 21def pi_helper(q_in: Queue, q_out: Queue) -> None:
 22
 23    while True:  # keep working
 24
 25        x, y = q_in.get(timeout=None)
 26
 27        if x == -1 and y == -1:  # we're done here
 28            break
 29
 30        r = x * x + y * y
 31
 32        if r > 1.0:
 33            q_out.put(False)
 34        else:
 35            q_out.put(True)
 36
 37
 38def main(num_workers):
 39
 40    # create Dragon queues
 41    q_send = Queue()
 42    q_recv = Queue()
 43
 44    # create & start processes
 45    processes = []
 46    for _ in range(num_workers):
 47        p = Process(target=pi_helper, args=(q_send, q_recv))
 48        p.start()
 49        processes.append(p)
 50
 51    # start calculation
 52    random.seed(a=42)
 53
 54    num_true = 0
 55    num_send = 0
 56    delta = 1
 57    count = 0
 58
 59    while abs(delta) > 1e-6:
 60
 61        # send new values to everyone
 62        for _ in range(num_workers):
 63            x = random.uniform(0.0, 1.0)
 64            y = random.uniform(0.0, 1.0)
 65
 66            q_send.put((x, y), timeout=0)
 67            num_send += 1
 68
 69        # receive results from everyone
 70        for _ in range(num_workers):
 71
 72            is_inside = q_recv.get(timeout=None)
 73
 74            if is_inside:
 75                num_true += 1
 76
 77        # calculate result of this iteration
 78        value = 4.0 * float(num_true) / float(num_send)
 79        delta = (value - math.pi) / math.pi
 80
 81        if count % 512 == 0:
 82            print(f"{count:04}: pi={value:10.7f}, error={delta:8.1e}", flush=True)
 83
 84        count += 1
 85
 86    print(f"Final value after {count} iterations: pi={value}, error={delta}")
 87
 88    # shut down all managed processes
 89    for _ in range(num_workers):
 90        q_send.put((-1.0, -1.0), timeout=0)  # termination message
 91
 92    # wait for all processes to be finished
 93    for p in processes:
 94        p.join(timeout=None)
 95
 96
 97if __name__ == "__main__":
 98
 99    print(f"\npi-demo: Calculate π = 3.1415 ... in parallel using the Dragon native API.\n")
100
101    try:
102        num_workers = int(sys.argv[1])
103    except:
104        print(f"USAGE: dragon pi_demo.py $N")
105        print(f"  N : number of worker puids to start")
106        sys.exit(f"Wrong argument '{sys.argv[1]}'")
107
108    print(f"Got num_workers = {num_workers}", flush=True)
109    main(num_workers)

The program can be run using 2 workers with dragon pi-demo.py 2 and results in the following output:

Listing 24 Output when running pi_demo.py with 2 workers
 1>$dragon pi_demo.py 2
 2
 3pi-demo: Calculate π = 3.1415 ... in parallel using the Dragon native API.
 4
 5Got num_workers = 2
 60000: pi= 4.0000000, error= 2.7e-01
 70512: pi= 3.1189084, error=-7.2e-03
 81024: pi= 3.1531707, error= 3.7e-03
 91536: pi= 3.1659076, error= 7.7e-03
102048: pi= 3.1449488, error= 1.1e-03
112560: pi= 3.1409606, error=-2.0e-04
123072: pi= 3.1389522, error=-8.4e-04
133584: pi= 3.1391911, error=-7.6e-04
144096: pi= 3.1354650, error=-2.0e-03
154608: pi= 3.1277934, error=-4.4e-03
165120: pi= 3.1267331, error=-4.7e-03
175632: pi= 3.1319013, error=-3.1e-03
186144: pi= 3.1446705, error= 9.8e-04
19Final value after 6342 iterations: pi=3.141595711132135, error=9.732459548770225e-07
20+++ head proc exited, code 0