Refactor charge density module (Useful Information for Refactoring a Module) - #7972
Merged
Merged
Conversation
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.
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).
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.
added 13 commits
September 20, 2026 15:45
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.
…_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].
conserve_setting() was introduced by 420f1ad (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.
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
approved these changes
Sep 20, 2026
AsTonyshment
left a comment
Collaborator
There was a problem hiding this comment.
If CI could pass, I'm OK with it.
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
总结
对
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。HContainer/ 密度矩阵。chg_dmr的核心现在处理原始双缓冲区(6d174ff2a,570dd49a2),Charge_Mixing的 DMR 封装已被移除,直接调用chg_dmr(86b4f5435)。chg_routine已与spin_constrain单例解耦 (a669eeae9);调用者显式传递所需的 spin-constrain 数据。PARAM读取已替换为显式配置结构体 ——MixingConfig、ScfMixingCtx、ReadCfg、NlcCtx(c9ba84b68,73eee044c,b8cb59d89,b96b14398) —— 因此模块的公共 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_init→chg_init,charge_mixing_rho→chg_tau,charge_atomic→chg_atomic,charge_math→chg_tools,charge_extra→chg_extra,mix_precond→chg_precond,mixing_config.h→chg_mix_cfg.h。内部跨 TU 辅助函数位于detail子命名空间下的detail头文件中。新的
unittests/目录约定source/source_estate/module_charge/下新建了unittests/目录,并迁移/重写了测试:test_chg_rhog_io.cpp、test_charge.cpp、test_chg_extra.cpp、test_chg_mix.cpp、test_chg_parallel.cpp、test_chg_tools.cpp。test_<source>.cpp以匹配其测试的源文件(例如chg_rhog_io.cpp对应test_chg_rhog_io.cpp)。这使得文件与测试的关系易于查找,并将在未来的模块重构中推广应用。Module_Charge步骤(已添加到全包含的-E列表以避免重复执行),添加到 coverage lcov 过滤器(*/unittests/*),以及添加到code_quality_score.py的SKIP_DIRS中。PR 内部修正(非上游 Bug 修复)
以下条目是本次 PR 引入 并在 PR 内部 解决的回归问题。它们不是原始程序的 Bug,不应被解读为稳定性声明;它们被列出是为了让审阅者了解每个中间步骤发生的事情。
34b441e1c→ce5054958/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.cpp、elecstate_energy.cpp、makov_payne.cpp)已用BUG(investigate)TODO 标记以供后续处理。nrxx == 0时的空分区误报 (d9685d4eb→25f51c526,配合module_base中的871f8f2cc)。重构后的pack_rho_mag/unpack_rho_mag添加的空指针检查未考虑到拥有零实空间网格点的进程分区是完全合法的空分区;它中止了例如在mpirun -np 4下的tests/02_NAO_Gamma/scf_elenum_spin2。修复:仅在nrxx > 0时要求非空缓冲区。#ifdef __MPI防护 (7a0013848移除 →4dcbd1c2a恢复)。这些防护是承重的:测试 TU 通过abacus_disable_feature_definitions去除了__MPI,但链接的是在__MPI下编译的libbase,其显式模板实例化包含真实的 MPI 调用;测试 TU 中未加防护的调用会解析为这些符号,并以 "MPI_Allreduce before MPI_Init" 终止。std::make_unique不是 C++11 (962b0b31e→03e3e5e48)。已重写为std::unique_ptr<T>(new T(...))。ee7080533)。一个“无用”的 include (xc_functional.h) 通过传递性提供了PW_Basis的完整类型和ModuleBase::TITLE;chg_drho_inner.cpp静默丢失了它们。已通过重新添加直接 include 修复。Makefile.Objects/ 手写 Makefile (e4e6fc93f,e074dd0fa)。在mix_precond→chg_precond重命名以及charge_math提取后,对象文件未注册。已通过更新两个 make 文件修复。gint_prec_ctrl迁移后的测试 include 路径 (dfe1dac83)。未来重构的经验教训
这些是后续模块工作应遵循的具体经验:
omega_指针看起来是多余的,但它被静默替换成了rhopw->omega,而后者是错误的体积(静态初始值 vs 当前 NPT 值)。对涉及单元胞数量的数值测试(例如095_PW_NPT)进行二分查找是捕获此问题的关键。__MPI(以及其他功能宏)视为构建配置,而不是样式选择。 包装器 TU 的 no-op stubs 取决于包装器 TU 的标志,而不是调用者的标志;如果调用者链接了在该宏下编译的库,调用者必须自行加防护,即使包装器看起来是“安全”的。nrxx == 0是合法的空分区,而不是错误情况。 从现在起,所有对rho[is][ir]风格缓冲区的指针验证都必须允许 null(仅有nrxx > 0时才需要非 null)。TITLE样式的宏。安全移除的最简单测试是:该 TU 能否在仅保留剩余 include 的情况下干净地编译。std::make_unique是 C++14。 仓库基线是 C++11;请使用std::unique_ptr<T>(new T(...))。CMakeLists、Makefile.Objects和所有手写的Makefile条目 —— 有两个平行的构建系统,漏掉任何一个都会导致链接器报 undefined-reference 错误。unittests/下,命名为test_<source>.cpp。 同时更新ctest标签、coverage lcov 过滤器和code_quality_score.py的SKIP_DIRS,以便测试源文件不会污染质量分数或覆盖率报告。工程规范:
detail头文件与已删除的拷贝/赋值detail约定。 在模块内多个翻译单元(TU)间共享、但不属于公共 API 的辅助函数和模板,不放在公共.h文件中;它们位于*_detail.h头文件中的module_charge::detail子命名空间下(例如chg_rho_detail.h、chg_drho_detail.h、chg_symm_detail.h、chg_atomic_detail.h、chg_tau.h)。规则很简单:如果某个符号不会被外部模块或单元测试调用,它就不应出现在公共头文件中。这使得公共头文件保持精简,防止意外依赖,并将实现细节放在需要它们的 TU 旁边。真正属于模块私有的辅助函数——仅由单个.cpp使用——甚至不进入detail头文件;它们位于匿名命名空间中。这种三层划分(<module>.h中的公共接口,<module>_detail.h中detail子命名空间下的跨 TU 内部辅助函数,以及.cpp内匿名命名空间私有的辅助函数)是未来模块重构的标准模式。已删除的拷贝/赋值构造函数。 对 RAII 管理的存储应用相同的规则,
Charge现在显式禁用拷贝和赋值:原因在于新的三层存储布局:
rho、rhog、rho_save和kin_r是double**/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=4和mpirun -np 1/2/3/4重新运行 —— 在nrxx == 0修复后不再中止。tests/01_PW/095_PW_NPT应力在omega修复后与参考值匹配(390.77675500 → 390.77871100)。docs/parameters.yaml/input-main.md。