Skip to content

Commit 71ee1d7

Browse files
authored
Fix: switch to support the solid spherical harmonics that is consistent with other functionalities (#7258)
* Fix: switch to support the solid spherical harmonics that is consistent with other functionalities * correct impl. and update tests * correct impl.: add the sqrt(2) coefficient for sl0 * correct the impl. of Lz * update unittest * correct the unittest
1 parent c37671b commit 71ee1d7

3 files changed

Lines changed: 222 additions & 117 deletions

File tree

source/source_io/module_hs/cal_pLpR.cpp

Lines changed: 115 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,75 +18,154 @@
1818
*
1919
* FIXME: the following part will be transfered to TwoCenterIntegrator soon
2020
*
21+
* Notation
22+
* --------
23+
* ylm: complex spherical harmonics
24+
* slm: solid (real) spherical harmonics
25+
*
26+
* Changelog
27+
* ---------
28+
* Switch to support the solid spherical harmonics to keep consistent with
29+
* implementations of other parts.
30+
* Formulation (in Chinese):
31+
* https://my.feishu.cn/wiki/D0enwcUKfiJgtSkJ5scc9Dagntc
2132
*/
2233

23-
// L+|l, m> = sqrt((l-m)(l+m+1))|l, m+1>, return the sqrt((l-m)(l+m+1))
24-
double _lplus_on_ylm(const int l, const int m)
34+
// L+ylm = sqrt((l-m)(l+m+1))ylm+1, return the sqrt((l-m)(l+m+1))
35+
double _lambda_plus(const int l, const int m)
2536
{
26-
return std::sqrt((l - m) * (l + m + 1));
37+
return std::sqrt((l - m) * (l + m + 1)); // NOTE: complex spherical harmonics
2738
}
2839

29-
// L-|l, m> = sqrt((l+m)(l-m+1))|l, m-1>, return the sqrt((l+m)(l-m+1))
30-
double _lminus_on_ylm(const int l, const int m)
40+
// L-ylm = sqrt((l+m)(l-m+1))ylm-1, return the sqrt((l+m)(l-m+1))
41+
double _lambda_minus(const int l, const int m)
3142
{
32-
return std::sqrt((l + m) * (l - m + 1));
43+
return std::sqrt((l + m) * (l - m + 1)); // NOTE: complex spherical harmonics
3344
}
3445

46+
const std::complex<double> i = {0., 1.};
47+
const double invsqrt2 = std::sqrt(2) * 0.5;
48+
3549
std::complex<double> ModuleIO::cal_LzijR(
3650
const std::unique_ptr<TwoCenterIntegrator>& calculator,
3751
const int it, const int ia, const int il, const int iz, const int mi,
3852
const int jt, const int ja, const int jl, const int jz, const int mj,
3953
const ModuleBase::Vector3<double>& vR)
4054
{
55+
if(mj == 0) {
56+
return std::complex<double>(0.);
57+
}
4158
double val_ = 0;
42-
calculator->calculate(it, il, iz, mi, jt, jl, jz, mj, vR, &val_);
43-
return std::complex<double>(mi) * val_;
59+
calculator->calculate(it, il, iz, mi, jt, jl, jz, -mj, vR, &val_);
60+
return i * static_cast<double>(mj) * val_;
4461
}
4562

46-
std::complex<double> ModuleIO::cal_LyijR(
63+
std::complex<double> ModuleIO::cal_LxijR(
4764
const std::unique_ptr<TwoCenterIntegrator>& calculator,
4865
const int it, const int ia, const int il, const int iz, const int im,
4966
const int jt, const int ja, const int jl, const int jz, const int jm,
5067
const ModuleBase::Vector3<double>& vR)
5168
{
52-
// Ly = -i/2 * (L+ - L-)
53-
const double plus_ = _lplus_on_ylm(jl, jm);
54-
const double minus_ = _lminus_on_ylm(jl, jm);
55-
double val_plus = 0, val_minus = 0;
56-
if (plus_ != 0)
57-
{
58-
calculator->calculate(it, il, iz, im, jt, jl, jz, jm + 1, vR, &val_plus);
59-
val_plus *= plus_;
69+
const double lmbdp = _lambda_plus(jl, jm);
70+
const double lmbdm = _lambda_minus(jl, jm);
71+
// two-center-integral placeholders
72+
double valp = 0.;
73+
double valm = 0.;
74+
if (jm > 1) {
75+
if (std::fabs(lmbdp) > 1e-12) {
76+
calculator->calculate(it, il, iz, im, jt, jl, jz, -(jm+1), vR, &valp);
77+
}
78+
if (std::fabs(lmbdm) > 1e-12) {
79+
calculator->calculate(it, il, iz, im, jt, jl, jz, -(jm-1), vR, &valm);
80+
}
81+
return i * 0.5 * (lmbdp * valp + lmbdm * valm);
6082
}
61-
if (minus_ != 0)
62-
{
63-
calculator->calculate(it, il, iz, im, jt, jl, jz, jm - 1, vR, &val_minus);
64-
val_minus *= minus_;
83+
if (jm == 1) {
84+
if (std::fabs(lmbdp) > 1e-12) {
85+
calculator->calculate(it, il, iz, im, jt, jl, jz, -2, vR, &valp);
86+
}
87+
return i * 0.5 * lmbdp * valp;
88+
}
89+
if (jm == 0) {
90+
const double lmbd = _lambda_plus(jl, 0); // std::sqrt(jl*(jl+1))
91+
if (std::fabs(lmbd) > 1e-12) {
92+
calculator->calculate(it, il, iz, im, jt, jl, jz, -1, vR, &valp);
93+
}
94+
return i * invsqrt2 * lmbd * valp;
95+
}
96+
if (jm == -1) {
97+
if (std::fabs(lmbdp) > 1e-12) {
98+
calculator->calculate(it, il, iz, im, jt, jl, jz, 0, vR, &valp);
99+
}
100+
if (std::fabs(lmbdm) > 1e-12) {
101+
calculator->calculate(it, il, iz, im, jt, jl, jz, 2, vR, &valm);
102+
}
103+
return -i * 0.5 * (std::sqrt(2) * lmbdp * valp + lmbdm * valm);
65104
}
66-
return std::complex<double>(0, -0.5) * (val_plus - val_minus);
105+
if (jm < -1) {
106+
if (std::fabs(lmbdp) > 1e-12) {
107+
calculator->calculate(it, il, iz, im, jt, jl, jz, -(jm+1), vR, &valp);
108+
}
109+
if (std::fabs(lmbdm) > 1e-12) {
110+
calculator->calculate(it, il, iz, im, jt, jl, jz, -(jm-1), vR, &valm);
111+
}
112+
return -i * 0.5 * (lmbdp * valp + lmbdm * valm);
113+
}
114+
assert(false); // inaccessible
67115
}
68116

69-
std::complex<double> ModuleIO::cal_LxijR(
117+
std::complex<double> ModuleIO::cal_LyijR(
70118
const std::unique_ptr<TwoCenterIntegrator>& calculator,
71119
const int it, const int ia, const int il, const int iz, const int im,
72120
const int jt, const int ja, const int jl, const int jz, const int jm,
73121
const ModuleBase::Vector3<double>& vR)
74122
{
75-
// Lx = 1/2 * (L+ + L-)
76-
const double plus_ = _lplus_on_ylm(jl, jm);
77-
const double minus_ = _lminus_on_ylm(jl, jm);
78-
double val_plus = 0, val_minus = 0;
79-
if (plus_ != 0)
80-
{
81-
calculator->calculate(it, il, iz, im, jt, jl, jz, jm + 1, vR, &val_plus);
82-
val_plus *= plus_;
123+
const double lmbdp = _lambda_plus(jl, jm);
124+
const double lmbdm = _lambda_minus(jl, jm);
125+
// two-center-integral placeholders
126+
double valp = 0.;
127+
double valm = 0.;
128+
if (jm > 1) {
129+
if (std::fabs(lmbdp) > 1e-12) {
130+
calculator->calculate(it, il, iz, im, jt, jl, jz, jm+1, vR, &valp);
131+
}
132+
if (std::fabs(lmbdm) > 1e-12) {
133+
calculator->calculate(it, il, iz, im, jt, jl, jz, jm-1, vR, &valm);
134+
}
135+
return -i * 0.5 * (lmbdp * valp - lmbdm * valm);
83136
}
84-
if (minus_ != 0)
85-
{
86-
calculator->calculate(it, il, iz, im, jt, jl, jz, jm - 1, vR, &val_minus);
87-
val_minus *= minus_;
137+
if (jm == 1) {
138+
if (std::fabs(lmbdp) > 1e-12) {
139+
calculator->calculate(it, il, iz, im, jt, jl, jz, 2, vR, &valp);
140+
}
141+
if (std::fabs(lmbdm) > 1e-12) {
142+
calculator->calculate(it, il, iz, im, jt, jl, jz, 0, vR, &valm);
143+
}
144+
return -i * 0.5 * (lmbdp * valp - std::sqrt(2) * lmbdm * valm);
145+
}
146+
if (jm == 0) {
147+
const double lmbd = _lambda_plus(jl, 0); // std::sqrt(l*(l+1))
148+
if (std::fabs(lmbd) > 1e-12) {
149+
calculator->calculate(it, il, iz, im, jt, jl, jz, 1, vR, &valp);
150+
}
151+
return -i * invsqrt2 * lmbd * valp;
152+
}
153+
if (jm == -1) {
154+
if (std::fabs(lmbdm) > 1e-12) {
155+
calculator->calculate(it, il, iz, im, jt, jl, jz, -2, vR, &valm);
156+
}
157+
return -i * 0.5 * lmbdm * valm;
158+
}
159+
if (jm < -1) {
160+
if (std::fabs(lmbdp) > 1e-12) {
161+
calculator->calculate(it, il, iz, im, jt, jl, jz, jm+1, vR, &valp);
162+
}
163+
if (std::fabs(lmbdm) > 1e-12) {
164+
calculator->calculate(it, il, iz, im, jt, jl, jz, jm-1, vR, &valm);
165+
}
166+
return i * 0.5 * (lmbdp * valp - lmbdm * valm);
88167
}
89-
return std::complex<double>(0.5) * (val_plus + val_minus);
168+
assert(false); // inaccessible
90169
}
91170

92171
ModuleIO::AngularMomentumCalculator::AngularMomentumCalculator(

source/source_io/module_hs/cal_pLpR.h

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
/**
2-
* calculate the <phi_i|Lx/Ly/Lz|phi_j> matrix elements, in which the Lx/Ly/Lz
3-
* are the angular momentum operators, |phi_i> and |phi_j> are the numerical
4-
* atomic orbitals (NAOs).
2+
* calculate the <phi_i|Lx/Ly/Lz|phi_j> matrix elements with the ACA (atom-centered
3+
* approximation), in which the Lx/Ly/Lz are the angular momentum operators,
4+
* |phi_i> and |phi_j> are the numerical atomic orbitals (NAOs).
5+
*
6+
* Note: this module is the most suitable for the case of SOC (spin-orbit coupling)
7+
* calculation. A strict implmentation requires the evaluation on the position operator
8+
* and the momentum operator, is beyond the scope of present implementation.
59
*
610
* Formulation
711
* -----------
@@ -13,30 +17,51 @@
1317
* Lx = (L+ + L-) / 2
1418
* Ly = (L+ - L-) / 2i
1519
*
16-
* With L+, the spherical harmonic function Ylm (denoted as |l, m> in the following)
17-
* can be raised:
20+
* With L+, the COMPLEX spherical harmonic function Ylm (denoted as |l, m> in
21+
* the following) can be raised:
1822
*
1923
* L+|l, m> = sqrt((l-m)(l+m+1))|l, m+1>
2024
*
2125
* Likely, with L-, the spherical harmonic function Ylm can be lowered:
2226
*
2327
* L-|l, m> = sqrt((l+m)(l-m+1))|l, m-1>
2428
*
25-
* Therefore the Lx matrix element can be calculated as:
29+
* Therefore, for the case where there is only one atom, the Lx matrix element
30+
* can be calculated as:
2631
*
2732
* <l, m|Lx|l, m'> = sqrt((l-m)(l+m+1)) * delta(m, m'+1) / 2
2833
* + sqrt((l+m)(l-m+1)) * delta(m, m'-1) / 2
2934
*
30-
* The Ly matrix element can be calculated as:
35+
* Likewise the Ly matrix element can be calculated as:
3136
*
3237
* <l, m|Ly|l, m'> = sqrt((l-m)(l+m+1)) * delta(m, m'+1) / 2i
3338
* - sqrt((l+m)(l-m+1)) * delta(m, m'-1) / 2i
3439
*
35-
* The Lz matrix element can be calculated as:
40+
* and the Lz matrix element can be calculated as:
3641
*
3742
* <l, m|Lz|l, m'> = m * delta(m, m')
3843
*
39-
* However, things will change when there are more than one centers.
44+
* ABACUS employs the REAL spherical harmonics, the transformation between the
45+
* REAL and COMPLEX spherical harmonics can be found from:
46+
* https://abacus.deepmodeling.com/en/latest/advanced/pp_orb.html
47+
*
48+
* For the case where there are multiple atoms, present calculation is based on
49+
* the Atom-Centered-Approximation (ACA), in which the global angular momentum
50+
* operator is decomposed to the sum of the local opeartor that only considers
51+
* one atom. Therefore, the matrix element can be calculated by combining the
52+
* ladder operators with the two-center-integral technique:
53+
*
54+
* <phi_i|Lx/Ly/Lz|phi_j> = <phi_i| (Lx/Ly/Lz |phi_j>)
55+
*
56+
* the two-center-integral will be performed after the application of the L
57+
* operator. For example after the application of Lz, the equation above yields
58+
*
59+
* <phi_i|Lz|phi_j> = mj <phi_i|phi_j>
60+
*
61+
* For Lx = 1/2 * (L+ + L-):
62+
*
63+
* <phi_i|Lx|phi_j> = sqrt((lj-mj)(lj+mj+1)) * <phi_i|phi_j' > / 2
64+
* + sqrt((lj+mj)(lj-mj+1)) * <phi_i|phi_j''> / 2
4065
*
4166
* Technical Details
4267
* -----------------

0 commit comments

Comments
 (0)