Skip to content

Commit 0d455cb

Browse files
Feature: enable ABACUS can finish SCF if charge density oscillation is found (#5421)
* add an input parameter to control the threshold for SCF oscillation * add a function to tell whether SCF oscillates * add a member oscillate_esolver in esolver_ks.h, similar to conv_esolver * add a read-in test for scf_thr_os * add a read-in test for scf_ene_thr * add a new parameter scf_os_ndim and corresponding test * set scf_os_ndim as mixing_ndim if default * add a UnitTest for new function * add some docs and comments * add a new parameter scf_os_stop * use scf_os_stop to control esovler * rename scf_thr_os as scf_os_thr * update the docs of scf_os_thr * add a value-check for scf_os_thr
1 parent cc9d759 commit 0d455cb

File tree

10 files changed

+177
-2
lines changed

10 files changed

+177
-2
lines changed

docs/advanced/input_files/input-main.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
- [scf\_thr](#scf_thr)
8989
- [scf\_ene\_thr](#scf_ene_thr)
9090
- [scf\_thr\_type](#scf_thr_type)
91+
- [scf\_os\_stop](#scf_os_stop)
92+
- [scf\_os\_thr](#scf_os_thr)
93+
- [scf\_os\_ndim](#scf_os_ndim)
9194
- [chg\_extrap](#chg_extrap)
9295
- [lspinorb](#lspinorb)
9396
- [noncolin](#noncolin)
@@ -1205,6 +1208,29 @@ Note: In new angle mixing, you should set `mixing_beta_mag >> mixing_beta`. The
12051208

12061209
- **Default**: 1 (plane-wave basis), or 2 (localized atomic orbital basis).
12071210

1211+
### scf_os_stop
1212+
1213+
- **Type**: bool
1214+
- **Description**: For systems that are difficult to converge, the SCF process may exhibit oscillations in charge density, preventing further progress toward the specified convergence criteria and resulting in continuous oscillation until the maximum number of steps is reached; this greatly wastes computational resources. To address this issue, this function allows ABACUS to terminate the SCF process early upon detecting oscillations, thus reducing subsequent meaningless calculations. The detection of oscillations is based on the slope of the logarithm of historical drho values.. To this end, Least Squares Method is used to calculate the slope of the logarithmically taken drho for the previous `scf_os_ndim` iterations. If the calculated slope is larger than `scf_os_thr`, stop the SCF.
1215+
1216+
- **0**: The SCF will continue to run regardless of whether there is oscillation or not.
1217+
- **1**: If the calculated slope is larger than `scf_os_thr`, stop the SCF.
1218+
1219+
- **Default**: false
1220+
1221+
### scf_os_thr
1222+
1223+
- **Type**: double
1224+
- **Description**: The slope threshold to determine if the SCF is stuck in a charge density oscillation. If the calculated slope is larger than `scf_os_thr`, stop the SCF.
1225+
1226+
- **Default**: -0.01
1227+
1228+
### scf_os_ndim
1229+
1230+
- **Type**: int
1231+
- **Description**: To determine the number of old iterations' `drho` used in slope calculations.
1232+
- **Default**: `mixing_ndim`
1233+
12081234
### chg_extrap
12091235

12101236
- **Type**: String

source/module_elecstate/module_charge/charge_mixing.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,4 +1701,60 @@ void Charge_Mixing::clean_data(std::complex<double>*& data_s, std::complex<doubl
17011701
data_s = nullptr;
17021702
data_hf = nullptr;
17031703
}
1704+
}
1705+
1706+
bool Charge_Mixing::if_scf_oscillate(const int iteration, const double drho, const int iternum_used, const double threshold)
1707+
{
1708+
ModuleBase::TITLE("Charge_Mixing", "if_scf_oscillate");
1709+
ModuleBase::timer::tick("Charge_Mixing", "if_scf_oscillate");
1710+
1711+
if(threshold >= 0) // close the function
1712+
{
1713+
return false;
1714+
}
1715+
1716+
if(this->_drho_history.size() == 0)
1717+
{
1718+
this->_drho_history.resize(PARAM.inp.scf_nmax);
1719+
}
1720+
1721+
// add drho into history
1722+
this->_drho_history[iteration - 1] = drho;
1723+
1724+
// check if the history is long enough
1725+
if(iteration < iternum_used)
1726+
{
1727+
return false;
1728+
}
1729+
1730+
// calculate the slope of the last iternum_used iterations' drho
1731+
double slope = 0.0;
1732+
1733+
// Least Squares Method
1734+
// this part is too short, so I do not design it as a free function in principle
1735+
double sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
1736+
for (int i = iteration - iternum_used; i < iteration; i++)
1737+
{
1738+
sumX += i;
1739+
sumY += std::log10(this->_drho_history[i]);
1740+
sumXY += i * std::log10(this->_drho_history[i]);
1741+
sumXX += i * i;
1742+
}
1743+
double numerator = iternum_used * sumXY - sumX * sumY;
1744+
double denominator = iternum_used * sumXX - sumX * sumX;
1745+
if (denominator == 0) {
1746+
return false;
1747+
}
1748+
slope = numerator / denominator;
1749+
1750+
// if the slope is less than the threshold, return true
1751+
if(slope > threshold)
1752+
{
1753+
return true;
1754+
}
1755+
1756+
return false;
1757+
1758+
ModuleBase::timer::tick("Charge_Mixing", "if_scf_oscillate");
1759+
17041760
}

source/module_elecstate/module_charge/charge_mixing.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ class Charge_Mixing
105105
int mixing_restart_step = 0; //which step to restart mixing during SCF
106106
int mixing_restart_count = 0; // the number of restart mixing during SCF. Do not set mixing_restart_count as bool since I want to keep some flexibility in the future
107107

108+
// to calculate the slope of drho curve during SCF, which is used to determine if SCF oscillate
109+
bool if_scf_oscillate(const int iteration, const double drho, const int iternum_used, const double threshold);
110+
108111
private:
109112

110113
// mixing_data
@@ -129,6 +132,8 @@ class Charge_Mixing
129132
double mixing_angle = 0.0; ///< mixing angle for nspin=4
130133
bool mixing_dmr = false; ///< whether to mixing real space density matrix
131134

135+
std::vector<double> _drho_history; ///< history of drho used to determine the oscillation, size is scf_nmax
136+
132137
bool new_e_iteration = true;
133138

134139
ModulePW::PW_Basis* rhopw = nullptr; ///< smooth grid

source/module_elecstate/test/charge_mixing_test.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,3 +1024,41 @@ TEST_F(ChargeMixingTest, MixDivCombTest)
10241024
EXPECT_EQ(datas2, nullptr);
10251025
EXPECT_EQ(datahf2, nullptr);
10261026
}
1027+
1028+
TEST_F(ChargeMixingTest, SCFOscillationTest)
1029+
{
1030+
Charge_Mixing CMtest;
1031+
int scf_nmax = 20;
1032+
int scf_os_ndim = 3;
1033+
double scf_os_thr = -0.05;
1034+
bool scf_oscillate = false;
1035+
std::vector<double> drho(scf_nmax, 0.0);
1036+
std::vector<bool> scf_oscillate_ref(scf_nmax, false);
1037+
drho = {6.83639633652e-05,
1038+
4.93523029235e-05,
1039+
3.59230097735e-05,
1040+
2.68356403913e-05,
1041+
2.17490806464e-05,
1042+
2.14231642508e-05,
1043+
1.67507494811e-05,
1044+
1.53575889539e-05,
1045+
1.26504511554e-05,
1046+
1.04762016224e-05,
1047+
8.10000162918e-06,
1048+
7.66427917682e-06,
1049+
6.70112820094e-06,
1050+
5.68594436664e-06,
1051+
4.80120233733e-06,
1052+
4.86519757184e-06,
1053+
4.37855804356e-06,
1054+
4.29922703412e-06,
1055+
4.36398486331e-06,
1056+
4.94224615955e-06};
1057+
scf_oscillate_ref = {false,false,false,false,false,true,false,false,false,false,
1058+
false,false,true,false,false,true,true,true,true,true};
1059+
for (int i = 1; i <= scf_nmax; ++i)
1060+
{
1061+
scf_oscillate = CMtest.if_scf_oscillate(i,drho[i-1],scf_os_ndim,scf_os_thr);
1062+
EXPECT_EQ(scf_oscillate, scf_oscillate_ref[i-1]);
1063+
}
1064+
}

source/module_esolver/esolver_ks.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,11 @@ void ESolver_KS<T, Device>::runner(const int istep, UnitCell& ucell)
531531
this->p_chgmix->mixing_restart_step = iter + 1;
532532
}
533533

534+
if (PARAM.inp.scf_os_stop) // if oscillation is detected, SCF will stop
535+
{
536+
this->oscillate_esolver = this->p_chgmix->if_scf_oscillate(iter, drho, PARAM.inp.scf_os_ndim, PARAM.inp.scf_os_thr);
537+
}
538+
534539
// drho will be 0 at this->p_chgmix->mixing_restart step, which is
535540
// not ground state
536541
bool not_restart_step = !(iter == this->p_chgmix->mixing_restart_step && PARAM.inp.mixing_restart > 0.0);
@@ -639,9 +644,13 @@ void ESolver_KS<T, Device>::runner(const int istep, UnitCell& ucell)
639644
#endif //__RAPIDJSON
640645

641646
// 13) check convergence
642-
if (this->conv_esolver)
647+
if (this->conv_esolver || this->oscillate_esolver)
643648
{
644649
this->niter = iter;
650+
if (this->oscillate_esolver)
651+
{
652+
std::cout << " !! Density oscillation is found, STOP HERE !!" << std::endl;
653+
}
645654
break;
646655
}
647656

source/module_esolver/esolver_ks.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class ESolver_KS : public ESolver_FP
8282
protected:
8383
std::string basisname; // PW or LCAO
8484
double esolver_KS_ne = 0.0;
85+
bool oscillate_esolver = false; // whether esolver is oscillated
8586
};
8687
} // end of namespace
8788
#endif

source/module_io/read_input_item_elec_stru.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,36 @@ void ReadInput::item_elec_stru()
528528
read_sync_double(input.scf_ene_thr);
529529
this->add_item(item);
530530
}
531+
{
532+
Input_Item item("scf_os_stop");
533+
item.annotation = "whether to stop scf when oscillation is detected";
534+
read_sync_bool(input.scf_os_stop);
535+
this->add_item(item);
536+
}
537+
{
538+
Input_Item item("scf_os_thr");
539+
item.annotation = "charge density threshold for oscillation";
540+
read_sync_double(input.scf_os_thr);
541+
item.check_value = [](const Input_Item& item, const Parameter& para) {
542+
if (para.input.scf_os_thr >= 0)
543+
{
544+
ModuleBase::WARNING_QUIT("ReadInput", "scf_os_thr should be negative");
545+
}
546+
};
547+
this->add_item(item);
548+
}
549+
{
550+
Input_Item item("scf_os_ndim");
551+
item.annotation = "number of old iterations used for oscillation detection";
552+
read_sync_int(input.scf_os_ndim);
553+
item.reset_value = [](const Input_Item& item, Parameter& para) {
554+
if (para.input.scf_os_ndim <= 0) // default value
555+
{
556+
para.input.scf_os_ndim = para.input.mixing_ndim;
557+
}
558+
};
559+
this->add_item(item);
560+
}
531561
{
532562
Input_Item item("scf_thr_type");
533563
item.annotation = "type of the criterion of scf_thr, 1: reci drho for "

source/module_io/test/read_input_ptest.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ TEST_F(InputParaTest, ParaRead)
164164
EXPECT_EQ(PARAM.inp.test_force, 0);
165165
EXPECT_EQ(param.inp.test_stress, 0);
166166
EXPECT_NEAR(param.inp.scf_thr, 1.0e-8, 1.0e-15);
167-
EXPECT_NEAR(param.inp.scf_ene_thr, -1.0, 1.0e-15);
167+
EXPECT_EQ(param.inp.scf_os_stop, 1);
168+
EXPECT_NEAR(param.inp.scf_os_thr, -0.02, 1.0e-15);
169+
EXPECT_EQ(param.inp.scf_os_ndim, 10);
170+
EXPECT_NEAR(param.inp.scf_ene_thr, 1.0e-6, 1.0e-15);
168171
EXPECT_EQ(param.inp.scf_nmax, 50);
169172
EXPECT_EQ(param.inp.relax_nmax, 1);
170173
EXPECT_EQ(param.inp.out_stru, 0);

source/module_io/test/support/INPUT

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ erf_sigma 4 #the width of the energy step for reciprocal ve
5151
fft_mode 0 #mode of FFTW
5252
pw_diag_thr 0.01 #threshold for eigenvalues is cg electron iterations
5353
scf_thr 1e-08 #charge density error
54+
scf_ene_thr 1e-06 #total energy error threshold
55+
scf_os_stop 1 #whether to stop scf when oscillation is detected
56+
scf_os_thr -0.02 #charge density threshold for oscillation
57+
scf_os_ndim 10 #number of old iterations used for oscillation detection
5458
scf_thr_type 2 #type of the criterion of scf_thr, 1: reci drho for pw, 2: real drho for lcao
5559
init_wfc atomic #start wave functions are from 'atomic', 'atomic+random', 'random' or 'file'
5660
init_chg atomic #start charge is from 'atomic' or file

source/module_parameter/input_parameter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ struct Input_para
115115
double scf_ene_thr = -1.0; ///< energy threshold for scf convergence, in eV
116116
int scf_thr_type = -1; ///< type of the criterion of scf_thr, 1: reci drho, 2: real drho
117117
bool final_scf = false; ///< whether to do final scf
118+
bool scf_os_stop = false; ///< whether to stop scf when oscillation is detected
119+
double scf_os_thr = -0.01; ///< drho threshold for oscillation
120+
int scf_os_ndim = 0; ///< number of old iterations used for oscillation detection
118121

119122
bool lspinorb = false; ///< consider the spin-orbit interaction
120123
bool noncolin = false; ///< using non-collinear-spin

0 commit comments

Comments
 (0)