Repository navigation
Fix: correct My sign in noncollinear Mulliken magnetism #8250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,10 @@ | |
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| #include <algorithm> | ||
| #include <cmath> | ||
| #include <complex> | ||
|
|
||
| template <typename TK> | ||
| class OutputMullikenTest : public testing::Test | ||
| { | ||
|
|
@@ -101,6 +105,110 @@ TYPED_TEST(OutputMullikenTest, nspin4) | |
| remove("./mulliken.txt"); | ||
| } | ||
|
|
||
| namespace | ||
| { | ||
| using cd = std::complex<double>; | ||
|
|
||
| /// spinor of the +1 eigenstate of mhat.sigma for a unit vector mhat | ||
| void spinor_from_direction(const double mhat[3], cd c[2]) | ||
| { | ||
| double cos_theta = mhat[2]; | ||
| cos_theta = std::max(-1.0, cos_theta); | ||
| cos_theta = std::min(1.0, cos_theta); | ||
| const double theta = std::acos(cos_theta); | ||
| const double phi = std::atan2(mhat[1], mhat[0]); | ||
| const double half_theta = 0.5 * theta; | ||
| const double cos_half = std::cos(half_theta); | ||
| const double sin_half = std::sin(half_theta); | ||
| const double cos_phi = std::cos(phi); | ||
| const double sin_phi = std::sin(phi); | ||
| c[0] = cd(cos_half, 0.0); | ||
| c[1] = sin_half * cd(cos_phi, sin_phi); | ||
| } | ||
|
|
||
| /// <c|sigma_i|c> with the standard Pauli matrices (sigma_y = [[0,-i],[i,0]]) | ||
| double pauli_expectation(const cd c[2], const int component) | ||
| { | ||
| const cd sigma[3][2][2] = { | ||
| {{cd(0.0, 0.0), cd(1.0, 0.0)}, {cd(1.0, 0.0), cd(0.0, 0.0)}}, | ||
| {{cd(0.0, 0.0), cd(0.0, -1.0)}, {cd(0.0, 1.0), cd(0.0, 0.0)}}, | ||
| {{cd(1.0, 0.0), cd(0.0, 0.0)}, {cd(0.0, 0.0), cd(-1.0, 0.0)}}, | ||
| }; | ||
| cd value(0.0, 0.0); | ||
| for (int a = 0; a < 2; ++a) | ||
| { | ||
| for (int b = 0; b < 2; ++b) | ||
| { | ||
| value += std::conj(c[a]) * sigma[component][a][b] * c[b]; | ||
| } | ||
| } | ||
| return value.real(); | ||
| } | ||
| } // namespace | ||
|
|
||
| // Regression test for the nspin=4 Mulliken M_y sign. | ||
| // The stored DM is conj-first (DM_{ab} = conj(c_a) c_b, see dm_from_psi.h), so | ||
| // the physical Pauli components of mud = DM.S (S = 1 here) are | ||
| // Mx = Re(mud_{up,down} + mud_{down,up}) | ||
| // My = Im(mud_{up,down} - mud_{down,up}) | ||
| // Mz = Re(mud_{up,up} - mud_{down,down}). | ||
| // The pre-fix code accumulated -Im(mud_{up,down}) + Im(mud_{down,up}) = -My, | ||
| // so this test fails before the fix and passes after it. | ||
| TEST(OutputMullikenSpinTest, Nspin4MyFollowsPauliConvention) | ||
| { | ||
| const double dirs[4][3] = { | ||
| {0.0, 1.0, 0.0}, // pure +y: transverse channel, maximal |My| | ||
| {0.0, -1.0, 0.0}, // pure -y | ||
| {0.6, 0.8, 0.0}, // in-plane moment with My > 0 | ||
| {0.36, 0.48, -0.8}, // general three-dimensional direction | ||
| }; | ||
|
|
||
| for (const auto& mhat : dirs) | ||
| { | ||
| cd c[2]; | ||
| spinor_from_direction(mhat, c); | ||
|
|
||
| // DM in the conj-first storage convention of dm_from_psi | ||
| cd dm[4]; | ||
| dm[0] = std::conj(c[0]) * c[0]; | ||
| dm[1] = std::conj(c[0]) * c[1]; | ||
| dm[2] = std::conj(c[1]) * c[0]; | ||
| dm[3] = std::conj(c[1]) * c[1]; | ||
|
|
||
| // one spatial orbital, one k-point, overlap S = 1: mud = DM * S^T = DM. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking: Consider a small nonzero-My case using |
||
| // mud is a ScaLAPACK column-major buffer; collect_MW_nspin4 accesses | ||
| // mud(ic, ir) as the (ir, ic) element of the logical matrix. | ||
| const int nw = 2; | ||
| Parallel_Orbitals pv; | ||
| pv.set_serial(nw, nw); | ||
| ModuleBase::ComplexMatrix mud(pv.ncol, pv.nrow, true); | ||
| for (int ir = 0; ir < nw; ++ir) | ||
| { | ||
| for (int ic = 0; ic < nw; ++ic) | ||
| { | ||
| const int idx = ir * 2 + ic; | ||
| mud(ic, ir) = dm[idx]; | ||
| } | ||
| } | ||
|
|
||
| ModuleBase::matrix MecMulP(4, 1, true); | ||
| ModuleIO::collect_MW_nspin4(MecMulP, mud, nw, &pv); | ||
|
|
||
| const double expected_charge = 1.0; | ||
| const double expected_mx = pauli_expectation(c, 0); | ||
| const double expected_my = pauli_expectation(c, 1); | ||
| const double expected_mz = pauli_expectation(c, 2); | ||
| EXPECT_NEAR(MecMulP(0, 0), expected_charge, 1e-12); | ||
| EXPECT_NEAR(MecMulP(1, 0), expected_mx, 1e-12) | ||
| << "Mx wrong for direction (" << mhat[0] << "," << mhat[1] << "," << mhat[2] << ")"; | ||
| EXPECT_NEAR(MecMulP(2, 0), expected_my, 1e-12) | ||
| << "My sign/value wrong (conj-first DM) for direction (" << mhat[0] << "," << mhat[1] << "," | ||
| << mhat[2] << ")"; | ||
| EXPECT_NEAR(MecMulP(3, 0), expected_mz, 1e-12) | ||
| << "Mz wrong for direction (" << mhat[0] << "," << mhat[1] << "," << mhat[2] << ")"; | ||
| } | ||
| } | ||
|
|
||
| #include "mpi.h" | ||
| int main(int argc, char** argv) | ||
| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: Please write
DM*S^There, matching the header and theN,TGEMM incal_orbMulP. For complex Hermitian overlap, the transpose is needed formud = conj(P*S);DM*Sdescribes a different contraction.