Skip to content

Implement UKS nuclear gradients for Laplacian-dependent meta-GGAs - #229

Open
susilehtola wants to merge 2 commits into
wavefunction91:masterfrom
susilehtola:uks-laplacian-mgga-exc-grad
Open

susilehtola wants to merge 2 commits into
wavefunction91:masterfrom
susilehtola:uks-laplacian-mgga-exc-grad

Conversation

@susilehtola

Copy link
Copy Markdown
Contributor

Builds on #228 (the Laplacian gradient fix); review that first, or this diff will
look like it contains both.

What

eval_exc_grad refused the spin-polarized case outright:

if(needs_laplacian and is_uks) {
  GAUXC_GENERIC_EXCEPTION("UKS Gradients + Laplacian Dependent MGGAs is Not Yet Implemented");
}

Everything below the assembly was already in place — eval_uvvar_mgga_uks
computes lapl for both spins (lapls, laplz, split into lapl[2i] /
lapl[2i+1]), and lapl/vlapl are allocated per spin — so only the gradient
assembly and the guard were missing.

How

The spin transformation mirrors the LDA, GGA and tau blocks around it:
rho_alpha = (rho_s + rho_z)/2, hence
0.5 (v+ + v-) N + 0.5 (v+ - v-) Z, with fac P lapl(B) built for both the
scalar and the spin density (xLmat, xZLmat; the extra blocks already fit in
host_data.zmat, which is sized 5 * spin_dim_scal when needs_laplacian).

One trap worth flagging for review: the tau branch immediately above
reassigns d2_term_* to the Z hessian term when polarized, and the
laplacian block consumed it directly. Reusing it would have paired the Z hessian
term with the N laplacian contribution — a wrong-index bug of exactly the kind
#228 fixes, sitting behind the exception. This block rebuilds hN_* and hZ_*
explicitly instead.

Verification

eval_exc_grad against a central difference of eval_exc over a displaced
molecule and displaced basis, so the grid and its partition weights ride on
the atoms and the comparison shares no algebra with the gradient code.
Cytosine (doublet) / cc-pVDZ / UltraFine, MGGA_X_R2SCANL+MGGA_C_R2SCANL,
atom 0, h = 1e-4:

d/dx   analytic -1.5224974586e-01   FD -1.5224974458e-01   rel 8.4e-09
d/dy   analytic  3.3558979268e-01   FD  3.3558978846e-01   rel 1.3e-08
d/dz   analytic -7.27e-16           FD -1.07e-10           (both zero; planar)

RKS is unchanged and still verifies at 1.6e-09 (BR89) / 9.4e-08 (r2SCANL).

gauxc_test: all 3640979 assertions in 56 test cases pass.

Not covered

The device path keeps its own, separate restriction
("Device EXC Gradients + Laplacian Dependent MGGAs Not Yet Implemented"),
which this does not touch.

🤖 Generated with Claude Code

susilehtola and others added 2 commits September 15, 2026 12:18
lapl(rho) = 2 X . lapl(B) + 2 sum_c X_c . d_c B, with X = fac P B and
X_c = fac P d_c B. One term of its nuclear derivative contracts the BARE
d_x B against 2 P lapl(B); the code contracted (2 P d_x B) against the
bare lapl(B) instead.

The two are equal only after summing over every basis function, so the
total gradient was right and translational invariance held -- but the sum
in exc_grad_local_work_ is restricted to the shells on one atom, so the
force on each individual atom was wrong. Measured against a central
difference of eval_exc over a displaced molecule and displaced basis
(benzene/cc-pVDZ/UltraFine, atom 0, worst of the three components):

                     before      after
  MGGA_X_BR89       1.595e-01  1.604e-09
  MGGA_X_R2SCANL    1.383e-01  9.365e-08
  MGGA_X_SCANL      1.608e-01       --

Three independent Laplacian functionals fail identically beforehand,
including BR89, which has none of SCAN's numerical difficulty; the sign
of the error also differs between them, which no fixed numerical defect
would do. LDA (5.1e-09), GGA (2.1e-09) and tau-only meta-GGA (4.3e-09)
are unaffected and unchanged, as are energies and potentials at every
rung.

The matrix contracted with the Laplacian is not otherwise formed, so it
is built with one extra eval_xmat and one extra block of host_data.zmat,
allocated only when needs_laplacian.

RKS only: exc_grad already refuses needs_laplacian with is_uks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
exc_grad refused the spin-polarized case outright:

  "UKS Gradients + Laplacian Dependent MGGAs is Not Yet Implemented"

Everything below the assembly was already in place -- eval_uvvar_mgga_uks
computes lapl for both spins, and lapl/vlapl are allocated per spin -- so
only the gradient assembly and the guard were missing.

The spin transformation mirrors the LDA, GGA and tau blocks around it:
rho_alpha = (rho_s + rho_z)/2, hence the 0.5 (v+ + v-) N + 0.5 (v+ - v-) Z
combination, with fac P lapl(B) built for both the scalar and the spin
density.

One trap: the tau branch immediately above reassigns d2_term_* to the Z
hessian term when polarized, and the laplacian block consumed it. Reusing
it here would have paired the Z hessian term with the N laplacian
contribution. This block now rebuilds hN_* and hZ_* explicitly instead.

Verified against a central difference of eval_exc over a displaced
molecule and displaced basis, cytosine (doublet) / cc-pVDZ / UltraFine,
MGGA_X_R2SCANL+MGGA_C_R2SCANL, atom 0:

  d/dx  analytic -1.5224974586e-01  FD -1.5224974458e-01  rel 8.4e-09
  d/dy  analytic  3.3558979268e-01  FD  3.3558978846e-01  rel 1.3e-08
  d/dz  both zero to 1e-10 (planar)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant