vibeqc.basis_crystal

CRYSTAL-format basis-set parser and NWChem/.g94 emitter.

Targets the per-element CRYSTAL basis-set files distributed by the Bredow group (Peintinger–Vilela Oliveira–Bredow, pob-*): one file per element, named ZZ_Sym (e.g. 06_C), plain-text CRYSTAL input.

Supported today

All-electron shells (S, SP, P, D, F, G) and explicit INPUT effective core potentials (the 200+Z atoms of the Rb-I and Cs-Po pob-* archives). emit_g94() writes the shells as a .g94 libint loads, emit_ecp_sidecar() the ECPs as the .ecp sidecar every SCF route reads beside it, and emit_crystal() both back as CRYSTAL input.

Not supported

CRYSTAL’s library pseudopotentials (HAYWSC, STUTSC, …) and the spin-orbit INPSOC form: their parameters are not in the text.

CRYSTAL per-element format summary

Line 1::

Z NSHELL

or for ECP-using atoms::

(200 + Z) NSHELL

Each shell starts with::

ITYPE LAT NG CHE SCAL

where

  • ITYPE – 0 = user-defined (the only case the Bredow files use).

  • LAT – shell angular-momentum code:

    0=S, 1=SP, 2=P, 3=D, 4=F, 5=G.

  • NG – number of primitive Gaussians.

  • CHE – formal occupancy (electrons) assigned to this shell.

  • SCAL – scale factor s on the exponents: CRYSTAL uses

    exponent * s**2 (the Pople zeta-scaling convention), and for this general-basis type both 1.0 and 0.0 mean as-written. See crystal_exponent_factor().

Followed by NG lines of exponent coefficient (or exponent s_coef p_coef for SP shells).

Functions

build_periodic_ecp_data(system, atoms)

Build ECP data for a PeriodicSystem from parsed CRYSTAL atom bases.

crystal_ecp_to_libecpint_arrays(ecp)

Flatten a CrystalECP into libecpint primitive arrays.

crystal_exponent_factor(scal)

Factor CRYSTAL multiplies a shell's exponents by for SCAL.

emit_crystal(atoms)

Convert per-element basis specs into CRYSTAL inline-basis format.

emit_ecp_sidecar(atoms[, header_comment])

Write the ECP blocks of CRYSTAL atom records as a .ecp sidecar.

emit_g94(atoms[, header_comment])

Convert a list of per-element basis specs into a single .g94 (Gaussian / NWChem format) string that libint can load.

fetch_bredow_basis_sets([dest_dir, names, ...])

Download the Bredow group all-electron basis sets, parse them, and optionally convert them to .g94 files loadable by libint.

parse_crystal_atom_basis(text[, source])

Parse a single CRYSTAL per-element basis file.

parse_crystal_atom_basis_file(path)

Convenience wrapper: parse a CRYSTAL per-element file from disk.

parse_crystal_inline_basis(text[, source])

Split a multi-element CRYSTAL inline basis block into per-element specs.

parse_crystal_inline_element(text, Z[, source])

The element-Z block of a CRYSTAL inline basis text.

scaled_exponents(shell)

The shell's effective exponents, with its SCAL applied.

Classes

CrystalAtomBasis(Z, has_ecp[, shells, ecp])

Parsed per-atom basis set (one Bredow file).

CrystalECP(znuc, m_local[, m_per_ell, terms])

Parsed CRYSTAL effective-core-potential block.

CrystalECPTerm(ell, alpha, coefficient, n_pow)

One Gaussian term inside a CRYSTAL ECP angular-momentum block.

CrystalShell(shell_type, occupancy, scale_factor)

One contracted Gaussian shell in CRYSTAL format.

LibecpintInlineArrays(exponents, ...)

Flat primitive arrays in the exact shape libecpint::ECPIntegrator:: set_ecp_basis(...) expects for one ECP center.

vibeqc.basis_crystal.crystal_exponent_factor(scal)[source]

Factor CRYSTAL multiplies a shell’s exponents by for SCAL.

CRYSTAL scales a general (ITYB=0) shell’s exponents by SCAL**2, the Pople convention in which SCAL is the Slater zeta and alpha(zeta) = zeta**2 * alpha(1) (Hehre, Stewart & Pople, J. Chem. Phys. 51, 2657 (1969)). Both 1.0 and 0.0 leave the exponents as written; 0.0 selects the standard Pople factor only for the built-in ITYB=1 STO-nG sets (CRYSTAL23 manual, section 2.2.1, note 2).

The manual (section 2.2.1, p. 25) calls SCAL only a “scale factor”, so the square was settled by calculation: CRYSTAL23 1.0.1 RHF on H2 with a shell written as (a, SCAL s) gives the same energy, to every printed digit, as (a*s**2, SCAL 1) and as (a*s**2, SCAL 0), and prints the exponents a*s**2 in its basis-set table; (a*s, SCAL 1) is 0.8 mEh away. tests/test_basis_crystal.py pins those energies.

vibe_basis.io.export carries a copy of this rule, because vibe-basis does not import vibe-qc; keep the two in step.

Parameters:

scal (float)

Return type:

float

class vibeqc.basis_crystal.CrystalShell(shell_type, occupancy, scale_factor, exponents=<factory>, coefficients=<factory>, coefficients_p=<factory>)[source]

Bases: object

One contracted Gaussian shell in CRYSTAL format.

The shell’s effective exponents are exponents times crystal_exponent_factor() of scale_factor, exactly as CRYSTAL reads an ITYB LAT NG CHE SCAL shell. The parser applies that factor on the way in and returns scale_factor=1.0, so a parsed shell’s exponents are already effective. A shell built by hand may carry unscaled exponents and a SCAL; every emitter applies the factor (scaled_exponents()).

Parameters:
shell_type: str
occupancy: float
scale_factor: float
exponents: list[float]
coefficients: list[float]
coefficients_p: list[float]
property n_primitives: int
vibeqc.basis_crystal.scaled_exponents(shell)[source]

The shell’s effective exponents, with its SCAL applied.

Parameters:

shell (CrystalShell)

Return type:

list[float]

class vibeqc.basis_crystal.CrystalECPTerm(ell, alpha, coefficient, n_pow)[source]

Bases: object

One Gaussian term inside a CRYSTAL ECP angular-momentum block.

Encodes the contribution

V_ℓ_term(r) = C . r^n . exp(-a . r^2)

in the standard Stuttgart-Köln / Hay-Wadt convention. ell is the angular momentum (0=s, 1=p, …, 4=g) – or the special label "local" for the M-counted local-potential terms in CRYSTAL’s eq. 3.18.

Parameters:
ell: object
alpha: float
coefficient: float
n_pow: int
class vibeqc.basis_crystal.CrystalECP(znuc, m_local, m_per_ell=(), terms=<factory>)[source]

Bases: object

Parsed CRYSTAL effective-core-potential block.

Built from the INPUT-keyword block that follows a 200+Z atom header in CRYSTAL-format basis files. Carries everything libecpint needs to build the V_ECP operator: the effective nuclear charge (Z_eff = Z - ncore), and the per-ℓ Gaussian expansion of the local + ℓ-projected potentials.

The CRYSTAL ECP convention has up to five ℓ-projected components (ℓ = 0..4) plus a “local” group counted by M in the header. terms collects them all in source order; group them by term.ell to match a specific potential expansion.

Parameters:
znuc: float
m_local: int
m_per_ell: tuple[int, ...] = ()
terms: list[CrystalECPTerm]
property total_terms: int
class vibeqc.basis_crystal.CrystalAtomBasis(Z, has_ecp, shells=<factory>, ecp=None)[source]

Bases: object

Parsed per-atom basis set (one Bredow file).

Parameters:
Z: int
has_ecp: bool
shells: list[CrystalShell]
ecp: CrystalECP | None = None
property element_symbol: str
vibeqc.basis_crystal.parse_crystal_atom_basis(text, source='<string>')[source]

Parse a single CRYSTAL per-element basis file.

source is used only in error messages.

Parameters:
Return type:

CrystalAtomBasis

vibeqc.basis_crystal.parse_crystal_atom_basis_file(path)[source]

Convenience wrapper: parse a CRYSTAL per-element file from disk.

Parameters:

path (str | Path)

Return type:

CrystalAtomBasis

vibeqc.basis_crystal.parse_crystal_inline_basis(text, source='<string>')[source]

Split a multi-element CRYSTAL inline basis block into per-element specs.

The inverse of emit_crystal(). Where parse_crystal_atom_basis() reads one element’s file, this reads the concatenated block that goes into a .d12 deck after ENDGEOM:

12 5                 <- Mg
0 0 8  2.0 0.0
  ...
8 4                  <- O
0 0 8  2.0 0.0
  ...
 99 0                <- terminator

Why it exists: the basis-optimization engines exchange candidate bases as exactly this text (EnergyEngine.crystal_energy’s basis_text). An engine that computes a crystal needs every element’s block, and an engine computing an isolated atom needs the right one. Reading only the first block and relabelling it with the requested Z – which is what the vibe-qc engine used to do – silently gives O the Mg basis, which converges and is wrong.

Splitting is line-structural (the CRYSTAL inline format is one record per line), then each per-element slice is handed to parse_crystal_atom_basis() so all the token-level quirks it handles – the SCAL typo, the exponent-scale convention, the positive-exponent guard – apply identically here.

An ECP INPUT block after a 200+Z header is framed by its own ZNUC M M0 M1 M2 M3 M4 record (M + M0 + ... + M4 term records follow), which is what emit_crystal() writes. CRYSTAL’s library pseudopotentials (HAYWSC, STUTSC, …) and the spin-orbit INPSOC form carry no parameters vibe-qc reads, so they are refused.

Parameters:
  • text (str) – The inline basis block. A trailing 99 0 terminator and/or END are optional and are not returned.

  • source (str) – Used only in error messages.

Returns:

One entry per element, in source order.

Return type:

list[CrystalAtomBasis]

Raises:

ValueError – On a malformed block, a CRYSTAL built-in shell (ITYPE != 0), a pseudopotential other than INPUT, or a duplicated element.

vibeqc.basis_crystal.parse_crystal_inline_element(text, Z, source='<string>')[source]

The element-Z block of a CRYSTAL inline basis text.

Picks by the block’s own atomic number. A reader that takes the first block and stamps Z on it gives O the Mg basis for MgO, which converges and is wrong; two optimizer readers did that until 2026-10.

Raises:
Parameters:
Return type:

CrystalAtomBasis

class vibeqc.basis_crystal.LibecpintInlineArrays(exponents, coefficients, ams, ns, local_ell, max_proj_ell)[source]

Bases: object

Flat primitive arrays in the exact shape libecpint::ECPIntegrator:: set_ecp_basis(...) expects for one ECP center.

Mapping convention (per CRYSTAL23 Sec.”Effective core pseudo-potentials” p. 84, eqs. 3.18 + 3.19, against libecpint’s XML convention where the local channel sits at lval = max_proj_ell + 1):

  • CRYSTAL M (ell="local" term group)

    -> libecpint ams[i] = local_ell = max_proj_ell + 1. This is the U_L channel that acts on all angular momenta (no projector applied), matching the local-potential semantics in CRYSTAL eq. 3.18.

  • CRYSTAL M_ℓ (ell in {0..4} term groups, with non-zero count)

    -> libecpint ams[i] = ℓ. Projected channels; libecpint applies P_ℓ during integration.

  • CRYSTAL NKL (the literal power of r in eqs. 3.18 + 3.19)

    -> libecpint ns[i] = NKL + 2. libecpint takes the Gaussian-format power and subtracts two, so a Stuttgart r^0 term is NKL = 0 here and ns[i] = 2 there.

Both conventions yield the same V_ECP matrix elements when summed against the basis. Verified against ecp10mdf.xml (K, Z=19) shell layout: local at lval=4 with placeholder zero, projectors at lval= 0..3 for s/p/d/f, max projector ell = 3.

Parameters:
exponents: tuple[float, ...]
coefficients: tuple[float, ...]
ams: tuple[int, ...]
ns: tuple[int, ...]
local_ell: int
max_proj_ell: int
property n_primitives: int
vibeqc.basis_crystal.crystal_ecp_to_libecpint_arrays(ecp)[source]

Flatten a CrystalECP into libecpint primitive arrays.

The conversion is bookkeeping – no algebraic rewrite of the ECP. Each CrystalECPTerm becomes one primitive with its (alpha, coefficient) carried over verbatim; the ell label is re-keyed onto libecpint’s per-primitive am convention and the radial power is converted between the two conventions (see LibecpintInlineArrays for both mappings).

Phase 14g pairs this with a C++ compute_ecp_matrix_inline that calls libecpint’s set_ecp_basis(...) (the inline- primitive form) instead of set_ecp_basis_from_library(...) (the XML form used today). That unblocks the 5th-period pob basis sets (Rb-I, Cs-Po) and any future inline-ECP archives (vDZP, mixed-row dhf-*).

Parameters:

ecp (CrystalECP) – Parsed CRYSTAL ECP block from parse_crystal_atom_basis().

