Hessians

Analytic nuclear Hessians of an integral are its second derivative with respect to the Cartesian coordinates of two atoms $A$, $B$ (which may be the same atom), e.g.

\[\frac{\partial^2 S_{\mu\nu}}{\partial \mathbf{R}_A \partial \mathbf{R}_B}, \qquad \frac{\partial^2 (\mu\nu|\lambda\sigma)}{\partial \mathbf{R}_A \partial \mathbf{R}_B}\]

returned as two extra trailing length-3 axes ($x,y,z$ for $A$, then $B$). These feed analytic vibrational frequency calculations and second-order response (CPHF/CPKS) machinery.

Units: `R_A`/`R_B` are in bohr, not Angstrom

As with Gradients, a BasisSet stores and accepts nuclear coordinates in Angstrom, but every Hessian here is with respect to bohr displacement of each nucleus. So ∇2overlap(bset, iA, iB) gives ∂²S/∂R_iA∂R_iB per bohr², not per Angstrom². To convert to a per-Angstrom² derivative, divide by Molecules.bohr_to_angstrom^2 (≈0.529177 Å/bohr, squared).

Throughout this page the examples use

julia> using GaussianBasis, Molecules

julia> water = Molecules.parse_string("""
       O        0.000000000000     -0.143225816552      0.000000000000
       H        1.638036840407      1.136548822547     -0.000000000000
       H       -1.638036840407      1.136548822547     -0.000000000000
       """);

julia> bset = BasisSet("sto-3g", water);   # 5 shells, 7 basis functions

Overlap

\[\frac{\partial^2 S_{\mu\nu}}{\partial \mathbf{R}_A \partial \mathbf{R}_B}\]

A full dense Hessian is computed directly from a basis set and two atom indices:

julia> hS = ∇2overlap(bset, 1, 2);   # ∂²S/∂R_O ∂R_H(first)

julia> size(hS)
(7, 7, 3, 3)

An in-place (mutating) version writes into a preallocated array:

julia> out = zeros(bset.nbas, bset.nbas, 3, 3);

julia> ∇2overlap!(out, bset, 1, 2);

julia> out == hS
true

A per-shell-pair option is also available. This is usually what integral-direct and CPHF code wants, since it never materializes the full array:

julia> b = ∇2overlap(bset, 1, 2, 4, 3)   # 4 and 3 are shell indexes
1×3×3×3 Array{Float64, 4}:
[:, :, 1, 1] =
 -0.00951483  -0.0358454  0.0
...

For maximum efficiency, use the mutating shell-pair form with a preallocated output and a caller-owned scratch buffer, which makes the call allocation-free:

julia> Nmax = maximum(num_basis, bset.shells);

julia> scratch = Vector{Float64}(undef, 9*Nmax^2);

julia> out = zeros(num_basis(bset[4]), num_basis(bset[3]), 3, 3);

julia> ∇2overlap!(out, bset, 1, 2, 4, 3; scratch=scratch);

julia> out == b
true
GaussianBasis.∇2overlapMethod
∇2overlap(BS::BasisSet, iA, iB) -> Array{Float64,4}

Second derivative (Hessian) of the AO overlap matrix S w.r.t. atoms iA,iB's three Cartesian coordinates each, ∂²S/∂R_iA∂R_iB, with R_iA/R_iB in bohr (see Hessians for units). Returns a dense nbas × nbas × 3 × 3 array. For repeated calls, see ∇2overlap!.

source
GaussianBasis.∇2overlap!Method
∇2overlap!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)
∇2kinetic!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)
∇2ERI_2e2c!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)

Shell-pair Hessian block: ∂²X_ij/∂R_iA∂R_iB for shells i,j of BS (shell indices, not AO indices). out must be (Ni,Nj,3,3) and is overwritten, so a reused buffer is safe.

Returns the free zero – no libcint call – whenever neither shell sits on iA, or neither sits on iB: the block cannot depend on both coordinates then.

Pass scratch (a vector of at least 9*Ni*Nj elements, or 9*Nmax^2 to cover any pair) to make the call allocation-free in a loop.

