Parallelism¶
MDAPY runs its compiled kernels on a thread pool of its own. This page shows how to choose the number of threads, how to follow and stop long computations, and how MDAPY behaves next to PyTorch and LAMMPS.
import mdapy as mp
mp.get_num_threads() # default: the logical CPUs this process may use
mp.set_num_threads(8) # for the rest of the process
with mp.num_threads(2): # only inside this block
...
Choosing the number of threads¶
| Name | Type | Default | Meaning |
|---|---|---|---|
set_num_threads(n) |
function | Use n threads from now on. |
|
num_threads(n) |
context manager | Use n threads inside a with block, then restore the previous value. |
|
get_num_threads() |
function | The current number of threads. | |
MDAPY_NUM_THREADS |
environment variable | logical CPUs this process may use | Initial number of threads, read once when MDAPY is imported. |
n must be a positive integer; anything else raises
InvalidParameter. An
invalid MDAPY_NUM_THREADS makes import mdapy fail with the same error,
rather than silently falling back to a default.
The setting is global to the process, not to the calling Python thread.
Progress and cancellation¶
Computations that can take long, such as
NeighborList.build, accept two keyword-only arguments.
| Name | Type | Default | Meaning |
|---|---|---|---|
progress |
callable or None |
None |
Called as progress(done, total) with the number of finished and total work items. |
cancel |
CancelToken or None |
None |
Stops the computation once token.cancel() is called. |
progress is called on the thread that started the computation, which
also does a share of the work, between its pieces of work and at most every
50 ms, and once more with done == total when it finishes.
done never decreases. If progress raises an exception, the computation
stops and that exception propagates to the caller.
A cancelled computation raises
Cancelled. The token can be
cancelled from any thread, including from inside progress, and can be
reused after token.reset().
token = mp.CancelToken()
# in a GUI: connect a "Stop" button to token.cancel
try:
result = some_analysis.compute(system, progress=report, cancel=token)
except mp.errors.Cancelled:
...
Pressing Ctrl-C stops a running kernel and raises KeyboardInterrupt as
usual.
Reproducible results¶
Results do not depend on the number of threads: the same computation with 1 or 64 threads gives bit-for-bit identical numbers.
Working with PyTorch and LAMMPS¶
MDAPY does not use OpenMP and loads no OpenMP runtime. Two copies of an
OpenMP runtime in one process (for example mdapy's and PyTorch's) is a common
cause of crashes such as OMP: Error #15 on macOS; this cannot happen with
MDAPY, so no workaround such as KMP_DUPLICATE_LIB_OK is needed.
Other libraries keep their own thread settings:
- PyTorch:
torch.set_num_threads. - NumPy (BLAS): the
OMP_NUM_THREADSorOPENBLAS_NUM_THREADSenvironment variables. - polars: the
POLARS_MAX_THREADSenvironment variable, which must be set before polars is first imported.
When several libraries compute at the same time, keep the total number of threads close to the number of CPU cores. After each piece of parallel work, mdapy's idle threads keep checking for more for 0.2 ms before they sleep, so that the steps of one computation follow each other without waking them.
Calling MDAPY from several threads¶
Compiled kernels release the Python global interpreter lock (GIL), so other Python threads, for example a GUI event loop, keep running while MDAPY computes.
The pool serves one computation at a time. If a second Python thread starts a computation while the pool is busy, that computation runs on its own thread instead of waiting. It gives the same result, only without parallel speed-up.
Many processes¶
Every process starts as many threads as there are CPUs, so when many processes compute at once, give each of them one thread: ten processes without a limit were six to seven times slower than ten with one thread each. See Screening alloys in many processes.
Start processes with spawn, as multiprocessing.get_context("spawn") gives it,
never with fork. MDAPY's own pool works with fork, a forked child creating
its own on first use, but this is a limit of polars: once a parent has used its
threads, as the functions of MDAPY do, a forked child that needs them hangs without
a message. Linux uses fork by default before Python 3.14, so ask for spawn
there. MDAPY gives a RuntimeWarning in every process that was forked.