Coverage for dragonfly_sim/utils/mpi_utils.py: 75%
20 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 03:37 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 03:37 +0000
1import numpy as np
4def allgather_numpy(comm, local_arr, global_size):
5 """Allgatherv a rank-local float64 array into a global array on every rank.
7 Each rank contributes a (possibly different-length) slice of the global
8 array. The slices are ordered by rank: rank 0 occupies [0, len_0),
9 rank 1 occupies [len_0, len_0+len_1), etc. This ordering matches the
10 contiguous DOF blocks produced by ``assign_global_dof_offset``.
12 Parameters
13 ----------
14 comm : mpi4py.MPI.Comm
15 local_arr : array-like, float64, 1-D
16 The local contribution on this rank. Cast to C-contiguous float64
17 before sending.
18 global_size : int
19 Total number of float64 elements summed across all ranks. Must be
20 the same value on every rank.
22 Returns
23 -------
24 np.ndarray, float64, shape (global_size,)
25 The gathered array, identical on every rank.
26 """
27 from mpi4py import MPI
28 send = np.ascontiguousarray(local_arr, dtype=np.float64)
30 # Serial fast-path: skip MPI overhead entirely
31 if comm.Get_size() == 1:
32 recv = np.empty(global_size, dtype=np.float64)
33 recv[:len(send)] = send
34 return recv
36 recv = np.zeros(global_size, dtype=np.float64)
37 sendcounts = np.array(comm.allgather(len(send)), dtype=np.int32)
38 displs = np.concatenate([[0], np.cumsum(sendcounts[:-1])]).astype(np.int32)
39 comm.Allgatherv(
40 sendbuf=send,
41 recvbuf=[recv, sendcounts, displs, MPI.DOUBLE],
42 )
43 return recv
46def assign_global_dof_offset(comm, local_count):
47 """Compute the global DOF offset for this rank via exclusive prefix-sum.
49 Assigns a contiguous block of global indices to each rank:
50 rank 0 → [0, local_count_0)
51 rank 1 → [local_count_0, local_count_0 + local_count_1)
52 ...
54 Implementation uses MPI ``Exscan`` (exclusive scan with SUM) to
55 compute the offset for each rank in a single collective, then
56 ``Allreduce`` on a scalar to obtain the global total. This avoids
57 allocating an N-rank array and is the canonical MPI pattern for
58 assigning contiguous global index ranges to processes.
60 Parameters
61 ----------
62 comm : mpi4py.MPI.Comm
63 local_count : int
64 Number of local DOFs on this rank.
66 Returns
67 -------
68 offset : int
69 Index of the first global DOF owned by this rank.
70 global_total : int
71 Total DOF count across all ranks.
72 """
73 from mpi4py import MPI
75 # Exclusive scan: offset[r] = sum of local_count for ranks 0 .. r-1
76 # local_count_arr = np.array(local_count, dtype=np.int64)
77 # offset_arr = np.zeros(1, dtype=np.int64)
78 # offset_init = 0
79 # Exscan is undefined on rank 0 — result stays 0, which is correct.assign_global_dof_offset
80 offset = comm.exscan(local_count, op=MPI.SUM)
81 if offset is None:
82 # correct the offset on rank 0
83 offset = 0
84 # offset = offset_init
86 # Global total: reduce the local count to a single sum on all ranks
87 # global_total_arr = np.array(local_count, dtype=np.int64)
88 global_total = comm.allreduce(local_count, op=MPI.SUM)
89 # global_total = int(global_total_arr[0])
91 return offset, global_total