Skip to content

Commit 2c234bf

Browse files
author
dyzheng
committed
docs(gga_grad): add comments explaining SF formulas and branching logic
- libxc_pot.cpp: comments for SF gradient decomposition, gga_grad branching - libxc_tools.cpp: comment for nspin=4 SF dh path with 4-component potential - xc_pot.cpp: comment for non-libxc SF builtin dispatch - xc_functional_gga_noncol_sf_builtin.cpp: header comment block with Scalmani-Frisch reference, chain-rule formulas, and step-by-step labels
1 parent 1c200ab commit 2c234bf

4 files changed

Lines changed: 51 additions & 0 deletions

File tree

source/source_hamilt/module_xc/libxc_pot.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ std::tuple<double,double,ModuleBase::matrix> XC_Functional_Libxc::v_xc_libxc( /
6464
}();
6565

6666
// converting rho
67+
// For nspin=4, the charge density has 4 components:
68+
// rho[0] = total charge, rho[1..3] = magnetization (mx, my, mz)
69+
// libxc works with spin-up/spin-down densities:
70+
// rho_up = 0.5*(rho[0] + |m|), rho_dn = 0.5*(rho[0] - |m|)
6771
std::vector<double> rho;
6872
std::vector<double> amag;
6973
std::vector<double> mag_part;
@@ -76,6 +80,11 @@ std::tuple<double,double,ModuleBase::matrix> XC_Functional_Libxc::v_xc_libxc( /
7680
std::tuple<std::vector<double>,std::vector<double>> rho_amag = XC_Functional_Libxc::convert_rho_amag_nspin4(nspin, nrxx, chr);
7781
rho = std::get<0>(std::move(rho_amag));
7882
amag = std::get<1>(std::move(rho_amag));
83+
// gga_grad=2/3: compute magnetization unit vector m_hat = m/|m|
84+
// needed for the Scalmani-Frisch (SF) gradient decomposition:
85+
// grad(rho_up) = 0.5 grad(rho[0]) + 0.5 m_hat_mu * grad(m_mu)
86+
// grad(rho_dn) = 0.5 grad(rho[0]) - 0.5 m_hat_mu * grad(m_mu)
87+
// gga_grad=1 (else): collinear approximation, uses grad(|m|) only
7988
if((PARAM.inp.gga_grad == 2 || PARAM.inp.gga_grad == 3) && (PARAM.globalv.domag || PARAM.globalv.domag_z))
8089
{
8190
mag_part = XC_Functional_Libxc::compute_mag_part_nspin4(nrxx, chr);
@@ -86,6 +95,9 @@ std::tuple<double,double,ModuleBase::matrix> XC_Functional_Libxc::v_xc_libxc( /
8695
std::vector<double> sigma;
8796
if(is_gga)
8897
{
98+
// gga_grad=2/3: use SF method to compute spin-up/spin-down gradients
99+
// via the chain-rule decomposition. This is more accurate than the
100+
// collinear approximation (gga_grad=1) for noncollinear magnetism.
89101
if(PARAM.inp.nspin==4 && (PARAM.inp.gga_grad == 2 || PARAM.inp.gga_grad == 3) && (PARAM.globalv.domag || PARAM.globalv.domag_z))
90102
{
91103
gdr = XC_Functional_Libxc::cal_gdr_sf(nspin, nrxx, rho, mag_part, tpiba, chr);
@@ -191,6 +203,9 @@ std::tuple<double,double,ModuleBase::matrix> XC_Functional_Libxc::v_xc_libxc( /
191203

192204
if(4==nspin_in)
193205
{
206+
// gga_grad=2/3: convert libxc spin-up/spin-down potential back to
207+
// nspin=4 representation via SF method (v_total, v_mag_hat)
208+
// gga_grad=1: standard conversion using |m| decomposition only
194209
if((PARAM.inp.gga_grad == 2 || PARAM.inp.gga_grad == 3) && (PARAM.globalv.domag || PARAM.globalv.domag_z))
195210
{
196211
v = XC_Functional_Libxc::convert_v_nspin4_sf(nrxx, chr, mag_part, v);

source/source_hamilt/module_xc/libxc_tools.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ std::pair<double,ModuleBase::matrix> XC_Functional_Libxc::convert_vtxc_v(
235235

236236
if(func.info->family == XC_FAMILY_GGA || func.info->family == XC_FAMILY_HYB_GGA)
237237
{
238+
// For nspin=4 with domag and gga_grad=2/3, use the SF (Scalmani-Frisch)
239+
// gradient correction. The potential v has 4 components:
240+
// v(0,:) = total density channel, v(1..3,:) = magnetization channels
241+
// cal_dh_sf returns dh[4][nrxx] in the same 4-component representation.
242+
// The vtxc contribution sums dh[is] * rho[is] for each channel.
238243
if(PARAM.inp.nspin==4 && (PARAM.inp.gga_grad == 2 || PARAM.inp.gga_grad == 3) && (PARAM.globalv.domag || PARAM.globalv.domag_z))
239244
{
240245
std::vector<double> mag_part_tmp = XC_Functional_Libxc::compute_mag_part_nspin4(nrxx, chr);

source/source_hamilt/module_xc/xc_functional_gga_noncol_sf_builtin.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ std::tuple<double, double, ModuleBase::matrix> v_xc_ncgga_sf_builtin(
2525
if (PARAM.inp.nspin != 4 || (!PARAM.globalv.domag && !PARAM.globalv.domag_z))
2626
throw std::domain_error("v_xc_ncgga_sf_builtin requires NSPIN==4.");
2727

28+
// ======================================================================
29+
// Scalmani-Frisch (SF) builtin for noncollinear GGA (gga_grad=3)
30+
//
31+
// Reference: Scalmani & Frisch, JCTC 8, 1069 (2012)
32+
//
33+
// Key formulas:
34+
// rho_up = 0.5*(rho + |m|), rho_dn = 0.5*(rho - |m|)
35+
// m_hat = m / |m| (magnetization unit vector)
36+
//
37+
// Gradient decomposition (chain rule via m_hat):
38+
// grad(rho_up) = 0.5 grad(rho) + 0.5 m_hat_mu * grad(m_mu)
39+
// grad(rho_dn) = 0.5 grad(rho) - 0.5 m_hat_mu * grad(m_mu)
40+
//
41+
// For GGA, the sigma = |grad(rho_up)|^2 etc. are computed from gdr1,gdr2,
42+
// then passed to spin-polarized XC functionals (xc_spin, gcx_spin, gcc_spin).
43+
// The resulting potential is converted back to nspin=4 representation:
44+
// v_tot = 0.5*(v_up + v_dn)
45+
// v_mag = 0.5*(v_up - v_dn) * m_hat
46+
// ======================================================================
47+
2848
ModulePW::PW_Basis* rhopw = chr->rhopw;
2949
const int npw = rhopw->npw;
3050
const double e2 = ModuleBase::e2;
@@ -33,6 +53,7 @@ std::tuple<double, double, ModuleBase::matrix> v_xc_ncgga_sf_builtin(
3353
const double fac = 0.5;
3454
const bool is_gga = (XC_Functional::get_func_type() == 2 || XC_Functional::get_func_type() == 4);
3555

56+
// Step 1: decompose charge density into spin-up/spin-down and compute m_hat
3657
std::vector<double> rhotmp1(nrxx), rhotmp2(nrxx), amag(nrxx);
3758
std::vector<double> mag_part(3 * nrxx, 0.0);
3859

@@ -83,6 +104,9 @@ std::tuple<double, double, ModuleBase::matrix> v_xc_ncgga_sf_builtin(
83104
}
84105
}
85106

107+
// Step 3: LDA contribution (xc_spin) and convert to nspin=4 potential
108+
// v(0,:) = 0.5*(vxc[0] + vxc[1]) -- total density channel
109+
// v(1..3,:) = 0.5*(vxc[0] - vxc[1]) * m_mu / |m| -- magnetization channels
86110
double etxc = 0, vtxc = 0;
87111
ModuleBase::matrix v(PARAM.inp.nspin, nrxx);
88112

@@ -114,6 +138,9 @@ std::tuple<double, double, ModuleBase::matrix> v_xc_ncgga_sf_builtin(
114138
etxc += e2 * exc * arho;
115139
}
116140

141+
// Step 4: GGA contribution (gcx_spin + gcc_spin) and divergence correction (grad_dot)
142+
// The gradient correction uses the same SF-decomposed gdr1, gdr2 and converts
143+
// back to nspin=4 via m_hat, matching the LDA conversion above.
117144
if (is_gga)
118145
{
119146
double etxcgc = 0, vtxcgc = 0;

source/source_hamilt/module_xc/xc_pot.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ std::tuple<double, double, ModuleBase::matrix> XC_Functional::v_xc(
5252
#endif
5353
}
5454

55+
// For non-libxc builds: gga_grad=3 uses the SF builtin that computes the
56+
// full nspin=4 GGA potential via the Scalmani-Frisch transformation.
57+
// This path handles spin-up/spin-down decomposition and gradient
58+
// corrections internally, returning (etxc, vtxc, v) directly.
5559
if (PARAM.inp.nspin == 4 && (PARAM.globalv.domag || PARAM.globalv.domag_z) && PARAM.inp.gga_grad == 3)
5660
{
5761
return ModuleXC::NCGGA_SF_Builtin::v_xc_ncgga_sf_builtin(nrxx, ucell->omega, ucell->tpiba, chr);

0 commit comments

Comments
 (0)