Skip to content

Commit 60d06e0

Browse files
committed
Perf: antiunitary ops are also used to reduce Hexx(R)
1 parent cc1bc63 commit 60d06e0

8 files changed

Lines changed: 131 additions & 24 deletions

File tree

source/source_io/module_parameter/input_conv.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ void Input_Conv::Convert()
509509
GlobalC::exx_info.info_opt_abfs.tolerence = PARAM.inp.exx_opt_orb_tolerence;
510510

511511
// Space-group symmetry is supported for LCAO EXX (nspin=1,2 via restore_dm/restore_HR;
512-
// nspin=4/SOC via restore_dm + restore_HR_soc), so symmetry=1 is honored here.
512+
// nspin=4/SOC via restore_dm + restore_HR_nspin4), so symmetry=1 is honored here.
513513

514514
GlobalC::exx_info.sync_from_global();
515515
}

source/source_lcao/module_ri/Exx_LRI.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class Exx_LRI
9393
const ModuleSymmetry::Symmetry_rotation* p_symrot = nullptr);
9494
// (nspin=4) real-space symmetry EXX: the spinor H(R) rotation couples the 4 spin channels via
9595
// the SU(2) part U(isym), so the 4 channels must be rotated together (not one-per-outer-loop).
96-
// Gathers the irreducible Hs of all 4 channels, calls Symmetry_rotation::restore_HR_soc, then
96+
// Gathers the irreducible Hs of all 4 channels, calls Symmetry_rotation::restore_HR_nspin4, then
9797
// finishes energy/gather per channel. Called from cal_exx_elec when p_symrot && nspin==4.
9898
void cal_exx_elec_soc(
9999
const std::vector<std::map<TA, std::map<TAC, RI::Tensor<Tdata>>>>& Ds,

source/source_lcao/module_ri/Exx_LRI.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ void Exx_LRI<Tdata>::cal_exx_elec_soc(
859859

860860
// pass 2: spinor-coupled rotation of the 4 channels from the irreducible sector to the full BZ
861861
std::array<std::map<TA, std::map<TAC, RI::Tensor<Tdata>>>, 4> Hs_full =
862-
p_symrot->restore_HR_soc(ucell.symm, ucell.atoms, ucell.st, 'H', Hs_irr);
862+
p_symrot->restore_HR_nspin4(ucell.symm, ucell.atoms, ucell.st, 'H', Hs_irr);
863863

864864
// pass 3: per-channel energy (full Hs, no repeat), then gather the repeated full Hs for abacus
865865
for (int is = 0; is < 4; ++is)

source/source_lcao/module_ri/module_exx_symmetry/irreducible_sector.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,38 @@
22
#include "source_io/module_parameter/parameter.h"
33
namespace ModuleSymmetry
44
{
5+
// Raw-index dispatch shared by the real-space sector helpers, matching the convention used
6+
// everywhere else (symmetry_rotation.h): isym < nrotk -> unitary gmatrix[isym];
7+
// isym >= nrotk -> spatial part of the antiunitary element Theta*gmatrix_anti[isym-nrotk].
8+
// Only the SPATIAL part is needed here: Theta acts on H(R) as sigma_y (.)^* sigma_y and
9+
// leaves R and the atom pair untouched, so the sector bookkeeping is identical for both kinds.
10+
static inline const ModuleBase::Matrix3& sector_gmatrix(const Symmetry& symm, const int isym)
11+
{
12+
return (isym < symm.nrotk) ? symm.gmatrix[isym] : symm.gmatrix_anti[isym - symm.nrotk];
13+
}
14+
static inline int sector_rotated_atom(const Symmetry& symm, const int isym, const int iat)
15+
{
16+
return (isym < symm.nrotk) ? symm.get_rotated_atom(isym, iat)
17+
: symm.get_rotated_atom_anti(isym - symm.nrotk, iat);
18+
}
19+
520
TC Irreducible_Sector::rotate_R(const Symmetry& symm,
621
const int isym, const int iat1, const int iat2, const TC& R, const char gauge) const
722
{
823
auto round2int = [symm](const double x) -> int { return x > 0 ? static_cast<int>(x + symm.epsilon) : static_cast<int>(x - symm.epsilon); };
924
const TCdouble R_double(static_cast<double>(R[0]), static_cast<double>(R[1]), static_cast<double>(R[2]));
25+
// return_lattice_ is already sized nrotk+nrotk_anti and indexed by the same raw isym.
26+
const ModuleBase::Matrix3& gmat = sector_gmatrix(symm, isym);
1027
const TCdouble Rrot_double = (gauge == 'L')
11-
? R_double * symm.gmatrix[isym] + this->return_lattice_[iat1][isym] - this->return_lattice_[iat2][isym]
12-
: R_double * symm.gmatrix[isym] + this->return_lattice_[iat2][isym] - this->return_lattice_[iat1][isym];
28+
? R_double * gmat + this->return_lattice_[iat1][isym] - this->return_lattice_[iat2][isym]
29+
: R_double * gmat + this->return_lattice_[iat2][isym] - this->return_lattice_[iat1][isym];
1330
return { round2int(Rrot_double.x), round2int(Rrot_double.y), round2int(Rrot_double.z) };
1431
}
1532
TapR Irreducible_Sector::rotate_apR_by_formula(const Symmetry& symm,
1633
const int isym, const TapR& apR, const char gauge) const
1734
{
18-
const Tap& aprot = { symm.get_rotated_atom(isym, apR.first.first), symm.get_rotated_atom(isym, apR.first.second) };
35+
const Tap& aprot = { sector_rotated_atom(symm, isym, apR.first.first),
36+
sector_rotated_atom(symm, isym, apR.first.second) };
1937
return { aprot, this->rotate_R(symm, isym, apR.first.first, apR.first.second, apR.second, gauge) };
2038
}
2139

@@ -183,11 +201,21 @@ namespace ModuleSymmetry
183201
for (auto& R : Rs)
184202
apR_all[{iat1, iat2}].insert(R);
185203

186-
// get invmap
204+
// get invmap over the operation set actually used by the sector search.
205+
// For nspin=4 magnetic that is the full Shubnikov group H (union) A, laid out as
206+
// [gmatrix[0..nrotk) | gmatrix_anti[0..nrotk_anti)]. gmatrix_invmap needs no change:
207+
// it searches the whole array for s[i]*s[j]==I, and A is closed under inversion
208+
// (if g in A had g^-1 in H then g = (g^-1)^-1 would be in H, contradicting H n A = {}),
209+
// so the concatenated array is exactly the parent group and every inverse is found,
210+
// with the inverse of a coset member landing inside the coset block.
187211
if (this->invmap_.empty())
188212
{
189-
this->invmap_.resize(symm.nrotk);
190-
symm.gmatrix_invmap(symm.gmatrix, symm.nrotk, invmap_.data());
213+
const int nop = symm.nrotk + symm.nrotk_anti;
214+
std::vector<ModuleBase::Matrix3> gmat_all(nop);
215+
for (int i = 0; i < symm.nrotk; ++i) { gmat_all[i] = symm.gmatrix[i]; }
216+
for (int j = 0; j < symm.nrotk_anti; ++j) { gmat_all[symm.nrotk + j] = symm.gmatrix_anti[j]; }
217+
this->invmap_.resize(nop);
218+
symm.gmatrix_invmap(gmat_all.data(), nop, invmap_.data());
191219
}
192220

193221
// get symmetry of BvK supercell
@@ -213,6 +241,13 @@ namespace ModuleSymmetry
213241
// if (!in_2d_plain[isym]) continue;
214242
// }
215243
const int& isym = this->isymbvk_to_isym_[isymbvk];
244+
// A BvK operation with no counterpart in the unit-cell operation set is marked -1.
245+
// For a magnetic nspin=4 system the unit-cell set is the Shubnikov group H (union) A,
246+
// which is generally a PROPER subset of the crystallographic group (operations that
247+
// merely tilt the moment belong to neither), so an unmatched BvK operation is a
248+
// normal outcome, not an error: it is simply not a symmetry of the magnetic system.
249+
// Skipping it only costs reduction, never correctness.
250+
if (isym < 0) { continue; }
216251
const TapR& apRrot = this->rotate_apR_by_formula(symm, this->invmap_[isym], irapR);
217252
const Tap& aprot = apRrot.first;
218253
const TC& Rrot = apRrot.second;

source/source_lcao/module_ri/module_exx_symmetry/irreducible_sector_bvk.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ namespace ModuleSymmetry
2525
break;
2626
}
2727
}
28+
// (nspin=4 magnetic) second pass over the antiunitary coset: the spatial part of
29+
// Theta*g is a genuine crystallographic operation of the BvK supercell too
30+
// (time-reversal factor is only needed when the data is rotated.)
31+
// Recorded after unitary ops so downstream (rotate_R, restore_HR_nspin4) can tell the two apart by isym vs. nrotk.
32+
if (isymbvk2isym[isymbvk] < 0)
33+
{
34+
for (int j = 0;j < symm.nrotk_anti;++j)
35+
{
36+
if (matequal(bvkgmat[isymbvk], symm.gmatrix_anti[j]))
37+
{
38+
isymbvk2isym[isymbvk] = symm.nrotk + j;
39+
break;
40+
}
41+
}
42+
}
43+
// Unmatched stays -1. That is legitimate for nspin=4 magnetic: the unit-cell set is the
44+
// Shubnikov group H (union) A, generally a PROPER subset of the crystallographic group
45+
// (operations that merely tilt the moment belong to neither), so a BvK operation may
46+
// have no counterpart. The consumer in find_irreducible_sector skips negative entries;
47+
// it must never use one as an index.
2848
}
2949
return isymbvk2isym;
3050
}
@@ -42,9 +62,12 @@ namespace ModuleSymmetry
4262
-> ModuleBase::Matrix3 {return ModuleBase::Matrix3(a1.x, a1.y, a1.z, a2.x, a2.y, a2.z, a3.x, a3.y, a3.z);};
4363
auto set_bvk_same_as_ucell = [&symm, this]()->void
4464
{
45-
this->bvk_nsym_ = symm.nrotk;
46-
this->isymbvk_to_isym_.resize(symm.nrotk);
47-
for (int isym = 0;isym < symm.nrotk;++isym) { this->isymbvk_to_isym_[isym] = isym; }
65+
// include the antiunitary coset (nspin=4 magnetic); nrotk_anti is 0 otherwise,
66+
// so this is unchanged for every other case.
67+
const int nop = symm.nrotk + symm.nrotk_anti;
68+
this->bvk_nsym_ = nop;
69+
this->isymbvk_to_isym_.resize(nop);
70+
for (int isym = 0;isym < nop;++isym) { this->isymbvk_to_isym_[isym] = isym; }
4871
};
4972
if (bvk_period[0] == bvk_period[1] && bvk_period[0] == bvk_period[2])
5073
{ //the BvK supercell has the same symmetry as the original cell
@@ -140,8 +163,10 @@ namespace ModuleSymmetry
140163
bvk_gmatrix.resize(bvk_nsg);
141164
bvk_gtrans.resize(bvk_nsg);
142165
this->bvk_nsym_ = bvk_nsg;
143-
// bvk suppercell cannot have higher symmetry than the original cell
144-
if (this->bvk_nsym_ > symm.nrotk)
166+
// bvk suppercell cannot have higher symmetry than the original cell.
167+
// The comparison is against the FULL operation set the sector search may use, i.e. the
168+
// Shubnikov group H (union) A for nspin=4 magnetic (nrotk_anti is 0 in every other case).
169+
if (this->bvk_nsym_ > symm.nrotk + symm.nrotk_anti)
145170
{
146171
std::cout << "reset bvk symmetry to the same as the original cell" << std::endl;
147172
set_bvk_same_as_ucell();

source/source_lcao/module_ri/module_exx_symmetry/symmetry_rotation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ namespace ModuleSymmetry
5050
{
5151
for (int i = 0;i < nop_tot;++i) { spin_U[i] = SpinRotation::so3_to_su2(gmatc[i]); }
5252
}
53-
this->spin_U_ = spin_U; // keep for restore_HR_soc (real-space EXX H(R) spin mixing)
53+
this->spin_U_ = spin_U; // keep for restore_HR_nspin4 (real-space EXX H(R) spin mixing)
5454

5555
// 2. calculate the rotation matrix in AO-representation for each ibz_kpoint and symmetry operation: M(k, isym)
5656
auto restrict_kpt = [](const TCdouble& kvec, const double& symm_prec) -> TCdouble

source/source_lcao/module_ri/module_exx_symmetry/symmetry_rotation.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,12 @@ namespace ModuleSymmetry
119119
/// orbital rotation T1^dagger(.)T2 (mode 'H') / T1^T(.)T2^* (mode 'D') applied to every
120120
/// channel, the SU(2) spin part U(isym) mixes them: H'^{ab}=sum_{cd} conj(U_{ca}) U_{db} [T1^dagger H^{cd} T2].
121121
/// The 4 channels are ordered is=a*2+b (a=row spin, b=col spin), matching RI_2D_Comm::split_is_block.
122-
/// Real-space (atom-pair) reduction uses space-group operations only, so no time-reversal branch is needed.
122+
/// (nspin=4 magnetic) The atom-pair reduction may also use the ANTIUNITARY elements of the
123+
/// Shubnikov group, flagged by isym >= nsym_. In real space time reversal acts as
124+
/// H(R) -> sigma_y H^*(R) sigma_y (R and the orbital indices untouched), which becomes a
125+
/// remap of the 4 channels applied after the SU(2) mixing; see symmetry_rotation_R.hpp.
123126
template<typename Tdata> // RI::Tensor type
124-
std::array<std::map<int, std::map<std::pair<int, TC>, RI::Tensor<Tdata>>>, 4> restore_HR_soc(
127+
std::array<std::map<int, std::map<std::pair<int, TC>, RI::Tensor<Tdata>>>, 4> restore_HR_nspin4(
125128
const Symmetry& symm, const Atom* atoms, const Statistics& st, const char mode,
126129
const std::array<std::map<int, std::map<std::pair<int, TC>, RI::Tensor<Tdata>>>, 4>& HR_irreducible_soc)const;
127130

@@ -207,7 +210,7 @@ namespace ModuleSymmetry
207210
std::vector<std::map<int, std::vector<std::complex<double>>>> Ms_;
208211

209212
/// (nspin=4) the SU(2) spin-1/2 rotation U(isym) for each symmetry operation, size [nsym].
210-
/// The spinor AO rotation is T(isym) (x) U(isym); restore_HR_soc uses it to mix the 4 spin
213+
/// The spinor AO rotation is T(isym) (x) U(isym); restore_HR_nspin4 uses it to mix the 4 spin
211214
/// channels of the real-space EXX H(R). Filled in cal_Ms (identity for nspin<4).
212215
std::vector<SpinRotation::Su2> spin_U_;
213216

source/source_lcao/module_ri/module_exx_symmetry/symmetry_rotation_R.hpp

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
#include <RI/physics/symmetry/Symmetry_Rotation.h>
77
namespace ModuleSymmetry
88
{
9+
/// Elementwise complex conjugation used by the time-reversal branch of restore_HR_nspin4.
10+
/// Overloaded (not specialized) so a real Tdata compiles to the identity.
11+
inline float conj_elem(const float v) { return v; }
12+
inline double conj_elem(const double v) { return v; }
13+
inline std::complex<float> conj_elem(const std::complex<float>& v) { return std::conj(v); }
14+
inline std::complex<double> conj_elem(const std::complex<double>& v) { return std::conj(v); }
15+
916
template<typename Tdata>
1017
inline void print_tensor(const RI::Tensor<Tdata>& t, const std::string& name, const double& threshold = 0.0)
1118
{
@@ -119,14 +126,24 @@ namespace ModuleSymmetry
119126
// which factorizes into the per-channel orbital rotation G^{cd}=T1^dagger H^{cd} T2 followed by the SU(2) spin mixing
120127
// H'^{ab} = sum_{cd} conj(U_{ca}) U_{db} G^{cd} (mode 'H')
121128
// H'^{ab} = sum_{cd} U_{ca} conj(U_{db}) G^{cd} (mode 'D')
122-
// Only space-group operations enter the atom-pair reduction, so no time-reversal branch is needed.
129+
//
130+
// (nspin=4 magnetic) The atom-pair reduction may also use the ANTIUNITARY elements Theta*g of
131+
// the Shubnikov group, flagged by isym >= nsym_. In real space time reversal acts as
132+
// H(R) -> sigma_y H^*(R) sigma_y
133+
// with R and the orbital indices untouched (in a real AO basis Theta = -i sigma_y K, and
134+
// H(R) = sum_k H(k) e^{-ikR} turns H(k) -> sigma_y H^*(-k) sigma_y into exactly this).
135+
// So the antiunitary case is the unitary result Y followed by one channel remap:
136+
// H'^{00} = conj(Y^{11}), H'^{01} = -conj(Y^{10})
137+
// H'^{10} = -conj(Y^{01}), H'^{11} = conj(Y^{00})
138+
// The map is an involution (sigma_y^* = -sigma_y, sigma_y^2 = I), so no extra sign is needed
139+
// when it is applied in either direction.
123140
template<typename Tdata>
124-
std::array<std::map<int, std::map<std::pair<int, TC>, RI::Tensor<Tdata>>>, 4> Symmetry_rotation::restore_HR_soc(
141+
std::array<std::map<int, std::map<std::pair<int, TC>, RI::Tensor<Tdata>>>, 4> Symmetry_rotation::restore_HR_nspin4(
125142
const Symmetry& symm, const Atom* atoms, const Statistics& st, const char mode,
126143
const std::array<std::map<int, std::map<std::pair<int, TC>, RI::Tensor<Tdata>>>, 4>& HR_irreducible_soc)const
127144
{
128-
ModuleBase::TITLE("Symmetry_rotation", "restore_HR_soc");
129-
ModuleBase::timer::start("Symmetry_rotation", "restore_HR_soc");
145+
ModuleBase::TITLE("Symmetry_rotation", "restore_HR_nspin4");
146+
ModuleBase::timer::start("Symmetry_rotation", "restore_HR_nspin4");
130147
assert(mode == 'H' || mode == 'D');
131148
std::array<std::map<int, std::map<std::pair<int, TC>, RI::Tensor<Tdata>>>, 4> HR_full;
132149

@@ -181,6 +198,7 @@ namespace ModuleSymmetry
181198
for (int is = 0;is < 4;++is) { G[is] = this->rotate_atompair_serial(Hir[is], isym, a1, a2, mode); }
182199
// step 2: SU(2) spin mixing of the 4 rotated channels into the output channels
183200
const SpinRotation::Su2& U = this->spin_U_[isym];
201+
std::array<RI::Tensor<Tdata>, 4> Hout_ch;
184202
for (int a = 0;a < 2;++a) {
185203
for (int b = 0;b < 2;++b)
186204
{
@@ -194,13 +212,39 @@ namespace ModuleSymmetry
194212
Hout += RI::Global_Func::convert<Tdata>(coeff) * G[c * 2 + d];
195213
}
196214
}
197-
HR_full[a * 2 + b][ap1][{ap2, R}] = Hout;
215+
Hout_ch[a * 2 + b] = Hout;
216+
}
217+
}
218+
// step 3 (antiunitary elements of the Shubnikov group): apply time reversal
219+
// sigma_y (.)^* sigma_y, i.e. the channel remap documented above.
220+
if (isym >= this->nsym_)
221+
{
222+
// NOTE: antiunitary elements only ever exist for nspin=4 (nrotk_anti is 0 otherwise), where Tdata is complex.
223+
// conj_elem() is the identity for a
224+
// real Tdata, so this branch must not be reached with one -- it would
225+
// silently degrade into a bare channel swap.
226+
static const int src[4] = { 3, 2, 1, 0 }; // 00<-11, 01<-10, 10<-01, 11<-00
227+
static const bool neg[4] = { false, true, true, false };
228+
std::array<RI::Tensor<Tdata>, 4> Y = Hout_ch;
229+
for (int is = 0;is < 4;++is)
230+
{
231+
const RI::Tensor<Tdata>& s = Y[src[is]];
232+
RI::Tensor<Tdata> t({ static_cast<size_t>(a1.nw), static_cast<size_t>(a2.nw) });
233+
for (size_t i = 0;i < t.shape[0];++i) {
234+
for (size_t j = 0;j < t.shape[1];++j)
235+
{
236+
const Tdata v = ModuleSymmetry::conj_elem(s(i, j));
237+
t(i, j) = neg[is] ? -v : v;
238+
}
239+
}
240+
Hout_ch[is] = t;
198241
}
199242
}
243+
for (int is = 0;is < 4;++is) { HR_full[is][ap1][{ap2, R}] = Hout_ch[is]; }
200244
}
201245
}
202246
}
203-
ModuleBase::timer::end("Symmetry_rotation", "restore_HR_soc");
247+
ModuleBase::timer::end("Symmetry_rotation", "restore_HR_nspin4");
204248
return HR_full;
205249
}
206250

0 commit comments

Comments
 (0)