See ∇2overlap for the dense whole-matrix form, and ∇2overlap_μμ!/∇2overlap_μν! for the bare primitives underneath.

source

Kinetic Energy

\[\frac{\partial^2 T_{\mu\nu}}{\partial \mathbf{R}_A \partial \mathbf{R}_B}\]

Identical structure to overlap – T depends on the same two shell centers and nothing else, so the same four levels are available:

julia> hT = ∇2kinetic(bset, 1, 1);   # diagonal block, A = B = O

julia> hT[1, 1, :, :]
3×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

That block is zero because shell 1 sits on atom 1, and translating a shell together with its own atom leaves $T$ unchanged.

GaussianBasis.∇2kineticMethod
∇2kinetic(BS::BasisSet, iA, iB) -> Array{Float64,4}

Second derivative (Hessian) of the AO kinetic energy matrix T w.r.t. atoms iA,iB's three Cartesian coordinates each, ∂²T/∂R_iA∂R_iB, with R_iA/R_iB in bohr (see Hessians for units). Returns a dense nbas × nbas × 3 × 3 array. For repeated calls, see ∇2kinetic!.

source
GaussianBasis.∇2kinetic!Method
∇2overlap!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)
∇2kinetic!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)
∇2ERI_2e2c!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)

Shell-pair Hessian block: ∂²X_ij/∂R_iA∂R_iB for shells i,j of BS (shell indices, not AO indices). out must be (Ni,Nj,3,3) and is overwritten, so a reused buffer is safe.

Returns the free zero – no libcint call – whenever neither shell sits on iA, or neither sits on iB: the block cannot depend on both coordinates then.

Pass scratch (a vector of at least 9*Ni*Nj elements, or 9*Nmax^2 to cover any pair) to make the call allocation-free in a loop.

See ∇2overlap for the dense whole-matrix form, and ∇2overlap_μμ!/∇2overlap_μν! for the bare primitives underneath.

source

Nuclear-electron Attraction

\[\frac{\partial^2 V_{\mu\nu}}{\partial \mathbf{R}_A \partial \mathbf{R}_B}\]

Nuclear attraction differs from overlap and kinetic in an important way: it depends on three kinds of position – the two shell centers and the position of every nucleus supplying the potential. A derivative with respect to atom $A$ can therefore land on a shell center or on $A$'s own nuclear charge, so the second derivative splits into

  1. both derivatives on shell centers, summed over all nuclei, and
  2. at least one derivative on a moving nuclear charge, isolated one nucleus at a time via a repositionable point-charge operator.

∇2nuclear assembles both transparently:

julia> hV = ∇2nuclear(bset, 1, 1);

julia> hV[1, 1, :, :]
3×3 Matrix{Float64}:
 -0.0284715   0.0         0.0
   0.0       -0.00452429  0.0
   0.0        0.0         0.0329958

Unlike overlap and kinetic, this block is not zero even though both shells sit on atom 1 – the potential's own center moves with the atom.

GaussianBasis.∇2nuclearMethod
∇2nuclear(BS::BasisSet, iA, iB) -> Array{Float64,4}

Second derivative (Hessian) of the AO nuclear attraction matrix V w.r.t. atoms iA,iB's three Cartesian coordinates each, ∂²V/∂R_iA∂R_iB (accounting for both shell-center and nuclear-charge-position derivatives), with R_iA/R_iB in bohr (see Hessians for units). Returns a dense nbas × nbas × 3 × 3 array. For repeated calls, see ∇2nuclear!.

source
GaussianBasis.∇2nuclear!Function
∇2nuclear!(out, BS::BasisSet, iA, iB)

Mutating counterpart of ∇2nuclear: writes the dense nbas × nbas × 3 × 3 Hessian into out instead of allocating it. out is zeroed first, so a reused buffer is safe.

Assembles both contributions – the piece with both derivatives on shell centers (summed over every nucleus) and the piece with at least one derivative on a moving nuclear charge (isolated one nucleus at a time through a repositionable point-charge operator, see ∇2nuclear_rinv_μμ!).

source

Two-Electron Four Centers

