From 521f03e7c81c2b4c7a941ceeca33cd167a922d92 Mon Sep 17 00:00:00 2001 From: lanshuyue <2000011337@stu.pku.edu.cn> Date: Fri, 30 Oct 2026 11:09:56 +0800 Subject: [PATCH 1/6] Move DFT+U ion-step output initialization to KS solver --- source/source_esolver/esolver_ks.cpp | 11 ++++++++++ source/source_esolver/esolver_ks_lcao.cpp | 6 +----- source/source_esolver/esolver_ks_pw.cpp | 8 +------ source/source_lcao/setup_dftu_lcao.cpp | 21 +++---------------- source/source_lcao/setup_dftu_lcao.h | 17 +-------------- .../source_pw/module_pwdft/setup_dftu_pw.cpp | 9 ++------ source/source_pw/module_pwdft/setup_dftu_pw.h | 7 +------ 7 files changed, 20 insertions(+), 59 deletions(-) diff --git a/source/source_esolver/esolver_ks.cpp b/source/source_esolver/esolver_ks.cpp index dcb2a3c61f6..7116cb289c2 100644 --- a/source/source_esolver/esolver_ks.cpp +++ b/source/source_esolver/esolver_ks.cpp @@ -19,6 +19,7 @@ #include "source_io/module_output/output_log.h" // use write_head #include "source_estate/elecstate_print.h" // print_etot #include "source_pw/module_pwdft/dftu_base.h" // Plus_U_Base::u_converged in iter_finish +#include "source_pw/module_pwdft/dftu_base_io.h" #include "source_hamilt/module_xc/general_exx_info.h" // for init_general_exx_info namespace ModuleESolver @@ -271,6 +272,16 @@ void ESolver_KS::before_scf(UnitCell& ucell, const int istep) void ESolver_KS::iter_init(UnitCell& ucell, const int istep, const int iter) { + if (this->inp_->dft_plus_u > 0) + { + const DFTU_BASE::OccmatOutputCfg occmat_cfg{this->inp_->out_freq_ion, + this->inp_->out_freq_elec, + this->scf_iter_limit(istep), + this->inp_->out_occ_mat, + this->inp_->dft_plus_u}; + DFTU_BASE::prepare_ion_step_output(PARAM.globalv.global_out_dir, istep, iter, occmat_cfg); + } + if(this->inp_->esolver_type != "tddft") { ModuleIO::write_head(GlobalV::ofs_running, istep, iter, this->basisname); diff --git a/source/source_esolver/esolver_ks_lcao.cpp b/source/source_esolver/esolver_ks_lcao.cpp index f476cf78d90..477edfe5bde 100644 --- a/source/source_esolver/esolver_ks_lcao.cpp +++ b/source/source_esolver/esolver_ks_lcao.cpp @@ -690,12 +690,8 @@ void ESolver_KS_LCAO::iter_finish(UnitCell& ucell, const int istep, int& this->kv, this->p_chgmix->get_mixing_beta(), hamilt_lcao, - global_out_dir, this->inp_->nspin, - gamma_only_local, - istep, - iter, - occmat_cfg); + gamma_only_local); // mohan add 2025-11: push DFT+U energy from Plus_U_Base to ElecState. // Covers both dft_plus_u==1 (new method, energy accumulated by DFTU_onsite::contributeHR diff --git a/source/source_esolver/esolver_ks_pw.cpp b/source/source_esolver/esolver_ks_pw.cpp index c700eefa055..5d999344d84 100644 --- a/source/source_esolver/esolver_ks_pw.cpp +++ b/source/source_esolver/esolver_ks_pw.cpp @@ -260,11 +260,6 @@ void ESolver_KS_PW::iter_init(UnitCell& ucell, const int istep, const // update local occupations for DFT+U // should before lambda loop in DeltaSpin - const DFTU_BASE::OccmatOutputCfg init_occmat_cfg{this->inp_->out_freq_ion, - this->inp_->out_freq_elec, - iter_limit, - this->inp_->out_occ_mat, - this->inp_->dft_plus_u}; DFTU_BASE::iter_init_dftu_pw(iter, istep, *this->dftu_, @@ -272,8 +267,7 @@ void ESolver_KS_PW::iter_init(UnitCell& ucell, const int istep, const this->pelec->wg, ucell, this->p_chgmix, - PARAM.globalv.global_out_dir, - init_occmat_cfg, + this->inp_->dft_plus_u, this->kv.isk.data(), this->inp_->nspin, this->inp_->kpar); diff --git a/source/source_lcao/setup_dftu_lcao.cpp b/source/source_lcao/setup_dftu_lcao.cpp index 5e3f7f33c30..08ae03c4fbf 100644 --- a/source/source_lcao/setup_dftu_lcao.cpp +++ b/source/source_lcao/setup_dftu_lcao.cpp @@ -2,7 +2,6 @@ #include "source_pw/module_pwdft/dftu_base.h" #include "source_lcao/module_dftu/dftu_nao_occ.h" #include "source_lcao/module_dftu/dftu_nao_energy.h" -#include "source_pw/module_pwdft/dftu_base_io.h" // mohan add 2025-11-08 #include "source_io/module_parameter/parameter.h" #include "source_lcao/hamilt_lcao.h" @@ -47,12 +46,8 @@ void finish_dftu_lcao(const bool conv_esolver, const K_Vectors& kv, const double mixing_beta, void* hamilt_lcao, - const std::string& global_out_dir, int nspin, - const bool gamma_only_local, - int istep, - int iter, - const DFTU_BASE::OccmatOutputCfg& occmat_cfg) + const bool gamma_only_local) { if (!dft_plus_u) { @@ -81,8 +76,6 @@ void finish_dftu_lcao(const bool conv_esolver, DFTU_LCAO::cal_energy_correction(*dftu_ptr, ucell, PARAM.inp.nspin); } } - DFTU_BASE::prepare_ion_step_output(global_out_dir, istep, iter, occmat_cfg); - /// use the converged occupation matrix for next MD/Relax SCF calculation if (conv_esolver) { @@ -99,12 +92,8 @@ template void finish_dftu_lcao(const bool conv_esolver, const K_Vectors& kv, const double mixing_beta, void* hamilt_lcao, - const std::string& global_out_dir, int nspin, - const bool gamma_only_local, - int istep, - int iter, - const DFTU_BASE::OccmatOutputCfg& occmat_cfg); + const bool gamma_only_local); template void finish_dftu_lcao>(const bool conv_esolver, int dft_plus_u, @@ -114,11 +103,7 @@ template void finish_dftu_lcao>(const bool conv_esolver, const K_Vectors& kv, const double mixing_beta, void* hamilt_lcao, - const std::string& global_out_dir, int nspin, - const bool gamma_only_local, - int istep, - int iter, - const DFTU_BASE::OccmatOutputCfg& occmat_cfg); + const bool gamma_only_local); } // namespace ModuleESolver diff --git a/source/source_lcao/setup_dftu_lcao.h b/source/source_lcao/setup_dftu_lcao.h index cd85efa00b2..374b4205498 100644 --- a/source/source_lcao/setup_dftu_lcao.h +++ b/source/source_lcao/setup_dftu_lcao.h @@ -1,18 +1,12 @@ #ifndef SETUP_DFTU_LCAO_H #define SETUP_DFTU_LCAO_H -#include #include #include "source_cell/unitcell.h" #include "source_cell/klist.h" class LCAO_Orbitals; -namespace DFTU_BASE -{ -struct OccmatOutputCfg; // defined in source_pw/module_pwdft/dftu_base_io.h -} - namespace ModuleESolver { @@ -54,13 +48,8 @@ void init_dftu_lcao(int dft_plus_u, * @param kv K-vectors * @param mixing_beta Mixing beta parameter * @param hamilt_lcao Hamiltonian LCAO object - * @param global_out_dir Output directory for occ_mat.txt and occ_matg{#}.txt * @param nspin Number of spin channels (1, 2, or 4) * @param gamma_only_local Whether only the Gamma point is used for LCAO - * @param istep Ionic-step index, starting from 0 - * @param iter Electronic-iteration index, starting from 1 - * @param occmat_cfg Configuration for occupation-matrix output; its out_occ_mat - * member is the master switch of the occ_mat*.txt files */ template void finish_dftu_lcao(const bool conv_esolver, @@ -71,12 +60,8 @@ void finish_dftu_lcao(const bool conv_esolver, const K_Vectors& kv, const double mixing_beta, void* hamilt_lcao, - const std::string& global_out_dir, int nspin, - const bool gamma_only_local, - int istep, - int iter, - const DFTU_BASE::OccmatOutputCfg& occmat_cfg); + const bool gamma_only_local); } // namespace ModuleESolver diff --git a/source/source_pw/module_pwdft/setup_dftu_pw.cpp b/source/source_pw/module_pwdft/setup_dftu_pw.cpp index 877445b88bc..5d07eba5f14 100644 --- a/source/source_pw/module_pwdft/setup_dftu_pw.cpp +++ b/source/source_pw/module_pwdft/setup_dftu_pw.cpp @@ -1,6 +1,5 @@ #include "source_pw/module_pwdft/setup_dftu_pw.h" #include "source_pw/module_pwdft/dftu_base.h" // mohan add 2025-11-06 -#include "source_pw/module_pwdft/dftu_base_io.h" // mohan add 2025-11-08 #include "source_pw/module_pwdft/dftu_pw.h" #include "source_pw/module_pwdft/dftu_pw_tools.h" @@ -14,20 +13,16 @@ void iter_init_dftu_pw(const int iter, const ModuleBase::matrix& wg, const UnitCell& ucell, Charge_Mixing* p_chgmix, - const std::string& global_out_dir, - const OccmatOutputCfg& occmat_cfg, + const int dft_plus_u, const int* isk, const int nspin, const int kpar) { - if (!p_chgmix || occmat_cfg.dft_plus_u <= 0) + if (!p_chgmix || dft_plus_u <= 0) { return; } - // Prepare the per-ionic-step file before the first snapshot is appended. - DFTU_BASE::prepare_ion_step_output(global_out_dir, istep, iter, occmat_cfg); - const bool first_scf_step = (iter == 1 && istep == 0); // Build the +U potential from the file-loaded occupation matrix when: diff --git a/source/source_pw/module_pwdft/setup_dftu_pw.h b/source/source_pw/module_pwdft/setup_dftu_pw.h index 0b242ce852b..a9f33d246e0 100644 --- a/source/source_pw/module_pwdft/setup_dftu_pw.h +++ b/source/source_pw/module_pwdft/setup_dftu_pw.h @@ -5,16 +5,12 @@ #include "source_base/matrix.h" #include "source_estate/module_charge/chg_mix.h" -#include - struct Input_para; class Plus_U_Base; // mohan add 2025-11-06 namespace DFTU_BASE { -struct OccmatOutputCfg; - void iter_init_dftu_pw(const int iter, const int istep, Plus_U_Base& dftu, // mohan add 2025-11-06 @@ -22,8 +18,7 @@ void iter_init_dftu_pw(const int iter, const ModuleBase::matrix& wg, const UnitCell& ucell, Charge_Mixing* p_chgmix, - const std::string& global_out_dir, - const OccmatOutputCfg& occmat_cfg, + const int dft_plus_u, const int* isk, const int nspin, const int kpar); From 45217dc34d7130891e3c5589a0cdd3bb8c46a5a5 Mon Sep 17 00:00:00 2001 From: lanshuyue <2000011337@stu.pku.edu.cn> Date: Mon, 2 Nov 2026 01:54:42 +0800 Subject: [PATCH 2/6] Rename DFT+U occupation matrix reader and writer sources --- source/Makefile.Objects | 4 ++-- source/source_esolver/esolver_ks_pw.cpp | 10 ++++------ .../source_lcao/module_dftu/unittests/CMakeLists.txt | 8 ++++---- source/source_pw/module_pwdft/CMakeLists.txt | 4 ++-- .../{dftu_base_io_read.cpp => dftu_base_read.cpp} | 0 .../{dftu_base_io_write.cpp => dftu_base_write.cpp} | 0 source/source_pw/module_pwdft/unittests/CMakeLists.txt | 4 ++-- 7 files changed, 14 insertions(+), 16 deletions(-) rename source/source_pw/module_pwdft/{dftu_base_io_read.cpp => dftu_base_read.cpp} (100%) rename source/source_pw/module_pwdft/{dftu_base_io_write.cpp => dftu_base_write.cpp} (100%) diff --git a/source/Makefile.Objects b/source/Makefile.Objects index 142750d9fb3..42fb9ef41a4 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -1012,8 +1012,8 @@ OBJS_SRCPW=h_ewald_pw.o\ update_cell_pw.o\ dftu_base.o\ dftu_base_io.o\ - dftu_base_io_read.o\ - dftu_base_io_write.o\ + dftu_base_read.o\ + dftu_base_write.o\ dftu_pw.o\ dftu_pw_tools.o\ yukawa_screening.o\ diff --git a/source/source_esolver/esolver_ks_pw.cpp b/source/source_esolver/esolver_ks_pw.cpp index 5d999344d84..4e499c5ebba 100644 --- a/source/source_esolver/esolver_ks_pw.cpp +++ b/source/source_esolver/esolver_ks_pw.cpp @@ -392,9 +392,8 @@ void ESolver_KS_PW::iter_finish(UnitCell& ucell, const int istep, int // overwrite occ_mat.txt with the latest occupation matrix and the // actual charge-density residual of this electronic step. At the very - // first step (istep 0, iter 1) the PW occupation matrix does not exist - // yet (iter_init_dftu_pw returns before calculating it), so skip it and - // keep the previous behavior where occ_mat.txt first appears at iter 2. + // first step (istep 0, iter 1) the PW occupation matrix exists only if + // it was loaded from file; otherwise it is first available at iter 2. const int iter_limit = this->scf_iter_limit(istep); const DFTU_BASE::OccmatOutputCfg occmat_cfg{this->inp_->out_freq_ion, this->inp_->out_freq_elec, @@ -408,8 +407,8 @@ void ESolver_KS_PW::iter_finish(UnitCell& ucell, const int istep, int // Read globalv values once into locals so the repeated DFT+U call sites // below do not each re-enter the global dependency surface. const std::string& global_out_dir = PARAM.globalv.global_out_dir; - const bool latest_ready = (iter > 1 || istep > 0); - if (latest_ready) + const bool occmat_ready = (iter > 1 || istep > 0 || this->dftu_->is_occmat_ready()); + if (occmat_ready) { DFTU_BASE::write_latest_occmat(*this->dftu_, ucell, @@ -423,7 +422,6 @@ void ESolver_KS_PW::iter_finish(UnitCell& ucell, const int istep, int // At istep 0 / iter 1 the PW occupation matrix does not exist yet, // unless it was loaded from dm_onsite_ini.txt or a previous // occ_mat.txt; snapshot an "N/A" placeholder in that case. - const bool occmat_ready = latest_ready || this->dftu_->is_occmat_ready(); DFTU_BASE::append_ion_step_snapshot(*this->dftu_, ucell, global_out_dir, diff --git a/source/source_lcao/module_dftu/unittests/CMakeLists.txt b/source/source_lcao/module_dftu/unittests/CMakeLists.txt index a28e4107cdb..1902c710bad 100644 --- a/source/source_lcao/module_dftu/unittests/CMakeLists.txt +++ b/source/source_lcao/module_dftu/unittests/CMakeLists.txt @@ -94,8 +94,8 @@ if(ENABLE_MPI) ../../../source_estate/module_dm/dmr_full.cpp ../../../source_pw/module_pwdft/dftu_base.cpp ../../../source_pw/module_pwdft/dftu_base_io.cpp - ../../../source_pw/module_pwdft/dftu_base_io_read.cpp - ../../../source_pw/module_pwdft/dftu_base_io_write.cpp + ../../../source_pw/module_pwdft/dftu_base_read.cpp + ../../../source_pw/module_pwdft/dftu_base_write.cpp ../../../source_pw/module_pwdft/yukawa_screening.cpp ../../../source_estate/occ_matrix.cpp ../../../source_estate/occ_mixer.cpp @@ -148,8 +148,8 @@ if(ENABLE_MPI) ../../../source_estate/module_dm/dmr_full.cpp ../../../source_pw/module_pwdft/dftu_base.cpp ../../../source_pw/module_pwdft/dftu_base_io.cpp - ../../../source_pw/module_pwdft/dftu_base_io_read.cpp - ../../../source_pw/module_pwdft/dftu_base_io_write.cpp + ../../../source_pw/module_pwdft/dftu_base_read.cpp + ../../../source_pw/module_pwdft/dftu_base_write.cpp ../../../source_pw/module_pwdft/yukawa_screening.cpp ../../../source_estate/occ_matrix.cpp ../../../source_estate/occ_mixer.cpp diff --git a/source/source_pw/module_pwdft/CMakeLists.txt b/source/source_pw/module_pwdft/CMakeLists.txt index 507a4771642..73bbb73f0aa 100644 --- a/source/source_pw/module_pwdft/CMakeLists.txt +++ b/source/source_pw/module_pwdft/CMakeLists.txt @@ -30,8 +30,8 @@ list( objects dftu_base.cpp dftu_base_io.cpp - dftu_base_io_read.cpp - dftu_base_io_write.cpp + dftu_base_read.cpp + dftu_base_write.cpp dftu_pw.cpp dftu_pw_tools.cpp yukawa_screening.cpp diff --git a/source/source_pw/module_pwdft/dftu_base_io_read.cpp b/source/source_pw/module_pwdft/dftu_base_read.cpp similarity index 100% rename from source/source_pw/module_pwdft/dftu_base_io_read.cpp rename to source/source_pw/module_pwdft/dftu_base_read.cpp diff --git a/source/source_pw/module_pwdft/dftu_base_io_write.cpp b/source/source_pw/module_pwdft/dftu_base_write.cpp similarity index 100% rename from source/source_pw/module_pwdft/dftu_base_io_write.cpp rename to source/source_pw/module_pwdft/dftu_base_write.cpp diff --git a/source/source_pw/module_pwdft/unittests/CMakeLists.txt b/source/source_pw/module_pwdft/unittests/CMakeLists.txt index cb24481fd8e..613d4ae1e83 100644 --- a/source/source_pw/module_pwdft/unittests/CMakeLists.txt +++ b/source/source_pw/module_pwdft/unittests/CMakeLists.txt @@ -30,8 +30,8 @@ addtest( SOURCES test_dftu_base.cpp ../dftu_base.cpp ../dftu_base_io.cpp - ../dftu_base_io_read.cpp - ../dftu_base_io_write.cpp + ../dftu_base_read.cpp + ../dftu_base_write.cpp ../dftu_pw_tools.cpp ../yukawa_screening.cpp ../../../source_estate/occ_matrix.cpp From 21fbc61cde427422073860488fd5c3865fd3b4ce Mon Sep 17 00:00:00 2001 From: lanshuyue <2000011337@stu.pku.edu.cn> Date: Mon, 2 Nov 2026 13:14:53 +0800 Subject: [PATCH 3/6] Test first-step PW DFT+U occupation output from file --- tests/01_PW/CMakeLists.txt | 5 ++ tests/01_PW/test_dftu_first_step_restart.py | 92 +++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tests/01_PW/test_dftu_first_step_restart.py diff --git a/tests/01_PW/CMakeLists.txt b/tests/01_PW/CMakeLists.txt index dd47e77df86..743bcc250c5 100644 --- a/tests/01_PW/CMakeLists.txt +++ b/tests/01_PW/CMakeLists.txt @@ -1,6 +1,11 @@ enable_testing() find_program(BASH bash) +find_package(Python3 COMPONENTS Interpreter REQUIRED) +add_test( + NAME 01_PW_dftu_first_step_restart + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_dftu_first_step_restart.py + ${ABACUS_BIN_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/relax_dftu_spin2) if(ENABLE_ASAN) add_test( NAME 01_PW_test_with_asan diff --git a/tests/01_PW/test_dftu_first_step_restart.py b/tests/01_PW/test_dftu_first_step_restart.py new file mode 100644 index 00000000000..6a8fe397315 --- /dev/null +++ b/tests/01_PW/test_dftu_first_step_restart.py @@ -0,0 +1,92 @@ +"""Check first-step PW DFT+U output with and without a loaded matrix.""" + +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile + + +def replace_setting(text, old, new): + if old not in text: + raise AssertionError(f"Missing fixture setting: {old}") + return text.replace(old, new, 1) + + +def first_matrix_element(text): + lines = text.splitlines() + for index, line in enumerate(lines[:-1]): + if line.strip().startswith("spin 1 nelec"): + return float(lines[index + 1].split()[0]) + raise AssertionError("First spin matrix is missing") + + +def run_case(executable, fixture, work, loaded): + for name in ("KPT", "STRU"): + shutil.copy2(fixture / name, work / name) + if loaded: + shutil.copy2(fixture / "occ_mat.txt", work / "occ_mat.txt") + + input_text = (fixture / "INPUT").read_text() + input_text = replace_setting(input_text, "calculation relax", "calculation scf") + input_text = replace_setting(input_text, "scf_nmax 70", "scf_nmax 1") + input_text = replace_setting(input_text, "out_freq_ion 2", "out_freq_ion 1") + if not loaded: + input_text = replace_setting(input_text, "init_occ_mat 2", "init_occ_mat 0") + data_dir = fixture.parent.parent / "PP_ORB" + input_text = replace_setting(input_text, "pseudo_dir ../../PP_ORB/", f"pseudo_dir {data_dir}/") + input_text = replace_setting(input_text, "orbital_dir ../../PP_ORB/", f"orbital_dir {data_dir}/") + (work / "INPUT").write_text(input_text) + + output_dir = work / "OUT.autotest" + output_dir.mkdir() + numbered = output_dir / "occ_matg1.txt" + numbered.write_text("# Electronic step 99\nSTALE SECTION\n") + + env = os.environ.copy() + env["OMP_NUM_THREADS"] = "1" + result = subprocess.run( + [str(executable)], cwd=work, env=env, capture_output=True, text=True, timeout=180 + ) + if result.returncode != 0: + raise AssertionError( + f"ABACUS failed in {'loaded' if loaded else 'fresh'} case: {result.returncode}\n" + + (result.stdout + result.stderr)[-4000:] + ) + + assert numbered.exists(), "Numbered occupation output is missing" + snapshot = numbered.read_text() + assert "STALE SECTION" not in snapshot + assert snapshot.count("# Electronic step 1") == 1 + latest = output_dir / "occ_mat.txt" + if loaded: + assert latest.exists(), "Loaded matrix was not written at the first PW step" + latest_text = latest.read_text() + assert "# Electronic step 1" in latest_text + assert "Fe Atom 1 L 2" in latest_text + assert "Fe Atom 1 L 2" in snapshot + assert "N/A" not in latest_text + snapshot + source_value = first_matrix_element((fixture / "occ_mat.txt").read_text()) + assert abs(first_matrix_element(latest_text) - source_value) < 1e-6 + assert abs(first_matrix_element(snapshot) - source_value) < 1e-6 + else: + assert not latest.exists(), "Fresh PW run wrote an unavailable first-step matrix" + assert "N/A" in snapshot + assert "Fe Atom 1 L 2" not in snapshot + + +def main(): + executable = Path(sys.argv[1]).resolve() + fixture = Path(sys.argv[2]).resolve() + with tempfile.TemporaryDirectory(prefix="abacus_dftu_pw_first_step_") as scratch: + root = Path(scratch) + for loaded in (True, False): + work = root / ("loaded" if loaded else "fresh") + work.mkdir() + run_case(executable, fixture, work, loaded) + print("PW DFT+U first-step loaded and fresh output checks passed") + + +if __name__ == "__main__": + main() From 2bfbea960594e922fce4456a1f7f58a147b6c281 Mon Sep 17 00:00:00 2001 From: lanshuyue <2000011337@stu.pku.edu.cn> Date: Tue, 3 Nov 2026 14:07:32 +0800 Subject: [PATCH 4/6] Keep PW restart regression in validation tools and require MPI --- tests/01_PW/CMakeLists.txt | 13 +++++++----- .../test_dftu_first_step_restart.py | 20 +++++++++---------- 2 files changed, 18 insertions(+), 15 deletions(-) rename tests/{01_PW => integrate/validation_tools}/test_dftu_first_step_restart.py (86%) diff --git a/tests/01_PW/CMakeLists.txt b/tests/01_PW/CMakeLists.txt index 743bcc250c5..65ebffbe071 100644 --- a/tests/01_PW/CMakeLists.txt +++ b/tests/01_PW/CMakeLists.txt @@ -1,11 +1,14 @@ enable_testing() find_program(BASH bash) -find_package(Python3 COMPONENTS Interpreter REQUIRED) -add_test( - NAME 01_PW_dftu_first_step_restart - COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_dftu_first_step_restart.py - ${ABACUS_BIN_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/relax_dftu_spin2) +if(ENABLE_MPI) + find_package(Python3 COMPONENTS Interpreter REQUIRED) + add_test( + NAME 01_PW_dftu_first_step_restart + COMMAND ${Python3_EXECUTABLE} + ${ABACUS_TEST_DIR}/integrate/validation_tools/test_dftu_first_step_restart.py + ${ABACUS_BIN_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/relax_dftu_spin2) +endif() if(ENABLE_ASAN) add_test( NAME 01_PW_test_with_asan diff --git a/tests/01_PW/test_dftu_first_step_restart.py b/tests/integrate/validation_tools/test_dftu_first_step_restart.py similarity index 86% rename from tests/01_PW/test_dftu_first_step_restart.py rename to tests/integrate/validation_tools/test_dftu_first_step_restart.py index 6a8fe397315..8da41d5b7a4 100644 --- a/tests/01_PW/test_dftu_first_step_restart.py +++ b/tests/integrate/validation_tools/test_dftu_first_step_restart.py @@ -22,18 +22,18 @@ def first_matrix_element(text): raise AssertionError("First spin matrix is missing") -def run_case(executable, fixture, work, loaded): +def run_case(executable, fixture, work, init_occ_mat): for name in ("KPT", "STRU"): shutil.copy2(fixture / name, work / name) - if loaded: + if init_occ_mat: shutil.copy2(fixture / "occ_mat.txt", work / "occ_mat.txt") input_text = (fixture / "INPUT").read_text() input_text = replace_setting(input_text, "calculation relax", "calculation scf") input_text = replace_setting(input_text, "scf_nmax 70", "scf_nmax 1") input_text = replace_setting(input_text, "out_freq_ion 2", "out_freq_ion 1") - if not loaded: - input_text = replace_setting(input_text, "init_occ_mat 2", "init_occ_mat 0") + if init_occ_mat != 2: + input_text = replace_setting(input_text, "init_occ_mat 2", f"init_occ_mat {init_occ_mat}") data_dir = fixture.parent.parent / "PP_ORB" input_text = replace_setting(input_text, "pseudo_dir ../../PP_ORB/", f"pseudo_dir {data_dir}/") input_text = replace_setting(input_text, "orbital_dir ../../PP_ORB/", f"orbital_dir {data_dir}/") @@ -51,7 +51,7 @@ def run_case(executable, fixture, work, loaded): ) if result.returncode != 0: raise AssertionError( - f"ABACUS failed in {'loaded' if loaded else 'fresh'} case: {result.returncode}\n" + f"ABACUS failed with init_occ_mat={init_occ_mat}: {result.returncode}\n" + (result.stdout + result.stderr)[-4000:] ) @@ -60,7 +60,7 @@ def run_case(executable, fixture, work, loaded): assert "STALE SECTION" not in snapshot assert snapshot.count("# Electronic step 1") == 1 latest = output_dir / "occ_mat.txt" - if loaded: + if init_occ_mat: assert latest.exists(), "Loaded matrix was not written at the first PW step" latest_text = latest.read_text() assert "# Electronic step 1" in latest_text @@ -81,11 +81,11 @@ def main(): fixture = Path(sys.argv[2]).resolve() with tempfile.TemporaryDirectory(prefix="abacus_dftu_pw_first_step_") as scratch: root = Path(scratch) - for loaded in (True, False): - work = root / ("loaded" if loaded else "fresh") + for init_occ_mat in (1, 2, 0): + work = root / f"init_occ_mat_{init_occ_mat}" work.mkdir() - run_case(executable, fixture, work, loaded) - print("PW DFT+U first-step loaded and fresh output checks passed") + run_case(executable, fixture, work, init_occ_mat) + print("PW DFT+U first-step output checks passed for init_occ_mat=0, 1, and 2") if __name__ == "__main__": From 6b137de1a6a74d8c56b4ea054d4cf3c73fe53c03 Mon Sep 17 00:00:00 2001 From: lanshuyue <2000011337@stu.pku.edu.cn> Date: Tue, 3 Nov 2026 14:13:45 +0800 Subject: [PATCH 5/6] Limit PW DFT+U regression to changed first-step cases --- .../validation_tools/test_dftu_first_step_restart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integrate/validation_tools/test_dftu_first_step_restart.py b/tests/integrate/validation_tools/test_dftu_first_step_restart.py index 8da41d5b7a4..f097236299f 100644 --- a/tests/integrate/validation_tools/test_dftu_first_step_restart.py +++ b/tests/integrate/validation_tools/test_dftu_first_step_restart.py @@ -81,11 +81,11 @@ def main(): fixture = Path(sys.argv[2]).resolve() with tempfile.TemporaryDirectory(prefix="abacus_dftu_pw_first_step_") as scratch: root = Path(scratch) - for init_occ_mat in (1, 2, 0): + for init_occ_mat in (2, 0): work = root / f"init_occ_mat_{init_occ_mat}" work.mkdir() run_case(executable, fixture, work, init_occ_mat) - print("PW DFT+U first-step output checks passed for init_occ_mat=0, 1, and 2") + print("PW DFT+U first-step output checks passed for init_occ_mat=0 and 2") if __name__ == "__main__": From 17071f708ea15eee8467329e47cf008a3704a14b Mon Sep 17 00:00:00 2001 From: lanshuyue <2000011337@stu.pku.edu.cn> Date: Tue, 3 Nov 2026 20:35:07 +0800 Subject: [PATCH 6/6] Remove standalone PW DFT+U regression test --- tests/01_PW/CMakeLists.txt | 8 -- .../test_dftu_first_step_restart.py | 92 ------------------- 2 files changed, 100 deletions(-) delete mode 100644 tests/integrate/validation_tools/test_dftu_first_step_restart.py diff --git a/tests/01_PW/CMakeLists.txt b/tests/01_PW/CMakeLists.txt index 65ebffbe071..dd47e77df86 100644 --- a/tests/01_PW/CMakeLists.txt +++ b/tests/01_PW/CMakeLists.txt @@ -1,14 +1,6 @@ enable_testing() find_program(BASH bash) -if(ENABLE_MPI) - find_package(Python3 COMPONENTS Interpreter REQUIRED) - add_test( - NAME 01_PW_dftu_first_step_restart - COMMAND ${Python3_EXECUTABLE} - ${ABACUS_TEST_DIR}/integrate/validation_tools/test_dftu_first_step_restart.py - ${ABACUS_BIN_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/relax_dftu_spin2) -endif() if(ENABLE_ASAN) add_test( NAME 01_PW_test_with_asan diff --git a/tests/integrate/validation_tools/test_dftu_first_step_restart.py b/tests/integrate/validation_tools/test_dftu_first_step_restart.py deleted file mode 100644 index f097236299f..00000000000 --- a/tests/integrate/validation_tools/test_dftu_first_step_restart.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Check first-step PW DFT+U output with and without a loaded matrix.""" - -import os -from pathlib import Path -import shutil -import subprocess -import sys -import tempfile - - -def replace_setting(text, old, new): - if old not in text: - raise AssertionError(f"Missing fixture setting: {old}") - return text.replace(old, new, 1) - - -def first_matrix_element(text): - lines = text.splitlines() - for index, line in enumerate(lines[:-1]): - if line.strip().startswith("spin 1 nelec"): - return float(lines[index + 1].split()[0]) - raise AssertionError("First spin matrix is missing") - - -def run_case(executable, fixture, work, init_occ_mat): - for name in ("KPT", "STRU"): - shutil.copy2(fixture / name, work / name) - if init_occ_mat: - shutil.copy2(fixture / "occ_mat.txt", work / "occ_mat.txt") - - input_text = (fixture / "INPUT").read_text() - input_text = replace_setting(input_text, "calculation relax", "calculation scf") - input_text = replace_setting(input_text, "scf_nmax 70", "scf_nmax 1") - input_text = replace_setting(input_text, "out_freq_ion 2", "out_freq_ion 1") - if init_occ_mat != 2: - input_text = replace_setting(input_text, "init_occ_mat 2", f"init_occ_mat {init_occ_mat}") - data_dir = fixture.parent.parent / "PP_ORB" - input_text = replace_setting(input_text, "pseudo_dir ../../PP_ORB/", f"pseudo_dir {data_dir}/") - input_text = replace_setting(input_text, "orbital_dir ../../PP_ORB/", f"orbital_dir {data_dir}/") - (work / "INPUT").write_text(input_text) - - output_dir = work / "OUT.autotest" - output_dir.mkdir() - numbered = output_dir / "occ_matg1.txt" - numbered.write_text("# Electronic step 99\nSTALE SECTION\n") - - env = os.environ.copy() - env["OMP_NUM_THREADS"] = "1" - result = subprocess.run( - [str(executable)], cwd=work, env=env, capture_output=True, text=True, timeout=180 - ) - if result.returncode != 0: - raise AssertionError( - f"ABACUS failed with init_occ_mat={init_occ_mat}: {result.returncode}\n" - + (result.stdout + result.stderr)[-4000:] - ) - - assert numbered.exists(), "Numbered occupation output is missing" - snapshot = numbered.read_text() - assert "STALE SECTION" not in snapshot - assert snapshot.count("# Electronic step 1") == 1 - latest = output_dir / "occ_mat.txt" - if init_occ_mat: - assert latest.exists(), "Loaded matrix was not written at the first PW step" - latest_text = latest.read_text() - assert "# Electronic step 1" in latest_text - assert "Fe Atom 1 L 2" in latest_text - assert "Fe Atom 1 L 2" in snapshot - assert "N/A" not in latest_text + snapshot - source_value = first_matrix_element((fixture / "occ_mat.txt").read_text()) - assert abs(first_matrix_element(latest_text) - source_value) < 1e-6 - assert abs(first_matrix_element(snapshot) - source_value) < 1e-6 - else: - assert not latest.exists(), "Fresh PW run wrote an unavailable first-step matrix" - assert "N/A" in snapshot - assert "Fe Atom 1 L 2" not in snapshot - - -def main(): - executable = Path(sys.argv[1]).resolve() - fixture = Path(sys.argv[2]).resolve() - with tempfile.TemporaryDirectory(prefix="abacus_dftu_pw_first_step_") as scratch: - root = Path(scratch) - for init_occ_mat in (2, 0): - work = root / f"init_occ_mat_{init_occ_mat}" - work.mkdir() - run_case(executable, fixture, work, init_occ_mat) - print("PW DFT+U first-step output checks passed for init_occ_mat=0 and 2") - - -if __name__ == "__main__": - main()