From 8883cd0b7d25495afb23dee820e9a30452584a26 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Wed, 28 Oct 2026 13:55:18 +0800 Subject: [PATCH 01/15] Fix relaxation termination summary status --- source/source_relax/relax_driver.cpp | 82 ++++++++++++------- source/source_relax/relax_driver.h | 19 ++++- source/source_relax/relax_nsync.cpp | 30 +------ .../unittests/test_relax_nsync.cpp | 41 +++++++--- .../unittests/test_relax_sync.cpp | 5 +- 5 files changed, 107 insertions(+), 70 deletions(-) diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 7a0b124ee0..f63435e3c1 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -51,36 +51,57 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, // After relax_step() proposes a new geometry, it is not evaluated until // the next esolve() call; if we exit the loop early, force/stress are stale. bool geometry_evaluated = false; + int ionic_steps = 0; + RelaxStopReason stop_reason = RelaxStopReason::Running; + + if (inp.relax_nmax == 0 + && (inp.calculation == "relax" || inp.calculation == "cell-relax")) + { + stop_reason = RelaxStopReason::DryRun; + } while (steps[0] < inp.relax_nmax) { this->iter_info(steps, inp); this->esolve(steps[0], p_esolver, ucell, inp, force, stress, etot); + ++ionic_steps; geometry_evaluated = true; this->stru_out(steps[0], ucell, inp, etot, stress, force); bool converged = this->relax_step(steps, p_esolver, ucell, inp, force, stress, etot, ofs_running); - if (!converged) - { - geometry_evaluated = false; - } this->json_out(p_esolver, ucell, inp, force, stress); // Check stop conditions if (converged) { - // Relaxation converged, exit loop immediately + if (inp.calculation == "relax" || inp.calculation == "cell-relax") + { + stop_reason = RelaxStopReason::Converged; + } + else + { + stop_reason = RelaxStopReason::Finished; + } + break; + } + + geometry_evaluated = false; + + if (ModuleIO::read_exit_file(my_rank_, "EXIT", ofs_running)) + { + stop_reason = RelaxStopReason::UserExit; break; } - else if (ModuleIO::read_exit_file(my_rank_, "EXIT", ofs_running)) + + if (ionic_steps >= inp.relax_nmax) { - // EXIT file detected, exit loop + stop_reason = RelaxStopReason::MaxSteps; break; } ++steps[0]; } - this->final_out(steps[0], ucell, inp, etot, stress, force, geometry_evaluated, ofs_running); + this->final_out(steps[0], ionic_steps, stop_reason, ucell, inp, etot, stress, force, geometry_evaluated, ofs_running); ModuleBase::timer::end("Relax_Driver", "relax_driver"); return; @@ -260,6 +281,8 @@ void Relax_Driver::json_out(ModuleESolver::ESolver* p_esolver, } void Relax_Driver::final_out(const int istep, + const int ionic_steps, + const RelaxStopReason stop_reason, UnitCell& ucell, const Input_para& inp, const double etot, @@ -294,37 +317,37 @@ void Relax_Driver::final_out(const int istep, write_force); } - // relax_nmax == 0 is a valid dry-run mode: no relaxation step was ever - // taken, so it must not be reported as a failed relaxation. - if (istep == inp.relax_nmax && inp.relax_nmax > 0) + if (stop_reason == RelaxStopReason::MaxSteps) { - if (is_relax) - { - print_not_converged_summary(inp, ofs_running); + print_not_converged_summary(ionic_steps, inp, ofs_running); - std::cout << "\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; - std::cout << " Geometry relaxation stops here due to reaching the maximum " << std::endl; - std::cout << " relaxation steps. More steps are needed to converge the results " << std::endl; - std::cout << " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; - } + std::cout << "\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; + std::cout << " Geometry relaxation stops here due to reaching the maximum " << std::endl; + std::cout << " relaxation steps. More steps are needed to converge the results " << std::endl; + std::cout << " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; } - else if (inp.relax_nmax > 0) + else if (stop_reason == RelaxStopReason::Converged) { - if (is_relax) - { - std::cout << "\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; - std::cout << " Geometry relaxation thresholds are reached within " << istep << " steps." << std::endl; - std::cout << " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; - } + std::cout << "\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; + std::cout << " Geometry relaxation thresholds are reached within " << ionic_steps << " steps." << std::endl; + std::cout << " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; } - - if (is_relax && inp.relax_nmax == 0) + else if (stop_reason == RelaxStopReason::UserExit) + { + ofs_running << " Geometry relaxation stopped by EXIT after " + << ionic_steps << " ionic step(s)." << std::endl; + std::cout << " Geometry relaxation stopped by EXIT after " + << ionic_steps << " ionic step(s)." << std::endl; + } + else if (stop_reason == RelaxStopReason::DryRun) { print_dry_run_message(); } } -void Relax_Driver::print_not_converged_summary(const Input_para& inp, std::ofstream& ofs_running) const +void Relax_Driver::print_not_converged_summary(const int ionic_steps, + const Input_para& inp, + std::ofstream& ofs_running) const { // Unified not-converged summary for both relaxation paths so ASE and // users can read the method, the actual number of steps taken, and @@ -333,7 +356,6 @@ void Relax_Driver::print_not_converged_summary(const Input_para& inp, std::ofstr = inp.uses_simultaneous_relaxation() ? rl.get_max_force_history() : rl_old.get_max_force_history(); const std::vector& stress_hist = inp.uses_simultaneous_relaxation() ? rl.get_max_stress_history() : rl_old.get_max_stress_history(); - const int ionic_steps = static_cast(force_hist.size()); const std::string method = inp.relax_method.empty() ? "unknown" : inp.relax_method; ofs_running << " Relaxation method: " << method << std::endl; ofs_running << " Relaxation stopped after " << ionic_steps << " ionic step(s) (relax_nmax = " << inp.relax_nmax diff --git a/source/source_relax/relax_driver.h b/source/source_relax/relax_driver.h index 7050eba29b..b1940f14ca 100644 --- a/source/source_relax/relax_driver.h +++ b/source/source_relax/relax_driver.h @@ -43,6 +43,16 @@ class Relax_Driver std::ofstream& ofs_running); private: + enum class RelaxStopReason + { + Running, + Converged, + MaxSteps, + UserExit, + DryRun, + Finished + }; + /// New relaxation optimizer (Relax class) Relax rl; /// Old relaxation optimizer (IonCellOptimizer class) @@ -148,6 +158,8 @@ class Relax_Driver * @brief Output final results after relaxation. * * @param istep Final iteration step. + * @param ionic_steps Number of evaluated ionic steps. + * @param stop_reason Reason why the calculation stopped. * @param ucell Reference to the unit cell. * @param inp Input parameters for the calculation. * @param etot Total energy of the final step. @@ -158,6 +170,8 @@ class Relax_Driver * omitted / marked N/A. */ void final_out(const int istep, + const int ionic_steps, + const RelaxStopReason stop_reason, UnitCell& ucell, const Input_para& inp, const double etot, @@ -173,10 +187,13 @@ class Relax_Driver * per-step largest force / stress history, so ASE and users can diagnose * why the relaxation stopped at relax_nmax. * + * @param ionic_steps Number of evaluated ionic steps. * @param inp Input parameters (selects the active optimizer and relax_nmax). * @param ofs_running Output stream for the running log. */ - void print_not_converged_summary(const Input_para& inp, std::ofstream& ofs_running) const; + void print_not_converged_summary(const int ionic_steps, + const Input_para& inp, + std::ofstream& ofs_running) const; /** * @brief Print the dry-run success message to screen. diff --git a/source/source_relax/relax_nsync.cpp b/source/source_relax/relax_nsync.cpp index 0bd4732b29..345e800b65 100644 --- a/source/source_relax/relax_nsync.cpp +++ b/source/source_relax/relax_nsync.cpp @@ -54,14 +54,13 @@ void IonCellOptimizer::init_relax(const int& natom, const Input_para& inp) * @brief Perform one step of relaxation (atomic and/or cell). * * Main relaxation loop that coordinates atomic and cell relaxation: - * 1. Check for maximum iteration limit - * 2. Determine calculation mode (relax vs cell-relax) - * 3. Perform atomic relaxation if needed and atoms can move - * 4. If in cell-relax mode and atomic relaxation converged, perform cell relaxation + * 1. Determine calculation mode (relax vs cell-relax) + * 2. Perform atomic relaxation if needed and atoms can move + * 3. If in cell-relax mode and atomic relaxation converged, perform cell relaxation * * Convergence behavior: * - Returns false if relaxation is still in progress - * - Returns true if relaxation has converged or maximum iterations reached + * - Returns true if the current geometry has converged * * @param istep Current total iteration step * @param energy Total energy of the system @@ -95,27 +94,6 @@ bool IonCellOptimizer::relax_step(const int& istep, return true; } - // Check if we've reached the maximum number of iterations. - if (istep == inp_->relax_nmax) - { - // This step never ran cal_movement, so no force was recorded for it; - // the actual number of ionic steps taken is the history length. - const int ionic_steps = static_cast(max_force_history_.size()); - ofs_running << " Relaxation method: " << inp_->relax_method << std::endl; - ofs_running << " Relaxation stopped after " << ionic_steps - << " ionic step(s) (relax_nmax = " << inp_->relax_nmax << " reached)." << std::endl; - if (!max_force_history_.empty()) - { - ofs_running << " Largest force per step (eV/Angstrom):" << format_relax_history(max_force_history_); - } - if (!max_stress_history_.empty()) - { - ofs_running << " Largest stress per step (kbar):" << format_relax_history(max_stress_history_); - } - ofs_running << " Relaxation is not converged after reaching relax_nmax!" << std::endl; - return true; - } - // Determine calculation mode const bool is_cell_relax = (inp_->calculation == "cell-relax"); const bool is_relax = (inp_->calculation == "relax"); diff --git a/source/source_relax/unittests/test_relax_nsync.cpp b/source/source_relax/unittests/test_relax_nsync.cpp index 7351c5eccf..42bef17a71 100644 --- a/source/source_relax/unittests/test_relax_nsync.cpp +++ b/source/source_relax/unittests/test_relax_nsync.cpp @@ -23,7 +23,6 @@ * Largest stress is ... kbar while threshold is ... kbar * Relaxation is converged! * Relaxation is not converged yet! - * Relaxation is not converged after reaching relax_nmax! * The markers are what the ASE interface greps for (issue #6546). */ @@ -252,13 +251,16 @@ TEST_F(IonCellOptimizerTest, RelaxNotConverged) EXPECT_THAT(log, testing::Not(testing::HasSubstr("Relaxation is converged!"))); } -// Reaching relax_nmax must terminate with an explicit not-converged marker. -TEST_F(IonCellOptimizerTest, RelaxNmaxReached) +// The last allowed ionic step must still be checked for convergence before +// the driver reports that relax_nmax was reached. +TEST_F(IonCellOptimizerTest, RelaxNmaxLastStepConverged) { UnitCell ucell; setup_ucell(ucell); + inp.relax_nmax = 1; optimizer.init_relax(natom, inp); ModuleBase::matrix force(natom, 3); + force(0, 0) = 1.0e-4; ModuleBase::matrix stress(3, 3); int force_step = 1; int stress_step = 1; @@ -268,15 +270,34 @@ TEST_F(IonCellOptimizerTest, RelaxNmaxReached) ofs.close(); EXPECT_TRUE(done); - // Key invariant: when relax_nmax terminates the loop, cal_movement is - // skipped and the geometry is NOT touched. The force/stress captured by - // the driver before relax_step are still consistent with ucell, so the - // driver's geometry_evaluated=true is honest. - EXPECT_FALSE(ucell.ionic_position_updated); const std::string log = read_log(); EXPECT_THAT(log, testing::HasSubstr(" Relaxation method: lbfgs")); - EXPECT_THAT(log, testing::HasSubstr(" ionic step(s) (relax_nmax = 50 reached).")); - EXPECT_THAT(log, testing::HasSubstr(" Relaxation is not converged after reaching relax_nmax!")); + EXPECT_THAT(log, testing::HasSubstr(" Relaxation converged in 1 step(s).")); + EXPECT_THAT(log, testing::HasSubstr("\n Relaxation is converged!")); +} + +// A non-converged last allowed step must return not-converged to the driver; +// the driver, rather than IonCellOptimizer, owns the relax_nmax summary. +TEST_F(IonCellOptimizerTest, RelaxNmaxLastStepNotConverged) +{ + UnitCell ucell; + setup_ucell(ucell); + inp.relax_nmax = 1; + optimizer.init_relax(natom, inp); + ModuleBase::matrix force(natom, 3); + force(0, 0) = 1.0; + ModuleBase::matrix stress(3, 3); + int force_step = 1; + int stress_step = 1; + std::ofstream ofs(log_file); + + const bool done = optimizer.relax_step(inp.relax_nmax, etot, ucell, force, stress, force_step, stress_step, ofs); + ofs.close(); + + EXPECT_FALSE(done); + const std::string log = read_log(); + EXPECT_THAT(log, testing::HasSubstr(" Relaxation is not converged yet!")); + EXPECT_THAT(log, testing::Not(testing::HasSubstr(" relax_nmax = 1 reached."))); EXPECT_THAT(log, testing::Not(testing::HasSubstr("Relaxation is converged!"))); } diff --git a/source/source_relax/unittests/test_relax_sync.cpp b/source/source_relax/unittests/test_relax_sync.cpp index dc27dd0157..09f114c481 100644 --- a/source/source_relax/unittests/test_relax_sync.cpp +++ b/source/source_relax/unittests/test_relax_sync.cpp @@ -780,8 +780,7 @@ TEST(RelaxSyncCellMove, FixedShapeIsotropicScale) // --------------------------------------------------------------------------- // Max-step termination on the Relax (simultaneous) path. // -// Unlike IonCellOptimizer::relax_step (which checks istep == relax_nmax and -// returns true without moving), Relax::relax_step has no max-step guard. +// Relax::relax_step has no max-step guard. // When the driver loop hits relax_nmax, the last relax_step still calls // move_cell_ions, so the geometry IS updated and the force/stress in the // driver become stale. The driver then sets geometry_evaluated=false. @@ -973,4 +972,4 @@ TEST(RelaxSyncCellMove, MixedMblMovesOnlyFreeComponents) EXPECT_NE(ucell.atoms[1].taud[1].z, 0.0); free_two_type_cell(ucell); -} \ No newline at end of file +} From 5c1317d27b8ac33a7324db23e0ccbd65666932be Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Wed, 28 Oct 2026 14:10:12 +0800 Subject: [PATCH 02/15] style: keep relax dry-run condition on one line --- source/source_relax/relax_driver.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index f63435e3c1..42480d05ba 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -54,8 +54,7 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, int ionic_steps = 0; RelaxStopReason stop_reason = RelaxStopReason::Running; - if (inp.relax_nmax == 0 - && (inp.calculation == "relax" || inp.calculation == "cell-relax")) + if (inp.relax_nmax == 0 && (inp.calculation == "relax" || inp.calculation == "cell-relax")) { stop_reason = RelaxStopReason::DryRun; } From 652b6ccfca74e5a6a65f475902b2f8a4d12646dd Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Wed, 28 Oct 2026 14:21:39 +0800 Subject: [PATCH 03/15] refactor: remove unused relaxation running state --- source/source_relax/relax_driver.cpp | 2 +- source/source_relax/relax_driver.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 42480d05ba..3601c35b8b 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -52,7 +52,7 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, // the next esolve() call; if we exit the loop early, force/stress are stale. bool geometry_evaluated = false; int ionic_steps = 0; - RelaxStopReason stop_reason = RelaxStopReason::Running; + RelaxStopReason stop_reason = RelaxStopReason::Finished; if (inp.relax_nmax == 0 && (inp.calculation == "relax" || inp.calculation == "cell-relax")) { diff --git a/source/source_relax/relax_driver.h b/source/source_relax/relax_driver.h index b1940f14ca..3f388dd6d9 100644 --- a/source/source_relax/relax_driver.h +++ b/source/source_relax/relax_driver.h @@ -45,7 +45,6 @@ class Relax_Driver private: enum class RelaxStopReason { - Running, Converged, MaxSteps, UserExit, From 3aac8b69d7b3ef2fa2aae7f37100569dbf9b0ae3 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Wed, 28 Oct 2026 14:36:38 +0800 Subject: [PATCH 04/15] refactor: derive reported relaxation steps --- source/source_relax/relax_driver.cpp | 13 ++++++++----- source/source_relax/relax_driver.h | 2 -- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 3601c35b8b..72b94ad470 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -51,7 +51,6 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, // After relax_step() proposes a new geometry, it is not evaluated until // the next esolve() call; if we exit the loop early, force/stress are stale. bool geometry_evaluated = false; - int ionic_steps = 0; RelaxStopReason stop_reason = RelaxStopReason::Finished; if (inp.relax_nmax == 0 && (inp.calculation == "relax" || inp.calculation == "cell-relax")) @@ -63,7 +62,6 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, { this->iter_info(steps, inp); this->esolve(steps[0], p_esolver, ucell, inp, force, stress, etot); - ++ionic_steps; geometry_evaluated = true; this->stru_out(steps[0], ucell, inp, etot, stress, force); bool converged = this->relax_step(steps, p_esolver, ucell, inp, force, stress, etot, ofs_running); @@ -91,7 +89,7 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, break; } - if (ionic_steps >= inp.relax_nmax) + if (steps[0] + 1 >= inp.relax_nmax) { stop_reason = RelaxStopReason::MaxSteps; break; @@ -100,7 +98,7 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, ++steps[0]; } - this->final_out(steps[0], ionic_steps, stop_reason, ucell, inp, etot, stress, force, geometry_evaluated, ofs_running); + this->final_out(steps[0], stop_reason, ucell, inp, etot, stress, force, geometry_evaluated, ofs_running); ModuleBase::timer::end("Relax_Driver", "relax_driver"); return; @@ -280,7 +278,6 @@ void Relax_Driver::json_out(ModuleESolver::ESolver* p_esolver, } void Relax_Driver::final_out(const int istep, - const int ionic_steps, const RelaxStopReason stop_reason, UnitCell& ucell, const Input_para& inp, @@ -290,6 +287,12 @@ void Relax_Driver::final_out(const int istep, const bool geometry_evaluated, std::ofstream& ofs_running) { + int ionic_steps = istep + 1; + if (stop_reason == RelaxStopReason::DryRun) + { + ionic_steps = 0; + } + // Structure final output is effective for scf/nscf/relax/cell-relax; // relax-specific screen messages remain guarded below. const bool is_relax = (inp.calculation == "relax" || inp.calculation == "cell-relax"); diff --git a/source/source_relax/relax_driver.h b/source/source_relax/relax_driver.h index 3f388dd6d9..34540f00b7 100644 --- a/source/source_relax/relax_driver.h +++ b/source/source_relax/relax_driver.h @@ -157,7 +157,6 @@ class Relax_Driver * @brief Output final results after relaxation. * * @param istep Final iteration step. - * @param ionic_steps Number of evaluated ionic steps. * @param stop_reason Reason why the calculation stopped. * @param ucell Reference to the unit cell. * @param inp Input parameters for the calculation. @@ -169,7 +168,6 @@ class Relax_Driver * omitted / marked N/A. */ void final_out(const int istep, - const int ionic_steps, const RelaxStopReason stop_reason, UnitCell& ucell, const Input_para& inp, From 21436416a567e35fe69d8b234e1bdd27a8a93d76 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Thu, 29 Oct 2026 18:12:43 +0800 Subject: [PATCH 05/15] fix: prevent unevaluated final relaxation geometry --- source/source_relax/ions_move_bfgs2.cpp | 12 +++-- source/source_relax/ions_move_lbfgs.cpp | 12 ++--- source/source_relax/ions_move_methods.cpp | 32 ++++++++++++++ source/source_relax/ions_move_methods.h | 3 ++ source/source_relax/lat_change_method.cpp | 12 +++++ source/source_relax/lat_change_method.h | 4 ++ source/source_relax/relax_driver.cpp | 19 ++++++-- source/source_relax/relax_nsync.cpp | 44 ++++++++++++++----- source/source_relax/relax_sync.cpp | 8 ++++ source/source_relax/relax_sync.h | 6 ++- .../unittests/test_relax_nsync.cpp | 26 +++++++++++ 11 files changed, 152 insertions(+), 26 deletions(-) diff --git a/source/source_relax/ions_move_bfgs2.cpp b/source/source_relax/ions_move_bfgs2.cpp index 25d731bcea..8b14a9efad 100644 --- a/source/source_relax/ions_move_bfgs2.cpp +++ b/source/source_relax/ions_move_bfgs2.cpp @@ -46,7 +46,6 @@ bool Ions_Move_BFGS2::relax_step(const ModuleBase::matrix& _force, UnitCell& uce } GetPos(ucell, pos); GetPostaud(ucell, pos_taud); - ucell.ionic_position_updated = true; assert(_force.nr == force.size() && _force.nc == 3); for (int i = 0; i < _force.nr; i++) { @@ -79,14 +78,19 @@ bool Ions_Move_BFGS2::relax_step(const ModuleBase::matrix& _force, UnitCell& uce k += ucell.atoms[i].na; } + this->CalculateLargestGrad(_force, ucell); + if (this->IsRestrain()) + { + return true; + } + this->PrepareStep(force, pos, H, pos0, force0, steplength, dpos, size, ucell); this->DetermineStep(steplength, dpos, maxstep); this->UpdatePos(ucell); - this->CalculateLargestGrad(_force, ucell); - bool converged = this->IsRestrain(); + ucell.ionic_position_updated = true; // print out geometry information during bfgs_trad relax unitcell::print_tau(ucell.atoms, ucell.Coordinate, ucell.ntype, ucell.lat0, ofs_running); - return converged; + return false; } void Ions_Move_BFGS2::GetPos(UnitCell& ucell, std::vector>& pos) diff --git a/source/source_relax/ions_move_lbfgs.cpp b/source/source_relax/ions_move_lbfgs.cpp index 5fbb01c96c..07d99fd47b 100644 --- a/source/source_relax/ions_move_lbfgs.cpp +++ b/source/source_relax/ions_move_lbfgs.cpp @@ -36,8 +36,6 @@ bool Ions_Move_LBFGS::relax_step(const ModuleBase::matrix _force, { get_pos(ucell, pos); get_pos_taud(ucell, pos_taud); - // solver=p_esolver; - ucell.ionic_position_updated = true; for (int i = 0; i < _force.nr; i++) { for (int j = 0; j < _force.nc; j++) @@ -65,14 +63,18 @@ bool Ions_Move_LBFGS::relax_step(const ModuleBase::matrix _force, } k += ucell.atoms[i].na; } + this->calculate_largest_grad(_force, ucell); + if (this->is_restrain(criteria)) + { + return true; + } this->prepare_step(force, pos, H, pos0, force0, dpos, ucell, etot); this->determine_step(steplength, dpos, maxstep); this->update_pos(ucell); - this->calculate_largest_grad(_force, ucell); - bool converged = this->is_restrain(criteria); + ucell.ionic_position_updated = true; // mohan add 2025-06-22 unitcell::print_tau(ucell.atoms, ucell.Coordinate, ucell.ntype, ucell.lat0, ofs_running); - return converged; + return false; } void Ions_Move_LBFGS::get_pos(UnitCell& ucell, std::vector>& pos) diff --git a/source/source_relax/ions_move_methods.cpp b/source/source_relax/ions_move_methods.cpp index 6929921380..910ec65df6 100644 --- a/source/source_relax/ions_move_methods.cpp +++ b/source/source_relax/ions_move_methods.cpp @@ -65,6 +65,14 @@ void Ions_Move_Methods::cal_movement(const int& istep, const Relax_Criteria& criteria) { ModuleBase::TITLE("Ions_Move_Methods", "init"); + if (relax_method == "bfgs_trad" || relax_method == "lbfgs") + { + this->check_converged(f, ucell, criteria); + if (converged_) + { + return; + } + } if (relax_method == "bfgs") { converged_ = bfgs.start(ucell, f, etot, force_step, update_iter_, ofs, etot_info_, criteria); @@ -97,6 +105,30 @@ void Ions_Move_Methods::cal_movement(const int& istep, return; } +void Ions_Move_Methods::check_converged(const ModuleBase::matrix& force, + const UnitCell& ucell, + const Relax_Criteria& criteria) +{ + Ions_Move_Basic::largest_grad = 0.0; + int iat = 0; + for (int it = 0; it < ucell.ntype; ++it) + { + for (int ia = 0; ia < ucell.atoms[it].na; ++ia) + { + for (int idir = 0; idir < 3; ++idir) + { + if (ucell.atoms[it].mbl[ia][idir]) + { + Ions_Move_Basic::largest_grad = std::max(Ions_Move_Basic::largest_grad, + std::abs(force(iat, idir))); + } + } + ++iat; + } + } + converged_ = Ions_Move_Basic::largest_grad < criteria.force_thr; +} + void Ions_Move_Methods::reset_after_cell_change(const std::string& relax_method, std::ofstream& ofs) { ModuleBase::TITLE("Ions_Move_Methods", "reset_after_cell_change"); diff --git a/source/source_relax/ions_move_methods.h b/source/source_relax/ions_move_methods.h index d52d4116ed..8f93d13190 100644 --- a/source/source_relax/ions_move_methods.h +++ b/source/source_relax/ions_move_methods.h @@ -28,6 +28,9 @@ class Ions_Move_Methods std::ofstream& ofs, std::string& relax_method, const Relax_Criteria& criteria); + void check_converged(const ModuleBase::matrix& force, + const UnitCell& ucell, + const Relax_Criteria& criteria); void reset_after_cell_change(const std::string& relax_method, std::ofstream& ofs); bool get_converged() const diff --git a/source/source_relax/lat_change_method.cpp b/source/source_relax/lat_change_method.cpp index 29900684e3..ab7f660ce4 100644 --- a/source/source_relax/lat_change_method.cpp +++ b/source/source_relax/lat_change_method.cpp @@ -32,3 +32,15 @@ void Lattice_Change_Methods::cal_lattice_change(const int& istep, return; } + +void Lattice_Change_Methods::check_converged(const ModuleBase::matrix& stress, + const UnitCell& ucell, + std::ofstream& ofs, + const Relax_Criteria& criteria) +{ + std::vector lat(9, 0.0); + std::vector grad(9, 0.0); + ModuleBase::matrix stress_copy(stress); + Lattice_Change_Basic::setup_gradient(ucell, lat.data(), grad.data(), stress_copy); + converged_ = Lattice_Change_Basic::check_converged(ucell, stress_copy, grad.data(), ofs, criteria.stress_thr); +} diff --git a/source/source_relax/lat_change_method.h b/source/source_relax/lat_change_method.h index 304cfae7b8..e598054919 100644 --- a/source/source_relax/lat_change_method.h +++ b/source/source_relax/lat_change_method.h @@ -24,6 +24,10 @@ class Lattice_Change_Methods UnitCell& ucell, std::ofstream& ofs, const Relax_Criteria& criteria); + void check_converged(const ModuleBase::matrix& stress, + const UnitCell& ucell, + std::ofstream& ofs, + const Relax_Criteria& criteria); bool get_converged(void) const { diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 72b94ad470..4e6849097a 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -64,10 +64,15 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, this->esolve(steps[0], p_esolver, ucell, inp, force, stress, etot); geometry_evaluated = true; this->stru_out(steps[0], ucell, inp, etot, stress, force); - bool converged = this->relax_step(steps, p_esolver, ucell, inp, force, stress, etot, ofs_running); this->json_out(p_esolver, ucell, inp, force, stress); + bool converged = this->relax_step(steps, p_esolver, ucell, inp, force, stress, etot, ofs_running); // Check stop conditions + if (ucell.ionic_position_updated || ucell.cell_parameter_updated) + { + geometry_evaluated = false; + } + if (converged) { if (inp.calculation == "relax" || inp.calculation == "cell-relax") @@ -81,8 +86,6 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, break; } - geometry_evaluated = false; - if (ModuleIO::read_exit_file(my_rank_, "EXIT", ofs_running)) { stop_reason = RelaxStopReason::UserExit; @@ -180,10 +183,18 @@ bool Relax_Driver::relax_step(std::vector& steps, } bool converged = false; + const bool last_ionic_step = steps[0] + 1 >= inp.relax_nmax; if (inp.uses_simultaneous_relaxation()) { - converged = this->rl.relax_step(ucell, force, stress, etot, ofs_running); + if (last_ionic_step) + { + converged = this->rl.check_converged(ucell, force, stress, ofs_running); + } + else + { + converged = this->rl.relax_step(ucell, force, stress, etot, ofs_running); + } // stress step +1 steps[2]++; // fix force step to 1 diff --git a/source/source_relax/relax_nsync.cpp b/source/source_relax/relax_nsync.cpp index 345e800b65..496914455d 100644 --- a/source/source_relax/relax_nsync.cpp +++ b/source/source_relax/relax_nsync.cpp @@ -129,7 +129,14 @@ bool IonCellOptimizer::relax_step(const int& istep, criteria.out_level = inp_->out_level; criteria.test_relax_method = inp_->test_relax_method; - IMM.cal_movement(istep, force_step, force, energy, ucell, ofs_running, relax_method, criteria); + if (istep >= inp_->relax_nmax) + { + IMM.check_converged(force, ucell, criteria); + } + else + { + IMM.cal_movement(istep, force_step, force, energy, ucell, ofs_running, relax_method, criteria); + } ++force_step; atom_relax_performed = true; atom_converged = IMM.get_converged(); @@ -141,7 +148,10 @@ bool IonCellOptimizer::relax_step(const int& istep, if (!atom_converged) { ofs_running << "\n Relaxation is not converged yet!" << std::endl; - ucell.ionic_position_updated = true; + if (istep < inp_->relax_nmax) + { + ucell.ionic_position_updated = true; + } return false; // not converged } else if (!is_cell_relax) @@ -182,7 +192,14 @@ bool IonCellOptimizer::relax_step(const int& istep, criteria.out_level = inp_->out_level; criteria.test_relax_method = inp_->test_relax_method; - LCM.cal_lattice_change(istep, stress_step, stress, energy, ucell, ofs_running, criteria); + if (istep >= inp_->relax_nmax) + { + LCM.check_converged(stress, ucell, ofs_running, criteria); + } + else + { + LCM.cal_lattice_change(istep, stress_step, stress, energy, ucell, ofs_running, criteria); + } bool converged = LCM.get_converged(); const double max_stress_kbar = LCM.get_largest_grad(); @@ -196,15 +213,18 @@ bool IonCellOptimizer::relax_step(const int& istep, else { ofs_running << "\n Relaxation is not converged yet!" << std::endl; - // Reset force_step counter after cell change for fresh atomic relaxation - force_step = 1; - stress_step++; - IMM.reset_after_cell_change(inp_->relax_method, ofs_running); - ucell.cell_parameter_updated = true; - - // Update cell-related parameters after volume change - unitcell::setup_cell_after_vc(ucell, ofs_running, inp_->nspin); - ModuleBase::GlobalFunc::DONE(ofs_running, "SETUP UNITCELL"); + if (istep < inp_->relax_nmax) + { + // Reset force_step counter after cell change for fresh atomic relaxation + force_step = 1; + stress_step++; + IMM.reset_after_cell_change(inp_->relax_method, ofs_running); + ucell.cell_parameter_updated = true; + + // Update cell-related parameters after volume change + unitcell::setup_cell_after_vc(ucell, ofs_running, inp_->nspin); + ModuleBase::GlobalFunc::DONE(ofs_running, "SETUP UNITCELL"); + } } return converged; diff --git a/source/source_relax/relax_sync.cpp b/source/source_relax/relax_sync.cpp index 34df2b21cc..276f4dc71f 100644 --- a/source/source_relax/relax_sync.cpp +++ b/source/source_relax/relax_sync.cpp @@ -102,6 +102,14 @@ bool Relax::relax_step(UnitCell& ucell, return relax_done; } +bool Relax::check_converged(UnitCell& ucell, + const ModuleBase::matrix& force, + const ModuleBase::matrix& stress, + std::ofstream& ofs_running) +{ + return this->setup_gradient(ucell, force, stress, ofs_running); +} + bool Relax::setup_gradient(const UnitCell& ucell, const ModuleBase::matrix& force, const ModuleBase::matrix& stress, diff --git a/source/source_relax/relax_sync.h b/source/source_relax/relax_sync.h index 8cdf70a27e..0c03513bfc 100644 --- a/source/source_relax/relax_sync.h +++ b/source/source_relax/relax_sync.h @@ -26,6 +26,10 @@ class Relax const ModuleBase::matrix& stress, const double etot_in, std::ofstream& ofs_running); + bool check_converged(UnitCell& ucell, + const ModuleBase::matrix& force, + const ModuleBase::matrix& stress, + std::ofstream& ofs_running); private: int istep = 0; // count ionic step @@ -170,4 +174,4 @@ class Relax } }; -#endif \ No newline at end of file +#endif diff --git a/source/source_relax/unittests/test_relax_nsync.cpp b/source/source_relax/unittests/test_relax_nsync.cpp index 42bef17a71..576740a96e 100644 --- a/source/source_relax/unittests/test_relax_nsync.cpp +++ b/source/source_relax/unittests/test_relax_nsync.cpp @@ -1,6 +1,7 @@ #include "source_base/global_variable.h" #include "source_cell/unitcell.h" #include "source_io/module_parameter/parameter.h" +#include "source_relax/ions_move_bfgs2.h" #include "source_relax/lattice_change_basic.h" #include "source_relax/relax_nsync.h" @@ -226,6 +227,27 @@ TEST_F(IonCellOptimizerTest, RelaxConverged) EXPECT_THAT(log, testing::HasSubstr(" Largest force per step (eV/Angstrom):")); EXPECT_THAT(log, testing::HasSubstr("\n Relaxation is converged!")); EXPECT_THAT(log, testing::Not(testing::HasSubstr("not converged"))); + EXPECT_FALSE(ucell.ionic_position_updated); +} + +TEST_F(IonCellOptimizerTest, BfgsTradConvergedBeforeMove) +{ + inp.relax_method = "bfgs_trad"; + inp.relax_nmax = 2; + UnitCell ucell; + setup_ucell(ucell); + optimizer.init_relax(natom, inp); + ModuleBase::matrix force(natom, 3); + ModuleBase::matrix stress(3, 3); + int force_step = 1; + int stress_step = 1; + std::ofstream ofs(log_file); + + const bool done = optimizer.relax_step(1, etot, ucell, force, stress, force_step, stress_step, ofs); + ofs.close(); + + EXPECT_TRUE(done); + EXPECT_FALSE(ucell.ionic_position_updated); } // relax + large force: not converged, and no ASE converged marker may appear. @@ -295,6 +317,7 @@ TEST_F(IonCellOptimizerTest, RelaxNmaxLastStepNotConverged) ofs.close(); EXPECT_FALSE(done); + EXPECT_FALSE(ucell.ionic_position_updated); const std::string log = read_log(); EXPECT_THAT(log, testing::HasSubstr(" Relaxation is not converged yet!")); EXPECT_THAT(log, testing::Not(testing::HasSubstr(" relax_nmax = 1 reached."))); @@ -336,6 +359,7 @@ TEST_F(IonCellOptimizerTest, CellRelaxConverged) TEST_F(IonCellOptimizerTest, CellRelaxNotConverged) { inp.calculation = "cell-relax"; + inp.relax_nmax = 1; UnitCell ucell; setup_ucell(ucell); optimizer.init_relax(natom, inp); @@ -351,6 +375,8 @@ TEST_F(IonCellOptimizerTest, CellRelaxNotConverged) ofs.close(); EXPECT_FALSE(done); + EXPECT_FALSE(ucell.ionic_position_updated); + EXPECT_FALSE(ucell.cell_parameter_updated); const std::string log = read_log(); EXPECT_THAT(log, testing::HasSubstr(" Largest stress is ")); EXPECT_THAT(log, testing::HasSubstr("\n Relaxation is not converged yet!")); From 8e8cee76bddb33aee243dbf1ca4f3342a1427adf Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Thu, 29 Oct 2026 18:40:35 +0800 Subject: [PATCH 06/15] refactor: pass relaxation criteria to traditional bfgs --- source/source_relax/ions_move_bfgs2.cpp | 11 +++++++---- source/source_relax/ions_move_bfgs2.h | 6 ++++-- source/source_relax/ions_move_methods.cpp | 10 +--------- .../source_relax/unittests/test_ions_move_bfgs2.cpp | 6 ++++-- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/source/source_relax/ions_move_bfgs2.cpp b/source/source_relax/ions_move_bfgs2.cpp index 8b14a9efad..e33a2d3ace 100644 --- a/source/source_relax/ions_move_bfgs2.cpp +++ b/source/source_relax/ions_move_bfgs2.cpp @@ -38,7 +38,10 @@ void Ions_Move_BFGS2::reset() is_initialized = false; } -bool Ions_Move_BFGS2::relax_step(const ModuleBase::matrix& _force, UnitCell& ucell, std::ofstream& ofs_running) +bool Ions_Move_BFGS2::relax_step(const ModuleBase::matrix& _force, + UnitCell& ucell, + std::ofstream& ofs_running, + const Relax_Criteria& criteria) { if (!is_initialized) { @@ -79,7 +82,7 @@ bool Ions_Move_BFGS2::relax_step(const ModuleBase::matrix& _force, UnitCell& uce } this->CalculateLargestGrad(_force, ucell); - if (this->IsRestrain()) + if (this->IsRestrain(criteria.force_thr_ev)) { return true; } @@ -374,7 +377,7 @@ void Ions_Move_BFGS2::CalculateLargestGrad(const ModuleBase::matrix& _force, Uni } } -bool Ions_Move_BFGS2::IsRestrain() +bool Ions_Move_BFGS2::IsRestrain(const double force_thr_ev) { - return Ions_Move_Basic::largest_grad * ModuleBase::Ry_to_eV / ModuleBase::BOHR_TO_A < PARAM.inp.force_thr_ev; + return Ions_Move_Basic::largest_grad * ModuleBase::Ry_to_eV / ModuleBase::BOHR_TO_A < force_thr_ev; } diff --git a/source/source_relax/ions_move_bfgs2.h b/source/source_relax/ions_move_bfgs2.h index 69d1c30883..78078169ee 100644 --- a/source/source_relax/ions_move_bfgs2.h +++ b/source/source_relax/ions_move_bfgs2.h @@ -4,6 +4,7 @@ #include "source_base/matrix.h" #include "source_base/matrix3.h" #include "source_cell/unitcell.h" +#include "source_relax/relax_criteria.h" #include #include @@ -18,7 +19,8 @@ class Ions_Move_BFGS2 void reset(); bool relax_step(const ModuleBase::matrix& _force, UnitCell& ucell, - std::ofstream& ofs_running); // a full iteration step + std::ofstream& ofs_running, + const Relax_Criteria& criteria); // a full iteration step /// @brief whether allocate() has run and the optimiser holds live state. /// reset() clears it; ions_move_methods_test checks that. @@ -57,7 +59,7 @@ class Ions_Move_BFGS2 std::vector>& dpos, int& size, UnitCell& ucell); // calculate the atomic displacement in one iteration step - bool IsRestrain(); // check if converged + bool IsRestrain(const double force_thr_ev); // check if converged void CalculateLargestGrad(const ModuleBase::matrix& _force, UnitCell& ucell); void GetPos(UnitCell& ucell, std::vector>& pos); void GetPostaud(UnitCell& ucell, std::vector>& pos_taud); diff --git a/source/source_relax/ions_move_methods.cpp b/source/source_relax/ions_move_methods.cpp index 910ec65df6..c8dbc2986b 100644 --- a/source/source_relax/ions_move_methods.cpp +++ b/source/source_relax/ions_move_methods.cpp @@ -65,14 +65,6 @@ void Ions_Move_Methods::cal_movement(const int& istep, const Relax_Criteria& criteria) { ModuleBase::TITLE("Ions_Move_Methods", "init"); - if (relax_method == "bfgs_trad" || relax_method == "lbfgs") - { - this->check_converged(f, ucell, criteria); - if (converged_) - { - return; - } - } if (relax_method == "bfgs") { converged_ = bfgs.start(ucell, f, etot, force_step, update_iter_, ofs, etot_info_, criteria); @@ -91,7 +83,7 @@ void Ions_Move_Methods::cal_movement(const int& istep, } else if (relax_method == "bfgs_trad") { - converged_ = bfgs_trad.relax_step(f, ucell, ofs); + converged_ = bfgs_trad.relax_step(f, ucell, ofs, criteria); } else if (relax_method == "lbfgs") { diff --git a/source/source_relax/unittests/test_ions_move_bfgs2.cpp b/source/source_relax/unittests/test_ions_move_bfgs2.cpp index c4f0022b37..f3662dbc50 100644 --- a/source/source_relax/unittests/test_ions_move_bfgs2.cpp +++ b/source/source_relax/unittests/test_ions_move_bfgs2.cpp @@ -183,10 +183,11 @@ TEST_F(BFGSTest, RelaxStepAutoInitialize) force(1, 2) = 0.0; std::ofstream ofs_running; + const Relax_Criteria criteria; // Before relax_step, is_initialized should be false EXPECT_FALSE(get_is_initialized()); - bfgs.relax_step(force, ucell, ofs_running); + bfgs.relax_step(force, ucell, ofs_running, criteria); // After relax_step, is_initialized should be true EXPECT_TRUE(get_is_initialized()); } @@ -387,8 +388,9 @@ TEST_F(BFGSTest, RelaxStepBasic) bfgs.allocate(ucell.nat); std::ofstream ofs_running; + const Relax_Criteria criteria; - bfgs.relax_step(force, ucell, ofs_running); + bfgs.relax_step(force, ucell, ofs_running, criteria); // Check that ionic_position_updated is true EXPECT_TRUE(ucell.ionic_position_updated); // Check that force values are set (converted units) From a83bfb5d7f1a8407dcbfc46546452fb530934650 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Thu, 29 Oct 2026 20:20:25 +0800 Subject: [PATCH 07/15] fix: preserve final relaxed coordinates --- source/source_relax/ions_move_methods.cpp | 24 ---------- source/source_relax/ions_move_methods.h | 3 -- source/source_relax/lat_change_method.cpp | 14 ------ source/source_relax/lat_change_method.h | 5 --- source/source_relax/relax_driver.cpp | 10 +---- source/source_relax/relax_nsync.cpp | 44 +++++-------------- source/source_relax/relax_sync.cpp | 8 ---- source/source_relax/relax_sync.h | 5 --- .../unittests/test_relax_nsync.cpp | 8 ++-- 9 files changed, 17 insertions(+), 104 deletions(-) diff --git a/source/source_relax/ions_move_methods.cpp b/source/source_relax/ions_move_methods.cpp index c8dbc2986b..1eabddd924 100644 --- a/source/source_relax/ions_move_methods.cpp +++ b/source/source_relax/ions_move_methods.cpp @@ -97,30 +97,6 @@ void Ions_Move_Methods::cal_movement(const int& istep, return; } -void Ions_Move_Methods::check_converged(const ModuleBase::matrix& force, - const UnitCell& ucell, - const Relax_Criteria& criteria) -{ - Ions_Move_Basic::largest_grad = 0.0; - int iat = 0; - for (int it = 0; it < ucell.ntype; ++it) - { - for (int ia = 0; ia < ucell.atoms[it].na; ++ia) - { - for (int idir = 0; idir < 3; ++idir) - { - if (ucell.atoms[it].mbl[ia][idir]) - { - Ions_Move_Basic::largest_grad = std::max(Ions_Move_Basic::largest_grad, - std::abs(force(iat, idir))); - } - } - ++iat; - } - } - converged_ = Ions_Move_Basic::largest_grad < criteria.force_thr; -} - void Ions_Move_Methods::reset_after_cell_change(const std::string& relax_method, std::ofstream& ofs) { ModuleBase::TITLE("Ions_Move_Methods", "reset_after_cell_change"); diff --git a/source/source_relax/ions_move_methods.h b/source/source_relax/ions_move_methods.h index 8f93d13190..d52d4116ed 100644 --- a/source/source_relax/ions_move_methods.h +++ b/source/source_relax/ions_move_methods.h @@ -28,9 +28,6 @@ class Ions_Move_Methods std::ofstream& ofs, std::string& relax_method, const Relax_Criteria& criteria); - void check_converged(const ModuleBase::matrix& force, - const UnitCell& ucell, - const Relax_Criteria& criteria); void reset_after_cell_change(const std::string& relax_method, std::ofstream& ofs); bool get_converged() const diff --git a/source/source_relax/lat_change_method.cpp b/source/source_relax/lat_change_method.cpp index ab7f660ce4..e153c97929 100644 --- a/source/source_relax/lat_change_method.cpp +++ b/source/source_relax/lat_change_method.cpp @@ -8,7 +8,6 @@ Lattice_Change_Methods::Lattice_Change_Methods() Lattice_Change_Methods::~Lattice_Change_Methods() { } - void Lattice_Change_Methods::allocate() { Lattice_Change_Basic::dim = 9; @@ -16,7 +15,6 @@ void Lattice_Change_Methods::allocate() return; } - void Lattice_Change_Methods::cal_lattice_change(const int& istep, const int& stress_step, const ModuleBase::matrix& stress, @@ -32,15 +30,3 @@ void Lattice_Change_Methods::cal_lattice_change(const int& istep, return; } - -void Lattice_Change_Methods::check_converged(const ModuleBase::matrix& stress, - const UnitCell& ucell, - std::ofstream& ofs, - const Relax_Criteria& criteria) -{ - std::vector lat(9, 0.0); - std::vector grad(9, 0.0); - ModuleBase::matrix stress_copy(stress); - Lattice_Change_Basic::setup_gradient(ucell, lat.data(), grad.data(), stress_copy); - converged_ = Lattice_Change_Basic::check_converged(ucell, stress_copy, grad.data(), ofs, criteria.stress_thr); -} diff --git a/source/source_relax/lat_change_method.h b/source/source_relax/lat_change_method.h index e598054919..ea2d1c451e 100644 --- a/source/source_relax/lat_change_method.h +++ b/source/source_relax/lat_change_method.h @@ -24,11 +24,6 @@ class Lattice_Change_Methods UnitCell& ucell, std::ofstream& ofs, const Relax_Criteria& criteria); - void check_converged(const ModuleBase::matrix& stress, - const UnitCell& ucell, - std::ofstream& ofs, - const Relax_Criteria& criteria); - bool get_converged(void) const { return converged_; diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 4e6849097a..75ce7b355b 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -183,18 +183,10 @@ bool Relax_Driver::relax_step(std::vector& steps, } bool converged = false; - const bool last_ionic_step = steps[0] + 1 >= inp.relax_nmax; if (inp.uses_simultaneous_relaxation()) { - if (last_ionic_step) - { - converged = this->rl.check_converged(ucell, force, stress, ofs_running); - } - else - { - converged = this->rl.relax_step(ucell, force, stress, etot, ofs_running); - } + converged = this->rl.relax_step(ucell, force, stress, etot, ofs_running); // stress step +1 steps[2]++; // fix force step to 1 diff --git a/source/source_relax/relax_nsync.cpp b/source/source_relax/relax_nsync.cpp index 496914455d..345e800b65 100644 --- a/source/source_relax/relax_nsync.cpp +++ b/source/source_relax/relax_nsync.cpp @@ -129,14 +129,7 @@ bool IonCellOptimizer::relax_step(const int& istep, criteria.out_level = inp_->out_level; criteria.test_relax_method = inp_->test_relax_method; - if (istep >= inp_->relax_nmax) - { - IMM.check_converged(force, ucell, criteria); - } - else - { - IMM.cal_movement(istep, force_step, force, energy, ucell, ofs_running, relax_method, criteria); - } + IMM.cal_movement(istep, force_step, force, energy, ucell, ofs_running, relax_method, criteria); ++force_step; atom_relax_performed = true; atom_converged = IMM.get_converged(); @@ -148,10 +141,7 @@ bool IonCellOptimizer::relax_step(const int& istep, if (!atom_converged) { ofs_running << "\n Relaxation is not converged yet!" << std::endl; - if (istep < inp_->relax_nmax) - { - ucell.ionic_position_updated = true; - } + ucell.ionic_position_updated = true; return false; // not converged } else if (!is_cell_relax) @@ -192,14 +182,7 @@ bool IonCellOptimizer::relax_step(const int& istep, criteria.out_level = inp_->out_level; criteria.test_relax_method = inp_->test_relax_method; - if (istep >= inp_->relax_nmax) - { - LCM.check_converged(stress, ucell, ofs_running, criteria); - } - else - { - LCM.cal_lattice_change(istep, stress_step, stress, energy, ucell, ofs_running, criteria); - } + LCM.cal_lattice_change(istep, stress_step, stress, energy, ucell, ofs_running, criteria); bool converged = LCM.get_converged(); const double max_stress_kbar = LCM.get_largest_grad(); @@ -213,18 +196,15 @@ bool IonCellOptimizer::relax_step(const int& istep, else { ofs_running << "\n Relaxation is not converged yet!" << std::endl; - if (istep < inp_->relax_nmax) - { - // Reset force_step counter after cell change for fresh atomic relaxation - force_step = 1; - stress_step++; - IMM.reset_after_cell_change(inp_->relax_method, ofs_running); - ucell.cell_parameter_updated = true; - - // Update cell-related parameters after volume change - unitcell::setup_cell_after_vc(ucell, ofs_running, inp_->nspin); - ModuleBase::GlobalFunc::DONE(ofs_running, "SETUP UNITCELL"); - } + // Reset force_step counter after cell change for fresh atomic relaxation + force_step = 1; + stress_step++; + IMM.reset_after_cell_change(inp_->relax_method, ofs_running); + ucell.cell_parameter_updated = true; + + // Update cell-related parameters after volume change + unitcell::setup_cell_after_vc(ucell, ofs_running, inp_->nspin); + ModuleBase::GlobalFunc::DONE(ofs_running, "SETUP UNITCELL"); } return converged; diff --git a/source/source_relax/relax_sync.cpp b/source/source_relax/relax_sync.cpp index 276f4dc71f..34df2b21cc 100644 --- a/source/source_relax/relax_sync.cpp +++ b/source/source_relax/relax_sync.cpp @@ -102,14 +102,6 @@ bool Relax::relax_step(UnitCell& ucell, return relax_done; } -bool Relax::check_converged(UnitCell& ucell, - const ModuleBase::matrix& force, - const ModuleBase::matrix& stress, - std::ofstream& ofs_running) -{ - return this->setup_gradient(ucell, force, stress, ofs_running); -} - bool Relax::setup_gradient(const UnitCell& ucell, const ModuleBase::matrix& force, const ModuleBase::matrix& stress, diff --git a/source/source_relax/relax_sync.h b/source/source_relax/relax_sync.h index 0c03513bfc..b43e5495bb 100644 --- a/source/source_relax/relax_sync.h +++ b/source/source_relax/relax_sync.h @@ -26,11 +26,6 @@ class Relax const ModuleBase::matrix& stress, const double etot_in, std::ofstream& ofs_running); - bool check_converged(UnitCell& ucell, - const ModuleBase::matrix& force, - const ModuleBase::matrix& stress, - std::ofstream& ofs_running); - private: int istep = 0; // count ionic step diff --git a/source/source_relax/unittests/test_relax_nsync.cpp b/source/source_relax/unittests/test_relax_nsync.cpp index 576740a96e..9005d2f66c 100644 --- a/source/source_relax/unittests/test_relax_nsync.cpp +++ b/source/source_relax/unittests/test_relax_nsync.cpp @@ -273,8 +273,8 @@ TEST_F(IonCellOptimizerTest, RelaxNotConverged) EXPECT_THAT(log, testing::Not(testing::HasSubstr("Relaxation is converged!"))); } -// The last allowed ionic step must still be checked for convergence before -// the driver reports that relax_nmax was reached. +// The last allowed ionic step still performs its optimizer move before the +// driver reports that relax_nmax was reached. TEST_F(IonCellOptimizerTest, RelaxNmaxLastStepConverged) { UnitCell ucell; @@ -317,7 +317,7 @@ TEST_F(IonCellOptimizerTest, RelaxNmaxLastStepNotConverged) ofs.close(); EXPECT_FALSE(done); - EXPECT_FALSE(ucell.ionic_position_updated); + EXPECT_TRUE(ucell.ionic_position_updated); const std::string log = read_log(); EXPECT_THAT(log, testing::HasSubstr(" Relaxation is not converged yet!")); EXPECT_THAT(log, testing::Not(testing::HasSubstr(" relax_nmax = 1 reached."))); @@ -376,7 +376,7 @@ TEST_F(IonCellOptimizerTest, CellRelaxNotConverged) EXPECT_FALSE(done); EXPECT_FALSE(ucell.ionic_position_updated); - EXPECT_FALSE(ucell.cell_parameter_updated); + EXPECT_TRUE(ucell.cell_parameter_updated); const std::string log = read_log(); EXPECT_THAT(log, testing::HasSubstr(" Largest stress is ")); EXPECT_THAT(log, testing::HasSubstr("\n Relaxation is not converged yet!")); From a7f1461bcd989ef1de967ae788fc854c795dd6ce Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Thu, 29 Oct 2026 20:23:12 +0800 Subject: [PATCH 08/15] style: restore unchanged lattice change files --- source/source_relax/lat_change_method.cpp | 2 ++ source/source_relax/lat_change_method.h | 1 + 2 files changed, 3 insertions(+) diff --git a/source/source_relax/lat_change_method.cpp b/source/source_relax/lat_change_method.cpp index e153c97929..29900684e3 100644 --- a/source/source_relax/lat_change_method.cpp +++ b/source/source_relax/lat_change_method.cpp @@ -8,6 +8,7 @@ Lattice_Change_Methods::Lattice_Change_Methods() Lattice_Change_Methods::~Lattice_Change_Methods() { } + void Lattice_Change_Methods::allocate() { Lattice_Change_Basic::dim = 9; @@ -15,6 +16,7 @@ void Lattice_Change_Methods::allocate() return; } + void Lattice_Change_Methods::cal_lattice_change(const int& istep, const int& stress_step, const ModuleBase::matrix& stress, diff --git a/source/source_relax/lat_change_method.h b/source/source_relax/lat_change_method.h index ea2d1c451e..304cfae7b8 100644 --- a/source/source_relax/lat_change_method.h +++ b/source/source_relax/lat_change_method.h @@ -24,6 +24,7 @@ class Lattice_Change_Methods UnitCell& ucell, std::ofstream& ofs, const Relax_Criteria& criteria); + bool get_converged(void) const { return converged_; From 17c51c23346997a6725c5c6f0e62f1389e057689 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Thu, 29 Oct 2026 20:49:11 +0800 Subject: [PATCH 09/15] test: cover relaxation driver stop states --- source/source_relax/unittests/CMakeLists.txt | 31 ++ .../unittests/test_ions_move_bfgs2.cpp | 35 ++ .../unittests/test_ions_move_lbfgs.cpp | 3 + .../unittests/test_relax_driver.cpp | 403 ++++++++++++++++++ 4 files changed, 472 insertions(+) create mode 100644 source/source_relax/unittests/test_relax_driver.cpp diff --git a/source/source_relax/unittests/CMakeLists.txt b/source/source_relax/unittests/CMakeLists.txt index 30eaa2b1f7..ba5b7eb31c 100644 --- a/source/source_relax/unittests/CMakeLists.txt +++ b/source/source_relax/unittests/CMakeLists.txt @@ -313,3 +313,34 @@ addtest( mock_remake_cell.cpp ../../source_cell/bcast_cell.cpp ../../source_cell/print_cell.cpp) + +addtest( + TARGET + MODULE_RELAX_relax_driver_test + KEEP_FEATURE_DEFINITIONS + __MPI + SOURCES + test_relax_driver.cpp + ../relax_driver.cpp + ../relax_nsync.cpp + ../relax_sync.cpp + ../relax_data.cpp + ../line_search.cpp + ../ions_move_methods.cpp + ../ions_move_bfgs.cpp + ../ions_move_bfgs2.cpp + ../ions_move_cg.cpp + ../ions_move_sd.cpp + ../ions_move_lbfgs.cpp + ../ions_move_basic.cpp + ../bfgs_basic.cpp + ../cg_base.cpp + ../matrix_methods.cpp + ../lat_change_method.cpp + ../lattice_change_basic.cpp + ../lattice_change_cg.cpp + LIBS + MPI::MPI_CXX + parameter + base + device) diff --git a/source/source_relax/unittests/test_ions_move_bfgs2.cpp b/source/source_relax/unittests/test_ions_move_bfgs2.cpp index f3662dbc50..1f63814c1f 100644 --- a/source/source_relax/unittests/test_ions_move_bfgs2.cpp +++ b/source/source_relax/unittests/test_ions_move_bfgs2.cpp @@ -400,3 +400,38 @@ TEST_F(BFGSTest, RelaxStepBasic) EXPECT_NEAR(get_pos()[0][0], 0.0, 1e-12); EXPECT_NE(get_pos()[1][0], 1.0); } + +TEST_F(BFGSTest, RelaxStepZeroForceReturnsBeforeStepConstruction) +{ + UnitCell ucell; + ucell.ntype = 1; + ucell.nat = 2; + ucell.lat0 = 1.0; + ucell.atoms = new Atom[ucell.ntype]; + ucell.atoms[0].na = 2; + ucell.atoms[0].tau = std::vector>(2); + ucell.atoms[0].taud = std::vector>(2); + ucell.atoms[0].mbl = std::vector>(2, {1, 1, 1}); + ucell.iat2it.resize(ucell.nat); + ucell.iat2ia.resize(ucell.nat); + ucell.iat2it[0] = 0; + ucell.iat2it[1] = 0; + ucell.iat2ia[0] = 0; + ucell.iat2ia[1] = 1; + ucell.atoms[0].tau[0].x = 0.25; + ucell.atoms[0].tau[1].x = 0.75; + + const double initial_tau0_x = ucell.atoms[0].tau[0].x; + const double initial_tau1_x = ucell.atoms[0].tau[1].x; + ModuleBase::matrix force(2, 3); + std::ofstream ofs_running; + Relax_Criteria criteria; + criteria.force_thr_ev = 1.0; + + const bool converged = bfgs.relax_step(force, ucell, ofs_running, criteria); + + EXPECT_TRUE(converged); + EXPECT_FALSE(ucell.ionic_position_updated); + EXPECT_NEAR(ucell.atoms[0].tau[0].x, initial_tau0_x, 1e-12); + EXPECT_NEAR(ucell.atoms[0].tau[1].x, initial_tau1_x, 1e-12); +} diff --git a/source/source_relax/unittests/test_ions_move_lbfgs.cpp b/source/source_relax/unittests/test_ions_move_lbfgs.cpp index 416f2d18a2..57c4ca0d2c 100644 --- a/source/source_relax/unittests/test_ions_move_lbfgs.cpp +++ b/source/source_relax/unittests/test_ions_move_lbfgs.cpp @@ -126,6 +126,7 @@ class IonsMoveLBFGSTest : public ::testing::Test TEST_F(IonsMoveLBFGSTest, RelaxStepConverged) { UnitCell ucell; + const double initial_tau_x = ucell.atoms[0].tau[0].x; ModuleBase::matrix force(natom, 3); force(0, 0) = 1.0e-4; // Ry/Bohr, ~0.005 eV/Angstrom const double etot = 0.0; @@ -140,6 +141,8 @@ TEST_F(IonsMoveLBFGSTest, RelaxStepConverged) EXPECT_TRUE(converged); EXPECT_NEAR(Ions_Move_Basic::largest_grad, 1.0e-4, 1e-12); // Ry/Bohr, lat0 = 1 EXPECT_LT(Ions_Move_Basic::largest_grad * ModuleBase::Ry_to_eV / ModuleBase::BOHR_TO_A, criteria.force_thr_ev); + EXPECT_NEAR(ucell.atoms[0].tau[0].x, initial_tau_x, 1e-12); + EXPECT_FALSE(ucell.ionic_position_updated); } // A force above force_thr_ev must be reported as not converged. diff --git a/source/source_relax/unittests/test_relax_driver.cpp b/source/source_relax/unittests/test_relax_driver.cpp new file mode 100644 index 0000000000..21b7c647c0 --- /dev/null +++ b/source/source_relax/unittests/test_relax_driver.cpp @@ -0,0 +1,403 @@ +#include "source_cell/cell_tools.h" +#include "source_cell/print_cell.h" +#include "source_cell/update_cell.h" +#include "source_esolver/esolver.h" +#include "for_test.h" +#include "source_relax/relax_driver.h" +#include "source_relax/relax_stru_io.h" +#include "source_relax/socket_driver.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include + +#ifdef __MPI +#include +#endif + +#ifdef __MPI +class MpiInitializer +{ + public: + MpiInitializer() + { + int initialized = 0; + MPI_Initialized(&initialized); + if (!initialized) + { + MPI_Init(nullptr, nullptr); + owns_mpi_ = true; + } + } + + ~MpiInitializer() + { + int finalized = 0; + MPI_Finalized(&finalized); + if (owns_mpi_ && !finalized) + { + MPI_Finalize(); + } + } + + private: + bool owns_mpi_ = false; +}; + +MpiInitializer mpi_initializer; +#endif + +namespace unitcell +{ +bool if_atoms_can_move(const Atom*, const int) +{ + return true; +} + +bool if_cell_can_change(const std::vector&) +{ + return false; +} + +void update_pos_tau(const Lattice& lat, const double* pos, const int ntype, const int nat, Atom* atoms) +{ + int iat = 0; + for (int it = 0; it < ntype; ++it) + { + for (int ia = 0; ia < atoms[it].na; ++ia) + { + for (int ik = 0; ik < 3; ++ik) + { + atoms[it].tau[ia][ik] = pos[3 * iat + ik] / lat.lat0; + } + ++iat; + } + } + EXPECT_EQ(iat, nat); +} + +void update_pos_taud(const Lattice&, const double*, const int, const int, Atom*) +{ +} + +void setup_cell_after_vc(UnitCell&, std::ofstream&, const int) +{ +} + +void remake_cell(Lattice&) +{ +} + +void print_tau(Atom*, const std::string&, const int, const double, std::ofstream&) +{ +} +} // namespace unitcell + +namespace ModuleIO +{ +void print_screen(const int&, const int&, const int&) +{ +} + +void output_after_relax(const bool, const bool, std::ofstream&) +{ +} +} // namespace ModuleIO + +namespace relax_stru_io +{ +std::string build_stru_header(const int, + const double, + const ModuleBase::matrix&, + const Input_para&, + const bool, + const bool) +{ + return std::string(); +} + +bool need_orbital(const Input_para&) +{ + return false; +} + +void write_stru(UnitCell&, + const Input_para&, + const std::string&, + const std::string&, + const ModuleBase::matrix&, + const bool, + const bool, + const int, + const bool) +{ +} +} // namespace relax_stru_io + +void Socket_Driver::socket_driver(ModuleESolver::ESolver*, UnitCell&, const Input_para&, std::ofstream&) +{ +} + +namespace +{ + +class DriverTestESolver : public ModuleESolver::ESolver +{ + public: + explicit DriverTestESolver(const double force_x) : force_x_(force_x) + { + } + + void before_all_runners(BaseCell&, const Input_para&) override + { + } + + void runner(BaseCell&, const int) override + { + ++runner_count; + } + + void after_all_runners(BaseCell&) override + { + } + + double cal_energy() override + { + return 0.0; + } + + void cal_force(BaseCell&, ModuleBase::matrix& force) override + { + force.zero_out(); + force(0, 0) = force_x_; + } + + void cal_stress(BaseCell&, ModuleBase::matrix& stress) override + { + stress.zero_out(); + } + + int runner_count = 0; + + private: + double force_x_; +}; + +class RelaxDriverTest : public ::testing::Test +{ + protected: + void SetUp() override + { + log_file_ = testing::TempDir() + "running.log"; + } + + static void make_cell(UnitCell& ucell) + { + ucell.ntype = 1; + ucell.nat = 1; + ucell.lat0 = 1.0; + ucell.omega = 1.0; + ucell.lat.lat0 = 1.0; + ucell.lat.latvec.Identity(); + ucell.latvec.Identity(); + ucell.atoms = new Atom[1]; + ucell.atoms[0].na = 1; + ucell.atoms[0].tau.resize(1); + ucell.atoms[0].taud.resize(1); + ucell.atoms[0].mbl.resize(1, {1, 1, 1}); + ucell.iat2it.resize(1); + ucell.iat2ia.resize(1); + ucell.iat2it[0] = 0; + ucell.iat2ia[0] = 0; + } + + std::string read_running_log() const + { + std::ifstream ifs(log_file_); + return std::string((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + } + + std::string log_file_; +}; + +TEST_F(RelaxDriverTest, RelaxNmaxZeroDoesNotRunESolver) +{ + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "lbfgs"; + inp.relax_nmax = 0; + inp.out_level = "m"; + + UnitCell ucell; + make_cell(ucell); + DriverTestESolver esolver(1.0); + std::ofstream ofs(log_file_); + Relax_Driver driver; + driver.relax_driver(&esolver, ucell, inp, ofs); + ofs.close(); + + EXPECT_EQ(esolver.runner_count, 0); +} + +TEST_F(RelaxDriverTest, FirstStepConvergesOnNonSimultaneousPath) +{ + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "lbfgs"; + inp.relax_nmax = 3; + inp.force_thr = 0.1; + inp.force_thr_ev = 0.1 * ModuleBase::Ry_to_eV / ModuleBase::BOHR_TO_A; + inp.cal_force = 1; + inp.out_level = "m"; + + UnitCell ucell; + make_cell(ucell); + DriverTestESolver esolver(1.0e-4); + std::ofstream ofs(log_file_); + Relax_Driver driver; + driver.relax_driver(&esolver, ucell, inp, ofs); + ofs.close(); + + EXPECT_EQ(esolver.runner_count, 1); + EXPECT_THAT(read_running_log(), testing::HasSubstr("Relaxation is converged!")); +} + +TEST_F(RelaxDriverTest, FirstStepConvergesOnSimultaneousPath) +{ + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "cg"; + inp.relax_nmax = 3; + inp.force_thr = 0.1; + inp.force_thr_ev = 0.1 * ModuleBase::Ry_to_eV / ModuleBase::BOHR_TO_A; + inp.cal_force = 1; + inp.out_level = "m"; + + UnitCell ucell; + make_cell(ucell); + DriverTestESolver esolver(1.0e-4); + std::ofstream ofs(log_file_); + Relax_Driver driver; + driver.relax_driver(&esolver, ucell, inp, ofs); + ofs.close(); + + EXPECT_EQ(esolver.runner_count, 1); + EXPECT_THAT(read_running_log(), testing::HasSubstr("Relaxation is converged!")); +} + +TEST_F(RelaxDriverTest, MaxStepsMovesFinalNonSimultaneousGeometry) +{ + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "lbfgs"; + inp.relax_nmax = 1; + inp.force_thr = 0.0; + inp.force_thr_ev = 0.0; + inp.cal_force = 1; + inp.relax_bfgs_rmax = 0.2; + inp.out_level = "m"; + + UnitCell ucell; + make_cell(ucell); + DriverTestESolver esolver(1.0); + std::ofstream ofs(log_file_); + Relax_Driver driver; + driver.relax_driver(&esolver, ucell, inp, ofs); + ofs.close(); + + EXPECT_EQ(esolver.runner_count, 1); + EXPECT_THAT(read_running_log(), testing::HasSubstr("reaching relax_nmax")); +} + +TEST_F(RelaxDriverTest, MaxStepsStopsOnSimultaneousPath) +{ + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "cg"; + inp.relax_nmax = 1; + inp.force_thr = -1.0; + inp.force_thr_ev = -1.0; + inp.cal_force = 1; + inp.out_level = "m"; + + UnitCell ucell; + make_cell(ucell); + DriverTestESolver esolver(1.0); + std::ofstream ofs(log_file_); + Relax_Driver driver; + driver.relax_driver(&esolver, ucell, inp, ofs); + ofs.close(); + + EXPECT_EQ(esolver.runner_count, 1); + EXPECT_THAT(read_running_log(), testing::HasSubstr("reaching relax_nmax")); +} + +TEST_F(RelaxDriverTest, ExitStopsAfterCurrentNonSimultaneousStep) +{ + char old_cwd[4096]; + ASSERT_NE(::getcwd(old_cwd, sizeof(old_cwd)), nullptr); + ASSERT_EQ(::chdir(testing::TempDir().c_str()), 0); + { + std::ofstream exit_file("EXIT"); + exit_file << "stop_ion true\n"; + } + + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "lbfgs"; + inp.relax_nmax = 3; + inp.force_thr = 0.0; + inp.force_thr_ev = 0.0; + inp.cal_force = 1; + inp.out_level = "m"; + + UnitCell ucell; + make_cell(ucell); + DriverTestESolver esolver(1.0); + std::ofstream ofs(log_file_); + Relax_Driver driver; + driver.relax_driver(&esolver, ucell, inp, ofs); + ofs.close(); + + ASSERT_EQ(::chdir(old_cwd), 0); + EXPECT_EQ(esolver.runner_count, 1); + EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT")); +} + +TEST_F(RelaxDriverTest, ExitStopsAfterCurrentSimultaneousStep) +{ + char old_cwd[4096]; + ASSERT_NE(::getcwd(old_cwd, sizeof(old_cwd)), nullptr); + ASSERT_EQ(::chdir(testing::TempDir().c_str()), 0); + { + std::ofstream exit_file("EXIT"); + exit_file << "stop_ion true\n"; + } + + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "cg"; + inp.relax_nmax = 3; + inp.force_thr = -1.0; + inp.force_thr_ev = -1.0; + inp.cal_force = 1; + inp.out_level = "m"; + + UnitCell ucell; + make_cell(ucell); + DriverTestESolver esolver(1.0); + std::ofstream ofs(log_file_); + Relax_Driver driver; + driver.relax_driver(&esolver, ucell, inp, ofs); + ofs.close(); + + ASSERT_EQ(::chdir(old_cwd), 0); + EXPECT_EQ(esolver.runner_count, 1); + EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT")); +} + +} // namespace From 2e2843e135092f6dd0fadf13b53e14e6d1a99850 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Thu, 29 Oct 2026 20:57:28 +0800 Subject: [PATCH 10/15] fix: link JSON sources in relax driver test --- source/source_relax/unittests/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/source/source_relax/unittests/CMakeLists.txt b/source/source_relax/unittests/CMakeLists.txt index ba5b7eb31c..51e53239f4 100644 --- a/source/source_relax/unittests/CMakeLists.txt +++ b/source/source_relax/unittests/CMakeLists.txt @@ -339,6 +339,7 @@ addtest( ../lat_change_method.cpp ../lattice_change_basic.cpp ../lattice_change_cg.cpp + ${socket_json_sources} LIBS MPI::MPI_CXX parameter From 4d5cd578ceb67920d63879c7bdbc6d77803caa28 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Thu, 29 Oct 2026 22:08:25 +0800 Subject: [PATCH 11/15] fix: build input documentation without full ABACUS install --- .readthedocs.yaml | 6 +++--- source/CMakeLists.txt | 14 ++++++++++++++ source/source_io/CMakeLists.txt | 9 +++++++++ source/source_io/input_help_main.cpp | 9 +++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 source/source_io/input_help_main.cpp diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 5584c66520..dcf297ffe0 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -19,9 +19,9 @@ build: - pkg-config jobs: pre_build: - - cmake -Bbuild -GNinja -DCMAKE_INSTALL_PREFIX=install -DENABLE_MPI=OFF -DENABLE_LCAO=OFF - - cmake --build build --target install -j $(nproc) - - install/bin/abacus --generate-parameters-yaml > docs/parameters.yaml + - cmake -Bbuild -GNinja -DENABLE_MPI=OFF -DENABLE_LCAO=OFF -DBUILD_TESTING=OFF + - cmake --build build --target abacus_input_doc -j $(nproc) + - build/abacus_input_doc > docs/parameters.yaml - python3 docs/generate_input_main.py docs/parameters.yaml # RTD builds PR (external) versions as HTML only, so PDF failures would # not surface until post-merge. Run the same latex->PDF chain here as a diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index d849396a7d..b2a6886f64 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -608,6 +608,20 @@ add_subdirectory(source_lcao/module_rdmft) # add by jghan add_library(driver OBJECT source_main/driver.cpp source_main/driver_run.cpp) +add_executable( + abacus_input_doc + source_io/input_help_main.cpp +) + +target_link_libraries( + abacus_input_doc + PRIVATE io_input_help + io_input + parameter + base + device + abacus::link_libs) + # __USE_NVTX gates the NVTX code in source_base/timer.cpp (target base) and # source_main/driver.cpp (target driver), so it must be defined on those # OBJECT libraries, not only on the final executable (whose sole TU, main.cpp, diff --git a/source/source_io/CMakeLists.txt b/source/source_io/CMakeLists.txt index 6444267178..127aa231e0 100644 --- a/source/source_io/CMakeLists.txt +++ b/source/source_io/CMakeLists.txt @@ -151,6 +151,15 @@ add_library( ${objects} ) +add_library(io_input_help OBJECT input_help.cpp) + +# Include directory for build_info.h (generated at build time) +target_include_directories( + io_input_help + PUBLIC + ${CMAKE_BINARY_DIR}/source/source_io +) + # Include directory for build_info.h (generated at build time) target_include_directories( io_basic diff --git a/source/source_io/input_help_main.cpp b/source/source_io/input_help_main.cpp new file mode 100644 index 0000000000..55643be4ab --- /dev/null +++ b/source/source_io/input_help_main.cpp @@ -0,0 +1,9 @@ +#include "input_help.h" + +#include + +int main() +{ + ModuleIO::ParameterHelp::generate_yaml(std::cout); + return 0; +} From ada1311872375ff0888efeb96f20771c28d05559 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Fri, 30 Oct 2026 15:20:21 +0800 Subject: [PATCH 12/15] Revert "fix: build input documentation without full ABACUS install" This reverts commit 4d5cd578ceb67920d63879c7bdbc6d77803caa28. --- .readthedocs.yaml | 6 +++--- source/CMakeLists.txt | 14 -------------- source/source_io/CMakeLists.txt | 9 --------- source/source_io/input_help_main.cpp | 9 --------- 4 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 source/source_io/input_help_main.cpp diff --git a/.readthedocs.yaml b/.readthedocs.yaml index dcf297ffe0..5584c66520 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -19,9 +19,9 @@ build: - pkg-config jobs: pre_build: - - cmake -Bbuild -GNinja -DENABLE_MPI=OFF -DENABLE_LCAO=OFF -DBUILD_TESTING=OFF - - cmake --build build --target abacus_input_doc -j $(nproc) - - build/abacus_input_doc > docs/parameters.yaml + - cmake -Bbuild -GNinja -DCMAKE_INSTALL_PREFIX=install -DENABLE_MPI=OFF -DENABLE_LCAO=OFF + - cmake --build build --target install -j $(nproc) + - install/bin/abacus --generate-parameters-yaml > docs/parameters.yaml - python3 docs/generate_input_main.py docs/parameters.yaml # RTD builds PR (external) versions as HTML only, so PDF failures would # not surface until post-merge. Run the same latex->PDF chain here as a diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index b2a6886f64..d849396a7d 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -608,20 +608,6 @@ add_subdirectory(source_lcao/module_rdmft) # add by jghan add_library(driver OBJECT source_main/driver.cpp source_main/driver_run.cpp) -add_executable( - abacus_input_doc - source_io/input_help_main.cpp -) - -target_link_libraries( - abacus_input_doc - PRIVATE io_input_help - io_input - parameter - base - device - abacus::link_libs) - # __USE_NVTX gates the NVTX code in source_base/timer.cpp (target base) and # source_main/driver.cpp (target driver), so it must be defined on those # OBJECT libraries, not only on the final executable (whose sole TU, main.cpp, diff --git a/source/source_io/CMakeLists.txt b/source/source_io/CMakeLists.txt index 127aa231e0..6444267178 100644 --- a/source/source_io/CMakeLists.txt +++ b/source/source_io/CMakeLists.txt @@ -151,15 +151,6 @@ add_library( ${objects} ) -add_library(io_input_help OBJECT input_help.cpp) - -# Include directory for build_info.h (generated at build time) -target_include_directories( - io_input_help - PUBLIC - ${CMAKE_BINARY_DIR}/source/source_io -) - # Include directory for build_info.h (generated at build time) target_include_directories( io_basic diff --git a/source/source_io/input_help_main.cpp b/source/source_io/input_help_main.cpp deleted file mode 100644 index 55643be4ab..0000000000 --- a/source/source_io/input_help_main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "input_help.h" - -#include - -int main() -{ - ModuleIO::ParameterHelp::generate_yaml(std::cout); - return 0; -} From a198f2607148308c57033b5f6d24f72f9a611237 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Fri, 30 Oct 2026 15:32:45 +0800 Subject: [PATCH 13/15] fix: reset relaxation geometry update state --- source/source_relax/ions_move_bfgs2.cpp | 1 + source/source_relax/ions_move_bfgs2.h | 3 +- source/source_relax/relax_sync.cpp | 3 + source/source_relax/unittests/CMakeLists.txt | 3 - .../unittests/test_ions_move_bfgs2.cpp | 1 + .../unittests/test_relax_driver.cpp | 49 +++++----------- .../unittests/test_relax_sync.cpp | 57 +++++++++++++++++++ 7 files changed, 77 insertions(+), 40 deletions(-) diff --git a/source/source_relax/ions_move_bfgs2.cpp b/source/source_relax/ions_move_bfgs2.cpp index e33a2d3ace..15ec27b970 100644 --- a/source/source_relax/ions_move_bfgs2.cpp +++ b/source/source_relax/ions_move_bfgs2.cpp @@ -6,6 +6,7 @@ #include "source_cell/print_cell.h" // lanshuyue add 2025-06-19 #include "source_cell/update_cell.h" #include "source_io/module_parameter/parameter.h" +#include "source_relax/relax_criteria.h" //! initialize H0、H、pos0、force0、force void Ions_Move_BFGS2::allocate(const int _size) diff --git a/source/source_relax/ions_move_bfgs2.h b/source/source_relax/ions_move_bfgs2.h index 78078169ee..37bab5a9aa 100644 --- a/source/source_relax/ions_move_bfgs2.h +++ b/source/source_relax/ions_move_bfgs2.h @@ -4,7 +4,6 @@ #include "source_base/matrix.h" #include "source_base/matrix3.h" #include "source_cell/unitcell.h" -#include "source_relax/relax_criteria.h" #include #include @@ -12,6 +11,8 @@ #include #include +struct Relax_Criteria; + class Ions_Move_BFGS2 { public: diff --git a/source/source_relax/relax_sync.cpp b/source/source_relax/relax_sync.cpp index 34df2b21cc..ba8deed576 100644 --- a/source/source_relax/relax_sync.cpp +++ b/source/source_relax/relax_sync.cpp @@ -68,6 +68,9 @@ bool Relax::relax_step(UnitCell& ucell, { ModuleBase::TITLE("Relax", "relax_step"); + ucell.ionic_position_updated = false; + ucell.cell_parameter_updated = false; + etot = etot_in * ModuleBase::Ry_to_eV; // convert to eV if (istep == 0) { diff --git a/source/source_relax/unittests/CMakeLists.txt b/source/source_relax/unittests/CMakeLists.txt index 51e53239f4..e0490708bb 100644 --- a/source/source_relax/unittests/CMakeLists.txt +++ b/source/source_relax/unittests/CMakeLists.txt @@ -317,8 +317,6 @@ addtest( addtest( TARGET MODULE_RELAX_relax_driver_test - KEEP_FEATURE_DEFINITIONS - __MPI SOURCES test_relax_driver.cpp ../relax_driver.cpp @@ -341,7 +339,6 @@ addtest( ../lattice_change_cg.cpp ${socket_json_sources} LIBS - MPI::MPI_CXX parameter base device) diff --git a/source/source_relax/unittests/test_ions_move_bfgs2.cpp b/source/source_relax/unittests/test_ions_move_bfgs2.cpp index 1f63814c1f..4401215b37 100644 --- a/source/source_relax/unittests/test_ions_move_bfgs2.cpp +++ b/source/source_relax/unittests/test_ions_move_bfgs2.cpp @@ -4,6 +4,7 @@ #include "source_relax/ions_move_basic.h" // for Ions_Move_Basic static members #include "source_relax/ions_move_bfgs2.h" #include "source_relax/relax_data.h" +#include "source_relax/relax_criteria.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/source/source_relax/unittests/test_relax_driver.cpp b/source/source_relax/unittests/test_relax_driver.cpp index 21b7c647c0..997e1fadea 100644 --- a/source/source_relax/unittests/test_relax_driver.cpp +++ b/source/source_relax/unittests/test_relax_driver.cpp @@ -15,42 +15,6 @@ #include #include -#ifdef __MPI -#include -#endif - -#ifdef __MPI -class MpiInitializer -{ - public: - MpiInitializer() - { - int initialized = 0; - MPI_Initialized(&initialized); - if (!initialized) - { - MPI_Init(nullptr, nullptr); - owns_mpi_ = true; - } - } - - ~MpiInitializer() - { - int finalized = 0; - MPI_Finalized(&finalized); - if (owns_mpi_ && !finalized) - { - MPI_Finalize(); - } - } - - private: - bool owns_mpi_ = false; -}; - -MpiInitializer mpi_initializer; -#endif - namespace unitcell { bool if_atoms_can_move(const Atom*, const int) @@ -237,10 +201,13 @@ TEST_F(RelaxDriverTest, RelaxNmaxZeroDoesNotRunESolver) DriverTestESolver esolver(1.0); std::ofstream ofs(log_file_); Relax_Driver driver; + testing::internal::CaptureStdout(); driver.relax_driver(&esolver, ucell, inp, ofs); + const std::string output = testing::internal::GetCapturedStdout(); ofs.close(); EXPECT_EQ(esolver.runner_count, 0); + EXPECT_THAT(output, testing::HasSubstr("relax_nmax = 0, DRY RUN TEST SUCCEEDS")); } TEST_F(RelaxDriverTest, FirstStepConvergesOnNonSimultaneousPath) @@ -259,11 +226,14 @@ TEST_F(RelaxDriverTest, FirstStepConvergesOnNonSimultaneousPath) DriverTestESolver esolver(1.0e-4); std::ofstream ofs(log_file_); Relax_Driver driver; + testing::internal::CaptureStdout(); driver.relax_driver(&esolver, ucell, inp, ofs); + const std::string output = testing::internal::GetCapturedStdout(); ofs.close(); EXPECT_EQ(esolver.runner_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("Relaxation is converged!")); + EXPECT_THAT(output, testing::HasSubstr("Geometry relaxation thresholds are reached within 1 steps.")); } TEST_F(RelaxDriverTest, FirstStepConvergesOnSimultaneousPath) @@ -282,11 +252,14 @@ TEST_F(RelaxDriverTest, FirstStepConvergesOnSimultaneousPath) DriverTestESolver esolver(1.0e-4); std::ofstream ofs(log_file_); Relax_Driver driver; + testing::internal::CaptureStdout(); driver.relax_driver(&esolver, ucell, inp, ofs); + const std::string output = testing::internal::GetCapturedStdout(); ofs.close(); EXPECT_EQ(esolver.runner_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("Relaxation is converged!")); + EXPECT_THAT(output, testing::HasSubstr("Geometry relaxation thresholds are reached within 1 steps.")); } TEST_F(RelaxDriverTest, MaxStepsMovesFinalNonSimultaneousGeometry) @@ -311,6 +284,7 @@ TEST_F(RelaxDriverTest, MaxStepsMovesFinalNonSimultaneousGeometry) EXPECT_EQ(esolver.runner_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("reaching relax_nmax")); + EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped after 1 ionic step(s) (relax_nmax = 1 reached)")); } TEST_F(RelaxDriverTest, MaxStepsStopsOnSimultaneousPath) @@ -334,6 +308,7 @@ TEST_F(RelaxDriverTest, MaxStepsStopsOnSimultaneousPath) EXPECT_EQ(esolver.runner_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("reaching relax_nmax")); + EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped after 1 ionic step(s) (relax_nmax = 1 reached)")); } TEST_F(RelaxDriverTest, ExitStopsAfterCurrentNonSimultaneousStep) @@ -366,6 +341,7 @@ TEST_F(RelaxDriverTest, ExitStopsAfterCurrentNonSimultaneousStep) ASSERT_EQ(::chdir(old_cwd), 0); EXPECT_EQ(esolver.runner_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT")); + EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT after 1 ionic step(s)")); } TEST_F(RelaxDriverTest, ExitStopsAfterCurrentSimultaneousStep) @@ -398,6 +374,7 @@ TEST_F(RelaxDriverTest, ExitStopsAfterCurrentSimultaneousStep) ASSERT_EQ(::chdir(old_cwd), 0); EXPECT_EQ(esolver.runner_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT")); + EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT after 1 ionic step(s)")); } } // namespace diff --git a/source/source_relax/unittests/test_relax_sync.cpp b/source/source_relax/unittests/test_relax_sync.cpp index 09f114c481..65cb65c91f 100644 --- a/source/source_relax/unittests/test_relax_sync.cpp +++ b/source/source_relax/unittests/test_relax_sync.cpp @@ -5,6 +5,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include #include #include @@ -840,6 +841,62 @@ TEST(RelaxSyncMaxStep, GeometryMovedAndNotConverged) delete[] ucell.atoms; } +TEST(RelaxSyncConvergence, ClearsPreviousGeometryUpdate) +{ + const int nat = 1; + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "cg"; + inp.force_thr = 0.01; + inp.force_thr_ev = 1.0; + inp.fixed_axes = "None"; + inp.fixed_ibrav = false; + + UnitCell ucell; + ucell.ntype = 1; + ucell.nat = nat; + ucell.atoms = new Atom[1]; + ucell.atoms[0].na = nat; + ucell.atoms[0].label = "Si"; + ucell.omega = 1.0; + ucell.lat0 = 1.0; + ucell.iat2it.resize(nat); + ucell.iat2ia.resize(nat); + ucell.iat2it[0] = 0; + ucell.iat2ia[0] = 0; + ucell.atoms[0].mbl.resize(nat); + ucell.atoms[0].taud.resize(nat); + ucell.atoms[0].tau.resize(nat); + ucell.atoms[0].dis.resize(nat); + ucell.atoms[0].mag.resize(nat); + ucell.atoms[0].vel.resize(nat); + ucell.atoms[0].mbl[0] = {1, 1, 1}; + ucell.latvec.Identity(); + + ModuleBase::matrix moving_force(nat, 3); + moving_force(0, 0) = 0.1; + ModuleBase::matrix converged_force(nat, 3); + ModuleBase::matrix stress(3, 3); + + Relax rl; + rl.init_relax(nat, inp); + const std::string log_file = testing::TempDir() + "running_relax_sync_flags.log"; + std::ofstream ofs(log_file); + const bool first_done = rl.relax_step(ucell, moving_force, stress, 0.0, ofs); + EXPECT_FALSE(first_done); + EXPECT_TRUE(ucell.ionic_position_updated); + + const bool second_done = rl.relax_step(ucell, converged_force, stress, 0.0, ofs); + ofs.close(); + std::remove(log_file.c_str()); + + EXPECT_TRUE(second_done); + EXPECT_FALSE(ucell.ionic_position_updated); + EXPECT_FALSE(ucell.cell_parameter_updated); + + delete[] ucell.atoms; +} + // fixed_axes="volume": the cell may change shape but its volume is re-scaled // back to the original omega after each move (relax_sync.cpp Step 1). TEST(RelaxSyncCellMove, FixedVolumePreservesOmega) From 65e8be5833eea85125f190b9273df09a98c7fab0 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Fri, 30 Oct 2026 20:51:57 +0800 Subject: [PATCH 14/15] fix: run final relaxation postprocessing on evaluated geometry --- source/source_main/driver_run.cpp | 5 ++- source/source_relax/relax_driver.cpp | 31 +++++++++++---- .../unittests/test_relax_driver.cpp | 38 ++++++++++++++++++- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/source/source_main/driver_run.cpp b/source/source_main/driver_run.cpp index 67da741a04..ccfa6e5595 100644 --- a/source/source_main/driver_run.cpp +++ b/source/source_main/driver_run.cpp @@ -129,7 +129,10 @@ void Driver::driver_run() { ModuleBase::WARNING_QUIT("Driver::driver_run", "cannot recognize the 'calculation' command"); } - p_esolver->after_all_runners(ucell); + if (cal != "scf" && cal != "relax" && cal != "cell-relax" && cal != "nscf") + { + p_esolver->after_all_runners(ucell); + } } delete p_esolver; diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 75ce7b355b..cf56ffd2da 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -29,6 +29,7 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, { Socket_Driver socket_driver; socket_driver.socket_driver(p_esolver, ucell, inp, ofs_running); + p_esolver->after_all_runners(ucell); ModuleBase::timer::end("Relax_Driver", "relax_driver"); return; } @@ -52,6 +53,7 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, // the next esolve() call; if we exit the loop early, force/stress are stale. bool geometry_evaluated = false; RelaxStopReason stop_reason = RelaxStopReason::Finished; + bool after_all_runners_called = false; if (inp.relax_nmax == 0 && (inp.calculation == "relax" || inp.calculation == "cell-relax")) { @@ -65,7 +67,23 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, geometry_evaluated = true; this->stru_out(steps[0], ucell, inp, etot, stress, force); this->json_out(p_esolver, ucell, inp, force, stress); - bool converged = this->relax_step(steps, p_esolver, ucell, inp, force, stress, etot, ofs_running); + + if (ModuleIO::read_exit_file(my_rank_, "EXIT", ofs_running)) + { + stop_reason = RelaxStopReason::UserExit; + break; + } + + const bool laststep = + (inp.calculation == "relax" || inp.calculation == "cell-relax") + && steps[0] + 1 == inp.relax_nmax; + if (laststep) + { + p_esolver->after_all_runners(ucell); + after_all_runners_called = true; + } + + const bool converged = this->relax_step(steps, p_esolver, ucell, inp, force, stress, etot, ofs_running); // Check stop conditions if (ucell.ionic_position_updated || ucell.cell_parameter_updated) @@ -86,12 +104,6 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, break; } - if (ModuleIO::read_exit_file(my_rank_, "EXIT", ofs_running)) - { - stop_reason = RelaxStopReason::UserExit; - break; - } - if (steps[0] + 1 >= inp.relax_nmax) { stop_reason = RelaxStopReason::MaxSteps; @@ -103,6 +115,11 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, this->final_out(steps[0], stop_reason, ucell, inp, etot, stress, force, geometry_evaluated, ofs_running); + if (!after_all_runners_called) + { + p_esolver->after_all_runners(ucell); + } + ModuleBase::timer::end("Relax_Driver", "relax_driver"); return; } diff --git a/source/source_relax/unittests/test_relax_driver.cpp b/source/source_relax/unittests/test_relax_driver.cpp index 997e1fadea..8aa08a6417 100644 --- a/source/source_relax/unittests/test_relax_driver.cpp +++ b/source/source_relax/unittests/test_relax_driver.cpp @@ -127,6 +127,7 @@ class DriverTestESolver : public ModuleESolver::ESolver void after_all_runners(BaseCell&) override { + ++after_all_count; } double cal_energy() override @@ -146,6 +147,7 @@ class DriverTestESolver : public ModuleESolver::ESolver } int runner_count = 0; + int after_all_count = 0; private: double force_x_; @@ -207,6 +209,7 @@ TEST_F(RelaxDriverTest, RelaxNmaxZeroDoesNotRunESolver) ofs.close(); EXPECT_EQ(esolver.runner_count, 0); + EXPECT_EQ(esolver.after_all_count, 1); EXPECT_THAT(output, testing::HasSubstr("relax_nmax = 0, DRY RUN TEST SUCCEEDS")); } @@ -232,6 +235,7 @@ TEST_F(RelaxDriverTest, FirstStepConvergesOnNonSimultaneousPath) ofs.close(); EXPECT_EQ(esolver.runner_count, 1); + EXPECT_EQ(esolver.after_all_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("Relaxation is converged!")); EXPECT_THAT(output, testing::HasSubstr("Geometry relaxation thresholds are reached within 1 steps.")); } @@ -258,6 +262,7 @@ TEST_F(RelaxDriverTest, FirstStepConvergesOnSimultaneousPath) ofs.close(); EXPECT_EQ(esolver.runner_count, 1); + EXPECT_EQ(esolver.after_all_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("Relaxation is converged!")); EXPECT_THAT(output, testing::HasSubstr("Geometry relaxation thresholds are reached within 1 steps.")); } @@ -283,6 +288,7 @@ TEST_F(RelaxDriverTest, MaxStepsMovesFinalNonSimultaneousGeometry) ofs.close(); EXPECT_EQ(esolver.runner_count, 1); + EXPECT_EQ(esolver.after_all_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("reaching relax_nmax")); EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped after 1 ionic step(s) (relax_nmax = 1 reached)")); } @@ -307,11 +313,36 @@ TEST_F(RelaxDriverTest, MaxStepsStopsOnSimultaneousPath) ofs.close(); EXPECT_EQ(esolver.runner_count, 1); + EXPECT_EQ(esolver.after_all_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("reaching relax_nmax")); EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped after 1 ionic step(s) (relax_nmax = 1 reached)")); } -TEST_F(RelaxDriverTest, ExitStopsAfterCurrentNonSimultaneousStep) +TEST_F(RelaxDriverTest, FinalAllowedStepConvergesAndCallsAfterAllOnce) +{ + Input_para inp; + inp.calculation = "relax"; + inp.relax_method = "lbfgs"; + inp.relax_nmax = 1; + inp.force_thr = 0.1; + inp.force_thr_ev = 0.1 * ModuleBase::Ry_to_eV / ModuleBase::BOHR_TO_A; + inp.cal_force = 1; + inp.out_level = "m"; + + UnitCell ucell; + make_cell(ucell); + DriverTestESolver esolver(1.0e-4); + std::ofstream ofs(log_file_); + Relax_Driver driver; + driver.relax_driver(&esolver, ucell, inp, ofs); + ofs.close(); + + EXPECT_EQ(esolver.runner_count, 1); + EXPECT_EQ(esolver.after_all_count, 1); + EXPECT_THAT(read_running_log(), testing::HasSubstr("Relaxation is converged!")); +} + +TEST_F(RelaxDriverTest, ExitStopsBeforeNonSimultaneousRelaxStep) { char old_cwd[4096]; ASSERT_NE(::getcwd(old_cwd, sizeof(old_cwd)), nullptr); @@ -340,11 +371,13 @@ TEST_F(RelaxDriverTest, ExitStopsAfterCurrentNonSimultaneousStep) ASSERT_EQ(::chdir(old_cwd), 0); EXPECT_EQ(esolver.runner_count, 1); + EXPECT_EQ(esolver.after_all_count, 1); + EXPECT_DOUBLE_EQ(ucell.atoms[0].tau[0][0], 0.0); EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT")); EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT after 1 ionic step(s)")); } -TEST_F(RelaxDriverTest, ExitStopsAfterCurrentSimultaneousStep) +TEST_F(RelaxDriverTest, ExitStopsBeforeSimultaneousRelaxStep) { char old_cwd[4096]; ASSERT_NE(::getcwd(old_cwd, sizeof(old_cwd)), nullptr); @@ -373,6 +406,7 @@ TEST_F(RelaxDriverTest, ExitStopsAfterCurrentSimultaneousStep) ASSERT_EQ(::chdir(old_cwd), 0); EXPECT_EQ(esolver.runner_count, 1); + EXPECT_EQ(esolver.after_all_count, 1); EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT")); EXPECT_THAT(read_running_log(), testing::HasSubstr("stopped by EXIT after 1 ionic step(s)")); } From 850b2bfe4d470d352ae38151205a541df926b2e5 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Sat, 31 Oct 2026 15:23:31 +0800 Subject: [PATCH 15/15] test: initialize MPI in relaxation driver test --- source/source_relax/unittests/CMakeLists.txt | 3 +++ .../unittests/test_relax_driver.cpp | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/source/source_relax/unittests/CMakeLists.txt b/source/source_relax/unittests/CMakeLists.txt index e0490708bb..51e53239f4 100644 --- a/source/source_relax/unittests/CMakeLists.txt +++ b/source/source_relax/unittests/CMakeLists.txt @@ -317,6 +317,8 @@ addtest( addtest( TARGET MODULE_RELAX_relax_driver_test + KEEP_FEATURE_DEFINITIONS + __MPI SOURCES test_relax_driver.cpp ../relax_driver.cpp @@ -339,6 +341,7 @@ addtest( ../lattice_change_cg.cpp ${socket_json_sources} LIBS + MPI::MPI_CXX parameter base device) diff --git a/source/source_relax/unittests/test_relax_driver.cpp b/source/source_relax/unittests/test_relax_driver.cpp index 8aa08a6417..06a52041e6 100644 --- a/source/source_relax/unittests/test_relax_driver.cpp +++ b/source/source_relax/unittests/test_relax_driver.cpp @@ -1,6 +1,7 @@ #include "source_cell/cell_tools.h" #include "source_cell/print_cell.h" #include "source_cell/update_cell.h" +#include "source_base/parallel_global.h" #include "source_esolver/esolver.h" #include "for_test.h" #include "source_relax/relax_driver.h" @@ -109,6 +110,29 @@ void Socket_Driver::socket_driver(ModuleESolver::ESolver*, UnitCell&, const Inpu namespace { +class MpiEnvironment final : public testing::Environment +{ + public: + void SetUp() override + { + int nproc = 0; + int nthread_per_proc = 0; + int my_rank = 0; + char arg0[] = "MODULE_RELAX_relax_driver_test"; + char* argv[] = {arg0, nullptr}; + int argc = 1; + Parallel_Global::read_pal_param(argc, argv, nproc, nthread_per_proc, my_rank); + } + + void TearDown() override + { + Parallel_Global::finalize_mpi(); + } +}; + +testing::Environment* const mpi_environment + = testing::AddGlobalTestEnvironment(new MpiEnvironment); + class DriverTestESolver : public ModuleESolver::ESolver { public: