Tatopenn's picture
Sync mirror with GitHub main (v8.1.62): subpackage refactor, mitigation promotions, MCP server refactor
2a87ede verified
Raw History Blame Contribute Delete
3.59 kB
"""Overlap integrals between Gaussian shells via the Obara-Saika recursion.
For two 1D Gaussians centered at A, B with exponents a, b, let
p = a+b and P = (aA+bB)/p be the center of their product (Gaussian
product theorem). The overlap S[i,j] = <(x-A)^i exp(-a(x-A)^2) |
(x-B)^j exp(-b(x-B)^2)> obeys two recursions:
Vertical (build up the first index from the base case S[0,0]):
S[i,0] = (P-A) S[i-1,0] + (i-1)/(2p) S[i-2,0]
Horizontal (build up the second index by shifting angular momentum
from center A to center B, exact for any two centers -- this is why
it needs no exponent-dependent term):
S[i,j] = (A-B) S[i,j-1] + S[i+1,j-1]
Both are linear recursions in one index with a 2-term memory, so each
maps directly onto jax.lax.scan: the whole angular-momentum ladder for
a shell pair compiles to one XLA loop instead of a Python for-loop.
"""
import functools
import jax
import jax.numpy as jnp
from dense_evolution.native_hf.gaussians import (
GaussianShell1D,
GaussianShell3D,
product_center,
product_prefactor,
)
def _base_overlap_1d(g1: GaussianShell1D, g2: GaussianShell1D) -> jax.Array:
p = g1.exponent + g2.exponent
mu = (g1.exponent * g2.exponent) / p
K = jnp.exp(-mu * jnp.square(g1.center - g2.center))
return jnp.sqrt(jnp.pi / p) * K
def _vertical_step(p_minus_a, inv_two_p, carry, i):
s_prev, s_prev2 = carry
s_next = p_minus_a * s_prev + (i - 1) * inv_two_p * s_prev2
return (s_next, s_prev), s_next
def _build_first_index(s00, g1: GaussianShell1D, g2: GaussianShell1D, n_extra: int) -> jax.Array:
"""Returns S[i, 0] for i = 0..n_extra."""
if n_extra == 0:
return s00[None]
p = g1.exponent + g2.exponent
P = (g1.exponent * g1.center + g2.exponent * g2.center) / p
step = functools.partial(_vertical_step, P - g1.center, 1.0 / (2.0 * p))
_, rest = jax.lax.scan(step, (s00, jnp.zeros_like(s00)), jnp.arange(1, n_extra + 1))
return jnp.concatenate([s00[None], rest])
def _horizontal_step(a_minus_b, column, _):
shifted = jnp.roll(column, shift=-1)
new_column = a_minus_b * column + shifted
return new_column, new_column
def _build_second_index(first_col: jax.Array, g1: GaussianShell1D, g2: GaussianShell1D) -> jax.Array:
"""first_col holds S[:,0]. Returns S[:, 0..g2.degree] (rows beyond
validity are garbage and get sliced away by the caller)."""
if g2.degree == 0:
return first_col[:, None]
step = functools.partial(_horizontal_step, g1.center - g2.center)
_, rest = jax.lax.scan(step, first_col, jnp.arange(1, g2.degree + 1))
return jnp.concatenate([first_col[:, None], jnp.moveaxis(rest, 0, -1)], axis=-1)
def overlap_1d(g1: GaussianShell1D, g2: GaussianShell1D) -> jax.Array:
"""S[i,j] for 0<=i<=g1.degree, 0<=j<=g2.degree. Shape (g1.degree+1, g2.degree+1)."""
s00 = _base_overlap_1d(g1, g2)
first_col = _build_first_index(s00, g1, g2, g1.degree + g2.degree)
full = _build_second_index(first_col, g1, g2)
return full[: g1.degree + 1, :]
@jax.jit
def overlap_3d(g1: GaussianShell3D, g2: GaussianShell3D) -> jax.Array:
"""S[ix,iy,iz,jx,jy,jz] for the full Cartesian shell pair.
Shape: (L1+1,L1+1,L1+1, L2+1,L2+1,L2+1) where L1, L2 are the shell
degrees (unphysical (i,j,k) combinations with i+j+k > degree are
simply never read by the caller).
"""
axes = [overlap_1d(g1.component(d), g2.component(d)) for d in range(3)]
return jnp.einsum("ad,be,cf->abcdef", *axes)