Examples#

Every example below is executed as part of building the docs (via sphinx-exec-code).

SerialPool#

 1"""Minimal working example of SerialPool."""
 2
 3import time
 4
 5from richpool import SerialPool
 6
 7SLEEP_TIME = 1
 8
 9
10def square_slowly(x):
11    time.sleep(SLEEP_TIME)
12    return x * x
13
14
15if __name__ == "__main__":
16    n = 20
17
18    start = time.perf_counter()
19    with SerialPool() as pool:
20        results = pool.map(square_slowly, range(n), desc="squaring")
21    elapsed = time.perf_counter() - start
22
23    print(results)
24    print(f"serial: {elapsed:.2f}s (no speedup, SerialPool runs one item at a time)")
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 20/20 0:00:20 0:00:00
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]
serial: 20.01s (no speedup, SerialPool runs one item at a time)

MultiPool#

 1"""Minimal working example of MultiPool."""
 2
 3import time
 4
 5from richpool import MultiPool
 6
 7SLEEP_TIME = 1
 8
 9
10def square_slowly(x):
11    time.sleep(SLEEP_TIME)
12    return x * x
13
14
15if __name__ == "__main__":
16    n = 20
17    serial_estimate = n * SLEEP_TIME
18
19    start = time.perf_counter()
20    with MultiPool(processes=4) as pool:
21        results = pool.map(square_slowly, range(n), desc="squaring")
22    elapsed = time.perf_counter() - start
23
24    print(results)
25    print(f"parallel: {elapsed:.2f}s (serial would take ~{serial_estimate:.2f}s)")
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 20/20 0:00:05 0:00:00
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]
parallel: 5.02s (serial would take ~20.00s)

MultiPool with emcee#

Adapted from schwimmbad’s Using MultiPool with emcee example:

emcee’s EnsembleSampler accepts any pool object exposing a .map() method, so richpool’s MultiPool can be passed directly to parallelize likelihood evaluations across worker processes. log_probability sleeps briefly to stand in for a realistic, non-trivial likelihood (e.g. one involving a simulation or numerical integration); without that, each evaluation is a handful of floating-point ops that finishes in microseconds.

emcee calls pool.map() several times per MCMC step internally, so letting MultiPool render its usual per-call progress bar produces a handful of tiny, near-instant, low-information bars, not real progress bars. Passing disable=True to MultiPool’s constructor turns off those per-call bars (the default for every map() call unless a call overrides it), and a single rich progress bar below tracks real progress across the n_steps MCMC steps instead, while the likelihood evaluations for each step still run in parallel underneath it.

Needs the extra emcee, numpy, and matplotlib dependencies:

uv pip install emcee numpy matplotlib
 1"""Using MultiPool with emcee for parallel MCMC sampling.
 2
 3Adapted from schwimmbad's "Using MultiPool with emcee" example:
 4https://schwimmbad.readthedocs.io/en/latest/examples/index.html#using-multipool-with-emcee
 5
 6emcee's EnsembleSampler accepts any pool object exposing a .map() method, so
 7richpool's MultiPool can be passed directly to parallelize likelihood
 8evaluations across worker processes. log_probability sleeps briefly to stand
 9in for a realistic, non-trivial likelihood (e.g. one involving a simulation
10or numerical integration); without that, each evaluation is a handful of
11floating-point ops that finishes in microseconds.
12
13emcee calls pool.map() several times per MCMC step internally (its default
14move splits the ensemble in half and updates each half in turn), so letting
15MultiPool render its usual per-call progress bar produces a handful of tiny,
16near-instant, low-information bars, not real progress bars. Passing disable=True to
17MultiPool's constructor turns off those per-call bars (the default for every
18map() call unless a call overrides it), and a single rich progress bar below
19tracks real progress across the n_steps MCMC steps instead, while the
20likelihood evaluations for each step still run in parallel underneath it.
21
22Needs the extra emcee, numpy, and matplotlib dependencies:
23pip install emcee numpy matplotlib.
24"""
25
26import time
27
28import matplotlib.pyplot as plt  # ty: ignore[unresolved-import]
29import numpy as np  # ty: ignore[unresolved-import]
30from emcee import EnsembleSampler  # ty: ignore[unresolved-import]
31from rich.progress import (
32    BarColumn,
33    MofNCompleteColumn,
34    Progress,
35    SpinnerColumn,
36    TextColumn,
37    TimeElapsedColumn,
38    TimeRemainingColumn,
39)
40
41from richpool import MultiPool
42
43LIKELIHOOD_SLEEP = 0.01
44
45
46def log_probability(p, mean, var):
47    time.sleep(LIKELIHOOD_SLEEP)
48    x = p[0]
49    return -0.5 * (x - mean) ** 2 / var
50
51
52def main():
53    n_walkers = 20
54    n_dim = 1
55    n_steps = 50
56    processes = 4
57    p0 = np.random.uniform(0, 10, (n_walkers, n_dim))
58    serial_estimate = n_steps * n_walkers * LIKELIHOOD_SLEEP
59
60    start = time.perf_counter()
61    with MultiPool(processes, disable=True) as pool:
62        sampler = EnsembleSampler(n_walkers, n_dim, log_probability, args=(5, 1.2), pool=pool)
63
64        columns = (
65            SpinnerColumn(),
66            TextColumn("[progress.description]{task.description}"),
67            BarColumn(),
68            TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
69            MofNCompleteColumn(),
70            TimeElapsedColumn(),
71            TimeRemainingColumn(),
72        )
73        with Progress(*columns) as progress:
74            task_id = progress.add_task("sampling", total=n_steps)
75            for _ in sampler.sample(p0, iterations=n_steps):
76                progress.advance(task_id)
77    elapsed = time.perf_counter() - start
78
79    print(f"mean of flatchain: {sampler.flatchain.mean():.3f}")
80    print(f"parallel: {elapsed:.2f}s (serial would take ~{serial_estimate:.2f}s)")
81
82    plt.figure()
83    plt.hist(sampler.flatchain, bins=30)
84    plt.xlabel("x")
85    plt.ylabel("count")
86    plt.title("emcee samples drawn via richpool's MultiPool")
87
88
89if __name__ == "__main__":
90    main()
  sampling ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 50/50 0:00:03 0:00:00
mean of flatchain: 5.031
parallel: 3.38s (serial would take ~10.00s)
_images/emcee_multipool_example.png

JoblibPool#

 1"""Minimal working example of JoblibPool."""
 2
 3import time
 4
 5from richpool import JoblibPool
 6
 7SLEEP_TIME = 1
 8
 9
10def square_slowly(x):
11    time.sleep(SLEEP_TIME)
12    return x * x
13
14
15if __name__ == "__main__":
16    n = 20
17    serial_estimate = n * SLEEP_TIME
18
19    start = time.perf_counter()
20    with JoblibPool(processes=4) as pool:
21        results = pool.map(square_slowly, range(n), desc="squaring")
22    elapsed = time.perf_counter() - start
23
24    print(results)
25    print(f"parallel: {elapsed:.2f}s (serial would take ~{serial_estimate:.2f}s)")
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 20/20 0:00:05 0:00:00
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]
parallel: 5.43s (serial would take ~20.00s)

MPIPool#

MPIPool needs to be launched with mpiexec, so it can’t run inline like the examples above. Run it with:

mpiexec -n 4 python examples/mpi_pool_example.py
 1"""Minimal working example of MPIPool.
 2
 3Run with: mpiexec -n 4 python mpi_example.py
 4Only the master rank (rank 0) prints output and shows the progress bar.
 5"""
 6
 7import time
 8
 9from richpool import MPIPool
10
11SLEEP_TIME = 1
12
13
14def square_slowly(x):
15    time.sleep(SLEEP_TIME)
16    return x * x
17
18
19if __name__ == "__main__":
20    n = 20
21    serial_estimate = n * SLEEP_TIME
22
23    with MPIPool() as pool:
24        if pool.is_master():
25            start = time.perf_counter()
26            results = pool.map(square_slowly, range(n), desc="squaring")
27            elapsed = time.perf_counter() - start
28
29            print(results)
30            print(f"parallel: {elapsed:.2f}s (serial would take ~{serial_estimate:.2f}s)")

p_map#

 1"""Minimal working example of richpool's p_map: a parallel ordered map."""
 2
 3import time
 4
 5from richpool import p_map
 6
 7SLEEP_TIME = 1
 8
 9
10def square_slowly(x):
11    time.sleep(SLEEP_TIME)
12    return x * x
13
14
15if __name__ == "__main__":
16    n = 20
17    serial_estimate = n * SLEEP_TIME
18
19    start = time.perf_counter()
20    results = p_map(square_slowly, range(n), desc="squaring")
21    elapsed = time.perf_counter() - start
22
23    print(results)
24    print(f"parallel: {elapsed:.2f}s (serial would take ~{serial_estimate:.2f}s)")
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 20/20 0:00:10 0:00:00
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]
parallel: 10.02s (serial would take ~20.00s)

p_imap#

 1"""Minimal working example of richpool's p_imap: a parallel ordered iterator.
 2
 3Unlike p_map, p_imap doesn't wait for the whole batch, it yields each result
 4as soon as it's ready, in the same order as the input.
 5"""
 6
 7import time
 8
 9from richpool import p_imap
10
11SLEEP_TIME = 1
12
13
14def square_slowly(x):
15    time.sleep(SLEEP_TIME)
16    return x * x
17
18
19if __name__ == "__main__":
20    n = 10
21    serial_estimate = n * SLEEP_TIME
22
23    start = time.perf_counter()
24    for result in p_imap(square_slowly, range(n), desc="squaring"):
25        print("got:", result)
26    elapsed = time.perf_counter() - start
27
28    print(f"parallel: {elapsed:.2f}s (serial would take ~{serial_estimate:.2f}s)")
got: 0
got: 1
got: 4
got: 9
got: 16
got: 25
got: 36
got: 49
got: 64
got: 81
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 10/10 0:00:05 0:00:00
parallel: 5.02s (serial would take ~10.00s)

p_umap#

 1"""Minimal working example of richpool's p_umap: a parallel unordered map.
 2
 3Unlike p_map, results come back in completion order, not input order. To make
 4that visible, earlier items are made to sleep longer than later ones, so later
 5items finish first.
 6"""
 7
 8import time
 9
10from richpool import p_umap
11
12
13def square_reverse_delay(x):
14    time.sleep((9 - x) * 0.3)
15    return x * x
16
17
18if __name__ == "__main__":
19    results = p_umap(square_reverse_delay, range(10), desc="squaring")
20    print("input order: ", list(range(10)))
21    print("result order:", results)
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 10/10 0:00:06 0:00:00
input order:  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
result order: [1, 0, 9, 4, 25, 16, 49, 36, 81, 64]

p_uimap#

 1"""Minimal working example of richpool's p_uimap: a parallel unordered iterator.
 2
 3Combines p_imap's lazy, as-ready yielding with p_umap's unordered delivery.
 4Earlier items sleep longer than later ones, so results arrive out of order.
 5"""
 6
 7import time
 8
 9from richpool import p_uimap
10
11
12def square_reverse_delay(x):
13    time.sleep((9 - x) * 0.3)
14    return x * x
15
16
17if __name__ == "__main__":
18    for result in p_uimap(square_reverse_delay, range(10), desc="squaring"):
19        print("got:", result)
got: 1
got: 0
got: 9
got: 4
got: 25
got: 16
got: 49
got: 36
got: 81
got: 64
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 10/10 0:00:06 0:00:00

t_map#

 1"""Minimal working example of richpool's t_map: a sequential map with a progress bar.
 2
 3No parallelism, t_map just wraps the builtin map() with a rich progress bar.
 4"""
 5
 6import time
 7
 8from richpool import t_map
 9
10SLEEP_TIME = 1
11
12
13def square_slowly(x):
14    time.sleep(SLEEP_TIME)
15    return x * x
16
17
18if __name__ == "__main__":
19    n = 10
20
21    start = time.perf_counter()
22    results = t_map(square_slowly, range(n), desc="squaring")
23    elapsed = time.perf_counter() - start
24
25    print(results)
26    print(f"sequential: {elapsed:.2f}s (no speedup, t_map runs one item at a time)")
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 10/10 0:00:10 0:00:00
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
sequential: 10.01s (no speedup, t_map runs one item at a time)

t_imap#

 1"""Minimal working example of richpool's t_imap: a sequential iterator.
 2
 3Same as t_map, but yields each result immediately instead of waiting to
 4return a full list.
 5"""
 6
 7import time
 8
 9from richpool import t_imap
10
11SLEEP_TIME = 1
12
13
14def square_slowly(x):
15    time.sleep(SLEEP_TIME)
16    return x * x
17
18
19if __name__ == "__main__":
20    for result in t_imap(square_slowly, range(5), desc="squaring"):
21        print("got:", result)
got: 0
got: 1
got: 4
got: 9
got: 16
  squaring ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 5/5 0:00:05 0:00:00