Effective core potentials (ECPs)

vibe-qc ships libecpint 1.0.7 vendored as a runtime dependency. ECP integrals, reduced-Z nuclear-attraction, and valence-electron-count accounting are wired through every molecular SCF driver (RHF, UHF, RKS, UKS). Heavy-element chemistry — including the d-block, the lanthanides, and the actinides — is reachable without constructing the all-electron basis.

The banner lists libecpint as a linked dependency:

linked: libint 2.13.1 · libxc 7.0.0 · spglib 2.7.0 · libecpint 1.0.7

What ships

Six ECP libraries bundled inside libecpint, MIT-licensed (see docs/license.md):

Library

Family

Atomic-number coverage

Citation family

ecp10mdf

Stuttgart-Köln MDF, 10-electron core

post-K (rows 4+)

Andrae, Häußermann, Dolg, Stoll, Preuß, Theor. Chim. Acta 77, 123 (1990) and follow-ups

ecp28mdf

Stuttgart-Köln MDF, 28-electron core

post-Cd

(ditto, per-element refs)

ecp46mdf

Stuttgart-Köln MDF, 46-electron core

post-Hg

(ditto)

ecp60mdf

Stuttgart-Köln MDF, 60-electron core

f-block (post-Yb)

(ditto)

ecp78mdf

Stuttgart-Köln MDF, 78-electron core

actinides

(ditto)

lanl2dz

Hay-Wadt LANL

post-Na (rows 3+)

Hay, Wadt, J. Chem. Phys. 82, 270 + 299 + 284 (1985)

The accompanying valence-only orbital basis sets (e.g. lanl2dz orbital basis, def2-svp for Stuttgart-MDF cores) are available in vibe-qc’s standard basis library.

The two recipes

There are two ways to wire an ECP into an SCF run:

  1. Manual ECPCenter recipe (always-available, all versions). You explicitly construct an ECPCenter per heavy atom and assign the right ECP library name. Verbose but transparent.

  2. vq.auto_ecp_centers(...) helper (basissetdev-conditional; see § Phase-14e auto-helper below). One-liner replacement: parses the bundled .ecp sidecar, picks the right library, and returns (ecp_centers, library_name) ready to drop onto any SCF Options.

If you’re writing tutorials or sample scripts and the auto-helper is available, use it. If you’re writing custom production scripts and want full control over the ECP metadata, the manual recipe is the right surface.

Manual recipe (always available)

import vibeqc as vq

# Pt atom at the origin.
mol = vq.Molecule([vq.Atom(78, [0.0, 0.0, 0.0])])
basis = vq.BasisSet(mol, "lanl2dz")    # valence-only orbital basis

# One ECPCenter per heavy atom.
ec = vq.ECPCenter()
ec.Z = 78                              # atomic number of the centre
ec.xyz = [0.0, 0.0, 0.0]               # bohr (Cartesian)

# Wire it onto the SCF options.
opts = vq.UHFOptions()
opts.ecp_centers = [ec]
opts.ecp_library = "lanl2dz"           # which ECP XML library
opts.multiplicity = 3                  # Pt ground state ⁵D₀

result = vq.run_uhf(mol, basis, opts)
print(result.energy_ha)                # −118.227 Ha (22 BFs, 125 SCF iters)

The same ecp_centers + ecp_library flags are accepted by run_rhf, run_rks, and run_uks. Multiple heavy atoms → build one ECPCenter per atom and pass them all in:

# Pt-Pt dimer, 10-bohr separation.
mol = vq.Molecule([
    vq.Atom(78, [0.0, 0.0, -5.0]),
    vq.Atom(78, [0.0, 0.0, +5.0]),
])
basis = vq.BasisSet(mol, "lanl2dz")

opts = vq.UHFOptions()
opts.ecp_centers = [
    vq.ECPCenter(Z=78, xyz=[0.0, 0.0, -5.0]),
    vq.ECPCenter(Z=78, xyz=[0.0, 0.0, +5.0]),
]
opts.ecp_library = "lanl2dz"
opts.multiplicity = 1                  # paired

Phase-14e auto-helper (vq.auto_ecp_centers)

The Phase 14e helper short-circuits the boilerplate above. Given a molecule and a basis name, it parses the bundled .ecp sidecar, picks the matching libecpint XML library, and returns ready-to-drop (ecp_centers, library_name):

import vibeqc as vq

mol = vq.Molecule([
    vq.Atom(78, [0.0, 0.0, -5.0]),
    vq.Atom(78, [0.0, 0.0, +5.0]),
])
basis = vq.BasisSet(mol, "lanl2dz")

opts = vq.UHFOptions()
opts.ecp_centers, opts.ecp_library = vq.auto_ecp_centers(mol, "lanl2dz")
opts.multiplicity = 1

result = vq.run_uhf(mol, basis, opts)
# Same −118.227 Ha as the manual recipe, but no per-atom ECPCenter
# wiring needed. Tutorial / sample-script-friendly.

The helper is the recommended path for tutorials and example scripts. Live numerics match the manual recipe: Pt/lanl2dz UHF = −118.227 Ha (singlet) at 22 BFs in 125 iters; Si/lanl2dz RHF = −3.475 Ha.

Important

Status note (2026-05-10). The vq.auto_ecp_centers helper ships from the basissetdev long-lived branch. Per the user’s standing rule, basissetdev does NOT merge into v0.8.0 — it stays a paper-writing branch.

