Troubleshooting

When a vibe-qc run fails the error message is almost always concrete and actionable, but knowing which knob to turn requires a bit of context. This page collects the errors users actually hit during a first month with the code, with the canonical fix for each. The homepage admonition carries the current known-issues list (the things that are bugs in vibe-qc rather than misconfigurations on your end); this page covers the recoverable situations.

For “I think I found a bug” cases see CONTRIBUTING.md § Filing a bug.

SCF didn’t converge

Symptom, molecular

  iter  85   -75.94731234   3.4e-04   2.1e-03   8
  iter  86   -75.94731201   3.3e-04   2.1e-03   8
  iter  87   -75.94731267   3.4e-04   2.1e-03   8
WARNING: SCF did NOT converge after 100 iterations
RuntimeError: SCF failed: max_iter (100) reached without convergence

The third column (dE) is bouncing instead of decreasing. The fourth column (\(\lVert\mathbf{e}\rVert_F\)) is plateaued. The DIIS subspace (the trailing integer) is full at 8.

Cause

DIIS is oscillating between two near-degenerate density solutions. Common triggers:

  • Stretched bond / dissociation-limit geometry (closed-shell reference becomes a poor description of the multireference state).

  • Transition-metal complex with ligand-field-degenerate d orbitals.

  • Broken-symmetry singlet biradical.

  • Wrong initial guess (e.g. AUTO picked SAP on a system where SAD starts closer to the true minimum).

Fix

In v0.8.0 the default scf_accelerator is already EDIIS_DIIS (see the stiff-convergence tutorial), if you’re seeing this on a v0.8.x build, the default already fired. The remaining levers, in order of bang-for-buck:

opts.level_shift   = 0.2          # Saunders-Hillier shift
opts.newton_threshold = 1.0       # Phase D2c Newton finalizer
opts.initial_guess = vq.InitialGuess.SAD  # if AUTO picked SAP
opts.max_iter      = 200          # last resort - buys time

If you specifically need the deprecated v0.7.x-style vanilla DIIS behaviour for parity work:

opts.scf_accelerator = vq.SCFAccelerator.DIIS

The full decision tree is in user_guide/scf_convergence.md § SCF accelerators.

Symptom, periodic

  iter  18   -114.821342   1.2e-05   4.7e-04
  iter  19   -114.821339   1.1e-05   4.8e-04
  iter  20   -114.821345   1.2e-05   4.7e-04

For periodic SCF, oscillation in the last two digits is usually a sign of a convergence aid problem (band crossing the Fermi level at a coarse k-mesh) rather than a hard SCF instability. The fix is Fermi-Dirac smearing:

opts.smearing_temperature = "metal"          # 0.005 Ha - see
                                             # the `fermi_dirac_smearing` tutorial

If the periodic SCF lands at an impossible energy (over-bound by Madelung-scale shifts of ~0.5 Ha on H₂/STO-3G/30-bohr-box) the problem is something more fundamental, see CLAUDE.md § 7: that’s a bug, not a convergence-aid problem. File an issue with the input that reproduces it.

“Canonical orth dropped too many basis directions”

Symptom

RuntimeError: Canonical orthogonalisation dropped 17 of 234 basis
directions (overlap eigenvalues below 1e-7). The basis set is
nearly linearly dependent on this geometry/cell. See
docs/user_guide/linear_dependence.md.

Cause

The overlap matrix \(\mathbf{S}\) has near-zero eigenvalues, two or more basis-function combinations are nearly identical at the input geometry. Most common triggers:

  • Using a molecular basis set in a periodic calculation (e.g. def2-tzvp for solid NaCl). Diffuse functions overlap with periodic images. Use the pob- family for periodic systems*, see Why solid-state calculations use pob-TZVP.

  • A vDZP / dhf- / x2c- basis** without an explicit ECP center (the heavy-element basis is short by Z core electrons; without the ECP the SCF reaches for them via diffuse-function overlap).

  • Atoms placed at or very near the same coordinates by an upstream geometry-conversion step (CIF → POSCAR rounding, duplicated atoms from a faulty supercell expansion).

Fix

Pick the appropriate fix for the trigger:

# 1. Use a solid-state basis instead.
basis = vq.BasisSet(sysp.unit_cell_molecule(), "pob-tzvp")

# 2. Filter out diffuse primitives at the BasisSet stage.
basis = vq.make_basis(sysp.unit_cell_molecule(), "def2-tzvp",
                      exp_to_discard=0.05)

# 3. Wire ECP centers explicitly for vDZP / dhf-* / x2c-*
#    (see docs/user_guide/ecp.md § ECPCenter recipe).
opts.ecp_centers = [
    vq.ECPCenter(Z=78, xyz=[0.0, 0.0, 0.0]),   # Pt
    ...
]
opts.ecp_library = "ecp60mdf"

# 4. Auto-optimise periodic cutoffs jointly with screening.
opts.auto_optimize_truncation = True    # default on v0.7+

user_guide/linear_dependence.md walks through the v0.7 diagnostic stack (vq.eigs_preflight, vq.disambiguate_critical_overlap, auto_optimize_truncation) and the matching fix recipe in detail.

Memory abort before the SCF starts

Symptom

vibe-qc estimates this calculation will require ~218.4 GB of memory:
    ERI tensor      186.0 GB
    ...
Available on this machine: 7.2 GB. ABORTING.

InsufficientMemoryError: Set `options.memory_override = True` (or
pass `memory_override=True` to `run_job`) to proceed anyway.

Cause

The pre-flight estimator (see user_guide/memory.md) sized the dense four-index ERI tensor or the MO transformation buffers and added the configured headroom (1.5x by default); that number exceeds the machine’s available RAM.

Fix, in order of severity

# 1. Smaller basis (always start here).
run_job(mol, basis="def2-svp", ...)

# 2. Density fitting - orders-of-magnitude smaller working set.
opts.density_fit = True
opts.aux_basis = vq.default_aux_basis_for(basis_name, kind="jk")

# 3. RIJCOSX - even smaller working set for large hybrid-DFT.
opts.density_fit = True
opts.cosx = True

# 4. Override the check (you accept the risk of swap-thrashing).
run_job(mol, ..., memory_override=True)

density_fit=True is the right answer for ~250-1000 basis functions; cosx=True is the right answer above ~1000. See user_guide/density_fitting.md.

Note

The old v0.7.3 DF integral-kernel SIGSEGV on auxiliary bases with l >= 1 shells is fixed in current builds. The active DF-RHF suite runs def2-universal-jkfit and cc-pvdz-jkfit and checks PySCF parity. If an older checkout still segfaults below the Python boundary, it usually has a stale libint install built without the required ERI2/ERI3 flags; follow the clean rebuild recipe in updating.

KeyError: 'Ne' (or any other element) on basis load

Symptom

KeyError: "basis set 'pob-tzvp' has no entry for element 'Ne'"

Cause

The bundled .g94 for that basis set covers a subset of the periodic table that doesn’t include the requested element. Known gaps:

Basis

Missing

Mitigation

pob-tzvp / pob-tzvp-rev2

Ne

Use the def2-TZVP Ne block, or switch the whole calculation to def2-TZVP / cc-pVTZ

pob-dzvp-rev2

Si, Fe, and the transition metals (only the 19 lightest elements are parameterised)

Use pob-tzvp-rev2 for silicates / TM systems

Fix