\[\frac{\partial^2 (\mu\nu|\lambda\sigma)}{\partial \mathbf{R}_A \partial \mathbf{R}_B}\]

There is deliberately no dense form here: the full tensor would be $nbas^4 \times 3 \times 3$, which is not worth materializing for any system where a Hessian is interesting. The shell-quartet form is the entry point:

julia> q = ∇2ERI_2e4c(bset, 1, 2, 3, 3, 1, 1);   # atoms 1,2; shells 3,3,1,1

julia> size(q)
(3, 3, 1, 1, 3, 3)

For a hot loop, use the mutating form with caller-owned scratch. The Xflag/Yflag form skips the atom-membership lookup for callers that have already screened:

julia> using GaussianBasis: on_atom_flags

julia> X = on_atom_flags(bset, 1, 3, 3, 1, 1);   # which shells sit on atom 1

julia> Y = on_atom_flags(bset, 2, 3, 3, 1, 1);   # ... and on atom 2

julia> buf = Vector{Float64}(undef, 9*Nmax^4);

julia> out = zeros(size(q));

julia> ∇2ERI_2e4c!(out, bset, X, Y, 3, 3, 1, 1, buf);

julia> out == q
true
GaussianBasis.∇2ERI_2e4cMethod
∇2ERI_2e4c(BS::BasisSet, iA::Int, iB::Int, i::Int, j::Int, k::Int, l::Int)
∇2ERI_2e4c(BS::BasisSet, Xflag::NTuple{4,Bool}, Yflag::NTuple{4,Bool}, i::Int, j::Int, k::Int, l::Int)

Shell-quartet-level dense 4-center ERI Hessian: ∂²(ij|kl)/∂R_iA∂R_iB for shells i,j,k,l w.r.t. atoms iA,iB's three Cartesian directions each, with R_iA/R_iB in bohr (see Hessians for units), as an (Ni,Nj,Nk,Nl,3,3) block. No Schwarz screening either way – callers wanting that should screen before calling.

Two forms, same relationship as ∇ERI_2e4c's: the iA::Int,iB::Int form computes Xflag/Yflag (which of i,j,k,l sit on iA/iB) and returns the free zero (no libcint call) whenever no shell touches iA, no shell touches iB, or iA==iB with all four shells on that one atom (translational invariance). The Xflag,Yflag::NTuple{4,Bool} form is the unchecked core: no membership computation, no zero-skip – for callers that have already screened at a higher level and precomputed the flags once outside a hot loop.

source
GaussianBasis.∇2ERI_2e4c!Method
∇2ERI_2e4c!(out, BS::BasisSet, Xflag, Yflag, i, j, k, l, buf)

Scratch-buffer-accepting core: buf (sized >= 9*Nmax^4, Nmax = the largest num_basis over any shell the caller will ever pass) is caller-owned and reused across every call instead of allocated fresh – zero-allocation, for callers in a hot per-quartet loop. Not thread-safe to share: each concurrent caller (e.g. each worker task) needs its own buf.

source

Two-Electron Three Centers

\[\frac{\partial^2 (\mu\nu|P)}{\partial \mathbf{R}_A \partial \mathbf{R}_B}\]

Here $\mu,\nu$ run over the regular basis and $P$ over an auxiliary/fitting basis (density fitting). A dense form exists but is usually the wrong shape – prefer the shell-triple form:

julia> aux = BasisSet("cc-pvqz-jkfit", water);

julia> t = ∇2ERI_2e3c(bset, aux, 1, 2, 3, 3, 1);   # atoms 1,2; shells 3,3 and aux shell 1

julia> size(t)
(3, 3, 1, 3, 3)

In a loop, hoist both the scratch buffer and the merged basis (which depends only on the two basis sets, never on the atoms or shells):

julia> using GaussianBasis: merge_basis

julia> Bm = merge_basis(bset, aux);

julia> scratch = Vector{Float64}(undef, 9*Nmax^2*maximum(num_basis, aux.shells));

julia> out = zeros(size(t));

julia> ∇2ERI_2e3c!(out, bset, aux, 1, 2, 3, 3, 1; scratch=scratch, Bmerged=Bm);

