@@ -193,6 +193,93 @@ BOOST_AUTO_TEST_CASE(make_groups) {
193193 }
194194}
195195
196+ // Regression test for the SUMMA broadcast-root computation
197+ // (Summa::get_row_group_root / get_col_group_root in
198+ // dist_eval/contraction_eval.h).
199+ //
200+ // When the right (resp. left) operand is block-sparse the SUMMA row (resp.
201+ // column) broadcast group is pruned to the ranks that own nonzero source
202+ // tiles, so its size drops below proc_cols (resp. proc_rows). In that case the
203+ // broadcast root's group-local rank no longer equals its process-column
204+ // (-row) index and must be looked up from its *world* rank. make_group always
205+ // inserts the geometric root into the group via ProcGrid::map_col / map_row,
206+ // whose returned world rank includes the grid's rank_offset_ -- the world rank
207+ // of the grid's first process. rank_offset_ is nonzero for the h-grouped 3-d
208+ // batched SUMMA grid (every slab group beyond group 0). The old root code
209+ // reconstructed the world rank by hand as rank_row*proc_cols + group_root
210+ // (resp. group_root*proc_cols + rank_col), *omitting rank_offset_*, so for an
211+ // offset grid Group::rank() failed to find the root and returned -1, tripping
212+ // MADNESS_ASSERT(group_root >= 0) in WorldGopInterface::bcast and aborting the
213+ // process.
214+ //
215+ // The fix recomputes the root's world rank with ProcGrid::map_col / map_row,
216+ // which is exactly the value make_group inserts. This test validates that
217+ // primitive: on an offset grid map_col / map_row must include the offset, so
218+ // the broadcast root the fix now uses is the value present in the group, while
219+ // the old offset-less formula would have missed it (differing by exactly the
220+ // nonzero offset). A full end-to-end SUMMA repro requires >= 4 ranks (proc_h_
221+ // > 1 needs P >= 2*proc_rows*proc_cols and a pruned group needs proc_cols or
222+ // proc_rows >= 2), beyond this suite's np<=2 runs.
223+ BOOST_AUTO_TEST_CASE (summa_bcast_root_offset) {
224+ // Baseline (np-independent): on an unoffset grid the offset-aware maps agree
225+ // with the bare geometric formula the old root code used. The test ctor
226+ // permits an arbitrary (test_rank, test_nprocs) regardless of world size and
227+ // leaves rank_offset_ == 0.
228+ {
229+ TiledArray::detail::ProcGrid grid (*GlobalFixture::world, /* test_rank=*/ 5u ,
230+ /* test_nprocs=*/ 12u , /* rows=*/ 8 ,
231+ /* cols=*/ 8 ,
232+ /* row_size=*/ 64 , /* col_size=*/ 64 );
233+ BOOST_REQUIRE_GT (grid.proc_cols (), 0u );
234+ BOOST_REQUIRE_GT (grid.proc_rows (), 0u );
235+ for (std::size_t c = 0 ; c < grid.proc_cols (); ++c)
236+ BOOST_CHECK_EQUAL (
237+ grid.map_col (c),
238+ grid.rank_row () * ProcessID (grid.proc_cols ()) + ProcessID (c));
239+ for (std::size_t r = 0 ; r < grid.proc_rows (); ++r)
240+ BOOST_CHECK_EQUAL (
241+ grid.map_row (r),
242+ grid.rank_col () + ProcessID (r) * ProcessID (grid.proc_cols ()));
243+ }
244+
245+ // Offset grid: this is the regression. Construct a process grid whose first
246+ // process is at a nonzero world rank (as the h-grouped batched SUMMA does
247+ // for slab groups beyond group 0) and verify the maps the fix now uses
248+ // include the offset -- so the broadcast root is the in-group world rank,
249+ // not the offset-less one the old code computed (which would be -1 once the
250+ // group is pruned). Requires the world to be large enough to host an offset
251+ // sub-grid (rank_subset_t ctor asserts rank_offset + nprocs <= world.size()).
252+ const auto world_size = GlobalFixture::world->size ();
253+ if (world_size >= 2 ) {
254+ const std::size_t nprocs = world_size - (world_size / 2 ); // upper ~half
255+ const ProcessID offset = world_size - ProcessID (nprocs); // nonzero offset
256+ BOOST_REQUIRE_GT (offset, 0 );
257+ TiledArray::detail::ProcGrid grid (*GlobalFixture::world,
258+ TiledArray::detail::rank_subset, offset,
259+ nprocs, /* rows=*/ 8 ,
260+ /* cols=*/ 8 , /* row_size=*/ 64 ,
261+ /* col_size=*/ 64 );
262+ // Only ranks inside the sub-grid have a valid (rank_row, rank_col).
263+ if (GlobalFixture::world->rank () >= offset) {
264+ for (std::size_t c = 0 ; c < grid.proc_cols (); ++c) {
265+ const ProcessID offset_less =
266+ grid.rank_row () * ProcessID (grid.proc_cols ()) + ProcessID (c);
267+ // map_col == the value make_group inserts as the row-broadcast root
268+ BOOST_CHECK_EQUAL (grid.map_col (c), offset_less + offset);
269+ // ... which differs from the old offset-less root computation
270+ BOOST_CHECK_NE (grid.map_col (c), offset_less);
271+ }
272+ for (std::size_t r = 0 ; r < grid.proc_rows (); ++r) {
273+ const ProcessID offset_less =
274+ grid.rank_col () + ProcessID (r) * ProcessID (grid.proc_cols ());
275+ // map_row == the value make_group inserts as the col-broadcast root
276+ BOOST_CHECK_EQUAL (grid.map_row (r), offset_less + offset);
277+ BOOST_CHECK_NE (grid.map_row (r), offset_less);
278+ }
279+ }
280+ }
281+ }
282+
196283#if 0
197284// This test case us used to evaluate distribute statistics. This unit test
198285// should only be enabled when changes are made to the ProcGrid algorithm, and
0 commit comments