Skip to content

Commit c15dc75

Browse files
author
abacus_fixer
committed
fix(parallel): correct band_group_root_rank formula and illegal test layout
The root of a band-group union is band_group * (nproc_in_pool[0] / bndpar), not band_group * nproc_in_band_group: the two formulas only coincide when kpar == 1. With asserts enabled (CI builds without CMAKE_BUILD_TYPE) the wrong assert aborted MODULE_BASE_ProcessTopology, while Release builds silently returned the wrong value. Also replace the synthetic 10-rank layout in ConstructAndAccessors, which violated the divide_pools constraint (BNDPAR>1 requires NPROC % (BNDPAR*KPAR) == 0), with a valid 12-rank KPAR=3/BNDPAR=2 layout, and fix the 4-rank create_topology expectation (band_group_root_rank(1) == 1 since bg1 = {1,3}).
1 parent 5539972 commit c15dc75

3 files changed

Lines changed: 52 additions & 53 deletions

File tree

source/source_base/parallel_topology.cpp

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,19 @@ int ProcessTopology::band_group_root_rank(int band_group) const
115115
// In ABACUS divide_pools the band-group layout over world ranks is
116116
// stripe-contiguous inside each k pool: within pool P the first
117117
// (nproc_in_pool[P]/bndpar) ranks belong to band-group 0, the next
118-
// slice to band-group 1, and so on. The first rank of the
119-
// concatenated "same band-group across all pools" set (i.e. the
120-
// root of bsame_kdiff_world for that band-group) is therefore the
121-
// first occurrence in pool 0, which falls at offset band_group *
122-
// (nproc_in_pool[0]/bndpar) from pool_root_rank(0). The invariant
123-
// bndpar_ * nproc_in_band_group_ == world_nproc_ + the even split
124-
// enforced by MPICommGroup::divide_group_comm make that offset
125-
// equal to (band_group * nproc_in_band_group_) directly because
126-
// each band-group contains exactly nproc_in_band_group_ processes
127-
// globally and they appear in ascending band-group id order in
128-
// world rank when scanned pool by pool.
118+
// slice to band-group 1, and so on. Pool 0 always starts at world
119+
// rank 0 (divide_mpi_groups assigns pools as contiguous blocks in
120+
// ascending world-rank order), so the first member of the
121+
// "same band-group across all pools" set -- i.e. the root of
122+
// bsame_kdiff_world for that band-group -- is the first occurrence
123+
// in pool 0, which falls at offset band_group * (nproc_in_pool[0]
124+
// / bndpar) from world rank 0.
125+
//
126+
// NOTE: this is generally NOT equal to band_group *
127+
// nproc_in_band_group_. The global band-group size is
128+
// kpar * (nproc_in_pool[0] / bndpar), so the two formulas coincide
129+
// only when kpar == 1 (or band_group == 0).
129130
const int per_bg_in_pool0 = nproc_in_pool_[0] / bndpar_;
130131
assert(per_bg_in_pool0 * bndpar_ == nproc_in_pool_[0]);
131-
const int via_pool0 = band_group * per_bg_in_pool0;
132-
const int via_global = band_group * nproc_in_band_group_;
133-
assert(via_pool0 == via_global);
134-
return via_global;
132+
return band_group * per_bg_in_pool0;
135133
}

source/source_base/parallel_topology.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,12 @@ class ProcessTopology
110110
int my_band_group() const { return my_band_group_; }
111111
int rank_in_band_group() const { return rank_in_band_group_; }
112112
int nproc_in_band_group() const { return nproc_in_band_group_; }
113-
/// World rank of the root (rank 0) inside a band-group union.
114-
/// Uses the same prefix-sum logic as pool_root_rank but over the
115-
/// band-group partition of MPI_COMM_WORLD. -1 on invalid index.
113+
/// World rank of the first member (root) of a band-group union.
114+
/// Band-group slices are stripe-contiguous inside each k pool, and
115+
/// pool 0 starts at world rank 0, so the root is
116+
/// band_group * (nproc_in_pool[0] / bndpar). This equals
117+
/// band_group * nproc_in_band_group() only when kpar == 1.
118+
/// -1 on invalid index.
116119
int band_group_root_rank(int band_group) const;
117120

