Skip to content

Commit 3338c0f

Browse files
committed
Fix(dft+u): stabilize occupation-matrix mixing and frozen potentials
1 parent 370e024 commit 3338c0f

11 files changed

Lines changed: 236 additions & 33 deletions

File tree

source/source_base/module_mixing/broyden_mixing.cpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#include "source_base/module_container/base/third_party/blas.h"
55
#include "source_base/timer.h"
66
#include "source_base/tool_title.h"
7+
8+
#include <algorithm>
9+
710
namespace Base_Mixing
811
{
912
template void Broyden_Mixing::tem_push_data(Mixing_Data& mdata,
@@ -170,16 +173,29 @@ void Broyden_Mixing::tem_cal_coef(const Mixing_Data& mdata, std::function<double
170173

171174
if (info != 0)
172175
{
173-
ModuleBase::WARNING_QUIT("Charge_Mixing", "Error when DSYSV.");
176+
// A rank-deficient history can occur when successive SCF residuals
177+
// become linearly dependent. Aborting loses a perfectly usable
178+
// latest fixed-point step; fall back to that step and let the next
179+
// iteration rebuild the history.
180+
ModuleBase::WARNING("Broyden_Mixing",
181+
"DSYSV failed in Broyden mixing; using the latest linear step.");
182+
std::fill(coef.begin(), coef.end(), 0.0);
183+
coef[mdata.start] = 1.0;
184+
// Discard the singular secant history before the next iteration.
185+
ndim_cal_dF = 0;
186+
start_dF = -1;
174187
}
188+
else
189+
{
175190

176-
// after solving, gamma store the coeficients for mixing
177-
coef[mdata.start] = 1 + gamma[dFindex_move(0)];
178-
for (int i = 1; i < ndim_cal_dF; ++i)
179-
{
180-
coef[mdata.index_move(-i)] = gamma[dFindex_move(-i)] - gamma[dFindex_move(-i + 1)];
191+
// after solving, gamma store the coeficients for mixing
192+
coef[mdata.start] = 1 + gamma[dFindex_move(0)];
193+
for (int i = 1; i < ndim_cal_dF; ++i)
194+
{
195+
coef[mdata.index_move(-i)] = gamma[dFindex_move(-i)] - gamma[dFindex_move(-i + 1)];
196+
}
197+
coef[mdata.index_move(-ndim_cal_dF)] = -gamma[dFindex_move(-ndim_cal_dF + 1)];
181198
}
182-
coef[mdata.index_move(-ndim_cal_dF)] = -gamma[dFindex_move(-ndim_cal_dF + 1)];
183199

184200
delete[] work;
185201
delete[] iwork;

source/source_base/module_mixing/test/mixing_test.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,33 @@ TEST_F(Mixing_Test, BroydenSolveLinearEq)
198198
clear();
199199
}
200200

201+
TEST_F(Mixing_Test, BroydenFallsBackForSingularHistory)
202+
{
203+
init_method("broyden");
204+
205+
Base_Mixing::Mixing_Data mdata;
206+
this->mixing->init_mixing_data(mdata, 2, sizeof(double));
207+
208+
// A rank-deficient residual history makes the symmetric linear system
209+
// passed to DSYSV singular. The mixer should retain the latest linear
210+
// step instead of aborting the SCF calculation.
211+
const auto singular_inner_product = [](double*, double*) { return 1.0; };
212+
const std::vector<double> data_in = {0.0, 0.0};
213+
std::vector<double> data_out(2);
214+
for (int scale = 1; scale <= 3; ++scale)
215+
{
216+
data_out[0] = static_cast<double>(scale);
217+
data_out[1] = static_cast<double>(2 * scale);
218+
this->mixing->push_data(mdata, data_in.data(), data_out.data(), nullptr, true);
219+
this->mixing->cal_coef(mdata, singular_inner_product);
220+
}
221+
222+
std::vector<double> mixed(2);
223+
this->mixing->mix_data(mdata, mixed.data());
224+
EXPECT_DOUBLE_EQ(mixed[0], 1.8);
225+
EXPECT_DOUBLE_EQ(mixed[1], 3.6);
226+
}
227+
201228
TEST_F(Mixing_Test, PulaySolveLinearEq)
202229
{
203230
#ifdef _OPENMP
@@ -306,4 +333,4 @@ TEST_F(Mixing_Test, OtherCover)
306333
EXPECT_EQ(nodata.length, 0);
307334

308335
clear();
309-
}
336+
}

source/source_estate/module_charge/charge_mixing.cpp

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
#include "charge_mixing.h"
22

3+
#include <functional>
4+
35
#include "source_io/module_parameter/parameter.h"
46
#include "source_base/module_mixing/broyden_mixing.h"
57
#include "source_base/module_mixing/pulay_mixing.h"
68
#include "source_base/parallel_common.h"
9+
#include "source_base/parallel_reduce.h"
710
#include "source_base/timer.h"
811
#include "source_hamilt/module_xc/xc_functional.h"
912

1013
Charge_Mixing::Charge_Mixing()
1114
{
1215
this->mixing = nullptr;
16+
this->mixing_uom = nullptr;
1317
this->mixing_highf = nullptr;
1418
}
1519

@@ -26,6 +30,12 @@ Charge_Mixing::~Charge_Mixing()
2630
delete this->mixing_highf;
2731
this->mixing_highf = nullptr;
2832
}
33+
34+
if (this->mixing_uom != nullptr)
35+
{
36+
delete this->mixing_uom;
37+
this->mixing_uom = nullptr;
38+
}
2939
}
3040

