Skip to content

Commit 4e5b62a

Browse files
committed
fix: correct Pauli-to-spinor conversion in DFT+U and DeltaSpin for nspin=4
Fix three critical bugs in non-collinear (nspin=4) LCAO calculations: 1. DFT+U transfer_vu (dftu_lcao.cpp): Fix sign error in Pauli-to-spinor conversion. The off-diagonal elements had wrong imaginary part sign: - Before: V_{up,down} = 0.5*(V_x + i*V_y) (wrong) - After: V_{up,down} = 0.5*(V_x - i*V_y) (correct, from sigma_y) 2. DFT+U force/stress (dftu_force_stress.hpp): Convert VU from Pauli basis to spinor basis before force calculation. The old code incorrectly mixed Pauli-basis VU with spinor-basis DM. 3. DeltaSpin force/stress (dspin_force_stress.hpp): Convert lambda from Pauli basis to spinor basis. The constraint force F = lambda·dM/dR requires proper Pauli-to-spinor conversion: - lambda_spinor = (lambda_z, lambda_x, lambda_x, -lambda_z) for (uu, ud, du, dd) components. These fixes ensure consistent Pauli-to-spinor conversion across all modules: - H construction (gint_common.cpp): already fixed - DFT+U Hamiltonian: fixed in this commit - DFT+U force/stress: fixed in this commit - DeltaSpin force/stress: fixed in this commit Verified by scf_u_spin4 test (nspin=4 + DFT+U): SCF converges correctly.
1 parent eb94066 commit 4e5b62a

3 files changed

Lines changed: 193 additions & 65 deletions

File tree

source/source_lcao/module_operator_lcao/dftu_force_stress.hpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,25 @@ void DFTU<OperatorLCAO<TK, TR>>::cal_force_stress(const bool cal_force,
148148
this->cal_v_of_u(occ, tlp1, u_value, &VU[0], eu_tmp);
149149
if(this->nspin == 4)
150150
{
151-
for (int i = 0; i < VU.size(); i++)
151+
// For nspin=4, VU from cal_v_of_u is in Pauli basis:
152+
// block 0 = V_0, block 1 = V_x, block 2 = V_y, block 3 = V_z
153+
// The force formula is F = -Tr(VU * dDM/dR).
154+
// Since DM is stored as BaseMatrix<double> (real, spinor basis),
155+
// we need VU in spinor basis as well. The conversion is:
156+
// V_{up,up} = 0.5 * (V_0 + V_z)
157+
// V_{down,down} = 0.5 * (V_0 - V_z)
158+
// V_{up,down} = 0.5 * (V_x - i*V_y) → Re = 0.5*V_x, Im = -0.5*V_y
159+
// V_{down,up} = 0.5 * (V_x + i*V_y) → Re = 0.5*V_x, Im = +0.5*V_y
160+
// For real DM, only the real part of VU contributes to force.
161+
// We convert VU in-place: block 0→V_uu, 1→V_ud(Re), 2→V_du(Re), 3→V_dd
162+
const int m_size2 = tlp1 * tlp1;
163+
std::vector<double> VU_pauli = VU; // save Pauli-basis VU
164+
for (int m = 0; m < m_size2; m++)
152165
{
153-
VU[i] /= 2.0;
166+
VU[m] = 0.5 * (VU_pauli[m] + VU_pauli[m + 3 * m_size2]); // V_uu = 0.5*(V_0+V_z)
167+
VU[m + m_size2] = 0.5 * VU_pauli[m + m_size2]; // Re(V_ud) = 0.5*V_x
168+
VU[m + 2 * m_size2] = 0.5 * VU_pauli[m + m_size2]; // Re(V_du) = 0.5*V_x
169+
VU[m + 3 * m_size2] = 0.5 * (VU_pauli[m] - VU_pauli[m + 3 * m_size2]); // V_dd = 0.5*(V_0-V_z)
154170
}
155171
}
156172

source/source_lcao/module_operator_lcao/dftu_lcao.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,14 @@ void hamilt::DFTU<hamilt::OperatorLCAO<std::complex<double>, std::complex<double
612612
assert(vu.size() == vu_tmp.size());
613613
#endif
614614

615-
// TR == std::complex<double> transfer from double to std::complex<double>
615+
// Pauli-to-spinor conversion for DFT+U potential:
616+
// V = V_0*I + V_x*sigma_x + V_y*sigma_y + V_z*sigma_z
617+
// sigma_y = [[0,-i],[i,0]], so:
618+
// V_{up,up} = 0.5*(V_0 + V_z)
619+
// V_{down,down} = 0.5*(V_0 - V_z)
620+
// V_{up,down} = 0.5*(V_x - i*V_y) <- note: minus sign from sigma_y
621+
// V_{down,up} = 0.5*(V_x + i*V_y) <- note: plus sign from sigma_y
622+
// This is consistent with the convention in gint_common.cpp merge_hr_part_to_hR().
616623
const int m_size = int(sqrt(vu.size()) / 2);
617624
const int m_size2 = m_size * m_size;
618625
vu.resize(vu_tmp.size());
@@ -627,9 +634,8 @@ void hamilt::DFTU<hamilt::OperatorLCAO<std::complex<double>, std::complex<double
627634
index[3] = m2 * m_size + m1 + m_size2 * 3;
628635
vu[index[0]] = 0.5 * (vu_tmp[index[0]] + vu_tmp[index[3]]);
629636
vu[index[3]] = 0.5 * (vu_tmp[index[0]] - vu_tmp[index[3]]);
630-
// vu should be std::complex<double> type, but here we use double type for test
631-
vu[index[1]] = 0.5 * (vu_tmp[index[1]] + std::complex<double>(0.0, 1.0) * vu_tmp[index[2]]);
632-
vu[index[2]] = 0.5 * (vu_tmp[index[1]] - std::complex<double>(0.0, 1.0) * vu_tmp[index[2]]);
637+
vu[index[1]] = 0.5 * (vu_tmp[index[1]] - std::complex<double>(0.0, 1.0) * vu_tmp[index[2]]);
638+
vu[index[2]] = 0.5 * (vu_tmp[index[1]] + std::complex<double>(0.0, 1.0) * vu_tmp[index[2]]);
633639
}
634640
}
635641
}

