Skip to content

Commit 0f95611

Browse files
committed
refactor(libxc): add weighted density and sigma sanitizer derivatives
1 parent 07cb1cb commit 0f95611

4 files changed

Lines changed: 370 additions & 1 deletion

File tree

source/source_hamilt/module_xc/libxc_abacus.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ class Charge;
1919

2020
namespace XC_Functional_Libxc
2121
{
22+
struct LibxcWeightedDerivatives
23+
{
24+
double energy_sum;
25+
std::vector<double> drho;
26+
std::vector<double> dsigma;
27+
};
28+
29+
2230
//-------------------
2331
// libxc_setup.cpp
2432
//-------------------
@@ -137,6 +145,20 @@ namespace XC_Functional_Libxc
137145
const std::vector<double> &rho,
138146
std::vector<double> exc);
139147

148+
// Reverse the density and sigma sanitizers for the weighted energy
149+
// accumulated by ABACUS. The result is in Hartree units and excludes the
150+
// real-space grid weight and ModuleBase::e2.
151+
extern LibxcWeightedDerivatives make_libxc_weighted_derivatives(
152+
const xc_func_type &func,
153+
const int nspin,
154+
const std::size_t nrxx,
155+
const std::vector<double> &sgn,
156+
const std::vector<double> &rho,
157+
const std::vector<double> &sigma,
158+
const std::vector<double> &exc,
159+
const std::vector<double> &vrho,
160+
const std::vector<double> &vsigma);
161+
140162
// converting vtxc and v from vrho and vsigma (libxc=>abacus)
141163
extern std::pair<double, ModuleBase::matrix> convert_vtxc_v(
142164
const xc_func_type &func,

source/source_hamilt/module_xc/libxc_tools.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,129 @@ double XC_Functional_Libxc::convert_etxc(
211211
return etxc;
212212
}
213213

214+
XC_Functional_Libxc::LibxcWeightedDerivatives
215+
XC_Functional_Libxc::make_libxc_weighted_derivatives(
216+
const xc_func_type &func,
217+
const int nspin,
218+
const std::size_t nrxx,
219+
const std::vector<double> &sgn,
220+
const std::vector<double> &rho,
221+
const std::vector<double> &sigma,
222+
const std::vector<double> &exc,
223+
const std::vector<double> &vrho,
224+
const std::vector<double> &vsigma)
225+
{
226+
assert(nspin == 1 || nspin == 2);
227+
assert(sgn.size() == nrxx * nspin);
228+
assert(rho.size() == nrxx * nspin);
229+
assert(exc.size() == nrxx);
230+
assert(vrho.size() == nrxx * nspin);
231+
assert(func.nspin == nspin);
232+
233+
const bool is_gga
234+
= func.info->family == XC_FAMILY_GGA || func.info->family == XC_FAMILY_HYB_GGA;
235+
const std::size_t nsigma = nspin == 1 ? 1 : 3;
236+
if (is_gga)
237+
{
238+
assert(sigma.size() == nrxx * nsigma);
239+
assert(vsigma.size() == nrxx * nsigma);
240+
}
241+
242+
LibxcWeightedDerivatives weighted;
243+
weighted.energy_sum = 0.0;
244+
weighted.drho.assign(nrxx * nspin, 0.0);
245+
if (is_gga)
246+
{
247+
weighted.dsigma.assign(nrxx * nsigma, 0.0);
248+
}
249+
250+
const double density_floor = func.dens_threshold;
251+
const double sigma_floor = func.sigma_threshold * func.sigma_threshold;
252+
double energy_sum = 0.0;
253+
#ifdef _OPENMP
254+
#pragma omp parallel for reduction(+:energy_sum) schedule(static, 512)
255+
#endif
256+
for (std::size_t ir = 0; ir < nrxx; ++ir)
257+
{
258+
double raw_density_sum = 0.0;
259+
double sanitized_density_sum = 0.0;
260+
double energy_weight = 0.0;
261+
for (int is = 0; is < nspin; ++is)
262+
{
263+
const std::size_t index = ir * nspin + is;
264+
raw_density_sum += rho[index];
265+
sanitized_density_sum += std::max(density_floor, rho[index]);
266+
energy_weight += sgn[index] * rho[index];
267+
}
268+
269+
// Libxc leaves all outputs zero below the total-density threshold.
270+
// Inside that branch, the ABACUS weighted energy is locally constant.
271+
if (raw_density_sum < density_floor)
272+
{
273+
continue;
274+
}
275+
276+
// ABACUS accumulates M*eps while Libxc differentiates Y*eps after
277+
// y_s=max(T,rho_s). Hence d eps/d y_s=(vrho_s-eps)/Y.
278+
energy_sum += energy_weight * exc[ir];
279+
const double libxc_weight = energy_weight / sanitized_density_sum;
280+
for (int is = 0; is < nspin; ++is)
281+
{
282+
const std::size_t index = ir * nspin + is;
283+
const double floor_jacobian = rho[index] > density_floor ? 1.0 : 0.0;
284+
weighted.drho[index]
285+
= sgn[index] * exc[ir]
286+
+ libxc_weight * floor_jacobian * (vrho[index] - exc[ir]);
287+
}
288+
289+
if (!is_gga)
290+
{
291+
continue;
292+
}
293+
294+
if (nspin == 1)
295+
{
296+
const double floor_jacobian = sigma[ir] > sigma_floor ? 1.0 : 0.0;
297+
weighted.dsigma[ir] = libxc_weight * floor_jacobian * vsigma[ir];
298+
continue;
299+
}
300+
301+
const std::size_t sigma_index = 3 * ir;
302+
const double sigma_uu = sigma[sigma_index];
303+
const double sigma_ud = sigma[sigma_index + 1];
304+
const double sigma_dd = sigma[sigma_index + 2];
305+
const double jacobian_uu = sigma_uu > sigma_floor ? 1.0 : 0.0;
306+
const double jacobian_dd = sigma_dd > sigma_floor ? 1.0 : 0.0;
307+
const double sanitized_uu = std::max(sigma_floor, sigma_uu);
308+
const double sanitized_dd = std::max(sigma_floor, sigma_dd);
309+
const double cross_limit = 0.5 * (sanitized_uu + sanitized_dd);
310+
311+
double cross_to_diagonal = 0.0;
312+
double cross_jacobian = 1.0;
313+
if (sigma_ud < -cross_limit)
314+
{
315+
cross_to_diagonal = -0.5;
316+
cross_jacobian = 0.0;
317+
}
318+
else if (sigma_ud > cross_limit)
319+
{
320+
cross_to_diagonal = 0.5;
321+
cross_jacobian = 0.0;
322+
}
323+
324+
weighted.dsigma[sigma_index]
325+
= libxc_weight * jacobian_uu
326+
* (vsigma[sigma_index] + cross_to_diagonal * vsigma[sigma_index + 1]);
327+
weighted.dsigma[sigma_index + 1]
328+
= libxc_weight * cross_jacobian * vsigma[sigma_index + 1];
329+
weighted.dsigma[sigma_index + 2]
330+
= libxc_weight * jacobian_dd
331+
* (vsigma[sigma_index + 2] + cross_to_diagonal * vsigma[sigma_index + 1]);
332+
}
333+
weighted.energy_sum = energy_sum;
334+
return weighted;
335+
}
336+
214337
// converting vtxc and v from vrho and vsigma (libxc=>abacus)
215338
std::pair<double,ModuleBase::matrix> XC_Functional_Libxc::convert_vtxc_v(
216339
const xc_func_type &func,

source/source_hamilt/module_xc/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ AddTest(
6262
AddTest(
6363
TARGET MODULE_HAMILT_XCTest_VXC
6464
LIBS parameter MPI::MPI_CXX Libxc::xc psi device container
65-
SOURCES test_xc5.cpp ../xc_grad.cpp ../xc_grad_prepare.cpp ../xc_grad_kernel.cpp ../xc_grad_assemble.cpp ../xc_grad_wfc.cpp ../xc_grad_utils.cpp ../xc_functional.cpp
65+
SOURCES test_xc5.cpp test_libxc_tools.cpp ../xc_grad.cpp ../xc_grad_prepare.cpp ../xc_grad_kernel.cpp ../xc_grad_assemble.cpp ../xc_grad_wfc.cpp ../xc_grad_utils.cpp ../xc_functional.cpp
6666
../xc_lda_wrap.cpp ../xc_gga_wrap.cpp
6767
../libxc_setup.cpp
6868
../libxc_lda_wrap.cpp
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#include "../libxc_abacus.h"
2+
3+
#include "gtest/gtest.h"
4+
#include <algorithm>
5+
#include <array>
6+
#include <cmath>
7+
#include <vector>
8+
9+
#ifdef __LIBXC
10+
11+
TEST(LibxcSanitizer, FullPolarizationUsesTheWeightedEnergyDerivative)
12+
{
13+
const std::array<int, 2> functional_ids = {{XC_LDA_X, XC_LDA_C_PZ}};
14+
const double density = 0.45;
15+
16+
for (std::size_t ifunc = 0; ifunc < functional_ids.size(); ++ifunc)
17+
{
18+
xc_func_type func;
19+
ASSERT_EQ(xc_func_init(&func, functional_ids[ifunc], XC_POLARIZED), 0);
20+
xc_func_set_dens_threshold(&func, 1.0e-6);
21+
22+
const auto evaluate = [&func](const double rho_up,
23+
const double rho_down,
24+
XC_Functional_Libxc::LibxcWeightedDerivatives* const weighted) {
25+
const std::vector<double> rho = {rho_up, rho_down};
26+
const std::vector<double> mask = {1.0, 1.0};
27+
std::vector<double> exc(1, 0.0);
28+
std::vector<double> vrho(2, 0.0);
29+
xc_lda_exc_vxc(&func, 1, rho.data(), exc.data(), vrho.data());
30+
if (weighted != nullptr)
31+
{
32+
*weighted = XC_Functional_Libxc::make_libxc_weighted_derivatives(func,
33+
2,
34+
1,
35+
mask,
36+
rho,
37+
std::vector<double>(),
38+
exc,
39+
vrho,
40+
std::vector<double>());
41+
}
42+
return (rho_up + rho_down) * exc[0];
43+
};
44+
45+
XC_Functional_Libxc::LibxcWeightedDerivatives weighted;
46+
const double energy = evaluate(density, 0.0, &weighted);
47+
ASSERT_EQ(weighted.drho.size(), 2U);
48+
EXPECT_DOUBLE_EQ(weighted.energy_sum, energy);
49+
50+
const double steps[] = {1.0e-3, 5.0e-4, 2.5e-4, 1.25e-4};
51+
std::array<double, 4> errors = {{0.0, 0.0, 0.0, 0.0}};
52+
for (std::size_t ieps = 0; ieps < 4; ++ieps)
53+
{
54+
const double step = steps[ieps];
55+
const double finite_difference
56+
= (evaluate(density + step, 0.0, nullptr) - evaluate(density - step, 0.0, nullptr)) / (2.0 * step);
57+
errors[ieps] = std::abs(weighted.drho[0] - finite_difference);
58+
EXPECT_LE(errors[ieps], 2.0e-7 * std::max(1.0, std::abs(weighted.drho[0])))
59+
<< "functional_id=" << functional_ids[ifunc] << ", step=" << step;
60+
}
61+
EXPECT_LE(errors[1], 0.4 * errors[0] + 1.0e-12);
62+
EXPECT_LE(errors[2], 0.4 * errors[1] + 1.0e-12);
63+
64+
const double inactive_density = 0.5 * func.dens_threshold;
65+
const double inactive_step = 0.2 * func.dens_threshold;
66+
XC_Functional_Libxc::LibxcWeightedDerivatives inactive_weighted;
67+
evaluate(density, inactive_density, &inactive_weighted);
68+
const double inactive_finite_difference = (evaluate(density, inactive_density + inactive_step, nullptr)
69+
- evaluate(density, inactive_density - inactive_step, nullptr))
70+
/ (2.0 * inactive_step);
71+
EXPECT_NEAR(inactive_finite_difference,
72+
inactive_weighted.drho[1],
73+
2.0e-8 * std::max(1.0, std::abs(inactive_finite_difference)));
74+
xc_func_end(&func);
75+
}
76+
}
77+
78+
TEST(LibxcSanitizer, GgaSigmaReverseMatchesTheWeightedEnergy)
79+
{
80+
xc_func_type func;
81+
ASSERT_EQ(xc_func_init(&func, XC_GGA_C_PBE, XC_POLARIZED), 0);
82+
xc_func_set_dens_threshold(&func, 1.0e-6);
83+
xc_func_set_sigma_threshold(&func, 1.0e-2);
84+
85+
const std::vector<double> mask = {1.0, 0.0};
86+
const std::vector<double> density = {0.40, 0.20};
87+
const double sigma_floor = func.sigma_threshold * func.sigma_threshold;
88+
const std::array<std::array<double, 3>, 5> sigma_states = {{{{0.040, 0.010, 0.030}},
89+
{{0.5 * sigma_floor, 0.0, 0.030}},
90+
{{0.040, 0.200, 0.030}},
91+
{{0.040, -0.200, 0.030}},
92+
{{0.5 * sigma_floor, 0.200, 0.030}}}};
93+
94+
const auto evaluate = [&func, &mask](const std::vector<double>& rho,
95+
const std::vector<double>& sigma,
96+
XC_Functional_Libxc::LibxcWeightedDerivatives* const weighted) {
97+
std::vector<double> exc(1, 0.0);
98+
std::vector<double> vrho(2, 0.0);
99+
std::vector<double> vsigma(3, 0.0);
100+
xc_gga_exc_vxc(&func, 1, rho.data(), sigma.data(), exc.data(), vrho.data(), vsigma.data());
101+
if (weighted != nullptr)
102+
{
103+
*weighted
104+
= XC_Functional_Libxc::make_libxc_weighted_derivatives(func, 2, 1, mask, rho, sigma, exc, vrho, vsigma);
105+
}
106+
return (mask[0] * rho[0] + mask[1] * rho[1]) * exc[0];
107+
};
108+
109+
for (std::size_t icase = 0; icase < sigma_states.size(); ++icase)
110+
{
111+
std::vector<double> sigma(sigma_states[icase].begin(), sigma_states[icase].end());
112+
XC_Functional_Libxc::LibxcWeightedDerivatives weighted;
113+
const double energy = evaluate(density, sigma, &weighted);
114+
EXPECT_DOUBLE_EQ(weighted.energy_sum, energy);
115+
ASSERT_EQ(weighted.drho.size(), 2U);
116+
ASSERT_EQ(weighted.dsigma.size(), 3U);
117+
118+
if (icase == 0)
119+
{
120+
for (int component = 0; component < 2; ++component)
121+
{
122+
std::vector<double> perturbed_density = density;
123+
const double step = 1.0e-6;
124+
perturbed_density[component] += step;
125+
const double energy_plus = evaluate(perturbed_density, sigma, nullptr);
126+
perturbed_density[component] -= 2.0 * step;
127+
const double energy_minus = evaluate(perturbed_density, sigma, nullptr);
128+
const double finite_difference = (energy_plus - energy_minus) / (2.0 * step);
129+
EXPECT_NEAR(finite_difference,
130+
weighted.drho[component],
131+
2.0e-7 * std::max(1.0, std::abs(weighted.drho[component])));
132+
}
133+
}
134+
135+
if (icase == 1 || icase == 4)
136+
{
137+
EXPECT_DOUBLE_EQ(weighted.dsigma[0], 0.0);
138+
}
139+
if (icase >= 2)
140+
{
141+
EXPECT_DOUBLE_EQ(weighted.dsigma[1], 0.0);
142+
}
143+
144+
for (int component = 0; component < 3; ++component)
145+
{
146+
const double step = 1.0e-6;
147+
sigma[component] += step;
148+
const double energy_plus = evaluate(density, sigma, nullptr);
149+
sigma[component] -= 2.0 * step;
150+
const double energy_minus = evaluate(density, sigma, nullptr);
151+
sigma[component] += step;
152+
const double finite_difference = (energy_plus - energy_minus) / (2.0 * step);
153+
EXPECT_NEAR(finite_difference,
154+
weighted.dsigma[component],
155+
2.0e-7 * std::max(1.0, std::abs(weighted.dsigma[component])))
156+
<< "case=" << icase << ", sigma component=" << component;
157+
}
158+
}
159+
xc_func_end(&func);
160+
}
161+
162+
TEST(LibxcSanitizer, UnpolarizedSelfSigmaReverseMatchesTheWeightedEnergy)
163+
{
164+
xc_func_type func;
165+
ASSERT_EQ(xc_func_init(&func, XC_GGA_C_PBE, XC_UNPOLARIZED), 0);
166+
xc_func_set_dens_threshold(&func, 1.0e-6);
167+
xc_func_set_sigma_threshold(&func, 1.0e-2);
168+
169+
const std::vector<double> mask = {1.0};
170+
const std::vector<double> density = {0.40};
171+
const double sigma_floor = func.sigma_threshold * func.sigma_threshold;
172+
173+
const auto evaluate = [&func, &mask, &density](const double sigma_value,
174+
XC_Functional_Libxc::LibxcWeightedDerivatives* const weighted) {
175+
const std::vector<double> sigma = {sigma_value};
176+
std::vector<double> exc(1, 0.0);
177+
std::vector<double> vrho(1, 0.0);
178+
std::vector<double> vsigma(1, 0.0);
179+
xc_gga_exc_vxc(&func, 1, density.data(), sigma.data(), exc.data(), vrho.data(), vsigma.data());
180+
if (weighted != nullptr)
181+
{
182+
*weighted = XC_Functional_Libxc::make_libxc_weighted_derivatives(func,
183+
1,
184+
1,
185+
mask,
186+
density,
187+
sigma,
188+
exc,
189+
vrho,
190+
vsigma);
191+
}
192+
return mask[0] * density[0] * exc[0];
193+
};
194+
195+
const double below_floor_sigma = 0.5 * sigma_floor;
196+
const double below_floor_step = 0.2 * sigma_floor;
197+
XC_Functional_Libxc::LibxcWeightedDerivatives below_floor_weighted;
198+
const double below_floor_energy = evaluate(below_floor_sigma, &below_floor_weighted);
199+
ASSERT_EQ(below_floor_weighted.dsigma.size(), 1U);
200+
EXPECT_DOUBLE_EQ(below_floor_weighted.energy_sum, below_floor_energy);
201+
EXPECT_DOUBLE_EQ(below_floor_weighted.dsigma[0], 0.0);
202+
const double below_floor_finite_difference = (evaluate(below_floor_sigma + below_floor_step, nullptr)
203+
- evaluate(below_floor_sigma - below_floor_step, nullptr))
204+
/ (2.0 * below_floor_step);
205+
EXPECT_NEAR(below_floor_finite_difference, below_floor_weighted.dsigma[0], 1.0e-12);
206+
207+
const double above_floor_sigma = 0.040;
208+
const double above_floor_step = 1.0e-6;
209+
XC_Functional_Libxc::LibxcWeightedDerivatives above_floor_weighted;
210+
const double above_floor_energy = evaluate(above_floor_sigma, &above_floor_weighted);
211+
ASSERT_EQ(above_floor_weighted.dsigma.size(), 1U);
212+
EXPECT_DOUBLE_EQ(above_floor_weighted.energy_sum, above_floor_energy);
213+
EXPECT_GT(std::abs(above_floor_weighted.dsigma[0]), 1.0e-12);
214+
const double above_floor_finite_difference = (evaluate(above_floor_sigma + above_floor_step, nullptr)
215+
- evaluate(above_floor_sigma - above_floor_step, nullptr))
216+
/ (2.0 * above_floor_step);
217+
EXPECT_NEAR(above_floor_finite_difference,
218+
above_floor_weighted.dsigma[0],
219+
2.0e-7 * std::max(1.0, std::abs(above_floor_weighted.dsigma[0])));
220+
221+
xc_func_end(&func);
222+
}
223+
224+
#endif

0 commit comments

Comments
 (0)