|
| 1 | +# MPI Communicator Injection Design |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +ABACUS hardcodes `MPI_COMM_WORLD` as the base communicator throughout its initialization |
| 6 | +and communication paths. This prevents workflows like parallel NEB, phonon calculations, |
| 7 | +or replica exchange MD from splitting MPI ranks into independent groups, each running its |
| 8 | +own ESolver instance in a separate subprocess. |
| 9 | + |
| 10 | +The pyabacus ESolver bindings have no mechanism to pass a custom MPI communicator from |
| 11 | +Python to C++. All sub-communicators (`POOL_WORLD`, `DIAG_WORLD`, `GRID_WORLD`, etc.) |
| 12 | +are derived from `MPI_COMM_WORLD`, and all global reduction/broadcast wrappers |
| 13 | +(`parallel_reduce`, `parallel_common`) hardcode `MPI_COMM_WORLD`. |
| 14 | + |
| 15 | +## Goal |
| 16 | + |
| 17 | +Enable general-purpose MPI communicator injection so that any workflow can pass a |
| 18 | +sub-communicator to the ESolver. One ESolver instance per process, but each process |
| 19 | +can use any communicator (not just `MPI_COMM_WORLD`). Default to `MPI_COMM_WORLD` |
| 20 | +when no communicator is provided, preserving full backward compatibility. |
| 21 | + |
| 22 | +## Architecture |
| 23 | + |
| 24 | +``` |
| 25 | +Python (mpi4py) C++ pybind11 bindings ABACUS core |
| 26 | +------------------- --------------------- ----------- |
| 27 | +comm = MPI.COMM_WORLD PyESolverLCAO::initialize( Parallel_Global::divide_pools( |
| 28 | +sub = comm.Split(color, key) input_dir, base_comm, ...) |
| 29 | + mpi_comm_handle) Parallel_Reduce::reduce_all( |
| 30 | +esolver.initialize( ..., base_comm) |
| 31 | + input_dir, Stores base_comm_ member Parallel_Common::bcast_*( |
| 32 | + comm_handle=sub.py2f()) Passes to Parallel_Global ..., base_comm) |
| 33 | +``` |
| 34 | + |
| 35 | +## Scope |
| 36 | + |
| 37 | +### In scope (this PR) |
| 38 | + |
| 39 | +1. Add `MPI_Comm base_comm` parameter to `Parallel_Global::divide_pools()`, |
| 40 | + `split_diag_world()`, `split_grid_world()` — defaulting to `MPI_COMM_WORLD` |
| 41 | +2. Add `MPI_Comm comm` parameter to all `Parallel_Reduce::reduce_all` and |
| 42 | + `Parallel_Common::bcast_*` functions — defaulting to `MPI_COMM_WORLD` |
| 43 | +3. Add `int mpi_comm_handle` parameter to `PyESolverLCAO::initialize()` and |
| 44 | + `PyESolverPW::initialize()` — defaulting to `-1` (meaning `MPI_COMM_WORLD`) |
| 45 | +4. Update pybind11 bindings to expose the new parameter |
| 46 | +5. Unit tests for communicator injection |
| 47 | + |
| 48 | +### Out of scope (follow-up PRs) |
| 49 | + |
| 50 | +- Parallel NEB orchestration in Python |
| 51 | +- Multi-instance-per-process support (would require encapsulating GlobalV) |
| 52 | +- Changing the ~280 scattered `MPI_COMM_WORLD` references in computation/IO modules |
| 53 | + (unnecessary for single-instance-per-process + subprocess model) |
| 54 | + |
| 55 | +## Detailed Changes |
| 56 | + |
| 57 | +### Layer 1: `source_base/parallel_global.h` and `.cpp` |
| 58 | + |
| 59 | +Add `MPI_Comm base_comm = MPI_COMM_WORLD` as the last parameter to: |
| 60 | + |
| 61 | +- `divide_pools()` — replace `MPI_COMM_WORLD` with `base_comm` in `MPICommGroup` |
| 62 | + constructor (line 337), `MPI_Comm_split` for `INT_BGROUP` (line 365), |
| 63 | + `MPI_Comm_dup` fallback (line 373), `MPI_Comm_split` fallback (line 374) |
| 64 | +- `split_diag_world()` — replace `MPI_COMM_WORLD` in `MPI_Comm_split` (line 60) |
| 65 | +- `split_grid_world()` — replace `MPI_COMM_WORLD` in `MPI_Comm_split` (line 80) |
| 66 | + |
| 67 | +~10 lines changed, ~3 signature changes. |
| 68 | + |
| 69 | +### Layer 2: `source_base/parallel_reduce.h` and `.cpp` |
| 70 | + |
| 71 | +Add `MPI_Comm comm = MPI_COMM_WORLD` to all `reduce_all` overloads (~13 functions), |
| 72 | +`reduce_double_allpool` (~2), `gather_int_all` (~1), `reduce_min` (~3), |
| 73 | +`reduce_max` (~2). Total: ~19 signature changes, ~19 substitutions. |
| 74 | + |
| 75 | +Functions using `POOL_WORLD`, `DIAG_WORLD`, `GRID_WORLD` are unchanged. |
| 76 | + |
| 77 | +### Layer 3: `source_base/parallel_common.h` and `.cpp` |
| 78 | + |
| 79 | +Add `MPI_Comm comm = MPI_COMM_WORLD` to all `bcast_*` functions (~10 functions). |
| 80 | +Total: ~10 signature changes, ~12 substitutions. |
| 81 | + |
| 82 | +### Layer 4: Pybind11 bindings |
| 83 | + |
| 84 | +**`py_esolver_lcao.hpp`**: Add `int mpi_comm_handle = -1` to `initialize()`. |
| 85 | + |
| 86 | +**`py_esolver_lcao_impl.cpp`**: Convert handle via `MPI_Comm_f2c()`, use resulting |
| 87 | +communicator for `MPI_Comm_size`, `MPI_Comm_rank`, and pass to `divide_pools()` etc. |
| 88 | + |
| 89 | +**`py_esolver_pw_impl.cpp`**: Same changes as LCAO. |
| 90 | + |
| 91 | +**`py_esolver_bindings.cpp`**: Update `.def("initialize", ...)` to include |
| 92 | +`"mpi_comm_handle"_a = -1`. |
| 93 | + |
| 94 | +~15 lines changed across 4 files. |
| 95 | + |
| 96 | +### Layer 5: Python interface |
| 97 | + |
| 98 | +**`esolver/workflow.py`**: Add optional `mpi_comm` parameter to `LCAOWorkflow` and |
| 99 | +`PWWorkflow` that forwards `comm.py2f()` to `esolver.initialize()`. |
| 100 | + |
| 101 | +**`ase/calculator.py`**: Add optional `mpi_comm` parameter to `AbacusCalculator`. |
| 102 | + |
| 103 | +### Scattered `MPI_COMM_WORLD` references (~280 in other modules) |
| 104 | + |
| 105 | +**No changes.** In the single-instance-per-process + subprocess model, each |
| 106 | +subprocess's `MPI_COMM_WORLD` is already the correct sub-communicator. These |
| 107 | +references are correct as-is. |
| 108 | + |
| 109 | +## Backward Compatibility |
| 110 | + |
| 111 | +1. **Standalone ABACUS binary**: `main.cpp` calls `read_pal_param()` which still |
| 112 | + uses `MPI_COMM_WORLD` directly. Unaffected. |
| 113 | +2. **Existing pyabacus scripts**: `initialize(input_dir)` with no second argument |
| 114 | + defaults to `MPI_COMM_WORLD`. Identical behavior. |
| 115 | +3. **Existing `parallel_reduce`/`parallel_common` callers**: Default parameter |
| 116 | + means all call sites compile and behave identically without modification. |
| 117 | + |
| 118 | +## Testing |
| 119 | + |
| 120 | +1. **Unit test: `parallel_global` with sub-communicator** — Split `MPI_COMM_WORLD`, |
| 121 | + call `divide_pools()` with sub-communicator, verify derived communicators are |
| 122 | + correct relative to the sub-communicator. |
| 123 | +2. **Unit test: `parallel_reduce` with custom communicator** — Verify reduction |
| 124 | + scoped to sub-communicator. |
| 125 | +3. **Integration test: pyabacus ESolver with sub-communicator** — `mpirun -np 4`, |
| 126 | + split into 2 groups of 2, each initializes ESolver independently. |
| 127 | +4. **Regression test**: Run existing tests without `mpi_comm_handle` argument, |
| 128 | + verify identical results. |
0 commit comments