source/source_lcao/module_operator_lcao/dspin_force_stress.hpp

Lines changed: 165 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -274,43 +274,95 @@ void DeltaSpin<OperatorLCAO<TK, TR>>::cal_force_IJR(const int& iat1,
274274
}
275275
double tmp[3] = {0.0};
276276
// calculate the local matrix
277-
for (int is = 1; is < nspin; is++)
277+
// For nspin=4, the constraint force is F = lambda · dM/dR where:
278+
// Mx = DM_ud + DM_du, My = -i*(DM_ud - DM_du), Mz = DM_uu - DM_dd
279+
// For real DM (dDM_ud = dDM_du), My contribution vanishes, so:
280+
// F = lambda_x * (dDM_ud + dDM_du) + lambda_z * (dDM_uu - dDM_dd)
281+
// We convert lambda from Pauli basis to spinor basis:
282+
// lambda_uu = lambda_z, lambda_dd = -lambda_z
283+
// lambda_ud = lambda_x, lambda_du = lambda_x
284+
if (nspin == 4)
278285
{
279-
const double lambda_tmp = nspin==2?lambda[2]:lambda[is-1];
280-
const double* dm_pointer = dmR_pointer->get_pointer();
281-
for (int iw1l = 0; iw1l < row_indexes.size(); iw1l += npol)
286+
// lambda in spinor basis: (lambda_uu, lambda_ud, lambda_du, lambda_dd)
287+
const double lambda_spinor[4] = {lambda[2], lambda[0], lambda[0], -lambda[2]};
288+
for (int is = 0; is < 4; is++)
282289
{
283-
const std::vector<double>& nlm1 = nlm1_all.find(row_indexes[iw1l])->second;
284-
for (int iw2l = 0; iw2l < col_indexes.size(); iw2l += npol)
290+
const double lambda_tmp = lambda_spinor[is];
291+
if (std::abs(lambda_tmp) < 1e-15) continue;
292+
const double* dm_pointer = dmR_pointer->get_pointer();
293+
for (int iw1l = 0; iw1l < row_indexes.size(); iw1l += npol)
285294
{
286-
const std::vector<double>& nlm2 = nlm2_all.find(col_indexes[iw2l])->second;
295+
const std::vector<double>& nlm1 = nlm1_all.find(row_indexes[iw1l])->second;
296+
for (int iw2l = 0; iw2l < col_indexes.size(); iw2l += npol)
297+
{
298+
const std::vector<double>& nlm2 = nlm2_all.find(col_indexes[iw2l])->second;
287299
#ifdef __DEBUG
288-
assert(nlm1.size() == nlm2.size());
300+
assert(nlm1.size() == nlm2.size());
289301
#endif
290-
const int length = nlm1.size() / 4;
291-
const int lmax = sqrt(length);
292-
int index = 0;
293-
for(int l = 0; l<lmax; l++)
302+
const int length = nlm1.size() / 4;
303+
const int lmax = sqrt(length);
304+
int index = 0;
305+
for(int l = 0; l<lmax; l++)
306+
{
307+
for (int m = 0; m < 2*l+1; m++)
308+
{
309+
index = l*l + m;
310+
tmp[0] = lambda_tmp * nlm1[index + length] * nlm2[index] * dm_pointer[step_trace[is]];
311+
tmp[1] = lambda_tmp * nlm1[index + length * 2] * nlm2[index] * dm_pointer[step_trace[is]];
312+
tmp[2] = lambda_tmp * nlm1[index + length * 3] * nlm2[index] * dm_pointer[step_trace[is]];
313+
force1[0] += tmp[0];
314+
force1[1] += tmp[1];
315+
force1[2] += tmp[2];
316+
force2[0] -= tmp[0];
317+
force2[1] -= tmp[1];
318+
force2[2] -= tmp[2];
319+
}
320+
}
321+
dm_pointer += npol;
322+
}
323+
dm_pointer += (npol - 1) * col_indexes.size();
324+
}
325+
}
326+
}
327+
else
328+
{
329+
// nspin=1 or nspin=2: original logic
330+
for (int is = 1; is < nspin; is++)
331+
{
332+
const double lambda_tmp = nspin==2?lambda[2]:lambda[is-1];
333+
const double* dm_pointer = dmR_pointer->get_pointer();
334+
for (int iw1l = 0; iw1l < row_indexes.size(); iw1l += npol)
335+
{
336+
const std::vector<double>& nlm1 = nlm1_all.find(row_indexes[iw1l])->second;
337+
for (int iw2l = 0; iw2l < col_indexes.size(); iw2l += npol)
294338
{
295-
for (int m = 0; m < 2*l+1; m++)
339+
const std::vector<double>& nlm2 = nlm2_all.find(col_indexes[iw2l])->second;
340+
#ifdef __DEBUG
341+
assert(nlm1.size() == nlm2.size());
342+
#endif
343+
const int length = nlm1.size() / 4;
344+
const int lmax = sqrt(length);
345+
int index = 0;
346+
for(int l = 0; l<lmax; l++)
296347
{
297-
index = l*l + m;
298-
tmp[0] = lambda_tmp * nlm1[index + length] * nlm2[index] * dm_pointer[step_trace[is]];
299-
tmp[1] = lambda_tmp * nlm1[index + length * 2] * nlm2[index] * dm_pointer[step_trace[is]];
300-
tmp[2] = lambda_tmp * nlm1[index + length * 3] * nlm2[index] * dm_pointer[step_trace[is]];
301-
// force1 = - VU * <d phi_{I,R1}/d R1|chi_m> * <chi_m'|phi_{J,R2}>
302-
// force2 = - VU * <phi_{I,R1}|d chi_m/d R0> * <chi_m'|phi_{J,R2>}
303-
force1[0] += tmp[0];
304-
force1[1] += tmp[1];
305-
force1[2] += tmp[2];
306-
force2[0] -= tmp[0];
307-
force2[1] -= tmp[1];
308-
force2[2] -= tmp[2];
348+
for (int m = 0; m < 2*l+1; m++)
349+
{
350+
index = l*l + m;
351+
tmp[0] = lambda_tmp * nlm1[index + length] * nlm2[index] * dm_pointer[step_trace[is]];
352+
tmp[1] = lambda_tmp * nlm1[index + length * 2] * nlm2[index] * dm_pointer[step_trace[is]];
353+
tmp[2] = lambda_tmp * nlm1[index + length * 3] * nlm2[index] * dm_pointer[step_trace[is]];
354+
force1[0] += tmp[0];
355+
force1[1] += tmp[1];
356+
force1[2] += tmp[2];
357+
force2[0] -= tmp[0];
358+
force2[1] -= tmp[1];
359+
force2[2] -= tmp[2];
360+
}
309361
}
362+
dm_pointer += npol;
310363
}
311-
dm_pointer += npol;
364+
dm_pointer += (npol - 1) * col_indexes.size();
312365
}
313-
dm_pointer += (npol - 1) * col_indexes.size();
314366
}
315367
}
316368
}
@@ -345,48 +397,102 @@ void DeltaSpin<OperatorLCAO<TK, TR>>::cal_stress_IJR(const int& iat1,
345397
step_trace[3] = col_indexes.size() + 1;
346398
}
347399
// calculate the local matrix
348-
for (int is = 1; is < nspin; is++)
400+
// For nspin=4, convert lambda from Pauli basis to spinor basis
401+
if (nspin == 4)
349402
{
350-
const double lambda_tmp = nspin==2?lambda[2]:lambda[is-1];
351-
const double* dm_pointer = dmR_pointer->get_pointer();
352-
for (int iw1l = 0; iw1l < row_indexes.size(); iw1l += npol)
403+
const double lambda_spinor[4] = {lambda[2], lambda[0], lambda[0], -lambda[2]};
404+
for (int is = 0; is < 4; is++)
353405
{
354-
const std::vector<double>& nlm1 = nlm1_all.find(row_indexes[iw1l])->second;
355-
for (int iw2l = 0; iw2l < col_indexes.size(); iw2l += npol)
406+
const double lambda_tmp = lambda_spinor[is];
407+
if (std::abs(lambda_tmp) < 1e-15) continue;
408+
const double* dm_pointer = dmR_pointer->get_pointer();
409+
for (int iw1l = 0; iw1l < row_indexes.size(); iw1l += npol)
356410
{
357-
const std::vector<double>& nlm2 = nlm2_all.find(col_indexes[iw2l])->second;
411+
const std::vector<double>& nlm1 = nlm1_all.find(row_indexes[iw1l])->second;
412+
for (int iw2l = 0; iw2l < col_indexes.size(); iw2l += npol)
413+
{
414+
const std::vector<double>& nlm2 = nlm2_all.find(col_indexes[iw2l])->second;
358415
#ifdef __DEBUG
359-
assert(nlm1.size() == nlm2.size());
416+
assert(nlm1.size() == nlm2.size());
360417
#endif
361-
const int length = nlm1.size() / 4;
362-
const int lmax = sqrt(length);
363-
double tmp = lambda_tmp * dm_pointer[step_trace[is]];
364-
int index = 0;
365-
for(int l = 0; l<lmax; l++)
418+
const int length = nlm1.size() / 4;
419+
const int lmax = sqrt(length);
420+
double tmp = lambda_tmp * dm_pointer[step_trace[is]];
421+
int index = 0;
422+
for(int l = 0; l<lmax; l++)
423+
{
424+
for (int m = 0; m < 2*l+1; m++)
425+
{
426+
index = l*l + m;
427+
stress[0]
428+
+= tmp * (nlm1[index + length] * dis1.x * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.x);
429+
stress[1]
430+
+= tmp * (nlm1[index + length] * dis1.y * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.y);
431+
stress[2]
432+
+= tmp * (nlm1[index + length] * dis1.z * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.z);
433+
stress[3] += tmp
434+
* (nlm1[index + length * 2] * dis1.y * nlm2[index]
435+
+ nlm1[index] * nlm2[index + length * 2] * dis2.y);
436+
stress[4] += tmp
437+
* (nlm1[index + length * 2] * dis1.z * nlm2[index]
438+
+ nlm1[index] * nlm2[index + length * 2] * dis2.z);
439+
stress[5] += tmp
440+
* (nlm1[index + length * 3] * dis1.z * nlm2[index]
441+
+ nlm1[index] * nlm2[index + length * 3] * dis2.z);
442+
}
443+
}
444+
dm_pointer += npol;
445+
}
446+
dm_pointer += (npol - 1) * col_indexes.size();
447+
}
448+
}
449+
}
450+
else
451+
{
452+
// nspin=1 or nspin=2: original logic
453+
for (int is = 1; is < nspin; is++)
454+
{
455+
const double lambda_tmp = nspin==2?lambda[2]:lambda[is-1];
456+
const double* dm_pointer = dmR_pointer->get_pointer();
457+
for (int iw1l = 0; iw1l < row_indexes.size(); iw1l += npol)
458+
{
459+
const std::vector<double>& nlm1 = nlm1_all.find(row_indexes[iw1l])->second;
460+
for (int iw2l = 0; iw2l < col_indexes.size(); iw2l += npol)
366461
{
367-
for (int m = 0; m < 2*l+1; m++)
462+
const std::vector<double>& nlm2 = nlm2_all.find(col_indexes[iw2l])->second;
463+
#ifdef __DEBUG
464+
assert(nlm1.size() == nlm2.size());
465+
#endif
466+
const int length = nlm1.size() / 4;
467+
const int lmax = sqrt(length);
468+
double tmp = lambda_tmp * dm_pointer[step_trace[is]];
469+
int index = 0;
470+
for(int l = 0; l<lmax; l++)
368471
{
369-
index = l*l + m;
370-
stress[0]
371-
+= tmp * (nlm1[index + length] * dis1.x * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.x);
372-
stress[1]
373-
+= tmp * (nlm1[index + length] * dis1.y * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.y);
374-
stress[2]
375-
+= tmp * (nlm1[index + length] * dis1.z * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.z);
376-
stress[3] += tmp
377-
* (nlm1[index + length * 2] * dis1.y * nlm2[index]
378-
+ nlm1[index] * nlm2[index + length * 2] * dis2.y);
379-
stress[4] += tmp
380-
* (nlm1[index + length * 2] * dis1.z * nlm2[index]
381-
+ nlm1[index] * nlm2[index + length * 2] * dis2.z);
382-
stress[5] += tmp
383-
* (nlm1[index + length * 3] * dis1.z * nlm2[index]
384-
+ nlm1[index] * nlm2[index + length * 3] * dis2.z);
472+
for (int m = 0; m < 2*l+1; m++)
473+
{
474+
index = l*l + m;
475+
stress[0]
476+
+= tmp * (nlm1[index + length] * dis1.x * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.x);
477+
stress[1]
478+
+= tmp * (nlm1[index + length] * dis1.y * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.y);
479+
stress[2]
480+
+= tmp * (nlm1[index + length] * dis1.z * nlm2[index] + nlm1[index] * nlm2[index + length] * dis2.z);
481+
stress[3] += tmp
482+
* (nlm1[index + length * 2] * dis1.y * nlm2[index]
483+
+ nlm1[index] * nlm2[index + length * 2] * dis2.y);
484+
stress[4] += tmp
485+
* (nlm1[index + length * 2] * dis1.z * nlm2[index]
486+
+ nlm1[index] * nlm2[index + length * 2] * dis2.z);
487+
stress[5] += tmp
488+
* (nlm1[index + length * 3] * dis1.z * nlm2[index]
489+
+ nlm1[index] * nlm2[index + length * 3] * dis2.z);
490+
}
385491
}
492+
dm_pointer += npol;
386493
}
387-
dm_pointer += npol;
494+
dm_pointer += (npol - 1) * col_indexes.size();
388495
}
389-
dm_pointer += (npol - 1) * col_indexes.size();
390496
}
391497
}
392498
}

0 commit comments

Comments
 (0)