Returns:

Bundle ready to feed libecpint’s inline-primitive API.

Return type:

LibecpintInlineArrays

Raises:

ValueError – If a term carries an unrecognised ell label (not "local" and not in 0..4).

vibeqc.basis_crystal.emit_ecp_sidecar(atoms, header_comment='')[source]

Write the ECP blocks of CRYSTAL atom records as a .ecp sidecar.

The layout is the one vibeqc.basis_toolkit.exporter_g94 renders next to every ECP-bearing orbital file and vibeqc.ecp_metadata.parse_inline_ecp_sidecar() reads back:

Ag     0
Ag-ECP     4     28
g potential
  1
2      1.0     0.0
s-g potential
  ...

Channels come straight from crystal_ecp_to_libecpint_arrays(), so the sidecar round-trips to the exact primitive arrays the periodic driver feeds libecpint; the header lmax is that bridge’s local channel and ncore = Z - Z_eff. A record without local terms gets the conventional zero placeholder so the local channel is present. Atoms without an ECP are skipped.

Parameters:
Return type:

str

vibeqc.basis_crystal.build_periodic_ecp_data(system, atoms)[source]

Build ECP data for a PeriodicSystem from parsed CRYSTAL atom bases.

Parameters:
Return type:

tuple

vibeqc.basis_crystal.emit_g94(atoms, header_comment='')[source]

Convert a list of per-element basis specs into a single .g94 (Gaussian / NWChem format) string that libint can load.

Every exponent and coefficient is written with shortest round-trip precision (_exact_number()), so the file carries exactly the doubles of atoms. Each shell’s CRYSTAL SCAL is applied to its exponents (scaled_exponents()); a parsed shell has scale_factor 1.0, so its exponents pass through unchanged.

Parameters:
Return type:

str

vibeqc.basis_crystal.emit_crystal(atoms)[source]

Convert per-element basis specs into CRYSTAL inline-basis format.

Produces text suitable for pasting into a CRYSTAL .d12 input deck after the geometry block (ENDGEOM). The format is:

Z NSHELL
0 LAT NPG OCC SCALE
  exp1  coeff1
  exp2  coeff2
...
 99 0
END

where LAT = 0(S), 1(SP), 2(P), 3(D), 4(F), 5(G). For SP shells each primitive line carries both s and p coefficients. Every number is written with shortest round-trip precision (_exact_number()), so CRYSTAL and parse_crystal_inline_basis() read back the doubles of atoms.

ECP-bearing atoms (has_ecp=True) are emitted with 200+Z as the header number, followed by their INPUT pseudopotential block (_crystal_ecp_lines()). All-electron atoms emit the raw Z.

Parameters:

atoms (Sequence[CrystalAtomBasis]) – Sequence of CrystalAtomBasis objects, typically parsed from the Bredow-group per-element CRYSTAL files via parse_crystal_atom_basis() or parse_crystal_atom_basis_file().

Returns:

Multi-line CRYSTAL basis-set block ready for insertion into a .d12 deck.

Return type:

str

Raises:

ValueError – On an atomic number outside 1-98, a shell type CRYSTAL has no LAT code for, a 200+Z atom without its ECP, or an ECP term projecting l > 4.

vibeqc.basis_crystal.fetch_bredow_basis_sets(dest_dir=None, names=None, *, verbose=True, return_atoms=False)[source]

Download the Bredow group all-electron basis sets, parse them, and optionally convert them to .g94 files loadable by libint.

Parameters:
  • dest_dir (str | Path | None) – Directory for the .g94 output files. There is no default destination: with dest_dir=None nothing is written and the call only parses, which is useful only with return_atoms=True (a call that would do neither raises ValueError). The default used to be a directory inside the installed package, so any caller that only wanted the records – the runtime ECP fallback, the ECP tests – rewrote a tracked file of whatever tree it ran in (#393). A maintainer refreshing the committed copy passes that directory explicitly.

  • names (Iterable[str] | None) – Subset of basis-set keys to fetch (from _BREDOW_ARCHIVES). None fetches all supported light-element archives.

  • verbose (bool) – Print progress + citation info.

  • return_atoms (bool)

Returns:

  • dict mapping basis name -> path of the generated .g94 file (empty when

  • dest_dir is None); with return_atoms=True, a tuple of that

  • dict and the parsed records per basis name.

Return type:

dict[str, Path] | tuple[dict[str, Path], dict[str, list[CrystalAtomBasis]]]