|
| 1 | +import numpy as np |
| 2 | +import mkl_random as rnd |
| 3 | + |
| 4 | +__doc__ = """ |
| 5 | +Let's solve a classic problem of MC-estimating a probability that 3 segments of a unit stick randomly broken in 2 places can form a triangle. |
| 6 | +Let $u_1$ and $u_2$ be standard uniform random variables, denoting positions where the stick has been broken. |
| 7 | +
|
| 8 | +Let $w_1 = \min(u_1, u_2)$ and $w_2 = \max(u_1, u_2)$. Then, length of segments are $x_1 = w_1$, $x_2 = w_2-w_1$, $x_3 = 1-w_2$. |
| 9 | +These lengths must satisfy triangle inequality. |
| 10 | +
|
| 11 | +The closed form result is known to be $\frac{1}{4}$. |
| 12 | +
|
| 13 | +""" |
| 14 | + |
| 15 | +def triangle_inequality(x1, x2, x3): |
| 16 | + """Efficiently finds `np.less(x1,x2+x3)*np.less(x2,x1+x3)*np.less(x3,x1+x2)`""" |
| 17 | + tmp_sum = x2 + x3 |
| 18 | + res = np.less(x1, tmp_sum) # x1 < x2 + x3 |
| 19 | + np.add(x1, x3, out=tmp_sum) |
| 20 | + buf = np.less(x2, tmp_sum) # x2 < x1 + x3 |
| 21 | + np.logical_and(res, buf, out=res) |
| 22 | + np.add(x1, x2, out=tmp_sum) |
| 23 | + np.less(x3, tmp_sum, out=buf) # x3 < x1 + x2 |
| 24 | + np.logical_and(res, buf, out=res) |
| 25 | + return res |
| 26 | + |
| 27 | + |
| 28 | +def mc_dist(rs, n): |
| 29 | + """Monte Carlo estimate of probability on sample of size `n`, using given random state object `rs`""" |
| 30 | + ws = np.sort(rs.rand(2,n), axis=0) |
| 31 | + x2 = np.empty(n, dtype=np.double) |
| 32 | + x3 = np.empty(n, dtype=np.double) |
| 33 | + |
| 34 | + x1 = ws[0] |
| 35 | + np.subtract(ws[1], ws[0], out=x2) |
| 36 | + np.subtract(1, ws[1], out=x3) |
| 37 | + mc_prob = triangle_inequality(x1, x2, x3).sum() / n |
| 38 | + |
| 39 | + return mc_prob |
| 40 | + |
| 41 | + |
| 42 | +def assign_worker_rs(w_rs): |
| 43 | + """Assign process local random state variable `rs` the given value""" |
| 44 | + assert not 'rs' in globals(), "Here comes trouble. Process is not expected to have global variable `rs`" |
| 45 | + |
| 46 | + global rs |
| 47 | + rs = w_rs |
| 48 | + # wait to ensure that the assignment takes place for each worker |
| 49 | + b.wait() |
| 50 | + |
| 51 | + |
| 52 | +def worker_compute(w_id): |
| 53 | + return mc_dist(rs, batch_size) |
| 54 | + |
| 55 | + |
| 56 | +if __name__ == '__main__': |
| 57 | + import multiprocessing as mp |
| 58 | + from itertools import repeat |
| 59 | + from timeit import default_timer as timer |
| 60 | + |
| 61 | + seed = 77777 |
| 62 | + n_workers = 12 |
| 63 | + batch_size = 1024 * 256 |
| 64 | + batches = 10000 |
| 65 | + |
| 66 | + t0 = timer() |
| 67 | + # Create instances of RandomState for each worker process from MT2203 family of generators |
| 68 | + rss = [ rnd.RandomState(seed, brng=('MT2203', idx)) for idx in range(n_workers) ] |
| 69 | + # use of Barrier ensures that every worker gets one |
| 70 | + b = mp.Barrier(n_workers) |
| 71 | + |
| 72 | + with mp.Pool(processes=n_workers) as pool: |
| 73 | + # map over every worker once to distribute RandomState instances |
| 74 | + pool.map(assign_worker_rs, rss, chunksize=1) |
| 75 | + # Perform computations on workers |
| 76 | + r = pool.map(worker_compute, range(batches), chunksize=1) |
| 77 | + |
| 78 | + # retrieve values of estimates into numpy array |
| 79 | + ps = np.fromiter(r, dtype=np.double) |
| 80 | + # compute sample estimator's mean and standard deviation |
| 81 | + p_est = ps.mean() |
| 82 | + pop_std = ps.std() |
| 83 | + t1 = timer() |
| 84 | + |
| 85 | + dig = 3 - int(np.log10(pop_std)) |
| 86 | + frm_str = "{0:0." + str(dig) + "f}" |
| 87 | + print(("Monte-Carlo estimate of probability: " + frm_str).format(p_est)) |
| 88 | + print(("Population estimate of the estimator's standard deviation: " + frm_str).format(pop_std)) |
| 89 | + print(("Expected standard deviation of the estimator: " + frm_str).format(np.sqrt(p_est * (1-p_est)/batch_size))) |
| 90 | + print("Execution time: {0:0.3f} seconds".format(t1-t0)) |
0 commit comments