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
# tutorial 42
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-tzvpfor solid NaCl). Diffuse functions overlap with periodic images. Use the pob- family for periodic systems* — see tutorial 16.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 a 20% headroom; 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.
Warning
The v0.7.3 DF integral-kernel SIGSEGV on auxiliary bases with
l ≥ 1 shells is still open as of v0.8.0 — only s-only auxiliary
bases (sto-3g, sto-6g, 6-31g) are safe in the DF / ADFT path. Use
the default aux_basis picker on a molecular run; periodic GDF
has its own metric path that’s not affected.
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 |
|---|---|---|
|
Ne |
Use the def2-TZVP Ne block, or switch the whole calculation to def2-TZVP / cc-pVTZ |
|
Si, Fe, and the transition metals (only the 19 lightest elements are parameterised) |
Use |
Fix¶
Either:
Switch to a basis that covers the element (
def2-tzvp/cc-pvtzfor molecular work,pob-tzvp-rev2for periodic).Add the element by hand: copy the matching block from BSE into a
custom/*.g94file (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.
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 HANDOVER_BIPOLE_GRADIENT.md;
the regression guards are in tests/test_bipole_gradient.py.
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.systemactually exists:ls output-h2o.*.Try the stem with the
.outsuffix —vibeqc-cite output-h2o.outalso 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.
D4NativeExperimentalWarning — native D4 dispersion is gated¶
Symptom¶
Every compute_d4(..., backend="native") call (and
generate_reference_dataset) emits D4NativeExperimentalWarning,
and native-backend dispersion energies are several times more
attractive than backend="dftd4" on the same system.
Cause¶
The shipped native reference dataset (d4_reference_data.json)
stores each reference system’s whole-molecule polarizability as
per-atom reference data — the per-atom extraction of Eq. 6 of
Caldeweyher et al. (2019) is not implemented yet — so reference C6
coefficients are 4–32× too large and dispersion energies ~8–32× too
attractive (2026-05-31 audit). This is a defect in the opt-in
native backend’s dataset, not in the D4 method wiring.
Fix¶
Use the default backend="dftd4" (optional dftd4 package,
bit-exact to upstream) for any production number — every built-in
dispersion="d4" route already does. The native backend stays
available for Phase D4b development; the un-gating bar (regenerated
dataset + parity vs dftd4) is tracked in
docs/handover_d4_native.md. The
experimental GFN2-xTB model’s post-SCF D4 term currently consumes
the defective dataset and over-binds dispersion accordingly (GFN2
is itself gated experimental).
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 passedmemory_override=Trueand the run is going through despite the estimate. Wait and see; the estimate’s 20% headroom plus the dense-ERI-tensor estimate is conservative.canonical_orth dropped 2 of 234 basis directionswith 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 extenddatabase.tomlin your next PR.Used 4 OpenMP threadseven though you setOMP_NUM_THREADS=16— the system’s actual core count (or the cgroup limit on a cluster node) wins over the env var. Sanity-check withnprocorsysctl 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 genuinely has multiple solutions — PySCF itself converges MgO/STO-3G Γ to different totals from different starting densities. 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_temperaturewhose 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 Γ atT = 0.01 Ha(≈0.27 eV) landed +330 mHa with a 42 mHa entropy term (2026-06-13). The RKS/UKS drivers now emit abasin_warning(logged + on the result object) when finite-T smearing straddles the gap. The fix is a sub-gap smearing: reducesmearing_temperatureuntil thebasin_warningclears, or drop smearing once converged. For a gap as small as MgO/STO-3G’s (where even0.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 Haor no smearing at all. The convergence aid that is safe and gap-independent is the driver’s auto-FMIXING (30% for pure DFT); smearing is for near-degeneracy, not as a blanket ionic-cell aid.
Workaround for the not-yet-fixed paths¶
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 absolute energies on tight ionic cells at multi-k use
the GDF route (run_krhf_periodic_gdf) until the multi-k exchange
channels land; watch the CHANGELOG.
Still stuck?¶
gitlab.peintinger.com/mpei/vibeqc/-/issues
When filing an issue, include:
The full error traceback.
The minimal Python script that reproduces it.
The
output-*.systemmanifest (carries the hardware + library versions vibe-qc needs to reproduce).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.