118121
#ifdef __MPI

source/source_base/test_parallel/parallel_topology_test.cpp

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -215,34 +215,31 @@ TEST(ProcessTopology, DefaultConstructorIsSingleProcess)
215215

216216
TEST(ProcessTopology, ConstructAndAccessors)
217217
{
218-
// Procs 10 world ranks 0..9.
219-
// KPAR=3 -> pool sizes {4,3,3} i.e. pools cover ranks:
220-
// pool0: 0..3, pool1:4..6, pool2:7..9
221-
// BNDPAR=2 splits each pool into 2 stripe-contiguous band groups.
222-
// Each band group is required globally to contain NPROC_IN_BAND_GROUP=5
223-
// (bndpar * nproc_in_band_group = 10 = world), so per-pool slice
224-
// sizes into band groups are:
225-
// pool0 (4) -> 2 each into bg0/bg1 -> bg0: 0,1 bg1: 2,3
226-
// pool1 (3) -> 1 (bg1 gets +1 due pool1 divide even=false via kpar)
227-
// Actually ABACUS bndpar_group.divide_group_comm(NG, even=true);
228-
// but the constructor only asserts the size invariants
229-
// provided by callers; we keep this synthetic vector
230-
// consistent with a valid layout and pick ranks such
231-
// that the band_group_root_rank helper produces 0/5:
232-
// bg0 global set -> {0,1 of pool0; first of pool1; first 2 of pool2} = 5 ranks -> root rank 0
233-
// bg1 global set -> {2,3 of pool0; last 2 of pool1; last 1 of pool2} = 5 ranks -> root rank 5
234-
const std::vector<int> pool_sizes = {4, 3, 3};
218+
// Real ABACUS layout with 12 world ranks 0..11, KPAR=3, BNDPAR=2.
219+
// divide_pools constraint (BNDPAR>1): NPROC % (BNDPAR*KPAR) == 0,
220+
// i.e. 12 % 6 == 0, so pools are even: {4,4,4} covering ranks
221+
// pool0: 0..3, pool1: 4..7, pool2: 8..11
222+
// Each pool is split by BNDPAR=2 (even) into 2 slices of
223+
// pool_size/bndpar = 2 ranks:
224+
// bg0: {0,1} {4,5} {8,9} -> 6 ranks, root = world rank 0
225+
// bg1: {2,3} {6,7} {10,11} -> 6 ranks, root = world rank 2
226+
// Global band-group size nproc_in_band_group = kpar * 2 = 6 and
227+
// bndpar * nproc_in_band_group = 12 = world.
228+
// my_rank = 6 sits in pool1 (start 4) at rank_in_pool 2, which
229+
// belongs to bg1 (pool1 slice [2,4)) at global band-group rank
230+
// RANK_IN_BPGROUP = my_pool * 2 + 0 = 2.
231+
const std::vector<int> pool_sizes = {4, 4, 4};
235232
#ifdef __MPI
236-
const ProcessTopology t(/*world=*/10,
233+
const ProcessTopology t(/*world=*/12,
237234
/*my_rank=*/6,
238235
/*kpar=*/3,
239-
/*my_pool=*/2,
240-
/*rank_in_pool=*/2, // world rank 7+2 = 9
236+
/*my_pool=*/1,
237+
/*rank_in_pool=*/2,
241238
pool_sizes,
242239
/*bndpar=*/2,
243240
/*my_band_group=*/1,
244-
/*rank_in_band_group=*/1,
245-
/*nproc_in_band_group=*/5,
241+
/*rank_in_band_group=*/2,
242+
/*nproc_in_band_group=*/6,
246243
/*pw_world=*/MPI_COMM_SELF,
247244
/*kmesh_world=*/MPI_COMM_NULL,
248245
/*bsame_kdiff=*/MPI_COMM_SELF,
@@ -252,35 +249,35 @@ TEST(ProcessTopology, ConstructAndAccessors)
252249
/*matrix_world=*/MPI_COMM_SELF,
253250
/*atom_world=*/MPI_COMM_WORLD);
254251
#else
255-
const ProcessTopology t(10, 6, 3, 2, 2, pool_sizes, 2, 1, 1, 5);
252+
const ProcessTopology t(12, 6, 3, 1, 2, pool_sizes, 2, 1, 2, 6);
256253
#endif
257254

258-
EXPECT_EQ(t.world_size(), 10);
255+
EXPECT_EQ(t.world_size(), 12);
259256
EXPECT_EQ(t.world_rank(), 6);
260257

261258
EXPECT_EQ(t.kpar(), 3);
262-
EXPECT_EQ(t.my_pool(), 2);
259+
EXPECT_EQ(t.my_pool(), 1);
263260
EXPECT_EQ(t.rank_in_pool(), 2);
264261
EXPECT_EQ(t.nproc_in_pool(0), 4);
265-
EXPECT_EQ(t.nproc_in_pool(1), 3);
266-
EXPECT_EQ(t.nproc_in_pool(2), 3);
262+
EXPECT_EQ(t.nproc_in_pool(1), 4);
263+
EXPECT_EQ(t.nproc_in_pool(2), 4);
267264

268265
EXPECT_EQ(t.bndpar(), 2);
269266
EXPECT_EQ(t.my_band_group(), 1);
270-
EXPECT_EQ(t.rank_in_band_group(), 1);
271-
EXPECT_EQ(t.nproc_in_band_group(), 5);
267+
EXPECT_EQ(t.rank_in_band_group(), 2);
268+
EXPECT_EQ(t.nproc_in_band_group(), 6);
272269

273270
// pool_root_rank prefix offsets -> pool start world rank.
274271
EXPECT_EQ(t.pool_root_rank(0), 0);
275272
EXPECT_EQ(t.pool_root_rank(1), 4);
276-
EXPECT_EQ(t.pool_root_rank(2), 7);
273+
EXPECT_EQ(t.pool_root_rank(2), 8);
277274
EXPECT_EQ(t.pool_root_rank(3), -1);
278275

279-
// band_group_root_rank over the 2 band groups:
280-
// bg0 root = 0 * 5 = 0 (first rank of bg0 set across all pools)
281-
// bg1 root = 1 * 5 = 5
276+
// band_group_root_rank: first member of each band-group union.
277+
// bg0 root = 0 * (4/2) = 0
278+
// bg1 root = 1 * (4/2) = 2 (world rank 2, first member of bg1)
282279
EXPECT_EQ(t.band_group_root_rank(0), 0);
283-
EXPECT_EQ(t.band_group_root_rank(1), 5);
280+
EXPECT_EQ(t.band_group_root_rank(1), 2);
284281
EXPECT_EQ(t.band_group_root_rank(2), -1);
285282

286283
#ifdef __MPI
@@ -348,8 +345,9 @@ TEST(ParallelGlobalCreateTopology, FourRanksKpar2Bndpar2DiagNp2)
348345
// BNDPAR=2 (even=true) in each pool of 2 procs -> each bg gets 1 proc
349346
// Global union bg size = kpar*1 = 2 = nproc_in_band_group
350347
EXPECT_EQ(t.nproc_in_band_group(), 2);
348+
// bg0 = {0, 2} -> root 0; bg1 = {1, 3} -> root = 1 * (2/2) = 1
351349
EXPECT_EQ(t.band_group_root_rank(0), 0);
352-
EXPECT_EQ(t.band_group_root_rank(1), 2); // band_group * nproc_in_band_group = 1 * 2
350+
EXPECT_EQ(t.band_group_root_rank(1), 1);
353351

354352
// ---- per-rank local values -------------------------------------
355353
// World ranks 0..3, KPAR=2 (even=false) -> pools {0: {0,1}, 1: {2,3}}.

0 commit comments

Comments
 (0)