3141
void Charge_Mixing::set_mixing(const std::string& mixing_mode_in,
@@ -112,7 +122,9 @@ void Charge_Mixing::init_mixing()
112122
ModuleBase::TITLE("Charge_Mixing", "init_mixing");
113123
ModuleBase::timer::start("Charge_Mixing", "init_mixing");
114124

115-
// (re)construct mixing object
125+
// (re)construct the charge mixer. The independent UOM mixer is
126+
// initialized explicitly by the DFT+U call path.
127+
116128
if (this->mixing_mode == "broyden")
117129
{
118130
delete this->mixing;
@@ -133,6 +145,9 @@ void Charge_Mixing::init_mixing()
133145
ModuleBase::WARNING_QUIT("Charge_Mixing", "This Mixing mode is not implemended yet,coming soon.");
134146
}
135147

148+
delete this->mixing_uom;
149+
this->mixing_uom = nullptr;
150+
136151
if ( PARAM.globalv.double_grid)
137152
{
138153
// ONLY smooth part of charge density is mixed by specific mixing method
@@ -190,6 +205,20 @@ void Charge_Mixing::init_mixing()
190205
return;
191206
}
192207

208+
void Charge_Mixing::init_mixing_uom()
209+
{
210+
ModuleBase::TITLE("Charge_Mixing", "init_mixing_uom");
211+
ModuleBase::timer::start("Charge_Mixing", "init_mixing_uom");
212+
213+
delete this->mixing_uom;
214+
// The occupation matrix is a small, strongly coupled nonlinear variable.
215+
// A conservative plain step avoids binding its history to the much
216+
// larger charge-density mixer.
217+
this->mixing_uom = new Base_Mixing::Plain_Mixing(0.5 * this->mixing_beta);
218+
219+
ModuleBase::timer::end("Charge_Mixing", "init_mixing_uom");
220+
}
221+
193222
void Charge_Mixing::set_rhopw(ModulePW::PW_Basis* rhopw_in, ModulePW::PW_Basis* rhodpw_in)
194223
{
195224
this->rhopw = rhopw_in;
@@ -205,6 +234,11 @@ void Charge_Mixing::mix_reset()
205234
{
206235
this->tau_mdata.reset();
207236
}
237+
if (this->mixing_uom != nullptr)
238+
{
239+
this->mixing_uom->reset();
240+
this->uom_mdata.reset();
241+
}
208242
}
209243

210244
bool Charge_Mixing::if_scf_oscillate(const int iteration, const double drho, const int iternum_used, const double threshold)
@@ -265,8 +299,12 @@ void Charge_Mixing::allocate_mixing_uom(int uom_size)
265299
ModuleBase::timer::start("Charge_Mixing", "allocate_mixing_uom");
266300
// For nspin=2, uom_size already includes both spin channels
267301
// (pot_uterm_pw.size() = pot_index * 2 for nspin=2)
268-
// So uom_fold should always be 1
269-
this->mixing->init_mixing_data(this->uom_mdata, uom_size, sizeof(double));
302+
// So uom_fold should always be 1. UOM has an independent history.
303+
if (this->mixing_uom == nullptr)
304+
{
305+
ModuleBase::WARNING_QUIT("Charge_Mixing", "UOM mixing object is not initialized.");
306+
}
307+
this->mixing_uom->init_mixing_data(this->uom_mdata, uom_size, sizeof(double));
270308
this->uom_mdata.reset();
271309
ModuleBase::timer::end("Charge_Mixing", "allocate_mixing_uom");
272310
return;
@@ -276,16 +314,53 @@ void Charge_Mixing::mix_uom(std::vector<double>& uom_in, std::vector<double>& uo
276314
{
277315
ModuleBase::TITLE("Charge_Mixing", "mix_uom");
278316
ModuleBase::timer::start("Charge_Mixing", "mix_uom");
317+
318+
if (uom_in.empty())
319+
{
320+
ModuleBase::timer::end("Charge_Mixing", "mix_uom");
321+
return;
322+
}
323+
if (this->mixing_uom == nullptr || this->uom_mdata.length != uom_in.size()
324+
|| uom_save_in.size() != uom_in.size())
325+
{
326+
ModuleBase::WARNING_QUIT("Charge_Mixing", "UOM mixing data is not initialized consistently.");
327+
}
328+
279329
double* uom_value_out = uom_in.data();
280330
double* uom_value_in = uom_save_in.data();
281-
// For all nspin cases, uom_array layout is already fully sized
282-
// and mixing operates on the entire array
283-
this->mixing->push_data(this->uom_mdata, uom_value_in, uom_value_out, nullptr, false);
284-
this->mixing->mix_data(this->uom_mdata, uom_value_out);
331+
const std::size_t length = this->uom_mdata.length;
332+
333+
// The UOM vector has its own history and its own contractive step. It
334+
// must not reuse the charge-density mixer: the two vectors have different
335+
// lengths and represent different nonlinear variables.
336+
this->mixing_uom->push_data(this->uom_mdata,
337+
uom_value_in,
338+
uom_value_out,
339+
nullptr,
340+
true);
341+
342+
auto inner_product_uom = [length](double* first, double* second) {
343+
double value = 0.0;
344+
#ifdef _OPENMP
345+
#pragma omp parallel for reduction(+ : value)
346+
#endif
347+
for (std::size_t i = 0; i < length; ++i)
348+
{
349+
value += first[i] * second[i];
350+
}
351+
#ifdef __MPI
352+
Parallel_Reduce::reduce_pool(value);
353+
#endif
354+
return value;
355+
};
356+
357+
this->mixing_uom->cal_coef(this->uom_mdata, inner_product_uom);
358+
this->mixing_uom->mix_data(this->uom_mdata, uom_value_out);
359+
285360
ModuleBase::timer::end("Charge_Mixing", "mix_uom");
286361
#ifdef __MPI
287362
// Synchronize mixed uom across all ranks to prevent divergence
288-
// after multiple Pulay steps (same pattern as mix_dmr)
363+
// after multiple mixing steps.
289364
Parallel_Common::bcast_double(uom_in.data(), uom_in.size());
290365
#endif
291366
return;

source/source_estate/module_charge/charge_mixing.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ class Charge_Mixing
5757
*/
5858
void init_mixing();
5959

60+
/**
61+
* @brief initialize the independent DFT+U occupation-matrix mixer
62+
*/
63+
void init_mixing_uom();
64+
6065
/**
6166
* @brief allocate memory of dmr_mdata
6267
* @param nnr size of real-space density matrix
@@ -128,6 +133,7 @@ class Charge_Mixing
128133

129134
// mixing_data
130135
Base_Mixing::Mixing* mixing = nullptr; ///< Mixing object to mix charge density, kinetic energy density and compensation density
136+
Base_Mixing::Mixing* mixing_uom = nullptr; ///< Independent mixer for the DFT+U occupation matrix
131137
Base_Mixing::Mixing_Data rho_mdata; ///< Mixing data for charge density
132138
Base_Mixing::Mixing_Data tau_mdata; ///< Mixing data for kinetic energy density
133139
Base_Mixing::Mixing_Data nhat_mdata; ///< Mixing data for compensation density

source/source_estate/module_charge/chgmixing.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ void module_charge::chgmixing_ks_pw(const int iter, // scf iteration number
130130
p_chgmix->mixing_restart_step = inp.scf_nmax + 1;
131131
if (inp.dft_plus_u && inp.mixing_dftu)
132132
{
133+
p_chgmix->init_mixing_uom();
133134
// enable mixing_dftu for DFT+U occupation mixing
134135
dftu.enable_mixing();
135136
// allocate memory for uom_mdata
@@ -143,6 +144,13 @@ void module_charge::chgmixing_ks_pw(const int iter, // scf iteration number
143144
p_chgmix->init_mixing();
144145
p_chgmix->mixing_restart_count++;
145146

147+
if (inp.dft_plus_u && inp.mixing_dftu)
148+
{
149+
p_chgmix->init_mixing_uom();
150+
dftu.enable_mixing();
151+
p_chgmix->allocate_mixing_uom(dftu.get_size_pot_uterm_pw());
152+
}
153+
146154
if (inp.dft_plus_u)
147155
{
148156
if (dftu.get_uramping() > 0.01 && !dftu.u_converged())

source/source_estate/test/charge_mixing_test.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,36 @@ TEST_F(ChargeMixingTest, InitMixingTest)
285285
EXPECT_EQ(CMtest.rho_mdata.length, 2 * pw_basis.nrxx);
286286
}
287287

288+
TEST_F(ChargeMixingTest, DftuOccupationMatrixUsesIndependentMixer)
289+
{
290+
Charge_Mixing CMtest;
291+
CMtest.set_rhopw(&pw_basis, &pw_basis);
292+
CMtest.set_mixing("broyden",
293+
0.8,
294+
3,
295+
0.0,
296+
false,
297+
0.0,
298+
0.0,
299+
0.1,
300+
0.0,
301+
false,
302+
ucell.omega,
303+
ucell.tpiba);
304+
CMtest.init_mixing();
305+
CMtest.init_mixing_uom();
306+
307+
EXPECT_NE(CMtest.mixing, CMtest.mixing_uom);
308+
CMtest.allocate_mixing_uom(2);
309+
std::vector<double> uom = {1.0, 2.0};
310+
std::vector<double> uom_save = {0.0, 0.0};
311+
CMtest.mix_uom(uom, uom_save);
312+
313+
// UOM uses 0.5 * mixing_beta, independently of the charge mixer.
314+
EXPECT_DOUBLE_EQ(uom[0], 0.4);
315+
EXPECT_DOUBLE_EQ(uom[1], 0.8);
316+
}
317+
288318
TEST_F(ChargeMixingTest, InnerDotRealTest)
289319
{
290320
Charge_Mixing CMtest;

source/source_lcao/module_dftu/dftu_lcao_op.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,8 @@ void hamilt::DFTU<hamilt::OperatorLCAO<TK, TR>>::contributeHR()
440440
// Purpose: Ensure occ_mat is recomputed from updated DMR in next SCF iteration,
441441
// rather than using stale pre-read data from file.
442442
// TODO: This logic is confusing. Consider explicit variable like `is_last_spin_channel`.
443-
if (this->current_spin == this->nspin - 1 || this->nspin == 4)
443+
if ((this->current_spin == this->nspin - 1 || this->nspin == 4)
444+
&& this->dftu->get_occ_mat_ctrl() != 2)
444445
{
445446
this->dftu->mark_occ_mat_dirty();
446447
}

0 commit comments

Comments
 (0)