julia> out == t
true
GaussianBasis.∇2ERI_2e3cMethod
∇2ERI_2e3c(BS1::BasisSet, BS2::BasisSet, iA, iB)

Second derivative (Hessian, atoms iA,iB, with R_iA/R_iB in bohr – see Hessians for units) of the 3-center two-electron integral (μν|P) (BS1=regular basis, BS2=auxiliary/fitting basis – density fitting). Output (BS1.nbas,BS1.nbas,BS2.nbas,3,3). See this file's header comment for the shell-position combinatorics and kernel mapping.

source
GaussianBasis.∇2ERI_2e3c!Method
∇2ERI_2e3c!(out, BS1::BasisSet, BS2::BasisSet, iA, iB, i::Int, j::Int, k::Int;
            scratch=nothing, Bmerged=nothing)
∇2ERI_2e3c(BS1::BasisSet, BS2::BasisSet, iA, iB, i::Int, j::Int, k::Int)

Shell-triple Hessian block of (μν|P): ∂²(ij|k)/∂R_iA∂R_iB for regular shells i,j of BS1 and auxiliary shell k of BS2 (shell indices, not AO indices). out must be (Ni,Nj,Nk,3,3) and is overwritten, so a reused buffer is safe.

This is the level integral-direct and CPHF code wants: the dense form is a BS1.nbas² × BS2.nbas × 3 × 3 tensor, which is rarely worth materializing.

Returns the free zero – no libcint call – unless at least one of the three shells sits on iA and at least one sits on iB.

Pass scratch (at least 9*Ni*Nj*Nk elements) and Bmerged (from the two bases; it depends only on them, never on the atoms or shells) to make the call allocation-free in a loop.

source

Two-Electron Two Centers

\[\frac{\partial^2 (P|Q)}{\partial \mathbf{R}_A \partial \mathbf{R}_B}\]

The density-fitting Coulomb metric. It depends on exactly two shell centers, so it shares the overlap/kinetic structure – only the basis set (auxiliary) and the kernel family differ:

julia> hJ = ∇2ERI_2e2c(aux, 1, 2);

julia> size(hJ)
(208, 208, 3, 3)
GaussianBasis.∇2ERI_2e2cMethod
∇2ERI_2e2c(auxbset::BasisSet, iA, iB)

Second derivative (Hessian, atoms iA,iB, with R_iA/R_iB in bohr – see Hessians for units) of the 2-center two-electron auxiliary metric (P|Q) (density fitting's J_PQ). Output (naux,naux,3,3). Reuses ∇21e!'s same-shell/cross-shell structure via the "metric" compute-case – see this file's header comment.

source
GaussianBasis.∇2ERI_2e2c!Method
∇2overlap!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)
∇2kinetic!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)
∇2ERI_2e2c!(out, BS::BasisSet{LCint}, iA, iB, i::Int, j::Int; scratch=nothing)

Shell-pair Hessian block: ∂²X_ij/∂R_iA∂R_iB for shells i,j of BS (shell indices, not AO indices). out must be (Ni,Nj,3,3) and is overwritten, so a reused buffer is safe.

Returns the free zero – no libcint call – whenever neither shell sits on iA, or neither sits on iB: the block cannot depend on both coordinates then.

Pass scratch (a vector of at least 9*Ni*Nj elements, or 9*Nmax^2 to cover any pair) to make the call allocation-free in a loop.

See ∇2overlap for the dense whole-matrix form, and ∇2overlap_μμ!/∇2overlap_μν! for the bare primitives underneath.

source

Choosing a level

Every Hessian here is available at the same levels as the Gradients, from most convenient to fastest. All of them go through the same libcint primitives, so they agree to the last bit – they differ only in how much bookkeeping is done for you.

levelexamplenotes
dense, allocating∇2overlap(bs, A, B)fresh nbas × nbas × 3 × 3 array; not offered for the 4-center ERI
dense, preallocated∇2overlap!(out, bs, A, B)reuses out; zeroes it for you
shell pair/triple/quartet∇2overlap!(out, bs, A, B, i, j)validates sizes, resolves shell membership, returns the free zero when the block vanishes
bare primitive∇2overlap_μμ!(out, bs, i, j)one libcint call, no checks at all