If basissetdev does merge into a future v0.8.x maintenance release, this helper becomes available. Until then, use the manual recipe above. The manual API is stable; the auto-helper is purely a convenience layer on top of it.

The current status of the basissetdev merge decision is tracked in .release-status/v0.8.0/basissetdev.md.

When you need it

ECPs are mandatory when:

  • You’re using a valence-only basis like lanl2dz, lanl2tz, def2-svp for elements ≥ K (Z ≥ 19) where the basis omits the core.

  • You’re using dhf-{svp,sv(p),tzvp,…} or x2c-* relativistic bases — these are valence-only by design.

  • The basis you picked has an .ecp sidecar in python/vibeqc/basis_library/basis/ (or third_party/libint/install/share/libint/2.13.1/basis/).

ECPs are not needed when:

  • You’re using an all-electron basis (6-31g*, def2-tzvp, cc-pvtz, pob-tzvp). The basis carries every core and valence electron explicitly.

  • All your atoms are H–Mg (Z ≤ 12) and your basis covers them all-electron. (Most commonly: any first/second-period organic chemistry.)

If you forget the ECP wiring on a basis that needs one, vibe-qc fails loudly at SCF with "canonical orth dropped too many basis directions" — the symptom of trying to fit valence orbitals to nuclei that still expect core electrons. The fix is to add the ecp_centers + ecp_library flags.

Choosing the right ECP library

For each heavy atom, the right ECP library is determined by the size of the core electron count the orbital basis implies:

Atomic number

Core size

Library

Orbital basis examples

Z = 19–36 (K–Kr)

10

ecp10mdf

def2-svp, def2-tzvp, def2-qzvp for K–Kr

Z = 37–54 (Rb–Xe)

28

ecp28mdf

def2-svp, def2-tzvp, def2-qzvp for Rb–Xe

Z = 55–86 (Cs–Rn)

46

ecp46mdf

def2-svp, def2-tzvp, def2-qzvp for Cs–Rn

Z = 57–71 (lanthanides)

60

ecp60mdf

dhf-tzvp / dhf-qzvp lanthanide variants

Z = 87–88 (Fr/Ra)

78

ecp78mdf

dhf-* actinide variants

Z = 21–86 (alternative)

varies

lanl2dz

lanl2dz, lanl2tz orbital bases

The Stuttgart-Köln MDF family is the def2-* default. The Hay-Wadt LANL family is the LANL2DZ orbital basis default. Mixing-and-matching across families is possible but should be done deliberately: auto_ecp_centers(mol, basis_name) picks the right library when the helper is available; manually, match library to orbital basis as the table above suggests.

Python API surface

import vibeqc as vq

# Version probe
vq.libecpint_version()              # "libecpint 1.0.7 (vendored, MAX_L=5)"
vq.library_versions()["libecpint"]  # same

# Per-atom ECP descriptor (manual path)
ec = vq.ECPCenter()
ec.Z = 78
ec.xyz = [0.0, 0.0, 0.0]

# AO matrix V_ECP_{μν} = ⟨χ_μ | V_ECP | χ_ν⟩  (spherical basis)
V_ecp = vq.compute_ecp_matrix(
    basis,                          # vibeqc.BasisSet
    ecp_centers=[ec],
    library_name="lanl2dz",
)                                   # → numpy.ndarray (n_bf, n_bf)

# Auto-helper (basissetdev-conditional; see § Phase-14e above)
ecp_centers, library_name = vq.auto_ecp_centers(mol, "lanl2dz")

# Drive any SCF with ECPs:
opts = vq.UHFOptions()
opts.ecp_centers = [ec]
opts.ecp_library = "lanl2dz"
result = vq.run_uhf(mol, basis, opts)

The same ecp_centers + ecp_library API works on every molecular SCF driver: run_rhf, run_uhf, run_rks, run_uks. Periodic SCF + ECP is not yet supported; queued for v0.x.x once the periodic-Γ JKBuilder lands a periodic ECP contribution.

Phase-14f: CRYSTAL INPUT ECP-block parser

basissetdev also adds a parser for CRYSTAL INPUT-format ECP blocks, so basis sets distributed in CRYSTAL’s ECP format (e.g. 5th-period pob bases) load without raising:

parsed = vq.basis_crystal.parse_crystal_atom_basis(crystal_input_block)
parsed.ecp                          # CrystalECP dataclass
parsed.ecp.terms                    # list of CrystalECPTerm

Same basissetdev caveat as the auto-helper above: if basissetdev doesn’t merge, this parser is also unavailable. Workaround: convert the CRYSTAL .basis file to BSE-format .g94 + .ecp sidecar via the basissetdev fetch + split scripts, then load via the standard BasisSet(mol, "name") path.

Citations

For published work using ECPs:

  • libecpint software: R. A. Shaw, J. G. Hill, J. Chem. Phys. 147, 074108 (2017); R. A. Shaw, J. Chem. Phys. 159, 014103 (2023).

  • Stuttgart-Köln MDF family: Andrae, Häußermann, Dolg, Stoll, Preuß, Theor. Chim. Acta 77, 123 (1990) and per-element follow-ups in the libecpint repository documentation.

  • Hay-Wadt LANL family: Hay, Wadt, J. Chem. Phys. 82, 270 (1985); 299 (1985); 284 (1985).

The libecpint upstream project documents the per-element citations in its source repository.

See also