Skip to content

Commit 9eedda4

Browse files
maki49claude
andcommitted
test(soc): SpinConventionCoupling now exercises the real func_xyz_to_updown
Replaces the local Pauli re-implementation with the actual elecstate::DensityMatrix_Tools::func_xyz_to_updown, fed the conj-first stored DM block (DM=conj(P)) exactly as the runtime does, and asserts (a) it recovers the physical magnetization and (b) spin_so3 (the rotation psymmg_soc applies to the grid) agrees with the SU(2) block rotation + real extraction. The two conventions can no longer drift apart silently; the test fails on the deepmodeling#7664 m_y flip. Links density_matrix.cpp only (no io/mocks needed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4c78816 commit 9eedda4

2 files changed

Lines changed: 50 additions & 31 deletions

File tree

source/source_cell/module_symmetry/test/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,9 @@ AddTest(
2121
TARGET MODULE_CELL_SYMMETRY_rho_soc
2222
LIBS parameter base ${math_libs} device symmetry
2323
SOURCES symm_rho_soc_test.cpp
24+
${ABACUS_SOURCE_DIR}/source_estate/module_dm/density_matrix.cpp
25+
${ABACUS_SOURCE_DIR}/source_hamilt/module_hcontainer/base_matrix.cpp
26+
${ABACUS_SOURCE_DIR}/source_hamilt/module_hcontainer/hcontainer.cpp
27+
${ABACUS_SOURCE_DIR}/source_hamilt/module_hcontainer/atom_pair.cpp
28+
${ABACUS_SOURCE_DIR}/source_basis/module_ao/parallel_orbitals.cpp
2429
)

source/source_cell/module_symmetry/test/symm_rho_soc_test.cpp

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "../symmetry.h"
77
#include "../symm_rot_spin.h"
88
#include "source_cell/unitcell.h"
9+
#include "source_estate/module_dm/density_matrix.h" // real func_xyz_to_updown
910

1011
/************************************************
1112
* unit test of Symmetry::rhog_symmetry_nspin4
@@ -187,35 +188,37 @@ TEST(RhogSymmetrySoc, GroupInvariance)
187188
}
188189

189190
// ---------------------------------------------------------------------------
190-
// Coupling test (nonzero m_y): the spin-density rotation W used for the grid
191-
// symmetrization (spin_so3) MUST agree with the SU(2) rotation of a spinor density
192-
// block followed by the standard sigma_y=[[0,-i],[i,0]] Pauli decomposition:
193-
// rho_0 = Re(uu+dd), rho_x = Re(ud+du),
194-
// rho_y = -Im(ud) + Im(du), rho_z = Re(uu-dd).
195-
// NOTE: this uses the PHYSICAL spin-density block (D_ud = mx - i*my), so the textbook
196-
// -Im(ud)+Im(du) is correct here. This is NOT the frame the runtime uses: the actual
197-
// stored DM is conj-first (DM=conj(P), cal_dm_psi), so func_xyz_to_updown / psymmg_soc
198-
// consume conj(P) and use the BARE +Im(ud)-Im(du) (see #7832 / the DM round-trip test in
199-
// source_estate/module_dm/test). This test is a spin_so3 sanity check in the physical
200-
// frame; it does NOT validate the DM-path sign and must not be read as pinning the #7664
201-
// convention. TODO: exercise the real func_xyz_to_updown/psymmg_soc instead of this local
202-
// re-implementation so the two conventions cannot drift apart silently.
191+
// Coupling test (nonzero m_y): the spin-density rotation W=spin_so3 used by psymmg_soc for the
192+
// grid symmetrization MUST agree with the SU(2) rotation of the physical spinor state followed by
193+
// the REAL func_xyz_to_updown extraction (which reads the conj-first stored DM, DM=conj(P), and
194+
// uses the bare +Im(ud)-Im(du)). This test now calls the actual func_xyz_to_updown rather than a
195+
// local re-implementation, so the grid-rotation and DM-extraction conventions cannot drift apart
196+
// silently (it fails on the #7664 m_y flip). The self-referential GroupInvariance test above
197+
// cannot catch this because it uses the same wspin as its own oracle.
203198
// ---------------------------------------------------------------------------
204199
namespace
205200
{
206201
using cd = std::complex<double>;
207-
// PHYSICAL spinor block D = r0*I + m.sigma (sigma_y = [[0,-i],[i,0]]); layout {uu,ud,du,dd}
202+
// PHYSICAL spinor block P = r0*I + m.sigma (sigma_y = [[0,-i],[i,0]]); layout {uu,ud,du,dd}
208203
ModuleSymmetry::SpinRotation::Su2 block_from_pauli(double r0, double mx, double my, double mz)
209204
{
210205
return {cd(r0 + mz, 0.0), cd(mx, -my), cd(mx, my), cd(r0 - mz, 0.0)};
211206
}
212-
// textbook Pauli extraction from the PHYSICAL block (distinct from func_xyz_to_updown, which
213-
// reads the conj-first stored DM); factor of 2 vs. m is harmless.
214-
void pauli_from_block(const ModuleSymmetry::SpinRotation::Su2& D, double& rx, double& ry, double& rz)
207+
// The runtime stores the DM conj-first (DM = conj(P), cal_dm_psi); this is what func_xyz_to_updown
208+
// actually consumes. Given a physical block P, the stored block is its element-wise conjugate.
209+
ModuleSymmetry::SpinRotation::Su2 stored_dm_from_phys(const ModuleSymmetry::SpinRotation::Su2& P)
215210
{
216-
rx = (D[1] + D[2]).real(); // Re(ud+du)
217-
ry = -D[1].imag() + D[2].imag(); // -Im(ud)+Im(du)
218-
rz = (D[0] - D[3]).real(); // Re(uu-dd)
211+
return {std::conj(P[0]), std::conj(P[1]), std::conj(P[2]), std::conj(P[3])};
212+
}
213+
// call the REAL func_xyz_to_updown on a 2x2 stored-DM block; return (m_x, m_y, m_z)
214+
ModuleBase::Vector3<double> real_extract(const ModuleSymmetry::SpinRotation::Su2& Dstored)
215+
{
216+
const cd tmp[4] = {Dstored[0], Dstored[1], Dstored[2], Dstored[3]}; // {uu,ud,du,dd}
217+
const int col_size = 2;
218+
const int step_trace[4] = {0, 1, col_size, col_size + 1};
219+
double out[4] = {0.0, 0.0, 0.0, 0.0}; // rho0/x/y/z written at icol=0
220+
elecstate::DensityMatrix_Tools::func_xyz_to_updown<double>(tmp, 0, step_trace, out);
221+
return ModuleBase::Vector3<double>(out[step_trace[1]], out[step_trace[2]], out[step_trace[3]]);
219222
}
220223
} // namespace
221224

@@ -238,19 +241,30 @@ TEST(RhogSymmetrySoc, SpinConventionCoupling)
238241
EXPECT_NEAR(Wgrid.e31, Wpauli.e31, TOL) << "g=" << g; EXPECT_NEAR(Wgrid.e32, Wpauli.e32, TOL) << "g=" << g;
239242
EXPECT_NEAR(Wgrid.e33, Wpauli.e33, TOL) << "g=" << g;
240243

241-
// (2) rotate the spinor block, extract Pauli comps (new convention), compare to Wgrid*m
244+
// (2) End-to-end with the REAL func_xyz_to_updown, exactly the runtime data flow:
245+
// physical block P(m) --conj--> stored DM (conj-first) --func_xyz_to_updown--> grid m.
246+
// Rotate the PHYSICAL block by the spinor SU(2) U (U P U^dagger, i.e. the physical state
247+
// rotation), conj to the stored block, extract again -> m'. psymmg_soc rotates the grid
248+
// components with Wgrid=spin_so3, so we must have m' == Wgrid * m. This catches any
249+
// mismatch (e.g. the #7664 m_y flip) between func_xyz_to_updown and spin_so3.
242250
for (const auto& m : mtest)
243251
{
244-
const ModuleSymmetry::SpinRotation::Su2 D = block_from_pauli(2.0, m[0], m[1], m[2]);
245-
const ModuleSymmetry::SpinRotation::Su2 Dp = ModuleSymmetry::SpinRotation::rotate_spin_block(D, U);
246-
double rx, ry, rz;
247-
pauli_from_block(Dp, rx, ry, rz);
248-
// Wgrid acts on the physical m; the block carries 2*m, so compare against 2*(Wgrid*m).
249-
const ModuleBase::Vector3<double> mv(m[0], m[1], m[2]);
250-
const ModuleBase::Vector3<double> mrot = Wgrid * mv;
251-
EXPECT_NEAR(rx, 2.0 * mrot.x, TOL) << "g=" << g;
252-
EXPECT_NEAR(ry, 2.0 * mrot.y, TOL) << "g=" << g << " (y-channel handedness)";
253-
EXPECT_NEAR(rz, 2.0 * mrot.z, TOL) << "g=" << g;
252+
const ModuleSymmetry::SpinRotation::Su2 P = block_from_pauli(2.0, m[0], m[1], m[2]);
253+
const ModuleSymmetry::SpinRotation::Su2 Pp = ModuleSymmetry::SpinRotation::rotate_spin_block(P, U);
254+
255+
const ModuleBase::Vector3<double> mF = real_extract(stored_dm_from_phys(P));
256+
const ModuleBase::Vector3<double> mFp = real_extract(stored_dm_from_phys(Pp));
257+
258+
// (2a) extraction recovers the physical magnetization (block carries 2*m)
259+
EXPECT_NEAR(mF.x, 2.0 * m[0], TOL) << "g=" << g;
260+
EXPECT_NEAR(mF.y, 2.0 * m[1], TOL) << "g=" << g << " (m_y extraction)";
261+
EXPECT_NEAR(mF.z, 2.0 * m[2], TOL) << "g=" << g;
262+
263+
// (2b) grid rotation spin_so3 agrees with the SU(2) block rotation + real extraction
264+
const ModuleBase::Vector3<double> mrot = Wgrid * mF;
265+
EXPECT_NEAR(mFp.x, mrot.x, TOL) << "g=" << g;
266+
EXPECT_NEAR(mFp.y, mrot.y, TOL) << "g=" << g << " (y-channel handedness)";
267+
EXPECT_NEAR(mFp.z, mrot.z, TOL) << "g=" << g;
254268
}
255269
}
256270
}

0 commit comments

Comments
 (0)