@@ -542,6 +542,191 @@ TEST(ParallelGlobalCreatePartition, FourRanksKpar2Bndpar2DiagNp2)
542542#endif
543543}
544544
545+ // ================================================================
546+ // Boundary tests: pure arithmetic, no MPI needed.
547+ // ================================================================
548+
549+ // Non-uniform kpar: 10 procs / kpar=3 -> pools {4, 3, 3}.
550+ // pool_root_rank must return the world rank of rank 0 in each pool.
551+ TEST (ParallelPartition, NonUniformKparPoolRootRank)
552+ {
553+ const std::vector<int > pool_sizes = {4 , 3 , 3 };
554+ const ParallelPartition t (/* world=*/ 10 ,
555+ /* my_rank=*/ 5 ,
556+ /* kpar=*/ 3 ,
557+ /* my_pool=*/ 1 ,
558+ /* rank_in_pool=*/ 2 ,
559+ pool_sizes,
560+ /* bndpar=*/ 1 ,
561+ /* my_band_group=*/ 0 ,
562+ /* rank_in_band_group=*/ 5 ,
563+ /* nproc_in_band_group=*/ 10
564+ #ifdef __MPI
565+ ,
566+ MPI_COMM_SELF , MPI_COMM_NULL , MPI_COMM_SELF ,
567+ MPI_COMM_NULL , MPI_COMM_NULL , MPI_COMM_NULL ,
568+ MPI_COMM_NULL , MPI_COMM_NULL
569+ #endif
570+ );
571+
572+ EXPECT_EQ (t.kpar (), 3 );
573+ EXPECT_EQ (t.nproc_in_pool (), pool_sizes);
574+ EXPECT_EQ (t.nproc_in_pool (0 ), 4 );
575+ EXPECT_EQ (t.nproc_in_pool (1 ), 3 );
576+ EXPECT_EQ (t.nproc_in_pool (2 ), 3 );
577+
578+ // Pool roots: pool 0 starts at wr 0; pool 1 at wr 4; pool 2 at wr 7.
579+ EXPECT_EQ (t.pool_root_rank (0 ), 0 );
580+ EXPECT_EQ (t.pool_root_rank (1 ), 4 );
581+ EXPECT_EQ (t.pool_root_rank (2 ), 7 );
582+ }
583+
584+ // bndpar>1 + kpar>1 cross: 12 procs / kpar=3 / bndpar=2.
585+ // Each pool {4,4,4}, each pool split into 2 band-groups of 2.
586+ TEST (ParallelPartition, BndparKparCrossBandGroupRootRank)
587+ {
588+ const std::vector<int > pool_sizes = {4 , 4 , 4 };
589+ const ParallelPartition t (/* world=*/ 12 ,
590+ /* my_rank=*/ 5 ,
591+ /* kpar=*/ 3 ,
592+ /* my_pool=*/ 1 ,
593+ /* rank_in_pool=*/ 1 ,
594+ pool_sizes,
595+ /* bndpar=*/ 2 ,
596+ /* my_band_group=*/ 1 ,
597+ /* rank_in_band_group=*/ 5 ,
598+ /* nproc_in_band_group=*/ 6
599+ #ifdef __MPI
600+ ,
601+ MPI_COMM_SELF , MPI_COMM_NULL , MPI_COMM_SELF ,
602+ MPI_COMM_NULL , MPI_COMM_NULL , MPI_COMM_NULL ,
603+ MPI_COMM_NULL , MPI_COMM_NULL
604+ #endif
605+ );
606+
607+ // nproc_in_band_group = kpar * (nproc_in_pool[0] / bndpar) = 3 * 2 = 6
608+ EXPECT_EQ (t.nproc_in_band_group (), 6 );
609+
610+ // band_group_root_rank uses pool 0's slice:
611+ // bg0 root = 0 * (4/2) = 0
612+ // bg1 root = 1 * (4/2) = 2
613+ // NOT band_group * nproc_in_band_group (would give 6, wrong).
614+ EXPECT_EQ (t.band_group_root_rank (0 ), 0 );
615+ EXPECT_EQ (t.band_group_root_rank (1 ), 2 );
616+ }
617+
618+ // Out-of-range indices must return -1, not crash.
619+ TEST (ParallelPartition, OutOfRangeReturnsMinusOne)
620+ {
621+ const std::vector<int > pool_sizes = {4 , 4 , 4 };
622+ const ParallelPartition t (/* world=*/ 12 ,
623+ /* my_rank=*/ 0 ,
624+ /* kpar=*/ 3 ,
625+ /* my_pool=*/ 0 ,
626+ /* rank_in_pool=*/ 0 ,
627+ pool_sizes,
628+ /* bndpar=*/ 2 ,
629+ /* my_band_group=*/ 0 ,
630+ /* rank_in_band_group=*/ 0 ,
631+ /* nproc_in_band_group=*/ 6
632+ #ifdef __MPI
633+ ,
634+ MPI_COMM_SELF , MPI_COMM_NULL , MPI_COMM_SELF ,
635+ MPI_COMM_NULL , MPI_COMM_NULL , MPI_COMM_NULL ,
636+ MPI_COMM_NULL , MPI_COMM_NULL
637+ #endif
638+ );
639+
640+ // pool_root_rank: valid 0..2, invalid otherwise
641+ EXPECT_EQ (t.pool_root_rank (-1 ), -1 );
642+ EXPECT_EQ (t.pool_root_rank (0 ), 0 );
643+ EXPECT_EQ (t.pool_root_rank (2 ), 8 );
644+ EXPECT_EQ (t.pool_root_rank (3 ), -1 );
645+ EXPECT_EQ (t.pool_root_rank (99 ), -1 );
646+
647+ // band_group_root_rank: valid 0..1, invalid otherwise
648+ EXPECT_EQ (t.band_group_root_rank (-1 ), -1 );
649+ EXPECT_EQ (t.band_group_root_rank (0 ), 0 );
650+ EXPECT_EQ (t.band_group_root_rank (1 ), 2 );
651+ EXPECT_EQ (t.band_group_root_rank (2 ), -1 );
652+ EXPECT_EQ (t.band_group_root_rank (99 ), -1 );
653+
654+ // nproc_in_pool(int pool): valid 0..2 returns size, invalid returns 0
655+ EXPECT_EQ (t.nproc_in_pool (0 ), 4 );
656+ EXPECT_EQ (t.nproc_in_pool (2 ), 4 );
657+ EXPECT_EQ (t.nproc_in_pool (3 ), 0 );
658+ EXPECT_EQ (t.nproc_in_pool (99 ), 0 );
659+ EXPECT_EQ (t.nproc_in_pool (-1 ), 0 );
660+ }
661+
662+ // Serial fallback: default-constructed partition is a safe trivial
663+ // single-process topology. This test runs even without MPI.
664+ TEST (ParallelPartition, SerialFallbackDefaultConstructor)
665+ {
666+ ParallelPartition t;
667+ EXPECT_EQ (t.world_size (), 1 );
668+ EXPECT_EQ (t.world_rank (), 0 );
669+ EXPECT_EQ (t.kpar (), 1 );
670+ EXPECT_EQ (t.my_pool (), 0 );
671+ EXPECT_EQ (t.rank_in_pool (), 0 );
672+ EXPECT_EQ (t.nproc_in_pool (), std::vector<int >{1 });
673+ EXPECT_EQ (t.nproc_in_pool (0 ), 1 );
674+ EXPECT_EQ (t.pool_root_rank (0 ), 0 );
675+ EXPECT_EQ (t.bndpar (), 1 );
676+ EXPECT_EQ (t.my_band_group (), 0 );
677+ EXPECT_EQ (t.rank_in_band_group (), 0 );
678+ EXPECT_EQ (t.nproc_in_band_group (), 1 );
679+ EXPECT_EQ (t.band_group_root_rank (0 ), 0 );
680+ EXPECT_EQ (t.band_group_root_rank (1 ), -1 ); // only bg 0 exists
681+ #ifdef __MPI
682+ EXPECT_EQ (t.pw_world_comm (), MPI_COMM_SELF );
683+ EXPECT_EQ (t.matrix_world_comm (), MPI_COMM_NULL );
684+ EXPECT_EQ (t.atom_world_comm (), MPI_COMM_NULL );
685+ #endif
686+ }
687+
688+ // Integration: create_partition with non-uniform kpar (7 procs / kpar=3
689+ // -> pools {3,2,2}). Verifies the factory's nproc_in_pool vector matches
690+ // the arithmetic from divide_mpi_groups(even=false).
691+ TEST (ParallelGlobalCreatePartition, NonUniformKparSevenProcsThreePools)
692+ {
693+ int world_size = 1 ;
694+ int world_rank = 0 ;
695+ #ifdef __MPI
696+ MPI_Comm_size (MPI_COMM_WORLD , &world_size);
697+ MPI_Comm_rank (MPI_COMM_WORLD , &world_rank);
698+ #endif
699+ if (world_size != 7 )
700+ {
701+ GTEST_SKIP () << " non-uniform kpar integration case requires exactly 7 MPI ranks (got "
702+ << world_size << " )." ;
703+ }
704+
705+ const int kpar = 3 ;
706+ const int bndpar = 1 ;
707+ const int diag_np = 1 ;
708+ const ParallelPartition t = Parallel_Global::create_partition (
709+ world_size, world_rank, kpar, bndpar, diag_np, diag_np);
710+
711+ // divide_mpi_groups(7, 3, even=false) -> base=2, extra=1
712+ // group 0: 3 procs, group 1: 2, group 2: 2
713+ const std::vector<int > expected_pools = {3 , 2 , 2 };
714+ EXPECT_EQ (t.kpar (), 3 );
715+ EXPECT_EQ (t.nproc_in_pool (), expected_pools);
716+ EXPECT_EQ (t.world_size (), 7 );
717+
718+ // Pool roots: 0, 3, 5
719+ EXPECT_EQ (t.pool_root_rank (0 ), 0 );
720+ EXPECT_EQ (t.pool_root_rank (1 ), 3 );
721+ EXPECT_EQ (t.pool_root_rank (2 ), 5 );
722+
723+ // Every rank must end up in exactly one pool whose size matches.
724+ const int my_pool = t.my_pool ();
725+ ASSERT_GE (my_pool, 0 );
726+ ASSERT_LT (my_pool, kpar);
727+ EXPECT_EQ (t.nproc_in_pool (my_pool), expected_pools[my_pool]);
728+ }
729+
545730int main (int argc, char ** argv)
546731{
547732#ifdef __MPI
0 commit comments