Skip to content

Refactor charge density module (Useful Information for Refactoring a Module) - #7972

Merged
mohanchen merged 111 commits into
deepmodeling:developfrom
mohanchen:20260916
Sep 21, 2026
Merged

mohanchen merged 111 commits into
deepmodeling:developfrom
mohanchen:20260916

Conversation

@mohanchen

@mohanchen mohanchen commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

总结

source/source_estate/module_charge 进行了系统性现代化改造:将类成员函数提取为 module_charge 命名空间下的自由函数,使用 RAII 管理内存,移除了粘性单例/全局变量耦合,并引入了 unittests/ 目录约定以及按源文件命名的测试文件。旨在不改变数值结果的前提下实现,下文“PR 内部修正”中提到的回归问题除外。

重构带来的优势

依赖项缩减

  • module_xc 已从 module_charge 中移除。 Meta-GGA 信息现在以对象状态标志 (Charge::meta_gga) 形式传递,该标志由 InitRhog::meta_gga 使用,用于 tau 的 TF-init/read;tau 的对称化/缩减由 kin_r 缓冲区的存在性驱动;mixing_tau 在 esolver 配置组装点解析,而不是在混合器内部读取 module_xc
  • DMR 混合不再依赖 HContainer / 密度矩阵。 chg_dmr 的核心现在处理原始双缓冲区(6d174ff2a570dd49a2),Charge_Mixing 的 DMR 封装已被移除,直接调用 chg_dmr86b4f5435)。
  • chg_routine 已与 spin_constrain 单例解耦 (a669eeae9);调用者显式传递所需的 spin-constrain 数据。
  • PARAM 读取已替换为显式配置结构体 —— MixingConfigScfMixingCtxReadCfgNlcCtx (c9ba84b6873eee044cb8cb59d89b96b14398) —— 因此模块的公共 API 接收的是值,而不是 PARAM
  • rhopw 被显式传递chg_init / chg_routine / chg_extra / chg_symm,而不是通过 Charge 访问 (1e492e5ea);GlobalV::ofs_running / ofs_warning 被替换为 std::ostream& 参数。
  • 移除了粘性/无效成员:冗余的 Charge::omega_ 指针 (34b441e1c),未使用的 Charge::prenspin (6a67c09a8),无效的 PAW 补偿电荷成员 (65252c4b2),无效的 init_final_scf / allocate_rho_final_scf (07ebcc1d9),重复的 Charge& cal_rhog_symm_soc 重载 (2411e80b9),以及旧的 divide_data / combine_data / clean_data 成员 (2670f86fe)。
  • gint_prec_ctrl 已移至 module_gint (619029d59) —— 它从未真正属于电荷模块。

将模块 I/O 带回原处

  • rhog_io.{h,cpp}(原位于 source/source_estate/)已移动到 module_charge 作为 chg_rhog_io.{h,cpp} (1528e79d8) —— reciprocal-space 密度 I/O 始终应属于此模块,现在也确实在此处。

内存管理

  • Charge 及其伴生类中,原始的 new / delete 已被 std::vector / std::unique_ptr 替换。rho / rhog / rho_save / kin_r 缓冲区现在使用三层布局:连续的 std::vector 存储 + 行指针向量 + 遗留的 double** 视图,以便旧代码仍能编译,同时 RAII 负责清理。Charge 副本已被禁止;Charge_Extra 历史数组已向量化。

命名规范化

chgmixing.*chg_routine.*charge_mixing.*chg_mix.*charge_initchg_initcharge_mixing_rhochg_taucharge_atomicchg_atomiccharge_mathchg_toolscharge_extrachg_extramix_precondchg_precondmixing_config.hchg_mix_cfg.h。内部跨 TU 辅助函数位于 detail 子命名空间下的 detail 头文件中。

新的 unittests/ 目录约定

  • source/source_estate/module_charge/ 下新建了 unittests/ 目录,并迁移/重写了测试:test_chg_rhog_io.cpptest_charge.cpptest_chg_extra.cpptest_chg_mix.cpptest_chg_parallel.cpptest_chg_tools.cpp
  • 文件与测试的映射现已成为约定:测试源文件命名为 test_<source>.cpp 以匹配其测试的源文件(例如 chg_rhog_io.cpp 对应 test_chg_rhog_io.cpp)。这使得文件与测试的关系易于查找,并将在未来的模块重构中推广应用。
  • 连接到 CTest 作为专用的 Module_Charge 步骤(已添加到全包含的 -E 列表以避免重复执行),添加到 coverage lcov 过滤器(*/unittests/*),以及添加到 code_quality_score.pySKIP_DIRS 中。

PR 内部修正(非上游 Bug 修复)

以下条目是本次 PR 引入 并在 PR 内部 解决的回归问题。它们不是原始程序的 Bug,不应被解读为稳定性声明;它们被列出是为了让审阅者了解每个中间步骤发生的事情。

  • NPT 应力回归 (34b441e1cce5054958 / 140be6549 / 46a9665d7)。移除 Charge::omega_ 指针后,Charge::sum_rho() 在重新归一化时错误地使用了 rhopw->omega(初始单元胞体积)而不是 ucell.omega(当前 NPT 体积)。对于 tests/01_PW/095_PW_NPT,这导致了 ~2e-3 的应力偏差,而总能量未受影响。已通过为 sum_rho() / renormalize_rho() / dm2rho 调用点添加显式的 omega 参数进行修复,使当前单元胞体积在每个调用点明确传递。rhopw->omega 的三处下游使用(estate_e_terms.cppelecstate_energy.cppmakov_payne.cpp)已用 BUG(investigate) TODO 标记以供后续处理。
  • nrxx == 0 时的空分区误报 (d9685d4eb25f51c526,配合 module_base 中的 871f8f2cc)。重构后的 pack_rho_mag / unpack_rho_mag 添加的空指针检查未考虑到拥有零实空间网格点的进程分区是完全合法的空分区;它中止了例如在 mpirun -np 4 下的 tests/02_NAO_Gamma/scf_elenum_spin2。修复:仅在 nrxx > 0 时要求非空缓冲区。
  • 测试 TU 中围绕并行包装器的 #ifdef __MPI 防护 (7a0013848 移除 → 4dcbd1c2a 恢复)。这些防护是承重的:测试 TU 通过 abacus_disable_feature_definitions 去除了 __MPI,但链接的是 __MPI 下编译的 libbase,其显式模板实例化包含真实的 MPI 调用;测试 TU 中未加防护的调用会解析为这些符号,并以 "MPI_Allreduce before MPI_Init" 终止。
  • std::make_unique 不是 C++11 (962b0b31e03e3e5e48)。已重写为 std::unique_ptr<T>(new T(...))
  • 过度激进的 include 移除后丢失完整类型 (ee7080533)。一个“无用”的 include (xc_functional.h) 通过传递性提供了 PW_Basis 的完整类型和 ModuleBase::TITLEchg_drho_inner.cpp 静默丢失了它们。已通过重新添加直接 include 修复。
  • 重命名后的 Makefile.Objects / 手写 Makefile (e4e6fc93fe074dd0fa)。在 mix_precondchg_precond 重命名以及 charge_math 提取后,对象文件未注册。已通过更新两个 make 文件修复。
  • gint_prec_ctrl 迁移后的测试 include 路径 (dfe1dac83)。

未来重构的经验教训

这些是后续模块工作应遵循的具体经验:

  1. 在移除粘性成员之前,检查所有下游使用点是否使用了正确的替代值,而不仅仅是任何替代值。 omega_ 指针看起来是多余的,但它被静默替换成了 rhopw->omega,而后者是错误的体积(静态初始值 vs 当前 NPT 值)。对涉及单元胞数量的数值测试(例如 095_PW_NPT)进行二分查找是捕获此问题的关键。
  2. __MPI(以及其他功能宏)视为构建配置,而不是样式选择。 包装器 TU 的 no-op stubs 取决于包装器 TU 的标志,而不是调用者的标志;如果调用者链接了该宏下编译的库,调用者必须自行加防护,即使包装器看起来是“安全”的。
  3. nrxx == 0 是合法的空分区,而不是错误情况。 从现在起,所有对 rho[is][ir] 风格缓冲区的指针验证都必须允许 null(仅有 nrxx > 0 时才需要非 null)。
  4. 在移除看起来“无用”的 include 之前,请验证该 TU 是否仍通过剩余的 include 获取了所需的完整类型。 仅检查直接符号使用是不够的 —— 头文件可以传递性地提供完整类型和 TITLE 样式的宏。安全移除的最简单测试是:该 TU 能否在仅保留剩余 include 的情况下干净地编译。
  5. std::make_unique 是 C++14。 仓库基线是 C++11;请使用 std::unique_ptr<T>(new T(...))
  6. 重命名/提取后,同时更新 CMakeListsMakefile.Objects 和所有手写的 Makefile 条目 —— 有两个平行的构建系统,漏掉任何一个都会导致链接器报 undefined-reference 错误。
  7. 将测试与模块放在一起,放在 unittests/ 下,命名为 test_<source>.cpp 同时更新 ctest 标签、coverage lcov 过滤器和 code_quality_score.pySKIP_DIRS,以便测试源文件不会污染质量分数或覆盖率报告。

工程规范:detail 头文件与已删除的拷贝/赋值

detail 约定。 在模块内多个翻译单元(TU)间共享、但不属于公共 API 的辅助函数和模板,不放在公共 .h 文件中;它们位于 *_detail.h 头文件中的 module_charge::detail 子命名空间下(例如 chg_rho_detail.hchg_drho_detail.hchg_symm_detail.hchg_atomic_detail.hchg_tau.h)。规则很简单:如果某个符号不会被外部模块或单元测试调用,它就不应出现在公共头文件中。这使得公共头文件保持精简,防止意外依赖,并将实现细节放在需要它们的 TU 旁边。真正属于模块私有的辅助函数——仅由单个 .cpp 使用——甚至不进入 detail 头文件;它们位于匿名命名空间中。这种三层划分(<module>.h 中的公共接口,<module>_detail.hdetail 子命名空间下的跨 TU 内部辅助函数,以及 .cpp 内匿名命名空间私有的辅助函数)是未来模块重构的标准模式。

已删除的拷贝/赋值构造函数。 对 RAII 管理的存储应用相同的规则,Charge 现在显式禁用拷贝和赋值:

// rho/rhog/kin_r 是 vector 支持的 _space_* 存储的视图,因此默认拷贝会复制悬空指针。禁止拷贝。
Charge(const Charge&) = delete;
Charge& operator=(const Charge&) = delete;

原因在于新的三层存储布局:rhorhogrho_savekin_rdouble** / std::complex<double>** 视图,指向行指针向量(_space_*),而后者又指向 std::vector 支持的连续存储。默认拷贝会按值复制指针视图,因此拷贝对象的视图会指向 原始 对象的 vector 缓冲区——一旦原始对象被销毁,这些视图就会失效。对于一个带有自身 MPI 分区、重达数百 MB 的密度对象,隐式拷贝从未有过明确定义,并可能静默引发 use-after-free。将两者都标记为 = delete 会使任何意外拷贝在 编译时 失败,而不是在运行时变成休眠 bug。如果未来确实需要深拷贝,它应该在构造函数体中显式实现,重新分配目标的 _space_* 存储并重新绑定其视图——绝不能依赖默认语义。同样的禁令适用于 Charge_Extra(其历史数组现在也已向量化),这是应用于通过指针视图访问存储的 RAII 管理类型的通用规则:一旦类的存储是通过指向自身 vector 的指针视图访问的,拷贝和赋值就会被删除,直到显式深拷贝被证明是必要的。
改动只在代码注释:原来三行英文压成一行英文,"拷"之后不再断行。其余段落不变。

验证

  • make -j 30 清洁构建(串行 + MPI);ctest -R MODULE_CHARGE 通过。
  • tests/02_NAO_Gamma/scf_elenum_spin2 使用 OMP_NUM_THREADS=4mpirun -np 1/2/3/4 重新运行 —— 在 nrxx == 0 修复后不再中止。
  • tests/01_PW/095_PW_NPT 应力在 omega 修复后与参考值匹配(390.77675500 → 390.77871100)。
  • INPUT 参数未更改;无需更新 docs/parameters.yaml / input-main.md

abacus_fixer added 5 commits September 16, 2026 09:02
…ol flow

Mechanical cleanup as the first step of the module_charge governance
refactor: convert leading tabs to 4-space indentation (1011 occurrences
across 11 files) and add braces around all single-statement if/for/while
bodies (11 sites). No functional change.
Introduce a MixingConfig POD that bundles the INPUT mixing parameters
with the runtime globals (nspin, scf_thr_type, double_grid), and change
set_mixing from a 12-argument interface to set_mixing(const MixingConfig&,
double&, double&). Charge_Mixing now stores the config and reads nspin /
scf_thr_type / double_grid from it instead of PARAM.inp / PARAM.globalv,
removing the direct PARAM reads in set_mixing and init_mixing.

The single production call site (esolver_ks.cpp) fills the config, and
the unit test drives set_mixing via a make_cfg() helper. The
'#define private public' access hack is kept for now with a TODO: the
test still must write Parameter::input/sys, Charge::_space_* and
XC_Functional privates, which need the Step 4/5 global-state
parameterization before it can be removed.

Verified: make -j30 MODULE_ESTATE_charge_mixing (build_max_para_test)
passes with no errors.
…th std::vector

Extract the repeated two-beta mixing functor in mix_rho_recip/mix_rho_real
into a make_twobeta_mix<T> template helper (6 lambda copies removed), and
convert all local raw new[]/delete[] buffers in charge_mixing_rho.cpp to
zero-initialized std::vector, dropping the paired ZEROS calls.
Extend MixingConfig with gamma_only_pw/domag/domag_z so mix_resid.cpp
(get_drho, get_dkin, inner_product_recip_{rho,simple,hartree,real}) no
longer reads PARAM/GlobalV; all branches now consume this->cfg_.
inner_product_recip_rho's raw pointer-array views are switched to
std::vector. Production fills the three new fields in esolver_ks, and
the test fixture gains a sync_cfg() helper to push PARAM mutations into
cfg_ for the inner-product branch tests.
Replace the six private raw _space_rho/_space_rho_save/_space_rhog/
_space_rhog_save/_space_kin_r/_space_kin_r_save buffers with
std::vector, so Charge's underlying contiguous storage self-manages and
the matching delete[] calls in destroy() (which relied on reading
possibly-uninitialized pointers) go away. The public rho/rhog/rho_save/
rhog_save/kin_r/kin_r_save views keep their double**/complex** shape and
still alias the vector memory via .data(), so all external consumers are
unaffected. Tests that drove _space_* directly are adapted to
resize()/.data() and drop their manual delete[] of the buffers.
@mohanchen mohanchen added Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0 labels Sep 16, 2026
abacus_fixer and others added 8 commits September 16, 2026 16:28
chgmixing_ks already takes a const Input_para& inp but still read
PARAM.inp.mixing_restart / PARAM.inp.scf_nmax from the global. Use the
inp argument instead so the function no longer reads INPUT state through
the global for these two fields. PARAM.globalv.ks_run is a runtime
per-process flag (set from band-parallel topology), not an input, so it
is intentionally left as-is rather than threading it through the
interface.
init_rho had a cyclomatic complexity of 36 from five sequential stages
(file read, atomic fallback, Thomas-Fermi tau, restart load, wfc read)
interleaved through shared read_error/read_kin_error flags. Extract the
four branches into private methods -- read_rho_from_file,
init_rho_atomic_and_tau, load_rho_from_restart, init_rho_from_wfc -- and
leave init_rho as a thin sequence of stage calls. Logic is unchanged; the
error flags are threaded through as parameters. The deepest stage
(read_rho_from_file) now sits at complexity 19, down from 36 for the
monolith. The remaining global reads inside the stages are untouched and
deferred to a later parameterization step.
…tions

sum_rho, cal_rho2ne and non_linear_core_correction each used Charge
members only to reach a handful of scalars (nrxx/nxyz/omega) or the
reciprocal-shell table (gg_uniq/ngg); the rest of each body is pure
numerics. Move the three bodies into a new charge_math namespace as free
functions with those values passed explicitly, and leave the Charge
members as thin forwarding wrappers so no caller outside the module
changes. The kernels are now unit-testable in isolation and no longer
coupled to Charge state. One behavior note: the pre-quit debug line that
printed sum_rho to ofs_warning is dropped so the free function stays free
of global-stream dependencies. charge_math.cpp is wired into the estate
library and the charge_test target.
The CMake build already picks up charge_math.cpp; mirror that in
Makefile.Objects so the legacy Makefile flow links the new charge_math
kernels too. The module_charge directory is already on VPATH, so adding
charge_math.o to the object list is sufficient.
…ction

Remove Charge::atomic_rho entirely and replace all call sites with
module_charge::atomic_rho(..., rhopw), eliminating the need for a thin
wrapper on the Charge class. This decouples atomic density initialization
from Charge's state and improves charge.cpp quality score from 2 to 44.
scf_out_chg_tau aborted in Parallel_Grid::reduce on
assert(rhoin != nullptr) because the kin_r_save[is] handed to
write_vdata_palgrid was not a valid buffer. After the _space_* storage
became std::vector (ecf5084), a copied/moved Charge leaves its
rho/kin_r views dangling into another object's vector buffer, and a
kin_r_save never allocated (ked_flag set after allocate) stays nullptr;
both surface as a null rhoin deep inside MPI gather instead of at the
source.

Delete Charge's copy constructor/assignment so any value copy of the
vector-aliasing views fails at compile time, and check kin_r_save in
ctrl_output_fp before writing tau.cube so a missing allocation reports
a clear message instead of tripping the MPI assert.

Verification: not run locally (per user request, user compiles).
scf_out_chg_tau (LCAO, SCAN, out_chg=1, 4 MPI ranks) aborted in
Parallel_Grid::reduce on assert(rhoin != nullptr). Bisecting between
83eb5d0 (good) and ecf5084 (bad) isolated the regression to
ecf5084, which moved Charge's _space_* storage from raw new[] to
std::vector.

Root cause: with 4 ranks the FFT grid is slab-decomposed so that the
last rank owns zero real-space points (nrxx == 0, confirmed via a
temporary diagnostic printing fn/is/rank/nrxx at the reduce call site).
Before ecf5084, _space_rho = new double[nspin * 0] == new double[0]
returned a unique non-null pointer, so rho_save[is] was non-null and the
assert passed. After the change, an empty vector's .data() returns
nullptr, so the rank with nrxx == 0 handed a null rhoin to reduce and
tripped the assert (Debug) or fed MPI_Gatherv a null buffer (Release).

A rank with nrxx == 0 is legitimate: MPI_Gatherv is invoked with
sendcount 0 and ignores the send buffer. Relax the assert to only flag a
null buffer when nrxx != 0, and revert the now-unneeded kin_r_save guard
in ctrl_output_fp (it would have falsely aborted on the nrxx == 0 rank).

Verification: Release build (build_max_para_test), ran
  cd tests/03_NAO_multik/scf_out_chg_tau &&
  OMP_NUM_THREADS=1 mpirun -np 4 ../../../build_max_para_test/abacus_max_para
Result: exit 0, chg.cube and tau.cube written; numerical comparison
against chg.cube.ref/tau.cube.ref gives maxdiff 0 (chg) and 1e-14 (tau).
abacus_fixer added 14 commits September 16, 2026 22:10
…ction

Move set_rho_core to charge_math::set_rho_core with rho_core,
rhog_core and rhopw passed explicitly instead of reading Charge
state, and call charge_math::non_linear_core_correction directly.
Remove the now-unused Charge::non_linear_core_correction wrapper,
use std::vector for the rhocg/vg scratch buffers, update the
init_scf call site, and drop the obsolete member stubs in the
elecstate unit tests.
Replace the raw new[]/delete[] displacement arrays (dis_old1, dis_old2,
dis_now) with std::vector and remove the hand-written destructor. This
fixes a read of uninitialized pot_order when an object is destroyed
before Init_CE, a memory leak when Init_CE is called repeatedly, and a
double-free risk from the implicitly generated shallow copy. The copy
constructor and copy assignment are deleted so the molecular-dynamics
trajectory history cannot be silently forked. The unit test now checks
vector sizes instead of non-null pointers.
- Rename module_charge/charge_math.{h,cpp} to chg_tools.{h,cpp} via git mv
- Change namespace charge_math to module_charge to match charge_atomic
  and chgmixing in the same directory
- Update include guard CHG_TOOLS_H and TITLE/timer labels accordingly
- Update call sites in init_scf.cpp, charge.cpp, charge_init.cpp
- Update build references in Makefile.Objects and both CMakeLists.txt
Convert the stateless class Symmetry_rho into namespace module_charge
free functions and rename files for consistency:
  symm_rho.{h,cpp}      -> chg_symm.{h,cpp}
  symm_rho_detail.h     -> chg_symm_detail.h
  symm_rhog.cpp         -> chg_symm_detail.cpp

- 5 public functions become module_charge::symmetrize_rho / cal_rhog_symm
  (2 overloads) / cal_rhog_symm_soc (2 overloads)
- 2 cross-TU helpers (psymmg/psymmg_soc) moved to module_charge::detail
  via chg_symm_detail.h
- 3 internal MPI helpers moved to anonymous namespace
- Delete dead code psymm (real-space symmetrization, never called)
- Remove empty ctor/dtor and parallel_grid.h include
- Rename begin/begin_soc to cal_rhog_symm/cal_rhog_symm_soc for clarity
- Update timer/TITLE labels from "Symmetry_rho" to "module_charge"
- Migrate all 14 call sites and 1 test stub
- Remove obsolete Makefile special rule (no more name collision)
…uct_recip_simple

Move MixingConfig from charge_mixing.h into its own mixing_config.h so
stateless residual kernels can include the config without dragging in
Charge_Mixing. Remove inner_product_recip_simple, which had no production
call sites, together with its unit test.
Relocate gint_prec_ctrl.{h,cpp} and its test into module_gint, update the
include in esolver_ks_lcao.h and rewire the CMake/Makefile object lists.
…ions

Rename mix_resid.cpp to chg_drho.cpp and turn inner_product_real and
inner_product_recip_hartree into module_charge free functions declared
in chg_drho.h; inner_product_recip_rho, which is only shared with the
unit test, moves to module_charge::detail in chg_drho_detail.h.
Charge_Mixing loses the three private inner-product members and
mix_rho_recip/mix_rho_real bind the free functions through lambdas.
get_drho/get_dkin stay as members for this step.
Move the get_drho/get_dkin implementations into file-local cal_drho/
cal_dkin free functions with all inputs explicit; the public
Charge_Mixing methods become thin forwarding wrappers so esolver call
sites stay unchanged.
…nctions

Move Charge_Mixing::Kerker_screen_recip/real to module_charge namespace
as free functions in chg_precond.{h,cpp}, renaming mix_precond.cpp via
git mv. Config/grid/geometry are passed explicitly via MixingConfig,
PW_Basis*, and tpiba, eliminating the function's direct read of
PARAM.inp.nspin. Replace 8 std::bind call sites in charge_mixing_rho.cpp
with lambdas, update 2 commented-out bind sites in charge_mixing_dmr.cpp,
and rewrite 12 test call sites in charge_mixing_test.cpp to construct an
independent MixingConfig instead of poking at Charge_Mixing privates.
Drop the now-unused member function declarations from charge_mixing.h.
…rename

Update the non-CMake object list to track the renamed translation unit so
make-based builds do not reference the deleted mix_precond.o.
Expose cal_drho/cal_dkin as module_charge free functions in chg_drho.h
and let ESolver_KS call them directly with explicit arguments; add
Charge_Mixing::get_mixing_config() as a const observer for the config.
Align with the chg_<feature> naming pattern used in the same directory
(chg_drho, chg_precond, chg_symm, chg_tools). Update include guard to
CHG_ROUTINE_H, the self-include in chg_routine.cpp, the entry in
source_estate/CMakeLists.txt and source/Makefile.Objects, and the three
#include sites in esolver_ks{,_pw,_lcao}.cpp. Function names
(chgmixing_ks{,_pw,_lcao}) and TITLE/timer tags are intentionally left
unchanged to keep the diff minimal.
Rename the MixingConfig header to align with the chg_* naming
convention in module_charge. Update the include guard and the four
in-tree includers; no CMake change is needed since the header is not
listed explicitly.
abacus_fixer added 10 commits September 20, 2026 16:49
tpiba2 was declared in chg_mix.h but never assigned by set_mixing()
nor read anywhere in the module. Grep across the whole source tree
confirms all tpiba2 references are either ucell.tpiba2 (a separate
UnitCell member) or local variables in unrelated modules. The
Charge_Mixing class never computed or used its own tpiba2 pointer;
only tpiba is consumed by the stateless Kerker kernels via
mix_rho_recip/mix_rho_real. Remove the dead declaration.
get_mixing_mode(), get_mixing_beta(), get_mixing_ndim() previously
returned the legacy mirror members that set_mixing() kept in sync
with cfg_ by hand. With cfg_ now treated as the immutable INPUT
snapshot, route the public getters through cfg_ directly so there
is a single source of truth for INPUT parameters. External callers
(esolver_ks_lcao, lcao_others, pw_others) are unaffected since
signatures are unchanged. The legacy members remain in place for
now; they are dropped in a later step after internal readers are
migrated.
init_mixing() branched on this->mixing_mode and passed
this->mixing_ndim/mixing_beta to the Broyden/Pulay/Plain_Mixing
constructors. These legacy mirrors were kept in sync with cfg_
manually by set_mixing(). Route through cfg_ directly so cfg_
remains the single source of INPUT parameters. The Mixing objects
themselves still copy beta/ndim into their own members at
construction; that is a one-time snapshot and not a continuous
sync surface, so it is left untouched.
Both mix_rho_recip and mix_rho_real built the twobeta_mix functor by
reading this->mixing_beta / this->mixing_beta_mag, which are legacy
mirrors that set_mixing() kept in sync with cfg_. Route the six
construction sites through cfg_.mixing_beta / cfg_.mixing_beta_mag
so cfg_ is the single source of INPUT parameters consumed by the
mixing logic. Behavior is unchanged since the mirrors and cfg_
hold identical values after set_mixing().
set_mixing() copied mixing_mode, mixing_beta, mixing_beta_mag,
mixing_ndim from cfg into legacy mirror members, then validation
and logging read from the mirrors. Now that all internal readers
(init_mixing, mix_rho_recip, mix_rho_real, getters) read from
cfg_, the mirror writes are dead work. Drop them and route
validation and log output through cfg_ directly. omega and tpiba
remain pointer members because they alias external runtime state
(cell volume, lattice constant) that changes across SCF iterations
and so do not belong in MixingConfig (an immutable INPUT snapshot).
…urce

Drop mixing_mode, mixing_beta, mixing_beta_mag, mixing_ndim mirror
members. After the previous commits every internal reader (getters,
init_mixing, mix_rho_recip, mix_rho_real, set_mixing validation
and log output) routes through cfg_, so the mirrors are dead state
that set_mixing() no longer writes. cfg_ is now the single source
of truth for INPUT mixing parameters.

Update test_chg_mix.cpp accordingly: the two assertions that
reached directly into CMtest.mixing_beta_mag and CMtest.mixing_mode
now read CMtest.get_mixing_config().mixing_beta_mag and
CMtest.get_mixing_mode(), matching the public API used by the
other assertions in the same block. No production caller accessed
these members directly (esolver_ks_lcao, lcao_others, pw_others
all used the getters), so the change is test-only on the consumer
side.
The non-static data member initializers in MixingConfig provided
plausible-looking defaults (e.g. mixing_beta=0.8, mixing_mode=
"broyden") that silently masked forgotten fields when a new field
was added but not wired up at construction sites. With the
defaults removed, every construction site must use aggregate
initialization (or copy-assign from a fully-initialized instance),
and a missing field yields value-initialized (zero/empty) members
that are far more likely to trip a test than the old defaults.
Combined with -Wmissing-field-initializers promoted to error in
the next commits, adding a field to MixingConfig without updating
all aggregate-initialization sites becomes a compile error.
Convert the 17-line field-by-field assignment of mix_cfg into a
single aggregate initialization in declaration order. Wrap it in
#pragma GCC diagnostic error "-Wmissing-field-initializers" so
that adding a field to MixingConfig without updating this list
becomes a compile error rather than silently using a default.
Each initializer is annotated with the field name it corresponds
to, making the declaration-order dependency auditable at a glance.
Convert make_cfg()'s 17-line field-by-field assignment into a
single aggregate initialization in declaration order, matching
the esolver-side change. Wrap in the same
#pragma GCC diagnostic error "-Wmissing-field-initializers" so
that adding a field to MixingConfig without updating the test
helper is also a compile error. Both construction sites (esolver
and test) now fail at compile time if a field is missing, closing
the maintenance gap where a new field could silently fall back to
a default value.

@AsTonyshment AsTonyshment left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If CI could pass, I'm OK with it.

abacus_fixer added 11 commits September 20, 2026 20:42
Add validation to turn latent misuse (skipped set_rhopw/set_mixing)
into clear WARNING_QUIT errors instead of null dereference or heap
corruption:
- init_mixing rejects a null rhopw
- if_scf_oscillate checks scf_nmax > 0 and iteration range
- mix_rho validates chr/chr->rhopw and the grid pointers

Fix three chg_mix unit tests that read cfg_ before set_mixing, which
caused a SIGSEGV in SCFOscillationTest and assertion failures in the
two inner-product tests.
Add test_chg_uspp.cpp covering split_dgrid/merge_dgrid (normal split,
round-trip, nspin=1/2, empty high-frequency/smooth boundaries, and
input-validation abort paths).

Add test_chg_dmr.cpp covering init_mixing_dmr/mix_dmr (nspin=1/2/4
mixing with Plain_Mixing analytically verified, empty-partition null
buffer allowance, and input-validation abort paths).

Wire both targets into unittests/CMakeLists.txt.
…ho_inner, chg_mix_rho

- test_chg_precond.cpp: kerker_screen_recip/real (early return, nspin=1/2/4
  filter, nspin=4 with mixing_angle resize, real-space matches reciprocal).
- test_chg_drho.cpp: inner_product_real, cal_drho real-space path
  (nspin=1/2/4+domag_z), cal_dkin (meta_gga false/true).
- test_chg_drho_inner.cpp: inner_product_recip_rho and
  inner_product_recip_hartree for nspin=1 with a single G component,
  analytically verified against the Coulomb metric.
- test_chg_mix_rho.cpp: mix_rho abort paths (null chr/chr->rhopw, unset
  rhopw, double_grid without rhodpw) and real-space plain mixing value.

Wire all four targets into unittests/CMakeLists.txt.
…g_atomic, chg_atomic_inner

- test_chg_symm.cpp: symmetrize_rho / cal_rhog_symm / cal_rhog_symm_soc
  no-op paths when symm_flag == 0, for nspin=1 and nspin=4.
- test_chg_symm_detail.cpp: psymmg and psymmg_soc idempotence on a
  manually built D_4 point group over a serial cubic PW_Basis.
- test_chg_atomic_inner.cpp: compute_rhoatm USPP direct-copy branch and
  NCPP integrate+scale-to-zv branch (Gaussian rho_at with known analytic
  integral); normalize_and_check renormalizes uniform density to nelec.
- test_chg_atomic.cpp: atomic_rho ntype==0 path (skips atom loop) and
  spin_number_need==3 abort path.

Wire all four targets into unittests/CMakeLists.txt.
…rious XC_Functional stubs

Fourth batch of module_charge unit tests:
- test_chg_tau.cpp: mix_tau_recip abort paths (null chr/grid/mixing, nspin<1,
  double_grid without high-f mixer) and non-double-grid plain mixing value.
- test_chg_routine.cpp: chgmixing_ks_pw/lcao iter==1 restart-step setup, and
  chgmixing_ks convergence branches (conv_esolver true / drho<hsolver_error
  skip mix_rho).
- test_chg_init.cpp: init_rho "wfc" with null wfcpw abort, and "atomic" with
  ntype==0 + meta_gga Thomas-Fermi tau initialization.
Wire all three targets into unittests/CMakeLists.txt.

Cleanup: remove the XC_Functional::func_type / ked_flag definitions from
test_chg_drho, test_chg_mix_rho, test_chg_symm, test_chg_atomic_inner,
test_chg_atomic, test_chg_tau, test_chg_routine, and test_chg_init. None of
the non-test sources compiled into these targets reference these statics
(charge.cpp, chg_*.cpp, and the linked base/cell_info/planewave_serial/
symmetry libraries are clean), so the definitions were pure dead weight.
Also drop the now-unneeded xc_functional.h include from test_chg_mix_rho.cpp
and correct the stub comments.
- include chg_atomic_detail.h instead of nonexistent chg_atomic_inner.h
  in test_chg_atomic_inner.cpp; add math_integral.h for Simpson_Integral
- include chg_drho.h in test_chg_drho_inner.cpp for
  module_charge::inner_product_recip_hartree
- include source_cell/magnetism.h in tests that define Magnetism stubs
  (test_chg_drho, test_chg_symm, test_chg_tau, test_chg_mix_rho)
- fix nonexistent source_charge/mixing includes in test_chg_tau.cpp to
  source_base/module_mixing
…ne/init targets

- test_chg_tau: use Plain_Mixing(beta) ctor and init_mixing_data with
  complex type_size (old set_mixing_beta/init_mixing no longer exist)
- test_chg_symm_detail: add Magnetism stub required by cell_info's
  unitcell.cpp, matching other tests in this directory
- test_chg_routine: adapt to two-arg set_rhopw and tpiba from ucell
- disable MODULE_CHARGE_routine and MODULE_CHARGE_init targets with
  documented reasons: their transitive dependencies (Plus_U_Base,
  elecstate, source_io) are deeply coupled; to be resolved later
@mohanchen
mohanchen merged commit 583a20b into deepmodeling:develop Sep 21, 2026
17 checks passed
@mohanchen mohanchen added the Useful Information Useful information for others to learn/study label Sep 21, 2026
@mohanchen mohanchen changed the title Refactor charge density module Refactor charge density module (Useful Information for Refactoring a Module) Sep 21, 2026
mohanchen pushed a commit that referenced this pull request Sep 21, 2026
Redo of the work in #7988 and #7990, both of which were closed while the
charge density module was being restructured. That restructuring landed in
#7972 and already did most of the decoupling those PRs proposed: allocate(),
renormalize_rho() and sum_rho() now take their inputs explicitly, the mixing
parameters are aggregated in a MixingConfig, and chg_mix.cpp / chg_drho.cpp /
charge.cpp are free of global parameter reads. What was left was the
test-side access.

Production changes are additive only - no existing signature moves and no
line is deleted from any production header:

  Charge::get_allocate_rho()          - report whether allocate() has run
  Charge_Mixing::get_rho_mdata()      - mirror the existing get_dmr_mdata()
  Charge_Mixing::get_tau_mdata()
  Charge_Mixing::set_mixing_config()  - pair for the existing getter, for
                                        callers that must update the snapshot
                                        without rebuilding the mixing history
  XC_Functional::set_func_type()      - pair for get_func_type()
  XC_Functional::set_ked_flag()       - pair for get_ked_flag()

Test changes:

  test_dm_r_init   - two sites move to the already public get_DMR_save()
  test_charge      - the global parameter scratchpad becomes fixture state
                     (32 refs -> 0); PW_Basis setup goes through the public
                     initgrids/initparameters/setuptransform sequence instead
                     of the protected distribute_r()/distribute_g()
  test_chg_mix     - the scratchpad becomes a fixture-owned MixingConfig
                     (163 refs -> 0); the three blocks that hand-wired
                     Charge::_space_* now take their buffers from the fixture,
                     which owns them as vectors and points the public
                     rho/rhog/kin_r views at them with the same stride

No expected value or tolerance was changed.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
mohanchen added a commit to mohanchen/abacus-mc that referenced this pull request Sep 22, 2026
* module_charge: normalize indentation and brace single-statement control flow

Mechanical cleanup as the first step of the module_charge governance
refactor: convert leading tabs to 4-space indentation (1011 occurrences
across 11 files) and add braces around all single-statement if/for/while
bodies (11 sites). No functional change.

* module_charge: aggregate Charge_Mixing params into MixingConfig

Introduce a MixingConfig POD that bundles the INPUT mixing parameters
with the runtime globals (nspin, scf_thr_type, double_grid), and change
set_mixing from a 12-argument interface to set_mixing(const MixingConfig&,
double&, double&). Charge_Mixing now stores the config and reads nspin /
scf_thr_type / double_grid from it instead of PARAM.inp / PARAM.globalv,
removing the direct PARAM reads in set_mixing and init_mixing.

The single production call site (esolver_ks.cpp) fills the config, and
the unit test drives set_mixing via a make_cfg() helper. The
'#define private public' access hack is kept for now with a TODO: the
test still must write Parameter::input/sys, Charge::_space_* and
XC_Functional privates, which need the Step 4/5 global-state
parameterization before it can be removed.

Verified: make -j30 MODULE_ESTATE_charge_mixing (build_max_para_test)
passes with no errors.

* module_charge: deduplicate twobeta_mix lambdas and replace raw new with std::vector

Extract the repeated two-beta mixing functor in mix_rho_recip/mix_rho_real
into a make_twobeta_mix<T> template helper (6 lambda copies removed), and
convert all local raw new[]/delete[] buffers in charge_mixing_rho.cpp to
zero-initialized std::vector, dropping the paired ZEROS calls.

* module_charge: move residual/inner-product globals into MixingConfig

Extend MixingConfig with gamma_only_pw/domag/domag_z so mix_resid.cpp
(get_drho, get_dkin, inner_product_recip_{rho,simple,hartree,real}) no
longer reads PARAM/GlobalV; all branches now consume this->cfg_.
inner_product_recip_rho's raw pointer-array views are switched to
std::vector. Production fills the three new fields in esolver_ks, and
the test fixture gains a sync_cfg() helper to push PARAM mutations into
cfg_ for the inner-product branch tests.

* module_charge: own Charge's _space_* storage with std::vector (Step 5a)

Replace the six private raw _space_rho/_space_rho_save/_space_rhog/
_space_rhog_save/_space_kin_r/_space_kin_r_save buffers with
std::vector, so Charge's underlying contiguous storage self-manages and
the matching delete[] calls in destroy() (which relied on reading
possibly-uninitialized pointers) go away. The public rho/rhog/rho_save/
rhog_save/kin_r/kin_r_save views keep their double**/complex** shape and
still alias the vector memory via .data(), so all external consumers are
unaffected. Tests that drove _space_* directly are adapted to
resize()/.data() and drop their manual delete[] of the buffers.

* module_charge: route chgmixing_ks through its inp parameter

chgmixing_ks already takes a const Input_para& inp but still read
PARAM.inp.mixing_restart / PARAM.inp.scf_nmax from the global. Use the
inp argument instead so the function no longer reads INPUT state through
the global for these two fields. PARAM.globalv.ks_run is a runtime
per-process flag (set from band-parallel topology), not an input, so it
is intentionally left as-is rather than threading it through the
interface.

* module_charge: split Charge::init_rho into per-stage private methods

init_rho had a cyclomatic complexity of 36 from five sequential stages
(file read, atomic fallback, Thomas-Fermi tau, restart load, wfc read)
interleaved through shared read_error/read_kin_error flags. Extract the
four branches into private methods -- read_rho_from_file,
init_rho_atomic_and_tau, load_rho_from_restart, init_rho_from_wfc -- and
leave init_rho as a thin sequence of stage calls. Logic is unchanged; the
error flags are threaded through as parameters. The deepest stage
(read_rho_from_file) now sits at complexity 19, down from 36 for the
monolith. The remaining global reads inside the stages are untouched and
deferred to a later parameterization step.

* module_charge: extract Charge density math into charge_math free functions

sum_rho, cal_rho2ne and non_linear_core_correction each used Charge
members only to reach a handful of scalars (nrxx/nxyz/omega) or the
reciprocal-shell table (gg_uniq/ngg); the rest of each body is pure
numerics. Move the three bodies into a new charge_math namespace as free
functions with those values passed explicitly, and leave the Charge
members as thin forwarding wrappers so no caller outside the module
changes. The kernels are now unit-testable in isolation and no longer
coupled to Charge state. One behavior note: the pre-quit debug line that
printed sum_rho to ofs_warning is dropped so the free function stays free
of global-stream dependencies. charge_math.cpp is wired into the estate
library and the charge_test target.

* module_charge: register charge_math.o in the hand-written Makefile build

The CMake build already picks up charge_math.cpp; mirror that in
Makefile.Objects so the legacy Makefile flow links the new charge_math
kernels too. The module_charge directory is already on VPATH, so adding
charge_math.o to the object list is sufficient.

* module_charge: extract Charge::atomic_rho into charge_atomic free function

Remove Charge::atomic_rho entirely and replace all call sites with
module_charge::atomic_rho(..., rhopw), eliminating the need for a thin
wrapper on the Charge class. This decouples atomic density initialization
from Charge's state and improves charge.cpp quality score from 2 to 44.

* module_charge: forbid Charge copies and guard tau.cube write

scf_out_chg_tau aborted in Parallel_Grid::reduce on
assert(rhoin != nullptr) because the kin_r_save[is] handed to
write_vdata_palgrid was not a valid buffer. After the _space_* storage
became std::vector (ecf5084d4), a copied/moved Charge leaves its
rho/kin_r views dangling into another object's vector buffer, and a
kin_r_save never allocated (ked_flag set after allocate) stays nullptr;
both surface as a null rhoin deep inside MPI gather instead of at the
source.

Delete Charge's copy constructor/assignment so any value copy of the
vector-aliasing views fails at compile time, and check kin_r_save in
ctrl_output_fp before writing tau.cube so a missing allocation reports
a clear message instead of tripping the MPI assert.

Verification: not run locally (per user request, user compiles).

* module_base: tolerate null grid buffer when a rank owns no grid points

scf_out_chg_tau (LCAO, SCAN, out_chg=1, 4 MPI ranks) aborted in
Parallel_Grid::reduce on assert(rhoin != nullptr). Bisecting between
83eb5d0f3 (good) and ecf5084d4 (bad) isolated the regression to
ecf5084d4, which moved Charge's _space_* storage from raw new[] to
std::vector.

Root cause: with 4 ranks the FFT grid is slab-decomposed so that the
last rank owns zero real-space points (nrxx == 0, confirmed via a
temporary diagnostic printing fn/is/rank/nrxx at the reduce call site).
Before ecf5084d4, _space_rho = new double[nspin * 0] == new double[0]
returned a unique non-null pointer, so rho_save[is] was non-null and the
assert passed. After the change, an empty vector's .data() returns
nullptr, so the rank with nrxx == 0 handed a null rhoin to reduce and
tripped the assert (Debug) or fed MPI_Gatherv a null buffer (Release).

A rank with nrxx == 0 is legitimate: MPI_Gatherv is invoked with
sendcount 0 and ignores the send buffer. Relax the assert to only flag a
null buffer when nrxx != 0, and revert the now-unneeded kin_r_save guard
in ctrl_output_fp (it would have falsely aborted on the nrxx == 0 rank).

Verification: Release build (build_max_para_test), ran
  cd tests/03_NAO_multik/scf_out_chg_tau &&
  OMP_NUM_THREADS=1 mpirun -np 4 ../../../build_max_para_test/abacus_max_para
Result: exit 0, chg.cube and tau.cube written; numerical comparison
against chg.cube.ref/tau.cube.ref gives maxdiff 0 (chg) and 1e-14 (tau).

* module_charge: extract Charge::set_rho_core into charge_math free function

Move set_rho_core to charge_math::set_rho_core with rho_core,
rhog_core and rhopw passed explicitly instead of reading Charge
state, and call charge_math::non_linear_core_correction directly.
Remove the now-unused Charge::non_linear_core_correction wrapper,
use std::vector for the rhocg/vg scratch buffers, update the
init_scf call site, and drop the obsolete member stubs in the
elecstate unit tests.

* module_charge: vectorize Charge_Extra history arrays and forbid copies

Replace the raw new[]/delete[] displacement arrays (dis_old1, dis_old2,
dis_now) with std::vector and remove the hand-written destructor. This
fixes a read of uninitialized pot_order when an object is destroyed
before Init_CE, a memory leak when Init_CE is called repeatedly, and a
double-free risk from the implicitly generated shallow copy. The copy
constructor and copy assignment are deleted so the molecular-dynamics
trajectory history cannot be silently forked. The unit test now checks
vector sizes instead of non-null pointers.

* Rename charge_math to chg_tools and unify namespace module_charge

- Rename module_charge/charge_math.{h,cpp} to chg_tools.{h,cpp} via git mv
- Change namespace charge_math to module_charge to match charge_atomic
  and chgmixing in the same directory
- Update include guard CHG_TOOLS_H and TITLE/timer labels accordingly
- Update call sites in init_scf.cpp, charge.cpp, charge_init.cpp
- Update build references in Makefile.Objects and both CMakeLists.txt

* module_charge: refactor Symmetry_rho class to free functions

Convert the stateless class Symmetry_rho into namespace module_charge
free functions and rename files for consistency:
  symm_rho.{h,cpp}      -> chg_symm.{h,cpp}
  symm_rho_detail.h     -> chg_symm_detail.h
  symm_rhog.cpp         -> chg_symm_detail.cpp

- 5 public functions become module_charge::symmetrize_rho / cal_rhog_symm
  (2 overloads) / cal_rhog_symm_soc (2 overloads)
- 2 cross-TU helpers (psymmg/psymmg_soc) moved to module_charge::detail
  via chg_symm_detail.h
- 3 internal MPI helpers moved to anonymous namespace
- Delete dead code psymm (real-space symmetrization, never called)
- Remove empty ctor/dtor and parallel_grid.h include
- Rename begin/begin_soc to cal_rhog_symm/cal_rhog_symm_soc for clarity
- Update timer/TITLE labels from "Symmetry_rho" to "module_charge"
- Migrate all 14 call sites and 1 test stub
- Remove obsolete Makefile special rule (no more name collision)

* module_charge: extract MixingConfig header and drop unused inner_product_recip_simple

Move MixingConfig from charge_mixing.h into its own mixing_config.h so
stateless residual kernels can include the config without dragging in
Charge_Mixing. Remove inner_product_recip_simple, which had no production
call sites, together with its unit test.

* module_gint: move gint_prec_ctrl from module_charge

Relocate gint_prec_ctrl.{h,cpp} and its test into module_gint, update the
include in esolver_ks_lcao.h and rewire the CMake/Makefile object lists.

* module_charge: extract mixing inner products into chg_drho free functions

Rename mix_resid.cpp to chg_drho.cpp and turn inner_product_real and
inner_product_recip_hartree into module_charge free functions declared
in chg_drho.h; inner_product_recip_rho, which is only shared with the
unit test, moves to module_charge::detail in chg_drho_detail.h.
Charge_Mixing loses the three private inner-product members and
mix_rho_recip/mix_rho_real bind the free functions through lambdas.
get_drho/get_dkin stay as members for this step.

* module_charge: hide cal_drho/cal_dkin in an anonymous namespace

Move the get_drho/get_dkin implementations into file-local cal_drho/
cal_dkin free functions with all inputs explicit; the public
Charge_Mixing methods become thin forwarding wrappers so esolver call
sites stay unchanged.

* module_gint: fix include path in test_gint_prec_ctrl after relocation

* module_charge: extract Kerker screen kernels into chg_precond free functions

Move Charge_Mixing::Kerker_screen_recip/real to module_charge namespace
as free functions in chg_precond.{h,cpp}, renaming mix_precond.cpp via
git mv. Config/grid/geometry are passed explicitly via MixingConfig,
PW_Basis*, and tpiba, eliminating the function's direct read of
PARAM.inp.nspin. Replace 8 std::bind call sites in charge_mixing_rho.cpp
with lambdas, update 2 commented-out bind sites in charge_mixing_dmr.cpp,
and rewrite 12 test call sites in charge_mixing_test.cpp to construct an
independent MixingConfig instead of poking at Charge_Mixing privates.
Drop the now-unused member function declarations from charge_mixing.h.

* module_charge: fix Makefile.Objects after mix_precond -> chg_precond rename

Update the non-CMake object list to track the renamed translation unit so
make-based builds do not reference the deleted mix_precond.o.

* module_charge: drop Charge_Mixing::get_drho/get_dkin wrappers

Expose cal_drho/cal_dkin as module_charge free functions in chg_drho.h
and let ESolver_KS call them directly with explicit arguments; add
Charge_Mixing::get_mixing_config() as a const observer for the config.

* module_charge: rename chgmixing.h/cpp to chg_routine.h/cpp

Align with the chg_<feature> naming pattern used in the same directory
(chg_drho, chg_precond, chg_symm, chg_tools). Update include guard to
CHG_ROUTINE_H, the self-include in chg_routine.cpp, the entry in
source_estate/CMakeLists.txt and source/Makefile.Objects, and the three
#include sites in esolver_ks{,_pw,_lcao}.cpp. Function names
(chgmixing_ks{,_pw,_lcao}) and TITLE/timer tags are intentionally left
unchanged to keep the diff minimal.

* module_charge: rename mixing_config.h to chg_mix_cfg.h

Rename the MixingConfig header to align with the chg_* naming
convention in module_charge. Update the include guard and the four
in-tree includers; no CMake change is needed since the header is not
listed explicitly.

* module_charge: convert Charge MPI helpers into chg_parallel free functions

Rename charge_mpi.cpp to chg_parallel.cpp and add chg_parallel.h, moving
the three stateless Charge member functions (reduce_diff_pools, rho_mpi,
kin_r_mpi) to module_charge namespace free functions that take the
Charge object explicitly. Remove their declarations from charge.h and
update all call sites in elecstate_pw, stress_mgga, read_wf2rho_pw and
sto_iter. Rename the unit test to test_chg_parallel.cpp and update the
test target name accordingly.

GlobalV/PARAM reads and the direct MPI_Allreduce in reduce_diff_pools
are preserved as pre-existing technical debt (migration-neutral).

* Rename charge_atomic files to chg_atomic

- Rename module_charge/charge_atomic.{h,cpp} to chg_atomic.{h,cpp}
- Update include guard to CHG_ATOMIC_H
- Update includes in charge_init.cpp and charge_extra.cpp
- Update source paths in CMakeLists.txt, test CMakeLists.txt
- Fix stale object names in Makefile.Objects: replace
  symm_rho_charge.o/symm_rhog.o with chg_symm.o/chg_symm_detail.o

* module_charge: extract USPP double-grid split/merge into chg_uspp free functions

Introduce module_charge::split_dgrid / merge_dgrid in chg_uspp.{h,cpp} as
RAII, parameter-explicit replacements for Charge_Mixing::divide_data /
combine_data / clean_data, which paired raw new[] with manual delete[]
across ~160 lines of mixing code.

- chg_uspp.{h,cpp}: stateless free functions in module_charge namespace;
  outputs are caller-pre-sized std::vector, no new/delete; parameter
  validation via WARNING_QUIT; TITLE/timer tags preserved
- charge_mixing_rho.cpp: rho and tau double-grid paths switched to the new
  functions; raw pointer aliases kept for !double_grid so the existing
  mixing call sites (nspin==1/2/4) are untouched
- CMakeLists.txt (source + test): wire chg_uspp.cpp

The legacy divide_data/combine_data/clean_data members are not yet removed;
that follows in a later step after the test is updated.

* module_charge: rewrite MixDivCombTest for the new split_dgrid/merge_dgrid

Drop the legacy alias-pointer assertions (EXPECT_EQ(datas, data.data()),
EXPECT_EQ(datas, nullptr) after clean_data) that coupled the test to the
old new[]/delete[] ownership model.

The rewritten case verifies the actual contract:
- split_dgrid fills smooth and high-frequency buffers with the dense
  data verbatim (per-element comparison)
- merge_dgrid is a left-inverse of split_dgrid (output == input)
- no explicit cleanup call is required: std::vector manages storage

Covers nspin == 1 and nspin == 2 paths.

* module_charge: drop legacy divide_data/combine_data/clean_data members

With the new module_charge::split_dgrid/merge_dgrid in chg_uspp.{h,cpp}
and all call sites in charge_mixing_rho.cpp migrated, the original
Charge_Mixing::divide_data / combine_data / clean_data members are dead.

- delete charge_mixing_uspp.cpp (the raw new[]/delete[] implementation)
- drop the three member declarations from charge_mixing.h
- remove charge_mixing_uspp.cpp from source/test CMakeLists.txt
- Makefile.Objects: drop charge_mixing_uspp.o, add chg_uspp.o
- refresh one stale comment in charge_mixing_rho.cpp to reference
  merge_dgrid instead of the removed combine_data

* module_charge: rename charge_extra files to chg_extra and move class into namespace

Rename charge_extra.h/cpp to chg_extra.h/cpp and wrap the Charge_Extra
class in the module_charge namespace, matching the rest of module_charge
(chg_atomic, chg_symm, chg_uspp). Update include guards, call sites in
esolver_fp.h and the unit test, and CMake/Makefile source lists.

* module_charge: extract DMR mixing into chg_dmr free functions

Move the DMR allocation/mixing logic out of Charge_Mixing members into
stateless module_charge functions (init_mixing_dmr, template mix_dmr
with explicit instantiation), passing the Mixing object, mixing data
and MixingConfig explicitly instead of reading PARAM. Merge the two
identical real/complex mix_dmr overloads, replace raw new[]/delete[]
of the magnetic buffers with std::vector, and de-duplicate the
two-beta mixing lambda into a file-local helper. The members stay as
thin timer-wrapped wrappers so external call sites are unchanged.

* module_charge: remove Charge_Mixing DMR wrappers, call chg_dmr directly

Delete charge_mixing_dmr.cpp and have the two call sites
(chg_routine.cpp, esolver_ks_lcao.cpp) invoke module_charge::
init_mixing_dmr/mix_dmr directly with the Mixing object, mixing data
and MixingConfig obtained through Charge_Mixing accessors. Expose the
owned DMR mixing history via a new get_dmr_mdata() accessor and drop
the now-unneeded density_matrix.h include from charge_mixing.h.
Timers move into the free functions with module_charge labels.
Add the direct parallel_orbitals.h include to esolver_gets.h, whose
value member previously relied on the removed transitive include.

* module_charge: decouple chg_dmr kernel from HContainer, mix raw buffers

Change module_charge::mix_dmr to take per-spin raw contiguous double
buffers and nnr instead of HContainer/DMR container references, and
drop the hcontainer.h include (and its atom_pair/parallel_orbitals
dependency chain) from chg_dmr.cpp. The sole call site in
esolver_ks_lcao.cpp now extracts the wrappers and saved buffers from
the DensityMatrix containers before calling the kernel. Move the
argument checks into a file-local check_dmr_inputs helper. The kernel
now depends only on the mixing module and MixingConfig.

* module_charge: refactor charge_mixing_rho free functions and cleanup

- Replace 17 PARAM.inp/globalv direct reads with cfg_ fields
- Unify mixing_tau: remove redundant member, use cfg_.mixing_tau
- Extract make_twobeta_mix as free function template in anonymous namespace
- Extract mix_tau_recip free function for kinetic energy density mixing
- Extract pack_rho_mag/unpack_rho_mag templates for nspin==2 dedup
- Hoist screen and inner_product lambdas before if-else chains (8+4 dups)
- Remove dead new_e_iteration member and its no-op if block
- Drop unused parameter.h include from charge_mixing_rho.cpp

* module_charge: split member functions into charge_mixing.cpp, free functions into chg_rho_detail.h

- Move mix_rho_recip/mix_rho_real/mix_rho from charge_mixing_rho.cpp to charge_mixing.cpp
- Create chg_rho_detail.h for make_twobeta_mix, pack_rho_mag, unpack_rho_mag templates and mix_tau_recip declaration
- charge_mixing_rho.cpp now only contains mix_tau_recip definition in module_charge::detail
- Restore accidentally deleted mix_uom member function

* module_charge: rename charge_{init,mixing_rho} to chg_{init,tau}, widen cube_io ofs_running to ostream

* charge_init.{cpp,h} -> chg_init.{cpp,h}: move Charge::init_rho stages
  (read_rho_from_file, init_rho_atomic_and_tau, load_rho_from_restart,
  init_rho_from_wfc) from Charge member functions to module_charge free
  functions, dropping the corresponding private declarations from
  charge.h. Continues the module_charge convention of stateless free
  functions in chg_* files.

* charge_mixing_rho.cpp -> chg_tau.cpp: rename for the module_charge
  short-underscore convention; the file only contains mix_tau_recip.

* Extract mix_tau_recip declaration from chg_rho_detail.h into a new
  chg_tau.h so chg_tau.cpp no longer pulls in the detail template
  helpers (make_twobeta_mix / pack_rho_mag / unpack_rho_mag).
  charge_mixing.cpp adds chg_tau.h while keeping chg_rho_detail.h for
  the template helpers it still uses.

* Widen ModuleIO::read_vdata_palgrid's ofs_running parameter from
  std::ofstream& to std::ostream& (cube_io.h / read_cube.cpp). The
  body only uses operator<<, so std::ostream& is sufficient; this
  fixes the chg_init.cpp compile error where read_rho_file /
  read_kin_file (per project rules, std::ostream&) could not bind to
  the old std::ofstream& parameter. Existing callers passing
  std::ofstream& (GlobalV::ofs_running, test fixture) convert
  implicitly via base-class reference.

Build lists updated: source/Makefile.Objects and
source/source_estate/{CMakeLists.txt,test/CMakeLists.txt}.

Verification: chg_init.* changes compile-verified by user before
this session; chg_tau rename and chg_tau.h extraction not yet
compile-verified; cube_io type widening not yet compile-verified.

* module_charge: rename charge_mixing.{h,cpp} to chg_mix.{h,cpp}, test to test_chg_mix.cpp

Pure rename, no logic change. Updates include guard, 12 #include sites,
CMakeLists (source_estate + test), and Makefile.Objects. CMake target
MODULE_ESTATE_charge_mixing kept (no external references). Class name
Charge_Mixing and module_charge namespace unchanged.

* module_charge: remove duplicate doc block comments (Phase 1a)

Remove or rephrase 14 duplicate comment lines across 7 files to
eliminate all duplicate_doc_block quality-score deductions.

- chg_mix.cpp: remove 7 duplicate comments in mix_rho_real that
  repeated mix_rho_recip's broyden/Kerker/magabs annotations
- chg_init.cpp: remove 2 duplicate comments in read_kin_file that
  repeated read_rho_file's binary-read and ParaWorld bridge notes
- chg_symm_detail.cpp: remove 1 duplicate step comment in psymmg_soc
- charge.h: rephrase kin_r_save comment to avoid repetition
- chg_extra.h: rephrase beta comment to avoid repetition
- chg_symm.cpp: remove 1 duplicate vector-management comment
- chg_precond.cpp: remove 1 duplicate Kerker comment

* module_charge: replace auto with explicit std::function types (Phase 1b)

Replace 14 auto-keyword lambda declarations with explicit
std::function types to eliminate all auto_keyword quality-score
deductions.

- chg_mix.cpp: 10 auto -> std::function (inner_product, screen,
  twobeta_mix in mix_rho_recip and mix_rho_real)
- chg_drho.cpp: 2 auto -> std::function<double()> (part_of_noncolin,
  part_of_rho)
- chg_tools.cpp: 1 auto -> std::function<void(int,int)> (kernel)
- chg_symm_detail.cpp: 1 auto -> std::function (build_wspin)

Added #include <functional> to all four files.

* module_charge: wrap lines over 120 chars (Phase 1c)

Break 21 lines exceeding the 120-char limit across 7 files to
eliminate all line_too_long quality-score deductions.

- charge.cpp: 3 WARNING_QUIT/cout lines split
- chg_atomic.cpp: 5 Simpson_Integral/exp/assert lines split
- chg_drho.cpp: 2 conj-product sum lines split
- chg_init.cpp: 1 warning message string split
- chg_mix.cpp: 5 make_twobeta_mix/recip_to_real/if_scf_oscillate lines split
- chg_mix.h: 3 member declaration/comment lines shortened
- chg_symm_detail.cpp: 2 MPI_Recv lines split

* module_charge: remove default parameter from Charge::init_rho (Phase 1d)

Remove the default nullptr values from init_rho's klist and wfcpw
parameters and update the two call sites (esolver_of.cpp,
esolver_double_xc.cpp) that relied on the defaults to pass nullptr
explicitly.

* module_charge: replace raw new/delete with std::vector and unique_ptr (Phase 2a-2d)

Replace all raw new/delete allocations in 4 files with RAII
containers to eliminate raw_new_keyword and unpaired_new_delete
quality-score deductions.

- chg_tools.cpp: 1 new -> std::vector<double> (aux buffer)
- chg_extra.cpp: 4 new -> std::vector<std::vector<double>> (rho_atom
  in extrapolate_charge and find_alpha_and_beta)
- chg_symm_detail.cpp: 14 new -> std::vector (rhog_piece, ig2isz,
  ipsz2ipw, nstnz_start, fftixy2is, rhogtot, ig2isztot, ixyz2ipw
  across reduce_to_fullrhog, rhog_piece_to_all, psymmg, psymmg_soc)
- chg_mix.{h,cpp}: 5 new + 5 unpaired -> std::unique_ptr for
  mixing and mixing_highf members; destructor and init_mixing
  simplified; get_mixing() returns .get()

charge.cpp (18 raw new) deferred to Phase 2e due to wider impact.

* module_charge: replace raw new/delete in Charge with vector-backed storage (Phase 2e)

Replace all 18 raw new and 10 unpaired delete in charge.cpp with
std::vector-backed storage to eliminate raw_new_keyword and
unpaired_new_delete deductions.

- charge.h: add _ptrs_rho, _ptrs_rhog, _ptrs_rho_save, _ptrs_rhog_save,
  _ptrs_kin_r, _ptrs_kin_r_save (std::vector<double*> / complex*),
  and _space_rho_core, _space_rhog_core (std::vector data buffers)
- charge.cpp allocate(): replace new double*[nspin] with vector resize;
  rho = _ptrs_rho.data() preserves double** interface
- charge.cpp init_final_scf(): replace both outer pointer and inner
  data new calls with _space_* vectors
- charge.cpp destroy(): replace delete[] with vector::clear() and
  nullptr assignment

charge.cpp score: 47 -> 69, now passing the 60 threshold.
Module average: 85.0 -> 85.7, 30/33 files passing.

* module_charge: replace std::make_unique with C++11-compatible unique_ptr(new T) (fix)

std::make_unique is a C++14 feature; the repo baseline is C++11.
Replace 4 make_unique calls with std::unique_ptr<T>(new T(...)) to
eliminate the post_cpp11_feature deduction (-40).

chg_mix.cpp score: 0 -> 15, module average: 85.7 -> 86.1.

* module_charge: fix duplicate doc block in charge.cpp init_final_scf

* module_charge: aggregate chgmixing_ks parameters into ScfMixingCtx struct (Phase 3a)

Replace 14-parameter chgmixing_ks with 7-parameter version by
grouping SCF convergence thresholds and status flags into a new
ScfMixingCtx struct, and deriving nrxx from chr.rhopw->nrxx.

- chg_routine.h: define ScfMixingCtx struct (hsolver_error, scf_thr,
  scf_ene_thr, converged_u, drho, oscillate_esolver, conv_esolver)
- chg_routine.cpp: unpack ctx members at function entry
- esolver_ks.cpp: pack ctx before call, unpack after

chg_routine.cpp score: 63 -> 70, too_many_parameters eliminated.

* module_charge: aggregate read_rho_file/read_kin_file parameters into ReadCfg (Phase 3b)

Replace 9-parameter read_rho_file and read_kin_file with 5-parameter
versions by grouping suffix, readin_dir, rank, ofs_running, ofs_warning
into a ReadCfg struct in the anonymous namespace.

chg_init.cpp score: 66 -> 70, too_many_parameters eliminated.

* module_charge: aggregate non_linear_core_correction parameters into NlcCtx (Phase 3c)

Replace 10-parameter non_linear_core_correction with 2-parameter
version by grouping all input data into a new NlcCtx struct.

chg_tools.cpp score: 96 -> 100, too_many_parameters eliminated.

* module_charge: split chg_mix.cpp into init and rho mixing files (Phase 4a)

Move mix_rho_recip, mix_rho_real, and mix_rho (440 lines) from
chg_mix.cpp into a new chg_mix_rho.cpp to eliminate file_too_long
deduction (-10).

- chg_mix.cpp: 727 -> 286 lines (constructor, set_mixing,
  init_mixing, set_rhopw, mix_reset, if_scf_oscillate,
  allocate_mixing_uom, mix_uom)
- chg_mix_rho.cpp: new file, 440 lines (mix_rho_recip,
  mix_rho_real, mix_rho)
- CMakeLists.txt: add chg_mix_rho.cpp to library and test targets

chg_mix.cpp score: 15 -> 60, now passing the 60 threshold.
32/34 files passing, module average improved.

* module_charge: split chg_drho.cpp and decompose inner product functions (Phase 4b)

Move inner_product_recip_rho and inner_product_recip_hartree from
chg_drho.cpp into a new chg_drho_inner.cpp, and decompose each
into per-nspin helper functions to reduce cyclomatic complexity.

- chg_drho.cpp: 520 -> 161 lines (cal_drho, cal_dkin,
  inner_product_real); score 49 -> 97
- chg_drho_inner.cpp: new file, 310 lines; score 100
  - inner_product_recip_rho decomposed into recip_rho_nspin1,
    recip_rho_nspin2, recip_rho_nspin4_mag helpers (CC 29 -> ~5 each)
  - inner_product_recip_hartree decomposed into
    recip_hartree_nspin2, recip_hartree_nspin4_trad,
    recip_hartree_nspin4_angle helpers (CC 37 -> ~5 each)
  - shared coulomb_sum_single extracted
- CMakeLists.txt: add chg_drho_inner.cpp to library and test targets

34/35 files passing, only chg_atomic.cpp remains below 60.

* refactor(module_charge): split atomic_rho and remove ZEROS in charge mixing

chg_atomic.cpp:
- Decompose atomic_rho (CC=60) into per-nspin helpers in
  chg_atomic_inner.cpp; CC reduced to 7, score 40->100.
- Replace all PARAM.inp.nelec/domag/domag_z/test_charge and
  GlobalV::ofs_warning with explicit AtomicRhoCfg parameter.
- Remove unused parameter.h include.
- Add chg_atomic_detail.h declaring detail helpers and RhoG3dCtx.

chg_init/chg_extra/esolver_*:
- Pass AtomicRhoCfg through call sites of atomic_rho,
  extrapolate_charge, and update_delta_rho.

Bug fixes:
- chg_drho_inner.cpp: fix duplicate const (const MixingConfig const&
  -> const MixingConfig&) and add detail:: prefix to helper calls.
- chg_mix_rho.cpp: use mixing.get()/mixing_highf.get() for unique_ptr.
- chg_tools.cpp: fix numeric -> numeric[it] in set_rho_core.

Memory safety / cleanup:
- Replace ModuleBase::GlobalFunc::ZEROS with std::fill in charge.cpp,
  chg_symm_detail.cpp, chg_tools.cpp; remove redundant ZEROS calls
  that precede full overwrites in chg_dmr.cpp and chg_mix_rho.cpp.

* Refactor: remove redundant Charge& overload of cal_rhog_symm_soc

The Charge& overload only forwarded chr.rho/chr.rhog to the raw-array
overload and had a single internal call site. Inline the member access
at that call site and drop the wrapper declaration and definition.

* module_charge: fix stale TITLE/timer labels and drop unused xc_functional.h includes

mix_tau_recip is now a free function in module_charge::detail, so update
its TITLE/timer labels from the legacy "Charge_Mixing" to "module_charge"
to match the convention of other free functions in the directory. Also
remove the unused xc_functional.h includes from chg_tau.cpp and
chg_symm_detail.cpp (label/include cleanup only, no behavior change).

* module_charge: remove redundant #ifdef __MPI guards around parallel wrappers

Parallel_Reduce::reduce_pool and Parallel_Common::bcast_double already
compile to no-op stubs when __MPI is undefined, so the outer guards add
nothing. Remove 11 such guards in chg_tools.cpp, chg_drho.cpp,
chg_drho_inner.cpp, chg_atomic_inner.cpp and chg_mix.cpp.

Guards enclosing raw MPI calls or MPI/serial dual paths are kept
(chg_parallel, chg_symm_detail, chg_routine BP_WORLD bcast, chg_extra.h).

* module_charge: decouple chg_routine from spin_constrain singleton

- forward-declare Plus_U_Base in chg_routine.h instead of including dftu_base.h
- query DeltaSpin mag_converged in ESolver_KS_PW and pass it to chgmixing_ks_pw

* module_charge: remove PARAM dependencies via explicit configuration structs

Remove the last four direct includes of parameter.h in module_charge
(chg_mix, chg_parallel, charge, chg_init) and the implicit PARAM.globalv.ks_run
read in chg_routine. INPUT values are now passed explicitly:

- MixingConfig gains scf_nmax for the drho oscillation history
- reduce_diff_pools/rho_mpi/kin_r_mpi take kpar, all_ks_run, bndpar, nspin,
  out_elf from callers instead of GlobalV::KPAR/PARAM
- Charge::kin_density/allocate/check_rho/renormalize_rho/init_final_scf take
  out_elf/test_charge/nelec as arguments with validation asserts
- new InitRhoCfg aggregates INPUT values for init_rho
- ScfMixingCtx gains ks_run; dm2rho takes nelec and drops its default
  skip_normalize argument per governance rule 5

No behavior change: save_rho_before_sum_band now uses the member nspin
set by allocate, identical to the previously read PARAM.inp.nspin.

* module_charge: restore #ifdef __MPI guards around parallel wrapper calls

The guards removed in 7a0013848 are load-bearing for serial-built unit
tests: source_estate/test strips __MPI from test translation units via
abacus_disable_feature_definitions, but links libbase built with __MPI,
whose explicit Parallel_Reduce instantiations contain real MPI calls.
Unguarded calls in the test TUs therefore bound to MPI_Allreduce and
abort with "called before MPI_INIT", failing MODULE_ESTATE_charge_test
and MODULE_ESTATE_charge_mixing.

Restore all 11 call-site guards in chg_tools.cpp, chg_atomic_inner.cpp,
chg_drho.cpp, chg_drho_inner.cpp and chg_mix.cpp. No behavior change for
MPI or serial production builds.

* Remove dead PAW compensation charge members

nhat, nhat_save in Charge and nhat_mdata in Charge_Mixing have had
no references since #6225 removed the PAW code; drop the orphaned
declarations and update the related comment.

* Refactor: remove unused Charge::prenspin member

prenspin recorded the spin-channel count read from legacy cube charge
files and drove collinear-to-noncollinear rearrangement in init_rho.
After read_rho was replaced by binary read_rhog (#5323, #5362) the
value is neither written nor read anywhere, so drop the dead member.

* Refactor: move Charge::cal_rho2ne/check_rho to module_charge free functions

- Add module_charge::check_rho in chg_tools.{h,cpp} with grid/geometry
  parameters passed explicitly; preserve all branches, thresholds and
  warning/abort messages of Charge::check_rho
- Remove the Charge::cal_rho2ne forwarding wrapper and Charge::check_rho
- Update the three esolver call sites (ks/of/double_xc) to pass rho,
  nspin, rhopw grid sizes and ucell.omega explicitly
- Drop the check_rho stubs in elecstate_pw/base tests and switch
  charge_test to the free cal_rho2ne
- Add test_chg_tools.cpp covering cal_rho2ne, total/spin-polarized
  checks, mismatch warning path and negative-channel aborts

* Refactor: remove redundant Charge::omega_ pointer

- Charge::sum_rho() now reads the cell volume from rhopw->omega, which
  is computed from the same lat0/latvec as ucell.omega and is already
  dereferenced on the same line for nxyz; this also makes the volume
  consistent with the grid rho lives on
- Drop the Charge::omega_ member, its set_omega() setter and the
  chg_init.cpp call site, removing a raw-pointer dependency on the
  UnitCell lifetime; update charge_test accordingly

Verified: MODULE_ESTATE_charge_test and MODULE_ESTATE_chg_tools pass,
elecstate library rebuilds cleanly.

* Remove dead Charge::init_final_scf and allocate_rho_final_scf

init_final_scf has had no production callers since the nscf refactor
(c6ae01236); its only remaining caller was the unit test added in
ba8b7ce9a. After the vector-backed storage refactor it was also a
broken duplicate of Charge::allocate: it never set nspin/nrxx/nxyz/
ngmc and skipped the kin_r buffers. Remove the function, its one-shot
guard flag, and the corresponding test case; destroy() now keys solely
on allocate_rho since vector storage self-manages cleanup.

* Refactor: pass rhopw explicitly to chg_init/chg_routine/chg_extra/chg_symm

Remove implicit reads of chr.rhopw/chr.ngmc from four module_charge files:
- chg_symm.cpp: size kin_g by the rho_basis used for its FFTs
- chg_routine: chgmixing_ks takes const PW_Basis&
- chg_init: orchestrator and four stage helpers take const PW_Basis&;
  the Charge::init_rho member signature is unchanged
- chg_extra: extrapolate_charge/update_delta_rho take const PW_Basis&

Call sites pass *chr.rhopw at the KS boundary or *pw_rhod where the
binding (esolver_fp.cpp chr.set_rhopw(pw_rhod)) makes them identical.
Verified: affected TUs compile and MODULE_ESTATE_charge_extra passes.

* Comments: add TODOs for LCAO+USPP double-grid follow-ups

Record the smooth/dense grid split to revisit if LCAO is ever allowed
with USPP: symmetrize_rho callers pass different grids, and the
ndx/ndy/ndz input path lacks the LCAO guard the ecutrho path has.

* Refactor: replace sticky Charge::cal_elf flag with explicit symm_kin argument

cal_elf was set to true once during ELF output and never reset, so every
later density symmetrization in the same run redundantly symmetrized
kin_r. Replace the mutable workflow flag with an explicit bool parameter
on the Charge& overload of module_charge::cal_rhog_symm:
- ctrl_output_fp passes true right before write_elf consumes kin_r
- symmetrize_rho wrapper and other callers pass XC_Functional::get_ked_flag()

Verified: full incremental build, read_wf2rho unit tests (serial/4 MPI),
write_elf logic test, and tests/01_PW/scf_out_elf (E difference 5e-10 eV,
ELF cube passes CompareFile.py at 3-decimal tolerance).

* Refactor: resolve mixing_tau at config assembly, drop XC dependency from chg_mix

esolver_ks now resolves mix_cfg.mixing_tau = inp.mixing_tau &&
XC_Functional::get_ked_flag() at the single production config assembly
point, so chg_mix/chg_mix_rho no longer query the XC global inside tau
mixing branches (6 sites). test_chg_mix mirrors the resolution in
make_cfg() and sets ked_flag before set_mixing where tau mixing is
expected. Also drop an unused xc_functional.h include from
chg_drho_inner.cpp.

Verified: full incremental build clean; MODULE_ESTATE_charge_mixing
11/11 tests pass; MODULE_ESTATE_charge/chg test suites all pass
(serial + 4-rank MPI).

* Fix: restore complete types in chg_drho_inner.cpp after include removal

Removing xc_functional.h in 87b818f4c broke compilation: the include was
load-bearing transitively, supplying the complete ModulePW::PW_Basis type
and ModuleBase::TITLE. Add the direct includes instead (pw_basis.h,
global_function.h) per IWYU.

Verified: make -j16 exits 0 with full log retained (previous verification
was invalid: a tail pipe masked both the exit code and the errors).

* Refactor: derive tau symmetrization/reduction from kin_r buffer existence

The Charge& cal_rhog_symm overload and rho_mpi/kin_r_mpi queried
XC_Functional::get_ked_flag() (plus a caller-supplied out_elf/symm_kin
flag) to decide whether to touch kin_r. Since Charge::allocate allocates
kin_r exactly when meta-GGA or ELF output needs it, both now check
chr.kin_r != nullptr directly, dropping the XC dependency and the extra
boolean parameters:
- rho_mpi/kin_r_mpi lose the out_elf parameter (2 production, 3 test
  call sites updated)
- the Charge& cal_rhog_symm overload loses the symm_kin parameter
  (ctrl_output_fp, setup_pot, read_wf2rho, update_state_rdmft revert to
  4 arguments); the raw-pointer overload now checks kin_r != nullptr
  only
- module_charge keeps XC references only in charge.cpp, chg_init.cpp,
  chg_drho.cpp (semantic "is meta-GGA" sites, resolved next)

Verified: make -j16 exit 0; 14/14 ctest charge/elecstate/read_wf2rho
tests (serial + 4-rank MPI); tests/01_PW/scf_out_elf integration case
reproduces the reference energy (-194.623411265 eV, diff 5e-10) and the
ELF cube passes CompareFile.py at 3-decimal tolerance.

* Refactor: remove module_xc dependency from module_charge (meta_gga state)

module_charge queried XC_Functional::get_ked_flag() at 5 semantic
"is meta-GGA" sites (tau TF init, tau file read, tau save, tau residual,
tau mixing resolution). Resolve the flag at upper layers instead:
- Charge::allocate takes an explicit meta_gga argument and stores it as
  object state; save_rho_before_sum_band and cal_dkin read it
- InitRhoCfg gains a meta_gga field, filled at the 3 esolver config
  assembly points (ks/of/double_xc)
- delete Charge::kin_density(); 6 esolver call sites inline
  get_ked_flag() || (out_elf[0] > 0) for buffer allocation and pass
  get_ked_flag() as meta_gga; non-SCF allocations pass false
- charge_test mirrors the inline expression

module_charge now has zero references to module_xc.

Verified: make -j16 exit 0 (full log); 14/14 charge/elecstate/
read_wf2rho ctests (serial + 4-rank MPI), including the mGGA tau mixing
and tau-save branches; tests/01_PW/scf_out_elf reproduces reference
energy (-194.623411265 eV, diff 5e-10) and the ELF cube passes
CompareFile.py at 3-decimal tolerance. A SCAN integration case
(205_PW_SCAN) still requires a libxc-enabled build/CI run.

* Fix: allow null rho buffers on ranks with empty real-space grid partition

pack_rho_mag/unpack_rho_mag in chg_rho_detail.h quit whenever any buffer
pointer is null. A rank may legitimately own zero real-space grid points
(nrxx == 0) when the grid is decomposed across more processes than it has
z-slabs (e.g. a 3x3x3 big-cell grid on 4 processes leaves one rank with
no slab); its zero-sized vectors then return null data() pointers even
though the packing loops perform no access. The unconditional check made
LCAO nspin==2 real-space mixing abort with "pack_rho_mag pointer is null"
on such ranks.

Restrict the null-pointer check to n > 0, matching the convention already
used by Parallel_Grid::reduce (only a null buffer with a non-zero size is
a genuine bug). n < 0 remains a hard error. Regression introduced in
d9685d4eb when the inline packing loops were extracted into these helpers.

* Refactor: move rhog_io into module_charge as chg_rhog_io

Relocate source_estate/rhog_io.{h,cpp} to source_estate/module_charge/
under the module_charge namespace, rename include guard to CHG_RHOG_IO_H,
and update the warning tags emitted at runtime. Update both callers
(chg_init.cpp, esolver_fp.cpp) and build files; adapt test_rhog_io.cpp in
place ahead of its move in a follow-up commit. No behavior change.

* Refactor: create module_charge/test with the rhog io unit test

Move test_rhog_io.cpp into module_charge/test/test_chg_rhog_io.cpp with
its support data charge-density.dat, register the new test subdirectory,
and rename the target to MODULE_CHARGE_rhog_io. Remove the migrated
AddTest block from the legacy source_estate/test/CMakeLists.txt.

* Refactor: move charge and charge-extra unit tests into module_charge/test

Rename charge_test.cpp to test_charge.cpp and charge_extra_test.cpp to
test_chg_extra.cpp per the test naming rule, move prepare_unitcell.h
alongside its only users, and register MODULE_CHARGE_charge /
MODULE_CHARGE_extra in the module_charge test CMakeLists. No test data
moves: prepare_unitcell.h only sets file-name strings at runtime, and
the extra test only writes cube files into ./support/.

* Refactor: move mix, parallel and tools unit tests into module_charge/test

Relocate test_chg_mix.cpp (fixing its relative includes), test_chg_parallel.cpp
and test_chg_tools.cpp into module_charge/test, register MODULE_CHARGE_tools /
MODULE_CHARGE_mix / MODULE_CHARGE_parallel with the 4-process mpirun test,
and drop the migrated blocks from the legacy source_estate/test CMakeLists.

* Refactor: rename module_charge test dir to unittests and wire CI for it

Rename source_estate/module_charge/test to unittests (relative CMake
paths are immune to the move). Sync the referencing points: the
add_subdirectory call, the coverage lcov filter (add '*/unittests/*' so
test sources stay excluded from the report), a dedicated Module_Charge
ctest step in test.yml with MODULE_CHARGE added to the catch-all -E
list to avoid double execution, and unittests/ added to the
code_quality_score.py SKIP_DIRS.

* Fix: pass ucell.omega to Charge::sum_rho/renormalize_rho to fix NPT stress

Root cause: commit 34b441e1c ("Refactor: remove redundant Charge::omega_
pointer") changed Charge::sum_rho() to read the cell volume from
rhopw->omega instead of ucell.omega. In variable-cell calculations (NPT),
pw_rho/pw_rhod are NOT rebuilt on cell change (only pw_wfc is), so
rhopw->omega keeps the initial cell volume while ucell.omega is updated
every MD step. The stale volume made sum_rho() return a wrong electron
count, which made renormalize_rho() scale rho by the wrong factor,
corrupting the stress (deviation ~0.002 in 095_PW_NPT) while the total
energy stayed near-correct (variational, second-order sensitive).

Fix: add an explicit omega parameter to Charge::sum_rho() and
renormalize_rho(); all call sites (init_scf, chg_routine, LCAO dm2rho path
through HSolverLCAO/dmToRho, RDMFT update_charge, OFDFT renormalize_psi)
now pass ucell.omega. This mirrors the existing check_rho(..., ucell.omega)
pattern.

Also mark three other rhopw->omega users with BUG(investigate) comments:
get_local_pp_energy, cal_delta_escf, and Makov-Payne correction. These are
pre-existing and were not changed by the refactor; they may have the same
stale-volume issue in NPT and should be investigated separately.

Bisected to 34b441e1c over the 20260916 module_charge refactor branch.

* Fix: add omega arg to remaining dm2rho call sites

Missed four LCAO_domain::dm2rho call sites in the previous commit:
- lcao_set.cpp init_chg_dm (skip_normalize=true, omega unused)
- esolver_dm2rho.cpp
- esolver_ks_lcao_tddft.cpp weight_dm_rho
- module_dm/init_dm.cpp

All now pass ucell.omega.

* Fix: restore HamiltHSMatrix hs declaration in cal_mw_from_lambda

Accidentally removed the line while editing the comment.

* Fix: close_kerker_gg0 actually disables Kerker; drop dead mixing_gg0 members

The chg_precond refactor (commit 6d127d517) made the Kerker kernels read
cfg_ (immutable INPUT snapshot) instead of Charge_Mixing members, but
close_kerker_gg0() kept writing the now-dead mixing_gg0/mixing_gg0_mag
members. As a result, the non-separate-loop EXX path in exx_lri_interface.hpp
silently failed to disable Kerker after convergence.

Fix: add a kerker_disabled_ flag on Charge_Mixing that the mix_rho_recip/
mix_rho_real screening lambdas short-circuit on. The flag lives on the
object, not in cfg_, so the immutable INPUT snapshot invariant is preserved.

Also drop the now-dead members mixing_gg0/mixing_gg0_mag/mixing_gg0_min/
mixing_angle/mixing_dmr and the get_mixing_gg0() getter; set_mixing/init_mixing
now read these from cfg_ directly. Add CloseKerkerGg0DisablesScreenReal
regression test that compares close_kerker_gg0() output against the
cfg.mixing_gg0=0 baseline and proves the flag is load-bearing.

* Fix: relax over-strict null-buffer asserts for empty grid partitions

reduce_diff_pools and Parallel_Grid::reduce_across_pools still forbade
null buffers unconditionally, contradicting the rule documented at
parallel_grid.cpp:355-360. A rank with nrxx == 0 may legitimately hold
a null rho/kin_r pointer; the MPI calls below use count 0 and ignore
the buffer. Align both call sites with the documented rule.

* Fix: relax over-strict null-buffer assert in ParaRgridWorld::reduce_across_pools

Same pattern as the previous fix: a rank with nrxx == 0 legitimately
holds a null buffer, and MPI_Allreduce with count 0 ignores it.
Align with the rule documented at parallel_grid.cpp:355-360.

* Fix: allow nnr == 0 in DMR mixing for empty MPI partitions

nnr is local to each MPI rank and may legitimately be zero when no
atom pairs survive the cutoff on that rank. The previous check
aborted DMR mixing for such distributions, whereas the historical
implementation allowed empty blocks. Relax the guard in
check_dmr_inputs() and init_mixing_dmr() to reject only negative
nnr, and require non-null DMR buffers only when nnr > 0, matching
the established nrxx == 0 convention in module_charge.

* Fix: split reciprocal rho copy from real-space |m| rescale in mix_rho_recip

The nspin==4 && mixing_angle>0 branch of mix_rho_recip mixed two
distinct operations in one loop bounded by npw, but rho_magabs is
sized nrxx (real-space) and the new |m| is written back by
recip2real into rho_magabs[0..nrxx-1]. Reading rho_magabs[npw+ig]
goes out of bounds once npw+ig >= nrxx (AddressSanitizer reproduces
with nrxx=125, npw=93) and the loop bound npw leaves the real-space
tail [npw, nrxx) of {mx,my,mz} unscaled. Split into two loops: the
reciprocal rho copy stays bounded by npw, the magnetization rescale
is bounded by nrxx and reads rho_magabs[ir].

* Refactor: remove unused Charge_Mixing::conserve_setting

conserve_setting() was introduced by 420f1ad00 (DeltaSpin feature
merge, 2026-06-15) but never wired up: no production caller, no
test reference, and the DeltaSpin module does not touch
Charge_Mixing. Drop the dead declaration per the project rule that
unused functions and their tests be removed.

* Refactor: drop dead Charge_Mixing::tpiba2 member

tpiba2 was declared in chg_mix.h but never assigned by set_mixing()
nor read anywhere in the module. Grep across the whole source tree
confirms all tpiba2 references are either ucell.tpiba2 (a separate
UnitCell member) or local variables in unrelated modules. The
Charge_Mixing class never computed or used its own tpiba2 pointer;
only tpiba is consumed by the stateless Kerker kernels via
mix_rho_recip/mix_rho_real. Remove the dead declaration.

* Refactor: route Charge_Mixing getters through cfg_

get_mixing_mode(), get_mixing_beta(), get_mixing_ndim() previously
returned the legacy mirror members that set_mixing() kept in sync
with cfg_ by hand. With cfg_ now treated as the immutable INPUT
snapshot, route the public getters through cfg_ directly so there
is a single source of truth for INPUT parameters. External callers
(esolver_ks_lcao, lcao_others, pw_others) are unaffected since
signatures are unchanged. The legacy members remain in place for
now; they are dropped in a later step after internal readers are
migrated.

* Refactor: init_mixing constructs Mixing from cfg_ not legacy mirrors

init_mixing() branched on this->mixing_mode and passed
this->mixing_ndim/mixing_beta to the Broyden/Pulay/Plain_Mixing
constructors. These legacy mirrors were kept in sync with cfg_
manually by set_mixing(). Route through cfg_ directly so cfg_
remains the single source of INPUT parameters. The Mixing objects
themselves still copy beta/ndim into their own members at
construction; that is a one-time snapshot and not a continuous
sync surface, so it is left untouched.

* Refactor: mix_rho_recip/mix_rho_real read mixing_beta from cfg_

Both mix_rho_recip and mix_rho_real built the twobeta_mix functor by
reading this->mixing_beta / this->mixing_beta_mag, which are legacy
mirrors that set_mixing() kept in sync with cfg_. Route the six
construction sites through cfg_.mixing_beta / cfg_.mixing_beta_mag
so cfg_ is the single source of INPUT parameters consumed by the
mixing logic. Behavior is unchanged since the mirrors and cfg_
hold identical values after set_mixing().

* Refactor: set_mixing stops mirroring cfg_ into legacy members

set_mixing() copied mixing_mode, mixing_beta, mixing_beta_mag,
mixing_ndim from cfg into legacy mirror members, then validation
and logging read from the mirrors. Now that all internal readers
(init_mixing, mix_rho_recip, mix_rho_real, getters) read from
cfg_, the mirror writes are dead work. Drop them and route
validation and log output through cfg_ directly. omega and tpiba
remain pointer members because they alias external runtime state
(cell volume, lattice constant) that changes across SCF iterations
and so do not belong in MixingConfig (an immutable INPUT snapshot).

* Refactor: drop legacy Charge_Mixing mirror members; cfg_ is single source

Drop mixing_mode, mixing_beta, mixing_beta_mag, mixing_ndim mirror
members. After the previous commits every internal reader (getters,
init_mixing, mix_rho_recip, mix_rho_real, set_mixing validation
and log output) routes through cfg_, so the mirrors are dead state
that set_mixing() no longer writes. cfg_ is now the single source
of truth for INPUT mixing parameters.

Update test_chg_mix.cpp accordingly: the two assertions that
reached directly into CMtest.mixing_beta_mag and CMtest.mixing_mode
now read CMtest.get_mixing_config().mixing_beta_mag and
CMtest.get_mixing_mode(), matching the public API used by the
other assertions in the same block. No production caller accessed
these members directly (esolver_ks_lcao, lcao_others, pw_others
all used the getters), so the change is test-only on the consumer
side.

* Refactor: drop NSDMI from MixingConfig to force explicit construction

The non-static data member initializers in MixingConfig provided
plausible-looking defaults (e.g. mixing_beta=0.8, mixing_mode=
"broyden") that silently masked forgotten fields when a new field
was added but not wired up at construction sites. With the
defaults removed, every construction site must use aggregate
initialization (or copy-assign from a fully-initialized instance),
and a missing field yields value-initialized (zero/empty) members
that are far more likely to trip a test than the old defaults.
Combined with -Wmissing-field-initializers promoted to error in
the next commits, adding a field to MixingConfig without updating
all aggregate-initialization sites becomes a compile error.

* Refactor: aggregate-init MixingConfig in esolver_ks with pragma guard

Convert the 17-line field-by-field assignment of mix_cfg into a
single aggregate initialization in declaration order. Wrap it in
#pragma GCC diagnostic error "-Wmissing-field-initializers" so
that adding a field to MixingConfig without updating this list
becomes a compile error rather than silently using a default.
Each initializer is annotated with the field name it corresponds
to, making the declaration-order dependency auditable at a glance.

* Refactor: aggregate-init MixingConfig in test_chg_mix with pragma guard

Convert make_cfg()'s 17-line field-by-field assignment into a
single aggregate initialization in declaration order, matching
the esolver-side change. Wrap in the same
#pragma GCC diagnostic error "-Wmissing-field-initializers" so
that adding a field to MixingConfig without updating the test
helper is also a compile error. Both construction sites (esolver
and test) now fail at compile time if a field is missing, closing
the maintenance gap where a new field could silently fall back to
a default value.

* Fix: fail-fast guards in Charge_Mixing and update chg_mix tests

Add validation to turn latent misuse (skipped set_rhopw/set_mixing)
into clear WARNING_QUIT errors instead of null dereference or heap
corruption:
- init_mixing rejects a null rhopw
- if_scf_oscillate checks scf_nmax > 0 and iteration range
- mix_rho validates chr/chr->rhopw and the grid pointers

Fix three chg_mix unit tests that read cfg_ before set_mixing, which
caused a SIGSEGV in SCFOscillationTest and assertion failures in the
two inner-product tests.

* test(module_charge): add unit tests for chg_uspp and chg_dmr

Add test_chg_uspp.cpp covering split_dgrid/merge_dgrid (normal split,
round-trip, nspin=1/2, empty high-frequency/smooth boundaries, and
input-validation abort paths).

Add test_chg_dmr.cpp covering init_mixing_dmr/mix_dmr (nspin=1/2/4
mixing with Plain_Mixing analytically verified, empty-partition null
buffer allowance, and input-validation abort paths).

Wire both targets into unittests/CMakeLists.txt.

* test(module_charge): add unit tests for chg_precond, chg_drho, chg_drho_inner, chg_mix_rho

- test_chg_precond.cpp: kerker_screen_recip/real (early return, nspin=1/2/4
  filter, nspin=4 with mixing_angle resize, real-space matches reciprocal).
- test_chg_drho.cpp: inner_product_real, cal_drho real-space path
  (nspin=1/2/4+domag_z), cal_dkin (meta_gga false/true).
- test_chg_drho_inner.cpp: inner_product_recip_rho and
  inner_product_recip_hartree for nspin=1 with a single G component,
  analytically verified against the Coulomb metric.
- test_chg_mix_rho.cpp: mix_rho abort paths (null chr/chr->rhopw, unset
  rhopw, double_grid without rhodpw) and real-space plain mixing value.

Wire all four targets into unittests/CMakeLists.txt.

* test(module_charge): add unit tests for chg_symm, chg_symm_detail, chg_atomic, chg_atomic_inner

- test_chg_symm.cpp: symmetrize_rho / cal_rhog_symm / cal_rhog_symm_soc
  no-op paths when symm_flag == 0, for nspin=1 and nspin=4.
- test_chg_symm_detail.cpp: psymmg and psymmg_soc idempotence on a
  manually built D_4 point group over a serial cubic PW_Basis.
- test_chg_atomic_inner.cpp: compute_rhoatm USPP direct-copy branch and
  NCPP integrate+scale-to-zv branch (Gaussian rho_at with known analytic
  integral); normalize_and_check renormalizes uniform density to nelec.
- test_chg_atomic.cpp: atomic_rho ntype==0 path (skips atom loop) and
  spin_number_need==3 abort path.

Wire all four targets into unittests/CMakeLists.txt.

* test(module_charge): add chg_tau/chg_routine/chg_init tests; drop spurious XC_Functional stubs

Fourth batch of module_charge unit tests:
- test_chg_tau.cpp: mix_tau_recip abort paths (null chr/grid/mixing, nspin<1,
  double_grid without high-f mixer) and non-double-grid plain mixing value.
- test_chg_routine.cpp: chgmixing_ks_pw/lcao iter==1 restart-step setup, and
  chgmixing_ks convergence branches (conv_esolver true / drho<hsolver_error
  skip mix_rho).
- test_chg_init.cpp: init_rho "wfc" with null wfcpw abort, and "atomic" with
  ntype==0 + meta_gga Thomas-Fermi tau initialization.
Wire all three targets into unittests/CMakeLists.txt.

Cleanup: remove the XC_Functional::func_type / ked_flag definitions from
test_chg_drho, test_chg_mix_rho, test_chg_symm, test_chg_atomic_inner,
test_chg_atomic, test_chg_tau, test_chg_routine, and test_chg_init. None of
the non-test sources compiled into these targets reference these statics
(charge.cpp, chg_*.cpp, and the linked base/cell_info/planewave_serial/
symmetry libraries are clean), so the definitions were pure dead weight.
Also drop the now-unneeded xc_functional.h include from test_chg_mix_rho.cpp
and correct the stub comments.

* test(module_charge): fix broken includes in unit tests

- include chg_atomic_detail.h instead of nonexistent chg_atomic_inner.h
  in test_chg_atomic_inner.cpp; add math_integral.h for Simpson_Integral
- include chg_drho.h in test_chg_drho_inner.cpp for
  module_charge::inner_product_recip_hartree
- include source_cell/magnetism.h in tests that define Magnetism stubs
  (test_chg_drho, test_chg_symm, test_chg_tau, test_chg_mix_rho)
- fix nonexistent source_charge/mixing includes in test_chg_tau.cpp to
  source_base/module_mixing

* test(module_charge): fix link/build issues; temporarily disable routine/init targets

- test_chg_tau: use Plain_Mixing(beta) ctor and init_mixing_data with
  complex type_size (old set_mixing_beta/init_mixing no longer exist)
- test_chg_symm_detail: add Magnetism stub required by cell_info's
  unitcell.cpp, matching other tests in this directory
- test_chg_routine: adapt to two-arg set_rhopw and tpiba from ucell
- disable MODULE_CHARGE_routine and MODULE_CHARGE_init targets with
  documented reasons: their transitive dependencies (Plus_U_Base,
  elecstate, source_io) are deeply coupled; to be resolved later

* fix bug

* format tool_quit

* remove a test due to WARNING_QUIT funcitno

* delete the support file charge-density.dat because unittests never need it

---------

Co-authored-by: abacus_fixer <mohanchen@pku.eud.cn>
Co-authored-by: Xiaoyang Zhang <tsfxwbbzxy@163.com>
mohanchen pushed a commit to mohanchen/abacus-mc that referenced this pull request Sep 22, 2026
…eling#7998)

Redo of the work in deepmodeling#7988 and deepmodeling#7990, both of which were closed while the
charge density module was being restructured. That restructuring landed in
deepmodeling#7972 and already did most of the decoupling those PRs proposed: allocate(),
renormalize_rho() and sum_rho() now take their inputs explicitly, the mixing
parameters are aggregated in a MixingConfig, and chg_mix.cpp / chg_drho.cpp /
charge.cpp are free of global parameter reads. What was left was the
test-side access.

Production changes are additive only - no existing signature moves and no
line is deleted from any production header:

  Charge::get_allocate_rho()          - report whether allocate() has run
  Charge_Mixing::get_rho_mdata()      - mirror the existing get_dmr_mdata()
  Charge_Mixing::get_tau_mdata()
  Charge_Mixing::set_mixing_config()  - pair for the existing getter, for
                                        callers that must update the snapshot
                                        without rebuilding the mixing history
  XC_Functional::set_func_type()      - pair for get_func_type()
  XC_Functional::set_ked_flag()       - pair for get_ked_flag()

Test changes:

  test_dm_r_init   - two sites move to the already public get_DMR_save()
  test_charge      - the global parameter scratchpad becomes fixture state
                     (32 refs -> 0); PW_Basis setup goes through the public
                     initgrids/initparameters/setuptransform sequence instead
                     of the protected distribute_r()/distribute_g()
  test_chg_mix     - the scratchpad becomes a fixture-owned MixingConfig
                     (163 refs -> 0); the three blocks that hand-wired
                     Charge::_space_* now take their buffers from the fixture,
                     which owns them as vectors and points the public
                     rho/rhog/kin_r views at them with the same stride

No expected value or tolerance was changed.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
mohanchen added a commit that referenced this pull request Sep 22, 2026
* Remove unneeded README20260902 file

* Fix build without LibRI: split BvK utils out of ri_util.h

module_lr is built whenever ENABLE_LCAO is on, but lr_io_krlist.cpp
unconditionally included module_ri/ri_util.h, which pulls in LibRI
headers (<RI/global/Array_Operator.h>) and fails to compile when
ENABLE_LIBRI is off (regression from #7849).

Move the LibRI-free Born-von Karmen helpers (get_Born_vonKarmen_period,
get_Born_von_Karmen_cells) into a new header ri_util_bvk.h; ri_util.h
now includes it, and lr_io_krlist.cpp includes only the new header.

Verified: target lr builds with ENABLE_LIBRI=OFF (build/), target ri
builds with ENABLE_LIBRI=ON (build_std_para/).

* Fix timer_enable_nvtx: define __USE_NVTX on the targets that consume it

__USE_NVTX was defined only on the final executable target, whose sole
translation unit main.cpp contains no NVTX code. The two OBJECT libraries
that actually guard NVTX calls with the macro -- base (source_base/timer.cpp)
and driver (source_main/driver.cpp) -- never saw it, so every NVTX block was
preprocessed away and timer_enable_nvtx had no effect in any CUDA build.

Move the definition onto base and driver, and link CUDA::nvToolsExt for
CUDA toolkits older than 12.9 (NVTX is header-only since 12.9).

Verified with build_pw_gpu (USE_CUDA=ON): base/driver targets compile with
NVTX symbols present in timer.cpp.o, driver.cpp.o references
timer::enable_nvtx_, and the full abacus_pw_gpu executable links
(v3.11.0-beta9).

* Refactor charge density module (#7972)

* module_charge: normalize indentation and brace single-statement control flow

Mechanical cleanup as the first step of the module_charge governance
refactor: convert leading tabs to 4-space indentation (1011 occurrences
across 11 files) and add braces around all single-statement if/for/while
bodies (11 sites). No functional change.

* module_charge: aggregate Charge_Mixing params into MixingConfig

Introduce a MixingConfig POD that bundles the INPUT mixing parameters
with the runtime globals (nspin, scf_thr_type, double_grid), and change
set_mixing from a 12-argument interface to set_mixing(const MixingConfig&,
double&, double&). Charge_Mixing now stores the config and reads nspin /
scf_thr_type / double_grid from it instead of PARAM.inp / PARAM.globalv,
removing the direct PARAM reads in set_mixing and init_mixing.

The single production call site (esolver_ks.cpp) fills the config, and
the unit test drives set_mixing via a make_cfg() helper. The
'#define private public' access hack is kept for now with a TODO: the
test still must write Parameter::input/sys, Charge::_space_* and
XC_Functional privates, which need the Step 4/5 global-state
parameterization before it can be removed.

Verified: make -j30 MODULE_ESTATE_charge_mixing (build_max_para_test)
passes with no errors.

* module_charge: deduplicate twobeta_mix lambdas and replace raw new with std::vector

Extract the repeated two-beta mixing functor in mix_rho_recip/mix_rho_real
into a make_twobeta_mix<T> template helper (6 lambda copies removed), and
convert all local raw new[]/delete[] buffers in charge_mixing_rho.cpp to
zero-initialized std::vector, dropping the paired ZEROS calls.

* module_charge: move residual/inner-product globals into MixingConfig

Extend MixingConfig with gamma_only_pw/domag/domag_z so mix_resid.cpp
(get_drho, get_dkin, inner_product_recip_{rho,simple,hartree,real}) no
longer reads PARAM/GlobalV; all branches now consume this->cfg_.
inner_product_recip_rho's raw pointer-array views are switched to
std::vector. Production fills the three new fields in esolver_ks, and
the test fixture gains a sync_cfg() helper to push PARAM mutations into
cfg_ for the inner-product branch tests.

* module_charge: own Charge's _space_* storage with std::vector (Step 5a)

Replace the six private raw _space_rho/_space_rho_save/_space_rhog/
_space_rhog_save/_space_kin_r/_space_kin_r_save buffers with
std::vector, so Charge's underlying contiguous storage self-manages and
the matching delete[] calls in destroy() (which relied on reading
possibly-uninitialized pointers) go away. The public rho/rhog/rho_save/
rhog_save/kin_r/kin_r_save views keep their double**/complex** shape and
still alias the vector memory via .data(), so all external consumers are
unaffected. Tests that drove _space_* directly are adapted to
resize()/.data() and drop their manual delete[] of the buffers.

* module_charge: route chgmixing_ks through its inp parameter

chgmixing_ks already takes a const Input_para& inp but still read
PARAM.inp.mixing_restart / PARAM.inp.scf_nmax from the global. Use the
inp argument instead so the function no longer reads INPUT state through
the global for these two fields. PARAM.globalv.ks_run is a runtime
per-process flag (set from band-parallel topology), not an input, so it
is intentionally left as-is rather than threading it through the
interface.

* module_charge: split Charge::init_rho into per-stage private methods

init_rho had a cyclomatic complexity of 36 from five sequential stages
(file read, atomic fallback, Thomas-Fermi tau, restart load, wfc read)
interleaved through shared read_error/read_kin_error flags. Extract the
four branches into private methods -- read_rho_from_file,
init_rho_atomic_and_tau, load_rho_from_restart, init_rho_from_wfc -- and
leave init_rho as a thin sequence of stage calls. Logic is unchanged; the
error flags are threaded through as parameters. The deepest stage
(read_rho_from_file) now sits at complexity 19, down from 36 for the
monolith. The remaining global reads inside the stages are untouched and
deferred to a later parameterization step.

* module_charge: extract Charge density math into charge_math free functions

sum_rho, cal_rho2ne and non_linear_core_correction each used Charge
members only to reach a handful of scalars (nrxx/nxyz/omega) or the
reciprocal-shell table (gg_uniq/ngg); the rest of each body is pure
numerics. Move the three bodies into a new charge_math namespace as free
functions with those values passed explicitly, and leave the Charge
members as thin forwarding wrappers so no caller outside the module
changes. The kernels are now unit-testable in isolation and no longer
coupled to Charge state. One behavior note: the pre-quit debug line that
printed sum_rho to ofs_warning is dropped so the free function stays free
of global-stream dependencies. charge_math.cpp is wired into the estate
library and the charge_test target.

* module_charge: register charge_math.o in the hand-written Makefile build

The CMake build already picks up charge_math.cpp; mirror that in
Makefile.Objects so the legacy Makefile flow links the new charge_math
kernels too. The module_charge directory is already on VPATH, so adding
charge_math.o to the object list is sufficient.

* module_charge: extract Charge::atomic_rho into charge_atomic free function

Remove Charge::atomic_rho entirely and replace all call sites with
module_charge::atomic_rho(..., rhopw), eliminating the need for a thin
wrapper on the Charge class. This decouples atomic density initialization
from Charge's state and improves charge.cpp quality score from 2 to 44.

* module_charge: forbid Charge copies and guard tau.cube write

scf_out_chg_tau aborted in Parallel_Grid::reduce on
assert(rhoin != nullptr) because the kin_r_save[is] handed to
write_vdata_palgrid was not a valid buffer. After the _space_* storage
became std::vector (ecf5084d4), a copied/moved Charge leaves its
rho/kin_r views dangling into another object's vector buffer, and a
kin_r_save never allocated (ked_flag set after allocate) stays nullptr;
both surface as a null rhoin deep inside MPI gather instead of at the
source.

Delete Charge's copy constructor/assignment so any value copy of the
vector-aliasing views fails at compile time, and check kin_r_save in
ctrl_output_fp before writing tau.cube so a missing allocation reports
a clear message instead of tripping the MPI assert.

Verification: not run locally (per user request, user compiles).

* module_base: tolerate null grid buffer when a rank owns no grid points

scf_out_chg_tau (LCAO, SCAN, out_chg=1, 4 MPI ranks) aborted in
Parallel_Grid::reduce on assert(rhoin != nullptr). Bisecting between
83eb5d0f3 (good) and ecf5084d4 (bad) isolated the regression to
ecf5084d4, which moved Charge's _space_* storage from raw new[] to
std::vector.

Root cause: with 4 ranks the FFT grid is slab-decomposed so that the
last rank owns zero real-space points (nrxx == 0, confirmed via a
temporary diagnostic printing fn/is/rank/nrxx at the reduce call site).
Before ecf5084d4, _space_rho = new double[nspin * 0] == new double[0]
returned a unique non-null pointer, so rho_save[is] was non-null and the
assert passed. After the change, an empty vector's .data() returns
nullptr, so the rank with nrxx == 0 handed a null rhoin to reduce and
tripped the assert (Debug) or fed MPI_Gatherv a null buffer (Release).

A rank with nrxx == 0 is legitimate: MPI_Gatherv is invoked with
sendcount 0 and ignores the send buffer. Relax the assert to only flag a
null buffer when nrxx != 0, and revert the now-unneeded kin_r_save guard
in ctrl_output_fp (it would have falsely aborted on the nrxx == 0 rank).

Verification: Release build (build_max_para_test), ran
  cd tests/03_NAO_multik/scf_out_chg_tau &&
  OMP_NUM_THREADS=1 mpirun -np 4 ../../../build_max_para_test/abacus_max_para
Result: exit 0, chg.cube and tau.cube written; numerical comparison
against chg.cube.ref/tau.cube.ref gives maxdiff 0 (chg) and 1e-14 (tau).

* module_charge: extract Charge::set_rho_core into charge_math free function

Move set_rho_core to charge_math::set_rho_core with rho_core,
rhog_core and rhopw passed explicitly instead of reading Charge
state, and call charge_math::non_linear_core_correction directly.
Remove the now-unused Charge::non_linear_core_correction wrapper,
use std::vector for the rhocg/vg scratch buffers, update the
init_scf call site, and drop the obsolete member stubs in the
elecstate unit tests.

* module_charge: vectorize Charge_Extra history arrays and forbid copies

Replace the raw new[]/delete[] displacement arrays (dis_old1, dis_old2,
dis_now) with std::vector and remove the hand-written destructor. This
fixes a read of uninitialized pot_order when an object is destroyed
before Init_CE, a memory leak when Init_CE is called repeatedly, and a
double-free risk from the implicitly generated shallow copy. The copy
constructor and copy assignment are deleted so the molecular-dynamics
trajectory history cannot be silently forked. The unit test now checks
vector sizes instead of non-null pointers.

* Rename charge_math to chg_tools and unify namespace module_charge

- Rename module_charge/charge_math.{h,cpp} to chg_tools.{h,cpp} via git mv
- Change namespace charge_math to module_charge to match charge_atomic
  and chgmixing in the same directory
- Update include guard CHG_TOOLS_H and TITLE/timer labels accordingly
- Update call sites in init_scf.cpp, charge.cpp, charge_init.cpp
- Update build references in Makefile.Objects and both CMakeLists.txt

* module_charge: refactor Symmetry_rho class to free functions

Convert the stateless class Symmetry_rho into namespace module_charge
free functions and rename files for consistency:
  symm_rho.{h,cpp}      -> chg_symm.{h,cpp}
  symm_rho_detail.h     -> chg_symm_detail.h
  symm_rhog.cpp         -> chg_symm_detail.cpp

- 5 public functions become module_charge::symmetrize_rho / cal_rhog_symm
  (2 overloads) / cal_rhog_symm_soc (2 overloads)
- 2 cross-TU helpers (psymmg/psymmg_soc) moved to module_charge::detail
  via chg_symm_detail.h
- 3 internal MPI helpers moved to anonymous namespace
- Delete dead code psymm (real-space symmetrization, never called)
- Remove empty ctor/dtor and parallel_grid.h include
- Rename begin/begin_soc to cal_rhog_symm/cal_rhog_symm_soc for clarity
- Update timer/TITLE labels from "Symmetry_rho" to "module_charge"
- Migrate all 14 call sites and 1 test stub
- Remove obsolete Makefile special rule (no more name collision)

* module_charge: extract MixingConfig header and drop unused inner_product_recip_simple

Move MixingConfig from charge_mixing.h into its own mixing_config.h so
stateless residual kernels can include the config without dragging in
Charge_Mixing. Remove inner_product_recip_simple, which had no production
call sites, together with its unit test.

* module_gint: move gint_prec_ctrl from module_charge

Relocate gint_prec_ctrl.{h,cpp} and its test into module_gint, update the
include in esolver_ks_lcao.h and rewire the CMake/Makefile object lists.

* module_charge: extract mixing inner products into chg_drho free functions

Rename mix_resid.cpp to chg_drho.cpp and turn inner_product_real and
inner_product_recip_hartree into module_charge free functions declared
in chg_drho.h; inner_product_recip_rho, which is only shared with the
unit test, moves to module_charge::detail in chg_drho_detail.h.
Charge_Mixing loses the three private inner-product members and
mix_rho_recip/mix_rho_real bind the free functions through lambdas.
get_drho/get_dkin stay as members for this step.

* module_charge: hide cal_drho/cal_dkin in an anonymous namespace

Move the get_drho/get_dkin implementations into file-local cal_drho/
cal_dkin free functions with all inputs explicit; the public
Charge_Mixing methods become thin forwarding wrappers so esolver call
sites stay unchanged.

* module_gint: fix include path in test_gint_prec_ctrl after relocation

* module_charge: extract Kerker screen kernels into chg_precond free functions

Move Charge_Mixing::Kerker_screen_recip/real to module_charge namespace
as free functions in chg_precond.{h,cpp}, renaming mix_precond.cpp via
git mv. Config/grid/geometry are passed explicitly via MixingConfig,
PW_Basis*, and tpiba, eliminating the function's direct read of
PARAM.inp.nspin. Replace 8 std::bind call sites in charge_mixing_rho.cpp
with lambdas, update 2 commented-out bind sites in charge_mixing_dmr.cpp,
and rewrite 12 test call sites in charge_mixing_test.cpp to construct an
independent MixingConfig instead of poking at Charge_Mixing privates.
Drop the now-unused member function declarations from charge_mixing.h.

* module_charge: fix Makefile.Objects after mix_precond -> chg_precond rename

Update the non-CMake object list to track the renamed translation unit so
make-based builds do not reference the deleted mix_precond.o.

* module_charge: drop Charge_Mixing::get_drho/get_dkin wrappers

Expose cal_drho/cal_dkin as module_charge free functions in chg_drho.h
and let ESolver_KS call them directly with explicit arguments; add
Charge_Mixing::get_mixing_config() as a const observer for the config.

* module_charge: rename chgmixing.h/cpp to chg_routine.h/cpp

Align with the chg_<feature> naming pattern used in the same directory
(chg_drho, chg_precond, chg_symm, chg_tools). Update include guard to
CHG_ROUTINE_H, the self-include in chg_routine.cpp, the entry in
source_estate/CMakeLists.txt and source/Makefile.Objects, and the three
#include sites in esolver_ks{,_pw,_lcao}.cpp. Function names
(chgmixing_ks{,_pw,_lcao}) and TITLE/timer tags are intentionally left
unchanged to keep the diff minimal.

* module_charge: rename mixing_config.h to chg_mix_cfg.h

Rename the MixingConfig header to align with the chg_* naming
convention in module_charge. Update the include guard and the four
in-tree includers; no CMake change is needed since the header is not
listed explicitly.

* module_charge: convert Charge MPI helpers into chg_parallel free functions

Rename charge_mpi.cpp to chg_parallel.cpp and add chg_parallel.h, moving
the three stateless Charge member functions (reduce_diff_pools, rho_mpi,
kin_r_mpi) to module_charge namespace free functions that take the
Charge object explicitly. Remove their declarations from charge.h and
update all call sites in elecstate_pw, stress_mgga, read_wf2rho_pw and
sto_iter. Rename the unit test to test_chg_parallel.cpp and update the
test target name accordingly.

GlobalV/PARAM reads and the direct MPI_Allreduce in reduce_diff_pools
are preserved as pre-existing technical debt (migration-neutral).

* Rename charge_atomic files to chg_atomic

- Rename module_charge/charge_atomic.{h,cpp} to chg_atomic.{h,cpp}
- Update include guard to CHG_ATOMIC_H
- Update includes in charge_init.cpp and charge_extra.cpp
- Update source paths in CMakeLists.txt, test CMakeLists.txt
- Fix stale object names in Makefile.Objects: replace
  symm_rho_charge.o/symm_rhog.o with chg_symm.o/chg_symm_detail.o

* module_charge: extract USPP double-grid split/merge into chg_uspp free functions

Introduce module_charge::split_dgrid / merge_dgrid in chg_uspp.{h,cpp} as
RAII, parameter-explicit replacements for Charge_Mixing::divide_data /
combine_data / clean_data, which paired raw new[] with manual delete[]
across ~160 lines of mixing code.

- chg_uspp.{h,cpp}: stateless free functions in module_charge namespace;
  outputs are caller-pre-sized std::vector, no new/delete; parameter
  validation via WARNING_QUIT; TITLE/timer tags preserved
- charge_mixing_rho.cpp: rho and tau double-grid paths switched to the new
  functions; raw pointer aliases kept for !double_grid so the existing
  mixing call sites (nspin==1/2/4) are untouched
- CMakeLists.txt (source + test): wire chg_uspp.cpp

The legacy divide_data/combine_data/clean_data members are not yet removed;
that follows in a later step after the test is updated.

* module_charge: rewrite MixDivCombTest for the new split_dgrid/merge_dgrid

Drop the legacy alias-pointer assertions (EXPECT_EQ(datas, data.data()),
EXPECT_EQ(datas, nullptr) after clean_data) that coupled the test to the
old new[]/delete[] ownership model.

The rewritten case verifies the actual contract:
- split_dgrid fills smooth and high-frequency buffers with the dense
  data verbatim (per-element comparison)
- merge_dgrid is a left-inverse of split_dgrid (output == input)
- no explicit cleanup call is required: std::vector manages storage

Covers nspin == 1 and nspin == 2 paths.

* module_charge: drop legacy divide_data/combine_data/clean_data members

With the new module_charge::split_dgrid/merge_dgrid in chg_uspp.{h,cpp}
and all call sites in charge_mixing_rho.cpp migrated, the original
Charge_Mixing::divide_data / combine_data / clean_data members are dead.

- delete charge_mixing_uspp.cpp (the raw new[]/delete[] implementation)
- drop the three member declarations from charge_mixing.h
- remove charge_mixing_uspp.cpp from source/test CMakeLists.txt
- Makefile.Objects: drop charge_mixing_uspp.o, add chg_uspp.o
- refresh one stale comment in charge_mixing_rho.cpp to reference
  merge_dgrid instead of the removed combine_data

* module_charge: rename charge_extra files to chg_extra and move class into namespace

Rename charge_extra.h/cpp to chg_extra.h/cpp and wrap the Charge_Extra
class in the module_charge namespace, matching the rest of module_charge
(chg_atomic, chg_symm, chg_uspp). Update include guards, call sites in
esolver_fp.h and the unit test, and CMake/Makefile source lists.

* module_charge: extract DMR mixing into chg_dmr free functions

Move the DMR allocation/mixing logic out of Charge_Mixing members into
stateless module_charge functions (init_mixing_dmr, template mix_dmr
with explicit instantiation), passing the Mixing object, mixing data
and MixingConfig explicitly instead of reading PARAM. Merge the two
identical real/complex mix_dmr overloads, replace raw new[]/delete[]
of the magnetic buffers with std::vector, and de-duplicate the
two-beta mixing lambda into a file-local helper. The members stay as
thin timer-wrapped wrappers so external call sites are unchanged.

* module_charge: remove Charge_Mixing DMR wrappers, call chg_dmr directly

Delete charge_mixing_dmr.cpp and have the two call sites
(chg_routine.cpp, esolver_ks_lcao.cpp) invoke module_charge::
init_mixing_dmr/mix_dmr directly with the Mixing object, mixing data
and MixingConfig obtained through Charge_Mixing accessors. Expose the
owned DMR mixing history via a new get_dmr_mdata() accessor and drop
the now-unneeded density_matrix.h include from charge_mixing.h.
Timers move into the free functions with module_charge labels.
Add the direct parallel_orbitals.h include to esolver_gets.h, whose
value member previously relied on the removed transitive include.

* module_charge: decouple chg_dmr kernel from HContainer, mix raw buffers

Change module_charge::mix_dmr to take per-spin raw contiguous double
buffers and nnr instead of HContainer/DMR container references, and
drop the hcontainer.h include (and its atom_pair/parallel_orbitals
dependency chain) from chg_dmr.cpp. The sole call site in
esolver_ks_lcao.cpp now extracts the wrappers and saved buffers from
the DensityMatrix containers before calling the kernel. Move the
argument checks into a file-local check_dmr_inputs helper. The kernel
now depends only on the mixing module and MixingConfig.

* module_charge: refactor charge_mixing_rho free functions and cleanup

- Replace 17 PARAM.inp/globalv direct reads with cfg_ fields
- Unify mixing_tau: remove redundant member, use cfg_.mixing_tau
- Extract make_twobeta_mix as free function template in anonymous namespace
- Extract mix_tau_recip free function for kinetic energy density mixing
- Extract pack_rho_mag/unpack_rho_mag templates for nspin==2 dedup
- Hoist screen and inner_product lambdas before if-else chains (8+4 dups)
- Remove dead new_e_iteration member and its no-op if block
- Drop unused parameter.h include from charge_mixing_rho.cpp

* module_charge: split member functions into charge_mixing.cpp, free functions into chg_rho_detail.h

- Move mix_rho_recip/mix_rho_real/mix_rho from charge_mixing_rho.cpp to charge_mixing.cpp
- Create chg_rho_detail.h for make_twobeta_mix, pack_rho_mag, unpack_rho_mag templates and mix_tau_recip declaration
- charge_mixing_rho.cpp now only contains mix_tau_recip definition in module_charge::detail
- Restore accidentally deleted mix_uom member function

* module_charge: rename charge_{init,mixing_rho} to chg_{init,tau}, widen cube_io ofs_running to ostream

* charge_init.{cpp,h} -> chg_init.{cpp,h}: move Charge::init_rho stages
  (read_rho_from_file, init_rho_atomic_and_tau, load_rho_from_restart,
  init_rho_from_wfc) from Charge member functions to module_charge free
  functions, dropping the corresponding private declarations from
  charge.h. Continues the module_charge convention of stateless free
  functions in chg_* files.

* charge_mixing_rho.cpp -> chg_tau.cpp: rename for the module_charge
  short-underscore convention; the file only contains mix_tau_recip.

* Extract mix_tau_recip declaration from chg_rho_detail.h into a new
  chg_tau.h so chg_tau.cpp no longer pulls in the detail template
  helpers (make_twobeta_mix / pack_rho_mag / unpack_rho_mag).
  charge_mixing.cpp adds chg_tau.h while keeping chg_rho_detail.h for
  the template helpers it still uses.

* Widen ModuleIO::read_vdata_palgrid's ofs_running parameter from
  std::ofstream& to std::ostream& (cube_io.h / read_cube.cpp). The
  body only uses operator<<, so std::ostream& is sufficient; this
  fixes the chg_init.cpp compile error where read_rho_file /
  read_kin_file (per project rules, std::ostream&) could not bind to
  the old std::ofstream& parameter. Existing callers passing
  std::ofstream& (GlobalV::ofs_running, test fixture) convert
  implicitly via base-class reference.

Build lists updated: source/Makefile.Objects and
source/source_estate/{CMakeLists.txt,test/CMakeLists.txt}.

Verification: chg_init.* changes compile-verified by user before
this session; chg_tau rename and chg_tau.h extraction not yet
compile-verified; cube_io type widening not yet compile-verified.

* module_charge: rename charge_mixing.{h,cpp} to chg_mix.{h,cpp}, test to test_chg_mix.cpp

Pure rename, no logic change. Updates include guard, 12 #include sites,
CMakeLists (source_estate + test), and Makefile.Objects. CMake target
MODULE_ESTATE_charge_mixing kept (no external references). Class name
Charge_Mixing and module_charge namespace unchanged.

* module_charge: remove duplicate doc block comments (Phase 1a)

Remove or rephrase 14 duplicate comment lines across 7 files to
eliminate all duplicate_doc_block quality-score deductions.

- chg_mix.cpp: remove 7 duplicate comments in mix_rho_real that
  repeated mix_rho_recip's broyden/Kerker/magabs annotations
- chg_init.cpp: remove 2 duplicate comments in read_kin_file that
  repeated read_rho_file's binary-read and ParaWorld bridge notes
- chg_symm_detail.cpp: remove 1 duplicate step comment in psymmg_soc
- charge.h: rephrase kin_r_save comment to avoid repetition
- chg_extra.h: rephrase beta comment to avoid repetition
- chg_symm.cpp: remove 1 duplicate vector-management comment
- chg_precond.cpp: remove 1 duplicate Kerker comment

* module_charge: replace auto with explicit std::function types (Phase 1b)

Replace 14 auto-keyword lambda declarations with explicit
std::function types to eliminate all auto_keyword quality-score
deductions.

- chg_mix.cpp: 10 auto -> std::function (inner_product, screen,
  twobeta_mix in mix_rho_recip and mix_rho_real)
- chg_drho.cpp: 2 auto -> std::function<double()> (part_of_noncolin,
  part_of_rho)
- chg_tools.cpp: 1 auto -> std::function<void(int,int)> (kernel)
- chg_symm_detail.cpp: 1 auto -> std::function (build_wspin)

Added #include <functional> to all four files.

* module_charge: wrap lines over 120 chars (Phase 1c)

Break 21 lines exceeding the 120-char limit across 7 files to
eliminate all line_too_long quality-score deductions.

- charge.cpp: 3 WARNING_QUIT/cout lines split
- chg_atomic.cpp: 5 Simpson_Integral/exp/assert lines split
- chg_drho.cpp: 2 conj-product sum lines split
- chg_init.cpp: 1 warning message string split
- chg_mix.cpp: 5 make_twobeta_mix/recip_to_real/if_scf_oscillate lines split
- chg_mix.h: 3 member declaration/comment lines shortened
- chg_symm_detail.cpp: 2 MPI_Recv lines split

* module_charge: remove default parameter from Charge::init_rho (Phase 1d)

Remove the default nullptr values from init_rho's klist and wfcpw
parameters and update the two call sites (esolver_of.cpp,
esolver_double_xc.cpp) that relied on the defaults to pass nullptr
explicitly.

* module_charge: replace raw new/delete with std::vector and unique_ptr (Phase 2a-2d)

Replace all raw new/delete allocations in 4 files with RAII
containers to eliminate raw_new_keyword and unpaired_new_delete
quality-score deductions.

- chg_tools.cpp: 1 new -> std::vector<double> (aux buffer)
- chg_extra.cpp: 4 new -> std::vector<std::vector<double>> (rho_atom
  in extrapolate_charge and find_alpha_and_beta)
- chg_symm_detail.cpp: 14 new -> std::vector (rhog_piece, ig2isz,
  ipsz2ipw, nstnz_start, fftixy2is, rhogtot, ig2isztot, ixyz2ipw
  across reduce_to_fullrhog, rhog_piece_to_all, psymmg, psymmg_soc)
- chg_mix.{h,cpp}: 5 new + 5 unpaired -> std::unique_ptr for
  mixing and mixing_highf members; destructor and init_mixing
  simplified; get_mixing() returns .get()

charge.cpp (18 raw new) deferred to Phase 2e due to wider impact.

* module_charge: replace raw new/delete in Charge with vector-backed storage (Phase 2e)

Replace all 18 raw new and 10 unpaired delete in charge.cpp with
std::vector-backed storage to eliminate raw_new_keyword and
unpaired_new_delete deductions.

- charge.h: add _ptrs_rho, _ptrs_rhog, _ptrs_rho_save, _ptrs_rhog_save,
  _ptrs_kin_r, _ptrs_kin_r_save (std::vector<double*> / complex*),
  and _space_rho_core, _space_rhog_core (std::vector data buffers)
- charge.cpp allocate(): replace new double*[nspin] with vector resize;
  rho = _ptrs_rho.data() preserves double** interface
- charge.cpp init_final_scf(): replace both outer pointer and inner
  data new calls with _space_* vectors
- charge.cpp destroy(): replace delete[] with vector::clear() and
  nullptr assignment

charge.cpp score: 47 -> 69, now passing the 60 threshold.
Module average: 85.0 -> 85.7, 30/33 files passing.

* module_charge: replace std::make_unique with C++11-compatible unique_ptr(new T) (fix)

std::make_unique is a C++14 feature; the repo baseline is C++11.
Replace 4 make_unique calls with std::unique_ptr<T>(new T(...)) to
eliminate the post_cpp11_feature deduction (-40).

chg_mix.cpp score: 0 -> 15, module average: 85.7 -> 86.1.

* module_charge: fix duplicate doc block in charge.cpp init_final_scf

* module_charge: aggregate chgmixing_ks parameters into ScfMixingCtx struct (Phase 3a)

Replace 14-parameter chgmixing_ks with 7-parameter version by
grouping SCF convergence thresholds and status flags into a new
ScfMixingCtx struct, and deriving nrxx from chr.rhopw->nrxx.

- chg_routine.h: define ScfMixingCtx struct (hsolver_error, scf_thr,
  scf_ene_thr, converged_u, drho, oscillate_esolver, conv_esolver)
- chg_routine.cpp: unpack ctx members at function entry
- esolver_ks.cpp: pack ctx before call, unpack after

chg_routine.cpp score: 63 -> 70, too_many_parameters eliminated.

* module_charge: aggregate read_rho_file/read_kin_file parameters into ReadCfg (Phase 3b)

Replace 9-parameter read_rho_file and read_kin_file with 5-parameter
versions by grouping suffix, readin_dir, rank, ofs_running, ofs_warning
into a ReadCfg struct in the anonymous namespace.

chg_init.cpp score: 66 -> 70, too_many_parameters eliminated.

* module_charge: aggregate non_linear_core_correction parameters into NlcCtx (Phase 3c)

Replace 10-parameter non_linear_core_correction with 2-parameter
version by grouping all input data into a new NlcCtx struct.

chg_tools.cpp score: 96 -> 100, too_many_parameters eliminated.

* module_charge: split chg_mix.cpp into init and rho mixing files (Phase 4a)

Move mix_rho_recip, mix_rho_real, and mix_rho (440 lines) from
chg_mix.cpp into a new chg_mix_rho.cpp to eliminate file_too_long
deduction (-10).

- chg_mix.cpp: 727 -> 286 lines (constructor, set_mixing,
  init_mixing, set_rhopw, mix_reset, if_scf_oscillate,
  allocate_mixing_uom, mix_uom)
- chg_mix_rho.cpp: new file, 440 lines (mix_rho_recip,
  mix_rho_real, mix_rho)
- CMakeLists.txt: add chg_mix_rho.cpp to library and test targets

chg_mix.cpp score: 15 -> 60, now passing the 60 threshold.
32/34 files passing, module average improved.

* module_charge: split chg_drho.cpp and decompose inner product functions (Phase 4b)

Move inner_product_recip_rho and inner_product_recip_hartree from
chg_drho.cpp into a new chg_drho_inner.cpp, and decompose each
into per-nspin helper functions to reduce cyclomatic complexity.

- chg_drho.cpp: 520 -> 161 lines (cal_drho, cal_dkin,
  inner_product_real); score 49 -> 97
- chg_drho_inner.cpp: new file, 310 lines; score 100
  - inner_product_recip_rho decomposed into recip_rho_nspin1,
    recip_rho_nspin2, recip_rho_nspin4_mag helpers (CC 29 -> ~5 each)
  - inner_product_recip_hartree decomposed into
    recip_hartree_nspin2, recip_hartree_nspin4_trad,
    recip_hartree_nspin4_angle helpers (CC 37 -> ~5 each)
  - shared coulomb_sum_single extracted
- CMakeLists.txt: add chg_drho_inner.cpp to library and test targets

34/35 files passing, only chg_atomic.cpp remains below 60.

* refactor(module_charge): split atomic_rho and remove ZEROS in charge mixing

chg_atomic.cpp:
- Decompose atomic_rho (CC=60) into per-nspin helpers in
  chg_atomic_inner.cpp; CC reduced to 7, score 40->100.
- Replace all PARAM.inp.nelec/domag/domag_z/test_charge and
  GlobalV::ofs_warning with explicit AtomicRhoCfg parameter.
- Remove unused parameter.h include.
- Add chg_atomic_detail.h declaring detail helpers and RhoG3dCtx.

chg_init/chg_extra/esolver_*:
- Pass AtomicRhoCfg through call sites of atomic_rho,
  extrapolate_charge, and update_delta_rho.

Bug fixes:
- chg_drho_inner.cpp: fix duplicate const (const MixingConfig const&
  -> const MixingConfig&) and add detail:: prefix to helper calls.
- chg_mix_rho.cpp: use mixing.get()/mixing_highf.get() for unique_ptr.
- chg_tools.cpp: fix numeric -> numeric[it] in set_rho_core.

Memory safety / cleanup:
- Replace ModuleBase::GlobalFunc::ZEROS with std::fill in charge.cpp,
  chg_symm_detail.cpp, chg_tools.cpp; remove redundant ZEROS calls
  that precede full overwrites in chg_dmr.cpp and chg_mix_rho.cpp.

* Refactor: remove redundant Charge& overload of cal_rhog_symm_soc

The Charge& overload only forwarded chr.rho/chr.rhog to the raw-array
overload and had a single internal call site. Inline the member access
at that call site and drop the wrapper declaration and definition.

* module_charge: fix stale TITLE/timer labels and drop unused xc_functional.h includes

mix_tau_recip is now a free function in module_charge::detail, so update
its TITLE/timer labels from the legacy "Charge_Mixing" to "module_charge"
to match the convention of other free functions in the directory. Also
remove the unused xc_functional.h includes from chg_tau.cpp and
chg_symm_detail.cpp (label/include cleanup only, no behavior change).

* module_charge: remove redundant #ifdef __MPI guards around parallel wrappers

Parallel_Reduce::reduce_pool and Parallel_Common::bcast_double already
compile to no-op stubs when __MPI is undefined, so the outer guards add
nothing. Remove 11 such guards in chg_tools.cpp, chg_drho.cpp,
chg_drho_inner.cpp, chg_atomic_inner.cpp and chg_mix.cpp.

Guards enclosing raw MPI calls or MPI/serial dual paths are kept
(chg_parallel, chg_symm_detail, chg_routine BP_WORLD bcast, chg_extra.h).

* module_charge: decouple chg_routine from spin_constrain singleton

- forward-declare Plus_U_Base in chg_routine.h instead of including dftu_base.h
- query DeltaSpin mag_converged in ESolver_KS_PW and pass it to chgmixing_ks_pw

* module_charge: remove PARAM dependencies via explicit configuration structs

Remove the last four direct includes of parameter.h in module_charge
(chg_mix, chg_parallel, charge, chg_init) and the implicit PARAM.globalv.ks_run
read in chg_routine. INPUT values are now passed explicitly:

- MixingConfig gains scf_nmax for the drho oscillation history
- reduce_diff_pools/rho_mpi/kin_r_mpi take kpar, all_ks_run, bndpar, nspin,
  out_elf from callers instead of GlobalV::KPAR/PARAM
- Charge::kin_density/allocate/check_rho/renormalize_rho/init_final_scf take
  out_elf/test_charge/nelec as arguments with validation asserts
- new InitRhoCfg aggregates INPUT values for init_rho
- ScfMixingCtx gains ks_run; dm2rho takes nelec and drops its default
  skip_normalize argument per governance rule 5

No behavior change: save_rho_before_sum_band now uses the member nspin
set by allocate, identical to the previously read PARAM.inp.nspin.

* module_charge: restore #ifdef __MPI guards around parallel wrapper calls

The guards removed in 7a0013848 are load-bearing for serial-built unit
tests: source_estate/test strips __MPI from test translation units via
abacus_disable_feature_definitions, but links libbase built with __MPI,
whose explicit Parallel_Reduce instantiations contain real MPI calls.
Unguarded calls in the test TUs therefore bound to MPI_Allreduce and
abort with "called before MPI_INIT", failing MODULE_ESTATE_charge_test
and MODULE_ESTATE_charge_mixing.

Restore all 11 call-site guards in chg_tools.cpp, chg_atomic_inner.cpp,
chg_drho.cpp, chg_drho_inner.cpp and chg_mix.cpp. No behavior change for
MPI or serial production builds.

* Remove dead PAW compensation charge members

nhat, nhat_save in Charge and nhat_mdata in Charge_Mixing have had
no references since #6225 removed the PAW code; drop the orphaned
declarations and update the related comment.

* Refactor: remove unused Charge::prenspin member

prenspin recorded the spin-channel count read from legacy cube charge
files and drove collinear-to-noncollinear rearrangement in init_rho.
After read_rho was replaced by binary read_rhog (#5323, #5362) the
value is neither written nor read anywhere, so drop the dead member.

* Refactor: move Charge::cal_rho2ne/check_rho to module_charge free functions

- Add module_charge::check_rho in chg_tools.{h,cpp} with grid/geometry
  parameters passed explicitly; preserve all branches, thresholds and
  warning/abort messages of Charge::check_rho
- Remove the Charge::cal_rho2ne forwarding wrapper and Charge::check_rho
- Update the three esolver call sites (ks/of/double_xc) to pass rho,
  nspin, rhopw grid sizes and ucell.omega explicitly
- Drop the check_rho stubs in elecstate_pw/base tests and switch
  charge_test to the free cal_rho2ne
- Add test_chg_tools.cpp covering cal_rho2ne, total/spin-polarized
  checks, mismatch warning path and negative-channel aborts

* Refactor: remove redundant Charge::omega_ pointer

- Charge::sum_rho() now reads the cell volume from rhopw->omega, which
  is computed from the same lat0/latvec as ucell.omega and is already
  dereferenced on the same line for nxyz; this also makes the volume
  consistent with the grid rho lives on
- Drop the Charge::omega_ member, its set_omega() setter and the
  chg_init.cpp call site, removing a raw-pointer dependency on the
  UnitCell lifetime; update charge_test accordingly

Verified: MODULE_ESTATE_charge_test and MODULE_ESTATE_chg_tools pass,
elecstate library rebuilds cleanly.

* Remove dead Charge::init_final_scf and allocate_rho_final_scf

init_final_scf has had no production callers since the nscf refactor
(c6ae01236); its only remaining caller was the unit test added in
ba8b7ce9a. After the vector-backed storage refactor it was also a
broken duplicate of Charge::allocate: it never set nspin/nrxx/nxyz/
ngmc and skipped the kin_r buffers. Remove the function, its one-shot
guard flag, and the corresponding test case; destroy() now keys solely
on allocate_rho since vector storage self-manages cleanup.

* Refactor: pass rhopw explicitly to chg_init/chg_routine/chg_extra/chg_symm

Remove implicit reads of chr.rhopw/chr.ngmc from four module_charge files:
- chg_symm.cpp: size kin_g by the rho_basis used for its FFTs
- chg_routine: chgmixing_ks takes const PW_Basis&
- chg_init: orchestrator and four stage helpers take const PW_Basis&;
  the Charge::init_rho member signature is unchanged
- chg_extra: extrapolate_charge/update_delta_rho take const PW_Basis&

Call sites pass *chr.rhopw at the KS boundary or *pw_rhod where the
binding (esolver_fp.cpp chr.set_rhopw(pw_rhod)) makes them identical.
Verified: affected TUs compile and MODULE_ESTATE_charge_extra passes.

* Comments: add TODOs for LCAO+USPP double-grid follow-ups

Record the smooth/dense grid split to revisit if LCAO is ever allowed
with USPP: symmetrize_rho callers pass different grids, and the
ndx/ndy/ndz input path lacks the LCAO guard the ecutrho path has.

* Refactor: replace sticky Charge::cal_elf flag with explicit symm_kin argument

cal_elf was set to true once during ELF output and never reset, so every
later density symmetrization in the same run redundantly symmetrized
kin_r. Replace the mutable workflow flag with an explicit bool parameter
on the Charge& overload of module_charge::cal_rhog_symm:
- ctrl_output_fp passes true right before write_elf consumes kin_r
- symmetrize_rho wrapper and other callers pass XC_Functional::get_ked_flag()

Verified: full incremental build, read_wf2rho unit tests (serial/4 MPI),
write_elf logic test, and tests/01_PW/scf_out_elf (E difference 5e-10 eV,
ELF cube passes CompareFile.py at 3-decimal tolerance).

* Refactor: resolve mixing_tau at config assembly, drop XC dependency from chg_mix

esolver_ks now resolves mix_cfg.mixing_tau = inp.mixing_tau &&
XC_Functional::get_ked_flag() at the single production config assembly
point, so chg_mix/chg_mix_rho no longer query the XC global inside tau
mixing branches (6 sites). test_chg_mix mirrors the resolution in
make_cfg() and sets ked_flag before set_mixing where tau mixing is
expected. Also drop an unused xc_functional.h include from
chg_drho_inner.cpp.

Verified: full incremental build clean; MODULE_ESTATE_charge_mixing
11/11 tests pass; MODULE_ESTATE_charge/chg test suites all pass
(serial + 4-rank MPI).

* Fix: restore complete types in chg_drho_inner.cpp after include removal

Removing xc_functional.h in 87b818f4c broke compilation: the include was
load-bearing transitively, supplying the complete ModulePW::PW_Basis type
and ModuleBase::TITLE. Add the direct includes instead (pw_basis.h,
global_function.h) per IWYU.

Verified: make -j16 exits 0 with full log retained (previous verification
was invalid: a tail pipe masked both the exit code and the errors).

* Refactor: derive tau symmetrization/reduction from kin_r buffer existence

The Charge& cal_rhog_symm overload and rho_mpi/kin_r_mpi queried
XC_Functional::get_ked_flag() (plus a caller-supplied out_elf/symm_kin
flag) to decide whether to touch kin_r. Since Charge::allocate allocates
kin_r exactly when meta-GGA or ELF output needs it, both now check
chr.kin_r != nullptr directly, dropping the XC dependency and the extra
boolean parameters:
- rho_mpi/kin_r_mpi lose the out_elf parameter (2 production, 3 test
  call sites updated)
- the Charge& cal_rhog_symm overload loses the symm_kin parameter
  (ctrl_output_fp, setup_pot, read_wf2rho, update_state_rdmft revert to
  4 arguments); the raw-pointer overload now checks kin_r != nullptr
  only
- module_charge keeps XC references only in charge.cpp, chg_init.cpp,
  chg_drho.cpp (semantic "is meta-GGA" sites, resolved next)

Verified: make -j16 exit 0; 14/14 ctest charge/elecstate/read_wf2rho
tests (serial + 4-rank MPI); tests/01_PW/scf_out_elf integration case
reproduces the reference energy (-194.623411265 eV, diff 5e-10) and the
ELF cube passes CompareFile.py at 3-decimal tolerance.

* Refactor: remove module_xc dependency from module_charge (meta_gga state)

module_charge queried XC_Functional::get_ked_flag() at 5 semantic
"is meta-GGA" sites (tau TF init, tau file read, tau save, tau residual,
tau mixing resolution). Resolve the flag at upper layers instead:
- Charge::allocate takes an explicit meta_gga argument and stores it as
  object state; save_rho_before_sum_band and cal_dkin read it
- InitRhoCfg gains a meta_gga field, filled at the 3 esolver config
  assembly points (ks/of/double_xc)
- delete Charge::kin_density(); 6 esolver call sites inline
  get_ked_flag() || (out_elf[0] > 0) for buffer allocation and pass
  get_ked_flag() as meta_gga; non-SCF allocations pass false
- charge_test mirrors the inline expression

module_charge now has zero references to module_xc.

Verified: make -j16 exit 0 (full log); 14/14 charge/elecstate/
read_wf2rho ctests (serial + 4-rank MPI), including the mGGA tau mixing
and tau-save branches; tests/01_PW/scf_out_elf reproduces reference
energy (-194.623411265 eV, diff 5e-10) and the ELF cube passes
CompareFile.py at 3-decimal tolerance. A SCAN integration case
(205_PW_SCAN) still requires a libxc-enabled build/CI run.

* Fix: allow null rho buffers on ranks with empty real-space grid partition

pack_rho_mag/unpack_rho_mag in chg_rho_detail.h quit whenever any buffer
pointer is null. A rank may legitimately own zero real-space grid points
(nrxx == 0) when the grid is decomposed across more processes than it has
z-slabs (e.g. a 3x3x3 big-cell grid on 4 processes leaves one rank with
no slab); its zero-sized vectors then return null data() pointers even
though the packing loops perform no access. The unconditional check made
LCAO nspin==2 real-space mixing abort with "pack_rho_mag pointer is null"
on such ranks.

Restrict the null-pointer check to n > 0, matching the convention already
used by Parallel_Grid::reduce (only a null buffer with a non-zero size is
a genuine bug). n < 0 remains a hard error. Regression introduced in
d9685d4eb when the inline packing loops were extracted into these helpers.

* Refactor: move rhog_io into module_charge as chg_rhog_io

Relocate source_estate/rhog_io.{h,cpp} to source_estate/module_charge/
under the module_charge namespace, rename include guard to CHG_RHOG_IO_H,
and update the warning tags emitted at runtime. Update both callers
(chg_init.cpp, esolver_fp.cpp) and build files; adapt test_rhog_io.cpp in
place ahead of its move in a follow-up commit. No behavior change.

* Refactor: create module_charge/test with the rhog io unit test

Move test_rhog_io.cpp into module_charge/test/test_chg_rhog_io.cpp with
its support data charge-density.dat, register the new test subdirectory,
and rename the target to MODULE_CHARGE_rhog_io. Remove the migrated
AddTest block from the legacy source_estate/test/CMakeLists.txt.

* Refactor: move charge and charge-extra unit tests into module_charge/test

Rename charge_test.cpp to test_charge.cpp and charge_extra_test.cpp to
test_chg_extra.cpp per the test naming rule, move prepare_unitcell.h
alongside its only users, and register MODULE_CHARGE_charge /
MODULE_CHARGE_extra in the module_charge test CMakeLists. No test data
moves: prepare_unitcell.h only sets file-name strings at runtime, and
the extra test only writes cube files into ./support/.

* Refactor: move mix, parallel and tools unit tests into module_charge/test

Relocate test_chg_mix.cpp (fixing its relative includes), test_chg_parallel.cpp
and test_chg_tools.cpp into module_charge/test, register MODULE_CHARGE_tools /
MODULE_CHARGE_mix / MODULE_CHARGE_parallel with the 4-process mpirun test,
and drop the migrated blocks from the legacy source_estate/test CMakeLists.

* Refactor: rename module_charge test dir to unittests and wire CI for it

Rename source_estate/module_charge/test to unittests (relative CMake
paths are immune to the move). Sync the referencing points: the
add_subdirectory call, the coverage lcov filter (add '*/unittests/*' so
test sources stay excluded from the report), a dedicated Module_Charge
ctest step in test.yml with MODULE_CHARGE added to the catch-all -E
list to avoid double execution, and unittests/ added to the
code_quality_score.py SKIP_DIRS.

* Fix: pass ucell.omega to Charge::sum_rho/renormalize_rho to fix NPT stress

Root cause: commit 34b441e1c ("Refactor: remove redundant Charge::omega_
pointer") changed Charge::sum_rho() to read the cell volume from
rhopw->omega instead of ucell.omega. In variable-cell calculations (NPT),
pw_rho/pw_rhod are NOT rebuilt on cell change (only pw_wfc is), so
rhopw->omega keeps the initial cell volume while ucell.omega is updated
every MD step. The stale volume made sum_rho() return a wrong electron
count, which made renormalize_rho() scale rho by the wrong factor,
corrupting the stress (deviation ~0.002 in 095_PW_NPT) while the total
energy stayed near-correct (variational, second-order sensitive).

Fix: add an explicit omega parameter to Charge::sum_rho() and
renormalize_rho(); all call sites (init_scf, chg_routine, LCAO dm2rho path
through HSolverLCAO/dmToRho, RDMFT update_charge, OFDFT renormalize_psi)
now pass ucell.omega. This mirrors the existing check_rho(..., ucell.omega)
pattern.

Also mark three other rhopw->omega users with BUG(investigate) comments:
get_local_pp_energy, cal_delta_escf, and Makov-Payne correction. These are
pre-existing and were not changed by the refactor; they may have the same
stale-volume issue in NPT and should be investigated separately.

Bisected to 34b441e1c over the 20260916 module_charge refactor branch.

* Fix: add omega arg to remaining dm2rho call sites

Missed four LCAO_domain::dm2rho call sites in the previous commit:
- lcao_set.cpp init_chg_dm (skip_normalize=true, omega unused)
- esolver_dm2rho.cpp
- esolver_ks_lcao_tddft.cpp weight_dm_rho
- module_dm/init_dm.cpp

All now pass ucell.omega.

* Fix: restore HamiltHSMatrix hs declaration in cal_mw_from_lambda

Accidentally removed the line while editing the comment.

* Fix: close_kerker_gg0 actually disables Kerker; drop dead mixing_gg0 members

The chg_precond refactor (commit 6d127d517) made the Kerker kernels read
cfg_ (immutable INPUT snapshot) instead of Charge_Mixing members, but
close_kerker_gg0() kept writing the now-dead mixing_gg0/mixing_gg0_mag
members. As a result, the non-separate-loop EXX path in exx_lri_interface.hpp
silently failed to disable Kerker after convergence.

Fix: add a kerker_disabled_ flag on Charge_Mixing that the mix_rho_recip/
mix_rho_real screening lambdas short-circuit on. The flag lives on the
object, not in cfg_, so the immutable INPUT snapshot invariant is preserved.

Also drop the now-dead members mixing_gg0/mixing_gg0_mag/mixing_gg0_min/
mixing_angle/mixing_dmr and the get_mixing_gg0() getter; set_mixing/init_mixing
now read these from cfg_ directly. Add CloseKerkerGg0DisablesScreenReal
regression test that compares close_kerker_gg0() output against the
cfg.mixing_gg0=0 baseline and proves the flag is load-bearing.

* Fix: relax over-strict null-buffer asserts for empty grid partitions

reduce_diff_pools and Parallel_Grid::reduce_across_pools still forbade
null buffers unconditionally, contradicting the rule documented at
parallel_grid.cpp:355-360. A rank with nrxx == 0 may legitimately hold
a null rho/kin_r pointer; the MPI calls below use count 0 and ignore
the buffer. Align both call sites with the documented rule.

* Fix: relax over-strict null-buffer assert in ParaRgridWorld::reduce_across_pools

Same pattern as the previous fix: a rank with nrxx == 0 legitimately
holds a null buffer, and MPI_Allreduce with count 0 ignores it.
Align with the rule documented at parallel_grid.cpp:355-360.

* Fix: allow nnr == 0 in DMR mixing for empty MPI partitions

nnr is local to each MPI rank and may legitimately be zero when no
atom pairs survive the cutoff on that rank. The previous check
aborted DMR mixing for such distributions, whereas the historical
implementation allowed empty blocks. Relax the guard in
check_dmr_inputs() and init_mixing_dmr() to reject only negative
nnr, and require non-null DMR buffers only when nnr > 0, matching
the established nrxx == 0 convention in module_charge.

* Fix: split reciprocal rho copy from real-space |m| rescale in mix_rho_recip

The nspin==4 && mixing_angle>0 branch of mix_rho_recip mixed two
distinct operations in one loop bounded by npw, but rho_magabs is
sized nrxx (real-space) and the new |m| is written back by
recip2real into rho_magabs[0..nrxx-1]. Reading rho_magabs[npw+ig]
goes out of bounds once npw+ig >= nrxx (AddressSanitizer reproduces
with nrxx=125, npw=93) and the loop bound npw leaves the real-space
tail [npw, nrxx) of {mx,my,mz} unscaled. Split into two loops: the
reciprocal rho copy stays bounded by npw, the magnetization rescale
is bounded by nrxx and reads rho_magabs[ir].

* Refactor: remove unused Charge_Mixing::conserve_setting

conserve_setting() was introduced by 420f1ad00 (DeltaSpin feature
merge, 2026-06-15) but never wired up: no production caller, no
test reference, and the DeltaSpin module does not touch
Charge_Mixing. Drop the dead declaration per the project rule that
unused functions and their tests be removed.

* Refactor: drop dead Charge_Mixing::tpiba2 member

tpiba2 was declared in chg_mix.h but never assigned by set_mixing()
nor read anywhere in the module. Grep across the whole source tree
confirms all tpiba2 references are either ucell.tpiba2 (a separate
UnitCell member) or local variables in unrelated modules. The
Charge_Mixing class never computed or used its own tpiba2 pointer;
only tpiba is consumed by the stateless Kerker kernels via
mix_rho_recip/mix_rho_real. Remove the dead declaration.

* Refactor: route Charge_Mixing getters through cfg_

get_mixing_mode(), get_mixing_beta(), get_mixing_ndim() previously
returned the legacy mirror members that set_mixing() kept in sync
with cfg_ by hand. With cfg_ now treated as the immutable INPUT
snapshot, route the public getters through cfg_ directly so there
is a single source of truth for INPUT parameters. External callers
(esolver_ks_lcao, lcao_others, pw_others) are unaffected since
signatures are unchanged. The legacy members remain in place for
now; they are dropped in a later step after internal readers are
migrated.

* Refactor: init_mixing constructs Mixing from cfg_ not legacy mirrors

init_mixing() branched on this->mixing_mode and passed
this->mixing_ndim/mixing_beta to the Broyden/Pulay/Plain_Mixing
constructors. These legacy mirrors were kept in sync with cfg_
manually by set_mixing(). Route through cfg_ directly so cfg_
remains the single source of INPUT parameters. The Mixing objects
themselves still copy beta/ndim into their own members at
construction; that is a one-time snapshot and not a continuous
sync surface, so it is left untouched.

* Refactor: mix_rho_recip/mix_rho_real read mixing_beta from cfg_

Both mix_rho_recip and mix_rho_real built the twobeta_mix functor by
reading this->mixing_beta / this->mixing_beta_mag, which are legacy
mirrors that set_mixing() kept in sync with cfg_. Route the six
construction sites through cfg_.mixing_beta / cfg_.mixing_beta_mag
so cfg_ is the single source of INPUT parameters consumed by the
mixing logic. Behavior is unchanged since the mirrors and cfg_
hold identical values after set_mixing().

* Refactor: set_mixing stops mirroring cfg_ into legacy members

set_mixing() copied mixing_mode, mixing_beta, mixing_beta_mag,
mixing_ndim from cfg into legacy mirror members, then validation
and logging read from the mirrors. Now that all internal readers
(init_mixing, mix_rho_recip, mix_rho_real, getters) read from
cfg_, the mirror writes are dead work. Drop them and route
validation and log output through cfg_ directly. omega and tpiba
remain pointer members because they alias external runtime state
(cell volume, lattice constant) that changes across SCF iterations
and so do not belong in MixingConfig (an immutable INPUT snapshot).

* Refactor: drop legacy Charge_Mixing mirror members; cfg_ is single source

Drop mixing_mode, mixing_beta, mixing_beta_mag, mixing_ndim mirror
members. After the previous commits every internal reader (getters,
init_mixing, mix_rho_recip, mix_rho_real, set_mixing validation
and log output) routes through cfg_, so the mirrors are dead state
that set_mixing() no longer writes. cfg_ is now the single source
of truth for INPUT mixing parameters.

Update test_chg_mix.cpp accordingly: the two assertions that
reached directly into CMtest.mixing_beta_mag and CMtest.mixing_mode
now read CMtest.get_mixing_config().mixing_beta_mag and
CMtest.get_mixing_mode(), matching the public API used by the
other assertions in the same block. No production caller accessed
these members directly (esolver_ks_lcao, lcao_others, pw_others
all used the getters), so the change is test-only on the consumer
side.

* Refactor: drop NSDMI from MixingConfig to force explicit construction

The non-static data member initializers in MixingConfig provided
plausible-looking defaults (e.g. mixing_beta=0.8, mixing_mode=
"broyden") that silently masked forgotten fields when a new field
was added but not wired up at construction sites. With the
defaults removed, every construction site must use aggregate
initialization (or copy-assign from a fully-initialized instance),
and a missing field yields value-initialized (zero/empty) members
that are far more likely to trip a test than the old defaults.
Combined with -Wmissing-field-initializers promoted to error in
the next commits, adding a field to MixingConfig without updating
all aggregate-initialization sites becomes a compile error.

* Refactor: aggregate-init MixingConfig in esolver_ks with pragma guard

Convert the 17-line field-by-field assignment of mix_cfg into a
single aggregate initialization in declaration order. Wrap it in
#pragma GCC diagnostic error "-Wmissing-field-initializers" so
that adding a field to MixingConfig without updating this list
becomes a compile error rather than silently using a default.
Each initializer is annotated with the field name it corresponds
to, making the declaration-order dependency auditable at a glance.

* Refactor: aggregate-init MixingConfig in test_chg_mix with pragma guard

Convert make_cfg()'s 17-line field-by-field assignment into a
single aggregate initialization in declaration order, matching
the esolver-side change. Wrap in the same
#pragma GCC diagnostic error "-Wmissing-field-initializers" so
that adding a field to MixingConfig without updating the test
helper is also a compile error. Both construction sites (esolver
and test) now fail at compile time if a field is missing, closing
the maintenance gap where a new field could silently fall back to
a default value.

* Fix: fail-fast guards in Charge_Mixing and update chg_mix tests

Add validation to turn latent misuse (skipped set_rhopw/set_mixing)
into clear WARNING_QUIT errors instead of null dereference or heap
corruption:
- init_mixing rejects a null rhopw
- if_scf_oscillate checks scf_nmax > 0 and iteration range
- mix_rho validates chr/chr->rhopw and the grid pointers

Fix three chg_mix unit tests that read cfg_ before set_mixing, which
caused a SIGSEGV in SCFOscillationTest and assertion failures in the
two inner-product tests.

* test(module_charge): add unit tests for chg_uspp and chg_dmr

Add test_chg_uspp.cpp covering split_dgrid/merge_dgrid (normal split,
round-trip, nspin=1/2, empty high-frequency/smooth boundaries, and
input-validation abort paths).

Add test_chg_dmr.cpp covering init_mixing_dmr/mix_dmr (nspin=1/2/4
mixing with Plain_Mixing analytically verified, empty-partition null
buffer allowance, and input-validation abort paths).

Wire both targets into unittests/CMakeLists.txt.

* test(module_charge): add unit tests for chg_precond, chg_drho, chg_drho_inner, chg_mix_rho

- test_chg_precond.cpp: kerker_screen_recip/real (early return, nspin=1/2/4
  filter, nspin=4 with mixing_angle resize, real-space matches reciprocal).
- test_chg_drho.cpp: inner_product_real, cal_drho real-space path
  (nspin=1/2/4+domag_z), cal_dkin (meta_gga false/true).
- test_chg_drho_inner.cpp: inner_product_recip_rho and
  inner_product_recip_hartree for nspin=1 with a single G component,
  analytically verified against the Coulomb metric.
- test_chg_mix_rho.cpp: mix_rho abort paths (null chr/chr->rhopw, unset
  rhopw, double_grid without rhodpw) and real-space plain mixing value.

Wire all four targets into unittests/CMakeLists.txt.

* test(module_charge): add unit tests for chg_symm, chg_symm_detail, chg_atomic, chg_atomic_inner

- test_chg_symm.cpp: symmetrize_rho / cal_rhog_symm / cal_rhog_symm_soc
  no-op paths when symm_flag == 0, for nspin=1 and nspin=4.
- test_chg_symm_detail.cpp: psymmg and psymmg_soc idempotence on a
  manually built D_4 point group over a serial cubic PW_Basis.
- test_chg_atomic_inner.cpp: compute_rhoatm USPP direct-copy branch and
  NCPP integrate+scale-to-zv branch (Gaussian rho_at with known analytic
  integral); normalize_and_check renormalizes uniform density to nelec.
- test_chg_atomic.cpp: atomic_rho ntype==0 path (skips atom loop) and
  spin_number_need==3 abort path.

Wire all four targets into unittests/CMakeLists.txt.

* test(module_charge): add chg_tau/chg_routine/chg_init tests; drop spurious XC_Functional stubs

Fourth batch of module_charge unit tests:
- test_chg_tau.cpp: mix_tau_recip abort paths (null chr/grid/mixing, nspin<1,
  double_grid without high-f mixer) and non-double-grid plain mixing value.
- test_chg_routine.cpp: chgmixing_ks_pw/lcao iter==1 restart-step setup, and
  chgmixing_ks convergence branches (conv_esolver true / drho<hsolver_error
  skip mix_rho).
- test_chg_init.cpp: init_rho "wfc" with null wfcpw abort, and "atomic" with
  ntype==0 + meta_gga Thomas-Fermi tau initialization.
Wire all three targets into unittests/CMakeLists.txt.

Cleanup: remove the XC_Functional::func_type / ked_flag definitions from
test_chg_drho, test_chg_mix_rho, test_chg_symm, test_chg_atomic_inner,
test_chg_atomic, test_chg_tau, test_chg_routine, and test_chg_init. None of
the non-test sources compiled into these targets reference these statics
(charge.cpp, chg_*.cpp, and the linked base/cell_info/planewave_serial/
symmetry libraries are clean), so the definitions were pure dead weight.
Also drop the now-unneeded xc_functional.h include from test_chg_mix_rho.cpp
and correct the stub comments.

* test(module_charge): fix broken includes in unit tests

- include chg_atomic_detail.h instead of nonexistent chg_atomic_inner.h
  in test_chg_atomic_inner.cpp; add math_integral.h for Simpson_Integral
- include chg_drho.h in test_chg_drho_inner.cpp for
  module_charge::inner_product_recip_hartree
- include source_cell/magnetism.h in tests that define Magnetism stubs
  (test_chg_drho, test_chg_symm, test_chg_tau, test_chg_mix_rho)
- fix nonexistent source_charge/mixing includes in test_chg_tau.cpp to
  source_base/module_mixing

* test(module_charge): fix link/build issues; temporarily disable routine/init targets

- test_chg_tau: use Plain_Mixing(beta) ctor and init_mixing_data with
  complex type_size (old set_mixing_beta/init_mixing no longer exist)
- test_chg_symm_detail: add Magnetism stub required by cell_info's
  unitcell.cpp, matching other tests in this directory
- test_chg_routine: adapt to two-arg set_rhopw and tpiba from ucell
- disable MODULE_CHARGE_routine and MODULE_CHARGE_init targets with
  documented reasons: their transitive dependencies (Plus_U_Base,
  elecstate, source_io) are deeply coupled; to be resolved later

* fix bug

* format tool_quit

* remove a test due to WARNING_QUIT funcitno

* delete the support file charge-density.dat because unittests never need it

---------

Co-authored-by: abacus_fixer <mohanchen@pku.eud.cn>
Co-authored-by: Xiaoyang Zhang <tsfxwbbzxy@163.com>

* tests: take three charge/DM tests off #define private public (#7998)

Redo of the work in #7988 and #7990, both of which were closed while the
charge density module was being restructured. That restructuring landed in
#7972 and already did most of the decoupling those PRs proposed: allocate(),
renormalize_rho() and sum_rho() now take their inputs explicitly, the mixing
parameters are aggregated in a MixingConfig, and chg_mix.cpp / chg_drho.cpp /
charge.cpp are free of global parameter reads. What was left was the
test-side access.

Production changes are additive only - no existing signature moves and no
line is deleted from any production header:

  Charge::get_allocate_rho()          - report whether allocate() has run
  Charge_Mixing::get_rho_mdata()      - mirror the existing get_dmr_mdata()
  Charge_Mixing::get_tau_mdata()
  Charge_Mixing::set_mixing_config()  - pair for the existing getter, for
                                        callers that must update the snapshot
                                        without rebuilding the mixing history
  XC_Functional::set_func_type()      - pair for get_func_type()
  XC_Functional::set_ked_flag()       - pair for get_ked_flag()

Test changes:

  test_dm_r_init   - two sites move to the already public get_DMR_save()
  test_charge      - the global parameter scratchpad becomes fixture state
                     (32 refs -> 0); PW_Basis setup goes through the public
                     initgrids/initparameters/setuptransform sequence instead
                     of the protected distribute_r()/distribute_g()
  test_chg_mix     - the scratchpad becomes a fixture-owned MixingConfig
                     (163 refs -> 0); the three blocks that hand-wired
                     Charge::_space_* now take their buffers from the fixture,
                     which owns them as vectors and points the public
                     rho/rhog/kin_r views at them with the same stride

No expected value or tolerance was changed.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refactor: replace the JSON path walker with schema operations (#7994)

* Fix: Correct FFTW version detection and NCCL build summary (#7995)

* Fix: Correct FFTW version detection and NCCL build summary

* Read FFTW version directly from adjacent pkg-config metadata

* Fix: restore LibRI centered cell folding in get_Born_von_Karmen_cells

The previous replacement dropped LibRI's Array_Operator::operator%
mapping (c % n + 3*n/2) % n - n/2, shifting cell coordinates from
[-n/2, n/2) to [0, n). Callers using exact coordinate keys (e.g.
58_KP_LR_BSE reading (-1,-1,-1) from a (2,2,2) Rlist) failed with
"R coordinates are not in Rlist". Reintroduce the centered folding
in both the 1D and recursive overloads.

* Fix: propagate CUDA::nvToolsExt through base's link interface

For CUDA < 12.9, NVTX symbols (nvtxRangePushA/nvtxRangePop) live in
libnvToolsExt. Since __USE_NVTX is defined on the OBJECT library base
(which compiles timer.cpp), every consumer of base's object files needs
that library on its link line. Linking it only to the main executable
left unit tests that link base directly (e.g. MODULE_CELL_SYMMETRY_analysis)
with undefined NVTX references on CUDA 12.2 CI. Attach the dependency to
base as INTERFACE so it propagates to the executable and all test targets.

---------

Co-authored-by: abacus_fixer <mohanchen@pku.eud.cn>
Co-authored-by: Xiaoyang Zhang <tsfxwbbzxy@163.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: SY Wang <uwsy1059@qq.com>
Co-authored-by: Taoni Bao <baotaoni@pku.edu.cn>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0 Useful Information Useful information for others to learn/study

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants