Skip to content

Commit 75145da

Browse files
authored
Fix: Correct PW KPAR/BNDPAR occupations and distributed output (deepmodeling#7845)
* Fix: Correct PW KPAR/BNDPAR occupations and distributed output * Resolve CI/CD failure * Add integration tests for BPCG and SDFT with bndpar and kpar * Rename 01_PW/087 and 01_PW/089 * Avoid competing write of cube files under bndpar * Rename test names in CASES_*.txt * Fix get_pchg_pw and get_wf_pw under bndpar * Resolver CI failure on 01_PW/211_PW_BPCG_KB_OCP_CHG * Fix elecstate_tools.cpp according to Copilot auto review * Fix Agent Governance check * Reduce the ref file 01_PW/087_PW_get_pchg_kpar_bndpar/pchgi4s1.cube.ref
1 parent 3af0946 commit 75145da

60 files changed

Lines changed: 2581 additions & 336 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/advanced/input_files/input-main.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -673,13 +673,22 @@
673673
### kpar
674674

675675
- **Type**: Integer
676-
- **Description**: Divide all processors into kpar groups, and k points will be distributed among each group. The value taken should be less than or equal to the number of k points as well as the number of MPI processes.
676+
- **Description**: Controls k-point parallelism. The value must be positive and should not exceed either the number of k-points or the number of MPI processes.
677+
- For PW calculations, divide all MPI processes into persistent k-point pools. Each pool stores and processes a subset of the k-points.
678+
- For LCAO calculations with lapack, genelpa, elpa, or scalapack_gvx, divide the diagonalization work into temporary k-point pools. After diagonalization, the eigenvalues and distributed wavefunctions are restored for all k-points before occupations, density matrices, and output are evaluated.
679+
- Multi-process LCAO cusolver uses its own active-GPU distribution and does not use this value to define its k-point layout. Other LCAO eigensolvers do not use the temporary k-point-pool implementation.
677680
- **Default**: 1
678681

679682
### bndpar
680683

681684
- **Type**: Integer
682-
- **Description**: Divide all processors into bndpar groups for SDFT or the BPCG solver. bndpar must be positive, no greater than the number of MPI processes, and kpar * bndpar must divide the number of MPI processes exactly.
685+
- **Availability**: *([`basis_type`](#basis_type)==pw and [`esolver_type`](#esolver_type)==sdft) or ([`basis_type`](#basis_type)==pw and [`esolver_type`](#esolver_type)==ksdft and [`ks_solver`](#ks_solver)==bpcg)*
686+
- **Description**: Controls band-group parallelism for PW SDFT and PW KSDFT calculations using the BPCG eigensolver.
687+
- Within each k-point pool, divide the MPI processes into bndpar band groups. Each group contains NPROC / (kpar * bndpar) processes when bndpar is greater than 1.
688+
- With BPCG, distribute contiguous ranges of global Kohn-Sham bands among the band groups. nbands does not need to be divisible by bndpar, but bndpar cannot exceed a positive nbands. Groups with lower indices receive one additional band when necessary.
689+
- In SDFT, distribute stochastic orbitals among the band groups. When the deterministic Kohn-Sham eigensolver is not BPCG, band group 0 calculates the deterministic orbitals and broadcasts them to the other groups.
690+
- bndpar must be positive and no greater than the number of MPI processes. When bndpar is greater than 1, kpar * bndpar must divide the number of MPI processes exactly.
691+
> Note: For PW calculations on GPU, if the input kpar * bndpar differs from the number of MPI processes, ABACUS automatically sets the effective kpar to NPROC / bndpar.
683692
- **Default**: 1
684693

685694
### latname
@@ -4514,9 +4523,9 @@
45144523
### ocp_set
45154524

45164525
- **Type**: String
4517-
- **Description**: Fixed occupation weights used when ocp is true. Values are assigned band by band for each k-point, following k-point order. In LCAO RT-TDDFT, the initial ground-state SCF uses its normally determined occupations, and this array is applied only during subsequent real-time propagation steps. The repetition syntax N*x expands to N copies of x.
4526+
- **Description**: Fixed occupation weights used when ocp is true. Values are assigned in band order for each k-point, following k-point order. In LCAO RT-TDDFT, the initial ground-state SCF uses its normally determined occupations, and this array is applied only during subsequent real-time propagation steps. The repetition syntax N*x expands to N copies of x.
45184527
- Example: 1 10*1 0 1 expands to 13 values, with the 12th value equal to 0 and all other values equal to 1.
4519-
- After expansion, the array length must equal nks * nbands.
4528+
- After expansion, provide one block of nbands values for each k-point. If nspin is 2, provide all k-point blocks for spin up followed by all k-point blocks for spin down; otherwise, provide one block per k-point.
45204529
- The sum of all weights must equal nelec; otherwise the calculation terminates with an error.
45214530
- **Default**: None
45224531

docs/parameters.yaml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,18 +100,26 @@ parameters:
100100
category: System variables
101101
type: Integer
102102
description: |
103-
Divide all processors into kpar groups, and k points will be distributed among each group. The value taken should be less than or equal to the number of k points as well as the number of MPI processes.
103+
Controls k-point parallelism. The value must be positive and should not exceed either the number of k-points or the number of MPI processes.
104+
* For PW calculations, divide all MPI processes into persistent k-point pools. Each pool stores and processes a subset of the k-points.
105+
* For LCAO calculations with lapack, genelpa, elpa, or scalapack_gvx, divide the diagonalization work into temporary k-point pools. After diagonalization, the eigenvalues and distributed wavefunctions are restored for all k-points before occupations, density matrices, and output are evaluated.
106+
* Multi-process LCAO cusolver uses its own active-GPU distribution and does not use this value to define its k-point layout. Other LCAO eigensolvers do not use the temporary k-point-pool implementation.
104107
default_value: "1"
105108
unit: ""
106109
availability: ""
107110
- name: bndpar
108111
category: System variables
109112
type: Integer
110113
description: |
111-
Divide all processors into bndpar groups for SDFT or the BPCG solver. bndpar must be positive, no greater than the number of MPI processes, and kpar * bndpar must divide the number of MPI processes exactly.
114+
Controls band-group parallelism for PW SDFT and PW KSDFT calculations using the BPCG eigensolver.
115+
* Within each k-point pool, divide the MPI processes into bndpar band groups. Each group contains NPROC / (kpar * bndpar) processes when bndpar is greater than 1.
116+
* With BPCG, distribute contiguous ranges of global Kohn-Sham bands among the band groups. nbands does not need to be divisible by bndpar, but bndpar cannot exceed a positive nbands. Groups with lower indices receive one additional band when necessary.
117+
* In SDFT, distribute stochastic orbitals among the band groups. When the deterministic Kohn-Sham eigensolver is not BPCG, band group 0 calculates the deterministic orbitals and broadcasts them to the other groups.
118+
* bndpar must be positive and no greater than the number of MPI processes. When bndpar is greater than 1, kpar * bndpar must divide the number of MPI processes exactly.
119+
[NOTE] For PW calculations on GPU, if the input kpar * bndpar differs from the number of MPI processes, ABACUS automatically sets the effective kpar to NPROC / bndpar.
112120
default_value: "1"
113121
unit: ""
114-
availability: ""
122+
availability: (basis_type==pw and esolver_type==sdft) or (basis_type==pw and esolver_type==ksdft and ks_solver==bpcg)
115123
- name: latname
116124
category: System variables
117125
type: String
@@ -2744,9 +2752,9 @@ parameters:
27442752
category: "RT-TDDFT: Real-Time Time-Dependent Density Functional Theory"
27452753
type: String
27462754
description: |
2747-
Fixed occupation weights used when ocp is true. Values are assigned band by band for each k-point, following k-point order. In LCAO RT-TDDFT, the initial ground-state SCF uses its normally determined occupations, and this array is applied only during subsequent real-time propagation steps. The repetition syntax N*x expands to N copies of x.
2755+
Fixed occupation weights used when ocp is true. Values are assigned in band order for each k-point, following k-point order. In LCAO RT-TDDFT, the initial ground-state SCF uses its normally determined occupations, and this array is applied only during subsequent real-time propagation steps. The repetition syntax N*x expands to N copies of x.
27482756
* Example: 1 10*1 0 1 expands to 13 values, with the 12th value equal to 0 and all other values equal to 1.
2749-
* After expansion, the array length must equal nks * nbands.
2757+
* After expansion, provide one block of nbands values for each k-point. If nspin is 2, provide all k-point blocks for spin up followed by all k-point blocks for spin down; otherwise, provide one block per k-point.
27502758
* The sum of all weights must equal nelec; otherwise the calculation terminates with an error.
27512759
default_value: None
27522760
unit: ""

source/Makefile.Objects

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ OBJS_IO=module_parameter/input_conv.o\
552552
cal_test.o\
553553
module_dos/write_dos_pw.o\
554554
nscf_fermi_surf.o\
555+
band_parallel_output.o\
555556
write_bands.o\
556557
module_dos/cal_dos.o\
557558
module_dos/cal_pdos_gamma.o\

source/source_base/parallel_grid.cpp

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -422,60 +422,81 @@ void Parallel_Grid::zpiece_to_stogroup(double* zpiece, const int& iz, double* rh
422422
// ofs_running << "\n iz = " << iz << " Done.";
423423
return;
424424
}
425+
426+
// Taoni modified on 2026-08-21, fixed BPCG out_chg MPI_ERR_RANK
425427
void Parallel_Grid::reduce(double* rhotot, const double* const rhoin, const bool reduce_all_pool) const
426428
{
427-
// ModuleBase::TITLE("Parallel_Grid","reduce");
428-
429-
// if not the first pool, wait here until processpr 0
430-
// send the Barrier command.
429+
// POOL_WORLD communicators are disjoint, so inactive pools may return
430+
// without skipping a collective required by an active pool.
431431
if (!reduce_all_pool && GlobalV::MY_POOL != 0)
432432
{
433433
return;
434434
}
435435

436-
double* zpiece = new double[this->ncxy];
436+
assert(rhoin != nullptr);
437+
assert(this->nrxx == this->ncxy * this->nczp);
437438

438-
for (int iz = 0; iz < this->ncz; iz++)
439-
{
440-
const int znow = iz - this->startz[GlobalV::MY_POOL][GlobalV::RANK_IN_POOL];
441-
const int proc = this->whichpro[GlobalV::MY_POOL][iz];
442-
const int proc_loc = this->whichpro_loc[GlobalV::MY_POOL][iz]; // Obtain the local processor index in the pool
443-
ModuleBase::GlobalFunc::ZEROS(zpiece, this->ncxy);
444-
int tag = iz;
445-
MPI_Status ierror;
446-
447-
// Local processor 0 collects data from all other processors in the pool
448-
// proc = proc_loc if GlobalV::MY_POOL == 0
449-
if (proc_loc == GlobalV::RANK_IN_POOL)
450-
{
451-
for (int ir = 0; ir < ncxy; ir++)
452-
{
453-
zpiece[ir] = rhoin[ir * this->nczp + znow];
454-
}
455-
// Send data to the root of the pool
456-
if (GlobalV::RANK_IN_POOL != 0)
457-
{
458-
MPI_Send(zpiece, ncxy, MPI_DOUBLE, 0, tag, POOL_WORLD);
459-
}
460-
}
439+
int pool_size = 0;
440+
MPI_Comm_size(POOL_WORLD, &pool_size);
441+
442+
// Derive slab ownership from the communicator used below. Precomputed
443+
// source ranks may belong to a larger BPCG layout and are not necessarily
444+
// valid ranks in the current POOL_WORLD.
445+
std::vector<int> local_z_counts(pool_size);
446+
MPI_Allgather(&this->nczp, 1, MPI_INT, local_z_counts.data(), 1, MPI_INT, POOL_WORLD);
461447

462-
// The root of the pool receives data from other processors
463-
if (GlobalV::RANK_IN_POOL == 0 && proc_loc != GlobalV::RANK_IN_POOL)
448+
// MPI_Gatherv concatenates complete [xy][local_z] buffers by rank.
449+
// These displacements therefore address rank blocks, not global z coordinates.
450+
std::vector<int> receive_counts(pool_size);
451+
std::vector<int> displacements(pool_size, 0);
452+
int total_z = 0;
453+
for (int rank = 0; rank < pool_size; ++rank)
454+
{
455+
receive_counts[rank] = local_z_counts[rank] * this->ncxy;
456+
if (rank > 0)
464457
{
465-
MPI_Recv(zpiece, ncxy, MPI_DOUBLE, proc_loc, tag, POOL_WORLD, &ierror);
458+
displacements[rank] = displacements[rank - 1] + receive_counts[rank - 1];
466459
}
460+
total_z += local_z_counts[rank];
461+
}
462+
assert(total_z == this->ncz);
467463

468-
if (GlobalV::RANK_IN_POOL == 0)
464+
// Only the pool root needs storage for the gathered global grid.
465+
// MPI ignores the receive buffer on all non-root ranks.
466+
std::vector<double> gathered_data;
467+
if (GlobalV::RANK_IN_POOL == 0)
468+
{
469+
assert(rhotot != nullptr);
470+
gathered_data.resize(this->ncxyz);
471+
}
472+
MPI_Gatherv(rhoin,
473+
this->nrxx,
474+
MPI_DOUBLE,
475+
gathered_data.data(),
476+
receive_counts.data(),
477+
displacements.data(),
478+
MPI_DOUBLE,
479+
0,
480+
POOL_WORLD);
481+
482+
if (GlobalV::RANK_IN_POOL == 0)
483+
{
484+
// Rank blocks cannot be copied directly to rhotot: each block stores
485+
// [xy][local_z], whereas Cube output expects [xy][global_z].
486+
// The slab decomposition is contiguous and ordered by POOL_WORLD rank.
487+
int global_z_start = 0;
488+
for (int rank = 0; rank < pool_size; ++rank)
469489
{
490+
const int local_z = local_z_counts[rank];
470491
for (int ixy = 0; ixy < this->ncxy; ++ixy)
471492
{
472-
rhotot[ixy * ncz + iz] = zpiece[ixy];
493+
for (int iz = 0; iz < local_z; ++iz)
494+
{
495+
rhotot[ixy * this->ncz + global_z_start + iz] = gathered_data[displacements[rank] + ixy * local_z + iz];
496+
}
473497
}
498+
global_z_start += local_z;
474499
}
475500
}
476-
477-
delete[] zpiece;
478-
479-
return;
480501
}
481502
#endif

source/source_cell/cal_atoms_info.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ class CalAtomsInfo
143143
result.nbands = nbands;
144144
unitcell::cal_nbands(static_cast<int>(result.nelec), result.nlocal, nelec_spin, result.nbands,
145145
esolver_type, lspinorb, nspin, basis_type, smearing_method);
146+
// Taoni add check for bndpar > nbands on 2026-08-21
147+
if (ks_solver == "bpcg" && result.nbands > 0 && bndpar > result.nbands)
148+
{
149+
ModuleBase::WARNING_QUIT("CalAtomsInfo", "bndpar cannot exceed nbands when ks_solver is bpcg");
150+
}
146151

147152
// calculate the number of nbands_local
148153
result.nbands_l = result.nbands;

source/source_cell/parallel_kpoints.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,20 @@ void Parallel_Kpoints::set_startpro_pool()
113113
return;
114114
}
115115

116-
117116
// gather kpoints from all processor pools, only need to be called by the first processor of each pool.
118117
void Parallel_Kpoints::gatherkvec(const std::vector<ModuleBase::Vector3<double>>& vec_local,
119118
std::vector<ModuleBase::Vector3<double>>& vec_global) const
120119
{
120+
int world_rank = 0;
121+
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
122+
// Taoni fix bndpar on 2026-08-21
123+
// rank_in_pool is repeated in every band group under BPCG. The first
124+
// world rank of the k-point pool instead identifies one unique contributor.
125+
const bool is_pool_root = (world_rank == this->startpro_pool[this->my_pool]);
121126
vec_global.resize(this->nkstot_np, ModuleBase::Vector3<double>(0.0, 0.0, 0.0));
122127
for (int i = 0; i < this->nks_np; ++i)
123128
{
124-
125-
if (this->rank_in_pool == 0)
129+
if (is_pool_root)
126130
{
127131
vec_global[i + startk_pool[this->my_pool]] = vec_local[i];
128132
}
@@ -154,7 +158,6 @@ void Parallel_Kpoints::pool_collection(double& value, const double* wk, const in
154158
{
155159
MPI_Status ierror;
156160
MPI_Recv(&value, 1, MPI_DOUBLE, this->startpro_pool[pool], ik, MPI_COMM_WORLD, &ierror);
157-
158161
}
159162
}
160163
else
@@ -215,7 +218,7 @@ void Parallel_Kpoints::pool_collection_aux(T* value, const V& w, const int& dim,
215218
T* p = &w.ptr[begin];
216219
// temprary restrict kpar=1 for NSPIN=2 case for generating_orbitals
217220
int pool = 0;
218-
if (this->nspin != 2)
221+
if (this->nspin != 2)
219222
{
220223
pool = this->whichpool[ik];
221224
}

source/source_esolver/esolver_ks_lcaopw.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ namespace ModuleESolver
145145
hsolver::HSolverLIP<T> hsolver_lip_obj(this->pw_wfc,
146146
PARAM.globalv.use_uspp,
147147
this->inp_->basis_type,
148-
this->inp_->calculation);
148+
this->inp_->calculation,
149+
this->inp_->nbands);
149150
hsolver_lip_obj.solve(static_cast<hamilt::Hamilt<T>*>(this->p_hamilt), *this->stp.template get_psi_t<T, base_device::DEVICE_CPU>(), this->pelec,
150151
*this->psi_local, skip_charge,ucell.tpiba,ucell.nat, this->general_exx_info_);
151152

0 commit comments

Comments
 (0)