Two things are worth knowing before dropping a level.

A Hessian needs two primitives per integral, not one. Where the gradients express every case as the single _μ! primitive with permuted arguments, a second derivative can put both derivatives on the same center (_μμ!, libcint's ipip family) or one on each (_μν!, the ipXip family). These are genuinely different kernels; neither is an argument permutation of the other. The 3- and 4-center ERIs need more still, because their centers fall into groups – see the tables in their primitive docstrings.

Hoistable state should be hoisted. Passing it in makes the call allocation-free:

routinekeywordbuild it with
∇2overlap!, ∇2kinetic!, ∇2ERI_2e2c! (shell pair)scratchVector{Float64}(undef, 9*Nmax^2)
∇2ERI_2e3c! (shell triple)scratch, Bmergedmerge_basis
∇2ERI_2e4c! (shell quartet)buf (positional)Vector{Float64}(undef, 9*Nmax^4)

Bare libcint primitives

The lowest level. Each does exactly one libcint call – no bounds checking, no zero-block skipping, no output-size validation. Unlike the gradient primitives they apply no sign flip: libcint differentiates with respect to the electron coordinate, each derivative contributes a factor of $-1$, and a second derivative applies two, which cancel.

Derivative-axis order

The cross primitives (_μν!, _μλ!, _μP!) return their two derivative-component axes in (q,p) order, reversed from the naive expectation. For same-center kernels this is invisible, since mixed partials of one point commute, but it is not invisible in general. Each primitive's docstring says which convention it uses.

GaussianBasis.∇2overlap_μμ!Function
∇2overlap_μμ!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2kinetic_μμ!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2nuclear_μμ!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2ERI_2e2c_μμ!(out, BS::BasisSet{LCint}, i::Int, j::Int)

Libcint call placing BOTH derivatives on shell i (the "μ" shell), for shells i,j of BS. out comes back as a raw (Ni,Nj,3,3) block and may be a contiguous view.

∇2nuclear_μμ! covers only the piece of the nuclear-attraction Hessian where both derivatives land on shell centers, summed over every nucleus; the terms where a derivative lands on a nuclear charge position are added separately by ∇2nuclear!.

No bounds checking, no zero-block skipping, no output-size validation.

source
GaussianBasis.∇2overlap_μν!Function
∇2overlap_μν!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2kinetic_μν!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2nuclear_μν!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2ERI_2e2c_μν!(out, BS::BasisSet{LCint}, i::Int, j::Int)

Libcint call placing one derivative on shell i and one on shell j, for shells i,j of BS. out comes back as a raw (Ni,Nj,3,3) block and may be a contiguous view.

Derivative-axis order

The two derivative-component axes come back in (ket, bra) order: out[:,:,q,p] is d²X / d(shell i)_p d(shell j)_q. For overlap and kinetic this is invisible – those depend only on the shell-shell separation, so the cross-Hessian is symmetric under p<->q – but it is not invisible in general (nuclear attraction's shell-only piece has a fixed background charge distribution that breaks the symmetry), so callers must orient it deliberately.

No bounds checking, no zero-block skipping, no output-size validation.

source
GaussianBasis.∇2nuclear_rinv_μμ!Function
∇2nuclear_rinv_μμ!(out, BS::BasisSet{LCint}, env, i::Int, j::Int)
∇2nuclear_rinv_μν!(out, BS::BasisSet{LCint}, env, i::Int, j::Int)

Libcint calls for the 1/|r-C| (single-nucleus) Hessian pieces of the nuclear-attraction integral, for shells i,j of BS. env selects WHICH nucleus: it is a copy of BS.lib.env with the rinv origin overwritten (see rinv_env), the same way ∇nuclear_μ! takes a charge-fudged atm array to select which potential it differentiates.

_μμ! places both derivatives on shell i; _μν! places one on shell i and one on shell j. Both return a raw (Ni,Nj,3,3) block and accept a contiguous view.

Derivative-axis order

_μν! returns its two derivative axes in (ket, bra) order. Unlike the two-center ipXip kernels, a mixed bra/ket derivative here involves three independent points (bra center, ket center, rinv origin), so it has no reason to be symmetric under swapping those axes and the caller must orient it explicitly.

No bounds checking, no zero-block skipping, no output-size validation.

source
GaussianBasis.∇2ERI_2e2c_μμ!Function
∇2overlap_μμ!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2kinetic_μμ!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2nuclear_μμ!(out, BS::BasisSet{LCint}, i::Int, j::Int)
∇2ERI_2e2c_μμ!(out, BS::BasisSet{LCint}, i::Int, j::Int)

Libcint call placing BOTH derivatives on shell i (the "μ" shell), for shells i,j of BS. out comes back as a raw (Ni,Nj,3,3) block and may be a contiguous view.

∇2nuclear_μμ! covers only the piece of the nuclear-attraction Hessian where both derivatives land on shell centers, summed over every nucleus; the terms where a derivative lands on a nuclear charge position are added separately by ∇2nuclear!.

No bounds checking, no zero-block skipping, no output-size validation.

source
GaussianBasis.∇2ERI_2e3c_μμ!Function
∇2ERI_2e3c_μμ!(out, BS::BasisSet{LCint}, i, j, k)
∇2ERI_2e3c_PP!(out, BS::BasisSet{LCint}, i, j, k)
∇2ERI_2e3c_μν!(out, BS::BasisSet{LCint}, i, j, k)
∇2ERI_2e3c_μP!(out, BS::BasisSet{LCint}, i, j, k)

Bare libcint calls for the 3-center (μν|P) Hessian. BS is the merged basis (regular shells then auxiliary), so an auxiliary shell k is addressed as k + BS1.nshells – same convention as ∇ERI_2e3c_μ!.

Four kernels are needed because the three centers fall into two groups (the two bra shells, and the lone ket/auxiliary shell), so a second derivative can land in four distinct ways:

primitivelibcintboth/one derivative on
_μμ!ipip1both on the FIRST shell argument
_PP!ipip2both on the auxiliary shell
_μν!ipvip1one on each of the two bra shells
_μP!ip1ip2one on the first bra shell, one on the auxiliary

ν is reached by passing it first (_μμ!(out, BS, j, i, k) etc.) and transposing the AO axes back.

Derivative-axis order

All four return their two derivative-component axes in (q,p) order, i.e. out[a,b,c,q,p] is ∂²/∂(first)_p ∂(second)_q. Callers must orient this deliberately; ∇2ERI_2e3c! folds the correction into its scatter.

out is a raw (Na,Nb,Nc,3,3) block and may be a contiguous view.

No bounds checking, no zero-block skipping, no output-size validation.

source
GaussianBasis.∇2ERI_2e4c_μμ!Function
∇2ERI_2e4c_μμ!(out, BS::BasisSet{LCint}, i, j, k, l)
∇2ERI_2e4c_μν!(out, BS::BasisSet{LCint}, i, j, k, l)
∇2ERI_2e4c_μλ!(out, BS::BasisSet{LCint}, i, j, k, l)

Bare libcint calls for the 4-center (ij|kl) Hessian. Three kernels cover every placement, because the four centers form two bra/ket pairs and a second derivative can land within one pair or across them:

primitivelibcintplacement
_μμ!ipip1both derivatives on shell-argument position 1
_μν!ipvip1one on position 1, one on position 2 (same pair)
_μλ!ip1ip2one on position 1, one on position 3 (across pairs)

Every other placement is reached by permuting the shell arguments – e.g. both derivatives on the third shell is _μμ!(out, BS, k, l, i, j) – and transposing the AO axes of the result back. ∇2ERI_2e4c! does exactly that, folding each permutation into the index expression of its accumulate.

Derivative-axis order

All three emit their two derivative-component axes in (q,p) order, reversed from the naive expectation. For _μμ! this is invisible (mixed partials of the same point commute), but not for the cross kernels.

out is a raw (Na,Nb,Nc,Nd,3,3) block and may be a contiguous view.

No bounds checking, no zero-block skipping, no output-size validation.

source