Either:

  • Switch to a basis that covers the element (def2-tzvp / cc-pvtz for molecular work, pob-tzvp-rev2 for periodic).

  • Add the element by hand: copy the matching block from BSE into a custom/*.g94 file (see user_guide/basis_sets.md § Custom basis sets) and rebuild the library with ./scripts/setup_basis_library.sh.

“DF gradient disagrees by ~115 mHa/bohr”

Symptom

Geometry optimisation drifts away from the true minimum when density_fit=True is on; or hand-written FD gradients disagree with compute_gradient by ~100 mHa/bohr on glycine / def2-TZVP.

Cause

Fixed in v0.8.0. A libint engine-state leak in the 3c-ERI gradient kernel (compute_3c_eri_gradient_weighted in cpp/src/df.cpp). Two adjacent same-l heavy atoms (e.g. the carboxyl O=C-O-H oxygens in glycine and formic acid) caused the engine to leak derivative-buffer state across compute() calls.

Fix

Upgrade to vibe-qc ≥ v0.8.0. The regression guard is tests/test_df_gradient.py::test_df_rhf_gradient_hcooh_def2_tzvp_matches_direct. density_fit=True gradients are now safe at def2-TZVP-class basis sets.

“Analytic RHF gradient is wrong”

Symptom

Geometry optimisation walks to the wrong minimum on a system with f-shells AND ≥2 different second-row elements (e.g. CO, CH₂O, glycine + def2-TZVP). Magnitude can reach 161 mHa on glycine with a recent libint build.

Cause

Fixed in v0.8.0. The two_electron_gradient_contribution (direct 4-index ERI gradient) kernel relied on libint’s internal DerivMapGenerator unscramble path, which had a buggy derivative-to-atom routing for high-l mixed-l shell quartets. Fix C rewrites the kernel as a canonical 1/8 shell-quartet loop with explicit l-canonical reorder before engine.compute().

Fix

Upgrade to vibe-qc ≥ v0.8.0. The regression guard is tests/test_gradient_f_bug.py::test_h2co_def2_tzvp_gradient_matches_pyscf. Direct analytic gradients with f-shells are now correct (post-fix H2CO/def2-tzvp matches PySCF to ~5e-11 Ha/bohr).

“Periodic (BIPOLE) forces / geometry optimisation look wrong”

Symptom

A periodic geometry optimisation with jk_method="bipole" walks the wrong way, or compute_bipole_gradient_rhf/uhf/rks/uks disagrees with a finite-difference of the BIPOLE energy, including at Γ, and emits a UserWarning about a “RESEARCH PREVIEW” gradient.

Cause

The analytic BIPOLE gradient is still a gated research preview. The maintained RHF/UHF Gamma cases, maintained RHF/UHF multi-k cases, and Gamma-local RKS/UKS LDA response regressions now track finite differences, but broader KS functional/cell coverage, meta-GGA tau-Pulay, and multipole far-field certification remain open. RKS/UKS finite-temperature, fractional-occupation, and multi-k analytic calls now fail fast to the finite-difference path. If a system sits outside the maintained surface, the analytic driver can still disagree with the real BIPOLE total-energy finite difference.

Since M5, erfc short-range energies use a larger precision-derived internal ket-image domain and attached-symmetry crystals use a pair-resolved Fock domain by default. The analytic derivative kernel does not yet differentiate either traversal, so analytic gradient calls now fail closed when the SCF result records one of them. Use the finite-difference path for the production M5 energy. Setting sr_image_precision=None and use_fock_symmetry_reduce=False is only a historical-domain diagnostic, not the recommended force workflow.

Fix

Use the exact finite-difference gradient, it is the default for all production paths as of the 2026-05-31 fix:

# Geometry optimisation (FD forces by default - force_mode="fd")
from vibeqc.bipole_optimize import relax_atoms
result = relax_atoms(system, "sto-3g", kmesh, method="RHF")

# Standalone exact gradient (any method)
from vibeqc.bipole_gradient import compute_bipole_gradient_fd
g = compute_bipole_gradient_fd(system, "sto-3g", kmesh, opts,
                               method="RKS", functional="pbe")

run_periodic_job(optimize=True) and the periodic NEB driver already use this path. If a displaced SCF point does not converge, the FD helper raises instead of differentiating a failed iterate; pass require_converged=False only when deliberately diagnosing a failed surface. The analytic drivers remain available for research (force_mode="analytic"); stay on the maintained regression surface if you compare them directly. The full analytic gradient certification is tracked in handovers/HANDOVER_BIPOLE_GRADIENT.md; the regression guards are in tests/test_bipole_gradient.py.

CASPT2/NEVPT2 compute_corr_grad=True gradient has no PT2 correction (v0.15.17)

Symptom

On v0.15.17, a CASPT2 or NEVPT2 analytic gradient requested with caspt2_options.compute_corr_grad=True (or the NEVPT2 equivalent) silently returns a gradient without the effective-density PT2 correction: an SA-CASPT2 gradient comes out identical to the state-specific one. No warning or error is raised.

Cause

The effective-density builder imported the state_rdm12_cpp C++ kernel outside its Python-fallback guard, and that kernel was never exported from the compiled core, so the import always raised, and the gradient driver’s graceful-degradation handler silently dropped the correction on every build.

Fix

Fixed on main (2026-07-02): the import moved inside the fallback guard and the pure-Python 2-RDM fallback was repaired (validated to machine precision against a brute-force spin-orbital reference), so the correction is always computed. Fixed again on current main (2026-07-03): the restored C++ state_rdm12_cpp fast path now splits the ket-side E_rs|c> and bra-side E_sr|c> branches correctly and is re-enabled. Current main also warns if a requested PT2 effective-density, chain-rule, CI-Lagrangian, or explicit Lagrangian correction has to fall back to a less complete gradient contribution. On v0.15.17, treat compute_corr_grad=True gradients as CASSCF-level only; regression guards live in tests/test_casscf_gradient.py (TestDyallChainRule, TestPT2Lagrangian, TestSASpecificity, and the degradation-warning regression) and tests/test_pt2_density_rdm.py.

Multi-k GDF HF / hybrid-KS exchange wrong on diffuse bases in tight cells (v0.15.26 to v0.15.31)

Symptom

On v0.15.26 through v0.15.31, run_krhf_periodic_gdf (and hybrid run_krks_periodic_gdf) on a true multi-k mesh returns a wrong total energy when the AO basis carries functions whose inter-cell overlap is large, for example the ultra-diffuse Li 2sp shells of STO-3G on the rocksalt LiH primitive cell. Measured on LiH/STO-3G at kmesh (2,1,1): +2.04e-2 Ha/cell versus the same Hamiltonian’s real-Gamma direct route and versus pre-v0.15.26 releases. Gamma-only runs, pure (non-hybrid) KS, and vacuum-padded cells (molecules in boxes, chains with vacuum) are unaffected at the 1e-8 gate level.

Cause

The v0.15.26 dense-core tail speedup mirrored shell-pair blocks of the AO-pair Fourier transform at k = 0, using the transpose identity FT_mu,nu(G) == FT_nu,mu(G). That identity additionally requires every evaluation frequency to be a reciprocal-lattice vector, which is false for the momentum-shifted meshes G+q that the Bloch-pair GDF fit evaluates for every (k_bra != Gamma, k_ket = Gamma) exchange pair. The mirrored fit is silently wrong wherever inter-cell AO-pair terms are non-negligible.

Fix

Fixed on main (2026-07-10): the mirror is disabled unless the evaluation frequencies are verified reciprocal-lattice vectors; the Gamma-tail speedup itself is kept. Flagged for the next v0.15.x patch release. On affected releases, cross-check any multi-k HF or hybrid-KS number on diffuse-basis tight cells against run_ccm_rhf_direct (real-Gamma route, unaffected) or upgrade to main. Regression pins: tests/test_rsgdf_dense_mesh_tail.py::test_gamma_mirror_disabled_on_momentum_shifted_mesh and the restored LiH (2,1,1) parity gates in tests/test_ccm_direct.py.

Multi-k BIPOLE RHF raises TypeError instantly; BIPOLE RKS ignores bz_integration="gilat" (v0.15.23 to v0.15.30)

Symptom

On v0.15.23 through v0.15.30, any run_periodic_job(..., method="RHF", jk_method="bipole", kpoints=...) run at a multi-k mesh fails in 0.0 s with

TypeError: run_pbc_bipole_rhf() got an unexpected keyword argument 'bz_integration'

even though you never passed bz_integration. On the same releases, method="RKS" with jk_method="bipole" accepts bz_integration="gilat" and records it in the .out, but the SCF silently runs with default Aufbau occupations - the Gilat-Raubenheimer integration announced in those releases is inert on the BIPOLE RKS route. (BIPOLE UHF/UKS + gilat correctly fail closed with NotImplementedError on all releases; GDF routes are unaffected.)

Cause

The commit wiring Gilat through GDF and BIPOLE RKS (891acead, first in v0.15.23) put the new keyword on the wrong callsite in periodic_runner.py: it passed bz_integration= unconditionally to run_pbc_bipole_rhf (which did not accept it yet) and never passed it to run_pbc_bipole_rks (which did).

Fix

Fixed by 38c4afed, shipped in v0.15.31 - upgrade, or pin a tag outside the window. No energy computed on the affected tags is wrong: the RHF route fails before any integral work, and the RKS route computes a correct default-occupation result (only the requested Gilat scheme is dropped). Regression pins: tests/test_periodic_runner_bipole_callsites.py (live RHF multi-k route + a static callsite/signature contract over every run_pbc_bipole_* dispatch) and the strengthened forwarding assertion in tests/test_bz_integration_gilat_scf.py. Full provenance: BUG-PER-002 in handovers/HANDOVER_OPEN_BUGS_V015.md.

ImportError: cannot import name 'EEQOptions' from 'vibeqc._vibeqc_core'

Symptom

ImportError: cannot import name 'EEQOptions' from
'vibeqc._vibeqc_core' (/path/to/_vibeqc_core.cpython-...so)

Cause

You have a Python source tree from a newer commit but the compiled _vibeqc_core.so is from an older one. Symbol mismatch.

Fix

pip install -e . --no-build-isolation --force-reinstall

This rebuilds the C++ extension against the current Python sources. On macOS the rebuild takes ~30-90 s; on Linux similar. If you’re on a worktree, memory dictates a per-worktree venv so the parent venv doesn’t get clobbered.

ModuleNotFoundError: No module named 'vibeqc'

Symptom

ModuleNotFoundError: No module named 'vibeqc'

Cause

You ran the script with the wrong Python, the system python3 instead of the venv-installed one.

Fix

Either give the full path to the venv’s interpreter:

~/path/to/vibeqc/.venv/bin/python my_input.py

or activate the venv first:

source ~/path/to/vibeqc/.venv/bin/activate
python my_input.py

“Cell-list construction returned 0 cells”

Symptom

RuntimeError: cell-list construction returned 0 cells for cutoff
12.0 bohr on this lattice - check that the lattice matrix is full
rank and the cutoff is positive.

Cause

Either a degenerate lattice (zero-rank column) or a non-positive lattice_opts.cutoff_bohr. Common when a 1D-chain input has the two vacuum axes accidentally set to 0 instead of a wide separation:

sysp = vq.PeriodicSystem(
    dim=1,
    lattice=np.diag([4.0, 0.0, 0.0]),    # <- bug - vacuum axes are 0
    unit_cell=[...],
)

Fix

Set the non-periodic axes to a generous vacuum separation:

sysp = vq.PeriodicSystem(
    dim=1,
    lattice=np.diag([4.0, 30.0, 30.0]),  # 30 bohr vacuum decouples images
    unit_cell=[...],
)

vibeqc-cite: manifest missing after a job

Symptom

$ vibeqc-cite output-h2o
error: manifest file not found: output-h2o.system

Cause

Either the job hasn’t finished writing the manifest (look for output-h2o.system.tmp), or the path is wrong, or the run was killed before the initial manifest landed.

Fix

  • Check output-h2o.system actually exists: ls output-h2o.*.

  • Try the stem with the .out suffix, vibeqc-cite output-h2o.out also works (the CLI normalises via .with_suffix(".system")).

  • For pre-v0.8.x runs (no manifest at all), use vibeqc-cite’s inability to find the manifest as a diagnostic, the run predates the citation surface. Re-run the SCF on a current build to get the bibliography.

Native D4 dispersion (backend="native") scope + accuracy

What it is

compute_d4(..., backend="native") uses vibe-qc’s own MPL-2.0 D4 implementation (no dftd4 dependency). As of 2026-06-26 it is parity-validated for elements H-Ne: its reference C6 (per-atom Eq.-6 extraction over a correlated CPKS/PBE38 polarizability) agree with the dftd4 package to ~5 % (CH₄) / ~8 % (H₂O) and the CH₄-dimer D4 energy to <0.05 kcal/mol. No warning is emitted. (The D4NativeExperimentalWarning class is retained for back-compat but is no longer raised – earlier versions emitted it while the reference data was being developed.)

Limitation

The native reference catalogue covers H, He, B, C, N, O, F, Ne only. backend="native" on a molecule containing any other element raises a clear error (no reference data). For full periodic-table coverage use the default backend="dftd4" (optional dftd4 package, bit-exact to upstream) – every built-in dispersion="d4" route does. Tracked in HANDOVER_D4_NATIVE.md; extending the catalogue past period 2 is the prerequisite for making native the default backend.

Things that are not errors

A handful of things look alarming but aren’t:

  • vibe-qc estimates this calculation will require ~218.4 GB, Proceeding (override), you passed memory_override=True and the run is going through despite the estimate. Wait and see; the estimate’s configured headroom plus the dense-ERI-tensor estimate is conservative.

  • canonical_orth dropped 2 of 234 basis directions with a small number, vibe-qc reports any drop; 1-3 dropped basis directions out of hundreds is typical of a tight cell or large basis. Only worry if the drop exceeds ~5% of the basis count.

  • # no citation route for basis 'foo' in .references, your basis isn’t in the routing table. The job ran fine; the bibliography is just missing one entry that you’ll need to add by hand. The fix is to extend database.toml in your next PR.

  • Used 4 OpenMP threads even though you set OMP_NUM_THREADS=16

    • the system’s actual core count (or the cgroup limit on a cluster node) wins over the env var. Sanity-check with nproc or sysctl hw.physicalcpu (macOS).

BIPOLE absolute energies on tight ionic crystals (fixed, corrected gauge is the default at Γ and multi-k, all four drivers)

Symptom

jk_method="bipole" total energies on dense ionic cells (MgO primitive is the reference case) sit several hartree off converged references at the same k-mesh, e.g. MgO/STO-3G RKS [2,2,2]: BIPOLE −266.1 Ha vs PySCF-GDF −270.5 Ha (which independently agrees with converged CRYSTAL to 5 mHa). The SCF converges cleanly, the stationary point’s absolute energy is what’s off.

Cause + status

Root-caused (2026-06-10, fixed-density component audit vs PySCF): (1) the EXT EL-SPHEROPOLE term is a double-count in vibe-qc’s explicit-jellium gauge; (2) the Γ-locality density projection drops real cross-cell contributions to T/V_ne/J on tight cells; (3) the full-Coulomb direct exchange series is formally divergent with the non-decaying finite-k-mesh Bloch density (any finite value is a cutoff artefact).

Fixed at Γ for all four drivers (RHF/RKS/UHF/UKS, on by default; 2026-06-10/11) by the Ewald exchange split: K = K_SR(erfc ω, direct) + K_LR(erf ω, reciprocal K≠0) + (ξ_M π/(Vω²))·S·D·S (probe-charge Ewald / Madelung G=0 correction, exchange_exxdiv='ewald', PySCF-equivalent), full-Bloch density, spheropole omitted. Validated out-of-process against PySCF GDF: exchange element-wise to 8e-4 at the converged MgO density; H₂ 12-bohr Γ to ~1 µHa (RHF) / ≤1 mHa (KS + open-shell variants). At multi-k the corrected gauge is now the DEFAULT for all four drivers too (option (b) Phase 3 + the Phase-5 flip, 2026-06-13): the q = k−k′ LR-exchange channels + BvK-supercell Madelung correction (α_HF-scaled for hybrid RKS/UKS, per spin for UHF/UKS), validated on the H₂ box [2,1,1] vs PySCF (RHF +0.12 mHa at cutoff 7 / −0.002 at 12, RKS SVWN/PBE0 +0.04/+0.06, UHF +0.03, UKS k-shift 0.013) and to +0.0001 mHa/cell against the explicitly-doubled supercell at Γ. Pass use_exchange_ewald_split=False for the legacy gauge (kept for the legacy-gauge analytic gradient + parity diagnostics; it mis-states ionic absolute energies). The corrected multi-k gauge needs a Monkhorst-Pack mesh; an ad-hoc k-list at multi-k falls back to the legacy gauge under the auto default.

If your Γ ionic-crystal energies still look wrong

  • Heed the S(Γ) fold-truncation line in the output. The corrected gauge contracts full Bloch folds; diffuse AO tails (STO-3G Mg 3sp, outermost exponent ≈ 0.046) keep cross-cell overlaps alive past 16 bohr. MgO/STO-3G needs cutoff_bohr 12 (≈5e-3 fold truncation) and ≥16 for tight work; at 8 bohr the fold error is 0.39 and the SCF can land in spurious metric-artifact states.

  • Check the SCF basin. Minimal-basis ionic Γ SCF can have multiple solutions when the density is corrupted by an under-converged operator (the pre-v0.14 cross-cell XC bug era produced such basins). At a healthy density the fixture is benign: PySCF KRKS converges MgO/STO-3G to one basin from every standard guess (minao/1e/atom, Γ and [2,2,2], re-verified 2026-07-13), and the corrected-gauge BIPOLE RKS does the same. For absolute claims, warm-start from a reference density (initial_density=) or compare stationary points explicitly.

  • Keep KS smearing well BELOW the gap. A minimal basis badly underestimates the gap (MgO/STO-3G: ~0.3 eV vs the ~7.8 eV experimental gap). A smearing_temperature whose window is comparable to that small gap fractionally occupies states across it and settles a near-metallic basin hundreds of mHa off the physical (gapped) solution, e.g. MgO RKS Γ at T = 0.01 Ha (≈0.27 eV) landed +330 mHa with a 42 mHa entropy term (2026-06-13). The RKS/UKS drivers now emit a basin_warning (logged + on the result object) when finite-T smearing straddles the gap. The fix is a sub-gap smearing: reduce smearing_temperature until the basin_warning clears, or drop smearing once converged. For a gap as small as MgO/STO-3G’s (where even 0.002 Ha ≈ 0.05 eV still straddles, the converged gap there is only ~0.1-0.3 eV depending on the basin) that can mean 0.001 Ha or no smearing at all. Usually no aid is needed at all: plain DIIS converges the MgO-class tight ionic cells monotonically in ~10 iterations (Gap-B validation 2026-07-13), so the BIPOLE KS auto-FMIXING (30%) now engages only for DIIS-off runs, where it damps the bare Roothaan iteration (CRYSTAL-style). Smearing is for near-degeneracy, not as a blanket ionic-cell aid.

  • Keep the M5 SR image-domain default for absolute energies. Every erfc short-range build now derives its absolute internal ket-image radius from sr_image_precision=1e-6 and enables separation-aware QQR screening. This repairs the 515 mHa short-range Hartree loss measured on the MgO split fixture. sr_image_precision=None restores the old truncated behavior for reproducibility only and suppresses automatic Fock reduction on an active erfc SR route; it is not an accuracy setting. An explicit sr_image_extent_bohr overrides the precision-derived radius.

  • Pure semilocal RKS still uses exact-FT Hartree by default. Retiring that route in favor of the repaired SR+LR composition is a separate change. Its dense-core reciprocal-tail warning remains relevant until that change lands. The M5 image-domain default applies whenever an erfc SR build is actually present.

Remaining reference choices

Energy differences at fixed cell/density class (geometry scans, relative energies on covalent/molecular-limit cells) are much less affected, and H₂-class validation cells agree with references to sub-mHa. For an independent implementation comparison, use the GDF route and a converged reciprocal cutoff. Historical BIPOLE values produced with the unpadded SR domain must be regenerated before absolute comparison.

Still stuck?

gitlab.peintinger.com/mpei/vibeqc/-/issues

When filing an issue, include:

  1. The full error traceback.

  2. The minimal Python script that reproduces it.

  3. The output-*.system manifest (carries the hardware + library versions vibe-qc needs to reproduce).

  4. The vibe-qc version (python -c "from vibeqc import VIBEQC_VERSION; print(VIBEQC_VERSION)").

The maintainer or release chat will triage. For security vulnerabilities follow SECURITY.md instead of opening a public issue.