Changelog¶
All notable changes from the current minor cycle. Format loosely
follows Keep a Changelog.
For pre-v0.9.0 history, see the tagged CHANGELOG.md at each tag
(e.g. git show v0.8.0:CHANGELOG.md); the project trimmed older
sections during v0.11.0-prep to focus on the road to 1.0.
[Unreleased]¶
v0.11.x — Sun’s Stingray (patch line). Coupled cluster (canonical CCSD(T)) remains roadmapped for v0.14.0.
[v0.11.3] — 2026-06-04¶
Correctness patch on Sun’s Stingray — two cherry-picked fixes from
main. No feature changes. Inherits the v0.11.0 codename.
Fixed — GDF: exxdiv='ewald' silently ignored on use_compcell=False¶
Multi-k run_krhf_periodic_gdf(..., exxdiv='ewald', use_compcell=False)
was silently ignoring the exxdiv request — the Madelung exchange
correction only lives in the use_compcell=True branch, so the user
got an uncorrected (unphysical) multi-k energy with no warning or error.
Now raises a clear ValueError pointing to the working path
(use_compcell=True + exxdiv='ewald'); invalid exxdiv strings are
also rejected. Shared by the RKS driver. (Cherry-picked a1731e96.)
Fixed — properties: ComplexWarning + imaginary cast on periodic runs¶
Periodic multi-k SCF results carry a complex-typed Hermitian density;
the molecular-property code (dipole, Mulliken/Löwdin charges,
Hirshfeld, Mayer bond orders) was silently float()-casting the
result and emitting 9 ComplexWarnings per run_periodic_job. New
_real_if_hermitian helper centralises the reduction; imaginary parts
that exceed machine noise now raise instead of silently discarding.
(Cherry-picked 7e0d26e5.)
[v0.11.2] — 2026-06-04¶
Docs-only patch on Sun’s Stingray — removes MyST {contents}
directives that rendered as red ERROR boxes on the published
vibe-qc.com site. No code changes. Inherits the v0.11.0 codename.
Fixed — docs: drop rejected {contents} TOC directives¶
13 tutorial and user-guide pages carried a MyST {contents} local
table-of-contents block that the vibeqc_cite.py Sphinx extension
rejects, rendering a red ERROR box on the Furo site. Furo already
provides a per-page TOC in the right sidebar, so the directive was
redundant. Removed the opening fence + options + closing fence from
each affected page; prose is otherwise untouched. Local
sphinx-build confirmed: warning count unchanged (222 → 222), no
new errors. (Cherry-picked ab77f423.)
[v0.11.1] — 2026-06-03¶
Correctness patch on Sun’s Stingray — EWALD_3D Hartree J
ω-invariance. One cherry-picked fix from main. Inherits the
v0.11.0 codename.
Fixed — EWALD_3D Hartree J now ω-invariant (F2 closure)¶
build_j_ewald_3d now defaults to an analytic AO-pair-FT Hartree
matrix in the periodic G≠0 Coulomb convention, replacing the
FFT-Poisson collocation long-range half. The FFT-Poisson path was
FFT-grid-error-dominated (error spread 137 Ha at 16³ grid → <1 Ha
at 48³; the auto-grid is only 20³), and the 1/ω² leak coefficient
was density-shape-dependent (MgO: 1.07·A_pred, LiH: 0.656·A_pred
— no single additive N_e formula could cancel both). The analytic
path uses the integrals chat’s ao_pair_fourier_transform_bloch_cxx
C++ kernel (74f3eb4a s-shell tier + 590d022d McMurchie-Davidson
general-L) — the same kernel as the multi-k GDF and V_ne analytic-FT
fixes. Inline code comments pin the validation targets per CLAUDE.md
§8: PySCF KRHF Γ exxdiv='ewald' MgO/STO-3G −271.04994 Ha and
LiH/STO-3G −8.33527 Ha. The legacy FFT-Poisson path remains
reachable via VIBEQC_J_EWALD3D_BACKEND=grid for bisection.
(Cherry-picked 821ced25.)
[v0.11.0] — 2026-06-03 — Sun’s Stingray¶
Periodic GDF chain closure — the fourth-cycle defer finally closes (v0.8.0 → v0.9.0 → v0.10.0 → v0.11.0). See the preamble above for the full narrative.
v0.11.0 — Sun’s Stingray (codename locked 2026-05-29). Reserved for the periodic-GDF chain — the fourth-cycle defer (v0.8.0 → v0.9.0 → v0.10.0 → v0.11.0) finally closes. The GDF chain’s RSGDF path reaches µHa parity vs
pyscf.pbc.df.GDF/pyscf.pbc.df.RSDFon the canonical ionic-crystal test case (LiH primitive FCC / sto-3g / def2-svp-jk) at a converged lattice cutoff (30 bohr) — Γ-only: -0.5 µHa at ke=800, +27 µHa at ke=200; multi-k (2,2,2): -1.0 µHa at ke=200 — both within PySCF’s own GDF↔RSDF noise floor (~1 µHa). The multi-k −2495 Ha bug (per-q cderi gauge + primitive-cell exxdiv Madelung) is fixed — see below.Γ-only V_long root cause closed: the v0.10.x
compute_nuclear_lattice_ewaldintegrated V_long on a real-space Becke-Lebedev molecular grid (Treutler-Ahlrichs radial × Lebedev- Laikov angular × Becke fuzzy-cell partition). That grid does not exactly preserve the crystal point-group symmetry of a non-cubic primitive cell (FCC, hexagonal, …), so Hcore elements that must vanish by site symmetry pick up a finite, non-symmetry-equivalent per-element residue. On LiH primitive FCC this produced +1.66e-2 errors on individual Hcore elements (e.g.(Li-1s, Li-pz)should be ~0 by atomic-on-atom symmetry; the grid gave +0.017). Self- consistency compounded that per-element symmetry break into a ~4 mHa SCF total-energy residue vs PySCF. The 2026-05-29 retraction of the earliercc5bbfb0ruling (where aV_ne = 1000·Imonkey-patch incorrectly cleared V_ne as the culprit — that probe couldn’t detect a symmetry-break bug because diagonal matrices are invariant under rotation) led to the systematic Lippert 1997 (DOI 10.1080/002689797170220), Sun-Berkelbach-McClain-Chan 2017 (DOI 10.1063/1.4998644) and McClain-Sun-Chan-Berkelbach 2017 (DOI 10.1021/acs.jctc.7b00049) re-read that fixed it.The Γ-only fix (in
python/vibeqc/periodic_v_ne.py,compute_v_ne_ewald_3d_ft_gamma): replace the molecular-grid V_long quadrature with the canonical analytic reciprocal-space Ewald-split formula. V_short uses the existing libint erfc-screened analytical lattice sum (compute_nuclear_erfc_lattice, shipped pre-v0.11.0, exact). V_long is computed as(1/Ω)·Σ_{G≠0} v_long(G)·conj(ρ̂_μν(G))wherev_long(G) = -Σ_I Z_I·(4π/G²)·exp(-G²/(4α²))·exp(-iG·R_I)andρ̂_μνreuses the integrals chat’sao_pair_fourier_transform_bloch_cxx(commits74f3eb4as-only tier +590d022dMcMurchie-Davidson general-L tier). The G=0 mode is dropped per PySCF’s convention (jellium-neutralised implicit background); a finite v_short(G=0) tail is subtracted from V_short to maintain PySCF parity. The reciprocal-space sum preserves crystal symmetry exactly — closes the FCC C3 break to machine precision.Scope of the V_long fix — Γ-only RSGDF path only. The new
compute_v_ne_ewald_3d_ft_gammais wired intorun_pbc_gdf_rhf’sgdf_method='rsgdf'branch. The same Becke-Lebedev molecular-grid V_long that this fix replaces still backscompute_nuclear_lattice_ewald, whichcompute_nuclear_lattice_dispatchfeeds to every EWALD_3D periodic driver (RHF/RKS/UHF/UKS Γ-only + multi-k, BIPOLE, GAPW). Those drivers therefore retain the same ~mHa-scale site-symmetry- break on non-cubic primitive cells (FCC, hexagonal, rhombohedral). It went unnoticed because the Ewald-3D stack was validated on conventional cubic cells (e.g. LiH rocksalt at859efe0a), whose symmetry the molecular grid does respect.Resolved dispatch-wide by the PBC chat (v0.11.x follow-up).
compute_nuclear_lattice_dispatch’s EWALD_3D branch now defaults to a per-cell analytical-FT V_long (compute_v_ne_ewald_3d_ft_latticeinpython/vibeqc/periodic_v_ne.py) — the lattice (LatticeMatrixSet) generalisation ofcompute_v_ne_ewald_3d_ft_gamma. It is a true drop-in: each per-cell block is the same real-space object the grid quadrature approximated (V_short via libint erfc + V_long via the symmetric reciprocal-space FT + the G=0 tail correction), computed analytically instead of by the symmetry-breaking molecular grid, so it Bloch-sums correctly at every k. One change point therefore fixes the V_ne site-symmetry break for RHF/RKS/UHF/UKS (Γ-only + multi-k), BIPOLE and GAPW. On LiH primitive FCC the(Li-1s|V_ne|Li-px/py/pz)block is restored from a 2.2e-2 spread to ~3e-17 (machine precision), andbloch_sumat Γ reproducescompute_v_ne_ewald_3d_ft_gammato 1.3e-13. The break is driven by per-atom L≥1 site symmetry, not cell shape: it appears on the FCC primitive cell AND on the conventional cubic LiH rocksalt cell (rocksalt(Li-1s|V_ne|Li-p)restored 1.1e-2 spread → 3e-18) — the earlier “non-cubic only” framing was imprecise. Systems with L≥1 atoms therefore receive a ~mHa V_ne symmetry correction that stays within existing test tolerances (crystal-bulk benchmarks + 230+ periodic consumer tests green; the only periodic reds are pre-existing and unrelated — the F2/F3 EWALD_3D Madelung/exxdiv gap; the F4e_nucleargauge bug is now fixed, see the F4 entry below). s-only systems (e.g. H₂ vacuum box) are unchanged to ~1.8e-10 (no L≥1 ⇒ the grid is already symmetric for s·s pairs). The legacy molecular-grid quadrature stays reachable viaVIBEQC_VNE_EWALD3D_BACKEND=gridfor bisection; a C++ port of the per-cell FT contraction is the remaining perf follow-up. Regression gate:tests/test_periodic_v_ne_ewald_ft.py.Multi-k GDF — M2 landed. The 2026-05-30 audit found the multi-k GDF driver (
run_krhf_periodic_gdf,use_compcell=True) converged to a non-physical −2495 Ha on LiH primitive FCC at kmesh=(2,2,2) (PySCF: −7.92 Ha). Two independent root causes, both now fixed:
Per-q
Lpqcache used the wrong cderi gauge. The periodic GDF cderiρ̂_μν^{k_i,k_j}(G+q) = Σ_R e^{+ik_j·R} ∫χ_μ(r)χ_ν(r−R)e^{−i(G+q)·r}drdepends on the pair (k_i, k_j), not the momentum transferq = k_j − k_ialone — the inter-cell (R≠0) terms carry the ket momentumk_j. A q-only cache (and a single broadcast Hartree J) is exact only in the vacuum-box limit, which is why the H₂ multi-k smoke gates passed while LiH FCC was catastrophic. Fixed bybuild_lpq_bloch_native_fft(python/vibeqc/aux_basis.py) — the (k_i,k_j)-resolved all-FT generalisation of the Γ-onlybuild_lpq_native_fft— with a per-k Hartree J and per-pair exchange K inrun_krhf_periodic_gdf.
exxdiv='ewald'used the primitive-cell Madelung. The finite- k-mesh exchange-divergence correction must use the Born–von-Kármán supercell Madelung (ξ for the (n₁,n₂,n₃) MP mesh, latticeA·diag(n)), not the primitive cell’s — the latter over-counts byNk^(1/3)and over-bound LiH (2,2,2) by −592 mHa. Fixed via_madelung_for_kmesh(matches PySCFtools.pbc.madelung(cell, kpts)).LiH primitive FCC (2,2,2) reaches µHa parity: at cutoff 30 bohr / ke=200 Ha it lands at −7.92200 Ha, −1.0 µHa of PySCF
KRHF.density_fit()(exxdiv='ewald') — within PySCF’s own GDF↔RSDF noise (~1–14 µHa here). At the cheaper production default (cutoff 15 bohr / ke=200) it is −7.92040 Ha, +1.6 mHa (chemical accuracy). The accuracy knob is the lattice cutoff (the Bloch-pair cell list), not a DF-method floor: the tight-cell Bloch pair density extends over many cells and the default 15-bohr list truncates it (Γ-only shows the same +4.7 mHa → +27 µHa → −0.5 µHa progression with cutoff/ke). No compensated-charge / MDF builder is needed for LiH. The CLAUDE.md §7 energy-sanity guard stays a silent backstop. Regression gate flipped from pinning the guard to asserting PySCF parity (chem-acc at the default + µHa at cutoff 30):tests/test_audit_20260530_periodic.py::test_multik_compcell_gdf_lih_physical.MgO known limit (Sun-Berkelbach 2017 §III all-electron GDF accuracy floor): the steep Mg-1s core × Mg-1s pair-FT exceeds ke=200’s Gmax bandwidth. LiH (light atoms) reaches µHa at the converged cutoff/ke; MgO lands at −515 mHa at ke=200 and ~−25 mHa at ke=800 (~5× per ke-doubling), and µHa would need ke ≈ 6400 (hours per SCF). Sun-Berkelbach 2017 documents this saturation and motivates the MDF (Mixed Density Fitting) extension. MgO µHa ships v0.12.0 with MDF; v0.11.0 demonstrates µHa on the canonical Sun-2017 light-atom test (LiH FCC) at both Γ-only and multi-k (2,2,2).
The integrals chat has accelerated the C++ AO-pair-FT kernel (
74f3eb4as-shell tier,590d022dgeneral-L McMurchie-Davidson) that was originally scoped for v0.11.x — closes the MgO 8-atom OOM ceiling.Open scope still tracked under the v0.11.0 contributor matrix: BIPOLE L_max≥2 + Madelung Ewald rebases (PBC/BIPOLE chat), ωB97X-D close (XC chat,
2f577cf5D2/CHG citation cited), D4 native-default flip decision, GAPW M3d/M3e + multi-k GPW + lmax≥4 spherical-harmonic compensators (GAPW chat, also already on v0.12-prep — restart format + DOS/band helpers + D3 dispersion). Solvation analytic gradients (CPCM + COSMO), DFT+U Increments 3 + 4d-bipole, and the semiempirical UPM6 open-shell NDDO driver all closed end-to-end onmain.Docs housekeeping: the
vqremote-job tutorial was renumbered 43 → 48 (docs/tutorial/48_vq_queue_remote_job.md) to clear a duplicate43_prefix it shared with the XSF/BXSF visualization tutorial; inbound links updated across the tutorial index, quickstart, and the ORCA/CRYSTAL/Gaussian migration guides.Coupled cluster (canonical CCSD(T)) remains roadmapped for v0.14.0; CC2 / CC3 for v0.15.0; FNO + DLPNO-CCSD(T) for v0.17.0 (see
docs/roadmap.md§ Wavefunction-method ladder).
Fixed — periodic EWALD_3D e_nuclear gauge consistency on open-shell + multi-k drivers (handover F4)¶
The Γ-only UKS and all multi-k (RKS / UHF / UKS) EWALD_3D SCF drivers
computed the nuclear-repulsion term e_nuclear in the molecular
gauge (the bare 1/d sum) while their Hartree J was built in the
Ewald-3D gauge. With a default options object (DIRECT_TRUNCATED),
nuclear_repulsion_per_cell returned the molecular value and
madelung_energy_correction_for_lat added the bare-gauge
+α_M·Q_e²/2L leak correction; the two only partially cancelled,
leaving a ~0.74 mHa residual on H₂/STO-3G/30-bohr (CLAUDE.md §7: an
inconsistent periodic energy is a bug, not a convergence artefact). The
closed-shell RHF/RKS Γ drivers and the RHF multi-k driver already forced
coulomb_method = EWALD_3D at entry; the open-shell Γ and the
RKS/UHF/UKS multi-k drivers did not.
Fix. Force
coulomb_method = EWALD_3Dat driver entry — gated onsystem.dim == 3, since the EWALD_3D V_ne dispatch is implemented only for 3D (1D/2D cells keepDIRECT_TRUNCATED) — inperiodic_uks_ewald.py,periodic_uhf_ewald.py,periodic_rks_multi_k_ewald.py,periodic_uhf_multi_k_ewald.pyandperiodic_uks_multi_k_ewald.py. This aligns V_ne (compute_nuclear_lattice_dispatch),e_nuclear(nuclear_repulsion_per_cell→ the periodic Ewald ion-ion sum) and the Madelung term (now 0 for EWALD_3D) with the Ewald-3D Hartree J — one gauge for all three Coulomb pieces, matchingrun_rks_periodic_gamma_ewald3d.Effect. On H₂/STO-3G/30-bohr (ω = 0.5) the Γ-only UKS and the multi-k RKS totals move from the molecular-gauge −1.150390 Ha to the periodic −1.151133 Ha — now bit-identical to the Γ-only RKS driver (|ΔE| ≤ 1e-13 at PBE and B3LYP). User-facing open-shell / multi-k energies shift by ~mHa only where a default (
DIRECT_TRUNCATED) options object was passed directly to one of these drivers; therun_*_periodic_scfdispatcher and the ASE calculator already setEWALD_3D, so those paths are unchanged.run_uhf_periodic_gamma_ewald3dis included even though its closed- shell-equals-RHF test passed: that test only passed because the preceding shared-optsRHF call forced the gauge first; a standalone call with default opts returned a molecular-gauge result.The
dim == 3gate was also added to the RHF multi-k driver’s existing F1 force block: that unconditional force had begun raising on 1D chains once the analytic-FT V_ne landed itsdim == 3guard (commit00f321e6), reding the dim=1 accelerator-uniformity tests; gating restores theirDIRECT_TRUNCATEDgauge.Tests. Fixes the two pre-existing reds (
test_periodic_uks_ewald.py::test_closed_shell_uks_matches_rks_to_microhartreeandtest_periodic_rks_multi_k_ewald.py::test_b3lyp_path_uses_hf_exchange). The two affectedtest_energy_decomposition_consistenttests (UKS Γ + RKS multi-k) were migrated to the EWALD path (E_total = E_elec + E_nuc, no Madelung term) and the now-bit-equivalenttest_multi_k_at_gamma_mesh_matches_gamma_drivertolerance tightened 2 mHa → 1e-10 — exactly as the A1 milestone did for the RHF/RKS Γ drivers. The dim=1 accelerator-uniformity multi-k tests are green again.
Fixed — SAD initial guess: per-l occupation pinning for transition metals¶
The SAD (superposition-of-atomic-densities) initial guess assigned the
wrong d-shell occupation for every 3d transition metal. The isolated-atom
SCF distributed electrons by a single global eigenvalue sort, and the
3d/4s/4p near-degeneracy makes that bare-atom order disagree with the
physical configuration — with nothing to correct it. The whole 3d row
came out wrong in both directions: Sc/Ti emptied their 3d entirely and
spilled the electrons into an empty 4p, while Ni over-filled to 3d¹⁰ 4s⁰
instead of 3d⁸ 4s². SAD is the AUTO-resolved default guess for every
periodic system and every open-shell / transition-metal molecule
(GuessEngine::resolve_auto), so every transition-metal job on defaults
seeded from a ±1–2 d-electron-wrong density (degrading convergence; found
during NiO/STO-3G validation).
Fix (
cpp/src/guess.cpp): atomic SAD occupations are now pinned per angular-momentum channel from the neutral-atom ground-state configuration instead of by a global eigenvalue sort. The spherically-averaged atomic Fock is block-diagonal in l, so each MO is a pure-l function; within each l-block the lowest-energy orbitals are Aufbau-filled up to the tabulated per-l electron count, with degenerate orbitals sharing fractional occupation (the atomic density stays spherical). The configuration table is the Madelung order plus the standard neutral-atom anomalies (Cr, Cu, Nb, Mo, Ru, Rh, Pd, Ag, La, Ce, Gd, Pt, Au, Ac, Th, Pa, U, Np, Cm, Lr) — what PySCF (scf.atom_hf), Psi4 and ORCA do for their atomic guesses. Sc–Zn now reproduce the experimental d-population (1, 2, 3, 5, 5, 6, 7, 8, 10, 10); main-group SAD is byte-identical; the electron counttrace(D·S) = Zis preserved. If a basis is too small to hold the tabulated configuration (valence-only ECP bases), the build falls back to the previous global-Aufbau fill, so ECP guesses are unchanged.Regression tests (
tests/test_guess.py) pin the d-population for Sc/Ti (under-fill), Ni (over-fill) and the Cr/Cu anomalies, the absence of spurious 4p occupation on Sc/Ti, and main-group invariance.Neutral-atom ground-state configurations are CODATA/NIST shared-background data with no single citable publication, so no citation-database entry is required (CLAUDE.md §8).
Fixed — pob-TZVP sulfur d-shell SCAL typo + hardened CRYSTAL parser¶
The Bredow
pob-TZVP/pob-TZVP-rev2sulfur sources (16_S) mis-keyed the d-polarization SCAL value1.0as two tokens1 0(0 3 1 0.0 1 0, the decimal point typed as a space). The in-package parservibeqc.basis_crystal.parse_crystal_atom_basisread the header as exactly five tokens, so the stray0leaked into the primitive stream: it became the d-shell exponent and the real exponent (0.5207… for pob-TZVP, 0.4107… for pob-TZVP-rev2) the coefficient — a physically invalidexp=0Gaussian, produced silently with no error. Fixed the typo in both source files and hardened the parser: the shell-header read is now line-aware and tolerates the1 0→1.0typo exactly the way the canonicalscripts/basisset_dev/pob_basis_verify.pydoes (the two parsers had diverged), rejects any other stray header token, and rejects any primitive with a non-positive Gaussian exponent. Latent: the shippedpob-tzvp.g94was already correct (regenerated by the tolerant verify tool), so.g94source-vs-shipped parity is unchanged and no regeneration was needed — the bug bit only directvibeqc.basis_crystaluse or a re-fetch from the still-typo’d upstream archive. Regression tests added intests/test_basis_crystal.py. (1a68a7da)
Fixed — optimizer / NEB robustness (silent-flag / convergence-gate audit)¶
The 2026-05-31 end-to-end audit confirmed the geometry-optimizer and NEB physics is correct (geometries ≤2e-6 bohr vs geomeTRIC; thermo = PySCF; NEB barrier 8e-13 vs PySCF; DFT+U gradient 6.9e-9 vs FD). These four were silent-flag / robustness / edge bugs around it:
optimize_moleculereportedconverged=Trueat a non-stationary geometry. scipy’s L-BFGS-B setsres.successon either its gradient (gtol) or its energy-reduction (ftol) criterion, and the driver mappedconvergedstraight fromres.success. With a looseconv_tol_energytheftolstop fired while the forces were still large (e.g. H₂O/STO-3G,conv_tol_grad=1e-6,conv_tol_energy=1e-3→converged=Trueat max-force 0.023 Ha/bohr, >20000× the request). Convergence is now gated on the actual max-component force via a shared_gradient_convergedhelper, applied identically tooptimize_moleculeand the BIPOLE relaxers (relax_atoms,relax_cell_gradient).A non-converged NEB image aborted the band with a cryptic C++ error.
_evaluate_imageran the per-image SCF then unconditionally built the gradient, which raisedRuntimeError: ... not convergedfrom the C++ gradient code. It now raises a clear, image-namedvibeqc.neb.NEBImageSCFError(image index + geometry + remediation) and the same guard protects_evaluate_image_periodic.NEB cold-start used a weaker guess than the single-point drivers. Cold images seeded the low-level
*_scf_with_jkentry with an Hcore fallback, so they needed many more SCF iterations thanrun_uhf/run_rks(AUTO→SAD) and could hitmax_iterwhere an equivalent single point converges. Cold starts now seed SAD (closed- and open-shell), matching the public driver; warm-start (cached density) is unchanged.optimize_molecule(method="rks", functional=None)raised aTypeError. An operator-precedence bug in the SCF dispatch ranopts.functional = Noneagainst the default-"LDA"options, which the pybind str setter rejects. The guard is now parenthesised correctly.
Reported |grad| in the optimizer progress lines is now the max-component
(inf-norm) force, matching the gtol convergence metric (was the 2-norm).
Regression coverage: tests/test_optimize_neb_robustness.py.
Perf — vibe-view: mesh cache, glyph instancing, pre-rendered energy frames (A7)¶
Colormap and opacity sliders no longer re-run marching cubes (A7-02). The last-marched isosurface mesh is cached in
ViewerStatekeyed by(isovalue, replication); slider changes that don’t affect the mesh topology hit the cache and skip straight toplotter.add_meshwith updated appearance. Only an isovalue or replication change invalidates the cache. Cache is cleared automatically when a new file is loaded.Replicated periodic cells render with glyph instancing (A7-08). Previously a 3×3×3 NaCl supercell (54 atoms) issued 54 individual
plotter.add_meshcalls. The structure renderer now groups atoms by(element, radius)and issues one glyph call per element type — typically 2–10 draw calls regardless of supercell size. The molecular (no-replication) path keeps per-atom named actors so the charge-overlay and CPK-restore paths can address individual atoms by name.Trajectory / reaction-path energy plots are pre-rendered (A7-04). Previously every frame step called
matplotlibto re-render the full energy curve. All N frame variants (differing only in where the frame-indicator dot sits) are now rendered once at section load and cached; per-frame updates are O(1) base64 lookups.
Fixed — vibe-view audit: MO normalization, vibrations, plot units, state lifecycle¶
A full audit of the viewer (vibe-view/AUDIT_2026-05-31.md) fixed a cluster
of scientific-correctness and state bugs:
Molecular-orbital isosurfaces were shape-distorted. The on-the-fly GTO evaluator double-applied the spherical-harmonic normalization (AO self-overlap was
(2l+1)/4πinstead of 1), an l-dependent error that mis-scaled s/p/d/f contributions relative to each other. The Cartesian path was wrong in the opposite direction (x²self-overlap 2,x³6). Both now yield unit-normalized AOs; thewavefunction.gtore-evaluation now matches the storedvolume.orbitalcube. (renderers/wavefunction.py)Vibrations animated at the world origin. The writer hard-coded
position: [0,0,0]/atomic_number: 0for every atom in thevibrationssection, so the viewer collapsed the molecule onto the origin. The writer now emits the real equilibrium geometry + atomic numbers, and stores un-mass-weighted Cartesian displacements (was shipping the mass-weighted eigenvector, giving wrong relative atomic amplitudes). The viewer also falls back to thestructuresection’s geometry for older archives. (output/formats/qvf.py,renderers/vibrations.py)Unrestricted MO picker could render the wrong-spin orbital (alpha/beta both keyed on bare integer index); now keyed on a composite
spin:indexvalue with energy / occupation / HOMO-LUMO labels.DOS ignored the Fermi level (unreadable on absolute-energy output); now Fermi-references and draws an E_F marker when E_F is in range.
Band-structure axis was mislabeled “Energy (a.u.)” when
fermi == 0.0although eigenvalues are always eV; ECD/VCD spectra dropped every negative Cotton band (intensity > 1e-10→abs(...)).Heavy elements rendered as hot-pink with a flat radius (atomic number was re-derived from a symbol table that stopped at Ba, Z=56); now uses the parsed atomic number across all CPK render paths (colours reach Z=96).
Clipping a signed volume (
volume.difference/volume.spin) dropped the negative lobe — the clip path only contoured the +isovalue. Both lobes (blue +, red −) are now clipped and shown.Clip planes appeared to do nothing (live-review finding): enabling a clip built a clipped overlay but never hid the full isosurface, so the un-clipped volume stayed on top. Enabling clip now hides the full volume (showing only the clipped slice); disabling restores it; and an isovalue/colormap change while clipping re-applies the clip.
Clip
Enableswitch raised a server error on every toggle (found with a trusted-event Playwright pass): the switch passed its new value to a zero-argumenttoggle_cliphandler, which raisedTypeErrorserver-side — the flag flipped via the two-way binding so the switch looked toggled, but the clip never actually applied. The handler now accepts the value and sets (rather than re-flips) it, matching the other display switches, and reports “Clip plane enabled/disabled” in the status bar.Atom-charge labels lingered after leaving Atomic Properties (live-review finding): switching to a 2D side panel (citations / SCF history / symmetry / bands / DOS / spectra / NMR) removed the charge billboards server-side but never pushed the cleaned 3D scene, so with client-side VtkLocalView the stale labels stayed visible until a 3D section forced a re-sync. The section-switch dispatch now pushes the viewport for those 2D-panel kinds.
Periodic structures drew no bonds. Explicit
bonds-section connectivity is now rendered for periodic cells too (minimum-image endpoint, so a bond across a cell face renders short); inferred covalent-radius bonds stay molecular-only.Bands + DOS now render as a single figure with a shared energy axis (band structure left, DOS rotated right, one Fermi line) — the standard solid-state plot — instead of two independent side-by-side charts.
Bond order (the writer’s per-pair
order) was discarded, so double / aromatic bonds rendered as single. The reader now threads(i, j, order)and the structure renderer draws double/triple bonds as parallel cylinders.QVF spec:
wavefunction.gtonow officially allows periodic systems at the Γ-point (k=0) — the writer already emitted Γ-point crystalline orbitals for periodic runs while the spec forbade it. The spec is corrected (§ 4.6), and the writer records an optionalmo_metadata["k_point"]so the archive is self-describing. Full k-resolved Bloch wavefunctions stay out of scope.CI: a new
vibe-view-testjob runs the viewer suite (pip-only, under xvfb,allow_failure, scoped tovibe-view/**changes) so the 47 viewer test files are exercised in CI without bloating the nativebuild-testjob.Sidebar / panel labels (live-review polish): several kinds showed their raw kind string in the sidebar —
dos.projected, the ECD/VCD/generic spectra, and the spin/ELF/difference volumes had no friendly title, andspectra.uvviswas mis-keyed asspectra.uvso UV-Vis never matched. All now have titles (guarded by a completeness test). The bands/DOS bottom panel was hardcoded “Band Structure” even when showing DOS only; it’s now titled for what it shows (“Density of States” / “Projected DOS” / “Band Structure & DOS”).Multi-file mode broke the entire app bar. The Files dropdown bound the
_active_file_idxstate key, but Vue 3 refuses to expose_-prefixed identifiers to template expressions — so opening more than one file threwReferenceErroron every render and wiped out the app bar (Files dropdown + screenshot / export / camera / measure buttons). Renamed the key toactive_file_idx; a guard test now rejects any_-prefixed template-bound key. (Found by live-browser review — headless tests never render the Vue bindings.)Stale state across file/section switches: a running vibration animation, the MO picker, atom selections, clip planes, and the static-structure ghost during trajectory/vibration playback are now reset/managed correctly.
Reader resilience: a forward-compat/reserved section kind no longer rejects the whole archive (it opens, that section is skipped); the per-section / root
critical: trueflag is now enforced (refuse to open rather than silently render a partial view). Decoded binary members are cached so isovalue/clip sliders and animation playback stop re-reading + re-hashing the blob every tick.Banner no longer mislabels a consumed
bondssection as “skipped, unsupported”; a writer↔viewer kind-drift test now guards the registry.
Fixed — BIPOLE periodic gradient: production forces use FD; multi-k Pulay fold¶
The 2026-05-31 end-to-end audit found the analytic BIPOLE atomic
gradient (vibeqc.bipole_gradient.compute_bipole_gradient_{rhf,uhf,rks,uks})
is gauge-inconsistent with the BIPOLE energy and wrong even at Γ: the
energy is built in CRYSTAL’s Ewald gauge (E_nn via
ewald_nuclear_repulsion, screened-erfc + reciprocal-FT V_ne,
J = J_SR(ω) + J_LR(ω), post-SCF spheropole), but the analytic gradient
assembled truncated direct-space full-Coulomb E_nn/V_ne/J
derivatives (no J^LR-reciprocal or spheropole derivative). The residual
of those ~1.5 Ha/bohr terms had the wrong sign. Only the kinetic and
overlap (Pulay) terms were gauge-correct. (CLAUDE.md §7: a gradient that
disagrees with the exact reference is a bug, not a tolerance knob.)
Production forces / stress / relaxation now route through the exact finite-difference gradient.
vibeqc.bipole_optimize(relax_atoms,relax_cell_gradient,relax_full) and thereforerun_periodic_job(optimize=True)default toforce_mode="fd", which central-differences the real total energy (correct by construction; ~6N SCFs per gradient). This matches what the periodic NEB driver already does. Passforce_mode="analytic"only for research on the analytic gradient itself.compute_bipole_gradient_fdgeneralised to all four methods (method=+functional=), so the exact path is available for UHF/RKS/UKS, not just RHF.The analytic drivers are now gated as a research preview — they emit a
UserWarningand document the gauge incompleteness.Multi-k Pulay bug fixed. The energy-weighted-density (Pulay) overlap-derivative term broadcast
W(Γ)into every lattice cell (correct only at Γ; wrong-sign multi-k forces). It now uses the proper inverse-Bloch foldW(g) = Σ_k w_k Re[e^{-ik·g} W(k)](the same convention asreal_space_density_from_kpointsand the non-BIPOLE multi-k gradient), enabled by passingkmesh=.Tests tightened + expanded (
tests/test_bipole_gradient.py): the old<1.0 Ha/bohranalytic-vs-FD assertion is replaced by a fold equivalence unit test, per-method FD coverage (RHF/UHF/RKS/UKS), translational-invariance and multi-k step-stability checks (<1e-3), and anxfailtripwire pinning the analytic↔FD gauge gap.
A correct analytic BIPOLE gradient needs five new derivative kernels
(Ewald nuclear-repulsion, Ewald-V_ne reciprocal, screened J_SR,
J_LR reciprocal, spheropole) — a new-physics milestone proposed in
HANDOVER_GRADIENT_BUGS.md for the PBC/BIPOLE chat. Kernels 1–2 of 5
(+ the shared FT-gradient linchpin) have landed (2026-05-31):
ewald_nuclear_repulsion_gradient(new C++ incpp/src/ewald.cpp) differentiates the EwaldE_nn, FD-validated<1e-6Ha/bohr.The AO-pair Fourier-transform atomic-position derivative (
vibeqc._aopair_ft.*_grad*) — the linchpin reused by the reciprocal kernels — FD-validated to ~1e-10._v_ne_ewald_gradientdifferentiates the EwaldV_ne(erfcV_shortvia new C++nuclear_erfc_lattice_gradient_contribution, reciprocalV_longvia the linchpin + nuclear structure factor, andbackground·S), FD-validated to4e-10Ha/bohr at fixed density._j_long_range_ewald_gradientdifferentiates the long-range Hartree energy ½ Σ_K kernel(K)|ρ̂(K)|² (kernel 4), reusing the FT-gradient linchpin with the electronic structure factor; FD-validated to3e-13Ha/bohr.eri_lattice_gradient_contributiongainsj_scale(J-suppression → exchange-only) andomega(erfc-screened operator) params (kernel 3), so the Ewald-gauge 2e gradient is assembled as ∂J_SR + ∂J^LR − ½∂K_full. Bit-identical to the old call atj_scale=1, ω=0; the screened J_SR branch is FD-validated to3e-11.The spheropole (EXT EL-SPHEROPOLE) gradient (kernel 5): the C++
compute_ext_el_spheropole_gradient_latticedifferentiates the exact emultipole2 bond-symmetrised second momentK_μν = 2Tr⟨r²⟩ − 2(A+B)·⟨r⟩ + (|A|²+|B|²)⟨1⟩fully analytically — the explicit A/B coefficients plus the moment-integral derivatives ∂⟨r^n⟩/∂R from libintemultipole2deriv_order=1(requires libint built withLIBINT2_DISABLE_ONEBODY_PROPERTY_DERIVS=OFF) — and_spheropole_ewald_gradientscatters them onto each AO’s home atom; validated to ~1e-11 vs a central FD of the energy on MgO (s,p) and NiO (d). (This supersedes the earlier CJAT33 complex-step kernel, removed with the rest of the CJAT33 spheropole code.) The J^LR electron–electron jellium background derivative was corrected (Coulomb ½ factor; the oldV_bg·Sblock was −2× off), FD-validated (1e-12).
The full analytic BIPOLE RHF gradient at Γ now matches the exact FD
gradient to ~1e-7 Ha/bohr (test_analytic_gradient_matches_fd_gamma,
formerly xfail, now passing), with machine-precision translational
invariance. Beyond the five electrostatic kernels this required two
further fixes:
Local-energy Pulay. BIPOLE diagonalises the Bloch-summed
F(Γ)but evaluates the energy as a LOCAL home-cell contractionTr[D(0)·H(0)](the Γ-only_zero_cross_cell_densityprojection), so the energy-weighted density must use∂E/∂P(0)(the home-cell Fock the local energy differentiates), not themo_energies(_corrected_w_gamma_closed/_bipole_de_dp_home_block; the occ-virt block of ∂E/∂P vanishes, so no CPHF is needed).J^LR mixed convention. The SCF builds J^LR with
rho_hatfrom the Bloch k-density but contracts against the local density, soE_jlr = ½ Σ_K kernel·Re[ρ̂_bloch·ρ̂_local*](verified to 1e-16), not½|ρ̂_local|²; kernel 4 + the ∂E/∂P J^LR term adopt this (Γ-only).The spheropole energy was reimplemented exactly via libint
emultipole2(commitfcd16eb5); the spheropole gradient now tracks that energy (∂E/∂P spheropole analytic from the moments; the HF gradient by FD of the cheap post-SCF spheropole energy).
The analytic drivers stay gated as a research preview: UHF/RKS/UKS Pulay and multi-k are not yet converted to the local-energy ∂E/∂P scheme, and the path is uncertified. Production forces remain the exact FD path throughout.
Fixed — BIPOLE multipole interaction tensor: exact Cartesian derivatives (L≥2 convention)¶
The 2026-05-31 end-to-end audit flagged the BIPOLE multipole-far-field
interaction tensor (vibeqc.bipole_multipole.multipole_interaction_tensor)
as wrong for L ≥ 2, with three pure-Python tests red on main.
Root cause. The tensor scaled each Cartesian derivative
∂^γ(1/|R|)by a single per-LfactorK_L(a{0:1, 1:−½, 2:4⁄3, 3:−4}table). One scalar perLcannot simultaneously normalise the diagonal (∂_xx) and off-diagonal (∂_xz) derivatives, so the recovered derivatives were individually wrong (even∂_zwas off by 2×). The monopole–monopole and an x-dipole·z-dipole case came out right only because the spherical pseudoinverse happened to cancel the errors there; the quadrupole–monopole channel was off by a clean −1/3.Fix.
K_Lis replaced by the exact closed-form Cartesian derivatives of1/|R|for orders 0–3 (_cartesian_derivative_inv_r, the standard Coulomb-kernel gradients, Jackson §4.1). The proven Cartesian-Taylor → spherical pseudoinverse conversion is unchanged, so the result is now exact to the documentedL = l₁+l₂ ≤ 3truncation: dipole–dipole reproduces−2μ²/R³and quadrupole–monopole converges as(size/R)²with no systematic error.Scope. The multipole far-field is opt-in and off by default (
use_multipole_far_field=Falsein all fourrun_pbc_bipole_*drivers), so default-path SCF energies are unaffected; this corrects the experimental far-fieldJ-build (build_j_far_field_multipole) and restores a greenmain.Cleanup. Removed the dead, stale-convention
_wigner_3j_realhelper (no longer referenced since the Cartesian-Taylor rework) and itsscipy.special.factorialimport.Tests (
tests/test_bipole_multipole.py,tests/test_bipole_cell_moments.py). The dipole–dipole and quadrupole–monopole “calibrations” were both degenerate (zero energy by geometry, so a0 == 0check masked the bug); they are rebuilt as non-degenerate direct-Coulomb calibrations with a convergence check that distinguishes truncation from a systematic error.test_Z_1m_dipole_componentsupdated to the live Stone (√2) convention; the stalecartesian_component_indices(0)expectation updated (L=0is valid).
Fixed — BIPOLE EXT EL-SPHEROPOLE: exact bond-symmetrised second moment for all l¶
compute_ext_el_spheropole (vibeqc.bipole_ext_el_pole) is added to the
BIPOLE production E_total, so its accuracy matters for total energies and
CRYSTAL parity. The prior per-(l_a, l_b) CJAT33 C++ kernel
(cpp/src/spheropole.cpp) had two correctness gaps: its p-p term used a
hard-coded empirical fudge (7.2 · γ² · …, “calibrated on MgO/STO-3G”)
and its d/f shells fell back to the s-s formula.
Fix. The per-pair spheropole kernel is exactly the bond-symmetrised second moment
⟨φ_μ|(r−A)²+(r−B−g)²|φ_ν⟩, now computed directly — for arbitrary angular momentum — from libintemultipole2via the existingcompute_multipole_moments_lattice. The assembly is l-agnostic (no per-(l_a, l_b) cases), so s/p/d/f are all exact; the empirical fudge and the d/f fallback are gone. Prefactorπ·N_e/(6·V)(the Ewald reciprocal K→0 coefficient × CRYSTAL’sTOTCHR), pinned against the sealed CRYSTAL reference.Accuracy. Reproduces CRYSTAL’s sealed MgO/STO-3G CYC 0 spheropole (+4.1191890 Ha) to < 0.02 mHa, vs ~7 mHa for the old fudged p-p kernel — so MgO
E_totalmoves ~+7 mHa toward CRYSTAL. d-containing systems (previously s-s-fallback) are now correct.Implementation. Python-only — no C++ rebuild. The now-unused C++ kernel (
spheropole.cpp,compute_spheropole_kernel_lattice,compute_spheropole_bare_integrals) is left in place pending a clean-tree removal (its binding sits in a file under concurrent edit).Tests (
tests/test_bipole_ext_el_pole.py). MgO parity tightened from ±20 % to a tight bound the old fudge fails; added an NiO/STO-3G d-shell regression guard. (Note: NiO is not a CRYSTAL parity check — vibe-qc’s SAD fills Ni 3d¹⁰ vs CRYSTAL’s d⁸ initial occupation, so the input densities differ; the kernel is validated exact on MgO.)
Added — relaxed-scan visualization polish (writer + vibe-view)¶
Reaction-coordinate axis labels.
ScanResult.write_qvfnow records a human-readable coordinate label + unit (e.g.bond 0–1/bohr) on thereaction.pathsection, and vibe-view labels the energy-plot x-axis with them ({label} ({unit})). Additive, optional metadata — noqvf_versionbump; v1/v2 readers that don’t know the fields ignore them. Surfaced throughwrite_reaction_path_qvf(..., reaction_coordinate_label=, reaction_coordinate_unit=).Verified transition-state tagging.
ScanResult.write_qvf(..., transition_state_frames=[i, ...])tags caller-confirmed frames with atransition_statewaypoint (red TS marker) and suppresses the genericpointwaypoint at those frames. The energy maximum is never auto-promoted to a TS — a scan max is not a verified saddle (CLAUDE.md §7).
Added — per-frame densities along reaction paths (writer + vibe-view)¶
Animated density isosurface on
reaction.path.ScanResult.write_qvf(..., emit_volumes_every=N)attaches the electron density on a shared real-space grid for everyN-th scan frame; vibe-view’sReactionPathRenderermorphs the isosurface as the frame changes. Cubes are opt-in + decimated (one extra single-point SCF per emitted frame); the box auto-sizes to the union of emitted geometries. Molecular scans only — periodic raisesNotImplementedError.QVF schema (v1 + v2)
SectionReactionPathgains two optional members —frame_volumes(a newVolume4DBinary,[n_emitted, nx, ny, nz]) +volume_grid— plusvolume_frame_index/volume_label/volume_isovaluein the section metadata. Additive + optional; old readers ignore them, no archives invalidated. Low-level API:write_reaction_path_qvf(..., frame_volumes=, volume_grid=, volume_frame_index=, volume_label=, volume_isovalue=).Shared isosurface builder. The marching-cubes core was factored out of
VolumeRenderer.make_meshintovibeview.renderers.volume.build_isosurface_mesh(...), reused by the reaction-path per-frame overlay.
Added — 2D relaxed scans + scan.surface QVF kind (writer + vibe-view)¶
vibeqc.relaxed_scan_2d(...)drives two internal coordinates over a grid, relaxing all other DOFs at each node (warm-started row by row), and returns aScanResult2Dwith a[nA, nB]energy grid + per-node geometries. Both molecular and periodic.New
scan.surfaceQVF kind (registered in v1 + v2 schemas + vibe-view). Carriesaxis_a/axis_b(1D coordinate values), the 2Denergiesgrid, per-axis label + unit, and optional flattened per-nodegeometries. High-level writerwrite_scan_surface_qvf(...);ScanResult2D.write_qvf(..., include_geometries=).vibe-view
ScanSurfaceRendererrenders a filled-contour energy map (matplotlib) with the minimum starred, a current-node crosshair, and both axes labelled from the coordinate names/units; readerread_scan_surface→ScanSurfaceData; registered inkinds.py, the renderer map, and the app activation + title.
Fixed — vibe-view test coverage gap for basis.ao¶
test_all_supported_kinds_have_rendererslacked abasis.aofixture after that kind was added toSUPPORTED_KINDS(b9e97161); added one so the dispatch-coverage gate is green again.
Fixed — vibe-view variable-dim reaction-path wrapping¶
ReactionPathRenderernow wraps each frame by its own dimensionality via a newdim_for_frame(index)helper that honoursdim_per_frame. Previouslyget_framekeyed off the scalardimonly, so a variable-dim path (scalardimabsent) silently fell back todim=3and mis-wrapped slab frames’ vacuum axis. Latent until a variable-cell/variable-dim producer lands, but now correct.
Fixed: ωB97X-D citation now carries the Grimme-2006 DFT-D2 reference¶
The Chai-Head-Gordon dispersion kernel (
compute_chg_dispersion) uses the Grimme-2006 DFT-D2 atomic C6 / vdW-radius tables, but the citation database did not carry the origin paper, so a ωB97X-D job under-attributed its dispersion. Added[entries.grimme_dftd2_2006](S. Grimme, J. Comput. Chem. 27, 1787, 2006; DOI 10.1002/jcc.20495) and wired it intoroutes.methods."wb97x-d"alongside the existing Chai-Head-Gordon 2008 references. A ωB97X-D job now emits Chai-Head-Gordon 2008 plus Grimme-2006 D2 in its.bibtex/.referencessidecars and the.outreferences block; the.systemmanifest carries all three.tests/test_citations.pycoverage was extended to pin both keys in the assembled citations and the.bibtexbody. (2f577cf5)
Fixed — periodic infrastructure (end-to-end audit, 2026-05-30)¶
KPoints.auto(length)generated ~1.76× too-dense meshes per axis (≈5.4× too many k-points): the bohr⁻¹→Å⁻¹ conversion kept the 2π factor, soauto(30)on Si primitive returned 18×18×18 instead of the intended ~11×11×11. Now consistent per-axis withfrom_kspacing(2π/length)(VASP’s Auto↔KSPACING equivalence). Pure over-spend of compute — not wrong physics — butauto()is an advertised entry point.madelung_constant_for_cellnow refuses dim<3. It is a 3D Ewald self-energy (3D neutralising background); for 1D/2D cells it silently returned a meaningless negative value that fed into theexxdiv='ewald'K-shift. Now raisesNotImplementedError, mirroring the existingrun_pbc_gdf_rhfand C++compute_nuclear_lattice_ewalddim guards. Defaultexxdiv='none'low-dim cells are unaffected.run_krhf_periodic_gdfnow RAISES on a converged non-physical energy instead of silently returning it. The multi-k compcell GDF path converges to garbage on tight ionic crystals (LiH primitive FCC at kmesh=(2,2,2): −2495 Ha, or +8.485 Ha withapply_aft_correction=True, vs PySCF −7.92 Ha) — the multi-k parity fix is the GDF chat’s open M2 milestone. Until it lands, a post-SCF energy-sanity guard rejects both the runaway-negative (|E| > max(10·ΣZ², 100)Ha) and the positive/unbound (Ebeyond a ~1 Ha bound-cell slack) signatures (CLAUDE.md §7: impossible energy = bug — surface it, don’t paper over with damping/thresholds). The slack lets cramped Bravais smoke-test cells through;check_energy_sanity=Falsebypasses the guard for parity/debug scripts. The working periodic-GDF path remains the Γ-onlyrun_pbc_gdf_rhf(gdf_method='rsgdf').The four
run_pbc_bipole_*drivers no longer crash on dim<3. The documented “direct-only path for dim<3 diagnostic runs” was killed by two bugs:compute_ext_el_spheropole(a 3D-Ewald reciprocal-space K=0-limit correction that raises for dim≠3) was called unconditionally from all four drivers, and RKS/UHF/UKS lefte_j_multipoleunbound in the direct (non-Ewald-split) Fock branch, raisingUnboundLocalErrorbefore the spheropole was even reached (RHF already initialised it). The spheropole call is nowdim==3-guarded — the term is identically zero in the direct gauge, soe_ext_el_spheropoleisNonefor 1D/2D — ande_j_multipoleis initialised on every branch. The dim<3 energy is the direct-truncated value, verified vacuum-independent and equal to the molecular RHF energy in the isolated-cell limit (the audit’s 1D/2D correctness criterion).use_ewald_j_split=Truestays explicitly rejected for dim<3. Orthogonal to the open BIPOLE L_max≥2 + Madelung-Ewald work.Non-converged UKS BIPOLE now reports the spheropole.
run_pbc_bipole_uksomittede_ext_el_spheropolefrom its per-iterationPBCBipoleEnergyComponentsand hard-codedE_sphero_final = Nonein its non-converged post-loop branch, so amax_iter-capped 3D UKS run returnedresult.e_ext_el_spheropole = Noneeven though the term was already folded intoE_total(RHF/RKS/UHF all reported it; ~0.003768 Ha on H₂/STO-3G/8-bohr). Both sites now mirror the RKS/UHF siblings;Nonestays correct for dim<3. Regression:tests/test_pbc_bipole_uks.py.Regression gates for the k-mesh and Madelung fixes, plus the multi-k- GDF guard (it must raise on the LiH catastrophe, not return it), in
tests/test_audit_20260530_periodic.py; the BIPOLE dim<3 path (all four drivers, vacuum-independence, isolated-molecule limit) intests/test_pbc_bipole_dim_lt3.py.
Added: CASSCF (orbital-optimized CASCI)¶
New method="casscf" and the low-level vibeqc.solvers.casscf: complete
active space SCF, optimizing the active-space CI vector together with the
molecular orbitals. Built on the validated CASCI + RDM engine; a two-step
macro-iteration rotates the inactive/active/virtual orbitals to a stationary
point using the non-symmetric generalized-Fock orbital gradient
g_pq = 2(F_qp − F_pq) (validated elementwise against finite differences of
the CASCI energy) with a backtracked augmented-Hessian step. Matches PySCF
mcscf.CASSCF to ≤1e-7 Ha on unique-minimum systems (H₂, LiH, HF, Be); the
variational sandwich E_FCI ≤ E_CASSCF ≤ E_CASCI and a vanishing orbital
gradient are the basis-independent correctness checks. As with every CASSCF
the energy surface is non-convex, so the optimizer reaches the stationary
point in the basin of the starting HF orbitals. NEVPT2 / CASPT2 can run on the
CASSCF reference. Ungated (validated vs PySCF). §8 citation route to
Roos/Taylor/Siegbahn 1980. For small active spaces + bases. See
docs/handover_multiref.md.
Fixed — multireference methods CASCI / NEVPT2 / CASPT2 (audit 2026-05-30)¶
The CASCI, NEVPT2 and CASPT2 routines that landed in 0c8f0fcf were
incorrect (CASCI ≈ −104 Ha and crashing for >1 same-spin electron; NEVPT2
wrong-sign; CASPT2 ≡ 0) and are rebuilt + validated against PySCF
out-of-process:
CASCI — rebuilt on the validated unrestricted Slater–Condon FCI engine with a correct frozen-core dressing. Matches PySCF
mcscf.CASCIto ≤1e-9 Ha (H₂O CAS(2,2)/(4,4)/(4,6), H₂, LiH); full active space reproduces FCI.NEVPT2 — strongly-contracted NEVPT2 (Angeli 2001) computed from the perturber-projection definition. Matches PySCF
mrpt.NEVPTper excitation class to ≤5e-9 Ha.CASPT2 — strongly-contracted, generalized-Fock H₀ (Andersson 1990). Its MP2 limit and full-space→0 limit are validated; the active-space terms have no local reference (PySCF ships no CASPT2), so CASPT2 is gated behind
VIBEQC_EXPERIMENTAL_MULTIREF=1pending OpenMolcas/ORCA validation.
All three are available via run_job(method="casci"/"nevpt2"/"caspt2", active_space=(n_orb, n_elec)) and the low-level vibeqc.solvers API, with a
new validated reduced-density-matrix module (solvers._rdm) and §8 citation
routes (Roos 1980 / Angeli 2001 / Andersson 1990). For small active spaces +
bases (the spin-orbital engine scales steeply). See
docs/handover_multiref.md.
Fixed — GFN2-xTB gated experimental + structural correctness (audit 2026-05-31)¶
The 2026-05-31 end-to-end audit found the molecular GFN2-xTB method
(run_job(method="gfn2_xtb"), shipped v0.10.0) returned silently-wrong
results: H₂ at +0.106 Ha (positive total energy), H₂O at −103.5 Ha (~20×
over-bound; xtb ref ≈ −5.07 Eh), wrong-sign Mulliken charges (O came out
+1.37), and it was not size-consistent — two H₂ 40 bohr apart over-counted
by +0.32 Ha vs 2×E(H₂). Root causes, all fixed:
Repulsion had the wrong functional form.
k_ab·exp(−α·R)— withαtaken from the chemical hardness and an arbitrary0.1scaling — decayed far too slowly (~0.08 Ha per H–H pair still left at 40 bohr), which was the size-inconsistency. Replaced with the GFN2 form(Zᴬ_eff·Zᴮ_eff/R)·exp(−√(αᴬ·αᴮ)·R^{3/2})(Bannwarth-Ehlert-Grimme 2019, Eq. 6), wired to the actual per-elementREPA(α)/REPB(Z_eff) that were previously parsed and discarded.Core/valence confusion. The driver filled the valence basis using the all-electron count (
mol.n_electrons()) and referenced charges against the shell capacity (2 for s, 6 for p). Now uses the neutral-atom valence electron count (Z − noble-gas core) with correct per-shell reference occupations; the global mean-subtraction that coupled non-interacting fragments is removed (Σ Δq = 0 now holds by construction).Energy double-counting in the shell-resolved 2nd-order electrostatics is corrected (
E = Σ 2εᵢ − ½ Δq_shell·γ_shell·Δq_shell + E_rep), and the SCC potential sign convention (Δq = pop − n0, Elstner 1998) is fixed — the pre-fixn0 − popran the SCC the wrong way.PBE-D4 dispersion bolt-on removed.
gfn2.pyhad addedcompute_d4_energy_total(..., functional="pbe")on top of the SCC energy, double-counting dispersion with the wrong damping; GFN2’s own self-consistent D4 is part of the deferred full-method work.
After the fix GFN2-xTB is size-consistent (E(2 far)−2·E(1) < 1e-9, with
the residual water-dimer interaction decaying as the physical 1/R³ dipole
tail) and has correct Mulliken charge signs (O⁻/H⁺, C⁺/O⁻). It is still
not quantitative, for two reasons: the GFN2-defining physics — anisotropic
electrostatics (AES), the 3rd-order on-site term, self-consistent D4 — is not
implemented, and a separate, pre-existing bug in the GFN2 H⁰/overlap
(build_gfn2_hamiltonian_zero + the STO-6G overlap) produces spurious deep
occupied eigenvalues whenever more than the s shell is filled, so absolute
energies run ~20× too deep (an isolated closed-shell Ne atom, Δq=0, comes out
at −4974 Ha with four −621 Ha occupied states instead of ~−5 Ha; H₂O at
−99.7 Ha). That deep-state bug is diagnosed but not fixed here — it is
H⁰/basis-reimplementation work, tracked in HANDOVER_SEMIEMPIRICAL.md.
GFN2-xTB is therefore now gated experimental: every evaluation emits a
GFN2ExperimentalWarning (the run_job path included) so an advertised method
no longer returns wrong numbers silently (CLAUDE.md §14), and the §8
Bannwarth-2019 citation + route are wired. The runner now reports the real SCC
iteration count (it previously hard-coded n_iter=1, the source of the
“converged in 1 iterations” log). The unrestricted (run_ugfn2_xtb) and
periodic (run_gfn2_xtb_gamma) variants share the model gaps and the
deep-state bug and are not in the run_job path. Regression gates (size
consistency, charge signs, repulsion decay, the experimental gate; quantitative
targets xfail): tests/test_gfn2_xtb.py. Patch-candidate: v0.10.x.
Fixed — GPW periodic density collocation: periodic-image AO sum (audit 2026-05-31)¶
collocate_density_on_grid (and the Hartree-J builder it backs) evaluated the
molecular AO accessor on the real-space FFT grid with no periodic-image
summation, so an AO whose centre sits near a cell face leaked its Gaussian tail
out of the box and the collocated density under-integrated. H₂/STO-3G in a
10-bohr cell with the atoms on the corner gave ∫ρ dV = 0.41 e against
tr(D·S) = 2 e (atoms at the centre were fine at 2 e). Every GPW density /
cube / ELF / QVF surface for a structure with atoms near a cell edge was wrong
by that leak, and the docstring’s “integrates to tr(D·S)” claim held only for
centred atoms (CLAUDE.md §7 — a source bug, not a quadrature problem). Root
cause: a divergent re-implementation — the periodic stack already had the
correct image-summed evaluator (vibeqc.ewald_j.evaluate_ao_periodic, the
v0.6.x translation-invariance fix), which the M2 GPW module never used.
Fix (python/vibeqc/periodic_gapw_j.py): a new _ao_values_on_grid helper
sums the AO over the 3³ nearest periodic images
(χ_μ(r) = Σ_R χ_μ^mol(r − R), _AO_IMAGE_RADIUS = 1) by shifting the sample
points along the grid lattice — the grid-native twin of evaluate_ao_periodic,
needing only the lattice (no PeriodicSystem). All three AO-on-grid sites now
route through it: collocate_density_on_grid (density/cube/ELF/QVF),
project_potential_to_ao (V→AO), and GpwJBuilder._chi (the cached χ that
build_J uses for both its density collocation and its projection) — so the
Hartree-J build no longer leaks charge for off-centre atoms either. After the
fix the corner density integrates to 2 e, and both ∫ρ and the GPW Hartree
energy ½tr(D·J) are translation-invariant to grid-quadrature noise (~1e-8).
No-op for centred atoms in a vacuum-padded cell (the image sum is
∝ exp(−α·L²)), so the existing GPW suite is unchanged. New regression tests in
tests/test_periodic_gapw_j.py:
test_collocate_density_corner_atoms_integrate_to_n,
test_collocate_density_is_translation_invariant,
test_gpw_hartree_energy_is_translation_invariant.
Fixed — multi-k GPW energy breakdown sums to the total (audit 2026-05-31)¶
The second of the two v0.12.x GPW audit findings (the collocation fix above
is the first). run_periodic_rks_gpw_multi_k reported the correct
result.energy but rebuilt result.breakdown by re-evaluating the kinetic +
nuclear-attraction terms at Γ (k = 0) only — tr(D_total · T^Γ) — dropping the
per-k Bloch phases. breakdown.e_total then disagreed with result.energy by
tens of mHa on a dispersive mesh (−60.8 mHa for [2,1,1] H₂/STO-3G in a
6-bohr cell), and the wrong decomposition was serialised into restarts. The
breakdown is now accumulated from the per-k Bloch-summed Hcore(k) — the same
sum that yields the reported total — so breakdown.e_total == result.energy.
Experimental jk_method="gpw" path; molecular all-electron path unaffected.
Regression test (tests/test_periodic_gapw_j.py):
test_multi_k_gpw_breakdown_matches_total.
Fixed — periodic job crashed on any artifact-writer failure (UnboundLocalError); molden non-ASCII hardening (audit 2026-06-01)¶
run_periodic_job carried a redundant local re-import of OutputFailureKind
/ warn_output_failure inside one late except branch
(from vibeqc.output.errors import …). Because Python binds a name
function-local if it is assigned anywhere in the body, that one import
shadowed the correct module-level import (from .output._errors import …)
across the entire function — so every earlier warn_output_failure(...)
call (the molden / extended-XYZ / POSCAR / CIF / XSF / population / density /
manifest error handlers) raised UnboundLocalError: cannot access local variable 'warn_output_failure' before that late import line ran. A failure
in any optional-artifact writer therefore crashed the whole periodic job
after the SCF had already converged, instead of emitting the intended
graceful vibe-qc output [optional_artifact] warning and continuing. The
shadowing import was itself broken — vibeqc.output.errors does not exist
(the module is vibeqc.output._errors, with the underscore), so the branch
would have raised ModuleNotFoundError even if reached. Fix: delete the
redundant local import; the module-level import already binds both names for
the whole function.
Concrete real-world trigger: a non-ASCII output= stem. The molden writer
opened its file encoding="ascii" and wrote the user-controlled [Title]
(the output basename), so an accented stem (e.g. output="café") raised
UnicodeEncodeError mid-write → the molden error handler fired →
UnboundLocalError → the job died. Molden is now opened encoding="utf-8"
and its [Title] is scrubbed through
vibeqc.output._text_safety.scrub_output_text — the same Trojan-Source
hardening v0.10.4 shipped for the Gaussian-cube header and the TOML manifests
(see the v0.10.4 Security entry below). This is the molden sink that v0.10.4’s
audit deferred as “reachable only via direct Python calls”: in fact both
run_job and run_periodic_job feed the user-facing output basename into the
molden title, so the widening to UTF-8 is paired with scrubbing to keep a
bidi / control byte from leaking into the file. A non-ASCII periodic job now
writes the molden artifact (UTF-8, accent preserved) instead of crashing or
skipping it. Regression tests:
tests/test_output_errors.py::test_periodic_scf_survives_writer_failure (the
UnboundLocalError reproducer) and two new molden pins in
tests/test_output_control_char_hardening.py
(test_molden_header_scrubs_tainted_title,
test_molden_file_accepts_accented_title_as_utf8). Patch-candidate for
v0.10.x.
[v0.10.5] — 2026-06-01¶
Correctness patch on Pisani’s Penguin — composite -3c method
total energies. One cherry-picked fix from main; no feature
changes. Inherits the v0.10.0 codename.
Fixed — composite “-3c” method total energies (audit 2026-05-31)¶
The composite -3c keywords gave wrong total energies (live in v0.10.3).
All fixes are validated against the grimme-lab/gcp (mctc-gcp) Fortran
reference run out-of-process (CLAUDE.md § 10). The SCF + arithmetic was
correct; the bugs were routing and the gCP/SRB correction terms.
hf-3cran FCI instead of HF. Pure-HF composites (functional is None) routed through the wavefunction auto-ladder (≤ 4 e⁻ → FCI), so a turnkeyhf-3con H₂ silently ranfci(ndet=4). Pure-HF composites now resolve torhf/uhf. (F1.1)D3-BJ was dropped from the composite total whenever the SCF returned a
SolverResult; the total now reads the D3-BJ term from a local rather than off the (unwrapped) result. (F1.2)wb97x-3cdowngradedRUNNABLE→PENDING_ECP. vDZP is a valence-only basis whose small-core ECPs need the libecpint inline-primitive feed (Phase 14g, not onmain); it was silently running all-electron (≈ −35.9 Ha on H₂O vs ≈ −76.4). It now raisesCompositeUnavailable.hse-3c(all-electron def2-mSVP) is unaffected and staysRUNNABLE. (F1.3)Short-range bond (SRB) correction rewritten to the canonical kind-discriminated HF-3c / B97-3c forms (
gcp.f90basegrad/srb_egrad2) using the bundled DFT-D3 r0ab radii (newvibeqc._d3_r0ab, extracted byscripts/extract_d3_r0ab.py). The previous single form was ≈ 2× too binding (−52 vs −22.2 kcal/mol SRB on H₂O for HF-3c). Matches mctc-gcp to < 1e-9 Ha. (F1.4)r2scan-3cgCP corrected from +10.18 to +1.12 kcal/mol on H₂O. Thedef2-mtzvppn_virtwere the generic basis-function counts, not the r²SCAN-3c-specific virtual counts, and the gCP damping term (gcp.f90damp=.true.) was absent. Added the damped gCP (opt-indamping=oncompute_gcp; recipe-sideGCPDamping) and correctedn_virtfor def2-mTZVPP / def2-mSVP / MINIX. B97-3c’s spurious gCP removed — it is SRB-only (mctc-gcp computes SRB-only forb973c). (F1.5)
PBEh-3c / B97-3c / B3LYP-3c / r²SCAN-3c / HSE-3c composite totals
shift by the corrected gCP/SRB terms; wb97x-3c now raises instead of
returning a wrong number. Adds tight-tolerance per-composite component +
total-assembly regression tests. (Cherry-picked 000c7f68.)
[v0.10.4] — 2026-06-01¶
Security + correctness patch on Pisani’s Penguin — two cherry-picked
fixes from main, no feature changes. Inherits the v0.10.0 codename.
Security — output writers neutralise bidi / zero-width / control characters (audit 2026-06-01)¶
User-facing output writers interpolated user-controlled free text — the
run_job(output=...) basename, the basis / functional / method
names, SCF exception messages — verbatim into file headers and the
hand-rolled .system / crash-.dump TOML manifests. A bidirectional
format control such as U+202E RIGHT-TO-LEFT OVERRIDE written raw
silently reverses how the file renders in terminals, editors, grep /
diff viewers, and log-parsing agents — the “Trojan Source” display-
deception class (CVE-2021-42574 / CWE-150). The manifests’ hand-rolled
TOML quoter escaped C0 controls (ord < 0x20) but let every codepoint
≥ 0x20 through, so the bidi (U+202A–E, U+2066–9), zero-width (U+200B–F),
BOM (U+FEFF) and C1 (U+0080–9F) classes passed unescaped; the
Gaussian-cube header wrote its title line with no quoting at all. An
audit of all ten output writers confirmed three sinks reachable from the
public run_job surface — the cube header, the .system manifest, and
the crash .dump; the remaining writers’ free-text parameters (QVF
labels, molden / xyz / POSCAR comments) were deferred — see the follow-up
entry below, which corrects this note: several are in fact reachable via
run_job / run_periodic_job. New shared
helper vibeqc.output._text_safety defines the dangerous-character class
once and provides toml_escape_str (escapes it as lossless \uXXXX —
tomllib round-trips the value back) and scrub_output_text (visible
\uXXXX tokens for the escape-less cube header); the three manifest
_toml_str emitters now delegate to it and the cube writer scrubs its
title / comment. Not a memory-safety / RCE issue — it matters mainly for
multi-tenant vq queues, where one user’s job spec is read back in
another’s output. Regression test:
tests/test_output_control_char_hardening.py. (Cherry-picked
6267edcd.)
Security — periodic-structure + trajectory writers scrub user comments (2026-06-01 audit follow-up)¶
Completes the output-writer coverage from the audit above. The cube /
.system / crash-.dump sinks were hardened in 6267edcd; molden was
hardened alongside a periodic crash fix (Fixed entry above). This lands the
remainder: the POSCAR, CIF, XYZ, extended-XYZ and
multi-frame XYZ trajectory writers now scrub their free-text comment /
header lines through vibeqc.output._text_safety.scrub_output_text. QVF
was initially believed safe (JSON serialisation) — that call was wrong (it
uses ensure_ascii=False); QVF is hardened in the follow-up entry below.
Reachability correction: the original audit deferred these as “reachable
only via direct Python calls”. That was an under-call (the molden fix found
the same). run_periodic_job embeds the functional and basis name —
both user-controlled run_* arguments — into the POSCAR and CIF comment
lines (periodic_runner.py), so those two are genuinely user-reachable, the
same Trojan-Source display-deception class (CWE-150) as the cube header. The
XYZ / extended-XYZ / trajectory comment parameters are not currently routed
from user input by the runners, but are hardened for completeness so a future
wiring cannot regress. Low severity; no API change. Regression tests added in
tests/test_output_control_char_hardening.py (test_xyz_comment_scrubbed,
test_extended_xyz_comment_scrubbed, test_poscar_comment_scrubbed,
test_cif_comment_scrubbed, test_trajectory_comment_scrubbed).
Security — QVF archive neutralises bidi / control characters in JSON members + member names (2026-06-02)¶
Closes the last output-writer gap from the 2026-06-01 audit, and corrects an
earlier wrong call: the QVF (.qvf) zip writer was assumed safe because it
serialises metadata as JSON — but every json.dumps used ensure_ascii=False
(28 sites). JSON only force-escapes C0 controls, so the bidi (U+202A–E,
U+2066–9), zero-width (U+200B–F), BOM (U+FEFF) and C1 (U+0080–9F) classes in
user-controlled labels / descriptions / coordinate units / provenance
(method / functional / basis) were written raw into the archive’s
JSON members — the same Trojan-Source display-deception class (CWE-150) as the
other writers, reachable via write_qvf(...) provenance and the periodic
runners. Separately, the generic-spectra section_id was interpolated into a
zip member path (spectra/{section_id}.json) without the _slug() pass
the volume labels use — a bidi and path-traversal gap.
Fix: a new vibeqc.output._text_safety.safe_json_bytes(obj, *, indent=)
serialises with ensure_ascii=False (legitimate non-ASCII — accents, Greek —
stays human-readable) but re-escapes only the dangerous class to a visible
\uXXXX token; all 28 qvf json.dumps sites route through it, and
section_id is now slugged. Volume / orbital labels were already slug-safe in
member names. Low severity; output bytes are unchanged for payloads without
dangerous characters, so existing QVF round-trip tests are unaffected.
Regression tests: test_safe_json_bytes_neutralises_and_round_trips
(tests/test_output_control_char_hardening.py) and
TestProvenance::test_bidi_control_chars_neutralised_in_archive
(tests/test_qvf_writer.py).
Fixed — periodic gauge consistency (audit F1/F3)¶
Multi-k Ewald / Γ-GDF gauge mismatch returned
converged=Trueat a non-physical energy (CLAUDE.md § 7).run_rhf_periodic_multi_k_ewald3dhard-codes the Hartree J to the Ewald-3D builder but readV_ne/e_nucfromlat_opts.coulomb_method; a defaultPeriodicRHFOptions()(DIRECT_TRUNCATED) paired bare-gaugeV_ne/e_nucwith the Ewald-3D J, so the Madelung / charged-cell self- energy did not cancel — LiH FCC primitive at Γ converged to −264.85 Ha (gauge-consistent: −7.52 Ha) with no warning. F1 forces EWALD_3D at entry with an INFO-log override; F3 matches the Γ-GDF molecular-limit / dim<3 nuclear cutoff to the integral cutoff. Only direct callers of the non-dispatcher drivers were exposed. (Cherry- pickeda261613a.)
Not in v0.10.4¶
The remaining 2026-05-31 audit fixes — the BIPOLE analytic-gradient
rebuild, GFN2-xTB experimental gating, BIPOLE dim<3 guards, the GDF
Γ-only µHa V_long fix, and the CASCI / NEVPT2 / CASPT2 rebuild — are
entangled with the post-v0.10.2 periodic / BIPOLE / GDF feature work
and do not cherry-pick cleanly onto the v0.10.3 base. They ride
v0.11.0 (Sun’s Stingray). The two hard v0.11.0 gates remain open:
multi-k GDF µHa parity and the F2 charged-cell analytic-FT Hartree J.
[v0.10.3] — 2026-05-30¶
Docs-only patch on Pisani’s Penguin — release-process and contributor-discipline updates targeting the v0.11.0-prep audit cycle. Inherits the v0.10.0 codename. No code changes.
Added — contributor discipline¶
CLAUDE.md § 8 — inline formula + reference-value comments in code. New standing rule: when a chat implements math from a published paper, the formula and any published reference values used for validation go into inline comments at the relevant code sites. The maintainer keeps PDFs of cited papers in a gitignored
references/folder; the inline comments are the only public-facing record of what equation the code implements and how anyone can verify it without leaving the source file. Targets the v0.11.0-prep audit by external (including LLM-based) reviewers. (Cherry-picked657a1644.)
Fixed — release-process playbook¶
docs/release_process.md— CHANGELOG carry-back step explicit. Closes a long-standing gap in the tag-day playbook: the patch-release flow tagged the new dated CHANGELOG section onto the hotfix branch (which became release tip) but never carried it back to main, leaving main’s CHANGELOG drifted by one section per patch cut. The v0.9.0 → v0.10.2 five-release reconciliation on 2026-05-29 traced to this missing step. Two playbook updates:Minor release § 2 (“Bump the version”) now explicitly bundles the
[Unreleased] → [vX.Y.Z]CHANGELOG promotionbanner
RELEASE_CODENAMES+CITATION.cff+docs/citing.mdedits in therelease: vX.Y.Zcommit.
Patch release § 8 is now “carry the new
[vX.Y.Z+1]dated section back to main” with the explicit mechanical recipe; steps 9-11 renumbered. (Cherry-picked7ad77f10.)
Not in v0.10.3¶
The CHANGELOG reconciliation + trim on main (ce8e8785 +
55bb61f3) is deliberately not in v0.10.3 — those edits
were constructed against main’s pre-reconciliation state and
the cherry-pick would conflict. Release’s CHANGELOG carries
the same [v0.9.0] / [v0.9.1] unpromoted-bucket gap until
v0.11.0’s normal cut flow (now governed by the carry-back
playbook from §1 above) lands a clean CHANGELOG on release at
tag-day.
The installation.md banner-sample refresh (0737543f) is
also not in v0.10.3 — it’s cosmetic and version-numbered for
the v0.10.2 → v0.11.0 transition; rides v0.11.0.
[v0.10.2] — 2026-05-29¶
Patch release on top of Pisani’s Penguin — curated fixes only. Inherits the v0.10.0 codename.
Fixed¶
BIPOLE multipole far-field — nuclear-charge compensation added; multi-k + UHF tests pinned (
e18e8f7e).BIPOLE
use_multipole_far_fielddefault flipped fromNonetoFalseso default behaviour is explicit (584679df).Semiempirical periodic OMx parity gap closed via Loewdin eigenvalue flooring (
3152672a). The molecular ↔ periodic agreement now matches on the regression suite.GFN2-xTB default
max_iterraised 100 → 200 to fix H₂O / CH₄ non-convergence in default-options runs (695bc7be).
Not in v0.10.2¶
Three further v0.10.0 follow-up commits hit cherry-pick conflicts on the v0.10.1 hotfix branch and are deferred to v0.10.3 or v0.11.0:
d7f4b3bd fix(madelung): proper Ewald sum replaces cubic Wigner shortcut — LiH closes to 2 mHa(substantial physics fix; conflicted ontests/test_pbc_gdf_compcell.py).ebe6ca95 fix(bipole): multipole L_max≥2 spherical conversion + RKS XC fixes + multi-iter test(conflicted onpython/vibeqc/pbc_bipole_rks.pyagainst the earlier-picked BIPOLE fixes).f6e01bbe docs(v0.9.0): catch user-guide + API ref up to recent landings(same CHANGELOG + gapw conflict as v0.10.1 deferral).
The post-v0.10.0 GDF chemical-accuracy work (018784c0
all-FT-Bloch path for RSGDF + c949d748 modrho convention + the
M1 investigation diagnostics) is deliberately not in v0.10.2
— that is the v0.11.0 (next minor) headline scope, not patch-grade
material. DFT+U Increment 3 (analytic gradient — 27afac4c) and
GPW M3b (68f986ee + companions) likewise stay queued for
v0.11.0.
[v0.10.1] — 2026-05-29¶
Patch release on top of Pisani’s Penguin. Documentation-only — no code changes. Inherits the v0.10.0 codename.
Fixed¶
Landing-page admonition refreshed —
docs/index.mdwas still surfacing the v0.9.0 “Knowles’s Kingfisher” headline block at v0.10.0 tag time; swapped to v0.10.0 Pisani’s Penguin per the prep doc’s pre-staged Block 2 (fallback variant, since periodic GDF is deferred to v0.11.0).
Added — documentation¶
DFT+U user-guide page (
docs/user_guide/dft_plus_u.md) with worked examples covering the molecular RHF/UHF/RKS/UKS + periodic Γ-only RHF/RKS surface that shipped in v0.10.0 (Increments 1–2 + 4a/4b/4d-light).GAPW user-guide updates — runner dispatch + the M3b GAPW path added in v0.10.0.
User-guide + API-reference catch-up to the v0.9.0 surface, the long-deferred docs-sprint sweep that didn’t run after the v0.9.0 / v0.9.1 cuts (item #22 in the post-tag docs queue — partial close; the CHANGELOG carry-forward gap stays on the queue for v0.10.2 or later).
from_*crosswalk tables brought up to the v0.9.0 surface.API reference index expanded to cover the v0.9.0 surface.
Not in v0.10.1¶
The post-v0.10.0 code fixes that landed on main (semiempirical
OMx periodic parity, BIPOLE multipole far-field, Madelung Ewald
sum proper, GDF chemical-accuracy improvements, DFT+U Increment 3
analytic gradient, GPW M3b) are not in v0.10.1 — they ride
v0.10.2 (or v0.11.0). v0.10.1 is a strict docs-only patch to
correct the stale landing-page surface; mixing in code fixes
would have made it a fix-grade patch rather than a docs-grade
one.
[v0.10.0] — 2026-05-27 — Pisani’s Penguin¶
Periodic-SCF maturity: multi-k EDIIS + Python accelerator family;
SYM6 crystal symmetry (full character-tables + M3b per-cell-pair J/K
kernel); BIPOLE L_max≥2 Cartesian→spherical multipole conversion;
GPW M2-full iterative SCF; CRYSTAL-α / Ewald-ω gauge unification (the
cluster-A1 fix at 859efe0a resolves +19 Ha translation-invariance
violation on LiH rocksalt by enforcing EWALD_3D and matching
CRYSTAL’s 2.8/V^(1/3) α formula); end-to-end CRYSTAL parity test
suite (P1–P5 at 5231ce6c).
Semiempirical methods diversification: GFN2-xTB (full stack: parameter parser/fetcher, Fock builder, SCC driver, analytic gradient, periodic Γ, UGFN2-xTB, periodic gradient + stress); PM6 (NDDO Fock builder, STO-overlap resonance, FD gradient, MOPAC-matching core-core damping); OM1/OM2/OM3 (skeletons, parameter loaders, Loewdin-correction Fock builder, 77-test benchmark suite); architectural Phase-0 framework — method registry
shared parameter interfaces + Hamiltonian builders +
CoreParameterSetbasis builder.
DFT+U (Hubbard correction, Dudarev convention) — Increments 1, 2,
4a/4b/4d-light: occupation-matrix machinery; C++ Eigen kernel +
pybind11 surface; RHF/RKS/UHF/UKS molecular SCF integration; periodic
Γ-only RHF + RKS; run_periodic_job(..., dft_plus_u=[HubbardSite(...)])
high-level surface. Increment 3 (analytic gradient) is queued for
v0.10.x — see Known limitations.
Nudged Elastic Band (NEB) — M1–M4 — path primitives (interpolate_linear + IDPP) → improved-tangent driver with joblib per-image SCFs → climbing-image NEB (Henkelman + Uberuaga + Jónsson 2000) → periodic dispatch via BIPOLE + FD gradient. Density warm- start across RHF/UHF/RKS/UKS drivers; end-to-end H + H₂ benchmark example.
Other v0.10.0 deliverables: PWPB95 double hybrid (first
meta-GGA / SOS double hybrid in vibe-qc); periodic D3-BJ dispersion;
native slab + adsorbate builder; relaxed coordinate scans
(molecular + periodic); Fermi-Dirac smearing consolidation; QVF
spec v1.1 (DOS, potential, RDG, Fermi surface, phonons, EOS,
extension governance); vibe-view Phase A (bond angles, isosurfaces,
bands+DOS panel, publication export); 87 BSE-fetched basis sets
integrated under the §1 Feist clearance (docs/license.md § 3a +
§ 3e for the in-house semiempirical parameters).
Retraction¶
v0.9.0 Known-issues — XC residuals retracted. v0.9.0’s CHANGELOG flagged the polarised-GGA xc-kernel API + M06 meta-GGA UKS parity as residuals “tracked for 0.9.1.” Subsequent investigation by the XC chat confirmed both were already closed at the v0.9.0 cut commit by
b34f051c— which bundled the XC fixes alongside the test-gate restore + the k-GDF rewrite, despite its misleadingfix(release):subject. M06 drift was a vibe-qc/PySCF grid-mismatch artefact, resolved by pinning a matched fine grid (_fine_m06_grid: 120/23/46; PySCFatom_grid=(120,590),prune=None,use_newton). Tolerance kept at 2e-5 — CLAUDE.md § 8 discipline respected, not loosened. No code change in v0.10.0 — this entry retracts an overcautious release note.
Known limitations / deferrals¶
Periodic GDF (compcell + multi-k + AFT) — deferred to v0.11.0. GDF chat made incremental progress (
dfa34b4aBloch-summed lattice pair-FT fix in_compcell_aft_correction_3c;871c5f4aG=0 Madelung correction with H₂ sub-µHa PySCF parity), but the M1 investigation (b8f1e509) characterised remaining bugs not closed in this cycle: RSGDF SR+LR calibration is 98.5% off for L=0 / 23.8% off for L=1 on periodic cells, and the compcell metric is near-singular (cond 1e10–1e14) for tight ionic crystals. Fourth-cycle GDF deferral (v0.8.0 → v0.9.0 → v0.10.0 → v0.11.0); seedocs/roadmap.mdfor the honest pattern.DFT+U Increment 3 (analytic gradient) — open math question flagged by the DFT+U chat in HANDOVER § 1i. The Increments that DID land (1, 2, 4a, 4b, 4d-light) ship in v0.10.0 — molecular RHF/UHF/ RKS/UKS + periodic Γ-only RHF/RKS. Numerical gradients still work; the analytic gradient lands in v0.10.x once the math question resolves.
Native D4 default-backend flip — stays opt-in. Available via
backend="native"; thedftd4LGPL Python dep remains the default until the production-dataset upgrade lands (queued for v0.10.x / v0.11.0).ωB97X-D — still blocked on a D2/CHG dispersion reference.
Solvation analytic gradients — slipped from v0.9.1; v0.10.x.
[v0.9.1] — 2026-05-22¶
Patch release on top of Knowles’s Kingfisher. Curated fixes from the v0.9.0 stabilization window; no new features. Inherits the v0.9.0 codename.
Fixed¶
QVF (vibe-view archive format) — critical contract fix. Align the QVF writer schema with the vibe-view consumer; align the producer manifest with the consumer contract; unify
voxel_vectorssemantics (per-voxel step, not full span). Fixes silent producer/consumer drift that could mis-render density / orbital cubes in vibe-view.Solvers — audit fixes. DMRG
_build_full_hamiltoniannow enforces particle-number conservation (N-electron sector).active_space, the DMRG particle-number gate, and the v2RDM Q/G rejection paths corrected.BIPOLE periodic driver — initialise
e_j_multipolein the non-Ewald-J-split path (no behaviour change in the split path).Symmetry — handle already-primitive cells in
symmetry=True, plus an M2a LMS fix.ECP — corrected Si UHF reference in
tests/test_ecp_scf.pyto the post-899ab16a³P ground state.Bundled basis library — restored per-publication citation headers on
pob-tzvp.g94/pob-dzvp-rev2.g94/pob-tzvp-rev2.g94(Peintinger 2013 / Vilela Oliveira 2019).
Documented / hardening¶
CI deploy — pin
vibe-qc.comSSH host keys instead of TOFU at job time (audit #6).Privacy — scrub maintainer home paths from runtime defaults (audit #7).
Packaging — removed the broken
viewer-gpuextra; the two-step install (pip install ./vibe-viewthenpip install vibe-qc) is now documented. A PEP 508 direct-URL restore is queued for v0.9.2.DF + f-shell gradient bug — resolution recorded in the v0.8.0 notes.
Not in v0.9.1¶
The vq-queue security improvements (vq v0.6.44–v0.6.48), the
periodic-Becke C++ default flip, the Ewald-ω cutoff derivation, and the
remaining QVF density-grid unit refinement are deferred to v0.9.2 /
v0.10.0 — these picks conflicted on the v0.9.0 hotfix branch and need
either intermediate dependencies or a separate patch effort. They
remain on main.
[v0.9.0] — 2026-05-22 — Knowles’s Kingfisher¶
Wavefunction methods (FCI / Selected-CI / DMRG / v2RDM), the native D4 dispersion backend, the semiempirical DFTB structure-optimization stack, M-series crystal symmetry, and the BIPOLE periodic driver.
Known issues¶
v0.9.0 follows a ship-then-iterate plan. It builds and imports cleanly and the ECP-SCF segfault regression is fixed and verified on the cut commit, but the full
pytest -m 'not slow'suite had not completed a clean end-to-end run on the exact cut commit at tag time. Residual failures from the v0.9.0 stabilization (polarised-GGA XC kernel, M06 meta-GGA parity) are tracked for 0.9.1.Periodic GDF and ωB97X-D are not in v0.9.0 — both deferred (see
docs/roadmap.md).
Added — native D4 dispersion backend (Phase D4b complete)¶
Native D4 dispersion — MPL-2.0 implementation replacing the optional LGPL-licensed
dftd4dependency. All 8 Phase D4b milestones landed:Imaginary-frequency CPHF polarizability (D4b-1)
Molecular C6 pipeline + DOSD validation (D4b-2)
Reference-system catalogue — 21 systems over period-2 (D4b-3)
CN + charge Gaussian-weighted C6 interpolation (D4b-4)
Two-body BJ damping + 51 per-functional parameter sets (D4b-5)
Three-body Axilrod-Teller-Muto term (D4b-6)
Analytical gradient (geometric + C6 chain rule) (D4b-7)
compute_d4(mol, func, backend="native")integration (D4b-8)
Reference dataset —
d4_reference_data.json(60 KB), computed from first principles at the def2-svp level. 8 elements (H-B-C-N-O-F-He-Ne), 21 reference systems. Upgrade path toaug-cc-pVDZfor production accuracy is documented.Modules:
vibeqc.dispersion_d4_model(14 public functions includingcompute_c9andd4_atm_triple_energy),vibeqc.dispersion_d4_parameters(51 functionals),vibeqc.dispersion_d4_reference_data(D4ReferenceDataset),vibeqc.dispersion_d4_reference_systems(21 systems).The
dftd4backend remains the default pending production dataset upgrade; switch viabackend="native". Seedocs/handover_d4_native.md.
Fixed — D4 ATM three-body test API (1655013e)¶
Exported
compute_c9andd4_atm_triple_energyfromvibeqc.dispersion_d4_model— the two factored helper functions the D4b-6 test suite expected but that were missing from the initial ATM three-body implementation. Refactoredcompute_d4_atm_energyto use them internally, removing duplicated math. (6 CI failures resolved.)
Fixed — ECP SCF segfault on molecules with fewer electrons than the ECP core¶
run_rhf/run_rks/run_uhf/run_ukssegfaulted the process when an ECP replaced more core electrons than the molecule had: the valence countn_elec = n_electrons − total_ncorewent negative, producing a negative occupied-orbital count and acblas_dgemmcall with a negative dimension. The drivers now raise a clearstd::invalid_argumentthat names the likely cause (aMoleculecharge that pre-subtracts the ECP core). Regression introduced by the ECP-ncore propagation change (899ab16a).Corrected three ECP tests (
test_ecp_validation.py,test_ecp_scf.py,test_solvation_cpcm.py) that built Zn²⁺ with a pre-subtractedMoleculecharge against the pre-899ab16a convention. They now pass the physical ionic charge (+2), soMolecule.n_electrons()is the full electron count the SCF expects before it subtracts the ECP core itself.
Added¶
Non-mean-field solvers (
vibeqc.solvers): four wavefunction-based electronic-structure methods that do not conceptually rely on a Hartree–Fock reference state.Selected CI (CIPSI-style): iterative determinant selection, variational diagonalization, PT2 correction.
DMRG: two-site MPS sweeps with bond-dimension scheduling and SVD truncation.
Variational 2-RDM: augmented-Lagrangian SDP with PQG N-representability constraints.
Transcorrelated Hamiltonian: similarity-transformed H via Gaussian/Jastrow correlators, consumable by CI/DMRG backends.
run_job(method="selected_ci" | "dmrg" | "v2rdm" | "transcorrelated_ci")— all four methods accessible through the standard runner.Unrestricted Slater–Condon rules and exact FCI in the determinant basis (verified against PySCF FCI to machine precision).
vibeqc.solverstop-level namespace:solve_selected_ci,solve_dmrg,solve_v2rdm,build_transcorrelated_hamiltonian,Hamiltonian, etc.Tutorial:
docs/tutorial/non_hf_solvers.md.