Skip to content

Commit 11d1112

Browse files
authored
Feature: fully utilized magnetic space group (MSG) enabling SOC (#7692)
* SOC symmetry: magmom filter, charge&DM symmetrization * Hexx(R) rotation * Feat: magnetic (Shubnikov) group * remove TRS_first_ * Fix rhog_symmetry: anti-unitary ops (used for reducing k-points) should be included * Perf: antiunitary ops are also used to reduce Hexx(R) * do not autoset magmom at symmetry=1 * Fix SOC bug: θ=π branch of so3_to_su2, and M⊗U convention * fix the phase factor: compatible with the formula * Fix: enable mixing_restart for EXX (instead of segfault) * print magnetic space group * doc and testing * refactor: parameterize symmetry in module_cell * refactor: remove default values of new parameters * fix parameter description out-of-sync * fix: if symmetry is not set, default -1 at lspinorb=1 (as before) * Fix: explicitly set mag 1 for nspin-2+symmetry test cases (was autoset to 1 before) * fix a UT format * refine the warning lines skipping autoset magmom * update results of nspin4+sym1 cases (wrong before spin-mixed charge density symmetrization is implemented)
1 parent 3e64d44 commit 11d1112

79 files changed

Lines changed: 2602 additions & 664 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/advanced/input_files/input-main.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@
614614
- **Description**: Takes value 1, 0 or -1.
615615
- -1: No symmetry will be considered. It is recommended to set -1 for non-colinear + soc calculations, where time reversal symmetry is broken sometimes.
616616
- 0: Only time reversal symmetry would be considered in symmetry operations, which implied k point and -k point would be treated as a single k point with twice the weight.
617-
- 1: Symmetry analysis will be performed to determine the type of Bravais lattice and associated symmetry operations. (point groups, space groups, primitive cells, and irreducible k-points)
617+
- 1: Symmetry analysis will be performed to determine the type of Bravais lattice and associated symmetry operations (point groups, space groups, primitive cells, and irreducible k-points). For a magnetic system, the symmetry of the initial magnetic structure will be analyzed and preserved.
618618

619619
> Note: When symmetry is enabled (value 1), k-points are reduced to the irreducible Brillouin zone (IBZ). For explicit k-point lists with custom weights (see KPT file), the custom weights are preserved during symmetry reduction. For Monkhorst-Pack grids, uniform weights are used.
620620
- **Default**: default

docs/parameters.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ parameters:
6363
Takes value 1, 0 or -1.
6464
* -1: No symmetry will be considered. It is recommended to set -1 for non-colinear + soc calculations, where time reversal symmetry is broken sometimes.
6565
* 0: Only time reversal symmetry would be considered in symmetry operations, which implied k point and -k point would be treated as a single k point with twice the weight.
66-
* 1: Symmetry analysis will be performed to determine the type of Bravais lattice and associated symmetry operations. (point groups, space groups, primitive cells, and irreducible k-points)
66+
* 1: Symmetry analysis will be performed to determine the type of Bravais lattice and associated symmetry operations (point groups, space groups, primitive cells, and irreducible k-points). For a magnetic system, the symmetry of the initial magnetic structure will be analyzed and preserved.
6767
6868
[NOTE] When symmetry is enabled (value 1), k-points are reduced to the irreducible Brillouin zone (IBZ). For explicit k-point lists with custom weights (see KPT file), the custom weights are preserved during symmetry reduction. For Monkhorst-Pack grids, uniform weights are used.
6969
default_value: default

python/pyabacus/src/ModuleDriver/py_driver.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,8 @@ CalculationResult PyDriver::run(
428428
impl_->ucell_->setup_cell(PARAM.globalv.global_in_stru, GlobalV::ofs_running, PARAM.inp.symmetry_prec, PARAM.inp.dfthalf_type, PARAM.inp.pseudo_dir, PARAM.inp.nspin,
429429
PARAM.inp.basis_type, PARAM.inp.orbital_dir, PARAM.inp.init_wfc,
430430
PARAM.inp.onsite_radius, PARAM.globalv.deepks_setorb, PARAM.inp.rpa,
431-
PARAM.inp.fixed_atoms, PARAM.inp.noncolin, PARAM.inp.calculation, PARAM.inp.esolver_type);
431+
PARAM.inp.fixed_atoms, PARAM.inp.noncolin, PARAM.inp.calculation, PARAM.inp.esolver_type,
432+
std::stoi(PARAM.inp.symmetry));
432433

433434
// Check atomic structure
434435
unitcell::check_atomic_stru(*impl_->ucell_, PARAM.inp.min_dist_coef);

source/source_cell/k_vector_utils.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,23 @@ void kvec_ibz_kpoint(K_Vectors& kv,
539539
kgmatrix[i] = symm.kgmatrix[i];
540540
}
541541

542-
if (!include_inv)
542+
if (symm.magnetic_nspin4)
543+
{
544+
// (nspin=4, magnetic) Time reversal Theta reverses the magnetization, so Theta alone is
545+
// NOT a symmetry and the blanket "-k is always equivalent" doubling below is invalid.
546+
// Only the antiunitary elements Theta*g with g in the moment-reversing coset belong to
547+
// the Shubnikov group; append exactly those, keeping the index convention
548+
// j + nrotk <-> Theta * gmatrix_anti[j] (decoded the same way in restore_dm).
549+
// (nspin=2 is unaffected: there the antiunitary operation is plain conjugation K, which
550+
// does not touch the spin, so D_s(-k)=D_s^*(k) holds even for a ferromagnet and the
551+
// generic branch below stays correct.)
552+
for (int j = 0; j < symm.nrotk_anti; ++j)
553+
{
554+
kgmatrix[j + symm.nrotk] = inv * symm.kgmatrix_anti[j];
555+
}
556+
nrotkm = symm.nrotk + symm.nrotk_anti;
557+
}
558+
else if (!include_inv)
543559
{
544560
for (int i = 0; i < symm.nrotk; ++i)
545561
{

source/source_cell/module_symmetry/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ add_library(
1212
symm_pricell.cpp
1313
symm_rho.cpp
1414
symmetry.cpp
15+
symmetry_rotation_spin.cpp
1516
)
1617

1718
if(ENABLE_COVERAGE)

source/source_cell/module_symmetry/symm_analysis.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,13 @@ void Symmetry::analy_sys(const Lattice& lat, const Statistics& st, Atom* atoms,
290290

291291
this->set_atom_map(atoms); // find the atom mapping according to the symmetry operations
292292

293+
// (nspin=4 / SOC) restrict to the unitary magnetic subgroup: drop operations that reverse
294+
// the magnetization (pseudovector), so they are not applied in k-reduction / density symmetrization.
295+
if (nspin == 4)
296+
{
297+
this->analyze_magnetic_group_nspin4(atoms, st, latvec1);
298+
}
299+
293300
// Do this here for debug
294301
if (calculation == "relax")
295302
{

source/source_cell/module_symmetry/symm_magnetic.cpp

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
#include "symmetry.h"
22
using namespace ModuleSymmetry;
33

4+
#include "symmetry_rotation_spin.h"
5+
#include "source_io/module_parameter/parameter.h"
6+
47
#include <set>
8+
#include <vector>
59

610
void Symmetry::analyze_magnetic_group(const Atom* atoms, const Statistics& st, int& nrot_out, int& nrotk_out)
711
{
@@ -74,6 +78,116 @@ void Symmetry::analyze_magnetic_group(const Atom* atoms, const Statistics& st, i
7478

7579
}
7680

81+
void Symmetry::analyze_magnetic_group_nspin4(const Atom* atoms, const Statistics& st, const ModuleBase::Matrix3& latvec)
82+
{
83+
// Restrict the space group to the unitary magnetic subgroup (nspin=4 / SOC):
84+
// operation g survives if it preserves the magnetic configuration as a pseudovector,
85+
// i.e. W(g) m_i = m_{g(i)} for every atom, with W(g) = spin_so3(gmatc).
86+
// Operations that reverse the moment (only symmetries together with time reversal) are dropped,
87+
// so they are no longer applied in k-reduction or density symmetrization.
88+
// Non-magnetic (m_i=0) keeps every operation.
89+
const ModuleBase::Matrix3 ilatvec = latvec.Inverse();
90+
std::vector<int> keep;
91+
keep.reserve(this->nrotk);
92+
int nrot_new = 0;
93+
94+
// Is the configuration actually magnetic? For m_i = 0 every operation both "preserves" and
95+
// "reverses" the moment, so the antiunitary coset is meaningless there:
96+
// Theta (TRS) itself is a symmetry and the grey group is handled by the usual -k shortcut in the k-reduction).
97+
bool has_moment = false;
98+
for (int iat = 0; iat < this->nat && !has_moment; ++iat)
99+
{
100+
const ModuleBase::Vector3<double>& m = atoms[st.iat2it[iat]].m_loc_[st.iat2ia[iat]];
101+
if (!this->equal(m.x, 0.0) || !this->equal(m.y, 0.0) || !this->equal(m.z, 0.0)) { has_moment = true; }
102+
}
103+
std::vector<int> anti; // operations that REVERSE the moment: Theta*g is a symmetry
104+
anti.reserve(this->nrotk);
105+
for (int isym = 0; isym < this->nrotk; ++isym)
106+
{
107+
const ModuleBase::Matrix3 gmatc = ilatvec * this->gmatrix[isym] * latvec;
108+
const ModuleBase::Matrix3 W = ModuleSymmetry::SpinRotation::spin_so3(gmatc);
109+
bool ok = true;
110+
for (int iat = 0; iat < this->nat && ok; ++iat)
111+
{
112+
const ModuleBase::Vector3<double>& m = atoms[st.iat2it[iat]].m_loc_[st.iat2ia[iat]];
113+
// pseudovector-rotated moment W*m (column-vector convention: m'^i = W_ij m^j)
114+
const ModuleBase::Vector3<double>& mrot = W * m;
115+
const int jat = this->get_rotated_atom(isym, iat);
116+
const ModuleBase::Vector3<double>& mj = atoms[st.iat2it[jat]].m_loc_[st.iat2ia[jat]];
117+
if (!this->equal(mrot.x, mj.x) || !this->equal(mrot.y, mj.y) || !this->equal(mrot.z, mj.z)) { ok = false; }
118+
}
119+
if (ok)
120+
{
121+
keep.push_back(isym);
122+
if (isym < this->nrot) { ++nrot_new; } // pure point-group rotations are the first nrot ops
123+
}
124+
else if (has_moment)
125+
{
126+
// g does not preserve m; check whether it exactly REVERSES it, i.e.
127+
// W(g) m_i = -m_{g(i)} for every atom. Then g alone is not a symmetry but the
128+
// antiunitary element Theta*g is, and it belongs to the Shubnikov group.
129+
bool anti_ok = true;
130+
for (int iat = 0; iat < this->nat && anti_ok; ++iat)
131+
{
132+
const ModuleBase::Vector3<double>& m = atoms[st.iat2it[iat]].m_loc_[st.iat2ia[iat]];
133+
const ModuleBase::Vector3<double>& mrot = W * m;
134+
const int jat = this->get_rotated_atom(isym, iat);
135+
const ModuleBase::Vector3<double>& mj = atoms[st.iat2it[jat]].m_loc_[st.iat2ia[jat]];
136+
if (!this->equal(mrot.x, -mj.x) || !this->equal(mrot.y, -mj.y) || !this->equal(mrot.z, -mj.z)) { anti_ok = false; }
137+
}
138+
if (anti_ok) { anti.push_back(isym); }
139+
}
140+
}
141+
142+
// Capture the antiunitary coset BEFORE the unitary arrays are compacted in place below
143+
// (the compaction overwrites gmatrix/kgmatrix/gtrans/isym_rotiat_ and would lose them).
144+
this->magnetic_nspin4 = has_moment;
145+
this->nrotk_anti = static_cast<int>(anti.size());
146+
if (this->nrotk_anti > 0)
147+
{
148+
this->isym_rotiat_anti_.resize(this->nrotk_anti);
149+
for (int j = 0; j < this->nrotk_anti; ++j)
150+
{
151+
const int isym = anti[j];
152+
this->gmatrix_anti[j] = this->gmatrix[isym];
153+
this->kgmatrix_anti[j] = this->kgmatrix[isym];
154+
this->gtrans_anti[j] = this->gtrans[isym];
155+
this->isym_rotiat_anti_[j] = this->isym_rotiat_[isym];
156+
}
157+
ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running,
158+
"MAGNETIC ANTIUNITARY OPERATIONS (Theta*g)", this->nrotk_anti);
159+
}
160+
161+
const int nrotk_new = static_cast<int>(keep.size());
162+
if (nrotk_new == this->nrotk) { return; } // nothing removed (non-magnetic or fully-preserving group)
163+
164+
// compact the operation arrays in ascending order (keeps the rotations-first layout).
165+
for (int i = 0; i < nrotk_new; ++i)
166+
{
167+
const int isym = keep[i];
168+
if (i != isym)
169+
{
170+
this->gmatrix[i] = this->gmatrix[isym];
171+
this->kgmatrix[i] = this->kgmatrix[isym];
172+
this->gtrans[i] = this->gtrans[isym];
173+
this->isym_rotiat_[i] = this->isym_rotiat_[isym];
174+
}
175+
}
176+
this->isym_rotiat_.resize(nrotk_new);
177+
this->nrot = nrot_new;
178+
this->nrotk = nrotk_new;
179+
180+
// refresh the point-/space-group labels for the reduced (unitary magnetic) group
181+
this->pointgroup(this->nrot, this->pgnumber, this->pgname, this->gmatrix, GlobalV::ofs_running, nullptr);
182+
this->pointgroup(this->nrotk, this->spgnumber, this->spgname, this->gmatrix, GlobalV::ofs_running, nullptr);
183+
ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "MAGNETIC POINT GROUP (unitary, nspin=4)", this->pgname);
184+
// space-group-consistent name of the unitary magnetic group (from nrotk, matching "POINT GROUP IN
185+
// SPACE GROUP"); pgname above is the pure-point-group-block name, which under-detects for hexagonal
186+
// (e.g. Co prints S_6 there but is C_6h here).
187+
ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "MAGNETIC POINT GROUP IN SPACE GROUP", this->spgname);
188+
ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "MAGNETIC SPACE GROUP OPERATIONS", this->nrotk);
189+
}
190+
77191
bool Symmetry::magmom_same_check(const Atom* atoms)const
78192
{
79193
ModuleBase::TITLE("Symmetry", "magmom_same_check");
@@ -96,3 +210,32 @@ bool Symmetry::magmom_same_check(const Atom* atoms)const
96210
return pricell_loop;
97211
}
98212

213+
int Symmetry::density_sym_ops(std::vector<ModuleBase::Matrix3>& kgmat,
214+
std::vector<ModuleBase::Vector3<double>>& gtr,
215+
std::vector<double>& trs_inv) const
216+
{
217+
// The density must be symmetrized with the SAME group that was used to fold the k-points
218+
// (see KVectorUtils::ibz_kpoint): otherwise the density accumulated over the IBZ is not
219+
// restored to the full BZ result. For nspin=4 with a non-zero moment that group is the
220+
// Shubnikov group H + Theta*A, so the antiunitary elements' spatial parts are appended here.
221+
// Theta leaves the charge invariant and reverses the magnetization, which is what `trs_inv`
222+
// encodes; the spatial bookkeeping (orbit grouping, phases) is identical for both kinds.
223+
const int nu = this->nrotk;
224+
const int na = (this->magnetic_nspin4 ? this->nrotk_anti : 0);
225+
kgmat.resize(nu + na);
226+
gtr.resize(nu + na);
227+
trs_inv.assign(nu + na, 1.0);
228+
for (int i = 0; i < nu; ++i)
229+
{
230+
kgmat[i] = this->kgmatrix[i];
231+
gtr[i] = this->gtrans[i];
232+
}
233+
for (int j = 0; j < na; ++j)
234+
{
235+
kgmat[nu + j] = this->kgmatrix_anti[j];
236+
gtr[nu + j] = this->gtrans_anti[j];
237+
trs_inv[nu + j] = -1.0;
238+
}
239+
return nu + na;
240+
}
241+

0 commit comments

Comments
 (0)