Skip to content

Commit 68d20f3

Browse files
committed
Fix: Correct PW KPAR/BNDPAR occupations and distributed output
1 parent fac9330 commit 68d20f3

19 files changed

Lines changed: 272 additions & 87 deletions

docs/advanced/input_files/input-main.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -671,13 +671,22 @@
671671
### kpar
672672

673673
- **Type**: Integer
674-
- **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.
674+
- **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.
675+
- For PW calculations, divide all MPI processes into persistent k-point pools. Each pool stores and processes a subset of the k-points.
676+
- 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.
677+
- 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.
675678
- **Default**: 1
676679

677680
### bndpar
678681

679682
- **Type**: Integer
680-
- **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.
683+
- **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)*
684+
- **Description**: Controls band-group parallelism for PW SDFT and PW KSDFT calculations using the BPCG eigensolver.
685+
- 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.
686+
- With BPCG, distribute contiguous ranges of global Kohn-Sham bands among the band groups. nbands does not need to be divisible by bndpar; groups with lower indices receive one additional band when necessary.
687+
- 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.
688+
- 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.
689+
> 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.
681690
- **Default**: 1
682691

683692
### latname
@@ -4492,9 +4501,9 @@
44924501
### ocp_set
44934502

44944503
- **Type**: String
4495-
- **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.
4504+
- **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.
44964505
- Example: 1 10*1 0 1 expands to 13 values, with the 12th value equal to 0 and all other values equal to 1.
4497-
- After expansion, the array length must equal nks * nbands.
4506+
- 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.
44984507
- The sum of all weights must equal nelec; otherwise the calculation terminates with an error.
44994508
- **Default**: None
45004509

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; 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
@@ -331,60 +331,81 @@ void Parallel_Grid::zpiece_to_stogroup(double* zpiece, const int& iz, double* rh
331331
// ofs_running << "\n iz = " << iz << " Done.";
332332
return;
333333
}
334+
335+
// Taoni modified on 2026-08-21, fixed BPCG out_chg MPI_ERR_RANK
334336
void Parallel_Grid::reduce(double* rhotot, const double* const rhoin, const bool reduce_all_pool) const
335337
{
336-
// ModuleBase::TITLE("Parallel_Grid","reduce");
337-
338-
// if not the first pool, wait here until processpr 0
339-
// send the Barrier command.
338+
// POOL_WORLD communicators are disjoint, so inactive pools may return
339+
// without skipping a collective required by an active pool.
340340
if (!reduce_all_pool && GlobalV::MY_POOL != 0)
341341
{
342342
return;
343343
}
344344

345-
double* zpiece = new double[this->ncxy];
345+
assert(rhoin != nullptr);
346+
assert(this->nrxx == this->ncxy * this->nczp);
346347

347-
for (int iz = 0; iz < this->ncz; iz++)
348-
{
349-
const int znow = iz - this->startz[GlobalV::MY_POOL][GlobalV::RANK_IN_POOL];
350-
const int proc = this->whichpro[GlobalV::MY_POOL][iz];
351-
const int proc_loc = this->whichpro_loc[GlobalV::MY_POOL][iz]; // Obtain the local processor index in the pool
352-
ModuleBase::GlobalFunc::ZEROS(zpiece, this->ncxy);
353-
int tag = iz;
354-
MPI_Status ierror;
355-
356-
// Local processor 0 collects data from all other processors in the pool
357-
// proc = proc_loc if GlobalV::MY_POOL == 0
358-
if (proc_loc == GlobalV::RANK_IN_POOL)
359-
{
360-
for (int ir = 0; ir < ncxy; ir++)
361-
{
362-
zpiece[ir] = rhoin[ir * this->nczp + znow];
363-
}
364-
// Send data to the root of the pool
365-
if (GlobalV::RANK_IN_POOL != 0)
366-
{
367-
MPI_Send(zpiece, ncxy, MPI_DOUBLE, 0, tag, POOL_WORLD);
368-
}
369-
}
348+
int pool_size = 0;
349+
MPI_Comm_size(POOL_WORLD, &pool_size);
350+
351+
// Derive slab ownership from the communicator used below. Precomputed
352+
// source ranks may belong to a larger BPCG layout and are not necessarily
353+
// valid ranks in the current POOL_WORLD.
354+
std::vector<int> local_z_counts(pool_size);
355+
MPI_Allgather(&this->nczp, 1, MPI_INT, local_z_counts.data(), 1, MPI_INT, POOL_WORLD);
370356

371-
// The root of the pool receives data from other processors
372-
if (GlobalV::RANK_IN_POOL == 0 && proc_loc != GlobalV::RANK_IN_POOL)
357+
// MPI_Gatherv concatenates complete [xy][local_z] buffers by rank.
358+
// These displacements therefore address rank blocks, not global z coordinates.
359+
std::vector<int> receive_counts(pool_size);
360+
std::vector<int> displacements(pool_size, 0);
361+
int total_z = 0;
362+
for (int rank = 0; rank < pool_size; ++rank)
363+
{
364+
receive_counts[rank] = local_z_counts[rank] * this->ncxy;
365+
if (rank > 0)
373366
{
374-
MPI_Recv(zpiece, ncxy, MPI_DOUBLE, proc_loc, tag, POOL_WORLD, &ierror);
367+
displacements[rank] = displacements[rank - 1] + receive_counts[rank - 1];
375368
}
369+
total_z += local_z_counts[rank];
370+
}
371+
assert(total_z == this->ncz);
376372

377-
if (GlobalV::RANK_IN_POOL == 0)
373+
// Only the pool root needs storage for the gathered global grid.
374+
// MPI ignores the receive buffer on all non-root ranks.
375+
std::vector<double> gathered_data;
376+
if (GlobalV::RANK_IN_POOL == 0)
377+
{
378+
assert(rhotot != nullptr);
379+
gathered_data.resize(this->ncxyz);
380+
}
381+
MPI_Gatherv(rhoin,
382+
this->nrxx,
383+
MPI_DOUBLE,
384+
gathered_data.data(),
385+
receive_counts.data(),
386+
displacements.data(),
387+
MPI_DOUBLE,
388+
0,
389+
POOL_WORLD);
390+
391+
if (GlobalV::RANK_IN_POOL == 0)
392+
{
393+
// Rank blocks cannot be copied directly to rhotot: each block stores
394+
// [xy][local_z], whereas Cube output expects [xy][global_z].
395+
// The slab decomposition is contiguous and ordered by POOL_WORLD rank.
396+
int global_z_start = 0;
397+
for (int rank = 0; rank < pool_size; ++rank)
378398
{
399+
const int local_z = local_z_counts[rank];
379400
for (int ixy = 0; ixy < this->ncxy; ++ixy)
380401
{
381-
rhotot[ixy * ncz + iz] = zpiece[ixy];
402+
for (int iz = 0; iz < local_z; ++iz)
403+
{
404+
rhotot[ixy * this->ncz + global_z_start + iz] = gathered_data[displacements[rank] + ixy * local_z + iz];
405+
}
382406
}
407+
global_z_start += local_z;
383408
}
384409
}
385-
386-
delete[] zpiece;
387-
388-
return;
389410
}
390411
#endif

source/source_cell/parallel_kpoints.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "parallel_kpoints.h"
66

77
#include "source_base/parallel_common.h"
8+
#include "source_base/parallel_comm.h"
89
#include "source_base/parallel_global.h"
910

1011
/// @note the kpoints here are reduced after symmetry applied.
@@ -118,11 +119,13 @@ void Parallel_Kpoints::set_startpro_pool()
118119
void Parallel_Kpoints::gatherkvec(const std::vector<ModuleBase::Vector3<double>>& vec_local,
119120
std::vector<ModuleBase::Vector3<double>>& vec_global) const
120121
{
122+
// Taoni fix bndpar on 2026-08-21
123+
int band_rank = 0;
124+
MPI_Comm_rank(BP_WORLD, &band_rank);
121125
vec_global.resize(this->nkstot_np, ModuleBase::Vector3<double>(0.0, 0.0, 0.0));
122126
for (int i = 0; i < this->nks_np; ++i)
123127
{
124-
125-
if (this->rank_in_pool == 0)
128+
if (this->rank_in_pool == 0 && band_rank == 0)
126129
{
127130
vec_global[i + startk_pool[this->my_pool]] = vec_local[i];
128131
}

source/source_estate/elecstate_tools.cpp

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
#include "elecstate_tools.h"
22
#include "occupy.h"
3+
#include "source_base/parallel_comm.h"
34
#include "source_base/parallel_reduce.h"
5+
6+
namespace
7+
{
8+
int get_band_offset(const int local_nbands)
9+
{
10+
int band_end = local_nbands;
11+
#ifdef __MPI
12+
MPI_Scan(&local_nbands, &band_end, 1, MPI_INT, MPI_SUM, BP_WORLD);
13+
#endif
14+
return band_end - local_nbands;
15+
}
16+
} // namespace
17+
418
namespace elecstate
519
{
620
void calEBand(const ModuleBase::matrix& ekb,const ModuleBase::matrix& wg,fenergy& f_en)
@@ -42,11 +56,14 @@ namespace elecstate
4256
const int nks = ekb.nr;
4357
if (!(Occupy::use_gaussian_broadening || Occupy::fixed_occupations))
4458
{
59+
// Taoni fix smearing_method=fixed for BPCG on 2026-08-21
60+
const int band_offset = get_band_offset(nbands);
4561
if (PARAM.globalv.two_fermi)
4662
{
4763
Occupy::iweights(nks,
4864
klist->wk,
4965
nbands,
66+
band_offset,
5067
nelec_spin[0],
5168
ekb,
5269
eferm.ef_up,
@@ -56,6 +73,7 @@ namespace elecstate
5673
Occupy::iweights(nks,
5774
klist->wk,
5875
nbands,
76+
band_offset,
5977
nelec_spin[1],
6078
ekb,
6179
eferm.ef_dw,
@@ -70,6 +88,7 @@ namespace elecstate
7088
Occupy::iweights(nks,
7189
klist->wk,
7290
nbands,
91+
band_offset,
7392
PARAM.inp.nelec,
7493
ekb,
7594
eferm.ef,
@@ -151,15 +170,15 @@ namespace elecstate
151170

152171
const double ne_thr = 1.0e-5;
153172

154-
const int num = klist->get_nks() * nbands;
155-
if (num != ocp_kb.size())
173+
const std::size_t expected_size = static_cast<std::size_t>(klist->get_nkstot()) * static_cast<std::size_t>(nbands);
174+
if (expected_size != ocp_kb.size())
156175
{
157176
ModuleBase::WARNING_QUIT("ElecState::fixed_weights",
158177
"size of occupation array is wrong , please check ocp_set");
159178
}
160179

161180
double num_elec = 0.0;
162-
for (int i = 0; i < ocp_kb.size(); ++i)
181+
for (std::size_t i = 0; i < ocp_kb.size(); ++i)
163182
{
164183
num_elec += ocp_kb[i];
165184
}
@@ -170,11 +189,28 @@ namespace elecstate
170189
"total number of occupations is wrong , please check ocp_set");
171190
}
172191

192+
const int band_offset = get_band_offset(wg.nc);
193+
if (klist->ik2iktot.size() < static_cast<std::size_t>(wg.nr)
194+
|| band_offset < 0
195+
|| band_offset + wg.nc > nbands)
196+
{
197+
ModuleBase::WARNING_QUIT("ElecState::fixed_weights", "invalid distributed occupation layout");
198+
}
199+
173200
for (int ik = 0; ik < wg.nr; ++ik)
174201
{
202+
const int global_k = klist->ik2iktot[ik];
203+
if (global_k < 0 || global_k >= klist->get_nkstot())
204+
{
205+
ModuleBase::WARNING_QUIT("ElecState::fixed_weights", "invalid global k-point index");
206+
}
175207
for (int ib = 0; ib < wg.nc; ++ib)
176208
{
177-
wg(ik, ib) = ocp_kb[ik * wg.nc + ib];
209+
// Taoni fix ocp_set under kpar and bndpar on 2026-08-21
210+
const int global_band = band_offset + ib;
211+
const std::size_t occupation_index
212+
= static_cast<std::size_t>(global_k) * static_cast<std::size_t>(nbands) + static_cast<std::size_t>(global_band);
213+
wg(ik, ib) = ocp_kb[occupation_index];
178214
}
179215
}
180216
skip_weights = true;

source/source_estate/occupy.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ void Occupy::decision(const std::string& name, const std::string& smearing_metho
120120
*
121121
* @param nks number of k points.
122122
* @param wk weight of each k point (consider symmetry).
123-
* @param nbands number of bands.
123+
* @param nbands number of locally owned bands.
124+
* @param band_offset global index of the first locally owned band.
124125
* @param nelec number of electrons for this spin direction.
125126
* @param ekb the array save the band energy.
126127
* @param ef output: the highest occupied Kohn-Sham level.
@@ -132,6 +133,7 @@ void Occupy::iweights(
132133
const int nks,
133134
const std::vector<double>& wk,
134135
const int nbands,
136+
const int band_offset,
135137
const double& nelec,
136138
const ModuleBase::matrix& ekb,
137139
double& ef,
@@ -168,7 +170,8 @@ void Occupy::iweights(
168170

169171
for (int ib = 0; ib < nbands; ++ib)
170172
{
171-
if (ib < ib_min)
173+
const int global_band = band_offset + ib;
174+
if (global_band < ib_min)
172175
{
173176
wg(ik, ib) = wk[ik];
174177
ef = std::max(ef, ekb(ik, ib));

source/source_estate/occupy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Occupy
3838
static void iweights(const int nks,
3939
const std::vector<double>& wk,
4040
const int nband,
41+
const int band_offset,
4142
const double& nelec,
4243
const ModuleBase::matrix& ekb,
4344
double& ef,

source/source_estate/test/elecstate_base_test.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ TEST_F(ElecStateTest, FixedWeights)
249249
PARAM.input.nelec = 30;
250250
K_Vectors* klist = new K_Vectors;
251251
klist->set_nks(5);
252+
klist->set_nkstot(5);
253+
klist->ik2iktot = {0, 1, 2, 3, 4};
252254
elecstate->klist = klist;
253255
elecstate->wg.create(klist->get_nks(), PARAM.input.nbands);
254256
std::vector<double> ocp_kb;
@@ -269,6 +271,8 @@ TEST_F(ElecStateDeathTest, FixedWeightsWarning1)
269271
PARAM.input.nelec = 30;
270272
K_Vectors* klist = new K_Vectors;
271273
klist->set_nks(5);
274+
klist->set_nkstot(5);
275+
klist->ik2iktot = {0, 1, 2, 3, 4};
272276
elecstate->klist = klist;
273277
elecstate->wg.create(klist->get_nks(), PARAM.input.nbands);
274278
std::vector<double> ocp_kb;
@@ -291,6 +295,8 @@ TEST_F(ElecStateDeathTest, FixedWeightsWarning2)
291295
PARAM.input.nelec = 29;
292296
K_Vectors* klist = new K_Vectors;
293297
klist->set_nks(5);
298+
klist->set_nkstot(5);
299+
klist->ik2iktot = {0, 1, 2, 3, 4};
294300
elecstate->klist = klist;
295301
elecstate->wg.create(klist->get_nks(), PARAM.input.nbands);
296302
std::vector<double> ocp_kb;

0 commit comments

Comments
 (0)