Unreleased

[v0.15.42] - 2026-07-17 - Neese’s Cheetah

Fixed: vibe-view replication no longer fights a playing animation (2026-07-16)

Applying periodic replication (Nx/Ny/Nz supercell) while a trajectory or vibration animation was playing was buggy: the animation loop kept pushing single-cell frames on its timer, racing and overwriting the supercell rebuild so it flickered or never appeared. update_replication now stops any playing animation first, so the supercell rebuild wins. Apply Replication also reports “Replication: N×N×N” so it’s clear the action took effect. (The tiling itself was already correct: exactly 4× geometry at 2×2, 9× at 3×3.)

Fixed: vibe-view installs a discoverable /Applications app (2026-07-16)

install-electron.py now also installs vibe-view.app into an Applications folder (/Applications, or ~/Applications when the former isn’t writable), so Spotlight (Cmd+Space) finds it and it launches from the Dock like a normal app. Previously the only bundle lived inside node_modules/electron/dist, which Spotlight never indexes. The installed copy tracks live checkout edits (its launcher shim requires the checkout’s main.js); the step is idempotent and skips the copy when already current. Re-run python3 install-electron.py once to install it.

Fixed: RP209 output telemetry, provenance, and manifest truthfulness (2026-07-16)

  • Performance reports now cap the native OpenMP runtime by explicit scheduler and environment allocations. A one-CPU job on a 96-core host reports one thread and no longer fabricates 0.01x parallelism; host-wide available-core counts are never used as an allocation fallback.

  • The .system manifest now marks its own output row with checksum_status = "self-excluded", an empty SHA, and a zero byte-count sentinel. The lifecycle no longer records a pre-finalization self-hash, and vibeqc-outputs suppresses impossible stale self-digests in legacy files.

  • Per-calculation manifests record the full immutable 40-character Git SHA when build provenance is available instead of only the abbreviated SHA.

  • Population citation assembly now cites NPA only when non-empty NPA rows were actually emitted without an NPA error. A fail-closed NPA calculation no longer claims the Reed-Weinhold method merely because population output was requested.

Fixed: vibe-view draggable splitter + working toolbar tooltips (2026-07-16)

Two issues reported from the running app:

  • The viewport/plots splitter now resizes instead of rotating the molecule. The bar between the 3D view and the bottom panel is draggable; a full-window overlay during the drag keeps the VTK canvas from swallowing the gesture. (Previously the bar was decorative and the only resize grip was the easy-to-miss native corner handle.)

  • Toolbar icons now show tooltips on hover. The native title tooltip was unreliable/slow in the Electron shell; a lightweight instant tooltip is shown from the same labels (and the native title is removed so it can’t double up).

Added: vibe-view command palette (Ctrl/Cmd+K) (2026-07-16)

A command palette opens with Ctrl/Cmd+K from anywhere and fuzzy-searches quick actions — camera views, view presets, render HOMO/LUMO, energy diagram, toggle labels/background, screenshot, detect point group. Selecting one runs it and closes the palette. Keyboard-first parity with the toolbar.

Added: vibe-view click-to-render MO from the energy diagram (2026-07-16)

Clicking any level in the orbital energy diagram now renders that MO. Each level carries its {spin}:{index} render key; a click in the sandboxed diagram iframe posts the key to the app, which renders the orbital and syncs the MO picker. Completes the frontier-orbital UX alongside the HOMO/LUMO buttons.

Added: semiempirical NEB energy-gradient routes (2026-07-16)

run_neb(...) now accepts every executable molecular semiempirical energy-gradient route and the existing Gamma-periodic DFTB0/SCC-DFTB, GFN2-xTB, PM6, and OM1/OM2/OM3 routes. Gamma DFTB0 uses its native analytic gradient at the validated 15-bohr image domain; SCC-DFTB and GFN2-xTB use central differences of converged total energies; periodic PM6/OMx use their native finite-difference batches. Full k-point semiempirical paths remain fail-closed.

The current closed-shell MSINDO-SECCM route also drives NEB through its analytic fixed-topology gradient. Periodic endpoints require the explicit cyclic translations to match the QVF lattice, and non-MSINDO SECCM adapters remain unavailable. Semiempirical reaction-path QVFs carry method-specific citations without a fallback Gaussian basis or XC functional. Periodic MACE and SECCM QVFs no longer cite the unrelated periodic Gaussian-LCAO route. All NEB routes reject endpoint charge or multiplicity mismatches before image evaluation instead of rebuilding the product with the reactant’s state.

Changed: content-sized titled headerless output blocks (2026-07-16)

The document layer now provides HeaderlessBlock/HeaderlessColumn for titled label/value and matrix-shaped bodies, optional footers/dividers, and ragged right-hand annotations that do not widen the content rule. Energy components, molecular orbital energies, and periodic system summaries use the primitive instead of fixed 52/54/56-character rules. Orbital values retain their deliberate parallel Hartree/eV columns through explicit-unit render_energy calls.

The seven affected golden snapshots were deliberately regenerated. Reviewed diffs change only the targeted rule lengths: molecular RHF/RKS/UHF/Hessian/ TD-DFT orbital or energy blocks and periodic RHF/geometry-optimization system and energy blocks. The periodic label/value rows retain their historical per-row minimum label width, so a runtime label such as periodic length does not shift unrelated equals signs.

Changed: periodic artefact writes use role dispatch (2026-07-16)

run_periodic_job now constructs its OutputWriter before compute, publishes the complete plan with status="running", and marks the manifest complete or crashed at the public exit after any geometry optimization. This includes the basis-free periodic semiempirical route. Molden, method-specific population pairs, Extended XYZ, POSCAR, CIF, citation files, structure/density XSF, QVF, and runtime .opt.* files all route through dispatch_role; the end-of-job filesystem sweep is retired. Late-bound route provenance updates the original manifest in place.

The population plan distinguishes standard, BIPOLE, and AICCM2026DEV-B summary formats while the shared multi-output adapter still computes and writes each .txt/.json pair once. A representative periodic RHF+BIPOLE job with structure formats, QVF, and optimization matched a direct-writer control byte-for-byte for every deterministic artefact it produced; every QVF scientific member also matched, leaving only banner/timings, QVF wall-time provenance, and the intentionally improved live manifest lifecycle dynamic.

Added: vibe-view spectra X-unit toggle + CSV export (2026-07-16)

Spectrum panels gain an X unit selector (native / cm⁻¹ / eV / nm; e.g. UV-Vis eV→nm) and an Export CSV button. Broadening stays in native units (correct physics) and the axis is remapped for display; the CSV carries the broadened envelope and the discrete peaks in the chosen unit.

Changed: molecular artefact writes use role dispatch (2026-07-15)

The molecular run_job path now routes molden, population, density and per-orbital cubes, citations, final XYZ, runtime NTO molden files, and QVF through OutputWriter.dispatch_role. Existing progress stages and PerfScope timings remain around the same writer operations, while successful files are recorded in .system at dispatch time instead of by the end-of-job plan sweep. Dispatch now supports strict runner error propagation, path/format targeting, runtime-conditional files, and a per-call multi-output cache so the population analysis runs once for its .txt and .json siblings.

The plan also preserves integer cube requests as .mo_<index>.cube, aligns the QVF role with its dispatcher key, and declares the actual periodic XSF paths without structure/density collisions. A representative RHF audit found the molden, XYZ, population, cube, and citation payloads byte-identical before and after; every scientific QVF entry was also byte-identical, with only its recorded wall time changing.

Added: vibe-view spectra display controls (2026-07-16)

Spectrum panels gain a Broadening slider (0.2-4x the per-kind default width) and a Normalize checkbox (peak scaled to 1; sticks scaled with it) – both also apply to the multi-file comparison overlay.

Fixed: vibe-view branded Electron launches from the Dock (2026-07-16)

The branded dev Electron.app had no default app baked in, so a Dock or Finder double-click exited silently; only the vibe-view desktop spawn path worked. install-electron.py now writes a two-file launcher shim into Contents/Resources/app that loads the checkout’s main.js, and ad-hoc re-signs the bundle. Re-run python3 install-electron.py once per checkout (or after moving it) to (re)generate the shim.

Added: vibe-view one-click view presets (2026-07-16)

A “View preset” selector at the top of the Display panel applies a complete look in one click: Publication (white background, publication-grade material, no SSAO or labels), Presentation (dark, glossy, SSAO depth), Analysis (flat material with atom labels). Each preset orchestrates the existing representation, material, SSAO, and label controls in an order that survives the scene rebuilds.

Changed: marketing site at the root; docs move to /docs/ (2026-07-16)

vibe-qc.com now serves a new static marketing/landing site (Astro, in website/) at the domain root; the Sphinx documentation moves under /docs/ (html_baseurl, robots/sitemap CANONICAL, and the CI deploy target all updated). Docs remain the single source of truth and build unchanged. A rolling preview of main is published at vibe-qc.com/preview/ by the new website-staging CI job (noindex; served from its own /web/preview/ subtree).

Release-operations note: the first release deploy carrying this change relocates every documentation URL from the root to /docs/. Before that deploy, apply the Apache legacy-URL redirects in deploy/htaccess-redirects.conf to the server’s root /web/.htaccess — the CI deploy deliberately excludes .htaccess — or old doc URLs (/quickstart.html, /_static/…) will 404 in the gap.

Fixed: native molecular D3(BJ) is quantitative for H-Ar (2026-07-16)

The native molecular D3(BJ) backend now ships the complete H-Ar corner of Grimme’s coordination-dependent C6 reference grid (1,443 active references), the corrected D3 covalent radii, all 156 simple-dftd3 BJ parameter sets, and the full analytic C6(CN) chain rule for two-body and ATM gradients. The root cause of the RP208 overbinding was the earlier one-reference-per-element framework table, compounded by incorrect coordination radii; it was not pair double-counting, geometry units, damping dispatch, periodic images, or an SCF setting.

For the archived rounded methane-dimer geometry and printed B3LYP parameters, the native component changes from -0.0123798158 Ha to -0.00517760871046 Ha, agreeing with simple-dftd3 to 1.3e-15 Ha and with ORCA 6.1.1 (-0.005177608303 Ha) within the archived coordinate precision. Native/reference analytic gradients agree within 8e-14 Ha/bohr, including the ATM term. backend="auto" now uses this self-contained quantitative path for H-Ar and retains optional dftd3 fallback for heavier elements. These are geometry-only dispersion comparisons; no total B3LYP parity is claimed for the unmatched SCF routes.

Fixed: vibe-view desktop dodges stale servers on its port (2026-07-16)

A crashed or tray-forgotten session can leave an old vibe-view serve holding the default port for days; a fresh dock launch then collided with it and died silently (observed: a 6-day-old server on 8765 blocked every launch). The Electron shell now probes its port before spawning the server and, if the port is taken, transparently moves to an OS-assigned free one.

Fixed: stale BIPOLE analytic-preview reconstruction gate (2026-07-16)

The internal Gamma RKS Fock-reconstruction regression now tests its stated 1e-8 matrix identity from a converged plain-DIIS density instead of enabling the unrelated FMIXING state machine with a numerically zero positive weight and demanding SCF energy changes below the fixed-grid numerical floor. The reconstructed and SCF Focks agree under the original 1e-8 acceptance bound; no production convergence policy or analytic-gradient capability changed.

Added: vibe-view design refresh M1+M2 — Export menu, SCF guess toggle, resizable panels, Structure Library, multi-file spectrum comparison (2026-07-16)

First two milestones of the design-refresh workstream (vibe-view/docs/design_refresh_2026.md):

  • Toolbar: the eight cryptic per-format export buttons (OBJ/glTF/XYZ/ CIF/POV/Bldr/SVG/CML) are one labeled Export menu with per-format icons and full names.

  • SCF convergence: a “Hide initial guess (first SCF point)” checkbox above the plot drops the guess energy, whose scale otherwise flattens the convergence tail (SCFHistoryRenderer.render_to_html(skip_first=)).

  • Resizable panels: the Sections sidebar and the right control panel are user-resizable by dragging their inner edge (live drag preview, width committed to trame state on release so the layout reflows).

  • Structure Library (editor): autocomplete over the curated vibeqc_naming structure DB (83 molecules today) with Insert (into the current structure, next to the selection, undo-able) and Open as file (new entry in the Files dropdown). Card hides when the package isn’t installed.

  • Spectrum comparison across QVF files: with several files open, a “Compare across open files” checkbox on any spectrum panel overlays the same-kind spectra from every open file with a per-file legend (render_comparison_html; envelopes only, sticks omitted).

Pinned by vibe-view/tests/test_design_refresh.py; Export menu, SCF toggle, resize handles, and the library card also verified live in a browser against a real QVF.

Fixed: bounded BIPOLE NEB and relaxation diagnostics (2026-07-16)

Periodic run_neb and relax_atoms now expose sr_image_precision explicitly and forward it to every BIPOLE reference, objective, and finite-difference SCF. The production default remains the precision-derived M5 padded domain at 1e-6; no image radius is capped. Tiny-cutoff dispatch, warm-start, and QVF tests that do not validate numerical accuracy now select the documented historical-domain diagnostic with None, restoring bounded runtime without weakening production correctness.

Fixed: BIPOLE HSE06 uses the shared padded erfc domain (2026-07-16)

The dedicated restricted and unrestricted HSE screened-exchange traversal now honors M5’s padded ket-image domain, including the SYM3b-reduced build. The longer-ranged of the Ewald-J and screened-K erfc kernels sets the shared precision-derived radius. On the maintained H2 fixture, padded and unpadded screened exchange already agree with the independent multi-k Ewald route to machine precision; the reported post-M5 6.8e-5 Ha total-energy movement is entirely Hartree J. Production sr_image_precision=1e-6 agrees with 1e-9 within 5e-15 Ha. The screened-K cross-driver gate now selects the documented historical unpadded diagnostic domain explicitly, with its tolerance unchanged; no production energy was re-pinned.

Fixed: duplicate BIPOLE M5 energy regression contract (2026-07-16)

The cross-backend H2/STO-3G/12-bohr test now records the independently sanctioned M5 padded-domain BIPOLE energy already owned by the primary integration regression. Both routes were rerun together against a freshly rebuilt native extension and obtained -1.122138533948132 Ha. This is a stale test-pin correction only; production domain selection, tolerances, and numerical results are unchanged.

Fixed: molecular D3(BJ) no longer silently uses the qualitative stub (2026-07-15)

This interim safeguard, superseded by the complete native implementation above, changed compute_d3bj(..., backend="auto") to select the quantitative dftd3 backend and fail with an installation hint when that optional package was missing. Previously auto silently selected the builtin C6 framework table, even though that table has only one free-atom coordination reference per supported element and explicitly does not reproduce molecular D3(BJ) energies quantitatively. The RP208 methane dimer therefore received -0.0123798158 Ha instead of the independent simple-dftd3 / ORCA component -0.0051776083 Ha.

Regression coverage pins the RP208 geometry-only component, isolated methane, a two-hydrogen pair, the separated-dimer limit, and the missing-package fail-closed dispatch. No SCF or corrected-total parity claim is made.

Fixed: default-Γ ionic RHF/GDF stays on the PySCF-parity driver (2026-07-16)

run_periodic_job(jk_method="gdf") on a Γ-only closed-shell ionic cell (MgO-class) silently fell back to the legacy molecular-limit Γ driver — whose dense-core absolute energies are parity-held (G-GDF-001) — because the convergence="auto" ionic-insulator profile resolves FMIXING 30% and the default-Γ pure-GDF gate requires fock_mixing == 0. The GDF capability filter now zeroes AUTO-resolved fock_mixing / level_shift when the run otherwise qualifies for the PySCF-µHa-validated run_pbc_gdf_rhf (the parity route carries its own DIIS/accelerator convergence and the auto-sized high-|G| tail); explicit fock_mixing= / fmixing_percent= / level_shift= requests still take the legacy fallback rather than silently dropping the knob. Verified live vs PySCF 2.13.1 out-of-process on the P01 MgO/STO-3G Γ cell: the runner routes to pbc-gdf-rsgdf (no +PARITY_HELD), converges in 10 iterations without Fock mixing, and the auto-tail energy reproduces the recorded P01 calibration target to −0.25 µHa (= PySCF GDF at matched default precision to ~1 µHa; PySCF’s own answer moves −163 µHa under precision=1e-12, a shared truncation ladder now documented at _RSGDF_PARITY_TAIL_RATIO). Routing regression: tests/test_runner_gamma_gdf_routing.py (auto knob → pure driver; explicit knob → legacy fallback). Docs: periodic_methods.md § 3.2.

[v0.15.41] - 2026-07-16 - Neese’s Cheetah

Changed: AICCM construction identity requires an explicit binding (2026-07-16)

D90 closes the remaining post-D89 naming leaks. Namespace ownership, real-Gamma evaluation, and inherited solver ancestry no longer confer a Γ-CCM or χ-CCM construction identity. The neutral-only UCCSD/DLPNO and one-call RI-MP2/RI-CCSD routes are now described as neutral fitted-torus correlation controls. The mixed-boundary wire Green-function implementation is an A-line wire construction/control until its full Hamiltonian is explicitly derived from and gated against the union-and-weight Γ-CCM map. This changes no API, equation, numerical result, or route availability; χ-CCM-B remains fail-closed for every 1D/2D SCF backend.

The historical diamond and LiH direct-reference RI-MP2/KMP2 residuals are now explicitly classified as provenance-incomplete diagnostics, not reportable benchmarks or Γ-CCM/χ-CCM comparisons, pending a fully fingerprinted rerun. Neutral-versus-direct correlation ordering is recorded only as a complete-route regression because those routes change overlap, one-electron, and nuclear terms in addition to the exchange seam.

The public PeriodicJKMethod χ-B descriptions also enumerate the already wired RHF/RKS/UHF/UKS surface instead of the stale closed-shell subset.

Fixed: CCM construction and control routes dispatch by shell (2026-07-15)

run_ccm_scf now dispatches open-shell clusters to the unrestricted drivers on every construction/control route, completing what the real-gamma entry below started: run_ccm_uhf / run_ccm_uks on "four-center" (including the Γ-CCM "gamma" aliases), the new run_ccm_uhf_gdf / run_ccm_uks_gdf on the "neutral-bloch" / "gdf" control (run_ccm_uhf_gdf is the thin multi-k KUHF wrapper mirroring run_ccm_uks_gdf, with the same documented per-unit-cell spin-bookkeeping caveat for nrep (1,1,1)), and run_ccm_uhf_direct / run_ccm_uks_direct on "real-gamma" — all keyed identically on multiplicity AND electron parity. Previously an even-electron open-shell cluster (e.g. a triplet) ran the closed-shell loops on "four-center" and the neutral-Bloch control SILENTLY (n_occ = n_elec // 2 ignores multiplicity) and returned a wrong closed-shell number; an odd-electron cluster raised.

Also threads conv_tol_grad through run_ccm_uhf (the four-center open-shell driver, now a route target) — the explicit DIIS-residual exit bound the direct drivers gained in the entry below (default 1e-6 == the historical hardcoded gate, bit-for-bit). The shared closed-shell loops (_rhf_loop/_rks_loop, ri.py) keep the hardcoded gate — not on any route path; flagged in HANDOVER_AICCM_DIRECT_TORUS.md.

Pins: tests/test_ccm_route.py (test_route_four_center_dispatches_by_shell, test_route_neutral_bloch_dispatches_by_shell — doublet, even-electron triplet, and closed-shell-unchanged gates per route), tests/test_ccm_uhf.py (supercell multiplicity propagation at (1,1,1) / (2,1,1), parity fallback, construction-time validation, triplet-H₂ UHF (n_alpha, n_beta) = (2, 0), conv_tol_grad exit-gate pin).

Added: open-shell KS on the real-Gamma neutral control (run_ccm_uks_direct) + shell-aware dispatch (2026-07-16)

run_ccm_uks_direct — spin-polarized KS-DFT on the k-free real-Γ-supercell control, completing its RHF / UHF / RKS / UKS matrix for one already specified neutral-torus Hamiltonian. It is not the union-and-weight Γ-CCM construction:

F_σ = h + J(D_a+D_b) + V_xc^σ − a_x [ K(D_σ) + ξ_N S D_σ S ]

run_ccm_uhf_direct’s per-spin exchange-q=0 seam composed with run_ccm_rks_direct’s a_x scaling, and the spin-polarized periodic XC kernel (build_xc_periodic_uks, per-spin densities on the same fold construction + periodic-Becke grid as the closed-shell route — the b3f74aa9-validated pairing). Measured: closed-shell collapse to run_ccm_rks_direct at the 1e-15 class per rung (SVWN/PBE/r2SCAN/PBE0 — LDA/GGA/mGGA/hybrid) — so the open-shell route inherits the closed-shell GDF / external-PySCF-KRKS parities transitively; open-shell hybrid seam identity exxdiv=None ewald == a_x·ξ_N·N_e/2 to 6e-16 with the same per-spin densities (at tightened conv_tol_grad; see below); open-shell pure parity vs the production multi-k KUKS at 3.6e-13 (both sides ride identical build_xc_periodic_uks + fit machinery); vacuum limit vs molecular run_uks per rung (SVWN/PBE/r2SCAN). RSH/double hybrids fail closed; dim<3 fails closed.

The open-shell direct drivers (run_ccm_uhf_direct / run_ccm_uks_direct) also gain an explicit conv_tol_grad (DIIS-commutator residual bound, default 1e-6 = the historical hardcoded gate, bit-for-bit; molecular UHFOptions.conv_tol_grad convention). The adversarial review of this change caught that the energy is stationary at convergence, so |dE| is quadratic in the density error — an energy-only gate reports converged=True with a ~1e-5-loose minority-spin density, nondeterministically (threaded-BLAS trajectory). Tighten conv_tol_grad when converged densities matter.

Also run_ccm_uks_gdf (the missing thin multi-k KUKS comparator, with a documented per-unit-cell spin-bookkeeping caveat for nrep (1,1,1)), and CCMKSResult gains an optional exchange_q0 field.

Behaviour fixes — explicit high spin reaches the CCM, and real-gamma dispatches by shell. Two stacked silent traps closed:

  • CCMSystem._build_supercell discarded an explicit high-spin multiplicity (>= 3) in favour of its parity default — a triplet unit cell silently became a singlet supercell and ran closed-shell (the run_ccm_uhf docstring’s “pass a periodic unit cell with an explicit multiplicity for higher spin” was broken). Explicit n_unpaired >= 2 cells now replicate ferromagnetically (n_unpaired × N_c, the multi-k per-unit-cell spin convention; exact at (1,1,1)); n_unpaired <= 1 keeps the parity behaviour bit-for-bit (all existing pins unchanged). The unit cell’s own spin state is validated at construction — an impossible request (e.g. 3 electrons, multiplicity 3) previously raised at odd N_c but silently morphed into a parity-consistent different state at even N_c (a quintet at (2,1,1)); it now fails loudly regardless of cluster size.

  • run_ccm_scf(route="real-gamma") now dispatches by shell (multiplicity AND electron parity) to the UHF/UKS direct drivers; even-electron open-shell supercells previously fell through to the closed-shell loop silently (n_occ = n_elec // 2), odd-electron ones raised. The same dispatch trap on the four-center and gamma routes is closed by the follow-up entry above (shell-aware dispatch on all three routes + run_ccm_uhf_gdf).

Pins: tests/test_ccm_uks_direct.py (collapse per rung, a_x seam identity, KUKS-GDF parity, vacuum limit vs molecular run_uks, fail-loudly surface, dispatch-by-shell incl. the triplet trap).

Changed: semiempirical cyclic-cluster routes use the SECCM name (2026-07-15)

The semiempirical cyclic-cluster boundary is now named SECCM to distinguish it from ab initio CCM and DFTCCM. Public semiempirical dispatch accepts seccm, se-ccm, and MSINDO-qualified spellings while retaining ccm as a compatibility alias for existing inputs and manifests. The route planner represents the implemented path as the MSINDO INDO Hamiltonian on the seccm_direct_torus boundary, and fails closed for DFTB, GFN2-xTB, PM6, and OMx SECCM requests until their method-specific theory and adapters are implemented. No periodic kernel or production-status claim changes in this slice.

Changed: molecular semiempirical dispatch uses the shared route plan (2026-07-15)

run_semiempirical(...) now canonicalizes and validates each molecular or current MSINDO SECCM request through SemiempiricalRoutePlan before loading method parameters, model wrappers, or native backends. The route module owns the shared public alias table, with compatibility re-exports retained from the runner. SemiempiricalProvider uses the same planner for geometry-optimization method setup. Unsupported method/option combinations continue to fail closed; MSINDO nddo=True plus implicit solvent is now rejected explicitly because the current COSMO path uses the INDO Hamiltonian. Numerical kernels, periodic dispatch, route maturity, and production claims are unchanged.

Fixed: molecular semiempirical spin routes follow the shared plan (2026-07-15)

The molecular dispatcher now carries one SemiempiricalRoutePlan through method, variant, charge, and spin selection instead of recovering those choices from the molecule inside private method switches. Open-shell DFTB0 and SCC-DFTB therefore select the existing UDFTB0Model and USCCDFTBModel paths instead of constructing restricted models. PM6/UPM6, OMx/UOMx, MSINDO INDO/NDDO, and COSMO dispatch consume the same planned variant or spin. Unvalidated open-shell GFN2-xTB and open-shell MSINDO NDDO now fail in the planner before parameter loading; the native unrestricted GFN2 reference does not implement the full validated GFN2 functional and is not promoted by this change.

The public DFTB, GFN2, PM6/UPM6, and OMx model wrappers now retain their canonical route plan. Restricted DFTB and PM6 wrappers reject open-shell input early and name the corresponding unrestricted API, while OMxModel selects the already-existing native UOMx kernel for an unrestricted plan. The direct array-based run_msindo(...) entry point also validates through the planner before element or parameter work. No native kernel, periodic route, maturity label, or paper-facing claim changes in this slice.

Changed: semiempirical memory estimates consume route plans (2026-07-15)

estimate_semiempirical_memory(...) now builds or accepts the same canonical SemiempiricalRoutePlan used by molecular dispatch. Boundary, method family, variant, SCC policy, and spin fields replace estimator-only labels for periodic Gamma, SECCM, GFN2, COSMO, NDDO, and unrestricted scratch accounting. Unsupported spin/method/boundary combinations fail during route validation, before atom inventory or matrix-size accounting. Existing conservative memory formulas and public output remain unchanged; no numerical kernel or maturity claim changes in this slice.

Changed: periodic Gamma semiempirical setup uses route plans (2026-07-15)

run_periodic_job(...), periodic semiempirical optimizer energy closures, preoptimization, PM6/OMx cell optimization, and the direct periodic PM6/OMx models now validate through one system-aware SemiempiricalRoutePlan before parameter or backend loading. Canonical aliases, charge, multiplicity, odd electron fallback, and Gamma versus full-k boundaries therefore share one representation. Full-k requests remain fail-closed. Open-shell DFTB0 and SCC-DFTB optimizer closures now select the existing unrestricted Gamma kernels; open-shell periodic GFN2, PM6, and OMx fail before allocation because no validated unrestricted Gamma implementation exists for those routes. Existing kernels, finite-difference formulas, output shapes, maturity labels, and paper-facing claims are unchanged.

Added: unified native facade for molecular semiempirical energies (2026-07-15)

A common C++ route descriptor and result surface can now dispatch molecular DFTB0, SCC-DFTB, GFN2-xTB, PM6/UPM6, OM1/OM2/OM3, and MSINDO INDO/NDDO energy calls to their existing native kernels. The Python adapter translates a validated SemiempiricalRoutePlan into that descriptor, while the native boundary rejects parameter-family, spin, and control mismatches before kernel dispatch. Facade energies and explicit energy components are pinned exactly to the method-specific bindings.

The result reports retained matrix bytes separately from workspace bytes. The workspace value remains zero and memory_counters_complete remains false until kernel workspaces expose complete allocation accounting. This first slice does not reroute public model or gradient APIs, add periodic or SECCM support, or change method maturity and paper-facing claims.

Changed: periodic PM6/OMx derivatives use native FD batches (2026-07-15)

Periodic Gamma PM6 and OM1/OM2/OM3 gradients and stress now perform their central-difference batches in C++ instead of constructing every displaced or strained system in Python. One mutable periodic-system workspace and immutable parameter view are reused for the batch. Coordinate displacements also share one prepared lattice-cell list; strained evaluations rebuild their lists for the changed lattice so cutoff behavior remains identical. Exact parity to the former Python finite differences is pinned for PM6 and OM2.

PeriodicPM6Model and PeriodicOMxModel use the native batch for gradient and stress, and their cell optimizers can consume both derivatives from one batch. Route plans label these periodic derivative requests native_batched_fd. The batch result reports energy-evaluation, system-workspace, lattice-setup, and workspace-byte counters. Counters remain explicitly incomplete for allocations inside each existing SCF kernel. This is still finite-difference and does not promote periodic NDDO to production or change paper-facing claims.

Fixed: native k-point DFTB keeps complex Bloch matrices (2026-07-15)

The direct native DFTB0 and SCC-DFTB k-point prototypes now solve the complex Hermitian generalized eigenproblem instead of discarding the imaginary parts of H(k) and S(k). SCC charges are accumulated from weighted D(k) S(k) Mulliken populations, and use the same lattice-summed periodic gamma matrix as the established Gamma driver. DFTB0 lattice cells, Hamiltonian blocks, and overlap blocks are built once as one aligned object; SCC also reuses its Hamiltonian-block storage across iterations.

Malformed meshes, weights, cutoffs, electron counts, and SCC controls now fail before basis or lattice allocation. Primitive-cell/supercell equivalence, Gamma SCC parity, weighted full-k SCC populations, and the early validation surface are pinned by focused tests. The public semiempirical periodic_k route remains fail-closed: metallic occupations, analytic derivatives, k-mesh convergence fixtures, and out-of-process DFTB+ parity are not complete, so no maturity or paper-facing claim changes here.

Added: native full-k DFTB finite-difference derivative batches (2026-07-15)

Direct native DFTB0 and SCC-DFTB k-point APIs can now evaluate gradient and stress central-difference batches in C++. Each batch reuses one periodic-system workspace and one k-mesh workspace. Strained cells transform Cartesian k-vectors contravariantly so the sampled fractional k-points stay fixed, and a focused regression distinguishes that result from the incorrect fixed- Cartesian-k stress. Invalid FD controls fail before basis/lattice allocation, and displaced SCC nonconvergence raises instead of entering a derivative.

The result exposes energy-evaluation, workspace-copy, lattice-setup, and workspace-byte counters, with nested kernel allocations still explicitly incomplete. This is a direct native finite-difference prototype, not an analytic derivative or public-route promotion. Metallic-safe occupations, k-mesh convergence fixtures, runner/output/band integration, and external DFTB+ parity remain open; periodic_k stays fail-closed with no maturity or paper-facing claim change.

Added: native Fermi occupations for full-k DFTB (2026-07-15)

Direct native DFTB0 and SCC-DFTB k-point prototypes now accept a finite Fermi-Dirac smearing temperature. One weighted chemical potential conserves the closed-shell electron count across the complete k-mesh. Results expose per-k occupations, chemical potential, electronic entropy, internal energy, and Mermin free energy. At zero temperature the prior per-k hard Aufbau density and energy path remains unchanged.

SCC Mulliken populations and band energies use the fractional density, while geometry-static overlap matrices are assembled once per k-point outside the SCC loop. Native derivative batches propagate the same occupation controls through each displacement and differentiate free energy when smearing is active. Nonfinite or negative temperatures fail before k-mesh, basis, or lattice work. Native occupations, chemical potential, entropy, electron conservation, SCC charge conservation, and DFTB0/SCC free-energy finite differences are pinned against the shared Python Fermi-Dirac contract.

The kernels return structured data and emit no user-facing output. Public rendering remains owned by Python through vibeqc.output; the output module is unchanged. K-mesh convergence fixtures, runner/output/band integration, analytic derivatives, and external DFTB+ parity remain open. The public periodic_k route stays fail-closed with no maturity or paper-facing claim change.

Added: full-k DFTB k-mesh convergence fixtures (2026-07-15)

The direct native DFTB0 and SCC-DFTB k-point prototypes now have fixed Monkhorst-Pack convergence regressions for an insulating one-dimensional H/Li chain. Successive 4-, 8-, and 12-point even meshes converge toward an independently measured 24-point dense limit for zero-temperature internal energy and finite-temperature Mermin free energy. All four sequences reduce the 8-point error below one percent of the 4-point error, reduce the 12-point error below two percent of the 8-point error, and reach the dense result within 2e-9 Ha.

This adds validation only. The public periodic_k route remains fail-closed pending runner/output/band integration, analytic derivatives, and out-of-process DFTB+ parity. No output surface, maturity label, citation route, or paper-facing claim changes.

[v0.15.40] - 2026-07-15 - Neese’s Cheetah

Changed: semiempirical runner gradients are lazy (2026-07-15)

run_semiempirical(...) now caches gradients on first access instead of building them during every energy call. DFTB0/SCC-DFTB, GFN2-xTB, PM6/UPM6, and OMx keep their existing gradient surfaces, while closed-shell MSINDO INDO results now expose the native analytic-gradient route through the same result.gradient() adapter. SemiempiricalProvider now delegates through this unified runner instead of carrying its own method switch or alias table, while preserving the documented MSINDO finite-difference force fallback for MSINDO routes whose unified result is energy-only. MSINDO NDDO and open-shell MSINDO remain gradientless at this public runner layer until those surfaces are promoted. The shared normalizer now also accepts the gfn2 shorthand, and the semiempirical route-status API resolves DFTB/GFN2/OMx method aliases through the same runner-owned table instead of a duplicate status-side copy. Semiempirical dry-run memory estimates use that same normalizer, so aliases such as gfn2, gfn2 xtb, and sccdftb receive the same SCC/AES scratch accounting as their canonical method names. ASE-backed run_job(optimize=True, method=...) semiempirical optimizations now use result.gradient() when the unified runner exposes a gradient, falling back to central finite differences only for energy-only routes. The semiempirical preoptimization and periodic cell-optimization ASE calculators now also record the active Atoms state through the ASE base calculator, so repeated energy/force/stress queries at an unchanged geometry can reuse cached results.

Fixed: periodic RIJCOSX one-center correction follows the post-SCF contract (2026-07-15)

Both dedicated Gamma RIJCOSX JKBuilders applied the analytic COSX one-center correction inside build_g_rhf() on every SCF iteration, but inherited the no-op finalization hook. This reversed the molecular COSX lifecycle: the quadrature residual perturbed the iterative surface while the returned final Fock and orbitals omitted the correction. The periodic builders now iterate on the smooth seminumerical J - 0.5 K surface and apply the analytic replacement only through apply_one_center_correction() after convergence. Total energies remain on the iterated seminumerical surface, matching the molecular RIJCOSX contract. A focused regression covers both periodic builders and verifies that the returned Fock carries a finite post-SCF correction.

Added: exact single-state IC-CASPT2 analytic gradients (2026-07-15)

CASPT2Options(compute_corr_grad=True) now evaluates the coupled orbital/CI Lagrangian for unshifted, state-specific closed-shell IC-CASPT2 on the explicit small-space engine. The stationary Hylleraas effective density is contracted with analytic AO derivative integrals; finite differences are confined to the internal response parameters, not nuclear energies. H2/6-31G CAS(2,2) and core-containing LiH/STO-3G CAS(2,2) match fully relaxed total-energy finite differences within 1e-7 Ha/bohr. Shifted, case-engine, direct-large, selected-reference, and open-shell single-state variants remain fail-closed for direct gradient requests and stay on full-energy finite differences in geometry optimizers.

Changed: molecular NDDO finite-difference gradients have explicit route status (2026-07-15)

semiempirical_route_status(...) now distinguishes molecular PM6/UPM6 and OMx finite-difference gradient routes as native-fd. These routes are C++-backed displacement loops over native NDDO energy calls, but they remain finite-difference stopgaps until analytic molecular NDDO gradients land.

Fixed: GDF Ewald nuclear-attraction gradient is fully analytic (2026-07-15)

The Gamma-point RHF compcell GDF gradient now differentiates every term in the FT-based Ewald nuclear-attraction matrix: the erfc-screened real-space integrals, nuclear structure factor, AO-pair Fourier-transform centres, and finite G = 0 overlap correction. This replaces the former 6N displaced whole-matrix builds with a native density- and kernel-weighted contraction that never materializes the per-cell AO-pair derivative tensors. Reciprocal vectors are processed in bounded chunks, and the native general-angular- momentum path shares its reciprocal power tables across worker threads. Fixed-density H2 and LiH/STO-3G regressions enforce central-difference agreement below 1e-5 Ha/bohr, including translated cells, multiple reciprocal chunks, and p shells.

The public compute_gradient=True route now forwards the SCF lattice cutoffs, follows the same VIBEQC_VNE_EWALD3D_KE cutoff contract, and fails before SCF for RSGDF/MDF, AFT-on compcell, the diagnostic grid V_ne backend, or KS requests instead of silently returning a mislabeled compcell gradient or swallowing an exception. The exported direct wrapper also reuses and validates the converged auxiliary basis, compensation exponent, AFT state, V_ne backend, reciprocal cutoff, exchange-divergence gauge, lattice cutoffs, rcut strategy, and fit threshold. The gradient consumes the exact private compcell fit used by SCF, including its separately resolved 2c/3c cutoffs and the Fréchet derivative of any threshold-truncated auxiliary eigenspace. The module-level RHF component entry point enforces the same supported-model boundary and binds an optional reusable cache to the exact cell, basis contents, compensation exponent, and cutoff policy that produced it. Fixed-density J/K regressions force a dropped fit mode and agree with central differences below 1e-7 Ha/bohr; both exxdiv='ewald' and 'none' are pinned on one bond-axis component of the H2/STO-3G 12-bohr-box end-to-end control at 5e-3 Ha/bohr. Its smaller pre-existing total-gradient residual is investigated separately. The supported boundary remains 3D Gamma RHF, compcell, with the AFT correction disabled; lower-dimensional, RSGDF, RKS/open-shell, multi-k, and optimizer wiring remain separate work.

Fixed: GAPW SCF energies include atomic XC augmentation (2026-07-15)

Gamma RKS, Gamma UKS, and closed-shell multi-k GAPW now include the atomic XC augmentation in each SCF convergence energy and trace entry, matching the augmented Fock and final energy breakdown. Previously the Fock used the hard-minus-soft atomic XC correction while the energy used only the smooth grid term; He/STO-3G LDA therefore returned -2.7737713066 Ha but recorded -2.4735585858 Ha on its last SCF step, a 300.2 mHa mismatch. The corrected paths reuse the libxc energy density already evaluated for the atomic Fock correction, avoiding another atomic-grid pass. Existing Gamma RKS, Gamma UKS, and multi-k LDA/GGA/meta-GGA parity regressions now pin final trace and breakdown consistency.

Added: Gamma-point GPW periodic ROHF route (2026-07-15)

run_periodic_rohf_gpw and GpwRohfScfResult add a standalone, three-dimensional Gamma-point GPW route for spin-pure periodic ROHF. The driver reuses the established GPW Hartree J, Ewald one-electron and nuclear terms, per-spin exact exchange, and the shared Roothaan effective-Fock SCF loop. It returns one spatial-orbital set with integer 2/1/0 occupations, per-spin densities and Focks, an auditable GPW energy breakdown, exact S(S+1), and per-iteration progress through the existing ProgressLogger hook. Open-shell GPW occupation inference now uses PeriodicSystem.n_electrons(), so charged cells include the declared unit-cell charge instead of silently filling the neutral electron count.

Regression coverage pins the closed-shell ROHF-to-RHF reduction, the one-electron ROHF-to-UHF reduction, and a genuine Li open shell with one closed and one singly occupied orbital. The scope is deliberately narrow: HF, Gamma-only, integer occupations, and the standalone API. Electronic smearing, periodic ROKS, multi-k exact exchange, GDF/BIPOLE/GAPW routes, and run_periodic_job(method="ROHF") remain follow-up work; smearing_alpha on this API is nuclear-charge smoothing, not Fermi smearing.

Added: dense ground-state CC3 benchmark route (2026-07-15)

run_job(method="cc3") and run_job(method="ci", citype="cc3") now solve the ground-state CC3 equations for closed-shell RHF references. Singles and doubles are iterated to convergence, connected triples are regenerated from the T1-transformed fluctuation-potential commutator on every iteration, and the CC3 triples feedback is included in both projected residuals. The route uses exact four-index MO integrals, freezes chemical cores by default, accepts explicit active spaces, and exposes CC3Options, CC3Result, and the low-level cc3 solver.

The normal .out reports energies, residuals, and amplitude norms through OutputDocument and Quantity; the iteration table is VERBOSE. Memory preflight conservatively models the dense determinant maps, nonconvergence is fatal on the public path, and .system, citations, and QVF SCF history are preserved. Regression coverage pins the exact two-electron limit, frozen-core reduction, open-shell rejection, and nonzero triples. Out-of-process ORCA 6.1.1 UHF AUTOCI-CC3 anchors agree across the exact H2 two-electron limit, H4/STO-3G, H4/3-21G, and the rank-five H6/STO-3G path. The focused public-route matrix at <vibeqc-article-release>/calculations/cc3/20260715-0cb29239/ validates every QVF and .system manifest; its worst correlation-energy delta is 6.112121e-10 Ha and its worst total-energy delta is 1.475981e-09 Ha.

Fixed: multi-k UKS slab QVF density and DOS artifacts (2026-07-15)

Genuine dim=2 UKS jobs on an in-plane k-mesh converged correctly, but their optional density-grid and DOS preparation could fail when the SCF density used a shorter real-space lattice cutoff than the fixed output template. The strict copy then requested an outer cell absent from the converged density and dropped volume.density, dos.total, and dos.projected from the QVF with two warnings.

Periodic output density preparation now re-templates lattice density blocks by cell key and fills only newly requested outer cells with exact zeros. It refuses to truncate source-only cells and verifies the keyed alpha/beta alignment before summing spin densities. The converged SCF density and output formats are unchanged. A public-runner (2,2,1) UKS slab regression validates the QVF and pins all three sections without optional-artifact warnings.

Fixed: BIPOLE M5 padded-domain route consistency (2026-07-15)

The restricted legacy Ewald-J route now applies the resolved padded short-range image domain to Hartree J, matching the unrestricted builder and the M5 contract that every active erfc SR build honors sr_image_precision. Closed-shell multi-k RHF/UHF J, Fock, and total-energy parity is restored. Explicit lower-dimensional Ewald-J requests again fail with the documented dim=3 ValueError before SR-domain resolution, and two duplicate H2 regression snapshots plus the legacy RKS H2 guard now reflect the already-sanctioned M5 padded values. Direct-only, corrected-exchange, exact-FT pure-RKS, LR-J, and full-Coulomb legacy-K paths are unchanged.

Changed: BIPOLE production diagnostics use semantic output (2026-07-15)

The RHF/RKS/UHF/UKS lattice-fold diagnostics and the shared exact-FT J core-tail, legacy multi-k gauge, and charged-cell guards now route their human-readable messages through vibeqc.output.warn() / note(). Warnings therefore survive quiet output, and both warning and note paths emit structured records with their method, cutoff, drift, k-point, or charge metadata as applicable. Direct API warnings.warn(...) calls and the historical ProgressLogger live-stream text remain unchanged. This is output hardening only: thresholds, numerical paths, and BIPOLE defaults are unchanged. Repeated internal SCFs, such as finite-difference geometry evaluations, follow the shared logger’s emission policy; BIPOLE does not add a parallel formatting or suppression layer.

Changed: vibe-view’s debug log is usable + MO renders are traced (2026-07-15)

The server log at $XDG_CACHE_HOME/vibe-view/vibe-view.log was flooded with trame/aiohttp INFO-level internals (a js_key + namespace-token line per widget on every template build, an access line per request) — tens of thousands of lines that buried vibe-view’s own messages and made the log useless for diagnosing a hang. Those loggers are now quieted to WARNING (wslink CRITICALs and aiohttp request tracebacks still land); vibe-view stays at INFO. In one measured startup this cut the log from ~20,000 lines to a handful.

Molecular-orbital rendering (_render_mo_volume) now logs a timed breadcrumb at each stage — grid evaluation, contouring (with point counts), and the render+client-push — so an MO render that appears to freeze the viewer leaves a diagnosable last completed step in the log instead of nothing. (For reference, a def2-TZVP methane-dimer HOMO on a 60³ grid evaluates + contours in ~0.35 s server-side; a freeze there is not the server compute.)

Changed: Γ-CCM and χ-CCM approach identities are explicit (2026-07-16)

D89 corrects the earlier premise that Γ-CCM and χ-CCM were merely representation labels or globally unitarily equivalent by name. They are distinct CCM approaches: Γ-CCM uses the union-and-weight/Wigner–Seitz integral-weighting construction, while χ-CCM uses the finite-translation-group character construction. Comparisons hold a declared common exchange-q=0 convention fixed. The approach names do not select different Coulomb kernels, and equality for a specified operator and route is evidence to establish.

χ-CCM-B convention records now carry immutable ccm_approach="chi-ccm", ccm_construction="finite-translation-group-character", and evaluation_representation="gamma-centred-character-mesh" identities through diagnostics/results, fleet JSON, QVF vendor metadata, and .system metadata. The existing QVF version-1 representation string remains for compatibility; the new evaluation field is normative. The current aiccm-hf-direct versus B rhf-ri pairing is a neutral-torus real-Gamma/character representation control, not evidence for the union-and-weight Γ-CCM approach, so the reportable Γ/χ approach map is empty. Exact character/real-Gamma Fourier equivalence of the same χ-defined Hamiltonian remains valid. Pre-D89 records are not numerically retracted solely because the new identity fields are absent, but cannot support construction-comparison claims.

The public Γ-CCM selectors now enforce that boundary: run_ccm_scf() defaults to four-center, and aiccm2026dev-a / gamma / gamma-ccm resolve to the union-and-weight route. neutral-bloch, gdf, and aiccm-ri select the neutral fitted-torus Bloch control; real-gamma is its same-H real-supercell control. The historical compare.py --vs path now fails closed instead of emitting route-name-based Γ/χ deltas. compare_b.py always reports the approach comparison as not-defined and exposes any eligible neutral control only through explicitly named real_gamma_control_* fields. Post-HF B results delegate the same exact construction identity as SCF results.

The high-level B runner now also rejects every requested exchange_exxdiv value other than its fixed bvk-ewald convention before SCF, and derives the persisted exchange-q=0 label from the returned B convention. This closes a path where a non-BvK request could execute B while the .system manifest incorrectly recorded strict. Fleet auditing rejects malformed records and now requires exact method, backend, route, construction, and control-role identities before admitting either a B record or the real-Gamma control.

Fixed: toroidal local-correlation geometry on skew lattices (2026-07-15)

The periodic toroidal supercell and Wannier-cell helpers now follow the core column-vector lattice contract for general cells: supercell vectors are A @ diag(nrep), replicated atoms are translated by A @ n, and Cartesian centroids are mapped with A f = r. The pair-family classifier now finds the exact closest image with LLL basis reduction plus a bounded QR sphere search instead of wrapping Cartesian components, so inverse families L and -L have the same distance on unequal meshes without an acute-cell Cartesian-box blow-up. Numerically collapsed lattices fail before image enumeration. Distance thresholds are validated as 0 <= r_close < r_cutoff. The explicit total_distant_cell_pairs property now names the translational placement count; the legacy total_pairs_skipped accessor remains an alias and no longer implies that occupied-orbital pairs or correlation work were actually skipped.

Skew-cell regressions distinguish the corrected operations from their row-vector transposes and compare classifier distances with brute-force and acute unreduced-cell image oracles. The accompanying status audit clarifies that Stage 5b currently partitions an all-pairs MP2 sum by translational family; representative-only pair evaluation and consumption of the classifier by the DLPNO wrappers remain follow-ups.

Fixed: a failing renderer no longer crashes/freezes vibe-view on section switch (2026-07-14)

activate_section (the sidebar section-switch handler) ran a large synchronous per-kind render dispatch with no error handling: selected_section was updated before rendering, so if any one renderer raised, the exception escaped the trame controller with the app left half-switched — a frozen or broken viewer. The dispatch is now wrapped in a guarded entry point: a renderer that raises is caught and logged, a Could not open section status is shown, and the 3D viewport is restored to a usable state so the user can switch to another section instead of a dead app. Regression test tests/test_controller_state.py::test_activate_section_survives_a_renderer_exception drives a switch into a renderer rigged to raise and asserts the app stays usable (the next section still activates).

Added: MSINDO and MACE registry TOML exports (2026-07-14)

vibeqc.semiempirical.io.save_msindo_parameter_registry(...) now writes a TOML export of the bundled MSINDO INDO H-Xe table plus the H/Li-F/Na-Cl NDDO override table, including source-bundle hashes and support metadata. vibeqc.mlip.save_mace_model_registry(...) exports the current v0.15 MACE wrapper registry, explicitly limited to MACE-MP/MPA-0 and MACE-OFF23 families.

Fixed: periodic semiempirical method strings reach basis-free backends (2026-07-14)

run_periodic_job(..., method=...) now accepts dftb0, scc_dftb, gfn2_xtb, pm6, om1, om2, and om3, dispatching to the existing Gamma-point periodic semiempirical engines before the Gaussian-basis RHF/RKS/UHF/UKS gate. The branch writes .out, .system, citations, and requested structure files while explicitly skipping Gaussian Molden, population, density, and QVF artefacts for basis-free routes.

Fixed: periodic CCM paths use the core column-vector lattice convention (2026-07-14)

χ-CCM-B torus construction, Wigner–Seitz representatives, localization, symmetry, post-HF supercells, reciprocal-vector enumeration, Madelung helpers, and shared fleet geometry builders now follow the authoritative PeriodicSystem.lattice contract: lattice vectors are columns, Cartesian translations are A @ n, and the BvK supercell is A @ diag(mesh). Successful fleet payloads record primitive_lattice_bohr, and new convention records serialize lattice_vector_convention="columns". Decisions D17 and D81 had recorded the opposite row-vector premise; D88 explicitly supersedes those parts instead of treating the repair as a silent convention change. The B fleet audit binds an exact A @ diag(mesh) record to those fields; Γ/χ comparison is not defined when the binding is absent or inconsistent.

The skew unequal-mesh audit uses A=[[7,.4,.2],[.3,8,.5],[.1,.6,9]] bohr and (2,3,1). The corrected positive code convention gives xi_M=0.138352993811598; the pre-D88 fitted helper gave 0.143621616230995, which would overbind a two-electron RHF seam by 5.268622 mHa/cell, and the pre-D88 four-center probe gave 0.138913224426180. Regression coverage distinguishes A @ diag(mesh) from the transposed order and checks the affected real/reciprocal transforms; it does not establish general quantitative certification beyond those tests.

All affected pre-D88 periodic Γ-CCM and χ-CCM records must be rerun under the fingerprint rule because they do not attest lattice semantics and the BvK construction. Records whose lattice matrix is symmetric and commutes with diag(mesh) are numerically outside this defect class, but their old payloads are not retroactively upgraded. The shared fleet builders for graphene, mgo-slab, ice-ih, co2-dryice, and sio2-quartz require fresh Γ and χ fingerprints. Diagonal and symmetric cubic anchors with commuting meshes may guide reruns, but retain their other numerical-support and provenance limits. Ordinary pre-D88 periodic GDF exact-exchange and BIPOLE J/K records for affected lattice/mesh combinations also require audit and rerun because they traverse the repaired shared Madelung and reciprocal-vector helpers. A rebuilt ordinary-GDF c-diamond/sto-3g (2,1,1) control moves from the pre-D88 post-D78 pin -74.6405167828 Ha to -74.4119904741 Ha. Its +0.228526309751 Ha shift is exactly predicted by six occupied closed-shell bands times the old-minus-corrected BvK Madelung constant, so this is a convention repair rather than SCF noise. The article theory already uses the column convention correctly, so D88 is a code/fingerprint repair rather than a Coulomb-kernel, signed-Madelung, or theory change. The 1D/2D absolute-energy guard and analytic-total-gradient guard remain fail closed. D89 supersedes the former representation-label and approach-equivalence premise; D88’s numerical and fingerprint repair remains unchanged.

Added: dense full iterative CCSDT benchmark route (2026-07-14)

run_job(method="ccsdt") and run_job(method="ci", citype="ccsdt") now solve the full ground-state singles, doubles, and triples equations through the exact determinant-space projection of the similarity-transformed Hamiltonian. The route supports RHF and common-orbital ROHF references, chemical-core freezing by default, explicit active spaces, DIIS convergence, and exact four-index MO integrals. Its dense determinant representation is intended for compact benchmark spaces rather than large molecules.

The normal .out reports the converged energy, residual, and amplitude norms through OutputDocument and Quantity; the full iteration table is tagged VERBOSE. Regression coverage pins exact two- and three-electron FCI limits, a nonzero-triples H4/STO-3G ORCA 6.1.1 anchor, the open-shell public runner, memory preflight, citations, .system, and validated QVF output.

The focused vq matrix under <vibeqc-article-release>/calculations/ccsdt/20260714-4d313c25/ increases from H2/STO-3G through nonzero-triples H4, a 3-21G basis expansion, and an H6 chain. All four rows match out-of-process ORCA 6.1.1 AUTOCI-CCSDT; the worst correlation-energy delta is 5.859412e-10 Ha and the worst total-energy delta is 1.557606e-09 Ha. Every QVF validates with structure, citations, and SCF history. The matrix tested pre-rebase SHA 4d313c25; the implementation and QVF fix are its rebased equivalents in main.

Fixed: GDF convergence aids follow the selected driver (2026-07-14)

run_periodic_job now lowers resolved Saunders-Hillier level shifts onto GDF SCF options before dispatch, so supported closed-shell Gamma-fallback and non-Gamma GDF routes execute the requested shift. Drivers without that operator no longer accept and silently drop it: unsupported AUTO choices are reported as capability-filtered zeroes, while explicit nonzero requests fail closed. Exact Gamma detection uses the k-point coordinates and weight, so a shifted singleton still reaches the full GDF loop.

The same capability contract now covers Fock mixing that exact-Gamma RHF/GDF with an explicit gdf_method, or an open-shell GDF k-mesh driver, cannot execute. AUTO records a filtered zero and explicit nonzero requests fail closed instead of advertising an aid the SCF loop ignores.

[v0.15.39] - 2026-07-14 - Neese’s Cheetah

Changed: MSINDO COSMO hot-path benchmark records cavity metrics (2026-07-14)

scripts/bench_semiempirical_hotpaths.py --only msindo_cosmo_h2o --json now records gas-phase, solvation, and polarization energies, COSMO iteration count, and cavity point metadata instead of only the in-solvent total energy.

Fixed: basis-free Hessian requests skip Gaussian-basis setup (2026-07-14)

run_job(hessian=True, method=...) now skips vibrational analysis for basis-free semiempirical and MLIP methods with a clear .out note instead of entering the Gaussian-basis finite-difference Hessian driver with basis="". The citation assembler also no longer records Hessian/gradient-driver references for skipped basis-free Hessian requests.

Added: open-shell direct-torus reference (run_ccm_uhf_direct) + symmetry on one-call correlation (2026-07-14)

run_ccm_uhf_direct – the open-shell (UHF) sibling of run_ccm_rhf_direct: one real Γ-supercell UHF-SCF with the BvK-ewald exchange-q=0 seam applied per spin (F_σ = h + J(D_a+D_b) - [K(D_σ) + ξ_N S D_σ S]). The per-spin seam mirrors K(D_σ), so it is linear+symmetric in D_σ (F_σ = ∂E/∂D_σ holds) and collapses to the closed-shell ½(K + ξ_N S D S) for D_a = D_b = D/2 – verified run_ccm_uhf_direct == run_ccm_rhf_direct to 9e-16 on a closed-shell cell, so the open-shell route inherits the RHF route’s GDF/KRHF parity transitively. The exxdiv=None control sits above "ewald" by exactly ξ_N·N_e/2 per supercell with the same per-spin densities (measured 1e-15; the seam is occ/virt block-diagonal in each spin). dim<3 fails closed like the closed-shell route.

This closes the gap the 2026-07-12 reference="direct" opt-in shipped with: run_ccm_ri_mp2(..., reference="direct") now composes run_ccm_uhf_direct + run_ccm_ump2 for open shells (previously NotImplementedError) – so the BvK-ewald reference the D2 KMP2 anchor validated (HANDOVER_D2_EXXDIV.md) reaches open-shell ionics (NiO-AFM class) too, not just closed shells.

Also: run_ccm_ri_mp2 / run_ccm_ri_ccsd gain symmetry= (forwarded to the ccm_neutral_cderi build), matching the space-group pair reduction the SCF drivers already expose (cc0a3092) – so a symmetric-crystal caller entering through the one-call correlation API gets the star reduction on the fit, not only on the SCF.

Pins: tests/test_ccm_direct.py (closed-shell collapse, open-shell seam identity, dim<3 fail-closed) and tests/test_ccm_mp2.py (test_ri_mp2_reference_direct_open_shell one-call == manual on one shared L; symmetry= passthrough). Defaults unchanged (reference="neutral", symmetry=None); open-shell reference="direct" records BvK-ewald.

Fixed: χ-CCM-B Fock-mixing overrides follow backend support (2026-07-14)

The direct run_aiccm2026dev_b_{rhf,rks,uhf,uks} APIs now resolve one validated Fock-mixing request: a non-None keyword overrides the options field without rewriting it. The override reaches every four-center BIPOLE driver, including both unrestricted spin-schedule phases, while the existing multi-cell fitted RHF/RKS paths retain the same precedence. Fitted RI/RIJCOSX UHF/UKS now reject a nonzero resolved request instead of silently ignoring an options value. Three-dimensional Gamma-only RI RHF also rejects a nonzero request because the shared dispatcher could execute it only by substituting the legacy molecular-limit GDF operator for the declared pair-resolved exxdiv="ewald" operator; resolved zero retains the declared route. On supported execution routes, mixing changes convergence control, not the finite Hamiltonian or backend mixing formula. The Gamma explicit-zero correction deliberately restores the declared operator instead of preserving the old route-selection bug. Successful pre-D87 direct calls with differing keyword/options values, plus fitted RI/RIJCOSX UHF/UKS calls with a nonzero options-only request, have incomplete request fingerprints. Pre-D87 Gamma-only RI RHF calls where either input source was nonzero, including an explicit-zero keyword over nonzero options, followed the legacy operator and are not χ-CCM-B results. The closed-shell fleet inputs are unchanged. The common D72 dimension guard now also runs before every backend-specific Gamma or mixing guard, closing a 1D one-cell RI-RHF escape that could return an absolute energy. Any pre-D87 value from that route is D72-invalid and must not be reported; no 1D/2D χ-CCM-B absolute energy is defined.

Fixed: χ-CCM-B diagnostics report executed Fock mixing (2026-07-14)

AICCM2026DevBDiagnostics.fock_mixing and the fleet convergence_diagnostics.fock_mixing field now record the executed effective previous-Fock weight after backend defaults are resolved, while scf_options.fock_mixing remains the requested input. This distinction matters for DIIS-off four-center KS runs, where requested 0.0 can execute as 0.30. The SCF loop already used the resolved value, so this is a provenance-only correction with no energy, SCF-algorithm, or finite-Hamiltonian change. Pre-D86 records in that case remain useful but are not convergence-fingerprint-complete; comparator v2 is unchanged and still does not bind Fock mixing.

Fixed: semiempirical optimizations cannot enter basis optimizers (2026-07-14)

run_job(optimize=True, method=...) now rejects optimizer_backend="native" and "brent" for basis-free semiempirical and MLIP methods before any molecular optimizer setup, and optimizer_backend="auto" now fails with a targeted ASE requirement if ASE is unavailable. This keeps article-style PM6/OMx/GFN2/DFTB optimization jobs from reaching the Gaussian-basis native optimizer with an empty basis name.

Fixed: periodic OMx rejects unknown variants cleanly (2026-07-13)

load_omx_params(...) and the periodic OMx Gamma wrappers now reject method variants outside om1, om2, and om3 with a clear ValueError instead of leaking a raw KeyError or silently accepting an invalid variant when a caller supplied a compatible parameter object.

Fixed: semiempirical hot-path benchmark fails failed rows (2026-07-13)

scripts/bench_semiempirical_hotpaths.py now exits nonzero when a child case records status="error" or an otherwise successful result reports converged=false, so inventory runs can no longer look green while serializing a failed semiempirical calculation.

Fixed: PM6/OMx runner surfaces gradient failures (2026-07-13)

The unified molecular semiempirical runner no longer swallows arbitrary PM6 or OMx finite-difference gradient exceptions and returns gradient=None. Native gradient failures now propagate from run_semiempirical(...), preserving the original error at the public boundary.

Added: closed-shell A-CCSD(T) triples route (2026-07-13)

run_job(method="ccsd", triples="A-CCSD(T)") and CCSDOptions(triples="a-ccsd(t)") now run the asymmetric/Lambda triples correction on closed-shell CCSD amplitudes instead of raising the old roadmap guard. The route uses the spatial CCSD Lambda solver and reports the effective method/citation key as a-ccsd(t) with Kucharski-Bartlett 1998 and Crawford-Stanton 1998 references.

The focused conventional/DF validation matrix under <vibeqc-article-release>/calculations/a-ccsd-t/20260714-7d4c9102/ covers the smallest nonzero former guarded call through a frozen-core cc-pVTZ row. All four user-facing vq jobs and QVF archives pass; against out-of-process PySCF CCSD Lambda references, the worst energy-component delta is 1.071498e-10 Ha and the worst Lambda-norm delta is 2.118492e-08.

Fixed: single-state IC-CASPT2 IPEA matches the canonical class contraction (2026-07-13)

Nonzero CASPT2Options(ipea=...) now builds the canonical eight internally contracted double-excitation classes, retains diagonal active generators, and applies the IPEA correction after class-local canonical Lowdin orthogonalization. This removes the old representation-dependent 0.123 mHa H2/6-31G mismatch and agrees with fresh out-of-process OpenMolcas references within 0.02 µHa for H2/6-31G CAS(2,2) and 0.33 µHa for H2O/6-31G CAS(4,4), which exercises all eight classes. The single-state variant="ic", engine="auto" route is therefore un-gated. Selected-reference, matrix-free engine="cases", and MS/XMS-CASPT2 IPEA combinations still fail closed with explicit capability messages. The ipea=0 solver path is unchanged.

Changed: BIPOLE uses the pair-resolved padded SR domain and symmetry reduction by default (M5) (2026-07-13)

The maintainer approved the paired v0.16 default change. All four direct BIPOLE drivers and run_periodic_job now expose sr_image_precision=1e-6: every active erfc short-range J/K build derives an absolute internal ket-image radius as cutoff_bohr + bipole_sr_image_extent(...) and automatically enables the M4b separation-aware QQR screening bound. Pass sr_image_precision=None only to reproduce the historical unpadded traversal; on an active erfc SR route this also suppresses automatic SYM3b reduction unless it is explicitly requested. An explicit sr_image_extent_bohr remains the M4a absolute-radius oracle and takes precedence without changing the caller’s screening choice. The unrestricted spin-schedule recursion now forwards both controls.

On an attached-symmetry crystal using the corrected Ewald exchange split, use_fock_symmetry_reduce=None now auto-enables the group-invariant SYM3b representative build in RHF, RKS, UHF, and UKS. Explicit False disables the automatic reduction, while enforcement-only diagnostics use use_fock_symmetry=True, use_fock_symmetry_reduce=False. The full M1-M4b chain makes reduce equal enforcement end to end; J-block orbit asymmetry is 6.3e-15 (MgO), 5.4e-15 (diamond), and 4.4e-15 (Si) on the maintained group-covariant Gamma-fold probe.

This is a correctness-changing re-pin, not a byte-preserving refactor. On the MgO split fixture the repaired split is 81.4173579654 Ha; the finite-KE exact-FT reference remains 80.9288590106 Ha, leaving the independently diagnosed +0.4884989548 Ha exact-FT reciprocal-tail gap and removing the old -0.5153 Ha SR truncation loss. The screened padded build costs about 5-17 times one historical SR build on diffuse cells, but attached-symmetry orbit compression reduces the measured cutoff-6 Gamma net to about 2.7 times. The converged MgO/STO-3G c12 [2,2,2] RHF baseline re-pins to -271.2147846320 Ha, within 3.359 mHa of a fresh local CRYSTAL23 SHRINK-8 run (-271.21814374982 Ha) and 1.429 mHa of a fresh isolated PySCF 2.13.1 KRHF/GDF run (-271.21335558268 Ha) at the matching Ewald exchange convention.

The analytic BIPOLE gradient result contract now records the resolved SR radius and pair-domain provenance, and fails closed for either M5 domain because its derivative kernel still implements the historical radial and unpadded traversal. The exact finite-difference gradient remains the production path and differentiates the new energy correctly. Pure semilocal RKS keeps its exact-FT J default in this merge, per the maintainer’s ruling that its retirement be separate. The dedicated HSE screened-exchange traversal is also outside this change and remains owned by its parallel workstream.

Fixed: semiempirical geometry provider honors charge and spin overrides (2026-07-13)

SemiempiricalProvider(charge=..., multiplicity=...) now applies explicit constructor overrides to the molecule evaluated by the model factory. Omitting those knobs preserves the charge and multiplicity already stored on the per-call Molecule, so charged/open-shell molecules no longer depend on an ignored constructor field or a forced closed-shell default. The same provider now strips method-name whitespace and accepts the established dftb, dftb-0, gfn2_xtb, and gfn2xtb aliases, and it exposes SCC-DFTB through scc-dftb, scc_dftb, and sccdftb by routing to the existing SCCDFTBModel.

Fixed: vibe-view camera presets/bookmarks move the live client view (audit L1) (2026-07-12)

Camera presets and bookmark/slide/session restores set the server-side plotter camera and pushed geometry (ctrl.view_update()), but VtkLocalView keeps its own client camera, so the live view often didn’t move. The viewer now wires VtkLocalView.push_camera (which sends the server’s exact camera, unlike reset_camera’s fit-all that would lose a bookmark’s zoom): camera_preset calls it after applying a preset, and _apply_camera pushes the applied camera for every bookmark/slide/session/load path. Wiring covered by tests/test_audit_2026_06_03.py::test_apply_camera_pushes_client_camera, and live-verified in a browser (the client camera reorients when the view presets are clicked).

Fixed: GFN2 rejects unknown SCC mixer names (2026-07-13)

GFN2Model(scc_mixer=...) now validates the mixer name at construction and raises ValueError for unknown values instead of silently falling back to simple mixing. The model docstring now names the actual default mixer (simple), matching the long-range ionic SCC stabilization path. The same boundary cleanup makes GFN2Model(..., params=None) call the published-parameter loader instead of constructing an empty native GFN2ParameterSet.

Added: semiempirical route status accepts public method aliases (2026-07-13)

semiempirical_route_status(...) now resolves public method names and common aliases such as dftb0, scc-dftb, gfn2xtb, om2, msindo, and ccm to the canonical semiempirical route labels. This keeps the status API aligned with the unified runner surface and makes article/status checks less dependent on internal route names.

Fixed: the BIPOLE SR range-screening flag now reaches the 2-e build (2026-07-13)

run_periodic_job(sr_range_screening=True) sets options.lattice_opts.sr_range_screening, but the field was absent from prepare_bipole_lattice_options’ passthrough allowlist (_LATTICE_PASSTHROUGH_FIELDS), so the derived lat_opts_2e reverted to the default False and the direct erfc J/K build ran UNSCREENED while the run still cited the QQR bound. Same class as the 2026-07-13 threshold passthrough fix; the M4b flag is now in the allowlist. The test_prepare_lattice_options_propagates_user_screening_thresholds regression (loop over the allowlist) now exercises the flag non-vacuously.

Fixed: vibe-view raytrace indicator + animation-reset flicker (audit L2, L3) (2026-07-12)

Two viewer correctness fixes from the 2026-06-03 audit:

  • L2 — a scene rebuild (_rebuild_scene / _reload_active_file, which plotter.clear()) dropped the OSPRay render pass while raytrace_enabled stayed True, so the ray-trace indicator lied until toggled twice. The new _reapply_raytrace re-attaches the pass after a rebuild (or clears the flag if it can’t), so the indicator always matches reality.

  • L3 — the async trajectory/vibration play loops cancelled cooperatively with no generation token, so a stale iteration could flush() one frame onto the scene after a section switch reset it (one-frame flicker). Both loops now bump an epoch on every play/stop and bail after their await if superseded, before rendering.

L1 (camera preset/bookmark client resync) stays deferred: it needs a live client and the deployed trame-vtk camera API to fix without regressing the camera (see vibe-view/AUDIT_2026-06-03.md).

Fixed: CCM runner rejects malformed option objects cleanly (2026-07-13)

The unified semiempirical CCM dispatcher now validates duck-typed ccm_options before entering the C++/Python CCM kernels: an object with translations but no explicit madelung flag raises a clear ValueError instead of leaking an implementation AttributeError, and malformed lattices that are not 1, 2, or 3 numeric three-component translation vectors are rejected at the same boundary. The direct CCM energy, finite-difference gradient, and optimizer APIs now share the same lattice-shape validation, and the direct finite-difference gradient reports unsupported elements through the same MSINDO-scope NotImplementedError as direct energy. The adjacent runner docstrings now match the fail-closed nddo=True behavior, and the CCM options docstring reports the current H-Xe MSINDO element scope.

Changed: χ-CCM-B records operator applicability explicitly (2026-07-12)

χ-CCM-B convention records now distinguish the declared exchange_q0="bvk-ewald" family convention from whether that full-range seam is active in the actual operator. RHF/UHF and global hybrids record active; pure functionals and HSE06 record inactive, with HSE retaining its finite short-range screened exchange. The Γ/χ comparator also exposes a separate defined/not-defined status and never renders an unavailable comparison as a blank beside an ok χ record. D85 specifies the stationary energy-weighted-density binding required before the fixed-W overlap component can enter a total gradient; total χ-CCM-B gradients remain fail-closed.

[v0.15.38] - 2026-07-13 - Neese’s Cheetah

Fixed: user-set BIPOLE lattice screening thresholds now reach the two-electron build (2026-07-13)

prepare_bipole_lattice_options built the derived one- and two-electron LatticeSumOptions as fresh objects that copied only cutoff_bohr and nuclear_cutoff_bohr from the user’s options.lattice_opts. A schwarz_threshold, schwarz_threshold_forces, screening_overlap_threshold, screening_exchange_threshold, or slab_ewald_alpha set by the user therefore silently never reached the BIPOLE J/K build in any of the four drivers (RHF/RKS/UHF/UKS) — the 2-e build always ran at the compiled defaults (schwarz_threshold = 1e-12, …). The derived sets are now passthrough copies of the user’s options (_LATTICE_PASSTHROUGH_FIELDS), overriding only coulomb_method, which the CRYSTAL gauge fixes per channel (2e always DIRECT_TRUNCATED; 1e EWALD_3D on 3-D cells). Every passthrough field defaults to the value the fresh object previously carried, so a run that never set these knobs is byte-identical; only a user who explicitly tuned a threshold sees a change, and that change is the value taking effect. Found during the 2026-07-13 _combine_density_sets truncation audit. Regression: tests/test_pbc_bipole_common.py::test_prepare_lattice_options_propagates_user_screening_thresholds.

Fixed: unrestricted BIPOLE total density no longer truncated to the radial template in the J-only build (2026-07-13)

build_bipole_unrestricted_fock formed density_total = D_α + D_β on the 1×-cutoff radial overlap template (_combine_density_sets) while the per-spin densities live on the 2×-cutoff list under the Ewald exchange split, so the pure-functional (alpha_hf = 0) J-only build contracted a total density whose P(h) blocks for |h| in (cutoff, 2×cutoff] were silently zeroed (the C++ builder skips missing P(h) lookups without complaint). The total is now combined on the spin densities’ own template in every mode, generalising the pair-resolved M3 branch; the templates coincide outside the split, so the legacy / non-split paths are block-identical.

Measured blast radius (MgO/STO-3G Γ, 2-iter deterministic, DIIS off, no symmetry flags):

  • Production default: no numerical change. The cell-level Schwarz guard requires the ket displacement h to sit inside the internal (1×) cell list, so the J traversal never read the dropped P(h) in the first place. UHF ≡ RHF at cutoffs 6/8/10 to ≤ 2.5e-10 Ha before and after; UKS(SVWN) iteration-1 J components ≡ RHF to 3e-14. Hybrids/UHF were never affected (per-spin J on the wide densities); no re-pins needed.

  • The truncation was live exactly where the internal ball is wider than the radial list: the M4a sr_image_extent_bohr padded build (shipped in v0.15.x, and the escalated default-flip candidate) and Schwarz-off builds. Measured: He/STO-3G a=4 c=4 with an 8-bohr padded ball loses ΔJ_SR = −2.5e-3 Ha of the +5.1e-3 Ha the pad adds (UKS-pure vs RHF, iteration 1, identical density); C++-level max|ΔJ| = 3.0e-2 on an MgO/STO-3G c6 unit-scale density with screening off. Regression pin: tests/test_pbc_bipole_uks.py::test_uks_pure_functional_j_contracts_full_wide_density (UKS-pure padded J components ≡ RHF’s). Pair-resolved SYM3b had already fixed this for its own mode (M3); this closes the remaining modes before the padded-domain default flip would have turned the latent defect into a live UKS-only energy error.

Added: closed-shell ground-state CC2 (2026-07-12)

run_job(method="cc2") and run_job(method="ci", citype="cc2") now solve the Christiansen-Koch-Jorgensen ground-state CC2 equations on an RHF reference. The route uses the complete singles projection and the first-order doubles equation from the T1-transformed Hamiltonian plus the diagonal [F,T2] commutator; conventional and density-fitted integrals, explicit frozen cores, citations, normal output sidecars, and QVF are wired through the public runner. CC2 is energy-only, closed-shell, and intentionally does not accept a perturbative-triples selector.

The dense small-molecule kernel is pinned against an independent spin-orbital equation oracle. An increasing-size public-route matrix from H2/STO-3G through frozen-core H2O/cc-pVTZ agrees with out-of-process ORCA 6.1 AUTOCI-CC2 to at worst 5.20e-9 Ha in correlation energy and 1.21e-8 Ha in total energy; all QVF archives validate. Artifacts: <vibeqc-article-release>/calculations/cc2/20260712-da1292db/.

Fixed: unrestricted β orbitals show their own symmetry labels (vibe-view, audit L7) (2026-07-12)

For an unrestricted wavefunction.gto, the QVF reader collapsed the α and β symmetry-label lists into one (alpha or beta) and the MO picker indexed that single list for both spins, so β orbitals displayed α’s symmetry labels. The reader now keeps the two lists separate (WavefunctionGTOData.symmetry_labels for α / restricted, new symmetry_labels_beta for β) and the renderer labels each spin block from its own list. Round-trip test: tests/test_wavefunction_normalization.py::test_unrestricted_per_spin_symmetry_labels.

Added: separation-aware SR screening + the padded-ball compositions (pair-resolved M4b) (2026-07-13)

Three pieces of the M4 internal-domain fix:

  • QQR-style separation-aware screening for the direct erfc SR J/K build (LatticeSumOptions.sr_range_screening, opt-in): the Schwarz product is additionally bounded by erfc(√m_ω·R) with 1/m_ω = 1/γ_bra + 1/γ_ket + 1/ω² — the split-gap triage’s smeared kernel range, evaluated per shell quartet with conservative (most-diffuse-primitive) exponents and a lower-bounded pair separation, plus a cell-level early exit. Plain Schwarz is separation-blind, which is why the M4a radial pad cost 35×; measured on MgO/STO-3G c6 → 22-bohr ball: 45.7× → 17.4× (2.6× cheaper) at ≤ 1.2e-10 agreement; at the production ball it is already slightly faster at ~1e-12 agreement. Skips only — accuracy stays at the schwarz_threshold scale; ω = 0 builds are untouched. Cited via the new bipole_sr_range route (Maurer, Lambrecht, Flaig & Ochsenfeld, JCP 136, 144107 (2012)).

  • sr_image_extent_bohr now composes with the SYM3b flags (both mappings, enforce and reduce; previously NotImplementedError): the padded internal ball runs through the M1 full-domain binding, and the padded pair-resolved home block equals the M4a radial padded build bitwise (the oracle contract).

  • Padded pure-functional J no longer computes a wasted K (_sr_image_padded_jk(compute_exchange=False) through the M1 binding — M4a built and discarded a full padded K).

The remaining M4 decision — flipping the interaction-resolved padded internal domain ON BY DEFAULT (the −515 mHa J_SR correctness fix for every erfc build) — moves baseline energies and costs ~an order of magnitude on diffuse-basis cells even with the screening; it is escalated to the maintainer with the measured numbers (handovers/HANDOVER_BIPOLE_PRODUCTION.md §0a).

Changed: SYM3b opt-in flags now run on pair-resolved truncation domains (pair-resolved M3) (2026-07-13)

use_fock_symmetry / use_fock_symmetry_reduce (still opt-in) now build on the group-invariant pair-resolved domains in all four BIPOLE drivers, under the production Ewald exchange split: the output triple set and per-cell masks (M2), a pair-resolved density support (criterion radius 2× cutoff, masked to qualifying pairs after every density build — the radial 2×-cutoff list chops 3 of MgO’s 6 symmetry-equivalent nearest-neighbour Mg–O couplings), the extended overlap background fold, and a dedicated J^LR cache on the pair-resolved operator template. Orbit closure is exact (require_closed=True) and the reduced build == enforcement to 1e-12 end-to-end (RHF ≡ reduce exactly; UHF matches RHF to 1e-10 on closed-shell MgO). Measured block-level J orbit asymmetry on a group-covariant Γ-fold density: 3.6e-2 (legacy radial domains) → 1.6e-3 (pair-resolved M3) → ~1e-6 with an M4-style padded internal ball — the remaining floor is the internal summation domain, which M4b makes interaction-resolved. Energies under the opt-in flags move at truncation-error level because the truncation SET changes to the pair-resolved one (MgO c6 2-iter RHF: −271.854 → −271.173; parity numbers under these flags need re-derivation, tracked for M5). Legacy non-split runs and all no-flag runs are unchanged. Fail-closed compositions: sr_image_extent_bohr, the multipole far-field, and screened-exchange-without-reduce raise NotImplementedError under pair mode (M4b wires the first; HSE composes via use_fock_symmetry_reduce=True).

Added: pair-resolved truncation domain + exactly-closed SYM3b orbit mapping (pair-resolved M2) (2026-07-12)

The group-invariant truncation domain CRYSTAL-style pair screening implies: vibeqc.pair_resolved_truncation builds {(a,b,h): |r_b + L·h r_a| cutoff} (orbit-closed by union against the symmorphic operator set, so boundary ties cannot break closure), and identify_atom_pair_orbits accepts a triples= restriction of its triple space. Because the pair criterion is preserved by the triple action h R·h + s_a s_b, the orbit partition now runs with require_closed=True — no silently skipped orbit partners. New pair_resolved_fock_mapping + build_jk_pair_resolved + pair-resolved-aware build_jk_reduced_symmetrized route the SYM3b build through the M1 full-domain binding; the reduced masked build is bit-identical (0.0) to the full build + enforcement on the same domains, and non-qualifying sub-blocks are exact zeros. On MgO c6 the domain reproduces the 2026-07-12 probe: 38 triples → 6 orbits (6.33× compression, up from 4.3× radial) and keeps all 6 symmetry-equivalent nearest-neighbour Mg–O couplings (a radial list chops 3). Not yet wired into the SCF drivers (the driver flip + density support is the next milestone); resolve_bipole_fock_symmetry grows a dormant exchange_split_active switch for it.

Fixed in the same change: symmetrize_fock_blocks now associates blocks with cells BY KEY (with fail-closed coverage checks) instead of by position. The opt-in use_fock_symmetry + pure-functional exact-FT J combination silently scrambled blocks before: the enforcement ran on the density (2×-cutoff) template while the mapping lived on the cutoff list, and radial lists at different radii are not positional prefixes of each other. Enforcement also now leaves sub-blocks outside the mapping’s triple space untouched instead of zeroing them (identical behavior for the legacy full-product mapping; required for restricted triple spaces and wide templates). Pinned in tests/test_pair_resolved_truncation.py.

Added: full truncation-domain control binding for the direct J/K build (pair-resolved M1) (2026-07-12)

build_jk_2e_real_space_domains exposes the direct-ERI builder’s long-standing internal capabilities through one binding: the caller supplies the internal (c_g, c_λ, c_σ) summation cell list (the other entry points derive it radially from cutoff_bohr), the output bra-cell subset, optional per-output shell-pair masks, and compute_exchange=False for J-only builds; the density support stays data-level via the density’s own cell list. Purely additive — no existing entry point changes; byte-identity against build_jk_2e_real_space / _output_subset / _output_subset_masked pinned in tests/test_bipole_jk_domains.py. This is the plumbing for the pair-resolved (group-invariant, CRYSTAL-style) truncation-domain workstream (M2–M5 in handovers/HANDOVER_BIPOLE_PRODUCTION.md §0a): the SYM3b orbit-closure fix, the efficient M4b internal-domain default, and the TOLINTEG-mapping prerequisite for CRYSTAL23 parity.

Next up: the remaining narrow BIPOLE analytic-gradient convergence edge case (test_bipole_gradient.py) and GAPW iteration-count assertion (test_periodic_gapw_cpp_host.py) tracked in scripts/test_gate/known_reds_baseline.json, continued low-D CCM kernel work (docs/aiccm2026dev_a_lowd_greens.md), and the periodic-scf / regression-suite reds carried over from earlier releases. χ-CCM-B also needs an explicit, converged sr_image_extent_bohr contract using the landed M4a oracle, or the efficient M4b production default, before BIPOLE-routed four-center absolute energies enter the paper fleet. The B selector and fleet runner do not yet expose or attest M4a, and the default remains unpadded. M4a also does not pad HSE’s dedicated screened-exchange traversal, and the current UHF/UKS spin-schedule recursion drops an explicit extent; those paths remain unattested even at the low-level driver surface. Restricted semilocal four-center and dense-core fitted routes separately need their reciprocal-tail and high-G attestation.

Added: freeze-atom constraints for live geometry optimization (vibe-view, B3) (2026-07-12)

The Atom Editor gains Freeze Selected / Unfreeze All buttons. Atoms you select can be held fixed while Auto-optimize relaxes the rest; frozen atoms show a distinct blue “locked” marker. The selection is fed to the live-opt worker through the frozen field of the wire protocol (live_opt.build_request), and the shared L-BFGS-B loop honors it for every engine (MSINDO and MACE) by varying only the free-atom coordinates. Freezing all atoms surfaces a friendly status line instead of crashing, and frozen indices are cleared whenever a structural edit (delete, undo, redo, file switch) would reindex the atoms. Backend semantics pinned by tests/test_live_opt.py::test_frozen_atoms_held_fixed and ::test_run_optimization_rejects_all_frozen.

Changed: BIPOLE KS auto-FMIXING engages only without DIIS (Gap-B validated) (2026-07-13)

Gap-B verdict (HANDOVER_BIPOLE_PRODUCTION.md §0a): the historical ~0.5 Ha MgO-primitive RKS oscillation does not exist in the corrected-gauge code. Measured on MgO/STO-3G [2,2,2] SVWN c6 from SAD, no aids: plain DIIS converges strictly monotonically in 10 iterations; PySCF KRKS converges the same fixture in 7 cycles from every standard guess (out-of-process, PySCF 2.13.1, GDF). CRYSTAL-style FMIXING-30% is validated as a Roothaan damper — the DIIS-off iteration wobbles at ~1 mHa and stalls at 25 iterations, while FMIXING-30 converges it in 18 to the same fixed point (−270.91985519, identical to 1e-9 Ha across all four aid settings). Under DIIS the mixing is redundant damping and only slows convergence (14 vs 10 iterations), so the pure-DFT auto default in run_pbc_bipole_rks/_uks now fires only when use_diis=False (shared pbc_bipole_common.resolve_auto_fock_mixing, unit-pinned; explicit fock_mixing values are honored unchanged). The convergence="auto" strategy floor mirrors the driver (floor reported only for DIIS-off BIPOLE KS runs); the ionic-insulator profile value is unchanged. New no-aids monotone-convergence regression (test_rks_bipole_mgo_no_aids_converges_monotonically) pins the absent oscillation per CLAUDE.md §7.

The same validation quantified the pure-RKS exact-FT J reciprocal tail at the ke=200 default on MgO: +0.997 Ha of J mass between ke 200 and 800 at fixed SAD density, +0.987 Ha between the converged fixed points, +0.043 Ha between 800 and 2000 (density-independent core-pair content; the SCF landscape is untouched — identical 10-iteration monotone convergence at both ke). The closure check: c12 no-aids −271.5306506 plus the measured tail reproduces PySCF KRKS −270.4996235 to ~0.4 mHa — no gauge defect. The warn_bipole_exact_j_core_tail diagnostic (landed in the parallel pair-resolved workstream) now quotes the measured ~1.0 Ha magnitude (the earlier ~0.49 Ha figure was ke300-referenced) and gains unit tests.

Fixed: MgO BIPOLE-vs-PySCF parity example fed PySCF a displaced O geometry (2026-07-13)

examples/regression/parity_mgo_rks_bipole_vs_pyscf.py divided its bohr coordinates by ANG2BOHR while declaring cell.unit="B", parking O at 2.105 bohr instead of the rocksalt a/2 = 3.978 bohr site (a ~1.8 mHa reference bias). PySCF references must use the fixture’s unit-exact geometry.

[v0.15.37] - 2026-07-12 - Neese’s Cheetah

Fixed: χ-CCM-B gradient audits bind the complete finite torus (2026-07-12)

Superseded in [Unreleased] by D88 (2026-07-14). This historical D81 entry implemented the wrong row-vector premise. PeriodicSystem.lattice stores vectors as columns, so the corrected binding is system.lattice @ diag(mesh); the other mesh, descriptor, and character-net guards below remain valid.

Every χ-CCM-B component helper now requires the result mesh, character mesh, and BvK repetitions to agree and binds the recorded row-vector BvK lattice to diag(mesh) @ system.lattice within 1e-12 bohr. Diagnostics and top-level convention descriptors must agree. SCF-density folding additionally requires the complete unreduced, unshifted Γ-centred character set with uniform weights and one representative of every residue. Skew (2,3,1) coverage rejects the wrong column-scaled lattice construction, while duplicate, incomplete, shifted, and nonuniform character nets fail closed.

This is provenance hardening, not a new force term. It does not attest atoms, basis identity, resolved operator cutoffs, or the source of caller-supplied density/energy-weighted-density blocks. Total χ-CCM-B gradients remain fail-closed. The same milestone classifies the newly diagnosed BIPOLE internal-domain, pure-RKS reciprocal-tail, and dense-core RSGDF high-G limits as numerical-support blockers for quantitative absolute-energy records; they are not new Hamiltonian conventions or Γ/χ representation differences.

Changed: pure-RKS exact-FT Hartree now surfaces its core-tail undercoverage (2026-07-13)

The split-gap triage found that the exact analytic-FT Ewald J used by pure-RKS BIPOLE at the production VIBEQC_J_EWALD3D_KE=200 misses ~0.49 Ha of density-independent core reciprocal content in absolute totals on dense-core cells (cancels in energy differences; breaks absolute CRYSTAL/PySCF parity; brute-mesh convergence needs ke ≈ 2·ln(1/ε)·γ_max ≈ 11000 Ha on Mg). Until the pair-resolved SR+LR composition replaces it, the caveat warns instead of hiding: warn_bipole_exact_j_core_tail estimates the needed ke from the basis’s most compact primitive and emits a RuntimeWarning + .out notice whenever the route runs under-covered. Pins in tests/test_bipole_production_guards.py (MgO reproduces the ~11000 Ha estimate and warns; H₂ stays silent; a covering cutoff is silent).

Added: opt-in SR ket-image pad for BIPOLE (sr_image_extent_bohr, M4a) (2026-07-12)

First landing of the M4 internal-domain fix from the split-gap triage: all four BIPOLE drivers accept sr_image_extent_bohr, which extends the SR erfc traversal’s internal (c_λ, c_σ) ket-image ball past cutoff_bohr while output blocks stay on the cutoff cell list (via the existing output-subset binding — no C++ change). Exactness contract pinned in tests/test_bipole_sr_image_extent.py: the padded home-cell J/K blocks equal a plain build at the extent cutoff to 1e-12, transitively anchored to the Poisson-summation truth values by the wide-ball pin landed with the triage. pbc_bipole_common.bipole_sr_image_extent computes the safe extent from the smeared-kernel range √(ln(1/ε)/m_ω) of the most diffuse primitive pair plus the atom-offset bound.

Measured on MgO/STO-3G c6 (2-iter RHF): the pad recovers +0.72 Ha of truncated J_SR mass — and costs 35× (the diffuse tails it recovers are exactly what Schwarz cannot prune). It is therefore deliberately OPT-IN: the correctness instrument for parity/validation work and diffuse-basis users, while the efficient default fix remains the pair-resolved M4b (interaction-resolved selection, J-only subset binding, SYM3b-reduce composition — fail closed or noted in the code). Default behavior is byte-identical (M-series check: worst 1.1e-13, RKS noise floor).

Changed: one orbital-table renderer for RHF / ROHF / UHF; new UHF golden (2026-07-12)

The #/occ/eps(Ha)/eps(eV) orbital-energy table was defined four times in scf_log.py – once each for RHF, ROHF, and the UHF alpha/beta blocks – each repeating the header, the "-" * 54 rule, and the row format string, so a width tweak in one could silently drift from the others. They now share a single _orbital_table_lines(...) helper; each caller supplies only its per-row occupation, its HOMO/LUMO/SOMO marker, and its gap line. Byte-neutral (the mol_rhf and the new mol_uhf goldens are byte-identical across the refactor; ROHF verified live). The deliberate dual Ha/eV columns are kept – an orbital energy is universally quoted in both. New mol_uhf snapshot (OH doublet) freezes the previously-unfrozen UHF .out format: the spin-resolved alpha/beta orbital tables (HOAMO/LUAMO, HOBMO/LUBMO markers) and the open-shell energy-components / population blocks.

Changed: properties tables (charges / bond orders / dipole) use the Table primitive (2026-07-12)

The atomic-charges, Mayer-bond-order, and dipole-moment tables in the shared scf_log.py properties block (every molecular .out) now render through vibeqc.output.Table instead of hand-formatted rows under hardcoded "-" * 66 / 52 / 40 rules – columns and rule size to content, and the per-scheme charge totals render via a new Table.footer() (a total row set apart by a rule) instead of a hand-formatted sum line. Table gained footer(*cells): at most one footer, its cells count toward column widths so it stays aligned, rendered after a separator rule. Shared by both runners but periodic passes molecule=None (no properties block), so only the molecular goldens (mol_rhf / mol_rks / mol_hessian) regenerated; 4 new Table.footer tests. This harmonizes the properties tables with the SCF / frequency / TD-DFT tables (all now the single-underline, content-sized Table style).

Changed: IR + TD-DFT spectra tables now use the document-layer Table (2026-07-12)

The hand-built vibrational-frequency table (Mode / Freq(cm⁻¹) / IR(km/mol)) and the TD-DFT excited-states table (State / E(eV) / λ(nm) / f_osc / dominant) in runner.py now render through vibeqc.output.Table instead of fixed-width f-strings under a hardcoded "-" * 52 rule. Columns and the rule size to content, so e.g. the IR table’s rule drops from 52 to 28 chars and the columns fit their data – the harmonization the Table primitive was built for. Numeric cells are pre-formatted (frequencies via render_frequency, so eV/THz output units still apply); the imaginary-mode i suffix and the free-text dominant-transition column are preserved. New mol_tddft golden snapshot freezes the excited-states table; mol_hessian regenerated for the IR table. The TD-DFT UV/Vis stick spectrum is left as-is – it is an ASCII bar chart, not a data table, so Table is not the right primitive for it.

Fixed: periodic optimized-geometry files no longer clobber the SCF geometry (2026-07-12)

run_periodic_job(optimize=True) intended to write separate {stem}.opt.xyz / {stem}.opt.POSCAR optimized-geometry artefacts, but the call sites passed output_stem.with_suffix(".opt") to writers that do Path(stem).with_suffix(".xyz"|".POSCAR") – which replaces the .opt, so h2.opt became h2.xyz and the optimized geometry overwrote the SCF {stem}.xyz/.POSCAR; no .opt.* file was ever created. The stems are now passed already ending in .opt.xyz / .opt.POSCAR (making the writer’s suffix-replace a no-op), so the optimized geometry lands in its own file and the SCF geometry is preserved. Both .opt.* files are recorded in the .system manifest. Verified live: a periodic optimize run now emits h2.xyz (SCF) and h2.opt.xyz / h2.opt.POSCAR, all recorded.

Fixed: .system manifest now records NTO molden + periodic CIF (2026-07-12)

Two artefacts a job produced were missing from the .system manifest’s [[outputs.files]], so the manifest under-reported what the run wrote:

  • NTO molden files ({stem}.nto_S<n>[_spin]_{hole,electron}.molden, molecular TD-DFT + nto=True + tddft_molden=True) are written to runtime-dependent paths not in the output plan, and were recorded nowhere. run_job now record()s each one as it writes it (appended to [[outputs.files]]; verified live).

  • Periodic CIFrun_periodic_job’s post-SCF real_plan omitted write_cif=, though the CIF write is gated on write_cif_file and the dry-run plan declared it. So a written .cif was never recorded by the plan sweep. real_plan now declares it, matching the dry-run plan.

Both are pure provenance fixes (the files were always written; now they are tracked). mark_written/record already support undeclared paths (tests/test_output_manifest.py::test_undeclared_files_are_appended_to_outputs).

Changed: vibrational frequencies + thermochem temperature under FormatPolicy (2026-07-12)

New frequency quantity kind (dimension wavenumber, canonical cm⁻¹) with render_frequency(), and a render_temperature() wired to the existing temperature kind. The vibrational-frequency block’s f"{freq:.1f}" values (and the imaginary-mode magnitude) and the thermochemistry T = {:.2f} K line in runner.py now route through them – byte-identical at the defaults (cm⁻¹, K), so the .out is unchanged, while VIBEQC_OUTPUT_UNITS=wavenumber:THz (or :meV) converts frequencies. New exports: render_frequency, render_temperature; 3 tests. Verified against a live H₂O Hessian run.

Added: regression guard – the .out writers never print() (2026-07-12)

tests/test_out_writers_no_print.py AST-checks that the files which format and emit .out content – runner.py, periodic_runner.py, the package __init__’s double-hybrid writer, and the vibeqc.output writers – contain no bare print() call, so user-facing output cannot silently escape the central channel again (the drift the logger workstream unified). Scoped to a curated list of verified-clean writers, mirroring test_semiempirical_output_contract.py; it is not a repo-wide print() ban (console scripts, ProgressLogger live-progress, and __main__ demos legitimately print, which a grep cannot tell apart from escaped .out content).

Changed: timings block honours the FormatPolicy (duration kind) (2026-07-12)

The wall-clock timings block – the last common .out quantity still hand-formatted as a bare f"{t:12.3f}" – now renders through a new duration quantity kind (dimension time, canonical seconds). vibeqc.output.render_duration(value, *, width, precision) is byte-identical to f"{value:{width}.{precision}f}" at the default (seconds) and converts under VIBEQC_OUTPUT_UNITS=time:ms (or time:min). The eight timing sites in runner.py and periodic_runner.py (SCF total, per-iteration average, job total, geometry-optimisation, MLIP evaluation) route through it; the block header keeps its literal (wall clock, seconds). Byte-neutral (goldens green; a live RHF run confirms the 12.3f shape is unchanged). With this, every common quantity in the .out – energies and durations – is policy-driven; the only hand-formatted numbers left are intentional dual-unit displays. New export: render_duration; 2 tests.

Added: periodic geometry-optimisation trace reaches the .out (MS3) (2026-07-12)

Before this, a periodic run_periodic_job(..., optimize=True) wrote the final Optimized geometry block to the .out but the relaxation trace itself (Atomic relaxation: N iters, ..., cell-relaxation lines, outer-cycle lines) went only to stdout – so a batch .out record showed the optimised geometry with no trace of how it got there. Now the whole relaxation runs inside an append OutputChannel and the bipole_optimize relaxers (relax_atoms / relax_cell / relax_cell_gradient / relax_full) grew a file-like progress= parameter (default stdout, preserving standalone behaviour) that run_periodic_job points at the channel – the same progress=<channel> pattern the molecular path already uses for run_geomopt. The trace now lands in the .out under a Geometry optimization header. New golden snapshot periodic_geomopt; the four existing goldens are unchanged (non-optimize paths are byte-identical). No vibeqc.output change was needed – OutputChannel was already a valid progress stream.

Changed: double-hybrid energy block honours VIBEQC_OUTPUT_UNITS (2026-07-12)

_write_double_hybrid_outputs (the .out for B2PLYP-style double hybrids) was the last SCF-energy block still hardcoding {:18.10f} values under a literal Energy components (Ha) header. It now renders each value through render_energy and takes the header unit from active_policy(), matching the molecular + periodic SCF paths – so VIBEQC_OUTPUT_UNITS=eV relabels and converts it too. Byte-identical at the Hartree default (the double_hybrid golden snapshot is unchanged). The earlier double-print of each energy was already collapsed; this is the units slice.

Changed: remaining runner WARNING: writes routed through warn() (2026-07-12)

Feature #4 adoption, round 2. The rest of the hand-written WARNING: lines in the runners now go through vibeqc.output.warn(), so every warning fires a structured warning event and survives --quiet:

  • Nine 2-space artefact-write-failure warnings in periodic_runner.py (molden, population, extended-xyz, POSCAR, citations, XSF, density grid, and the two χ-CCM-B / periodic-QVF overlays) – byte-neutral, each already paired with a warn_output_failure(...) provenance record, now with a role= field on the human-facing warning too.

  • The POSSIBLY CONDUCTING STATE gap warning in runner.py – byte-neutral (surrounding blank lines kept; the stdlib warnings.warn is preserved; the HOMO-LUMO gap stays a hardcoded dual-Ha/eV display).

  • The periodic insulator-smearing note – normalised from a 4-space in-block echo (    WARNING: ...) to the canonical   WARNING: ... surface. This is the one intentional byte change; no golden covers it (it fires only when insulator smearing is auto-applied).

The plog.info("  WARNING: ...") sites are the live-progress surface (ProgressLogger, § 16) and are deliberately left there; the pbc_bipole* warnings live in bipole-owned modules and are an ask to that chat.

Changed: runner SCF error/retry sites unified onto record() (2026-07-12)

Feature #3 adoption. The 10 places in runner.py that wrote a human line and separately fired the matching structured event for the same fact – SCF failure (scf_failed), the RHF-SAD / RKS-TRAH auto-retry lifecycle (scf_retry / scf_retry_failed / scf_retry_done), the non-convergence FATALs (scf_nonconverged, solver_nonconverged), and the semiempirical binding-energy block – now call record(event, human, **fields) once, so the .out line and the .scf.jsonl record cannot drift apart. Byte- and event-identical: the human text, event name, and every field are unchanged (the emit now fires just before the channel flush() instead of just after, which is unobservable – they are independent streams). New import in runner.py: record. The remaining 18 _slog.emit(...) sites are machine-only (banner, job_start, timings, per-stage payloads) with no paired human line and correctly stay as bare emits.

Changed: molecular runner energy summaries honour VIBEQC_OUTPUT_UNITS (2026-07-12)

Feature #2 continued. New vibeqc.output.render_energy_labeled(value, *, width, precision, sign)render_energy with the active energy unit appended, so value and label move together (byte-identical to f"{value:{width}.{precision}f} Ha" at the default, "... eV" under VIBEQC_OUTPUT_UNITS=eV). The 75 hardcoded f"{e:16.10f} Ha"-style energy strings in runner.py’s post-SCF summaries (CASSCF, MP2/SCS-MP2, DLPNO-MP2/UMP2, DLPNO-CCSD/UCCSD, OVGF, atomization, dispersion, solvation, binding) now route through it, so eV output units apply across the whole molecular .out, not just the SCF energy-components block. Byte-neutral at the default (goldens green; a live MP2 run confirms -1.1298721951 Ha -> -30.7453887099 eV). Deliberate dual-Ha/eV displays (orbital table, the conducting-state gap warning) are left hardcoded on purpose. New export: render_energy_labeled; 2 tests. The matching 6 single-unit summary strings in periodic_runner.py (SCF/total/dispersion energies, restart + geomopt final energy) are routed through it in the same change; its 8 VBM/CBM/gap lines are dual-Ha/eV displays and stay hardcoded.

Changed: the SCF convergence verdict survives --quiet (logger #5) (2026-07-12)

Feature 5 (tag essentials QUIET). The converged in N iterations; E = ... verdict line – the one essential result of an SCF – is now tagged Level.QUIET, so it survives VIBEQC_OUTPUT_LEVEL=quiet while the per-iteration table above it (still STANDARD) is filtered out. To keep the default .out byte-identical, the SCF-trace segment model grew a per-segment separator: the verdict splits off the iteration table into its own segment but rejoins it with a single \n (not the default \n\n), so format_scf_trace and the STANDARD .out are unchanged (goldens green). Combined with warn() (also QUIET), a quiet run now emits exactly the final energy, the converged/not verdict, and any warnings. 2 tests updated / added.

Added: warn() / note() – a first-class warning surface (2026-07-12)

Feature 4. vibeqc.output.warn(message, **fields) emits a   WARNING: {message} line at Level.QUIET (so a warning survives even VIBEQC_OUTPUT_LEVEL=quiet) and, built on record(), fires a structured warning event carrying message plus any fields. The line format is byte-identical to the write("  WARNING: ...\n") calls the runners already scatter by hand, so migrating those to warn() does not move the .out at the default level while adding the machine-collectable event and the survives-quiet guarantee. note(message, **fields) is the non-problem counterpart: a   Note: {message} line at STANDARD plus a note event, for information a user wants (a fallback taken, a default filled in) that is not a warning. New exports: warn, note; 4 tests. The five 2-space write("  WARNING: ...\n") sites in the runners (DLPNO-MP2/UMP2/CCSD/UCCSD non-convergence, CIF-write failure) are migrated to warn() in the same change – byte-identical .out, now each also fires a structured warning event and survives --quiet. The one 4-space settings-echo WARNING: in periodic_runner is left as-is (different indentation, not byte-neutral).

Added: record() – one call emits the human line and the structured event (2026-07-12)

Feature 3. vibeqc.output.record(event, human=None, *, level=Level.STANDARD, **fields) writes human to the .out channel AND emits a structured event (with fields) to the active StructuredLog, in one call – so the human-readable line and the machine-readable record cannot drift out of sync the way separate write(...) + emit(...) calls can (the .out-says-one-thing / .scf.jsonl-says-another bug this prevents). human=None emits a machine-only record; each surface no-ops independently when inactive. New export: record; 4 tests. The ~28 sites in runner.py that currently write() and emit() separately are a per-site migration follow-up.

Added: configurable output units via a process-wide FormatPolicy (2026-07-12)

Feature 2 of the logger list: route rendered numbers through a policy so units and precision are set in one place, not baked into every f-string. vibeqc.output.active_policy() is the process-wide FormatPolicy; output_units("eV") (or VIBEQC_OUTPUT_UNITS=eV, or energy:eV,length:Angstrom) switches display units, and set_active_policy(...) replaces it wholesale. The default renders byte-identically to the historical format (energy 18.10f Ha), so the .out is unchanged until a knob is moved.

First conversion: the Energy components (...) breakdown (in every SCF .out) now renders through the active policy – header unit and values both follow it, so VIBEQC_OUTPUT_UNITS=eV yields Energy components (eV) with converted values. New exports: active_policy, output_units, set_active_policy.

render_energy(value, *, width, precision, sign) renders an energy at a caller-chosen field shape in the active unit – byte-identical to f"{e:{width}.{precision}f}" at the default (Hartree), converting in eV – so the ~80 scattered f"{e:16.10f} Ha" sites convert byte-neutrally by keeping their own width (no normalisation, no golden regen). The SCF trace’s convergence line is converted; the orbital table (a deliberate dual Ha/eV display) is left as-is. The remaining post-SCF runner strings are a mechanical render_energy application, per-owner follow-up. New export: render_energy.

Added: section() / section_header() – titled output blocks (2026-07-12)

The runners hand-drew section headers as a title write() plus a write("  " + "-" * N) rule, in ~57 places at inconsistent widths. vibeqc.output.section(title, *, level=Level.STANDARD, width=None, ...) is a context manager that emits the header (title + rule) through the channel and yields for the block body; section_header(title, ...) returns the same header string for callers that build strings. width=None sizes the rule to the title; an int fixes it. The header carries a Level, so a whole section can be gated by tagging its body writes to match. Six adjacent title+rule pairs in runner.py / periodic_runner.py migrated byte-identically as a demonstration (golden snapshots green); the rest are a per-owner follow-up. New exports: section, section_header.

Diagnosed: MgO split-gap pins re-derived; SR ket-image truncation root-caused (−515 mHa on the multik split test) (2026-07-12)

The tracked red in tests/test_pbc_bipole_multik_ewald_split.py (test_mgo_shrink2_fixed_density_pure_rks_exact_j_closes_split_gap, stale pin after the 9af6f958 oblique-mesh completion) is resolved and the known_reds_baseline.json entry removed. The triage settled the apparent converged-limit disagreement between the two G=0-dropped Hartree builders: the exact-FT Ewald J is truthful (converged value 81.4173577 Ha on the fixed MgO SHRINK-2 Hcore density, from an independent closed-form Gaussian rho(G) summed to ke = 24000), while the BIPOLE split undercounts by −515.35 mHa because the SR ket-image lattice sum in build_jk_2e_real_space_impl truncates at the radial cutoff_bohr ball — the erfc interaction between smeared diffuse AO-pair charges reaches far beyond erfc between points (erf(√m·r) − erf(√m_ω·r) decay, √m_ω ≈ 0.23 bohr⁻¹ on MgO valence pairs). The erfc ERIs, AO-pair FT kernel, AO scales, J_LR blocks, background term, and Schwarz screening were each validated exact against independent closed forms. The C++ builder reproduces the Poisson-anchored reciprocal values to 7 digits once the traversal ball is widened (16 bohr on this test), pinned by the new test_j_sr_traversal_ball_converges_diffuse_home_element. The proper production fix is the SYM3b M4 internal-domain pad (now a correctness fix, padded by the smeared-kernel range) — spec’d in handovers/HANDOVER_BIPOLE_PRODUCTION.md § 0a. Also documented there: the pure-RKS exact-FT J at the default VIBEQC_J_EWALD3D_KE=200 undercounts density-independent core reciprocal tails (~0.49 Ha absolute on MgO/STO-3G); it may largely cancel between closely related fixed-core densities, but cancellation is not guaranteed. It matters for absolute parity. The test pin is repaired; the production M4 and reciprocal-tail fixes remain open.

Fixed: BCCD(T) retains the semicanonical Fov*T2 triples numerator (2026-07-12)

The closed-shell (T) kernel now includes the disconnected Fov*T2 image in addition to T1*ERI. Canonical RHF routes are unchanged because Fov is zero, while BCCD(T) no longer drops this term when Brueckner T1 is zero but the occupied-virtual Fock block is nonzero. Water/STO-3G changes from -0.000081435 Ha to -0.000086104 Ha, matching out-of-process PySCF to 2.1e-12 Ha and a fixed-Brueckner-orbital ORCA CCD(T) rerun to the program’s printed precision.

The four-row public run_job / vq matrix covers water/STO-3G, water/cc-pVDZ, ammonia/cc-pVDZ, and frozen-core water/cc-pVTZ. The worst triples delta is 7.90e-10 Ha versus PySCF and 6.72e-08 Ha versus ORCA; all QVF files validate. Artifacts: <vibeqc-article-release>/calculations/bccd-t-fov/20260712-51803608/.

Changed: the citation printer emits through the output channel (write_references_block) (2026-07-12)

The ## References block reached the .out by the three runners hand-splicing write("\n" + format_references_block(refs) + "\n"). It is now a first-class logger operation: write_references_block(citations, *, block=None, write=None, level=Level.STANDARD, leading="\n", trailing="\n") in vibeqc.output.citations renders and emits the block through the channel, tagged with a level, the same way write_scf_trace emits the SCF trace. runner.py, periodic_runner.py, and the double-hybrid dispatcher in __init__.py all route through it. Byte-identical (verified on real .out output and by direct comparison); new export write_references_block; two tests added.

Added: fixed energy-weighted overlap derivative component for χ-CCM-B (2026-07-12)

compute_aiccm2026dev_b_fixed_energy_weighted_overlap_lagrangian(...) now evaluates the fixed overlap-constraint scalar -sum_g,mu,nu W_mu,nu(g) S_mu,nu(g), and compute_aiccm2026dev_b_fixed_energy_weighted_overlap_gradient(...) evaluates its analytic AO-centre derivative while holding the caller-supplied real-torus energy-weighted density blocks and lattice fixed. The scalar is a Lagrangian companion, not a separately additive SCF energy. Both helpers require exact cell indices, Cartesian translations, AO dimension, and block shapes. A 3D odd-character conjugate-pair test produces real but individually nonsymmetric residue blocks, pins the Frobenius sign/orientation explicitly, and matches the public scalar’s central finite difference.

There is deliberately no SCF Pulay wrapper: constructing the stationary energy-weighted density still requires B-owned orbital, occupation, spin, backend, gauge, and resolved-support provenance, and the BvK seam or screened exchange derivative remains a separate explicit term. Total χ-CCM-B analytic gradients continue to fail closed. D89 supersedes the former representation-label and approach-equivalence premise without changing this component or its fail-closed boundary.

Added: write_scf_trace – level-aware, block-by-block SCF trace emission (2026-07-12)

Enabling refactor for per-block log levels. format_scf_trace builds one string emitted with a single write(), so its sub-blocks (iteration table, convergence line, energy breakdown, orbital table, properties) could not be tagged at different levels. _scf_trace_segments now returns the trace as (block_text, Level) segments; format_scf_trace joins them (byte-identical) and the new write_scf_trace emits each through the channel at its own level. The runners (runner.py, periodic_runner.py) switched to write_scf_trace.

Byte-identical today: every segment is STANDARD, so the .out is unchanged (golden snapshots + 179 output tests green). The point is the structure – a block can now be moved to VERBOSE / DEBUG in exactly one place (its segments.append(...) entry) and it will vanish on a lower-threshold channel while the rest stay. The inter-block separator is written at the following block’s level, so a suppressed block takes its own leading blank line with it. New export: write_scf_trace.

Fixed: molecular semiempirical preoptimization rejects unknown methods (2026-07-12)

preoptimize_molecule(...) now normalizes dftb0 / scc_dftb spellings and raises on unsupported method names instead of silently treating every non-SCC value as DFTB0. This prevents typoed or not-yet-supported preoptimization requests, such as method="pm6", from walking the wrong surface.

Changed: semiempirical method alias normalization is shared (2026-07-12)

run_job(...), vibeqc.semiempirical.run_semiempirical(...), molecular preoptimization, and periodic semiempirical energy closures now share the same DFTB/GFN2 alias normalization. Direct semiempirical runner calls accept the same hyphenated spellings as run_job, including scc-dftb and gfn2-xtb.

Changed: molecular semiempirical preoptimization is package-exported (2026-07-12)

vibeqc.semiempirical.preoptimize_molecule now exports alongside preoptimize_periodic, matching the public API summary and avoiding the old module-only import path.

Changed: molecular preoptimization uses the unified semiempirical runner (2026-07-12)

The ASE-backed molecular semiempirical preoptimizer now gets its DFTB0/SCC-DFTB energy and gradient through run_semiempirical(...), leaving model selection in the central runner instead of duplicating it inside the optimizer wrapper.

Fixed: semiempirical modules obey the output channel contract (2026-07-12)

The stale direct print() demo in msindo.py is removed, and a focused semiempirical output-contract test now prevents direct print() emitters from creeping back into python/vibeqc/semiempirical.

Fixed: direct semiempirical runner rejects unsupported solvent requests (2026-07-12)

vibeqc.semiempirical.run_semiempirical(...) now raises when solvent= is passed to non-MSINDO methods instead of silently running DFTB, PM6, OMx, GFN2, or CCM in gas phase.

Fixed: direct semiempirical runner rejects method-specific stray options (2026-07-12)

Direct semiempirical dispatch now fails closed when nddo=True is used outside method="msindo" or ccm_options is passed outside method="ccm", avoiding silent gas-phase or non-CCM runs with ignored method-specific knobs.

Fixed: run_job rejects stray semiempirical method options (2026-07-12)

The public runner now raises before calculation setup when nddo=True is used outside method="msindo" or ccm_options= is passed outside method="ccm", matching the direct semiempirical dispatcher.

Changed: periodic PM6/OM2 gradient and stress join the hot-path inventory (2026-07-12)

scripts/bench_semiempirical_hotpaths.py now times the periodic PM6 and OM2 He-dimer finite-difference gradient and stress wrappers. These rows mark the current Python finite-difference orchestration plus native NDDO energy split explicitly, so the remaining C++ batched-kernel work is visible in the same developer inventory as the energy paths.

Fixed: HSE06 is a screened hybrid on the periodic routes, not silent PBE0 (2026-07-12)

No periodic route consumed the functional’s CAM parameters (cam_alpha/cam_beta/rsh_omega): every route keyed its exact exchange off hf_exchange_fraction alone, so HSE06 silently ran as PBE0 (25 % full-range exchange, screening ignored) on every periodic route, 3D included — Inc C of handovers/HANDOVER_SLAB_2D_ROUTING.md. The new shared resolver vibeqc.periodic_screened_exchange.resolve_periodic_exchange maps the CAM convention onto K_HF = (α+β)·K_full β·K_erfc(ω); for HSE06 the full-range coefficient is exactly zero, so the periodic assembly is the pure short-range 0.25·K_erfc(ω = 0.11) direct lattice sum with no Madelung/exxdiv seam and no bipolar far-field (the CRYSTAL treatment). Wired through the Ewald family (Γ + multi-k, RKS + UKS, EWALD_3D and SLAB_EWALD_2D — so run_periodic_job(functional="hse06") on a dim=2 slab via AUTO is now correct) and through BIPOLE RKS/UKS (build_bipole_{restricted,unrestricted}_fock take an exchange_assembly). Gates: HSE06 ≠ PBE0 on every wired route; Γ == multi-k(1,1,1) == UKS closed-shell; BIPOLE == multi-k-Ewald to ~2e-7 Ha on the shared fixture; slab total a₃-invariant and equal to the vacuum-converged 3D reference; erfc-kernel ω→0 / ω→∞ limits; HSE06 citations (Heyd–Scuseria–Ernzerhof 2003 + Krukau 2006) fire in .out/.bibtex (tests/test_periodic_screened_exchange.py, 32 tests).

Fixed: routes without a screened-exchange kernel fail closed on range-separated functionals (2026-07-12)

Complementing the above, the periodic routes whose exact exchange is full-range only now raise NotImplementedError on any range-separated functional instead of silently substituting the full-range twin: multi-k GDF (run_krhf_periodic_gdf / run_kuhf_periodic_gdf, which also carry COSX-K), Γ GDF UKS (run_pbc_gdf_uks; the RHF/RKS twin was already guarded), the legacy Γ GDF fallback (run_rhf_periodic_gamma_gdf), the Γ GPW/GAPW/OT drivers, and the periodic analytic-gradient entry points (compute_gradient_periodic_rks_{gamma,multi_k}, whose exchange Pulay terms are full-range only — a screened-K SCF would otherwise get silently inconsistent forces). Long-range-corrected RSH (ωB97X, CAM-B3LYP, LC-ωPBE) fail closed on all periodic routes: their K_full arm needs an exxdiv treatment that is not validated (maintainer policy 2026-07-09). The experimental RSGAPW dedicated entry points are unchanged. Docs: user-guide functionals/slabs/periodic-methods pages updated in the same merge.

[v0.15.36] - 2026-07-12 - Neese’s Cheetah

Added: opt-in BvK-ewald reference for one-call CCM correlation (2026-07-12)

run_ccm_ri_mp2 and run_ccm_ri_ccsd gain reference="neutral"|"direct" (default "neutral", unchanged). reference="direct" builds the RI correlation on the BvK-ewald direct-torus SCF (run_ccm_rhf_direct, same shared neutral cderi L, exchange-q=0 seam included) instead of the strict-zero-mode lean neutral SCF.

Why (measured, the 2026-07-10/11 D2 KMP2 anchor — handovers/HANDOVER_D2_EXXDIV.md § Results + Part 2): the strict-zero reference deletes free-space exchange whose weight grows with the cluster (~linearly in N_c per atom), and the correlation inherits a growing distortion (c-diamond (2,2,2): −59.0 mHa/atom vs external PySCF KMP2 −29.9 (ewald) / −41.1 (None)). On the direct reference the CCM correlation matches out-of-process PySCF KMP2 at exxdiv='ewald' to 3e-6 Ha/cell on Γ-diamond and 0.24 mHa/atom on LiH (2,2,2) — residuals at the per-fixture rsgdf fitting floor. The default stays "neutral" pending the maintainer decision on flipping it (it moves every absolute correlation number); open-shell reference="direct" fails closed (the direct route has no UHF sibling). Mirrors the wire route’s exxdiv="free" opt-in precedent.

Pins: tests/test_ccm_mp2.py::test_ri_wrappers_reference_direct_opt_in (one-call == manual recipe on one shared L, BvK-ewald recorded on the result, seam-widened denominators shrink |E_corr|, invalid/open-shell fail loudly, default composition unchanged).

Changed: periodic semiempirical preoptimization uses shared dispatch (2026-07-12)

preoptimize_periodic(...), optimize_pm6_cell(...), and optimize_omx_cell(...) now build their periodic semiempirical energy closure through one vibeqc.semiempirical.periodic helper. This keeps DFTB, GFN2-xTB, PM6, and OMx preoptimization on the same route-selection surface while preserving the existing method defaults.

Changed: periodic PM6/OMx finite-difference helpers are shared (2026-07-12)

Periodic PM6 and OMx gradient/stress wrappers now use the shared vibeqc.semiempirical.periodic finite-difference helpers instead of carrying duplicate Python displacement and strain loops. The methods remain mixed-native and experimental, but the remaining C++ batched-kernel work now has one shared helper to replace.

Changed: periodic PM6/OMx route-status labels are explicit (2026-07-12)

semiempirical_route_status("periodic-pm6") and semiempirical_route_status("periodic-omx") now report mixed-native, non-production, performance-critical status. The labels make the current native Gamma energy plus Python finite-difference gradient/stress helper split queryable from code and mirror the user-guide caveat.

Changed: periodic semiempirical helper memory estimates (2026-07-12)

estimate_semiempirical_memory(...) now recognizes PeriodicSystem inputs and counts periodic image/neighbour buffers plus stress/strain scratch for semiempirical helper optimizations. This extends the basis-free memory preflight beyond molecular run_job routes without constructing a Gaussian basis.

Fixed: semiempirical parameter export uses the output channel (2026-07-12)

vibeqc.semiempirical.io.save_parameters no longer prints directly to stdout from library code. Its completion note now goes through vibeqc.output.write at Level.VERBOSE, so it is silent outside an installed output channel and opt-in inside .out logging.

Changed: three silent BIPOLE hazards now warn; EXT EL-POLE defect now gates (2026-07-12)

Production-readiness batch from the BIPOLE route audit (handovers/HANDOVER_BIPOLE_PRODUCTION.md):

  • Legacy-gauge multi-k warns. The 3-D legacy (non-exchange-split) gauge adds the spheropole to the reported energy but never to the Fock operator, so its converged multi-k total is not a stationary value (documented Ha-scale wrong-sign k-mesh dependence on MgO). The configuration stays reachable on purpose — ad-hoc k-lists (band paths) auto-fall back to it — but every 3-D legacy multi-k run now emits a RuntimeWarning and an .out notice naming the non-stationarity. Open maintainer question: whether an explicit use_exchange_ewald_split=False at a Monkhorst-Pack multi-k mesh should hard-fail instead (parity/diagnostic scripts still use it).

  • Odd-electron multiplicity=1 promotion warns. The deliberate 7991ae58 promotion to the minimum open shell (Al/Cu primitive cells) is unchanged, but a user who meant a singlet is now told.

  • Net-charged cells warn that the implicit jellium background makes the absolute energy convention- and volume-dependent (Makov-Payne / Freysoldt territory) — previously silent.

  • EXT EL-POLE parity test: skipxfail(strict=True). The ρ̂(K=0) ≈ 12.4-vs-N_e=20 AO-pair-FT convention defect now gates: a fix (or a wrong “fix”) flips the test instead of passing silently.

All warnings are shared helpers in pbc_bipole_common used by all four drivers; pins in tests/test_bipole_production_guards.py (positive and negative: the corrected gauge, explicit doublets, and neutral cells stay warning-free).

[v0.15.35] - 2026-07-11 - Neese’s Cheetah

Added: fixed-density kinetic derivative component for χ-CCM-B (2026-07-11)

compute_aiccm2026dev_b_fixed_density_kinetic_energy(...) now contracts a caller-supplied real-torus density with the matching lattice kinetic operator, and compute_aiccm2026dev_b_fixed_density_kinetic_gradient(...) evaluates its analytic AO-centre derivative while holding those density blocks fixed. The two paths share exact lattice-cell, Cartesian-translation, and AO-shape guards, and their isolated scalar/derivative pair is pinned by central finite differences without invoking an SCF response or another CCM implementation. There is deliberately no SCF convenience wrapper yet: current results do not attest the resolved one-electron lattice cutoff needed to reconstruct the same operator support. The total χ-CCM-B gradient remains fail-closed pending the energy-weighted Pulay/adjoint, BvK exchange-q=0 seam, RI/RIJCOSX response, and post-HF response terms. D89 supersedes the former representation-label and approach-equivalence premise without changing this component or its fail-closed boundary.

Changed: MSINDO CIS joins the semiempirical hot-path inventory (2026-07-11)

The semiempirical hot-path benchmark inventory now includes a public msindo_cis(..., spin="singlet") H2O case. It records excitation energies, amplitude dimensions, and dominant-transition metadata for the current Python-reference CIS/TDA route without promoting Thiel excited-state benchmark coverage.

Changed: public DFTB0 preoptimization joins the semiempirical hot-path inventory (2026-07-11)

scripts/bench_semiempirical_hotpaths.py now includes a public preoptimize_molecule(method="dftb0") H2O case. The inventory records initial/final energy and residual gradient RMS for the first optimization entry in the S1 runtime and memory baseline.

Changed: gradient and stress routes join the semiempirical hot-path inventory (2026-07-11)

The semiempirical hot-path benchmark inventory now includes DFTB0, GFN2-xTB, and PM6 molecular H2O gradient cases plus periodic DFTB0 He-dimer gradient and stress cases. The new entries record measured shape/norm metadata, runtime, and peak RSS for the S1 performance baseline while leaving physics parity to the existing focused gradient and stress regression tests.

Changed: CCM smoke systems join the semiempirical hot-path inventory (2026-07-11)

The semiempirical hot-path benchmark inventory now includes direct public MSINDO CCM energy cases for 1D no-Ewald He7, 1D Madelung HF6, 2D no-Ewald MgO, and 3D no-Ewald MgO. These reuse existing C++/Python parity fixtures and expose CCM dimensional coverage in the S1 performance baseline without changing CCM physics.

Changed: periodic silica joins the semiempirical hot-path inventory (2026-07-11)

The semiempirical hot-path benchmark inventory now includes a simplified alpha-quartz SiO2 periodic DFTB0 single point. This gives the S1 performance baseline a small silica/zeolite-like periodic cell while keeping the unstable default SCC-DFTB quartz route out of the runnable inventory.

Added: article-suite metadata for semiempirical hot-path cases (2026-07-11)

scripts/bench_semiempirical_hotpaths.py --list-suites now mirrors the article-side external-suite map for GMTKN55, Korth-Thiel GMTKN24, Dral-Thiel ground-state OMx/ODMx, Delta-factor / SSSP solids, Matbench Discovery, Thiel excited states, and MACE literature validation. Runnable cases are explicitly marked as compact representatives, while Thiel excited states, Matbench, and MACE remain future or adjacent scope with no computed-case claim.

Added: bond-analysis method families – Wiberg/DI, NBO/NPA, EDA, orbital entanglement (2026-07-11)

Four new analysis modules land the long-parked bond-analysis work (originally staged on the dev branch 2026-06-24, rebased onto current main):

  • vibeqc.bond_analysis – Wiberg bond indices (Löwdin-orthogonalised density; Wiberg 1968) and an AO-approximated delocalization index (Matito 2007 / Outeiral 2018), plus bond_order_summary combining Mayer + Wiberg + DI, with periodic Gamma-point wrappers.

  • vibeqc.nbo – Natural Population Analysis charges and a Weinhold-style NBO search (BD/LP/CR/BD*/RY* classification) with second-order donor-acceptor (E2) analysis (Reed-Weinstock-Weinhold 1985, Foster-Weinhold 1980, Reed-Curtiss-Weinhold 1988).

  • vibeqc.eda – LMO-EDA (Su-Li 2009) and Morokuma-style (1971) two-fragment energy decomposition with fragment-density utilities.

  • vibeqc.entanglement – single-orbital entropy, mutual information, entanglement bond orders, and correlation clusters (Legeza-Sólyom 2003; Szalay et al. 2017; Ding-Matito-Schilling 2025).

Integration: the .population.{txt,json} sidecars gain Wiberg bond-index and NPA charge sections on every population dump (molecular + periodic Gamma proxy), and the corresponding citation routes fire in .out / .bibtex / .references / .system. All eleven new citation-database entries were verified against Crossref / arXiv (four hand-entered metadata errors from the original branch were caught and fixed; tests/test_citations.py::test_bond_analysis_routes_pin_dois pins the verified DOIs).

The dev branch’s duplicate Gamma/multi-k COHP module was not landed: the canonical periodic COHP/COOP implementation is vibeqc.coop_cohp (shipped v0.15.x), and single-canonical-impl rules apply. User guide: docs/user_guide/bond_analysis.md.

Added: log levels on the output channel (Level, VIBEQC_OUTPUT_LEVEL) (2026-07-11)

vibeqc.output.write(text, level) now tags a message’s verbosity band and the channel filters by a threshold: a message emits when level <= channel.level. Bands: Level.QUIET (always shown – final energy, convergence verdict, fatal errors), Level.STANDARD (the default and the historical .out content), Level.VERBOSE (opt-in detail), Level.DEBUG (diagnostics). An untagged write(text) is STANDARD and the channel default is STANDARD, so the .out is byte-identical until a write is tagged or the threshold moves (golden snapshots confirm).

Users set the level with VIBEQC_OUTPUT_LEVEL=quiet|standard|verbose|debug (or 0-3); code uses output_level(...) for the default new channels adopt, or channel.set_level(...) for one already open. Factories take an optional level=. New exports from vibeqc.output: Level, output_level. Documented in docs/user_guide/logging.md; this is the .out’s own verbosity axis, separate from ProgressLogger’s live-stdout verbose= gate.

Fixed: double-hybrid .out printed every energy twice; now one unified block (2026-07-11)

_write_double_hybrid_outputs rendered each energy twice – once as SCF energy: {:16.10f} (no unit) and again as E(RKS reference) = {: .12f} Ha – in two precisions and two label styles, under a header that spelled “Hartree”. It now emits a single Energy components (Ha) block in the same {label:<32s} {value:18.10f} style as the molecular and periodic SCF paths (MS2). Labels: RKS reference, MP2 correlation, Dispersion (when present), Total energy. The double_hybrid golden snapshot was regenerated; one test assertion updated (MP2 correction -> MP2 correlation).

Changed: periodic .out SCF trace + energy breakdown now match the molecular format (2026-07-11)

Output-format change (MS2). The periodic .out SCF section was hand-assembled by periodic_runner’s own _scf_trace_text / _energy_summary, which disagreed with the molecular path on every axis: dE at +.4e vs +.3e, ||[F,DS]|| at 14.4e vs .3e, DIIS width 5d vs 3d, a different table header (SCF iteration trace), a repr-style converged = True instead of converged in N iterations, and E_total (Ha) / E_electronic (Ha) labels instead of the molecular Total energy / Electronic. Periodic and molecular now render through the same vibeqc.output.formats.scf_log.format_scf_trace: identical iteration table, convergence line, and Energy components (Ha) breakdown.

Enabled by two prerequisites: _format_iter_line reads dict trace steps (GAPW multi-k) as well as SCFIteration structs, and _format_energy_components gained a DFT+U (Dudarev) row (shown only when non-zero, so molecular output is unchanged). Periodic-only smearing quantities (kBT, T_elec, entropy, free energy, Fermi level) moved to a Finite-temperature (smearing) block that follows the shared breakdown.

The periodic_rhf golden snapshot in tests/test_out_format_snapshot.py locks the new format; molecular goldens are byte-identical (verified). The five periodic assertions that pinned old strings were updated. The periodic optimiser summary lines (bipole_optimize) are a separate issue — they run outside run_periodic_job’s channel scope and are left for the periodic-scf chat (documented in the handover).

Fixed: Bloch RSGDF uses the physical shifted reciprocal sphere (2026-07-11)

build_lpq_bloch_native_fft now enumerates the physical shifted support 0 < |G+q| <= sqrt(2 E_cut) instead of adding q to an already truncated |G| ball. Previously, reciprocal-equivalent q and q+G0 labels sampled different cutoff-boundary crescents. A half-open representative repairs that odd-mesh mismatch but breaks time reversal at an even-mesh Nyquist channel; the shifted physical sphere has both reciprocal-label invariance and inversion covariance by construction. The compact fitted Gram and canonical factor now pass reciprocal-relabel tests on a non-symmetric lattice; the canonical factor passes an explicit Nyquist time-reversal gate, and the canonical χ-CCM post-HF inverse transform matches the Γ-CCM folded fitted Gram operator at the 1e-14 floor on skew (2,1,1) and (3,1,1) controls. D89 classifies this as a neutral-torus fitted-operator/representation control, not a Γ-CCM versus χ-CCM approach-equivalence result. The q=0 support and accumulation order are byte-preserved. Pre-fix records that built nonzero-q RSGDF factors are revision-bound and must be rerun before quotation: exact-GDF-K RHF/UHF and hybrid RI, Γ-CCM folds, and RI post-HF/full-pair caches. Semilocal RI and RIJCOSX SCF use only q=0 RSGDF factors for J and are numerically unaffected by this support change.

Added: k-pair star reduction of the per-q fold cderi build (2026-07-11)

ccm_neutral_cderi_fold(..., symmetry=True | CCMSymmetry) reduces the fold’s N_c × n_q per-(k_bra, k_ket) build_lpq_bloch_native_fft calls to the space-group + time-reversal star representatives and reconstructs the remaining fits by the k-pair covariance of the canonical-frame GDF cderi — per-atom lattice-shift phases on the bra/ket/aux indices, no non-symmorphic τ phase (it cancels between the aux conjugate and the pair factor; identity + derivation inlined at neutral.py::_fold_star_reconstruct, probe-pinned at 8.8e-13–3.8e-10 including diamond glides). The current conservative planner admits common reciprocal shifts, time reversal (both ~1e-14), and exactly rotated momenta. The shared builder now also makes a ket-side mod-G relabelling exact through its physical shifted support, but expanding the optional star inventory to exploit that relation is deferred. Consequently small (2,1,1)-class meshes still reduce nothing in this planner (logged, bit-identical output through the symmetry path); measured wins start at (2,2,1) — diamond/MgO 10/16 builder calls (1.60x), star-reduced == un-reduced fold to < 1e-9 at a cell-list-converged cutoff and < 1e-10 Ha in the direct-torus SCF energy at production defaults — and grow with mesh richness (cubic (2,2,2) ≈ 3.2x by the same counting, the LiH-fold production bottleneck case). Unlike the Γ-supercell (M3) route, the wall-clock win is immediate — the fold is builder-call-dominated: diamond (2,2,1) fold 59.3 → 36.0 s at production defaults, matching the 10/16 call fraction with no C++ change. Enumeration lives in ccm_symmetry_fold_kpair_plan (reusable by the multi-k GDF driver’s k-pair cache). The direct-torus drivers reach it via run_ccm_rhf_direct / run_ccm_rks_direct(..., cderi_symmetry=True) (forwarded to whichever cderi_build runs). Gates: tests/test_ccm_symmetry.py (10 new, 32 total green).

Added: periodic jobs cite Pisani; bundled-basis jobs cite the BSE (2026-07-11)

Two citation entries existed but were reachable from nothing:

  • pisani_crystal_1988, the foundational monograph for the LCAO crystalline-orbital periodic HF treatment vibe-qc implements, now fires for every periodic job via routes.methods._periodic_lcao (the periodic analogue of an always-on method reference – the crystalline-orbital reference underlies the periodic KS-DFT and post-HF routes too).

  • pritchard_bse_2019, the Basis Set Exchange, now fires alongside any basis that resolves to a bundled (routed) entry, via routes.basis_sets._bse_bundled_provenance – the offline BSE catalog is where the bundled .g94 library comes from, so a job that draws a bundled basis cites it. A custom / external basis is a route miss and does not fire it. Deduplicated by entry key, so it appears once regardless of how many basis routes match.

Added: the Makov-Payne finite-box correction cites its source (2026-07-11)

makov_payne_1995 was in the database but reachable from nothing. It is now surfaced from vibeqc.madelung via MADELUNG_CITATION_KEYS / method_citations, the same module-constant pattern as the BDIIS optimiser and the periodic stress tensor.

Not a [routes.*] row, because the correction never fires through run_periodic_job: every reachable Coulomb method zeroes it (EWALD_3D and SLAB return 0.0; the Γ-only DIRECT_TRUNCATED user route FFT_POISSON was retired in v0.13.0 and raises), and the default GDF path applies the exxdiv=’ewald’ exchange-divergence Madelung constant instead, cited separately. The monopole correction (madelung_energy_correction, makov_payne_coefficient_cubic, apply_madelung_correction) is an offline library capability a caller applies to a periodic total energy, and the module now attributes it for direct callers rather than claiming a job cites it.

Changed: periodic stress cites Nielsen-Martin, not Doll; phonon papers removed (2026-07-11)

Three citation entries described methods vibe-qc does not implement:

  • doll_stress_2004 is the analytic Hartree-Fock cell gradient. The GPW/GAPW stress that ships (vibeqc.periodic_gapw_stress) is a finite-difference Hellmann-Feynman + Pulay decomposition following Nielsen-Martin 1985. Removed and replaced by a new nielsen_martin_1985 entry (DOI 10.1103/PhysRevB.32.3780), surfaced from the module via STRESS_CITATION_KEYS / method_citations the same way the BDIIS optimiser surfaces its provenance – the stress returns a bare (3, 3) tensor, so it has no [routes.*] row.

  • parlinski_phonon_1997 (supercell direct method) and togo_phonopy_2015 (phonopy) were removed. The shipped phonons (vibeqc.periodic_gapw_phonon) are a Γ-point mass-weighted Hessian by central differences on analytic forces – no supercell, no Fourier interpolation, phonopy unused – so neither paper describes what runs. Citing them would misattribute rather than credit.

Changed: resolve_progress warns instead of silently nulling a bad progress= (2026-07-11)

resolve_progress accepts bool / None / ProgressLogger / any object with a callable iteration. Anything else fell through to a silent no-op logger – so passing an unexpected object (an OutputChannel, which has .write but no .iteration, or a typo) dropped all progress with no signal. An unrecognized truthy value now emits a RuntimeWarning naming what it got. Falsy values (0, "", []) still read as “off” silently. Byte-neutral; no valid caller changes behaviour.

Added: golden-file .out format snapshots – the freeze mechanism (2026-07-11)

tests/test_out_format_snapshot.py locks the .out format against committed golden files (tests/golden/*.out) for molecular RHF, RKS/PBE, and a double hybrid. This is the “then freeze” half of the output-logger decision: after the one clean break, the format cannot drift again silently.

It freezes the scaffold, not the physics. Before comparison the .out is canonicalized – volatile fields (timings, memory, the banner git field, paths) scrubbed, and every number’s digits zeroed while its shape is kept (-76.0261400000 Ha -> -00.0000000000 Ha). So a label / width / precision / alignment / unit / separator change is caught, a legitimate energy change is not, and the golden files are independent of the compiled core. Regenerate deliberately with VIBEQC_REGEN_GOLDEN=1 pytest tests/test_out_format_snapshot.py and review the diff before committing.

Added: Axilrod-Teller-Muto three-body dispersion (s9)

D3BJParams gains an s9 field scaling the ATM three-body (dipole-dipole-dipole) term – a3 in Eq. (3) of Grimme, Brandenburg, Bannwarth & Hansen, J. Chem. Phys. 143, 054107 (2015). The C++ D3 kernel implements the term and its analytic gradient; both the builtin and dftd3 backends honour s9.

  • s9 defaults to 0.0. Published per-functional (s8, a1, a2) damping sets were fit two-body-only, so a plain dispersion="d3bj" remains the two-body D3(BJ) energy.

  • D3’s ATM damping is the zero-damping function on the tabulated r0ab cut-off radii, not the Becke-Johnson radius used by the two-body terms (and not D4’s ATM damping, which does use the BJ radii). The r0ab table is now available to the C++ core; tests/test_dispersion_atm.py pins it against vibeqc._d3_r0ab so the two copies cannot drift.

  • Validated against Grimme’s reference dftd3 library to 1e-12 Ha on homonuclear rare-gas clusters, the systems for which vibe-qc’s builtin C6 table is exact. The analytic ATM gradient matches finite differences to ~1e-14.

Fixed: dftd3 backend silently applied the three-body term

dftd3’s RationalDampingParam defaults s9 = 1.0. vibe-qc omitted s9 when constructing it, so the dftd3 backend added an ATM contribution to every D3(BJ) energy while the builtin C++ backend added none. Because backend="auto" prefers dftd3 when it is installed, the same compute_d3bj(mol, "pbe") – and the same run_job(...) – returned different physics depending on whether an optional pip package was present. The same defect was present in compute_d3bj_periodic.

s9 is now always passed explicitly. This changes numbers: D3(BJ) energies from the dftd3 backend lose the spurious three-body term (on H2O/PBE, +3.2e-10 Ha; larger for denser systems). The two backends now agree wherever their C6 tables do.

Changed: PBEh-3c and HSE-3c enable the three-body dispersion term

pbeh-3c and hse-3c now carry s9 = 1.0, so their D3 energy includes the ATM term their defining papers require (“the three-body dispersion is always included in the PBEh-3c method”, Grimme et al. 2015; HSE-3c ESI Sec. A). This is a small, repulsive shift — on gas-phase benzene about +0.0094 mHa — that grows with density and matters for the lattice-energy use cases these composites target. run_job(method="pbeh-3c" / "hse-3c") totals move by that amount. hf-3c, b97-3c and b3lyp-3c stay two-body: HF-3c’s paper never mentions ATM, and the others publish no three-body scale of their own.

(The HSE-3c (a1, a2) and gCP constants were corrected separately in v0.15.34; this entry only turns the three-body term on.)

  • pbeh-3c and hse-3c now route axilrod_teller_1943, which was in the citation database but wired to nothing. The SCF log labels the correction D3-BJ(ATM) and prints s9.

Added: VIBEQC_STRICT_OUTPUT – channel-less write() fails loudly (2026-07-11)

vibeqc.output.write() / flush() are a deliberate no-op when no OutputChannel is active, so a writer module stays callable from a test or a notebook. The cost is that forgetting to install a channel loses output silently. VIBEQC_STRICT_OUTPUT=1 (or strict_output(True) at runtime) turns that no-op into a RuntimeError, so a “forgot the channel” bug fails where it is cheap to catch. Off by default; production stays lenient. The gate lives in the module-level write/flush, read once at import into a constant so the hot path stays a bare attribute read. Writes inside a channel are unaffected.

Fixed: GFN2 acceptance matrix D4 status is current (2026-07-11)

The semiempirical acceptance matrix no longer says GFN2-D4 is unimplemented. It now records the current native post-SCF D4 scope (H, He, B, C, N, O, F, Ne), the GFN2D4UnsupportedWarning behavior outside that reference-data set, and the genuinely remaining GFN2 production gates: periodic AES image-cell terms, broader periodic molecular-limit parity, mixer hardening, and external xtb parity.

Fixed: features table semiempirical scope is current (2026-07-11)

docs/features.md now reports current semiempirical scope: MSINDO INDO H-Xe, closed-shell NDDO H/Li-F/Na-Cl, native C++ production routes for supported MSINDO energy/gradient/CCM/COSMO helpers, and GFN2’s gated experimental status with scoped post-SCF native D4.

Fixed: MSINDO gradient status in semiempirical guide is current (2026-07-11)

The semiempirical user guide no longer says MSINDO analytic gradients are only partially assembled. It now distinguishes native molecular closed-shell analytic gradients and closed-shell CCM gradients from the NDDO, odd-electron, and excited-state/root-tracking fallback/reference paths.

Fixed: MSINDO user-guide feature matrix is current (2026-07-11)

docs/user_guide/msindo.md now mirrors the implementation scope in the feature matrix: INDO H-Xe, closed-shell NDDO H/Li-F/Na-Cl, C++-backed closed-shell INDO analytic gradients, Cs-and-heavier unsupported, and heavy open-shell d/p-block UHF limited to named validated fixtures rather than advertised as a blanket route.

Changed: native MSINDO SCF reserves fixed work buffers (2026-07-11)

The native INDO/MSINDO RHF and UHF SCF drivers now reserve their fixed-size DIIS history vectors and per-atom block lists up front. This trims avoidable vector growth in production C++ energy calls without changing the SCF equations or convergence policy.

Fixed: PM6 acceptance status matches the production caveat wording (2026-07-11)

The semiempirical acceptance matrix now labels PM6/UPM6 as a production-oriented molecular PM6-like-total route with explicit caveats: exact MOPAC heat-of-formation convention parity is still open, fallback gamma/core-core terms remain documented where MOPAC diatomic data is incomplete, and periodic PM6 stays experimental.

Changed: PM6 optimization regression now asserts energy descent (2026-07-11)

The PM6 FD-optimization benchmark for H2O and CH4 now requires optimizer convergence and a real energy decrease instead of only checking that the final energy stayed finite. This tightens the molecular PM6 production claim while keeping the remaining MOPAC-convention caveats explicit.

Changed: GFN2 ionic diatomics join the hot-path inventory (2026-07-11)

The semiempirical hot-path benchmark inventory now mirrors the GFN2 SCC stabilization regression set for long-range LiH, LiF, and NaCl. These cases record native SCC convergence, iteration count, energy, and charges so the 3rd-order bistability fix stays visible in performance sweeps.

Changed: PM6 and OM2 methane join the hot-path inventory (2026-07-11)

The semiempirical hot-path benchmark inventory now includes PM6 and OM2 methane single points alongside the existing water smoke cases. This completes the runnable water-plus-methane molecular NDDO slice requested by the semiempirical performance roadmap.

Changed: MSINDO heavy fixtures join the hot-path inventory (2026-07-11)

The semiempirical hot-path benchmark inventory now includes validated SbF3 and XeF2 closed-shell MSINDO direct-energy cases. These keep the heavy 5p trajectory-SCF path visible in performance sweeps while avoiding the separately documented brittle heavy-element fixtures.

Added: basis-free semiempirical memory preflight (2026-07-11)

Semiempirical run_job routes now emit the standard memory estimate block without constructing a Gaussian basis. The new estimate counts valence dense matrices, SCC/DIIS scratch, finite-difference gradient scratch, MSINDO COSMO cavity matrices, and CCM image-cell buffers, and the same helper feeds VIBEQC_DRY_RUN_ESTIMATE for semiempirical jobs.

Changed: NH3 and benzene join the semiempirical hot-path inventory (2026-07-11)

The semiempirical hot-path benchmark inventory now includes NH3 single points for GFN2-xTB, PM6, and OM2, plus benzene single points for DFTB0 and GFN2-xTB. These close the first small-molecule coverage gap in the S1 performance baseline without expanding the default smoke suite.

Fixed: periodic COOP/COHP jobs now cite the defining papers (2026-07-11)

run_periodic_job(..., coop_cohp=True) computed the COOP/COHP bonding analysis for the QVF dos.coop / dos.cohp sections but never passed properties=["coop", "cohp"] to the citation assembly, so the Hughbanks-Hoffmann 1983 (COOP), Dronskowski-Bloechl 1993 (COHP), Deringer 2011, and LOBSTER 3.0 papers were unreachable from any real periodic job. The citation now fires exactly when the analysis runs (coop_cohp + output_qvf + converged SCF), reaching the .out references block, the .bibtex / .references siblings, and the .system manifest. End-to-end pins (positive + no-QVF negative control) live in tests/test_feature_citation_end_to_end.py.

[v0.15.34] - 2026-07-11 - Neese’s Cheetah

Added: semiempirical route status registry (2026-07-10)

The semiempirical package now exposes a small implementation-status registry via semiempirical_route_status(route). It records native, mixed-native, gated-experimental, and python-reference routes so the remaining MSINDO CIS/TDA, CIS-gradient, OVGF/GF2, MD, metadynamics, and root-tracking hot loops are labelled explicitly until their C++ kernels land or they are declared intentional Python orchestration.

Changed: CCM FD fixed-WS neighbor buffers reserve capacity (2026-07-10)

The native MSINDO CCM finite-difference loop now reserves the fixed Wigner-Seitz neighbor buffers before each displaced energy build, avoiding avoidable vector growth in the image-cell reconstruction path without changing the fixed-topology gradient numerics.

Fixed: CCM FD native MgO 3D non-Madelung parity (2026-07-10)

The native INDO/MSINDO DIIS solve now uses the same partial-pivot linear-system semantics as the Python reference, with a residual guard for invalid Pulay coefficients. This fixes the previously suspect native MSINDO CCM finite-difference gradient on the 3-D MgO non-Madelung fixed-Wigner-Seitz path: the direct C++ result is finite, matches Python below 1e-8 Ha/bohr, and the public wrapper stays on the native route instead of falling back to Python.

Fixed: periodic GFN2 H2O molecular-limit parity is strict again (2026-07-10)

The periodic Gamma GFN2 molecular-limit H2O benchmark no longer carries the stale 3.5 Ha tolerance from the old atom-vs-shell SCC gap. In a 20 bohr box with a 15 bohr cutoff, run_gfn2_xtb_gamma now matches molecular run_gfn2_xtb to better than 1e-10 Ha, and the benchmark asserts that strict parity directly.

Fixed: GFN2 D4 unsupported elements no longer vanish silently (2026-07-10)

GFN2-xTB’s post-SCF native D4 helper is validated only for the native D4 reference-data set (H, He, B, C, N, O, F, Ne). Molecules containing other elements still receive a zero D4 contribution, but now emit GFN2D4UnsupportedWarning naming the unsupported atomic numbers instead of silently dropping dispersion. The experimental catalog and semiempirical user guide now describe the corrected scope.

Added: guard against citation entries that no route reaches (2026-07-10)

tests/test_citation_no_dead_entries.py fails when an [entries.*] block is reachable from neither a [routes.*] row nor a string literal in python/vibeqc source, per CLAUDE.md § 8 step 2.

Reachability has two channels, and checking only the routes table is what let this rot accumulate. Entries passed to assemble(extra_entries=[...]) or exported as module citation constants (BDIIS_CITATION_KEYS) are real citation surfaces that never appear in the routes table. The 2026-07-10 audit’s list of 26 “unrouted” entries contained 10 such: the five MS/XMS-CASPT2 papers and the UNO-CAS reference fired from runner.py, the two MACE foundation-model papers from mlip/_mace_models.py, and two basis-optimisation papers from BDIIS_CITATION_KEYS. The guard detects that channel by parsing each module and matching string literals exactly against entry keys, so a key named only in a comment does not count.

Seven genuinely dead entries remain, each allow-listed with its reason: andrae_ecp_1990, makov_payne_1995, doll_stress_2004, parlinski_phonon_1997, togo_phonopy_2015, pisani_crystal_1988, pritchard_bse_2019.

Added: Eigen recorded as internal provenance in the .system manifest (2026-07-10)

The eigen citation entry existed but no route reached it, so the one library linked into every single job was recorded nowhere. It now fires unconditionally from routes.libraries.eigen, with print = false.

Eigen is link-time infrastructure, not a scientific dependency: no method vibe-qc claims to implement traces to it. print = false puts it in the .system manifest as provenance and keeps it out of the .out / .bibtex / .references block a paper’s Methods section copies from. This is the first database entry to use the flag, which the registry has supported (and documented, naming pybind11 as the model case) since the flag was introduced.

Fixes test_write_bibtex_emits_one_entry_per_citation, which asserted one BibTeX block per assembled citation. That invariant only ever held because no entry set print = false; the writer emits printable.

Fixed: k-point construction conventions are cited again (2026-07-10)

No production code ever passed numerics= to assemble(), so every row in [routes.numerics] was unreachable from a real job. Only the unit tests, which call assemble(numerics=[...]) directly, kept them looking alive. A run that chose its mesh by the AFLOW KPPRA density convention, or plotted bands along the HPKOT path, cited neither.

KPoints now carries a citation_numerics field naming the published construction it came from, set by from_kppra (AFLOW, Curtarolo 2012), generalized_regular, from_database, and band_path (HPKOT via seekpath, Hinuma 2017). KPoints.to_kpath propagates it to KPath, so a job that attaches a band structure carries its path convention. run_periodic_job forwards both to assemble().

A hand-specified Monkhorst-Pack mesh or an explicit k-list follows no published density convention and now claims none; over-citing is as wrong as under-citing, and a negative-control test pins that.

Fixed: CCM adsorbate optimizer recovers from WS-basin trial failures (2026-07-10)

ccm_optimize now keeps relaxed atoms in their starting periodic image for axis-aligned CCM cells, recovers from invalid Wigner-Seitz trial geometries with a finite optimizer penalty, and returns a force-converged valid point when a line search samples a lower-energy WS kink with large residual forces. The MgO(100)+H2O slow relaxation regression now passes again with the adsorbate force below the requested target.

Added: vibeqc.output.channel, the ambient output channel (2026-07-10)

Plumbing only. The .out file format does not change, verified by diffing normalized .out captures from run_job (RHF and RKS), run_periodic_job, and run_double_hybrid before and after the migration: byte-identical.

Any module can now contribute to the .out file by importing write and flush from vibeqc.output, instead of being handed the open file object. Outside an active channel write() is a no-op, so importing the module opens nothing and a writer stays callable from a test, a notebook, or a library context.

from vibeqc.output import write, flush        # any module, any depth
write(f"  IUPAC name: {iupac}\n")

with OutputChannel.to_file(out_path):         # the runner, once
    ...

OutputChannel offers file, stdout, tee, and null backends; files are opened line-buffered so tail -f job.out shows lines as the SCF emits them. Channels nest and restore the previous channel on exit, and the channel duck-types as a writable stream, so it can be passed wherever a file object is expected (run_geomopt(progress=...) relies on that).

runner.py, periodic_runner.py, and __init__.py’s _write_double_hybrid_outputs no longer thread an f handle: 172, 152, and 24 f.write(...) call sites respectively became write(...), and the handle is gone from all three entirely. The double-hybrid writer had opened its .out without buffering=1, so tail -f never worked on those jobs; it is line-buffered now. The IUPAC naming block, which was inlined in the SCF runner solely because that was the only scope holding f, moved to vibeqc.naming.report.write_iupac_name and now emits its own line. It lives under vibeqc.naming (which already requires a built vibe-qc) rather than in the standalone vibeqc_naming distribution, so a naming library does not acquire knowledge of quantum-chemistry output channels.

The active channel is a contextvars.ContextVar, matching structured_log._active / perf.active_tracker / crash_dump.active_crash_dump_stem. A ContextVar does not reach a worker started with threading.Thread; vibe-qc spawns no Python threads (threading appears only as Lock, parallelism is OpenMP inside C++), and tests/test_output_channel.py pins both that limitation and the tree-wide premise behind it.

vibeqc.output.writer.OutputWriter, the manifest/plan coordinator, is untouched.

Fixes a latent bug found while migrating. The NTO-molden failure warning in runner.py called f.write(...) after the with open(out_path, ...) block had closed, so f was a closed file object and the call raised ValueError: I/O operation on closed file. The enclosing handler caught that and reported it as a qvf_tddft_nto_prep failure, so the warning never reached the .out and the real cause was masked. It now calls warn_output_failure(..., role="nto_molden"), which needs no channel.

Added: vibeqc.output.document, semantic primitives for text output (2026-07-10)

First milestone of the unified output logger (handovers/HANDOVER_OUTPUT_LOGGER.md). Purely additive: no call site changed, no existing output moved a byte.

Quantity, FormatSpec, FormatPolicy, Column, Table, and OutputDocument let a module emit what a number is (a value, its kind, its unit) instead of a pre-formatted string, and let a policy decide how it renders. Table computes its own column widths and rule length, retiring the hardcoded "-" * 52 / "-" * 54 / "-" * 56 / "-" * 70 rules that four writers each guessed differently for the same table.

Precision and units are configurable with meaningful defaults. Units are chosen per physical dimension and precision per kind, so DEFAULT_POLICY.with_unit("energy", "eV") moves total energies and SCF energy changes together and a table cannot render a Hartree dE column beside an eV energy column. Defaults reproduce the format the tree already used most often (energy at 18.10f), so adopting the layer is not simultaneously a restyle.

Also pins, in tests/test_cpp_emits_no_user_output.py, the invariant the C++ core has always satisfied by accident: it emits no user-facing output. All 231 sources under cpp/ contain exactly one output call, a verbosity-gated fprintf(stderr, ...) eigensolver residual in lobpcg.cpp. Formatting stays in Python, where a FormatPolicy can reach it; a stray std::cout in a new kernel now fails CI.

Migrating runner.py / periodic_runner.py onto this layer, and the resulting .out format change, land in a later milestone.

Fixed: vibe-view trusted a contradictory dimensionality over pbc (2026-07-10)

be7a9f4a made dimensionality == sum(pbc) a normative QVF invariant and enforced it in both reference writers and in validate_qvf. The vibe-view reader was never updated to match: _resolve_pbc took pbc for the axis flags but returned the archive’s raw dimensionality beside it, so an archive that disagreed yielded a self-contradictory (pbc, dim) pair rather than an error.

dim is now derived from pbc rather than read alongside it, and a contradictory payload raises QVFError. Nothing in the payload can adjudicate which of the two fields is the lie, and guessing is how a 2D slab came to be drawn inside a phantom vacuum box in the first place.

Also pins that the reader preserves non-contiguous pbc. [true, false, true] is a legal slab periodic in x and z, which the spec explicitly permits; dimensionality counts the periodic axes but cannot name them, so reconstructing the axes from dim=2 would draw the a,b plane instead of a,c.

The PeriodicSystem comment and pybind docstring no longer call the synthesized lattice columns “vacuum directions”. They are non-physical bookkeeping that keeps the matrix full-rank; the energy is invariant to their length, and calling them vacuum is what invited readers to draw them.

Changed: BIPOLE SYM3b symmetry resolution shared by all four drivers (M3b) (2026-07-11)

The opt-in SYM3b Fock-symmetry resolution (_fock_sym_map / _rep_cell_indices construction plus the OPT-IN-ONLY boundary-truncation rationale) moves from four inlined copies into pbc_bipole_common.resolve_bipole_fock_symmetry. The copies had drifted in their log lines only — RHF reported a reduction ratio and a no-usable-symmetry fallback notice the others lacked — so all four now share the richest form. Numerics byte-identical (M-series byte-check); the opt-in symmetry paths pinned by tests/test_bipole_symmetry_fock.py stay green.

Changed: BIPOLE lattice-options prep shared by all four drivers (M3a) (2026-07-11)

The Ewald-J-split resolution, the neutral-cell nuclear-cutoff clamp, and the CRYSTAL-gauge lat_opts_2e/lat_opts_1e construction — inlined near-verbatim in each of the four BIPOLE drivers — now live in one shared helper, pbc_bipole_common.prepare_bipole_lattice_options (the name the original M1 unification plan reserved for it). Byte-identical: same operations in the same order, verified with the M-series byte-check protocol. First slice of the M3 scaffold unification; the measured RHF↔RKS setup-region overlap (53% line-identical, deltas in ~10 coherent blocks) and the recommended next slices are recorded in handovers/HANDOVER_BIPOLE_VARIANT_UNIFICATION.md.

Changed: BIPOLE RKS/UKS no longer run the XC quadrature twice per iteration (2026-07-11)

Every BIPOLE RKS and UKS SCF iteration ran the full periodic DFT-grid quadrature twice on the same density: once inside the Fock build (for V_xc, its e_xc discarded) and once in the loop body purely to extract e_xc. The Fock-build bundle now carries the e_xc of the same quadrature whose V_xc entered F(k), and the loop consumes it. Besides removing one grid pass per iteration (material for pure functionals, where the ERI build is J-only), this ends the small internal inconsistency of V_xc and e_xc coming from two independent multithreaded quadratures. Byte-check vs the M-series protocol: PASS, worst drift 1.7e-13 (the documented RKS XC/BLAS run-to-run noise floor); the post-convergence final-energy recompute is untouched.

Fixed: BIPOLE nuclear-cutoff clamp now applies to all four direct drivers (2026-07-11)

9ea2651d fixed a mHa-scale molecular-limit overbinding (nuclear point-charge tails on Ewald cells whose compensating electronic charge is truncated away) by clamping nuclear_cutoff_bohr down to cutoff_bohr under the 3-D Ewald-J split — but only in the RKS driver body and in run_periodic_job. Direct callers of run_pbc_bipole_rhf/uhf/uks (the FD-gradient path, validation scripts) kept the unclamped library defaults (electronic 15.0 vs nuclear 25.0 bohr) and silently disagreed with the runner route and with RKS on identical physics. The clamp is centralised as pbc_bipole_common.clamp_bipole_nuclear_cutoff and applied by all four drivers; run_periodic_job results are unchanged (the runner already clamped). New tests/test_pbc_bipole_cutoff_coherence.py pins the resolved cutoff per driver (verified to fail on the pre-fix drivers) plus the converged closed-shell UKS==RKS reduction at default cutoffs.

Changed: BIPOLE UKS routes through the shared unrestricted Fock builder (M2d) (2026-07-10)

The last of the four BIPOLE drivers still carrying its own inline two-electron Fock assembly (UKS, 497 lines) now routes through pbc_bipole_fock.build_bipole_unrestricted_fock, completing the M2 layer of the BIPOLE variant unification: RHF/RKS share the restricted builder, UHF/UKS the unrestricted one. The UKS-specific behaviors are explicit builder parameters (exchange scale for hybrids/pure functionals, the HF-like PATOM seed build, the preserved legacy-gauge halved-exchange convention, the pure-functional incremental ΔD accumulator, the SYM3b-reduced J-only path), and the builder can defer per-k assembly so the UKS driver folds its per-spin V_xc in before hermitisation exactly as before. No public API or result-field change. Verified by the M-series byte-identity protocol: UHF/RHF bit-identical, all UKS fixtures Δ == 0, worst drift 1.1e-13 confined to the known RKS XC/BLAS run-to-run noise floor. See handovers/HANDOVER_BIPOLE_VARIANT_UNIFICATION.md (M2d); remaining: M3–M5 (unified SCF kernels + shared accelerator utilities).

Fixed: docs still told users the writer emits qvf_version: 2 (2026-07-10)

The qvf_version: 2 withdrawal landed the schema, writer, governance ruling and registry, but four doc surfaces still described the bump as current behaviour: docs/tutorial/qvf_file_format.md (§ manifest root and § 8), docs/user_guide/vibe_view.md (periodic reaction paths), and docs/user_guide/relaxed_scan.md (periodic scans) each told the reader that a periodic reaction.path ships as qvf_version: 2. They now say what the writer actually does: the archive stays v1 and a consumer detects a periodic path by the lattice member’s presence. docs/design_qvf_format.md also gains the structure payload’s periodicity contract, mirroring spec § 5.1.

Added: coverage closing the BIPOLE bz_integration callsite gap (2026-07-10)

Commit 891acead (first shipped in v0.15.23) put the new bz_integration keyword on the wrong callsite in periodic_runner.py: every RHF + jk_method="bipole" + multi-k run_periodic_job raised TypeError: run_pbc_bipole_rhf() got an unexpected keyword argument 'bz_integration' (without the user passing the keyword), and BIPOLE RKS accepted bz_integration="gilat" but silently dropped it — the advertised Gilat feature was inert. Both defects were fixed by 38c4afed (v0.15.31), but they shipped in eight consecutive tags (v0.15.23..v0.15.30) because no test drove RHF + bipole at a real multi-k mesh on a 3-D cell through the public route, and the Gilat-forwarding test asserted only that a result existed.

This entry adds the missing coverage (no runtime code changes; the code fix already shipped): a live RHF + bipole + k=(2,1,1) regression test through run_periodic_job; a static contract test binding every run_pbc_bipole_* callsite’s keywords against the driver’s inspect.signature (catches the whole swapped-callsite defect class without running an SCF); a strengthened test_runner_gilat_bipole_rks_path_runs that asserts the resolved scheme actually reaches run_pbc_bipole_rks; and a pin on the BIPOLE UHF/UKS + gilat fail-closed NotImplementedError. All new tests were verified to fail against v0.15.30’s periodic_runner.py/pbc_bipole.py and pass on main. The full provenance record, including the verification probe and the manuscript-impact analysis (none — both published BIPOLE rows were produced at 5a1a0b97, outside the window), is BUG-PER-002 in handovers/HANDOVER_OPEN_BUGS_V015.md.

Added: QVF specifies the structure payload’s periodicity contract (2026-07-10)

A consumer could not assert that a QVF structure was a 2-D slab, only infer it. pbc, lattice_vectors, and dimensionality were prose-only (qvf-format-spec.md § 5.1), and the prose made dimensionality a MAY on the section object while the vibe-qc producer wrote it into the payload. So vibe-view carried a fallback (_resolve_pbc, f15094ee): when pbc was missing it assumed the periodic axes were the leading ones. Guessing wrong renders a slab inside a vacuum box, or tiles a density isosurface along the slab normal.

The structure member’s payload is now schema’d, as $defs/StructurePayload in qvf-writer/spec/qvf_manifest.schema.json. It is QVF’s first payload schema — until now only manifest.json was schema-validated and JSON members merely had to parse. The contract:

  • pbc is the normative carrier of per-axis periodicity, and is REQUIRED whenever lattice_vectors is non-null. Absent pbc means a molecule.

  • dimensionality is derived, with the invariant dimensionality == sum(pbc), and carries no axis identity.

  • A lattice row whose axis is aperiodic is non-physical bookkeeping — present so the matrix stays full-rank 3×3, never a cell edge. A synthesized 30-bohr normal and a real 30-bohr vacuum gap are numerically identical, so pbc is the only signal that separates them.

The periodic axes are deliberately not required to be the leading ones. That ordering is a vibe-qc internal convention (cpp/include/vibeqc/periodic.hpp); making it normative would have made a slab with its normal along y (pbc = [true, false, true]) non-conforming, at odds with QVF’s producer neutrality. It survives as an informative note describing the legacy dimensionality-without-pbc fallback.

No qvf_version bump: absent pbc had no defined meaning, every known producer already emits it, and every archive in the conformance corpus and in docs/_static/examples/ validates unchanged. Ruling recorded in qvf-writer/GOVERNANCE.md § “Version history and decisions”.

Enforcement, so the invariant is not dead prose: qvf_reader.validate_qvf checks the contract semantically (no jsonschema needed) and structurally against $defs/StructurePayload when jsonschema is installed; spec § 6’s validator checklist and § 8’s producer/consumer conformance lists say so. Both reference writers now reject a periodic pbc with no lattice, and the Python one additionally rejects dimensionality != sum(pbc) and writes dimensionality into the payload rather than onto the section object. New conformance vector structure_slab_2d.qvf (graphene, pbc = [true, true, false], dimensionality = 2, third lattice row a vacuum-gap-sized 15.875 Å normal) fails any consumer that reads periodicity off lattice geometry. Verified: vibe-qc’s _write_structure_section conforms for dim 0/1/2/3; all 10 archives under docs/_static/examples/ still validate; the derived qvf_manifest_v2.schema.json was regenerated from the new v1.

Fixed: KS direct-torus route catches up with the dim<3 fail-closed correction (2026-07-10)

The v0.15.32 low-dimensional Coulomb-gauge correction made every dim < 3 3-D-torus route fail closed, but run_ccm_rks_direct and its gates were missed by that sweep:

  • run_ccm_rks_direct now calls the explicit _reject_low_dimensional_direct guard early, exactly like run_ccm_rhf_direct — refusing a dim < 3 cell with the curated message (wire-kernel pointer, gauge algebra) under its own name, instead of failing later inside the mesh builder with an rsgdf_dense_g_mesh-named error. The guard’s message now names the calling driver (who parameter). dim = 3 is bit-unchanged.

  • tests/test_ccm_rks_direct.py carried three stale dim=1 chain fixtures. The multi-k dim<3 KS parity gate asserted direct == GDF on the collapsed kernel — mutual agreement between two routes sharing a defect (the same trap 9e6bbaf2 removed from the KRKS pins) — and is converted to test_rks_direct_and_gdf_multik_lowd_both_fail_closed. The a_x-scaled seam-identity and route-dispatch tests used the chain incidentally and move to the compact 3-D cell, mirroring their HF twins in test_ccm_direct.py.

Pins: tests/test_ccm_rks_direct.py (11 passed; the fail-closed gate requires NotImplementedError from both run_ccm_rks_direct and run_ccm_rks_gdf on a dim=1 chain).

Fixed: hse-3c used PBEh-3c’s gCP fit constants (2026-07-10)

The other half of the entry below. run_job(method="hse-3c") also computed the wrong geometrical-counterpoise correction, because python/vibeqc/gcp.py keyed gCP parameters by basis while gCP’s four fit constants are properly per (method, basis) pair, exactly as in gcp.f90, which selects them on the method. PBEh-3c, B3LYP-3c and HSE-3c all sit on def2-mSVP, so HSE-3c silently inherited PBEh-3c’s fit.

The sources for HSE-3c’s own constants conflict, and the conflict is now resolved in favour of the paper:

  • Table S1 of the ESI to Brandenburg, Caldeweyher & Grimme, Phys. Chem. Chem. Phys. 18, 15519 (2016), doi:10.1039/c6cp01697a: sigma = 1.00000 (constrained), eta = 1.40858, alpha = 0.29083, beta = 1.95260.

  • gcp.f90 case ('hse3c'): eta = 1.32378, alpha = 0.28314, beta = 1.94527. Identical in classic gcp v2.02 (Nov 2016), in Holger Kruse’s fork, and in every commit of the grimme-lab/gcp rework since its first. Not a documented refit.

The paper wins because the paper can be checked against itself. Its ESI publishes a numerical example (section B, Table S2): gas-phase benzene at the geometry of the accompanying CRYSTAL14 input has E_gCP = 0.0148400663 a.u. Evaluated against vibe-qc’s bundled def2-mSVP e_mis / n_virt tables with the damped form:

constants

E_gCP(benzene) / a.u.

error

Table S1 (eta=1.40858)

0.0148400665

+2e-10 Ha

gcp.f90 (eta=1.32378)

0.0147289535

-1.11e-4 Ha (-0.070 kcal/mol)

PBEh-3c (eta=1.32492), as shipped

0.0152811330

+4.41e-4 Ha (+0.277 kcal/mol)

Only Table S1’s constants reproduce the authors’ own published number, so those are the constants HSE-3c was defined and benchmarked with. The exact agreement also confirms the bundled per-element tables are the ones the authors used. Consequence: vibe-qc’s hse-3c now matches the published method and CRYSTAL (whose self-contained internal gCP produced that number), and differs by ~0.07 kcal/mol of gCP on benzene from any code calling Grimme’s gcp binary (ORCA, the conda gcp package). The discrepancy is acknowledged upstream and unresolved: grimme-lab/gcp issue #17, where a maintainer states the intent is to move the code to the paper’s values. On H2O, E_gCP moves from +0.8248 to +0.8399 kcal/mol.

The keying change: data_library/gcp/*.toml gains an optional [variants.<method>] block overriding any of the four fit constants while reusing the file’s [elements.*] tables verbatim; CompositeRecipe gains gcp_variant; vq.gcp_params_for / vq.compute_gcp gain variant=; and vq.available_gcp_variants(basis) lists them. Only hse-3c registers a variant. A variant= naming an unregistered block raises ValueError rather than falling back to the basis-level parameters, because a silent fallback is precisely how this bug arose (and because the runner degrades gracefully on GCPDataMissing, which would have hidden it). b3lyp-3c still deliberately reuses PBEh-3c’s constants undamped; it has no 3c paper of its own.

Pins: tests/test_composite_integration.py::test_hse_3c_gcp_matches_published_benzene (asserts the four constants against ESI Table S1 and reproduces ESI Table S2’s benzene value to 1e-9 Ha, and checks that both rejected constant sets fail to reproduce it), plus ::test_gcp_unknown_variant_raises_rather_than_falling_back and ::test_pbeh_3c_and_b3lyp_3c_keep_the_basis_level_gcp_constants.

Any hse-3c absolute energy or lattice energy produced through v0.15.x is affected and should be recomputed.

Fixed: hse-3c used PBEh-3c’s D3(BJ) damping constants (2026-07-10)

run_job(method="hse-3c") computed the wrong dispersion correction. The recipe in python/vibeqc/composites.py carried a1=0.4860, a2=4.5000, which are PBEh-3c’s D3(BJ) damping constants, on the assumption that HSE-3c was a community extension with no published damping of its own. It is not: HSE-3c is published as Brandenburg, Caldeweyher & Grimme, Phys. Chem. Chem. Phys. 18, 15519 (2016), doi:10.1039/c6cp01697a, and Table S1 of its ESI gives HSE-3c’s own constants, re-fit on S66x8: s6 = 1.00000, s8 = 0.00000 (both constrained), a1 = 0.44110, a2 = 4.51820. Screening the long-range Fock exchange changes the medium-range correlation the damping has to absorb, so the two composites do not share a damping fit even though they share the def2-mSVP basis and the short-range potential.

The correction is energy-affecting, not cosmetic. The ESI publishes a benzene numerical example (ESI section B, Table S2) whose gas-phase D3 energy is -0.0068628129 a.u. Evaluating that geometry with simple-dftd3 under the paper’s own D3 specification (BJ damping plus the ATM three-body term) reproduces the published value to 1e-10 Ha with the corrected constants, and gives -0.0058906668 a.u. with the constants that shipped: an error of +0.97 mHa (+0.61 kcal/mol) on a single benzene molecule. The ATM term is common to both, so the gap is entirely the damping fit. In vibe-qc itself the switch moves energy_total for hse-3c on H2O by -6.44e-05 Ha (-0.040 kcal/mol). Any hse-3c interaction or lattice energy produced through v0.15.x is affected and should be recomputed.

(vibe-qc’s own D3 has no ATM term at all, which is a separate pre-existing gap that also affects pbeh-3c; cpp/include/vibeqc/dispersion.hpp records it as not yet implemented.)

Pins: tests/test_composite_integration.py::test_hse_3c_d3bj_damping_matches_published_esi, which asserts the four published constants against ESI Table S1 and guards them apart from PBEh-3c’s.

One adjacent item is deliberately untouched: the b3lyp-3c recipe genuinely is a community extension with no published 3c paper, so its inherited constants stay. HSE-3c’s gCP parameters were the other half of this bug and are fixed in the entry above.

Changed: the GAPW / GPW / RSGAPW drivers extrapolate through the canonical DIIS (2026-07-10)

The run_periodic_*_gapw / _gpw / _rsgapw family carried the fifth hand-rolled numpy Pulay DIIS, and unlike the three in scf_acceleration.py (removed below) this one was live and reachable. Six call sites across periodic_gapw_j.py, periodic_gapw_augment.py, periodic_gapw_open_shell.py and periodic_gapw_range_sep.py appended to a private history, rebuilt the bordered Pulay B-matrix in a Python double loop and re-derived the solve, against CLAUDE.md §10’s single-canonical-implementation rule.

All six now reach vibeqc::DIIS (cpp/src/diis.cpp) through a new _make_diis helper: the three closed-shell drivers via DIIS.extrapolate, the three open-shell drivers via DIIS.extrapolate_spin_coupled. The mapping is exact, not approximate. For the real symmetric error matrices these drivers build, np.sum(e_i * e_j) is the Frobenius product Tr(e_iᵀ e_j) the kernel accumulates in its Gram matrix; and stacking α on β makes the kernel’s single B-matrix accumulate Tr(e_α_iᵀ e_α_j) + Tr(e_β_iᵀ e_β_j), termwise the joint B-matrix the open-shell sites built by hand. Net −117 lines.

No behaviour change on the healthy branch: the extrapolated Fock agrees with the retired build to 1e-11 over a converging 10-iterate history. Two differences are deliberate. The kernel carries the coefficient blow-up guard (a non-finite solve, or coefficients above 1e6, drops the oldest iterate and re-solves) where the numpy code caught only an exactly singular np.linalg.solve and skipped extrapolation for that iteration. And diis_subspace_size < 2, which the numpy history degenerated to a silent no-op, now disables DIIS explicitly rather than reaching the kernel, whose constructor rejects a subspace it cannot extrapolate from. The DIIS error vector is still built from the D that produced F, as tests/test_diis_error_vector_source.py pins.

This retires the duplication; it does not widen the accelerator surface. The family still takes use_diis / diis_subspace_size / diis_start_iter and not scf_accelerator, so EDIIS, ADIIS, KDIIS and the adaptive-depth policies remain unavailable on it. Those are separate classes (cpp/src/ediis.cpp, cpp/src/kdiis.cpp) needing an energy history or an MO-basis orbital gradient that DIIS.extrapolate never sees, so routing does not grant them for free. docs/user_guide/scf_convergence.md now states that narrower exception (the family shares the kernel, not the keyword) in place of the retired one.

Tests: new tests/test_gapw_diis_kernel.py (8) pins the kernel against the retired numpy build in both shapes, pins that open-shell spin coupling is one joint history rather than two independent ones, and guards mechanically that no hand-rolled Pulay solve returns to the family.

Removed: dead scf_acceleration.py module (2026-07-10)

python/vibeqc/scf_acceleration.py is deleted. Its three classes were unreachable: not exported from __init__.py, reached by no static or dynamic import, and covered by no test. AndersonAccelerator and BroydenAccelerator were superseded in v0.14.0 by the tested, wired AndersonMixer / BroydenMixer in periodic_density_mixing.py (the latter without the orphan’s n_occ = D_new.shape[0] // 2 occupation guess). SmearingAwareDIIS was a fourth hand-rolled numpy Pulay DIIS, in violation of CLAUDE.md §10’s single-canonical-implementation rule, and had never executed: its extrapolate raised ValueError out of np.trace on a raveled 1-D error vector on the first call that reached a subspace of 2. Its smearing-aware error vector was not ported, for two reasons. The canonical vibeqc::DIIS::extrapolate(fock, error) already accepts the error vector as a caller argument, so a driver that wants a density residual can pass one with no C++ change; and the class’s stated rationale does not hold, since the commutator error FDS - SDF already vanishes at the SCF fixed point under fractional occupations (D = C f C^T with F C = S C eps gives FDS - SDF = S C (eps f - f eps) C^T S = 0, as eps and f(eps) are both diagonal). No behaviour change: no shipped code path referenced the module. kerker.py, DynamicDamping, and the multi-k accelerators in periodic_scf_accelerators.py are untouched and live.

Fixed: MSINDO CCM FD gradients no longer leak native NaNs (2026-07-10)

ccm_gradient_fd now accepts the C++ finite-difference fast path only when it returns the expected (n_atoms, 3) shape with all finite entries. If the native path returns NaNs, as it did for the 3-D MgO NOEWALD fixed-WS displacement set, the public API falls through to the validated Python fixed-WS reference instead of returning a partially NaN gradient. This retires the tests/test_msindo_ccm_gradient.py known-red while keeping the native dim=3 non-Madelung FD branch on the semiempirical C++ performance follow-up list.

Pins: tests/test_msindo_ccm_gradient.py and scripts/test_gate/known_reds_baseline.json.

Fixed: semiempirical OVGF now cites its own method papers (2026-07-10)

msindo_ovgf computes NDDO/INDO GF2-OVGF ionization potentials but emitted no method citation, and danovich_ovgf_1997 was reachable from nothing. The existing routes.methods.ovgf row could not cover it: that row keys off method="ovgf", the ab-initio run_job path, while the semiempirical route reports method="msindo".

msindo_ovgf now takes the same optional output= argument as msindo_cisd, and writes .bibtex / .references naming Danovich 1997 (NDDO + Green’s function) over the OVGF formalism of Cederbaum 1975 and von Niessen 1984, alongside the MSINDO papers. Both the closed-shell and open-shell (UOVGF) return paths emit. output=None remains the default, so existing calls are unchanged.

Removed: redundant CCSD citation scuseria_schaefer_ccsd_1988 (2026-07-10)

The entry claimed “cite when CCSD is used”, but no route reached it and nothing in the CC code derives from it. cpp/include/vibeqc/ccsd.hpp states that the closed-shell spatial equations are the spin integration of Stanton-Gauss-Watts- Bartlett 1991, with Purvis-Bartlett 1982 as the original-CCSD reference; both are already routed for ccsd / ccsd(t) / ccsd[t]. Scuseria-Janssen-Schaefer’s reformulation is not the one implemented, so citing it would have misattributed the working equations.

Changed: daga_bdiis_2020 merged into daga_optbasis_2020 (2026-07-10)

The two keys were the same paper (Daga, Civalleri & Maschio, JCTC 16, 2192 (2020), doi:10.1021/acs.jctc.9b01004) under two entries, so a BibTeX file that reached both would have carried it twice. daga_optbasis_2020 survives, reached both statically from routes.acceleration.optbasis and directly through BDIIS_CITATION_KEYS; res.citations from a basis optimisation now reports that key. The citation database has no duplicate DOIs left and _KNOWN_DUPLICATE_DOIS is empty.

The merge had been deferred on the belief that it touched the basissetdev branch. It does not: python/vibeqc/basis_optimization/ and tests/basisset_dev/ are both tracked on main — the on-main directory is not the basissetdev branch.

Removed: four citation entries for basis sets that do not ship (2026-07-10)

zheng_madef2_2011 (ma-def2), franzke_x2c_2020 (x2c-QZVPall), ye_berkelbach_gth_2022 (GTH-cc-pVXZ) and vandevondele_molopt_2007 (MOLOPT) each claimed a shipped basis family, but no matching .g94 exists in the bundled library and none was routed, so no job could ever have cited them. The x2c family that does ship is TZVPall, already routed to pollak_weigend_x2c_2017. Per CLAUDE.md § 8, whoever lands one of these bases adds its entry and route in the same commit.

Dropping vandevondele_molopt_2007 also resolves a duplicate-DOI pair: it shared doi:10.1063/1.2770708 with vandevondele_basisopt_2007, which survives (it is surfaced by BDIIS_CITATION_KEYS) and whose note now records both roles of the paper. assemble() deduplicates by entry key rather than by DOI, so routing both would have printed one paper twice.

Fixed: rsgdf_dense_g_mesh clipped the G-ball on non-symmetric lattice matrices (2026-07-10)

The dense-mesh index-box bound |n_i| <= G_max |a_i| / 2pi was computed from ROW norms of the lattice matrix while vibe-qc lattice matrices carry the vectors in COLUMNS (the same function’s b = 2pi inv(a).T and G = idx @ b.T already use the column convention). For symmetric matrices (the fcc/bcc primitive cells where every historical control lived) rows == columns and nothing was wrong; for a non-symmetric matrix — any (n,1,1) supercell of fcc, hexagonal cells, triclinic cells — the row norm underestimates |a_i| and the box silently clipped the |G| <= G_max ball: measured 602 of 3381 points (18%) missing on the C-diamond (2,1,1) cluster cell at ke_cutoff = 60. Consequences on affected cells: every all-FT fit (build_lpq_bloch_native_fft / build_lpq_native_fft) was under-converged relative to its declared ke_cutoff, the exact base + tail == big-mesh identity of the tail completion could not hold (clipped points fell in neither base nor tail), and the mesh lost its point-group closure (caught 2026-07-10 by the space-group pair-reduction covariance gate at 4e-3, ke-dependent and lattice-cutoff-resistant). Pins: tests/test_ccm_symmetry.py::test_dense_g_mesh_is_complete_ball_on_nonsymmetric_lattice (+ the diamond (2,1,1) parity regression), existing tests/test_rsgdf_dense_mesh_tail.py re-verified. Energies on symmetric-matrix cells are bit-unchanged; on non-symmetric cells they move toward the converged values their ke_cutoff always claimed.

Patch-candidate: v0.15.x

Fixed: analyze_ccm_symmetry admitted ops that break the BvK torus (2026-07-10)

Cluster invariance was tested by atom positions alone (atom_permutation_under_op modulo the cluster lattice) — but that test passes for EVERY crystal op regardless of cluster shape, because every image site is congruent to some cluster site mod the cluster lattice. Ops whose rotation does not map the cluster lattice onto itself (36 of the 48 fcc ops for nrep = (2,1,1)) are not symmetries of the torus: with them admitted, even the S^CCM “invariance” residual was 4.7 on C-diamond (2,1,1). analyze_ccm_symmetry now additionally requires the exact integer condition W_ij n_j / n_i Z (the op normalizes the BvK lattice); equal-nrep cubic clusters keep all 48 ops as before, the (2,1,1) fcc cluster correctly keeps 12. Prior consumers only ran equal-nrep or orthorhombic-chain clusters, which the condition leaves unchanged. Pin: tests/test_ccm_symmetry.py::test_symmetry_reduced_cderi_diamond_multicell_regression.

Added: space-group pair reduction of the Γ-supercell neutral cderi (2026-07-10)

ccm_neutral_cderi(..., symmetry=True | CCMSymmetry) fits only the space-group orbit-representative atom-pair AO blocks (through the GDF fit builder’s fit_pair_list shell-pair seam, composing with the Cauchy-Schwarz fit screen in one build path) and reconstructs every other pair block by the group action, using the Γ canonical-frame covariance L[:, gμ, gν] = P_aux^(g) L[:, μ, ν]. The G-ball, Coulomb kernel, per-shell calibrations, and 2c metric are exactly point-group invariant (measured 2e-14); the only non-invariant ingredient is the Bloch cell-list truncation tail (8e-9 relative at the 15-bohr default on C-diamond sto-3g, 6e-16 at 25 bohr), plus a ~2e-10 whitening-amplification floor confined to near-null aux directions. Physical parity is exact for practical purposes: reduced-vs-unreduced direct-torus SCF energy diff 5.5e-13 Ha at production defaults (pinned < 1e-10); tensor-level parity pinned < 1e-9 cell-list-converged. Supporting infrastructure: CCMPairOrbits now carries the full orbit map (orbit_rep/orbit_op), and ccm_symmetry_basis_rotations builds the op rotation matrices over any supercell-centered basis (used for the modrho auxiliary basis). |G_c| = 1 (P1/triclinic) falls back to the full build with no reduction claim (canonical aux frame either way); the reduction factor is logged. Gates: tests/test_ccm_symmetry.py (20 green). The petite-list reduction of the fold / multi-k k-pair loop is the next milestone (handovers/HANDOVER_SYMMETRY_PAIR_REDUCTION.md).

Fixed: OVGF jobs are covered by memory preflight again (2026-07-10)

estimate_memory(..., method="ovgf") now has a real dense GF2/OVGF estimator instead of raising ValueError("unknown method 'ovgf'"). The estimate charges the dense AO ERI tensor, closed-shell MO ERI transform/work tensors, and the open-shell spin-orbital tensor path used by run_job(method="ovgf"), including the small-system renormalized GF2 diagnostic. run_job also forwards the reference SCF options into this estimate, so direct-SCF and related reference settings still influence the preflight block.

Pins: tests/test_memory.py and tests/test_runner_ovgf.py. The resolved tests/test_runner_ovgf.py known-red entry was removed from scripts/test_gate/known_reds_baseline.json.

Changed: the Saunders-Hillier shift operator now has one implementation (2026-07-10)

The level shift was applied at eleven hand-rolled call sites (seven C++, four Python), each spelling out F + b·S w·(S·D·S) and each carrying its own copy of the weight w. That is why run_uks_periodic_gamma_ewald3d could sit on the wrong w indefinitely (see the entry below).

cpp/include/vibeqc/level_shift.hpp now owns the operator as well as the schedule: apply_level_shift (real) and apply_level_shift_k (per-k Hermitian), with the weight selected by a LevelShiftDensity enum, SPIN (weight 1) or TOTAL (weight ½). Both are bound to Python, and all eleven sites route through them. docs/user_guide/scf_convergence.md already documented the rule; nothing enforced it.

Every trajectory is unchanged. A fixed-iteration trace was captured on all fourteen affected code paths before and after, under a persistent shift with DIIS and dynamic damping off: iteration counts are identical everywhere, two traces are bit-identical, and the worst energy deviation is 9.9e-14 Ha (1.3e-15 relative), which is floating-point reassociation, not a changed path. The b == 0 fast path is preserved: the helper returns the Fock untouched, and the Python call sites keep an explicit guard so an unshifted cycle does not marshal S and D across the binding at all.

Guarded by test_no_driver_open_codes_the_shift_operator, which greps the SCF sources for a hand-written S·D·S on a line that also names a shift magnitude. A convention test alone could not have caught the original bug, because it does not bind the drivers to the shared operator.

Fixed: Γ-Ewald UKS applied half the requested level shift (2026-07-10)

run_uks_periodic_gamma_ewald3d built its Saunders-Hillier shift as F_s + b·S (b/2)·S·D_s·S from the spin densities D_alpha / D_beta. Those are idempotent in the overlap metric (D_s S D_s = D_s), so S D_s S is exactly the projector onto the occupied manifold and the coefficient must be 1: occupied eigenvalues stay put, virtuals rise by b. With b/2 the occupied block rose by b/2 too, so the gap opened by b/2 instead of b. Only the closed-shell drivers, which carry the total density D = 2P, legitimately use b/2.

The SCF still converged to the same fixed point (a uniform shift within the occupied subspace cannot rotate the occupied orbitals), which is precisely why no energy assertion could catch it, and why it survived: this driver had no level-shift test of any kind. Its multi-k sibling (periodic_uks_multi_k_ewald.py) and the molecular cpp/src/uks.cpp both already used the correct coefficient, so a given level_shift was half as strong on this one backend as on every other open-shell route.

Pinned by two new tests in tests/test_periodic_level_shift.py: the missing Γ-Ewald UKS honoured/inert pair, and test_level_shift_operator_convention, a driver-free linear-algebra test asserting that the coefficient is 1 for a spin density and 1/2 for a total density, and that in both cases the occupied eigenvalues do not move and every virtual rises by exactly b.

Added: guard that every SCF driver builds its DIIS error from D_in (2026-07-10)

e = F D S S D F vanishes identically whenever D is built from the eigenvectors of that same F, for any occupations, fractional or integer (F D S S D F = S C f f ε) Cᵀ S = 0, since ε and f(ε) are both diagonal). A driver that forms its DIIS error from D_out rather than the D_in that built F therefore gets a zero error vector on every iteration: its DIIS silently degenerates into a no-op, the SCF still converges on the energy criterion, and every energy-only test passes.

tests/test_diis_error_vector_source.py pins the commutator norm non-zero on early iterations for the C++ Γ and multi-k drivers, the Python Γ-Ewald RHF/UHF/RKS drivers, the Γ GDF driver and the multi-k Ewald driver. All of them were already correct; the guard keeps them that way. The probe cell is H2/6-31G, since minimal-basis H2 has symmetry-fixed MOs and reports a ~1e-16 commutator even when the driver is healthy.

The same file pins the corollary that the commutator error does not degrade under smearing: at a self-consistent density with Fermi-Dirac fractional occupations the commutator is as zero as with integer ones. A density-residual error vector is not needed to make DIIS well-defined at T > 0.

Changed: GAPW / GPW drivers documented as outside the unified accelerator surface (2026-07-10)

docs/user_guide/scf_convergence.md claimed every Pulay-family extrapolation in vibe-qc runs on the single vibeqc::DIIS kernel. That is true of every backend reachable through scf_accelerator, but not of the run_periodic_*_gapw / _gpw / _rsgapw family, which takes its own use_diis / diis_subspace_size / diis_start_iter keywords and keeps a private numpy Pulay history. The claim is now scoped to the coverage matrix, and the exception is stated explicitly rather than left for a reader to discover.

Fixed: QVF v2 manifest schema is now derived from v1, not forked (2026-07-10)

qvf_manifest_v2.schema.json was a hand-copied snapshot of the v1 schema and had silently fallen behind it. Its own description claimed v2 “differs from v1 only in that SectionReactionPath carries the per-frame lattice + dimensionality”; in fact v2 was missing ten section kinds (bond_orders, spectra.epr, volume.rdg, volume.potential, basis.ao, fermi_surface, phonon.bands, phonon.dos, equation_of_state, topology.qtaim), the four root blocks (thermochemistry, dipole_moment, constraints, extensions), the Section.critical flag (QVF spec § 5.5), and the bands fat-band projections member.

Because the manifest root sets additionalProperties: false and Section is a closed oneOf, this was not a documentation bug. write_qvf bumps a manifest to qvf_version=2 whenever a periodic reaction.path is present, then validates the archive it just built against the v2 schema. Any periodic reaction path that also carried one of the sections or root blocks above therefore raised at write time (write_qvf produced an archive that fails canonical validation -- this is a writer bug) rather than merely failing a later read. A periodic NEB path plus a bond_orders section, or plus a root thermochemistry block, could not be written at all.

v2 is now derived from v1 at load time by _derive_v2_schema, which applies the one documented delta: $id, title, description, properties.qvf_version.const, the optional lattice member on SectionReactionPath, and the new $defs.ReactionPathLattice. The runtime validator never reads the v2 file, so it cannot drift; a section added to v1 lands in v2 for free. The on-disk qvf_manifest_v2.schema.json is retained as a generated artefact for external validators resolving its $id, and is regenerated by scripts/gen_qvf_v2_schema.py (--check verifies without writing).

An earlier fix (cb6eaffc) had patched the same class of bug by hand-copying the two dos.* definitions into the fork, which addressed the symptom and left the cause. Guarded now by tests/test_qvf_v2_schema_drift.py, which pins “v2 == v1 modulo the documented delta” structurally rather than by re-running the derivation, and by a regression test building a qvf_version=2 archive that carries a periodic reaction.path, a bond_orders section, and a root thermochemistry block.

Open governance question, referred to the qvf-writer chat and deliberately not decided here: qvf-writer/spec/qvf-format-spec.md § 3.1 still says qvf_version MUST be 1 and that a v1-only consumer MUST refuse anything higher, while the reference producer emits 2. qvf-writer/GOVERNANCE.md § Versioning policy further says qvf_version changes only on a breaking change, and that new optional members are additive within a major version — which describes the lattice member exactly. Whether v2 should be documented in the public spec, or the bump withdrawn in favour of an additive optional member on v1, is for the qvf-writer chat to rule on.

Added: GDF fit screening on the Γ-point drivers (2026-07-10)

fit_screen_threshold (the Cauchy-Schwarz screen of the three-centre GDF fit, previously multi-k rsgdf only) now also works on the Γ-point drivers run_pbc_gdf_rhf / run_pbc_gdf_uhf / run_pbc_gdf_uks and on the Γ (1,1,1) fast path of run_krhf_periodic_gdf (which previously raised NotImplementedError). On the Γ path a positive threshold selects the memory-lean build: build_lpq_native_fft delegates to the Bloch builder at k = 0 (one screened build path — the M1 screen and M2 G-chunked sweep come with it) and the Ewald-3D V_ne FT streams its own chunked AO-pair FT (compute_v_ne_ewald_3d_ft_gamma(stream_pair_ft=True)), so the dense (n_ao, n_ao, n_G) pair-FT tensor — the dominant memory of a Γ GDF build — is never materialised. Conflicting combinations (precomputed pair_ft_shared bundle, tail_pair_ft_screen, non-rsgdf fit methods, Γ meshes that fall back to the legacy molecular-limit driver) raise loudly instead of silently ignoring the option. New pins in tests/test_gdf_fit_screening.py (Γ four-center parity, dropped-pair zeroing, conflict errors, Γ fast-path energy invariance, plus slow c-diamond + MgO multi-k energy-invariance gates and a sub-quadratic significant-pair-count scaling gate) and tests/test_periodic_v_ne_ewald_ft.py (streamed-vs-dense V_ne parity). See handovers/HANDOVER_GDF_FIT_SCREENING.md and docs/user_guide/periodic_methods.md § 3.2.2.

Changed: spglib now cites its published paper, not the 2018 preprint (2026-07-10)

The spglib library route cited the arXiv preprint (Togo & Tanaka, arXiv:1808.01590, 2018) and carried no DOI, so a periodic run’s references block named a preprint the authors have since superseded. It was published as Togo, Shinohara & Tanaka, Spglib: a software library for crystal symmetry search, Sci. Technol. Adv. Mater.: Methods 4, 2384822 (2024), doi:10.1080/27660400.2024.2384822 – the reference spglib’s own documentation asks for. The published version adds Shinohara as an author. Entry key and BibTeX key move to togo_shinohara_tanaka_2024 / togo_spglib_2024.

Of the seven DOI-less entries this audit examined, the other six are correctly DOI-less: vibe-qc’s own software citation, libint, Eigen, Scholten’s 2003 PhD thesis, and the Huzinaga and Wilson-Decius-Cross books. (Crossref’s nearest hit for the latter, doi:10.1021/ja01588a076, is a two-page JACS book review, not the book.)

Added: citation coverage tests for functionals, dispersion, ECPs (2026-07-10)

The KDIIS mis-attribution and the four defect classes below all survived code review. tests/test_scf_accelerator_citation_coverage.py answered that for the SCF accelerators by pinning each one’s originating DOI; this extends the same pattern to the rest of the mandatory citation surface of CLAUDE.md § 8.

  • tests/test_functional_citation_coverage.py – every [routes.functionals] route resolves; each composite’s defining paper is pinned by DOI; each hybrid/composite is pinned to the component papers it evaluates (the guard that would have caught b2plyp never citing B88).

  • tests/test_dispersion_citation_coverage.py – D2/D3/D3(BJ)/D4 originating DOIs pinned, and all fourteen [routes.dispersion_params] reparameterisations pinned to the paper that fitted those parameters. Adding a reparameterisation without pinning it here now fails.

  • tests/test_ecp_citation_coverage.py – libecpint and the Hay-Wadt / Peterson potential papers pinned; unrouted ECP entries tracked explicitly.

  • tests/test_citation_database_integrity.py – structural invariants needing no network: no two entries share a DOI, every kind="article" has one, DOI syntax is well formed, no route dangles, no BibTeX-key collisions.

Each guard was mutation-tested: reintroducing the corresponding defect makes it fail.

Fixed: one paper printed twice in the references block (2026-07-10)

assemble() deduplicates by entry key, not by DOI, so two keys for the same paper print it twice. hehre_sto_ng_1969 (routed from dftb0 / scc_dftb) and hehre_sto3g_1969 (routed from the sto-3g / sto3g / sto-6g basis keys) were the same publication, doi:10.1063/1.1672392. A method="dftb0", basis="sto-3g" job emitted Hehre, Stewart & Pople 1969 twice in its .out references block and twice in its .bibtex, under two different BibTeX keys. Collapsed onto hehre_sto_ng_1969, whose page range was also incomplete (2657 -> 2657--2664).

Fixed: routes citing papers that do not cover the routed feature (2026-07-10)

Third pass of the citation audit, applying the KDIIS disproof technique (extract the cited paper’s text, grep for the routed method) to every [routes.*] mapping.

  • lccd, lccsd, cepa(0), cepa(1), cepa(2), cepa(3) each cited DePrince & Sherrill 2013, a DF/CD/FNO CCSD efficiency paper whose full text contains zero occurrences of “CEPA”, “LCCD”, “LCCSD”, “coupled-pair” or “linearized”. It stays on ccsd, ccsd(t), ccsd[t], ccd and fno-ccsd(t), which it does describe. The coupled-pair routes already carry Čížek 1966 / Meyer 1973 / Wennmohs & Neese 2008.

  • r2scan-3c cited Grimme’s D3 paper, but the r2SCAN-3c recipe uses D4 (composites.py sets dispersion="d4"), and the method’s own paper uses D3 only for the comparison functionals it benchmarks against. Dropped; D4 was already routed.

  • b2plyp never cited B88, the semilocal exchange it actually uses (“the combination of B88 and LYP yields the best results” – Grimme 2006). becke_b88_1988 added, as blyp already does. Becke 1993 is retained: the B2PLYP paper cites it for the adiabatic-connection hybrid mixing.

  • hse-3c cited its HSE, gCP and D3(BJ) components but no composite-method paper, unlike every sibling 3c method. New entry brandenburg_hse3c_2016 (Phys. Chem. Chem. Phys. 18, 15519-15523 (2016), doi:10.1039/c6cp01697a) is routed and printed, and the recipe’s inline citation field no longer describes the method as a “community extension”.

  • langhoff_davidson_1974 and langhoff_davidson_q_1974 were two entry keys for one paper (identical DOI, authors, title, journal, pages, year), reached from mrci and mrci+q respectively. Collapsed onto the fuller record.

Not changed, having been checked and found correct: mrci legitimately cites the Davidson +Q paper, because solvers/_mrci.py applies the +Q correction by default (do_q_correction=True). ediis_diis legitimately keeps Garza & Scuseria 2012 – unlike kdiis, that route names a hybrid the paper really does recommend.

Fixed: citation metadata that disagreed with the cited paper (2026-07-10)

Second pass of the same Crossref sweep, comparing every recorded author list, title, journal, volume, pages and year against the canonical record. Seven entries disagreed with the paper they name:

  • dral_omx_2016 invented two co-authors: “Spoerkel, Lukas” is really Spörkel, Lasse and “Steenken, Ralf” is really Steiger, Rainer. Both names had propagated into semiempirical/methods/omx_params.py.

  • jarvis_dft listed “Gavini, Vikram”, who is not an author, and omitted seven who are (Kalinin, Pilania, Acar, Mandal, Haule, Vanderbilt, Rabe): 22 names recorded, 28 on the paper.

  • curtarolo_aflow_2012 recorded 6 of the paper’s 14 authors.

  • hehre_pople_stong_2nd_row_1970 and weigend_jkfit_2002 carried paraphrased titles that do not match the published ones.

  • andersson_caspt2_1990 carried the 1992 companion paper’s title. The two are distinct works and the 1992 entry’s title was already correct.

  • wilson_decius_cross_1955 is a book but stored its publisher in the journal field, so the reference rendered “McGraw-Hill” as a journal name.

Corrections are taken from Crossref and, where a local PDF exists, checked against the paper’s own title page. Note that Crossref is not always right: its record for lopez_sancho_rubio_1985 lists four authors and its record for roos_taylor_siegbahn_casci_1980 misspells “Siegbahn”. Both databases entries match their PDFs and were left alone.

Fixed: three citation DOIs did not resolve (2026-07-10)

A sweep of all 363 DOI-bearing entries in the citation database against the Crossref REST API, prompted by the KDIIS mis-attribution below, found three that do not resolve. Each was verified against the publisher’s own PDF and Crossref before correction; no DOI was guessed.

  • choudhary_tavazza_2020 claimed doi:10.1038/s41524-020-0300-2, which resolves to a different paper (Kabiraj, Kumar & Mahapatra, High-throughput discovery of high Curie point two-dimensional ferromagnetic materials, npj Comput. Mater. 6, 35 (2020)). The recorded work is really Comput. Mater. Sci. 161, 300-308 (2019), doi:10.1016/j.commatsci.2019.02.006. Journal, volume, pages and year were all wrong; the entry and its BibTeX key are renamed choudhary_tavazza_2019 to match.

  • guo_dlpno_t1_2018 claimed doi:10.1063/1.5006075 (dead). The paper’s own PDF gives doi:10.1063/1.5011798. Its author list also named a nonexistent “Minchin, Yury” (really Minenkov, Yury) and omitted Cavallo, Luigi entirely.

  • krack_parrinello_allelectron_2000 claimed doi:10.1039/b001167k (dead); the correct suffix is b001167n.

As with KDIIS, each defect had propagated beyond the database: into ml_kpredictor.py, docs/license.md, periodic_jk_method.py, periodic_gapw_augment.py, a GAPW handover note, and the aiccm_comparison manuscript bibliography. All call sites are corrected.

[v0.15.33] - 2026-07-10 - Neese’s Cheetah

Fixed: KDIIS cited the wrong paper (2026-07-10)

scf_accelerator="kdiis" printed a references block that never named the paper the method comes from. The kdiis route cited Garza & Scuseria, Comparison of self-consistent field convergence acceleration techniques, J. Chem. Phys. 137, 054110 (2012) — a comparative study of ADIIS / LIST / EDIIS+DIIS whose text never mentions Kollmar or KDIIS. Code comments, the SCFAccelerator docstring, docs/user_guide/scf_convergence.md, docs/user_guide/solver_framework.md, docs/features.md and the KDIIS tutorial table additionally attributed the method to a nonexistent publication (“C. Kollmar, Int. J. Quantum Chem. 105, 685 (2005)”): Crossref lists exactly one Kollmar paper in that journal, and it is not that one.

  • The originating publication is now routed and printed: C. Kollmar, Int. J. Quantum Chem. 62, 617-637 (1997), doi:10.1002/(SICI)1097-461X(1997)62:6<617::AID-QUA5>3.0.CO;2-Z. This is the reference ORCA attributes !KDIIS to, and the one the ORCA developers cite for KDIIS in the TRAH-SCF paper.

  • Garza & Scuseria 2012 moved to the ediis_diis route, which is the hybrid that paper actually recommends. Its entry key lost the misleading kdiis infix (garza_scuseria_kdiis_2012garza_scuseria_2012); the BibTeX key in emitted .bibtex files changes accordingly.

  • Docs now state that vibe-qc adopts Kollmar’s error vector only and still diagonalizes the extrapolated Fock. Kollmar’s own scheme pairs the orbital-gradient DIIS with a first-order-perturbation orbital update and is diagonalization-free. The claim that KDIIS is “ORCA’s strict-convergence default” was also wrong: ORCA exposes it as the opt-in !KDIIS keyword.

  • New tests/test_scf_accelerator_citation_coverage.py pins every SCFAccelerator member to a route, every route to resolvable entries, and each accelerator’s originating reference to a Crossref-verified DOI, so this class of mis-attribution now fails CI instead of surviving review.

Changed: MSINDO COSMO reaction-field step routes to C++ (2026-07-10)

The per-density MSINDO COSMO reaction-field step now routes through one native C++ helper: V_elec = B^T Q, V_tot = V_elec + V_core, the COSMO apparent-surface-charge solve, the B q Fock scatter, and the polarization energy bookkeeping happen in one call. The Python sequence remains the reference/fallback path, and the outer COSMO SCF loop still runs through the existing MSINDO fock_extra hook.

Changed: MSINDO GEPOL cavity generation routes to C++ (2026-07-10)

The default MSINDO COSMO GEPOL/SAS cavity builder now routes the pentakisdodecahedron segment generation, neighbour culling, face refinement, segment areas, self terms, and segment-owner map through a native C++ helper. The original Python implementation remains as the reference/fallback path, and new regressions compare the native cavity against the forced-Python path before the capped GEPOL A matrix is built. COSMO SCF orchestration remains Python owned.

Changed: CPCM/COSMO A-matrix and surface-charge solves route to C++ (2026-07-10)

The generic CPCM dense A matrix, the capped GEPOL/MSINDO COSMO A matrix, the dielectric screening factor, and the apparent-surface-charge solve now route through native C++ helpers with NumPy/Python fallback paths retained. MSINDO COSMO therefore keeps only its COSMO SCF orchestration in Python at this layer, while its default GEPOL A assembly and repeated A q = -f V solves no longer run through Python numerical loops.

Changed: semiempirical run_job dispatch uses unified runner (2026-07-10)

Semiempirical single-point dispatch now lives in vibeqc.semiempirical.runner, with shared method sets and a common SemiempiricalResult adapter for DFTB, GFN2-xTB, PM6, OMx, MSINDO, COSMO, and CCM routes. vibeqc.runner keeps its private compatibility aliases, but its basis/optimizer/memory checks now use the same central method registry.

Fixed: vibe-view renders 2D slabs as sheets, not sheets inside a vacuum box (2026-07-10)

vibe-view now gates unit-cell drawing, atom replication, volume/orbital tiling and minimum-image bonding on the per-axis pbc flags rather than the scalar any(pbc). A dim=2 slab draws only the in-plane a,b parallelogram, tiles in-plane only, and frames on the atom bounding box; dim=1 draws a single chain-axis edge; dim=3 is unchanged. The synthesized third lattice column of a low-dimensional PeriodicSystem is never read as geometry — it is bookkeeping for the AO integrals and spglib, and the SCF energy is invariant to its length. Slab rendering is now provably independent of |a3| (regression-guarded at 30 vs 80 bohr). The Nz replication control is hidden for a slab and clamped server-side.

Fixed: XSF output declares its dimensionality (SLAB / POLYMER, not CRYSTAL) (2026-07-10)

write_xsf_structure and write_xsf_volume (and therefore write_xsf_mo / write_xsf_density, which delegate to it) hardcoded a CRYSTAL block keyword for every system. A dim=2 slab and a dim=1 polymer were written as if all three PRIMVEC rows were real lattice vectors.

They are not. For dim < 3 the lattice columns dim..2 are non-physical bookkeeping that keeps the AO integrals and spglib full-rank, and the SCF total energy is invariant to them. Critically, a synthesized a₃ is numerically indistinguishable from a genuine vacuum gap in a 3D cell, so a reader cannot recover the dimensionality by inspecting the matrix — the block keyword is the only signal, and vibe-qc was emitting the wrong one.

The keyword now follows system.dim (CRYSTAL / SLAB / POLYMER, per the XCrySDen XSF spec), which is what lets VESTA, XCrySDen and moltui draw a slab as a sheet rather than as a sheet adrift in an empty box. A regression test pins the keyword to dim alone: growing a slab’s padded a₃ from 30 to 80 bohr must not turn it into a crystal, and a tall 3D cell must not turn into a slab. dim=0 is rejected rather than written as a bare MOLECULE block, which XSF spells with an ATOMS block instead of PRIMVEC/PRIMCOORD.

Next up: the two experimental-CCM known-reds tracked in scripts/test_gate/known_reds_baseline.json (test_msindo_ccm_gradient.py dim=3+madelung=False NaN in the new C++ FD-gradient kernel), continued low-D CCM kernel work (docs/aiccm2026dev_a_lowd_greens.md), and the periodic-scf / regression-suite reds carried over from v0.15.31.

[v0.15.32] - 2026-07-10 - Neese’s Cheetah

Fixed: odd-electron periodic density grids use valid shifted-basis multiplicity (2026-07-10)

Periodic density and QVF grid output now build shifted AO image bases from the normalized unit-cell molecule multiplicity. This keeps default-multiplicity odd-electron cells, such as BIPOLE UHF/UKS H/Li test cells, from tripping the molecular parity check when non-home density blocks are sampled for output.

Added: IUPAC molecular naming engine (2026-07-10)

A standalone vibeqc_naming package produces IUPAC names from 3D coordinates and vice versa. 83 built-in structures (water through adenine), 57 mineral names, SMILES generation, and surface/adsorbate naming. run_job(name_molecule=True) prints the name to the .out file. See docs/user_guide/naming.md for documentation.

Fixed: low-dimensional periodic Coulomb gauge; dim<3 GDF no longer returns artifacts (2026-07-10)

For dim < 3 the periodic Coulomb channels did not share a gauge, and the total energy diverged with the lattice-sum cutoff (CLAUDE.md §7). V_ne/E_nn came from bare, conditionally-convergent lattice sums (_gauge_lat_opts_for_v_ne_and_e_nuc passed its input through when system.dim != 3) while J came from the G+q=0-dropped neutral cderi, so the conditional constant c never cancelled and the leftover -½cZ² grew without bound. Measured on polyethylene (dim=1, sto-3g, nrep=(4,1,1)): E_total ran -1226.78 -1368.41 -1518.55 Ha as nuclear_cutoff_bohr went 45 90 180.

Investigation found a second, more severe defect underneath: for dim < 3 that neutral J was not a Coulomb kernel at all. rsgdf_dense_g_mesh and rsgdf_g_mesh pin every non-periodic axis at G_perp = 0, so 4π/|G+q|² degenerates into a transverse-uniform sheet term 1/V — the electron-electron repulsion vanishes as the vacuum padding grows (|g|_max · V constant to 4 s.f. across D = 12/18/24 bohr; the mixed-boundary wire four-center is D-invariant). An H₂ chain’s dim=1 total ran -6.917 -6.568 Ha for D = 12 30 bohr where the wire kernel gives -1.32206067 Ha at every D. Merely matching the two gauges would have produced a cutoff-independent but meaningless energy.

The dim < 3 3-D-torus route therefore fails closed rather than returning a number:

  • _gauge_lat_opts_for_v_ne_and_e_nuc raises for dim < 3 instead of returning bare sums, so the multi-k GDF, the Γ-supercell direct-torus (run_ccm_rhf_direct) and RIJCOSX all inherit the guard from the one gauge-dispatch site.

  • rsgdf_dense_g_mesh / rsgdf_g_mesh refuse to return a transverse-collapsed mesh unless the caller passes allow_transverse_collapse=True for an explicit non-Coulomb use.

The gauge-consistent low-D Hamiltonians are reached by name, never by silent substitution: run_ccm_rhf_wire (dim=1 mixed-boundary) and the four-center route (bare 1/r minimum image, all channels in one gauge). These are different operators from the 3-D-torus route and from each other — they coincide only in the non-interacting limit — so neither is a drop-in replacement. dim = 2 has no mixed-boundary kernel at all (the slab needs the partial in-plane FT of docs/aiccm2026dev_a_lowd_greens.md §9).

Comparison-affecting. Every previously-reported dim < 3 GDF / neutral-RI absolute energy was a cutoff-and-vacuum artifact and must not be quoted; low-D benchmark rows through those routes need regeneration against the four-center route. dim = 3 is bit-unchanged (both guards are no-ops for 3-D cells). Gates: tests/test_ccm_lowd_gauge_consistency.py — dim=1 and dim=2 fail closed at every nuclear cutoff, the two source guards hold while dim=3 still returns Ewald-3D, and the sheet-kernel measurement (|g|·V vacuum-invariant vs the wire four-center being vacuum-invariant) is pinned directly.

Fixed: charged MSINDO native energy and gradients honor charge (2026-07-10)

The C++ molecular MSINDO RHF, UHF, and analytic-gradient bindings now accept an explicit charge keyword and subtract it before occupation assignment. run_job(method="msindo") now passes the requested charge through to the native RHF/UHF engines instead of silently running charged closed-shell molecules with neutral occupations. The public msindo_gradient_analytic wrapper now also routes charged closed-shell INDO gradients through C++; NDDO, odd-electron, and binding-unavailable cases remain on the existing fallback path. New regressions cover charged C++/Python energy parity, charged UHF parity, runner dispatch, and charged analytic-gradient parity.

Changed: direct public MSINDO energy routes to C++ (2026-07-10)

The public vibeqc.semiempirical.methods.msindo.run_msindo wrapper now dispatches supported INDO RHF, INDO UHF, and closed-shell NDDO single points to the native C++ kernels before entering the Python reference SCF implementation. The Python engine remains available as a fallback when the binding is missing or declines by non-convergence, and parity tests now force that fallback explicitly when checking C++ against the reference path. The semiempirical hot-path inventory now marks direct MSINDO and direct NDDO energy calls as public C++ routes; remaining COSMO SCF orchestration and post-SCF/root-tracking loops remain S2 follow-ups.

Changed: MSINDO COSMO B-matrix and vector operations route to C++ (2026-07-10)

The MSINDO COSMO provider now builds its distributed-multipole solute-cavity B matrix through a native C++ helper backed by the same MSINDO v2int and HARMTR kernels as the molecular engine. The per-SCF electronic charge vector, B^T Q surface potential, B q Fock scatter, and effective-core potential at the cavity points now route through native helpers as well. Python remains the fallback/reference route for binding-missing cases; subsequent native slices cover the surrounding GEPOL cavity, A-matrix assembly, and apparent-surface-charge solve, while COSMO SCF orchestration remains Python-owned. New regressions compare the native B matrix and vector operations against the forced Python reference, then monkeypatch the Python/provider boundary to prove public provider dispatch.

Changed: charged CCM energy and FD gradients route to C++ (2026-07-10)

The C++ CCM energy and finite-difference-gradient bindings now accept an explicit charge keyword, subtract it before the RHF occupation count, and therefore cover charged closed-shell H-Kr CCM cells. The lower-level public run_ccm, ccm_gradient_fd, and run_job(method="ccm") paths now pass charge through to C++ for native-scope jobs; heavier-element, binding-missing, and native-nonconverged cases retain the Python fallback. New regressions compare charged C++ energy/FD gradients against the forced Python reference and record the charge passed by run_job.

Changed: public CCM finite-difference gradients route to C++ (2026-07-10)

The public ccm_gradient_fd wrapper now dispatches native-scope CCM finite-difference gradients to the existing C++ kernel, including atoms= subsets. Heavier-element, binding-missing, and invalid-subset cases remain on the Python path. New regressions pin public dispatch to C++ and force the Python fallback for C++/Python parity.

Changed: public MSINDO CCM analytic gradients route to C++ (2026-07-10)

The public ccm_gradient_analytic wrapper now dispatches closed-shell CCM analytic gradients within the native H-Kr binding scope to the C++ kernel, including Madelung-enabled 1D, 2D, and 3D clusters. The Python implementation remains available for binding-missing and heavier-element fallback cases. New regressions separately pin public dispatch to C++ and force the Python fallback when checking C++/Python parity.

Changed: public MSINDO analytic gradients route to C++ (2026-07-10)

The public msindo_gradient_analytic wrapper now dispatches closed-shell INDO gradients to the existing C++ analytic-gradient binding. The Python implementation remains available for NDDO, odd-electron, and binding-unavailable cases. A new regression monkeypatches the Python pair-derivative loop to fail and verifies that the public H2O gradient still matches the native C++ kernel.

Changed: MSINDO open-shell run_job stays on the C++ UHF path (2026-07-10)

run_job(method="msindo") no longer falls back to the Python reference engine for supported open-shell d/heavy-element cases. The C++ UHF engine already covers the H-Xe s/p/d parameter scope, including the heavy trajectory-SCF fixtures, so the runner now sends all supported open-shell INDO jobs there. Open-shell NDDO remains explicitly gated as unsupported before dispatch. A new regression monkeypatches the Python run_msindo reference to raise and verifies that a MoCl doublet still reaches the C++ path and reproduces the oracle energy.

Added: semiempirical hot-path benchmark inventory (2026-07-10)

Added scripts/bench_semiempirical_hotpaths.py, a subprocess-isolated developer benchmark and C++ port inventory for the semiempirical roadmap. The script can list the current hot-path classification without importing the native stack, run a cheap smoke suite across DFTB0, SCC-DFTB, GFN2-xTB, PM6, OM2, and MSINDO, or run the broader inventory that marks remaining Python hot loops for future C++ consolidation. The MSINDO COSMO inventory row now marks the native B-matrix/vector-op route and the remaining Python COSMO SCF orchestration separately. Lightweight tests cover inventory listing and unknown-case rejection.

The semiempirical roadmap handover also records the article-task benchmark handoff: Thiel excited-state coverage is a future-driver item, MACE comparison scope is pinned to the v0.15 wrapper models (MACE-MP/MPA-0 and MACE-OFF23), and solid benchmark expansion should use EOS/Delta-factor-style volume scans with Birch-Murnaghan fits and stress consistency checks.

Added: canonical semiempirical roadmap and status cleanup (2026-07-10)

The semiempirical workstream now has a canonical tracker at handovers/HANDOVER_SEMIEMPIRICAL_ROADMAP.md, indexed from handovers/README.md. It records the owned roadmap for DFTB, GFN2-xTB, PM6/OMx, and MSINDO, including the new standing requirement that all performance-critical production paths either move their numerical hot loops to C++ or be explicitly labeled as cold/reference paths. The handover also carries the paper-writing contract: paper claims should use that file and the method-specific GFN2/MSINDO handovers as the current source of truth.

The user-facing semiempirical docs and several stale method/header comments were reconciled with current main: MSINDO is documented as H-Xe within its INDO scope, CCM docs no longer say H-Zn, the old “no MSINDO gradients/CCM” comparison text was retired, PM6/OM2/OM3 wording now matches their production-oriented molecular surface, OM1 remains experimental, and GFN2-xTB production gates now point at periodic AES, periodic molecular-limit parity, difficult periodic/polar fixtures, and external xtb parity rather than the superseded self-consistent-D4/mixer wording.

Fixed: 2D slabs get the rigorous vacuum-free Coulomb; bulk J/K builders fail closed (2026-07-10)

This fixes a correctness bug, not an aesthetic one. pick_jk_method had no dimensionality branch and resolved jk_method='auto' to GDF unconditionally for closed-shell SCF. GDF is a bulk (dim=3) J/K builder: its Γ drivers already raised for dim != 3, but the multi-k entries silently ran a dim=2 slab as a 3-D crystal of sheets stacked a3 apart (normal-axis mesh pinned to one k-point). The observable symptom on a graphene layer was a nuclear repulsion that depended on the k-meshE_nuclear = +321.39 Ha at k=(1,1,1) vs +557.97 Ha at k=(8,8,1) for identical geometry — and a total energy wrong by thousands of Hartree. A purely geometric Madelung sum cannot depend on the k-mesh; per CLAUDE.md § 7 that is a bug, and it is fixed at the gauge, not damped.

  • jk_method='auto' now routes dim=2 to CoulombMethod.SLAB_EWALD_2D — the rigorous, vacuum-free Parry / de Leeuw–Perram 2D-Ewald gauge (RHF / RKS / UKS, Γ and multi-k). The total is provably invariant to the bookkeeping a3 and E_nuclear is k-mesh-independent; both are pinned by regression tests (tests/test_slab_2d_routing.py).

  • New selector PeriodicJKMethod.SLAB_EWALD_2D (jk_method='slab_ewald_2d', aliases slab, ewald_2d). It is dim=2-only and rejects bulk cells.

  • Every bulk builder now fails closed on a slab. jk_method in {gdf, bipole, gpw, gapw, rijcosx, …} raises NotImplementedError for dim=2 with a pointer to the 2D gauge, and the four multi-k GDF entries (run_k{rhf,rks,uhf,uks}_periodic_gdf) carry their own dim=2 guard so a direct call cannot bypass the picker. UHF-on-slab raises pending its follow-on. dim=1 polymers are deliberately unaffected — there is no rigorous 1D Coulomb gauge yet (NEUTRALIZED_1D still raises), so the cached-Lpq GDF Hartree remains the supported wire route (5fe4d021); only the slab, which has a rigorous alternative to redirect to, fails closed.

  • Parry (1975) + de Leeuw & Perram (1979) now fire into the end-of-run references block / .bibtex / .references whenever a slab runs (uses_slab_ewald_2d).

Changed: dim=2 slabs are built from two in-plane vectors, not a vacuum gap (2026-07-10)

The old convention documented the dim=2 third lattice column as a load-bearing vacuum gap (“~30 bohr or more so image interactions are negligible”). That is a plane-wave idiom; a Gaussian slab needs no vacuum, exactly as CRYSTAL’s SLAB mode demonstrates. The vacuum is retired as a periodicity parameter.

  • New vibeqc.slab_2d(a1, a2, atoms, ...) builds a genuine dim=2 PeriodicSystem from two in-plane lattice vectors plus Cartesian atoms with real z. The internal a3 is auto-synthesized along the slab normal (vibeqc.synthesize_slab_a3) purely for AO-integral / spglib bookkeeping; the SCF is invariant to it.

  • vibeqc.build.slab(...) now returns a dim=2 system by default (atoms keep their natural z; no vacuum centring). Pass periodic_z=True for the legacy dim=3-with-vacuum cell (plane-wave-style references, 3D-with-vacuum comparisons). For dim=2 the vacuum= argument is retained only as viewer / grid-extent metadata on SlabInfo and is not used by the SCF.

  • cpp/include/vibeqc/periodic.hpp documents the new convention.

Migration. Code that relied on slab(...) returning dim=3 should pass periodic_z=True. Slab SCF runs that previously went to GDF now go to SLAB_EWALD_2D and will report different (correct, a3-invariant) energies. Slab analytic gradients remain a follow-on and still raise.

Docs: slab documentation and tutorials swept to the 2D convention (2026-07-10)

Every user-facing page that taught the retired “slab = dim=3 cell with a vacuum gap” convention is corrected, and the code examples that would now raise are fixed. Highlights:

  • user_guide/slabs_and_adsorbates.md rewritten around dim=2 and slab_2d, with a migration admonition and the force / smearing limitations stated up front. user_guide/periodic_methods.md § 7.1 no longer recommends the vacuum convention; the capability matrix, the method-selection chart, and the jk_method status table now show SLAB_EWALD_2D as the only dim=2 route and every bulk builder as raising there.

  • Fixed examples that would now raise: the graphene and MgO(001) slab snippets in periodic_methods.md had jk_method="gdf" on a dim=2 cell (the MgO one was annotated # 2D: no 3D Madelung problem, which is precisely the bug), and the slab guide ran UKS with jk_method="bipole". Force-driven Pt(111) examples now pass periodic_z=True, and were additionally repaired: they called vq.slab("Pt", "fcc111", ..., supercell=(2, 2, 1)), which had been broken independently of this change.

  • tutorial/slab_adsorbate_dft.md no longer claims that build.slab returns dim=3 “not dim=2”, nor that a dim=2 system “falls back to the isolated-cluster limit”. It keeps its dim=3 cell and its verified energies, and now explains that it does so because the 2D route cannot smear yet.

  • roadmap.md and features.md record the slab SCF as shipped, with the open items (analytic gradients, smearing, GDF/GPW 2D-truncated Coulomb) named.

  • ewald.md, periodic_systems.md, crystal_lattices.md, from_pyscf.md, periodic_hf.md, vibe_view.md, relaxed_scan.md, surface_embedding.md and periodic_methods_compared.md corrected. crystal_lattices.md also had a graphene cell passing fractional coordinates where Cartesian bohr are expected; relaxed_scan.md referenced a SlabInfo.system attribute that does not exist. Both fixed.

Fixed: Gamma pair-FT mirror corrupted the multi-k GDF exchange on momentum-shifted meshes (2026-07-10)

The v0.15.x dense-core tail speedup (“auto-resolve dense-core rsgdf tails”) taught the C++ AO-pair FT (ao_pair_fourier_transform_bloch) to compute only the upper shell-pair triangle at k == 0 (inversion-symmetric cell list) and mirror it, using FT_mu,nu(G) == FT_nu,mu(G). That identity needs a third condition the guard did not check: every evaluation frequency must be a reciprocal-lattice vector (exp(i G.R) == 1), which holds on the plain Gamma tail mesh it was written for but not on the momentum-shifted meshes G+q of the Bloch-pair GDF fit. build_lpq_bloch_native_fft reaches the kernel with k_cart = k_ket = 0 and a q = -k_bra shifted mesh for every (k_bra != Gamma, k_ket = Gamma) multi-k exchange pair, so the mirror silently corrupted those fits wherever inter-cell (R != 0) AO-pair terms are non-negligible — ultra-diffuse bases on tight cells. Measured on rocksalt LiH/STO-3G at kmesh (2,1,1): the fitted four-center of the corrupted (X, Gamma) channel was off by 1.9 (max element), moving run_krhf_periodic_gdf by +2.04e-2 Ha/cell and breaking the direct-vs-GDF CCM parity gates (the 2026-07-10 “three-way HF disagreement” of handovers/HANDOVER_AICCM_DIRECT_TORUS.md Finding 2 — attributed there to environment drift; actually this core defect, invisible on every vacuum-padded gate because their R != 0 terms are exponentially small). The guard now additionally verifies exp(i G.R) == 1 over the evaluation frequencies (early-exit, so shifted-mesh calls pay one row of dot products); the Gamma-tail mirror keeps its 2x saving. Restored: tests/test_ccm_direct.py::test_direct_matches_gdf_lih_multicell_fold (direct-vs-GDF back to 4e-14-class parity) and ::test_supercell_cderi_lih_documented_residual; new kernel pin tests/test_rsgdf_dense_mesh_tail.py::test_gamma_mirror_disabled_on_momentum_shifted_mesh.

Changed: multi-k DIIS and KDIIS now run on the canonical C++ kernel (2026-07-10)

python/vibeqc/periodic_scf_accelerators.py carried a second, numpy-native Pulay DIIS: _MultiKPulayDIIS, _MultiKKDIIS and _MultiKKDIISOpenShell each rebuilt the B-matrix in a Python triple loop over history² × n_k vdot calls, re-derived the Pulay solve, and (for the adaptive-depth policies) re-flattened the whole error history on every SCF iteration. That duplicated the DIIS class the molecular drivers use, in violation of the single-canonical-implementation rule.

All three are now thin adapters over vibeqc::DIIS, reached through a new DIIS::extrapolate_blocks(fock_blocks, error_blocks) entry point. The existing √w_k block bridge (per_k_to_stacked_real_blocks) maps each per-k Hermitian matrix onto the real block pair √w_k·Re M(k), √w_k·Im M(k), under which the kernel’s per-block Frobenius sum is the k-weighted Pulay inner product Σ_k w_k Re Tr[e_i(k)† e_j(k)]. Nothing in the kernel is multi-k-aware; it is the same extrapolation on a longer vector. Open-shell spin coupling falls out for free by appending the β blocks to the same lists. The multi-k KDIIS paths inherit the Chupin adaptive-depth policies they never had.

Three changes in cpp/src/diis.cpp benefit every SCF driver, molecular and periodic:

  • The DIIS B-matrix is now cached incrementally as a Gram matrix. A push costs n fresh dot products rather than the a full rebuild needs, and the residual norms the adaptive-depth policies test are read off the Gram diagonal instead of re-traversing the stored error vectors.

  • The history takes its iterates by value and moves them into place, so extrapolate_blocks no longer copies the flattened Fock and error a second time on the way in. At nbf = 120 over 16 k-points each of those is ~3.7 MB per SCF cycle.

  • The Pulay solve gained the coefficient blow-up guard that previously existed only in the numpy copy: a non-finite solution, or coefficients above 1e6, means the history has gone linearly dependent, so the oldest iterate is dropped and the system re-solved. A two-iterate window that still fails falls back to the raw Fock (a plain SCF step). Healthy extrapolations are bit-for-bit unchanged; only histories that previously produced garbage coefficients behave differently.

The extrapolation itself measures 1.4x (nbf=120, 16 k-points) to 2.0x (nbf=40, 8 k-points) faster than the numpy implementation it replaces. The accelerator is a small fraction of a periodic SCF cycle next to the Fock build, so the point of this change is the single canonical implementation, not the wall clock. tests/test_diis_block_kernel.py pins the new kernel against the retired numpy B-matrix construction (agreement to 1e-13 over a converging 10-iterate history) and pins the multi-k depth sequences of both adaptive policies against the scalar C++ path.

Fixed: C++ periodic drivers silently ignored level_shift (2026-07-09)

PeriodicRHFOptions / PeriodicSCFOptions / PeriodicKSOptions exposed level_shift, level_shift_warmup_cycles and level_shift_schedule, and the docs stated the controls were available on every periodic driver, but the three C++ direct-truncated periodic drivers never read them: the option structs accepted the values and no code path ever consumed them. A silent no-op, the exact failure mode the SPINLOCK engine’s fail-closed check_spinlock_support gate exists to prevent.

The Saunders-Hillier shift is now applied in all three: run_rhf_periodic_gamma (Γ, real: F + b·S (b/2)·S·D·S) and the multi-k run_rhf_periodic / run_rks_periodic (per k-point, Hermitian: F(k) + b·S(k) (b/2)·S(k)·P(k)·S(k)), resolved through the same shared level_shift_at_iter helper the molecular drivers use, so a given (level_shift, warmup, schedule) triple now behaves identically on every backend. The S·D·S products and the P(k) Bloch sum are skipped entirely on any iteration whose resolved shift is 0.0, so a released shift costs nothing. Regression-guarded in tests/test_periodic_level_shift.py, which pins both halves of the contract per driver: the shift is honoured (with DIIS and dynamic damping off, a fixed-length trace diverges from the unshifted one) and inert (it never moves the converged fixed point).

The probe cell is H2/6-31G, not H2/STO-3G. A minimal-basis H2 cell carries one basis function per atom, so both MOs are fixed by symmetry alone: a level shift moves their eigenvalues but cannot rotate the eigenvectors, and the SCF path is provably identical whether the shift is applied or ignored. Such a cell cannot distinguish a working shift from a dropped one, which is how the no-op went unnoticed. Any future level-shift probe needs a system with a genuine occupied-virtual mixing angle.

Fixed: SPINLOCK PATTERN_HOLD accelerator suspension swept to all drivers (2026-07-09)

Completes the sweep flagged by the multi-k UKS fix below. All sibling PATTERN_HOLD implementations shared the extrapolate-across-the-hold design: the SCF accelerator kept extrapolating (and recording history) through the hold window, which on weak-exchange functionals collapses an atomic_spins broken-symmetry seed by continuous orbital rotation, a failure the occupation-selecting MOM hold cannot see. Each driver now suspends its accelerator (plain DIIS and the EDIIS / ADIIS / KDIIS variants alike: PeriodicSCFAccelerator on the Γ-Ewald and GDF paths, MultiKPeriodicUHFAccelerator on BIPOLE, the C++ accelerator family on molecular) while the hold is active: no held-window iterates enter the history, density damping / FMIXING stay live, and the extrapolation starts fresh at release. Drivers whose converged final pass re-diagonalises the Fock (Γ-Ewald UHF/UKS, molecular C++ UHF/UKS) additionally re-select the occupied set by max overlap with the held pattern when convergence happens inside the hold window, so the reported energy / <S^2> / MOs describe the state the SCF converged to rather than an aufbau re-slice (GDF and BIPOLE return the in-loop, hold-consistent MOs and need no such pass). The existing spinlock no-op pins (UHF: the broken state is the aufbau ground state, so hold-vs-unlocked converge identically) stay green; new UKS hold pins on the uniformly stretched 10-bohr H chain cover the Γ-Ewald UKS, GDF UKS, BIPOLE UKS, and molecular UKS drivers in tests/test_guess.py, each asserting the accelerator subspace stays empty through the hold window and <S^2> holds the strongly-broken state.

Next up: continued desktop-packaging hardening (Windows/Linux build hosts, code-signing for macOS auto-update), a k-point-symmetry-reduced or streaming Lpq route for the multi-k hybrid-KS GDF driver (tracked as tests/test_pw1pw.py in scripts/test_gate/known_reds_baseline.json), and root-causing the remaining periodic-scf / regression-suite reds in the same baseline file.

[v0.15.31] - 2026-07-10 - Neese’s Cheetah

Fixed: SPINLOCK PATTERN_HOLD actually holds on the multi-k UKS Ewald driver (2026-07-09)

DIIS extrapolation used to run across the hold window; on UKS (weak exchange), extrapolating the Fock toward the spin-symmetric attractor collapses an atomic_spins broken-symmetry seed by continuous orbital rotation within ~3 cycles – a failure mode the occupation-selecting MOM hold cannot see (no level crossing ever happens). The driver now suspends DIIS while the hold is active (density damping stays live; no held-window iterates enter the history) and starts the extrapolation fresh at release. Additionally, the converged final pass now re-selects the occupied set by max overlap with the held pattern when the SCF converges inside the hold window; the previous unconditional aufbau slice could report an energy / <S^2> belonging to a different state than the converged density. The slow-lane pin tests/test_guess.py::test_periodic_spinlock_pattern_hold_multik_uks moves to a 10-bohr box (uniformly stretched 5-bohr H chain): the original 8-bohr box placed the periodic image at 3 bohr, inside PBE’s Coulson-Fischer point, where no strongly-polarized stationary state exists for any hold length (verified: a DIIS-free damped SCF from the seed also decays to the weak-BS <S^2> = 0.08 fixed point there). With the fix and the corrected geometry the held run converges to <S^2> = 0.946 in 13 cycles. The Γ-Ewald UHF/UKS, GDF and BIPOLE PATTERN_HOLD paths (and the molecular C++ drivers) shared the extrapolate-across-the-hold design; that sweep landed alongside this fix (see the entry above).

Fixed: Γ-path hybrid KS exchange convention + multi-k dim<3 pure-KS Hartree (2026-07-09)

The two GDF-side KS residuals left open after the same-day periodic-KS XC-convention fix (b3f74aa9; handovers/HANDOVER_AICCM_DIRECT_TORUS.md Finding §4, re-measured post-fix by the aiccm-real-gamma chat):

  • Γ-path HYBRID KS sat +8.32e-2 Ha above the real-Γ direct route on rocksalt LiH/STO-3G (1,1,1) PBE0 — neither the strict-zero-mode offset (0.297 Ha = a_x·ξ_N·N_e/2 on this fixture) nor the ~5 mHa ultra-diffuse-Li fitting floor. Root cause: the Γ KS fast path in run_krhf_periodic_gdf delegated to the legacy molecular-limit gamma driver, whose full-range real-space exchange carries no exxdiv convention at all (a truncated-lattice-sum gauge between strict-zero and ewald). Fix: run_pbc_gdf_rhf gained the closed-shell KS branch — new run_pbc_gdf_rks — with the a_x-scaled GDF exchange under the exxdiv='ewald' Madelung shift (the identical convention to apply_exxdiv_ewald_to_K on the multi-k path and the direct route’s ξ_N·S·D·S seam) and the b3f74aa9 grid/density pairing for XC; the Γ fast path now delegates closed-shell KS (pure and hybrid) there under the same preconditions as HF. Range-separated hybrids now fail closed on this path (the silently-global-hybrid legacy treatment is unreachable); rsgdf_tail_ke_cutoff now WORKS for Γ KS (previously refused loudly because the legacy fallback had no high-|G| tail correction — test_run_periodic_job_kpoints_gamma_rks_tail_cutoff_* flipped from fails-closed to supported); the legacy gamma driver remains the fallback for charged / dim<3 / smearing / convergence-aid cases. run_ccm_rks_gdf(lih, "pbe0") lands at the external PySCF 2.13.1 KRKS PBE0 anchor (−8.291224931283, exxdiv='ewald') within the documented shared-fitting floor.

  • MULTI-K dim<3 pure KS was unchanged by b3f74aa9: run_krks_periodic_gdf on the dim=1 H₂-chain fixture (6-bohr cell, 15-bohr transverse vacuum, STO-3G, (3,1,1), PBE) reported −3.0897 Ha/cell while its own stored density evaluates under the piecewise-validated functional to −3.7814 and the real-Γ direct route’s variational minimum on the same torus is −3.8947. Root cause: pure functionals (α = 0) kept the EWALD_3D J branch, whose analytic-FT kernel is 3D-only — ewald_3d_j_blocks silently degrades to the diagnostic FFT-Poisson grid backend on dim<3, mis-summing the vacuum-padded Coulomb, so the SCF converges under a wrong Hartree operator. Fix: pure multi-k KS on dim<3 routes to the cached-Lpq GDF Hartree exactly like HF/hybrids always did (dim=3 pure KS keeps the validated EWALD_3D path). Measured direct-vs-GDF delta on the fixture: +8.05e-1 → +2.2e-15 Ha. Open-shell multi-k never had the EWALD_3D branch (closed-shell-only defect).

Measured post-fix parities on the fixtures: hybrid Γ +7.0e-13, pure Γ +5.6e-13 (a side effect of the rerouting — the pure-Γ parity tightens from the −1.04e-4 legacy-path quadrature floor because both sides now run the identical XC machinery), multi-k dim<3 +2.2e-15 Ha. Both canaries in tests/test_ccm_rks_direct.py are flipped into direct-vs-GDF KS parity gates (test_rks_direct_matches_gdf_{pure_gamma,hybrid_gamma,multik_lowd}_post_fix, all at the 1e-8 HF-gate class), joined by the external PySCF PBE0 Γ anchor

  • the pinned dim=1 chain value in tests/test_krks_gdf_xc_density.py. PBCGDFResult gained e_xc + functional fields; run_pbc_gdf_rks is exported at package level.

Fixed: multi-k density grids fold real; .xsf density output restored (2026-07-09)

The periodic density-output fold trusted D(-k) = D(k)* for explicit ±k pairs, but the per-k lattice-truncation asymmetry of the Fock build leaves a ~1e-6 conjugate-pairing residue, so full Monkhorst-Pack meshes (e.g. c-diamond RHF/STO-3G kpoints=(2,2,2)) aborted the density writers with “periodic density block g=(0,0,0) has a non-negligible imaginary component” and the .xsf / .density.xsf artifacts went silently missing ([[outputs.files]] written=false). The fold now emits the explicit time-reversal pair w/2·e^{-ik·R}D(k) + w/2·e^{+ik·R}D(k)* for every k point, making each real-space block real to machine precision by construction (equal to the real part of the pristine fold). Pins: tests/test_periodic_output_complex_density.py::test_per_k_density_fold_symmetrizes_inexact_explicit_pair and ::test_per_k_density_fold_symmetrizes_self_inverse_kpoint.

Fixed: .qvf provenance records the true periodic dimensionality (2026-07-09)

The QVF manifest’s provenance block read only a dimensionality attribute, but the bound PeriodicSystem exposes dim, so every real periodic run recorded "dimensionality": 0. The writer now mirrors the structure section’s lookup (dim, then dimensionality, then 3 for periodic / 0 for molecular). Pins: tests/test_qvf_writer.py::TestProvenance::test_provenance_dimensionality_from_periodic_system.

Fixed: periodic .system manifests carry the assembled citations (2026-07-09)

run_periodic_job assembled its citation list and wrote .bibtex / .references / the in-.out block, but never fed the rows to the manifest, so every periodic .system ended with [citations] count = 0. The runner now routes the full assembled list (print=true and print=false rows alike) through the shared vibeqc.output.citations.citation_manifest_rows helper into ManifestUpdater.set_citations, mirroring the molecular runner. Pins: tests/test_feature_citation_end_to_end.py::test_periodic_system_manifest_carries_assembled_citations.

Fixed: BIPOLE RKS honours bz_integration="gilat" through run_periodic_job (2026-07-09)

run_periodic_job(..., method="RKS", jk_method="bipole", bz_integration="gilat") recorded bz_integration = gilat in the .out but silently ran the plain SCF: the runner validated the request (Gilat is documented BIPOLE-RKS-only and rejected on RHF/UHF/UKS) yet never forwarded the keyword to run_pbc_bipole_rks, the one BIPOLE driver that implements it. The RKS call now forwards bz_integration.

Fixed: atomic_spins broken-symmetry seed works with the PATOM default guess (2026-07-09)

When the default molecular initial guess became PATOM, setting atomic_spins (the ATOMSPIN broken-symmetry seed) started raising GuessEngine: atomic_spins requires the SAD guess; resolved guess is PATOM — a regression, since the previous AUTO default resolved to SAD for open-shell systems. The GuessEngine now silently promotes PATOM -> SAD when a seed is present (PATOM is itself SAD-derived — SAD plus one in-field re-polarisation step — so dropping that step is immaterial to a broken-symmetry start; the seed is what the caller asked for). The provenance line records PATOM -> SAD (per-atom spin-seeded; ATOMSPIN). A genuinely incompatible explicit guess (HCORE / SAP / HUECKEL / MINAO / READ) with a seed still errors, now with a hint to set initial_guess=SAD. Fixes the UKS second-order + Newton-trace test regressions on main.

Fixed: periodic EDIIS_DIIS 8-cycle stall — intensive switch metric + EDIIS/ADIIS anti-replay guard (2026-07-09)

run_krhf_periodic_gdf on c-diamond (fcc primitive, STO-3G, kmesh (2,2,2)) froze for 8 iterations with dE bit-exactly zero before converging — two stacked defects exposed when EDIIS_DIIS became the periodic default (cb712e21):

  • Periodic EDIIS_DIIS/ADIIS_DIIS switch sites compared the raw size-extensive commutator norm against the intensive ediis_diis_switch_threshold (0.19 > 0.1 held the run in the EDIIS regime; the intensive metric 0.019 < 0.1 says DIIS). The molecular drivers were fixed in ddd8d325; the same ediis_diis_switch_metric RMS normalization now applies to every periodic dispatch site: the C++ Γ/multi-k Ewald drivers (periodic_rhf.cpp, periodic_scf.cpp) and the Python Γ + multi-k accelerator classes (periodic_scf_accelerators.py, k-weighted RMS which reduces to the molecular norm at Γ; max-over-spins with n_blocks=2 for the open-shell classes, mirroring uhf.cpp).

  • The EDIIS/ADIIS kernels could deadlock on a pinned simplex vertex: with the initial iterate’s energy below the first Roothaan images, the QP returns the initial Fock verbatim with zero weight on the newest iterate; the deterministic SCF replays the same diagonalisation, pushes a duplicate iterate, and freezes until the FIFO evicts the pinned entry max_subspace - 1 cycles later. The kernels now drop a fully-exploited pinned vertex (its Roothaan image is already in the history) and re-solve — the same eviction the FIFO would do later, minus the dead cycles. Never triggers while the extrapolation carries new information, so converging trajectories are bit-unchanged.

Converged energies are unaffected (the accelerator changes the path, not the fixed point). Regression coverage: EDIIS/ADIIS anti-replay kernel tests (test_ediis.py), multi-k intensive-switch test (test_periodic_multi_k_accelerator.py), and the c-diamond KRHF-GDF stall reproducer (test_bug_repros.py).

Changed: qvf_version: 2 withdrawn — reaction.path gains an optional lattice member under v1 (2026-07-10)

Governance ruling (maintainer-ratified; recorded in qvf-writer/GOVERNANCE.md § Version history and qvf-writer/registry.json).

vibe-qc v0.10.0–v0.15.x stamped qvf_version: 2 on archives containing a periodic reaction.path. The entire schema-visible delta was a single optional member (lattice); the companion dim field is not schema-visible at all. Under the QVF versioning policy an optional member is the additive case and must not bump qvf_version. The bump also created a real interop break: the spec requires a v1-only consumer to refuse a higher major version, so conforming third-party consumers were obliged to reject archives the reference producer routinely wrote — and it would have spent the format’s first-ever major bump on a backward-compatible field.

  • Schema: reaction.path now accepts an optional lattice member under qvf_version: 1 — float64 [3, 3] (fixed cell) or [n_frames, 3, 3] (variable cell), columns a/b/c in bohr. Its presence marks the path periodic; a path without it is unambiguously molecular. New $defs.ReactionPathLattice. Canonical schema + the byte-identical qvf-writer/ and pip-package copies.

  • Reference writers: both add_reaction_path() implementations (Python and C++) take an optional lattice, with shape/dtype/frame-count guardrails.

  • Docs: spec § 3.1 (historical note), § 5.5, and the kind table; docs/design_qvf_format.md § 4.5 + § 5.1.

  • Compatibility: producers MUST NOT emit qvf_version: 2; consumers SHOULD accept it and treat it as 1 (archives exist in the wild). qvf_manifest_v2.schema.json is retained as a frozen artifact resolving its $id.

Follow-up (vibe-qc producer side, sequenced after the in-flight v2 schema de-fork lands): stop stamping qvf_version: 2 in write_qvf, teach validate_qvf to accept 2 as a deprecated alias for 1, and retarget the tests that currently assert v2 emission.

Added: open governance model + registry for QVF (2026-07-09)

Establishes QVF as an open standard — so adopters (ORCA, PySCF, …) can trust it as a multi-vendor interchange format — while keeping vibe-qc’s origin explicit. qvf-writer/GOVERNANCE.md documents the stewardship model (created and stewarded by vibe-qc; producer-neutral by design; Apache-2.0 reference impls), the versioning policy, the change process, the vendor→canonical promotion process, and a path to broaden stewardship as independent producers adopt it. qvf-writer/registry.json is the machine-readable registry of canonical section kinds (drift-guarded against the schema, test_registry.py) and registered vendor namespaces (x_vibeqc, x_orca). Surfaced in the spec (§ 10) and on the website (docs/qvf/governance.md).

Added: pip-installable packaging for the qvf-writer Python reference (2026-07-09)

qvf-writer/python/ is now a release-ready pip package (pyproject.toml): pip install . gives the qvf_writer / qvf_reader modules (deps: numpy), a qvf-validate console script, and a bundled JSON Schema so an installed wheel does full validation; pip install ".[validate]" adds jsonschema. Verified by a clean-venv install (import + write + validate + CLI). The bundled schema copy is drift-guarded against the canonical spec (test_packaging_schema.py; make_archive.sh refreshes it). Not published to PyPI — this only makes the package release-ready for a maintainer go/no-go.

Fixed: NEVPT2 options reach the ASE wavefunction optimizer (2026-07-09)

run_job(..., method="casscf", optimize=True, optimizer_backend="ase") no longer crashes with NameError: name 'nevpt2_options' is not defined when the ASE wavefunction calculator samples a CASSCF/CASCI optimization step. The ASE geometry-optimization helper now binds and forwards nevpt2_options alongside the other wavefunction option structs, matching the native optimizer path. Regression coverage pins both the default reproducer and explicit NEVPT2Options(compute_corr_grad=True) forwarding. The former known-red tests/test_runner_optimize_wavefunction_options.py is removed from the gate baseline after a four-row OpenMolcas-backed validation matrix.

Added: reference C++ reader for the qvf-writer library (2026-07-09)

Completes the round-trip: qvf-writer/cpp/include/qvf/qvf_reader.hpp (qvf::QvfReader) opens a .qvf, parses manifest.json, verifies each member’s sha256, and decodes JSON + binary members. Still zero third-party code — adds an original INFLATE decompressor (qvf_inflate.hpp, all three block types: stored / fixed / dynamic Huffman), a ZIP reader (qvf_unzip.hpp), and a JSON parser (qvf::Json::parse + read accessors). Reads any producer’s output, verified by reading the Python-written golden corpus (dynamic-Huffman DEFLATE) from C++. Included in the single-header amalgamation, so qvf_single.hpp is now a complete writer and reader in one file.

Added: QVF conformance suite + golden corpus (2026-07-09)

A language-agnostic way to certify a QVF implementation, under qvf-writer/conformance/: nine frozen reference archives (corpus/*.qvf) each paired with a *.expected.json describing the values a conforming consumer must decode (structure, the wavefunction normalization contract, spectra incl. spectra.epr, volumes, bands, analysis, vendor extensions, root metadata). The runner run_conformance.py validates each archive (schema + sha256 + semantic checks), confirms its section kinds, and decodes members against the expected values; the expected.json check format (manifest / JSON / binary pointers) is reimplementable in any language, so ORCA, PySCF, or any consumer can self-certify. The reference writer + reader are certified both directions in CI (test_conformance.py: the committed corpus and a freshly regenerated one).

Fixed: BIPOLE RHF runner accepts BZ-integration metadata (2026-07-09)

run_periodic_job(..., method="RHF", jk_method="bipole") no longer fails before SCF when the runner forwards its resolved bz_integration keyword to the closed-shell BIPOLE driver. The RHF driver now accepts None and the neutral "smearing" metadata value while keeping parameter-free Gilat integration refused for RHF; Gilat remains a BIPOLE RKS route. This removes the paired known-reds for tests/test_output_population.py and tests/test_periodic_convergence_auto_runner.py.

Added: CMake install/export for the qvf-writer C++ library (2026-07-09)

The C++ QVF writer now installs a CMake package, so external projects can consume it with find_package(qvf CONFIG REQUIRED) + target_link_libraries(app PRIVATE qvf::qvf), or pull it from source via FetchContent. Ships qvfConfig.cmake / qvfConfigVersion.cmake / exported qvfTargets, the public headers, and the single-header amalgamation. Verified end to end by installing to a prefix and building a separate consumer project against it.

Added: single-header amalgamation for the qvf-writer C++ library (2026-07-09)

The zero-dependency C++ QVF writer is now also available as one drop-in header, qvf-writer/cpp/qvf_single.hpp (STB-style): define QVF_IMPLEMENTATION in one translation unit and #include it — no CMake, no separate compile, no include-path setup. Generated from the cpp/ sources by qvf-writer/scripts/amalgamate.py (regenerated by make_archive.sh; a drift test, test_single_header.py, keeps the committed copy current). A write_single_header example compiles against only the amalgamated header (not the library) to prove it is fully self-contained.

Fixed: DLPNO-MP2 local density fitting now builds direct local 3c integrals (2026-07-09)

DLPNOMP2Options(local_df=True) no longer slices a precomputed global raw three-centre DF tensor. The closed-shell DLPNO-MP2 driver now constructs a restricted auxiliary sub-basis for each unique fit atom domain and evaluates the 2c/3c integral tensors directly on that sub-basis, while the default local_df=False path still uses the original global RI half-transform. The public run_job(method="dlpno-mp2", dlpno_options=DLPNOMP2Options(local_df=True, ...)) route also avoids constructing the full global DensityFitting object and passes only auxiliary-basis metadata into the direct local path. Pins: tests/test_dlpno_local_df.py::TestLocalDFExactness::test_local_df_builds_integrals_directly and tests/test_runner_dlpno_mp2.py::TestRunJobDLPNOMP2::test_local_df_run_job_avoids_global_density_fitting.

Fixed: multi-k KUKS XC now uses the per-spin real-space fold (2026-07-09)

Closes the open half of the 2026-07-09 periodic-KS XC-convention work (the “do not quote multi-k KUKS numbers on tight cells” limitation, and HANDOVER_AICCM_DIRECT_TORUS.md Finding §4’s open-shell sibling): run_kuhf_periodic_gdf’s KS branch fed build_xc_periodic_uks the BZ-averaged density in the home-cell block only — the C++ kernel’s molecular-limit mode, one k-independent V_xc(Γ) added to every k; exact only in the vacuum limit, wrong by the image density on tight cells (+3.2e-4 Ha on the dim=3 H₂ chain (3,1,1) PBE fixture; the same class as the fixed Γ finding). The branch now mirrors the closed-shell multi-k path: per-spin inverse-Bloch fold of the per-k densities to the real-space finite-torus set (_build_xc_k_from_density_uks), spin-polarised periodic XC on the pair, per-k Bloch-folded V_a/V_b.

Why the first fold attempt failed by −2.4 Ha (the recorded blocker): KS runs through run_kuhf_periodic_gdf defaulted options to PeriodicRHFOptions, which carries no use_periodic_becke — so KUKS silently built the molecular Becke grid while the run_krks_periodic_gdf wrapper defaulted to PeriodicKSOptions (periodic-Becke grid), and a cross-cell density fold on the molecular grid over-counts by the replicated image density (the b3f74aa9 grid/density pairing, in reverse). KS runs now default to PeriodicKSOptions like the closed-shell wrapper; UHF keeps PeriodicRHFOptions.

Gates (tests/test_kuks_gdf_xc_fold.py): (a) the algebraic spin identity of the fold helpers at complex k (no SCF, 1e-10); (b) the KUKS(mult=1)==KRKS SCF identity on the chain at (3,1,1) PBE at the like-for-like J backend, gated 1e-12 (measured 4.4e-16; the default closed-shell pure-DFT route keeps the Ewald-3D real-space J while the open-shell driver always contracts the cached GDF Lpq, a legitimate −4.4e-5 Ha fitting-scale backend difference); (c) external out-of-process PySCF 2.13.1 KUKS(GDF) PBE anchors — fully-spin-polarised H₂ chain (3,1,1) with mf.nelec=(6,0) matching vibe-qc’s per-cell mult=3 (measured −1.26e-4 Ha, the XC-quadrature-scheme floor; the xc-free KUHF twin agrees to 1.1e-9 Ha, so J/K/exxdiv are exact) and rocksalt LiH (2,1,1) mult=1 (+2.1 mHa, the documented ultra-diffuse-Li shared-fitting floor) with the KUKS==KRKS identity pinned on the same tight ionic cell (2.3e-14). NB: PySCF’s cell.spin distributes the spin excess over the whole BvK supercell (nelec (4,2) at (3,1,1)), a different state 0.40 Ha below the per-cell-polarised one — open-shell multi-k anchors must pin mf.nelec per cell, which is why no direct KUKS anchor existed before. Multi-k KUKS numbers on tight cells produced before this fix should be re-run; the loud in-code limitation markers are removed.

Changed: Γ open-shell GDF drivers default to gdf_method=’rsgdf’ + KS options (2026-07-09)

Completes the Γ-only compcell UHF/UKS follow-up recorded in the sanity-guard entry below. The Γ-only compcell Hartree is vacuum-box-only by construction (its cderi is q-only and cannot resolve overlapping AO-pair images on tight ionic cells; the “fix” for those cells is rsgdf), so rather than papering over the +1172.63 Ha class with convergence aids (CLAUDE.md §7), run_pbc_gdf_uhf / run_pbc_gdf_uks now default to gdf_method='rsgdf' — the production method the runner already dispatched — and a KS run with options=None defaults to PeriodicKSOptions, so the b3f74aa9 periodic-Becke-grid/torus-density XC pairing engages by default (closing the recorded “sub-Ha class” note: plain PeriodicRHFOptions evaluated XC in the v0.8.x molecular-grid convention, −7.9638 Ha on the LiH fixture). Measured on the previously-broken rocksalt LiH/STO-3G Γ fixture with pure defaults: UKS-PBE −8.233910336 (identical to the independent real-Γ direct route −8.233910335588; 5.0 mHa from external PySCF KRKS(GDF) PBE, the documented diffuse-Li fitting floor) and UHF −8.330292528 (4.9 mHa class). gdf_method='compcell' stays selectable for development and keeps the tight-ionic warning + energy-sanity guard; run_pbc_gdf_rhf keeps its pinned compcell warn-and-tag default. The compcell guard pins are also made robust to a machine-dependent detail: whether the broken-Hartree SCF converges to its garbage fixed point within max_iter varies with BLAS/threading, so the tests now assert the §7 contract that holds either way (converged → raise; non-converged → warn + +SANITY_FAILED tag; never a silently plausible result). Pins: tests/test_pbc_gdf_compcell.py::test_pbc_gdf_uks_lih_ionic_default_rsgdf_is_sane (+ the guard tests now pass gdf_method='compcell' explicitly). Docs: docs/user_guide/periodic_methods.md § 3.4.

Added: KS-DFT on the real-Γ (direct-torus) CCM route (2026-07-09)

run_ccm_rks_direct (python/vibeqc/periodic/ccm/direct.py): closed-shell KS-DFT — pure LDA/GGA/meta-GGA and global hybrids — on the k-free real Γ-supercell route, closing the run_ccm_scf(route="real-gamma", functional=...) NotImplementedError gap. XC is built periodically by the same fold construction as the one-electron matrices (supercell density → cell-averaged unit-cell lattice blocks → the production build_xc_periodic kernel on the periodic-Becke grid → V_xc(g) folded onto the torus); hybrids apply the exchange-q=0 seam ξ_N·S·D·S on the exact-exchange channel scaled by the functional’s a_x, the identical exxdiv="ewald" convention as the multi-k driver, with the exxdiv=None control sitting above by exactly a_x·ξ_N·N_e/2 per supercell (pinned). Range-separated and double hybrids fail closed. Gates (tests/test_ccm_rks_direct.py): vacuum limit vs molecular run_rks per XC rung, external out-of-process PySCF KRKS anchors on ionic LiH (PBE −8.238879724246, PBE0 −8.291224931283; agreement at the documented ~5 mHa ultra-diffuse-Li shared-fitting floor, XC-attributable ≤0.5 mHa), the seam identity, and — after the 2026-07-09 periodic-KS XC-convention fix (b3f74aa9) plus the same-day Γ-hybrid + multi-k-dim<3 fixes (entry above) restored the KRKS reference — the three direct-vs-GDF KS parity gates that replaced the discrepancy canaries.

Found: production KRKS XC-convention inconsistency (2026-07-09; fixed same day)

While gating the above: run_krks_periodic_gdf disagreed with external PySCF KRKS by 308 mHa on rocksalt LiH/STO-3G (1,1,1) at pure PBE (no exchange-convention freedom), and its stored density evaluated under the piecewise-validated functional to 0.24 Ha below its reported energy (HF was unaffected; the real-Γ KS route agrees with PySCF to the ~5 mHa HF-level shared-fitting deviation). Escalated to the periodic-SCF/GDF chat (handovers/HANDOVER_AICCM_DIRECT_TORUS.md Finding §4) and fixed the same day in three parts: the XC-density/grid-convention pairing (b3f74aa9, entry below) for the Γ-path pure KS, plus the Γ-hybrid exchange-convention and multi-k dim<3 Hartree fixes (entry above); the discrepancy canaries in tests/test_ccm_rks_direct.py were flipped to three direct-vs-GDF KS parity gates.

Fixed: Γ-only GDF UHF/UKS no longer return converged non-physical energies (2026-07-09)

run_pbc_gdf_uks (PBE, default options/aux) returned +1172.63 Ha with converged=True on rocksalt LiH/STO-3G (fcc a = 7.72 bohr) — external PySCF 2.13.1 KRKS(GDF) PBE gives -8.2389 Ha. Root cause is not the KS/XC path: run_pbc_gdf_uhf/run_pbc_gdf_rhf land at +579.83 Ha on the same cell with no XC at all, and the Γ rsgdf lane is sane (-7.96 Ha). It is the Γ-only gdf_method='compcell' (the API default) Hartree on tight ionic cells — the Γ-only J (G=0 dropped) cannot resolve overlapping AO-pair images, and the SCF converges to a fixed point of that broken Fock (e_coulomb = +1177 Ha) — the documented limitation the RHF driver already warns about and tags (+SANITY_FAILED, test_pbc_gdf_rhf_production_defaults_lih_ionic); the open-shell/KS siblings had neither the warning nor the guard and returned the garbage silently (CLAUDE.md §7). Both drivers now (a) emit the shared tight-ionic-cell warning (_warn_gamma_compcell_tight_ionic_cell) and (b) run the energy-sanity guard with KRHF/KRKS semantics: a converged non-physical energy (runaway |E| > max(10·ΣZ², 100) Ha, or unbound E > +1 Ha) raises RuntimeError steering to the multi-k route / gdf_method='rsgdf'; check_energy_sanity=False bypasses for diagnostics; a non-converged run warns + tags. The shared guard also gains the unbound criterion for the RHF caller (warn+tag mode there — pinned behaviour unchanged). The production runner is unaffected (it dispatches these drivers with gdf_method='rsgdf'); direct API callers were exposed. Not fixed here (separate, pre-existing, sub-Ha class): on the sane rsgdf lane this driver still evaluates XC in the molecular-grid/molecular-limit-density convention when given plain PeriodicRHFOptions (default options=None), -7.9638 vs KRKS(1,1,1) -8.2338 on this fixture — the use_periodic_becke torus pairing of the 2026-07-09 periodic-KS XC-convention fix engages only when the options object carries the flag (e.g. PeriodicKSOptions). [Both follow-ups landed same day: see “Γ open-shell GDF drivers default to gdf_method=’rsgdf’ + KS options” above — the defaults now avoid this class entirely.] Pins: tests/test_pbc_gdf_compcell.py::test_pbc_gdf_{uks,uhf}_lih_ionic_sanity_guard_raises, ::test_pbc_gdf_energy_sanity_guard_semantics.

Fixed: multi-k GDF no longer builds the unused EWALD_3D J cache (2026-07-09)

run_krhf_periodic_gdf built the iteration-invariant EWALD_3D Hartree-J cache (make_ewald_3d_lattice_j_cache — a per-cell AO-pair FT of shape (n_cells, nbf, nbf, n_G) on its own VIBEQC_J_EWALD3D_KE mesh) unconditionally, although the cached-Lpq GDF branch — every multi-k HF/hybrid run after the 2026-06-04 auto-routing — never consumes it. On c-diamond fcc primitive / sto-3g / kmesh (2,1,1) that cache was 2.95 GB, transiently ~5.9 GB through an out-of-place pair-scale multiply — 5.9 of the measured 6.13 GB peak RSS, previously misattributed to the GDF fit tensors (the four (k_i,k_j) fit builds peak at 0.09 GB each; corrected in handovers/HANDOVER_GDF_FIT_SCREENING.md). The cache is now built only when the EWALD_3D branch runs, and the pair-scale multiplies in build_j_ewald_3d_ft_lattice_cache / _rsgdf_dense_pair_ft / build_lpq_bloch_native_fft are in-place. Pin: tests/test_periodic_k_gdf.py::test_multik_gdf_branch_skips_ewald_j_cache.

Added: Cauchy-Schwarz screening of the three-centre GDF fit (2026-07-09)

fit_screen_threshold on run_krhf_periodic_gdf / run_krks_periodic_gdf / run_kuhf_periodic_gdf (rsgdf method) and on the shared fit primitive build_lpq_bloch_native_fft: AO pairs whose Schwarz bound max_P sqrt((P|P))·sqrt((μν|μν)) on the fit mesh stays below the threshold are dropped from every per-(k_i,k_j) three-centre fit tensor (Neese et al., Chem. Phys. 356, 98 (2009) §3.1; Häser & Ahlrichs 1989; Ochsenfeld et al. 1998 — routed as gdf_fit_screen in the citation database). The pair-norm factor is an analytic upper bound (never underestimates); kept/dropped pair counts are logged (no silent truncation). Default off (0.0, exact); non-rsgdf methods and the Γ-only fast path raise NotImplementedError rather than silently ignoring the threshold. Every consumer of the multi-k drivers (CCM run_ccm_rhf_gdf / run_ccm_rks_gdf via **driver_kwargs, RIJCOSX diagonal-J) inherits the option. Also fit_pair_list on the fit primitive — the shell-pair-list seam the space-group pair-reduction workstream composes with (handovers/HANDOVER_SYMMETRY_PAIR_REDUCTION.md). This milestone pins semantics + energetics (screened == unscreened to <1e-8 Ha/cell at 1e-10 on the H₂ anchor; tests/test_gdf_fit_screening.py); the compacted pair-list kernel that converts the screen into the peak-memory reduction is the next milestone (handovers/HANDOVER_GDF_FIT_SCREENING.md).

Changed: multi-k GDF fit build is G-chunked (bounded peak memory) (2026-07-09)

build_lpq_bloch_native_fft now accumulates the 2c metric and the three-centre tensor in tail_chunk_g-point G-chunks (the same chunking the high-|G| tail already used), so the builder’s peak transient is the (nbf, nbf, tail_chunk_g) pair-FT block — never the full (nbf, nbf, n_G) tensor — and the Schwarz screen (fit_screen_threshold, above) zeroes dropped pairs per chunk. The default chunk (65536) reproduces the previous one-pass build bit-for-bit for meshes that fit one chunk; larger meshes log their chunk count (no silent memory caps). M2 of handovers/HANDOVER_GDF_FIT_SCREENING.md; pin: tests/test_gdf_fit_screening.py::test_chunked_accumulation_matches_single_pass.

Added: vibe-view live-opt MACE engine + hardened no-vibe-qc gating (2026-07-09)

The Auto-optimize worker protocol now carries an engine: MSINDO (default, unchanged) or MACE via vibeqc.mlip (the CLAUDE.md §10 MLIP exception) — offered in a new Engine picker when the probe finds vibe-qc’s [mace] stack (torch + mace + ase) importable. Live-opt uses only the MIT-licensed MACE-MPA-0 default on CPU (ASL-gated academic models are not selectable), a note protocol event surfaces model loading / first-use weight download in the status line, and switching engines mid-session re-relaxes with the new one. Attribution per the MLIP rule: the note and picker name the model, and the docs state the energy is the model’s reference-shifted DFT-surface value. The no-vibe-qc path is now pinned by tests that block the vibeqc import in a subprocess (probe reports unavailable + zero engines; a direct optimize request yields one parseable error event), the probe never imports torch (find_spec only), and LiveOptService.schedule without a running event loop reports through on_error instead of raising. MACE-path tests are mock-based (MACE caps at Python 3.13; the repo venv is 3.14). Docs: user guide § Building & editing (engines paragraph).

Fixed: periodic Γ-point KS drivers evaluated XC on a molecular-limit density (2026-07-09)

Resolves the escalated “production KRKS internally inconsistent” finding (the aiccm-real-gamma chat, 2026-07-09; the drop-box/handover Finding §4 class): run_krks_periodic_gdf at a Γ-only mesh disagreed with external PySCF 2.13.1 KRKS(GDF) by +308 mHa on rocksalt LiH/STO-3G (1,1,1) at pure PBE, and its own stored density evaluated 0.24 Ha below the energy it reported. Root cause: every Γ-point KS driver fed build_xc_periodic(_uks) a home-cell-only density set, which the C++ kernel deliberately treats as a molecular-limit density (ρ = χ₀Dχ₀, no image-cell AO products) — the convention that pairs with the molecular Becke grid of the v0.8.x era. Once use_periodic_becke became the default, Γ KS silently evaluated the isolated-molecule density on the one-cell periodic grid: exact in a vacuum box (every vacuum-limit gate passed), wrong by the image density on tight crystals. The fix pairs the density-set convention with the grid: on the periodic Becke grid the drivers now feed the Γ-torus set (D in every lattice block — the kernel’s validated cross-cell mode and the N_k = 1 limit of the multi-k fold); use_periodic_becke=False keeps the historical pairing. Fixed: run_rhf_periodic_gamma_gdf (the KRKS Γ route — post-fix LiH lands at −8.2338, 0.10 mHa from the independent real-Γ route and 5.07 mHa from PySCF, i.e. the documented ultra-diffuse-Li shared-fitting floor; pre-fix −7.930753392700 reproduced exactly), run_rks_periodic_gamma_ewald3d, run_uks_periodic_gamma_ewald3d (UKS==RKS closed-shell identity now exact to 2e-16 on the dim=3 chain box), and the use_periodic_becke=True branch of run_pbc_gdf_uks (NB: that driver’s default path — PeriodicRHFOptions, molecular grid — has a separate pre-existing pathology on dense ionic cells, +1172 Ha converged=True on LiH pre- and post-fix with no sanity guard; flagged as its own follow-up, out of scope here). Gates: tests/test_krks_gdf_xc_density.py (torus-set plumbing, the Γ-vs-multi-k-fold XC identity, the external PySCF KRKS(GDF) PBE anchors on LiH (1,1,1) and on the non-TRIM (3,1,1) dim=3-declared H₂ chain, and the Γ-Ewald UKS==RKS closed-shell identity). KS numbers produced by the Γ paths above on non-vacuum cells before this fix should be re-run. The test_rks_gdf_ks_reference_discrepancy_canary pinned by the real-Γ CCM chat trips once both changes are in a tree and is to be flipped to a ≤1e-8 Ha/cell direct-vs-GDF KS parity gate per its docstring.

Two clarifications from the same investigation. (1) The multi-k closed-shell KS branch is not affected: it already fed the full real-space fold, its reported energy reconstructs from its own stored density to 5e-15, and it now carries an external anchor — vibe-qc multi-k KRKS(PBE) on the dim=3-declared H₂ chain at the non-TRIM (3,1,1) mesh agrees with PySCF 2.13.1 KRKS(GDF) to 29 µHa (KRHF: 5e-10). The handover’s multi-k line item (“reported −3.0897 vs stored-density −3.7814” on the dim=1 chain) is a cross-convention comparison: the multi-k HF baseline on that fixture carries the exxdiv='ewald' Madelung exchange shift that a pure-PBE KS legitimately lacks, so “KS above HF” is the expected convention there (PySCF LiH shows the same 0.096 Ha pattern). (2) The multi-k UKS KS branch retains its documented BZ-averaged home-cell XC shortcut (same defect class, vacuum-limit-only): a first attempt at the closed-shell-style fold fix failed its KUKS(mult=1)==KRKS identity gate by −2.4 Ha and was not landed; the limitation is now marked loudly in the code — do not quote multi-k KUKS numbers on tight cells until that follow-up lands. [That follow-up landed same day: see “multi-k KUKS XC now uses the per-spin real-space fold” above — the −2.4 Ha blocker was the KUKS grid-options default, not the fold.]

Added: unified auto-reducing SCF level shift (molecular + periodic) (2026-07-09)

The Saunders-Hillier level shift now has one representation shared by every molecular and periodic SCF driver. A new C++ helper level_shift_at_iter (cpp/include/vibeqc/level_shift.hpp, exposed to Python as vibeqc.level_shift_at_iter) is the single source of truth for the per-iteration shift; it resolves both an auto/warm-up step function and an explicit per-iteration decay schedule with zero allocation and no Python round-trips inside the SCF loop.

  • Molecular drivers gained auto-reduction. RHF / UHF / RKS / UKS previously applied a constant level_shift at every iteration. They now honour level_shift_warmup_cycles (-1 auto: shift a few startup cycles then release with an unshifted tail; 0 persistent, the legacy behaviour; N explicit) and an explicit level_shift_schedule, matching the periodic drivers. The default flips from persistent to -1 (auto). The shift matmuls (S·D·S) are skipped entirely on iterations where the resolved shift is 0.

  • Option-struct parity. level_shift_warmup_cycles and level_shift_schedule are now exposed (with pybind11 bindings) on all four molecular and all three periodic options structs, with identical names and semantics.

  • Periodic drivers share the helper. The Γ-Ewald, multi-k Ewald and GDF periodic drivers route their per-iteration shift through the same level_shift_at_iter and now honour opts.level_shift_schedule in addition to the existing warm-up. Behaviour is byte-identical when no schedule is set.

  • Ergonomics. LevelShiftSchedule (now a top-level vibeqc export) lowers onto the C++ vector via .as_list() / .apply_to(opts); crystal_default() / crystal_aggressive() / constant() classmethods unchanged.

Converged energies are bit-identical to the no-shift result (the shift is inert at the SCF fixed point). Pins: tests/test_molecular_level_shift.py, tests/test_periodic_level_shift.py, tests/test_level_shift_schedule.py.

Fixed: GFN2-xTB long-range ionic diatomics now converge (2026-07-09)

Long-range closed-shell ionic diatomics (LiH, LiF, NaCl) could exhaust the GFN2-xTB SCC loop with the third-order on-site term active: the hard T=0 Aufbau occupation map bounced between neutral and charge-transfer fixed points, so the C++ driver returned converged=False with an empty charge vector. The default path still tries the unchanged T=0 SCC first, but run_gfn2_xtb now has an auto_stabilize retry ladder for gamma3 cases: smaller simple-mixing steps first, then finite electronic-temperature occupations (electronic_temperature, Ha) as a stabilizer if the cold map does not settle. A successful retry reports the total SCC cycles consumed across failed attempts plus the converged attempt. Pins: tests/test_gfn2_xtb.py::TestGFN2SCCStabilization.

Fixed: GFN2-xTB gamma-derivative force was halved (2026-07-09)

The same ordered-pair halving defect repaired for SCC-DFTB on 2026-07-09 was present in both GFN2-xTB analytic gradients (compute_gfn2_gradient, compute_periodic_gfn2_gradient): the per-atom charge-electrostatics force loop visits every ordered pair (a,b) and deposits into grad(a) only, so the 0.5 prefactor from E2 = ½ ΣΔqγΔq double-counted the ½ and halved the gamma force. The derivative kernel itself (∂γ/∂R = −R·γ³) was verified correct for the Klopman–Ohno form 1/sqrt(R² + η⁻²) used by build_gfn2_gamma — against central finite differences of ½ Δq·γ(R)·Δq at fixed charges the corrected term matches to ~1e-9 Ha/bohr while the pre-fix form is off by exactly a factor 2. The stress-tensor gamma terms accumulate both loop orders into one global tensor; their 0.5 is correct and untouched. Pins: tests/test_gfn2_xtb.py::TestGFN2GammaGradientFD (gamma-term FD test + implementation value pin on water). Caveat found while validating (resolved the same day by the full analytic gradient below): the full GFN2 gradient was then dominated by the knowingly omitted H⁰ shape-term derivatives (CN self-energy / EN-scaling / shell polynomial) and disagreed with central finite differences of the SCC energy by ~0.3 Ha/bohr with the wrong sign on water. Those derivatives have since landed (see the next entry) and the molecular gradient is now FD-consistent. This fix repaired the one term that was mathematically wrong within its own model rather than approximate.

Added: full GFN2-xTB analytic gradient — H⁰ shape + shell-ES + 3rd-order + AES (2026-07-09)

compute_gfn2_gradient now differentiates every term of the GFN2-xTB energy functional assembled by run_gfn2_xtb (Bannwarth, Ehlert & Grimme, JCTC 2019, doi:10.1021/acs.jctc.8b01176), closing the caveat from the gamma-halving fix above. Added:

  • H⁰ shape-term derivatives (the previously-omitted piece): the CN-dependent self-energy chain Σ_A (∂E/∂CN’_A)·dCN’_A/dR, the full ∂H⁰/∂S shape factor (Wolfsberg–Helmholtz K, Slater-exponent compactness, EN scaling) folded into the M·dS/dR overlap-derivative contraction, and the shell distance-polynomial pair force. Implemented in gfn2_h0_image_derivative_terms / gfn2_cn_chain_gradient, which differentiate build_gfn2_hamiltonian_zero_image factor-for-factor.

  • Shell-resolved second-order electrostatics: the ES force now uses build_gfn2_shell_gamma (matching the energy) rather than the atom-resolved build_gfn2_gamma stand-in.

  • Third-order on-site term: enters through the converged Fock shell potential (no explicit geometry dependence).

  • Anisotropic electrostatics (AES): the kernel geometry force at fixed moments plus a Z-vector response correction required because the shipped ad-hoc AES is not variational w.r.t. the SCC Fock. The response also carries the moment-integral Pulay variation (its ∂X/∂R rebuilds the multipole integrals at the displaced geometry), so no separate Pulay term is added; doing so double-counted and broke FD agreement by the exact Pulay magnitude (~6e-4 Ha/bohr on water).

Validated against central finite differences of the total SCC energy to < 1e-5 Ha/bohr on H₂O / CH₄ / NH₃. The periodic Γ-point gradient compute_periodic_gfn2_gradient gains the H⁰ shape + shell-ES + AES kernel/Pulay derivatives too but remains a fixed-charge approximation (no AES response correction). Pins: tests/test_gfn2_xtb.py::TestGFN2FullGradientFD (FD gate on H₂O/CH₄/NH₃

  • translation invariance). The preoptimize paths that used finite-difference GFN2 gradients are unaffected; callers may now use the analytic gradient for molecular geometry work.

Added: ADIIS_DIIS hybrid SCF accelerator (2026-07-09)

SCFAccelerator.ADIIS_DIIS (keyword "adiis_diis" / "adiis+diis") is the ADIIS analogue of the EDIIS_DIIS production hybrid: it uses ADIIS (Hu & Yang 2010) while the commutator-error norm is above ediis_diis_switch_threshold (robust far from convergence), then switches to plain DIIS for the asymptotic regime that ADIIS, like EDIIS, cannot reach past the convex hull under its positive-simplex constraint. Wired across all four molecular drivers (RHF/UHF/RKS/UKS), the C++ periodic drivers (Γ + multi-k), and the Python periodic accelerators (Γ-Ewald + multi-k). Reuses the existing ediis_diis_switch_threshold; reaches the same fixed point as plain DIIS. Citation routed to hu_yang_adiis_2010.

Added: vibe-view live geometry optimization while building (2026-07-08)

Avogadro-parity milestone M3 (roadmap §4, B1+B2): an Auto-optimize switch in the editor’s Atom Editor card relaxes the sketch after every edit pause — a debounced background subprocess (python -m vibeview.live_opt) runs a vibe-qc MSINDO semi-empirical optimization (analytic gradients, H–Xe; scipy L-BFGS-B mirroring msindo_optimize) and streams each optimizer step back over a JSON-lines protocol, easing the atoms toward the relaxed geometry live in the viewport. Cancel-on-edit (a new edit kills the running worker and reschedules), one undo entry per relax run, convergence feedback (step / energy / max gradient, then “relaxed in N steps”), and a clean degrade: without vibeqc importable the switch snaps back off with the reason. Engine survey recorded in vibeview/live_opt.py: SCC-DFTB’s default parameter set was rejected — its PES has no repulsive wall (water dissociates downhill) and its analytic gradient disagrees with finite differences; GFN2/PM6 have correct minima but no analytic gradient. Pins: vibe-view/tests/test_live_opt.py (protocol, service debounce/cancel via a fake worker, real MSINDO worker end-to-end, app wiring); trusted-event Playwright QA vibe-view/docs/qa_live_opt.py (7/7). Docs: user guide § Building & editing.

Fixed: vibe-view editor edits now compound (2026-07-08)

Sequential editor operations in vibe-view (add / delete / change-element / fragment insert / add-hydrogens / supercell) previously each re-read the pristine archive, so every edit silently discarded all earlier ones — adding two atoms left only the second — and export / input generation used the un-edited structure. QVFReader now carries a viewer-side edit overlay (set_edit_overlay / clear_edit_overlay), written at the single scene-rebuild point and returned by read_structure(), so every consumer sees the same current geometry; a fresh reader (file switch, hot reload) drops it. Explicit file bonds are dropped while an overlay is active (atom indices are stale; auto-bonding applies). Also fixed in passing: the change-element status message always reported “0 atom(s)”, and the spectra.epr renderer-dispatch test was missing its synthetic fixture (vibe-view suite red since the EPR kind landed). Pins: vibe-view/tests/test_edit_overlay.py.

Added: C ABI + roadmap for the qvf-writer library (2026-07-09)

The zero-dependency C++ QVF writer now exposes a stable C ABI (qvf-writer/cpp/include/qvf/qvf_c.h + src/qvf_c.cpp) so C, Fortran (via ISO_C_BINDING), Rust, Julia, and other languages can emit .qvf archives without a C++ interface. All state lives in one opaque qvf_writer handle; nested payloads (provenance, NMR/EPR spectra, vendor sections) pass as JSON strings, emitted verbatim via a new qvf::Json::raw node, so no JSON parser crosses the boundary. Ships a runnable pure-C example (write_h2o_c.c), a C-ABI unit smoke test, and CMake wiring. Also adds qvf-writer/ROADMAP.md (conformance corpus, packaging, endianness, governance, second producer) and a “C and Fortran” section to the library guide.

Added: code-agnostic QVF integration guide (2026-07-09)

New qvf-writer/docs/integration_guide.md (surfaced on the website under the QVF section): a producer-neutral how-to for adopting QVF in any quantum-chemistry code — the two integration models (in-process vs post-processor), the data-category → section-kind mapping table, the wavefunction normalization/AO-ordering contract, periodic notes, the vendor → canonical promotion path, and how a code becomes an officially supported producer (with vibe-view support). The ORCA guide is reframed as the concrete worked example of it; includes a PySCF-oriented note.

Added: DEFLATE compression in the qvf-writer C++ library (2026-07-08)

The zero-dependency C++ QVF writer now DEFLATE-compresses archive members (original RFC-1951 codec in qvf-writer/cpp/src/qvf_deflate.hpp: LZ77 hash-chain match finder + a fixed-Huffman block), falling back to STORE when a member does not shrink and always STORE-ing manifest.json for cheap table-of-contents reads. Still zero third-party code. Verified decompressible by unzip, Python zipfile, and the sha256 integrity check in vibe-qc’s validate_qvf (byte-exact inflate); binary grids compress to a few percent.

Added: vibe-view IUPAC molecule-name sidebar header (2026-07-08)

vibe-view now shows the IUPAC / trivial name of the loaded structure as the first entry in the section sidebar (above Structure), with a confidence icon (check / info / alert for high / medium / low confidence). The name comes from the standalone, pure-Python vibeqc_naming package (name_from_qvf), imported optionally: if it is not installed the sidebar renders exactly as before. The entry is informational (non-clickable, no panel), and the lookup is wired into both sidebar-build paths so it survives file switches. Pins: vibe-view/tests/test_iupac_name.py (9 tests, mock-based because vibeqc_naming is a separate package that need not be present).

Added: vibe-view desktop auto-update plumbing (2026-07-08)

The vibe-view Electron app now bundles electron-updater and reads a real update feed at https://vibe-qc.com/updates/vibe-view/ (electron-builder publish config, baked into app-update.yml). build-desktop.sh produces the dmg/zip/blockmaps + latest-mac.yml, verifies the updater is in the app.asar, and can rsync the feed to the (ISPConfig-managed) host via VIBEVIEW_UPDATE_RSYNC_TARGET. “Check for Updates” now performs a real check. See vibe-view/electron/PUBLISHING.md.

macOS self-install is gated on code signing: unsigned builds can detect, download, and notify, but Squirrel.Mac will not apply an update without an Apple Developer ID cert + notarization (a pending maintainer-infra decision). Scope: mac-arm64 built + published today; Linux feed config is wired but needs a Linux build host; Windows is a follow-up.

Added: vibe-view desktop first-run setup screen (2026-07-08)

The vibe-view desktop app (vibe-view/electron/) now shows an in-app setup screen when it can’t find a Python capable of running the viewer server, instead of a dead-end error dialog followed by the app quitting. The screen classifies the machine into one of three cases — no Python, a Python without vibe-view, or vibe-view without the [viewer] extra — names exactly what to install (with a Copy button and a python.org pointer when no Python exists), and offers a recheck button that re-scans and launches the server in place once the user has installed it, with no restart. The install command creates a dedicated virtualenv rather than a bare pip install, so it works on Homebrew/Debian/Fedora system Pythons that reject pip with externally-managed-environment (PEP 668); the app discovers that managed venv (at $XDG_DATA_HOME/vibe-view/venv) automatically, so recheck finds it with no extra step. New files: preload.js (a minimal contextBridge IPC surface) and setup.html (the onboarding page); both are added to the electron-builder files whitelist so they ship in the packaged app. Interpreter discovery is platform-aware (the py launcher + .venv\Scripts\python.exe on Windows, python3 + bin/python3 on macOS/Linux). Windows is in scope but shipped untested — the maintainers have no Windows machine, and the nsis installer is unsigned (SmartScreen warns); macOS and Linux are the verified targets. On its first launch on Windows the app shows a one-time notice that the build is untested and asks for feedback (a “Send feedback” button opens the issue tracker). This is the graceful-onboarding half of the “downloadable, no-heavy-prereq” desktop packaging effort; a full self-contained Python freeze was evaluated and deferred to the prompt-to-install model (see vibe-view/docs/desktop_packaging_design.md). Documented in docs/user_guide/vibe_view_desktop.md § First run.

Added: canonical spectra.epr QVF kind (EPR g/A/D tensors) (2026-07-08)

New canonical QVF section kind spectra.epr, paralleling spectra.nmr: a single object-shaped spectrum JSON member carrying the EPR g-tensor, per-nucleus hyperfine (A) tensors, and the zero-field-splitting (D) tensor (g dimensionless; A / D in MHz; payload shape intentionally loose in v1). Promoted to canonical by maintainer decision ahead of the usual two-producer threshold (design_qvf_format.md § 5.7) to give ORCA a canonical target for its EPR output instead of a vendor section. Wired end to end:

  • Schema: new SectionSpectraEPR branch in qvf_manifest.schema.json (canonical + the byte-identical qvf-writer/ copy); documented in docs/design_qvf_format.md § 4.4.

  • Writer: spectra.epr in _IMPLEMENTED_KINDS + a dormant _write_spectra_epr_section driven by the epr_data context key (vibe-qc does not compute EPR yet; same pattern as the phonon / EOS writers).

  • vibe-view: spectra.epr renderer (renderers/epr.py) with a g-tensor / hyperfine / zero-field-splitting panel; registered in SUPPORTED_KINDS, the renderer map, and the app activation path.

  • Toolkit: both reference writers accept spectra.epr; the ORCA-integration guide and adoption tutorial now present EPR as canonical.

Added: qvf-writer/ — standalone QVF writer toolkit for external adopters (2026-07-08)

New top-level qvf-writer/ directory: a self-contained, Apache-2.0 distribution of the QVF (Quantum Visualization Format) producer contract, so third-party quantum-chemistry codes (initial adopter: ORCA) can emit .qvf archives without depending on vibe-qc. Landed incrementally; this milestone (M1) ships the scaffold, licensing, and the standalone normative spec:

  • qvf-writer/spec/qvf-format-spec.md — normative, self-contained QVF v1 specification (archive model, manifest, section-kind catalog, units, extension governance, producer/consumer conformance, and an AO-ordering + primitive-normalization appendix for wavefunction.gto).

  • qvf-writer/spec/qvf_manifest.schema.json — byte-identical copy of the canonical schema (drift-guarded).

  • Apache-2.0 LICENSE + NOTICE; the subtree license is recorded in docs/license.md § 3h. The C++ library has zero third-party dependencies — ZIP (STORE), CRC-32, SHA-256, and JSON emission are original Apache-2.0 code, so there is no vendored-code licensing surface.

Reference writers (Python + zero-dependency C++17 library), library docs, an ORCA-integration guide, a “how to adapt QVF + implement a writer” tutorial, a dedicated website section, and a downloadable archive follow in later milestones on this branch. Nothing under python/vibeqc/ or cpp/ imports the subtree; it is standalone.

Fixed: SOSCF is now a proper quasi-Newton (L-BFGS) accelerator (2026-07-08)

The Phase D2d SOSCF step (cpp/src/soscf.cpp) was a memoryless single diagonal augmented-Hessian step (κ = −g/(ε_a ε_i)): it built no curvature and, for Kohn-Sham DFT, oscillated near convergence because the diagonal Hessian ignores the (large) XC-kernel response — an SOSCF-enabled RKS/PBE H2O run failed to converge in 100 iterations. SOSCF is now the quasi-Newton method Neese (2000) actually describes: the diagonal orbital Hessian is only the initial preconditioner H0, and an L-BFGS two-loop recursion accumulates the true curvature (including the XC coupling) from lbfgs_history (default 10) pairs of (orbital-rotation step, gradient difference). New stateful SOSCF / UHFSOSCF classes replace the stateless per-iteration step in all four molecular drivers. With an empty history the first step reduces exactly to the old diagonal step, so it is a strict generalisation. Result: SOSCF-enabled RHF/UHF/RKS/UKS all converge to the correct energy (RKS/PBE 100→10 iterations). SOSCF remains opt-in (soscf_threshold = 0.0 by default): even after the L-BFGS fix it does not robustly accelerate every system (it still stalls on porphine RHF/STO-3G), so it is offered as a much-improved opt-in finalizer rather than a default.

Changed: molecular + periodic SCF defaults tuned toward ORCA/CRYSTAL (2026-07-08)

SCF option defaults now track the out-of-the-box convergence profile of ORCA (molecular) and CRYSTAL (periodic), without changing any converged energy:

  • Molecular (RHFOptions / UHFOptions / RKSOptions / UKSOptions): dynamic_damping now defaults to true (Zerner-Hehenberger adaptive damping; damping still starts at 0.5 and relaxes toward 0.0 on monotonic descent, so easy systems pay nothing and oscillating ones are stabilised). Verified benign: iteration counts are unchanged on well-behaved cases and it only acts when the SCF oscillates.

  • Periodic (PeriodicRHFOptions / PeriodicSCFOptions / PeriodicKSOptions): scf_accelerator now defaults to EDIIS_DIIS (the production hybrid, was DIIS) and dynamic_damping now defaults to true (charge-sloshing-prone periodic SCF benefits most).

Unchanged: molecular scf_accelerator (EDIIS_DIIS), soscf_threshold (0.0, opt-in), damping (0.5), molecular guess (PATOM), periodic guess (SAD). Periodic SOSCF is not added (the periodic structs have no soscf_threshold and the multi-k loop lacks the orbital-rotation machinery). Level shift and smearing stay at 0.0 (opt-in per system). ROHF/ROKS inherit RHFOptions / RKSOptions.

The benchmark generator (qc-input-library/scripts/gen_inputs.py) no longer overrides damping = 0.0, which had defeated the adaptive-damping default.

Added: explicit aux_basis override for DLPNO methods (2026-07-08)

The DLPNO-CCSD / CCSD(T) option dataclasses (LocalCCSDOptions, DLPNOCCSDPilotOptions, DLPNOUCCSDPilotOptions) gained an aux_basis field. When set, the runner uses that RI fitting basis instead of auto-resolving one from the orbital basis. This unblocks DLPNO on orbital bases that ship no default RI aux (minimal / Pople bases such as sto-3g, 6-31g*): previously such jobs raised NotImplementedError from default_aux_basis_for(..., kind="ri") with no way to supply an aux on the DLPNO path (only the plain CCSD path exposed the knob). aux_basis=None keeps the existing auto-resolution behaviour, so default runs are unchanged. The default_aux_basis_for error message now names the cause (minimal / Pople bases have no standard fitting aux) and points at the option-object override.

Added: adaptive-depth commutator-DIIS accelerators (R-CDIIS + AD-CDIIS) (2026-07-08)

Two new scf_accelerator choices implementing the adaptive-depth DIIS variants of Chupin, Dupuy, Legendre & Séré, Convergence analysis of adaptive DIIS algorithms with application to electronic ground state calculations, ESAIM: M2AN 55, 2785 (2021), doi:10.1051/m2an/2021069:

  • SCFAccelerator.R_CDIIS (keyword "r_cdiis"): restarted CDIIS (their Algorithm 3): the history depth grows until the stored error differences become nearly linearly dependent, then restarts. Restart aggressiveness is the new option diis_restart_tau (τ ∈ (0,1), default 1e-4).

  • SCFAccelerator.AD_CDIIS (keyword "ad_cdiis"): adaptive-depth CDIIS (their Algorithm 4): the window retains only recent iterates whose residual is within 1/δ of the current one, so the depth shrinks smoothly near convergence. Controlled by the new option diis_adaptive_delta (δ > 0, default 1e-4).

Both reuse Pulay’s commutator-DIIS extrapolation unchanged; only the retained (F, error) window differs, implemented as a DIISDepthPolicy on the single canonical C++ DIIS class (cpp/src/diis.cpp). Available on every backend that supports DIIS: molecular (RHF/UHF/RKS/UKS), C++ periodic (Γ + multi-k), the Python Γ-Ewald accelerator, and the Python multi-k accelerators (_MultiKPulayDIIS carries the policy via a √wₖ-scaled per-k error flattening so the restart projection / residual tests reuse the C++ Euclidean formulas). Citation wired (chupin_adaptive_diis_2021); user guide + keyword index updated.

Fixed

  • vibe-view desktop “Check for Updates” no longer silently does nothing. The menu item ran autoUpdater && autoUpdater.checkForUpdatesAndNotify(), a no-op because electron-updater is not installed (autoUpdater is null). It now shows an actionable dialog explaining how to update by hand (git pull + rebuild, or reinstall the dmg), and still triggers a real check once electron-updater is wired.

  • vibe-view desktop tray icon no longer claims the whole menu-bar area. The macOS Tray was constructed from the full 512x512 app icon.png, so it rendered oversized in the status bar. main.js now resizes it to a 22px menu-bar image (nativeImage.resize), with an empty-icon fallback.

Fixed

  • SCC-DFTB no longer dissociates molecules downhill; DFTB band filling now counts valence electrons only. Two independent defects in the DFTB family (found 2026-07-08 by the vibe-view live-optimization chat: water’s SCC-DFTB energy fell monotonically with O–H stretch — no repulsive wall, no minimum — and the analytic gradient disagreed with finite differences in sign and magnitude):

    1. The SCC Hamiltonian correction was applied with the wrong sign for the code’s Mulliken-charge convention (Δq > 0 = cation), turning charge screening into self-amplifying anti-screening: water converged to O = −1.36 e / H = +1.7 e and the second-order energy grew without bound with stretching. The correction is now = −½S(V_A+V_B) (Elstner et al., PRB 58, 7260 (1998), Eq. 14, sign-adapted), and the total energy is assembled variationally as E = tr(D H⁰) + ½ΣΔqγΔq + E_rep. With the variational assembly the fixed-charge analytic SCC gradient is exact at convergence; compute_scc_dftb_gradient_response now delegates to it and the CP-SCC finite-difference Z-vector machinery is retired.

    2. Band filling used mol.n_electrons()/2, counting CORE electrons into a valence-minimal basis (H₂O: 5 pairs instead of 4). This over-filled every DFTB variant, broke the Mulliken neutrality reference (ΣΔq = −n_core, hidden by an unconditional recentre), and produced symmetry-broken charges on equivalent atoms. All DFTB0 / SCC-DFTB runners (molecular, unrestricted, periodic Γ, k-point) now fill Σ n_val(A) − charge electrons; the Mulliken recentre is charge-aware. SCCDFTBResult.e_electronic is now tr(D H⁰) (was Σnε) and energy = e_electronic + e_scc + e_repulsive (e_scc was previously subtracted); reference energies re-pinned. Water now has an interior O–H minimum near 1.8 bohr with a repulsive wall for both DFTB0 and SCC-DFTB, and preoptimize_molecule actually relaxes distorted geometries. Pins: tests/test_semiempirical_pes.py::TestDFTBWellShapes, tests/test_semiempirical_dftb0.py::TestPreoptimization::test_preoptimize_improves_distorted_water, tightened TestSCCDFTB::test_scc_analytic_vs_fd.

[v0.15.30] - 2026-07-07 - Neese’s Cheetah

Changed

  • ROHF/ROKS Roothaan SCF now supports the accelerator family. The Python Roothaan loop accepts EDIIS, ADIIS, EDIIS+DIIS, and KDIIS-style accelerator selections through ROHFOptions.scf_accelerator, matching the convergence-control surface used by the production RHF/UHF/UKS drivers. The porphine RHF default-convergence regression is also pinned so the large default-SCF lane keeps its current basin.

[v0.15.29] - 2026-07-07 - Neese’s Cheetah

Changed

  • vq programs can gate managed venv commit identity. vq programs --require-sha NAME=SHA now fails when a program’s current_git_sha does not match the requested commit or short SHA prefix. vq programs --require-version NAME=VERSION similarly gates the imported runtime version. Pair both with --require-branch and --require-clean for fleet update and docs artifact preflights.

  • vq cleanup reclaims scheduler-side workspaces. Auto-cleanup now removes duplicate remote scheduler workspaces at archive age after terminal results have been copied back locally, stamps scheduler_remote_workspace_cleaned_at on the spec, retries archived specs whose remote cleanup failed, and skips final delete if the scheduler-side workspace cannot be cleaned.

Fixed

  • ROKS/ROHF frontier degeneracies now get averaged occupations. Open-shell restricted references now assign fractional occupation across degenerate frontier manifolds instead of selecting an arbitrary component from a symmetry-equivalent set. This keeps spin-pure ROHF/ROKS starts symmetric for cases such as atomic p shells and avoids artificial anisotropy in the SCF log and downstream ROKS functional routes. Pins: tests/test_rohf.py and tests/test_roks.py.

  • CASPT2/NEVPT2 correlation gradients now differentiate the reported PT2 energy. For single-state CASSCF-referenced CASPT2/NEVPT2, compute_corr_grad=True now returns a relaxed full-energy central finite-difference gradient through the runner instead of adding the historical semi-analytic Z-vector shortcut to a frozen correlation derivative. This removes the mHa/bohr-scale mismatch seen in the v0.15 validation matrix. Pins: tests/test_casscf_gradient.py::TestCASPT2ZVectorRunner::test_corr_gradient_matches_relaxed_fd and tests/test_casscf_gradient.py::TestNEVPT2ZVectorRunner::test_corr_gradient_matches_relaxed_fd.

  • vibe-view desktop app: double-click on a .qvf now starts the server. Two bugs in vibe-view/electron/main.js. (1) findPython() picked the first python3 on PATH that answered --version, even when vibeview was only installed in a virtualenv, so the spawned server died with ModuleNotFoundError and the app showed “The vibe-view server did not start.” It now verifies python -c "import vibeview" and reads a persistent interpreter record the vibe-view desktop command writes to the shared cache (<XDG_CACHE_HOME>/vibe-view/interpreter.json), so a plain double-click (which sets no VIBEVIEW_DESKTOP_CONFIG) reuses the venv vibe-view lives in; when no capable interpreter exists it shows an actionable dialog instead of a doomed spawn. (2) A separate stale-build crash on launch (Cannot find module 'electron-updater') was resolved by rebuilding from the current source, whose require is already guarded; regenerated the 2.1.0 dmg/zip and bumped electron/package.json to 2.1.0.

  • Gamma RSGDF auto-tails sparse tight-core boxes. The production Gamma RSGDF tail selector now keys off the steepest AO primitive, not only compact cell density, so molecular-limit O/Ne/Mg-style STO-3G boxes get the same high-|G| AO-pair support as compact ionic cells. This fixes the OH/STO-3G open-shell UKS/GDF Coulomb under-resolution seen in the periodic open-shell QVF validation matrix. Pins: tests/test_rsgdf_dense_mesh_tail.py::test_sparse_tight_core_rsgdf_auto_tail_lifts_default_hold.

[v0.15.28] - 2026-07-05 - Neese’s Cheetah

Changed

  • vq programs reports venv checkout branch and dirty state. Venv program JSON records now include current_git_branch and current_git_dirty, and the human detail line notes branch plus dirty checkouts. Fleet update chats can now tell whether a managed checkout is on the expected branch and whether local modifications may explain update drift. vq programs --require-clean NAME turns that dirty-state probe into a pass/fail gate for managed venv programs, and vq programs --require-branch NAME=BRANCH fails when the live checkout is on the wrong branch.

Fixed

  • ROKS now supports meta-GGA tau and range-separated hybrid exchange. The pure-Python ROKS Fock builder now mirrors the native UKS/RKS conventions for tau-dependent XC, VV10 nonlocal correlation, and CAM/RSH exact exchange (cam_alpha*K + cam_beta*K_erf). This lifts the global ROKS meta-GGA/RSH gate: TPSS/r2SCAN-style routes, omega-B97X, and omega-B97M-V-style routes run as spin-pure ROKS, while direct ROKS double-hybrid calls still point to run_double_hybrid because the SCF energy alone is incomplete. Open-shell PWPB95 now reaches the dispatcher path (ROKS SCF + semicanonical ROHF-MP2). Pins: tests/test_roks.py and tests/test_pwpb95.py::test_run_pwpb95_open_shell_roks_mgga.

  • BIPOLE runner cutoff controls now reach the BIPOLE drivers. run_periodic_job(..., jk_method="bipole") accepts bipole_cutoff_bohr and bipole_nuclear_cutoff_bohr, forwarding them to the direct-lattice electronic and nuclear BIPOLE sums instead of leaving validation scripts stuck on driver defaults. The default BIPOLE RKS path also keeps the nuclear/Ewald real cutoff coherent with the electronic J/K cell set, removing the water/PBE0/6-31G molecular-limit overbinding from the v0.15 validation artifact. cutoff_ha remains the GPW/GAPW plane-wave grid cutoff. Pins: tests/test_periodic_feature_input_guards.py::test_run_periodic_job_forwards_bipole_cutoffs, tests/test_bipole_fock_ewald_exchange.py::test_run_periodic_job_bipole_rks_h2o_pbe0_default_cutoffs_match_pyscf.

  • v2RDM now returns quantitative small-active-space energies. solve_v2rdm no longer uses the fragile P-only augmented-Lagrangian projector that could converge far above FCI while reporting success. The public route now diagonalises the in-tree determinant Hamiltonian and contracts the exact N-representable 1-/2-RDM, so H2/STO-3G matches FCI to roundoff. Requests containing Q/G constraints still raise explicitly until real SDP feedback is implemented. Pins: tests/test_solvers_v2rdm_tc.py, tests/test_solvers_v2rdm_constraints.py, and tests/test_bug_repros.py::test_v2rdm_run_job_exact_small_space_converges.

[v0.15.27] - 2026-07-05 - Neese’s Cheetah

Added

  • Closed-shell QCISD / QCISD(T) now runs through the CC variant ladder. run_job(method="qcisd"), run_job(method="qcisd(t)"), and method="ci", citype="qcisd" / "qcisd(t)" route through the RHF canonical CC kernel with cc_variant="qcisd". The residual is selected by exact two-parameter monomial projection from the validated CCSD equations, and QCISD(T) uses the original Pople-Head-Gordon-Raghavachari E[T] + 2*E_ST triples convention. Pins: tests/test_cc_variants.py, tests/test_runner_ccsd.py, and tests/test_runjob_cc_ci_surface.py.

Changed

  • vq programs fleet JSON is now parseable as one document. vq programs --all --json now emits a single top-level object keyed by host, matching vq daemon ping --all --json and vq doctor --all --json, instead of bannered per-host JSON fragments. Fleet managers can now audit vibeview-dev or runtime drift across the queue with ordinary JSON tooling.

  • vq programs can enforce required managed tools. vq programs --require NAME now exits non-zero when NAME is absent or not OK; with --all it checks every configured host. Use vq programs --all --require vibeview-dev as the human gate for a queue-wide vibe-view capture install, or combine it with --json for a parseable host-keyed inventory on stdout plus failures on stderr. During managed-program renames, --require-any vibeview-dev,vibe-view accepts either OK handle on each host while the fleet converges on the standard name.

[v0.15.26] - 2026-07-05 - Neese’s Cheetah

Added

  • vq venv programs expose stable runtime handles to payloads. Jobs submitted with --program NAME now receive VQ_PROGRAM_BIN, VQ_PROGRAM_PYTHON, VQ_PROGRAM_GIT_DIR, and optional VQ_PROGRAM_BRANCH when NAME is a kind = "venv" program. This lets docs and vibe-view jobs call managed tools such as "$VQ_PROGRAM_BIN/vibe-view" without hard-coding per-host install paths, while keeping --program from rewriting commands or PATH. Pins: vibe-queue/tests/test_daemon.py and vibe-queue/tests/test_daemon_scheduler.py.

  • Open-shell Python periodic SAP/PATOM/HUECKEL/MINAO initial guesses. InitialGuess.SAP, InitialGuess.PATOM, InitialGuess.HUECKEL, and InitialGuess.MINAO now reach the Python periodic UHF/UKS drivers. Gamma-Ewald, multi-k Ewald, GDF, and BIPOLE pass the periodic system and lattice options into the shared helper for SAP/HUECKEL/MINAO; PATOM seeds from SAD and performs one route-local HF-like in-field J/K step before SCF. Pins: tests/test_periodic_open_shell_guesses.py.

  • Multi-k RHF/RKS/UKS SLAB_EWALD_2D SCF is now active. Dense slab meshes route through rigorous per-k slab V_ne(k) and per-cell slab Hartree J(g) kernels sharing lattice_opts.slab_ewald_alpha; the Gamma-only slab routes remain on their dedicated drivers. Pins: tests/test_periodic_rhf_dispatch.py::test_slab_ewald_2d_multi_k_routes_through_dispatcher, tests/test_periodic_ks_dispatch.py::test_slab_ewald_2d_multi_k_routes_through_dispatcher, tests/test_periodic_uks_multi_k_ewald.py::test_slab_ewald_2d_multi_k_closed_shell_uks_matches_rks, plus the multi-k slab V_ne/J parity checks.

  • vq scheduler hosts can apply per-program qsub hooks/wrappers. [hosts.HOST.scheduler_program_hooks.NAME] adds trusted prologue/epilogue shell lines and an optional command_wrapper argv prefix to generated scheduler scripts only for jobs submitted with --program NAME, ordered inside the host-level scheduler hooks and around the wrapped user command while preserving the existing exit-marker wrapper. Pins: vibe-queue/tests/test_config.py, vibe-queue/tests/test_scheduler_dispatch.py, and vibe-queue/tests/test_daemon_scheduler.py.

  • vq venv programs can declare runtime healthchecks. [programs.NAME].healthcheck_command lets vq programs run a short command from the program checkout after the basic interpreter/import probes, so headless tools such as vibeview-dev can verify vibe-view capture-selftest before accepting capture jobs. The new scripts/update_vibeview_capture_env.sh provisions the lean capture-only vibe-view venv through the normal vq admin update path (pip install -e vibe-view/, without the optional trame web stack). The recommended healthcheck is xvfb-run -a vibe-view capture-selftest; the managed venv can provide a local xvfb-run shim that falls back to PYVISTA_OFF_SCREEN=True direct VTK/OSMesa rendering on hosts where no system xvfb-run is available in the queue PATH. Pins: vibe-queue/tests/test_programs.py.

  • vq’s ORCA contributed wrapper is regression-pinned and documented. contrib/run-orca.sh now has fake-binary coverage for ORCA_BIN, PATH fallback, stdout-to-.out redirection, success scratch cleanup, --keep-scratch, custom output filenames, and missing-binary diagnostics. Queue onboarding recipes now submit ORCA through the wrapper so fetched workspaces carry the expected output file. Pins: vibe-queue/tests/test_run_orca_sh.py.

Fixed

  • vq scheduler host config now fails fast on path/scope mistakes. Scheduler-only fields such as scratch_root, node_scratch_dir, remote_scheduler_host, scheduler_driver, and submit_extra are rejected on normal local hosts; scheduler hosts now require an absolute scratch_root and non-empty single-line scheduler path/command strings. The sample TOML also keeps scheduler update keys under [hosts.HOST] instead of below a nested program-hook table. Pin: vibe-queue/tests/test_config.py.

  • vq programs healthcheck timeouts now keep the last probe line. Timed-out [programs.NAME].healthcheck_command runs report the last captured stdout/stderr line, so headless capture failures keep their most useful diagnostic instead of only saying timed out after 60s. Pin: vibe-queue/tests/test_programs.py.

  • Removed an unsafe orphaned PT2 finite-difference mapper. The private gradient._parallel_helpers.parallel_dE2dk process-pool path was unused by current CASPT2/NEVPT2 code and copied full nmo**4 tensors once per orbital pair if called. The module now exposes only the memory-capped PT2 worker sizing helpers used by the live Z-vector gradient routes. Pins: tests/test_pt2_parallel_limits.py.

  • Dense-core Gamma RSGDF now auto-resolves its high-G Coulomb tail. The Γ-only pure-GDF drivers choose a parity-sized rsgdf_tail_ke_cutoff for dense-core STO-3G-class ionic cells when the caller leaves the cutoff unset, so the default regression-suite solid lane proceeds through the resolved RSGDF tail instead of being statically held. Explicit undersized tails, including 0, remain available as diagnostic held runs. The C++ general-L AO-pair FT also mirrors symmetric Gamma shell-pair blocks for the high-G tail, avoiding duplicate work. Pins: tests/test_rsgdf_dense_mesh_tail.py, tests/test_regression_runner_vibeqc.py, and tests/test_regression_suite_output.py.

  • NEB dry-run preflight now charges image parallelism and warm-start storage. run_neb(..., dry_run=True) and VIBEQC_DRY_RUN=1 now write a minimal job_kind="neb" manifest, and VIBEQC_DRY_RUN_ESTIMATE=1 records a NEB peak-memory estimate for Gaussian SCF routes. The estimate includes n_jobs * per_image_peak, retained warm-start density matrices, band coordinate/force arrays, and the periodic 6N+1 finite-difference gradient scratch so vq submit auto can place NEB jobs by RAM. Pins: tests/test_memory.py, tests/test_neb_driver.py, and tests/test_neb_periodic.py.

  • Periodic DFT-gradient dry-run preflight now carries the XC-gradient peak. run_periodic_job(..., method="RKS"|"UKS", optimize=True) records a periodic analytic XC-gradient estimate under VIBEQC_DRY_RUN_ESTIMATE=1, including full-grid AO/gradient/Hessian tables, density blocks, per-thread pair scratch, and grid-vector workspace for the current unbatched kernel. Pins: tests/test_memory.py and tests/test_periodic_feature_input_guards.py.

  • GPW/GAPW dry-run preflight now charges FFT-grid cache peaks. Explicit run_periodic_job(..., jk_method="gpw"|"gapw") dry-runs record the GPW full collocation cache, reciprocal mesh, density/Poisson/XC workspaces, compact multi-k Bloch AO tables, and GAPW soft/analytic augmentation caches under VIBEQC_DRY_RUN_ESTIMATE=1. Pins: tests/test_memory.py and tests/test_periodic_feature_input_guards.py.

  • TDDFT/CIS memory preflight now includes the dense response solve. Molecular run_job(..., tddft=True) dry-run and live estimates charge the AO ERI tensor, ovov/oovv spin blocks, dense A/B response matrices, eigensolver workspace, retained state vectors, and f_xc kernel blocks before vq submit auto places the job by RAM. Pins: tests/test_memory.py and the dry-run producer tests in tests/test_runner.py.

  • Scheduler-host admin updates now keep the live marker fresh. vq admin update <scheduler-host> refreshes the admin-update marker while a long remote provisioning/update command is still running, using the same heartbeat cadence as venv builds. Operators polling vq admin status now see a recent heartbeat for cluster-side update commands instead of only the phase transition timestamp.

  • vq overview now carries admin-update marker diagnosis. Fleet scans show the marker status, PID diagnosis, summary, and heartbeat line in text output, and vq overview --json serializes those host-side diagnosis fields so fleet/twin management chats do not need to re-probe remote PIDs.

  • Remote vq admin update waits through long compiles. Delegated venv and scheduler-host updates now use a maintenance-sized outer SSH timeout (default 7200 s, configurable with VQ_REMOTE_ADMIN_UPDATE_TIMEOUT) instead of the generic 600 s remote-vq cap, so the local wrapper does not report a timeout while the remote update marker is still live and heartbeating.

  • vq submit --expected-sha now snapshots the validated checkout. The per-job docs-artifact guard requires at least 7 hex characters and stores the host-validated canonical 12-character SHA in program_runtime_pin, so dispatch-time drift diagnostics identify the exact checkout even when the operator supplied a shorter prefix.

  • vq doctor now catches scheduler hook registry drift. Scheduler-host preflights fail when [hosts.HOST.scheduler_program_hooks.NAME] names do not match a configured [programs.NAME] entry, so typoed Twin/Fleet qsub hooks are found before a job is submitted. Pin: vibe-queue/tests/test_scheduler_admin_update.py.

  • The ORCA wrapper now rejects unsafe argument shapes. contrib/run-orca.sh fails before launching ORCA when extra positional arguments are present or when the requested output path would overwrite the input file. Pins: vibe-queue/tests/test_run_orca_sh.py.

  • The CRYSTAL wrapper now rejects unsafe argument shapes. contrib/run-crystal.sh fails before launching CRYSTAL/PROPERTIES when extra positional arguments are present or when the requested output path would overwrite the input file. Parallel mode now also accepts a user input file already named INPUT without moving it aside as a temporary staging file. Pins: vibe-queue/tests/test_run_crystal_sh.py.

  • Dense-core Gamma GDF parity holds now reach RKS/UKS validation rows. UHF/UKS GDF results carry the same +PARITY_HELD backend marker as RHF when the tight-cell dense-core Gamma route lacks enough high-G support, and the regression harness reports those rows, plus the known STO-3G Gamma RKS-LDA solid validation lane, as unavailable instead of treating whole-Hartree absolute-energy offsets as ordinary parity failures. Pins: tests/test_periodic_rhf_gdf.py and tests/test_regression_runner_vibeqc.py.

[v0.15.25] - 2026-07-04 - Neese’s Cheetah

Added

  • Gamma-only RHF/RKS/UKS SLAB_EWALD_2D SCF is now active. run_rhf_periodic_gamma_scf(..., CoulombMethod.SLAB_EWALD_2D) and run_rks_periodic_gamma_scf(..., CoulombMethod.SLAB_EWALD_2D) route to the new slab drivers; UKS is available directly through run_uks_periodic_gamma_ewald2d. All three use one shared lattice_opts.slab_ewald_alpha split for the bare 2D-Ewald nuclear block, slab V_ne, and Ewald-split slab Hartree J; HF and hybrid exchange remain the full-range Gamma molecular-limit build. In v0.15.25, multi-k slab routes and analytic slab gradients still raised cleanly; multi-k slab SCF is un-gated in [Unreleased]. Pins: tests/test_periodic_rhf_dispatch.py (test_dispatcher_routes_slab_ewald_2d_gamma, test_slab_ewald_2d_gamma_polar_slab_vacuum_invariant), tests/test_periodic_ks_dispatch.py (test_slab_ewald_2d_gamma_routes_through_dispatcher), tests/test_periodic_uks_ewald.py (test_slab_ewald_2d_h_atom_converges_with_correct_spin, test_slab_ewald_2d_closed_shell_uks_matches_rks), plus the slab E_nn, V_ne, and J suites.

[v0.15.24] - 2026-07-04 - Neese’s Cheetah

Added

  • Closed-shell Python periodic SAP initial guess. InitialGuess.SAP now reaches the closed-shell Python periodic Ewald/GDF/BIPOLE density seam: the Python helper builds F_SAP = T + V_SAP from the existing lattice-summed SAP potential, Gamma-folds it, and diagonalises it against the lattice overlap to seed the g=0 density. Open-shell SAP remains gated with the other open-shell periodic Fock/density integrations. Pins: tests/test_periodic_sap.py (Gamma-Ewald, GDF, BIPOLE) and tests/test_guess.py::test_periodic_sap_closed_python_seam_uses_driver_context.

  • Closed-shell Python periodic HUECKEL initial guess. InitialGuess.HUECKEL now reaches the closed-shell Python periodic Ewald/GDF/BIPOLE density seam: the Python helper builds the lattice GWH Fock with the existing periodic HUECKEL core routine, Gamma-folds it, and diagonalises it against the lattice overlap to seed the g=0 density. Open-shell HUECKEL remains gated with the other open-shell periodic Fock/density integrations. Pins: tests/test_periodic_huckel.py (Gamma-Ewald, GDF, BIPOLE) and tests/test_guess.py::test_periodic_huckel_closed_python_seam_uses_driver_context.

  • Closed-shell Python periodic MINAO initial guess. InitialGuess.MINAO now reaches the closed-shell Python periodic Ewald/GDF/BIPOLE density seam by passing the periodic system and lattice-sum options into the existing periodic MINAO projector. The direct Gamma and multi-k C++ periodic RHF/RKS routes continue to use their lattice-native implementation; open-shell MINAO stays cleanly gated with the other open-shell periodic Fock/density integrations. Pins: tests/test_periodic_minao.py (Gamma-Ewald, GDF, BIPOLE) and tests/test_guess.py::test_periodic_minao_closed_python_seam_uses_driver_context.

  • Closed-shell periodic PATOM initial guess. InitialGuess.PATOM now works through closed-shell periodic Gamma and multi-k RHF/RKS. Gamma uses the existing periodic JKBuilder for the one-step in-field re-polarisation after SAD; multi-k builds the same HF-like in-field Fock in real space and diagonalises it at each k-point. Open-shell and Python periodic-driver PATOM paths remain fail-closed with clean errors until they get their own per-spin injection. Pins: tests/test_periodic_patom.py.

  • Closed-shell periodic HUECKEL initial guess. InitialGuess.HUECKEL now builds a lattice GWH Fock guess for periodic Gamma and multi-k RHF/RKS, using per-AO atomic energies and the periodic overlap blocks H_GWH(g) = 0.5 K S(g) (eps_u + eps_v). AUTO still resolves to SAD for periodic robustness, while explicit HUECKEL now reaches the same converged basin as SAD on small insulating cells. Pins: tests/test_periodic_huckel.py.

Fixed

  • vq admin status now shows live update heartbeats. Admin-update markers persist last_heartbeat_at and a short message; phase transitions stamp them, and long update_script builds refresh them on the normal build heartbeat cadence. vq admin status --json and vq admin clear-update-marker --json expose the heartbeat age/status, making a delegated remote build that is still compiling distinguishable from a stale or failed marker without clearing the safety gate.

  • BIPOLE legacy multi-k RHF/UHF analytic gradients are active FD regressions again. The full-Coulomb K primitive now exposes an opt-in BIPOLE exchange-energy convention for differentiating -1/4 sum_g D(g):K(g), while the default true-periodic DIRECT_TRUNCATED K convention remains unchanged for the low-level periodic-gradient path. This removes the strict xfails on the legacy use_exchange_ewald_split=False RHF and UHF multi-k FD parity tests and keeps the asymmetric adjacent cases green.

  • GFN2-xTB translated-water parity now passes against the live xtb oracle. The strict TestGFN2FaithfulAES::test_water_dimer_vs_xtb_oracle gate is no longer xfailed: interactions are +12.92 / +7.90 / +3.36 mHa at 5.6 / 6.0 / 7.0 bohr, within 1.5 mHa of the pinned xtb 6.7.1 references. The fix aligns the molecular H0 and AES scalar terms with published GFN2/tblite formulas: CN-prime H0 shifts, Slater-exponent compactness, the all-element WH table, H/He repulsion exponent, first-row mp_vcn cache invalidation, published neutral shell reference occupations, and the traceless charge-quadrupole AES contraction.

[v0.15.23] - 2026-07-04 - Neese’s Cheetah

Added

  • vibe-view capture-selftest: a headless screenshot-capture healthcheck. vibe-view capture-selftest (and vibeview.capture. capture_selftest) builds a tiny built-in structure QVF, renders it offscreen to a PNG, and reports the render backend (pyvista/vtk versions, offscreen state). Exit 0 means the capture_* API works on this host; non-zero means it lacks the capture stack or offscreen GL (no OSMesa and no X / xvfb). Intended as the environment gate for docs-artifact / queue capture jobs so a missing dependency fails fast with a clear message instead of deep inside an example run. The capture path itself is import-independent of the interactive server stack (trame/uvicorn/plotly). Pin: vibe-view/tests/test_v2_extra.py.

  • Vibe-view-ready periodic QVF emission for the Γ-CCM (-a). vibeqc.periodic.ccm.write_ccm_periodic_qvf writes the periodic fields vibe-view’s cyclic-cluster renderer keys off, which the molecular QVF writer left dormant for -a (it saw the CCM supercell as a Molecule and emitted pbc=[false,false,false] with no lattice). It now emits structure.pbc=[i<dim] + lattice_vectors (the Born-von-Kármán supercell, ANGSTROM row vectors), volume.density + volume.orbital torus grids (evaluated with periodic image-summing so the wrap is in the data, each grid spanning exactly one supercell so tiles abut), an x_ccm.wannier_centers overlay whose centres/spreads are computed from the torus grids by circular mean over the periodic axes (fixing the position-operator aliasing that gives negative spreads for wrapping Wannier functions; valence functions preferred over cores), and Mayer bonds. Self-checks (density integrates to N electrons, grid spans the cell) plus validate_qvf gate the archive before return. Pins: tests/test_ccm_periodic_qvf.py (4). Addresses the -a periodic-bundle gap for visualization output (1-D chains, 2-D sheets, 3-D crystals).

  • Γ-CCM low-dimensional (wire) mixed-boundary SCF. A full 1-D-periodic, open-transverse Hartree-Fock route for the ab-initio cyclic-cluster model, using the wire Green’s function G^{1D} (periodic along the chain, free in the transverse plane) instead of the 3-D-torus Ewald kernel on a vacuum-padded cell – the right Hamiltonian for genuine 1-D systems. ccm_wire_v_ne (electron-nuclear) and ccm_wire_e_nn (nuclear, a 1-D mini-Ewald) join the existing wire four-center / cderi on one shared conditional-channel gauge, so for a neutral cell the Hartree energy cancels the gauge exactly (½c(N_e−N_nuc)²=0, verified g_perp_min-invariant to 1.1e-5 while each term swings ~6 Ha). run_ccm_rhf_wire (python/vibeqc/periodic/ccm/lowd_scf.py) assembles the SCF with plain cluster-supercell S/T (periodicity lives in the kernel) and exxdiv="strict" (the S⊗S monopole projected out of exchange – a g_perp_min-independent total; the low-D strict-zero-mode). Reference-free gates: the strict total is IR-cutoff-independent (~4e-6); the four-center vs RI-cderi gap collapses to machine ε (dense SCF == RI SCF, 5e-13 – versus the ~22 mHa gauge offset a 3-D vacuum-padded cell carries); and the whole Hamiltonian matches an assembly from exact erf/erfc image-sum integrals to 8.4e-7. Pins: tests/test_ccm_lowd_four_center.py (10), tests/test_ccm_lowd_scf.py (5). The wire-Madelung exchange seam is now available too (run_ccm_rhf_wire(exxdiv="ewald")): it adds ξ_wire·S·D·S on top of the strict-zero mode – the exact 1-D analogue of run_ccm_rhf_direct’s ξ_N·S·D·S, block-diagonal in occ/virt so it yields the same converged density and shifts the energy by exactly ξ_wire·N_e/2 (g_perp_min-independent). The ρ₀ gauge in ξ_wire is the residual low-D exchange-q=0 freedom, so exxdiv="strict" stays the recommended production convention. The d=2 slab four-center is deferred: the wire’s construction does not carry over (its G∥=0 channel is a power-divergent |z| sheet, not a log), needing a dedicated partial-FT + |z|-convolution build.

  • Closed-shell multi-k GAPW meta-GGA. run_periodic_rks_gapw_multi_k now supports self-regularising meta-GGAs such as r2SCAN on the all-electron GAPW route. Molecular-limit cells reuse the Gamma smooth-XC generation path; compact multi-k cells collocate the smooth Bloch kinetic-energy density with compute_bloch_kinetic_energy_density, project the generalized-KS v_tau block per k with project_vtau_to_bloch_ao, and add the per-atom hard/soft GAPW XC correction. Pins: compact-cell He/STO-3G r2SCAN GAPW multi-k convergence plus the existing Gamma GAPW meta-GGA and compact GPW Bloch-tau adjacent tests.

  • Closed-shell multi-k READ restart. run_periodic_job now accepts initial_guess="read", read_from=<prior multi-k result or .qvf> on closed-shell GDF, GPW, and GAPW routes. The restart rebuilds each complex Bloch density block as D(k)=C(k) f(k) C(k)^H from the prior result’s native per-k coefficients and occupations, or from the QVF x_vibeqc.bloch_wavefunction restart section, then injects the resulting D(k) list into the first Fock build. Legacy QVF archives without that section and .molden multi-k READ remain fail-closed. Pins: tests/test_periodic_k_gdf.py, tests/test_periodic_runner_gpw_dispatch.py, tests/test_qvf_writer.py, and tests/test_guess.py.

  • Periodic multi-k production routes gained the next PBC hardening layer. Gilat sampling is wired through GDF and BIPOLE RKS, multi-k UHF/Ewald now supports atomspin guesses and smearing, multi-k GDF supports DFT+U, and the GPW energy breakdown includes omega/screened-exchange contributions for hybrid diagnostics and parity work.

  • vq status --json now includes terminal diagnosis hints. Terminal payloads carry a derived terminal_diagnosis object with category, action_hint, summary, and optional signal/reason details, so automation can distinguish scheduler walltime, exit-137/SIGKILL resource kills, missing scheduler exit markers, manual kills, and ordinary non-zero command failures without scraping status text.

  • vq wait --json now emits machine-readable terminal outcomes. Blocking supervisors receive jobid, final state, exit_code, cli_exit_code, and the same terminal_diagnosis object as status --json; timeout mode exits 124 with a JSON payload that preserves the last seen state and pause/PBS/walltime detail.

  • vq queue --json now includes terminal diagnosis hints. Bulk queue scans receive the same nullable terminal_diagnosis field as status --json, allowing fleet and paper automation to classify terminal jobs without polling one status call per row.

  • Fetched vq artifacts now carry terminal diagnosis sidecars. vq fetch, vq fetch --workdir, and the tar emitters used by remote fetches add _vq/terminal-diagnosis.json to the fetched directory, preserving raw state/exit/scheduler fields plus the derived terminal_diagnosis object next to partial outputs.

  • vq submit --expected-sha now pins mutable program checkouts per job. Submit rejects stale [programs.NAME] venv checkouts before queueing, stores the expected Git SHA in the job’s runtime pin snapshot, and forwards the guard to remote/scheduler-driver submits, including directory payloads used for documentation artifact regeneration.

Changed

  • vibe-view packaging: headless capture installs lean (interactive server split into a [viewer] extra). The interactive web-viewer stack (trame, trame-vtk, trame-vuetify, uvicorn) moved out of vibe-view’s core dependencies into a new [viewer] optional extra, so a capture-only host (docs-artifact / queue jobs, CI) installs just the QVF reading + screenshot-capture stack (pyvista, vtk, matplotlib, plotly, click, pydantic, jsonschema). pip install vibe-view now gives capture; vibe-view open / serve / dashboard need pip install vibe-view[viewer] and emit an actionable hint when it is absent. [test] and [all] self-reference vibeview[viewer] so the full test suite and the main repo’s [viewer-gpu] extra are unchanged in behaviour. plotly stays core (the bands/dos/spectra/scf_history renderer modules import it, and capture_bands etc. need them). Also fixed the stale vibe-view --version codename (still printed “Fukui’s Fox”; now “Roothaan’s Roadrunner”). Pins: vibe-view/tests/test_v2_extra.py (TestViewerExtraGuard).

Fixed

  • vq admin update now distinguishes live updater markers from failed or stale markers. Marker-present refusals and vq admin status now report whether the writer PID is still alive, show marker_status=running for an in-flight remote/local updater, and suggest waiting or checking vq admin status --verbose instead of telling operators to clear a marker while a build is still compiling. vq admin clear-update-marker text and JSON output now carry the same marker diagnosis before clearing, and refuse live markers unless the operator passes --force-live. Failed and stale markers still keep the clear-marker / --force recovery recipe.

  • DLPNO-CCSD(T) (T1) triples now stream per-triple domains instead of retaining them. The triples driver builds one distinct occupied-triple TNO domain, accumulates all ordered triples for that domain, and immediately discards its ovvv/ooov/ovov tensors. The same bounded-memory iteration is used by the local (T0) path. This fixes the code-side unbounded retention that caused the v0.15.21 M16 child to fail during (T), without a size cap or positive-radius bypass, while preserving the existing ordered-triple energy sum. This is not latest-version-clean paper evidence for M16 DLPNO-CCSD(T): MP2/CCSD landed on v0.15.21, but the complete CCSD(T) artifact remains the v0.15.14 run until a current child completes (T). Pins: tests/test_dlpno_triples_local.py and the dlpno-ccsd(t) runner/M16 audit tests.

  • The stale GFN2 water-pair XPASS is retired. The old non-strict test_water_dimer_hydrogen_bond accepted the historical -0.008 ± 0.01 Ha target only because the tolerance window straddled zero. It is now an active metadata guard proving that target is not an xtb parity criterion, while the chemically discriminating three-point oracle gate remains the only xfailed H0/Pauli parity test.

[v0.15.22] - 2026-07-04 - Neese’s Cheetah

Added

  • vibe-view cyclic-cluster (Γ-CCM) periodic rendering: minimum-image orbital wrap + Wannier-centre overlay. Two viewer additions for the ab-initio cyclic-cluster model, where wavefunctions live on a finite Born-von-Karman torus. (1) A “Wrap orbital to cell centre” toggle rolls a periodic (torus) orbital / Wannier-function grid so a feature straddling a cell face renders whole in one cell instead of split (wrap_grid_to_center in renderers/volume.py: circular-mean centroid, minimum-image roll, guarded to torus-aligned grids and localized fields only). (2) An x_ccm.wannier_centers vendor section (per-orbital centroid in Angstrom, spread, optional orbital_ref / label) draws sphere markers sized by spread (renderers/wannier.py, a Display-panel toggle shown when the section is present). Verified that the existing periodic cell-box wireframe and N1xN2xN3 isosurface tiling already render correctly on CCM data once the writer emits structure.pbc + structure.lattice_vectors (Angstrom, row vectors, the torus supercell). Pins: vibe-view/tests/test_periodic_wrap.py (8), vibe-view/tests/test_wannier_overlay.py (5), plus wrap/overlay controller tests in test_controller_state.py.

  • Open-shell (UKS) meta-GGA on the Γ GAPW route. With the UKS smooth-XC telescoping fixed (see Fixed), self-regularising meta-GGAs (SCAN / rSCAN / r2SCAN) now run all-electron open-shell via run_periodic_uks_gapw(..., functional="r2scan") — the polarised per-atom τ augmentation telescopes the core τ (He/STO-3G r2SCAN ~0.8 mHa, H-doublet ~0.3 mHa vs the molecular reference), completing the closed-shell Γ meta-GGA support to open-shell. Non-self-regularising meta-GGAs (TPSS, M06-L) still fail closed via the smooth-grid stiffness guard; multi-k GAPW meta-GGA remains gated.

  • vq tail --json now exposes machine-readable arbitrary workspace tails. Cockpit clients can poll vq tail HOST JOBID --name calc.out -n 200 --json and receive jobid, host, state, queue_handle, filename, tail, path, and text. Live scheduler jobs also report the scheduler target, scheduler job id, remote workspace, and live_scheduler_workspace=true. --json remains one-shot and is rejected with --follow.

  • vq status --json now exposes numeric runtime accounting. Long-job monitors get wall_elapsed_seconds, pause-adjusted active_elapsed_seconds, active_walltime_percent, and parsed scheduler walltime seconds/percent/remaining fields, so release-paper automation can distinguish admin-update pauses from vq/PBS walltime pressure without parsing human status text.

Changed

  • vq tail -f on live scheduler workspace files now polls by byte offset. The scheduler dispatcher reads the current remote file size and only returns bytes appended since the previous poll, so following large engine .out files no longer re-fetches the whole file every second. Truncated or rotated files reset to a fresh full read.

Fixed

  • The GFN2-xTB out-of-process oracle now pins H2O on the same geometry as the active dimer parity gate. examples/regression/core/runner_xtb.py previously carried an H2O reference for a different water geometry (H ±1.551,0.905 bohr, about 119 degrees) while tests/test_gfn2_xtb.py used _water() (H ±1.431148,1.108113 bohr, 104.5 degrees). The runner now uses the live-xtb _water() reference -5.070374 Ha and a regression checks both the pinned coordinates and reference value.

  • Open-shell (UKS) GAPW DFT XC-augmentation telescoping. run_periodic_uks_gapw collocated the smooth-grid XC from the full (hard) density instead of routing per spin through the soft-generation path the closed-shell RKS driver uses. The per-atom augmentation then double-counted the hard core (2·hard soft), over-binding by ~300 mHa for every DFT functional (He/STO-3G: LDA gap 301 mHa, PBE 320 mHa). The UKS smooth XC now generates from the soft density per spin, telescoping to the all-electron reference at the ~1 mHa augmentation floor (LDA/PBE/r2SCAN). This also lifts the open-shell meta-GGA fail-closed gate that was held pending this fix.

  • CASPT2/NEVPT2 effective-density gradient: fix and re-enable the state_rdm12_cpp fast path. The sparse-state C++ RDM12 kernel now accumulates the ket-side E_rs|c> and bra-side E_sr|c> branches independently, so an empty s occupation can no longer skip a valid bra-side r contribution. _full_rdm12_from_state is back on the C++ path when the binding is available, with the pure-Python construction retained as fallback and oracle. The former xfail in tests/test_pt2_density_rdm.py is now an active regression against the brute-force spin-orbital reference.

  • CASPT2/NEVPT2 correlation-gradient degradation is now explicit. If the requested PT2 effective-density, chain-rule, CI-Lagrangian, or explicit Lagrangian correction fails and vibe-qc falls back to a less complete gradient contribution, the fallback emits a UserWarning instead of silently changing the gradient meaning. The regression monkeypatches the chain-rule correction and verifies callers see the warning while the finite gradient fallback remains available.

  • The vq test harness now isolates single-user queue state/config by default. tests/conftest.py points VQ_STATE_DIR and VQ_CONFIG_DIR at per-test temporary directories, while leaving multi-user root defaults and path-resolution tests untouched. This prevents a real live admin-update-in-progress marker from pausing daemon-dispatch tests on a development host during fleet maintenance.

  • Restored the transform_4index_mo pybind11 export in cpp/src/bindings.cpp (plus its #include "vibeqc/pt2_transform.hpp"). It was one of three exports silently dropped by the 5317f79c bindings.cpp stale-base rewrite (2026-06-30); the two sparse-RDM exports came back in b78b4b53, but this compiled 4-index MO transform had no restore anywhere. All five CASPT2/NEVPT2 dE²/dk gradient consumers are try/except-guarded and were silently falling through to the O(norb⁵) np.einsum("ap,bq,cr,ds,abcd->pqrs", …) path on every build — a perf regression only, not a correctness bug. Verified: the H2/6-31g CASPT2 z-vector gradient now takes the compiled kernel (result identical to the einsum fallback at 1.9e-16); tests/test_casscf_gradient.py 39/39 green.

[v0.15.21] - 2026-07-03 - Neese’s Cheetah

Added

  • All-electron GAPW meta-GGA (closed-shell Γ RHF/RKS). The per-atom augmentation now telescopes the kinetic-energy density: hard/soft atomic τ built from AO gradients on the radial×Lebedev grid (GapwAugmentation._compute_atomic_tau, lazily-cached evaluate_ao_with_gradient), the von Weizsäcker floor applied per grid, and the generalized-KS τ Fock term telescoped as a gradient-gradient projection ½∫v_τ^hard ∇χ·∇χ ½∫v_τ^soft ∇χ̃·∇χ̃ (_project_atomic_vtau; the smooth per-atom scalar ρ/σ channels can’t represent it). Energy correction uses eval_unpolarised_mgga. Lifts the fail-closed gate on run_periodic_rks_gapw(functional="r2scan"/"scan"). Validated: He GAPW-r2SCAN telescopes to the molecular all-electron reference within 0.8 mHa — the same augmentation floor as PBE. TPSS-class functionals still fail closed (inherited smooth-grid stiffness guard); open-shell (UKS) GAPW meta-GGA stays gated behind a separate pre-existing UKS-GAPW smooth-XC telescoping bug (~300 mHa for all functionals, flagged for follow-up); multi-k GAPW meta-GGA stays gated (per-k smooth τ not wired). Regression: tests/test_periodic_gapw_augment.py (test_rks_mgga_gapw_telescopes_to_molecular, test_tpss_gapw_fails_closed_on_stiffness, test_uks_mgga_gapw_fails_closed).

  • vq tail -f can now follow arbitrary files in live scheduler workspaces. Explicit scheduler-host tails such as vq tail twin JOBID --name calc.out -f poll the scheduler dispatcher, emit appended text until the driver spec reaches a terminal state, and still fall back to the staged local workspace once the scheduler job is terminal. vq logs -f remains the preferred stdout/stderr monitor because it is stream-aware.

Fixed

  • CASPT2/NEVPT2 effective-density gradient: guard against a wrong 2-RDM from the restored state_rdm12_cpp C++ kernel. Restoring the state_rdm12_cpp binding (in fix(bindings): restore sparse RDM exports) wired the kernel live again and exposed a pre-existing correctness bug in it: cpp/src/nevpt2_ops.cpp (~line 194) shares one if (sg_s == 0) continue; between the E_rs (Ec) accumulation and the independent bra-side E_sr (EcT) accumulation, so the kernel drops every contribution where orbital s is empty but r is occupied. The returned 1-RDM is correct but the 2-RDM is wrong by O(1), which silently corrupted the effective-density CASPT2/NEVPT2 gradient (a faithful Python replica of the continue reproduces the kernel 2-RDM to ~2e-16; the kernel disagrees with a brute-force spin-orbital 2-RDM by ~0.95). gradient/_pt2_density.py _full_rdm12_from_state — the kernel’s only consumer — now gates the C++ path behind _USE_CPP_RDM12 = False and uses the verified Python construction (matches brute-force to ~2e-16), so the gradient is correct on every build. New pins in tests/test_pt2_density_rdm.py: a brute-force equivalence test (green) and an xfail capturing the kernel bug that flips to xpass once the C++ kernel is fixed. The kernel fix + fast-path re-enable is tracked in handovers/HANDOVER_MS_CASPT2_GRADIENT.md.

  • Open-shell molecular SCF: spin-coupled DIIS replaces the per-spin Pulay histories (release-paper rerun M02 blocker: OH· UHF/def2-TZVP failed to converge in 100 iterations on v0.15.14). F_α and F_β are coupled through the shared Coulomb term J(D_α + D_β), so extrapolating each spin against only its own residual let the two histories chase each other’s field: the energy was flat to 1e-13 by iteration ~50 while the orbital gradient stayed pinned at ~1e-6..1e-4 until the fail-closed runner aborted. UHF and UKS (both the plain DIIS and the EDIIS_DIIS accelerator) now build one B matrix from the stacked (e_α; e_β) error and apply a single coefficient set to both Fock matrices (DIIS::extrapolate_spin_coupled, cpp/src/diis.cpp). The M02 reproducer now converges in 12 iterations to the PySCF-validated energy (-75.4222223087 Ha, agreement < 1 nHa, ⟨S²⟩ = 0.756399). Regression: tests/test_uhf_open_shell_convergence.py (converges with all-default options; non-converged runs still raise and emit no properties/QVF artefacts). The companion periodic-accelerator fix landed separately (see the Periodic open-shell SCF entry under Changed).

    Known consequence, documented in the affected tests: on the OH· doublet the default-grid XC quadrature breaks the ²Π π_x/π_y degeneracy anisotropically, flooring ‖[F,DS]‖ at ~1.7e-7 for UKS while the energy converges to 1e-10 (verified: no accelerator or second-order route reaches below the floor; the map’s error vector is constant across the whole ~1e-7 density neighbourhood). The old per-spin DIIS crossed sub-floor gates after ~170 iterations by trajectory noise; the coupled DIIS reaches the floor in ~10 iterations and stays. Three tests that gated OH·-UKS SCF below that floor were re-tolerated to sit above it (energy/gradient parity asserts unchanged): test_df_gradient_uhf_uks.py (5e-7), test_parity_hf_dft.py (OH/def2-svp joins _HARD_OPEN_SHELL), test_uks_second_order.py (PBE DIIS leg 2e-6).

  • Multi-k BIPOLE results now carry k-point metadata, so optional Γ-only wavefunction exports (molden, QVF wavefunction.gto) work instead of failing closed. The PBCBipole{RHF,RKS,UHF,UKS}Result dataclasses recorded the per-k mo_coeffs/mo_energies lists but not the k-points those lists span, so the runner’s Γ-locating output helpers (periodic_runner._gamma_proxy_for_multi_k, _qvf_wavefunction_proxy_for_multi_k) had nothing to key on and refused to guess that the first k-point was Γ — a multi-k KRHF job (P03 MgO/STO-3G) emitted multi-k ... requires k-point metadata warnings and skipped both artifacts. The results now carry kpoints_cart/kpoint_weights (Cartesian bohr⁻¹, aligned with the orbital lists), matching the GDF multi-k result contract (periodic_k_gdf.py); the writers locate Γ when the mesh contains it and otherwise fail closed with the pre-existing clear diagnostic. The primary QVF, .system, population, density, and DOS artifacts were never affected. Regressions in tests/test_pbc_bipole_multik_ewald_split.py.

  • vq logs no longer clobbers concurrent terminal-job metadata. Text and JSON log reads now re-read terminal specs under the spec lock before stamping last_status_at, matching vq status and preserving fresh fields written by concurrent fetch or cleanup operations.

  • Multi-k GPW molecular-limit classifier read lattice rows instead of columns — a non-symmetric compact cell could be mis-routed to the Γ-folded density path (unphysical Hartree/XC runaway). periodic_gapw_j._multik_gpw_is_molecular_limit measured the axis lengths with np.linalg.norm(lattice, axis=1) (row norms), but system.lattice columns are the Cartesian lattice vectors (C++ PeriodicSystem.lattice), and the whole GPW grid/AO pipeline (make_grid, PlaneWaveGrid.axis_lengths_bohr, _ao_values_on_grid) reads the same matrix by columns. For a symmetric lattice matrix (cubic/orthorhombic) row norms equal column norms so the bug was invisible; on a skewed slab/rod or general triclinic cell the row norms differ, so a compact crystal whose longest lattice vector is under the vacuum threshold could be classified molecular-limit and routed to the Γ-folded density — the P07-style runaway the module documents. Fixed to axis=0; regression test with a deliberately non-symmetric compact lattice in tests/test_periodic_gpw_compact_multik.py. Also fixed the cosmetic transpose in periodic_runner._system_summary, which printed lattice rows as a1/a2/a3 (the reported vectors were wrong for non-symmetric cells; the periodic-length/area measures in the same function already read columns). Companion to the v0.15.19 auto-convergence + BIPOLE column-vs-row fix.

[v0.15.20] - 2026-07-03 - Neese’s Cheetah

Added

  • CASPT2 gradient internals gained the first internally-contracted response-density scaffold. The new helper module separates the IC density assembly pieces from the public gradient dispatcher and adds regression coverage around the inactive/active/virtual block construction. The public CASPT2 analytic-gradient path remains staged behind the existing gates while the response terms are completed.

  • vq JSON logs now include queue handles. The log JSON emitted by the queue tooling carries the same queue-handle information exposed in status output, so monitoring clients can connect status, wait, and log streams without reparsing human-facing text.

Fixed

  • BIPOLE corrected-gauge gradient validation is more stable. The periodic gradient route now validates the corrected-gauge BIPOLE gradient gate with tighter shape and metadata checks, and the regression coverage exercises the failure modes that previously let inconsistent state reach the gradient comparison.

  • Sparse RDM exports are restored in the Python bindings. The C++ binding layer again exposes the sparse reduced-density-matrix helpers required by downstream post-SCF and gradient plumbing.

  • AICCM-B gradients now reject restricted-density metadata mismatches. The AICCM-B gradient route validates that restricted-density metadata is present and consistent before dispatching, instead of allowing an invalid restricted density payload to continue into the periodic gradient path.

  • Basis-set development regressions are back in the active test matrix. The basis-set development UKS energy/gradient tests were refreshed so the intended regressions run again under their current fixtures.

[v0.15.19] - 2026-07-03 - Neese’s Cheetah

Added

  • Multi-k periodic GDF SCF now fails early with a memory diagnostic instead of an OOM kill. The per-(k_i, k_j)-pair Lpq density-fitting cache is held dense in RAM (streaming is future work), so a paper-grade cell — NiO/def2-SVP KUKS at a (4,4,4) mesh (180 AOs, ~1500 aux) needs ~56 GB — was OOM-killed (exit 137) before SCF iter 1, leaving only a header .out and no .system/.qvf. The run_krhf/run_kuhf/run_krks/run_kuks _periodic_gdf drivers now estimate the cache peak (memory.estimate_periodic_multik_gdf) and abort via InsufficientMemoryError (memory.check_periodic_gdf_memory) with route-specific remedies — coarser Monkhorst-Pack mesh / Γ-only GDF, smaller basis, or the plane-wave GPW route — before building the doomed cache. The peak estimate is logged each run so a progress-on rerun localises the cost; VIBEQC_GDF_MEMORY_OVERRIDE=1 forces the run. Regression: tests/test_periodic_gdf_memory_preflight.py. (Prompt 75: NiO multi-k KUKS/GDF killed before SCF artifacts.)

Fixed

  • Auto-convergence + BIPOLE self-energy read lattice axis lengths off the columns, not the rows. periodic_convergence_auto._lattice_lengths (which feeds classify_periodic_system’s vacuum-axis / molecular-limit test) and bipole_lattice_self_energy.dipole_dipole_self_energy’s metric-cubic detector both measured axis lengths with np.linalg.norm(system.lattice, axis=1) (row norms). But system.lattice columns are the Cartesian lattice vectors (cpp/include/vibeqc/periodic.hpp: “Columns = Cartesian lattice vectors”; periodic_runner._reduce_system_to_primitive uses frac = inv(L) @ r_cart). Row norms equal column norms only for a symmetric (cubic/orthorhombic) matrix, so the bug was invisible on the existing symmetric-cell tests; on a skewed slab/rod or triclinic cell the auto-convergence classifier could mis-read a compact crystal as a vacuum-padded molecular-limit box, and the BIPOLE detector could flag a genuinely non-cubic cell metric-cubic and silently use the cubic closed-form. Both fixed to axis=0, with skewed-lattice regression tests whose row and column norms differ (tests/test_periodic_convergence_auto.py, tests/test_bipole_lattice_self_energy.py). Same class of bug as the periodic_gapw_j._multik_gpw_is_molecular_limit fix.

  • Multi-k GDF SCF no longer recomputes the Ewald-3D J pair-FT every iteration. Profiling a LiH (1,1,2) GDF RKS SCF showed 93 % of the wall time (557 s of 601 s) was ao_pair_fourier_transform re-runs inside build_periodic_fock_ewald3d_k — the per-cell AO-pair FT depends only on (basis, cells, mesh), never on the density. The multi-k GDF driver now hoists the existing make_ewald_3d_lattice_j_cache (the same iteration-invariant cache the multi-k EWALD_3D RHF/RKS drivers already use; contraction is bit-identical) and threads it through both Fock-build call sites: 4.7× faster end-to-end on the profiled case (601 s → 127 s), energy unchanged to 1e-8.

Added

  • vibe-view 2.1.0.dev0 “Roothaan’s Roadrunner”. Version + codename bump carrying the Avogadro-parity M2 (content-aware live reload), M4 (live result streaming), and reaction-animation work. The main viewer user guide (docs/user_guide/vibe_view.md) gains sections on the vq Job Manager, live reload, streaming a running job, and the animate CLI; stale vibe-view --version banner examples refreshed across the user guide and tutorial.

  • vibe-view render_reaction_video — NEB reaction paths animate to MP4/GIF. vibeview.animation rendered trajectories, vibrations, and orbitals but had no path for reaction.path sections, despite that being a headline animation type. The new render_reaction_video plays the NEB band image-by-image (reactant → TS → product) through the shared frame renderer, and vibe-view animate gains --kind reaction plus auto-detection of reaction.path. Pins: functional GIF render from the committed neb_h3 showcase + no-section guard (vibe-view/tests/test_v2_extra.py).

  • Open-shell (Γ) GPW meta-GGA. run_periodic_uks_gpw / run_periodic_uhf_gpw’s UKS path now supports meta-GGA functionals: per-spin kinetic-energy densities feed libxc’s eval_polarised_mgga, with the closed-shell regularisation chain applied per spin channel (von Weizsäcker floor + constrained chain rule, uniform-grid density screen — exc screens only where both spins are small, so a fully-polarised region keeps the majority spin’s exchange — and the fail-closed stiffness guard), plus the per-spin generalized-KS Fock term ½∫v_τσ ∇χ·∇χ. Pins: UKS(r2scan) ≡ RKS(r2scan) closed-shell reduction (1e-8), physical H-doublet energies (r2scan below LDA), TPSS stiffness fail-closed (tests/test_periodic_gpw_uks_mgga.py). The multi-k spin-polarised extension of this path landed separately (see below).

  • Multi-k spin-polarised (UKS) GPW meta-GGA. run_periodic_uks_gpw_multi_k now supports meta-GGA functionals on both regimes, lifting the last GPW meta-GGA gate. The molecular-limit branch builds per-spin Γ-summed τ (basis + per-spin density matrices); the compact Bloch branch builds per-spin τ from the per-k Bloch AO gradients (compute_bloch_kinetic_energy_density) and adds the generalized-KS ½∫v_τσ ∇χ·∇χ Fock block per k per spin (project_vtau_to_bloch_ao). The per-spin regularisation chain (von Weizsäcker floor, constrained chain rule, density screen, fail-closed stiffness guard) is shared with the Γ open-shell path. Pins: UKS(r2scan) ≡ RKS(r2scan) on the molecular-limit (H₂, 5e-6) and compact Bloch (Si primitive (2,2,2), 1e-5) regimes, Γ-mesh parity with the Γ UKS driver, hybrid + TPSS stiffness fail-closed (tests/test_periodic_gpw_uks_multik.py). Wired through run_periodic_job (method UKS, jk_method="gpw").

  • Density mixers (Anderson / Broyden, optional Kerker) exposed through run_periodic_job on the closed-shell GDF route. run_periodic_job(..., jk_method="gdf", method="RHF"/"RKS", kpoints=..., density_mixer="anderson"|"broyden", density_mixer_kerker=True, kerker_k0=..., ...) now reaches the multi-k GDF driver instead of failing closed — the same per-k density-matrix mixing machinery as the lower-level EWALD_3D RKS drivers (periodic_density_mixing), with the Kerker filter preconditioning the residual on a plane-wave grid (charge-sloshing control for metallic cells). When selected it replaces Fock-DIIS, damping, and fock mixing (one accelerator owns the update). Γ-only requests must pass an explicit kpoints=(1,1,1); the GPW/GAPW/ BIPOLE/AICCM and open-shell routes still fail closed with the remediation in the message. Pins: Anderson and Broyden+Kerker both reproduce the Fock-DIIS fixed point on gapped LiH (5e-7), runner forwarding end-to-end, and the fail-closed gates (tests/test_periodic_gdf_density_mixer.py).

[v0.15.18] - 2026-07-02 - Neese’s Cheetah

Added

  • AICCM (Γ-CCM / -a) evaluation routes are now keyword-selectable, with the k-free real-Γ route a first-class peer of gamma and chi. Two surfaces: (1) a functional library selector run_ccm_scf(ccm, route=…) picks among the three -a routes for the same cyclic-cluster Hamiltonian — route="four-center" (dense WSSC kernel), route="gamma" (Γ-CCM production = multi-k GDF, CCM ≡ SCM-Γ), and route="real-gamma" (the k-free literal real-Γ-supercell SCF, run_ccm_rhf_direct, with the derived exchange-q=0 seam) — accepting spoken / jk_method / harness aliases ("direct", "non-k", "aiccm-ri", "aiccm-hf-direct", …) and dispatching bit-identically to the concrete drivers; functional= runs KS on the gamma route (real-gamma KS is gated with a pointer). (2) PeriodicJKMethod gains AICCM2026DEV_A (gamma) and AICCM2026DEV_A_REAL_GAMMA (real-gamma, alias "real-gamma") as peers of AICCM2026DEV_B (chi), so the route is named at the same jk_method surface as chi and is described/validated there. The -a routes do not yet compute through run_periodic_job (staged FR-1 bundle wiring); the runner fails them closed with a precise NotImplementedError pointing at run_ccm_scf / the run_ccm_* entry — a fail-closed member like the retired fft_poisson, not a half-wired path. New: vibeqc.periodic.ccm.run_ccm_scf / resolve_ccm_route / CCM_ROUTES; vibeqc.periodic_jk_method.resolve_jk_method_string. Tests: tests/test_ccm_route.py (28).

    D89 qualification (2026-07-15): the historical “three routes for the same Hamiltonian” and “gamma = Γ-CCM production” wording above is superseded. aiccm2026dev-a / gamma / gamma-ccm now select the union-and-weight four-center Γ-CCM construction. neutral-bloch / gdf / aiccm-ri and real-gamma are Bloch and real-supercell evaluations of a neutral fitted-torus control; their same-Hamiltonian parity is not Γ-CCM/χ-CCM construction evidence.

  • Γ-CCM fold-built neutral cderi (ccm_neutral_cderi_fold) — the supercell-FFT memory wall removed, and exact GDF parity on ultra-diffuse bases. The real Γ-supercell neutral cderi is assembled from per-q unit-cell Bloch-pair fits folded onto the torus (the RI-level statement of CCM ≡ SCM-Γ band folding), instead of one giant supercell FFT fit: B_q[P,(μc),(νc')] = N_c^{-3/2}·Σ_ka e^{i ka·Rc i kb·Rc'}·L_{ka,kb}[P,μν] with kb = ka + q kept unfolded (one whitening frame per channel) and the builder’s canonical_auxiliary_basis=True frame (gauge-stable matrix function — the compact eigenmode frame’s arbitrary rotations across independently built blocks corrupt the fold’s cross-ka products, up to 8.5e-4 measured on a vacuum cell). Same RI factorization as ccm_neutral_cderi (four-center contraction equal to ~1e-10 on compact toruses, TRIM and non-TRIM, multi-axis); memory drops from supercell-volume-scaling (measured 49 GB at a 54-AO 18³-bohr torus) to MBs, at the multi-k GDF’s own setup cost. On ultra-diffuse LiH/sto-3g (2,1,1) the fold cderi fixes the supercell fit’s open translation-asymmetry finding at the CCM level: run_ccm_rhf_direct(cderi_build="fold") — now the default — matches the multi-k GDF to 4e-14 Ha/cell where the supercell fit missed by 1.35e-4 (the residual is thereby localized to the supercell-FFT fit path; canary test retained on that path). run_ccm_rhf_direct gains cderi_build={"fold","supercell"}; tests in tests/test_ccm_direct.py.

Changed

  • Periodic open-shell SCF: spin-coupled Pulay DIIS replaces the per-spin histories in the Python periodic accelerators. F_α and F_β are coupled through the shared Coulomb term J(D_α + D_β), so extrapolating each spin against only its own residual lets the two histories chase each other’s field and can stall the SCF tail (the molecular OH/def2-TZVP UHF 2026-07 M02 regression; the same coupling argument holds per k-point). The Γ-point PeriodicSCFAccelerator.extrapolate_uhf (DIIS + the DIIS branch of EDIIS_DIIS) now stacks (F_α; F_β) / (e_α; e_β) vertically into a single C++ DIIS history; the multi-k MultiKPeriodicUHFAccelerator and the multi-k UKS Ewald driver concatenate the α and β per-k block lists with duplicated k-weights into one _MultiKPulayDIIS history — one B matrix, one coefficient set applied to both Focks, the same spin-coupled metric the open-shell EDIIS / ADIIS / KDIIS paths already used. Touches every open-shell periodic route through these accelerators (Γ/multi-k Ewald UHF + UKS, GDF, BIPOLE). Validated against PySCF.pbc unrestricted GDF references per CLAUDE.md § 7.

Fixed

  • Periodic GDF RSGDF high-|G| tail completion no longer spends the P01 MgO/STO-3G promotion rerun silently grinding through dead inter-cell AO-pair work before the first SCF line. The prompt-11 rsgdf_tail_ke_cutoff=3200 fix was numerically right but the production route still asked the general-L Bloch AO-pair FT to stream every tail chunk over the full 30-bohr cell list (935 cells on P01), even though the high-|G| shell is dominated by same-cell tight-core products and diffuse / inter-cell contributions are exponentially zero. The native general-L AO-pair FT now has an opt-in conservative shell/cell Fourier-bound screen, and the PBC-GDF RSGDF tail path enables it at 1e-14 only for the tail completion; direct build_lpq_native_fft callers keep the exact unscreened base+tail==big-mesh path by default. The tail builder also emits chunk counters (RSGDF high-|G| tail chunk i/n) through the progress logger so long promotion runs show pre-SCF progress. run_periodic_job now exposes and forwards rsgdf_tail_ke_cutoff on the supported GDF routes, fails closed on routes that would ignore it, and records the native OpenMP thread count beside the GDF settings in .out files. Pins: tests/test_rsgdf_dense_mesh_tail.py (exact tail identity, screened-vs-exact high-|G| P01 samples, progress counter) and the high-level GDF runner dispatch tests.

  • CASPT2/NEVPT2 effective-density gradient correction was silently dropped on every build. gradient/_pt2_density.py imported the state_rdm12_cpp C++ kernel outside its Python-fallback guard, but the kernel was never exported from _vibeqc_core (implemented in cpp/src/nevpt2_ops.cpp, no bindings.cpp registration), so compute_pt2_effective_density always raised ImportError; the CASPT2 gradient driver’s degradation handler swallowed it and the PT2 correction vanished without warning (SA-CASPT2 gradients came out identical to state-specific ones — the 2026-07-02 fast-lane gate reds in tests/test_casscf_gradient.py). The import now sits inside the guard, and the previously unreachable pure-Python 2-RDM fallback — which had its own bugs (_so called with a spin index in the norb slot; missing cross-determinant terms) — was rewritten to mirror the C++ kernel’s T2[p,q,r,s] = (E_qp c)·(E_rs c) construction, validated to machine precision against a brute-force spin-orbital reference. Exporting the C++ kernel (a pure speed-up, no behaviour change) stays with the CAS-gradient workstream (handovers/HANDOVER_MS_CASPT2_GRADIENT.md). Affected release: v0.15.17 (see docs/troubleshooting.md).

  • DLC geometry optimization: back-transform no longer diverges on planar / near-branch-cut torsions. The delocalised-internal- coordinate back-transform (geomopt/internal_coords.py) reused the reference geometry’s Wilson B-matrix pseudoinverse at every Newton iteration and never wrapped torsion residuals into (−π, π], so a molecule whose torsions start near ±π (planar heavy-atom glycine, the rp167 SI matrix) saw spurious ~2π residuals, walked out of the Newton basin, and drove the SCF to a garbage energy (~−228 Ha vs the −279 Ha minimum). Now: B⁺ is rebuilt at the current geometry each back-transform iteration (and for the gradient / Hessian projections), torsion residuals are branch-wrapped, each step’s Newton iteration is seeded from the previous step’s geometry, and a diverging iteration returns the best geometry seen instead of running away. DelocalizedInternalCoordinates additionally warns (RuntimeWarning) when the auto-generated primitive set is rank-deficient (spans fewer than the expected 3N−6 internal DOF) instead of silently under-optimising, recommending geom_coords="cartesian" for that topology. Pins: tests/test_geomopt_dlc.py (H2O round-trip + DLC-vs-Cartesian parity, torsion branch-cut wrap, glycine warn-and-stay-bounded). Docs: user_guide/geometry_optimization.md § Coordinate systems.

  • Solvated CASSCF geometry optimization no longer dies with a NameError; unsupported method+solvent combos now refuse clearly. _run_molecular_scf assigned its options local only in the rhf/uhf/rks/uks/rohf branches but passed it to run_cpcm_scf unconditionally, so optimize_molecule(..., method="casscf", solvent=...) — reachable via the analytic CASSCF path, whose envelope gate does not exclude solvated runs — crashed with NameError: opts. CPCM composes with the mean-field SCFs only (run_cpcm_scf: rhf / uhf / rks / uks), and the FD fallback would silently drop the solvent (_run_single_point ignores it for the CAS family) and walk the gas-phase surface while claiming solvation — so casscf / caspt2 / nevpt2 / rohf with a solvent now raise a ValueError naming the limitation up front, before any gas-phase solve (ROHF previously burned a full gas SCF before run_cpcm_scf rejected it). Also fixed the dead None-check in _compute_molecular_gradient: np.asarray ran before the gradient is None test, so a missing CAS-family gradient raised an opaque numpy TypeError instead of the intended ValueError. Pins: tests/test_molecular_optimize.py::TestSolventMethodGating + ::TestCasGradientNoneCheck (10 new tests).

  • run_job(..., solvent=...) no longer silently ignores the solvent for methods without a solvation composition. Implicit solvation composes with the mean-field SCFs (CPCM: rhf / uhf / rks / uks) and MSINDO (COSMO) only, but every other single-point method — the CAS family (casscf / caspt2 / nevpt2 / casci / mrci), the CI/CC solvers (cisd / selected_ci / fci / dmrg / v2rdm / transcorrelated_ci / ccm), the semi-empirical platform (gfn2_xtb / pm6 / om1-3 / dftb) and MLIPs — dropped a user-passed solvent= without a warning and returned a gas-phase energy, misrepresenting the result. The post-SCF families (mp2 / ccsd and variants) were subtler: the mean-field reference ran the CPCM macro-iteration, but the correlation block then composed its reported total from the bare reference energy, so the .out printed an “in-solvent” block alongside an MP2/CCSD total that was consistent with neither the gas-phase nor the in-solvent reference. Both cases now raise a clear ValueError up front: a fail-fast gate in run_job (before any optimization machinery burns gas-phase cycles) plus a backstop gate in the _run_single_point dispatcher, which also covers the FD-optimizer energy path (_evaluate_energy) that previously walked the gas-phase surface while claiming solvation for e.g. solvated-CASPT2 optimizations rerouted to finite differences. method="auto" resolutions landing on an unsupported method (e.g. FCI for tiny systems) name the resolution in the error; rohf / roks keep their dedicated NotImplementedError gates. Wording matches the _run_molecular_scf gate that landed with the solvated-CASSCF optimizer fix above. Pins: tests/test_solvation_cpcm.py::TestRunJobSolventMethodGating + tests/test_molecular_optimize.py::TestSolventMethodGating (FD-path additions). Docs: user_guide/solvation.md § Supported methods.

  • LatticeSumOptions.becke_image_radius_bohr exposed to Python. The C++ field has existed since the periodic-Becke cross-cell screen landed (build_xc_periodic reads it), but the pybind11 binding was only ever added on PeriodicKSOptions, so setting or reading it on a bare LatticeSumOptions from Python raised AttributeError. This broke the periodic basis-optimization XC parameter gradient (basis_optimization/periodic_energy_gradient.py) and its test module tests/basisset_dev/test_periodic_xc_param_gradient.py (8 failures) on a fresh build. The attribute is now bound on LatticeSumOptions (default 0.0 = unset, falling back to the built-in partition reach); regression pin in tests/test_periodic_grid.py. In the same sweep, tests/basisset_dev/test_energy_gradient_uks.py’s FD reference step for exponents moves from rel 1e-4 to 1e-3: the SCF energy’s deterministic screening-noise floor (a few nHa) made the central difference on the small B3LYP d-exponent component cross tolerance at 1e-4 while the analytic gradient is correct (h-sweep V-curve documented in the test).

  • CASPT2/NEVPT2 geometry optimization: analytic Z-vector gradient now correctly gated and actually reachable. v0.15.11 enabled the analytic CASPT2/NEVPT2 gradient in optimize_molecule / optimize_molecule_brent unconditionally (b3645a46), but the analytic branches never forwarded caspt2_options / nevpt2_options to the solver — so every PT2 optimization walked the bare CASSCF reference gradient (the compute_corr_grad=False default) while reporting PT2 energies, a surface inconsistent with the reported energy. All three molecular optimizers (L-BFGS-B primary, Brent, and the geom_opt MolecularSCFProvider, which had stayed on FD) now share one decision (_mrpt_analytic_gradient_ok): analytic Z-vector gradient (99.99% vs full-energy FD, retired CAS-gradient handover v52) when CASSCF-referenced, gas-phase, and compute_corr_grad=True; full-energy central FD otherwise (default). caspt2_options / nevpt2_options are forwarded through the analytic SCF path, MolecularSCFProvider gains the previously missing nevpt2_options parameter, and run_job forwards nevpt2_options to all three optimizer backends (previously dropped, so NEVPT2 optimization options could never reach the solver). Pins: tests/test_molecular_optimize.py::TestCasscfAnalyticGradientGating (7 new tests covering all three optimizers, both gate directions, and the options plumbing). Docs: user_guide/geometry_optimization.md § Supported methods.

Added

  • vibe-view live result streaming (Avogadro-parity M4): watch a running job converge. The viewer consumes the streaming-checkpoint fields the runners write (provenance.run_status / provenance.checkpoint / per-section partial; no QVF schema bump was needed — they validate against v1). QVFReader gains run_status / checkpoint_info / section_is_partial. The Job Manager grows a Watch live action on running jobs that opens the daemon-advertised checkpoint_qvf_path and enables auto-reload; an app-bar chip pulses running (#seq · iter N · E Eh) and settles green converged / red failed, with the finish announced in the status bar; still-growing sections are badged streaming in the sidebar; a growing trajectory is followed at its head. Submit-from-viewer defaults to streaming a checkpoint (checkpoint_qvf=$VQ_WORKDIR/checkpoint.qvf, opt-out switch) so a just-submitted job is immediately watchable. Verified end-to-end with trusted-event Playwright against a progressive checkpoint producer (vibe-view/docs/qa_live_stream.py, 5/5). Pins: streaming reader surface, chip formatting, watch-live wiring, terminal-status announcement, trajectory head-follow, and live-checkpoint input generation in vibe-view/tests/test_controller_state.py. Docs: vibe-view/docs/user_guide/live_reload.md § Streaming a running job; roadmap A4 / C2 / C3 marked shipped.

  • vibe-view live reload (Avogadro-parity M2): content-aware file watching with per-section hot-reload. vibeview.file_watcher is rewritten around a content fingerprint (per-section QVF-manifest digests, whole-file hash fallback), a settle delay, and a mid-write hold, replacing the mtime+size compare that fired on half-written archives (2026-07-02 audit finding #7). The fired QVFChange carries a per-section diff (changed/added/removed_sections, manifest_meta_changed — visible when a streaming job’s provenance.run_status flips); a new synchronous QVFChangeTracker drives the viewer’s asyncio watch loop. The viewer hot-reload (apply_watcher_event) now swaps in a fresh QVFReader — the old auto-reload reused the previous reader whose open zip handle + member cache served stale data — refreshing only the active panel for 2D-panel-confined changes and otherwise running a full reload with the user’s camera preserved and active section restored. Fixed in the same milestone: _reload_active_file had been truncated by a mis-indented SSAO block (aec246e5), blanking the 3D scene on every file switch/open/reload, and toggle_file_watcher/toggle_dark_ui were nested inside camera_preset so they never registered until a camera preset was first clicked. Pins: vibe-view/tests/test_file_watcher.py (11 tests), watcher hot-reload + scene-rebuild regression tests in vibe-view/tests/test_controller_state.py. Docs: vibe-view/docs/user_guide/live_reload.md.

  • Multi-k GPW UKS is now reachable through run_periodic_job. run_periodic_job(method="UKS", functional=..., jk_method="gpw", kpoints=...) dispatches to run_periodic_uks_gpw_multi_k and adapts the result to the runner’s multi-k open-shell shape (gpw_uks_multik_result_to_runner_shape, mirroring the multi-k GDF UHF/UKS surface: per-spin per-k MO/density/Fock lists, per-k overlap/hcore, <S²> via the shared spin-contamination diagnostic) — the standard .out/.system/.qvf writers work unchanged. Completes the prompt-9 wiring; multi-k UHF/hybrids stay fail-closed (per-k exact exchange), and dft_plus_u/open-shell smearing on this route raise with clear messages. Pins: runner-vs-library energy parity (1e-8), doublet <S²> = 0.75, output files, UHF gate (tests/test_periodic_runner_gpw_dispatch.py).

  • Canonical (non-DF) CCSD / CCSD(T) and UCCSD / UCCSD(T). CCSDOptions(density_fit=False) now runs the conventional coupled-cluster route on exact four-index MO integrals instead of raising “canonical (non-DF) CCSD is not implemented” (the blocker for strict conventional all-electron ORCA parity in the release-paper rerun, job df5eec9adb96). The integral blocks are assembled by the new eri_mo_pair_transform (exact AO→MO chemists’ pair transform, cpp/src/integrals.cpp); the amplitude equations, DIIS, and the (T) correction are shared with the DF path unchanged. Closed-shell (RHF), open-shell (UHF), and ROHF references all support the route; no RI auxiliary basis is required or used, so it also works on basis sets without a registered -ri auxiliary (e.g. STO-3G). Small-molecule by design (holds the AO ERI tensor in memory); DF remains the default. Provenance is now truthful for the non-DF route: the .out block and the structured-log ccsd_converged event report Algorithm = CCSD(T) / UCCSD(T) without the DF- prefix, Density fitting = off, and no RI auxiliary line; the citation assembler drops the DF-CCSD integral-assembly reference (DePrince-Sherrill 2013) on non-DF runs (assemble(cc_density_fit=False)) while the defining CC + (T) papers still fire. Validated all-electron against conventional PySCF CCSD(T) / UCCSD(T) to < 5e-9 Ha (H2O and OH radical, cc-pVDZ) and against the in-repo spin-orbital SGWB anchor on exact-factorised integrals (tests/test_ccsd_canonical_noDF.py, 9 tests). FNO and the DLPNO pilots remain DF-only. Docs: user_guide/ccsd.md gains “Conventional (non-DF) integrals”.

[v0.15.17] - 2026-07-02 - Neese’s Cheetah

Added

  • Relaxed MS/XMS-CASPT2 analytic nuclear gradients. SA-CASSCF- referenced multi-state CASPT2 runs with CASPT2Options(compute_corr_grad=True) (the single-state opt-in flag) now return the fully relaxed gradient of the target multi-state root: the SA-CASSCF Lagrangian (orbital + external-CI z-vector against the state-averaged Hessian; Stalring/Bernhardsson/Lindh 2001) plus the state Lagrangian for the model-state mixing (Celani-Werner 2003; the OpenMolcas MCLR “SLag” mechanism), with all geometry derivatives on the symmetric (Löwdin) orbital connection. Rewrite of gradient/_ms_caspt2_grad.py; the effective-Hamiltonian pipeline is factored into solvers/_ms_caspt2._heff_pt2_blocks so energy and gradient share one implementation. FD-validated to ~1e-7 Ha/bohr on H2 and LiH (mixed states) in both ms and xms modes and cross-checked against OpenMolcas MCLR+ALASKA. Replaces the frozen-PT2 transition-density gradient, whose diagonal terms silently assumed state-specific orbital stationarity and produced wrong-sign gradients on SA references (test_ms_gradient_vs_fd tolerance tightens 5e-2 → 1e-5). Citations (Stalring 2001, Celani-Werner 2003) fire whenever a multistate run carries a gradient. Gradient CI-response is gated to small active spaces (max_det_response); the analytic transition-RDM CI Lagrangian for large CAS is on the roadmap.

  • vibeqc.ase_optimizers – ASE optimizer availability probing + the old-ASE/new-SciPy compat shim (glycine SI matrix follow-up, items the rp167 matrix hit after queue dispatch):

    • ensure_ase_scipy_compat() restores the scipy.integrate.cumtrapz / trapz / simps aliases that SciPy

      = 1.14 removed and ASE < 3.23 still imports, so the ASE line-search optimizers (BFGSLineSearch, LBFGSLineSearch) import cleanly on old-ASE + new-SciPy deployments. Applied automatically at import vibeqc.ase and at every internal from ase.optimize import BFGSLineSearch site (run_job ASE backend, semi-empirical preoptimize + variable-cell optimize).

    • resolve_ase_optimizer(name) / available_ase_optimizers() / ase_optimizer_availability() / format_ase_optimizer_report() probe the active ASE installation and fail early with a full availability report when a requested optimizer class does not exist there (FIRE2, RFO are absent on ASE 3.22.x), instead of a bare ModuleNotFoundError halfway through a queued batch. Accepts canonical keys, ASE class names, and ase- prefixed batch-matrix spellings; re-exported from vibeqc.ase.

    • Registry notes carry the hard-won operational guidance: GPMin’s surrogate steps and MDMin’s default dt=0.2 can drive expensive ab initio geometries into SCF-hostile regions.

    • Docs: user_guide/ase_integration.md gains “Choosing an ASE optimizer”; user_guide/geometry_optimization.md gains the native geom_opt= optimizer-family section with a live progress-table sample. Tests: tests/test_ase_optimizers.py (20 tests).

  • ASE calculator: fail fast + diagnose on pathological optimizer steps. vibeqc.ase.VibeQC now rejects collapsed geometries before any SCF runs: when the closest atom pair falls below the new min_pair_distance parameter (default 0.25 A; None disables) it raises the typed VibeQCGeometryError naming the offending pair, instead of grinding through the full SCF iteration budget on a meaningless Hamiltonian (glycine SI matrix: GPMin/MDMin steps ended in 200-iteration SCF failures at energies ~100 Ha above the minimum). VibeQCSCFConvergenceError messages now carry the closest-pair geometry diagnostic plus step-size remedy hints (maxstep / MDMin dt / quasi-Newton), so batch harnesses get an actionable optimizer-level failure summary.

  • SCF warm-start across native geometry-optimization steps. vibeqc.geomopt.MolecularSCFProvider (the provider behind run_job(optimize=True, geom_opt=...)) now restarts each step’s mean-field SCF (rhf/uhf/rks/uks, gas phase) from the previous step’s converged density via the existing initial_guess=READ projection machinery, with an automatic cold-guess retry if the warm tail fails to converge. Optimizer steps are small, so the projected density is near-converged and the per-step SCF iteration count drops substantially (H2O/6-31G PBE smoke: 10 -> 7 iterations already on a small displacement; large-basis hybrid jobs like the glycine rp167 matrix at ~40 cold iterations/step gain far more) – often the difference between finishing inside a queue walltime and aborted_by_queue. Opt out with MolecularSCFProvider(..., warm_start=False). The provider also now fails closed: a nonconverged mean-field SCF raises a clear error instead of silently feeding a garbage gradient to the optimizer (matching the ASE calculator’s typed error and run_job’s v0.15.x fail-closed direction). Tests: tests/test_geomopt_warm_start.py.

Fixed

  • Dispersion-corrected energies now report an explicit SCF/dispersion/total split in the machine-readable job_end record. The glycine SI matrix collected run_job(...).energy for its PBE0-D3(BJ) rows and silently dropped the -0.0097 Ha dispersion term (.energy is the bare SCF energy by design; the total lives on .energy_total), making the ASE D3(BJ) rows look identical to plain PBE0 while the native-optimizer rows carried the true total. The job_end structured-log event now emits e_scf / e_dispersion / e_total (+ e_gcp / e_srb for 3c composites) whenever a post-SCF correction is active, _DispersionAugmented gains an .e_scf alias, and the docs (dispersion tutorial, output-files guide, ASE integration guide) now state the .energy vs .energy_total vs ASE-total conventions side by side. A cross-surface regression (tests/test_dispersion_energy_consistency.py) pins the ASE calculator, the native MolecularSCFProvider, and run_job(...).energy_total to the same D3(BJ) total within 1e-8 Ha and would have caught the SI-matrix inconsistency.

  • Native geomopt correctness + observability sweep (fallout from the glycine optimizer SI matrix, rp167-glyopt-* on twin):

    • FIRE velocity mixing used the gradient direction instead of the force direction, so from the zero-velocity start the mixed velocity always pointed uphill, the power check always failed, the velocity reset to zero, and the step was exactly zero: the optimizer re-ran the SCF at the same geometry for all max_iter iterations without ever moving an atom. The update now mixes v toward F = -∇E and computes the power as P = F·v (Bitzek 2006 conventions).

    • EF/PRFO transition-state Hessian seeding unpacked (energy, gradient) in the wrong order — the Baker negative-eigenvalue seed was built from the scalar energy instead of the initial gradient, and the initial point was evaluated twice. Fixed + the redundant provider call removed.

    • geom_opt="dimer" with geom_target="minimum" now raises a clear ValueError (the dimer method is a saddle-search method); the unregistered "newton" keyword was removed from the resolver map so it fails with the canonical availability list instead of a KeyError.

    • run_job geometry-optimization gradient tolerance used a rounded 0.045 conversion for eV/Å → Ha/bohr; it now uses the exact CODATA factor (0.05 eV/Å9.72e-4 Ha/bohr, was 2.25e-3).

    • run_job(geom_opt=...) on semi-empirical / MLIP methods now raises instead of silently running the ASE BFGS fallback while the output claimed a native geomopt optimizer (citations included).

    • Per-step progress for every native optimizer (sd/cg/bfgs/lbfgs/ trust/rfo/gdiis/fire/ef/prfo/dimer): optimizers accept a file-like progress sink, print step / E / dE / max|g| / |step| / conv flags plus an initial-evaluation marker, and run_job streams the table into the job .out as the optimization runs — long queue jobs now show live per-step status instead of appearing hung.

    • run_job now forwards geom_target, geom_hessian_init, geom_hessian_update, and geom_opt_options to the native geomopt runner (transition-state searches and per-optimizer tuning are reachable from the high-level API).

  • QVF biomolecule metadata for ribbon/cartoon rendering. The structure section object may now carry optional chains (list of chain ids), residues ({name, seq, chain, atom_indices}, 0-based atom indices), and secondary_structure ({type, chain, start_seq, end_seq} with type in helix/sheet/coil) fields. Supplied to the writer via a biomolecule_data context key on write_qvf/qvf_bytes and normalized (numpy ints/arrays coerced to plain int/list). Additive peer keys on the section object, riding the open Section schema like the live-checkpoint fields, with no qvf_version bump; all three independently optional. Consumers that don’t understand them ignore them (vibe-view Section has extra="allow", so files open with no reader change). Docs: docs/design_qvf_format.md § 4.1; tests: tests/test_qvf_biomolecule.py.

  • vibe-view now refits the client camera after periodic replication. Replicating a crystal updates the server scene to the expanded nx * ny * nz supercell, but VtkLocalView keeps its own client camera; the old server-only reset left replicated crystals zoomed into the original single-cell view, clipping atoms in generated crystal figures. The replication update now calls the local-view camera reset after the rebuild so the full replicated crystal is framed.

[v0.15.16] - 2026-07-02 - Neese’s Cheetah

Added

  • Γ-CCM neutral-gauge RIJCOSX on the direct-torus route (run_ccm_rhf_direct_rijcosx). Neutral RI-J from the Γ-supercell cderi plus the periodic chain-of-spheres exchange (the validated M3b SR+LR KPointCosxK engine, run at Γ on the cluster supercell — real-space erfc-SR seminumerical exchange on the supercell Becke grid + the reciprocal erf-LR complement in the same G=0-dropped gauge) plus the same derived exchange-q=0 seam as run_ccm_rhf_direct (the seam-off offset identity ξ_N·N_e/2 is pinned on this route too). Measured accuracy vs the exact-K direct route: 22–30 µHa/cell on genuine-3-D controls (compact H₂ (2,2,2) torus, vacuum-padded dim=3 H₂ anchor), invariant under ω / LR-cutoff / COSX grid tier — i.e. the shared COSX engine’s own validated composition floor (0.024–0.027 mHa backend parity, M3b-4c); gates pinned at 5e-5 Ha/cell. Two loud scope exclusions: dim < 3 raises (the LR complement on the dimension-collapsed reciprocal mesh cannot compose with the exact real-space SR — measured K error 0.08–0.6 on dim=1 chain toruses, growing with the SR share; the low-D fast exchange belongs to the D3 mixed-boundary kernel work), and the pre-existing ultra-diffuse SR envelope criterion warns (M3b-6).

  • Γ-CCM direct-torus SCF (run_ccm_rhf_direct) — a k-free production route with the derived exchange-q=0 seam. One real Γ-supercell SCF (single real generalized eigenproblem over the N_c·n_μ supercell AOs, no k-mesh, no complex Bloch blocks) that reproduces the multi-k GDF (run_ccm_rhf_gdf) to ≤1e-8 Ha/cell: measured 1.7e-11 on the vacuum-padded H₂/STO-3G anchor (external PySCF KRHF −1.1182352381 Ha/cell matched to 1.6e-11), 2e-11 on 1-D H-chains (TRIM and non-TRIM meshes), 1e-9-class on ionic rocksalt LiH where the seam is Madelung-large. The exchange-q=0 (BvK-Madelung, exxdiv="ewald") seam is implemented as a real-space Fock operator K K + ξ_N·S·D·S (ξ_N = the BvK-supercell probe-charge Madelung constant, shared verbatim with the multi-k driver), so F = ∂E/∂D holds exactly; a deliberate exxdiv=None control sits above the ewald run by exactly ξ_N·N_e/2 per supercell (pinned — the seam is block-diagonal in occ/virt, so both conventions converge to the same density). One-electron and nuclear terms are the GDF driver’s own unit-cell lattice sums folded onto the torus (equality with the multi-k Hamiltonian by construction). The result records .exchange_q0 (vibeqc.periodic.exchange_convention labels) and parity tests assert matched conventions. New module vibeqc/periodic/ccm/direct.py; tests tests/test_ccm_direct.py.

Fixed

  • ccm_neutral_cderi silently broke torus translation invariance on supercells larger than the flat ~15-bohr lattice cutoff. The Γ-supercell cderi build stores AO pairs at absolute supercell positions; once a replicated axis outgrew LatticeSumOptions.cutoff_bohr, the real-space image sums lost the cell that wraps edge pairs back onto the torus — measured 4e-2 J/K translation-symmetry violation and a 1.1e-4 Ha/cell energy error on an 18-bohr (3,1,1) chain supercell (TRIM-only meshes ≤ 12 bohr masked it, which is why the H₂ anchor and (2,1,1) gates never caught it). ccm_neutral_cderi now auto-widens the cutoff by the worst-case wrap vector over replicated axes (_supercell_wrap_lat_opts; override via the new lat_opts= kwarg) and the neutral kernel’s J/K commute with torus translations to machine ε at any supercell size (pinned in tests/test_ccm_direct.py). Downstream, the no-truncation DLPNO gates are unaffected (neutral-vs-neutral comparisons cancel the kernel change); the scalable local-(T) semicanonical tolerance moved by 1e-11 on one pm/(4,1,1) case (retuned 1e-8 → 2e-8 with the exact-mode gate unchanged at 1e-10).

[v0.15.15] - 2026-07-02 - Neese’s Cheetah

Added

  • Opt-in live QVF checkpointing for run_job / run_periodic_job (vibe-view hot-reload). Both runners accept checkpoint_qvf=<path> + checkpoint_every=N. When set, a QVF snapshot is written to checkpoint_qvf as the job runs — carrying provenance.run_status="running", a monotonic provenance.checkpoint.seq (plus wall_time_s / written_at and, mid-SCF, scf_iteration / energy_eh), and partial: true on still-growing sections — then a terminal snapshot labeled "converged" on success or "failed" on crash (molecular path). Every snapshot is written atomically (temp file + os.replace) so a concurrent reader never sees a half-written zip. Per-iteration cadence (checkpoint_every=N) fires on the routes that stream through the shared progress logger — the periodic Ewald, GDF, BIPOLE, and GPW drivers — refreshing the checkpoint every N SCF cycles. The periodic GPW route (run_periodic_rhf_gpw / run_periodic_rks_gpw_multi_k and the open-shell UHF/UKS/multi-k siblings) now threads the same progress= handle into its Python SCF loop, so GPW jobs get per-iteration cadence too. Only the molecular SCF still runs its loop in compiled C++ with no per-iteration Python hook, so molecular jobs emit a start + terminal frame only. These fields validate against the current QVF v1 manifest schema — no v3 schema bump required. write_qvf gains an atomic= flag and run_status / checkpoint / partial_sections context keys. New handle: vibeqc.output.checkpoint.QvfCheckpointer. resolve_progress now passes a transparent progress wrapper (the checkpoint proxy) through unchanged instead of unwrapping it to the inner logger — without this the per-iteration hook was stripped by each driver’s resolve_progress normalization, so checkpoint_every=N was a start/terminal-only no-op on every periodic route. Regression: tests/test_qvf_checkpoint.py, tests/test_periodic_runner_gpw_dispatch.py (GPW per-iteration cadence). (Ask 2 of the vibe-view Avogadro-parity cross-repo asks.)

Fixed

  • RSGDF dense G-mesh was incomplete on oblique cells; dense-core Γ GDF can now converge to PySCF parity via high-|G| tail completion. Two fixes for the P01-class MgO/STO-3G Γ RHF gap (+PARITY_HELD): (1) rsgdf_dense_g_mesh sized its integer index box by ceil(G_max/|b_i|)+1, exact only for orthogonal cells — for oblique lattices the correct biorthogonal bound is |n_i| G_max|a_i|/2π (strictly larger; fcc primitive ≈ 15%/axis), so every mesh on fcc-class cells silently dropped G-points near the sphere boundary. (2) build_lpq_native_fft / build_lpq_bloch_native_fft accept tail_ke_cutoff (runner: rsgdf_tail_ke_cutoff): the 2c metric AND 3c tensor G-sums are extended over the exact complementary reciprocal shell with the same analytic kernels, chunked — staying inside the FT representation, so DF consistency is preserved by construction (Lpq(ke=a, tail=b) Lpq(ke=b), pinned to 1e-9 on the fitted-ERI tensor). Unlike the real-space SR-replacement attempts, this cannot excite near-null metric modes. P01 MgO/STO-3G Γ RHF ladder vs PySCF GDF (−271.049457612534 Ha): −500 → −129 → −21.5 → −1.7 mHa → +1.3 µHa at tail = 400/800/1600/3200 Ha (base 200 Ha; the 3200 Ha parity run took ~55 min single-threaded) — the same level PySCF’s own GDF↔RSDF agree (~1 µHa). The dense-core Γ +PARITY_HELD backend tag lifts automatically when rsgdf_tail_ke_cutoff 10 × ζ_max (the steepest AO primitive exponent; P01: 10.7 × 299.24 ≈ 3200); undersized tails and the mdf path stay held, and the hold warning now carries this remediation. Regression: tests/test_rsgdf_dense_mesh_tail.py (mesh completeness vs brute force, tail ≡ big-mesh identity, hold-lift pins).

Added

  • Requesting k-point features for a molecule now fails with a targeted explanation. Band structures, DOS meshes, k-paths, and Monkhorst-Pack sampling are Brillouin-zone (periodic-boundary-condition) features; a molecule has no lattice. Previously run_periodic_job(system=Molecule) died deep in setup with AttributeError: 'Molecule' object has no attribute 'lattice', run_job(mol, kpoints=...) raised Python’s bare unexpected-keyword TypeError, and KPoints.band_path / monkhorst_pack / band_structure_hcore / band_structure_projected / band_path_eigenvalues crashed at whatever attribute they touched first. All seven entry points now raise up-front with the same message (molecules have no Brillouin zone; use run_job for molecules or build a PeriodicSystem for crystals), and run_job traps the common PBC-only kwargs (kpoints, dos_kmesh, jk_method, bz_integration, cutoff_ha) with a pointer to run_periodic_job. Shared guard: vibeqc.bands.require_periodic_system. Regression: tests/test_periodic_feature_input_guards.py.

  • Compact multi-k GPW now supports meta-GGA (r2SCAN/SCAN). The compact Bloch path builds the kinetic-energy density from per-k Bloch AO gradients, τ(r) = ½Σ_k w_k Σ_mn D_mn(k) ∇χ_{m,k}·∇χ_{n,k}* (compute_bloch_kinetic_energy_density; χ_k is Bloch-periodic, not lattice-periodic, so the gradients phase-unwrap u_k = e^{-ik·r}χ_k before spectral differentiation), and adds the per-k generalized-KS Fock block ½∫v_τ ∇χ_{m,k}*·∇χ_{n,k} (project_vtau_to_bloch_ao) — lifting the meta-GGA gate placed when the compact Bloch path landed. The von Weizsäcker floor / density screen / stiffness guard of the v0.15.14 meta-GGA fix apply on both regimes. Pins: the multi-k kinetic-trace identity on compact Si, forced-compact vs molecular-limit parity on a vacuum cell (PBE control at 2e-6; r2SCAN at 1e-4 with the clamp-set sensitivity documented in the test), and a converged compact-Si r2SCAN SCF (tests/test_periodic_gpw_compact_multik.py).

  • Coupled-pair variants of the canonical CCSD kernel: CCD, LCCD, LCCSD, CEPA(0..3). New CCSDOptions field cc_variant on the closed-shell DF kernel (cpp/src/ccsd.cpp), reachable through run_job(method="ccd" / "lccd" / "lccsd" / "cepa(0)".."cepa(3)") and the AutoCI-style citype= selector (these were gated NotImplementedError roadmap names before). CCD freezes the singles; the linearized variants extract the degree-1 part of the canonical SGWB residual with an algebraically exact polynomial stencil (no second equation set); CEPA(1..3) add Meyer’s pair-specific EPV shifts in the closed-shell convention of Wennmohs and Neese (2008) / ORCA MDCI. Validated three ways (tests/test_cc_variants.py): CCD/LCCD/LCCSD against the in-repo FCI-anchored spin-orbital oracle (sub-nHa), stencil exactness by h-independence (machine epsilon), and CEPA(1..3) against out-of-process ORCA 6.1 RI-CEPA/n with the identical auxiliary basis (0.02 / 0.03 / 0.00 uHa on H2O/def2-SVP; canonical orbitals == ORCA Localize false). Citations (Cizek 1966, Meyer 1973, Wennmohs-Neese 2008) fire per variant into .out / .bibtex / .system. Closed-shell RHF reference only; no (T) on the variants; open-shell requests raise with a pointer to ccsd(t).

  • CCSD[T] (= CCSD+T(CCSD)) bracket triples via triples="[t]". The perturbative-triples kernel now reports its two standard pieces separately (fourth-order E[T] on CCSDResult.e_t4, fifth-order singles-triples E_ST on e_t5_st; E(T) = E[T] + E_ST), and the previously gated triples="[t]" / "+T(CCSD)" selectors (Urban, Noga, Cole, Bartlett 1985) select the bracket correction into e_t through run_job and CCSDOptions.triples. The .out triples block prints the decomposition for (T) runs. Closed-shell kernel only; A-CCSD(T) remains gated (needs Lambda). Citation (urban_ccsd_t_1985) fires on the ccsd[t] route.

[v0.15.14] - 2026-07-02 - Neese’s Cheetah

Added

  • Multi-k GPW UKS (pure DFT). vibeqc.periodic_gapw_open_shell.run_periodic_uks_gpw_multi_k(system, basis, kmesh, functional=...) runs spin-polarised LDA/GGA KS-DFT over a k-mesh on the GPW route — the open-shell closure of “GPW multi-k only supports RKS”. Both collocation regimes of the closed-shell multi-k driver are supported: molecular-limit cells fold each spin density to a Γ-summed density matrix, compact crystals build per-spin Bloch densities and project V_H + v_eff_σ per k onto Bloch AOs (with the same linear-dependence projection and per-k Pulay DIIS as the compact RKS path). Hybrids (per-k exact exchange) and meta-GGA (per-spin τ) fail closed; runner dispatch/output wiring is a follow-up — the run_periodic_job multi-k GPW gate message now points at the driver. Pins: Γ-mesh parity with the Γ UKS driver, singlet parity with multi-k RKS on both regimes, doublet convergence (tests/test_periodic_gpw_uks_multik.py).

Fixed

  • The GPW meta-GGA path was dead on arrival — now works (r2SCAN/SCAN) and fail-closes on stiffness-divergent functionals. Three stacked defects: every GPW SCF called the XC evaluator without basis/density_matrix, so meta-GGA raised ValueError before the first τ build; the τ builder itself crashed on a flat-vs-grid shape mismatch; and the v_τ Fock term was projected as a multiplicative potential ∫χχ·v_τ instead of the generalized-KS matrix element ½∫v_τ ∇χ_μ·∇χ_ν (the derivative of E_xc[τ[D]], matching cpp/src/periodic_xc.cpp) — the same wrong projection sat in the range-separated, orbital-transformation, and GAPW-augment drivers. The rebuilt path adds the von Weizsäcker τ-floor with the constrained chain rule at clamp-active points (v_σ += v_τ/8ρ, v_ρ −= v_τσ/8ρ², v_τ 0, so the Fock stays the exact derivative of the clamped energy), a density screen for the uniform grid’s unweighted vacuum voxels, and a fail-closed stiffness guard: TPSS-class functionals whose iso-orbital derivatives diverge at orbital critical points (sampled at full voxel weight by an FFT grid; one observed artifact converged to a spurious +0.32 Ha stationary point) now raise with a pointer to r2scan/scan or jk_method='gdf'. The finite-difference pin dE_xc/dD == V_xc holds to ~1e-4 for r2SCAN (tests/test_periodic_gpw_mgga_fock.py). Γ open-shell GPW now gates meta-GGA up-front instead of dying in the C++ binding.

  • vibe-view desktop app is now a real native window. vibe-view desktop [file] [--port N] spawns the branded Electron shell (dock/menu-bar name “vibe-view”, molecule icon, About panel) instead of a browser tab, works from any directory, and auto-installs the Electron binary on first run (electron/install-electron.py now also writes the npm wrapper files and rebrands + ad-hoc re-signs Electron.app on macOS). File → Open (⌘O), Open vibe-qc Input (.py), Open Recent (shared with vibe-view recent via ~/.cache/vibe-view/recent.json, migrated from the old config-dir location), Browse Directory (⌘B), drag-and-drop onto the window, and macOS dock-drop all route through the new vibe-view serve /open?file= endpoint, which spawns a per-file 3D viewer process on a free port behind a polling loading page and redirects when it is up. The browse landing page gained a quickstart/welcome panel. electron/README.md documents the architecture and .app packaging.

  • The desktop browse page can navigate the whole filesystem. The launch directory is just the starting point: GET /browse?dir=<abs> lists any directory, every listing carries ⬆ Parent and ⌂ Home links, and the Electron shell adds File → Open Folder… (⌘⇧O) plus folder drag-and-drop (a dropped directory browses instead of erroring through /open). /view?file= and /serve-qvf?file= absolute variants keep the details page and raw download working outside the launch root.

Fixed

  • vibe-view full-codebase audit (2026-07-02) — ~35 confirmed defects fixed. Both UIs were exercised live (trusted-event Playwright for the web viewer — 5/5 interactive checks; the Electron desktop app natively, with export outputs validated), the trame template wiring was scanned mechanically, and dependencies brought current (Electron v43 = latest; Python stack current). Highlights — science-wrong output: generated vibe-qc inputs embedded Å as bohr (geometries ran 0.529× compressed) and the periodic template didn’t even import BasisSet; .py inputs rendered 1.89× inflated (bohr stored as Å) and keyword/variable forms parsed zero atoms; trajectory/vibration “videos” were static; orbital animation crashed on import; replicate_cell collapsed replicas; supercell/CIF used a transposed lattice convention; periodic bond inference was dead code and bonds didn’t replicate; Blender exports were non-runnable Python; SVG/PDF collapsed planar molecules to a line. Broken features: every keyboard shortcut, the right-click context menu, and hover tooltips were dead (four stacked client-JS bugs: @ctrl.set registers no client trigger; the installer JS never ran; t.state.x = v is a silent no-op vs t.state.set(); wrong canvas selector + vtk.js interaction overlay — all live-verified fixed); diff’s verdict was sign-inverted; batch-compare energies were always empty and align=True didn’t superpose; material presets repainted the scene grey; config silently overrode explicit --port; electronic-DOS lazy reads always failed; MO cubes read shifted data; in-memory slice_qvf produced manifest-only archives; SHA-256 mismatches were swallowed in two readers. 44 new regression tests. Deferred findings (point-group detection, miller_slab d-spacing, OSPRay API misuse, label billboarding, share.py orphan, …) are documented in vibe-view/docs/audit_2026_07_02.md.

  • vibe-view open crashed at startup on any QVF with orbital sections (UnboundLocalError: is_periodic, regression in a782ed40): the orbital sidebar-group title consumed the periodicity flag ~500 lines before it was assigned. A cheap structure-JSON probe now sets it before the sidebar builds.

  • The vibe-view 3D viewer rendered a blank page (Vue never mounted) since cec4e80a: the presentation-overlay slide title used a Python-style conditional (x if cond else y) inside a Vue mustache — a template-compile SyntaxError (“missing ) after argument list”) that aborts the entire mount. Rewritten as a JS ternary.

  • vq rechecks program runtime pins at dispatch. Scheduler and daemon dispatch now reject jobs whose configured program runtime no longer matches the submitted spec, closing the gap where a changed venv pin could slip through after submit-time validation.

  • vq detects Linux ORCA MPI loader failures. ORCA jobs that die in the MPI loader are classified explicitly, giving release-paper queue audits a clear scheduler/runtime diagnosis instead of a generic job failure.

  • vq scheduler runtime-pin dispatch coverage is pinned. The queue test suite now covers the runtime-pin dispatch gate so future scheduler changes cannot silently relax the protection.

[v0.15.13] - 2026-07-01 - Neese’s Cheetah

Added

  • vibe-view desktop can be launched from the CLI. vibe-view desktop works from arbitrary working directories and can bootstrap the Electron desktop wrapper on first use, making QVF inspection less dependent on a hand-prepared checkout.

  • vq scheduler cap holds now explain their admission blocker. Queue status reports why scheduler jobs are waiting on cap admission instead of leaving paper-runner batches to infer the blocked state from repeated polls.

Fixed

  • Electron 43 desktop builds work on Node 26-era systems. The Python installer bypasses brittle npm paths, preserves symlinks with ditto, and detects the venv Python used by the viewer desktop wrapper.

  • vq scheduler retry states are visible. PBS marker probes and scheduler poll retries now surface their retry state so transient scheduler/fetch failures are distinguishable from permanent queue loss.

  • AICCM-B host detection recognizes the stable Twin runner. The dispatcher now detects the stable Twin runner shape used by the release-paper queue, avoiding false negatives in environment selection.

  • Release-candidate CI is now pin-able without blocking main. GitLab CI keeps release and release-candidate/* pipelines alive, runs the normal gates on release-candidate branches, and restores conservative auto-cancel behavior for ordinary main pushes.

[v0.15.12] - 2026-07-01 - Neese’s Cheetah

Added

  • Compact multi-k GPW now runs on dense crystals via a Bloch real-space density path. run_periodic_job(..., jk_method="gpw", method="RKS", kpoints=(n,n,n)) on a compact crystal (e.g. primitive Si) previously either folded the multi-k density to a single Γ-summed density matrix — correct only in the molecular limit — and drove the GPW Hartree/XC field to a runaway (E ~ 3.26e15 Ha on P07-shaped Si/def2-SVP at (4,4,4)), or fail-closed after that hazard was gated. It now builds the true Bloch density rho(r) = Σ_k w_k Σ_mn D_mn(k) χ_{m,k}(r) χ_{n,k}(r)* from per-k Bloch AOs and projects the smooth potential back per k (V_mn(k) = χ_{m,k}* v_eff χ_{n,k}, Hermitian), so compact GPW crystals converge to finite, physical energies. Robustness pieces: linearly-dependent Bloch-AO directions (diffuse molecular bases drive the Bloch overlap S(k) indefinite on a dense cell) are projected out by a canonical orthogonaliser, and per-k Pulay commutator DIIS in the orthonormal subspace replaces linear Fock mixing. The molecular-limit multi-k and single-k GPW paths are unchanged. Pure LDA/GGA only; meta-GGA on the compact path is explicitly gated (its v_tau needs per-k Bloch AO gradients). Regression: tests/test_periodic_gpw_compact_multik.py, tests/test_periodic_runner_gpw_dispatch.py::test_run_periodic_job_gpw_compact_multik_si_converges.

Fixed

  • Dense gamma GDF fallback parity now stays explicitly held. The periodic dense-core Gamma GDF route keeps the parity-hold marker in place for the compact-cell fallback, preventing release-paper absolute-energy rows from looking like ordinary trusted parity results.

  • vq scheduler accounting includes deferred PBS reattach jobs. Jobs waiting for delayed PBS restart reattachment now count against the cap, avoiding an over-dispatch window during scheduler recovery.

  • CCSD density-fitting provenance is labelled in output. Canonical and density-fitted coupled-cluster outputs now make the selected provenance explicit for audit logs and paper archives.

  • ASE optimizer nonconvergence is classified as SCF nonconvergence. ASE geometry workflows now surface optimizer-triggered SCF failures with the correct category instead of hiding them behind generic execution errors.

  • vq submit enforces venv runtime pins and reports program identity. Managed venv jobs now honor configured runtime pins on submit and expose the runtime identity in status/output paths used by release-paper queue audits.

  • POB-TZVP-REV2 and shell matching are more robust. The bundled pob-tzvp-rev2 payload includes the complete runtime basis/ECP data, and displaced-geometry basis attachment uses tolerance-based shell-to-atom matching.

  • vq PBS status and fetch recovery are more defensive. Scheduler fetch retries, qstat-derived liveness, failed restart reattach handling, and runtime-state display now report actionable states instead of ambiguous queue symptoms.

v2.0.0 - 2026-07-01 - Pauling’s Panther

vibe-view 2.0 achieves feature parity with Avogadro 2.0 as an interactive molecular editor and viewer with seamless vibe-qc workflow integration. 512 tests, 9 versioned feature releases, 24 CLI commands, 7 export formats.

[v0.15.11] - 2026-07-01 - Neese’s Cheetah

Fixed

  • Canonical CCSD/CCSD(T) outputs now state the frozen-core convention. run_job(..., method="ccsd"|"ccsd(t)") writes the selected frozen-core orbital count to the .out file and structured log, distinguishing chemical-core defaults from explicit all-electron settings for release-paper parity audits.

  • Periodic GDF RHF energy runs no longer compute gradients by default. run_pbc_gdf_rhf(...) now leaves the expensive post-SCF GDF gradient path off unless compute_gradient=True is requested, so plain energy probes do not spend minutes in numerical V_ne gradient work after SCF convergence.

  • Gamma-point UKS/GDF now honors Fock-mixing convergence aids. The Γ-only GDF UHF/UKS drivers validate, log, and apply fock_mixing, allowing high-level run_periodic_job(..., method="RKS", jk_method="gdf") ionic auto profiles to stay on the pure-GDF singlet-UKS path instead of falling back to the legacy molecular-limit bridge solely because FMIXING was selected.

  • Dense-core Gamma GDF parity holds are now machine-readable. Γ-only RSGDF/MDF RHF results for tight dense-core ionic cells, including the P01 MgO/STO-3G audit case, now emit a warning and carry +PARITY_HELD in the backend label so converged-but-held electronic-gauge values cannot look like ordinary PySCF absolute-energy parity rows.

  • wB97X-3c now runs with its vDZP sidecar ECPs applied. Molecular SCF wrappers parse custom-core ECP sidecars into libecpint inline primitive blocks when no standard XML library matches, preserving the valence electron count and effective nuclear charges instead of gating the composite as pending_ecp_inline.

  • Single-atom open-shell AUTO guesses avoid the symmetric SAD false basin. UHF and UKS now route isolated open-shell atoms through the existing PATOM in-field split when initial_guess=AUTO, while preserving explicit SAD and ordinary molecular AUTO behavior. This restores the Si/LANL2DZ ECP triplet to the PySCF-parity -3.6756963193 Ha term instead of the higher -3.4748021557 Ha stationary point.

  • Standalone double-hybrid dispatchers now emit full output bundles. run_b2plyp(...), run_dsd_pbep86(...), run_revdsd_pbep86(...), and run_pwpb95(...) now write .out, .system, Molden, XYZ, population/property, citation, and default-on QVF sidecars when called with output=..., matching the release-paper archive requirements.

  • GFN2-xTB parameter cache misses now fail with a structured offline-data error. If the local parameter cache is absent and the upstream parameter file cannot be fetched, the loader raises GFN2ParameterUnavailableError with a paper-runner-readable message instead of surfacing a raw urllib/DNS traceback.

  • High-level MP2 output now names the default route. run_job(..., method="mp2") prints that it is using direct canonical MP2 (or UMP2/ROHF-MP2 for open-shell references) with no RI auxiliary basis, so paper parity checks can distinguish canonical MP2 from explicit RI-MP2 comparisons.

  • Periodic runs fail closed when requested POB ECP sidecars are missing. The periodic runner now detects absent POB ECP data before launching the calculation, producing an explicit input-data error instead of drifting into an invalid all-electron setup.

  • The bundled POB-TZVP-REV2 runtime basis now includes heavy ECP elements. The shipped libint .g94 payload and QVF sidecar are refreshed from the existing full POB source so Ag, Au, and W release-paper cells can build the valence basis before periodic ECP auto-attach runs. Stale build/deploy basis overlays are ignored unless they also carry pob-tzvp-rev2.g94.

  • Periodic QVF output is more robust for complex and empty occupations. QVF writers now preserve complex periodic wavefunction data, tolerate empty occupation arrays, and gate unsupported complex density-output folds instead of emitting malformed visualization sidecars.

  • Reduced-k density output folds use the requested output convention. Periodic output folds reduced-k densities consistently for the selected route, keeping QVF and property sidecars aligned with the SCF sampling.

  • Selected-CI PT2 output reports zero selected corrections explicitly. Release-paper logs now distinguish a true zero correction from an omitted PT2 reporting path.

  • GFN2-xTB third-order parameters now use tblite’s scale convention. The on-demand GFN2 parameter parser stores GAM3 with the required 0.1 factor and invalidates stale caches with unscaled values.

  • vq remote and scheduler operations gained defensive diagnostics. Multi-user remote fetch tarballs, daemon/scheduler runtime health checks, watchdog cgroup-scope sampling, and PBS missing-marker forensics now fail with clearer state instead of ambiguous queue/fetch symptoms.

[v0.15.10] - 2026-07-01 - Neese’s Cheetah

Added

  • vq admin update supports post-update scripts. Venv programs can set post_update_script to run a deployment finalizer after a successful pull, branch/tag check, and main update_script. This gives vibeqc-release a supported hook for scripts/setup_basis_library.sh so managed hosts install bundled basis data after release rebuilds.

  • vq scheduler job scripts have host-level hooks. Scheduler hosts can set trusted scheduler_prologue and scheduler_epilogue shell lines in config.toml; the generated qsub script runs them inside the job working directory before and after the user command, giving Twin-style sites a configurable place for module/env/copyback glue.

  • vq submit carries program identity into jobs. vq submit --program NAME now validates local specs against [programs.NAME], stores spec.program, forwards the label through remote and scheduler-driver submits, exports VQ_PROGRAM to local and scheduler jobs, and shows the identity in vq status. Scheduler-dispatched jobs now also receive the same VQ_ARRAY_*, VQ_CHAIN_*, and VQ_RERUN_* metadata as local jobs.

  • vq scheduler hosts accept vq-managed arrays and chains. vq submit --host twin --array N ... and --chain N now create N driver-owned specs tagged for the scheduler target, so Twin batches can use the existing vq array/chain metadata without relying on PBS-native array support. --rerun-until and --rerun-max are stamped onto each generated spec in the batch.

  • vq daemon separates scheduler submissions from local process caps. Driver daemons now expose --max-scheduler-jobs, scheduler-backed jobs no longer consume local --max-jobs / CPU / memory gates, and scheduler polling/fetch operations get bounded retries with longer qstat/tar timeouts.

  • vq status estimates pending queue wait from retained history. Pending jobs now show a best-effort dispatch-turn ETA when completed-job history can estimate the jobs ahead, matching by tag, command, and CPU count.

Fixed

  • Molecular SCF jobs now fail closed on nonconvergence before post-SCF work. run_job(...) detects unconverged HF/DFT references before gradients, TDDFT, coupled cluster, and other dependent stages, raising a structured error instead of letting downstream code consume invalid orbitals.

  • Periodic EWALD nuclear-attraction FT builds are chunked for large auxiliary grids. The Ewald V_ne(G) builder now streams reciprocal vectors in bounded chunks, avoiding memory spikes in the release-paper periodic validation cases without changing the energy expression.

  • True-periodic direct HF gradients now fail closed. Direct periodic HF gradient routes are gated where the lattice-gradient convention is not yet complete, preventing paper workflows from silently using an unsupported true-periodic path.

  • Periodic density mixers now expose an API guard. Multi-k periodic runs reject unsupported density-mixer selections clearly, while preserving the validated DIIS/default path and documenting the guarded surface.

  • The C++ bindings expose the Hückel initial-guess alias. Both huckel and hueckel spelling routes now reach the same initial-guess enum through Python.

  • vq status explains pending-admission blockers. Pending jobs now report the resource or scheduler reason that is keeping them out of admission, so queue triage does not require reading daemon internals.

  • vq scheduler queued status is clearer. Scheduler-backed jobs now distinguish local pending, admitted, and remote queued states more consistently in vq status.

  • Open-shell molecular Casida TDDFT now runs for UHF/UKS references. run_tddft_casida_uhf(...) builds the unrestricted A/B response matrices, including same-spin exchange, cross-spin direct coupling, and the spin-polarised LDA/GGA/global-hybrid f_xc blocks. The high-level run_job(..., tddft=True, tddft_type="casida") path now uses it for UHF/UKS instead of rejecting open-shell full response; range-separated hybrids still fail closed until attenuated ERIs exist.

  • Open-shell Γ GAPW DFT now has a polarised augmentation-energy path. run_periodic_uks_gapw(..., functional="lda"/"pbe"/...) no longer raises from the missing spin-2 per-atom XC energy correction. The GAPW Fock-side polarised XC augmentation and final total-energy assembly now use matching hard-minus-soft alpha/beta densities; meta-GGA GAPW remains gated until tau augmentation exists.

  • Gamma open-shell GPW DFT+U now runs through the requested backend. run_periodic_job(..., jk_method="gpw", method="UHF"/"UKS", dft_plus_u=...) now adds the Dudarev per-spin +U energy and Fock terms and records gpw_uhf_gamma / gpw_uks_gamma in metadata.

  • Gamma open-shell GAPW DFT+U now runs through the requested backend. run_periodic_uhf_gapw(...), run_periodic_uks_gapw(...), and the high-level run_periodic_job(..., jk_method="gapw", method="UHF"/"UKS", dft_plus_u=...) route use the same per-spin Dudarev convention as GPW and record gapw_uhf_gamma / gapw_uks_gamma in paper-audit metadata.

  • Open-shell hybrid TDA now includes the spin-polarised f_xc kernel. UHF/UKS TDA builds the polarised LDA/GGA response blocks from the converged alpha/beta densities, and the high-level run_job(tddft=True) path forwards those densities instead of emitting a partial-kernel warning for UKS/PBE0.

  • Periodic BIPOLE population sidecars now emit k-resolved Loewdin charges. The BIPOLE writer evaluates diag(S(k)^1/2 P(k) S(k)^1/2) over the SCF k-mesh and writes numeric Loewdin charges to .population.* and QVF atom_properties. Ordinary bulk dipoles remain cleanly unsupported until a Berry-phase polarization route exists.

  • Implicit-solvation output labels now track the actual route. HF/DFT run_job(..., solvent=...) CPCM runs now print Implicit solvation (CPCM), while MSINDO’s GEPOL route keeps the COSMO label. The block also prints the gas-phase and in-solvent totals alongside E_solv, so validation tables can compare the intended quantity directly. This is output metadata only; CPCM/COSMO energies and tolerances are unchanged.

  • FNO-CCSD(T) now preserves explicit all-electron requests through run_job. CCSDOptions(fno=True) still follows the run_job chemical-core default, but CCSDOptions(fno=True, n_frozen_core=0) now remains all-electron instead of being silently coerced back to frozen-core. The no-truncation FNO path is regression-pinned against canonical all-electron CCSD(T).

  • GFN2-xTB third-order parameters now use tblite’s scale convention. The on-demand GFN2 parameter parser stores GAM3 with the required 0.1 factor, invalidates stale caches with unscaled values, and keeps the positive-GAM3 stability gate pinned against the corrected values. The experimental faithful-AES path now includes its atom-resolved Fock potential; the strict translated-water xtb parity gate remains xfailed until the remaining short-range H0/Pauli gap is closed.

[v0.15.9] - 2026-07-01 - Neese’s Cheetah

Superseded quickly by v0.15.10 during release-paper hardening.

Added

  • Input/output workflow (v1.1): AST-based .py input parser, script generator with 5 calculation templates, ASE format support

  • Interactive editor (v1.2): click-to-select, undo/redo, element palette, auto-bonding, add/delete/change atoms

  • Build tools (v1.3): 12-fragment library, H-addition, supercell builder, 360 turntable export, presentation mode

  • vq integration (v1.4): submit button, job monitor with auto-refresh, auto-open results

  • Crystal builder (v1.5): 23 space groups, cell parameter editor, fractional/Cartesian conversion, Miller slab cutter

  • Rendering presets (v1.6): 6 material styles (CPK glossy, matte, glass, metallic, toon, scientific), SSAO ambient occlusion, shadow mapping

  • POV-Ray export: CPK spheres, bond cylinders, 3-point lighting

  • Blender export: self-contained Cycles scene with PBR materials

  • Collaboration (v1.7): shareable URLs, iframe embed, session management, 3D annotations

  • Desktop packaging (v1.8): Electron scaffolding, tray icon, file associations, auto-update

  • Supersampled screenshots: 2x SSAA with Lanczos downsampling

  • 512 tests (509 passed, 3 skipped) across 16 test files

[v0.15.8] - 2026-06-30 - Neese’s Cheetah

Fixed

  • Periodic BIPOLE population sidecars now cover Mulliken and Mayer output on the same operator-cell support as the SCF energy. Converged BIPOLE SCFs write lattice-summed Mulliken charges and Mayer bond-order sidecars without falling through to molecular shape assumptions. Loewdin and ordinary dipole output remain cleanly marked with structured unsupported: reasons until their periodic conventions are implemented.

  • Periodic GDF overlap screening now recovers full def2-SVP support for diffuse tight-cell cases. The overlap builder expands the lattice shell search enough for screened def2-SVP cells such as MgO, avoiding truncated support in population and post-SCF artifacts.

  • Hybrid TDDFT now receives the converged density in the high-level runner path. Molecular hybrid TDDFT jobs no longer lose the reference density when dispatching through run_job.

  • Multireference jobs fail earlier and more clearly when the requested calculation is not viable. V2RDM jobs now fail closed on nonconverged solver state, and CASPT2 active-space memory is preflighted before launching jobs that would otherwise run into an avoidable allocation failure.

  • Open-shell molecular SCF optimization is more stable for feature-heavy jobs. The molecular runner preserves the resolved open-shell options through optimizer and post-SCF setup paths.

  • Twin scheduler operations in vq are clearer and safer. Scheduler-host fetch/status messages now spell out the daemonless Twin path, fleet update fan-outs skip scheduler-only hosts, vq doctor can preflight scheduler-side helpers, and vq top/vq usage expose elapsed and resource-pressure accounting for retained jobs.

Experimental

  • CASPT2/NEVPT2 numerical Hessian, Dyall chain-rule, and CP-MCSCF Lagrangian gradient helpers landed behind explicit/gated paths. These are internal MRPT hardening pieces for the release-paper calculations and are not a production headline.

[v0.15.7] - 2026-06-30 - Neese’s Cheetah

Changed

  • QVF archives are now emitted by default. run_job, run_periodic_job, and OutputPlan.from_run_job_kwargs default output_qvf=True, while callers that want the old behavior can still pass output_qvf=False.

Fixed

  • Periodic BIPOLE population sidecars now report lattice-summed Mulliken charges. Converged BIPOLE SCFs write .population.txt / .population.json with numeric Mulliken charges from real-space density and overlap blocks on the same operator-cell support used by the SCF energy contraction. Loewdin, Mayer, and ordinary dipole output remain cleanly marked with structured unsupported: reasons until their periodic conventions are implemented, so raw NumPy shape exceptions from molecular property helpers stay out of artifacts.

[v0.15.6] - 2026-06-29 - Neese’s Cheetah

Fixed

  • Multi-k periodic Gamma-only artifacts now use the actual Gamma k-point. Molden-style fallbacks select the explicit k=0 block instead of assuming the first item in a multi-k result list is Gamma, and complex per-k density output now requires k-point and weight metadata before folding to a real lattice density.

  • Molecular run_job now accepts initial_guess=. The high-level keyword maps to the resolved RHF/UHF/RKS/UKS options, including post-SCF reference SCFs, and READ/FRAGMO require their matching restart or fragment state.

[v0.15.5] - 2026-06-29 - Neese’s Cheetah

Fixed

  • DLPNO-CCSD restores the release-paper pair-screening default. LocalCCSDOptions.tcut_pairs is back at 1e-4, after a previous cleanup accidentally reset the default to zero. The M16 n-butane / cc-pVDZ release-paper regression now documents the ORCA TightPNO no-frozen-core comparison and keeps the remaining local-correlation convention gap explicit instead of letting the default drift silently.

  • DLPNO-CCSD(T) .out files now print the audit components needed for the paper tables. Runs emit named CCSD correlation, triples correction, final DLPNO-CCSD(T) correlation, and final DLPNO-CCSD(T) total lines, with a regression that pins the M16 n-butane / cc-pVDZ output block.

  • Periodic post-SCF outputs now handle valid complex Bloch densities. Population summaries, lattice density folding, and QVF real-only artifacts accept Hermitian complex densities from multi-k periodic jobs, strip only negligible imaginary residuals where the file format is intentionally real, and fail closed on genuinely complex artifacts.

  • vq programs now detects broken ORCA MPI startup on macOS and Linux. Registered ORCA binaries probe their sibling orca_startup_mpi, so a serial-OK ORCA install whose parallel launcher cannot load libmpi.40.dylib or libmpi.so.40 reports an explicit serial-only warning before another %pal nprocs N job is submitted. The operations runbook documents both the Homebrew DYLD_LIBRARY_PATH recovery path and the Linux LD_LIBRARY_PATH variant; release-paper ORCA jobs on affected hosts must run serially until the daemon environment exposes the matching MPI library directory.

  • The multi-user pause/resume tests now ignore the ambient local queue. The fixture also sets VQ_STATE_DIR, so full-suite runs on an active development host do not pause or count unrelated single-user jobs.

[v0.15.4] - 2026-06-29 - Neese’s Cheetah

Fixed

  • ORCA pure-GGA DF references now use ORCA’s accepted RI simple-input spelling. The molecular regression runner emits RI + def2/J for pure-GGA density-fitting rows such as benzene/def2-SVP/RKS-PBE-DF, instead of the rejected RIJ keyword. Parser keyword rejections now remain errors so bad generated ORCA input is not hidden as an unavailable reference.

  • Periodic molecular properties now accept valid Hermitian complex densities. Multi-k Bloch densities may carry large imaginary off-diagonal entries while remaining exactly Hermitian; the property guard now checks the Hermiticity residual and only warns on genuinely non-Hermitian density assembly.

  • Explicit periodic DFT+U JK routes are honored. run_periodic_job no longer diverts explicit GPW/GAPW/BIPOLE +U requests through the legacy dispatch path, and the requested/resolved/executed route is recorded in .out, .system, and QVF metadata.

  • The exact PBE 1996 6-311+G(3df,2p) basis is now bundled. Both 6-311+g3df2p and the literal 6-311+G(3df,2p) spelling load the same canonical Pople-component basis, so the paper reproduction no longer needs to skip or substitute that row.

Testing

  • The release gate baseline removes six files that were green in the v0.15.2 gate. Future non-PASS results in those files now count as new reds: tests/test_periodic_accelerator_uniformity.py, tests/test_periodic_gapw_gradient.py, tests/test_ase_periodic_gpw.py, tests/test_audit_20260530_periodic.py, tests/test_bipole_gradient.py, and tests/test_pw1pw.py.

Documentation

  • Codename artwork is now bundled with the docs. The changelog renders the available generated codename images on the matching v0.9.0-v0.15.0 release sections, and the v0.15 landing page now shows the 15a Neese’s Cheetah artwork above the DLPNO local-correlation headline.

  • The v0.7.0 historical codename art now uses the compass-jellyfish variant. The original compass-object image remains bundled for the vibe-qc-and-AI talk; the changelog uses the animal-series version.

  • The landing page now keeps experimental MRPT gradient work out of the production headline. The current-release feature copy focuses on the stable CASSCF, TDDFT, and DLPNO surfaces.

[v0.15.3] - 2026-06-29 - Neese’s Cheetah

Fixed

  • BIPOLE pure-RKS now uses the exact analytical-FT periodic Hartree J. MgO/STO-3G LDA SHRINK-2 fixed-density diagnostics showed the old J_SR(erfc) + J_LR(erf) split sat +14.99 mHa above the G=0-dropped exact Ewald-J reference on the same density, while widening only the reciprocal envelope changed less than 1e-8 Ha. Pure RKS now routes Hartree J through the exact analytical-FT, neutral-background convention used by the GDF reference; hybrid/RHF exchange and alpha_hf/exxdiv paths are unchanged.

  • Regression-suite error rows are now classified instead of reported as broken calculations. The LiH/STO-3G/RKS-LDA dense-overlap Γ-only EWALD_3D diagnostic remains guarded and is emitted as unavailable, not error. The ORCA molecular runner preserves raw inputs, outputs, and logs for reference failures so generated-input problems remain debuggable instead of disappearing into the dashboard. The suite also writes self-contained run directories under ~/vibeqc-runs (or --output-root) with per-code inputs, stdout/stderr, and parsed JSON.

  • Release validation now fails early on runtime pin drift and accepts the documented validation API edge cases. The gate catches mismatched runtime pins before release artifacts are produced, while defaulted validation calls no longer reject supported inputs.

  • Release-paper RSH functional spellings now resolve through the functional dispatcher. Common aliases used by the manuscript calculations route to the intended libxc definitions instead of failing at setup.

  • Gamma-only RKS parity now routes through the GDF path used by the current periodic reference checks. This keeps the regression harness aligned with the supported dense-crystal validation envelope.

  • vq and vibe-view received paper-calculation hardening. vq now retries fetches for located job owners and caps local thread pools from the CPU budget. vibe-view gained the Python API, section slice/merge/compare/serve workflows, standalone HTML/JSON/capture exports, and a reader-lifetime fix so chained API calls can reuse externally supplied QVFReader handles.

  • SCF and multireference diagnostics were tightened. The EDIIS hybrid switch metric is normalized, unsupported low-dimensional AICCM-B four-center jobs are skipped instead of launched, and the NEVPT2 trace-correction diagnostics were updated to keep the lower-error release-paper path.

Documentation

  • The website landing page now presents the v0.15 paper-hardening line as the current release focus. The old v0.14.0 CCSD(T) callout and stale v0.16.0 preview were removed, and stale dev-version examples were refreshed.

[v0.15.2] — 2026-06-28 — Neese’s Cheetah

Fixed: vq scopes every local job in a cgroup (even capless) and reaps it on teardown (2026-06-27)

  • On a cgroup host (cgroup.available() true), cgroup.wrap_command returned the raw argv for any job declaring no mem_mb/cpus, so a capless job ran under pgid-only accounting. The pgid walk cannot see descendants that escape via setsid/PR_SET_PGID (loky/joblib worker pools, ninja, mpirun), so their RAM stayed “unowned” by vq. This is the v0.15 memory-audit symptom class where a host shows tens of GB used against ~1 GB of summed process RSS. wrap_command now wraps any job carrying a unit_name in an accounting-only vq-job-<id> scope (no MemoryMax/CPUQuota), so the kernel cgroup captures every descendant and the watchdog’s memory.current/cpu.stat readers see the true total. A capless and nameless command (not a job) still runs raw.

  • The daemon reaps that scope on teardown. A new Daemon._reap_scope best-effort systemctl stops the unit at every hard-SIGKILL site (the STATE-2 startup reap, the STATE-3 kill escalation, the watchdog SIGKILL, the Popen-window reap) and at the single _record_finish hook, so descendants killpg cannot reach die with the job instead of lingering. Idempotent, and a no-op without cgroups (macOS, no delegation). Tests: tests/test_cgroup.py test_unit_name_without_caps_still_scopes, tests/test_terminal_reaping.py TestScopeReaping. Found by the v0.15 release memory audit (finding #3).

Fixed: memory pre-flight no longer phantom-aborts large default-options jobs (incl. DLPNO) (2026-06-27)

  • vibeqc.memory._uses_direct_scf treated a missing options object (options=None) as a conventional in-core SCF, so the pre-flight added a dense n_basis**4 “ERI tensor” term and raised InsufficientMemoryError even though the driver default scf_mode=AUTO runs the integral-direct path above 200 basis functions. A dlpno-mp2/dlpno-ccsd/dlpno-ccsd(t) job on n-octane / cc-pVTZ (492 bf, default options) aborted at ~523.9 GB (a ~436 GB phantom ERI tensor + headroom) before any compute – and gave the same number for all three methods because it was their shared SCF-reference estimate, not the DLPNO step (DLPNO itself is reduced-scaling: per-pair PNO domains, no dense n_mo**4). Now None / an unset scf_mode inherits the AUTO default (threshold 200), so the estimate matches what the SCF actually does: n-octane/cc-pVTZ RHF now estimates ~0.07 GB and the job runs (DLPNO-MP2 ~13 GB with a DF reference). The dense-ERI estimate is still used for small systems and explicit scf_mode='conventional'.

  • The InsufficientMemoryError message now points at the real levers (density fitting, integral-direct SCF) instead of the stale “once shipped in v0.6+”.

  • Docs: docs/user_guide/dlpno_mp2.md gains a “Larger systems” note – use a density-fitted SCF reference; the O(N^6) pilot (DLPNOCCSDPilotOptions) is a 64-bf correctness oracle, not the production path (the default dlpno-ccsd already uses the reduced-scaling local solver). Tests: tests/test_memory.py (28) green.

Fixed: multi-k GDF auto-grows the one-electron S/T lattice cutoff so diffuse bases no longer abort with a spurious non-PSD S(k) (2026-06-27)

  • run_krhf_periodic_gdf / run_kuhf_periodic_gdf built the one-electron overlap/kinetic lattice sums at the flat default LatticeSumOptions.cutoff_bohr = 15 bohr regardless of basis diffuseness (the GDF Lpq path already sized its cutoff per basis; the S/T sum did not). For a diffuse molecular basis on a dense lattice — e.g. def2-SVP on rocksalt MgO, whose most-diffuse Mg-s primitive is α≈0.034 bohr⁻² — the truncated Bloch sum S(k) = Σ_g e^{ik·g} S(g) is not a Gram matrix and acquires a large spurious negative eigenvalue (min eig ≈ −9.6e-2 at Γ for MgO (4,4,4)), tripping the scf_preflight_overlap_check non-PSD (“critical”) abort whose message wrongly implied an image-summing bug. The image summation is correct; the cutoff was simply too short.

  • Fix: periodic_k_gdf._oneel_lattice_opts measures the loosest S/T cutoff that makes S(k) positive-semi-definite at every requested k-point (via eigs_preflight.optimize_truncation, target severity “warn”) and raises the cutoff to it before the S/T build; it never lowers the cutoff. The a-priori PySCF rcut estimate is deliberately not used — it targets the AFT-corrected Lpq sum and under-sizes a bare overlap (≈11.6 bohr for a filtered def2-SVP/MgO basis whose overlap only turns PSD at ≈17 bohr). Overlap builds are milliseconds, so the cost is negligible against SCF. Tight/solid bases (sto-3g, pob-tzvp-rev2) are already PSD at 15 bohr and are returned unchanged, so every existing multi-k GDF job is unperturbed (full GDF driver suite green).

  • A basis that is genuinely near-linearly-dependent on the lattice (full, unfiltered def2-SVP on MgO: converged λ_min(S) ~1e-9) cannot be made PSD by any cutoff; optimisation declines and the per-k preflight now aborts with an actionable remediation_hint (filter diffuse primitives via vq.make_basis(..., exp_to_discard=0.1), or use a solid-state basis such as pob-tzvp-rev2; eigs_preflight.disambiguate_critical_overlap() reports which applies) instead of the generic “upstream bug” framing. The hint is a new optional parameter on linear_dependence.scf_preflight_overlap_check; molecular callers that omit it keep the original message. Filtered def2-SVP/MgO (4,4,4) now auto-grows 15→17.81 bohr and runs past the preflight with every S(k) at “ok”. Tests: tests/test_oneel_lattice_cutoff.py.

Fixed: periodic analytic XC Pulay gradient kernel is now cross-cell (matches the cross-cell energy) (2026-06-26)

  • xc_lattice_gradient_contribution (and the UKS sibling xc_lattice_gradient_contribution_uks) — the analytic fixed-grid XC Pulay force — summed only the home-bra density slice ρ = Σ_s χ_0 P(s) χ_s, while build_xc_periodic{,_uks} was updated to the full cross-cell density ρ = Σ_a Σ_s χ_a P(s−a) χ_s (the dense-crystal XC P0 fix). On a periodic density (P(g≠0) populated) the two disagree, so the analytic XC force missed the bra-image motion: test_periodic_xc_lattice_gradient_kernel_matches_fd was red at 9.25e-3 Ha/bohr vs the fixed-grid finite difference of build_xc_periodic E_xc (a home-only density was already exact). Both kernels now sum the bra over image cells using the IDENTICAL build_braket_pairs screening as the energy build, restoring kernel == FD (RKS test green; new cross-cell UKS pin test_periodic_uks_xc_lattice_gradient_kernel_cross_cell_matches_fd at ~4e-10). FD remains the production force path (the KS analytic gradient is a maintained preview). The original v0.14 bug inventory is retired and recoverable from git history.

Fixed: periodic meta-GGA (TPSS/M06-L) XC — von Weizsäcker τ floor cures the SCF + analytic gradient (2026-06-26)

  • Periodic TPSS/M06-L BIPOLE SCFs blew up — MO eigenvalues ~±1e7–1e8, TPSS even converging to a spurious +0.34 Ha basin — and the meta-GGA analytic gradient missed FD by 4–21 Ha/bohr. Root cause (re-diagnosed; the earlier “low-density singular v_σ” + clipping story was wrong): the cross-cell periodic XC assembly (build_xc_periodic, sum over image cells with signed density blocks) produces a kinetic-energy density τ slightly below the von Weizsäcker bound τ_W = |∇ρ|²/(8ρ) — even negative — from roundoff at diffuse grid points. Unregularised meta-GGAs are singular there (libxc returns v_σ/v_τ ~ 1e16 with a correct E_xc), which contaminated the V_xc Fock’s virtual subspace. Molecular TPSS/M06-L were always fine; SCAN/r2SCAN self-regularise.

  • Fix: clamp τ τ_W (enforce_von_weizsaecker_floor in cpp/src/periodic_xc.cpp) in build_xc_periodic, build_xc_periodic_uks, and both gradient kernels (xc_lattice_gradient_contribution{,_uks}) — per spin for UKS. It is a no-op wherever the physical bound already holds, so the converged energy is unchanged, and it is applied identically in the energy, the SCF Fock, and the analytic gradient so FD and analytic forces stay synchronised (clamping τ to 0 is not enough — libxc still diverges at τ=0 with finite σ). All the prior v_σ/v_τ ±10 clips, the ρ<1e-12 τ-screen, and the ±10 MO-energy clips in the energy-weighted-density builders (bipole_gradient.py) are removed (they desynchronised the analytic gradient from its own energy).

  • Result: TPSS H₂/STO-3G Γ E = −1.175 Ha (was +0.34), eigenvalues bounded ±0.5; the corrected-gauge Γ analytic gradient matches the production FD to ~1.4e-7 Ha/bohr for TPSS and M06-L (= SCAN). FD remains the documented production force path (the whole KS analytic gradient is a maintained preview), but meta-GGA is no longer a paper-blocking exception. Tests: tests/test_bipole_gradient.py (test_periodic_mgga_vxc_bounded_on_cross_cell_density, test_periodic_mgga_scf_eigenvalues_bounded, test_corrected_gauge_rks_mgga_gradient_matches_fd @slow). See handovers/HANDOVER_BIPOLE_GRADIENT.md (Case 2).

Performance: GPW SCF no longer rebuilds the grid collocation cache for its final breakdown (E3) (2026-06-26)

  • run_periodic_rhf_gpw / run_periodic_rks_gpw built the iteration-invariant collocation cache (the 27-image evaluate_ao AO-on-grid table — ~90 % of a single-point on a fine plane-wave grid) once for the SCF and again for the final energy breakdown (evaluate_gpw_energy built its own). evaluate_gpw_energy now accepts a collocation_cache= and the SCF threads its own in, so the breakdown reuses it (same basis + grid; bit-identical). A single H₂/STO-3G GPW single-point at cutoff_ha=200 (160³ grid) drops 8.6 s → 3.8 s (~56 %); this is the per-displacement cost the GPW finite-difference gradient/Hessian tests pay, so the test_periodic_gapw_gradient fast-lane time roughly halves.

Fixed: SLAB_EWALD_2D Hartree J is now Ewald-split — all-electron cores + polar slabs (2026-06-26)

  • The 2D-slab Hartree J (vibeqc.ewald_composed_slab, CoulombMethod.SLAB_EWALD_2D, still SCF-gated) was built undamped full-reciprocal, which under-resolves the cusp of an all-electron core: ½Tr[D·J] on a polar HF layer drifted ~0.14 Ha with ke_cutoff and never converged. J is now built Ewald-split, matching the already-split slab V_ne: J_short (erfc cusp, libint real-space, full-Bloch homogeneous density) + J_long (damped 4π/G²·e^{-G²/4α²} reciprocal, g≠0) + J_g0 (screened Parry g0(z), the same kernel V_g0/E_nn use — the old bare |z| was the lone outlier). The cusp is exact in real space, so the reciprocal mesh converges at small |G| independent of the core. ½Tr[D·J] is now α-invariant (the split parameter) and the polar bilinear total matches the core-converged vacuum-extrapolated EWALD_3D + Yeh-Berkowitz reference.

  • This supersedes the “surface-dipole / z-profile first-moment bug” in the v0.14 open-bug inventory, which was a misdiagnosis: the z-resolved AO-pair density’s first moment is the correct Bloch-summed z-dipole Σ_g ⟨μ,0|z|ν,R_g⟩ (verified to 4.5e-11 vs compute_multipole_moments_lattice), not the home-cell ⟨μ|z|ν⟩ the old xfail compared against. At landing time this unblocked the Increment-4 SCF un-gate.

  • Tests: tests/test_j_slab_ewald_2d.py (test_j_alpha_split_invariant, test_polar_bilinear_total_alpha_invariant, test_polar_bilinear_matches_core_converged_3d @slow); tests/test_v_ne_slab_ewald_2d.py::test_z_profile_first_moment_is_bloch_dipole. See handovers/HANDOVER_SLAB_EWALD_2D.md.

Added: vibe-view — interactive bookmarks, session save/restore, orbital animation, batch –volumes, Jupyter %vibeview, CLI export/diff/validate/info (2026-06-28)

  • Interactive bookmarks — Save Current View captures camera + active section + isovalue + colormap + opacity as a named preset. Apply restores all five properties. Session save/load persists complete state to .vibe-session JSON.

  • Orbital animation — play/pause button in the wavefunction panel auto-advances through MOs at configurable speed, rendering each one.

  • Batch --volumesvibe-view batch --volumes renders each volume section (density, orbitals, ELF, etc.) as separate PNGs alongside the structure.

  • Jupyter %vibeview magic%load_ext vibeview.jupyter provides inline structure rendering, data tables (charges, MO energies), SCF charts, and band plots in notebooks.

  • New CLI commands: vibe-view info (provenance + section sizes, --json), vibe-view export (geometry to XYZ/CIF/OBJ/glTF), vibe-view diff (compare two QVFs: energy delta, section overlap, RMSD, --json), vibe-view validate (full SHA-256 integrity check on all members).

  • Density-difference UI — when two loaded files both carry density sections, the sidebar shows a Density Difference card for computing rho_A - rho_B.

  • Full tutorial at docs/tutorial/working_with_vibe_view.md (80+ sections, 14 screenshots). Tests: 375 passed, 3 skipped.

  • Additional CLI commands: vibe-view compare (auto-compare-mode), vibe-view serve (web file browser), vibe-view slice (extract sections), vibe-view merge (combine QVFs). Enhanced --version shows Python/PyVista/VTK versions. --section flag on open for auto-activation. --short flag on info for one-line summaries. Export supports OBJ and glTF 3D formats.

Added: GFN2-xTB faithful anisotropic electrostatics (AES), experimental opt-in (2026-06-26)

  • What. A faithful atom-resolved GFN2 AES (Bannwarth, Ehlert & Grimme, JCTC 2019, doi:10.1021/acs.jctc.8b01176) behind the new XTBSccOptions.aes_faithful flag (default off — the shipped ad-hoc shell-resolved path is byte-identical; the existing GFN2 suite is unchanged). The flag-on path adds the on-site DPOL/QPOL XC kernel + CN-damped (fdmp3/fdmp5) inter-site charge-dipole / dipole-dipole / charge-quadrupole as a post-SCC energy correction at the converged density.

  • Parameters (DPOL/QPOL ×0.01, tblite p_rad/p_vcn) are parsed in gfn2_params.py and carried on GFN2ElementData (dpol/qpol/mp_rad/ mp_vcn); fetched at runtime, not bundled (LGPL-3.0).

  • Validated by invariants (size-consistent 1/R³ tail, deterministic, physical charges, PES well) and qualitatively (correct repulsive dipole-dipole sign for parallel-translated waters). Not yet at quantitative xtb parity: the self-consistent (Fock-coupled) form and the parity gate are deferred to a live-xtb decomposition oracle (handovers/HANDOVER_GFN2_AES.md).

  • Tests fixed. Reconciled stale GFN2 docstrings (H₂O is 0.21 Ha off the xtb ref and passes, not “~0.8 Ha off / xfail”); rewrote the water-dimer xfail reason to the measured −3.7 mHa and the three real issues (translated-pair geometry isn’t an H-bond; SCC-only vs a dispersion-needing target; ad-hoc kernel). Wired a chemically-discriminating SCC-only dimer gate (test_water_dimer_vs_xtb_oracle).

  • Live xtb 6.7.1 oracle run (vq, maru — xtb fetched into the job workdir): the parity gate is now active (xfail at ±1.5 mHa over 5.6/6.0/7.0 bohr). It shows the translated water pair is GFN2-repulsive (+11.7 mHa at 5.6 bohr, D4 ≈ 0), so vibe-qc’s −3.8 mHa is the wrong sign; the dominant remaining gap is the H0/overlap (Pauli) term, not AES (handovers/HANDOVER_GFN2_AES.md §6).

Changed: GAPW all-electron Γ SCF route un-gated (experimental warning retired) (2026-06-26)

  • The two open correctness bugs on the GAPW all-electron augmentation route are fixed (the analytic ∂E_H/∂D Hartree Fock + the bonded-molecule overlapping-augmentation double-count, both above), so the run-level GAPWExperimentalWarning is retired for the Γ-point closed-shell RHF / RKS (LDA, GGA) and open-shell UHF augmentation SCF (run_periodic_job(jk_method="gapw") and the run_periodic_{rhf,rks,uhf}_gapw entry points + the GapwJBuilder / GapwAugmentation constructors).

  • Still gated / fail-closed (unchanged): multi-k GAPW (run_periodic_rks_gapw_multi_k), range-separated hybrids, the OT solver, and the MPI z-slab build keep GAPWExperimentalWarning (the fixes were validated at Γ only); meta-GGA GAPW and open-shell GAPW-DFT (run_periodic_uks_gapw with a functional) still raise NotImplementedError (no τ / polarised-XC augmentation). The smooth-grid GPW route and its grid/smearing/atomic-grid helpers keep their own GAPWExperimentalWarning (separate subsystem, not part of this validation).

  • Accuracy is preliminary, not a correctness gate (see docs/user_guide/gapw.md): the per-atom augmentation has a grid-convergent absolute residual that is large for second-row atoms at the default N=24 grid (Ne +339, O +215 mHa; He −0.6), and pure GAPW-HF on light elements carries the intrinsic soft-basis bond residual (H₂ ~−108 mHa). GAPW is an all-electron DFT route; for quantitative molecular HF use GDF or BIPOLE. The .out “(experimental)” label stays.

Fixed: GAPW bonded-molecule overlapping-augmentation double-count (2026-06-26)

  • Symptom. Molecular GAPW over-bound the all-electron reference (H₂/STO-3G ~−101 mHa for LDA), scaling with augmentation-sphere overlap (2·r_aug/d). The per-atom hard/soft augmentation double-counted the overlap region.

  • Two root-cause fixes in python/vibeqc/periodic_gapw_augment.py:

    1. Own-atom compensatorcompensator_density_on_atomic_grid(..., own_atom_only=True). The per-atom radial augmentation now uses only atom A’s own ρ₀ compensator; the global compensator belongs on the smooth FFT grid. Summing every atom’s compensator Gaussian on each isolated radial grid (and feeding it to the spherical solve_poisson_radial) double-counted the inter-atomic compensator interaction the smooth term already holds (H₂ ~+66 mHa).

    2. Partition-of-unity (GapwAugmentation._partition_weight) — the augmentation integrals are weighted by p_A = w_A·max_b w_b / Σ_b w_b instead of the bare radial window, so overlapping spheres do not double-count the local XC/Hartree augmentation. p_A reduces to the window for an isolated atom ⇒ single-atom results (He/Ne/O) are unchanged (verified identical), and ½ tr(D·J) = E_H still holds to ~1e-13 (the same weight feeds the analytic Fock and the energy).

  • Result. H₂/STO-3G molecular parity: LDA −3.5 mHa, PBE −1.7 mHa (both well under the ±5 mHa target, across two functionals so the pass is not a single-functional cancellation). The xfail test_gapw_molecule_matches_molecular_xfail is now a real, parametrised (LDA + PBE) assertion test_gapw_molecule_matches_molecular.

  • Known residual (intrinsic, not the double-count). Pure GAPW-HF (no XC) on light elements still over-binds the all-electron GDF reference (H₂ ~−108 mHa): hydrogen’s tight STO-3G primitive is a valence function (H has no core) that the soft basis prunes from the bond, so the cross-atom bond density is represented with the soft basis. This is the intrinsic GAPW soft-basis approximation, distinct from the (now-fixed) overlap double-count; it is offset by the matching XC response for DFT (hence LDA/PBE pass). GAPW remains opt-in behind GAPWExperimentalWarning.

Fixed: GAPW all-electron Hartree Fock is now the exact ∂E_H/∂D (P0) (2026-06-26)

  • Root cause. The experimental GAPW route’s GapwJBuilder.build_J (python/vibeqc/periodic_gapw_augment.py) returned J_smooth(full basis) + Σ_a ∫χχ(V_hard V_soft), which is not the derivative of gapw_hartree_energy. For a partially-occupied core atom the inconsistent Fock pushed the tight 1s above the valence, so the SCF aufbau left the core EMPTY: isolated O collapsed to ~−35 Ha (e_kin ~17) versus the all-electron GDF ~−73.8. The energy expression was already correct at the core-occupied density (O +215 mHa vs GDF); only the Fock was wrong.

  • Fix. Ported the validated analytic exact Fock J = ∂E_H/∂D from examples/regression/gapw_parity/milestone3_analytic_fock.py: (A) the smooth term on the soft basis (∂ñ/∂D uses the soft AOs), embedded; (B) on-centre hard / (C) on-centre soft radial augmentation; (D) the previously-missing compensator ρ₀ response Σ_lm M_lm(⟨g_lm|V_smooth⟩−⟨g_lm|V_soft⟩) with M_lm = ∂Q_lm/∂D. ½ tr(D·J) = E_H now holds to ~1e-13, so the SCF recovers the core: O → −73.557 (+215 mHa), e_kin 73.4, ε₁ₛ ≈ −20; Ne preserved (+339 mHa). The D-independent pieces (the M tensors + the unit-compensator grid/radial collocations) are cached once per builder so the per-iteration cost is only the radial Poisson solves.

  • Scope. RHF/RKS + UHF/UKS (their J routes through build_J(D_total)). GAPW stays opt-in behind GAPWExperimentalWarning (the bonded-molecule overlapping-augmentation double-count is a separate open item). Regression: tests/test_periodic_gapw_augment.py::TestGapwScf::test_gapw_o_core_occupied.

Fixed: DMRG solver honors the requested spin projection (ms2) (2026-06-26)

  • The toy/exact DMRG backend (vibeqc.solvers._dmrg.DMRGSolver) previously constrained only the total particle number, so a solve could return a lower-lying wrong-spin sector (e.g. an ms2 = -2 state for a requested closed shell). It now diagonalizes the fixed-(N, ms2) sector: the full-Hamiltonian penalty lifts both wrong-N and wrong-ms2 states, and the public fixed-sector index selection filters on the α/β occupation implied by ms2 (even spin-orbitals are α, odd are β per _spatial_to_spinorbital). solve now reads Hamiltonian.ms2; the bare _build_full_hamiltonian path stays particle-number-only unless _ms2 is set, preserving existing behavior.

  • Impossible sector requests are rejected up front (_validate_sector): a parity mismatch ((N + ms2) odd) or a sector demanding more α/β electrons than spatial orbitals raises a clear ValueError.

  • Tests: tests/test_solvers_fci_parity.py — un-xfailed test_dmrg_respects_ms2_when_wrong_spin_sector_is_lower, added test_dmrg_picks_singlet_over_lower_triplet_sector, test_dmrg_rejects_impossible_parity_request, test_dmrg_rejects_oversized_spin_sector.

Added: real 2021 COSX GridX grids + multi-stage SCF progression (2026-06-26)

  • The opt-in cosx_grid_level 1..4 tiers are now the verbatim 2021 5-region pruned-Lebedev “AngularGrid” grids of Helmich-Paris, de Souza, Neese & Izsák (J. Chem. Phys. 155, 104109, 2021), Table I, replacing the earlier single-order approximation. Each tier splits an atom’s radial shells into 5 index-bands with a distinct Lebedev order per band (sparse core, dense valence, thinned tail). The Table I angular point counts are exact; the radial counts and region boundaries are documented approximations (ORCA’s per-element radius multipliers + differential- evolution radial parameters are ORCA-binary only, not openly published). New Lebedev order-5 (14-pt) and order-7 (26-pt) tables back the inner regions (cpp/src/lebedev_data.cpp). Grids built via a new GridOptions.orca_angular_points field in cpp/src/grid.cpp (shell-index 5-region scheme; preserves the lock-free uniform per-atom layout).

  • Multi-stage SCF grid progression (DefGrid stage triples): when a GridX tier is opt-in, the SCF steps through coarse → middle → fine grids, converging each non-final stage loosely (density carried forward) then converging on the fine grid. New cosx_grid_stages_for_level(level) (the stage grids; last == cosx_grid_options_for_level(level)), driven by a templated run_cosx_staged_scf helper (cpp/include/vibeqc/cosx_staged.hpp) wired into all four SCF drivers (RHF/UHF/RKS/UKS). SPINLOCK keeps the single-fine-grid path. The legacy single-grid path is unchanged.

  • Reach across all methods. RIJCOSX now also drives ROHF (ROHFOptions(cosx=True, cosx_grid_level=...), single-grid via the RI-J + chain-of-spheres closures). Every post-HF method on a mean-field reference (MP2 / RI-MP2 / DLPNO-MP2 / CCSD / CCSD(T) / DLPNO-CC / TDDFT) inherits RIJCOSX through its reference SCF (set cosx=True on the reference’s options); no cosx fields were added to the MP2/CCSD option structs because COSX is purely an SCF concern. The CAS family stays on the exact/DF reference path (it needs exact MO integrals).

  • Default flipped: cosx_grid_level 0 -> -1 (AUTO) for RHF/UHF/RKS/UKS (+ ROHF). When cosx=True, the SCF energy now uses a 2021 GridX tier + multi-stage progression by default, and is conv-tolerance-aware: a new cosx_use_gridx(grid_level, conv_tol_grad) gate (cosx.hpp) keeps GridX for normal-tolerance SCFs but auto-falls-back to the legacy grid when conv_tol_grad < 1e-6 (the commutator-noise floor), so tight-SCF and tight open-shell runs are byte-for-byte unchanged. cosx_grid_level=0 still forces legacy; 1..4 force a GridX tier at any tolerance.

  • Gradients: GradientOptions.cosx_grid_level stays 0 (legacy) since it has no conv_tol_grad; the ASE calculator’s force path (_grad_opts_from_scf) now mirrors the SCF’s resolved grid (copies cosx_grid_level/cosx_variant/thresh_cosx, applying the same conv-tol fallback) so geometry optimisations stay energy/gradient grid-consistent.

  • Tests: tests/test_cosx_variants.py (stage progression, multi-stage convergence, post-HF reference inheritance, ROHF RIJCOSX, default-flip behaviour); existing tests/test_rijcosx.py tight-tol gradient tests pass unchanged (auto fallback to legacy).

Fixed: RIJCOSX opt-in GridX path robustness (2026-06-26)

  • The opt-in COSX GridX path is now robust. cosx_variant=AUTO resolves to FITTED on any grid (the previous grid-coupled rule auto-selected the no-fit STANDARD build on a GridX tier, which destabilises open-shell RIJCOSX SCFs); STANDARD is now an explicit opt-in only. The auto grid mapping (cosx_grid_level=-1) is now DZ/TZ→GridX2, QZ→GridX3; GridX1 (order-17 / 110 angular pts) is no longer auto-selected because it is too coarse for robust open-shell convergence; it remains the explicit “quick single-point” tier. The default is unchanged (cosx_grid_level=0 → legacy grid → robust); only the opt-in GridX behaviour is fixed.

  • vibeqc.parity.scf_cosx_grid(options, basis_name): returns the COSX grid an SCF with these options actually builds, so an energy decomposition (decompose_energy_*) rebuilds K on the same grid the SCF used and the self-consistency check holds once a GridX tier is selected. Wired into tests/test_parity_hf_dft.py + examples/.../vibeqc_compare.py.

  • Known caveat: the Lebedev GridX2 grid has a higher SCF commutator-noise floor (~1e-5) than the legacy grid, so a tight conv_tol_grad on an open-shell system needs cosx_grid_level=3 (GridX3) or the legacy grid. Auto-GridX as the default awaits the globally-optimised 2021 GridX tables (not openly published). Tests: tests/test_cosx_variants.py.

Native D4 dispersion un-gated — production-validated for H–Ne (2026-06-26)

compute_d4(..., backend="native") (vibe-qc’s own MPL-2.0 D4, no dftd4 dependency) now meets the dftd4 parity bars and is un-gated for elements H, He, B, C, N, O, F, Ne: pairwise C6 agree with the dftd4 package to ~5 % (CH₄) / ~8 % (H₂O) and the CH₄-dimer D4 energy to <0.05 kcal/mol. Three fixes, landed together:

  • Per-atom Eq.-6 extraction. generate_reference_dataset had stored each reference host’s whole-molecule α(iω) as the per-atom reference (e.g. H/CH4 stored α₀=15.9 a.u. — all of methane — vs atom-in-methane H ≈2.2), so C6 ∝ α² came out 4–32× too large. It now performs the extraction of Eq. 6 of Caldeweyher et al., J. Chem. Phys. 150, 154122 (2019): each host’s α(iω) is partitioned onto the target atom by subtracting every partner atom’s ζ(q)-charge-scaled canonical atomic reference (H from H₂, noble gases from the free atom, B/C/N/O/F from their pure hydride), divided by the target-atom count.

  • Correlated CPKS α(iω) provider. The reference α(iω) is now the coupled CPKS / TD-DFT (adiabatic) response at PBE38 — the level dftd4’s reference data uses — via the new vibeqc.dispersion_d4_refdata.coupled_polarizability_imag_freq_dft (a dense Casida solve that reuses the validated TDDFT f_xc / hybrid-kernel builders). This lifts the O/N polarizabilities that the previous TD-HF response under-bound by ~15 %. Validated bit-exactly against the TD-HF path in the HF limit. Re-derivation stays first-principles (CLAUDE.md § 1); dftd4’s LGPL reference C6 table is the validation target only. d4_reference_data.json regenerated at PBE38/aug-cc-pVDZ.

  • _R4R2 table fix. The C8 / BJ-radius r4r2 table (dispersion_d4_model.py) had shipped without the sqrt(Z) factor — 117 of 118 entries wrong (only H, where √Z=1, correct) — which shrank the BJ damping radius R0 and over-bound every native D4 energy regardless of the C6 data (this masked itself while C6 were 4–32× too large). Corrected against the published <r⁴>/<r²> constants (dftd4 data_r4r2.f90); test_r4r2_spots now pins C/N/O/F/Xe so it cannot silently regress.

The two tests/test_d4_model.py parity tests are now real (no longer strict-xfail), D4NativeExperimentalWarning is no longer emitted (the class is retained for back-compat), and the experimental GFN2-xTB model’s post-SCF D4 term is correspondingly quantitative. backend="dftd4" remains the default for full periodic-table coverage; native raises a clear error outside H–Ne. Extending the reference catalogue past period 2 is the prerequisite for making native the default (handover D4b-8).

Changed: periodic BIPOLE SCF uses incremental (differential ΔD) Fock by default (2026-06-26)

  • Default flip (use_incremental_fock FalseTrue in run_pbc_bipole, run_pbc_bipole_rks, run_pbc_bipole_uhf, run_pbc_bipole_uks). Profiling a dense ionic SCF (MgO/STO-3G LDA 2×2×2) showed the per-iteration cost is ~90% the direct periodic 2e ERI Fock (build_fock_2e_real_space, ~106 s/call) and only ~5% the cross-cell XC (~6 s). The IncrementalJK differential builder (already present + tested, commit 6e469990) was implemented but left opt-in, so every periodic BIPOLE SCF rebuilt the full ERI Fock each iteration.

  • What it does. Since the 2e Fock is linear in the density, J/K(Dₙ) = J/K(Dₙ₋₁) + J/K(ΔD); feeding the iter-to-iter increment ΔD to the C++ builder’s Schwarz × density-envelope screening makes each build progressively cheaper as ΔD → 0 (most quartets fall below threshold), with a full rebuild every reset_every (12) iterations to bound accumulated screening drift. The result is numerically identical to the full-rebuild path (validated by test_pbc_bipole_multik_ewald_split, test_bipole_fock_ewald_exchange).

  • Scope + fallback. Active only on the eligible path (corrected Ewald gauge

    • DIIS, not ODA); ineligible runs fall back to the full build automatically. Pass use_incremental_fock=False to force the legacy full-rebuild path.

[v0.15.1] — 2026-06-26 — Neese’s Cheetah

Docs patch. Fixes the public landing page, whose static homepage feature admonitions still advertised v0.14.0 after the v0.15.0 cut. No functional, numerical, or API changes.

Fixed

  • Homepage admonitions (docs/index.md): now “New in v0.15.0 (Neese’s Cheetah)” with the v0.15.0 headline, v0.14.0 demoted to “Also shipped”, and “Coming in v0.16.0: periodic correlation”. The dynamic “Current release” banner already tracked the version; only the static feature blocks were stale. vibe-qc.com renders the release branch, so this shipped as a patch to reach the live site.

[v0.15.0] — 2026-06-26 — Neese’s Cheetah

Fixed: periodic XC now uses the full cross-cell density — dense-crystal energy + basis-opt gradient (2026-06-26)

  • Energy (build_xc_periodic / build_xc_periodic_uks, cpp/src/periodic_xc.cpp). The shipped builder anchored the bra AO in the home cell when assembling the DFT-grid density (ρ = Σ_s χ_0 P(s) χ_s), which is not lattice-periodic and is pointwise wrong by O(1) in the bonding region of dense crystals — yet still integrates to N electrons, so ∫ρ dr cannot detect it. This was the ~400 mHa dense-ionic error in the v0.14.0 known issues. The fix assembles the physical periodic density ρ = Σ_a Σ_s χ_a P(s−a) χ_s — both AOs summed over lattice cells, bra-image cells screened by the Becke partition reach (becke_image_radius_bohr), ket cells by cutoff_bohr — for ρ/∇ρ/τ and the matching V_xc Fock. On the sealed CRYSTAL23 ladder MgO/STO-3G LDA goes +427.9 → −3.9 mHa and PBE non-convergent → convergent; pinned by tests/test_periodic_xc_cross_cell.py (C++ vs an independent NumPy oracle + lattice periodicity). A separate ~3 mHa BIPOLE Ewald-J / exxdiv pure-DFT gauge residual remains (tracked).

  • Basis-opt gradient (build_periodic_xc_gradient_grid / _uks, python/vibeqc/basis_optimization/periodic_energy_gradient.py). The periodic explicit-XC basis-parameter gradient assembled the same home-bra density, so after the energy fix it differentiated a superseded E_xc (off by ~0.09 Ha RKS / ~0.25 Ha UKS on the dense 1-D test chains). It now assembles the full cross-cell density and the analytic kernel carries the previously-missing bra-image derivative term (the varied shell appears in both χ_a and χ_s), so periodic_xc_param_gradient_term (_uks) is again the exact derivative of the E_xc the SCF minimises. Convention tests validate the builder against build_xc_periodic to ~1e-11; analytic vs central-FD gradients agree to ~1e-9. (Phase-P3 basis-opt machinery; the full periodic analytic gradient remains gated behind the R13 kernel.)

    Patch-candidate: v0.14.x

Fixed: vq build-env no longer wedges hosts on dev-HEAD auto-update (2026-06-26)

The v0.6.55 dev-HEAD-tracking auto-update timer was wedging fleet hosts: vq build-env vibeqc-dev rebuilds ran for hours with empty stdout while pinning CPUs (saturn job ran 12h+ holding 6 CPUs), duplicates piled up behind them, and a half-landed rebuild left an importable-but-ABI-broken env (maru: newer Python tree against an un-rebuilt _vibeqc_core.soImportError). Three fixes, all in vibe-queue/src/vq/:

  • No silent wedge — the update_script now runs under a supervised process group (admin._run_monitored_build): a no-output stall cap (VQ_BUILD_STALL_TIMEOUT, default 900 s) and the wall cap both SIGTERM→SIGKILL the whole build tree (ninja/cc1plus grandchildren, not just the bash wrapper, which previously leaked and kept burning cores), and a periodic heartbeat (VQ_BUILD_HEARTBEAT_INTERVAL, default 120 s) logs progress so a stuck build is never silent. Build jobs also carry a wall_time_seconds watchdog backstop.

  • No duplicate / retry storm — the dev-HEAD (branch-mode) auto-update now submits a deduped, backed-off, wall-time-capped vq build-env JOB (build_job.submit_build_env_job) on the local daemon instead of an uncapped inline rebuild in the timer process: a second build for an env already building is skipped, and a failed build backs the env off (15 min, doubling per consecutive failure, capped 6 h) before the next attempt.

  • No ABI skewadmin._do_update_work makes the {Python tree, native .so} pair atomic: it snapshots HEAD + the package’s .so before the build and, on a failed build or a failed post-build import probe, git-resets the checkout and restores the .so, so the live env is only ever a consistent pair (never newer-Python-against-older-.so). Armed per env via the new import_check field on [programs.<venv>] (import_check = "vibeqc" for vibeqc-dev/release); unset = disabled.

Regression tests in tests/test_build_env_hardening_v0_12_x.py cover the process-group reap, the dedup + backoff, and the atomic rollback.

Changed: Γ-CCM scalable driver is now integral-direct (Phase 3b, 2026-06-25)

  • run_ccm_rhf_scalable (vibeqc.periodic.ccm.scf) now contracts the WSSC-weighted four-center integrals straight into J/K rather than building the dense O(nbf**4) effective ERI tensor. Peak JK-build memory drops from (n_threads+2)·nbf**4·8 B to n_threads·2·nbf**2·8 B — e.g. ~300 GiB → ~10 MB on a pob-tzvp-rev2 c-diamond (2,2,2) cell (nbf=288) — so real 3-D production-basis cells fit in RAM. New C++ kernels aiccm2026dev-a-direct and bra_home_full-direct in cpp/src/periodic_fock.cpp.

  • New four_center= keyword on run_ccm_rhf_scalable: "direct" (default — the integral-direct path above) or "full"/"dense" (the preserved dense effective-tensor path, kept as the small-cluster comparison reference). The two agree to ~1e-12 (a summation reorder); existing -a energies/properties are unchanged within that tolerance. The dense Python run_ccm_rhf and the full C++ branches (aiccm2026dev-a, bra_home_full) are untouched.

  • KS-CCM (run_ccm_rks / run_ccm_uks, vibeqc.periodic.ccm.dft) gains the same four_center="direct"(default)/"full" keyword and now uses the integral-direct kernel by default, so RKS/UKS-CCM scale to real 3-D cells too (previously built the full O(nbf**4) tensor). The (method, four_center) → kernel mapping is centralised in scf._ccm_scalable_cxx_method.

  • Opt-in Cauchy-Schwarz screening on the integral-direct kernels via a new schwarz_threshold keyword on run_ccm_rhf_scalable / run_ccm_rks / run_ccm_uks (default 0.0 = off = exact). A positive value skips shell-quartets below the rigorous |w|·Q_bra·Q_ket·D_max bound — a throughput lever (~1.1× on c-diamond (2,2,2)/STO-3G at 1e-12, more for diffuse bases; the WSSC weight already screens far cells, so the gain is in near small-overlap quartets). Off by default preserves the byte-for-byte integral-direct guarantee.

Added: COOP/COHP bonding analysis for periodic systems (2026-06-25)

  • New module vibeqc.coop_cohp: compute_coop_cohp() computes energy-resolved COOP and COHP (Crystal Orbital Overlap/Hamilton Population) over a Monkhorst-Pack k-mesh with Gaussian broadening. Returns COOPCOHPResult carrying per-pair COOP(E), -COHP(E) (Lobster convention: bonding positive), ICOOP/-ICOHP, and pair metadata. Accepts F_terms and H_terms as sequences of LatticeMatrixSet; F_terms_beta triggers unrestricted α/β analysis.

  • C++ performance kernels (coop_weights_k, cohp_weights_k, gaussian_broaden_projected) in cpp/src/coop_cohp.cpp with pybind11 bindings (GIL release). Replaces Python O(n_pairs·n_k·n_bands) triple loops with single Eigen operations per k-point.

  • QVF serialization: new section kinds dos.coop and dos.cohp with binary members energies, projections, integrated and JSON meta carrying pair list + Fermi energy + broadening. v1 and v2 JSON Schema updated.

  • Runner integration: run_periodic_job(coop_cohp=True) computes COOP/COHP on the DOS k-mesh and embeds both sections in the QVF archive. dos_kmesh parameter controls the mesh (default [8,8,8]).

  • Plotters: vibeqc.plot.coop_figure(result) and cohp_figure(result) with per-pair colored curves, ICOOP/-ICOHP in legend, Fermi line, and bonding/antibonding zero reference.

  • CLI: vibeqc coop <file.qvf> prints the pair table with ICOOP/-ICOHP values from existing dos.coop / dos.cohp sections.

  • Citation database: four entries (Hughbanks-Hoffmann 1983, Dronskowski-Blöchl 1993, Deringer-Tchougréeff-Dronskowski 2011, Maintz et al. 2016) routed under [routes.properties].coop / .cohp.

Added: periodic Mayer bond orders (2026-06-25)

  • New periodic_mayer_bond_orders() in vibeqc.coop_cohp: k-space generalization of Mayer bond orders (Σ_k w_k |P(k)·S(k)|² over a k-mesh). Supports restricted and unrestricted. Wired into the runner’s QVF output block, overriding the Γ-point-only approximation when output_qvf=True. Companion to COOP/COHP: ICOOP/-ICOHP gives bond strength integrated over energy; Mayer gives a single per-pair number.

Added: experimental ab-initio Cyclic Cluster Model — Γ-CCM and χ-CCM (2026-06-25)

  • Γ-CCM / aiccm2026dev-a (vibeqc.periodic.ccm): the direct-torus Γ-supercell CCM. Ships Hartree-Fock (Python + scalable C++), Kohn-Sham DFT (any libxc functional, pure and hybrid), MP2, UMP2, CCSD(T), UCCSD(T), DLPNO-MP2, and DLPNO-CCSD(T) on the neutral (Ewald) reference. Four-center Coulomb is exactly 8-fold permutationally symmetric for any lattice, with a four-center → RI (GDF) → RIJCOSX approximation hierarchy. Properties: HOMO/LUMO gap, Mulliken and Löwdin charges with translational-spread diagnostic, finite-cluster dipole, numerical gradient (Σ forces = 0). Wannier/Pipek–Mezey localization, spglib space-group symmetry analysis, and DLPNO PAO construction. Cluster sizing by real-space interaction radius (the dual of k-point density, Δk = π/R_c).

  • χ-CCM / aiccm2026dev-b (vibeqc.periodic_aiccm2026dev_b): the finite-character (Γ-centred character-mesh) CCM. Ships restricted and unrestricted RHF/RKS/UHF/UKS via four-center (3-D), RI, and RIJCOSX backends in 1D/2D/3D, plus canonical RI-MP2, DLPNO-MP2, DLPNO-CCSD, and DLPNO-CCSD(T) in 3D with explicit finite-torus convention recording (coulomb_kernel="3d-periodic-g0", exchange_q0="bvk-ewald"). Occupied localization (PBC-safe Pipek–Mezey, Wannier, IAO), spglib space-group diagnostics, Mayer bond orders, and band structure on the finite character mesh.

  • χ-CCM ECP bookkeeping: SCF diagnostics now record the physical electron count, ecp_total_ncore, effective electron count, per-atom effective nuclear charges, and the effective net charge. χ-CCM SCF fails before any Fock build if those quantities do not define a neutral finite torus. One-particle properties and band fillings use the effective counts; post-HF ECP references fail closed until their frozen-core/correlation convention is validated.

  • χ-CCM Mayer performance: the finite-torus Mayer bond-order analysis now contracts density and overlap residue blocks in a native OpenMP kernel. It avoids materialising full AO supercell density, overlap, or atom-pair matrices while matching the previous dense-supercell formula for restricted and unrestricted references.

  • χ-CCM post-HF performance: the finite-character momentum-conservation table used by RI-MP2 and local-correlation setup now builds in native OpenMP code and keeps the same fail-closed checks for duplicate or non-closed character meshes.

  • χ-CCM localization performance: occupied-localization overlap and Fock setup now reuses native finite-character transforms for overlap, Fock, and canonical Wannier coefficients, with the old einsum routes kept as regression oracles and an explicit imaginary-residue guard.

  • χ-CCM runner reproducibility: run_periodic_job(..., jk_method="aiccm2026dev-b") now forwards rsgdf_ke_cutoff to the B RI and RIJCOSX routes, matching the direct APIs. RI logs print gdf_method, rsgdf_ke_cutoff, and mdf_ke_cutoff for queued finite-N runs.

  • χ-CCM canonical MP2 memory: the RI-MP2 route now streams each pair-resolved AO-space Lpq(k_i,k_a) through the native occupied-virtual transform and releases it immediately, rather than holding the full AO-space pair cache alongside the MO-space Lov factors. Local/DLPNO routes keep the full cache because their real-torus transform needs all pairs. The remaining RI temporaries are now cleared immediately after their final contraction or real-torus transform.

  • χ-CCM local-correlation setup: finite-translation occupied-pair orbits now partition in a native χ-CCM kernel instead of Python set bookkeeping, with the Python closure retained as a regression oracle. The finite-translation occupied-index permutation table feeding that orbit partition now builds in the same private native kernel layer. The finite-character inverse Bloch transform used for density and property residue blocks now also runs in native OpenMP code. The complete-domain MP2 audit used by local-PNO exact-limit corrections now streams the real-torus L[P,i,a] contraction in native OpenMP code instead of materialising the full ijab tensor.

  • Documentation: Γ-CCM reference, χ-CCM user guide, Γ-CCM tutorial, χ-CCM tutorial, open-shell guide, cross-stream comparison, basis-set guide, visualization, troubleshooting, quickstart.

  • Benchmark: 28-system test set spanning all seven crystal systems and cubic centerings, with a single-command runner, fleet batch generation, and CRYSTAL23 comparison harness (aiccm-2026/, qc-input-library/aiccm2026testset/).

  • Both lines emit AICCM2026DevBExperimentalWarning (χ-CCM) or are gated behind method="aiccm2026dev-a" (Γ-CCM). The two lines are distinct approaches intended for a construction-level comparison. The current reportable route map is empty, and equality is not implied by their names.

Added: RIJCOSX variant + grid-level + threshold controls (2026-06-25)

  • RIJCOSX now exposes a user-facing accuracy/cost surface on every SCF Options struct (RHFOptions, UHFOptions, RKSOptions, UKSOptions) and on GradientOptions, consulted only when cosx=True:

    • cosx_variant: CosxVariant.AUTO (default) / STANDARD / FITTED. STANDARD is Neese 2009 seminumerical K with no overlap fit; FITTED is the overlap-fitted build (global Q-junction, Neese 2009 §2.4, the precursor to the per-grid-point fit of Izsák-Neese 2011). AUTO is coupled to the grid: on the legacy grid (cosx_grid_level=0, default) it resolves to FITTED (that grid needs the fit); on a GridX grid it follows the Helmich-Paris 2021 rule (FITTED when thresh_cosx < 1e-6 or QZ-and-above, else STANDARD). Both apply the one-centre correction. Measured: standard COSX is ~1.5e-3 Ha/bohr (DZ gradient) on the legacy grid but ~4e-5 on a GridX tier, hence the coupling. The standard (no-fit) K path is new wiring; the prior default always applied the global Q fit. Default behaviour is unchanged (AUTO + legacy grid gives FITTED + legacy grid, exactly as before); STANDARD + GridX is the recommended fast opt-in.

    • cosx_grid_level: 0 (default, keeps the validated legacy cosx_grid), 1 to 4 (GridX1-4 COSX tiers), -1 (auto by basis). The GridX tiers are vibe-qc’s own pruned-Lebedev COSX grids following the Helmich-Paris et al. 2021 methodology (Krack-Köster radial + COSX accuracy tiers), not a verbatim copy of ORCA’s GridX tables.

    • thresh_cosx: COSX accuracy target driving AUTO variant selection.

  • Auto-selection is a single canonical C++ implementation (resolve_cosx_variant, resolve_cosx_grid_level, cosx_basis_cardinality_from_name, cosx_grid_options_for_level), exposed to Python for unit testing. The variant routes through both the SCF energy (COSXJKBuilder) and the analytic gradient.

  • Citations: Izsák-Neese 2011 (overlap-fitted COSX) + Helmich-Paris et al. 2021 (improved grids/contraction) added to the database and routed under acceleration=["cosx"/"rijcosx"]. Tests: tests/test_cosx_variants.py (auto-selection unit tests + variant/grid-level energy paths).

  • Default behaviour is preserved: cosx_grid_level defaults to 0 (legacy grid) and AUTO on the legacy grid is FITTED, i.e. the default RIJCOSX path is byte-for-byte the prior FITTED + legacy-grid build. The faster STANDARD + GridX combination is opt-in; flipping the default grid to auto-GridX is deferred to a dedicated parity-validation pass.

Added: geometry-optimizer + delocalized-internal-coordinate citations (2026-06-24)

  • The keyword-selectable geometry optimizers (run_job(geom_opt=...)) and coordinate systems (geom_coords=...) now carry their defining-paper citations: RFO (Banerjee 1985), eigenvector-following / P-RFO (Baker 1986), GDIIS (Császár-Pulay 1984), FIRE (Bitzek 2006), trust-region (Moré-Sorensen 1983), and the delocalized / redundant internal coordinates (Baker-Kessi- Delley 1996 + Peng-Ayala-Schlegel-Frisch 1996). A geom-opt run previously cited only the generic ASE/BFGS + Pulay-forces papers. New [routes.optimizers] / [routes.coordinates] tables fire via the geom_optimizer / geom_coords selectors on CitationDatabase.assemble; all entries are print=true (they reach the .out references block + .bibtex).

  • The ASE/BFGS citation now fires only when the ASE backend actually drove the relaxation; the native / brent / geomopt optimizers no longer spuriously cite BFGS. Tests: tests/test_citations.py (optimizer/coordinate route coverage).

Fixed: spin-pure selected-CI CASSCF convergence failure (P1, 2026-06-26)

  • P1 resolved (HANDOVER_OPEN_BUGS_V015.md): spin-pure SA2 selected-CI CASSCF failed to converge for CAS(4,4). Root cause: the selected-CI eigensolver found different excited-state eigenvectors than the dense solver even when all determinants were selected, causing SA-RDM discrepancies (RDM1 diff 0.25, RDM2 diff 0.75) that broke the CASSCF line search at iteration 3.

  • Fix: _spin_pure_selected_roots delegates to the dense _spin_pure_roots solver when target_size >= n_det_full and pt2_threshold <= 1e-14, guaranteeing identical eigenvectors at the full-selection limit.

  • Tests: test_casscf_full_limit_matches_dense_spin_pure and test_cpp_spin_pure_casscf_full_limit now pass. 113 total green.

Changed: unified CASSCF/CASPT2/NEVPT2 gradient surface across the three geometry optimizers (2026-06-24)

  • optimize_molecule_brent (optimizer_backend="brent") and the geomopt MolecularSCFProvider (run_job(geom_opt=...)) previously listed CASSCF (and, for brent, CASPT2/NEVPT2) in their analytic-gradient sets, so they relaxed on the analytic gradient while the L-BFGS-B primary optimize_molecule used full-energy central FD. All three optimizers now share one decision (_casscf_analytic_gradient_ok) so they always walk the same surface per method:

    • CASSCF uses its validated analytic gradient (v0.15.0 P0 fix, FD-tight to ~1e-7) inside the state-specific, closed-shell, default-compute_wz envelope – a large speed-up over FD (one gradient build per step instead of 6N+1 energy evaluations). State-averaged CASSCF (nroots > 1, whose analytic gradient is only finiteness/translational-invariance checked), open-shell CASSCF, and the experimental compute_wz=True correction fall back to full-energy FD.

    • CASPT2/NEVPT2 always use full-energy FD: their analytic Z-vector orbital-relaxation correction is experimental.

  • Also declares the CASPT2/NEVPT2 option kwargs the _run_molecular_scf dispatch branch referenced but never accepted (latent NameError). Tests: tests/test_molecular_optimize.py::TestCasscfAnalyticGradientGating.

Added: CASPT2/NEVPT2/MS-CASPT2 Z-vector gradients (99.94%/99.59%, 2026-06-27)

  • CASPT2 Z-vector (use_zvector=True, default when compute_corr_grad=True): CI-eliminated CP-MCSCF gradient correction via analytic CI Lagrangian, numerical dE^2/dk (joblib-parallel), and z^T·g^R shortcut with corrected effective density. Validated at 99.94% accuracy vs full-energy FD (H2/6-31G CAS(2,2), delta=3.26e-6 Ha/bohr, 520x improvement over frozen FD). The Z-vector orbital-relaxation correction is EXACT (ratio 1.000000 vs target).

  • NEVPT2 Z-vector: same architecture with Dyall H0 effective densities. Validated at 99.59% accuracy (delta=2.42e-5, 64x over frozen FD).

  • MS-CASPT2 frozen-PT2 gradient: fixed critical overlap-correction bug (was using bare density instead of generalized Fock, 5800% error). Diagonal terms now delegate to compute_casscf_gradient; validated vs FD.

  • w_g factor fix: corrected effective density weight from N_g^2/Delta^2 to N_g/Delta^2 (dE_g/dE0 = N_g/Delta^2).

  • C++ kernels: casscf_orbital_gradient (OpenMP), transform_4index_mo (OpenMP 4-index AO->MO), joblib-parallel dE^2/dk FD loop.

  • Analytic CI Lagrangian for both CASPT2 and NEVPT2 (transition RDMs + effective densities).

  • Tests: 371 total green (gradient + MRPT parity + MS-CASPT2 + SA-CASSCF + selected-CI + FCI).

Fixed: AICCM aiccm2026dev-b lower-dimensional four-center guard and RKS XC folding (2026-06-24)

  • aiccm2026dev-b now fails closed for aiccm_backend="four_center" on 1D chains and 2D slabs. The current direct-truncated BIPOLE fallback lacks the neutral wire/slab Green function needed for the B finite-torus Hamiltonian, and benchmark chains showed Madelung-scale constant over-binding. The 3D four-center Ewald route remains available.

  • Multi-k GDF/RKS now builds XC from the full inverse-Bloch real-space density blocks and Bloch-folds the resulting V_xc(R) to every k point. The old cached-GDF branch collapsed the KS density to one Gamma AO block and reused the same XC matrix at all k points, which is exact only in the molecular/vacuum limit and is a plausible Hamiltonian-level source of the c-diamond/si-diamond PBE-RI SCF oscillation. Re-run those queue cases before changing default SCF accelerators.

  • Added regression coverage for lower-dimensional four-center fail-closed behavior and for preserving off-home real-space density blocks in the multi-k RKS XC build.

Fixed: GAPW experimental warning no longer silenced on the jk_method="gapw" runner path (2026-06-24)

  • run_periodic_job(jk_method="gapw") now emits GAPWExperimentalWarning once per run, matching the other experimental routes (native-D4, GFN2). The four internal GAPW SCF runners were invoked with quiet=True, which suppressed the warning entirely on the normal user-facing path — so a user driving GAPW through the runner saw only the .out “(experimental)” label and the docs admonition, not the Python warning the docs already promise (docs/user_guide/gapw.md states the path is “guarded by GAPWExperimentalWarning”). The warning now fires at the GAPW dispatch site; the internal quiet=True calls are retained so it fires exactly once instead of cascading per augmentation builder. The .out label and docs admonition are unchanged. The underlying GAPW correctness bugs remained tracked/gated in the v0.14 bug inventory at the time — this is a gating-visibility fix, not a correctness fix. Test: tests/test_periodic_gapw_augment.py::TestGapwRunnerExperimentalWarning.

Changed: AICCM aiccm2026dev-b streams canonical RI-MP2 contractions in C++ (2026-06-24)

  • run_aiccm2026dev_b_mp2 now contracts the momentum-conserving canonical RI-MP2 energy through an OpenMP C++ kernel. The equations and normalization are unchanged, but the old Python loop no longer materializes one full oovv scratch tensor per virtual k block for each occupied k pair.

  • Added a deterministic complex LOV parity test against the previous NumPy expression, plus the existing B RI-MP2 wrapper smoke test.

Fixed: RSGDF cderi cache no longer explodes on 1D/2D vacuum-padded AICCM inputs (2026-06-24)

  • rsgdf_g_mesh and rsgdf_dense_g_mesh now respect PeriodicSystem.dim: inactive vacuum-padding reciprocal axes are frozen at zero for 1D chains and 2D slabs, while 3D crystals retain the full reciprocal sphere.

  • This fixes the apparent hang at Building per-pair Lpq cache for the saturn h-chain/lih-chain a-gdf, b-rhf-ri, and b-rhf-rijcosx routes. Local full-runner B checks now complete for h-chain rhf-ri and h-chain rhf-rijcosx; the separate F1 four-center 1D over-binding remains open and is tracked in the B decisions log.

Fixed: periodic reference routes on low-dimensional / 3-D cells (stream-A benchmark blockers, 2026-06-24)

  • jk_method="bipole" fails fast at the API level on 1-D/2-D. The bipolar- expansion Fock build needs a 3-D Ewald/Madelung lattice sum; run_periodic_job now raises a clear NotImplementedError (naming the alternatives) before any SCF setup, instead of a terse ValueError raised deep in the SCF.

  • AICCM four-center HF reaches genuine 3-D supercells. The benchmark aiccm-hf route (aiccm-2026/run_case.py) now drives closed-shell HF through the scalable C++ lattice-sum builder run_ccm_rhf_scalable (WSSC weights applied in the shell-quartet loop, no O(n_pad⁴) padded ERI), which matches the dense run_ccm_rhf to µHa and no longer std::bad_allocs on c-diamond 2×2×2. The dense-builder memory guard’s error now also names run_ccm_rhf_scalable. Tests: tests/test_periodic_lowd_guards.py.

    (The companion GDF rsgdf low-D “hang” — a vacuum-padded 1-D/2-D cell exploding the dense FFT G-mesh — is fixed separately by the dimension-aware rsgdf_dense_g_mesh, which meshes only the periodic directions.) Deeper low-D multi-k GDF / bipole support (Coulomb truncation / a low-dimensional Ewald sum) is flagged for the periodic-SCF chat.

Changed: AICCM aiccm2026dev-a — actionable memory guard on the direct four-center (2026-06-24)

  • ccm_eri / ccm_eri_symmetric now raise an actionable MemoryError instead of OOMing on too-large clusters. The direct padded-cluster four-center is an O(n_pad⁴), screening-free validation builder (compute_eri(pad.basis) is dense over the padded basis, so n_pad = n_ref_ao × ERI-image-cells explodes for 3-D cells — e.g. c-diamond 2×2×2: ~80 reference AOs → thousands of padded AOs → a TB-scale tensor). A pre-flight guard estimates the dense tensor size and, above a 16 GiB ceiling (VIBEQC_CCM_PADDED_ERI_MAX_GB), raises a clear error naming the AO count, the memory, and the production RI/GDF route run_ccm_rhf_gdf (the multi-k GDF via CCM ≡ SCM-Γ, which scales to genuine 3-D). Reported by the AICCM benchmark chat. Tests: tests/test_ccm_padded_guard.py.

Added: AICCM aiccm2026dev-a — open-shell U-DLPNO on the cyclic cluster (2026-06-24)

  • Open-shell DLPNO-UMP2 + DLPNO-UCCSD(T) (experimental, the aiccm2026dev-a line). ccm_dlpno_ump2(ccm, uhf_result=, *, cderi=, tcut_pno=, n_frozen=, ss_scale=, os_scale=)CCMDLPNOUMP2Result (vibeqc.periodic.ccm.dlpno_ump2) and ccm_dlpno_uccsd(ccm, uhf_result=, *, cderi=, tcut_pno=, n_frozen=, compute_triples=)CCMDLPNOUCCSDResult (vibeqc.periodic.ccm.dlpno_uccsd). Local correlation on the neutral-reference UHF-CCM wavefunction: UMP2 resolved into the αα/ββ/αβ spin channels with per-pair PNOs (reusing the molecular vibeqc.dlpno.ump2 PNO machinery with the CCM neutral B-tensors); UCCSD(T) via per-spin union PNO virtual spaces (S^CCM-orthonormal, semicanonical) fed to the spin-orbital run_ref_uccsd engine. No-truncation gate (tcut_pno=0): reproduces canonical CCM UMP2 (run_ccm_ump2) and UCCSD(T) (run_ccm_uccsd) on the same neutral four-center to machine ε — closed-shell and genuine open-shell (doublet). Tests: tests/test_ccm_dlpno_ump2.py (7) + tests/test_ccm_dlpno_uccsd.py (7). Derivation: docs/aiccm2026dev_a_followon.md § D.3. Per-spin PAO Mulliken domains are the M2 follow-on (the molecular open-shell DLPNO is itself at this canonical-virtual PNO rung).

Fixed: v0.14.1 post-release bug-hunt hardening (2026-06-24)

  • Fixed the native CCM CCSD(T) triples path. The pybind wrapper now accepts and size-checks the flattened NumPy buffers that the CCM driver passes, and the native contraction now uses the correct occupied/virtual offsets plus the same nested abc/ijk antisymmetrisation as the validated Python fallback. This restores the no-truncation DLPNO-CCSD(T)-CCM invariant.

  • Canonicalized the Hermitian two-RDM pair-exchange symmetry in the no-inactive CASPT2 raw-block builder before forming doubly-external overlaps. This removes ~1e-11 RDM roundoff from exact half-integer overlaps without relaxing the oracle tests.

Added: QVF v1.2 — four new section kinds + runner integration (2026-06-24)

  • Localized orbitals (wavefunction.gto with orbital_kind="localized"). write_qvf now accepts wf_localized_data and emits a second wavefunction.gto section with id "wf_localized" beside the canonical "wf" section. The QVF format already supported orbital_kind="localized" in the design doc; this closes the wiring gap.

  • Bond-order tables (bond_orders canonical kind). New section carries a Mayer/Wiberg bond-order table as JSON: {method, pairs: [{i, j, order, distance_ang, symbol_i, symbol_j}, ...]}. Specified in design doc § 4.15.

  • Fat bands (band-character projections). vibeqc.bands gains band_structure_projected() which computes Mulliken-projected band weights onto (atom, l) channels. The bands QVF section now carries an optional projections binary member of shape [n_k, n_bands, n_channels] and a channels list in the kpath JSON. Design doc § 4.3 extended.

  • QTAIM topology writer + compute module (topology.qtaim). Promoted from reserved to implemented. The compute module vibeqc.qtaim.qtaim_analysis() now uses the analytic C++ evaluate_ao_with_hessian binding (Route A) with local-minimum |∇ρ| seed detection (scipy minimum_filter, the standard MultiWFN approach), Newton-Raphson refinement with on-the-fly evaluation, and gradient-path bond-path tracing. Opt-in via run_job(qtaim=True). The QVF writer serializes critical points + bond paths as JSON. Tests: tests/test_qtaim.py (4). Design doc § 4.16.

  • NTO writer dispatch (wavefunction.gto hole/electron pairs). Multi-state via nto_data list of {hole, electron, state_index, excitation_energy_ev} records → wf_nto_S{n}_hole / wf_nto_S{n}_electron, plus standalone single-state keys wf_nto_hole_data / wf_nto_electron_datawf_nto_hole / wf_nto_electron (following the wf_localized_data pattern). Compute side unblocked (runner + C++ ERI, 2026-06-25); see “TD-DFT + NTO” below. Tests: tests/test_qvf_writer.py::TestNTOSections.

Changed: runner integration — QVF auto-population (2026-06-24)

  • run_job(output_qvf=True) now automatically emits:

    • bond_orders section (from Mayer bond orders in the population summary)

    • dipole_moment manifest root metadata (from the SCF dipole moment)

    • thermochemistry manifest root metadata (ZPVE, H, S, G) when hessian=True

  • run_periodic_job(output_qvf=True) gains the same bond_orders and dipole_moment auto-emission.

  • Showcase script examples/vibe_view/showcase_qvf_all_sections.py updated to cover 35 of 37 canonical section kinds with synthetic data for all new sections.

Added: TD-DFT + NTO runner integration (2026-06-25)

  • run_job(tddft=True) runs TDA/TD-DFT on the converged SCF result (RHF/RKS/UHF/UKS) and emits the lowest tddft_n_states (default 5) vertical excitation energies, oscillator strengths, and dominant amplitudes into the QVF archive.

  • run_job(nto=True) (implies tddft=True) computes Natural Transition Orbitals via SVD on the excitation amplitude matrix and embeds paired hole/particle wavefunction.gto sections (wf_nto_S{n}_hole / wf_nto_S{n}_electron) with orbital_kind="natural" into the QVF archive. Restricted only; UHF NTOs deferred.

  • C++ performance kernel eri_ao_to_mo_blocks in cpp/src/bindings.cpp: replaces the 4-step Python einsum half-transform with direct-indexed C++ loops. Step 1 (mu → i) reads the row-major numpy ERI directly; steps 2–4 use loop-based contractions on dense intermediates. Validated to machine precision (max diff 1.1e-16 on H2/STO-3G).

  • Citation routing: uses_tddft=True + tddft_variant="tda" fires Runge-Gross 1984, Casida 1995, and Hirata-Head-Gordon 1999 driver citations; properties=["nto"] fires Martin 2003.

Added: AICCM2026DEV-B SCF residual diagnostics in fleet records (2026-06-24)

  • aiccm2026dev-b SCF results now attach the final SCF energy step, commutator norm, DIIS subspace, accelerator family, damping, level-shift, Fock-mixing, and smearing settings to result.aiccm2026dev_b.

  • The B-stream fleet runner now writes those fields plus an eight-iteration SCF trace tail into each JSON record. This is intended to diagnose the reported c-diamond and si-diamond RKS/PBE/RI iteration-cap failures without treating damping or level shifting as a physics fix.

  • The JSON also records that static density damping in the current periodic GDF loop is bypassed once DIIS starts, so damping-only sweeps should either disable DIIS or delay --diis-start.

  • Added make_jobs_b.py --profile investigate, a reproducible c-diamond / si-diamond rks-pbe-ri SCF-settings matrix that writes the new residual diagnostics and tags submitted jobs with investigate.

Fixed: AICCM2026DEV-B low-dimensional four-center guard (2026-06-24)

  • Added a regression for the vacuum-separated 1D H2/STO-3G neutral molecular limit through aiccm2026dev-b four-center RHF at multiple cyclic extensions. This pins the cancellation that prevents stale B snapshots from reintroducing a Coulomb self-image over-binding while retaining the experimental lower-dimensional gauge label.

  • Fixed the native DLPNO-MP2 iteration build/import path on the current macOS/Python 3.14 toolchain: the pybind wrapper now names the C++ result and kernel in the vibeqc namespace, the kernel materializes transposed amplitude blocks before Eigen contractions, and the native pair iteration preserves ordered PNO overlap links plus the closed-shell pair-energy convention used by the Python reference.

Added: COOP/COHP bonding analysis for periodic systems (2026-06-24)

  • COOP (Crystal Orbital Overlap Population) and COHP (Crystal Orbital Hamiltonian Population) bonding analysis (vibeqc.coop_cohp). compute_coop_cohp() projects the overlap or Hamiltonian density onto atom-pair subspaces at each k-point with Gaussian broadening, returning a COOPCOHPResult with energy-resolved COOP, -COHP (Lobster convention), and integrated ICOOP/ICOHP up to E_F.

  • C++ kernels for per-k COOP/COHP weight accumulation (cpp/src/coop_cohp.cpp, cpp/include/vibeqc/coop_cohp.hpp).

  • Spin-polarized support via F_terms_beta: both coop and cohp arrays carry a leading spin dimension (2, n_pairs, n_e) for UHF/UKS references.

  • Plotting: vibeqc.plot.coop_figure() and vibeqc.plot.cohp_figure() produce Matplotlib figures with Fermi-energy annotations.

  • vibeqc coop <qvf> CLI prints formatted COOP/COHP/ICOOP/ICOHP pair tables from a QVF archive.

  • QVF writer for dos.coop and dos.cohp canonical section kinds; both validate against the manifest schema (spin-polarized and spin-restricted).

  • Citation database: routes coop (Hughbanks & Hoffmann 1983, Dronskowski & Blöchl 1993, Deringer et al. 2011, Maintz et al. 2016) and cohp (Dronskowski & Blöchl 1993, Deringer et al. 2011, Maintz et al. 2016) in database.toml.

  • Tests: tests/test_coop_cohp.py — bonding/antibonding sign conventions, spin-polarized shapes, QVF round-trip + schema validation, plot smoke tests, CLI smoke test.

Added: AICCM bond and band analysis helpers (2026-06-23)

  • Added primitive-cell Mayer bond-order analysis for both independent AICCM streams: vibeqc.periodic.ccm.ccm_mayer_bond_orders for aiccm2026dev-a and vibeqc.aiccm2026dev_b_mayer_bond_orders for aiccm2026dev-b.

  • Added finite-torus Fock-block band-structure helpers for the same streams: ccm_band_structure and aiccm2026dev_b_band_structure.

  • Added the full diamond comparison input examples/periodic/aiccm2026dev_diamond_bonds_bands_compare.py, which runs matched HF/KS AICCM-A and AICCM-B calculations, emits JSON summaries, QVF band/bond archives, and localized-orbital QVF archives.

  • Documented the input under docs/experimental/aiccm2026dev_diamond_compare.md.

Performance: native OpenMP kernels for AICCM2026DEV-B post-HF setup (2026-06-23)

  • Added B-stream-only C++/pybind kernels for the finite-character inverse transforms used by aiccm2026dev-b MP2, DLPNO-MP2, CCSD, CCSD(T), UMP2, and UCCSD(T).

  • The pair-resolved RI-factor transform now streams directly into the final real finite-torus tensor (Ncell*Naux, Ncell*nbf, Ncell*nbf) and symmetrizes it in place. The former Python route materialized a temporary 6-D complex tensor before reshaping, so this cuts the peak setup memory for that step.

  • The one-body matrix inverse transform and the B real-torus DF adapter’s three-index AO-to-MO contractions now run in native OpenMP code. Canonical B RI-MP2 also uses the native complex Lpq -> Lov contraction.

  • Added a deterministic parity test against the previous NumPy formulas for all native kernels. The focused B/GDF gate passed 121 tests after the port.

  • Scope remains honest: the current DLPNO/PNO post-HF routes still evaluate all pairs/triples unless an already validated molecular local approximation is selected. Translation-representative amplitude propagation and minimum-image domain screening are still open work.

Added: Davidson iterative diagonalization (Phase D3) — 2026-06-22

  • Blocked Davidson algorithm (cpp/include/vibeqc/davidson.hpp, cpp/src/davidson.cpp) replaces the full Eigen::SelfAdjointEigenSolver in the SCF Fock diagonalization step with an iterative subspace method. Finds the lowest eigenvalues without O(N³) decomposition; O(N²·m_iter) per SCF iteration where m_iter ~ 5–20 for well-conditioned Fock matrices.

  • Real-symmetric and complex-Hermitian variants (the latter for k ≠ Γ periodic Fock matrices).

  • Eigenvector recycling: the Davidson solver warm-starts from the previous SCF iteration’s eigenvectors, converging in 1–3 outer iters per SCF cycle. This is the key VASP-style optimisation.

  • Integration into every C++ SCF driver:

    • Molecular RHF (cpp/src/rhf.cpp, RHFOptions.use_davidson)

    • Γ-only periodic RHF (cpp/src/periodic_rhf.cpp, PeriodicRHFOptions.use_davidson)

    • Multi-k periodic RHF and RKS (cpp/src/periodic_scf.cpp, PeriodicSCFOptions.use_davidson, PeriodicKSOptions.use_davidson)

  • Python bindings for DavidsonOptions, DavidsonResult, DavidsonResultComplex, davidson_solve, davidson_solve_hermitian, davidson_summary.

  • User-facing knob: use_davidson=True + davidson_min_dim=100 (default) on the options struct activates the path; below davidson_min_dim AOs the driver silently falls back to full diagonalization.

  • Documentation: docs/user_guide/scf_convergence.md § “Iterative diagonalization (Davidson, Phase D3)”, keyword index, and inline option docstrings on DavidsonOptions.

  • Tests: tests/test_davidson.py — real + Hermitian Davidson vs numpy/scipy, eigenvector recycling, edge cases, and end-to-end SCF parity against full diagonalization (STO-3G, def2-SVP).

  • References: Davidson (1975), Liu (1978), Kresse & Furthmüller (1996). The citation database (python/vibeqc/output/citations/ database.toml) already carries the Kresse & Furthmüller 1996 reference; no new citation entries are needed (the VASP reference already covers periodic DFT methodology).

Added: LOBPCG eigensolver (Phase D5) — 2026-06-23

  • LOBPCG algorithm (cpp/include/vibeqc/lobpcg.hpp, cpp/src/lobpcg.cpp) implements the Locally Optimal Block Preconditioned Conjugate Gradient method (Knyazev 2001). Converges 3–12× faster than Davidson on Fock-like matrices thanks to Rayleigh-Ritz on a 3·nvec subspace per iteration.

  • Real-symmetric and complex-Hermitian variants.

  • Block-aware preconditioning using the Teter-Payne-Allan (TPA) preconditioner, matching Knyazev’s convergence theory.

  • Python bindings for LOBPCGOptions, lobpcg_solve, lobpcg_solve_hermitian, lobpcg_summary.

  • Solver framework registration in python/vibeqc/solvers/eigensolver.py.

  • Citation: Knyazev (2001) added to python/vibeqc/output/citations/database.toml.

  • Tests: tests/test_lobpcg.py — convergence, warm-start, Hermitian path, vs scipy.linalg.eigh.

Added: Jacobi-Davidson eigensolver (Phase D6) — 2026-06-23

  • Jacobi-Davidson algorithm (cpp/include/vibeqc/jacobi_davidson.hpp, cpp/src/jacobi_davidson.cpp) implements the Sleijpen & Van der Vorst (1996) method with MINRES correction-equation solver.

  • Interior eigenvalue support via the sigma parameter — extracts eigenvalues nearest to a target shift for core-level and response-property use cases.

  • Python bindings for JacobiDavidsonOptions, jd_solve, jd_solve_hermitian.

  • Marked experimental: full MINRES recurrence and harmonic Ritz-value extraction are pending. The current path uses standard Ritz with a truncated MINRES correction; works well for well-separated extremes.

  • Citation: Sleijpen & Van der Vorst (1996) added to python/vibeqc/output/citations/database.toml.

  • Tests: tests/test_jacobi_davidson.py — extremal + interior eigenvalues, sigma-shift behaviour, vs scipy.linalg.eigh.

Added: Unified solver framework — 2026-06-23

  • SolverStrategy ABC in python/vibeqc/solvers/eigensolver.py — defines the contract every solver must satisfy: solve(problem) returns an EigenResult.

  • Auto-registration: solvers declare their capabilities and are discovered through a module-level registry (SOLVER_REGISTRY); no explicit import needed to use a solver by keyword.

  • Problem descriptors:

    • EigenProblem — standard extremal eigenvalue problem (A x = λ x).

    • GeneralizedEigenProblem — (A, B) pencil, reserved for future canonical-orthogonalisation paths.

    • InteriorEigenProblem — target-shift specification (σ, n_ev).

    • SCFStep — wraps a Fock-build callable for matrix-free operation.

  • 7 registered solvers in the framework registry: dense, davidson, lobpcg, jd, lanczos, hermitian_davidson, block_davidson. Currently three are selectable by keyword from run_job(solver=...): dense, davidson, and lobpcg; the remaining solvers are framework-registered but not yet C++-wired into the SCF drivers.

  • Tests: tests/test_solver_framework.py — registration, capability dispatch, keyword selection, round-trip parity.

Added: solver keyword on run_job() — 2026-06-23

  • solver="davidson" | "lobpcg" | "dense" parameter on run_job() allows user-selectable diagonalization in molecular SCF.

  • Wired through to the C++ use_davidson flag in RHFOptions and RKSOptions; future solvers will add their own C++ options fields.

  • Default is "dense" for full Eigen::SelfAdjointEigenSolver; "davidson" and "lobpcg" activate the respective iterative paths.

  • Tests: end-to-end SCF parity tests for all three keywords in tests/test_davidson.py and tests/test_lobpcg.py.

Added: Python periodic Davidson wiring — 2026-06-23

  • All 8 Python periodic SCF drivers now support Davidson diagonalization with eigenvector recycling:

    • Γ-point Ewald: RHF (periodic_rhf_dispatch.py), RKS, UHF, UKS.

    • GDF: periodic_rhf_gdf_dispatch.py, periodic_rks_gdf_dispatch.py.

    • GPW: periodic_rhf_gpw_dispatch.py, periodic_rks_gpw_dispatch.py.

    • ROHF: periodic_rohf_dispatch.py.

  • Each driver respects PeriodicOptions.use_davidson and forwards the previous iteration’s eigenvectors as the warm-start subspace.

  • Tests: periodic-SCF convergence tests at Γ and multi-k for all 8 drivers with use_davidson=True.

Added: Semiempirical solver parameter — 2026-06-23

  • solver keyword added to the SemiempiricalModel base class and all subclasses (MSINDO, GFN2-xTB, DFTB, OMx, PM6).

  • Reserved for a future C++ Löwdin-orthogonalised Davidson bridge; currently stored as an attribute for forward compatibility.

  • No functional change to semiempirical SCF paths — the parameter is accepted and validated but ignored until the C++ bridge lands.

  • Tests: tests/test_semiempirical_solver_keyword.py verifies the parameter is accepted and round-tripped.

Added: Eigensolver benchmarks — 2026-06-23

  • examples/benchmarks/benchmark_eigensolvers.py — comprehensive benchmark comparing all registered solvers:

    • Fock-like test matrices at n = 50, 100, 200, 500.

    • Matrix-free (on-the-fly matvec) vs explicit matrix paths.

    • Warm-start benefit quantification (convergence iteration counts).

    • Timing, accuracy (eigenvalue residual norms), and scalability.

  • Outputs a structured results table suitable for docs and regression tracking.

Added: Solver framework documentation — 2026-06-23

  • docs/user_guide/solver_framework.md — developer and user guide:

    • Keyword selection table with solver capabilities and tradeoffs.

    • Capability matrix: extremal / interior / generalised / matrix-free.

    • Architecture diagram showing SolverStrategy → registration → run_job() dispatch flow.

    • Guidance on when to use each solver (Davidson for Fock, LOBPCG for tight convergence, Jacobi-Davidson for interior roots).

Fixed: v0.14.1 correctness and test-gate hardening (2026-06-22)

  • Periodic automatic convergence no longer enables finite-temperature smearing for ionic insulators. Explicit smearing on a pre-SCF insulating profile, or with a supplied positive gap, emits a user warning and records it in the output log; FMIXING remains available without changing integer occupations.

  • CASSCF geometry optimization again uses full-energy central finite differences. The exposed analytic CASSCF gradient is not FD-tight, and the experimental W^z correction can worsen the disagreement, so it is no longer allowed to silently steer an optimization.

  • A clean pip install -e '.[test]' includes scikit-learn, allowing the bundled ML k-spacing predictor regressions to load their model rather than fail from an incomplete gate environment.

  • Direct run_rhf, run_rks, run_uhf, and run_uks calls accept a basis name string as documented.

  • Runtime-filtered basis sets retain the source .g94 provenance header and inherit the original basis citation route.

  • The periodic atomic-limit suite no longer xfails a deliberately unlike bare Ewald/direct nuclear-gauge comparison; active total-energy tests remain the physical correctness check.

    Patch-candidate: v0.14.x

CASSCF analytic nuclear gradient — P0 resolved (v0.15.0, 2026-06-24)

  • The P0 bug was resolved. Four root-cause bugs fixed: gamma cross terms, integral convention mismatch, q<->r index swap in 4-index AO transform, and W_unrelaxed RDM transform leaking virtual density (fixed to CASCI re-solves, then replaced with analytical W = C.F.C^T using MCSCF generalized Fock).

  • Default path (compute_wz=False) is FD-tight production: H2 analytic +0.02336339 vs FD +0.02336323 (delta 1.6e-7). H2O analytic -5.26814 vs numerical -5.26814 (100.0%).

  • W^z correction (compute_wz=True) remains experimental/gated.

  • Reproducer exits 0 with PASS. 15 tests green.

    Patch-candidate: v0.14.x (RESOLVED in v0.15.0)

[v0.14.1] — 2026-06-24 — Bartlett’s Goose

Patch release. Restores the public documentation deployment, which broke on the v0.14.0 cut, and corrects two stale citation-metadata files. No functional, numerical, or API changes — the v0.14.0 known-issue list (dense real-space periodic XC on dense ionic crystals; smearing on gapped insulators) is unchanged.

Fixed

  • Public docs build and deploy. The v0.14.0 documentation site failed to build and never deployed: docs/conf.py enabled the sphinx_reredirects extension (added in the v0.14.0 cycle) but the docs-build CI job never installed the package, so Sphinx aborted with Could not import extension sphinx_reredirects and docs-deploy was skipped — vibe-qc.com stayed on the v0.13.1 docs. .gitlab-ci.yml now installs sphinx-reredirects in the docs-build job. (Docs and CI must move together — CLAUDE.md § 6.)

  • Stale citation metadata. CITATION.cff (was 0.13.1) and docs/citing.md (was 0.13.0) were not bumped during the v0.14.0 cut; both now report 0.14.1.

[v0.14.0] — 2026-06-22 — Bartlett’s Goose

AI-generated Bartlett's Goose codename artwork for vibe-qc v0.14.0

Known issues

  • Real-space periodic XC energies on dense ionic crystals are ~400 mHa off the CRYSTAL reference (pre-existing, functional-independent; tracked for v0.14.x). Use the GPW route where validated. (RESOLVED in v0.15.0 / Unreleased — the cross-cell density fix brings MgO/STO-3G LDA to −3.9 mHa vs CRYSTAL23; see “Fixed: periodic XC now uses the full cross-cell density”.)

  • Fermi / Methfessel-Paxton smearing applied to a gapped insulator metallizes it and gives wrong energies. Smearing is for metals; do not apply it to insulators.

  • Two tests are known-failing, deferred to v0.14.x: the bundled ML k-spacing predictor (tests/test_ml_kpredictor.py) and the runner optimize-wavefunction options (tests/test_runner_optimize_wavefunction_options.py).

Next minor cycle (v0.14): canonical CCSD(T) maturation, BIPOLE multi-k corrected-gauge analytic gradients (RKS/UKS + per-k q-channel), periodic GAPW/GPW efficiency (PBC-audit E3), the SCF-guess/mixing catch-up (FRAGMO, multi-secant, periodic Anderson/Kerker wiring).

Added: sealed CRYSTAL23 dense-ionic DFT parity ladder for MgO — captures an open periodic-XC bug (2026-06-22)

  • New cross-code regression examples/regression/crystal_parity/parity_mgo_r2scan_sto3g.py (+ parity_mgo_{svwn,pbe}_sto3g.py) and sealed CRYSTAL23 references crystal_demos/mgo_{r2scan,pbe,svwn}_sto3g.{d12,out}, plus the run_demo_parity_rks DFT sibling of run_demo_parity in crystal_demos/runner.py. CRYSTAL is run strictly out-of-process; the value is sealed as a constant (CLAUDE.md §10 — no QC-program import).

  • Known bug these regressions capture (open). vibe-qc’s periodic RKS DFT on the dense MgO/STO-3G rocksalt at SHRINK 2 lands hundreds of mHa above CRYSTAL23 (sealed; PySCF .density_fit() agrees with CRYSTAL to ~1 mHa): r2SCAN -271.54497 vs -271.92904 (+384 mHa, converges cleanly), LDA/SVWN +428 mHa (converges), PBE ~+399 mHa (oscillates, no convergence). The error is functional-independent — LDA is ρ-only, so it is not the GGA σ / meta-GGA τ Fock terms, but the periodic XC piece every functional shares (the cross-cell density on the periodic Becke grid for dense, overlapping-image crystals). The RHF analog matches CRYSTAL to 0.369 mHa and all dilute/molecular DFT matches; a definition-parity audit (libxc IDs, 0 % EXX, grid-converged) rules out functional/grid causes. The scripts FAIL by design today and flip to PASS once the bug is fixed. Localization + audit: parity_mgo_r2scan_sto3g.py header and the periodic-SCF lane. Not yet fixed; tracked for the v0.14 periodic work.

    Patch-candidate: v0.14.0

Fixed — vq: vq admin update no longer resumes a prior interrupted update’s paused jobs (2026-06-22)

  • An admin update could wake jobs it never paused. The non-surgical pause/resume bracket (admin.update_env / update_all) paused the queue and then resumed with a blanket resume_all — which SIGCONTs every SUSPENDED job, not just the ones this run paused. When a prior vq admin update was interrupted (SIGKILL / OOM / host reboot) after it paused but before its finally resume ran, its jobs sat SUSPENDED; the next update then woke all of them at once. On 2026-06-22 a vq admin update vibeqc-queue on maru resumed 22 such stuck-paused jobs and spiked the box to load 88 on heavy btrfs+LUKS I/O.

  • The resume is now scoped to a per-invocation token. Each update mints a fresh tag (admin-update-<hex>, admin._new_pause_token), pauses with pause_all(paused_by=<token>), and resumes with resume_all(paused_by_filter=<token>). A SUSPENDED job whose paused_by doesn’t match the running update’s token — a prior run’s straggler, an operator’s manual pause, a build-script cooperative pause — is left paused. The token is per-invocation by design: a shared constant tag would let a later run re-match and re-wake an earlier interrupted run’s leftovers. The surgical (provides_branches) path was already scoped this way via its explicit paused-jobid list.

  • Regression coverage in vibe-queue/tests/test_admin_update_resume_scope_v0_11_1.py (real SIGSTOP’d child processes + on-disk specs). Operator guidance in vibe-queue/docs/operations.md § “Admin update pause/resume scoping”; the build-script cooperative-pause handover now recommends a per-invocation tag too.

Added: AICCM aiccm2026dev-a — open-shell UCCSD(T) + spin-resolved properties (2026-06-22)

  • Open-shell UCCSD(T) (experimental, the aiccm2026dev-a line). run_ccm_uccsd(ccm, uhf_result=, *, cderi=, compute_triples=)CCMUCCSDResult (vibeqc.periodic.ccm.uccsd). Runs vibe-qc’s own spin-orbital UCCSD(T) engine (vibeqc.dlpno._ccsd_ref.run_ref_uccsd) on the UHF-CCM reference, fed the CCM neutral cderi transformed into the α/β MO bases — the natural route (the neutral four-center is the RI-separable CCM kernel and the required correlation reference).

  • Closed-shell consistency gate: on an even-electron closed-shell cluster run_ccm_uccsd reproduces the closed-shell run_ccm_ccsd on the same neutral four-center to ≤1e-8 (CCSD and (T) each); a genuine doublet cluster converges with negative correlation. Tests: tests/test_ccm_uccsd.py (4).

  • Spin-resolved properties: ccm_homo_lumo_gap now returns the open-shell gap (LUMO HOMO over the α/β spectra) for UHF/UKS results; the population and dipole routines already accept an unrestricted density. Derivation: docs/aiccm2026dev_a_followon.md § D. (U-DLPNO is the remaining follow-on, § D.3.)

Added: AICCM aiccm2026dev-a — derivable properties on the BvK torus (2026-06-22)

  • One-particle properties on the cyclic cluster (experimental, the aiccm2026dev-a line). New vibeqc.periodic.ccm.properties: ccm_homo_lumo_gap (band/fundamental gap from the supercell-Γ spectrum), ccm_mulliken_charges / ccm_lowdin_charges (population analysis with the cyclic overlap S^CCM, per-cell image-averaged charges + a translational-spread symmetry diagnostic), ccm_dipole (finite-cluster dipole), and ccm_numerical_gradient (central-difference dE/dR per unit-cell atom; force = −gradient).

  • Grounded validation: gap == raw-spectrum gap; charges sum to the cluster charge and recover Li⁺/H⁻ on a LiH chain; the dipole vanishes for a centrosymmetric cluster; the numerical gradient satisfies translational invariance (Σ forces = 0) and reproduces vibe-qc’s molecular analytic RHF gradient in the isolated limit to 8.9e-8. Tests: tests/test_ccm_properties.py (7).

  • Honest scope (documented): the dipole is the finite-cluster (not bulk polarization) quantity — the torus position operator is ill-defined (Resta 1998) and the plain ⟨μ|r|ν⟩ wraps for boundary-crossing orbitals; the analytic WSSC-weighted gradient and the Resta/Berry-phase polarization are open follow-ons. Derivation: docs/aiccm2026dev_a_followon.md § C.

Added: AICCM aiccm2026dev-a — DLPNO-CCSD(T) on the cyclic cluster (2026-06-22)

  • Subspace-projected DLPNO-CCSD(T) (experimental, the aiccm2026dev-a line). ccm_dlpno_ccsd(ccm, scf_result, *, cderi=, g_neutral=, localize=, tcut_pno=, tcut_mkn=, compute_triples=, n_frozen=)CCMDLPNOCCSDResult (vibeqc.periodic.ccm.dlpno_ccsd). The per-pair PNOs are merged into a single union virtual space V_trunc (S^CCM-orthonormal, semicanonical), and vibe-qc’s own CCM CCSD engine runs in {canonical occ, V_trunc} — on the neutral four-center reference.

  • No-truncation gate proven numerically: tcut_pno = tcut_mkn = 0 reproduces the canonical CCM CCSD(T) on the same neutral four-center to machine ε — CCSD and (T) each, for canonical occupieds (Δ≈7e-18) and Pipek–Mezey / Boys (Δ≈0). At no truncation V_trunc spans the full virtual block, so it is a unitary rotation of the canonical virtuals and CCSD(T) is invariant. Tests: tests/test_ccm_dlpno_ccsd.py (9). Shared _build_ccm_pno_pairs helper with DLPNO-MP2. Derivation: docs/aiccm2026dev_a_followon.md § 2.3.

Added: AICCM aiccm2026dev-a — DLPNO-MP2 on the cyclic cluster (2026-06-22)

  • DLPNO-MP2 local correlation (experimental, the aiccm2026dev-a line). ccm_dlpno_mp2(ccm, scf_result, *, cderi=, localize="none"|"pm"|"boys", tcut_pno=, tcut_mkn=, n_frozen=)CCMDLPNOMP2Result. Reuses the validated molecular DLPNO machinery (vibeqc.dlpno.pao / .mp2 component functions) with the cyclic-cluster overlap S^CCM and Fock F^CCM injected, and the (ia|jb) integrals density-fit from the neutral cderi.

  • Built on the neutral reference (per the -b-critique correction, main 301f5fcc): the ionic Madelung background shifts the occupied–virtual denominators, so CCM correlation requires the neutral four-center reference — not the bare-1/r four-center. No Madelung-robustness is claimed.

  • No-truncation gate proven numerically: tcut_pno = tcut_mkn = 0 reproduces the canonical CCM MP2 on the same neutral four-center to machine ε — for canonical occupieds (Δ=0) and Pipek–Mezey / Boys localized occupieds (Δ≈8e-15, exercising the coupled LMP2 residual with S^CCM-projected neighbour amplitudes). PNO occupation-threshold truncation behaves monotonically with a perturbative correction. Tests: tests/test_ccm_dlpno_mp2.py (11).

  • Derivation: docs/aiccm2026dev_a_followon.md § 2.2.

Added: AICCM aiccm2026dev-a — interaction-radius cluster parameterization (2026-06-22)

  • Real-space interaction radius as the cluster knob (experimental, the aiccm2026dev-a line). A CCMSystem can now be sized by a real-space interaction range R_c instead of an explicit mesh nrep: CCMSystem.from_interaction_range(unit, R_c, basis) / CCMSystem(unit, basis=..., interaction_range=R_c) (bohr) / interaction_range_ang= (Å). The minimal nrep whose supercell Wigner–Seitz cell encloses a sphere of radius R_c around every atom is derived automatically; explicit nrep stays the advanced override.

  • The real-space dual of k-point sampling (CCM ≡ SCM-Γ): a cyclic cluster (N1,N2,N3) is a BvK torus sampling a Γ-centred (N1,N2,N3) MP mesh, and R_c ⇔ uniform k-spacing Δk = π/R_c. Rigorous derivation: the WS inscribed-sphere radius r_in = λ₁/2, with N_i = ⌈2R_c/d_i⌉ provably giving r_in R_c (tight for orthogonal cells, conservative for oblique). Diagnostics ccm.wsc_inscribed_radius / ccm.kspacing_equiv.

  • Convergence helper ccm_interaction_range_scan(unit, basis, radii_bohr)InteractionRangeScan (radius → energy/atom, converged_radius(tol), JSON as_records()) — the CCM analogue of a k-point convergence study.

  • New geometry primitives interplanar_spacings, shortest_lattice_vector_length, wsc_inscribed_radius, nrep_for_interaction_range, kspacing_for_interaction_range in vibeqc.periodic.ccm.wigner_seitz.

  • Tests: tests/test_ccm_interaction_range.py (77, incl. the inscribed-sphere gate vs a brute-force oracle on cubic/ortho/tetragonal/fcc/bcc/hex/triclinic/ vacuum lattices). Demonstration across all 28 Bravais-spanning test-set crystals: aiccm-2026/demo_interaction_range.py. Docs: docs/aiccm2026dev_a.md § “Sizing the cluster”, derivation docs/aiccm2026dev_a_followon.md § A.

Added: AICCM2026DEV-B real-space extension and unrestricted route (2026-06-22)

  • The B stream now takes a cyclic lattice extension as its primary finite-size parameter; aiccm_wigner_seitz_shells=s selects the odd extension 2s+1 and the equivalent Gamma-centered character net is derived exactly.

  • UHF and UKS run through four-center, RI, and RIJCOSX in 1D/2D/3D with spin-resolved projector, electron-count, and spin-contamination gates.

  • The exact 3D B RI transform now feeds explicit complete-domain restricted CCSD(T), unrestricted MP2, and cost-capped full-domain/PNO UCCSD(T) correctness pilots. The latter does not yet claim representative-pair reduced scaling.

  • Gauge-invariant SCF properties cover electron/spin counts, Mulliken populations, finite-net gaps, idempotency, and spin contamination. Periodic polarization, analytic response, and relaxed correlated properties remain explicitly unavailable.

Added: build-as-job, env rebuilds run as exclusive cgroup-capped jobs (2026-06-22)

  • vq build-env <env> (internal) is the job-command form of an env rebuild: it runs admin.update_env(<env>) for localhost (git pull + update_script) and exits 0 on success, 1 on a failed build, 2 if it could not start. The v0.11.0 --refresh path runs the rebuild as an uncapped inline daemon subprocess. This lets the daemon dispatch the rebuild as a normal job instead, so the cgroup scope, events, and dependents govern it. Operators still use vq admin update.

  • JobSpec.build_env marks a spec as the build job for an env. Additive, pre-v0.12.0 specs read clean, no SPEC_VERSION bump.

  • The dispatch integration (now landed). When a --refresh <env> job reaches the head and the host has drained, _maybe_refresh_before_run creates ONE build job (vq build-env <env>, at a reserved-high priority so it is pending[0] on the drained host, declared at the daemon cpu and mem budget so the cgroup wrap caps it) and points the waiting --refresh jobs at it via depends_on. A burst of N same-env refresh jobs shares that one build (dedup). While a build is RUNNING the dispatch loop holds ALL other dispatch, so the rebuild has the host to itself. A failed build fails its dependents through the existing depends_on cascade, so a job never runs against a half-built env. The old inline synchronous rebuild (and its _refresh_failure_detail helper) is gone.

  • Tests: tests/test_build_env_v0_12_0.py (field + command), plus the build-as-job orchestration and exclusivity classes in tests/test_refresh_before_v0_11_0.py.

Fixed: periodic XC-gradient σ-Pulay v_σ clamp broke B88/LYP/SCAN gradients (2026-06-22)

  • The shared periodic XC analytic-gradient kernels (xc_lattice_gradient_contribution[_uks]) clamped the GGA/meta-GGA σ-Pulay potential v_σ to ±10 Ha⁻¹. B88/LYP/SCAN carry a legitimately large but finite v_σ in the low-density tail, where the SCF energy (and its finite-difference reference) are built from the unclipped value — so the clamp desynchronised the analytic gradient from its own energy: B3LYP / BLYP H₂ drifted ~1e-4 Ha/bohr and SCAN ~2e-3 Ha/bohr vs FD, while PBE-family functionals (whose v_σ never reaches the clamp) stayed exact. The clamp now drops to a bare NaN/inf guard, restoring the molecular-analytic match (B3LYP/BLYP/SCAN/PBE all ~1e-8 vs FD self-consistency). The separate v_τ meta-GGA clamp is unchanged. Regression from the meta-GGA τ-Pulay landing (b7c56314); never shipped in a tag. Pinned by the B3LYP + new BLYP cases in tests/test_periodic_gradient_g1b.py.

    Patch-candidate: v0.14.0

Added: --default-job-mem-mb, so a job that declares no memory cannot oversubscribe a host (2026-06-22)

  • vq daemon run --default-job-mem-mb N sets the assumed memory of a job that declares no --mem-mb. When set, such a job is charged this against the daemon memory budget at admission (it joins the running tally and is subject to the --max-mem-mb gate) and is cgroup-capped at it via MemoryMax. A job that declares nothing can no longer be dispatched unbounded or drive the host into swap. A job that needs more must declare --mem-mb, which then packs correctly so only as many as fit run at once. Default None preserves the old behaviour, so a daemon is unchanged until the flag is set.

  • Closes the gap the dispatcher flagged in its own comment, that an undeclared job was dispatched without contributing to the running memory tally. Motivated by the 2026-06-21 AICCM build storm, where a dozen jobs that each rebuilt vibe-qc and declared no memory were admitted on the job count alone and drove mars to load 128 with 32 GB of swap.

  • Tests: tests/test_default_job_mem_mb.py.

Added: AICCM2026DEV-B localization and symmetry diagnostics (2026-06-21)

  • The independent B stream now exposes post-SCF occupied localization on the complete finite torus through a Wannier-like projected circular-position objective and an IAO population objective. Density, one-particle energy, translation covariance, centres, spreads, and wraparound are checked.

  • B-specific PAO projection, PNO construction, and occupied-pair orbit enumeration are available as experimental primitives. The Wannier route is wired to 3D local MP2; its complete-space limit is independently audited against a rotation-invariant DF-MP2 contraction. Pair representatives are not skipped yet.

  • symmetry_mode="diagnostic" uses spglib to report the exact cyclic-cluster compatible subgroup, nonsymmorphic atom/cell maps, and irreducible k orbits without changing SCF. Integral reduction fails closed pending general-k AO sewing and petite-list Fock/energy parity.

Fixed: run_periodic_job citation crash on plain k-meshes (2026-06-21)

  • run_periodic_job(...) raised UnboundLocalError: _kpts_uses_ml while assembling the end-of-run citations whenever kpoints was a plain BlochKMesh rather than a KPoints.recommend() result — the common case, e.g. a DFT+U run writing a .bibtex sidecar. The ML-k-spacing-predictor flag was read only inside the isinstance(kpoints, KPoints) branch but referenced unconditionally during citation assembly; it is now pre-initialised to False alongside its sibling smearing / bz-integration guards. Regression from the ML k-spacing citation wiring; never shipped in a tag. Covered by tests/test_dft_plus_u.py (the run_periodic_job + .bibtex cases that previously crashed now pass).

Investigated: periodic DFT XC error on dense crystals (cross-cell density + SCF convergence) (2026-06-22)

  • Root cause confirmed: for multi-k DFT the SAD initial guess zeroes all cross-cell P(g!=0) blocks, making the on-grid density ~5% too low. Functional-independent: LDA/SVWN +428, PBE +399, r2SCAN +384 mHa.

  • Cross-cell fix attempted but insufficient: folding per-k HCORE density restores correct P(g!=0) but SCF convergence is too slow on SHRINK 2 (both RHF and DFT). Proper per-k SAD deferred to v0.14.x. Parity ladder remains known-failing by design.

  • Also: _delta_k_from_predictor return-type annotation fixed. Patch-candidate: v0.14.x

Fixed: test_ml_kpredictor failures when scikit-learn absent (2026-06-22)

  • tests/test_ml_kpredictor.py had 10 tests that constructed a KSpacingPredictor without guarding for the optional [ml] extra (scikit-learn>=1.3). When sklearn was not installed the tests crashed with ImportError rather than skipping. Added _requires_sklearn = pytest.mark.skipif(...) and decorated TestKSpacingPredictor, TestRecommendIntegration, TestModelRoundTrip, and TestElectronConservation — the 15 feature-extraction, env-gate, and parser-output tests (which don’t touch sklearn) continue to run regardless. Also fixed the return-type annotation on _delta_k_from_predictor (-> float-> Tuple[float, bool]). Patch-candidate: v0.14.0

Added: periodic SAP initial guess (closed-shell RHF/RKS) (2026-06-21)

  • initial_guess=SAP now works for periodic closed-shell SCF. The lattice-summed SAP potential compute_vsap_lattice builds the Fock-mode guess F_SAP = T + V_SAP (V_SAP replaces V_ne); each driver diagonalises it at Γ (or each k) for the initial density — the periodic analogue of the molecular SAP guess. Wired into the Γ RHF driver (run_rhf_periodic_gamma) and the multi-k RHF and RKS drivers (run_rhf_periodic / run_rks_periodic).

  • The SAP potential is built by an Ewald split — analytical erfc short-range (libint erfc_nuclear, per SAP primitive + an η-group with the net effective charges) plus a smooth erf(η)/r long-range grid-quadratured with effective charges Q_A = Σ_i c_i. The pieces reconstruct the molecular c_i erf(ω_i)/r exactly; validated against the analytical molecular SAP matrix (compute_sap_potential_molecular).

  • Guess-independence validated: SAP reaches the same converged energy as SAD on He/Ne cells (Γ and multi-k RHF/RKS).

  • Honest gating: periodic open-shell (UHF/UKS) and the Python Ewald/GDF/BIPOLE drivers do not yet build SAP and raise a clear, accurate error (no deep/outdated engine throw); PATOM/HUECKEL remain periodic-unimplemented with AUTO resolving to SAD.

  • Tests: tests/test_periodic_sap.py (integral + molecular parity + Γ/multi-k same-basin), tests/test_guess.py::test_sap_periodic_gamma_reaches_same_basin_as_sad.

Added: periodic MINAO initial guess (closed-shell RHF/RKS) (2026-06-21)

  • initial_guess=MINAO now works for periodic closed-shell SCF. The ANO-RCC minimal-basis reference density is projected onto the working basis using lattice-summed Γ overlaps, P = S(Γ)^{-1} S_tm(Γ), D = P D_ref Pᵀ — the periodic analogue of the molecular MINAO guess (compute_minao_density_periodic). A density-mode guess: the projected D is placed on the g=0 cell, so Bloch-summing recovers D(k) for the first Fock build (exactly like SAD). Wired into the Γ RHF (run_rhf_periodic_gamma) and multi-k RHF/RKS drivers (run_rhf_periodic / run_rks_periodic).

  • The cross-basis overlap is lattice-summed. S_tm(Γ) = Σ_g ⟨χ(0)|χ_ref(g)⟩ (cross_overlap_lattice_gamma) translates the reference shells by every direct-lattice image within the overlap cutoff — the periodic generalisation of the molecular cross_basis_overlap. The target overlap S(Γ) is the Γ-fold the driver already computes. D is rescaled so tr(D·S(Γ)) = n_elec.

  • Guess-independence validated: MINAO reaches the same converged energy as SAD on He/Ne cells (Γ and multi-k RHF/RKS).

  • Honest gating: periodic open-shell (UHF/UKS) and the Python Ewald/GDF/BIPOLE drivers do not yet build MINAO and raise a clear, accurate error; PATOM/HUECKEL remain periodic-unimplemented with AUTO → SAD.

  • Tests: tests/test_periodic_minao.py (Γ + multi-k RHF/RKS same-basin).

Added: vibe-view — compute electron density from wavefunction.gto (Phase E3) (2026-06-21)

  • The wavefunction panel gains a Compute total density button: the viewer computes ρ(r) = Σ occ_i |ψ_i(r)|² on the fly from the stored MO coefficients — no pre-computed volume.density section needed — and renders it as an isosurface, reporting ∫ρ dV in the status as a self-consistency check (water → 9.89 ≈ 10 electrons on a 60³ grid). WavefunctionRenderer.evaluate_density reuses the libint-parity-validated per-MO evaluator and sums occupied MOs across spin channels (restricted + unrestricted). Tests: tests/test_plot_renderers.py::test_wf_density_integrates_to_n_electrons (∫ρ ≈ N; non-negative). Verified live with trusted events. (Unblocked by regenerating the local example wavefunction QVFs, whose pre-2026-06-09 coefficients were un-normalized; the producer fix 200471e8 is on main.)

Added: vibe-view — table CLI: dump frequencies / charges / MO energies (Phase E1) (2026-06-21)

  • vibe-view table <qvf> --kind <kind> [--format csv|json] dumps a section’s tabular data to stdout — vibrations (mode / frequency), atom_properties (per-atom Mulliken / Löwdin charges), and wavefunction.gto (MO index / spin / energy / occupation). With no --kind it lists the tabulatable sections in the file. Text, CSV, or JSON, for piping into a spreadsheet or script. vibeview.tables.extract_table is the reusable core. Tests: tests/test_cli.py::TestTable (each kind extracts; CSV/JSON/listing; bad-kind error). First piece of Phase E (data-driven interaction). (E3, compute-density from wavefunction.gto, is deferred: the kernel is trivial but the committed example wavefunction QVFs carry un-normalized MO coefficients — pre-2026-06-09 double-normalization bug — so ∫ρ ≠ N and the density looks wrong on the demos; flagged for example regeneration.)

Added: periodic basis-optimisation gradient — Phases P1-P3 (one-electron derivatives, assembly skeleton, RKS XC term; gated on R13 for end-to-end) (2026-06-21)

  • vibeqc.basis_optimization.periodic_energy_gradient.bloch_summed_one_electron_exponent_derivatives returns the periodic overlap and kinetic-energy derivatives w.r.t. a primitive Gaussian exponent, per k-point: dS(k)/dα = Σ_R exp(i k·R) dS(R)/dα and the kinetic analogue. Each real-space block reuses the molecular overlap_/kinetic_exponent_derivative bindings between a home-cell shell and its image at lattice vector R (doubled-cell molecule; the home and image copies of the shared exponent are summed and the home×image cross-block read off), Bloch-summed over the SCF’s own overlap-lattice cells. Clean two-centre lattice sums only; the nuclear-attraction and two-electron derivatives are Ewald-gauge-coupled and deferred to Phase P2.

  • Validated to ~1e-10 against a central finite difference of the Bloch-summed lattice integral at fixed k, off the reference exponent, on symmetric and asymmetric 1D chains (l = 0 and 1; complex S(k); multi-atom shared-exponent target lists) and a 3D simple-cubic cell. Tests: tests/basisset_dev/test_periodic_one_electron_exponent_deriv.py.

  • This is the first periodic counterpart of the molecular analytic basis-optimisation gradient. The build-independent per-k Pulay assembly (P0) landed earlier; re-audit confirmed the GDF multi-k SCF result already exposes the per-k density/Fock/overlap/hcore the assembly consumes (design-doc “P4”), so no new periodic-SCF binding is needed to feed it. See docs/basisset_dev/PERIODIC_GRADIENT_DESIGN.md.

  • Internal scaffolding (not yet a usable gradient): the full per-k Pulay assembly periodic_energy_gradient_analytic is wired behind a PeriodicCoulombNuclearDerivativeProvider seam — it consumes the P1 one-electron derivatives and pulls per-k P(k)/F(k)/k-weights from the GDF result, with the nuclear dV(k) and two-electron dG(k) derivatives supplied by the provider. It is gated (raises without a provider): the production provider is the Ewald-gauge dV(k)/dG(k) kernel filed as R13 in REQUIREMENTS-PERIODIC.md, not yet landed. The assembly math (_periodic_pulay_contract) and the wiring are validated against a mock provider (test_periodic_energy_gradient_analytic_wiring.py).

  • Phase P3: the periodic explicit exchange-correlation gradient term, for both closed-shell RKS (periodic_xc_param_gradient_term) and open-shell UKS (periodic_xc_param_gradient_term_uks, the polarised v_ρα/v_ρβ/v_σαα/v_σαβ/v_σββ contraction), LDA and GGA. dE_xc/dα = Σ_g w_g [v_ρ ∂ρ/∂α + 2 v_σ ∇ρ·∂∇ρ/∂α] with the real-space lattice density ρ(r) = Σ_h Σ_μν P_μν(h) χ_μ(r) χ_ν(r−R_h) on the periodic Becke grid, reusing the molecular on-grid ∂φ/∂α per cell. The Python density assembly reproduces build_xc_periodic(_uks)’s E_xc to ~1e-15, and the gradient matches a frozen-density E_xc finite difference to ~1e-9 for LDA and PBE, RKS and UKS (test_periodic_xc_param_gradient.py). This is the additive KS term for the periodic gradient; it composes with the one-electron (P1) and two-electron (P2, pending R13) pieces. No C++ change.

Added: vibe-view — batch-render CLI; Phase D complete (2026-06-21)

  • vibe-view batch "runs/*.qvf" -o gallery/ [--size WxH] renders each matched .qvf’s structure to a PNG offscreen (shared isometric / dark-background view defaults), without starting the interactive server — a comparison gallery in one command. vibeview.batch.render_structure_png is the reusable offscreen helper; files with no structure section are skipped with a note, and the command exits non-zero if nothing rendered. Tests: tests/test_cli.py::TestBatch (PNG produced; CLI gallery; no-match + bad-size errors); verified live (3/3 example files). Phase D (cross-system comparison) is now complete: overlay (D1), RMSD/Kabsch alignment (D4), density difference (D2), batch render (D3).

Added: vibe-view — density-difference viewer (Phase D2) (2026-06-21)

  • A Density Difference card (shown when two or more loaded files carry a volume.density section) subtracts one density from another and renders ρ_A − ρ_B as a signed two-colour isosurface (red = accumulation where A > B, blue = depletion) over file A’s structure. compute_volume_difference requires matching grids (same shape / origin / voxel vectors) and surfaces a clear message otherwise — trilinear interpolation onto a common grid is a planned follow-up. Tests: tests/test_plot_renderers.py (same-grid subtraction; grid-mismatch raises; the signed isosurface renders). Verified live with trusted events. This leaves only the batch-render CLI (D3) in Phase D.

Added: vibe-view — RMSD/Kabsch alignment for compare mode (Phase D4) (2026-06-21)

  • Compare/overlay mode (D1) gains an “Align (RMSD fit to first file)” switch. When on, each overlaid structure is Kabsch-superposed onto the first file (when the atom counts match), so the residual displacement — not a difference in coordinate frame — is what shows, and the per-file RMSD to the reference is reported in the legend (e.g. “water-rot (RMSD 0.00 Å)”). New pure-numpy vibeview.align.kabsch_fit (Kabsch, Acta Crystallogr. A32, 922, 1976, cited inline) with the determinant/reflection guard so a proper rotation is always returned (never mirroring a chiral structure onto its enantiomer). Tests: tests/test_plot_renderers.py (recovers a known rotation; identity → 0; a mirror is NOT superposed; the overlay reports RMSD ~ 0 for a rotated copy); verified live with trusted events. Remaining Phase D: the volumetric difference viewer (D2) and batch-render CLI (D3).

Added: vibe-view — cross-system compare/overlay mode (Phase D1) (2026-06-21)

  • The first piece of Phase D (cross-system comparison). When more than one .qvf is open (vibe-view open a.qvf b.qvf ), a Compare Files card in the right panel has an “Overlay all structures” switch that overlays every loaded structure in one viewport, one translucent colour per file, with a colour-keyed legend of file names. _render_compare_overlay glyph-instances each file’s atoms into a cmp_<i> actor; ctrl.toggle_compare_mode enters/leaves the mode (entering clears the single-file scene; leaving restores the active structure; navigating into any section also exits it). Structures are drawn at their stored coordinates — RMSD/Kabsch alignment (D4), the volumetric difference viewer (D2), and batch render (D3) are the planned follow-ups. Test: tests/test_plot_renderers.py::test_compare_overlay_draws_per_file_actors; verified live with trusted events (toggle → 2-file overlay + legend).

Fixed: Methfessel-Paxton / Marzari-Vanderbilt smearing test + docstrings reconciled with the shipped code (2026-06-21)

  • test_smearing_package.py carried a semantically-wrong (red) testtest_apply_smearing_rejects_unimplemented_flavors asserted apply_smearing raises NotImplementedError for methfessel-paxton / marzari-vanderbilt, but those flavors ship (M6/M7: smearing/apply.py dispatches both, KPoints.recommend defaults metals to MP). The build-test CI gate runs no tests, so the red was invisible until the full suite. Replaced with a verification test (electron conservation for MP orders 1 & 2 and MV; free energy = -T·S; occupations finite and within [0, g]), plus a regression guard that the MP generalised entropy is correctly negative for order 2 (it is not positive-definite — do not clamp it to >= 0). The stale-named T=0 test is renamed to reflect that T=0 is Aufbau for any flavor.

  • Stale docstrings corrected: SmearingOptions (smearing/options.py) and run_periodic_job (periodic_runner.py) no longer claim only fermi-dirac is implemented.

Added: vibe-view — Fermi-surface band selector (2026-06-21)

  • The fermi_surface panel gains a right-panel multi-select (shown when more than one band sheet crosses E_F) to toggle which bands are drawn, so an individual sheet can be isolated. ctrl.update_fermi_bands re-renders the chosen subset; _activate_fermi reads fermi_selected_bands (None on a fresh activation means “all”). Test: tests/test_plot_renderers.py::test_fermi_band_selection_filters_sheets.

Added: vibe-view — Fermi-surface renderer; Phase J complete (DEFERRED_KINDS empty) (2026-06-20)

  • The viewer now renders the fermi_surface QVF kind (the sixth and last of the kinds the producer promoted from reserved) in reciprocal space. renderers/fermi.py builds the reciprocal lattice from the real-space lattice_vectors (crystallographer’s convention b·a = 2π δ), lays each band’s E(k) E_F mesh into Cartesian k-space (γ rolled to the grid centre), and contours each band at E = E_F to produce one Fermi sheet per band, drawn in distinct colors inside the reciprocal-cell wireframe (spec §4.12). _activate_fermi clears the real-space molecular scene while it is shown (k space is a different coordinate system) and restores it on switch-away.

  • Phase J is complete: with phonon_bands, phonon_dos, equation_of_state, volume.potential, volume.rdg, and now fermi_surface all rendering, kinds.DEFERRED_KINDS is now empty (kept as the structural home for any future writer-ahead-of-viewer kind, pinned by the kind-drift guard). The QVF design-doc matrix is 35/35 yes/yes.

  • Tests (286 passed): reciprocal_lattice validated against the b·a = 2π δ relation (orthorhombic + non-orthogonal cells); build_fermi_sheets validated against a free-electron sphere (E(k) = |k|² − k0² contours to a sphere of radius k0 centred at γ); activation integration (clears the real-space scene, draws sheets + cell); dispatch + classify guards. Verified end-to-end: the real banner classifies fermi_surface as rendered. Docs: CHANGELOG, design doc, user guide.

Added: multi-system (calibration-set) molecular basis optimisation (2026-06-20)

  • recipes.molecular.optimize_molecular_basis_multi / recipes.objective.make_multi_native_objective_gradient optimise one basis jointly against a set of molecules — the way real basis sets are calibrated (objective = Σ_s w_s · E(system_s), one shared basis), the molecular analytic-gradient analogue of make_multi_objective. Each per-system energy/gradient is the validated in-process make_native_objective_gradient pair (penalty off); the conditioning/LD penalty is applied once at the joint level; the gradient is Σ_s w_s ∇E_s. All systems share one parametrisation and reference (functional/open_shell); weights default to 1.0 per system. Validated: the joint analytic gradient matches the FD of the joint objective, and an end-to-end RHF run jointly optimises an H basis against two H₂ bond lengths (test_molecular_recipe.py). The single-system optimize_molecular_basis now shares the BDIIS-drive/result helper.

Added: vibe-view — NCI (volume.rdg) renderer (2026-06-20)

  • The viewer now renders the volume.rdg QVF kind (the fifth of the six the producer promoted from reserved) as a Non-Covalent Interaction (NCI) surface. renderers/nci.py contours the reduced density gradient s(r) at the NCIPLOT default isovalue (0.3) and colors the surface by sign(λ₂)ρ — the second (middle) eigenvalue of the density Hessian times ρ — computed on the grid from a co-present volume.density section (the producer ships only s(r) per spec §4.11). The Hessian is formed by second-order finite differences; the eigenvalue sign convention and the blue-green-red color map (attractive / van der Waals / repulsive, clamped ±0.05 a.u.) follow Johnson et al. (JACS 132, 6498, 2010) and the NCIPLOT conventions of Contreras-García et al. (JCTC 7, 625, 2011), cited inline at the kernel. With no usable density section the RDG surface renders uncolored. Wired through the existing volume path (a branch in _rebuild_volume, so the isovalue slider drives the RDG surface); the formula provenance lives in inline comments (the viewer does not assemble citations). kinds.DEFERRED_KINDS drops to one (fermi_surface). Tests: the sign(λ₂)ρ kernel validated against analytic density extrema (maximum → negative, minimum → positive), colored + uncolored render integration, classify/dispatch guards. Verified end-to-end (the real banner classifies it rendered).

Changed: BIPOLE multipole far-field (G1) retired as a production candidate; never auto-enables (2026-06-20)

  • The opt-in BIPOLE multipole far-field branch (use_multipole_far_field, python/vibeqc/bipole_fock_multipole.py) is retired as a default-on / production candidate and is now a gated research artifact: it never auto-enables — only an explicit use_multipole_far_field=True activates it (previously a cutoff/far-pair heuristic could silently enable it in the legacy gauge). It is broken in general and produces Ha-scale errors — on current main even neutral dipole-free cells (H₂ ~0.5 Ha off), so the old “dipole-free cells are accurate” claim no longer holds.

  • Corrected the root-cause diagnosis. The earlier “L≥1 interaction-tensor normalization” attribution was wrong — the bare tensor is unit-test-correct vs exact Coulomb (tests/test_bipole_multipole.py). The Ha-scale failure is in the periodic far-field composition under the Ewald-J split: (A) cell moments built from the P0-localized Γ density carry a spurious per-cell net charge; (B) the far-field potential replaces (rather than adds to) J_SR for every cell while dropping the reciprocal J_LR — a conditionally-convergent bare-Coulomb lattice sum for dipolar cells; and (C) a J_SR aliasing double-count at the apply_multipole_far_field call site that corrupts even dipole-free cells (confined to the enabled-multipole path; the default production path is unaffected). The exact Ewald-J split already handles the far field optimally in reciprocal space, so there is neither an accuracy nor a speed case for the branch.

  • The runtime warning in resolve_multipole_config now states the true root cause; docs/bipole_status.md G1 is marked RETIRED. New regression test tests/test_bipole_multipole_far_field_retired.py pins the gating (never auto-enables), the spurious-charge root cause, and the dipolar-cell inaccuracy. No production-path behaviour change (the corrected exchange gauge already forced the branch off).

Added: vibe-view — electrostatic-potential (volume.potential) renderer (2026-06-20)

  • The viewer now renders the volume.potential QVF kind (the fourth of the six the producer promoted from reserved). It reuses the shared VolumeRenderer (its members are grid + data, identical to volume.density) and is treated as a signed field, so the marching-cubes path contours both the positive and negative lobes (app._is_signed) with a diverging coolwarm default colormap (viewer_defaults) — the standard ESP convention (red positive, blue negative, hartree/e per spec §4.10). kinds.DEFERRED_KINDS drops to two (volume.rdg, fermi_surface). Tests: the parametrized both-lobes render check in tests/test_audit_2026_06_03.py now covers volume.potential, plus dispatch + classify/diverging-default guards. Verified end-to-end (the real banner classifies it rendered).

Performance: SYM3b shell-pair mask for the BIPOLE Fock build (2026-06-20)

The opt-in point-group-reduced direct-ERI Fock build (use_fock_symmetry_reduce=True) now reduces at atom-pair (shell-pair) granularity instead of whole cells: it builds only the orbit-representative atom-pair sub-blocks within each representative cell, then recovers the rest by point-group rotation (symmetrize_fock_blocks, unchanged). New C++ build_jk_2e_real_space_output_subset_masked (cpp/src/periodic_fock.cpp + binding) takes a per-rep-cell nshells*nshells flag array built by the new representative_shell_masks in python/vibeqc/bipole_symmetry_fock.py. Because the reconstruction only ever read the representative sub-blocks, the masked build is bit-identical to the whole-cell path (test_sym3b_reduced_build_equals_symmetrized_full_build, Δ=0; new test_sym3b_shell_mask_skips_nonrep_subblocks pins the mask). Measured MgO/STO-3G direct-ERI build speedup vs the full build: c8 2.66×, c10 2.95×, c12 4.44× (vs the whole-cell subset’s 1.93× / 2.20× / 2.67×), growing with cutoff. Remains off the SCF hot path / opt-in; FD-validated exactness is unchanged.

Added: semicanonical ROHF-MP2 (2026-06-20)

  • vibeqc.run_rohf_mp2(molecule, basis, rohf_result) computes second-order Møller-Plesset correlation on a spin-pure ROHF reference (Knowles, Andrews, Amos, Handy & Pople, Chem. Phys. Lett. 186, 130 (1991)). The ROHF orbitals are semicanonicalised per spin (occ-occ and vir-vir Fock blocks diagonalised); the energy is the usual doubles plus a singles term Σ_ia |f_ia|²/(ε_i−ε_a) from the off-diagonal Fock that is non-zero for an open-shell restricted reference (and vanishes for a canonical UHF/RHF reference, recovering ordinary UMP2). Returns opposite-/same-spin components and supports SCS/SOS doubles scaling.

  • Reference kernel vibeqc.dlpno._ccsd_ref.run_ref_ump2 / run_ref_rohf_mp2 — reference-agnostic spin-orbital MP2 reusing the density-fitted _spin_orbital_eri_uhf machinery, the MP2 sibling of the run_ref_uccsd CC anchor. Citation entry knowles_rmp2_1991 + routes.methods["rohf-mp2"].

  • Verification: tests/test_rohf_mp2.py checks the kernel build-free on synthetic integrals against four independent references — textbook closed-shell RMP2, an independent spatial-form UMP2 (open-shell canonical and semicanonical, incl. OS/SS split), the gold-standard Slater-Condon CI engine as a sum-over-states PT2, an exact two-level singles toy, and closed/open/virtual rotation invariance. Build-validated end-to-end on the OH radical vs PySCF: ROHF SCF to 7e-13 Ha, MP2 doubles to the RI fitting error (PySCF’s plain MP2 omits the Knowles singles term vibe-qc includes).

  • Reachable from run_job: run_job(method="mp2", mp2_reference="rohf") on an open-shell system runs an ROHF SCF then the semicanonical ROHF-MP2 (all-electron, like the native RMP2/UMP2 path), labels the output block ROHF-MP2, reports the Brillouin singles term, and fires the rohf-mp2 citation route. scs-mp2/sos-mp2 honour mp2_reference="rohf" too (doubles scaled). Validated against the standalone run_rohf_mp2 (tests/test_runner_mp2.py).

  • Files: python/vibeqc/dlpno/_ccsd_ref.py, python/vibeqc/cc.py, python/vibeqc/__init__.py, python/vibeqc/output/citations/database.toml, python/vibeqc/runner.py, tests/test_rohf_mp2.py, tests/test_runner_mp2.py, tests/test_citations.py.

Added: open-shell double hybrids via ROKS (B2PLYP, DSD-PBEP86)

  • run_double_hybrid now handles open-shell molecules (vibe-qc’s first open-shell double hybrid). Multiplicity > 1 runs the spin-pure ROKS SCF half then a semicanonical ROHF-MP2 doubles correction scaled by the functional’s mp2_c_os / mp2_c_ss, combined as E = E(ROKS) + c_os·e_os + c_ss·e_ss. The Knowles singles term is excluded, matching the closed-shell RMP2 convention and standard open-shell double hybrids (e.g. ORCA U-B2PLYP); the reference stays spin-pure (⟨S²⟩ exact).

  • Enabled by the new ROHF-MP2 (run_rohf_mp2), which consumes the ROKS result directly (its mo_coeffs / fock_alpha / fock_beta surface). The roks.py double-hybrid gate now points users to run_double_hybrid and carries an internal _allow_double_hybrid_scf flag for the SCF half; a direct run_roks(..., functional="b2plyp") still raises (SCF-only is incomplete).

  • GGA-based double hybrids only (B2PLYP, DSD-PBEP86); meta-GGA ones (pwpb95) raise via ROKS (no τ term yet). Closed-shell B2PLYP / DSD-PBEP86 unchanged. Tests: tests/test_b2plyp.py::test_run_b2plyp_open_shell_roks (+ gate test). Validated on OH / cc-pVDZ: SCF converges, the correction lowers the energy, and the doubles match a standalone run_rohf_mp2.

  • Files: python/vibeqc/__init__.py, python/vibeqc/roks.py, tests/test_b2plyp.py.

Added: one-call native molecular basis-optimisation recipe (2026-06-20)

  • recipes.molecular.optimize_molecular_basis turns the completed molecular analytic energy gradient (RHF/UHF/RKS/UKS × exponents + coefficients) into a single user-facing call: it builds the consistent in-process (objective, analytic-gradient) pair and drives the robust optimize_bdiis loop with the analytic gradient (no per-parameter re-SCF finite differences). The reference is selected by functional/open_shell (HF vs KS, restricted vs unrestricted); tol_grad defaults to 1e-5 (HF) / 1e-4 (grid-based KS); analytic=False falls back to the driver’s FD gradient (for SP coeff_s/coeff_p). Returns a MolecularOptResult carrying the optimised {symbol: CrystalAtomBasis} dict + a summary() (per-parameter start→optimum in physical space). Unlike recipes.production (multi-crystal, derivative-free NLopt via the external-program Calculator), this is the vibe-qc-native molecular path. Tests: test_molecular_recipe.py (RHF H₂, UKS/PBE C ³P, FD fallback).

Added: vibe-view — equation-of-state (V-E curve) renderer (2026-06-19)

  • The viewer now renders the equation_of_state QVF kind (the third of the six the producer promoted from reserved). renderers/eos.py plots the sampled (volume, energy) points and overlays the fitted EOS curve, reconstructed by evaluating the published energy form (3rd-order Birch-Murnaghan, Birch 1947; or Murnaghan 1944) at the producer’s fit params, with V0/B0/B0’ and the model citation annotated in-panel. The B0·V0 prefactor is converted GPa·Å³ to eV per the spec §4.14 units. kinds.DEFERRED_KINDS drops to three (volume.potential, volume.rdg, fermi_surface). vibe-qc’s core does not fit the EOS (study scripts / external tools do), so the formula provenance lives in inline comments + the in-panel annotation rather than the producer’s references block. Tests: tests/test_plot_renderers.py (render, the BM3 formula validated at hand-computed points, Murnaghan selection, activate wiring) + the renderer-dispatch and kind-drift guards.

Added: AICCM aiccm2026dev-a symmetric-torus CCM, full method stack (2026-06-21)

The aiccm2026dev-a development line of the ab-initio Cyclic Cluster Model — a Γ-point finite Born–von-Kármán supercell with WSSC integral weighting (CCM ≡ SCM-Γ), feeding vibe-qc’s molecular HF/KS/post-HF kernels. Developed independently from, and kept separate for head-to-head comparison with, the aiccm2026dev-b line below. Standalone docs: docs/aiccm2026dev_a.md; theory AICCM_ALGORITHM.md §13; demo examples/periodic/aiccm2026dev_a_demo.py.

  • Symmetric Born–von-Kármán-torus four-center (method="aiccm2026dev-a"): symmetric bridge ¼(ω_μρ+ω_νρ+ω_μσ+ω_νσ) with ρ, σ folded independently to the home bra’s WSC — exactly 8-fold permutationally symmetric for any lattice (≤3e-18 in 1D, 2D-hex, 2D-oblique; genuinely 3D via the scalable C++ route), where the historical eq-18 union12 weight breaks ρ↔σ / μ↔ν symmetry (1.6e-3 in 1D → 2.1e-2 in 2D). union12 stays the default.

  • Full method stack on the CCM-weighted integrals, all via method=: HF (run_ccm_rhf, open-shell run_ccm_uhf; 1D H₄ → gold −0.542875), KS-DFT (run_ccm_rks/run_ccm_uks, pure + hybrid, == molecular run_rks/run_uks to <1e-6 for PBE/PBE0/B3LYP), and correlation (run_ccm_mp2, open-shell run_ccm_ump2, run_ccm_ccsd with perturbative (T) — CCSD == molecular DF-CCSD to the DF gap, (T) to ~1e-8). The scalable C++ four-center run_ccm_rhf_scalable (build_jk_ccm_weighted) reaches genuine 3D.

  • 4-center → RI → RIJCOSX hierarchy: RI via multi-k GDF on the unit cell (run_ccm_rhf_gdf/run_ccm_rks_gdf, ≲0.1 mHa/atom from the four-center in 1D and 3D — CCM ≡ SCM-Γ); explicit WSSC RI-J run_ccm_rhf_rij and RIJCOSX run_ccm_rhf_rijcosx (isolated == molecular RIJCOSX to ~5 µHa).

  • Neutral (Ewald) four-center + the Madelung gap, resolved (ccm_eri_neutral, AICCM_ALGORITHM.md §13.7/§13.9): the bare-1/r four-center is quantitative for covalent / molecular / 1D but over-binds ionic 3D crystals by a Madelung-scale shift (LiH rocksalt RKS-PBE/atom: 4c −4.540 vs gdf −4.203). The neutral four-center g_eff = (μν|v_E|ρσ) (RI density-fit of the neutral torus kernel) is the gapless object; the gap is exactly a rank-1 mean-field background g_eff = bare ξ·S⊗S (ξ = the cell Madelung constant). It is ∝ S in the Fock (absolute ionic HF/KS energies use the GDF route) and ξ·I⊗I in the MO basis — so it cancels exactly in the correlation energy (machine-ε, any ξ): the CCM post-HF stack is rigorously Madelung-robust.

  • RI-consistent three-center (ccm_neutral_cderi, ccm_ri_tensors_neutral, ccm_ri_j_neutral/ccm_ri_k_neutral): the neutral cderi L; its RI-J/K reproduce the neutral four-center to machine ε, where the bare-1/r union RI-J floors at ~1e-3 (the bare four-center is non-separable). The RI-consistent energy is the GDF route.

  • AICCM-2026 benchmark (aiccm-2026/): 18-system test set (1D/2D/3D, covalent / ionic / molecular / oxide, closed / open-shell) with a run_case.py driver, compare.py aggregator, and a vq batch generator, comparing the hierarchy against vibe-qc bipole/gdf and CRYSTAL23.

  • Tests: tests/test_ccm_aiccm2026dev_a.py, test_ccm_dft.py, test_ccm_ri.py, test_ccm_neutral.py, test_aiccm2026_testset.py. Experimental; reuses the existing CCM (Peintinger & Bredow 2014) and periodic GDF (Sun et al. 2017) citation routes.

Added: independent AICCM2026DEV-B finite-torus RHF/RKS (2026-06-21)

  • Real-torus local-PNO MP2, CCSD, and CCSD(T). The pair-resolved B RI Hamiltonian now has an exact inverse finite-group transform to real finite-torus overlap, Fock, and three-index factors. No-truncation local MP2 agrees with character-space canonical MP2 to 7e-18 Ha per cell. On the nonzero-triples LiH/STO-3G control, local versus canonical finite-torus DF-CCSD and (T) differ by 6.9e-12 and 1.4e-12 Ha total. PBC-safe Pipek–Mezey localization is the default. Molecular Boys localization, Euclidean pair/coupling screens, and local fitting fail closed until minimum-image periodic domains are implemented.

  • Canonical 3D RI-MP2 now uses the B finite Hamiltonian directly. run_aiccm2026dev_b_mp2 contracts pair-resolved three-center tensors on the full character mesh with exact finite-group momentum conservation. A canonical auxiliary-AO factor removes independent q/-q eigenspace phases. On the H2/STO-3G (1,1,2) control, HF and correlation energies agree with out-of-process PySCF KRHF/KMP2 within 0.6 nanohartree per cell. The older Gamma-supercell post-HF bridge is intentionally not reused: its HF reference differs by 4.641 mHa per cell on the compact two-cell control. MP2 fails closed in 1D/2D until their Coulomb gauges are matched.

  • A separately selectable jk_method="aiccm2026dev-b" defines a neutral, zero-temperature, closed-shell finite Born-von-Karman torus for RHF and RKS (including hybrids) without changing the existing CCM route. The constrained Gamma-supercell problem is evaluated through its exactly equivalent full, unreduced, Gamma-centred character mesh. The electron-repulsion backend is explicitly selectable as corrected-gauge four-centre, pair-resolved RI, or RIJCOSX.

  • Independent boundary construction and variational definition: exact Wigner-Seitz closest-image representatives include non-Bravais centre offsets and split geometric ties by a partition of unity. Historical pair-product four-centre weights are deliberately not used because their displayed form does not preserve every ERI permutation needed for a conventional scalar RHF functional.

  • Dimensional support and fail-closed physics: RHF/RKS now execute in 1D, 2D, and 3D through all three backends. Charged cells, open shell, smearing, shifted or non-cyclic meshes, and the inconsistent q-only compcell fit are rejected. Results report density idempotency, electron count, Wigner-Seitz partition, inverse-Bloch, timing, and finite-cluster diagnostics.

  • Validation and comparison: the H4/STO-3G chain at cyclic lengths 1, 2, and 4 is identical to the direct Gamma-centred character-mesh result to all printed digits; the difference from the historical union12 CCM falls to 1.257 mHa/cell at length 4. The separately developed symmetric aiccm2026dev-a peer is also reported and, as expected in this 1D case, matches union12 to the printed digits. A compact two-cell H2 control runs RHF and PBE0 through all three backends with a maximum 43.7 microhartree spread. The larger H4 direct/RI discrepancy remains explicitly open. The derivation, decisions log, comparative LaTeX manuscript, benchmark harness, tests, citation route, and persistent handover ship in the same milestone.

  • Full B-stream fleet inputs: aiccm-2026/run_case_b.py and make_jobs_b.py reuse the exact 18-system A-stream geometry registry while dispatching only aiccm2026dev-b. The explicit matrix contains RHF, PBE, and PBE0 with four-center, RI, and RIJCOSX backends plus canonical RI-MP2, DLPNO-MP2, DLPNO-CCSD, and DLPNO-CCSD(T). JSON results include orbital gaps, energy components, finite-torus invariants, local-pair diagnostics, and timings. compare_b.py joins the result set to compatible A and CRYSTAL energies without treating basis- or functional-mismatched values as parity.

  • Experimental documentation is separate by stream: the B implementation has its own user guide, tutorial, periodic example, benchmark harness, warning class, theory/decisions files, and manuscript sections. The paper now audits the 2008 Janetzko–Köster–Salahub deMon2k KS-ADFT weights: its two-center and RI-adjoint proofs remain open, while its explicitly two-order-averaged three-center equation is not assigned the 2014 four-center symmetry defect without further evidence.

Added: periodic ECP integrals from CRYSTAL-format inline data (2026-06-20)

  • ECP application in periodic SCF unblocks 107 of 325 cohesive-energy reference compounds (every 4d/5d element: Ag, Au, W, Pt, …) by wiring the already-parsed CRYSTAL inline-ECP Gaussian expansions through libecpint’s set_ecp_basis(…) inline-primitive API (Phase 14g).

  • Two new C++ entry points: compute_ecp_matrix_from_primitives (molecular single-cell) and compute_ecp_lattice_from_primitives (periodic lattice-summed, mirroring compute_nuclear_lattice). Both accept ECPPrimitiveBlock flat arrays — the per-centre (α, c, n, am) primitives that libecpint expects — and produce a real symmetric V_ECP_{μν} matrix in the spherical AO basis.

  • Periodic SCF drivers (run_rhf_periodic / run_rks_periodic) now accept ECP data via PeriodicSCFOptions.ecp_primitive_blocks / .ecp_home_centers / .ecp_effective_charges / .ecp_total_ncore. When present, H_core uses Z_eff nuclear attraction + V_ECP, n_elec is reduced to valence-only, and nuclear repulsion uses Z_eff. All-electron behaviour is unchanged.

  • Auto-attach for pob-TZVP-REV2: run_periodic_job detects ECP-bearing pob bases (Z+200 convention) via _resolve_ecp_data, calls build_periodic_ecp_data to convert the parsed CrystalECPECPPrimitiveBlock lists, and sets them on the C++ options struct. No user wiring needed.

  • Validation targets (PBE/pob-TZVP-REV2 + ECP against PySCF.pbc, ≤1 mHa/atom): Ag fcc, Au fcc, W bcc. Regression tests in tests/test_periodic_ecp.py (pending PySCF cross-validation for final reference values).

  • META.yaml: heavy-element-ecp blocker removed from Ag/Au/W validation systems (they still carry periodic-meta-gga and geometry-optimization).

  • Files touched: cpp/include/vibeqc/ecp.hpp, cpp/src/ecp.cpp, cpp/src/periodic_scf.cpp, cpp/include/vibeqc/periodic_scf.hpp, cpp/include/vibeqc/lattice_integrals.hpp, cpp/src/lattice_integrals.cpp, cpp/src/bindings.cpp, python/vibeqc/basis_crystal.py, python/vibeqc/periodic_runner.py, python/vibeqc/__init__.py, tests/test_periodic_ecp.py.

Added: vibe-view — phonon band-structure + phonon DOS renderers (2026-06-19)

  • The viewer now renders the phonon_bands and phonon_dos QVF kinds, two of the six the producer promoted from reserved in M15-M19 (previously surfaced as “skipped, not yet rendered”). renderers/phonon.py plots the phonon dispersion along the Brillouin-zone q-path and the phonon DOS, both in cm^-1, on the same 2D side-panel machinery as the electronic bands/DOS. Soft (imaginary) modes are drawn below the zero-frequency line rather than clamped, so a dynamical instability stays visible. kinds.SUPPORTED_KINDS gains both kinds and DEFERRED_KINDS drops to four (volume.potential, volume.rdg, fermi_surface, equation_of_state). The viewer plots producer-computed frequencies faithfully, so there is no new scientific citation. Tests: tests/test_plot_renderers.py (render, soft-mode preservation, activate-helper wiring) plus the renderer-dispatch and kind-drift guards.

Added: r²SCAN01 meta-GGA functional, molecular + periodic (2026-06-20)

  • functional="r2scan01" registers the r²SCAN01 meta-GGA (Furness, Kaplan, Ning, Perdew, Sun, J. Chem. Phys. 156, 034109 (2022)): a re-regularization of r²SCAN with a larger η that restores the fourth-order term of the slowly-varying density-gradient expansion r²SCAN had traded away for smoothness, targeting slowly-varying densities (bulk metals, bonding regions) where r²SCAN slightly underbinds. libxc XC_MGGA_X_R2SCAN01 (645) + XC_MGGA_C_R2SCAN01 (642), both τ-only (laplacian-free).

  • Molecular and periodic out of the box. Being a τ-only meta-GGA it rides the existing molecular (RKS/UKS SCF + analytic gradient) and periodic (GDF / Ewald / multi-k / BIPOLE Γ) meta-GGA machinery with no driver changes, exactly as r2scan does.

  • Validation: H₂O/def2-SVP RKS matches PySCF (MGGA_X/C_R2SCAN01, grids.level=5) to 3.9 µHa; periodic H₂/STO-3G in a 30-bohr box tracks the molecular limit to <1e-7 Ha. Citation routes for functional="r2scan01" (Sun 2015 + Furness 2020 + Furness 2022). Tests in tests/test_xc.py.

Added: frozen natural orbitals (FNO) for closed-shell CCSD(T) (2026-06-20)

  • CCSDOptions(fno=True) truncates the virtual space to the dominant MP2 natural orbitals before CCSD(T) (DePrince-Sherrill, J. Chem. Theory Comput. 9, 2687 (2013)), the standard lever for making canonical CCSD(T) affordable on larger molecules. The MP2 virtual one-particle density is diagonalized, the dominant natural orbitals are kept (occupation cutoff fno_occ_threshold, default 1e-5, or a fixed fno_keep_fraction), the retained block is semicanonicalized so the perturbative (T) stays exact, and a delta-MP2 correction (fno_delta_mp2, default on) recovers the truncated MP2 correlation.

  • New C++ entry run_ccsd_from_mos (cpp/src/ccsd.cpp, bound as _vibeqc_core.run_ccsd_from_mos): closed-shell CCSD(T) from explicit MO coefficients + an AO Fock matrix, sharing run_ccsd_core with the canonical run_ccsd (whose numerics are unchanged). This is the closed-shell sibling of run_uccsd_from_mos and the kernel entry the FNO path drives.

  • Reachable from run_job: run_job(method="ccsd(t)", ccsd_options=CCSDOptions(fno=True)) runs FNO-CCSD(T) and reports the kept / total natural-orbital count and the delta-MP2 correction in the .out. Orchestration is vibeqc.cc.run_fno_ccsd, returning an FNOCCSDResult (CCSDResult fields plus n_virtual_kept / n_virtual_total / delta_mp2).

  • Validation (tests/test_ccsd_fno.py, always-on): with no truncation the FNO path reproduces canonical CCSD(T) to machine precision (CCSD is invariant to virtual rotation; semicanonicalization restores the canonical (T)); truncation + delta-MP2 recovers correlation as expected. Closed-shell (RHF) only; open-shell FNO and an in-kernel C++ FNO build are future work. Citation: the DePrince-Sherrill 2013 entry already routes for ccsd/ccsd(t).

Added: FRAGMO fragment-superposition initial guess (molecular) (2026-06-20)

  • InitialGuess.FRAGMO — the molecular fragment-MO assembly guess (roadmap D2h). The user partitions the molecule into fragments; each fragment is converged on its own SCF and the fragments’ converged density matrices are assembled block-diagonally (one diagonal block per fragment, inter-fragment blocks zero) into the supersystem guess density. For weakly-interacting assemblies (hydrogen-bonded dimers, van-der-Waals complexes) the fragments already carry almost all of the electronic structure, so the guess starts much closer to the supersystem solution than SAD and converges in fewer SCF iterations.

  • API: run_rhf/run_rks/run_uhf/run_uks(mol, basis, opts, fragments=...) with opts.initial_guess = InitialGuess.FRAGMO. fragments is a list of vibeqc.Fragment(atoms, charge=0, multiplicity=1) specs, or plain atom-index sequences (e.g. [[0,1,2],[3,4,5]], each a neutral closed-shell fragment). Covers RHF / UHF / RKS / UKS — fragments are converged with the supersystem’s method/functional; an open-shell supersystem feeds each fragment’s α/β blocks to the matching spin. Rides the same opts.read_density injection path as the READ guess.

  • Honest errors (CLAUDE.md §7): fragments must be a disjoint partition covering every atom exactly once and their charges must sum to the molecule charge (electron conservation) — each violation raises a clear ValueError; a non-convergent fragment SCF raises; fragments= without initial_guess=FRAGMO raises; FRAGMO on a periodic system raises a clean NotImplementedError (a crystal has no finite-fragment partition).

  • New module python/vibeqc/guess_fragmo.py (sibling of guess_read.py); vibeqc.Fragment exported at the package top level. C++ InitialGuess enum + the four molecular drivers consume the injected density for FRAGMO exactly as for READ.

  • Tests: tests/test_fragmo.py — guess-independence (FRAGMO energy == SAD energy on water dimers, RHF + RKS/PBE + UHF), the iteration-count win on weakly-interacting dimers, and the full honest-error surface.

Fixed/Added: periodic density mixers (Anderson + Broyden) wired into the multi-k RKS driver (D4) (2026-06-20)

  • density_mixer="anderson" / "broyden" now selects real-space density-matrix mixing in run_rks_periodic_multi_k_ewald3d and run_rks_periodic_scf (default None → unchanged Fock-DIIS). When active a mixer replaces Fock-DIIS, linear damping and the quadratic fallback (they are alternative convergence strategies, not stacked — CLAUDE.md §7); smearing and the level shift still apply. BroydenMixer (limited-memory Broyden second method) is added alongside AndersonMixer, superseding the orphaned, occupation-guessing scf_acceleration.BroydenAccelerator.

  • Correctness fix (§7): the mixers now mix the per-k density matrices (electron-count-exact) and rebuild the real-space density via the canonical fold. The initial wiring mixed the real-space LatticeMatrixSet and Bloch-summed it back, but the lattice-sum cutoff cell list does not invert cleanly to the k-mesh — bloch_sum(D_real,k) is an aliased/scaled proxy, so the per-k electron count exploded and the SCF converged to a nonphysical over-bound energy (~−14 Ha on a 2-electron metallic H-chain). Validated: Anderson/Broyden reach the same stationary point as the converged DIIS reference to µHa across gapped (1e-10), Fermi-smeared metallic, and T=0 Gilat-Raubenheimer (1e-11) regimes.

  • Honesty surface: scf_accelerator="anderson"/"broyden" (a Fock-space selector that has no SCFAccelerator enum value) now raises a NotImplementedError pointing the user at the density-space density_mixer= entry point instead of just “unwired”. run_rks_periodic_scf fails closed if density_mixer is requested on a route that cannot honour it (DIRECT_TRUNCATED / Γ-only).

  • Kerker preconditioner (D4b) is now wired: density_mixer_kerker=True (with kerker_k0 / kerker_strength / kerker_cutoff_ha) applies the genuine reciprocal-space filter K(G)=|G|²/(|G|²+G₀²) to the density mixer’s residual — collocate the cell-average residual to a plane-wave grid (periodic_gapw_j), filter the small-|G| charge-sloshing modes, project back with the S⊗S density-fit metric. Because it preconditions the residual (zero residual → zero correction) the converged energy is provably the exact SCF solution (CLAUDE.md §7-safe) — validated to µHa vs DIIS across gapped/Fermi/gilat. Honest caveat: on the minimal-basis Gaussian test cells (STO-3G H-chains) it is convergence-neutral — its target, long-wavelength sloshing of large metallic supercells / Be-fcc, was not reproduced here, so it ships as a correct opt-in tool, not a demonstrated speed-up. Requires a density_mixer; fails closed otherwise.

  • Tests: tests/test_periodic_density_mixing.py (+ BroydenMixer + per-k bridge unit tests) and tests/test_periodic_density_mixing_wiring.py (DIIS-parity across gapped/smeared/gilat, gating, dispatch threading + fail-closed, Kerker §7-safety incl. the zero-residual→zero-correction invariant).

Added: open-shell DLPNO-UCCSD(T) pilot (UHF reference), M2 (2026-06-21)

  • vibeqc.dlpno.run_dlpno_uccsd_pilot – the open-shell sibling of the closed-shell DLPNO-CCSD pilot (run_dlpno_ccsd_pilot). Spin-orbital subspace-projected UCCSD(T) on a UHF reference: the residuals are evaluated by the FCI-anchored spin-orbital engine (dlpno._ccsd_ref, SGWB-1991, the same kernel that anchors cpp/src/uccsd.cpp) while the ansatz is the DLPNO one. Because the reference is unrestricted the spin-orbital integrals already encode spin, so there is one virtual space and no explicit αα/ββ/αβ channel split – far simpler than re-deriving spin-adapted open-shell residuals (Saitow et al., J. Chem. Phys. 146, 164105 (2017)).

  • Ansatz: T2 amplitudes live in per-pair PNO spaces (built from the spin-orbital MP2 pair density D = T Tᵀ + Tᵀ T, truncated by occupation tcut_pno, quasi-canonicalised); T1 in the full spin-orbital singles space (diagonal pairs (P,P) vanish by Pauli antisymmetry, so there is no diagonal-pair PNO set). Each macro-iteration expands the constrained amplitudes to the full spin-orbital space, evaluates the exact SGWB residuals, and projects them back into the retained subspaces.

  • Exactness / projection ratchet: untruncated (tcut_pno=0) the pilot reproduces the spin-orbital run_ref_uccsd anchor to ~µHa – canonical and Foster-Boys-localised occupieds (full-rank PNO rotations exercise expand/project), and with a frozen core (OH/STO-3G: ≤6e-10 Ha; OH/def2-SVP, CH₃/def2-SVP: ≤3e-10 Ha). tcut_pno=1e-7 recovers ~99.8-99.9% with real PNO compression (OH/def2-SVP 11.9/29 spin-orbital PNOs).

  • (T): the perturbative triples (_ccsd_ref.so_triples_correction, Raghavachari 1989, spin-orbital) on the converged amplitudes match the anchor at full domains – localise="none" exact in the spin-orbital basis, "boys" via rotating the occupieds back to canonical first (the exact-(T) path, mirroring the closed-shell pilot). ≤5e-11 Ha vs run_ref_uccsd(compute_triples=True).

  • O(N⁶) full-space-contraction pilot, capped at max_nbf=64 – the correctness oracle a reduced-scaling open-shell engine will be validated against (mirrors the closed-shell pilot/local-solver relationship). Tests: tests/test_dlpno_uccsd.py (14: exactness/frozen-core/diagonal-pair-exclusion/(T) fast lane + DZ recovery/exactness/(T) slow lane).

  • User-facing: run_job(method="dlpno-ccsd"|"dlpno-ccsd(t)") now auto-routes an open-shell reference (multiplicity > 1) to UHF + the DLPNO-UCCSD(T) pilot (it raised before). _resolve_scf returns uhf; the effective label dlpno-uccsd/dlpno-uccsd(t) drives a spin-orbital .out block, a dlpno_uccsd_done structured-log event, and the open-shell paper (Saitow 2017) reaching .references/.bibtex via routes.methods["dlpno-uccsd"/"dlpno-uccsd(t)"]. The result exposes res.dlpno_ccsd (a DLPNOUCCSDPilotResult); pass a DLPNOUCCSDPilotOptions as dlpno_ccsd_options to control localise / tcut_pno / frozen core. Closed-shell DLPNO-CCSD/(T) continue to use the reduced-scaling local solver. Tests: tests/test_runner_dlpno_ccsd.py (open-shell e2e + citation

    • options passthrough + pilot cap).

Added: open-shell DLPNO-UMP2 (UHF reference), M1-M1c (2026-06-20)

  • vibeqc.dlpno.run_dlpno_ump2 – the open-shell analogue of run_dlpno_mp2. The UHF-reference MP2 correlation is resolved into the three spin channels (αα/ββ same-spin antisymmetrised + αβ opposite-spin Coulomb; Szabo & Ostlund §6.7), each built from the density-fitted α/β B-tensors – the mixed αβ block is a single contraction of the α and β B-tensors over the auxiliary index.

  • M1 (localise="none", canonical occupieds): per-spin-pair PNOs truncated by occupation number (tcut_pno) – same-spin pairs one PNO set, opposite-spin pairs a separate α-PNO and β-PNO set. Exactness ratchet: tcut_pno=0 reproduces canonical UMP2 (correlation.ump2_energy, same DF integrals) channel by channel to machine precision (≤3e-17 Ha on OH/STO-3G, OH/def2-SVP, CH₃/def2-SVP, frozen-core); tcut_pno=1e-8 recovers ~99.99% with real PNO compression.

  • M1b (localise="boys"): Foster-Boys localised α/β occupieds with the coupled LMP2 residual solved per spin channel in the full virtual space (the off-diagonal localised Fock couples each pair to its neighbours; the three channels are independent at first order and solved separately). Also reproduces canonical UMP2 to machine precision (LMP2 with full virtual == canonical MP2).

  • M1c (localise="boys", tcut_pno>0): per-pair PNO truncation on top of the localised coupled residual. Each pair carries its own truncated PNO basis and the coupling sums project a neighbour’s amplitude into the target pair’s PNO space via the PNO overlap S = U_target^T U_src (same-spin: one set, sign-carried for the pair antisymmetry; opposite-spin: separate α/β sets, dual-sided projection). Because the PNOs are pair-specific rotations even at tcut_pno=0, the full-domain limit exercises the projections and stays exact – the projection ratchet cross-checks the per-pair-PNO solver against the independent full-virtual solver and canonical UMP2. At tcut_pno=1e-8 it recovers ~99.6-99.8% with PNO compression (the standard DLPNO localised-domain approximation).

  • Locality (tcut_pairs, Boys path): a pair whose semicanonical MP2 estimate is below tcut_pairs is dropped from the coupled solve and contributes that cheap full-virtual estimate directly, turning the pair list O(N) for extended systems. No-op on compact molecules (every pair strong) and lossless on separated fragments – an OH radical + a water 8 Å away screens all ~90 inter-fragment pairs for < 0.001 kcal/mol. Default 0 (exact); a calibrated default and PAO virtual domains are later steps.

  • SCS/SOS spin-component scaling and frozen core supported on all paths.

  • User-facing: run_job(method="dlpno-mp2") now auto-routes an open-shell reference (multiplicity > 1) to UHF + DLPNO-UMP2 (it raised before). The .out carries a spin-channel-resolved DLPNO-UMP2 block, a dlpno_ump2_done structured-log event fires, the result exposes res.dlpno_ump2, and the open-shell paper (Saitow et al., J. Chem. Phys. 146, 164105 (2017)) reaches the .references / .bibtex via routes.methods["dlpno-ump2"]. Pass a DLPNOUMP2Options as dlpno_options to control thresholds. Tests: tests/test_dlpno_ump2.py (21) + tests/test_runner_dlpno_mp2.py (open- shell e2e + citation + options passthrough).

Added: CASSCF analytic nuclear gradient (single-state, z-vector-free) (2026-06-20)

  • Analytic CASSCF nuclear gradient for single-state RHF-CASSCF, surfaced on SolverResult.gradient (shape (n_atoms, 3), Ha/bohr). Computed by contracting the effective AO 1- and 2-particle density matrices with AO derivative integrals via the existing C++ gradient kernels, plus an overlap-gradient correction (W_unrelaxed) from the orthonormalization response. Validated in a three-way comparison: against ORCA 6.1 EnGrad (energy Δ = 0.6 mHa), OpenMolcas &ALASKA (energy Δ = 1e-8 Ha), and in-repo full CASSCF finite differences. The gradient captures ~87% of the full CP-MCSCF gradient; the remaining ~13% (W^z, CI+orbital relaxation via the Handy-Schaefer z-vector) is gated behind CASSCFOptions(compute_wz=True) (see 2026-06-22 entry below).

  • SolverResult.gradient — new optional field (n_atoms × 3 ndarray, None for methods without gradients).

  • 8-fold AO gamma symmetrization — resolves the C++ kernel’s l-canonical shell reordering for systems with mixed angular-momentum shells (essential for p-elements, no-op for pure s-shells).

  • Citation database: handy_schaefer_cp_mcscf_1984 entry + route under casscf.

  • Parity script: examples/regression/parity_casscf_gradient.py — out-of-process ORCA + OpenMolcas comparison.

  • Tests: tests/test_casscf_gradient.py (6 tests, H2/6-31G and H2O/STO-3G, all green).

Added: CASSCF gradient — W^z correction, SA gradients, geometry optimization (2026-06-22)

  • W^z (CI+orbital relaxation) correction — CP-MCSCF z-vector approach via energy-FD g^R (correct ∂²E/∂κ∂R without double-counting the metric response). Gated behind CASSCFOptions(compute_wz=True). Captures ~60% of the W^z gap at equilibrium; the remaining ~40% requires the proper Handy-Schaefer F^z construction.

  • State-averaged CASSCF gradients — using state-averaged RDMs (make_rdm12_sa). W^z not yet implemented for SA.

  • Geometry optimizationoptimize_molecule(method="casscf") now uses analytic gradients instead of FD on energy. H2 CAS(2,2)/6-31G verified: R=1.5 → R≈1.4 in ~3 L-BFGS steps.

  • CI coupling infrastructure_build_ci_hamiltonian (H_det), _build_ci_orbital_coupling_energy_fd (H_oc), CI-eliminated Hessian (H_eff). Makes <3% difference for H2O; H_eff can have negative eigenvalues (saddle-point character in full orbital+CI space).

  • Tests: tests/test_casscf_gradient.py (6→10 tests, +SA +optimization).

  • Fixed: _run_molecular_scf and analytic-path closures silently dropped casci_options / cas_reference on the casscf branch — an SA-CASSCF optimization with cas_reference="uno" or a casci_options solver override would have walked the default surface per geometry step while the final single-point used the requested options.

Added: VV10 nonlocal correlation (catch-up Tier 3 #5; un-gates ωB97X-V) (2026-06-20)

  • The VV10 nonlocal-correlation functional (Vydrov & Van Voorhis, J. Chem. Phys. 133, 244103 (2010)) is now evaluated — previously it existed only as a comment in cpp/src/xc.cpp and was never computed. libxc supplies the semilocal part of a VV10-paired functional and the (b, C) parameters via xc_nlc_coef, but not the nonlocal double-grid integral; cpp/src/vv10.cpp (compute_vv10) is that evaluator — energy + self-consistent v_ρ / v_σ potential, folded into the RKS and UKS V_xc on the same grid each SCF iteration.

  • xc="wb97x-v" is now the complete ωB97X-V functional. It previously silently omitted VV10 (resolved to a libxc id but returned the semilocal+RSH energy only — an ~8 mHa error on H₂O). Functional now exposes needs_vv10 / vv10_b / vv10_C, and any functional libxc flags with XC_FLAGS_VV10 gets the nonlocal term automatically.

  • New xc="vv10" alias — the original standalone VV10 functional (libxc XC_GGA_XC_VV10, b = 5.9, C = 0.0093).

  • vibeqc.compute_vv10(points, weights, rho, sigma, b, C) exposes the kernel for direct validation.

  • Validated three ways (tests/test_vv10.py): the kernel reproduces PySCF’s reference _vv10nlc (energy + both potential channels) on a shared grid to ~1e-8; the analytic potential is the finite-difference derivative of the energy; and the end-to-end self-consistent ωB97X-V total matches PySCF (nlc="VV10") to sub-µHa on H₂O / cc-pVDZ. Citation route [routes.functionals]."vv10" added (vydrov_vanvoorhis_vv10_2010).

  • Note: the nonlocal integral is O(N_grid²) on the main XC grid and runs each SCF iteration; fine for molecular work, a dedicated coarse VV10 grid is a documented future optimisation.

Added: ωB97M-V meta-GGA hybrid; ωB97M(2) gated (catch-up Tier 3 #6) (2026-06-20)

  • xc="wb97m-v" (ωB97M-V; Mardirossian & Head-Gordon, J. Chem. Phys. 144, 214110 (2016)) — the range-separated meta-GGA hybrid with VV10 nonlocal correlation, the most accurate ωB97-V functional. It composes all three advanced XC machineries at once: the erf-attenuated range-separated exchange, the τ-dependent meta-GGA Kohn-Sham path, and the VV10 nonlocal term (now that VV10 has landed). Validated end-to-end against PySCF (nlc="VV10") to grid accuracy on H2O / cc-pVDZ (tests/test_wb97m_v.py); citation route added (mardirossian_wb97mv_2016 + vydrov_vanvoorhis_vv10_2010).

  • ωB97M(2) is gated, not shipped. Unlike the other double hybrids, ωB97M(2) (Mardirossian-Head-Gordon 2018) is not a composition of stock libxc components: its SCF base is a re-optimised B97M power-series functional that libxc 7.0.0 does not provide. xc="wb97m(2)" raises a specific message pointing to ωB97M-V and explaining that the double hybrid needs a dedicated from-scratch meta-GGA kernel — rather than the generic “unknown name”.

  • ωB97M(2) building blocks (infrastructure, no user-facing functional yet). The from-scratch generic B97M-form semilocal kernel landed and is validated to machine precision against libxc’s ωB97M-V (feed ωB97M-V’s coefficients → reproduce its energy density to 4.4e-16 and its v_ρ/v_σ/v_τ potential to ~1e-9): vibeqc.compute_b97m_semilocal_exc (energy) and compute_b97m_semilocal_vxc (potential), cpp/src/b97m_energy.cpp, tests/test_b97m_kernel.py. These are the correctness-critical pieces of the self-consistent ωB97M(2) variant (maintainer’s choice over the published xDH form). The remaining work is the self-consistent SCF driver

    • PT2 on the converged orbitals; until it lands, xc="wb97m(2)" stays gated and these kernels have no SCF consumer (tested building blocks only). Full spec + recipe: HANDOVER_WB97M2.md. Tracked as a follow-up.

Added: revDSD-PBEP86-D4 double hybrid (catch-up Tier 1 #5 / Tier 3 #7) (2026-06-20)

  • xc="revdsd-pbep86" now resolves and computes (previously threw Functional: unknown name — the surface existed only as a D4-parameter row + citation route). It is the GMTKN55-retrained revision of DSD-PBEP86 (Santra, Sylvetsky & Martin, J. Phys. Chem. A 123, 5129 (2019)): the D4 linear fit — 0.69·HF + 0.31·PBE-X + 0.4210·P86-C for the SCF piece, c_os = 0.5922, c_ss = 0.0636 for the MP2 correction (Table 4). The no-dash alias revdsdpbep86 resolves identically. The -D3BJ member uses a different XC/MP2 fit and is deliberately not reachable through this alias.

  • run_revdsd_pbep86(mol, basis, ...) is the named dispatcher (thin wrapper over run_double_hybrid); dispersion="d4" folds in the Caldeweyher-Bannwarth-Grimme D4 term (damping s6 = 0.5132, a1 = 0.44, a2 = 3.60, read from dispersion_d4_parameters via revdsdpbep86) for the published revDSD-PBEP86-D4 total.

  • Validated against PySCF (hand-rolled hybrid SCF + scaled DF-MP2 from the identical primitive XC pieces) to grid accuracy on H2O / cc-pVDZ (tests/test_revdsd_pbep86.py). Citation routes added under [routes.functionals] + [routes.methods] (santra_martin_revdsd_2019).

Changed: DLPNO-CCSD(T) pair screening (tcut_pairs) default-on (2026-06-20)

  • LocalCCSDOptions.tcut_pairs now defaults to 1e-4 (was 0), matching ORCA’s TCutPairs. A pair whose full-virtual MP2 energy is below the threshold is treated at MP2 level (its exact pair energy added directly) instead of being iterated at truncated-PNO CCSD. For a genuinely weak pair the CCSD correction is negligible and full-virtual MP2 has no PNO-truncation error, so screening is accuracy-neutral while removing that pair’s CCSD cost.

  • Calibrated on the 7-mol def2-SVP benchmark (all-electron, vs canonical CCSD(T)): a no-op on the 5-occupied molecules (H₂O/NH₃/CH₄/HF screen 0 pairs) and the weak ~10-14 % tail on larger ones (CO/N₂ 10.7 %, H₂CO 13.9 %); aggregate MAE 0.369 vs 0.368 for tcut_pairs=0 (identical, max error unchanged at 0.84 kcal/mol). On extended systems it is the linear-scaling lever: two waters 8 Å apart screen all 25 inter-fragment pairs (45 %) for a -0.0006 kcal/mol change and ~2x speedup. 3e-4 begins to cost accuracy (MAE 0.379), 1e-3 cuts real short-range pairs (MAE 0.496). Set tcut_pairs=0 for the no-screening exactness reference (the parity ratchets pin it).

  • Coverage: tests/test_dlpno_ccsd_solver.py::TestPairScreening (separated-fragment losslessness, monotone screening count) + ::TestPairScreeningAccuracy (N₂ default accuracy-neutral). Exactness ratchets pin tcut_pairs=0 explicitly, mirroring the coupling_radius default-on precedent.

  • Docs corrected to the honest shipped default. The benchmark, the DLPNO tutorial, and the user guide claimed tcut_pno=1e-8 / MAE 0.17 as the default; the actual default is tcut_pno=1e-7 / MAE 0.37 (the 1e-8 value was reverted in an earlier fix without updating the prose). The ORCA-matching 0.17 recipe (tcut_pno=1e-8) is documented as the high-accuracy option.

Added: rigorous 2D (slab) Ewald energy primitive — foundation for CoulombMethod.SLAB_EWALD_2D (2026-06-20)

  • vibeqc.ewald_2d_point_charge_energy(lattice, positions_cart, charges, options) — the exact 2D-periodic (slab) Madelung energy of Parry (Surf. Sci. 49, 433 (1975)) and de Leeuw & Perram (Mol. Phys. 37, 1313 (1979)): real-space + reciprocal (g≠0) + the special g=0 slab term + self. The true 2D lattice sum — no vacuum-gap parameter and no spurious inter-image dipole field along the normal — for a system periodic in lattice columns 0,1 and finite along the normal. Requires a charge-neutral cell (a net-charged periodic plane diverges); raises ValueError otherwise. cpp/src/ewald.cpp + cpp/include/vibeqc/ewald.hpp.

  • vibeqc.ewald_2d_point_charge_potential(...) (M2a) — the matching 2D (slab) Ewald electrostatic potential at arbitrary points (real-space + 2D reciprocal g≠0 + g=0 term, include_short_range flag). The long-range building block for the electron-nuclear attraction in a slab SCF. Validated by the exact functional-partner identity E = ½ Σ_i q_i V(r_i) + E_self against the energy summer, plus α-invariance and 3D-vacuum agreement of potential differences.

  • The SCF dispatch is still unchanged (gated). Selecting CoulombMethod.SLAB_EWALD_2D on the periodic SCF drivers still raises NotImplementedError; the electronic 2D-Ewald J/K coupling that un-gates a self-consistent slab run is the next milestone (HANDOVER_SLAB_EWALD_2D.md). NEUTRALIZED_1D (1D wire) is unchanged. Workaround for 1D/2D systems today: CoulombMethod.DIRECT_TRUNCATED.

  • Closes roadmap-catchup audit Tier 1 #2 / Tier 4 #6 (docs/roadmap_catchup_audit_2026_06.md): the maintainer chose implement the rigorous method over removing the enum.

  • Citations: parry_2d_ewald_1975 + de_leeuw_perram_2d_ewald_1979 added to the database with a routes.methods.slab_ewald_2d route. Tests: tests/test_ewald_2d_slab.py (α-invariance, 3D-vacuum limit, 2D square Madelung constant 1.6155426267, neutrality guard, geometric invariances, citation route).

Fixed — vq: vq overview daemon health no longer false-FAILs on macOS (2026-06-20)

  • A healthy local daemon on macOS read as daemon: FAIL (pidfile). vq overview / vq daemon health derive the daemon verdict from lifecycle.verify_user_systemd_contract(), whose four-source check is Linux-systemd-specific (loginctl, pgrep 'systemd --user', systemctl --user, the pidfile). On macOS none of those exist, so two probes returned FAIL and flipped the verdict to FAIL even though the daemon was demonstrably serving — vq queue / vq programs answered over the RPC socket and jobs dispatched normally. The muspelheim guarded node (a documented exception precisely because those controls are Linux-only) hit this every time.

  • The verdict now falls back to RPC-socket liveness when no systemd-user manager is present. When all three systemd signals are absent (no loginctl, no systemd --user, systemctl --user unreachable), verify_user_systemd_contract() skips the inapplicable four-source contract and reports ok=True iff the daemon answers ping on <state_root>/daemon.sock — the same signal vq queue / dispatch use — carrying the pidfile pid through for display. A healthy macOS node now reports daemon: OK ... (pidfile). Linux is unchanged: a real host always has a loginctl State + manager PID, so it keeps the full contract, and the orphan/zombie incidents still FAIL correctly. Mirrors the STATUS-1 (v0.8.18) precedent of trusting daemon liveness — not a Linux-only mechanism — as the source of truth.

  • Regression coverage in tests/test_lifecycle.py (TestVerifyContractRpcFallback: responsive→OK, unresponsive→FAIL, complete verdict surface, drain pass-through) + tests/test_overview.py (the daemon: OK ... (pidfile) render). Docs: docs/lifecycle.md § “Hosts without systemd-user”.

Fixed — vq: a stale admin-update marker no longer wedges dispatch (2026-06-18)

  • A dead vq admin update marker silently parked the queue. The in-progress marker (<state_root>/admin-update-in-progress) gates the daemon’s dispatch loop, but nothing checked that the writing process was still alive: a marker left by a killed update — or one whose host rebooted mid-update, since the marker persists in the state dir — held every submitted job at pending indefinitely while the daemon was otherwise healthy and the host reported plain up/OK. Hit saturn and maru on 2026-06-18 (a pid=66408 marker, 5 days stale, survived a reboot; vq admin clear-update-marker -y un-wedged it instantly).

  • The daemon now reaps a stale marker — at startup and on every dispatch tick — when the recorded pid is gone, the pid was recycled (a new pid_start_time /proc fingerprint catches the post-reboot case), or the marker is older than 24h; it then resumes dispatch. A live update (pid alive + young, including the brief daemon-restart window) is never reaped and still correctly holds the queue. admin.admin_update_marker_stale_reason is the shared predicate.

  • vq overview surfaces a lingering stale marker with a loud STALE line + an admin_marker_stale_reason JSON field — it only lingers when a host’s daemon is down and so can’t self-reap — instead of reporting plain OK.

  • Regression coverage in tests/test_admin_stale_marker_v0_11_0.py (dead / recycled / aged predicate, daemon reap-and-dispatch, live-marker-kept, overview surfacing) + tests/test_daemon.py::TestMarkerPidLivenessCheck updated to the reap behaviour. Operator guidance: docs/operations.md § “Stale markers are auto-reaped”.

Added: AUTO k-point recommender auto-read in periodic SCF drivers (Phase K8-E) (2026-06-19)

  • run_periodic_job and run_{rhf,rks}_periodic_scf now auto-read .smearing and .bz_integration from a KPoints.recommend() result when the user passes the recommended spec as kpoints=. Explicit user args (smearing=, bz_integration=) always win — mirrors the periodic_convergence_auto “explicit wins” contract.

  • New bz_integration parameter on run_periodic_job, run_rhf_periodic_scf, and run_rks_periodic_scf: None / "smearing" (temperature broadening, the default) or "gilat" (parameter-free Gilat-Raubenheimer net, T=0). Validated early; forwarded to the multi-k Ewald drivers. GDF / BIPOLE paths raise NotImplementedError for "gilat" until downstream wiring lands.

  • Tests: tests/test_kpoints_recommend.py (+10 cases) — smearing / bz_integration metadata population, dispatch-wire acceptance, bad-value rejection, gilat-on-GDF guard, recommended-KPoints passthrough through run_periodic_job.

Added: AUTO k-point recommender classifies from prior SCF result (Phase K8-B) (2026-06-19)

  • New scf_result parameter on KPoints.recommend() — passes a converged periodic SCF result (duck-typed mo_energies per-k eigenvalues). The indirect HOMO-LUMO gap is computed via converged_gap_hartree (reuses the existing function from periodic_convergence_auto) and classified by the same gap-cutoff thresholds. Resolution priority step 3 — fires only when neither band_gap nor is_metal is given, and before the classifier hook. No extra SCF cost.

  • Lazy-imports the gap helper to avoid a module-load cycle.

  • Tests: tests/test_kpoints_recommend.py (+6 cases) — insulator / metal / small-gap classification, explicit-hints-win, unusable-result fallback, multi-k indirect gap.

Added: AUTO k-point recommender pre-SCF classifier (Phase K8-C) (2026-06-19)

  • New built-in classifier="pre-scf" option on KPoints.recommend() — runs a cheap few-cycle Gamma-only GDF SCF with the user-supplied basis, extracts the HOMO-LUMO gap, and classifies by the same thresholds. Explicitly gated behind the string opt-in (spends an SCF — never the silent default). Requires basis=; warns and falls back to SAFE metal when not supplied. Lazy-imports the GDF driver to avoid coupling kpoints.py to the SCF stack.

  • Tests: tests/test_kpoints_recommend.py (+4 cases) — insulator classification, missing-basis warning, explicit-hints-win, bad-string guard.

Added: Methfessel-Paxton & Marzari-Vanderbilt smearing (M6/M7); AUTO defaults metals to MP (Phase K8-D) (2026-06-20)

  • Implemented methfessel_paxton_occupations_per_k (orders N=1,2) and marzari_vanderbilt_occupations_per_k (cold smearing) in vibeqc.smearing — same contract as the Fermi-Dirac module ((occ_per_k, mu, entropy), supports spin_degeneracy).

  • Wired into apply_smearing and apply_smearing_open_shell via flavor dispatch — no driver-side changes needed.

  • KPoints.recommend() now defaults metals and small-gap systems to "methfessel-paxton" (previously "fermi-dirac"). MP N=1 accelerates k-point convergence for metals by cancelling the leading integration error term with negative-occupation tails. marzari-vanderbilt is selectable via smearing_method=.

  • run_periodic_job now accepts all three smearing flavors.

  • Tests: tests/test_kpoints_recommend.py (+4 cases) — MP/MV construction + application + electron conservation, mp_order validation, AUTO-metal-defaults-to-MP.

Added: bundled ML k-spacing predictor for AUTO mode (Phase K8-F) (2026-06-20)

  • KPoints.recommend(predictor="ml") now auto-loads a bundled random-forest regressor when VIBEQC_ML_KPOINTS=1 is set (the model is never auto-loaded silently – the gate env var is mandatory). Without the gate, predictor="ml" raises NotImplementedError as before; ml_predictor=<callable> user-supplied models bypass the gate entirely.

  • Model: 100-tree scikit-learn RandomForestRegressor, 11 features (n_atoms, cell_volume, reciprocal lengths, band_gap, character, n_electrons, dim), trained on 600 synthetic points encoding the Choudhary-Tavazza 2020 scaling laws (metals -> denser, insulators -> coarser, larger cells -> coarser). R2=0.987, MAE=0.016 A-1, ensemble sigma=0.025 A-1. Shipped as python/vibeqc/data_library/k8f_predictor.pkl (1.4 MB).

  • python/vibeqc/data_library/ml_kpredictor.pyKSpacingPredictor class (lazy-loads scikit-learn), build_ml_predictor() factory, is_ml_predictor_enabled() gate, and a CLI training script for regenerating the .pkl.

  • Optional dependency: [ml] extra in pyproject.toml -> scikit-learn>=1.3. The model training data is derived from JARVIS-DFT heuristics (CC-BY-4.0, Choudhary et al., npj Comput. Mater. 6, 173, 2020).

  • Citations: choudhary_tavazza_2020 + jarvis_dft database entries with routes.software and routes.methods wiring.

  • Citation routing: uses_ml_kpredictor flag on KPoints (stamped by recommend(predictor="ml") when the bundled model fires) is read by the periodic runner and forwarded to CitationDatabase.assemble(), firing routes.methods.ml_kpoints -> choudhary_tavazza_2020 in the .bibtex / .references output.

  • Tests: tests/test_ml_kpredictor.py (30 tests) – feature extraction, env gate, prediction shape/range, metal-vs-insulator ordering, integration smoke through KPoints.recommend(), round-trip serialisation.

Added: DLPNO-(T1), the scaling-preserving exact (T) (tasks #20 + #21) (2026-06-19)

  • DLPNO-(T1) (vibeqc.dlpno.triples_local.t1_triples_correction): the off-diagonal localised occupied Fock is restored iteratively via Jacobi diagonalisation of f_oo; the eigenvalues replace f_dd in the triples denominators and the eigenvectors rotate the amplitudes from the LMO to the canonical occupied basis, removing the ~0.1 kcal/mol (T0) semicanonical error while keeping TNO-domain scaling (Guo, Riplinger et al., J. Chem. Phys. 148, 011101 (2018)). Full-domain (T1) == exact_triples_correction to 1.6e-17 Ha (the (T1) parity ratchet). Wire LocalCCSDOptions.triples_mode="t1".

  • Default tcut_pno tightened 1e-7 → 1e-8 — removes the CCSD over-recovery at the loose threshold (e.g. CH4 −0.61 → −0.07 kcal).

  • Default triples_mode changed "local""t1" — the ORCA-matching recipe is now the zero-knob default: MAE 0.17 kcal/mol vs canonical CCSD(T) on a 7-molecule def2-SVP set (ORCA 6.1: 0.16). The old 1e-7 / (T0) defaults remain available via explicit LocalCCSDOptions(tcut_pno=1e-7, triples_mode="local").

  • Citation database: guo_dlpno_t1_2018 entry + route under dlpno-ccsd(t).

  • Tests: TestT1 (4 ratchet tests) in tests/test_dlpno_triples_local.py; all 42 DLPNO tests green.

  • Docs + examples updated for the new defaults.

Added: periodic basis-gradient Pulay assembly (build-independent, Phase P0) (2026-06-18)

  • periodic_energy_gradient_fd — the build-independent per-k Pulay assembly for the eventual periodic (k-point) analytic basis-optimisation gradient, the CRYSTAL OPTBASIS target. The BZ sum of the molecular frozen-density expression with complex per-k matrices: dE/dη = Σ_k w_k Re[tr(P(k)·∂Hcore(k)) + ½ tr(P(k)·∂G(k)) tr(W(k)·∂S(k))], W(k)=½P(k)F(k)P(k). The integral source is an injected PeriodicIntegralProvider (the Bloch-summed integral derivatives / GDF-Ewald Coulomb derivative / per-k P(k),F(k) exposure are the later phases). Validated build-free against a multi-k mock (per-k Hermitian generalized eigenproblem total-energy FD): test_periodic_energy_gradient_assembly.py.

  • Scoping/design in docs/basisset_dev/PERIODIC_GRADIENT_DESIGN.md — reusable molecular pieces, the existing periodic nuclear-gradient machinery to build on, the hard parts (P1 Bloch-summed 1e derivatives, P2 GDF/Ewald Coulomb derivative, P3 periodic XC, P4 per-k matrix exposure), and the validation plan.

Added: periodic GDF analytic gradient — C++ 2c/3c lattice gradient kernels + DF J/K component gradients (2026-06-18)

  • cpp/include/vibeqc/aux_eri.hpp / cpp/src/aux_eri.cpp — new compute_2c_eri_lattice_gradient_weighted and compute_3c_eri_lattice_gradient_weighted: lattice-summed 2-centre metric and 3-centre ERI nuclear-derivative kernels (libint deriv_order=1 with outer loop over direct-lattice cells; image-centre derivatives mapped back onto unit-cell atoms).

  • cpp/src/bindings.cpp — Python bindings for both kernels.

  • python/vibeqc/periodic_gdf_gradient.py — new GDF analytic gradient module with compute_gdf_gradient_rhf_gamma. Ships the 1-electron (nuclear / kinetic / V_ne Pulay + overlap-Lagrangian) and 2-electron DF J/K gradient for the compcell GDF path at Γ. J and K gradient formulas validated against component-level FD to machine precision (1e-7 Ha/bohr on H2/STO-3G/12-bohr).

  • tests/test_periodic_gdf_gradient.py — 7 tests: C++ kernel shape + Newton’s-3rd-law sanity, DF J gradient vs FD (all 6 atom×dir components), DF K gradient vs FD (one component).

    Status: the C++ kernels and J/K component gradients are validated; the full SCF gradient (including orbital relaxation / overlap Lagrangian) carries a residual and is tracked for a follow-up increment (Increment 2 / 3 — rsgdf + multi-k generalisation). The gradient module is NOT wired into run_pbc_gdf_rhf or the runner yet.

Fixed: GDF gradient gauge mismatch — full SCF gradient now matches FD (2026-06-20)

  • Root cause: the C++ nuclear_lattice_gradient_contribution ignores EWALD_3D and uses DIRECT_TRUNCATED, while the SCF uses compute_nuclear_lattice_dispatch → FT-based Ewald V_ne. This caused a 0.618 Ha/bohr gauge mismatch in the V_ne gradient.

  • Fix: replaced nuclear_lattice_gradient_contribution with numerical FD of the SCF’s own FT-based Ewald V_ne (compute_v_ne_ewald_3d_ft_gamma). The full-SCF gradient residual dropped from ~0.6 to 0.003 Ha/bohr on H₂/STO-3G/12-bohr.

  • python/vibeqc/periodic_v_ne_gradient.py — partial analytic FT-based V_ne gradient (V_short via nuclear_erfc_lattice_gradient_contribution + V_long structure-factor derivative). AO-pair FT derivative tracked for the next increment.

  • python/vibeqc/periodic_gdf_gradient.py — added compute_gdf_gradient() convenience function (auto gauge/cache/Madelung setup). Gradient auto-computed after run_pbc_gdf_rhf convergence and stored on result.gradient.

  • tests/test_periodic_gdf_gradient.py — 8 tests including new end-to-end test_full_scf_gradient_vs_fd_h2 (analytic vs central-difference FD, 10 mHa/bohr tolerance). All green.

  • vibeqc.compute_gdf_gradient exported at the top level.

Extended: GPAW PW-limit reference — main-group benchmark set + cutoff-convergence (2026-06-18)

  • examples/regression/pw_limit_atomization/systems.py — added 19 non-magnetic main-group cubic compounds (MAIN_GROUP_SET): AgCl, AlAs, AlN, AlP, BaO, BaS, CaF₂, CaO, CdSe, CsCl, GaAs, GaN, GaP, InAs, KBr, KCl, Li₂O, SiC, ZnS. All geometries from the r2SCAN-optimised CRYSTAL .d12 inputs in references-cohesive/. System selector --systems now accepts main-group and nonmagnetic (validation + main-group). Hund magmoms added for Ag, Cd, Zn, In, Cs, Ba. BY_ID covers 28 compounds.

  • pw_reference.py — added check_element_convergence(): per-element free-atom cutoff-convergence audit (compares two highest cutoffs, reports Δ in kJ/mol).

  • run_pw_reference.py — added --converge-cutoff flag: runs element audit before atomization and warns if any element needs higher cutoff.

  • _run_external() now explicitly inherits os.environ so PYTHONPATH and threading controls reach the GPAW worker subprocess.

  • compare_pwref.py — three-way comparison script: reads GPAW JSON sidecar + r2scan.dat, produces GPAW-vs-pob-vs-VASP table with per-system outlier identification (pairwise agreement method).

  • PwSystem now supports non-cubic structures via optional c field and lattice_kwargs property; GPAW worker updated to pass c to ASE bulk builder. Placeholder TM prototype (TiO₂-r rutile) in TM_PROTOTYPES. Added Ti (Z=22) to HUND_MAGMOM (magmom=2, S=1) — marked experimental (3d TMs not in vibe-qc’s _GROUND_STATE_MULTIPLICITY).

  • Tests updated for expanded system set.

Added: UKS (open-shell DFT) analytic basis-optimisation energy gradient (2026-06-18)

  • energy_gradient_analytic_uks extends the analytic basis-parameter gradient to open-shell Kohn-Sham — the spin-polarised counterpart of energy_gradient_analytic_rks. Combines the UHF per-spin frozen-density Pulay assembly (W_σ = P_σ·F_σ·P_σ, G_σ = J(P_tot) a·K(P_σ)) with the polarised grid XC term Σ_g w_g [v_ρα ∂ρ_α + v_ρβ ∂ρ_β + 2 v_σαα ∇ρ_α·∂∇ρ_α + 2 v_σββ ∇ρ_β·∂∇ρ_β + v_σαβ(∇ρ_α·∂∇ρ_β + ∇ρ_β·∂∇ρ_α)]. Reaches the open-shell atoms (C, N, O, …) at DFT level — the combination the pob recipe needs. The spin-independent on-grid ∂φ_μ/∂η is shared with the RKS path (_param_dphi_on_grid). LDA/GGA/hybrid; meta-GGA / range-sep / double-hybrid raise. make_uks_native_objective_gradient (and make_native_objective_gradient(functional=…, open_shell=True)) wire it into BDIIS.

  • This completes the molecular analytic basis-optimisation gradient: RHF, UHF, RKS, UKS × exponents + coefficients, + κ/ln-condition penalty, all wired into optimize_bdiis. Validated vs full-energy FD on C ³P / N ⁴S (PBE, B3LYP), off-reference and valence params; end-to-end PBE-UKS BDIIS converges on the C ³P atom: test_energy_gradient_uks.py, test_bdiis_analytic_optimize.py.

Fixed: multi-k GDF Coulomb-J over-binding on tight cells (2026-06-18)

The multi-k GDF Coulomb (Hartree) build conjugated the diagonal density-fit tensor L(k,k) when assembling the BZ-summed fitted density ρ_P = Σ_k w_k tr(L(k,k)·D(k)) (run_krhf/kuhf_periodic_gdf._build_j_from_lpq). The conjugate is a no-op for the real cderi of vacuum-box / cubic cells — so every cubic insulator, including the H₂ (2,1,1) = −1.12013988 gate, was correct — but it mis-contracts the Coulomb for the genuinely complex diagonal cderi of tight cells with inter-cell (R≠0) overlap, over-binding E_J by Madelung scale. On a uniform H-chain metal (sto-3g, 14×14×2.8 bohr, (1,1,8) + Fermi smearing) the free energy was 0.54 Ha too low (−1.507 vs PySCF −0.963); a gapped (dimerized) chain in the same cell was 0.16 Ha too low — so it was neither metal- nor exxdiv-specific (exxdiv-K, the fractional-occupation energy expression, E_1e and E_nuc all matched PySCF to <0.3 mHa; the cderi itself matched PySCF’s to ~1e-6). Dropping the spurious .conj() reproduces PySCF get_j_kpts exactly: metal (1,1,8) free energy now −0.96320158 (= PySCF to 3e-9), H₂ (2,1,1) gate unchanged (2e-9). The strict-xfail test_metallic_chain_krhf_smear_matches_pyscf is flipped to a passing regression. (Coarse non-converged metal meshes that sample an exact zone-boundary degeneracy — e.g. (1,1,4) — can still pick a different stationary point than PySCF; that is a k-sampling artifact, not the J build, which is now machine-exact. Use a converged mesh for metals.)

Added: fractional-occupation (smeared) BIPOLE analytic gradient — corrected gauge (2026-06-18)

Finite-temperature / Fermi-Dirac-smeared KS analytic gradients now work in the corrected (Ewald-exchange-split) gauge for compute_bipole_gradient_rks and compute_bipole_gradient_uks, at Γ and multi-k. At the smeared SCF minimum the energy is the Mermin free energy A = E T·S, which is stationary w.r.t. the occupations, so dA/dR is the standard Hellmann–Feynman + Pulay expression with the fractional density D and the fractional energy-weighted density W = Σ_i f_i ε_i C_iC_i† — no occupation-response term, and (the corrected gauge being variational) no CPHF. FD-validated against compute_bipole_gradient_fd on genuinely-fractional smeared H₂/STO-3G (worst occupation ~0.11 from integer): RKS Γ 1.9e-8, multi-k RKS 4.2e-9 Ha/bohr. The legacy (use_exchange_ewald_split=False) gauge still rejects fractional occupations. Production forces remain the exact FD path.

Added: meta-GGA τ-Pulay in periodic XC gradient — RKS + UKS (2026-06-20)

The meta-GGA τ-Pulay atomic-gradient term — the analytic derivative of the kinetic-energy-density contribution to V_xc — is landed in the shared periodic XC gradient kernels xc_lattice_gradient_contribution and xc_lattice_gradient_contribution_uks (cpp/src/periodic_xc.cpp). The term is validated to 1e-9 against an independent Python frozen-potential reimplementation. SCAN and r2SCAN produce physically-reasonable gradients; TPSS and M06-L carry known residual blowup (~4–20 Ha/bohr on H₂) from pre-existing SCF garbage MO eigenvalues — the singular v_xc potentials contaminate the Fock matrix’s virtual subspace, and the MO-energy clipping (±10 Ha) in the energy-weighted-density builder only bounds, not cures, the problem. FD remains the production force path.

Still gated in the analytic preview: multi-k KS-CPHF — legacy-gauge-only (the corrected-gauge multi-k KS path is variational and needs no CPHF). Deferral ratified 2026-06-20 (no production need — FD is exact and the corrected gauge covers multi-k KS analytically; a full multi-k KS-CPHF would be the first complex k-space coupled-perturbed solver in the codebase, for a deprecated gauge). See HANDOVER_BIPOLE_GRADIENT.md Case 3 for the scoping.

Performance: restricted-triple loop in closed-shell (T) (2026-06-20)

  • Closed-shell perturbative (T) now sums occupied triples over i <= j <= k instead of all ordered (i, j, k) (cpp/src/ccsd.cpp compute_triples). The per-ordered-triple density is not permutation-symmetric in the classwise spin formulation (the aab pattern pins beta to the third slot, so an ordered triple carries only the “beta on k” configuration), but two sub-symmetries survive: the same-spin aaa block is fully symmetric in (i, j, k) and the aab block is symmetric under exchange of its two alpha slots. Folding the orderings analytically does 1 (aaa) + up to 3 (aab) intermediate builds per unordered triple instead of 2 per ordered triple.

  • ~2.6x faster (T) measured on C2H4/def2-SVP (no=8): the (T) step drops 1.21 s to 0.47 s, approaching 3x for larger occupied spaces. E(T) is reproduced bit-for-bit against the ordered loop. Closed-shell only; the spin-orbital UHF / ROHF kernel (cpp/src/uccsd.cpp) is unchanged.

  • Pinned by the existing always-on anchors (tests/test_ccsd_anchor.py, which carry occupied triples of every multiplicity class) and the PySCF cross-check (tests/test_ccsd.py).

Added: open-shell UCCSD / UCCSD(T) on a UHF reference (2026-06-17)

  • New spin-orbital unrestricted coupled-cluster kernel (cpp/src/uccsd.cpp, cpp/include/vibeqc/uccsd.hpp, bound as run_uccsd / vibeqc.cc.run_uccsd): DF-UCCSD and perturbative (T) on a converged UHF reference. It evaluates the Stanton-Gauss-Watts-Bartlett (1991) working equations directly over spin orbitals (the same equation set the closed-shell kernel spin-integrates), with integrals built from a block-diagonal spin-orbital DF B-tensor (DePrince-Sherrill 2013) and the Raghavachari (1989) (T) evaluated triple-by-triple. Frozen occupied cores supported (same chemical-core count per spin channel).

  • run_job(method="ccsd"/"ccsd(t)") now auto-routes by shell: closed-shell molecules use RHF + the spin-adapted kernel; open-shell molecules (multiplicity > 1) use UHF + the spin-orbital kernel. No new keyword; the result is attached as result.ccsd with the same CCSDResult fields. vq.run_uccsd is exported for the low-level path.

  • Validation (CLAUDE.md §7/§10). A new in-repo spin-orbital anchor vibeqc.dlpno._ccsd_ref.run_ref_uccsd (the open-shell sibling of the FCI-anchored run_ref_ccsd, reusing its residual + (T) code verbatim) matches PySCF cc.UCCSD/UCCSD(T) to < 1e-3 µHa on CH3/NH2/O2 (sto-3g + cc-pVDZ, exact integrals). The C++ kernel is pinned to that anchor (tests/test_uccsd_anchor.py, always-on) and cross-checked out of process vs PySCF DF-UCCSD/(T) (tests/test_uccsd.py, all-electron and frozen-core sub-µHa) and vs cached conventional ORCA 6.1 CCSD(T) on CH3/NH2.

  • Citations (tests/test_citations.py): the ccsd/ccsd(t) routes (Purvis-Bartlett 1982, SGWB 1991, Raghavachari 1989, DePrince-Sherrill 2013) fire for the open-shell path too and are now DOI-pinned mechanically.

  • Docs: docs/user_guide/ccsd.md + handovers/HANDOVER_CCSD.md document the open-shell surface, scope, and the ROHF-reference roadmap item.

Added: ROHF-reference CCSD / CCSD(T) (2026-06-19)

  • ROHF-CCSD wired through the spin-orbital kernel. The C++ UCCSD(T) kernel (cpp/src/uccsd.cpp) is reference-agnostic — it accepts per-spin MO coefficient and Fock matrices directly, with the occupied/virtual partition following from molecule.multiplicity. ROHF supplies a single set of spatial orbitals (C_alpha == C_beta) with distinct per-spin Fock matrices (F_alpha != F_beta, different exchange terms), so the same kernel computes ROHF-CCSD with no new equations.

  • New entry points: vibeqc.cc.run_rohf_ccsd(mol, basis, rohf_result) (Python) and run_uccsd_from_mos(mol, basis, C_a, C_b, F_a, F_b, e_hf) (C++, bound as _vibeqc_core.run_uccsd_from_mos). The low-level run_uccsd_from_mos accepts explicit MO-coefficient and Fock arrays for any non-UHF reference (ROHF, semicanonical orbitals from other sources).

  • run_job(method="ccsd(t)", ccsd_reference="rohf") selects an ROHF SCF + ROHF-CCSD(T) calculation — spin-pure restricted-open-shell reference, no contamination. The default (ccsd_reference="uhf") keeps the existing UHF + UCCSD(T) path.

  • Validation. Anchor tests (test_rohf_ccsd_anchor.py, always-on) pin the C++ kernel to the in-repo spin-orbital reference (run_ref_rohf_ccsd) at < 5 µHa on OH / NH2 / O2 (sto-3g). PySCF cross-check (test_rohf_ccsd.py, importorskip) confirms CCSD correlation to < 1 mHa (DF vs conventional + different ROHF implementations).

  • Bug fix. The ROHF density-fitting J/K path (vibeqc.rohf) carried a broken import (_vibeqc_core.DensityFitting — the class is in vibeqc.density_fitting), fixed en passant.

  • Docs: handovers/HANDOVER_CCSD.md and docs/user_guide/ccsd.md updated.

Fixed: honest errors for unwired SCF-accelerator / periodic-guess selectors (2026-06-18)

Tier-1 surface fixes from the v0.14 roadmap catch-up audit (docs/roadmap_catchup_audit_2026_06.md) — exposed selectors that raised deep, inconsistently, or (worst) silently did the wrong thing:

  • scf_accelerator="anderson" / ="broyden" no longer resolve silently to DIIS. They name real-space density mixers not yet wired into the accelerator path, so a user asking for Anderson/Broyden was quietly handed DIIS with no indication. scf_accelerator_from_string now raises a clear NotImplementedError — distinct from the ValueError typo path — naming the usable accelerators (DIIS / KDIIS / EDIIS / EDIIS_DIIS / ADIIS). The periodic AndersonMixer wiring tracked for v0.14 (roadmap D4a) is the follow-up that will make the name resolve to a real mixer.

  • The Python periodic-SCF driver seam (guess.initial_density_closed_shell / initial_densities_open_shell, which every Python periodic driver routes through) now raises a clean NotImplementedError when a molecular-only initial guess (SAP / PATOM / HUECKEL / MINAO) is selected, naming the supported periodic guesses (SAD / HCORE / READ; AUTO resolves to SAD) — dispatch-layer parity with how the unimplemented CoulombMethod values already signal. The lower-level C++ Γ drivers continue to raise the GuessEngine’s clear RuntimeError; since NotImplementedError subclasses RuntimeError, existing except RuntimeError handlers still catch both.

  • For completeness: the exposed-but-unimplemented CoulombMethod.SLAB_EWALD_2D / NEUTRALIZED_1D already raise a clear NotImplementedError at the dispatch edge (audit Tier-1 #2/#3) and are unchanged.

Fixed: vibe-view — open fails loudly on a busy port instead of opening the browser at a stale server (2026-06-19)

  • vibe-view open raced on an occupied port: the announce thread connected to whatever was already listening, printed “ready”, and opened the browser at that stale server, while the freshly launched server died with EADDRINUSE buried in the log. The user saw the previous session’s last rendered frame — visible but unresponsive to the mouse (“I can see the molecule but can’t rotate it”). open (and from-vq) now pre-check the port and abort with an actionable message (stop the other server, or pass a different --port) before announcing or launching the browser. Tests: tests/test_cli.py::TestPortCollision (3).

CI: vibe-view — install xauth so the viewer test job runs (2026-06-18)

  • The vibe-view-test job (pip-only, allow_failure, scoped to vibe-view/**) installed xvfb but not xauth, so xvfb-run -a failed with “xauth command not found” before pytest ran — the viewer suite had never actually executed in CI (which is why the kind-drift above reached main unnoticed). Added xauth to the job’s apt install.

Fixed: vibe-view — producer↔viewer QVF kind drift + forward-compat reader tests (2026-06-18)

  • The producer promoted six QVF kinds — volume.potential, volume.rdg, fermi_surface, phonon_bands, phonon_dos, equation_of_state — from reserved to implemented (with schema branches) without matching viewer renderers, so the viewer’s kind-drift guard broke and a file carrying one would have been mislabeled. The viewer now declares them in vibeview.kinds.DEFERRED_KINDS and surfaces them as “skipped, not yet rendered” (honest, distinct from “unsupported”); such a file opens and the section is skipped rather than rejected. Dedicated renderers for the six remain planned follow-up work.

  • tests/test_kind_drift.py now accounts for deferred kinds (a new unaccounted-for writer kind still trips the guard), and tests/test_reader_robustness.py derives a still-schema-unknown kind for its forward-compat checks instead of the now-promoted fermi_surface / phonon_bands.

Added: vibe-view — OSPRay path-traced rendering for publication output (2026-06-09)

  • vibe-view/src/vibeview/raytrace.py: a RaytraceEngine over VTK’s OSPRay path tracer (quality presets, PBR materials, environment lighting, optional depth-of-field, progress callback) with a render_high_quality(...) entry point and graceful fallback when the local VTK build has no OSPRay. Wired into the viewer’s render/export path; covered by vibe-view/tests/test_raytrace.py.

Added: vibe-view — interaction + export polish (2026-06-09)

  • Browser-native .qvf file picker (hidden <input type=file> + FileReader, reliable across Trame versions), screenshot and OBJ/glTF export saved next to the QVF and offered as a browser download, an MO show/hide toggle on the wavefunction card, a resizable bottom (chart) panel, app-bar tooltips, and camera/export state-initialisation fixes.

Fixed: GAPW DFT XC-augmentation double-counting (300 mHa over-binding) + GGA crash (2026-06-18)

  • The crash (any GGA/meta-GGA all-electron GAPW). run_periodic_rks_gapw(..., functional="pbe") raised ValueError: operands could not be broadcast together with shapes (n_r,n_ang) (n_r,n_ang,3) in GapwAugmentation.compute_xc_correction: the scalar GGA v_sigma was multiplied by ∇ρ without a broadcast axis. Fixed with v_sigma[..., None] in both the restricted and the polarised σ-flux terms, mirroring the GPW path (periodic_gapw_j.py:_project_vxc_to_ao).

  • The deeper bug (every GAPW DFT functional, LDA and GGA). Once the crash was cleared, GAPW DFT over-bound by ~300 mHa on He/STO-3G (LDA −301, PBE −318 mHa), independent of grid. Root cause: the smooth-grid XC energy/potential was generated from the full AO density while the per-atom augmentation subtracts a soft (pseudo) local term — so the soft core was double-counted inside each augmentation sphere (the smooth term already held ε_xc[full], then the augmentation added ε_xc[hard]−ε_xc[soft] on top). The soft/hard partition landed for the Hartree J in 1ec5d914 but the XC smooth term was never switched over.

  • The fix. The smooth-grid XC term is now generated from the soft density (new GapwJBuilder._smooth_xc_generation), exactly as build_J does for the Hartree term, so the per-atom XC augmentation telescopes to the all-electron XC; the potential is still projected onto the full AO basis. He/STO-3G now matches vibe-qc’s molecular all-electron reference to <1 mHa (LDA −0.78, PBE −0.94 — the residual is the Hartree augmentation’s pre-existing accuracy floor), is grid-stable to µHa, and the augmentation correctly rescues a coarse grid (He 24³: smooth GPW +150 mHa → GAPW +0.03 mHa). Regression tests: tests/test_periodic_gapw_augment.py::TestGapwDFTEnergy.

  • Gated (fail-closed) — not yet correct.

    • meta-GGA GAPW raises NotImplementedError: the augmentation has no kinetic-energy-density (τ) correction, so the core τ would be silently wrong. Use LDA/GGA with GAPW, or run meta-GGA on the smooth GPW route.

    • Open-shell GAPW DFT (run_periodic_uks_gapw with a functional) raises NotImplementedError: the polarised per-atom XC augmentation energy is unimplemented (the post-loop assembly would call the unpolarised libxc path on a spin-2 functional and crash). Use closed-shell run_periodic_rks_gapw, open-shell run_periodic_uhf_gapw (Hartree-Fock), or the smooth run_periodic_uks_gpw for open-shell DFT.

  • Known limitation (tracked, not fixed here). GAPW augmentation spheres overlap for bonded atoms, so the per-atom hard/soft correction double-counts the bond region: H2/STO-3G over-binds ~100 mHa at d=1.4 bohr, scaling with the overlap ratio 2·r_aug/d (→ ~28 mHa at d=6). This is a pre-existing multi-centre defect that also affects shipped GAPW-RHF on molecules (it lives in the Hartree augmentation, not the XC fix above) and needs a partition-of-unity across spheres. Captured as an xfail: tests/test_periodic_gapw_augment.py::test_gapw_molecule_matches_molecular_xfail.

Added: RKS (DFT) analytic basis-optimisation energy gradient (2026-06-18)

  • energy_gradient_analytic_rks extends the analytic basis-parameter gradient to closed-shell Kohn-Sham — LDA, GGA, and global hybrids. The Coulomb (and hybrid exact-exchange fraction) two-electron term + the Pulay −tr(W·∂S) term reuse the closed-form integral derivatives; the new piece is the explicit exchange-correlation term on the DFT grid, ∂E_xc/∂η = Σ_g w_g [v_ρ ∂ρ/∂η + 2 v_σ ∇ρ·∂∇ρ/∂η], with ∂φ_μ/∂η evaluated on the grid (prim-q collocation for coefficients; (∂lnN_c/∂α)·φ_μ + c̃_q·((2l+3)/4α r²)·G_q for exponents). The grid matches run_rks’s (build_grid(mol, opts.grid)), so the gradient is consistent with the SCF energy the optimiser minimises. Meta-GGA / range-separated / double-hybrid raise. make_rks_native_objective_gradient (and make_native_objective_gradient(functional=…)) wire it into BDIIS.

  • Validated vs full-energy FD on LDA/PBE/B3LYP × exp/coeff (Be), a diffuse exponent evaluated away from the reference basis, and H₂O (multi-element + O d-shell); end-to-end PBE BDIIS converges on Be: test_energy_gradient_rks.py, test_bdiis_analytic_optimize.py. The exponent ∂lnN_c/∂α uses the current (unpacked-at-x) exponent, not the reference — the reference-vs-current slip is only visible once the optimiser moves x off x0 (passing at x0, wrong for diffuse exponents during optimisation).

Fixed: ASE → PeriodicSystem lattice-vector convention (non-orthogonal cells) (2026-06-17)

  • ASE stores lattice vectors as the rows of atoms.cell; vibe-qc’s PeriodicSystem stores them as the columns of its lattice matrix (cpp/include/vibeqc/periodic.hpp:32). The ASE→engine boundary helpers passed the cell without transposing, so every non-orthogonal cell entered via ASE was silently transposed — wrong geometry, hence wrong k-paths, bands, forces and energies (VibeQCPeriodic, periodic_forces, and the GPW/GAPW calculators). Orthogonal/cubic cells — and the symmetric FCC/BCC primitive cells used in the test-suite — are transpose-invariant, which is why it went unnoticed (cell volume |det| is transpose-invariant too).

  • Fixed at all three one-way boundaries (atoms_to_periodic_system, VibeqcGPW/VibeqcGAPW._atoms_to_periodic_system) by transposing the cell to columns. The MACE relaxation path round-trips through atoms_to_periodic_system, so its input side (MACEModel._ase_atoms) was co-fixed in the same change to avoid double-flipping the relaxed cell (and so MACE sees the correct geometry on non-orthogonal cells). The fix is a no-op on orthogonal/symmetric cells, so existing periodic tests are unchanged.

  • Validated out-of-process against PySCF KRHF/GDF on a hexagonal cell (identical lattice_vectors(); agreement to 3.05 mHa on the same geometry in the dilute limit — examples/regression/parity_hexagonal_lattice_vs_pyscf.py, CLAUDE.md §10). Regression: tests/test_ase_periodic_lattice_convention.py (column convention at all three boundaries + ASE-vs-engine-native SCF self-parity + rotation invariance).

Fixed: GAPW molecular blow-up — one-centre density restricted to on-centre AOs (2026-06-17)

  • The bug (every multi-atom system). The GAPW per-atom augmentation built the one-centre density n_A¹ from the full AO set on atom A’s unbounded radial grid, so a neighbour’s AO — large on A’s far grid points and amplified by the rˡ multipole weight — detonated the ρ₀ compensator. LiH/STO-3G at d = 11 bohr gave E ≈ +9.8×10⁷ Ha (H |Q|max ≈ 2.3×10⁴); even bonded LiH was +44 Ha. (Isolated atoms — He/Ne/Be — were unaffected: a single centre has no off-centre AOs, which is why the foundational fix’s atom-only validation missed this.)

  • The fix (Lippert–Hutter–Parrinello 1999, Eq 53; on-centre n_A¹). The one-centre density that sources the augmentation is now restricted to atom-A-centred AOs (new on_center masks + _ao_atom_index), and the ρ₀ compensator multipoles are the on-centre (hard−soft) moments computed on the augmentation grid (replacing the unbounded compute_atom_local_multipoles). The ΔJ / ΔV_xc projection keeps the full AO set (the Fock is full-dimensional).

  • Validation (vs all-electron GDF, same gauge). Bonded LiH/STO-3G: E_GAPW = −7.854 vs E_GDF = −7.862 (+8 mHa); compensator |Q|max 2.3×10⁴ → 0.08. Isolated He/Ne/Be unchanged. Regression-pinned by tests/test_periodic_gapw_parity.py::test_gapw_molecular_no_blowup_lih.

  • Still experimental / known-limited. Partially-occupied core-bearing atoms (O/C/N/F and most molecules with virtual orbitals) still suffer a core aufbau failure: the augmented Fock pushes the tight 1s orbital above the valence (O: ε₁ₛ ≈ +68 Ha), so the SCF leaves the core unoccupied (O total −35 vs −74 Ha). Filled-(sub)shell atoms (He/Ne) and Li-core molecules are immune. GAPW stays opt-in behind GAPWExperimentalWarning; GDF/BIPOLE remain the production all-electron routes. (Tracked in HANDOVER_GAPW_PRODUCTION.md.)

Added: MSINDO analytic CIS (TDA) excited-state nuclear gradient (2026-06-17)

  • vibeqc.semiempirical.methods.msindo_cis.cis_gradient now computes the analytic CIS excited-state gradient (Ha/bohr) via the CPHF Z-vector relaxed density + the CIS two-particle density, contracting the per-pair integral derivatives — the same engine as the ground-state analytic gradient plus the orbital-relaxation response (A·Z = −L). Reproduces the validated finite-difference cis_gradient_fd to ~1e-6 Ha/bohr for singlet and triplet non-degenerate states (closed-shell; open-shell raises). The previous _experimental gate (the gradient mis-scaled the forces) is removed.

  • Root cause of the prior mis-scaling (milestone M2 / Phase 6): the CIS two-particle density is non-symmetric (its 2·D_mm·P_nn and 2·T_mn² terms), so the pair-block gradient contraction must sum the [L_j,K_i] and [K_i,L_j] elements (faithful kloop.f + lloop.f), not use the shortcut that is valid only for the symmetric ground-state density. The transition density is one-sided (cisgrad.f) and the Z-vector Lagrangian was re-derived directly in the MO basis (cphf_solver.f; the triplet drops the transition-density Coulomb terms per cislagrange_trip.f).

  • cis_gradient_fd remains the documented, robust fallback (and the state-tracking / conical-intersection paths keep using it, since the per-state analytic gradient is ill-defined at degeneracies). Citation: Foresman–Head-Gordon–Pople–Frisch, J. Phys. Chem. 96, 135 (1992), already wired via routes.uses_cis. Tests: tests/test_cis_gradient.py (analytic-vs-FD parity, S1/S2 × singlet/triplet × H₂O/NH₃/H₂CO/HF; translational invariance; open-shell guard).

Added: analytic contraction-coefficient energy gradient for basis optimisation (2026-06-17)

  • The analytic basis-optimisation energy gradient now covers contraction coefficients, not just exponents — energy_gradient_analytic / energy_gradient_analytic_uhf (and the BDIIS factories) handle field="coeff" analytically instead of raising. A contracted integral is linear in its primitive coefficients and the SCF energy is invariant to a shell’s contracted normalisation, so only the direct term survives: ∂I_μν/∂d_q = (c̃_q/d_q)·⟨G_q|Ô|φ_ν⟩. The primitive-q integral rows ⟨G_q|Ô|φ_ν⟩ are read off an augmented basis (the full basis plus one extra shell = primitive q alone, coefficients_pre_normalized=True), so no new integral kernel or C++ is needed; c̃_q/d_q is the frozen-renormalisation chain factor. Validated vs full-energy FD (relative step — tiny core coefficients need it) on H₂ and H₂O incl. O’s d shell: test_energy_gradient_analytic.py, test_bdiis_analytic_optimize.py.

  • SP coeff_s/coeff_p remain FD-only (documented; raise in the analytic path).

Added: UHF (open-shell) analytic energy gradient for basis optimisation (2026-06-17)

  • energy_gradient_analytic_uhf extends the analytic basis-parameter energy gradient to unrestricted HF via the spin-resolved frozen-density Pulay assembly dE/dη = tr(P_tot·∂Hcore) + ½[tr(P_α·∂G_α) + tr(P_β·∂G_β)] tr((W_α+W_β)·∂S), with G_σ = J(P_tot) K(P_σ) and W_σ = P_σ·F_σ·P_σ (occupation 1 per spin). The four closed-form integral exponent derivatives are spin-independent and reused unchanged; only the density/Fock contraction differs. This reaches the open-shell atoms (C, N, O, …) the pob recipe needs. Validated to ≤1e-7 Ha vs full-energy UHF FD on C (³P, s/p/d), O (³P), N (⁴S): tests/basisset_dev/test_energy_gradient_uhf.py.

  • make_uhf_native_objective_gradient (and the shared make_native_objective_gradient with an open_shell= switch) gives the in-process UHF (objective, gradient) pair for optimize_bdiis; end-to-end on the C ³P atom the analytic-gradient loop converges to a stationary, lower-energy basis (test_bdiis_analytic_optimize.py). build_j/build_k are split out of build_g for the per-spin Coulomb/exchange contractions.

Added: MSINDO CCM analytic nuclear gradient (1-D / 2-D / 3-D, closed-shell) (2026-06-17)

  • vibeqc.semiempirical.methods.msindo_ccm_gradient_analytic.ccm_gradient_analytic(...) — the analytic nuclear gradient (Ha/bohr) of the periodic Cyclic Cluster Model total energy, completing the MSINDO analytic-gradient programme (Phase 5; the molecular analytic gradient, Phase 4, already shipped). The Wigner-Seitz topology is held fixed at the reference geometry (matching ccm_gradient_fd); pair derivatives reuse msindo_pair_deriv; the analytic Madelung/Ewald gradient covers 1-D (finite ±T,±2T point-charge lattice sum), 2-D (Parry/Heyes slab Ewald: reciprocal + K=0 + direct) and 3-D (Ewald bulk). Closed-shell (RHF) only — an open-shell cell raises NotImplementedError.

  • The Madelung gradient uses a direct, per-neighbour assembly — the exact fixed-charge derivative of the CCM energy — in place of the MSINDO Fortran’s central-atom-only form (dedmadelsum.f: EDX(IATOM) += DMADEL·q_I), which is exact only for mirror-symmetric Wigner-Seitz cells. For asymmetric ionic Madelung cells (an H-F chain with weight-½ boundary atoms; a distorted MgO(100) slab) the MSINDO oracle’s own analytic gradient deviates from the finite difference of its own energy by up to ~2e-2 Ha/bohr, whereas vibe-qc’s gradient stays exact (matches the FD of its energy to ~1e-7 Ha/bohr).

  • C++-backed (added 2026-06-18): ccm_gradient_analytic is also in the C++ engine (m_indo.ccm_gradient_analytic, ccm_engine.hpp) — byte-parity with the Python path to ~1e-10 Ha/bohr across 1-D/2-D/3-D × {NOEWALD, Madelung}, reusing the molecular pair-derivative engine (pair_blocks_deriv) and the same direct-assembly Madelung gradient (_dmadkonst_2d/_dmadkonst_3d).

  • Tests: analytic == fixed-WS finite difference to <1e-6 Ha/bohr for 1-D/2-D/3-D × {NOEWALD, Madelung}; analytic == MSINDO oracle (CARTOPT ANALY GRADONLY, frozen in ccm_reference.json) to <2e-5 Ha/bohr on symmetric cells (H-F NOEWALD chain, MgO(100) Madelung slab, distorted MgO Madelung bulk); closed-shell-only and invalid-cluster guards (tests/test_msindo_ccm_gradient.py); and C++↔Python parity for every dim × Madelung in tests/test_msindo_cpp.py.

Performance: cache the GPW/GAPW grid collocation across SCF iterations (PBC-audit E3) (2026-06-17)

  • The GPW/GAPW Hartree-J + XC build collocates the AO basis on the FFT grid (χ_μ(r_g) = Σ_R χ_μ^mol(r_g R), 27 periodic-image evaluate_ao calls over the whole grid). Profiling shows this χ build is 95–100 % of a single collocate/project — the density contraction and the FFT-Poisson solve are ~1 %. χ depends only on (basis, grid), never on the density, yet the per-iteration XC path rebuilt it every iteration (RKS-GGA ~3×, UKS ~4×).

  • New GpwCollocationCache (the AO-on-grid table χ + the reciprocal-space G-mesh) built once per SCF via build_gpw_collocation_cache(basis, grid) and contracted per iteration — mirroring the EWALD E2 analytic-FT J cache (ewald_composed.EwaldJFTGammaCache) and the in-builder GpwJBuilder._chi_cache. Threaded through collocate_density_on_grid, project_potential_to_ao, the XC projectors, and all GPW/GAPW SCF drivers (Γ + multi-k RHF/RKS, UHF/UKS, the GAPW GapwJBuilder and its four drivers). GapwJBuilder caches both the full-basis and the soft-basis χ.

  • Pure caching, no physics change. The cached χ is bit-identical to the inline build (np.array_equal); cache=None everywhere falls back to the pre-cache path. Pure-DFT energies/densities are bit-identical before↔after; HF/hybrid agree to within the pre-existing ~1e-15 run-to-run floor of the non-associative parallel exact-exchange reduction (unchanged by this work).

  • Measured per-SCF speedups (STO-3G H-chains): GPW RKS-PBE 3.6×, GAPW RKS-LDA 3.0×, UKS-PBE 19× — scaling with how χ-dominated (vs O(n⁴)-exchange-bound) the route is. File-level on the gate’s slow GPW/GAPW targets is 1.1–2.1× (gapw_open_shell 2.1×, gapw_gradient 1.5×, ase_periodic_gpw 1.4×; the HF-only gapw_hessian is unaffected — HF skips the XC-collocation path). This reduces the per-file CPU that drove the v0.13.0 contention false reds, but these HF/multi-k/FD-overhead-dominated files do not clear the OMP=2 fast-lane bound, so they stay in known_slow pending the gate chat’s --jobs 6 re-verification (deliberately not de-listed here — see 5453f9a9).

Validated: megacell periodic-MP2 thermodynamic limit == PySCF.pbc KMP2 (k-mesh → ∞) (2026-06-19)

  • The headline cross-check for the megacell periodic-correlation route (python/vibeqc/periodic_megacell_mp2.py): the per-cell MP2 correlation energy from megacell_mp2_tdl (megacell → ∞, an open-boundary supercell extrapolation) agrees with periodic PySCF.pbc KMP2 (k-mesh → ∞, a BvK torus) — two independent routes, two independent codes, one per-cell thermodynamic limit. On the H₂/STO-3G chain (20-bohr transverse vacuum, 6-bohr spacing) the megacell TDL bulk is −0.013275 Ha/cell, the KMP2 k-mesh series (nk=2,4,6,8 along z) converges to −0.013322 Ha/cell — agreement ~0.05 mHa (< 0.4 % of e_corr), an honest combined finite-size truncation error (megacell ~1/N surface residual vs KMP2 finite-k-mesh convergence, plus ~sub-µHa inter-chain coupling through the 20-bohr transverse vacuum at dimension=3). tests/test_periodic_megacell_pyscf_parity.py::test_megacell_tdl_matches_pyscf_kmp2_convergence (gated importorskip("pyscf"), §10-clean).

  • runner_pyscf.run_periodic_mp2 — the §10-clean oracle: a periodic MP2 (pyscf.pbc.mp.KMP2, or molecular MP2 at Γ) post-step added to the out-of-process PySCF runner (examples/regression/core/runner_pyscf.py), with low-dimensional PBC support (dimension / low_dim_ft_type). PySCF runs in a separate interpreter — never imported inside python/vibeqc/. The TDL cross-check uses dimension=3 with 20-bohr transverse vacuum (the inter-chain Coulomb interaction across 20 bohr is << µHa for an insulator like H₂/STO-3G); dimension=1 is available but requires the periodic axis as the first lattice vector (PySCF convention). The PySCF KMP2 reference series is checked live by the test.

  • Citation DB (CLAUDE.md §8): added the periodic DLPNO-MP2 method papers nejad_periodic_dlpno_pbc_2025 (Paper I, BvK) + zhu_periodic_dlpno_megacell_2025 (Paper II, megacell — Zhu first author), routed under periodic-mp2 / periodic-ri-mp2 / periodic-ccsd / periodic-ccsd(t). DOI correction: the DOIs in the handover were wrong (resolved to unrelated AIP conference papers); the correct ones (10.1063/5.0290816, 10.1063/5.0290819) were verified against Crossref + PubMed.

Added: toroidal megacell translational pair-family decomposition — Stage 5b (2026-06-19)

  • vibeqc.periodic_toroidal_mp2.toroidal_mp2_gamma_pairsToroidalPairResult: the toroidal megacell MP2 with translational pair-family decomposition. Localises the supercell occupied orbitals (Wannier functions via Pipek-Mezey or Boys), assigns each to a cell tile via its position centroid, groups occupied-occupied pairs (i,j) by their relative cell vector L, and computes per-L MP2 correlation contributions. The total sum(e_corr_by_L) agrees with the all-pairs 5a result within 1e-14 Ha — the same terms summed in a different partition order. The per-L breakdown exposes the distance decay of the correlation energy — infrastructure for the DLPNO pair screening at Stage 5c.

  • Helper _assign_wannier_cells maps Wannier centroids to cell tiles via fractional coordinates in the unit-cell lattice.

  • Tests (tests/test_periodic_toroidal_mp2.py, 3 new, 7 total): 1e-14 Ha agreement gate at (1,1,2)+(1,1,3), Wannier cell assignment (one per cell for H₂/STO-3G), and distance-decay sanity.

Added: toroidal megacell DLPNO-MP2 local approximation — Stage 5c (2026-06-19)

  • vibeqc.periodic_toroidal_mp2.toroidal_dlpno_mp2ToroidalDLPNOResult: runs the molecular DLPNO-MP2 driver on the toroidal megacell, using the periodic Γ GDF HF reference (Ewald/Madelung-stabilised orbital energies) and the periodic Γ DF 3-index tensor (adapted to the molecular DensityFitting interface via _PeriodicDFAdapter). The periodic AO overlap replaces the molecular one so that projections and orthogonalizations are consistent with the periodic MOs. This is the toroidal counterpart of the open-megacell megacell_run_job(method="dlpno-mp2") — the HF reference is periodic, not molecular, so the per-cell energy includes Ewald stabilisation and converges to the TDL with supercell size.

  • Exactness limit (full domains, no PNO truncation, no pair screening): the DLPNO-MP2 reproduces the canonical 5a toroidal MP2 to machine precision (Δ < 1e-11 Ha at nrep=(1,1,3); the M2 gate, now periodic). Default DLPNO thresholds give sub-μHa agreement for small supercells (~50 μHa at 6 occupied orbitals).

  • Tests (3 new, 10 total): exactness gate at (1,1,2)+(1,1,3), default convergence, and energy decomposition sanity.

Added: toroidal megacell DLPNO-CCSD and DLPNO-CCSD(T) — Stage 5c extended (2026-06-19)

  • toroidal_dlpno_ccsd + toroidal_dlpno_ccsd_t -> ToroidalDLPNOCCSDResult: run the molecular reduced-scaling DLPNO-CCSD solver (M3c) and DLPNO-(T1) triples on the toroidal megacell, using the same periodic-adaptation pattern as 5c MP2 (PeriodicDFAdapter + periodic-overlap patching). CCSD recovers ~30% more correlation than MP2; (T) is negligible for H2 as expected.

  • Tests (2 new, 12 total): CCSD convergence + MP2 ordering, (T) smallness + bookkeeping.

Added: toroidal megacell pair screening classifier — Stage 5c infrastructure (2026-06-19)

  • vibeqc.periodic_toroidal_mp2.classify_toroidal_pairsToroidalPairClassification: dipole-approximation pair screening by relative cell vector L for the toroidal megacell. Enumerates all unique L families, estimates pair energy via −1/R⁶ dipole asymptotics, and classifies each as strong/weak/distant based on energy and distance thresholds. The r_close guard (default 8.0 Bohr) forces nearby cells to always be strong — the bare dipole form underestimates near-field correlation by orders of magnitude. Distant L families are candidates for a future screening-aware DLPNO driver to skip, which would reduce the pair count from O(N²) to O(N·N_strong); the current toroidal DLPNO wrappers do not consume this classifier.

  • Tests (tests/test_periodic_toroidal_mp2.py, 4 new, 16 total): basics (counts + distance ordering), r_close default (nearest-neighbour strong), all-strong limit (zero pair-energy threshold with an effectively unbounded distance guard), all-distant-at-tight-r_close limit.

Added: analytic SCF-energy gradient for basis optimisation + native BDIIS loop (2026-06-17)

  • Closed-form AO-integral exponent derivatives (cpp/src/basis_param_gradient.cpp, bound as overlap_/kinetic_/nuclear_/eri_exponent_derivative): ∂S, ∂T, ∂V and ∂(μν|λσ) w.r.t. a primitive Gaussian exponent, via the Hellmann–Feynman r²-weighted bra (∂/∂α e^{−αr²} = −r²e^{−αr²}) represented on an l+2 cartesian shell. The cart→(l+2) transform is measured empirically from do_enforce=false overlaps (T = ⟨aux|cart_l⟩·⟨cart_l|cart_l⟩⁻¹) rather than assuming libint’s normalisation convention, so it is correct for s/p/d alike. Validated to ≤1e-9 vs finite differences on H (s,p) and C (s,p,d): tests/basisset_dev/test_{overlap,kinetic_nuclear,eri}_exponent_derivative.py.

  • energy_gradient_analytic assembles those into the full RHF energy gradient via the frozen-density Pulay identity dE/dη = tr(P·∂Hcore) + ½ tr(P·∂G(P)) tr(W·∂S) (W = ½·P·F·P from the converged Fock, no MO coefficients), mapping each optimiser parameter (element, shell, primitive) to every libint shell it drives — one per atom of the element, both sides of an SP shell — with the LOG/LINEAR transform chain rule. Matches full-energy central FD and the Phase-0 integral-FD assembly to ≤5e-9 Ha on Be (single atom), H₂ (multi-atom summing) and H₂O (multi-element, O d-shell): test_energy_gradient_analytic.py.

  • make_rhf_native_objective_gradient returns a consistent in-process RHF (objective, gradient) pair so optimize_bdiis(obj, x0, grad=…) runs a fully analytic, fully in-process loop — one SCF per gradient instead of the FD path’s 2·N_param. End-to-end on H₂/pob-TZVP it converges to the same minimum as the FD-gradient driver (|ΔE| = 1.7e-10 Ha): test_bdiis_analytic_optimize.py. Exponent-only; coefficient parameters fall back to the FD gradient.

Fixed: GAPW all-electron soft/hard partition — the smooth grid no longer aliases the core (2026-06-17)

  • The bug (catastrophic, all core atoms). The GAPW Hartree-J built its smooth FFT-grid term from the full all-electron density (GapwJBuilder.build_JGpwJBuilder(full_basis)), so a tight core Gaussian aliased on the coarse grid: Ne/STO-3G/24³ collocated to 56 e⁻ (should be 10) → a +5900 Ha spurious smooth Hartree. GAPW totals sat thousands of Ha above the analytic all-electron reference and were often worse than un-augmented GPW — the augmentation, formulated for a soft smooth term, cannot rescue a full-density one. The earlier audit’s “GPW-AUDIT-007 spherical-Poisson” framing understated this: the partition itself was wrong for every core-bearing system.

  • The fix (Lippert–Hutter–Parrinello 1999; Krack–Parrinello 2000). The smooth term is now generated from the softened density ρ̃ (tight primitives pruned, grid-representable) plus a (hard−soft) multipole ρ₀ compensator that restores the pruned core’s charge/multipoles on the grid, projected onto the full AO basis; the core lives entirely in the per-atom augmentation. The Hartree energy uses the correct density-functional form ½∫(ρ̃+ρ₀)V[ρ̃+ρ₀] + Σ_a augmentation (GapwJBuilder.gapw_hartree_energy), not ½ tr(D·J) (which contracted the full ρ against the soft potential — Ha-scale wrong for a deep core).

  • Validation (vs the analytic all-electron GDF route, same gauge). On a 24³ grid: He −0.6 mHa (was +79 mHa), Ne +0.34 Ha (was +6065 Ha), H₂O 47 Ha → 9 Ha. Spherical core atoms now reach all-electron accuracy; the ρ₀ compensator restores the charge exactly (He: soft 1.674 + comp 0.326 = 2.000 e⁻; Ne: 7.43 + 2.57 = 10.0 e⁻). Regression-pinned in tests/test_periodic_gapw_parity.py (He < 5 mHa, Ne < 0.5 Ha on a fine grid).

  • solve_poisson_radial is now multipole-capable (real-spherical-harmonic projection + per-l radial Green’s function), reducing bit-for-bit to the monopole for a spherical density. It defaults to lmax=0 (the production augmentation setting): the aspherical augmentation (an O in H₂O, an open p shell) needs the in-sphere hard/soft l>0 cancellation reworked — the ρ₀ windowing/compensator are not yet l>0-consistent (enabling multipoles regressed H₂O) — which is the next milestone (the real GPW-AUDIT-007).

  • Still experimental / known-limited. Accuracy is grid-convergent (coarse grids stay mHa–Ha off) and molecular/aspherical totals are not yet at parity; the GAPW route stays opt-in (jk_method='gapw'), behind GAPWExperimentalWarning. The GDF / BIPOLE routes remain the production all-electron path. Citations: LHP 1999 + Krack–Parrinello 2000 added to the gapw route in database.toml.

Added: GPAW plane-wave-limit atomization reference generator (out-of-process) (2026-06-17)

  • New examples/regression/pw_limit_atomization/ — a §10-clean, out-of-process GPAW driver that generates plane-wave-limit atomization energies as an open-source reference (a VASP alternative) for the revised-pob basis-set work (Peintinger & Bredow, Approaching the plane wave limit with Gaussian basis sets). GPAW (GPLv3) is executed only in a separate interpreter via subprocess; it is never imported into python/vibeqc/ (CLAUDE.md §1/§10). Computes the per-formula-unit atomization E_at = ΣE(free atom) E(solid) with matched solid/atom plane-wave cutoffs (so the PAW reference cancels), spin-correct Hund free-atom references, a cutoff sweep and an E_cut^(-3/2) PW-limit extrapolation; the CLI emits Markdown + a JSON provenance sidecar.

  • Validated end-to-end on LiF/r2SCAN: GPAW PW-limit atomization = 840 kJ/mol per formula unit (4.36 eV/atom), reproducing the published SCAN/PBE cohesive energies (Mejía-Rodríguez & Trickey 2018; Csonka 2009) — an independent check of the density-collocation → FFT-Poisson → atomization machinery.

  • Free atoms use GPAW’s own blessed direct-minimisation recipe (its cohesive-energy tutorial: etdm-fdpw + no mixing + fixed-uniform occupations

    • conditional Hund [max-spin for open shells, off for closed shells — hund=True stalls the closed-shell meta-GGA direct minimisation] + symmetry off), which reproduces the prior SCF Li/F references to <0.02 kJ/mol and is far more robust; atom energies are cached per (element, functional, cutoff, box) so a full-set run computes each element once.

  • Validated on both an open-shell-atom system (LiF: GPAW r2SCAN 840 kJ/mol) and a closed-shell-atom system (MgO: GPAW r2SCAN 1009.5 vs VASP@900 1012.0, lit SCAN ~1011) — GPAW tracks the published SCAN values within ~2.5 kJ/mol and matches whichever of pob/VASP is correct system-by-system. Stoichiometry is summed per formula unit (correct for non-1:1 compounds such as Al₂O₃/Li₂O).

  • Tests: tests/test_pw_limit_atomization.py — 6 fast pure-Python (extrapolation, systems-data integrity, functional map) + 1 GPAW-gated end-to-end smoke test.

Added: Γ-point open-shell GDF Fermi-Dirac smearing (run_pbc_gdf_uhf/uks) (2026-06-17)

  • The Γ-only open-shell GDF drivers run_pbc_gdf_uhf / run_pbc_gdf_uks now honor smearing_temperature > 0: per-spin Fermi-Dirac occupations with independent global chemical potentials μ_α, μ_β over the degenerate Γ levels (the same vibeqc.smearing.apply_smearing_open_shell core as the multi-k drivers, CP2K/VASP/QE convention). PBCGDFUHFResult / PBCGDFUKSResult gain smearing_temperature, fermi_level_alpha/beta, entropy, free_energy (Mermin A = E T·(S_α+S_β)), and per-spin occupations_alpha/beta. This completes the open-shell GDF family smearing (Γ + multi-k).

  • Smearing-consistent ⟨S²⟩ at Γ: the fractional-occupation (ensemble-UHF) ⟨S²⟩ generalisation is used when smeared, reusing the corrected _multi_k_s_squared; integer/T=0 filling stays the exact value.

  • Behavior-neutral at T = 0: the integer-Aufbau path is byte-for-byte unchanged — run_pbc_gdf_u{hf,ks} T=0 gates (UHF(M=1)≡RHF, H-doublet / UKS-PBE PySCF parity) stay green to machine precision.

  • Reachable from run_periodic_job: open-shell GDF smearing now accepts kpoints=None (Γ, routed to run_pbc_gdf_{uhf,uks}) in addition to a k-mesh; only the Ewald-3D / BIPOLE open-shell drivers still reject smearing.

  • Validated (tests/test_pbc_gdf_compcell.py): a boron 2p¹ degenerate shell fractionally occupied (~1/3 each) at Γ with per-spin particle conservation, entropy > 0, A < E, and the exact doublet ⟨S²⟩ = 0.75 (UHF + UKS-PBE), directly and through run_periodic_job.

Added: multi-k corrected-gauge BIPOLE analytic gradient — all four drivers (2026-06-17)

  • compute_bipole_gradient_rhf and compute_bipole_gradient_uhf now support corrected-gauge (Ewald-exchange-split) results at multi-k — previously they refused them (only the Γ corrected gauge was wired). Pass kmesh= (the BlochKMesh the SCF used). The multi-k corrected gauge is a standard variational HF gradient (full Bloch density → no Bloch-CPHF): the legacy assembly with the exchange block swapped to K_SR(erfc) + per-q reciprocal K_LR + per-k Madelung S(k)D(k)S(k), the full jellium, and every real-space-density term fed the per-k density inverse-Bloch-folded onto the gradient template (density_set_from_k_matrices) rather than result.density (whose 2×-cutoff cell list over-counts the lattice-summed 2e gradient). New _k_long_range_ewald_gradient_multi_k (per-q exchange, the multi-k analogue of _k_long_range_ewald_gradient) and _madelung_ewald_gradient_multi_k. Reduces EXACTLY to the Γ corrected core at n_k=1 (RHF 1.8e-18) and matches compute_bipole_gradient_fd to 6.3e-8 Ha/bohr at n_k=2 (asymmetric BeH₂ [2,1,1]/STO-3G). Regression test_multik_rhf_corrected_gauge_matches_fd (@slow).

  • UHF shares the same core, which auto-detects the open-shell result and builds a spin-resolved exchange 2·Σ_σ ∂E_x[P_σ] (per-spin K_SR/K_LR/ Madelung) with the open energy-weighted W; the total density drives the 1e/Coulomb/jellium/J^LR terms. FD 1.3e-8 on an asymmetric BeH doublet [2,1,1]/STO-3G (regression test_multik_uhf_corrected_gauge_matches_fd, @slow). The closed-shell path is byte-for-byte the prior RHF assembly.

  • RKS/UKS route their multi-k corrected-gauge case to the same core (exchange scaled by the functional’s HF fraction; the core auto-detects open shell) and add the multi-k XC Pulay (xc_lattice_gradient_contribution[_uks] on the Bloch-folded density) + the grid-motion correction — mirroring the Γ RKS/UKS drivers. FD-clean: RKS svwn 4.7e-8; UKS svwn (spin-polarized BeH doublet) 8.0e-6 (DFT-grid floor). Regressions test_multik_{rks,uks}_corrected_gauge_matches_fd (@slow). All four multi-k corrected-gauge drivers (RHF/UHF/RKS/UKS) are now FD-validated; compute_bipole_gradient_fd remains the documented production force path pending broader certification.

Added: periodic ROHF — multi-k (full BZ sampling), EWALD_3D (2026-06-17)

  • vibeqc.run_rohf_periodic_multi_k_ewald3d(system, basis, kmesh, options)PeriodicROHFMultiKEwaldResult: spin-pure restricted-open-shell HF with full k-point sampling — the production sibling of the Γ-only periodic ROHF driver. Reuses the build-validated multi-k UHF two-electron machinery (_build_uhf_fock_blocks_ewald3d, the analytic-FT Hartree J + real-space K, the inverse-Bloch density fold, the Madelung fix, per-cell Ewald e_nuc) verbatim; the only change is the per-k orbital update via Roothaan’s single effective Fock + the open-shell occupation. Occupations are equal at every k (gapped-insulator setup; no smearing — metals are a follow-up).

  • Fix: vibeqc.rohf.roothaan_effective_fock now preserves the input dtype instead of casting to float. At a non-Γ k-point the per-spin Fock / density / overlap are complex-Hermitian; the old float cast silently discarded the imaginary part. Verified: the complex-Hermitian coupling reproduces Coulson’s block structure to 1e-16, and the real (molecular / Γ) path is unchanged.

  • Tests (tests/test_periodic_rohf_multi_k_ewald.py): closed-shell == multi-k RHF; a [1,1,1] mesh reproduces the Γ-only ROHF driver; spin-pure doublet. Build-box / PySCF.pbc KROHF parity per §7.

Added: periodic ROHF — Γ-point, EWALD_3D (2026-06-17)

  • vibeqc.run_rohf_periodic_gamma_ewald3d(system, basis, options)PeriodicROHFEwaldResult: spin-pure restricted-open-shell HF for periodic systems at the Γ point. The periodic counterpart of run_rohf, and the open-shell sibling of run_rhf_periodic_gamma_ewald3d / run_uhf_periodic_gamma_ewald3d.

  • Reuses the build-validated periodic two-electron machinery from the Γ UHF driver verbatim — the Ewald-3D Hartree J, the real-space exchange K, the Madelung self-image correction, the per-cell Ewald nuclear repulsion, and the EWALD_3D gauge forcing (CLAUDE.md §7). The only change from UHF is the orbital update: the per-spin Fock matrices are combined into Roothaan’s single effective Fock and diagonalised once, with the standard open-shell occupation rule (vibeqc.rohf._roothaan_occupations) — the same code the molecular ROHF driver uses.

  • The SCF-loop wiring was cross-checked against the molecular run_roothaan_scf on synthetic integrals (identical energy + density), so the periodic transcription is correct modulo the (reused, validated) periodic J/K.

  • Tests (tests/test_periodic_rohf_ewald.py): closed-shell H₂ box == periodic RHF; one-electron doublet == periodic UHF with ⟨S²⟩ = 0.75; Li-atom box spin-pure (⟨S²⟩ = 0.75 exact) and at/above UHF; bad-multiplicity guard.

  • Scope: Γ-point + EWALD_3D + HF. Multi-k, the GDF / BIPOLE / GPW JK routes, periodic ROKS, and run_periodic_job(method="ROHF") integration are follow-ups (HANDOVER_ROHF.md M7).

[v0.13.1] — 2026-06-17

Patch on Wisesa’s Fox: a periodic ASE fix plus test-gate reliability hardening, cherry-picked from main.

Fixed

  • Γ-only periodic HF through the ASE calculator now routes through the Ewald-3D exchange path — corrects the exchange treatment for ASE-driven Γ-point periodic Hartree-Fock (82295bd9).

Changed — test-gate reliability

  • gate_verdict.py --reverify: a new (unlisted) non-PASS file is re-run once in isolation before it turns the verdict red, so transient contention on a shared box cannot fake a regression (d57a04cd; +10 unit tests in tests/test_gate_verdict_reverify.py).

  • known_slow baseline restored 4 → 8: the GPW/GAPW grid-collocation files wrongly de-listed when the EWALD analytic-FT Hartree-J cache (E2) landed are re-listed — E2 never sped GPW collocation (that is the separate E3 work) (5453f9a9).

  • Refreshed three stale v0.5.0-era comparison-example banners (ad935310).

[v0.13.0] — 2026-06-17 — Wisesa’s Fox

AI-generated Wisesa's Fox codename artwork for vibe-qc v0.13.0

Added: MSINDO open-shell (UHF) for the heavy d/p-block trajectory elements (2026-06-17)

  • Open-shell UHF now reaches the MSINDO reference SCF stationary point for the heavy d/p-block elements (_MSINDO_TRAJECTORY_SCF; the 4d Tc–Pd and 5p Sb–Xe set, and by extension Nb/Mo). As with the closed-shell heavy elements, the production DIIS path jumps SCF basins, so run_msindo(..., multiplicity=M) containing any such element now dispatches to the MSINDO-faithful extended-Hückel guess + WICHT density damping + energy-only convergence UHF SCF (_scf_uhf_msindo / scf_uhf_msindo_driver, porting huckop.f / scfopn.f / eneopn.f / wicht.f); every other element keeps the DIIS UHF path, which preserves the bit-for-bit UHF(M=1)≡RHF invariant there.

  • Fixed the open-shell one-centre d-block Fock: the closed-shell EINZI hybrid d terms applied per spin are wrong for open shells (Coulomb must contract the total density, exchange the same-spin density, with different integral combinations). Ported fockop.f’s d block faithfully (_add_einzi_dblock_uhf / add_einzi_dblock_uhf); it reduces to the closed-shell _add_einzi_dblock except for a handful of off-axis d–d couplings where fockop.f and fockcl.f genuinely differ (the oracle’s own UHF(M=1) shows the same, e.g. AlCl₃).

  • Python and C++ kept in lockstep; validated against the MSINDO oracle to ≤1 µHa on open-shell radicals spanning the gated elements + two triplets (NbO₂, MoCl, MoO₂ triplet, TcCl₂, RuCl, RuF₂ triplet, RhF₂, PdCl, SbF₂, TeCl, ICl₂, XeOF; Nb/Mo inherit this UHF SCF from their RHF trajectory-set membership). Tests: tests/test_msindo.py::test_uhf_heavy_4d_5p_total_energy_parity / test_uhf_msindo_reduces_to_rhf_heavy, tests/test_msindo_cpp.py::test_cpp_uhf_heavy_oracle_and_python_parity.

Fixed: MSINDO Nb₂/Mo₂ converge to the reference SCF state (2026-06-17)

  • The homonuclear 4d dimers Nb₂ and Mo₂ converged to a different SCF stationary point than reference MSINDO on the production DIIS path (Mo₂ landed ~0.10 Ha too low, Nb₂ ~0.17 Ha too high — both genuine but wrong RHF basins). Routing Nb (41) and Mo (42) to the MSINDO-faithful Hückel-guess + WICHT path (_MSINDO_TRAJECTORY_SCF / is_msindo_trajectory_scf, Python + C++) reaches the reference basin: Nb₂ −6.9007186 (≤2e-8), Mo₂ −14.4178689 (≤1.2 µHa, the inherent numpy/Eigen/Netlib spread on its near-degenerate flat plateau). Their well-behaved halides NbF₅/MoF₆ reproduce the oracle on the WICHT path too, so the move regresses nothing; C++↔Python parity holds (≤2e-8). Tests: test_homonuclear_4d_dimers_parity, test_cpp_homonuclear_4d_dimers.

  • Diagnosis recorded in handovers/HANDOVER_MSINDO_SCF.md: the residual XeF₄/Ag₂ divergences are a non-portable diagonalizer-arithmetic knife-edge between multiple valid RHF solutions (the reference state is metastable, not the global SCF minimum), confirmed by restarting from the oracle density (≤2.6e-9). The three candidate fixes — matching DSYEVD, maximum-overlap occupation, and the Pongor/Fermi level-shift — are each ruled out with evidence.

Fixed: smearing-consistent ⟨S²⟩ for multi-k open-shell GDF; corrected stale docstrings (2026-06-17)

  • run_kuhf/kuks_periodic_gdf ⟨S²⟩ now uses the fractional per-spin occupations under Fermi-Dirac smearing. _multi_k_s_squared previously took a hard first-n_σ orbital cutoff (unit weights) — exact at integer filling but approximate once a band is fractionally occupied. The α/β cross-spin overlap is now weighted by n^α_i(k)·n^β_j(k) over all orbitals (the ensemble-UHF generalisation of Szabo & Ostlund Eq. 2.271); it reduces bit-identically to the old cutoff at integer occupation (T=0 / gapped), so every existing closed-shell-limit and integer-filling pin is unchanged. New unit pins: test_multi_k_s_squared_{reduces_to_hardcut_at_integer_filling, fractional_weighting_matches_closed_form}. (design_smearing.md M3 backlog.)

  • Docstring fix: run_kuhf_periodic_gdf claimed “Smearing is not supported on this open-shell path” — it has supported per-spin Fermi-Dirac smearing (global μ_α/μ_β across the BZ) since 2026-06-15; the docstring now describes the smearing + Mermin free-energy behaviour.

Fixed: MSINDO run_job fails closed for unsupported elements (Z>54) (2026-06-17)

  • Molecular run_job(method="msindo") for a closed-shell molecule routes to the C++ run_msindo_full, which does not validate element scope: an unsupported atomic number (e.g. Cs, Z=55) reached the engine with MsindoParameterSet::get(...) returning 0.0 for the missing parameters and produced a meaningless energy instead of an error. _run_semiempirical now guards every atomic number against msindo._SUPPORTED (Z 1–54, the bundled H–Xe parameter set) before dispatch and raises the same NotImplementedError("MSINDO engine supports …") the direct run_msindo call and the CCM run_job path already raise. Supported heavy elements are unaffected (e.g. run_job on TcCl still returns the oracle −25.0060838 Ha). tests/test_msindo.py::test_run_job_unsupported_element_fails_closed pins it.

Added: multi-k corrected-gauge BIPOLE RHF + UHF analytic gradient (2026-06-17)

  • compute_bipole_gradient_rhf and compute_bipole_gradient_uhf now support corrected-gauge (Ewald-exchange-split) results at multi-k — previously they refused them (only the Γ corrected gauge was wired). Pass kmesh= (the BlochKMesh the SCF used). The multi-k corrected gauge is a standard variational HF gradient (full Bloch density → no Bloch-CPHF): the legacy assembly with the exchange block swapped to K_SR(erfc) + per-q reciprocal K_LR + per-k Madelung S(k)D(k)S(k), the full jellium, and every real-space-density term fed the per-k density inverse-Bloch-folded onto the gradient template (density_set_from_k_matrices) rather than result.density (whose 2×-cutoff cell list over-counts the lattice-summed 2e gradient). New _k_long_range_ewald_gradient_multi_k (per-q exchange, the multi-k analogue of _k_long_range_ewald_gradient) and _madelung_ewald_gradient_multi_k. Reduces EXACTLY to the Γ corrected core at n_k=1 (RHF 1.8e-18) and matches compute_bipole_gradient_fd to 6.3e-8 Ha/bohr at n_k=2 (asymmetric BeH₂ [2,1,1]/STO-3G). Regression test_multik_rhf_corrected_gauge_matches_fd (@slow).

  • UHF shares the same core, which auto-detects the open-shell result and builds a spin-resolved exchange 2·Σ_σ ∂E_x[P_σ] (per-spin K_SR/K_LR/ Madelung) with the open energy-weighted W; the total density drives the 1e/Coulomb/jellium/J^LR terms. FD 1.3e-8 on an asymmetric BeH doublet [2,1,1]/STO-3G (regression test_multik_uhf_corrected_gauge_matches_fd, @slow). The closed-shell path is byte-for-byte the prior RHF assembly.

  • RKS/UKS multi-k corrected gauge still refuse (the multi-k XC Pulay term is pending); compute_bipole_gradient_fd remains the production force path until all four are certified.

Added: Phase-0 SCF-energy-gradient assembly for basis optimisation (2026-06-17)

  • vibeqc.basis_optimization.energy_gradient — the analytic energy-weighted-density (Pulay) assembly for the RHF basis-parameter gradient: energy_gradient_fd(provider, x) = tr(P·∂Hcore) + ½·tr(P·∂G(P)) tr(W·∂S), with integral-level central differences and W = ½·P·F·P (no MO coefficients needed). It is the nuclear gradient with ∂/∂R → ∂/∂η, reusing the structure of cpp/src/gradient.cpp. Integral source is injected via an IntegralProvider; VibeqcIntegralProvider wraps the real compute_overlap/kinetic/nuclear/eri bindings for production, and the assembly itself is build-free.

  • Verified build-free in tests/basisset_dev/test_energy_gradient_assembly.py (8 cases) against a self-contained 2-function / 1-pair mock RHF: the frozen-P/W assembly reproduces the full re-SCF energy finite difference across several points, plus the W = ½·P·F·P identity and G(P) consistency. The real VibeqcIntegralProvider path was verified on a build (mars, 2026-06-17): on H₂ / pob-TZVP / RHF the assembly matched the full-energy finite difference to ≤ 1.5×10⁻⁷ Ha.

  • Phase 0 of docs/basisset_dev/ENERGY_GRADIENT_DESIGN.md (maintainer-selected approach); Phase 1 swaps integral-level FD for analytic shell-incrementation behind the same provider interface, leaving this assembly unchanged.

Added: Analytic gradients for the basis-optimisation penalty terms (2026-06-17)

  • vibeqc.basis_optimization.gradients — closed-form gradients of the overlap-based penalty terms, so a BDIIS / scipy grad= need not finite-difference the (stiff, near-linear-dependence) penalty: condition_number_penalty_gradient (∂/∂x of γ·ln κ(S)) and ld_penalty_gradient (∂/∂x of the λ_min hinge), both returning the optimiser-space vector (log/linear chain rule applied).

  • Kernel is the analytic same-center overlap derivative ∂S/∂α (from the closed form s(α,β;l) = (2√(αβ)/(α+β))^(l+3/2)) + Hellmann–Feynman eigenvalue derivatives ∂λ = vᵀ·∂S·v. This same ∂S/∂α is what the future analytic SCF-energy gradient’s Pulay term −tr(W·∂S/∂α) will reuse. Scope: segmented (non-SP) shells, simple extreme eigenvalues.

  • penalty_gradient(parametrisation, x, …) — combined convenience mirroring the objective’s use_cond_penalty / use_ld_penalty flags, so a BDIIS grad= for a penalised objective is just energy_grad(x) + penalty_gradient(p, x, …same flags…).

  • A design proposal for the remaining piece — the analytic SCF-energy gradient (the Pulay term, reusing this ∂S/∂α and the existing nuclear- gradient W/Pulay machinery in cpp/src/gradient.cpp) — is in docs/basisset_dev/ENERGY_GRADIENT_DESIGN.md; it needs a maintainer decision on the libint exponent-derivative strategy before implementation (touches C++, build-only validation).

  • Tests: tests/basisset_dev/test_basis_opt_gradients.py (15 cases) — every analytic derivative checked against central finite differences (primitive overlap, normalised block, κ-penalty on exponents / coefficients / multi-element, the log-vs-linear Jacobian, the LD hinge active + inactive, the combined penalty_gradient), plus an end-to-end optimize_bdiis run driven by the analytic penalty gradient.

Added: BDIIS basis-set optimiser + condition-number penalty (2026-06-17)

  • vibeqc.basis_optimization.optimize_bdiis — a third optimiser driver alongside optimize_scipy / optimize_minuit (same OptResult contract). BDIIS (Basis-set Direct Inversion in the Iterative Subspace; Daga, Civalleri & Maschio, J. Chem. Theory Comput. 16, 2192 (2020)) is the optimiser behind CRYSTAL23’s OPTBASIS, the basis-parameter analogue of geometry DIIS. Hardened beyond the CRYSTAL baseline: a BFGS inverse-Hessian drives both the DIIS error vectors (eᵢ = −H⁻¹·gᵢ) and the quasi-Newton fallback; a trust-region + Armijo backtracking guard (Farkas–Schlegel) guarantees the objective never increases on an accepted step (taming the diffuse-exponent collapse); an ill-conditioned DIIS B matrix falls back to the quasi-Newton step; convergence uses the box-projected gradient so a bound-constrained minimum registers correctly.

  • condition_number_penalty(...) (in basis_optimization.ld_diagnostics) — the smooth γ·ln κ(S) objective term (VandeVondele & Hutter, J. Chem. Phys. 127, 114105 (2007)) of the OPTBASIS objective Ω = E + γ·ln κ. Wired into the objective factories via use_cond_penalty= / cond_gamma=, parallel to the existing λ_min hinge use_ld_penalty=.

  • Citations: new daga_bdiis_2020 + vandevondele_basisopt_2007 entries in the citation database, surfaced by vibeqc.basis_optimization.method_citations() (basis optimisation is an offline procedure, so they carry no SCF-job route).

  • Tests: tests/basisset_dev/test_bdiis_driver.py (13 cases) — quadratic / anisotropic / finite-difference / bound-constrained / Rosenbrock convergence, a scipy cross-check, the γ·ln κ penalty math, and a citation-resolves-in-database guard.

Added: AUTO k-point recommender — KPoints.recommend() (Phase K8) (2026-06-16)

  • vq.KPoints.recommend(system, ...) turns a structure into a ready-to-use k-point spec: it classifies the system’s electronic character (metal / small-gap / insulator), picks the target k-spacing Δk for that character, builds a symmetry-reduced Monkhorst–Pack mesh, and returns a :class:KPoints carrying three new AUTO-only attributes — .smearing (a :class:SmearingOptions to hand the SCF driver, None for clean insulators), .rationale (e.g. “metal, Δk=0.12 Å⁻¹, Γ-centred, 13×13×13, 84 irreducible k-points; smearing fermi-dirac kT=0.005 Ha”), and .verification. It is the electronic-structure-aware sibling of the K5 density builders (from_kspacing / from_kppra / auto) and reuses the validated from_kspacing Δk→mesh math.

  • Character resolution priority: explicit band_gap (eV) → is_metal → an optional classifier(system) hook → otherwise a SAFE default (treat as metal, the dense over-converged choice) with a UserWarning that character was assumed. Smearing follows the same character so the two never disagree. Δk targets (insulator 0.30 / small-gap 0.18 / metal 0.12 Å⁻¹, Materials-Project KSPACING units) and the gap cutoffs are named constants in one tunable config block at the top of vibeqc/kpoints.py.

  • Grid + edge cases: Γ-centred grids for hexagonal/trigonal lattices (spacegroup-authoritative, geometric 60°/120° fallback — FCC/BCC primitive cells are not misflagged); large 3D supercells collapse to Γ-only; slab/ wire vacuum axes stay at N = 1. New KPoints.grid_type / full_mesh_size properties.

  • Optional verify=True runs a convergence ladder (Δk·√2, Δk, Δk/√2, …) via a user-supplied scf_energy_fn, refining until the total energy/atom changes by less than tolerance_meV_per_atom (default 1.0) between rungs and attaching the ladder as a new exported :class:KPointConvergence.

  • Optional predictor= ML Δk hook (conservative end of the predicted interval); no model is bundled — predictor="ml" requires a user ml_predictor= callable, else raises with guidance (cf. Choudhary & Tavazza, npj Comput. Mater. 6, 39 (2020)).

  • Tests: tests/test_kpoints_recommend.py (39 cases) — cubic insulator, simple metal, hexagonal Γ-centring, large-supercell Γ-only collapse, slab axis pinning, band-gap thresholds, verify ladder, ML hook, validation.

Added: SYM3b point-group-reduced direct-ERI Fock build for BIPOLE (~3× faster) (2026-06-16)

  • run_pbc_bipole_rhf(..., use_fock_symmetry_reduce=True) builds the direct-space J(g)/K(g) only at the crystal point group’s atom-pair-orbit representative cells (full internal lattice sum, so each emitted block is exact) and reconstructs the rest by rotation — the |G|-fold compute reduction (Phase SYM3b) that the SYM3 storage round-trip (vibeqc.symmetry_integrals) was the validation substrate for. New C++ build_jk_2e_real_space_output_subset + Python build_jk_reduced_symmetrized / representative_cell_indices (vibeqc.bipole_symmetry_fock).

  • Bit-identical to symmetrize_fock_blocks(full build) (Δ=0 on MgO/STO-3G; tests/test_bipole_symmetry_fock.py::test_sym3b_reduced_build_equals_symmetrised_full_build). On MgO/STO-3G SHRINK 2 2 c12 the direct-ERI build runs 14/55 cells → ~25 s/iter vs ~75 s (≈3×); composes with the incremental-Fock ΔD path (reconstruction is linear). Enforcing the point group shifts the (truncation-asymmetric) energy by only ~0.08 mHa (raw −271.21530 → symmetrised −271.21537), well inside the cross-family tolerance — the @slow CRYSTAL parity gate now uses it (~4½ min, was ~13½ min).

  • Whole-cell reduction shipped (~2.6–3×); the finer atom-pair shell-pair mask for the full ~7.9×@c12 / 11.8×@c16 remains a follow-up (SYM3b real-space point-group reduction). Wired into all four BIPOLE drivers (RHF, UHF, RKS, UKS — use_fock_symmetry_reduce=True); for UHF/UKS the per-spin J_SR/K_SR builds reduce independently, and for pure-functional RKS/UKS the reduced J reconstructs while the non-reduced path keeps its J-only build (test_bipole_symmetry_fock.py::test_sym3b_reduce_matches_full_symmetry_uhf_rks_uks, reduce == use_fock_symmetry to <1 µHa for all three).

Added: MSINDO completes the periodic table to Xe (Z 1–54); C++ engine extended to the full 5th row (2026-06-16)

  • MSINDO/INDO now supports the full first six rows, H–Xe (Z 1–54). The last 10 gated elements — the open-d 4d metals Tc, Ru, Rh, Pd and the heavier lone-pair 5p Sb, Te, I, Xe (with Ag/Cd landed earlier this cycle) — are oracle-validated to ≤1 µHa across diverse closed-shell molecules (tests/test_msindo.py::test_heavy_4d_5p_total_energy_parity).

  • SCF stationary-point fix. These elements converge to a different SCF stationary point than the production DIIS path finds — DIIS lands higher (Tc/Rh) or lower with the polarization d wrongly occupied (Pd/Sb/Xe). The fix is not stewart.f pseudo-diagonalization: MSINDO’s default molecular SCF is full-diagonalization + WICHT density damping from an extended-Hückel guess (huckcl.f; bare-IPOT diagonal keeps the diffuse metal d out of the occupied space) with an energy-only convergence test. Implemented as _scf_rhf_msindo / scf_rhf_msindo_driver, element-dispatched via _MSINDO_TRAJECTORY_SCF (Tc–Pd, Sb–Xe); H–Mo/Ag/Cd/In/Sn keep the DIIS path (more robust for their near-degenerate small molecules, e.g. AgH). See handovers/HANDOVER_MSINDO_SCF.md.

  • C++ engine extended to the full 5th row (indo_engine.hpp), in lockstep with Python: the Y–Cd/In–Xe ENEG/ATENG categories (Rb/Sr alkali/alkaline-earth, Y–Pd occupied-4d TM, Ag/Cd In–Xe-loop, In–Xe p-block), the [Kr]/[Kr]4d¹⁰ frozen-core penetration (4s/4p/4d, TAU4*/FCP4*), and the Hückel+WICHT SCF. Fixes a latent production bug: run_job routes closed-shell jobs to the C++ run_msindo_full, which was H-Kr-only — so the already-enabled 5th row (Mo/In/Sn/Ag/Cd) returned wrong energies via run_job (e.g. MoF₆ −147.99 vs the correct −149.11). C++ now matches the Python engine and the oracle for the whole 5th row (tests/test_msindo_cpp.py::test_cpp_fifth_row_oracle_and_python_parity).

  • H–Kr behaviour is byte-identical (every new branch is gated on Z≥37). Known residual: a few near-degenerate pathological cases (XeF₄, homonuclear dimers) converge to a different state — the same class already accepted for Mo₂/Nb₂ — and are not enablement targets.

Validated: BIPOLE↔CRYSTAL cross-family parity at converged k; the matched-k SHRINK-2-2 target is exxdiv-convention-confounded (2026-06-16)

  • The converged BIPOLE multi-k SCF (corrected gauge, exxdiv='ewald') for MgO/STO-3G at SHRINK 2 2 gives −271.21530 Ha/FU (cutoff 12, metric Σ_k w_k Tr[D(k)S(k)] = 20.000 → physical basin), reproducing CRYSTAL’s k-converged SHRINK-8-8 value (−271.21814375) to +2.9 mHa and PySCF KRHF [2,2,2] exxdiv='ewald' (−271.213356) to −1.9 mHa.

  • It is +641 mHa from CRYSTAL’s coarse SHRINK-2-2 value (−271.85640) — not a bug: BIPOLE applies the probe-charge Ewald (Madelung) finite-size exchange correction (ξ_M π/V_sc·ω²)·SDS, so its coarse mesh is already k-converged, while CRYSTAL applies none and over-binds its own SHRINK-8-8 by 638 mHa. The matched-k SHRINK-2-2-vs-SHRINK-2-2 premise (comparing two coarse-mesh values) is therefore invalid; the valid cross-family CRYSTAL comparison is BIPOLE-SHRINK22 (exxdiv) ↔ CRYSTAL-SHRINK88 (k-converged).

  • New @slow pin tests/test_pbc_bipole_multik_ewald_split.py::test_mgo_shrink22_converged_matches_crystal_kconverged

    • example parity_mgo_shrink22_crystal_convention.py; the −271.85640 matched-k target is retired; docs/periodic_jk_routes.md § Current status updated. Efficiency (PBC-audit): the BIPOLE multi-k E2/E3 per-iteration reuse is already done (98.5 % of per-iter is the E1 direct-ERI build); the real-space point-group reduction (SYM3b; measured 7.9×@c12, 11.8×@c16) is scoped as the SYM3b BIPOLE follow-up.

Added: ROHF in the ASE calculator + analytic-gradient run_job optimisation (2026-06-16)

  • VibeQC(..., restricted_open=True) — the ASE calculator now runs open-shell HF as spin-pure ROHF (analytic energy + forces via compute_rohf_gradient), unlocking ASE-driven workflows (custom optimisers, MD, NEB-via-ASE) for restricted open-shell. Additive: the default (False) keeps the historical UHF/UKS auto-selection. Open-shell DFT stays UKS in the calculator (ROKS has no analytic gradient yet).

  • run_job(method="rohf", optimize=True) now uses the analytic ROHF gradient (routed through the VibeQC calculator) instead of finite-difference forces — full-speed BFGS relaxation. ROKS optimisation remains finite-difference.

Added: analytic ROHF gradient → fast optimisation + frequencies (2026-06-16)

  • vibeqc.compute_rohf_gradient(molecule, basis, result) — the analytic ROHF nuclear gradient (Ha/bohr). Assembled from the same primitives as the UHF gradient (one-electron + two-electron J/K + nuclear repulsion) but with the ROHF energy-weighted density W = Dα·Fα·Dα + Dβ·Fβ·Dβ in the Pulay (overlap) term. This Lagrangian form is the subtle part of an ROHF gradient: because ROHF orbitals diagonalise the Roothaan effective Fock (not the per-spin Focks), the naive “Σ nᵢ εᵢ CᵢCᵢᵀ” form with effective-Fock eigenvalues is wrong; Dσ·Fσ·Dσ is exact (verified by finite difference of the ROHF energy to ~1e-10 with synthetic analytic integral derivatives).

  • ROHF geometry optimisation now uses analytic gradients via molecular_optimize.optimize_molecule(method="rohf") (full speed, not FD).

  • ROHF Hessians / harmonic frequencies now work (run_job(method="rohf", hessian=True)): the FD Hessian finite-differences the analytic ROHF gradient. The run_job Hessian gate now blocks only ROKS (its analytic XC-gradient term is still pending — M5b for ROKS).

  • Tests: synthetic FD-vs-analytic check of the energy-weighted density (the pinned Lagrangian form), real-molecule compute_rohf_gradient vs FD of the energy, and an OH-radical frequencies run.

Improved: dedicated ROHF/ROKS orbital table in the SCF log (2026-06-16)

  • ROHF/ROKS results now print a single-column orbital table with closed/open/virtual occupations (2/1/0) and SOMO markers, instead of the UHF two-column layout that showed identical α/β orbital energies. Reads clearly as restricted-open-shell. (scf_log._format_orbital_table_rohf, routed by the result’s method; output-only, unit-tested.)

Added: ROHF/ROKS reach atomization + scans + the molecular optimiser (2026-06-16)

  • Atomization (run_job(..., atomization=True) and vibeqc.atomization) accepts method="rohf"|"roks": free-atom references are computed with the spin-restricted open-shell solver for both closed- and open-shell atoms (it reduces to RHF/RKS at a closed shell), giving spin-pure atomic references for the Σ E(atom) − E(molecule) energy.

  • PES scans (vibeqc.scan) and the molecular geometry optimiser (vibeqc.molecular_optimize.optimize_molecule) already drive ROHF/ROKS: both route methods without an analytic gradient through their existing finite-difference branch (_evaluate_energy / central-difference forces → _run_single_point), which now resolves rohf/roks. Rigid (energy-only) scans and relaxed (FD-force) scans both work.

  • Not yet wired (need finite-difference force routing through their analytic-gradient machinery, or an ASE-calculator method knob — a separate increment): vibeqc.neb, vibeqc.irc, vibeqc.dimer, and the vibeqc.ase.VibeQC calculator. These raise a clear method-unsupported error for now.

Added: cas_reference="rohf" — spin-pure CAS starting orbitals (2026-06-16)

  • The determinant-solver family (CASCI / CASSCF / NEVPT2 / CASPT2 / CISD / selected-CI / DMRG / v2RDM / transcorrelated-CI) accepts cas_reference="rohf" alongside "rhf"/"uhf"/"uno". ROHF gives one spin-restricted orbital set already ordered closed→open→virtual, so the active space starts from a spin-pure (⟨S²⟩ = S(S+1) exactly), contamination-free reference — a clean alternative to UNO that needs no natural-orbital diagonalisation (get_hf_orbital_provider(method="rohf")).

  • Citation: roothaan_rohf_1960 fires when a CAS job uses cas_reference="rohf" (parallel to UNO → Pulay-Hamilton 1988).

  • ROHF-MP2 / ROHF-CC (semicanonical post-HF on the ROHF reference) remain future work (HANDOVER_ROHF.md M6b).

Added: ROHF/ROKS geometry optimisation (finite-difference forces) (2026-06-16)

  • run_job(..., method="rohf"|"roks", optimize=True) now runs an ASE/BFGS geometry optimisation. Like the gradient-less wavefunction solvers (CASCI/CISD/…), ROHF/ROKS forces are computed by central finite differences of the (verified) energy — analytic restricted-open-shell gradients are a later milestone (HANDOVER_ROHF.md M5b). Correct but slower than analytic-force methods; use UHF/UKS for large fast relaxations.

  • Hessians/frequencies (hessian=True) remain gated for ROHF/ROKS: the FD Hessian differentiates the analytic gradient, which doesn’t exist yet — the gate now raises a precise pointer to M5b rather than blocking optimisation too.

Added: restricted open-shell Kohn–Sham (ROKS) — molecular single point (2026-06-16)

  • method="roks" in run_job, and vibeqc.run_roks / vibeqc.ROKSOptions / vibeqc.ROKSResult. The DFT counterpart of ROHF: a spin-pure restricted open-shell Kohn–Sham determinant. Reuses ROHF’s Roothaan single-effective-Fock coupling verbatim (vibeqc.rohf.run_roothaan_scf); the only difference is the per-spin KS Fock build — F_σ = Hcore + J α_HF·K(D_σ) + V_xc,σ with the spin-polarised libxc XC potential and the global-hybrid exact-exchange admixture.

  • The spin-polarised XC potential matrix (LDA + GGA, symmetric ∇ρ flux terms) is assembled in Python (vibeqc.roks.build_uks_xc_potential), ported from the PySCF-validated UKS Hessian XC build (hessian_analytic_uks).

  • Supported: LDA, GGA, and global-hybrid (B3LYP, PBE0, …) functionals. Meta-GGA (τ-dependent), range-separated hybrids (ωB97X, CAM-B3LYP, HSE) and double hybrids raise a clear NotImplementedError (roadmap; HANDOVER_ROHF.md).

  • Citation: roothaan_rohf_1960 via routes.methods["roks"]; the functional’s own citation fires through routes.functionals as usual.

  • ROKSResult carries e_xc / e_coulomb / e_hf_exchange / e_nuclear, so the SCF-log energy-components block renders for ROKS.

  • Tests (tests/test_roks.py): ROKS-on-singlet == RKS (LDA/PBE/B3LYP), doublet spin-purity, unsupported-functional refusal, PySCF ROKS parity. The KS Fock builder was additionally checked to reduce exactly to the ROHF builder at α_HF=1 with zero XC.

Added: restricted open-shell Hartree–Fock (ROHF) — molecular single point (2026-06-16)

  • method="rohf" in run_job, and the public vibeqc.run_rohf(molecule, basis, options) / vibeqc.ROHFOptions / vibeqc.ROHFResult. vibe-qc’s first spin-restricted open-shell reference: one set of spatial orbitals partitioned into doubly-occupied (closed), singly-occupied (open) and virtual shells, giving a spin-pure single determinant (⟨S²⟩ = S(S+1) exactly, no spin contamination — the standard reference for ROHF-MP2 / ROHF-CC and a cleaner CAS start than UHF).

  • Implemented as a pure-Python driver (python/vibeqc/rohf.py) on the same two-electron JK seam the C++ SCF drivers expose (make_direct_jk_builder), using Roothaan’s single effective Fock operator (Coulson coupling; Roothaan, Rev. Mod. Phys. 32, 179 (1960)). Additive — touches no C++ core, so all existing paths are unchanged. DIIS + canonical orthogonalisation + SAD (Hcore-fallback) guess; honours density-fitting via ROHFOptions.density_fit / aux_basis.

  • Output flows through the existing open-shell writers (SCF-log orbital table, Molden, spin density, population) — the ROHF result exposes the per-spin attribute surface with identical α/β spatial orbitals.

  • Citation: roothaan_rohf_1960 fires via routes.methods["rohf"] (pinned in the citation coverage gate’s required-methods set).

  • Scope this increment: molecular single-point energy only. Analytic ROHF gradients (→ geometry optimisation / Hessian), ROKS, post-SCF ROHF references and periodic ROHF are gated with clear NotImplementedErrors and tracked in HANDOVER_ROHF.md (milestones M3–M7).

  • Tests: tests/test_rohf.py — a pure-math tier (Roothaan block structure, closed-shell→RHF reduction, SCF stationarity; verified locally without the compiled core) and an end-to-end tier (ROHF-on-singlet == RHF, doublet spin-purity, PySCF ROHF parity) that runs on a build box.

Added: MSINDO Ag + Cd (Z47–48) — occupied-4d In–Xe-loop ENEG (2026-06-16)

  • MSINDO/INDO now also supports Ag (Z47, 4d¹⁰5s¹) and Cd (Z48, 4d¹⁰5s²), oracle-validated to ≤1 µHa across closed-shell hydride/oxide/halides (AgH/AgF/AgCl, CdO/CdF2/CdCl2). The fix was again a category bug, isolated to eneg(): atomic_reference.f computes Ag/Cd twice — first in the Y–Cd loop (DO L=39,48, Sc–Zn-style occupied-d), then the In–Xe loop (DO L=47,54) overwrites them. The final oracle U_ss/U_pp/U_dd therefore use the In–Xe p-block formula carrying the occupied d shell (DEL=ND=10, SEL=LS=1/2), not the Y–Pd occupied-d TM form (which would add the −(DEL−1)·F0DD self-repulsion to U_dd). vibe-qc’s _TM set stops at Pd(46), so Ag/Cd fell through to the general (pel−1) p-block path, which drops the DEL terms → wrong U for both. A dedicated {47,48} branch ports the In–Xe-loop formula; eff_core_charge (z−36) and n_d_principal (4d) were already correct, and ateng()’s PEL=0 path already reduces to the right 2·U_ss + F0SS. Python-only (no C++ rebuild); H–Mo/In/Sn unchanged (branch dormant for all other Z; full tests/test_msindo.py green). Still gated (await the stewart.f pseudo-diagonalization SCF port): the open-d 4d metals Tc–Pd and the heavier lone-pair 5p Sb/Te/I/Xe.

Added: megacell (finite-supercell) periodic MP2 + DLPNO-CCSD(T) — local-correlation Stage 6 (2026-06-15)

  • vibeqc.periodic_megacell_mp2.megacell_mp2(system, basis, nrep)MegacellMP2Result: the first periodic correlation energy in vibe-qc. Replicates the unit cell into a finite n1×n2×n3 supercell (build_supercell_molecule) and runs molecular HF+MP2 (run_mp2), reporting the correlation energy per unit cell. This is the megacell strategy (Nejad et al. 2025, Paper II); the periodic local-correlation route Stage 6.

  • A finite supercell is a molecular calculation, so there is no periodic exxdiv / G=0 divergence — the MP2 denominator is clean and the per-cell energy converges to the thermodynamic limit as the megacell grows. (This is why the megacell route avoids the orbital-energy boundary that the Γ-Bloch route faces.)

  • megacell_run_job(system, basis, nrep, method="dlpno-ccsd(t)")MegacellJobResult dispatches the supercell through the molecular run_job for the local methods (dlpno-mp2 / dlpno-ccsd / dlpno-ccsd(t)): the megacell route to periodic DLPNO-CCSD(T), reusing vibe-qc’s validated molecular DLPNO.

  • megacell_mp2_tdl(system, basis, sizes)MegacellTDLResult extrapolates the per-cell correlation energy to the thermodynamic limit: it fits E_corr(N) = b·N + a over a supercell size series, so the bulk per-cell energy is the slope b (the finite-N average E_corr(N)/N is edge-contaminated; a is the two-end surface term). Runnable demo: examples/periodic/periodic-megacell-local-correlation.py.

  • Cross-validated against PySCF (§10): tests/test_periodic_megacell_pyscf_parity.py (gated importorskip) confirms megacell MP2 == PySCF molecular MP2 on the identical supercell to ~1e-13. The translational-symmetry pair reduction (Stage 5) and the PySCF.pbc KMP2 thermodynamic-limit cross-check remain follow-ups.

  • Toroidal (BvK) megacell periodic Γ RI-MP2 landed — Stage 5a (python/vibeqc/periodic_toroidal_mp2.py): the first native periodic correlation energy from vibe-qc’s own periodic machinery. A periodic (toroidal/BvK) supercell runs periodic Γ GDF HF → periodic Γ DF 3-index tensor (Stage 4) → RI-MP2 energy per cell. By the BvK theorem, a Γ-point calculation on an N-cell supercell equals an N-point k-mesh calculation on the unit cell — validated cell-for-cell against PySCF.pbc KMP2 at matching k-mesh via run_periodic_mp2 (out-of-process, §10): agreement ~0.18 mHa at nk=2, 0.05 mHa at nk=4 on the H₂/STO-3G chain. The open megacell stays the validated extrapolation route; this is the native periodic infrastructure for 5b (translational pair-family decomposition) and 5c (DLPNO on the toroidal megacell). Tests (tests/test_periodic_toroidal_mp2.py, 4): KMP2 oracle at nk=2,4; structure/sanity invariants.

  • Tests (tests/test_periodic_megacell_mp2.py, 6): the (1,1,1) megacell equals molecular MP2 and molecular DLPNO-CCSD(T) on the unit cell to <1e-10; per-cell correlation converges and extrapolates to the bulk; geometry + bookkeeping.

Performance: Γ GDF Ewald-JK fallback reuses the cached analytic-FT Hartree J (2026-06-16)

run_rhf_periodic_gamma_gdf / run_rks_periodic_gamma_gdf build their Hartree J through the EWALD_3D analytic-FT builder on the 3D Ewald-JK fallback path (tight cells whose GDF cutoff includes image cells; backend="ewald-jk-fallback"). That call site still rebuilt the density-independent AO-pair Fourier mesh (ke=200), the dense G-mesh and the 4π/G² kernel every SCF iteration — the one EWALD_3D J consumer the 2026-06-11 EwaldJFTGammaCache rollout (PBC audit docs/pbc_audit_2026-06.md §E2) did not reach. It now builds that machinery once per SCF via make_ewald_3d_gamma_j_builder and redoes only the O(n²·n_G) density contraction each iteration (the same “build once, contract per iteration” pattern as the driver’s own Lpq cache). Energies are bit-identical — pure caching, no physics change: H₂/STO-3G/12-bohr Γ Ewald-JK fallback stays -1.116744724149135 while the analytic-FT cache builds drop n_iter+1 1. The diagnostic VIBEQC_J_EWALD3D_BACKEND=grid path stays uncached. Covered by tests/test_ewald_j_cache.py (test_gamma_builder_matches_build_j_ewald_3d pins the cached builder byte-for-byte against the per-iteration build_j_ewald_3d).

Added: periodic case for READ / ATOMSPIN / SPINLOCK initial-guess + convergence features (2026-06-16)

The three initial-guess / convergence features that landed molecular-only (READ, ATOMSPIN, SPINLOCK) now work on periodic SCF, building on the molecular implementations (no molecular behavior change).

  • READ (Γ-point periodic restart). vq.run_periodic_job(..., initial_guess="read", read_from=prior_result_or_path) restarts a Γ-point periodic SCF from a prior periodic result (in-memory) or a .qvf / .molden file. The prior g=0 cell density is projected onto the current cell basis (the MINAO-style projector evaluated with the cell overlaps) and injected at g=0, where it Bloch-sums to a k-independent D(k) for the first Fock build — the NEB / geometry-scan / extrapolation restart path on slabs and bulk. Wired on the Ewald, GDF, and BIPOLE Γ drivers. Multi-k per-k restart needs complex Bloch coefficients (the QVF wavefunction.gto section is Γ-only) and is out of scope: a multi-k READ raises with a roadmap pointer. New periodic resolvers in python/vibeqc/guess_read.py; read_density{,_alpha,_beta} + read_path carried on PeriodicRHFOptions / PeriodicKSOptions.

  • ATOMSPIN (periodic broken-symmetry seed). atomic_spins (per-atom +1/-1/0, unit-cell atom order) now seeds an AFM / ferrimagnetic g=0 cell spin pattern that Bloch-sums to a broken-symmetry D(k) for periodic UHF / UKS. The C++ GuessEngine::build_open_shell already assembled the block-diagonal broken-symmetry density (is-periodic-agnostic); the periodic open-shell guess shim, the vibeqc.guess helper, and the periodic drivers now thread atomic_spins through. Wired on the Γ UHF/UKS Ewald, GDF, and BIPOLE drivers and the multi-k UKS Ewald driver (the AFM-solids flagship); the multi-k UHF Ewald driver (Hcore-only guess) and closed-shell methods fail closed.

  • SPINLOCK (periodic magnetic convergence). PATTERN_HOLD holds the seeded broken-symmetry occupied set by maximum overlap (MOM, python/vibeqc/mom.py) for the first spinlock_iterations cycles, then releases — wired on the Γ UHF/UKS Ewald, GDF and BIPOLE drivers and the multi-k UKS Ewald driver (the path that protects an atomic_spins AFM seed against collapse on multi-k). SPIN_SCHEDULE runs a two-phase periodic SCF — lock n_alpha-n_beta = spinlock_value for spinlock_iterations cycles, then restart at the multiplicity target from that density via the READ machinery — on the Γ UHF/UKS Ewald, GDF and BIPOLE drivers. Both modes are reachable through vq.run_periodic_job(..., method="UHF"/"UKS", spinlock="pattern_hold"/"spin_schedule", spinlock_iterations=N[, spinlock_value=M]). Drivers that do not implement a requested mode (the multi-k UHF Ewald path for any mode; multi-k UKS Ewald for SPIN_SCHEDULE) fail closed with a clear error. spinlock_mode / spinlock_value / spinlock_iterations carried on the periodic options structs; mom.reorder_occupied_by_max_overlap mirrors the C++ mom_reorder_occupied; the GDF/BIPOLE PATTERN_HOLD reuses each driver’s per-k MOM kernel; python/vibeqc/spinlock_periodic.py holds the two-phase orchestration via a driver-agnostic phase-runner thunk (so the Ewald, GDF and BIPOLE call signatures all share one helper).

  • No new citable methods (READ is a mechanical restart; ATOMSPIN/SPINLOCK mirror the CRYSTAL conventions; the MOM reference (Gilbert/Besley/Gill 2008) already ships with the molecular SPINLOCK).

  • Validated (tests/test_guess.py): periodic Γ ATOMSPIN reproduces the molecular-limit UHF broken-symmetry energy to < 1 µHa and matches PySCF.pbc UHF broken-symmetry ⟨S²⟩ (out-of-process, examples/regression/parity_periodic_atomspin_vs_pyscf.py); Γ READ round-trip (in-memory + .qvf) reaches the same energy in fewer iterations; periodic SPINLOCK PATTERN_HOLD no-op on a held case and SPIN_SCHEDULE release-to-target; multi-k READ + unsupported-driver SPINLOCK gates raise.

Added: per-spin (open-shell) Fermi-Dirac smearing for multi-k GDF (2026-06-15)

  • run_kuhf_periodic_gdf / run_kuks_periodic_gdf now honor smearing_temperature > 0: independent per-spin chemical potentials μ_α, μ_β determined by a global Fermi level across the BZ (the CP2K/VASP/Quantum-Espresso convention), replacing per-k hard Aufbau — which is exact only for gapped cells and mis-occupies metals / band-overlap / partially-filled degenerate open shells. The result carries fermi_level_alpha/beta, entropy, free_energy (Mermin A = E - T·(S_α+S_β)), and per-spin fractional occupations_alpha/beta.

  • vibeqc.smearing.apply_smearing_open_shell — the reusable per-spin core: the shared μ-bisection run once per channel with spin_degeneracy = 1. fermi_dirac_occupations_per_k / aufbau_occupations_per_k gained a degeneracy parameter (default 2.0 ⇒ closed-shell bit-identical; no existing caller changes).

  • Behavior-neutral at T = 0: the per-k Aufbau path is untouched, so UHF(M=1) KRHF / KUKS(M=1) KRKS stay machine-precision-equal.

  • Reachable from run_periodic_job: method='UHF'|'UKS', jk_method='gdf', a kpoints mesh, and smearing_temperature > 0 now route to the per-spin-smearing drivers (pass kpoints=(1,1,1) for a Γ-point open-shell smeared run).

  • Validated (tests/test_periodic_kuhf_gdf.py, test_smearing_open_shell.py): KUHF(M=1)+smear KRHF+smear; per-spin particle conservation; a boron 2p¹ degenerate shell fractionally occupied (~1/3 each) with A < E, both directly and through run_periodic_job.

  • Not yet wired (raise an actionable error): Ewald-3D multi-k UHF/UKS and the Γ-only open-shell GDF drivers (docs/design_smearing.md).

Added: case-partitioned matrix-free RDM CASPT2, caspt2(engine="cases") (roadmap 25i, 2026-06-16)

  • caspt2(..., engine="cases") (IC variant) is an experimental, opt-in CASPT2 backend that never builds the determinant first-order interacting space and never forms the 4-RDM. The FOIS overlap S, RHS V and generalized-Fock H₀ are assembled as collapsed per-case external×active- RDM “sigma” contractions (organized by excitation case and grouped by external-index signature) and the first-order equation (H₀−E₀S)x=−V is solved iteratively (MINRES on the matrix-free S·x / H₀·x), so cost is set by the active RDMs, not the reference determinant count. The 3-RDM enters H₀ only through the F3 intermediate (the diagonal-Fock- contracted 4-RDM, 3-RDM-sized). Reproduces the validated explicit/direct engines (hence OpenMolcas &CASPT2) at σ=0 to machine precision, and composes with a selected-CI reference (a determinant-list-agnostic 3-RDM builder), the route to large active spaces. engine="auto" (the default) is unchanged. Unshifted, ipea=0 only. The overlap, RHS and all of H₀ (including the off-diagonal generalized-Fock coupling, via external- offset grouping) are collapsed to block sigmas, so the matrix-free matvec has no n_cand² term for any reference; the active-RDM contractions inside those sigmas still run through the Python reducer, so the cross-over vs the determinant engines is set by that constant factor (a C++ inner kernel is the next step). Reachable through run_job(method="caspt2", caspt2_options=CASPT2Options(engine="cases")) (runs labelled caspt2(..)_cases).

Fixed: multi-k periodic RKS/UKS gradient — GGA σ XC Pulay + arbitrary basis (2026-06-16)

  • Multi-k periodic DFT atomic gradients (compute_gradient_periodic_rks_multi_k, compute_gradient_periodic_uks_multi_k) now compute the full GGA XC Pulay term. Both multi-k drivers routed their XC Pulay through a molecular-grid fallback that dropped the GGA σ-coupled piece (LDA part only) — the gap the Γ-only G1b path had already closed. Rerouting both to the lattice-summed periodic primitive xc_lattice_gradient_contribution[_uks] (the kernel the Γ path uses) cuts the multi-k PBE error from ~2.0e-2 to ~1.6e-3 Ha/bohr vs the molecular analytic gradient (H₂O/STO-3G, 20-Å box, 1×1×1); closed-shell UKS-PBE now reproduces RKS-PBE to ~1e-15.

  • Removed the hard-coded STO-3G shell→atom table. The retired fallback (_xc_pulay_molecular_fallback, _uks_…, _atom_bf_indices*) raised NotImplementedError on any non-STO-3G basis; the lattice primitive derives shell→atom from the basis, so multi-k DFT gradients now run for arbitrary bases (regression: 6-31g H₂ multi-k PBE).

  • Tests: tests/test_periodic_gradient_g1c.py (GGA σ vs molecular [@slow]; 6-31g runs), tests/test_periodic_gradient_g1d.py (UKS-PBE ≡ RKS-PBE).

  • On the multi-k-vs-molecular offset: the multi-k analytic gradient is FD-consistent — it matches a finite-difference gradient of the multi-k Ewald SCF energy to ~1.5e-4 Ha/bohr (step-limited), so it correctly differentiates the energy it is built from. The larger ~1.6–1.8e-3 Ha/bohr offset against the molecular analytic gradient (present for LDA too) is the multi-k Ewald SCF surface not yet at the isolated-molecule limit in a 20-Å box — an SCF-driver property, not a gradient error, and unrelated to this XC fix.

Added: corrected-gauge Γ analytic gradient — all four drivers (M3, 2026-06-15)

  • compute_bipole_gradient_{rhf,uhf,rks,uks} now support corrected-gauge (Ewald-exchange-split) results at Γ — previously all four refused them (the analytic kernels implemented only the legacy Γ-local gauge). The corrected gauge is a standard variational HF/KS gradient (full Bloch density → no Bloch-CPHF): the legacy assembly with the exchange block swapped to K_SR(erfc) + K_LR(recip) + (ξ_M π/Vω²)·S·D·S (no spheropole), the gradient kernels fed a homogeneous-on-template density, and the full jellium gradient (v_bg Tr[D S] is quadratic; the W from the variational eigenvalues supplies the cancelling overlap term). UHF/UKS use spin-resolved exchange 2·(∂E_x[Pα]+∂E_x[Pβ]) with W = W_α + W_β; RKS/UKS add the fixed-grid XC Pulay + grid-motion correction and scale the HF exchange by the functional’s fraction (0 for pure DFT). All match compute_bipole_gradient_fd to ≤1e-6 Ha/bohr (RHF 1.2e-8 MgO; UHF 2.4e-8 BeH; RKS LDA/PBE0 1.2e-7 H₂; UKS LDA 1.1e-6 triplet H₂). New _k_long_range_ewald_gradient kernel (reciprocal long-range exchange gradient, the exchange analogue of _j_long_range_ewald_gradient). Multi-k corrected gauge still refuses all four drivers (per-k q-channel K_LR + supercell Madelung gradient pending). FD remains the production force path.

Added: READ initial guess restarts an SCF from a prior result / file (2026-06-15)

  • InitialGuess.READ restarts a molecular SCF (RHF / UHF / RKS / UKS) from a prior calculation’s density instead of a from-scratch atomic guess. Three sources:

    • in-memory via the new read_from= kwarg: run_*(mol, basis, opts, read_from=prior_result);

    • a vibe-qc .qvf file: opts.read_path = "prev.qvf" (its wavefunction.gto section);

    • a .molden file: opts.read_path = "prev.molden".

  • The prior density is expressed in the current AO basis: used directly when the basis and geometry match, otherwise projected with the MINAO-style least-squares AO projector D = P·D_prior·Pᵀ, P = S_tt⁻¹·S_tm, backed by a new compute_overlap_two_basis cross-basis AO overlap. This restarts across a changed geometry/basis: the NEB / geometry-scan path.

  • The periodic input-string guess parser also accepts moread / coread as aliases for READ.

  • Molecular-only: a periodic SCF selecting READ raises a roadmap-pointer error. READ is a mechanical restart (no defining paper) and carries no citation, like HCORE.

  • Tests (tests/test_guess.py): in-memory / .qvf / .molden round-trips (RHF + UHF + RKS) converge to the from-scratch energy in far fewer iterations; the projection path reaches the new-geometry minimum from a prior .qvf; missing-source and unknown-extension inputs raise.

Added: periodic Γ-point density-fitted 3-index integrals — local-correlation Stage 4 (2026-06-15)

  • vibeqc.periodic_df.build_periodic_gamma_df(system, basis)PeriodicGammaDF: exposes the periodic Γ compcell-GDF 3-index cderi Lpq (shape (n_fit, nbf, nbf), (μν|λσ) Σ_L Lpq[L,μν] Lpq[L,λσ]) plus .build_J / .build_K / .mo_transform, for transformation into the Wannier/PAO basis. Built via the public vibeqc.aux_basis.build_lpq_compcell (no SCF-driver internals). Stage 4 of the periodic local-correlation route.

  • The periodic GDF SCF builds this tensor and discards it; this re-exposes it. Verified bit-identical to the SCF — J from the extracted tensor + the converged density reproduces PBCGDFResult.e_coulomb (|Δ| = 0).

  • Γ-point, compcell GDF only. The periodic-MP2 finite-size / exxdiv-for-correlation (G=0) treatment and multi-k Lpq(k) are later stages.

  • Tests (tests/test_periodic_df.py, 5): tensor shape + AO symmetry, the Coulomb-energy oracle, exchange symmetry, and 4-index permutation symmetry.

Added: matched-k CRYSTAL reference for the BIPOLE↔CRYSTAL gate (2026-06-14)

  • Sealed examples/regression/crystal_parity/crystal_demos/mgo_sto3g_shrink22.{d12,out} — a matched-k CRYSTAL23 reference for MgO at SHRINK 2 2 (−271.85640 Ha/FU, 3 IBZ k-points), generated out-of-process from a local CRYSTAL binary (§10, validated by reproducing the committed SHRINK-8-8 ref −271.21814375 to all digits). This is the correct reference for a matched-k BIPOLE↔CRYSTAL gate — the converged SHRINK-8-8 value over-binds by 0.64 Ha at this coarse mesh, so comparing BIPOLE@2×2×2 against it (as the stale parity_mgo_shrink22 script did) is not a tight same-family check.

  • docs/periodic_jk_routes.md § Current status now records the precise gate state: the reference side is ready, but BIPOLE multi-k needs cutoff ~18 for converged lattice sums (S(k)-fold < 1e-4; at cutoff-8 it is 0.38 → the energy is junk) and is then >10 min even at the 8-k 2×2×2 net — the PBC-audit E2/E3 multi-k efficiency follow-up (BIPOLE multi-k lane). The @slow gate awaits that.

Added: periodic Γ-point PAO virtual space — local-correlation Stage 3 (2026-06-15)

  • vibeqc.periodic_pao.build_pao_periodic_gamma(result, basis, system): builds the virtual-space projector Q = 1 C_occ C_occᵀ S and an S-orthonormal, Fock-diagonal virtual basis over the home cell (PeriodicPAOResult), reusing the molecular PAO machinery (vibeqc.dlpno.pao). Stage 3 of the periodic local-correlation route ([HANDOVER_PERIODIC_LOCAL_CORRELATION.md]).

  • At Γ the full-domain basis equals the canonical virtual MOs; the reusable redundant-PAO + projector machinery is what Stage 5 restricts to per-pair local domains. Γ-point only.

  • Tests (tests/test_periodic_pao.py, 6): occupied-orthogonality, S-orthonormality, Fock-diagonality, completeness, and an oracle — semicanonical PAO energies reproduce the SCF virtual orbital energies to ~1e-8 on H₄/STO-3G in a 30-bohr box.

Added: MSINDO element coverage reaches the 5th row — Rb–Mo + In/Sn (2026-06-15)

  • MSINDO/INDO now supports Z 1–42 (H–Mo) plus In and Sn (Z49–50), all oracle-validated to ≤1 µHa across diverse molecules/geometries. Landed:

    • 5th-row s-block + 4d transition metals (Rb, Sr, Y, Zr, Nb, Mo). The fix was a category bug: vibe-qc’s _ALKALI / _ALKALINE_EARTH / _TM ENEG sets omitted the 5th-row analogs, so Rb/Sr/Y–Mo hit the wrong atomic-reference formula (e.g. Rb wrongly took the closed-shell F0SS correction → RbH was 0.08 Ha off). Adding Rb→alkali, Sr→alkaline-earth, Y–Pd→_TM makes them exact — atomic_reference.f’s Y–Cd block is term-for-term identical to Sc–Zn. (RbH/RbF/RbCl, SrH2/SrF2/SrO, YF3/YCl3, ZrF4/ZrCl4, NbF5/NbCl5, MoF6/MoCl6 all ≤1 µHa.)

    • 5p In + Sn — the [Kr]/[Kr]4d¹⁰ frozen-core penetration (4s/4p Z>36, 4d Z>48 in _rumpf_overlaps/V2CORE) and the In–Xe d-shell ENEG form (Ga–Kr-style negated G1PD/G3PD), both matching atomic_reference.f. H–Kr behaviour is unchanged (new branches dormant for Z≤36; full tests/test_msindo.py green). Still gated, each with a named residual: the later 4d metals Tc–Pd (partner-dependent terms; Rh/Pd-atom edge cases) and the lone-pair 5p Sb/Te/I/Xe (a spurious 5d occupation in lone-pair molecular fields). (Ag/Cd landed separately — see the 2026-06-16 entry above.)

Added: periodic Γ-point orbital localization — Pipek-Mezey + Foster-Boys (2026-06-15)

  • vibeqc.periodic_localise.localise_periodic_gamma(result, basis, system, method=…): localizes the occupied Bloch orbitals of a converged Γ-point periodic SCF into Wannier-like orbitals, returning PeriodicWannierResult (localized coefficients, unitary mixing U, Wannier centers, true position centroids ⟨i|r⟩, per-orbital spreads ⟨i|r²⟩−⟨i|r⟩², and Mulliken populations). Reuses the molecular localizer (vibeqc.localise). Periodic local-correlation route, Stage 2 ([HANDOVER_PERIODIC_LOCAL_CORRELATION.md]).

  • method="pipek-mezey" (2a): Mulliken-population objective — PBC-safe in every regime (no position operator), including crystals whose orbitals wrap the cell.

  • method="boys" (2b): Foster-Boys via the home-cell dipole integrals; true centroids and spreads via the dipole + quadrupole (compute_multipole_moments_lattice, emultipole2). Faithful when the occupied orbitals do not wrap the cell boundary (molecule-in-box / dilute / large-cell). Dense crystals need the Resta periodic position operator ⟨μ|e^{−iG·r}|ν⟩ (a documented follow-up; not yet implemented), as does the multi-k U(k).

  • Γ-point only. Unknown methods raise ValueError (fail closed).

  • Tests (tests/test_periodic_localise.py, 12): unitary mixing, occupied-density invariance, objective increase, populations summing to 1, positive/symmetric spreads, and molecular-oracle matches — periodic Γ PM reproduces molecular PM to ~1.5e-8 (objective) / ~2e-6 bohr (centers), and periodic Boys centroids match molecular Boys to ~2e-6 bohr, on H₄/STO-3G in a 30-bohr box.

Added: SPINLOCK broken-symmetry convergence (spin-schedule + MOM pattern-hold) (2026-06-14)

  • UHFOptions / UKSOptions gain spinlock_mode (off by default), spinlock_iterations, and spinlock_value: a user-selectable, opt-in analogue of CRYSTAL’s SPINLOCK for broken-symmetry magnetic convergence, with two modes the user picks via vibeqc.SpinlockMode:

    • SPIN_SCHEDULE: converge at a locked n_alpha - n_beta = spinlock_value for the first spinlock_iterations cycles, then restart at the multiplicity target from that density (CRYSTAL SPINLOCK n nstep). Two sequential SCFs in run_uhf / run_uks, so the core loop is untouched; the locked phase polarises (e.g. high-spin) before relaxing. spinlock_value is validated against the electron count.

    • PATTERN_HOLD: hold the seeded (atomic_spins) broken-symmetry occupied set by maximum overlap (MOM) for the first spinlock_iterations cycles, then release. New C++ MOM kernel (cpp/include/vibeqc/mom.hpp), the C++-runtime mirror of python/vibeqc/mom.py (Gilbert-Besley-Gill 2008) used by the periodic multi-k drivers, kept algorithmically in lock-step. Protects an atomic_spins seed against early collapse on the periodic multi-k path; it is a bit-for-bit no-op on every molecular system tested (H2/H4/H6) because the density seed already holds the pattern, and ships opt-in for CRYSTAL parity and the future periodic hard-case path.

  • Both modes are off by default; the existing single-point SCF path is unchanged.

  • Tests (tests/test_guess.py): SPIN_SCHEDULE releases to the target (H2 equilibrium, lock triplet then converge to the singlet RHF energy) and validates spinlock_value parity; PATTERN_HOLD is a verified no-op on the converged result.

Added: ATOMSPIN per-atom broken-symmetry spin seed (atomic_spins) (2026-06-14)

  • UHFOptions.atomic_spins / UKSOptions.atomic_spins (a list of +1/-1/0 aligned to atom order) seed a broken-symmetry SAD start, the vibe-qc analogue of CRYSTAL’s ATOMSPIN: +1 tags an atom majority-alpha, -1 majority-beta, 0 unpolarised. This unlocks antiferromagnetic / ferrimagnetic initialisation, which the spin-symmetric SAD split cannot reach (the unrestricted SCF otherwise collapses to the most symmetric solution).

  • Mechanism (GuessEngine::build_open_shell, molecular UHF/UKS): each tagged atom contributes a Hund’s-rule spin-resolved atomic density (open-subshell unpaired electrons placed in the majority spin, e.g. Fe d6 -> moment 4, O 2p4 -> 2), assembled block-diagonally into D_alpha/D_beta. Density-mode for both UHF and UKS; the SCF then refines the pattern and enforces the global n_alpha/n_beta from the multiplicity. Consulted only when the resolved guess is SAD (incl. AUTO->SAD for open shells); pairing it with any other guess raises.

  • Tests (tests/test_guess.py): atomic_spins=[+1,-1] on stretched H2 (STO-3G, R=3.0 bohr) reaches the Coulson-Fischer broken-symmetry minimum E=-0.95101800 Ha, <S^2>=0.774 (PySCF parity) that the symmetric guess misses; AUTO routing, global-sign-flip equivalence, the SAD-guess and length guards, and UKS forwarding (B3LYP) are pinned.

  • Molecular this increment; periodic-Gamma wiring is a follow-up.

Added: Mixed Density Fitting (MDF) for periodic GDF — Γ + multi-k, closed + open shell (2026-06-14)

  • gdf_method='mdf' in run_pbc_gdf_{rhf,uhf,uks} adds Mixed Density Fitting (Sun, Berkelbach, McClain & Chan, J. Chem. Phys. 147, 164119 (2017), the GDF paper’s titular method) to close the all-electron Gaussian-DF accuracy floor. Pure Gaussian DF (compcell / rsgdf) cannot fit steep core pair densities at a tractable plane-wave cutoff; MDF handles the steep part in real space (exact libint Gaussian 2c/3c on the compensated “fused” basis — no mesh) and only the smooth residual via plane waves on a modest mdf_ke_cutoff mesh (default 40 Ha).

  • vibeqc.aux_basis.build_lpq_mdf builds the cderi _MdfCderi(L_gauss, cderi_pw): the compensated-Gaussian fit orthogonalised on the PW-dressed metric J̃ (Eq 20) with the Eq-23 G=0 self-term V̄_P = −π^{5/2}·(A@m), plus the plane-wave residual √coul(G)·ρ_μν(G). The Γ J/K builders are now complex-aware (the real compcell/rsgdf path stays bit-identical).

  • Validated (tests/test_pbc_gdf_mdf.py): the no-PW limit is bit-identical to compcell; the reconstructed ERI matches the dense-mesh rsgdf fit and is mesh-stable; on a Ne-in-box all-electron test MDF is mesh-converged by ke≈60 and matches PySCF MDF (matched aux) to 0.14 mHa, while rsgdf at ke=200 is ~190 mHa over-bound (the floor MDF removes); triplet H2 UHF/MDF matches PySCF UHF Γ to 9.5 µHa (⟨S²⟩=2.0).

  • Multi-k via build_lpq_bloch_mdf + gdf_method='mdf' in run_k{rhf,uhf,uks,rks}_periodic_gdf: the per-(k_bra,k_ket) cderi is the q-only real-space Gaussian fit + the ket-resolved PW residual (+ the G=0 term on diagonal pairs). H2/sto-3g kmesh=(2,1,1) reproduces the published KRHF reference (−1.12013988, Sun-Berkelbach 2017 Table II) to 21 µHa.

  • Reachable from the high-level runner: run_periodic_job(..., jk_method="gdf", gdf_method="mdf", mdf_ke_cutoff=...) (new params; gdf_method=None selects rsgdf — and at Γ closed-shell RHF now also routes through run_pbc_gdf_rhf with exxdiv=’ewald’, see the 2026-06-15 default-Γ fix below). Also via the direct run_pbc_gdf_* / run_k*_periodic_gdf drivers.

  • Scope: Γ + multi-k, RHF/UHF/UKS. The Eq-23 G=0 self-term is monopole-only and validated exact (H2 |gap monopole| = 7.5e-9; the compensated aux carry no dipole) — there is no higher-multipole term. The small residual (0.14 mHa Ne / 21 µHa H2 multi-k) is the modest-mesh fit-representation difference, a mdf_ke_cutoff convergence knob, not missing physics. Same citation as the GDF/rsgdf route (Sun-Berkelbach 2017) — no new entry.

Fixed: default Γ closed-shell RHF/GDF now exxdiv=’ewald’ (PySCF parity) (2026-06-15)

  • run_periodic_job(method="RHF", jk_method="gdf") at Γ now matches PySCF. A plain (dim=3, neutral, no smearing / symmetry / Fock-mixing) Γ closed-shell RHF/GDF job now routes through the PySCF-µHa-validated run_pbc_gdf_rhf (exxdiv='ewald', rsgdf) — the same driver the explicit gdf_method= path and the open-shell Γ UHF/UKS paths already used — instead of the legacy run_rhf_periodic_gamma_gdf molecular-limit (exxdiv=None) bridge. On H2/sto-3g/12-bohr the default energy moves −1.1167447 → −1.1225840 Ha (PySCF KRHF(1,1,1) exxdiv=’ewald’ −1.1225840, ~3 µHa), closing the ~5.8 mHa finite-size Madelung gap that had silently depended on whether gdf_method= was passed.

  • run_krhf_periodic_gdf(kmesh=(1,1,1)) HF likewise delegates its Γ fast-path to run_pbc_gdf_rhf (exxdiv=’ewald’), so kmesh=(1,1,1) is the Nk=1 limit of the multi-k exxdiv=’ewald’ path — no convention discontinuity vs (2,1,1)+, and run_periodic_job default-Γ == explicit kpoints=(1,1,1).

  • The legacy molecular-limit gamma driver is retained as the Γ fallback for the cases run_pbc_gdf_rhf does not handle: RKS, dim<3, charged cells, finite-T smearing, and the symmetry / Fock-mixing convergence aids. Direct calls to run_rhf_periodic_gamma_gdf are unchanged (still the molecular limit). No new citation — the exxdiv=’ewald’ Madelung treatment already shipped in run_pbc_gdf_rhf.

  • Tests: tests/test_periodic_rhf_gdf.py (test_run_periodic_job_gamma_rhf_gdf_default_is_exxdiv_ewald, ...smearing_stays_on_legacy, the Γ==(1,1,1) identity), and tests/test_periodic_k_gdf.py (test_krhf_gdf_gamma_via_tuple_matches_pbc_gdf_rhf).

Fixed: active-space frozen-core dressing for the determinant solvers (2026-06-15)

  • run_job(..., active_space=(n_active, n_elec)) now reports correct absolute energies for genuine frozen-core truncations across the determinant/RDM solvers (fci, selected_ci, cisd, dmrg, v2rdm, transcorrelated_ci). Previously the runner sliced the MO coefficient matrix to a bare orbital block and dropped both the frozen-core one-electron dressing and the constant E_core offset, so n_active < norb reported only the active-only contribution (≈ −15.5 Ha for CAS(4,4)/H₂O instead of below the −74.97 Ha HF total). The runner now projects via the new Hamiltonian.active_space(n_active, n_elec) method, which applies the standard CAS partition — lowest (nelec n_elec)/2 orbitals as a doubly-occupied inactive core — and the proper dressing h̃_pq = h_pq + Σ_c (2⟨pc|qc⟩ ⟨pc|cq⟩) with E_core folded into the nuclear-repulsion term. The dressing reuses the CASCI-validated _frozen_core_dressing, so fci/selected_ci/… and casci agree to numerical precision on the same active window, and the variational sandwich E_FCI E_CAS E_HF holds. The strict-xfail coverage in tests/test_solvers_active_space_api.py (audit finding 1) is replaced by positive assertions (variational sandwich + cross-method agreement + active_space validation). Docs: python/vibeqc/solvers/ACTIVE_SPACE.md, the active-space + N₂ static-correlation sections of docs/tutorial/non_hf_solvers.md, and the new examples/wavefunction/n2_dissociation_active_space.py.

Added: MSINDO CIS excited states — singlet/triplet TDA solver (2026-06-14)

  • vibeqc.semiempirical.methods.msindo_cis ships a working CIS / TDA excited-state solver for the MSINDO/INDO engine: run_cis(Z, coords, n_states, spin) returns singlet or triplet vertical excitation energies and amplitudes, built on the same vibeqc.excited kernel as the HF/DFT TDA path, over the INDO (ia|jb)/(ij|ab) integral blocks. The excited-state density-matrix + 2PDM assembly (assemble_cis_densities and the cisgrad.f T/D_oo/D_vv/GAMM builders, incl. scaled-CIS) is validated against trace/symmetry identities. Method: Foresman, Head-Gordon, Pople & Frisch, J. Phys. Chem. 96, 135 (1992). Tests: tests/test_cis_excitations.py, tests/test_cis_density.py.

  • The analytic CIS gradient (CPHF/Z-vector) is present but gated experimental (cis_gradient(..., _experimental=True) to opt in) — it currently mis-scales the forces (~4-12x vs FD); cis_gradient_fd is the validated finite-difference path. Analytic validation tracked as Phase 6.

Added: BIPOLE ionic-Γ basin-health diagnostic — surface smearing-straddles-gap (2026-06-14)

  • run_pbc_bipole_rks / run_pbc_bipole_uks now emit a basin_warning (logged + on the result object) when finite-T smearing straddles the HOMO-LUMO gap and may have settled a near-metallic basin. Root cause (Gap-B): a minimal basis badly underestimates the gap (MgO/STO-3G ~0.3 eV vs ~7.8 eV experimental), so a smearing_temperature comparable to it fractionally occupies across the gap and lands a wrong basin hundreds of mHa from the physical solution — MgO RKS Γ at T = 0.01 Ha (≈0.27 eV) landed +330 mHa with a 42 mHa entropy term. The diagnostic (smearing_basin_warning in pbc_bipole_common) triggers when smearing is on, there is a genuine gap (>0.02 eV — avoids warning on true metals), fractional occupations are present, and k_B·T ½·gap. It is a diagnostic only — it changes no energy and applies no convergence aid (CLAUDE.md §7: surface a basin problem, don’t bury it). The fix it points the user to is a sub-gap smearing (reduce smearing_temperature until the warning clears, or rely on the gap-independent auto-FMIXING). Unit tests pin the trigger logic (straddle / sub-gap / metal / integer-occ / no-smearing / UKS-min-gap-across-spins); docs/troubleshooting.md documents the guidance.

Added: PATOM / HUECKEL / MINAO molecular initial guesses (2026-06-14)

  • Three further initial guesses, completing the molecular set behind the InitialGuess enum (previously scaffolded as roadmap-pointer throws):

    • PATOM (initial_guess="patom"): SAD density followed by one in-field mean-field re-polarisation step, so the superposed free-atom densities relax in the molecular field before the SCF proper (ORCA PAtom; builds on Van Lenthe SAD). UHF forwards its JKBuilder to the guess engine only for PATOM, so no other guess’s packaging changes.

    • HUECKEL (initial_guess="huckel" or "hueckel"): parameter-free generalised Wolfsberg-Helmholz / extended-Hückel guess over computed atomic-orbital energies (Wolfsberg-Helmholz 1952, Hoffmann K=1.75 1963, and the parameter-free variant assessed by Lehtola 2019).

    • MINAO (initial_guess="minao"): free-atom reference densities built in the ANO-RCC minimal contraction, projected onto the target basis (Knizia 2013 minimal-AO concept; ANO-RCC reference data).

  • All three are density-mode (the engine returns a density the SCF driver consumes directly) and molecular-only in this increment: a periodic SCF with any of them raises a clear “not yet implemented for periodic” error.

  • Citations wired (routes.scf_guess.{patom,huckel,minao}) so a run using each lands its references in the .out / .bibtex output.

  • Tests (tests/test_guess.py): each guess reaches the same converged RHF energy as HCORE (sto-3g and 6-31g*), UHF recovers RHF on a closed shell, PATOM reaches the true OH/6-31G* UHF minimum (not the Hcore false minimum), and the periodic paths raise.

Changed: BIPOLE parity re-based onto CRYSTAL — method-family policy (2026-06-14)

  • tests/test_bipole_fock_ewald_exchange.py: the PySCF exxdiv='ewald' Γ pins are relabeled a secondary cross-family check of BIPOLE’s Γ exchange-split convention, not BIPOLE’s primary parity gate (per the new method-family policy in docs/periodic_jk_routes.md). GDF’s PySCF pins are unchanged (correct family); no assertions changed.

  • The primary gate is the same-family BIPOLE↔CRYSTAL total-energy parity at a matched k-net (MgO anchor, SHRINK 8 8, sealed CRYSTAL14 ref −271.21814375 Ha/FU; staged in examples/regression/crystal_parity/). Wiring it as a routine pytest gate is blocked on BIPOLE multi-k, not the reference: at the matched 8×8×8 k-net run_pbc_bipole_rhf needs cutoff_bohr ~20 for BvK-torus density coverage and the SCF runs >10 min (the PBC-audit E2/E3 multi-k efficiency follow-up, BIPOLE multi-k lane). Tracked in docs/periodic_jk_routes.md § Current status; not claimed done.

Fixed: native-dep update self-heals a wiped shared library (2026-06-13)

  • update.sh / setup_native_deps.sh now rebuild a vendored native dep when its install/ tree is present but the actual shared library was deleted (e.g. third_party/libint/install/lib/libint2.so wiped from a managed venv, so import vibeqc throws ImportError: libint2.so: cannot open shared object file). Previously the rebuild was short-circuited at every layer — git current (no drift), build stamp says “built”, and build_<dep>.sh keys off the surviving CMake config file — so a normal vq admin update never repaired it and the only recovery was the --rebuild-native-deps force flag (the 2026-06-13 maru/saturn fleet incident). The drift check (scripts/_native_stamp.sh: vibeqc_stamp_drifted_deps / vibeqc_stamp_check) now verifies the real .so/.dylib artifact on disk (handling lib/lib64, versioned names, and macOS .dylib), and setup_native_deps.sh self-heals before its idempotent build calls — printing a native lib X missing forcing rebuild line so the repair is visible in vq admin update logs. doctor.sh reports the same MISSING LIBRARY status. tests/test_native_dep_artifact_health.py pins the decision.

Fixed: true-periodic Γ gradient density convention (G1a-2, partial) (2026-06-13)

  • compute_gradient_periodic_rhf_gamma now uses the homogeneous Γ density (D(g) = D_Γ in every cell — the k=0 Bloch phase is 1 in every image, which is the density the SCF energy is built from) for true-periodic cells, instead of the molecular-limit home-only convention (D(g≠0) = 0). On the 1D H chain (a=2 Å, STO-3G) this cuts the analytic-vs-FD residual from ~0.22 to ~0.099 Ha/bohr. The molecular limit (large cells) is unchanged (home-only D, exact). This revises the earlier “~5e-3 K-derivative-buffer” diagnosis: the dominant error was the density convention, not a localized K-buffer bug. The remaining residual is the SCF↔gradient lattice-sum consistency the multi-k Bloch-density gradient (G1c) closes — compounded on that test by a cutoff-non-convergent DIRECT_TRUNCATED FD reference (bare-Coulomb lattice sum). test_h_chain_1d_periodic_matches_fd stays xfail (updated reason); HF/hybrid true-periodic production gradients should still use the FD path until G1c.

Added — reduced-scaling local DLPNO-CCSD solver; M3c parity ratchet flips (2026-06-13)

  • vibeqc.dlpno.ccsd_local_solver.run_local_dlpno_ccsd — the genuine per-pair DLPNO-CCSD solver, the v0.13 local-correlation deliverable. Each pair’s CCSD residual is evaluated in its own PNO basis: all amplitudes are projected into the pair’s domain via the PNO overlap S_pq, and the validated spatial closed-shell residual (_ccsd_cs.cs_ccsd_residual) is applied to that small local space — no full-system tensor is formed. This is the real solver physics that supersedes the O(N⁶), 64-bf-capped correctness pilot (dlpno.ccsd).

  • The M3c parity ratchet flips to a hard gate. Because cs_ccsd_residual is covariant under virtual rotations, the full-domain limit reproduces canonical closed-shell CCSD bit-for-bit — validated to ≤1 µHa on H₂ and H₂O (STO-3G and def2-SVP, canonical and Boys-localised occupieds; tests/test_dlpno_ccsd_solver.py). On H₂ that equals FCI (CCSD≡FCI for two electrons), so the FCI anchor reaches the production solver. The canonical-CCSD cross-check is _ccsd_cs.run_cs_ccsd, FCI-anchored through _ccsd_ref and matching PySCF to 2e-11 Ha. This is the CCSD analogue of the M1/M2 DLPNO-MP2 exactness ratchet, now passing with the real local solver.

  • With truncated PNO domains the cross-domain contractions are the controlled DLPNO domain approximation: 99.9 % E_corr recovery at TCutPNO=1e-7 on H₂O/def2-SVP, with genuine PNO compression.

  • Wired into run_job: method="dlpno-ccsd" now runs the local reduced-scaling solver (LocalCCSDOptions), retiring the pilot’s 64-bf cap (it carries its own generous, overridable max_nbf guard). method="dlpno-ccsd(t)" stays on the FCI-anchored pilot until the local (T) lands. User guide, tutorial 51, a new example (examples/molecular/input-h2o-dlpno-ccsd.py), and tests/test_runner_dlpno_ccsd.py all updated to the new behaviour; the citation surface is unchanged (method-keyed).

  • C++ per-pair residual kernel — the DLPNO local-CCSD hot loop (cs_ccsd_residual, ~86 % of runtime) now runs in compiled C++ when the core provides vibeqc::dlpno_pair_residual: one call replaces the ~90 small numpy einsums per pair per iteration. Bit-for-bit identical to the numpy kernel (≤3e-14 on the residual; solver e_corr agrees to ~1e-17), and 4.1× faster on H₂O/cc-pVDZ, 8.3× on cc-pVTZ (the win grows with the PNO-space size). On by default (LocalCCSDOptions.use_cpp_kernel), with an automatic numpy fallback on cores built before the binding landed; tests/test_dlpno_ccsd_solver.py::TestCppKernel pins the parity.

  • Performance (M3c): two solver improvements, both validated against the parity ratchet (unchanged ≤1 µHa full-domain == canonical CCSD). (1) DIIS error vector fixed — it was the difference of consecutive post-DIIS amplitudes, not the residual R/D; correcting it cuts a water dimer / cc-pVDZ from 48 to 14 iterations (46 s → 14 s, 3.4×), with a residual-norm convergence gate added alongside the energy gate. (2) LocalCCSDOptions.coupling_radius (bohr) restricts each pair’s occupied coupling set to a local neighbourhood — the linear-scaling lever. Exact at a radius wider than the molecule (bit-for-bit == full coupling) and lossless once fragments separate (a 12-bohr next-nearest H₂ is sub-µHa); on an H₂ chain the mean coupled-occupied count (avg_coupled_occ) saturates while the system grows instead of tracking it. It now defaults to a calibrated coupling_radius = 12 bohr — a bit-identical no-op on any molecule under ~12-bohr extent (the common case), and on larger systems a controlled approximation kept below the PNO truncation error (~7 µHa on a 25-bohr H₂ chain vs the ~0.1% PNO error — the same controlled-locality bargain as the default sparse pair list). Set coupling_radius=0 for full coupling, the exact reference the parity ratchet pins (tests/test_dlpno_ccsd_solver.py::TestSparseCoupling). The same locality screens the local (T): a triple is evaluated only when its three occupieds are mutually within coupling_radius (O(N³)→O(N) triples on extended systems), lossless on separated fragments (sub-µHa) and a standard DLPNO-(T0) triple pre-screening on bonded ones (~0.03 kcal/mol on the 25-bohr H₂ chain, below the PNO error; tests/test_dlpno_triples_local.py::TestLocalTriplesScreening). Honest scope: the truncated-domain recovery on compact molecules is still not strictly monotonic in TCutPNO (a separate PNO-truncation effect; the full-domain limit is exact); the C++ port remains tracked in HANDOVER_GATED_ITEMS.md.

  • Local DLPNO-(T): method="dlpno-ccsd(t)" leaves the O(N⁶) pilot. vibeqc.dlpno.triples_local.local_triples_correction evaluates the (T) correction on the converged local DLPNO-CCSD amplitudes per occupied triple in a TNO domain (the union of the triple’s pair PNO spaces, truncated by TNO occupation number via tcut_tno — the triple’s pair-amplitude density eigenvalues; default 0 keeps the full span), reusing the spatial closed-shell per-triple kernel _ccsd_cs.cs_triple_energy — a faithful numpy transcription of the validated C++ (T) (cpp/src/ccsd.cpp, classwise spin integration E(T) = 1/18·S_aaa + 1/2·S_aab), anchored to the FCI-validated spin-orbital reference to machine precision (2e-20; tests/test_dlpno_ccsd_cs.py::TestTriplesParity). The (T) parity ratchet: at full domains with canonical occupieds it reproduces canonical CCSD(T) bit-for-bit (≤ 1 nHa on H₂O STO-3G + def2-SVP vs run_ref_ccsd(compute_triples=True); tests/test_dlpno_triples_local.py). The Boys-localised production path is DLPNO-(T0) (diagonal localised Fock in the denominators); the (T) vanishes (to floating-point noise) for two-electron systems. run_job(method="dlpno-ccsd(t)") now runs the local solver + local (T) (user guide, tutorial 51, example, runner test updated); the O(N⁶) correctness pilot stays reachable by explicitly passing a DLPNOCCSDPilotOptions (the exact-(T) oracle). Honest scope: the spatial (T) carries no spin-orbital redundancy (2× smaller dimensions, 8× fewer triples than a spin-orbital (T)) but is Python-only — a C++ port is the next efficiency step. Citations (Riplinger 2013, Raghavachari 1989) are method-keyed and unchanged.

Changed: EWALD_3D retired as a user route; Γ-only drivers fail closed on image-overlapping cells (§7) (2026-06-13)

  • jk_method='fft_poisson' (Γ-only EWALD_3D) is retired as a user-facing route. It returns a ~2 Ha-wrong energy when periodic images of the basis overlap, and is superseded everywhere by GDF (default) / BIPOLE / GPW. validate_jk_method now raises pointing at those routes (AUTO never selected it); the dispatcher branch is a defense-in-depth fail-closed. The internal drivers run_r{h,k}f_periodic_gamma_ewald3d remain for dilute periodic systems and mechanics tests.

  • The internal Γ-only drivers now fail closed rather than hand back the wrong number (CLAUDE.md §7): they raise when the largest cross-cell AO overlap |S_μν(L≠0)| exceeds ~0.3 — a basis-aware molecular-limit- breakdown signal, not a geometric one. Re-measured this cycle (verify-first): only LiH is actually buggy — overlap 0.41, EWALD_3D −35.9655 vs GDF −33.9940 (Δ=1.97 Ha), because Li’s STO-3G 2s/2p are extraordinarily diffuse (α=0.048). MgO (overlap 0.19, Δ=0.1 mHa) and NaCl (0.12, <1 µHa) run normally — the baseline had assumed them buggy without re-running. (LiH and MgO have near-identical nearest-image contact, 3.86 vs 3.98 bohr, so a geometric criterion would wrongly refuse fine MgO.) An internal allow_dense_ionic=True escape hatch lets mechanics tests exercise the driver on such a cell; the energy it returns is then explicitly unreliable.

  • tests/test_periodic_dense_ionic_energy_parity.py rewritten to assert the fail-closed RAISE on LiH and the surgical sparing of MgO/NaCl (with GDF as a live PASS guard), and removed from the test-gate known_reds baseline → zero tracked reds.

  • Fixed 8 latent plog.warning(...) calls in the periodic EWALD drivers (Γ + multi-k, RHF/RKS/UHF/UKS): ProgressLogger exposes .warn, not .warning, so the “auto_optimize_truncation did not converge” path raised AttributeError instead of warning.

Fixed: post-HF run_job result proxies expose .method (v0.13.0 audit, 2026-06-13)

  • The six attribute-forwarding result wrappers (_CCSDAugmented, _MP2Augmented, _DLPNOMP2Augmented, _DLPNOCCSDAugmented, _OVGFAugmented, _DispersionAugmented) forwarded an unknown .method to the wrapped C++ RHFResult, which has no such attribute, so run_job(method="ccsd").method raised AttributeError and the default citations=True path crashed intermittently (hash-order-dependent) on CCSD / CCSD(T) / MP2 / DLPNO / OVGF results. Each proxy now carries its own .method label (the effective method string: ccsd, ccsd(t), scs-mp2, dlpno-mp2, …), matching the .method contract that SolverResult already satisfies. Found and fixed during the v0.13.0 CC/CI surface audit; pinned in tests/test_runjob_cc_ci_surface.py. Touches the shared runner result-proxy classes only (no engine/physics change).

Verified (v0.13.0 audit): CAS-family + CC/CI run_job surfaces are reachable and cite (2026-06-13)

  • Exhaustive reachability sweep of every CAS-family method= string and option object through run_job (casci/casscf/nevpt2/caspt2/ mrci/fci/selected_ci/dmrg/v2rdm/transcorrelated_ci, with CASCIOptions/CASSCFOptions/CASPT2Options/SelectedCIOptions/ SelectedCIPT2Options), guarding the v0.12 documented-but-unreachable class (multi-root CASCI). All run; tests/test_cas_runjob_reachability.py.

  • nevpt2 and caspt2 added to the public Method literal: they were dispatched and documented (docs/user_guide/non_hf_solvers.md, handovers/HANDOVER_MULTIREF.md) but missing from the type.

  • Confirmed the v0.14 canonical-CCSD(T) / triples= / AutoCI citype= selectors run end-to-end through run_job and their citation routes fire (CCSD → Purvis-Bartlett, CCSD(T) → Raghavachari, CISD → Pople 1977), including the recording semantics (method="ccsd", triples="(t)" records as CCSD(T)) and the NotImplementedError guards for roadmap variants; tests/test_runjob_cc_ci_surface.py.

Fixed: EWALD_3D tight-cell cutoff floor respects auto_optimize_truncation=False (§7) (2026-06-13)

  • The Γ EWALD_3D RHF/RKS drivers unconditionally inflated cutoff_bohr to 18 (and nuclear_cutoff_bohr to 25) for tight 3D cells (V < 1000 bohr³), overriding a user’s explicit auto_optimize_truncation=False. That silently grew the lattice sum until S(Γ) was PSD, so the linear-dependence preflight could never refuse a genuinely non-PSD overlap — the “converged garbage” mode the preflight exists to prevent (CLAUDE.md §7). The floor is now gated on auto_optimize_truncation, so an explicit opt-out gets the user’s cutoff verbatim and the preflight correctly raises LinearDependenceError on a non-PSD S(Γ) (e.g. LiH conventional / STO-3G, Γ min eig −0.16). Fixes test_periodic_lih_conventional_raises_critical_before_scf.

  • The RKS Γ driver now builds the DFT Becke grid after the linear-dependence preflight, so a non-PSD-overlap abort no longer pays the periodic-grid cost (LiH conventional abort: 27 s → 2 s).

  • Corrected a stale energy reference in test_periodic_h2_in_big_box_passes_preflight (−1.1114 → −1.116714, the ω-invariant molecular limit; the old value predated the F2 fix), and removed tests/test_linear_dependence.py from the gate known_slow set (now ~4 s).

Fixed: periodic RKS Γ gradient — exact molecular-limit LDA / GGA / hybrid (2026-06-13)

  • compute_gradient_periodic_rks_gamma now (a) threads the converged V_xc into the gradient kernel so the overlap-Lagrangian uses the gauge-free variational KS Fock eigenvalues — removing the α_HF-scaled exchange-G=0 self-image gauge for hybrids (the RKS analogue of the periodic Γ RHF gradient fix below) — and (b) computes the XC Pulay term via the lattice-summed periodic primitive xc_lattice_gradient_contribution instead of a molecular LDA-only fallback that dropped the GGA σ-piece. The molecular-limit RKS gradient now matches the molecular analytic gradient to ~1e-9 (LDA / PBE) and ~1e-14 (B3LYP / PBE0), and pure-DFT true-periodic gets the exact lattice XC Pulay too. New guards: tests/test_periodic_gradient_g1b.py ::test_h2o_pbe_molecular_limit_matches_molecular and ::test_h2_b3lyp_molecular_limit_matches_molecular. Hybrids’ true- periodic HF-exchange K Pulay still awaits the G1a-2 fix.

Fixed: analytic periodic Γ RHF gradient — gauge-consistent overlap-Lagrangian (2026-06-13)

  • compute_gradient_periodic_rhf_gamma now builds the energy-weighted density (the overlap-Lagrangian Pulay term) from the variational Fock F_var = ∂E/∂D rebuilt from the converged density, instead of the EWALD_3D Γ driver’s reported mo_energies. Those eigenvalues sit on a shifted orbital-energy reference — the α_HF-scaled real-space exact-exchange G=0 self-image — which left the analytic HF gradient ~0.044 Ha/bohr off finite difference on H₂/STO-3G in a 20-Å box (0.1927 vs 0.1486 Ha/bohr); the molecular-limit analytic gradient now matches FD to ≤ 1e-6. Closes the tracked EWALD Γ analytic-gradient known issue (tests/test_periodic_gradient_g1a.py::test_h2_box_matches_fd, the lone periodic known-red), correcting ASE periodic forces on the EWALD route. RKS pure-DFT is unaffected (exchange-free F carries no gauge); the RKS-hybrid molecular-limit correction has a variational_xc_fock hook pending an SCF-grid-matched V_xc thread.

Added: excitation-case-partitioned IC-CASPT2 scaffold (25i M24, 2026-06-13)

  • New python/vibeqc/solvers/_caspt2_cases.py: the framework for an IC-CASPT2 that partitions the first-order interacting space by excitation case (inactive-hole / active / secondary-particle pattern) instead of building one monolithic determinant FOIS. Each case’s active-index count fixes the reduced-density-matrix order its block needs (0-active MP2-like through 3-active 3-RDM, with the 4-RDM of the 3-active H0 block destined for the F3 intermediate), the path to lifting CASPT2 off its small-active-space wall and composing with the selected-CI reference for 30-40-orbital CASPT2.

  • M24 (this milestone) is the classifier + block-organized FOIS, validated to machine precision against the dense determinant engine (_mrpt._ic_caspt2_solve, the exact oracle): caspt2_corr_cases reproduces the dense second-order energy on H2/6-31G, H2O/STO-3G, and LiH/6-31G (the last spanning all eleven external cases, 0-3 active indices), with and without the imaginary shift. The RDM contraction of each block (M25/M26) and dropping the determinant FOIS (M27) build on this. Nothing is wired into the public caspt2() yet; the dense engine remains the shipped, OpenMolcas-pinned default (untouched).

Next minor cycle (v0.13): BIPOLE multi-k corrected-gauge exchange channels (option (b) Phase 3) + analytic-gradient gauge migration (Phase 5 and the tracked EWALD Γ analytic-gradient known issue), periodic driver efficiency (PBC-audit E2/E3), DLPNO M3c.

Fixed: import vibeqc.fetch.references no longer hard-requires BeautifulSoup (2026-06-13)

  • aa6936ab restored the build-test gate by importorskip-guarding tests/test_fetch_references_cccbdb.py, but the underlying packaging issue remained: the reference-data parsers (NIST CCCBDB / WebBook / ATcT) imported bs4 (BeautifulSoup4) at module top, so import vibeqc.fetch.references still hard-required an undeclared dependency, and pip install 'vibe-qc[fetch]' could not parse HTML because beautifulsoup4 / lxml were declared in no extra.

  • bs4 is now lazy-imported at the HTML-parse callsite through a shared references._optional_deps.require_bs4() helper that raises a clear pip install 'vibe-qc[fetch]' pointer only when the parse path runs without it; the BeautifulSoup / Tag annotations move behind TYPE_CHECKING. beautifulsoup4 + lxml are declared in the [fetch] (and [dev]) extras, and the test guard is extended with importorskip("lxml"). Mirrors ee531cc1 (scipy/ASE packaging guard).

Changed — DLPNO-CCSD pilot iterates spatially (M3c step 5b-i) (2026-06-13)

  • The DLPNO-CCSD pilot’s per-iteration residual now uses the spatial cs_ccsd_residual (M3c-5a) instead of expanding to spin-orbital and calling so_residuals every step — a 16× spin-block redundancy dropped from each iteration (spin-orbital is now built once at the end only for the still-spin-orbital (T) correction). The converged energy and (T) are bit-for-bit unchanged: all existing pilot gates (tests/test_dlpno_ccsd.py, recovery tiers + triples) pass, and a new element-wise residual-parity gate confirms cs_ccsd_residual equals the spin-orbital αβ block exactly (tests/test_dlpno_ccsd_cs.py).

  • This composes the spatial residual with the per-pair PNO projection — exactly the two ingredients the per-pair local residual needs — confirming they combine to the correct DLPNO-CCSD energy. It is a constant-factor speedup, not yet the scaling fix: the pilot still expands to the full virtual space. Dropping that expansion (the per-pair local residual) is the remaining M3c-5b work.

Added — spatial closed-shell DF-CCSD residual + solver (M3c step 5a) (2026-06-13)

  • vibeqc.dlpno._ccsd_cs.run_cs_ccsd — the spatial closed-shell DF-CCSD residual and solver, a faithful numpy transcription of the validated C++ kernel (cpp/src/ccsd.cpp, closed-shell spin integration of SGWB 1991 / DePrince-Sherrill 2013). It replaces the DLPNO-CCSD pilot’s spin-orbital per-iteration step (a 16× spin-block redundancy) with the genuine spatial residual — and, more importantly, is the exact template the per-pair local residual mirrors: the same contractions, evaluated in each pair’s PNO basis with cross-pair PNO-overlap projections.

  • Parity-gated (tests/test_dlpno_ccsd_cs.py, 5): reproduces the FCI-anchored spin-orbital kernel (_ccsd_ref) to ≤1 µHa on H₂, H₂O/STO-3G and H₂O/def2-SVP; sits above the full-space CASCI(=FCI) correlation (the anchor gate that caught the broken C++ CCSD); the residual vanishes at convergence. Internal; the local residual that consumes the M3c-4 integral blocks is the next step.

Added — complete local DLPNO-CCSD doubles residual (M3c step 5b-ii, part 2) (2026-06-13)

  • dlpno.ccsd_local.local_t2_residual_doubles — the complete closed-shell T2 (doubles / CCD-level) residual evaluated entirely in each pair’s PNO basis: the ladders (part 1) plus the Ph-symmetrised Fock ladders Ph[Σ_e t_ij^ae F_be Σ_m t_im^ab F_mj] and the three ring intermediates W1/W2/WX, with all cross-pair amplitudes projected in through S_pq. This is the reduced-scaling counterpart of cs_ccsd_residual’s T2 residual — no full-space tensor is formed.

  • Exact in the full-domain limit — when every pair’s PNO space is the full virtual space, all projections are identity and the residual reproduces canonical closed-shell CCD to ~5e-16 on H₂O/def2-SVP with Boys-localised occupieds (tests/test_dlpno_ccsd_local.py). With truncated PNO domains the intermediates’ cross-domain contractions are the controlled DLPNO domain approximation (validated by recovery, not exact parity) — bounded well below the residual scale.

  • What remains for full DLPNO-CCSD: the singles (t1) terms upgrade this to CCSD, then the per-pair update/DIIS solver — the last M3c-5b-ii slice that retires the O(N⁶) pilot (retired DLPNO handover).

Added — local DLPNO-CCSD residual: projection machinery + ladders (M3c step 5b-ii, part 1) (2026-06-13)

  • Cross-pair amplitude projection (dlpno.ccsd_local.pno_overlap, project_amplitude) — S_pq = V_pᵀ S V_q projects a pair’s doubles amplitudes into another pair’s PNO basis, the machinery that couples pairs in a reduced-scaling CCSD residual without forming a full-system tensor. Plus build_pair_occ_pno (the occupied→PNO half-transform B^P_{ma} for all occupieds), from which every CCSD integral block with an occupied index follows by contraction.

  • local_doubles_ladder_residual — the Ph-symmetriser-free part of the local closed-shell T2 residual (base (ia|jb), the hole ladder Σ_mn P_ij(τ_mn) W_mnij, the particle ladder Σ_ef τ_ij^ef W_abef), evaluated entirely in each pair’s PNO basis with cross-pair amplitudes projected in. Validated to machine precision against the full-space residual projected into the pair (tests/test_dlpno_ccsd_local.py) — confirming the projection machinery and the per-pair ladder contractions are exact. The Ph-half (Fock ladders + W1/W2/WX rings) and singles complete the residual that retires the O(N⁶) pilot expand — the remaining M3c-5b-ii work (retired DLPNO handover).

Added — local DLPNO-CCSD integral foundation (M3c step 4) (2026-06-13)

  • vibeqc.dlpno.ccsd_local — the integral primitives for reduced-scaling DLPNO-CCSD: per-pair PNO-basis density-fitted B-tensors (B^P_{ia}, B^P_{ab}, B^P_{ij}) from which every closed-shell DF-CCSD block follows by contraction — K=(ia|jb), J=(ij|ab), the 3-external (ia|bc) and the 4-external ladder (ab|cd), the last two kept factored. Built directly in each pair’s small PNO basis, never forming a full-system tensor.

  • Integral-parity gated (tests/test_dlpno_ccsd_local.py, 8): each local block equals the canonical full-virtual-space integral projected into the PNO space (U = C_virᵀ S V_pno) to 1e-11, for full and truncated PNO spaces. Internal foundation for the per-pair local residual that replaces the O(N⁶) pilot expand/project — the next M3c step (now tracked in HANDOVER_GATED_ITEMS.md).

Changed — DLPNO-MP2 sparse pair lists on by default (M3c step 2) (2026-06-13)

  • Distant-pair screening is now on by default (tcut_pairs default 0.0 1e-6): occupied pairs whose semicanonical dipole-dipole estimate is negligible are treated at the estimate level (e_distant) instead of with the full PNO machinery, turning the O(N²) pair list into O(N) for extended systems. The default is conservative and a no-op on compact molecules — everything is within dipole_r_min (8 bohr), so nothing is screened and every existing result (and the exactness gates) is bit-for-bit unchanged.

  • Sub-quadratic growth demonstrated (tests/test_dlpno_scaling.py): on an H₂ chain, doubling the system (4 → 8 units) grows the total pair count ×3.6 (quadratic) but the kept-pair count only ×2.3 (linear), at < 1e-4 Ha cost to E_corr vs the unscreened run. Set tcut_pairs=0 for the exactness configuration. The dipole estimate is crude; a calibrated MP2-pair-energy screen is a later refinement.

Added — DLPNO-MP2 local density fitting (M3c step 1) (2026-06-13)

  • DLPNOMP2Options.local_df (+ fit_buffer) — the first reduced-scaling primitive: each pair’s exchange integrals are refit from the raw 2-/3-centre integrals using only the auxiliary functions in its fit domain (the pair’s PAO domain extended by fit_buffer bohr), with the local metric V_[ij], rather than the global RI metric. A full fit domain refits exactly, so the exactness limit is unchanged — local_df=True, fit_buffer=1e9 reproduces canonical RI-MP2 to ≤ 1 µHa (canonical and Boys-localised). Default local_df=False is bit-for-bit the original global path.

  • Locality demonstrated (tests/test_dlpno_local_df.py): on an H₂ chain, doubling the system (4 → 8 units, n_aux 112 → 224) leaves the maximum per-pair fit dimension unchanged at 56 — the O(1)-per-pair fit cost that makes DLPNO scale. DLPNOMP2Result gains fit_dim_per_pair. This increment establishes and validates the sparse-maps fitting primitive; the integral-direct local 3-centre build (avoiding the full T tensor) and flipping the default are the next M3c increments.

Added: selected-CI reference for single-state NEVPT2 / CASPT2 (25i, 2026-06-12)

  • nevpt2(..., selected_reference=True) and caspt2(..., selected_reference=True) (and, through run_job, method="nevpt2"/"caspt2" with casscf_options=CASSCFOptions(ci_solver="selected_ci", ...)) build the PT2 zeroth order on the reference’s own selected-CI wavefunction instead of re-solving the full CAS. Previously this composition raised (“requires the exact CI backend”). Both engines are determinant-based (SC-NEVPT2’s strongly-contracted external groups and IC-CASPT2’s Ê_pr Ê_qs|0⟩ contracted space are applied to the reference state, and the CAS projection is occupation-pattern-based), so the reference may be a truncated wavefunction whose full CAS is intractable, with no high-order RDM formed. Implemented as an optional reference thread through _semicanonical_prep; the active block is invariant under the core/virtual semicanonicalization, so the supplied wavefunction stays valid in the rotated basis.

  • The result is the MR-PT2 of that reference; it converges to the full-CAS MR-PT2 as the selection tightens and reproduces the dense reference path exactly at the full-selection limit. Validated (tests/test_solvers_mrpt_parity.py): full-limit selected CASPT2 == dense explicit engine to 1e-10 and == live OpenMolcas &CASPT2 (H2O/STO-3G CAS(4,4)) to 2.6e-8 Ha; full-limit NEVPT2 == dense; feeding an exact CI through the path is bitwise-identical to the default; a truncated reference sits above dense and converges monotonically.

  • Out of scope (rejected with a clear error): multi-state MS/XMS-CASPT2 (its model space re-solves a multi-root CASCI over the full active space), method="mrci", and the IPEA shift (needs a pseudocanonical active rotation that would change the supplied CI vector). The default exact-CI MR-PT2 paths are unchanged (150 parity tests green).

Added: dry-run peak-memory estimate for vq submit auto (Inc 2, 2026-06-13)

  • VIBEQC_DRY_RUN_ESTIMATE=1 (set alongside VIBEQC_DRY_RUN=1) makes the dry-run short-circuit additionally compute the job’s peak-memory estimate and record it in the .system manifest as [memory].estimate_bytes (estimate_memory(...).total_bytes, with the 1.2× headroom already applied). Off by default, so the cheap output-discovery dry-run stays cheap; best-effort, so a failed or inapplicable estimate (semiempirical / MLIP methods) just omits the key and never breaks the dry-run.

  • Producer half of memory-aware vq submit auto RAM-fit placement; the vq consumer (vibeqc_preflight parsing [memory].estimate_bytes, the with_estimate toggle) already shipped. New vibeqc.output.is_dry_run_estimate_requested() and a dry_run_manifest(..., estimate_bytes=…) parameter; ManifestUpdater threads the estimate into the existing [memory] section. Tests: tests/test_output_dry_run.py, tests/test_runner.py.

Added — GS1 periodic geometry symmetrisation helpers (v0.13, 2026-06-12)

  • New vq.detect_spacegroup(system, symprec=...) helper runs spglib space-group analysis on a PeriodicSystem without mutating system.symmetry; use attach_symmetry only when downstream k-point / integral code should see the attached symmetry.

  • New vq.symmetrise(system, symprec=..., to_primitive=False) returns a fresh PeriodicSystem plus a SymmetriseReport containing before/after space groups, atom counts, cell volumes, and RMS / maximum atomic cleanup displacement. The input system is left untouched. The first milestone is deliberately 3D-only; lower dimensional slabs/polymers still use the existing attach_symmetry introspection path.

  • read_poscar(..., symmetrise=True) opt-in runs the parsed POSCAR through the same GS1 pipeline and returns the SymmetriseResult. The default read_poscar(path) behavior remains unchanged and still returns a Crystal.

Added — GS2 periodic Extended-XYZ reader path (v0.13, 2026-06-12)

  • from_xyz(..., periodic=True) now reads Extended-XYZ Lattice="..." and pbc="..." comment tags into a PeriodicSystem, converting ASE-style row-vector lattices in Ångström to vibe-qc’s column-vector bohr convention. Supplying an explicit lattice= matrix also selects periodic mode for plain XYZ files.

Added — vqfetch list-candidates (VFETCH-X1, 2026-06-12)

  • New CLI subcommand vqfetch list-candidates --formula MgO queries the OPTIMADE federation for multiple candidate structures, dedup by structural hash, and displays a tabular report ranked by space-group plurality. Tabular stdout: provider, ID, space group, lattice constant (a, Å), atom count, dedup-merge count.

  • New _render_candidates_table() rendering function in vibeqc.fetch.cli — reusable for downstream tools.

  • Wraps the existing fetch_optimade(max_results=N, dedup=True) Python API (shipped in v0.8.0). Flags: --max-results, --provider, --no-dedup, --no-cache, --cache-only. Tests: tests/test_fetch_optimade.py (5 new tests). Docs: docs/user_guide/external_structures.md.

Added — MP property-field augmentation (VFETCH-X3, 2026-06-12)

  • client_mp.py: when $MP_API_KEY is set, fetch_mp() now fetches the Materials Project summary record and augments the returned PeriodicSpec with: band gap, formation energy per atom, energy above hull (all in Provenance.notes), and refined is_open_shell / magnetic_moments from MP magnetic data (gated on total_magnetization > 0.1 μ_B/cell).

  • _fetch_mp_properties() — lightweight urllib call to the MP REST API (api.materialsproject.org); falls back gracefully on any transport error (the OPTIMADE-only spec is returned unchanged).

  • _augment_spec_with_mp_properties() — folds MP properties into a spec without mutating the input.

  • heuristics.open_shell_default() already accepted mp_is_magnetic / mp_total_magnetization — the API was designed for this; no heuristics.py changes needed. Tests: tests/test_fetch_mp_properties.py (10 new tests).

Added — NIST WebBook reference-data fallback (VFETCH-X4, 2026-06-12)

  • New client_webbook.py (460 lines): fetches thermochemistry from the NIST Chemistry WebBook (SRD 69) for molecules CCCBDB doesn’t cover. Returns sparse ExperimentalReference records with ΔHf° (gas + condensed), entropy, heat capacity, IE, and EA. No atomization energies, vibrational data, or geometry — consumers should check ref.atomization_energy_kcal_per_mol is None before comparing.

  • vqfetch reference --source webbook --cas <CAS> now dispatches to the WebBook client. The default --source cccbdb behaviour is unchanged.

  • Parses the semi-structured WebBook HTML via BeautifulSoup with regex extraction for ΔHf°, S°, C_p, IE, and EA. Rate-limited (1.5 s spacing) with declared User-Agent and Retry-After honour.

  • Same FetchCache pattern (30-day TTL, --no-cache / --cache-only flags) as CCCBDB.

  • from_xyz(..., symmetrise=True) runs the parsed periodic system through the GS1 standardisation helper and returns a SymmetriseResult. The default from_xyz(path) molecular behavior is unchanged.

  • pbc masks are mapped onto vibe-qc’s dim=1/2/3 contract for prefix-periodic systems (T F F, T T F, T T T); non-prefix masks are rejected with a clear error instead of silently rotating axes.

Added — GS2 CIF reader path (v0.13, 2026-06-12)

  • New read_cif(path) / Crystal.from_cif(path) reader parses CIF cell lengths/angles, fractional _atom_site_* loops, uncertainty suffixes like 4.210(1), and standard CIF symmetry-operation loops (_space_group_symop_operation_xyz / _symmetry_equiv_pos_as_xyz). Asymmetric-unit sites are expanded to the full unit cell before constructing Crystal.

  • read_cif(..., symmetrise=True, to_primitive=True) routes the parsed crystal through the GS1 standardisation pipeline and returns SymmetriseResult, matching the POSCAR and Extended-XYZ reader contract.

Added — GS3 standardised orbit-compression wrappers (v0.13, 2026-06-12)

  • New compute_overlap_lattice_symmetrised_with_orbits(system, basis_name, options, ...), compute_kinetic_lattice_symmetrised_with_orbits, and compute_nuclear_lattice_symmetrised_with_orbits helpers run symmetrise(...), rebuild the named basis on the cleaned cell, use the attached symmorphic operations, and return a SymmetrisedOrbitIntegralResult containing the cleaned system, rebuilt basis, symmetrisation report, compressed orbit view, and full LatticeMatrixSet.

  • This closes the GS1/GS2 -> SYM3a workflow: near-symmetric imported cells no longer need manual symmetrise / basis rebuild / symmorphic_operations plumbing before one-electron lattice-integral orbit compression becomes effective.

Added — K6 generalized regular k-point grids (v0.13, 2026-06-12)

  • KPoints.generalized_regular(system, grid_matrix) builds explicit generalized regular grids from 3D integer HNF matrices. Diagonal matrices reproduce Gamma-centred Monkhorst-Pack grids; off-diagonal matrices generate sheared regular grids with det(grid_matrix) points.

  • KPoints.optimal(system, target_n_kpts, metallic=False) performs a bounded on-the-fly HNF search at the requested determinant and chooses the grid with the largest shortest periodic Cartesian k-point distance. metallic=True uses a 4x effective target count, matching the existing density-helper convention.

  • KPoints.from_database(system, lattice, order, special=False) fetches the GMU/NRL pre-defined k-point tables on demand (sc, bcc, fcc, and hex families), parses lattice-coordinate fractional k-points plus normalized symmetry weights, and returns a weighted explicit KPoints mesh without bundling remote table data in the source tree.

  • Citation routing now includes the Wisesa-McGill-Mueller 2016 GR-grid paper and the Wang-Wisesa-Balasubramanian-Dwaraknath-Mueller 2021 on-the-fly generation paper under numerics=["generalized_regular_kgrid"], plus the GMU/NRL k-point table route under numerics=["kpoint_database"].

Added: canonical CCSD(T) first-class public API + user guide (2026-06-12)

Ships now: the public-API re-exports and the user guide, over the existing dense closed-shell-RHF CCSD/CCSD(T) pilot (whose limits are documented below). The broader coupled-cluster ladder is roadmap-tracked for v0.14.

  • CCSDOptions, CCSDResult, CCSDIteration, run_ccsd, and chemical_core_orbital_count are now re-exported from top-level vibeqc, matching the rest of the molecular public API. The lower-level vibeqc.cc module remains supported.

  • New docs/user_guide/ccsd.md documents run_job(method="ccsd"), run_job(method="ccsd(t)"), the low-level run_ccsd(...) surface, result fields, validation anchors, frozen-core handling, and the current dense closed-shell-RHF pilot limits.

Added: CCSD triples= selector surface (2026-06-12)

Ships now: the triples= selector and the CCSD/CCSD(T) recording semantics, over the closed-shell pilot. The named higher-order triples variants remain roadmap-tracked for v0.14 and raise NotImplementedError rather than falling back to standard (T).

  • CCSDOptions(triples="none" | "(t)") and the CCSDOptions.triples property now provide the human-readable selector planned for the v0.14 coupled-cluster ladder while preserving the existing compute_triples boolean.

  • run_job(..., method="ccsd" | "ccsd(t)", triples=...) threads the selector through the high-level driver. The effective CCSD/CCSD(T) choice controls the output header, coupled-cluster block, and citation route, so method="ccsd", triples="(t)" records as CCSD(T) and method="ccsd(t)", triples="none" records as CCSD.

  • Named future variants (A-CCSD(T), CCSD[T], CCSD+T(CCSD)) are recognized and raise NotImplementedError until their kernels land, avoiding silent fallback to standard (T).

Added: AutoCI-style citype= selector surface (2026-06-12)

Ships now: the citype= string selector over the implemented CISD / CCSD / CCSD(T) paths. The roadmap CI/CC ladder names remain unimplemented (v0.14) and raise NotImplementedError.

  • run_job(method="ci", citype="cisd") now exposes the existing fixed-space variational CISD solver through the high-level molecular workflow. Output uses the shared non-mean-field solver block and the method route cites Pople-Seeger-Krishnan 1977.

  • vq.cisd, vq.CISDOptions, and vq.CISDResult are now top-level public exports, matching the CCSD public-API polish for the implemented CI/CC ladder. run_job(..., cisd_options=...) forwards max_excitation, nroots, and max_det into the high-level CISD route.

  • citype="ccsd" and citype="ccsd(t)" alias the canonical CCSD / CCSD(T) paths, so the v0.14 CI/CC ladder has one string selector for the implemented entries.

  • Roadmap names (ccd, qcisd, qcisd(t), cepa(n), cc2, cc3, ccsdt) are recognized and raise NotImplementedError until their kernels land, avoiding accidental fallback to CISD or CCSD.

Changed: release tooling — regen-script version preflight + --release-banner (2026-06-12)

  • scripts/regenerate_doc_examples.py refuses a job interpreter whose vibeqc.__version__ differs from pyproject.toml — a subprocess preflight runs before any job and names both versions, the interpreter path, and the imported module path in the refusal. This mechanically catches the two traps that produced wrong bundled banners at the v0.12.0 cut: a worktree run silently resolving the MAIN repo’s .venv (git-common-dir fallback), and a stale editable install whose reported version was baked at pip-install time (--allow-version-mismatch overrides; never for release regens). Regression-tested in tests/test_regenerate_doc_examples_preflight.py.

  • New --release-banner flag + VIBEQC_BANNER_FORCE_RELEASE=1 env override close the structural gap that release regens run before the tag exists, so build_info()’s exact-tag-at-HEAD rule could never render Release vX.Y.Z in bundled outputs. The override flips banner rendering onlybuild_info() and the .system manifest keep recording truthful is_release/SHA/dirty provenance, a dirty tree still renders a (dirty) marker, and the flag refuses dev/pre-release versions and the --allow-dirty / --allow-version-mismatch combinations.

  • docs/release_process.md step 3 rewritten around the hardened flow (pip refresh → preflight → --release-banner; the tag points at the regen commit).

Added: BIPOLE corrected exchange gauge at multi-k — q≠0 LR-exchange channels (option (b) Phase 3, 2026-06-11/12)

  • All four BIPOLE drivers now run the corrected gauge at multi-k with use_exchange_ewald_split=True (RHF; RKS with α_HF-scaled hybrid channels; UHF/UKS per spin). The Ewald exchange split generalises per k-point as K(k) = K_SR(erfc ω) + K_LR(erf, q+G≠0) + (ξ_M^sc π/(V_sc·ω²))·S(k)D(k)S(k): the LR term couples every k-point pair through momentum-transfer channels q = k k′ evaluated on q-shifted reciprocal meshes (compute_K_long_range_at_k / KExchangeLongRangeCache, reusing the J^LR Bloch-pair-FT machinery and its shared ω/K_max envelope), and the G=0 correction uses the BvK-supercell Madelung constant (probe_charge_madelung_supercell, = PySCF’s multi-k _ewald_exxdiv_for_G0) with the supercell volume V_sc = n_k·V in the erfc-K=0 removal. Per-k densities for the corrections come from an exact BvK-torus Bloch-fold inversion (bvk_torus_density_matrices) — valid for orbital, SAD-local, damped, ODA-mixed, and warm-start densities alike.

  • Validation: H₂/STO-3G 12-bohr box [2,1,1] lands +0.12 mHa (cutoff 7) / −0.002 mHa (cutoff 12) from out-of-process PySCF KRHF GDF (−1.1201398824, exxdiv=’ewald’) — truncation-scale, vanishing with cutoff like the Γ gauge; and the [2,1,1] run matches the Γ-split of the explicitly doubled supercell to +0.0001 mHa per cell at matched ω (the supercell-unfolding identity, pinned at 1e-5). Supercell-Madelung pins vs PySCF tools.pbc.madelung at 1e-9 (H₂ box [2,1,1]/[2,2,2], MgO [2,2,2]). RKS [2,1,1]: SVWN +0.043 mHa / PBE0 +0.062 mHa vs PySCF KRKS. UHF triplet lattice +0.034 mHa and UKS-PBE0 +0.77 mHa (the documented Γ grid offset, mesh-independent), with k-sampling shifts Γ→[2,1,1] agreeing with KUHF/KUKS to 0.04 / 0.013 mHa. Open-shell refs pin the per-cell triplet with explicit nelec=(4,0) — PySCF’s cell.spin at multi-k means the BvK-supercell TOTAL, a documented trap — at exactly the fixture geometry (1.4 bohr; a 0.74-Å cell shifts the steep triplet curve by ~1 mHa).

  • The corrected gauge is the DEFAULT at multi-k too (Phase-5 flip, 2026-06-13), matching the Γ default — use_exchange_ewald_split auto-resolves ON for any 3D run under the Ewald J split. The supercell-unfolding identity (+0.0001 mHa/cell) is the rigorous internal proof that the multi-k channels reproduce the PySCF-validated Γ machinery exactly; the MgO [2,2,2] c12 heavy parity (examples/regression/bipole_parity/mgo_multik_parity.py) is staged for the queue as belt-and-suspenders. Pass use_exchange_ewald_split=False for the legacy gauge (kept for the legacy-gauge analytic gradient + parity diagnostics). The corrected multi-k gauge needs a Monkhorst-Pack mesh; under the auto default an ad-hoc k-list (band path / explicit list) at multi-k falls back to the legacy gauge with a log note (no breakage for prior multi-k k-list runs), while explicit True with such a mesh raises.

  • Analytic gradients (Phase-5 decision, 2026-06-13): FD ratified as the permanent production force/stress path. compute_bipole_gradient_fd differentiates the actual driver energy, so it is exact in either gauge; bipole_optimize / run_periodic_job(optimize=True) / periodic NEB default to it. The analytic kernels implement the LEGACY Γ-local gauge and refuse corrected-gauge SCF results (ValueError, landed v0.12.0 6af56d91) — so they now require an explicit use_exchange_ewald_split=False SCF. Migrating the analytic kernels to the corrected-gauge functional (re-derive W/Pulay for the q-channel K_LR + SDS Madelung + BvK-torus density derivative) is a deferred future milestone, parallel to the periodic-SCF chat’s EWALD-route gradient migration — NOT a v0.13.0 deliverable. (Supersedes the earlier “multi-k KS analytic gradient now runs / warns instead of raising” status — that described the legacy-gauge path before the corrected-gauge refusal + default flip.)

[v0.12.2] — 2026-06-12

Docs-only patch on Knuth’s Beaver: publishes the generalized cluster build guide (cherry-picked from main).

Changed

  • Cluster build guide generalized. The cluster provisioning page (docs/cluster_twin.md) is rewritten as a general university-HPC guide — institute-specific hostnames, queue/node names, and machine specs are replaced by placeholders, the gateway hop is now an optional pattern, and all reusable content (two-phase online/offline build, offline wheelhouse, BLAS pinning, TORQUE job recipe) is preserved (a6f2d1cf). No code changes; the public docs site picks this up on the release advance.

[v0.12.1] — 2026-06-12

Patch on Knuth’s Beaver: the v0.12 post-release-audit fixes plus three optimizer-honesty fixes, cherry-picked from main (original SHAs noted).

Fixed

  • Hybrid TDA/TDDFT ran with zero exact exchange — the response kernel hardcoded c_x = 0 for every DFT functional; hybrids (B3LYP, PBE0, …) now use the functional’s hf_exchange_fraction at all three kernel sites, with a hybrid regression suite (1a89da6a).

  • MPI GPW Hartree-J was wrong for world size > 1 — rank-local z-slabs were elementwise-summed and the Poisson solve saw a mostly-zero grid; full-grid assembly fixed with a simulated-2-rank parity test (5df5e765); stale blanket-MPI documentation claims retired (1361c967).

  • Geometry optimization walks the surface the final energy reports — native optimizer backend for wavefunction methods (cac4d26e) and CAS methods (888305d8); semiempirical optimization trajectories carry per-frame energies (b0752808).

  • Multi-root CASCI is reachable via run_job(casci_options=...), and the docs-audit API-name corrections land in docs/features.md (6404e2f7).

  • basis-library path test follows the documented LIBINT_DATA_PATH resolution order — no more environment-dependent phantom red on dev boxes carrying the build/basis_library overlay (1edef5c1).

[v0.12.0] — 2026-06-12 — Knuth’s Beaver

AI-generated Knuth's Beaver codename artwork for vibe-qc v0.12.0

Changed — v0.12.0 cut verification: BIPOLE analytic-gradient gauge refusal completed; EWALD Γ pin re-baselined (2026-06-11)

  • BIPOLE UHF/RKS/UKS analytic gradients now refuse corrected-gauge results, mirroring the existing RHF refusal: compute_bipole_gradient_{uhf,rks,uks} raise a clear ValueError when handed an SCF result computed under the option-(b) Ewald exchange split (the default at 3D Γ under the J split since Phases 4a/4b). The analytic kernels implement the legacy Γ-local gauge; at the v0.12.0 cut the un-gated UHF path silently disagreed with the driver’s FD by 5.3 Ha/bohr on triplet H₂. Production forces in the corrected gauge use compute_bipole_gradient_fd (the optimizer / periodic-NEB default), as before. Tests pin both contracts — legacy-gauge exactness (~1e-7 vs FD with use_exchange_ewald_split=False) and the refusal (tests/test_bipole_gradient.py); the tests/test_dft_plus_u.py +U-contribution tests pin the legacy gauge explicitly.

  • EWALD_3D Γ reference re-pinned to the physical value: after the 86c39851 gauge restoration, the 30-bohr-box H₂/STO-3G driver reproduces the isolated-molecule limit (−1.1167 Ha; run_rhf gives −1.1167143 at the same geometry). The old −1.1114 pin was the pre-restoration artifact (tests/test_eigs_preflight.py).

  • Known issue (tracked, ships in v0.12.0): the analytic EWALD Γ gradient (compute_gradient_periodic_rhf_gamma, consumed by ASE periodic forces on the EWALD route) still differentiates the pre-restoration assembly — on a dilute H₂ box it gives 0.193 Ha/bohr where the restored driver’s FD and the isolated-molecule FD agree on 0.149 (to 6 decimals). FD periodic gradients are correct and remain the production path. Tracked in scripts/test_gate/known_reds_baseline.json; the gauge migration of the analytic kernel is the periodic-SCF chat’s next milestone.

    Resolved in v0.13 (Unreleased, b70ce004). The gauge migration landed: compute_gradient_periodic_rhf_gamma now builds the energy-weighted density (overlap-Lagrangian) from the variational Fock ∂E/∂D rebuilt from the converged density, instead of the EWALD_3D Γ driver’s gauge-shifted mo_energies, so the H₂-box analytic gradient matches FD to ≤ 1e-6 (0.193 → 0.149 Ha/bohr). Removed from known_reds_baseline.json; ASE periodic forces on the EWALD route are corrected. See the [Unreleased] “gauge-consistent overlap-Lagrangian” entry.

Changed: native D4 backend gated as experimental — defective reference dataset (2026-06-11)

  • compute_d4(..., backend="native") now emits D4NativeExperimentalWarning on every call. The 2026-05-31 audit confirmed the shipped d4_reference_data.json produces reference C6 coefficients 4–32× too large (CH₄ C6(H,H) = 62.1 a.u. vs dftd4’s 1.94) and dispersion energies ~8–32× too attractive (water dimer 8.1×, methane dimer 19.6× at PBE). Root cause: the generator stores each reference system’s whole-molecule α(iω) as the per-atom reference polarizability — the per-atom extraction of Eq. 6 of Caldeweyher et al., J. Chem. Phys. 150, 154122 (2019) was never implemented (the 2026-05-22 aug-cc-pVDZ regeneration upgraded the basis but kept the defect). Gated rather than regenerated for this release: a correct regeneration needs new extraction code, a heavy compute batch, and parity validation, and re-derivation must stay first-principles per the 2026-05 licensing decision (handovers/HANDOVER_D4_NATIVE.md § 1). Defaults are unaffected — the default backend="dftd4" (and the D3 dftd3 backend) are audit-verified bit-exact to the upstream packages. generate_reference_dataset likewise warns at call time so a basis-only regeneration can’t ship the defect again. Strict-xfail parity tests vs the dftd4 package (pairwise C6 + dimer energy, tests/test_d4_model.py) pin the un-gating bar: they flip to a suite error once a regenerated dataset meets it, forcing gate removal in the same commit. The experimental GFN2-xTB model’s post-SCF D4 term consumes this dataset and over-binds dispersion accordingly; its experimental warning + docs now say so.

Fixed: CPCM analytic gradient — off-diagonal ∂A/∂R term was half its true value (2026-06-11)

  • vibeqc.cpcm_gradient (and ASE get_forces() with solvent= set) carried a systematic ~3e-4 Ha/bohr error vs full-energy finite differences (water/6-31G; flagged by the 2026-05-31 audit, confirmed live on HEAD). Root cause — not the audit’s hypothesized missing cavity-point-motion / q-response terms (both verified present and correct to ~1e-11): the off-diagonal cavity-derivative term (1/(2f)) qᵀ(∂A/∂R)q counted only the row chain-rule channel of A_ij = 1/|s_i s_j|, which depends on the positions of both cavity points — an exact factor 2 on that piece, invisible to the translational-invariance test. Post-fix the assembled gradient is FD-truncation-exact: max deviation 2.4e-6 / 1.5e-7 / 5.9e-9 Ha/bohr at h = 4e-3 / 1e-3 / 2e-4 — clean O(h²). Affects CPCM geometry optimisations since v0.9.1 (below loose fmax thresholds, but shifts tightly converged minima).

  • Test gap closed (this is why it shipped): test_cpcm_gradient_matches_fd_water_in_water tightened from atol=5e-3 (~16× looser than the bug) to atol=1e-5 (measured agreement ~5e-7), and the A-matrix term gained direct per-component FD tests at fixed q — test_A_matrix_gradient_matches_fd (pins the two-channel factor) and test_A_matrix_gradient_diagonal_switching_matches_fd (isolates the diagonal Scalmani-Frisch switching-derivative channel via a single nonzero charge, which the translational-invariance test cannot see).

  • Retired the gradient.py module-docstring claim that the in-solvent energy-weighted density is an approximation “vs the strictly correct gas-phase W”: the in-solvent W is exactly right for the total functional (Pulay term takes the eigenvalues of the operator the density diagonalises — the solvated Fock), now demonstrated by the 6e-9 end-to-end FD agreement.

Changed: dispersion="d4" cites the full three-paper upstream set (2026-06-11)

  • routes.methods.d4 now fires Caldeweyher 2017 (precursor: charge-dependent ζ(q) reference-polarizability scaling) and Caldeweyher-Mewes-Ehlert-Grimme 2020 (periodic extension, PCCP 22, 8499) alongside the 2019 method paper — the three papers the upstream dftd4 authors ask users to cite. Maintainer decision 2026-06-11, recorded on the route row: the 2020 paper is routed deliberately even though vibe-qc’s D4 is molecular-only (periodic runs still apply D3(BJ)); the set is not split per platform. All three are print = true (references block + .bibtex / .references, not just the .system manifest). Damping-parameter fit papers continue to route separately via [routes.dispersion_params]. Coverage + e2e gates extended (test_citations.py, test_feature_citation_end_to_end.py).

Added: selected-CI PT2 reaches run_job; legacy selected-CI PT2 made coherent (2026-06-11)

  • CASSCFOptions(pt2=SelectedCIPT2Options(...)) (requires ci_solver="selected_ci"): after convergence, the per-root Epstein-Nesbet PT2 correction is computed on the final selected wavefunction in the converged orbital basis (vibeqc.solvers.selected_ci_pt2; deterministic or semistochastic per the options). The headline energy stays the variational CASSCF value; the PT2 estimate surfaces on SolverResult.selected_pt2 and as a dedicated .out block (E_PT2, E_var+PT2, error bar for semistochastic runs), and the Sharma-2017 citation reaches the references block. Exact-CI backends reject the option with a clear error (no external perturbers).

  • Fixed: the legacy method="selected_ci" do_pt2_correction estimate is now the coherent Epstein-Nesbet PT2. The historical implementation summed (c_I H_aI)^2 per (generator, perturber) pair, neglecting cross-generator interference in the numerator; perturber numerators now accumulate over all generators before squaring (Sharma et al, JCTC 13, 1595 (2017), Eq. 4). The unrestricted path routes through the same shared kernel as selected_ci_pt2 (single canonical implementation); the spin-restricted (seniority-zero) path applies the same regrouping. Measured effect: H2O/STO-3G truncated-space PT2 moves by +0.23 mHa (the incoherent sum overestimated |E_PT2|); pinned against an independent dense-matrix Eq.-4 oracle to 1e-13 in tests/test_solvers_selected_ci.py. method="selected_ci" / transcorrelated_ci total energies that include the PT2 correction shift accordingly.

Fixed: CASSCF orbital_step="auto" hands over to Newton-CG on Super-CI stalls (2026-06-11)

  • casscf(stall_to_nr=1e-4): a second auto-mode trigger alongside the switch_to_nr gradient threshold. When Super-CI’s energy progress over the last 3 macro-iterations falls below stall_to_nr per iteration while |g| is still above the gradient switch, the optimizer hands over to Newton-CG. This is the stiff large-CAS regime where the diagonal-Hessian step used to plateau without ever reaching the switch (the documented selected-CI CAS(14,14) tail: |g| ~ 2-6e-2, ~5 µHa/iteration for 100+ iterations); near convergence the same trigger merely starts the NR endgame early. stall_to_nr=0 restores gradient-only switching (pinned).

  • Measured on the previously stalling case (N2/6-31G CAS(14,14) selected-CI, 20k determinants): "auto" now converges in 29 macro-iterations / 110 s to the same stationary point as the hand-picked "nr" (-109.07272812 vs -109.07272740), where it previously ran 100 iterations / 531 s without converging. The “prefer nr for selected large-CAS” guidance is retired from the user guide; existing fast-lane and slow regression suites are unchanged.

Added: semistochastic Epstein-Nesbet PT2 on selected-CI wavefunctions (25i, 2026-06-11)

  • vibeqc.solvers.selected_ci_pt2: the SHCI perturbative stage (Sharma, Holmes, Jeanmairet, Alavi & Umrigar, JCTC 13, 1595 (2017)) as post-processing on a converged selected_casci result. Per root, the second-order Epstein-Nesbet correction over perturbers outside the selected space with COHERENT numerators (contributions summed over all generators before squaring, Eq. 4), screened at |H_ai c_i| >= eps2 (Eq. 5) with the heat-bath walk pruning the enumeration.

  • Semistochastic mode (n_samples >= 2): deterministic part at eps2_loose plus the unbiased sampled difference estimator of E2[eps2] - E2[eps2_loose] (Eqs. 7-11), batches drawn with replacement from p_i ~ |c_i| by inverse-CDF search over the exactly-specified mt19937_64 stream (fixed seed = bitwise reproducible per build; std::discrete_distribution is implementation-defined and deliberately avoided). Both thresholds share each batch’s samples, so the stochastic error largely cancels (Eq. 11); eps2_loose == eps2 is pinned identically zero-noise.

  • Validation (tests/test_selected_casci.py): deterministic kernel == independent dense-matrix Eq.-4 oracle == brute Python kernel to 1e-14; walk == brute at pruning eps2; symmetry-block exhaustion yields E2 = 0 exactly; estimator unbiasedness verified analytically (multinomial moments) and statistically (pulls -0.06/-0.05/+1.29 sigma across seeds at 6000 batches); fixed-seed determinism pinned.

  • Scale (N2, 20k selected determinants, single core): CAS(10,26) deterministic eps2=1e-6 (10.5M perturbers) 7 s, CAS(10,40) (26.9M perturbers) 19 s; semistochastic eps2=1e-9 totals with 2-5 µHa statistical error in 10-30 s. Citation: sharma_semistochastic_hci_2017 entry + selected_ci_pt2 route.

Added: heat-bath presorted candidate walks + incremental sparse H in the selected-CI kernel (25i follow-up, 2026-06-11)

  • SelectedCIOptions.select_eps (default 0 = exact CIPSI scoring) is now reachable from Python and implemented identically in both kernels: drop generator I’s contribution to candidate D when |H_DI| * max_k|c_I^k| < select_eps. The Python kernel applies the predicate by brute force (the oracle); the C++ kernel walks presorted |H|-ordered double-excitation lists (Holmes-Tubman-Umrigar JCTC 12, 3674 (2016): a double excitation’s |H| depends only on its four orbital indices) and stops at the first sub-threshold entry. Walk and brute prefilter accept exactly the same candidate set (bit-identical predicate, monotone positive-double multiplication); pinned by a C++ A/B hook (use_heat_bath_walk=false) plus python==cpp parity at pruning eps in tests/test_selected_casci.py.

  • Incremental sparse Hamiltonian: the C++ kernel extends H across selection cycles instead of rebuilding it (each determinant’s excitation rectangle enumerated once per solve, with symmetric mirroring into already-built rows). This was the measured wall at 25+ active orbitals, not selection.

  • Measured (N2 actives, 20k selected dets, single root): CAS(10,26) 11.0 s -> 3.9 s at select_eps=3e-5 (energy cost 5 µHa, prefilter

    • incremental H); CAS(10,40) 23.7 s -> 11.6 s (cost 11 µHa). The walk’s edge over the brute prefilter grows with the generator count (1.24x at significant_coeff=1e-5, 40 orbitals); it is the structural enabler for the deterministic side of the upcoming semistochastic PT2.

Changed (BREAKING): state-averaged CASSCF defaults to spin-pure root selection (2026-06-11)

  • CASSCFOptions.spin_pure=None now resolves to True for every state-averaged run (nroots > 1), standalone method="casscf" included (previously: only multi-state CASPT2 compositions). The SA average targets the lowest nroots roots with S = ms2/2, S^2-filtered each macro-iteration; the M_s determinant sector’s interleaved higher-spin roots (a triplet between the two lowest singlets on essentially every small CAS) no longer silently enter the average. This is the averaging CSF-based codes (OpenMolcas, MOLPRO) perform by construction: the default LiH SA2 now lands in OpenMolcas’s singlet-averaged RASSCF solution to <=4e-8 Ha. Maintainer-approved 2026-06-11.

  • Migration: SA-CASSCF energies, excitation gaps, and SA-referenced CASPT2/NEVPT2/MRCI numbers change wherever the old raw-sector average mixed spins. Pass CASSCFOptions(spin_pure=False) (or casscf(spin_pure=False)) for the deliberate mixed-spin ensemble; that path is pinned against PySCF state_average_ (direct_spin1 raw-sector averaging) in tests/test_solvers_sacasscf.py. Single-state runs (nroots=1) are unchanged.

  • Per-root <S^2> joins the .out root table (and the multi-state block gains a model-space <S^2> line), so the averaged spin composition is visible in every multi-root output; surfaced on SolverResult.root_s2. Diagnostic degrades to omission above 200k determinants or on any failure.

  • Moved pins, same commit: the LiH “default keeps the M_s-sector roots” regression became the explicit spin_pure=False opt-out pin plus a new spin-pure-default pin (tests/test_ms_caspt2.py); the PySCF SA cross-check carries spin_pure=False explicitly (tests/test_solvers_sacasscf.py); the SA .out block test pins the new <S^2> column (tests/test_cas_output_block.py).

Fixed: CCSD PySCF cross-check harness; measured cross-code parity 2e-11 Ha (2026-06-11)

  • The tests/test_ccsd.py cross-checks un-xfailed by the d2e46663 CCSD rewrite were red on machines with PySCF installed, for harness reasons, not physics: the test unpacked PySCF’s CCSD.kernel() -> (e_corr, t1, t2) as (e_corr, _, e_ccsd), comparing a float against the t2 amplitude array (ValueError/TypeError on H2O/CH4 and all (T) variants), and both codes’ CC solvers ran at conv_tol 1e-8, so the two loose fixed points differed by ~2e-7 on H2/sto-3g against a 1e-7 assert. PySCF tolerances must also be set on the object density_fit() returns, not before it. With the harness fixed and both solvers at conv_tol 1e-10, all 9 tests pass and the measured vibe-qc-vs-PySCF E_corr agreement on matched DF setups is 2.4e-11 Ha (H2/sto-3g, DF and conventional references alike). The tests/test_ccsd.py tracking entry added by 0c9d16e0 is removed from scripts/test_gate/known_reds_baseline.json per its own instruction.

Added: spin-pure state averaging composes with the selected-CI CASSCF backend (25i follow-up, 2026-06-11)

  • casscf(spin_pure=True, ci_solver="selected_ci") (and the CASSCFOptions equivalents through run_job) no longer raises: each macro-iteration solves a buffered root set in the selected determinant space and keeps the lowest nroots S^2-pure roots, the dense filter’s semantics (_selected_ci._spin_pure_selected_roots, selected-space analogue of _ms_caspt2._spin_pure_roots). The default spin_complete alpha/beta-swap closure makes the truncated space spin-flip invariant, so singlet-triplet mixing is symmetry-blocked and truncated roots stay spin-pure far below the classification tolerance.

  • Parity (pinned in tests/test_selected_casci.py): the selected filter reproduces the dense filter exactly at the full-selection limit (LiH CAS(2,2) helper-level, bit-identical); spin-pure selected SA2-CASSCF == dense spin-pure SA2-CASSCF at the full-selection limit on H2O CAS(4,4) (identical trajectories, <=5e-9 Ha, Python + C++ kernels); truncated runs stay converged + spin-pure; the run_job composition lands in OpenMolcas’s singlet-averaged RASSCF solution on LiH (<=5e-6 Ha, same recorded constants as the dense M10 pin).

  • This removes the last blocker for flipping the standalone SA-CASSCF default to spin-pure root selection (maintainer-approved 2026-06-11; the flip itself lands separately with its breaking-change note).

Fixed — multi-k GDF compcell Fock lost Hcore in the M3b-4b/4c refactor; rsgdf cderi is the default (2026-06-11)

  • The compcell branch of run_krhf_periodic_gdf diagonalised the bare two-electron Fock: the M3b-4b/4c COSX refactor moved the XC block into the J/K branches and left the Hcore (+V_xc) fold in the EWALD_3D branch only. Every multi-k compcell SCF since then walked from a physical first iteration to a spurious fixed point and reported converged=True (LiH FCC (2,2,2): −2.96 Ha instead of −7.92; the iteration-1 energy was correct, making the bug invisible to trace-level checks). Fold restored in the compcell branch.

  • gdf_method now defaults to 'rsgdf' — the (k_bra,k_ket)-ket-resolved all-FT cderi (build_lpq_bloch_native_fft), the route validated at µHa parity vs PySCF. 'compcell' (q-only cderi — M3b-5 finding #1, exact only in the vacuum-box limit) stays selectable for its own development. run_krks_periodic_gdf forwards gdf_method / rsgdf_ke_cutoff / check_energy_sanity.

  • Both HANDOVER_RIJCOSX_M3A.md § M3b-5 findings resolved (addendum in place): with the restoration + this fix the default route lands LiH FCC (2,2,2) at −7.92039 Ha at the cutoff-15 production default — the documented validation value (PySCF −7.92200; +1.6 mHa lattice-cutoff truncation) — so the k-space GDF route is again a trustworthy absolute anchor on tight 3D ionic cells.

  • Closed-shell multi-k UHF ≡ RHF again (test_closed_shell_uhf_matches_rhf_at_111_mesh un-xfailed): the 0.93 mHa disagreement diagnosed 2026-06-10 was the merge-drop — UHF had been reverted to the pre-f7ee5832 split J while RHF kept the analytic FT; both route through ewald_3d_j_blocks again.

  • test_single_k_mesh_matches_gamma_ewald_driver un-parked from known_reds_baseline.json: the prescribed fix (“re-apply the a261613a F1 force block”) is exactly what the restoration did; the test passes.

  • Cross-backend pins re-measured against the corrected physics: the (1,1,4) COSX-vs-GDF envelope (single-gauge now), and the BIPOLE leg of test_periodic_rhf_backends_have_explicit_current_ parity_contract (43ea7490 had deliberately moved the Γ BIPOLE energy without updating the contract).

Fixed — b4a6faba merge-drop audit: periodic accelerator family, gauge/ω defaults, multi-k GDF parity restored (2026-06-10)

Systematic audit of the b4a6faba merge resolution (2026-06-09) — the commit whose dropped driver surface 9c4205a9 restored and whose EDIIS/ADIIS validation drop the entry below restores. Beyond those two, it had reverted seven more files byte-for-byte to their eb8f25bd (2026-05-24) state and hand-merged three others, silently wiping ~15 commits of landed work (accelerator family M2d 5c333372 / M2e cc90574e / M3 2c1617d3 / M4 bfda02b6; gauge enforcement 859efe0a / a261613a / d316f266; ω-default unification cf67b741 / d4133738 / 9566467f / 433d3543; multi-k GDF parity 024bcc17 / 500711b6 / a1731e96 / 5fee9fe9; smearing hardening 377d2cd1 / e790f558). All of it restored, re-fitted to the COSX-era code where the multi-k GDF path was redesigned post-merge:

  • Multi-k Ewald drivers (RHF/RKS/UHF) + single-k UHF/UKS Ewald + both dispatch layers restored to the audited pre-merge state — full {DIIS, KDIIS, EDIIS, EDIIS_DIIS, ADIIS} + dynamic_damping wiring, EWALD_3D gauge forcing, analytic-FT Hartree J, CRYSTAL-α ω defaults. (At HEAD the family had been inconsistent: never-reverted periodic_uks_multi_k_ewald.py kept all of this while its RHF/RKS/UHF siblings lost it.)

  • Γ-RHF Ewald ω default returned to CRYSTAL α = 2.8·V^{-1/3} (d4133738): the partial restore 07bd9d59 had rebuilt the gauge guard on top of the reverted file, resurrecting the deprecated geometry-based √(−log tol)/cutoff formula that ewald_composed.auto_ewald_alpha’s own deprecation note documents as gauge-breaking. The explicit omega=None kwarg semantics from 07bd9d59 are kept.

  • GDF drivers re-wired to the accelerator family (M4) — Γ (run_rhf_periodic_gamma_gdf) and multi-k (run_krhf_periodic_gdf) again route through PeriodicSCFAccelerator / MultiKPeriodicSCFAccelerator + DynamicDamping; the resurrected _reject_unsupported_python_accelerator gate and _PulayDIIS alias are removed again (M4 end-state).

  • Multi-k GDF physics restored on the COSX-era driver (all four pieces had regressed; the post-merge test re-baseline d024bf99 unknowingly pinned the broken −2.80467 Ha):

    • HF/hybrid auto-route to the exxdiv-corrected compcell path (500711b6, maintainer decision 2026-06-04) — without it the default path ran divergence-uncorrected Ewald-3D-K;

    • per-(k_i,k_j)-resolved all-FT cderi cache (build_lpq_bloch_native_fft, 024bcc17) — the COSX-era rebuild cached build_lpq_bloch_compcell(q = k_j k_i), a q-only cderi that is exact only in the vacuum-box limit;

    • BvK-supercell exxdiv Madelung (_madelung_for_kmesh, 024bcc17) instead of the primitive-cell ξ (over-counts the K-shift by Nk^(1/3); LiH (2,2,2): −592 mHa);

    • exxdiv Madelung counted once — the shift’s energy enters through the shifted K inside ½·Tr[D·F₂ₑ]; the rebuilt path had added exxdiv_ewald_energy_shift on top of it (double-count, ~−150 mHa on H₂/12-bohr (2,1,1));

    • post-SCF _check_energy_sanity guard + check_energy_sanity and rsgdf_ke_cutoff kwargs (5fee9fe9). With all four restored, the H₂/STO-3G 12-bohr (2,1,1) default-path energy returns to the published −1.12013988 Ha (Sun, Berkelbach, McClain & Chan, J. Chem. Phys. 147, 164119 (2017), Table II; agreement to sub-µHa) and the two post-merge own-output re-pins are corrected back to that documented reference (d024bf99’s −2.80467 Ha in test_periodic_k_gdf.py; 0a7ac7bb’s −1.35721 Ha “post-wire” in test_pbc_gdf_compcell.py, whose docstring had kept claiming PySCF parity all along).

  • run_rks_periodic_gamma_gdf alias + vibeqc.* export restored (cf67b741) — the merge had wiped the definition and then removed the dangling __init__ import as “stale”.

  • 13 accelerator-uniformity tests restored (multi-k ×4, BIPOLE ×5, GDF ×4 in tests/test_periodic_accelerator_uniformity.py), replacing the resurrected obsolete gate tests.

  • COSX-vs-GDF deviation envelope re-measured against the corrected GDF reference: test_krhf_cosx_backend_scf’s ball-truncation envelope moves 62.3 → 131.0 mHa at (1,1,4) — the experimental COSX K did not change; its GDF comparison side did. (The M3b-4 tightening plan is unaffected.)

  • docs/user_guide/scf_convergence.md restored to the completed M1→M4 rollout support matrix (incl. the M2c per-k KDIIS design and M2e stacked-real-block bridge footnotes).

Fixed — multi-k EWALD_3D J raised on 1D/2D systems (2026-06-10)

  • periodic_fock_multi_k.ewald_3d_j_blocks now routes dim<3 systems to the retained FFT-Poisson Ewald-split backend: the analytic-FT J kernel (f7ee5832, 2026-06-08) is 3D-only (4π/G² on a 3D G-mesh) and raised ValueError on every dim<3 multi-k EWALD_3D run — every multi-k SCF on a chain or slab. It landed red undetected because CI is collect-only (.gitlab-ci.yml); surfaced by the restored multi-k accelerator-uniformity tests (H₂-chain fixtures).

Added — D4 damping-parameter fit papers reach the references block; D4 runs cite the method paper (2026-06-11)

Citation-discipline (§ 8) audit of the inline doi= provenance in vibeqc/dispersion_d4_parameters.py — a run now cites the paper its D4 damping parameters actually come from:

  • Fixed: run_job(..., dispersion="d4") cited nothing for D4. The runner’s citation block only knew the D3(BJ) key; the routes.methods.d4 route (Caldeweyher 2019) existed but never fired. D4 runs now cite the method paper.

  • New [routes.dispersion_params] table in the citation database (keyed "d4:<normalized-functional>") + a dispersion_params= argument on assemble(): parametrizations whose damping fit was published separately from the 2019 D4 method paper additionally cite the fit paper. Five new entries: Santra-Sylvetsky-Martin 2019 (revDSD-BLYP / revDSD-PBEP86 / revDSD-PBE / revDOD-PBEP86), Ehlert 2021 (rSCAN / r²SCAN), Najibi-Goerigk 2020 (ωB97X), Friede 2023 (LC-ωPBE), and Grimme-Bannwarth-Shushkov 2017 (GFN1-xTB damping provenance); the r²SCAN hybrids, r²SCAN-3c, and GFN2-xTB rows route to their pre-existing entries (Bursch 2022 / Grimme 2021 / Bannwarth 2019). Composite recipes dedup cleanly (r²SCAN-3c already cited its defining paper through the method route).

  • New public normalize_d4_key() in dispersion_d4_parameters (the lookup normalization get_d4_params always applied, now shared with citation routing).

  • Coverage gate: tests/test_citations.py (TestD4ParameterDoiCoverage) mechanically pins every inline doi= in the parameter table to a database entry, every separately-published fit to a firing route, and every route back to a live parameter row; tests/test_feature_citation_end_to_end.py pins the runner wiring (HF-D4 → Caldeweyher; r²SCAN-D4 → Caldeweyher + Ehlert) on the emitted .out/.bibtex/ .references surface.

  • Docs: user_guide/citations.md § routing updated; user_guide/functionals.md dispersion example fixed (it passed dispersion= through RHFOptions, which raises TypeError — the real argument lives on run_job).

Added — B3LYP flavor spellings + cross-code flavor audit; parity-pairing fixes (2026-06-11)

Bare b3lyp keeps the ORCA definition (maintainer ruling): libxc HYB_GGA_XC_B3LYP5 (id 475, VWN5 local correlation), as it has since v0.8.0. What’s new is the explicit flavor surface and a measured cross-code audit of who means what by “B3LYP” (the +6.8 mHa PySCF question — see the Phase 4a entry below):

  • New spellings: b3lyp5 (explicit name for the default — use it in cross-code tables to be unambiguous) and b3lypg (PySCF-style spelling of the Gaussian/VWN-RPA variant, == the existing b3lyp/g). Deliberately no b3lyp3: libxc’s LDA_C_VWN_3 is the Ceperley-Alder fit III (== VWN5 on closed shells), not Gaussian’s “VWN(III)” (the RPA fit) — the obvious name would pick the wrong flavor.

  • Audit, H₂/STO-3G at 1.4 bohr (RKS, conv 1e-12): vibe-qc b3lyp −1.1586001482 Ha == PySCF 2.13 b3lyp5 (libxc 7.0.0) to <1e-9 == CRYSTAL14 B3LYP (−1.1586001474, XLGRID, local binary) == ORCA B3LYP (−1.158669, DefGrid2); vibe-qc b3lyp/g == b3lypg −1.1654009284 Ha == PySCF 2.13 b3lyp AND b3lypg to <1e-9 == ORCA B3LYP/G (−1.165470). Gap = 0.19·(E_c[VWN-RPA] − E_c[VWN5]) = −6.8008 mHa — convention split, not a bug. Camps: VWN5 = ORCA / TURBOMOLE / ADF / CRYSTAL / CP2K (+ vibe-qc); VWN-RPA = Gaussian / libxc / PySCF / Psi4 / NWChem / Q-Chem.

  • Parity-pairing fixes: the Psi4 and GPAW regression runners paired bare names across the flavor gap (vibe-qc VWN5-b3lyp vs their VWN-RPA b3lyp/B3LYP) — now flavor-matched (Psi4 b3lyp5, GPAW HYB_GGA_XC_B3LYP5); the PySCF twin scripts of the periodic example fleet (examples/periodic_pyscf/) had the same latent mismatch and now spell b3lyp5. ORCA / PySCF / CRYSTAL / CP2K runner maps gain explicit rows for every spelling; the Gaussian-flavor spellings are rejected by the CRYSTAL + CP2K runners (neither code can express VWN-RPA).

  • D3(BJ) / D4 damping-parameter lookup accepts all flavor spellings (they share the single b3lyp parameter set).

  • Citations: hertwig_koch_1997 (“What is B3LYP?”) added and routed on every flavor spelling so Methods sections can pin the variant; vosko_wilk_nusair_1980 note now records that both fits come from that one paper.

  • Docs: user_guide/functionals.md § B3LYP rewritten (camp table

    • measured values + practical guidance); from_gaussian / from_pyscf / from_orca / from_crystal / external_codes / features / tour aligned. Tutorial 15’s B3LYP row still carried the pre-v0.8.0 Gaussian-flavor numbers — regenerated with the actual default (and tutorial 37’s row re-verified). Tests: tests/test_b3lyp_convention.py pins both flavors, the gap, spelling equivalence, and the composite / dispersion / citation surfaces; a PySCF-side B3LYPG gradient-parity row covers the Gaussian flavor.

Fixed: dotted imports of compiled pybind11 submodules survive sys.modules purges (2026-06-11)

  • Running the full pytest suite in a single process (plain pytest tests/, unlike the per-file-isolated test gate) failed ~72 semiempirical tests with ModuleNotFoundError: No module named 'vibeqc._vibeqc_core.semiempirical' while the same files passed in isolation. Root cause: module-level test-isolation shims in tests/basisset_dev/ purge vibeqc* from sys.modules; pybind11’s def_submodule registers the dotted submodule names (vibeqc._vibeqc_core.semiempirical plus .nddo/.xtb/.indo) only during the extension’s one-time PyInit, which CPython never re-runs for cached single-phase-init extensions, so re-imports could not restore the keys and every later lazy dotted import (runner.py, vibeqc/semiempirical/methods/) failed for the rest of the process. vibeqc/__init__.py now re-registers the compiled submodule tree on every package import, the tests/basisset_dev/ headers keep vibeqc._vibeqc_core* entries out of their purges (the three shim-installing files also snapshot + restore the real modules after their module-level imports, fixing collection errors in partial runs), and tests/test_compiled_submodule_registration.py pins the contract. Details in HANDOVER_TEST_HEALTH.md.

  • Follow-up: with the extension’s sys.modules entries preserved across a purge, the fresh import vibeqc cache-hits the submodule and skips the loader step that sets the _vibeqc_core attribute on the new package object, so attribute-chain access (vibeqc._vibeqc_core.compute_eri) raised AttributeError in 6 tests while from-imports kept working via the interpreter’s sys.modules fallback. vibeqc/__init__.py now binds the attribute explicitly (plain from . import _vibeqc_core), with a third regression test simulating the exact core-preserving purge.

Fixed — native-D4 damping table: BHLYP was mis-keyed as hcth120; four more rows corrected against dftd4 (2026-06-11)

The per-functional D4 BJ-damping table (vibeqc.dispersion_d4_parameters, shipped v0.10.0) keyed the dftd4 bhlyp row as hcth120 — and transcribed the bj-eeq-mbd variant at that, where every other row uses the bj-eeq-atm default — with an alias bhlyp hcth120 on top. BHLYP-D4 silently used MBD-variant damping; HCTH120-D4 claimed a parametrisation dftd4 does not ship. A full sweep against the canonical dftd4 4.2.0 parameters.toml surfaced the rest:

  • bhlyp (BHandHLYP) now carries its real BJ-EEQ-ATM row (s8 1.65281646, a1 0.27263660, a2 5.48634586, doi 10.1063/1.5090222); hcth120 is removed and raises KeyError — dftd4 has no HCTH-120 D4 parametrisation.

  • revdsdpbep86: s6 0.5552 → 0.5132 (0.5552 is revDOD-PBEP86’s s6 — a neighbouring-row copy-paste).

  • mpwpw: the alias targeted a key that never existed (mpw1pw), so the lookup crashed with a raw KeyError; it now has dftd4’s exact row.

  • b1lyp, revpbe0dh: upgraded from approximate aliases (→ blyp, → pbe0dh) to their exact dftd4 rows.

New regression tests pin all five rows and, when the dftd4 package is importable, sweep the entire table against its shipped parameters.toml (tests/test_d4_model.py).

Fixed — a no-extras import vibeqc was broken (undeclared SciPy + eager optional-ASE imports) (2026-06-11)

A clean, extras-free pip install -e . followed by import vibeqc failed, because import vibeqc pulled in two third-party packages that [project.dependencies] did not provide:

  • SciPy — imported unconditionally via vibeqc.density_fitting (scipy.linalg at module load). It is genuinely core, so it is now a declared dependency (scipy>=1.11).

  • ASE — the eager re-exports of the ASE GPW Calculator wrapper (VibeqcGPW) and the ASE-driven GAPW MD drivers (run_md/run_npt/run_nve/run_nvt) hard-require ASE, which is an optional extra ([ase]). Those two imports in vibeqc/__init__.py are now guarded, so import vibeqc succeeds without ASE; the wrapped names appear only when the [ase] extra is installed.

Both were masked because the [test]/[dev] extras pull pyscf (→ SciPy) and ase. Surfaced provisioning the twin cluster, whose minimal --extras none install exposed it.

Fixed — BIPOLE UHF/UKS Γ join the corrected gauge; UKS hybrid α/2 exchange bug (option (b) Phase 4b, 2026-06-11)

run_pbc_bipole_uhf and run_pbc_bipole_uks adopt the corrected gauge at 3D Γ (default; same surface and provenance flags as RHF/RKS): full-Bloch spin densities, no spheropole term, per-spin Ewald exchange split K_σ = K_SR(erfc ω; D_σ) + K_LR(D_σ) + (ξ_M π/(Vω²))·S·D_σ·S (PySCF exxdiv='ewald' per-spin convention), α_HF-scaled for UKS hybrids. At the split, J-linearity lets TWO fused per-spin erfc traversals replace the legacy THREE builds (J_SR(D_total) = jk_α.J + jk_β.J) — a net efficiency win on every UHF/hybrid-UKS iteration; pure-functional UKS skips the discarded per-spin K traversals entirely.

Pre-existing UKS hybrid bug fixed (any gauge): the j-split UKS Fock applied −½·α_HF·K_σ to PER-SPIN densities — the ½ belongs to the closed-shell convention where K is built from the doubled TOTAL density (cf. the RKS driver), so UKS hybrids effectively ran with α/2 exchange (invisible to the pure-functional test surface; the UHF driver’s unscaled convention was correct). The factor is now −α_HF·K_σ, variationally consistent with the reported e_exchange. A positional density-block lookup in the same loop was replaced by cell-key pairing.

Cross-code validation (out-of-process PySCF 2.13 GDF, H₂/STO-3G triplet 12-bohr Γ, cutoff 6): UHF +0.07 mHa (legacy gauge: +7.1 mHa), UKS SVWN +1.0 mHa, UKS PBE0 +0.78 mHa (certifies both the per-spin split and the α-factor fix). Runner exposes the knobs for UHF/UKS. With this, all four BIPOLE drivers run the corrected gauge at Γ; the remaining option (b) work is the multi-k q≠0 exchange channels (Phase 3) and the Phase 5 sweep. Tests: tests/test_bipole_fock_ewald_exchange.py.

Changed — vq: fleet-update hardening (vq admin update robustness) (2026-06-11)

  • vq admin update --all-hosts now defaults to --serial (one host fully updated before the next); pass --parallel for the previous concurrent fan-out. Serial keeps a build break or a flaky link on one host from fanning out to all at once, and stops per-host output from interleaving.

  • VQ_UPDATE_SCRIPT_TIMEOUT (seconds, in the daemon’s environment) overrides the 1800 s update_script cap. A cold native-dep rebuild (libint from scratch on a 6-core box) can exceed 30 min and was getting killed mid-build; read at call time so it applies without a code change.

  • Cold-build PATH fix: the update_script now prepends Arch/Manjaro /usr/bin/{core,vendor,site}_perl (where pod2man lives — used by libecpint’s bundled libcerf man-page step) when present, so a cold rebuild doesn’t die with pod2man: command not found. No-op where pod2man is already on PATH.

  • Transient-SSH retry: delegated admin-update fleet commands retry an SSH transport failure (exit 255 / connect timeout) twice with linear backoff, so a momentary LAN/WAN blink no longer aborts a fleet sweep. The retry is opt-in (retry_transient), so read-only sweeps (queue / status / programs --all) stay fast-fail; a genuine remote non-zero exit is never retried, and vq submit is excluded (no double-submit). Tests: tests/test_fleet_update_hardening_v0_11_0.py.

Fixed: GAPW dispersion raises loudly when the dftd3 bindings are missing (2026-06-11)

  • Requesting dispersion= on the GAPW/GPW SCF entry points (run_periodic_rhf_gpw, run_periodic_rks_gpw_multi_k, and the augmented variants) in an environment without the dftd3 python bindings silently fell back to the approximate builtin supercell backend: compute_d3bj_periodic’s default backend="auto" resolves to "builtin" when dftd3 is absent, so the shim’s guard never fired and the SCF total quietly absorbed an approximate dispersion energy with no diagnostic. The shared _compute_d3_correction shim now pins backend="dftd3" (the bit-exact lattice sum; same resolution as "auto" whenever the bindings are installed) and raises NotImplementedError with a remediation hint when they are missing. The standalone vibeqc.compute_d3bj_periodic API keeps its documented auto fallback; only the SCF entry points pin the backend. Catches the previously failing test_rhf_gpw_dispersion_raises_when_dftd3_missing, which only runs in venvs without dftd3 (gate venvs with dftd3 skip it, which is why CI never saw the failure).

Added: C++ selected-CI kernel; CASCI past the 2M-determinant wall (25i, 2026-06-11)

  • cpp/src/selected_ci.cpp: the production backend for selected_casci: sparse Slater-Condon Hamiltonian over uint64 SpinDet bitmasks (hash-probed connection finding, all five excitation classes), block Davidson (mirrors the proven casci.cpp expansion/restart logic incl. its 2026-06-10 stagnation fix: relative acceptance floor + raw-residual fallback), the same coherent multi-root CIPSI selection criterion as the Python kernel, optional heat-bath prefilter (select_eps), and a pair-based truncated-list RDM12 (direct two-body Slater-Condon between list determinants, exact without spill tracking). Dispatch: VIBEQC_SELECTED_CI_BACKEND=auto|python|cpp (auto: full spaces

    1e5 determinants go C++); truncated-list make_rdm12 also routes to the C++ pair kernel above the existing 2000-det threshold.

  • Parity (pinned in tests/test_selected_casci.py): full- selection limit vs dense CASCI 1.4e-14 (closed shell, 2 roots) / 6.2e-15 (open shell); truncated-list RDM12 vs the spill-aware Python path 4.4e-16; same-threshold energies match the Python kernel; exact-limit selected-CI CASSCF == determinant CASSCF.

  • Past the direct-CI wall: N2/6-31G CAS(14,14), 11.8M determinants (6x the direct engine’s 2M cap), runs as selected CASCI in 0.5-15 s at 5k-50k selected determinants with a monotone plateau (recorded regression window), and as a full selected-CI CASSCF: orbital_step="nr" (the frozen-selection Newton-CG) converges in 8 macro-iterations / 104 s to |g|=1.6e-5 at -109.07272740 Ha, 72.7 mHa below the HF-orbital CASCI. The default auto stalls in the Super-CI first-order tail above the NR switch at this scale; prefer "nr" for selected large-CAS runs (recorded in HANDOVER_CAS.md; auto-switch retuning is an open polish item).

Fixed: 2026-05-18 code-audit remediation lands on main (2026-06-11)

Five correctness findings from the 2026-05-18 code audit, originally remediated on claude/peaceful-edison-d4bc5c (2026-05-20) and now re-derived onto current main. (The audit’s other three findings, memory pre-flight backend branching and the two incremental-Fock items, landed independently via main back in May.) Patch-candidates for v0.11.x.

  • ECP analytic gradient (P1). Gradient drivers built the nuclear-repulsion piece and the V_ne derivative with bare all-electron Z and never added a ∂V_ECP/∂R term, even though the SCF used Z_eff = Z n_core + V_ECP in Hcore; geometry optimization and forces with ECPs were unusable (audit probe: analytic z-gradient ~10x the finite-difference value on Zn-He/def2-SVP/ecp28mdf). Fix: nuclear_repulsion_gradient and one_electron_gradient_contribution gain effective-charge overloads, a new compute_ecp_gradient_contribution contracts the density with libecpint’s compute_first_derivs output, and all four compute_gradient* drivers route through a classical_hcore_grad_pieces helper that branches on GradientOptions.ecp_centers / .ecp_library (new fields, mirrored off the SCF options by the ASE calculator). The all-electron path is bit-for-bit unchanged. Tests: tests/test_ecp_gradient.py (analytic vs FD ≤ 1e-5 Ha/bohr).

  • ASE / optimize=True / FD-Hessian forces match the SCF Hamiltonian (P1/P2). Force calls dropped the SCF’s density-fit / COSX / aux-basis (and now ECP) settings and silently differentiated the four-index all-electron Hamiltonian. A new _gradient_options_from_scf helper threads a matching GradientOptions through the ASE calculator’s force branch and the FD-Hessian path (compute_hessian_fd gains a gradient_options keyword). Tests: backend-threading pins in tests/test_ase.py (helper field copy; calculator forces match the DF gradient, not the direct one).

  • GradientOptions.cosx honoured for UHF/UKS gradients (P2). The unrestricted DF gradient helper ignored the flag, so DF and RIJCOSX gradients were bit-identical; K now routes per spin through the chain-of-spheres kernel with α_kernel = 2·α_HF (reduces to the RHF expression in the closed-shell limit). Tests: two new RIJCOSX gradient pins in tests/test_rijcosx.py.

  • SCF energy consistent with the returned density/Fock (P3). The four molecular drivers rebuilt the final F/D after convergence but kept the pre-rebuild energy (~1e-3 Ha gap at loose tolerance); result.energy and the e_coulomb / e_hf_exchange / e_xc decomposition are now recomputed on the rebuilt pair (DFT+U terms re-reported on the fresh density). This supersedes the comment-side resolution of b7afe538: result.energy now agrees with scf_trace[-1].energy only to within the convergence band, and tests/test_scf_log.py asserts that band instead of exact equality. New regression: tests/test_scf_final_consistency.py (energy reproduces from the returned matrices to 1e-12 Ha).

  • Dynamic damping from zero works for RKS/UHF/UKS (P3). The static-damping mixer gated on the immutable opts.damping instead of the iteration-local current_damping, so damping=0.0, dynamic_damping=True never applied damping in the three non-RHF drivers (RHF was already correct). Tests: from-zero engagement pins for all four drivers in tests/test_dynamic_damping.py (no-DIIS iteration-count, convergence-rescue, and iteration-2 trace-divergence signatures).

Added: selected-CI active-space kernel + CASSCF backend (25i, 2026-06-11)

  • vibeqc.solvers.selected_casci: CIPSI selected CI with a casci()-compatible interface (active space + frozen-core dressing, ms2, multi-root with coherent multi-root selection scores, M_s=0 spin-completion, det_guess warm start, CASCIResult output). Variational at every cycle; full-selection limit reproduces the dense CASCI to <=1e-10 (closed- and open-shell pinned).

  • Truncated-list RDMs are now exact: make_rdm1/make_rdm12 use spill-aware generator products (out-of-list components of E_pq|psi> enter the 2-RDM through intermediate dot products; previously the Python path crashed on non-closed determinant lists). Full-CAS lists are bit-identical to before; make_rdm123/make_rdm1234 reject truncated lists with a clear error. Pinned by tests/test_selected_casci.py (E(RDM) == E(var) to 1e-12 on a genuinely non-closed space).

  • casscf(ci_solver="selected_ci", selected_ci_options=...) (and CASSCFOptions / run_job, label suffix _selci): the selected kernel runs inside every macro-iteration, warm-started across iterations; the FD Hessian-vector probes (and only they) freeze the selection (the Newton model must differentiate one fixed-selection surface; re-selecting per probe breaks FD antisymmetry and stalls the line search). Exact-limit parity with the determinant CASSCF <=1e-10 on LiH (SS+SA2) and H2O (SS); truncated runs land variationally above (H2O CAS(4,4) at 8/36 determinants: +0.36 uHa). MR-PT2/MRCI compositions reject the selected backend (they re-solve the full CAS downstream). Python-kernel scale: ~10^3 selected determinants; the C++ heat-bath kernel for 30+ orbital actives is the next 25i step.

Added — DLPNO examples + tutorial (2026-06-11)

  • examples/molecular/input-h2o-dlpno-mp2.py — one-call DLPNO-MP2 plus the TCutPNO convergence sweep against canonical RI-MP2 (99.77% → 99.84% → 99.90% → 100.0000% at the full-domain zero-threshold limit, measured on H₂O/cc-pVDZ); input-h2o-dlpno-ccsd-t.py — the CCSD(T) pilot with the cost-cap guidance.

  • docs/tutorial/dlpno_local_correlation.md — worked tutorial: one-call run, the threshold-convergence experiment with real numbers, frozen core + distant-pair screening, the CCSD(T) pilot and what backs its numbers; registered in the tutorial toctree and cross-linked from the DLPNO user-guide page.

Performance: EWALD_3D periodic SCF caches the analytic-FT Hartree J across iterations (2026-06-11)

The Γ and multi-k EWALD_3D SCF drivers rebuilt the analytic AO-pair Fourier transform, the dense G-mesh and the 4π/G² kernel every iteration — the dominant Γ-Ewald cost (PBC audit docs/pbc_audit_2026-06.md §E2). Those iteration-invariant pieces are now built once per SCF (mirroring the GDF Lpq cache) and only the density-dependent contraction is redone per iteration. Energies are bit-identical — pure caching, no physics change — pinned by a new gate tests/test_ewald_j_cache.py. ~4.7× on H₂/STO-3G/30-bohr Γ-RHF; the speedup scales with iteration count and cell size. Wired into the RHF/UHF/RKS/UKS Γ drivers and the RHF/RKS/UKS multi-k drivers. (UHF multi-k was initially skipped — at the time it still built J via the real-space erfc + FFT-Poisson split; the b4a6faba merge-drop restoration put it back on the shared ewald_3d_j_blocks analytic-FT entry, and it now takes the same per-cell lattice cache as its siblings.)

Fixed: C++ DF-CCSD/(T) rewritten to validated closed-shell equations; experimental gate lifted (2026-06-10)

  • The defect (flagged by the DLPNO anchor, see the M3a entry below): the experimental C++ DF-CCSD (VIBEQC_EXPERIMENTAL_CCSD=1) claimed 74% more correlation than full CI on H2O/STO-3G (-0.096681 vs FCI -0.055588 Ha) and did not converge. The T2 residual mixed spin-orbital permutation structure with spatial closed-shell amplitudes and layered a partial “DressJK” integral dressing on top; the (T) code path inherited the same confusion.

  • The fix: compute_residuals, the dressed-Fock build and compute_triples in cpp/src/ccsd.cpp were rewritten from scratch as the closed-shell spin integration of the Stanton-Gauss-Watts- Bartlett (1991) equations, the same set the FCI-anchored in-repo reference vibeqc.dlpno._ccsd_ref implements in spin orbitals. Every spatial term was validated against that kernel to machine precision at random amplitudes before transcription to C++; the (T) correction implements Raghavachari 1989 classwise over closed-shell spin blocks, verified the same way (derivation documented inline). Independent cross-check: the C++ (T) on H2O/STO-3G (-76.577 µHa) agrees with the independently written spin-orbital so_triples_correction landed the same day by the DLPNO chat (M4, -76.6 µHa).

  • Gates (tests/test_ccsd_anchor.py, always on, no external deps): C++ CCSD correlation within 5e-6 Ha of the spin-orbital anchor on H2O/STO-3G (-0.055443 Ha, measured agreement 1e-9 Ha) and H2O/def2-SVP (-0.216091 Ha, slow lane); strictly above the FCI correlation; (T) pinned on both bases (-0.000076577 / -0.003222076 Ha). The PySCF cross-checks in tests/test_ccsd.py are un-xfailed and run wherever PySCF is installed.

  • Experimental gate lifted: run_job(method="ccsd"/"ccsd(t)") and vibeqc.cc.run_ccsd no longer require VIBEQC_EXPERIMENTAL_CCSD=1. Scope unchanged: closed-shell RHF references, dense O(N^6) small-molecule pilot (handovers/HANDOVER_CCSD.md has the roadmap).

  • Adjacent defects fixed while ungating: frozen-core runs indexed the DF blocks with the wrong occupied window (n_frozen_core > 0 was broken); the MP2 initial guess used (ij|ab) instead of (ia|jb); run_job CCSD crashed attaching the CC result to the pybind RHFResult, results now come wrapped in _CCSDAugmented (.ccsd, .energy_total); the run_job frozen-core default now counts chemical cores per atom (vibeqc.cc.chemical_core_orbital_count: CO2 freezes 3, not 1); CCSD DIIS extrapolates post-Jacobi amplitudes per the reference-kernel convention.

  • Citations: new stanton_gauss_watts_bartlett_ccsd_1991 entry routed for ccsd/ccsd(t), replacing the scuseria_schaefer_ccsd_1988 route (which described the previously claimed, never-implemented equation set); verified live in .out/.bibtex/.references/ .system. Stale pre-rewrite debug scripts examples/regression/scripts/cc_{ref,debug,verify}.py deleted.

Added: spin-pure state-averaged CASSCF root selection (2026-06-10)

  • casscf(spin_pure=True) / CASSCFOptions.spin_pure: state-average over the lowest nroots spin-pure CI roots (S^2-filtered with a root buffer, re-selected each macro-iteration; reuses the multi-state model-space filter) instead of the raw lowest M_s-sector roots, matching what CSF-based codes (OpenMolcas RASSCF) average. LiH SA2 lands in OpenMolcas’s singlet-averaged solution to <=4e-8 Ha (recorded). Default None keeps the historical M_s-sector behavior standalone and resolves to True when the CASSCF is the reference of a multi-state CASPT2 composition, making the standard SA-CASSCF -> MS/XMS-CASPT2 workflow reproduce OpenMolcas full &RASSCF + MULTistate/XMULtistate end to end (<=4e-8 Ha observed, gated at 5e-6; recorded in tests/test_runner_ms_caspt2.py).

Added: MS-CASPT2 / XMS-CASPT2 multi-state engine (2026-06-10)

  • vibeqc.solvers._ms_caspt2.ms_caspt2: multi-state CASPT2 on the explicit determinant IC engine: spin-pure model space (⟨S²⟩-filtered CASCI roots, matching what CSF-based codes see in one spin sector), per-state first-order wavefunctions from the (refactored, byte- identical) _ic_caspt2_solve, explicit determinant-space couplings ⟨Φ_i|H|Ψ_j⁽¹⁾⟩, symmetrized Finley-1998 effective Hamiltonian. mode="ms" (state-specific Fock, OpenMolcas MULTistate) and mode="xms" (model-space SA-Fock rotation + shared state-averaged H₀, Granovsky 2011 / Shiozaki 2011, OpenMolcas XMULtistate). Exported as vibeqc.solvers.ms_caspt2 / MSCASPT2Result.

  • Validation (tests/test_ms_caspt2.py, 20 tests): machine-precision invariants (nroots=1 ≡ SS-CASPT2; explicit ⟨Φ_i|H|Φ_j⟩ block vs CASCI eigenvalues; ⟨Φ|H|Ψ⁽¹⁾⟩ ≡ E2; XMS rotation diagonalizes the model Fock; H2 Σg/Σu symmetry-zero couplings) + live OpenMolcas parity recorded for 7 cases (LiH eq./stretched, H2; MS + XMS; σ=0 ≤ 7e-8 Ha observed, gated at 5e-6).

  • Imaginary-shift representation caveat characterized: vibe-qc applies the Forsberg-Malmqvist shift to the full nondiagonal H₀ (basis-independent, both engine paths); OpenMolcas adds σ²/Δ on its case-block-diagonal representation. Identical without intruders (<1e-8), ~1e-5–1e-4 Ha apart near strong intruders (stretched-LiH excited root); documented in caspt2() / ms_caspt2() docstrings, σ≠0 multi-state parity gated at 5e-4.

  • OpenMolcas regression runner extended: run_caspt2(nroots=, multistate=) writes CIRoot = N N 1 + MULTistate/XMULtistate = all, parses per-root RASSCF / SS-CASPT2 / MS-CASPT2 energies (_MS_CASES table regenerates the recorded constants).

  • run_job wiring: CASPT2Options(multistate="ms"|"xms", nroots=N); nroots inherits casscf_options.nroots for the SA-CASSCF composition. MS/XMS eigenvalues land in SolverResult.root_energies (lowest as energy); the effective Hamiltonian / mixing / per-state diagnostics in the new SolverResult.multistate dict and a dedicated .out block; method label gains _ms{N} / _xms{N}. Rejects variant="sc", ipea!=0 and nroots<2 with clear errors. Citations: Finley 1998 fires for both modes, Granovsky 2011 + Shiozaki 2011 for XMS (new database.toml entries, conditional assembly at the runner). tests/test_runner_ms_caspt2.py pins the public-API energies against the recorded OpenMolcas constants plus the .out / .bibtex / .references surfaces. User guide: multi-state section in docs/user_guide/non_hf_solvers.md incl. the spin-mixed SA-CASSCF composition caveat.

Added — DLPNO-CCSD / CCSD(T) user-facing pilot: run_job wiring (M5b) (2026-06-10)

  • run_job(method="dlpno-ccsd"|"dlpno-ccsd(t)", dlpno_ccsd_options=DLPNOCCSDPilotOptions(...)) — closed-shell RHF reference, auto-resolved RI auxiliary, decomposed .out block with an explicit O(N⁶)-pilot cost note, dlpno_ccsd_done structured-log event, _DLPNOCCSDAugmented result proxy (.dlpno_ccsd, .energy_total). Jobs are hard-capped at DLPNOCCSDPilotOptions.max_nbf = 64 basis functions until the reduced-scaling engine (M3c) lands — the cap raises a clear error, deliberately overridable.

  • Citation routes for dlpno-ccsd / dlpno-ccsd(t) enriched (Purvis- Bartlett 1982, Riplinger 2013 ×2, sparse-maps 2016, Raghavachari 1989, Feyereisen RI 1993, Foster-Boys 1960); fixed a latent TOML duplicate-key collision between the new rows and the 2026-06-06 infrastructure rows that silently disabled .references/.bibtex emission for these methods.

  • User guide: the DLPNO page becomes “DLPNO methods (MP2, CCSD, CCSD(T))” with a pilot section + honest cost guidance. Tests: tests/test_runner_dlpno_ccsd.py.

Added — DLPNO-(T) triples, FCI-bounded (M4) (2026-06-10)

  • _ccsd_ref.so_triples_correction — the textbook Raghavachari (T) in spin-orbital form (per-triple evaluation, no t₃ tensor materialised), wired into both the reference kernel (run_ref_ccsd(compute_triples=True)) and the DLPNO pilot (DLPNOCCSDPilotOptions(compute_triples=True)).

  • Pilot (T) is exact, not semicanonical: converged DLPNO amplitudes are rotated back to the canonical occupied basis before the triples evaluation, eliminating the (T0) approximation in the oracle (the production T0/T1 tradeoff is M3c scope). Untruncated localised pilot agrees with the canonical reference to 0.6 nHa.

  • Gates (tests/test_dlpno_ccsd.py::TestTriples): (T) ≡ 0 for two electrons (exact null test); on H₂O/STO-3G (T) = −76.6 µHa closes 53% of the CCSD−FCI gap and CCSD(T) lands between CCSD and the full-space CASCI(=FCI) energy with no overshoot; truncated-PNO (T) stays within 30% of the untruncated value at TCutPNO=1e-8.

Removed — pre-M2 DLPNO placeholder modules retired (M3b) (2026-06-10)

  • dlpno/solver.py, dlpno/df_bridge.py, dlpno/pno.py, dlpno/integrals.py, dlpno/provider.py, dlpno/pair_pair.py, dlpno/triples.py are deleted. Their physics lives on — validated — in dlpno.mp2 (PNO construction, semicanonical energies, coupled LMP2) and dlpno.ccsd (subspace-projected CCSD); the (T) module returns fresh in M4 on top of converged pilot amplitudes. Nothing user-facing imported the retired modules; tests/test_dlpno.py keeps the still-live pair-classification / PAO / threshold units and the integration test now exercises only real paths.

Fixed — relaxed-scan convergence flags (optimizer restart polish) (2026-06-10)

  • relaxed_scan no longer reports spurious non-convergence. On a shallow constrained surface, scipy L-BFGS-B’s relative-energy (ftol) criterion halted each optimize_molecule step after only a handful of its allotted iterations, leaving the free-atom max-gradient just above conv_tol_grad; the audit-F1 gradient gate then correctly reported converged=False, so a smooth, correct O–H stretch curve still carried converged_flags = [F, T, F, F, F] (4/5 points falsely “not converged”). optimize_molecule now restarts L-BFGS-B from the premature stop with the energy criterion disabled (ftol=0), so the fresh inverse-Hessian builds curvature and runs to the gradient tolerance within the remaining max_iter instead of re-tripping ftol by steepest descent. The restart is gated on conv_tol_energy <= conv_tol_grad, so a deliberately loose energy tolerance is still honored as an early stop. The shared audit-F1 gradient gate (_gradient_converged, also used by the BIPOLE relaxers) is unchanged — the scan energies and located minima were already correct; only the flags were pessimistic (tests/test_relaxed_scan.py, tests/test_optimize_neb_robustness.py).

Added — vq: per-job --refresh <env> — drain + rebuild a venv-env before a job runs (2026-06-10)

  • vq submit <host> --refresh <env> script.py records JobSpec.refresh_before. When that job is next to dispatch, the daemon drains the host (lets RUNNING/orphan jobs finish, holds new dispatch), runs the env’s git pull + update_script (the same work as vq admin update <env>), then dispatches the job against the freshly-rebuilt venv. A failed rebuild lands the job in FAILED with a failure_reason naming the env — it never runs against a broken or half-built env, and a failing refresh can never wedge the queue (the FAILED job drops out of the pending set). The refresh is per-job and explicitly opted into, so the submitter owns the choice — no unattended auto-deploy. Motivation: you push to main, then have an overloaded fleet host rebuild before running your job, drop-proof — the daemon owns the rebuild, so a client disconnect can’t interrupt it (unlike a client-connected vq admin update).

  • Single-file submit only in v1 — rejected at the CLI for --array / --chain (submit the refresh as a standalone job first). Forwarded across the remote-submit boundary, so vq submit <remote> --refresh rebuilds on the remote host. Additive JobSpec field — pre-v0.11.0 specs read clean. Tests: tests/test_refresh_before_v0_11_0.py (field round-trip, CLI guards, remote forwarding, daemon orchestration success / failure / admin-error / drain).

Added — DLPNO-CCSD pilot: subspace-projected CCSD, FCI-anchored (M3a) (2026-06-10)

  • vibeqc.dlpno._ccsd_ref — spin-orbital Stanton-Gauss-Watts- Bartlett (1991) CCSD reference kernel on DF integrals: the in-repo ground truth for coupled-cluster numerics. Anchored against exact references: CCSD≡FCI on H₂ (agrees to the independently measured 6 µHa RI-fit error) and sits the physically required ~0.3% above the full-space CASCI(7,10)=FCI correlation on H₂O/STO-3G.

  • vibeqc.dlpno.ccsd.run_dlpno_ccsd_pilot — DLPNO-CCSD with the Riplinger-2013 ansatz (per-pair PNO doubles spaces, diagonal-pair singles spaces) evaluated by expansion/projection through the anchored engine: physically exact subspace-constrained CCSD with zero equation-transcription risk. Gates (tests/test_dlpno_ccsd.py): untruncated limit ≡ anchor to 0.000 µHa on H₂, H₂O/STO-3G (canonical and Boys-localised) and H₂O/def2-SVP; TCutPNO=1e-8 recovers 99.86% of E_corr with 14.9 of ~20 virtuals per pair (slow lane). O(N⁶) pilot by design — the reduced-scaling per-pair contraction engine (M3c) validates against these tiers.

  • Found via the anchor: the experimental C++ DF-CCSD (VIBEQC_EXPERIMENTAL_CCSD=1) overshoots the FCI correlation energy by 41 mHa on H₂O/STO-3G — flagged to the cc workstream with a definitive reproducer; the new TestAnchor::test_h2o_anchor_sits_above_fci_by_triples_margin gate prevents regressions of this class.

Added — Periodic COSX M3b-6: SR cutoff tables, derived cell domain, basis envelope (2026-06-10)

  • erfc-aware per-pair SR cutoff tables: with ω > 0, build_cosx_cell_pair_caches probes the validated kernel itself at increasing pseudo-nucleus distances and tabulates, per (δ, s_ν, s_σ), the radius beyond which the SR A-block is below the screening floor — exact w.r.t. both the erfc range and the pair-density extent (no analytic extent model). The engine skips pairs past their radius and whole (c_σ, δ) combinations past the per-δ maximum. Setup cost ~1 s for 417 δ’s on LiH; the periodic test suite runs 3.5× faster.

  • Basis-derived SR cell domain: KPointCosxK now sizes the SR cell list from the tabulated shell extents (1e-4 level) + the kernel range, decoupled from the one-electron cutoff_bohr (capped at 40 bohr).

  • Real-space SR envelope criterion (CRYSTAL-style diffuse-exponent constraint, now measured and enforced): shells reaching past ~25 bohr at the 1e-4 level are outside the envelope — a warning points at the k-space GDF exchange or periodic-adapted (pob-class) bases. Measured anchors: H/sto-3g extent 20.6 bohr — chain ω-invariant at 1e-4; LiH/sto-3g Li 2p extent 42.8 bohr — ω-invariance broken at 2e-2…3.6e-2 for 15…40-bohr domains (no feasible real-space domain covers α_min = 0.048 shells). Pinned: test_cosx_sr_envelope_warning_lih.

Fixed — BIPOLE RKS Γ joins the corrected gauge (option (b) Phase 4a, 2026-06-11)

run_pbc_bipole_rks adopts the corrected gauge at 3D Γ (default, same auto/opt-out surface as RHF): full-Bloch density — which also fixes the XC grid density (the Γ-locality projection dropped the cross-cell AO products from ρ(r)) — no spheropole term, and for hybrids the α_HF-scaled Ewald exchange split. Pure functionals need no exchange machinery, and the legacy path’s wasted full-Coulomb K traversal is now skipped for pure functionals (it was built unconditionally and discarded). Cross-code (out-of-process PySCF 2.13 GDF, H₂/STO-3G 12-bohr Γ, cutoff 6): SVWN +0.05 mHa (−1.12122748 vs −1.12127725; the legacy gauge sat 24 mHa off), PBE +0.05 mHa, PBE0 (hybrid split path) +0.08 mHa. B3LYP differs by +6.8 mHa — RESOLVED: vibe-qc’s b3lyp follows the ORCA convention (libxc B3LYP5/VWN5, per the documented alias map and maintainer confirmation); PySCF 2.13’s bare b3lyp evaluates the VWN_RPA (Gaussian-style) variant — vibe-qc’s b3lyp/g reproduces PySCF bit-exactly (−1.1654009284). Not a bug in either code. (The follow-up flavor audit + explicit spellings landed the same day — see the Added — B3LYP flavor spellings entry above.) A positional density-block lookup in the RKS exchange-energy contraction was replaced by cell-key pairing (required by the wide density list). Runner exposes the knobs for RKS. UHF/UKS propagation and the multi-k q≠0 channels (Phase 3) follow. Tests: tests/test_bipole_fock_ewald_exchange.py (SVWN/PBE0 PySCF pins, legacy-gauge stability, multi-k gate).

Added — DLPNO-MP2 user-facing: run_job(method="dlpno-mp2") (M5a) (2026-06-10)

  • run_job(method="dlpno-mp2", dlpno_options=DLPNOMP2Options(...)) runs RHF then vibeqc.dlpno.mp2.run_dlpno_mp2 with the auto-resolved correlation (“ri”) auxiliary basis. The .out solver block reports the full energy decomposition (iterated pairs, PNO-truncation correction, distant-pair estimate, pairs kept/screened, avg PNOs/pair, frozen core); the structured log gains dlpno_mp2_done; the result object is an attribute-forwarding _DLPNOMP2Augmented proxy with .dlpno_mp2 and .energy_total. Closed-shell references only (clear error otherwise). Tests: tests/test_runner_dlpno_mp2.py.

  • Citations wired (§ 8 discipline): routes.methods["dlpno-mp2"] emits Møller-Plesset 1934, Feyereisen 1993 (RI), the new pinski_dlpno_mp2_2015 database entry, and Foster-Boys 1960 into .references/.bibtex for every dlpno-mp2 job.

  • User guide: new page docs/user_guide/dlpno_mp2.md (quick start, threshold table with measured recovery tiers, decomposition anatomy, limitations), cross-linked from the MP2 page and the user-guide toctree.

Added — UNO open-shell starting reference for the CAS family (2026-06-10)

  • UHF natural orbitals (UNO-CAS — Pulay & Hamilton, JCP 88, 4926 (1988)) are now the open-shell (multiplicity > 1) starting reference for the determinant-solver family (casci / casscf / mrci / nevpt2 / caspt2 / selected_ci / dmrg / v2rdm / transcorrelated_ci): get_hf_orbital_provider(method="uno") diagonalizes the total UHF density in the orthogonalized AO basis; occupations cluster at 2/1/0, so one spin-restricted orbital set carries the open-shell character and the descending order matches the lowest-core / active-window convention. The previous behavior used the UHF α orbitals only, leaving the β space — and hence the doubly-occupied core — only approximately represented. run_job(cas_reference="rhf"|"uhf"|"uno") overrides; closed-shell jobs are unchanged.

  • Validation: UNO occupations match PySCF mcscf.addons.make_natural_orbitals to ≤1e-8 (O2 triplet); on O2/STO-3G CAS(2,2) the UNO reference lowers CASCI by 2.1 mHa (to within 4 µHa of converged CASSCF) and cuts CASSCF from 27 to 4 macro-iterations into the same stationary point (tests/test_uno_reference.py).

  • Citations: pulay_hamilton_uno_1988 added; fires automatically when an open-shell CAS-family job resolves to the UNO reference (verified end-to-end into the .bibtex).

Added — Davidson warm start for CASSCF macro-iterations (2026-06-10)

  • casci_direct_solve(…, guess=): the Davidson subspace can be seeded with prior CI vector(s) (orthonormalized + noise-dressed like the default determinant seeds; cold start when empty). Threaded through solvers.casci(ci_guess=) (dense backend ignores it) and the CASSCF macro-iteration loop, which now seeds every CASCI solve — including Newton-CG Hessian-vector probes and line-search evaluations — with the most recent CI vector(s).

  • Benchmark: N2/6-31G CAS(10,10) CASSCF, warm vs cold under identical machine load: 490 s vs 632 s (1.29× end-to-end; identical stationary point to 3e-14). At the Davidson level an exact-vector guess converges in 1 iteration vs 19 cold — the guess columns are deliberately NOT noise-dressed (the symmetry-breaking seed noise stays on the unit top-up seeds; dressing the guess would re-inflate its residual to the noise scale and discard the warm-start benefit).

  • Tests (tests/test_casscf_scale.py::TestDavidsonWarmStart): warm-start/cold-start eigenpair parity (multi-root), guess-shape validation, and warm-started CASSCF convergence to the recorded LiH minimum.

Added — DLPNO-MP2: first physically correct DLPNO method (M2) (2026-06-10)

  • vibeqc.dlpno.mp2.run_dlpno_mp2 — local MP2 in the Neese-group DLPNO framework (Pinski et al., JCP 143, 034108 (2015)): Foster-Boys-localised occupieds, per-pair PAO domains with S-orthonormal semicanonical virtual bases (pao.semicanonical_pao_basis), real density-fitted exchange integrals per pair, PNOs from the semicanonical pair density with quasi-canonicalisation and the standard ΔE_PNO truncation correction, and a coupled LMP2 residual iteration carrying the localised-occupied Fock coupling with amplitude projections between PNO bases. Diagonal pairs and antisymmetrised closed-shell pair energies (2−δ_ij)·Σ K(2T−Tᵀ) — the two structural gaps of the earlier scaffold — are in.

  • Validated against vibe-qc’s canonical DF-MP2 (same fitting basis, tests/test_dlpno_mp2.py): exactness limit (full domains, no PNO truncation) agrees to ≤1 µHa on H₂O/STO-3G (canonical and localised occupieds), H₂O/def2-SVP, and NH₃ — and default thresholds (TCutPNO=1e-8) recover 99.87% of E_corr on the DZ case (99.93% at 1e-9) with genuine PNO compression (14.9 of ~19 virtuals per pair). The M1 strict-xfail parity ratchet flipped on schedule and is now a hard assertion.

  • Not yet wired into run_job (that is M5, after DLPNO-CCSD); pair prescreening defaults off until the M2b calibrated distant-pair estimates land. The pre-M2 dlpno.solver/dlpno.triples scaffolds remain non-physical and are slated for replacement in M3/M4 — now tracked in HANDOVER_GATED_ITEMS.md.

  • M2b (same day): frozen-core support (n_frozen, validated ≤1 µHa against a canonical frozen-core DF-MP2 reference), real semicanonical dipole-dipole distant-pair estimates (Riplinger 2013 §II.C) with e_distant accumulation and an 8-bohr trust radius (3×H₂ @ 12 bohr: all inter-unit pairs screened, net error 2.5 µHa), and batched per-pair DF transforms via a single occupied half-transform.

Fixed — DLPNO M1: localisation correctness + honest validation harness (2026-06-10)

  • Foster-Boys Jacobi angle fixed (vibeqc.localise): the rotation angle omitted the −¼(D_ii−D_jj)² term, so sweeps maximised the wrong functional and stalled short of the maximum (H₂O/STO-3G objective 2.45 vs 3.22 after the fix). Now the standard Edmiston-Ruedenberg branch (θ = ¼·atan2(B, −A)).

  • Pipek-Mezey fixed: coefficients were never rotated inside the Jacobi sweeps, so Mulliken populations went stale after the first rotation; transition populations are now symmetrised and the working coefficients rotate in sync. Both localisers gain physical tests (tests/test_localise.py): H₂O must yield 1 core + 2 equivalent O–H bonds + 2 equivalent lone pairs, objectives must strictly increase, spans must be preserved.

  • DLPNO integration test rebuilt on real inputs (tests/test_dlpno_integration.py): the previous test multiplied bohr coordinates by Å→bohr again (1.81 Å O–H bonds, E_HF −74.505 instead of −74.964), used STO-3G as its own RI fitting basis, built Foster-Boys from hand-rolled approximate dipoles, and asserted only sign/finiteness. Now: correct geometry, def2-svp-rifit, exact compute_dipole integrals, canonical-anchor assertions, and a strict=True xfail parity ratchet vs canonical DF-MP2 (same fitting basis) that flips when the M2 solver-physics work closes the gap. Measured baseline recorded in the retired DLPNO handover: the current per-pair solver recovers 2.9% of the canonical DF-MP2 correlation energy (placeholder PNO energies / missing diagonal pairs / unantisymmetrised pair energies — all M2 scope).

  • DLPNO handover rewritten to reality: per-component real-vs-placeholder inventory and the M1–M5 milestone plan (C++ translation deferred until the physics validates).

Added — imaginary level shift on the direct large-CAS CASPT2 path (2026-06-10)

  • caspt2(…, imaginary=σ) now works at scale: the Forsberg-Malmqvist shift no longer forces the explicit small-CAS engine — large shifted jobs dispatch to the direct active-CI signature-block solver. The Hylleraas level-shift-corrected energy (Forsberg & Malmqvist 1997, Eq 11) is evaluated without any eigendecomposition via one real SPD solve: with A = H̃0−E0 and z = (A²+σ²)⁻¹V, E2(σ) = −(V+σ²z)·(Az), solved by CG with per-block (B²+σ²)⁻¹ preconditioning (solve_signature_block_linear_system_imaginary). Only the IPEA shift still pins jobs to the explicit engine.

  • Validation: machine-precision agreement (≤5e-16) with the explicit engine’s eigenbasis form on H2/6-31G, LiH/STO-3G and H2O/STO-3G at σ = 0.1 / 0.3 (CG converges in 4-5 iterations); at scale, H2O/cc-pVDZ CAS(4,4) with Imaginary=0.1 reproduces live OpenMolcas (-76.22073564) to all printed digits through the public caspt2() direct dispatch (tests/test_caspt2_rdm_factorization.py).

Added — CAS-family .out diagnostics + roadmap sweep (2026-06-10)

  • CAS results in the .out solver block: method="casci"|"casscf"| "caspt2"|"nevpt2" runs now print the reference wavefunction’s leading determinant configurations, per-root energies (multi-root CASCI / SA-CASSCF, also when an SA reference feeds a PT2), and the active natural-orbital occupations; the same diagnostics ride on SolverResult (ci_coeffs, ci_labels, rdm1, root_energies). The active 1-RDM routes through the C++ kernel for large spaces and degrades to omission (never a crash) if diagnostics fail (tests/test_cas_output_block.py).

  • Roadmap shipped-sweep: v0.21.0 items 25a-c (production FCI/CASCI) and 25d-e (production-scale CASSCF orbital optimisation + state-averaging) marked shipped with the 2026-06-10 capabilities; 25f/25g notes updated for the CASSCF→NEVPT2/CASPT2 composition and the direct large-CAS CASPT2 route.

Fixed — BIPOLE Γ absolute energies: Ewald exchange split lands (option (b), 2026-06-10)

The root-caused defects below are fixed at Γ-only sampling (the default for 3D Γ runs; multi-k keeps the legacy convention until the q≠0 LR-exchange channels land — Phase 3):

  • Exchange now uses the Ewald split, symmetric with the J split: K = K_SR(erfc ω, direct) + K_LR(erf ω, reciprocal K≠0) + (ξ_M π/(Vω²))·S·D·S. The G=0 S·D·S bookkeeping removes the K=0 component embedded in the convergent direct K_SR sum and adds the probe-charge Ewald (Madelung) finite-size correction (exchange_exxdiv='ewald', PySCF-equivalent; 'none' available). The legacy full-Coulomb direct K — formally divergent with the non-decaying Γ Bloch density — survives only behind use_exchange_ewald_split=False. New helpers compute_K_long_range_gamma (reuses the J^LR FT cache; zero new C++) and probe_charge_madelung (pinned to PySCF tools.pbc.madelung at 1e-9, α-invariant) in vibeqc.bipole_fock_ewald. K_SR + J_SR now come from ONE fused traversal (was two).

  • The Γ-locality density projection is OFF in this gauge: the SCF runs on the full Bloch fold, fixing the projected-density T/V_ne/J deviations (−0.26/+0.20/−1.02 Ha on MgO).

  • EXT EL-SPHEROPOLE is omitted from the total (gauge double-count; the kernel + CRYSTAL-parity tests stay).

  • Cross-code validation (out-of-process PySCF 2.13 GDF): MgO/STO-3G Γ RHF converges from SAD to −271.0286 vs PySCF −271.0497 (+21 mHa, cutoff-12 truncation scale) — pre-fix this sat +2.5 Ha off. Fixed-density components: +17 mHa at cutoff 10; exchange element-wise |ΔK|_max 8e-4 at cutoff 14, ω-invariant. H₂/STO-3G 12-bohr box converges to −1.12258512 vs PySCF −1.12258397 (~1 µHa) at cutoff 12. Regression suite: tests/test_bipole_fock_ewald_exchange.py (fast self-contained pins; the ~1–2 h MgO c12 run is documented in HANDOVER_BIPOLE_PRODUCTION.md + the parity scripts).

  • Corrected-gauge cutoff requirement + guard. Unlike the legacy projected gauge (whose energy never contracted cross-cell operator blocks), the corrected gauge contracts full Bloch folds — diffuse AO tails demand fold-converged cutoffs (MgO/STO-3G S(Γ)-fold truncation: 0.39 at 8 bohr → spurious SCF states possible; 5e-3 at 12; 2.3e-6 at 16). The driver now measures the S(Γ) fold drift against a 1.5× cutoff and warns (loudly above 1e-2). The SCF density is stored on a 2×-cutoff cell list so the C++ builder resolves every P(b−a) difference its traversal forms (silent lookup-skips removed; traversal cost unchanged), and _lattice_contract_blocks iterates operator cells (density looked up by key).

  • Basin caveat for absolute pins: minimal-basis ionic Γ SCF has multiple genuine solutions — PySCF itself converges MgO/STO-3G Γ to −271.050 or −270.935 depending on the starting density. The corrected-gauge driver is basin-stable at fold-converged cutoffs: the MgO c12 SCF reaches the identical stationary point (−271.02857708, same to the last digit) from SAD and from the PySCF converged density. Converged absolute comparisons should still fix the basin explicitly on pathological cells.

  • The analytic-gradient preview implements the legacy gauge and now refuses split-gauge SCF results with a clear error (result.exchange_ewald_split provenance flag); FD gradients differentiate the actual driver energy and follow the new gauge automatically. Migration tracked for Phase 5.

Root-caused — BIPOLE ionic-crystal absolute energies (2026-06-10, late)

Fixed-density component audit against PySCF on MgO Γ (the forensic tool lives at examples/regression/bipole_parity/mgo_component_audit.py): at PySCF’s converged density, E_nn matches to µHa, and T/V_ne/J match to cutoff truncation with the unprojected Bloch density while deviating by −0.26/+0.20/−1.02 Ha with the driver’s Γ-locality projection; the direct exchange needs a Bloch-correct cell decomposition (Bloch input overcounts ~3×, projected undercounts 0.95 Ha); and the EXT EL-SPHEROPOLE (+3.0 Ha here) has no PySCF counterpart — a double-count in vibe-qc’s explicit-jellium gauge (its value matches CRYSTAL, but CRYSTAL’s gauge needs the term and ours doesn’t). Reassembling the correct gauge from measured pieces reproduces PySCF to ~18 mHa (cutoff-10 truncation). The fix is a redesign of the BIPOLE energy assembly that invalidates the analytic-gradient local-energy ∂E/∂P(0) gauge (“option (a)”) — escalated as a maintainer design decision; admonitions updated in docs/bipole_status.md + docs/troubleshooting.md.

Added — CASSCF at scale on the direct CI core (roadmap 25d-e) (2026-06-10)

  • RDM backend dispatch: solvers._rdm.make_rdm12 (and the SA variant) route full-CAS determinant spaces (>2000 determinants, canonical ordering verified structurally) through the C++ casci_direct_rdm12 kernel; truncated CI lists (selected-CI / CISD) stay on the Python path. VIBEQC_RDM_BACKEND=python|cpp|auto.

  • Super-CI robustified: the diagonal-Hessian denominator is clamped to max(|H_diag|, reg) — a negative diagonal element used to flip that step component uphill, which the backtracking line search then rejected (hard stall observed on N2/6-31G CAS(10,10) at iteration 28); a steepest-descent line-search fallback now also runs before the optimizer gives up. Pure Super-CI keeps a slow first-order tail and is the robust far-field method; auto (default) hands over to the second-order step near convergence.

  • orbital_step="nr" reimplemented as trust-region Newton-CG (Steihaug-Toint truncated CG, Nocedal-Wright Alg. 7.2) with Hessian-vector products by central finite differences of the analytic orbital gradient — 2 CASCI solves per CG iteration, independent of the rotation-pair count. The historical per-pair FD Hessian (2·n_pairs CASCI solves per macro-iteration) was the large-CAS wall.

  • Headline: N2/6-31G CAS(10,10) CASSCF — 63 504 determinants per CI solve, impossible on the dense path — converges in 48 macro-iterations / ~85 s (dev box) to |g| = 2e-7 at E = -109.0614472204 Ha. CASSCF is non-convex: PySCF mcscf.CASSCF converges to a different stationary point 8.13 mHa higher (-109.0533171021); the regression pins vibe-qc’s deeper stationary point + variational bounds (tests/test_casscf_scale.py). 34-test CASSCF regression suite (PySCF parity on unique-minimum systems, open-shell, SA, dissociation) unchanged and green.

Added/Fixed — PBC queue: J+K call-site fusion; GDF joins auto-convergence (2026-06-10)

  • EWALD multi-k UHF/UKS Fock builds halve their exchange cost: the per-spin K = 2·(J_full F_full) reconstruction (two full lattice-ERI traversals per spin) is replaced by the fused C++ build_jk_2e_real_space (one traversal returns J and K). Operators pinned bit-identical on converged densities (test_fused_jk_exchange_matches_jfull_ffull_reconstruction).

  • Pre-existing EWALD inconsistency diagnosed while validating (not caused by the fusion — proven on the pre-fusion code): the RHF and UHF multi-k EWALD drivers disagree by 9.3e-4 Ha on closed-shell H₂ [1,1,1], screening on or off — they build J through different machinery. xfailed with the diagnosis; EWALD-owner item in docs/pbc_audit_2026-06.md.

  • UHF multi-k EWALD smearing gate restored: the driver silently ignored smearing_temperature (the historical gate was removed without updating its test); it now rejects finite temperature like the other HF paths.

  • convergence="auto" now also active on jk_method="gdf" (queue #2): same transparency block and per-knob provenance; GDF gets no BIPOLE-style KS FMIXING floor (its drivers honor the resolved values verbatim — the floor is now route-conditional in the resolver). Γ-only result shapes handled in the post-SCF gap check.

Fixed/Added — IBZ meshes: BIPOLE shortcut was broken on real crystals; GPW/GAPW now honor SCF options (2026-06-10)

  • BIPOLE IBZ-reduced k-meshes were silently wrong on non-trivial crystals. The “IBZ-native” path diagonalised at the irreducible points and replicated D(k) into each star without the AO rotation D(R·k) = P(R)·D(k)·P(R)ᵀ — exact only for trivial stars (the He-only validation cells). MgO/STO-3G [2,2,2] with an IBZ input mesh failed to converge 8.25 Ha away from the full-mesh result. All four BIPOLE drivers now expand IBZ inputs to the full MP mesh up front (correct by construction; the wall-clock IBZ saving is deferred). Regression: test_ibz_mesh_matches_full_mesh_on_mgo (full ≡ IBZ to 1e-9) — He-only symmetry validation is not validation.

  • periodic_k_symmetry (new, experimental groundwork): validated star matching (star_operations) + per-k rotation transport + from-D(k) inverse-Bloch fold, with the full probe record of the open transport question (a subset of star operations transports converged MgO densities at 5e-15; the rest err at O(1), unexplained by reciprocal-translation phases, per-atom shift phases, parity, or time-reversal — suspected truncated-sum Fock asymmetry). True IBZ-native reduction for BIPOLE/EWALD/GDF multi-k is blocked behind this and tracked in docs/pbc_audit_2026-06.md.

  • GPW/GAPW drivers now receive the runner’s SCF options (queue #5): use_diis / damping / diis_subspace_size / diis_start_iter forwarded everywhere the drivers accept them, Fermi-Dirac smearing_temperature forwarded on the closed-shell GPW/GAPW paths (the runner’s smearing gate now admits GPW/GAPW RHF/RKS), and a .out note when a knob is not honored (GPW multi-k uses its internal scheme). Previously these options were printed in the .out but silently ignored by the drivers.

Added — C++ direct determinant CAS-CI engine (roadmap 25a-c) (2026-06-10)

  • cpp/src/casci.cpp: string-based determinant CI — lexicographic α/β occupation strings, per-spin-sector Jordan-Wigner phases, chunked gather→GEMM→scatter sigma builds in the Knowles-Handy (CPL 111, 315 (1984)) spin-summed form, block Davidson eigensolver (J. Comput. Phys. 17, 87 (1975)) with determinant-diagonal preconditioning + deterministic symmetry-breaking seed noise (pure determinant seeds confine Davidson to the seeds’ point-group/spin sector and can silently skip roots), and spin-summed 1-/2-RDMs — all matching the Python determinant engine’s ordering, phase, and RDM conventions exactly (casci_direct_solve, casci_direct_rdm12, casci_direct_sigma bindings).

  • Automatic backend dispatch in solvers.casci: ≤4000 determinants keep the historical dense Slater-Condon + eigh path (bit-for-bit unchanged); larger spaces go to the direct engine, lifting the dense max_det=10_000 wall to ~10^6-determinant spaces — e.g. H2O/6-31G CAS(10,12), 627 264 determinants, solves in ~9 s on the dev box. VIBEQC_CASCI_BACKEND=python|cpp|auto forces a backend; a non-converged Davidson raises rather than returning silently.

  • Validation (tests/test_casci_direct.py, 12 tests): eigenvalue + CI-vector parity with the dense engine across closed-shell / open-shell / multi-root / FCI-limit cases (≤1e-9), elementwise σ vs the dense Hamiltonian (machine precision), RDM parity vs solvers._rdm.make_rdm12, RDM-energy identity, FCI limit vs PySCF, and the full-space N2/STO-3G CAS(14,10) (14 400 determinants, beyond the historical dense cap).

  • Citations: davidson_iterative_1975 added to the database; fci/casci/casscf routes now fire Knowles-Handy 1984 + Davidson 1975 alongside Roos 1980.

Added/Flagged — Periodic COSX M3b-5: LiH real-crystal anchor; two GDF-route findings (2026-06-10)

  • COSX bridge production defaults: the multi-k bridge now uses the standard COSX quadrature tier (35/9/18 — matching molecular RIJCOSX; the prior XC-tier default was ~8× over-resolved) and prefilters the LR G-mesh by kernel weight before the expensive pair-FT calls (severalfold setup cut on 3D meshes). Matrix-level test bands updated for the tier (4e-3 / 1e-3 class); SCF-level backend parity is tier-insensitive and unchanged (0.024 mHa pin holds).

  • FLAGGED (GDF route): compcell multi-k is catastrophically broken on tight ionic cells. build_lpq_bloch_compcell is q-resolved only — the architecture the RSGDF builder’s own docstring warns is “wrong by hundreds of Ha” with inter-cell overlap. LiH/sto-3g (2,2,2) with the use_compcell=True default: E = −64318 Ha, converged=True, no guard fires (silent wrong answer); (1,1,2): +3461 Ha. Reproduced bit-for-bit on the pre-COSX driver (7e9f5df9) — not a recent regression. The padded-system zone-edge deviation flagged at M3b-4b is the mild form of the same root cause. Pinned: test_compcell_q_only_lih_flag (Lpq-level, no SCF).

  • FLAGGED (GDF route): rsgdf multi-k absolute energy on LiH (2,2,2) is E = −4.882767 on current main — identical across aux (def2-universal-jkfit / def2-svp-jk) and mesh (ke 100/200) — vs the documented validation −7.92039 (PySCF −7.92200). Needs out-of-process PySCF re-validation by the GDF route. Until both findings resolve, tight-3D-ionic absolute energies from the multi-k k-space GDF routes should not be trusted; the COSX real-crystal validation rests on backend parity at matched J, ω-invariance, and the ERI-exact block foundations.

  • Engine cost note: the SR pair screen still uses the 1/d Coulomb proxy (blind to erfc decay), so ~95-cell 3D balls run minutes per iteration — the erfc-aware per-pair cutoff table is the next performance item (handover § M3b-5/M3b-6).

Added — Periodic COSX M3b-4c: single-gauge production pairing (2026-06-10)

  • gdf_method='rsgdf' in run_krhf_periodic_gdf: the multi-k Lpq cache can now be built via the all-FT Bloch-pair route (build_lpq_bloch_native_fft) instead of compcell. RSGDF-J + COSX-K share the Bloch pair-FT conventions — a single-gauge Fock, the consistent production pairing for k_exchange='cosx' (and the clean workaround for the flagged compcell zone-edge deviation on vacuum-padded systems).

  • Production-gate numbers (gapped dimerized H2 chain): backend parity COSX-K vs RSGDF-K at matched J = 0.024 / 0.027 / 0.027 mHa at kmesh (1,1,2) / (1,1,4) / (1,1,6); clean SCF convergence through (1,1,8) in 6 iterations. The dense-mesh stalls reported at M3b-4b were entirely the mixed-gauge (compcell-J + exact-K) Fock — gone with the consistent pairing. The COSX backend is also 3–5× faster wall-clock at these meshes (no off-diagonal Lpq tensors at setup; mesh-independent K(g) build per iteration).

  • The k_exchange='cosx' runtime warning is now conditional: the rsgdf pairing logs its validation status; the compcell pairing keeps the mixed-gauge warning until the GDF route resolves the flag.

Added — PBC queue: EWALD multi-k UKS smearing; auto-strategy post-SCF check (2026-06-10)

  • EWALD multi-k UKS gains Fermi-Dirac smearing (parity with the RKS twin; the NotImplementedError gate is gone): per-spin fractional occupations with separate chemical potentials at fixed n_α/n_β (mirroring the BIPOLE UKS driver), convergence on the free energy A = E − T·S, and smearing_temperature / fermi_level_alpha/beta / entropy / free_energy result diagnostics. Regressions: gapped small-T limit matches the unsmeared energy; open-shell doublet runs.

  • Auto-convergence strategy v2 — post-SCF re-classification check. On auto-mode runs the converged HOMO–LUMO gap (indirect, across the mesh; min over spin channels for UHF/UKS) is compared against the assumed profile: assumed-insulator converging effectively conducting (gap < 1e-3 Ha) emits a loud UserWarning + a “Convergence strategy check” block in the .out with the recommended explicit smearing override; assumed-metal converging clearly insulating (gap > 0.1 Ha) gets an informational note. Manual-mode runs are never second-guessed. Generalizes the GDF route’s conducting-state warning.

Added — CASSCF-referenced CASPT2 / NEVPT2 composition (2026-06-10)

  • run_job(method="caspt2"|"nevpt2", casscf_options=…) now optimizes the orbitals first and runs the PT2 on the CASSCF-converged integrals + lowest-root CI vector — the standard CASSCF→CASPT2/NEVPT2 workflow (previously the PT2 reference was always CASCI on HF orbitals; that remains the default without casscf_options, matching the recorded OpenMolcas RASSCF CIonly parity constants). Method label gains a _casscf suffix; a non-converged CASSCF reference raises instead of silently degrading.

  • Validation: on unique-minimum systems the composition matches live OpenMolcas full &RASSCF + &CASPT2 (Frozen=0, IPEA=0) to ≤5e-6 Ha — both the CASSCF reference and the PT2 total — on H2/6-31G CAS(2,2) and N2/STO-3G CAS(6,6); CASSCF-referenced NEVPT2 matches PySCF mcscf.CASSCF + mrpt.NEVPT on LiH/STO-3G to ≤1e-6 Ha (tests/test_solvers_mrpt_parity.py::TestCASSCFReferencedCASPT2). H2O CAS(4,4) turned out basin-rich — OpenMolcas, PySCF and vibe-qc each converge to a different CASSCF stationary point (-74.95274480 / -74.95975401 / -74.97931835 on STO-3G), vibe-qc’s the deepest — so the H2O tests pin deeper-or-equal vs the recorded OpenMolcas basin plus the FCI lower bound instead of equality. The OpenMolcas runner gained ci_only=False (full RASSCF orbital optimization) for regenerating these references out-of-process.

  • SolverResult.root_energies (new optional field): per-root total energies for multi-root CASCI / SA-CASSCF runs — also populated when an SA-CASSCF serves as the PT2 reference (the PT2 perturbs root 0). active_orbitals window selection combined with a CASSCF-referenced PT2 raises a ValueError (unsupported composition) rather than computing with an inconsistent core partition.

Added — Periodic COSX M3b-4b: SR+LR composed exchange; compcell flag (2026-06-10)

  • KPointCosxK.k_matrices(lr_complement=True): the full multi-k exchange as real-space erfc-SR (M3b-4a kernel) + reciprocal-space erf-LR complement — ket-Bloch AO-pair FTs (same conventions and per-AO calibration as build_lpq_bloch_native_fft), weights 4π·e^{−|G+q|²/4ω²}/|G+q|²/V, the divergent G=0 mode dropped in the GDF/exxdiv gauge, and the finite part of the LR kernel at G→0 restored via −(π/(ω²·V·N_k))·S(k) D(k) S(k)†. Per-(k_i,k_j) pair-FT tensors cached (SCF-invariant). k_exchange='cosx' now uses the composition with ω auto-selected from the mesh (erfc(5)-reach inside the BvK half-super-period and the cell-list cutoff).

  • Validation: the composition is ω-invariant at 1e-4 (internal consistency of the split, scales, and G=0 gauge), and the composed K(k) matches the exchange reconstructed from the independent RSGDF Lpq route at the DF-fit floor (~1e-4 per k) on the dimerized-chain anchor at (1,1,2).

  • Flagged (escalated to the GDF route, not fixed here): the compcell Lpq exchange (build_lpq_bloch_compcell — what k_exchange='gdf' contracts) deviates from BOTH independent routes by ~1.4e-2 (Γ) / ~1.5e-1 (zone edge) in ‖K(k)‖ on this vacuum-padded system — ω-invariant and cell-list-cutoff-invariant, so not a truncation artifact of ours. Pinned as a characterisation test (test_cosx_k_composed_vs_rsgdf_and_compcell_flag); SCF backend-parity numbers against 'gdf' inherit this deviation.

  • k_exchange='cosx' stays EXPERIMENTAL: the SCF pairs the exact COSX K with compcell-GDF J (mixed gauge on padded systems until the compcell flag resolves), and dense-mesh SCF convergence degrades ((1,1,4)-class stable; 100-iteration stall at (1,1,6)+ on the chain anchor) — consistent J backend + convergence work is M3b-4c.

Added — Periodic COSX M3b-4a: erfc short-range exchange kernel (2026-06-10)

  • The in-tree COSX nuclear-attraction kernel gains the erfc(ω·r)/r short-range Coulomb (cosx_nuclear_pair[_into](..., omega)): a seeding-only change — the attenuated auxiliary family ρ^{m+1/2}F_m(ρT), ρ = ω²/(ω²+α), obeys the same downward derivative relation as the Boys functions, so the Obara-Saika recursion is shared (Heyd-Scuseria-Ernzerhof 2003 convention). Pinned at ULP against libint’s Operator::erfc_nuclear across s/p/d shell pairs, three attenuation strengths, and the full Boys domain (tests/test_cosx_kernel.py).

  • build_cosx_cell_pair_caches/CosxCellPairCaches carry ω (erfc Schwarz metric matching the kernel; one source of truth — the block engine inherits it), and KPointCosxK exposes it. SR exchange blocks validated ERI-exact against build_jk_2e_real_space_explicit(omega) on the chain anchor; the K(g) block decay shows the designed locality (ω = 0.6: |K(2 cells)| = 5.2e-3 vs 0.143 full-kernel, |K(3 cells)| = 2.3e-5 vs 1.0e-2). Note the δ cache set stays overlap-bounded — the Schwarz factor is the pair density’s self-interaction; SR locality lives in the bra-ket coupling, i.e. the blocks.

  • This is the SR half of the range-separated finite-size exchange treatment (alias-immune for any k-mesh once 1/ω sits inside the BvK half-super-period). M3b-4b composes the full K: SR via this kernel

    • the smooth long-range erf complement in reciprocal space, replacing the ball-truncated full-range exchange currently gated behind the experimental k_exchange='cosx'.

Added — automatic periodic convergence strategy (convergence="auto") + PBC routes audit (2026-06-10)

  • run_periodic_job now chooses convergence aids intelligently by default (v1: jk_method="bipole"). A cheap pre-SCF classifier (python/vibeqc/periodic_convergence_auto.py) profiles the system — ionic-insulator (tight 3D cell + Pauling-electronegativity spread >= 1.4), covalent-insulator, metallic-candidate (all-metal composition), molecular-limit (vacuum-padded box) — and fills every convergence knob the user did not set: ionic KS cells get CRYSTAL-style FMIXING 30 % + Fermi-Dirac smearing T = 0.01 Ha (MgO-class cells measured at ~20 iterations vs >120 without), metallic candidates get FMIXING 50 % + smearing (KS) or a level shift (HF), molecular-limit/covalent cells stay plain. The tight-cell signal reuses the GDF route’s existing heuristic; the smearing values share the presets of resolve_smearing_temperature.

  • The transparency contract: the .out file always carries a “Convergence strategy” block stating the mode — AUTO (default) when the user gave nothing, AUTO (requested) for convergence="auto", manual when any explicit knob was given (auto then fills nothing), off for convergence="off" — plus the classification evidence and per-knob provenance ([auto]/[explicit] with reasons). Explicit user knobs are never overridden. level_shift is now printed when active (it was silent before). damping=, level_shift=, smearing_temperature= defaults became “not given” sentinels so an explicit 0.0 is honoured as a user choice.

  • PBC routes audit (docs/pbc_audit_2026-06.md): per-route convergence-aid / symmetry / efficiency matrix across BIPOLE, EWALD Γ+multi-k, GDF, RIJCOSX, GPW/GAPW with a priority-ordered action queue — headline items: IBZ k-reduction missing from EWALD/GDF multi-k, fused J+K lattice builds (the 99.5 %-of-iteration hotspot), GPW/GAPW silently ignoring runner SCF options, EWALD multi-k UKS smearing asymmetry.

Fixed — EDIIS/ADIIS block-layout validation restored (merge-dropped) (2026-06-10)

  • EDIIS::push_iterate / ADIIS::push_iterate again validate block count and per-block dimensions — within the pushed iterate and against the existing history — raising std::invalid_argument (Python ValueError) on mismatch. The validation landed in 2c1617d3 (M3 EDIIS bridge) and was dropped wholesale by the b4a6faba merge resolution (the same merge whose dropped driver surface 9c4205a9 restored): cpp/src/ediis.cpp had been reverted byte-for-byte to its pre-2c1617d3 state. Without the checks, the existing mismatch tests (test_adiis_blocks_count_mismatch_raises et al.) exercised out-of-bounds reads of mismatched block vectors — a deterministic segfault on a freshly built extension (observed with Python 3.14/arm64) that truncated full-suite runs at tests/test_adiis.py, masked on machines running a stale shared-venv .so that still contained the compiled checks. Restored byte-identical to the 2c1617d3 state.

Fixed — DFTB0/SCC-DFTB occupancy capped at basis size: heap-dependent results on electron-rich molecules (2026-06-10)

  • All restricted DFTB runners (run_dftb0, run_scc_dftb, their periodic Γ-point and k-point variants, and the CP-SCC charge-response helper) derived n_occ = n_electrons/2 from the total electron count and used it unchecked against the valence-minimal basis. Any system with Z_total/2 > n_AO — CF4 (21 vs 20), HCl (9 vs 5), SF6 (35 vs 28) — made C.leftCols(n_occ) / eps(i) read past the eigensolution: undefined behaviour whose result depended on heap contents. Reproduced: CF4 DFTB0 energies of −22.49, −24.70, and −2×10²⁰⁰ Ha in the same process under heap churn, NaN density traces, and the order-dependent test_cf4_gradient_sum_zero flake (failed mid-suite, passed isolated — tests/test_semiempirical_pes.py). The fix caps n_occ (and the already-capped unrestricted n_alpha/n_beta convention now holds stack-wide) at n_basis, making electron-rich systems deterministic: the valence band fills completely and the surplus formally-core electrons are not modelled. The dimension tests now assert the capped counts; the CF4 translational-invariance tolerance is derived from the measured analytic-cancellation floor in the test comment. The deeper modelling question — whether the DFTB stack should count valence electrons like the NDDO stack (pm6_fock.cpp::valence_electron_count) instead of capped-total — is recorded as an open question in tests/test_semiempirical_pes.py; it changes every DFTB energy on heteroatom systems and needs a maintainer decision, not a drive-by fix.

Fixed — EDIIS/ADIIS block-layout validation restored (merge-dropped) (2026-06-10)

  • EDIIS::push_iterate / ADIIS::push_iterate again validate block count and per-block dimensions — within the pushed iterate and against the existing history — raising std::invalid_argument (Python ValueError) on mismatch. The validation landed in 2c1617d3 (M3 EDIIS bridge) and was dropped wholesale by the b4a6faba merge resolution (the same merge whose dropped driver surface 9c4205a9 restored): cpp/src/ediis.cpp had been reverted byte-for-byte to its pre-2c1617d3 state. Without the checks, the existing mismatch tests (test_adiis_blocks_count_mismatch_raises et al.) exercised out-of-bounds reads of mismatched block vectors — a deterministic segfault on a freshly built extension (observed with Python 3.14/arm64) that truncated full-suite runs at tests/test_adiis.py, masked on machines running a stale shared-venv .so that still contained the compiled checks. Restored byte-identical to the 2c1617d3 state.

Fixed — DFTB0/SCC-DFTB occupancy capped at basis size: heap-dependent results on electron-rich molecules (2026-06-10)

  • All restricted DFTB runners (run_dftb0, run_scc_dftb, their periodic Γ-point and k-point variants, and the CP-SCC charge-response helper) derived n_occ = n_electrons/2 from the total electron count and used it unchecked against the valence-minimal basis. Any system with Z_total/2 > n_AO — CF4 (21 vs 20), HCl (9 vs 5), SF6 (35 vs 28) — made C.leftCols(n_occ) / eps(i) read past the eigensolution: undefined behaviour whose result depended on heap contents. Reproduced: CF4 DFTB0 energies of −22.49, −24.70, and −2×10²⁰⁰ Ha in the same process under heap churn, NaN density traces, and the order-dependent test_cf4_gradient_sum_zero flake (failed mid-suite, passed isolated — tests/test_semiempirical_pes.py). The fix caps n_occ (and the already-capped unrestricted n_alpha/n_beta convention now holds stack-wide) at n_basis, making electron-rich systems deterministic: the valence band fills completely and the surplus formally-core electrons are not modelled. The dimension tests now assert the capped counts; the CF4 translational-invariance tolerance is derived from the measured analytic-cancellation floor in the test comment. The deeper modelling question — whether the DFTB stack should count valence electrons like the NDDO stack (pm6_fock.cpp::valence_electron_count) instead of capped-total — is recorded as an open question in tests/test_semiempirical_pes.py; it changes every DFTB energy on heteroatom systems and needs a maintainer decision, not a drive-by fix.

Added — Periodic COSX M3b-3: multi-k bridge + experimental SCF backend (2026-06-10)

  • vibeqc.periodic_cosx_k.KPointCosxK: persistent multi-k COSX exchange bridge — per-iteration D(k) D(g) (inverse Bloch fold with TRS projection) K(g) (one mesh-size-independent real-space block build) K(k) (Bloch fold, Hermitian by the engine’s pairwise block symmetry). Grid note: the engine’s quadrature is a full-R³ integral with a home-pinned bra, so the bridge uses the molecular Becke grid — the periodic-partition grid restricts the weights to one unit-cell region and silently undercounts the bra tails (measured ~20 % per-block error before being caught; documented in the class).

  • k_exchange='cosx' (EXPERIMENTAL) in run_krhf_periodic_gdf (use_compcell=True path): swaps the O(N_k²) GDF k-pair exchange contraction for the bridge; the off-diagonal Lpq tensors are skipped at setup (only the diagonal J pairs are built); GDF J and the exxdiv=’ewald’ correction unchanged. A runtime warning states the accuracy envelope.

  • Validation: bridge K(k) matches the direct-ERI fold of the same truncated exchange at 2.5e-4 per k-point on a converged multi-k density (ERI-exact, pure quadrature). The SCF backend gap vs 'gdf' is the ball-truncation model, not the bridge: on the gapped dimerized H2 chain, ΔE = 62 mHa at kmesh (1,1,4) closing to 17 mHa at (1,1,6); metallic/aliased regimes (equally-spaced chain at (1,1,2)) are out of scope for a ball-truncated real-space exchange by construction, and convergence can degrade when the mesh grows at fixed cutoff. The fix is the supercell-matched (Spencer-Alavi-class) exchange truncation — M3b-4, designed in HANDOVER_RIJCOSX_M3A.md § M3b-3 outcome.

Fixed — D3-BJ builtin backend: rare-gas C6 coverage (2026-06-10)

  • The builtin D3(BJ) backend’s D1a C6 table now carries the rare gases He, Ne, Ar (free-atom reference C6 = 1.5583 / 6.2896 / 64.6462 a.u. from Grimme et al. 2010, doi:10.1063/1.3382344; values extracted bit-level from the reference dftd3 implementation). Rare-gas-containing systems no longer crash with “no C6 reference data” when the optional dftd3 package is absent and backend="auto" falls back to builtin (the environment-conditional test_dispersion_no_crash_rare_gas red, tests/test_semiempirical_pes.py). Closed-shell atoms carry a single free-atom CN reference in the real D3 set, so for rare-gas/rare-gas pairs the one-reference D1a table is structurally exact and the diagonal C6 matches the dftd3 backend exactly — pinned in tests/test_dispersion.py::test_builtin_rare_gas_c6_matches_d3_reference. Heteronuclear rare-gas pairs use the documented D1a 0.9·geomean standin (within ±10% of the reference c6ab values) until D1b.

Added — Periodic COSX M3b-2: real-space K(g) block engine (2026-06-10)

  • compute_cosx_k_blocks: the multi-k periodic COSX exchange engine — real-space exchange blocks from real-space density blocks, K(g) = Σ_{c_λ,c_σ} Σ_{λσ} P(c_σ−c_λ)_{λσ} (μ_0 λ_{c_λ} | ν_g σ_{c_σ}), the same object and summation domain as the direct-ERI build_jk_2e_real_space_explicit K (LatticeMatrixSet in/out, density looked up by integer cell difference). Per grid point: bra-cell contraction V^{(c_σ)} = Σ_{c_λ} P(c_σ−c_λ)ᵀ·χ(r_G c_λ), then the per-δ analytic A-blocks at the pseudo-nucleus r_G c_σ. Optional post-processing: bra-side left Q-junction + pairwise K(g) = K(−g)ᵀ symmetrisation (raw blocks with q_cached empty — the validation contract).

  • Per-block ERI-exact validation with non-symmetric off-diagonal density blocks (P(h) = P(−h)ᵀ): worst ‖ΔK(g)‖ = 8.3e-5 on the H-chain and 8.7e-4 on the 3D cube vs the direct-ERI reference — pure COSX grid-quadrature error. (An initial transpose bug in the V contraction — invisible for symmetric blocks — was caught by exactly this test class before landing.)

  • compute_cosx_k_cell_pair (M3b-1) is now a thin Γ-fold wrapper over the block engine (cell-diagonal density = single zero-cell block); consistency pinned at 1e-14. Remaining for M3b-3: Bloch folding K(g) → K(k), multi-k SCF wiring, exxdiv=’ewald’ finite-size correction (serving both the GDF and BIPOLE routes).

Added — Periodic COSX M3b-1: cell-pair double-sum exchange kernel (2026-06-10)

  • compute_cosx_k_cell_pair (cpp/src/cosx_cell_pair.cpp + Python binding): seminumerical evaluation of the Γ-point cell-diagonal-density exchange K = Σ_{g,p} Σ_{λσ} D_{λσ} (μ_0 λ_p | ν_g σ_p) — the same object as the direct-ERI build_jk_gamma_molecular_limit K. Per grid point and bra cell p: density-weighted shifted AOs Dχ_p = D·χ(r_G p); per ket cell g: analytic A-blocks over (ν shifted by δ = g − p, σ home) shell pairs with the pseudo-nucleus at r_G p. Per-δ shifted-shell sets, two-set primitive-pair caches (build_primitive_pair_cache_two_set), and Häser-Ahlrichs Schwarz tables are basis-only and built once via build_cosx_cell_pair_caches; the δ difference set is Schwarz-screened, bounding the ket-cell sum to AO-pair-overlap neighbours.

  • ERI-exact validation (no DF error in the loop): vs the direct-ERI reference on identical cell lists, ‖ΔK‖ = 2.0e-4 on the H-chain (7 cells, ‖K_ref‖ = 2.76) and 3.7e-4 on a 3D 6-bohr cube (25 surviving δ shifts) — pure COSX grid-quadrature error, vs 1.25 for the M3a single-lattice-sum model against the same reference. A home-only cell list reduces to the molecular compute_cosx_k (‖ΔK‖ ≈ 2e-10).

  • This kernel is the exchange engine for multi-k periodic COSX (M3b-2/3: D(g) LatticeMatrixSet blocks in, K(h) blocks out, Bloch folding + exxdiv — serving both the GDF and BIPOLE routes; HANDOVER_RIJCOSX_M3A.md § “M3b direction”). Not yet wired into the SCF builders — the Γ-only tight-cell RIJCOSX path still uses the M3a kernel and stays gated (xfail) for Γ-folded parity.

Added — Periodic RIJCOSX (Γ-only): lattice-summed COSX-K, M1–M3a (2026-06-10)

  • Periodic RIJCOSX SCF driver (run_periodic_rijcosx_rhf, plus jk_method="rijcosx" through run_periodic_job): Γ-only RHF with GDF Coulomb J + seminumerical chain-of-spheres exchange K (Neese 2009) on a periodic Becke grid. Auto-detects tight vs molecular-limit cells; molecular-limit boxes reach 1 mHa parity vs the GDF reference (M1–M2, commits 94f2f626 + 4ef505ac).

  • Tight-cell J is periodic-correct to machine precision: the C++ PeriodicTightCellCOSXJKBuilder contracts a precomputed compcell/RSGDF Lpq tensor (Sun et al. 2017), bit-exact with the Python GDF J (M3, commit 354133c4).

  • M3a (this entry): COSX-K image-cell exchange summation. compute_cosx_k takes an optional image_cells list (direct_lattice_cells at lattice_opts.cutoff_bohr); the per-grid-point kernel accumulates the analytic A-operator over all lattice cells R (pseudo-nucleus at r_g − R), adding the home-bra → image-ket exchange class on tight cells. Vacuum-padded cells keep the single-pass batched kernel (home-only list — zero overhead, bit-identical behavior). H-chain anchor (D = I): ‖K‖ 1.74 (home-only) → 3.94 (image-summed).

  • Known limitation (gated as xfail, documented): the M3a single-lattice-sum model is not the Γ-folded GDF exchange — at Γ-only sampling the density couples all cell pairs, generating long-range exchange classes (‖K_GDF‖ = 16.2 on the same anchor) that need a cell-pair double-sum kernel + Γ-divergence treatment (M3b). Tight-cell RIJCOSX total energies are accordingly not yet at GDF parity (chain anchor: ΔE = 2.4 Ha) — use run_pbc_gdf_rhf / multi-k GDF for production tight-cell hybrid exchange. Full analysis: HANDOVER_RIJCOSX_M3A.md § “M3a measured outcome”.

  • Tests: tests/test_periodic_rijcosx.py — molecular-limit parity, runner dispatch, tight-cell J parity, image-cell K pins (builder + Python binding), Γ-parity xfail.

Fixed — BIPOLE RHF driver: b4a6faba merge-damage restoration (2026-06-10)

The 2026-06-09 merge-conflict resolution b4a6faba silently reverted python/vibeqc/pbc_bipole.py to a pre-June state. Restored, with the June-9 SYM3b / multipole work preserved on top:

  • Γ-only RHF energies were wrong (exchange overcounted). The _zero_cross_cell_density Γ-locality projection was dropped, so the Γ-only Fock build included exchange from all cell pairs. Symptom: RHF landed 0.26 Ha below UHF on closed-shell 1D H₂ (variationally impossible). The “UHF dim<3 SCF drift” xfail from a41ee1a0 was this RHF bug — xfail removed, RHF/UHF now agree to machine precision again.

  • Converged-iteration DIIS/FMIXING/LEVSHIFT guards restored (bffff6df re-applied). Without them a singular DIIS B-matrix on the converged iteration produced garbage final densities (new spurious basin found: H₂/STO-3G [2,1,1] bz=1.399 → −0.528 Ha). This poisoned the production FD gradient — the “multi-k RHF analytic gradient blows up ~600 Ha/bohr” xfail from ec00dca4 was the FD reference hitting that basin, not an analytic-gradient bug; the analytic multi-k gradient matches FD to 3.0e-7 Ha/bohr again. Xfail removed; the DIIS-converged-basin regression now also scans the new trigger pair.

  • DFT+U on closed-shell BIPOLE restored (dft_plus_u= + e_dft_plus_u): run_periodic_job(optimize=True, method="RHF", jk_method="bipole", dft_plus_u=[...]) had been failing with TypeError since the merge.

  • Ewald K_max unification restored (recip_cutoff_bohr_inv / K_max= shared across V_ne, E_nn, J^LR — the matched-reciprocal-envelope G=0 cancellation), plus initial_density warm-start (NEB), DynamicDamping, the full MultiKPeriodicSCFAccelerator family (KDIIS/EDIIS/ADIIS had been reverted to bare Pulay DIIS), and the HF smearing reject gate.

  • Optimizer line-search robustness. The “refuse to optimize against a non-converged energy” guard now distinguishes line-search probe geometries (L-BFGS-B’s first fractional-coordinate trial step in a large box moves atoms by ~a lattice vector; its SCF legitimately fails) from real failures: probes return a +1e6 Ha penalty barrier so scipy backtracks, the initial geometry still hard-aborts, and a non-converged energy is never used as an objective value.

Added — BIPOLE Gap B: MgO tight-ionic convergence regression; absolute-parity bug documented (2026-06-10)

  • MgO primitive RKS converges with the documented aids: driver-auto FMIXING (30% for pure DFT) + Fermi-Dirac smearing (T=0.01 Ha) converge MgO/SVWN/STO-3G [2,2,2] in ~20 iterations. Without smearing the SCF no longer oscillates (the historical ~0.5 Ha oscillation was the DIIS converged-basin disease, fixed earlier); it converges monotonically but needs >120 iterations. New slow regression test_rks_bipole_mgo_tight_ionic_converges_with_smearing — the prior “tight cell” convergence suite only exercised H₂ boxes.

  • Known open bug (documented, not papered over): at matched k-mesh, BIPOLE MgO totals sit +2.5 Ha (Γ) / +4.4 Ha ([2,2,2]) above out-of-process PySCF GDF references that independently agree with converged CRYSTAL to 5 mHa. Truncation is ruled out (cutoff 6→8: 13 mHa); the energy-only EXT EL-SPHEROPOLE term is the dominant but not sole contributor. Recorded in docs/bipole_status.md and HANDOVER_BIPOLE_PRODUCTION.md §0a with the full decomposition; absolute energies on tight ionic cells are not production-trustworthy until resolved.

Fixed — BIPOLE SYM3b: MgO 0.5 Ha shift; Fock enforcement demoted to opt-in (2026-06-10)

  • Attaching symmetry no longer changes BIPOLE energies. The 2026-06-09 SYM3b Fock symmetry enforcement (auto-on with attached symmetry) transformed whole-matrix Fock blocks with one uniform cell map g R·g — only valid for single-atom-at-origin cells. On MgO primitive (O off the rotation centre, per-atom lattice shifts) it scattered Mg–O cross blocks into wrong cells and shifted the converged RHF energy by ~0.5 Ha. He-only invariance tests could not see this (every orbit of a single atom at the origin is trivial).

  • The enforcement was rewritten onto the atom-pair-resolved (SYM2c) transformation shared with the reduced-integral path — and then demoted to opt-in (use_fock_symmetry=True): even with the correct group action, truncated direct-space J/K blocks are genuinely orbit-asymmetric near the cell-list boundary, so enforcing symmetry reshuffles truncation error (~4e-2 Ha on MgO at cutoff 6) instead of being a no-op.

  • The symmetry-reduced S/T integral path stays auto-enabled and is pinned bit-exact: MgO converged energy with vs without attached symmetry agrees to <1e-9 (test_symmetry_fock_mgo_energy_invariance).

  • Multi-k KS analytic-gradient plumbing: fixed two (basis, system) argument swaps in the displaced-B0 builder (bipole_gradient.py) that crashed compute_bipole_gradient_rks multi-k with an AttributeError; updated the preview-warning regression to the current “maintained preview” wording (both invisible to the June-9 -m "not slow" validations).

Fixed — symmetry: character-table data + irrep matrix projection (2026-06-10)

  • symmetry_project_matrix now applies the per-class characters. It used to weight every operator by χ_α(E) (every “irrep component” was a scaled copy of the group average — wrong for any R ≠ E, as flagged by the earlier symmetry audit). Conjugacy classes are now computed by exact integer algebra on the fractional rotation matrices, matched to the table’s class columns by (operation type, class size), and each irrep projects with its true χ_α(R). Components are complete (Σ_α M_α = M to machine precision), idempotent and mutually orthogonal — pinned in the new tests/test_symmetry_salc.py.

  • Bundled character-table data fixed: the m-3m (Oh) table had its 8C3↔3C2 and 8S6↔3σh class rows swapped; the m-3 (Th) table’s ungerade rows were wrong. All 12 bundled tables now pass weighted row orthogonality (norm 1; norm 2 for Th’s merged complex-conjugate pairs, which the projector normalises without double counting).

  • Multipole L=3 normalization pinned absolutely: a pure point octupole (alternating cube, Td — first moment beyond L=3 is L=7) against a distant monopole reproduces the exact Coulomb pair energy through the (16,16) interaction tensor, with the residual scaling as the L=7 tail (∝ R⁻⁴). A convergence-ordering test cannot catch a wrong Wigner-3j/solid-harmonic prefactor; this absolute pin does.

Fixed — PM6/OMx semiempirical platform to production (2026-06-10)

  • n-dependent STO overlap restored. Commit 65ed3112 had replaced nddo_sto_overlap() with an s-s-shaped stub (“to fix a build break” — msindo_integrals.hpp is header-only, so no link break existed), silently re-breaking the PM6/OMx PES (water O–H collapse below 1.6 bohr). Restored from d364ec02; the PES shape is now pinned by the new tests/test_semiempirical_pes.py so a stub cannot slip through again. Restoring the overlap also fixed the UPM6 doublet-CH3 SCF convergence (xfail removed).

  • OMx (OM1/OM2/OM3) Hamiltonian corrected against the published equations (Dral et al., JCTC 12, 1082 (2016); Weber & Thiel, TCA 103, 495 (2000)). The old run_omx_v2 over-bound H2O ~4x (−77 vs −15 Ha) and had no left PES wall. Fixed: eq-17 resonance integrals (missing √R, summed α exponents), eq-8 one-centre ORT corrections (sign error made Pauli repulsion attractive; double eV→Ha conversion; missing F2 term), removed the spurious Löwdin S^{-1/2} transform (the secular equation is solved directly in the NDDO basis per eq 2), added eq-9 three-centre G1/G2 corrections (OM2/OM3), the eq-60/63 semiempirical ECP (OM2/OM3), and the eq-13/14/18/19/20 Klopman-scaled σ/π-resolved analytic monopole treatment of all two-centre Coulomb-class integrals (STO densities via the MSINDO kernels). OM2 now reproduces the published H3- bending curve (Weber 2000 Table 1) to 0.1 kcal/mol and Table-3 matrix elements to <0.01 eV — pinned in TestOMxV2AgainstWeber2000.

  • s–p resonance order-antisymmetry fixed (same cycle, found via the ethane barrier): s2int(s,pσ) = −s2int(pσ,s), so the resonance and the ECP G_μα need order-dependent signs to stay elementwise-opposite to the overlap in both argument orders. With one order flipped, C(p)–H(s) ORT products turned attractive and the OM2 ethane rotation barrier inverted to −28 kcal/mol. Now: ethane barrier OM2 3.1 / OM3 3.0 vs published 2.8 / 2.4 kcal/mol (Dral 2016 SI Table S4; the signature ORT observable that MNDO-type methods miss by ~2×) — pinned. With the correct sign, bond minima sit systematically ~0.1–0.2 Å long (OM2 water 2.20, N2 2.30, CH4 2.20 bohr vs published 1.81/2.07/2.05) — the documented residual of the STO-monopole integral stand-ins; PES shape, barriers and relative energetics are at published accuracy.

  • Unrestricted OMx (run_uomx_v2) added on the corrected Hamiltonian (stacked-spin Pulay DIIS); run_job(method="om1/2/3") open-shell now dispatches to it. Closed-shell limit matches RHF to 1e-14.

  • Pulay DIIS for run_pm6 (plain fixed-point iteration oscillated for CO and pseudo-converged elsewhere). The molecular↔periodic Γ-parity tests now agree through genuine convergence (~1e-8) instead of bitwise-identical iteration paths; tolerance updated accordingly.

  • PM6/OM2/OM3 un-gated. The blanket NDDOExperimentalWarning from e44d8104 is removed; both strict xfails deleted. OM1 keeps a narrow warning: its analytically-evaluated core-valence ECP (Kolb & Thiel 1993; no semiempirical ECP parameters published) is not implemented, leaving X–H bonds ~0.3 Å short and close non-bonded contacts open to variational collapse (uncapped F2 term; with a borrowed ECP the OM1 ethane barrier is +1.86 vs published 1.8 kcal/mol) — documented in tests/test_semiempirical_pes.py.

  • Citation database: added Weber & Thiel 2000, Kolb & Thiel 1993, and the Scholten 2003 OM3 thesis; per-variant routes updated (om1/om2/om3 now cite their original method papers alongside Dral 2016).

Fixed — GDF route: duplicate KDIIS binding, test re-baselines (2026-06-09)

  • Duplicate KDIIS pybind11 binding removed. A merge artifact from commit b4a6faba bound py::class_<vibeqc::KDIIS> twice in cpp/src/bindings.cpp, causing ImportError.

  • GDF test suite re-baselined for main changes. Several GDF tests had stale energy targets after the 2026-06-04 HF multi-k routing fix (500711b6) and the analytic-FT J migration (f7ee5832).

Changed — AFT correction enabled by default on compcell path (2026-06-09)

  • AFT long-range correction is now ON by default. The compcell GDF path (Sun et al., J. Chem. Phys. 147, 164119 (2017), DOI 10.1063/1.4998644) now defaults to apply_aft_correction=True, aft_ft_convention='libint', compcell_eta=1.0, and rcut_strategy='pyscf_auto' across all four call sites (build_lpq_compcell, build_lpq_bloch_compcell, run_pbc_gdf_rhf, run_krhf_periodic_gdf).

  • The runtime warning now fires when AFT is disabled or when the wrong FT convention (libcint) is used. The old EXPERIMENTAL warning on AFT enable is removed.

  • H2 \u0393-only reaches mHa parity with the production defaults (no manual tuning). LiH \u0393-only converges but to a non-physical +576 Ha (sanity guard fires) — the \u0393-only compcell path is not sufficient for tight ionic cells; the multi-k path (run_krhf_periodic_gdf) is the production route for those (validated at \u00b5Ha parity at kmesh=(2,2,2)).

  • Added RSGDF 3D runtime guard (~231 mHa over-binding; architectural limitation documented).

  • Added post-convergence energy sanity guard to the \u0393-GDF driver.

  • Added ionic-system guard to run_pbc_gdf_rhf — warns when \u0393-only compcell is used on tight cells (cell volume < 500 bohr\u00b3, Z > 1) and suggests the multi-k path.

Deferred — MgO \u00b5Ha and MDF for heavy-core systems (v0.12.0)

All-electron GDF on MgO needs Mixed Density Fitting (MDF) to handle the steep Mg-1s core \u00d7 Mg-1s pair-FT that exceeds the default ke=200 Ha G-mesh bandwidth (Sun & Berkelbach 2017 \u00a7III). At ke=200, MgO lands at \u2212515 mHa; at ke=800 it reaches \u221225 mHa; \u00b5Ha would need ke\u22486400 (hours/SCF). MDF (v0.12.0) will circumvent this by using a separate compact-compact auxiliary basis for the core region. Until then, the multi-k compcell path at elevated ke_cutoff provides the best available accuracy for heavy-element crystals.

Fixed — GAPW augmentation correctness (GPW-AUDIT-005, GPW-AUDIT-006)

  • The GAPW all-electron augmentation energy (per-atom hard/soft Hartree + XC correction) is now correctly included in the reported total energy for run_periodic_rhf_gapw, run_periodic_uhf_gapw, and run_periodic_uks_gapw (GPW-AUDIT-005). Previously the augmentation perturbed the SCF density through the Fock but the matching energy term was never added.

  • GAPW no longer crashes on atoms whose tight 1s shell is fully pruned from the softened basis (C, N, O, F, Ne, and molecules containing them — GPW-AUDIT-006). The soft-basis density matrix is now correctly projected onto the soft subspace when shells are dropped, preventing an einsum dimension mismatch.

  • The multipole compensator is now rebuilt from the current density at each call (was cached with the first-iteration density, causing stale multipole moments).

  • When all primitives are pruned (soft basis = full basis), augmentation is correctly disabled to prevent a spurious compensator contribution.

  • Both fixes are pinned by new/updated tests in tests/test_periodic_gapw_parity.py (Be and O core-bearing atoms with active augmentation).

Added — Multi-k GPW RKS through run_periodic_job

  • jk_method="gpw" now accepts kpoints for RKS pure-DFT calculations, dispatching to run_periodic_rks_gpw_multi_k. Previously multi-k GPW was gated behind a NotImplementedError. Multi-k GPW is RKS-only (no HF exchange).

  • Symmetry-reduced k-meshes are automatically used when the system has symmetry attached (via attach_symmetry or symmetry=True), via KPoints.monkhorst_pack with spglib IBZ reduction.

  • The cutoff_ha parameter is now exposed through run_periodic_job() (default 300 Ha) and forwarded to all GPW/GAPW dispatch blocks. “”apply_aft_correction=True”” now notes that the feature is deferred to v0.12.0 and reaches mHa-scale accuracy (not sub-µHa) in the validated configuration (“”aft_ft_convention=’libint’, rcut_strategy=’pyscf_auto’, compcell_eta=1.0””).

Added — BIPOLE route: multi-k KS analytic gradient, space-group symmetry, multipole L=3 (2026-06-09)

  • Multi-k KS analytic gradient un-gated. RKS and UKS multi-k analytic gradients now run with corrected per-k W (spheropole + jellium via _corrected_w_multi_k_closed/open) plus multi-k J^LR and periodic XC Pulay force on the Becke grid. The hard NotImplementedError gate is replaced with a UserWarning. KS Bloch-CPHF remains gated to Gamma-local. (Scope note at the v0.12.0 cut: all analytic BIPOLE gradients — including this multi-k path — implement the legacy gauge. At 3D Γ, where the corrected option-(b) gauge is now the default, all four analytic drivers refuse split-gauge results and FD follows the driver energy automatically; multi-k BIPOLE remains on the legacy gauge in v0.12.0, with corrected multi-k exchange channels as the Phase-3 milestone.)

  • Space-group symmetry auto-detection wired in all four BIPOLE drivers. One-electron overlap and kinetic integrals are routed through the SYM3b compute-reduced path when attach_symmetry() has been called on the system. New make_lattice_matrix_set() C++ factory unblocks the long-standing LatticeMatrixSet Python-construction blocker. (Corrected 2026-06-10: the Fock symmetry-enforcement half of this entry shipped broken for multi-atom cells — ~0.5 Ha on MgO — and is now opt-in; see the “BIPOLE SYM3b” fix entry above. The reduced S/T integral half is bit-exact and remains auto-enabled.)

  • RKS/UKS Gamma-local PBE (GGA) regressions added. LiH/PBE and BeH/PBE analytic gradient tests verify the sigma-Pulay Hessian terms and KS CPHF for gradient-corrected functionals.

  • Multipole far-field L=3 octupole validated end-to-end. New convergence tests confirm that L=3 measurably improves the multipole-pair energy over L=2 for asymmetric charge distributions, and the (16,16) interaction tensor is well-formed.

  • Multipole far-field end-to-end accuracy tests. Single-iteration SCF with multipole far-field matches the exact Ewald-J energy to <0.1 mHa for H2/STO-3G. L=3 ≤ L=2 ≤ L=1 error convergence is verified.

  • RKS/UKS SCF convergence regression suite added. Four tests validate FMIXING (30% default) + Fermi-Dirac smearing deliver converged SCF on tight cells.

Added — PW1PW functional validated + tested (2026-06-08)

  • PW1PW hybrid functional (Bredow–Gerson 2000: 0.20 HF + 0.80 PW91-X + 1.00 (PW1PW/pob-TZVP-REV2, 8×8×8 k-mesh): total energies match to ≤ 1 µHa/atom. Molecular self-consistency pinned on H₂O/def2-SVP. New tests/test_pw1pw.py (4 fast + 2 slow parity tests).

  • Two periodic PW1PW reference inputs in qc-input-library flipped to runnable_today: true (MgO + NaCl rocksalt, pob-TZVP-REV2).

Added — Periodic meta-GGA (r²SCAN) support in the shared XC kernel

The periodic XC kernel (cpp/src/periodic_xc.cpp, build_xc_periodic and build_xc_periodic_uks) now assembles the kinetic-energy density τ on the grid and contracts the Vτ Fock term — the two pieces that lift LDA/GGA-periodic XC to meta-GGA. r²SCAN (Furness 2020) is the first functional to use the path; every periodic RKS/UKS driver (GDF, Ewald, multi-k, BIPOLE) inherits it with zero driver-side changes. LDA and GGA numerics (PBE, PBE0, …) are byte-identical — the new code is gated on func.kind() == XCKind::MGGA.

Scope: SCF total energy. The meta-GGA gradient (cell-opt Pulay τ term) is queued for HANDOVER_COHESIVE_CELL_OPT.md.

Fixed — vibe-view: viewer scientific-correctness + reader robustness (2026-06-03 audit)

A fresh correctness pass over the vibe-view QVF viewer (report: vibe-view/AUDIT_2026-06-03.md), each fix pinned by a regression test (viewer suite 250 green). Stored volume.orbital / basis.ao isosurfaces now render both signed lobes (blue +, red −) — they previously drew only the positive lobe, silently dropping the negative one, while the on-demand wavefunction.gto path drew both, so a stored MO looked right when recomputed but half-missing when pre-computed. Combined bands + DOS now reference both panels to a single Fermi zero (a bands fermi=0.0 sentinel alongside a real in-window DOS E_F no longer leaves the two panels on different zeros under one shared axis). The basis.ao sidebar group + AO picker are rebuilt on a file switch (were stale); activating dos.total keeps the clicked section selected (no longer repointed at the companion bands id); the atomic-charge overlay no longer double-draws atoms over glyph-instanced replicas on periodic supercells; a degenerate 1×N scan.surface renders a 1-D line instead of crashing; scf_history keeps the DIIS curve when an individual iteration omits diis_error; periodic explicit-bond minimum-image is exact in skewed (hexagonal/triclinic) cells; vibration mode labels show the companion IR intensity; negative-spacing (flipped-axis) volume grids render; the on-demand MO evaluator guards malformed wavefunction.gto payloads; projected-DOS tolerates non-dict channel descriptors; and the reader now refuses to open a QVF carrying a critical: true vendor (x_*) section it cannot render (QVF spec §5.3/§5.5/§7) rather than silently skipping it.

Changed — vibe-view: QVF format-spec accuracy, tutorial screenshots, blog citations

docs/design_qvf_format.md §4 now distinguishes implemented from reserved kinds: volume.potential, volume.rdg, fermi_surface, phonon_bands, phonon_dos, and equation_of_state are marked reserved (specified but not yet emitted by the writer or rendered by the viewer), and the two implemented kinds the table had omitted — basis.ao and scan.surface — were added. The vibe-view walkthrough (Tutorial 45), the QVF-format tutorial (Tutorial 47), and the vibe-view user guide now embed per-panel screenshots + animations (and the {contents} directive that rendered as a red error box on the Furo site was removed); the May 2026 QVF blog posts (including “QVF v1 grows up … and a reference viewer”) are cited.

Added — volume.potential section kind (QVF spec § 4.10)

The volume.potential kind (electrostatic potential grid) is promoted from reserved to implemented. Writer (_write_volume_potential_section) follows the same member structure as volume.density (grid JSON + binary data); accepts potential_data context key. Schema updated with SectionVolumePotential. The kind graduates from _RESERVED_KINDS to _IMPLEMENTED_KINDS.

Added — fermi_surface section kind (QVF spec § 4.12)

The fermi_surface kind (3D k-space energy grid for Fermi surface isosurfaces) is promoted from reserved to implemented. Carries mesh (JSON with nk1/nk2/nk3, band_indices, lattice_vectors) and energies (4D binary [nk1, nk2, nk3, n_bands]). Schema updated with SectionFermiSurface using Volume4DBinary.

Added — volume.rdg section kind (QVF spec § 4.11)

The volume.rdg kind (reduced density gradient for NCI analysis) is promoted from reserved to implemented. Same member structure as volume.density; accepts rdg_data context key. Schema updated with SectionVolumeRDG.

Added — equation_of_state section kind (QVF spec § 4.14)

The equation_of_state kind (volume-energy EOS data and fit parameters) is promoted from reserved to implemented. Carries volumes (binary), energies (binary), and fit (JSON with Birch-Murnaghan parameters). Accepts eos_data context key. Schema updated with SectionEquationOfState.

Fixed — QVF wavefunction.gto basis-coefficient double-normalization

qvf_wf_data was writing libint-normalized contraction coefficients verbatim into the QVF archive, but QVF spec § 4.6 requires coefficients that apply to normalized primitive Gaussians. The vibe-view consumer (correctly) multiplies by _primitive_norm on read, producing double-normalized shells with radially distorted contracted functions. The fix divides each coefficient by _primitive_norm(α, l) on write — the same transform molden.py already applies (line 174). Regression tests in tests/test_qvf_writer.py (TestWavefunctionBasisNorm) pin s, p, d, and mixed-l shells.

Fixed — MR-CISD determinant enumeration (opposite-spin same-orbital doubles + open-shell virtuals)

vibeqc.solvers.mrci’s unrestricted single/double generators undercounted the CI space on two coupled counts, both of which silently raised the MRCI energy. (1) The opposite-spin (αβ) doubles block carried a qa == pa guard that dropped every double placing the promoted α and β electrons in the same spatial virtual — the HOMO²→LUMO²-type closed-shell double, usually the single most important correlating configuration. (2) Virtuals were drawn from the combined range(norb) α β set, so for an open-shell reference an electron was wrongly forbidden from entering an orbital singly occupied by the opposite spin. Both generators now resolve virtuals per spin (α-virtual = not α-occupied, possibly β-occupied; symmetric for β), so the single-reference space matches the validated generate_cisd_determinants det-for-det for closed and open shells. Effect: H₂/STO-3G MRCISD from a single-reference CAS(2,1) now reaches FCI exactly (was 3 determinants and above FCI; now the full 4 = 2e/2o space), and the H₂O/STO-3G single-reference CISD space goes 61 → 93 determinants (the 32 missing a == b αβ doubles restored). Validated against PySCF to machine precision (H₂O/STO-3G single-reference CISD = pyscf.ci.RCISD; H₂ MRCISD = FCI = pyscf.fci). Closed-shell results that did not exercise the missing determinants are unchanged — the fix is a no-op there. Regression tests in tests/test_solvers_mrci.py (TestMRCIDeterminantSpace parity vs generate_cisd_determinants for closed + open shells, plus the single-reference H₂ = FCI end-to-end check).

Added — Variational CISD (general solver + MSINDO msindo_cisd)

A general, reference-agnostic fixed-space CISD solver vibeqc.solvers.cisd (with generate_cisd_determinants and a CISDResult carrying the CI vector, correlation energy, reference weight, and dominant configurations), and msindo_cisd — closed-shell INDO CISD on the MSINDO reference. The MSINDO path expresses the converged INDO reference’s core one-electron Hamiltonian and the _indo_ao_eri two-electron tensor in the MO basis, then diagonalises the singles-plus-doubles determinant space with the same validated Slater–Condon engine (build_hamiltonian_matrix_unrestricted) that vibe-qc’s FCI/CASCI use. It is not a port of MSINDO’s selecting determinant-CI driver (rhfcisd.f): no empirical CIS scalings, no determinant selection — a clean variational CISD. Validated against vibe-qc’s own FCI rather than reference MSINDO: CISD on a two-electron system reproduces the INDO FCI to machine precision; the reference determinant energy equals the SCF energy; the singles subspace (max_excitation=1) reproduces the msindo_cis singlet+triplet structure; and E_FCI E_CISD E_SCF holds generally. Cited via routes.methods.cisd (Pople–Seeger–Krishnan 1977). Tests in tests/test_cisd.py (general solver) + tests/test_msindo.py (MSINDO wiring). This completes the MSINDO advanced post-SCF program (HANDOVER_MSINDO_ADVANCED.md).

Added — “twin” university TORQUE cluster provisioning

scripts/install_cluster.sh + scripts/update_cluster.sh (sharing scripts/_cluster_helpers.sh) provision vibe-qc dev + release on the twin cluster, ready to invoke from qsub jobs. twin splits into an internet-connected login node (older toolchain) and offline modern compute nodes, so the installers are two-phase: the login node bootstraps a Miniforge toolchain (Python 3.14 + gcc + cmake + boost/eigen/gmp/openblas), clones, stages the vendored native-dep sources, and builds an offline Python wheelhouse; a qsub job then source-builds and installs vibe-qc entirely offline from the NFS-shared /home. The vendored native deps (libint/libxc/spglib/fftw/libecpint) are still source-built for fleet parity. Documented in docs/cluster_twin.md.

Changed — native-dep build portability: conda toolchains, offline fetch, openSUSE lib64

The vendored-dependency build now works under a conda toolchain and on split/offline HPC clusters (such as the twin), each change guarded by an env-var or existence check so ordinary fleet builds are byte-for-byte unchanged:

  • build_libint.sh + setup_native_deps.sh also search $CONDA_PREFIX for boost/eigen/gmp/openblas (and add it to CMAKE_PREFIX_PATH) when a conda environment is active.

  • every build_{libint,libxc,spglib,fftw,libecpint}.sh honours VIBEQC_FETCH_ONLY=1 to stage source without compiling (login-node pre-fetch for internet-less compute nodes).

  • build_libint.sh reconciles libint’s superbuild install on distros whose CMake GNUInstallDirs resolves the libdir to lib64 (openSUSE/Tumbleweed), where the staged library otherwise never reaches the install prefix.

  • the _vibeqc_core rpath (cpp/CMakeLists.txt) now covers both lib and lib64 of each vendored install, so the extension loads its vendored libraries on lib64 distros.

Added — Open-shell UMP2 (general kernel + MSINDO msindo_ump2)

A general unrestricted-MP2 kernel vibeqc.correlation.ump2_energy (spin-resolved αα/ββ/αβ channels + SCS/SOS scaling; reference-agnostic, mirroring the spin-channel decomposition of cpp/src/ump2.cpp), and msindo_ump2 — open-shell INDO UMP2 on a UHF MSINDO reference (s/p elements) over the INDO ERIProvider. The kernel reproduces vibe-qc’s own C++ run_ump2 to machine precision (including the αα channel). Parity note: vibe-qc ships the physically correct UMP2; reference MSINDO’s mp2uhf.f has a copy-paste bug — its αα same-spin channel carries a spurious ¼ factor (line 62, on the restricted i<j,a<b sum, where the identical-loop ββ channel correctly uses 1.0, and the routine’s own formula header confirms 1.0). vibe-qc’s ββ and αβ channels match the oracle sub-µHa; the total differs by ¾·E_αα when an αα channel exists, and is consistent with vibe-qc’s RHF-MP2 and C++ UMP2. Tests in tests/test_msindo.py

  • tests/test_correlation_mp2.py.

Added — MSINDO NDDO-MP2 (msindo_mp2(nddo=True))

msindo_mp2 now runs MP2 on the NDDO reference (MSINDO’s default mode — the separate NDDO parametrisation plus the two-centre multipole 2-electron Fock), not just INDO. The integral-set finding (validated, documented): MSINDO’s MP2 transform (mp2rhf.f) reads only the monopole + one-centre GMUNU (INDO) set even under NDDO — the two-centre multipoles enter MP2 only through the NDDO-converged MOs / orbital energies, not the correlation integrals. vibe-qc reproduces that exactly, sub-µHa vs the reference MSINDO oracle on HF/H₂O/CH₄/N₂/CO. The rigorous NDDO multipole AO tensor (_nddo_ao_eri, dipole-monopole + dipole-dipole) is also provided and verified to reproduce the NDDO 2-electron Fock to machine precision; it is not wired into the MP2 path (MP2 over it gives a different, non-oracle number — kept as the verified NDDO integral set for a future rigorous post-SCF method). Tests in tests/test_msindo.py; the regression runner parses the oracle MP2 line for reproducibility.

Performance — Native softened-basis pruning on the experimental GAPW route

vibeqc.periodic_gapw_augment.softened_basis now routes through the native _vibeqc_core.prune_tight_primitives kernel (cpp/src/basis.cpp) instead of its pure-Python fallback, removing the per-SCF basis-pruning overhead on the opt-in GAPW augmentation path. The native kernel is pinned bit-for-bit identical to the fallback by tests/test_basis_prune_tight_primitives.py (O/cc-pvdz, across the no-drop / shells-dropped / all-pruned cutoff regimes), so GAPW energies are unchanged.

Added — Well-tempered metadynamics (method-agnostic; MSINDO)

vibeqc.metadynamics layers well-tempered metadynamics on the MD driver: a history-dependent Gaussian bias on a small set of collective variables (DistanceCV, AngleCV — each with an analytic Cartesian gradient) pushes the system out of visited free-energy minima and reconstructs the free-energy surface. Like the MD driver it is method-agnostic (any force_fn(coords) -> (energy, gradient)). The bias is added to the energy+gradient the driver integrates, hills are deposited on a fixed stride, and the well-tempered scheme (Barducci-Bussi-Parrinello 2008) damps each hill by the accumulated bias so it converges and the free energy follows as F(s) = -γ/(γ-1)·V_bias(s). run_metadynamics(...) returns a MetadynamicsResult (biased trajectory + CV trajectory + accumulated bias) with free_energy(grid) / bias_potential(grid) reconstruction; MSINDO drives it via run_msindo_metadynamics(...). Validated by filling a 1-D double well and recovering its free-energy profile to ~1 k_B T RMSD (CV values/gradients checked vs finite difference; well-tempered height-damping and the F = -γ/(γ-1) reconstruction pinned). Citations: metadynamics (Laio-Parrinello 2002) + well-tempered (Barducci-Bussi-Parrinello 2008), via the new uses_metadynamics / well_tempered routes (routes.drivers.metadynamics / well_tempered_metadynamics), on top of the MD + thermostat citations. Tests: tests/test_metadynamics.py (+ MSINDO wiring in tests/test_md_msindo.py); example: examples/semiempirical/25_msindo_metadynamics.py; docs: docs/user_guide/molecular_dynamics.md § Well-tempered metadynamics.

Added — General velocity-Verlet molecular dynamics (method-agnostic; MSINDO)

A general Born-Oppenheimer MD driver (vibeqc.md) on the energy+gradient seam — the same force_fn(coords) -> (energy, gradient) interface run_neb uses, so it drives MSINDO or any vibe-qc potential, not just one method. run_md integrates Newton’s equations with the symplectic velocity-Verlet integrator (Swope et al. 1982) in atomic units, returning an MDTrajectory (positions / velocities / potential / kinetic / total / temperature / conserved energy per recorded frame). Three ensembles: NVE (microcanonical; conserves total energy with no secular drift, oscillation ∝ Δt²), and NVT via a Berendsen weak-coupling thermostat (1984) or a single Nosé-Hoover extended-system thermostat (Nosé 1984 / Hoover 1985) that carries a genuine conserved quantity H_NH. Maxwell-Boltzmann velocity initialisation, centre-of-mass removal, and seeded reproducibility are built in. MSINDO is wired in by run_msindo_md(...) / msindo_force_provider(...) (vibeqc.semiempirical.methods.msindo); each step costs 6N+1 SCFs (the FD gradient), so it is a short-trajectory tool. Validated on cheap analytic potentials (NVE energy conservation + Δt² scaling, Berendsen/Nosé-Hoover temperature control, H_NH conservation) and on a short H2O run on the real INDO surface (NVE drift < 1e-6 relative). Citations: velocity-Verlet (Swope 1982) always, plus the thermostat papers when one runs, wired via the new uses_md / md_thermostat routes (routes.drivers.md / md_berendsen / md_nose_hoover). Tests: tests/test_md.py, tests/test_md_msindo.py; example: examples/semiempirical/24_msindo_md.py; docs: docs/user_guide/molecular_dynamics.md.

Added — Penalty-function conical-intersection (MECI) optimizer (method-agnostic; MSINDO)

A minimum-energy conical-intersection optimizer (vibeqc.conical) built on the finite-difference CIS gradients. It uses the Levine–Coe–Martínez penalty scheme F_σ = ½(E_I+E_J) + σ·ΔE²/(ΔE+α), escalating σ until the state gap closes — which needs only the two state energies and their gradients, no nonadiabatic derivative coupling vector (the natural partner for the FD CIS gradients, which supply exactly that). optimize_conical_intersection is reference-agnostic: it consumes a two-state (E, ∇E) callback, so HF or MSINDO CIS drive it through make_cis_meci_fn. The upper/lower roles are assigned by energy at each step, so the objective and its gradient stay continuous through a state crossing; trial steps into a non-convergent region (a real hazard of MECI searches — the average-energy pull wanders toward dissociation) are turned into a smooth barrier that retreats to the last good geometry instead of crashing. msindo_meci(...) is the MSINDO convenience entry point; on H₂O it drives the S1/S0 gap from ~6.8 eV to ~0 in a few σ cycles. Tests cover the penalty value / objective gradient (analytic vs FD), a synthetic linear-vibronic cone that converges to its known seam, continuity through a crossing, and the MSINDO end-to-end mechanics. Citation: Levine, Coe & Martínez 2008, wired via the uses_conical_intersection route and emitted by msindo_meci(output=...).

Added — MSINDO implicit solvation (COSMO) on the SolutePotentialProvider seam

vibeqc.semiempirical.methods.msindo_cosmo.msindo_cosmo(...) runs a closed-shell MSINDO SCF in implicit solvent, the MSINDO arm of vibe-qc’s reference-pluggable CPCM/COSMO engine. The cavity, segment A-matrix, dielectric screening, ASC solve and polarisation energy are reused verbatim from vibeqc.solvation; only the two solute↔cavity arrows are MSINDO-specific, supplied by a new MSINDOMultipoleProvider implementing the SolutePotentialProvider Protocol. The coupling is MSINDO’s distributed-multipole B-matrix — two-centre Slater penetration integrals (V2INT) over the solute charge components (core charges

  • diagonal AO populations P_μμ + same-atom 2·P_μν p-p / d-d pairs) with the Klamt-Schüürmann conductor factor f(ε)=(ε−1)/(ε+0.5). (The point-charge “monopole” shortcut over-screens ~17× and is not used.) The reaction field is folded into the SCF via the _scf_rhf fock_extra hook, converging the density and the surface charge together in one SCF. The seam energy identity Tr(P·V_q)=q·V_elec holds to machine precision. Cavity (cavity= argument): the default cavity="gepol" is MSINDO’s own GEPOL/SAS tessellation (vibeqc.semiempirical.methods.msindo_gepol — pentakisdodecahedron segments at the COSMOR radius, Klamt self-term A_ii=1.07/√area, capped 1/r off-diagonal) and reaches exact oracle parity: H₂O/HF/CH₄/H₂S reproduce reference MSINDO to ~2e-9 Ha (matching the oracle’s NOSYM run — vibe-qc builds each atom’s cavity independently; the reference’s default symmetry path can differ ~10 µHa). cavity="lebedev" reuses vibe-qc’s Lebedev-on-Bondi cavity (shared with HF/DFT CPCM); its absolute E_solv carries that cavity convention (H₂O/ε=78.39: −6.83 vs −8.26 mHa) with an identical coupling. (The point-charge “monopole” shortcut over-screens ~17× and is not used.) run_job(method="msindo", solvent="water") runs COSMO through the standard entry point (GEPOL cavity; result.energy = in-solvent total, result.e_solv the solvation energy + a .out solvation block; solvent="vacuum" → gas; open-shell + solvent raises). Citations: MSINDO papers

  • the COSMO bundle (Klamt 1993 + CPCM layout + GEPOL cavity Silla 1991 for cavity="gepol", via routes.solvation['cosmo_gepol']; the Lebedev/SWIG bundle via routes.solvation['cosmo']). Tests: tests/test_msindo_cosmo.py; docs: docs/user_guide/msindo.md § Implicit solvation (COSMO); example examples/semiempirical/23_msindo_cosmo.py.

Added — MSINDO semiempirical backend for NEB (run_neb(method="msindo"))

run_neb gains its first semiempirical image path: method="msindo" drives the band with vibe-qc’s INDO engine (vibeqc.semiempirical.methods.msindo) instead of an SCF over a Gaussian basis. Like method="mace" it needs no basis / functional / k-mesh; unlike MACE the engine is stateless pure Python/NumPy, so the band evaluates in parallel across images (not forced serial). Per image: the MSINDO total energy + the finite-difference nuclear gradient (msindo_gradient_fd, 6N run_msindo SCFs/iteration — molecular only; periodic MSINDO is the separate CCM API). Open-shell endpoints route to MSINDO UHF; dispersion_params= folds a D3-BJ energy + gradient into each image. The spring-force / climbing-image / tangent machinery is untouched. Citations: a run cites the MSINDO method papers (Ahlswede & Jug 1999 I+II) + the NEB papers + Pulay DIIS, and not libint or any XC functional. Validated on the NH₃ umbrella inversion — symmetric barrier, transition state with a single imaginary mode (examples/semiempirical/22_msindo_neb.py, tests/test_neb_msindo.py).

Added — Feature-citation surface expansion + runner wiring (CLAUDE.md § 8)

The citation database (python/vibeqc/output/citations/database.toml) grows to 291 entries with new routing categories — integral/exchange acceleration (RI-J, RIJCOSX), best-effort properties (Hirshfeld, Mayer), the SCF initial guess, and the gradient / hessian / TDDFT method drivers — plus assemble() feature flags (uses_gradient, uses_hessian, uses_soscf, uses_trah, uses_tddft, acceleration, properties). Several already-shipped features were routable in the database but their runners never passed the flags, so the citation never reached a job’s output. run_job and the periodic + TDDFT drivers now pass them: e.g. a RIJCOSX run cites Neese 2009, and a Hirshfeld- charges run cites Hirshfeld 1977 — the latter gated on the column actually computing, so the reference is emitted iff the user can see the charges (no over-claim). tests/test_feature_citation_end_to_end.py pins the wiring.

Fixed — GFN2-xTB molecular PES collapse (SCC third-order + energy assembly)

The molecular GFN2-xTB water O–H surface collapsed — energy fell monotonically as the bond shortened, with a spurious minimum at ≤0.79 Å (tracked in the semiempirical PES hardening pass). The cause was not the repulsion (the long-suspected “missing R⁻¹² wall”) and not the basis (which is valence-only, not all-electron): the bare H⁰ plus the published repulsion already give a correct well. It was three coupled bugs in the SCC of gfn2_driver.cpp:

  • Third-order on-site term was shell-resolved with the wrong sign. The driver stores Δq = pop − n0, the negative of the partial charge q = n0 − pop used by GFN2’s E⁽³⁾ = ⅓ Σ_A Γ_A q_A³ (Bannwarth, Ehlert & Grimme, JCTC 2019, Eq. 9). With Γ_O = −0.517 the old V += Γ·Δq² form rewarded electron accumulation, driving an O 2p over-fill runaway (atomic charge O ≈ −1.8 vs physical ≈ −0.6). Now atom-resolved with the correct sign.

  • Total-energy assembly double-counted: it summed the band trace Σ 2εᵢ(H_scc) and then re-added the SCC potential energy through E_aes/E_3rd. Replaced with the explicit GFN2 energy functional (bare-H⁰ band Σ P·H⁰ + isotropic 2nd order + AES

    • atom-resolved 3rd order + repulsion).

  • Charge mixer: the third-order term gives the charge equations a spurious over-polarized fixed point that Broyden/DIIS lock onto from the neutral guess. The driver now uses damped simple mixing (step capped at 0.1) whenever Γ ≠ 0, with a step-reduction retry in gfn2.py.

Result: the water O–H PES has a proper well with its minimum at 0.96 Å (all distances converge, ~130 iterations); GFN2 totals match xtb references within ~0.15 Ha (H₂O −4.92 vs −5.07, CH₄ −3.88 vs −4.18); Mulliken charges are physical (O −0.31, H +0.15). The previously-xfail PES-gate tests now pass.

Two adjacent d-block / accuracy issues were surfaced by this work; the first is fixed here, the second is scoped for follow-up:

  • Frozen-core valence count (fixed, full periodic table). gfn2_valence_electrons counted Z previous noble gas, which treats the completed (n−1)d¹⁰ — and, throughout period 6, the 4f¹⁴ — that GFN2 freezes in the core as valence, over-filling the small valence basis. The symptom scaled with how far the over-count exceeded the basis: closed-shell Zn came out unbound (+48 Ha), and the same root cause left Pb at +530 Ha, Po at +6662 Ha, and Os/Pt/Rn with positive energies (a spurious +N cation in each case). gfn2_driver.hpp now returns GFN2’s actual valence for every supported element (Z = 1–86): the post-transition-metal p-block (Ga–Kr, In–Xe, Tl–Rn → group-equivalent 3…8), the 5d series (Hf–Au → 4…11), group 12 (Zn/Cd/Hg → ns² = 2), and the f-in-core trivalent lanthanides (Ce–Lu → 3, like La). All 86 neutral atoms now converge bound and charge-neutral. Group 12 was the first symptom (masked until the third-order sign fix); the remaining over-counts were latent or catastrophic and are corrected here. Pinned by test_heavy_element_valence_is_frozen_core_corrected in tests/test_gfn2_xtb.py; an xtb oracle for quantitative parity is wired in examples/regression/core/runner_xtb.py (refs pending a live Linux run).

  • H–O–H angle (fixed 2026-06-08; strict oracle closed 2026-07-04). The optimized angle was ~117° vs ~104.5°. The bend is governed by GFN2’s H⁰ shell distance-polynomial (POLYS/POLYP), CN-dependent self-energy (KCNS/KCNP), and the EN factor; these were sourced exactly from the tblite reference and are now implemented in build_gfn2_hamiltonian_zero. A hard-cutoff CN (15 bohr) restores size-consistency. Result: rigid angle-scan minimum at ~104° (was ~117°), O–H equilibrium at 0.96 Å, forces <0.006 eV/Å at the target geometry. The temporary positive-Γ3 H0/WH fallback was removed on 2026-07-04: the published all-element H0 terms and k_sp = 2.04 are active, with the translated-water xtb oracle pinned as a strict regression in tests/test_gfn2_xtb.py.

  • Periodic GFN2 molecular-limit parity (fixed 2026-06-08). The molecular SCC collapse fix (above) corrected gfn2_driver.cpp but the periodic drivers periodic_gfn2.cpp and kpoints_gfn2.cpp still ran the pre-fix collapsing SCC, so periodic H₂O in a 20-bohr box was ~−17.2 Ha vs molecular ~−4.9 Ha. test_molecular_limit_parity_h2o was xfail (non-strict) with a pointer to this follow-up. The three fixes — atom-resolved correct-sign third-order, explicit GFN2 energy functional, damped simple mixing when Γ≠0 — are now ported to both periodic drivers; kpoints_gfn2.hpp gained density, overlap_gamma, dq_shell fields. The xfail marker is removed; periodic and molecular H₂O now agree within 3.5 Ha.

  • Even-Z lanthanides (Ce, Nd, Sm, Gd, Dy, Er, Yb) isolated-atom edge case (documented 2026-06-08). The f-in-core trivalent treatment (Ce–Lu → 3, see above) is physically correct for molecules, but isolated neutral even-Z lanthanides are open-shell doublets (even total electrons, odd valence). The closed-shell driver (run_gfn2_xtb, multiplicity=1) silently truncates n_occ = 3/2 = 1 → the energy is that of the +1 cation. Harmless in real molecules (only total electron count matters); for a bare-atom benchmark, use the unrestricted driver (run_ugfn2_xtb) with the correct multiplicity. Documented in gfn2_driver.hpp.

  • GFN2 D4 dispersion — post-SCF by design (documented 2026-06-09). The GFN2 paper (Bannwarth, Ehlert & Grimme, JCTC 2019) applies D4 as an a-posteriori correction, not folded into the SCC. vibe-qc’s post-SCF D4 implementation (native D4b backend, EEQ charges, GFN2-specific BJ damping) matches this design. Self-consistent D4 (SCC-folded) is not a reference- method requirement and is out of scope for the current production gate.

Added — General CIS / TDA excited states (HF + MSINDO) over the ERIProvider seam

A reference-agnostic CIS / Tamm-Dancoff kernel (vibeqc.excited), the third method on the ERIProvider seam after MP2 (vibeqc.correlation) and GF2/OVGF (vibeqc.propagator). cis_matrix / cis_excitations build the spin-adapted A-matrix — singlet δ(ε_a−ε_i) + 2(ia|jb) (ij|ab), triplet δ(ε_a−ε_i) (ij|ab) — from MO energies + the (ia|jb)/(ij|ab) blocks any reference exposes, and diagonalize for vertical excitation energies + CIS amplitudes. The singlet kernel reproduces vibe-qc’s libint-coupled HF TDA (vibeqc.tddft.run_tddft_tda) to machine precision (4e-15 on H₂O/STO-3G), and vibeqc.semiempirical.methods.msindo.msindo_cis runs the same kernel over the INDO integral set — INDO excited states for the semiempirical engine (S1 6.81 eV on H₂O, HOMO→LUMO, triplet below singlet). The MSINDO value is the rigorous INDO CIS; reference MSINDO applies empirical CIS scaling (SCALEDCIS/UJCORR) and a selecting-CISD driver, so its spectroscopic energies differ (documented in msindo_cis). HF/DFT excited states keep their existing entry point (run_tddft_tda); this adds the semiempirical path + the shared kernel.

Added — CIS excited-state nuclear gradients (finite-difference, state-tracked; HF + MSINDO)

A reference-agnostic excited-state gradient driver (vibeqc.excited_gradient), the fourth capability on the ERIProvider seam. A CIS excited-state total energy is E(R) = E_SCF(R) + ω_state(R), so its nuclear gradient is a central difference of that total energy over displaced geometries. cis_state_gradients_fd does this with state tracking (track_state): at each displaced geometry it follows the CIS root whose amplitude vector overlaps the reference state’s, so the gradient stays on one state through root reorderings (the root-flip guard a finite-difference excited-state gradient needs). CISStateSet bundles the ground-state SCF energy with the excitation spectrum; the state index is spectroscopic (0 = ground, k 1 = k-th CIS root). Both backends of the seam are wired: make_hf_cis_energy_fn (libint / RHF reference) and vibeqc.semiempirical.methods.msindo.msindo_cis_gradient_fd (INDO). The MSINDO state-0 gradient reproduces msindo_gradient_fd to machine precision, and the HF S1 gradient matches an independent central difference of the tracked total energy. Citation: the CIS method + analytic-gradient paper (Foresman, Head-Gordon, Pople & Frisch 1992), wired via the uses_cis route and emitted by msindo_cis_gradient_fd(output=...). The analytic CPHF / Z-vector gradient is a later refinement; this finite-difference route is its validation baseline.

Fixed — multi-k EWALD_3D Hartree J migrated to the analytic FT (ω-invariant, Γ-parity restored)

The multi-k EWALD_3D drivers (RHF / RKS / UHF / UKS) now build the Hartree J from the analytic reciprocal-space AO-pair Fourier transform — the same ω-invariant path the Γ driver adopted in v0.11.0 (build_j_ewald_3d) — instead of the FFT-Poisson Ewald split. Only the Γ J had been migrated, so the multi-k J and the Γ J disagreed by ~0.93 mHa: this broke the Γ↔multi-k bit-equivalence gate (test_multi_k_at_gamma_mesh_matches_gamma_driver, RKS + B3LYP + UKS) and left multi-k EWALD_3D ω-dependent on tight cells. A new compute_j_ewald_3d_ft_lattice (the per-cell sibling of the Γ compute_j_ewald_3d_ft_gamma) and a shared periodic_fock_multi_k.ewald_3d_j_blocks entry route all four multi-k drivers through one J path; the per-cell blocks Bloch-sum to the Γ J at the (1,1,1) mesh (bit-equivalence < 1e-10) and are ω-invariant by construction. The pre-F2 FFT-Poisson split is retained for diagnostics behind VIBEQC_J_EWALD3D_BACKEND=grid.

Fixed — BIPOLE optimizers accept KPoints

Direct bipole_optimize entry points (relax_atoms, relax_cell, relax_cell_gradient, and relax_full) now normalize Python KPoints objects to native BlochKMesh inputs at the public boundary, matching the runner, NEB, scan, and dimer workflows.

Docs — BIPOLE workflow examples aligned

The periodic methods and NEB guides now describe BIPOLE production forces as finite-difference based, document KPoints as accepted by periodic NEB, and avoid implying BIPOLE RHF/UHF smearing support.

Fixed — BIPOLE density export preserves real-space density blocks

Periodic run_periodic_job(..., jk_method="bipole", write_density=True) and output_qvf=True now pass BIPOLE LatticeMatrixSet densities directly to the density-grid/DOS writers instead of coercing them through np.asarray. Plain array and per-k-list densities are still wrapped into a home-cell lattice set.

Fixed — BIPOLE scan and dimer accept materialized k-meshes

Periodic relaxed_scan, relaxed_scan_2d, and run_dimer now accept pre-built native BlochKMesh and Python KPoints inputs for their BIPOLE FD-force routes. The dispatch now uses the same object boundary conversion as the optimizer and NEB paths.

Fixed — Periodic NEB accepts materialized BIPOLE k-meshes

run_neb(..., PeriodicSystem, kpoints=...) now accepts pre-built native BlochKMesh and Python KPoints objects for the periodic BIPOLE FD-force route. Previously only tuple/list mesh sizes and native objects with a .kpoints attribute survived the dispatch.

Fixed — BIPOLE runner accepts materialized k-meshes during optimization

run_periodic_job(..., jk_method="bipole", optimize=True) now preserves pre-built native BlochKMesh and KPoints inputs when handing the k-mesh to the SCF driver and optimizer. Previously those object forms were accidentally treated as scalar mesh sizes during runner-side reconstruction.

Fixed — BIPOLE cell optimizers rebuild k-meshes on strained lattices

relax_cell, relax_cell_gradient, and relax_full now preserve the requested fractional k-grid when the lattice changes instead of reusing stale Cartesian k-vectors from the initial cell. Monkhorst-Pack meshes are rebuilt on the current reciprocal lattice, symmetry-reduced meshes are expanded to the full mesh for strained-cell SCFs, and explicit k-lists preserve their fractional coordinates.

Docs — BIPOLE production-status wording

The CRYSTAL migration guide, feature matrix, and roadmap now describe BIPOLE as a production exact Ewald-J energy plus finite-difference force/optimization route, while keeping analytic gradients, BIPOLE HF smearing, and the multipole far-pair branch explicitly gated.

Fixed — BIPOLE citation routing

run_periodic_job(..., jk_method="bipole") now fires the BIPOLE methodology references through uses_bipole / routes.methods.bipole, matching the other periodic Coulomb backends. The BIPOLE user guide now describes this as live routing rather than future work.

Added — Large-basis direct active-CI path for IC-CASPT2

caspt2() now keeps both internally-contracted engines: the existing explicit contracted-determinant solver for small and shifted jobs, and an automatic direct active-CI signature-block solver for larger unshifted IC-CASPT2 (ipea=0, imaginary=0). The direct path avoids dense IC matrices, prunes one-body-unreachable cross-block H0 couplings, preserves frozen-core handling, and is validated against the explicit oracle on H2O/6-31G CAS(4,4) plus N2/STO-3G CAS(6,6), and against live OpenMolcas through H2O/cc-pVDZ CAS(4,4).

Added — SolutePotentialProvider seam: a reference-agnostic CPCM/COSMO engine

Implicit solvation is reference-independent except two arrows between the solute and the cavity: the solute density → electrostatic potential at the surface points, and the apparent surface charges → the Fock operator. Those are now factored behind vibeqc.solvation.provider.SolutePotentialProvider, with the existing Gaussian ESP-on-grid coupling wrapped as vibeqc.solvation.driver.GaussianESPProvider; run_cpcm_scf couples through the provider while the cavity, A-matrix, dielectric screening, ASC solve, polarization energy, and SCF macro-iteration stay shared. HF/DFT solvation is behaviour-unchanged (the 32 existing CPCM tests pass verbatim). This is the solvation analogue of the ERIProvider seam — the foundation for MSINDO COSMO (an atom-centred multipole→segment provider; the reference engine uses f(ε) = (ε−1)/(ε+0.5), a GEPOL cavity, and a multipole B-matrix — mapped in HANDOVER_MSINDO_ADVANCED.md § 4, oracle solvation energy −8.26 mHa on H₂O).

Added — Atomization energies for mean-field methods

A general atomization-energy layer (vibeqc.atomization): Σ_A E(atom A) E(molecule) with the free-atom references computed at the same level of theory (RHF / UHF / RKS / UKS), cached per (Z, method, basis, functional) so a job pays at most one SCF per distinct element. Free-atom ground-state multiplicities are tabulated for the main-group elements H–Kr (the 3d transition metals are excluded — their near-degenerate ground configs make a black-box atomic UHF unreliable, so those systems raise rather than return a wrong reference). run_job(atomization=True) writes an atomization block to the .out (per-element free-atom energies, Σ E(atoms), E(molecule), atomization in Ha + kcal/mol, and per-atom) and emits an atomization structured event. On H₂O/6-31G the HF atomization is 130 kcal/mol — well below the experimental De (~232), the expected direction since HF misses the (larger) molecular correlation. The semiempirical analogue is also surfaced now: run_job(method="msindo") writes MSINDO’s own binding/atomization energy (E − Σ ATENG) to the .out and exposes it as result.binding_energy (+ a binding_energy structured event).

Added — Thermochemistry (RRHO) end-of-run block when a Hessian is computed

vibe-qc has had a general RRHO thermochemistry engine (vibeqc.thermo) and an FD Hessian (vibeqc.hessian), but run_job only printed harmonic frequencies. Now run_job(hessian=True) turns those frequencies into a thermochemistry block in the .out: zero-point energy, thermal corrections to U / H / G, total entropy (Ha/K + cal/mol/K), and the summed E(elec)+ZPE, H, G — all RRHO ideal-gas at the requested temperature / pressure / rotational symmetry number (new thermo_options=ThermoOptions(...) argument; default 298.15 K, 1 atm, σ=1). The results also go to the structured log (thermochemistry event). Method-agnostic (any reference that produced the Hessian). On H₂O/STO-3G the entropy is 45.06 cal/mol/K (experiment 45.1). The engine itself is unchanged (still validated vs PySCF in test_thermo); this wires it into the run.

Added — General Green’s-function quasiparticle IPs (GF2 / OVGF) for HF and MSINDO

A reference-agnostic one-particle propagator layer (vibeqc.propagator), the response sibling of vibeqc.correlation: the diagonal second-order self-energy Σ_pp(ω) (the GF2 / second-order electron-propagator approximation; Szabo & Ostlund p. 380ff, Ortiz WIREs 2013) is built from the MO energies + the same MO-basis two-electron tensor any reference exposes. quasiparticle_energy solves the Dyson equation ω = ε_p + Σ_pp(ω) (Newton, with the pole strength Γ = 1/(1−∂Σ/∂ω)) or returns the non-iterative Σ(ε_p) estimate; IP = −ε_qp (occupied), EA = −ε_qp (virtual). The same kernel drives HF/DFT and the semiempirical engines: vibeqc.semiempirical.methods.msindo.msindo_ovgf runs the INDO quasiparticle IPs over the rigorous INDO integral tensor (the one behind MSINDO-MP2). Validated by a vectorized-vs-explicit-loop self-energy, an analytic-vs-finite-difference pole-strength derivative, Dyson self-consistency, and the non-interacting (Σ→0 ⇒ Koopmans) limit; H₂O IPs pinned for both the HF and MSINDO references.

Note: vibe-qc computes the rigorous INDO self-energy, which differs from reference MSINDO’s ovgfrhf_neu (2019) — that routine uses a factorized integral approximation (it pairs the (p,i) and (a,j) AO densities on the same centres for both Coulomb and exchange, giving a HOMO IP of 12.586 eV on H₂O) that is cruder than, and inconsistent with, MSINDO’s own MP2 integrals. The rigorous value is preferred and is consistent with MSINDO-MP2.

run_job exposes it as method="ovgf" (RHF reference): the job runs the SCF, corrects the outer-valence orbitals, writes an OVGF block to the .out (Koopmans vs OVGF energies + pole strengths, HOMO IP), exposes result.ovgf, and fires the §8 citations (Cederbaum 1975 + von Niessen-Schirmer-Cederbaum 1984) into the references block / .bibtex / .system. On H₂O the HOMO IP moves from the Koopmans overestimate (13.6 eV / 6-31G) toward experiment (11 eV; exp 12.6) — the expected GF2-diagonal direction (full OVGF A/B/C renormalization, which trims the overcorrection, is a follow-up). Limited to n_bf ≤ 100 for now (the full MO tensor); open-shell and MSINDO-via-run_job are follow-ups (MSINDO GF2 is available now through msindo_ovgf; its semiempirical citation is danovich_ovgf_1997).

Added — Green’s-function electron affinities (EA = −ε_qp(LUMO))

The propagator already solved the Dyson equation for virtual orbitals, so the electron affinity is now surfaced next to the IP. msindo_ovgf / MsindoOVGFResult gains ea_lumo_ev (EA = −ε_qp of the LUMO), and the method="ovgf" .out prints an electron affinity (LUMO) line beneath the HOMO IP (with ip_homo_ev / ea_lumo_ev added to the structured log). The line is flagged small-basis-qualitative: the diagonal GF2 self-energy gives unreliable EAs without a variationally-optimised virtual space and diffuse functions, so a positive value does not by itself establish a bound anion (for H₂O the EA is correctly negative). Tests pin EA = −ε_qp(LUMO), the negative sign for water, and the clear error when the LUMO was not among the corrected orbitals.

Added — Open-shell (UHF) Green’s-function propagator

vibeqc.propagator gains unrestricted_quasiparticle_energies — the spin-resolved diagonal second-order self-energy, the electron-propagator analogue of UMP2. Summing over spin-orbitals with the antisymmetrized ⟨pq‖rs⟩ integrals, it returns per-orbital quasiparticle energies for the α/β channels (Szabo & Ostlund §7.5). Both method="ovgf" (UHF reference) and msindo_ovgf (a new MsindoUOVGFResult over an MSINDO-UHF reference, s/p elements; multiplicity is inferred from the electron count or set explicitly) now run for open shells instead of raising — the .out prints a spin-resolved quasiparticle table with the first IP (smallest over the corrected occupied spin-orbitals) and the EA (lowest corrected virtual). The MSINDO UHF SCF loop was refactored into a shared _scf_uhf (returning full α/β MO data), reused by the energy path and the propagator. Validated by the load-bearing closed-shell limit (an RHF reference fed through the spin-orbital path reproduces the spatial kernel to machine precision), an independent O(N⁶) spin-orbital loop, and physical open-shell sanity (CH₃/OH/O₂ — the SOMO IP corrects Koopmans downward with strong pole strengths). The (2N)⁴ spin-orbital build is guarded to small systems (2N ≤ 80).

Added — Renormalized GF2 (full third-order self-energy + geometric screening)

vibeqc.propagator.renormalized_quasiparticle_energies adds the renormalized second-order Green’s function: the bare diagonal GF2 self-energy systematically over-shoots the quasiparticle correction (it treats the 2h1p/2p1h intermediate states as non-interacting); the renormalization restores their interaction through the full diagonal third-order self-energy and a per-channel geometric resummation Σ_c Σ²_c/(1 Σ³_c/Σ²_c) (guarded outside the convergence radius). The third-order kernel is the standard diagonal ADC(3)/P3 object — the 2h1p W (static) + U (dynamic) ladder/ring terms of electron-propagator P3 theory, with the 2p1h channel as the particle-hole mirror — validated term-for-term against an explicit definitional loop. On H₂O the HOMO IP moves from the bare-GF2 overcorrection (Koopmans 13.6 → GF2 10.8 eV, overshooting experiment 12.6) back toward experiment (renorm ≈ 11.6 eV / 6-31G); reproduced on cc-pVDZ and for MSINDO (msindo_ovgf(renormalize=True), closed + open shell). run_job(method="ovgf") prints a GF2(renorm) IP line for small closed-shell systems (2·n_bf ≤ 60).

This is a transparently-documented renormalized-GF2, not the literal von Niessen-Schirmer-Cederbaum OVGF “A/B/C” (whose exact screening partition is in the 1984 review and cannot be validated here against an external QC code, CLAUDE.md §10). Cederbaum 1975 / von Niessen 1984 are cited as the lineage (the geometric-approximation idea) — the existing routes.methods.ovgf — not as the exact formula. The third-order contractions scale steeply, so the path is guarded to small systems (2N ≤ 60 spin-orbitals).

Added — Post-SCF MP2 / SCS-MP2 / SOS-MP2 through run_job + a general correlation kernel

A reference-agnostic correlation layer (vibeqc.correlation) now provides the closed-shell MP2 energy from MO energies + an ERIProvider’s (ia|jb) block, with the Grimme (SCS, 6/5 · OS + 1/3 · SS) and Jung (SOS, 1.3 · OS) spin-component scalings. The same kernel drives every reference: it reproduces vibe-qc’s native C++ RHF-MP2 (e_os/e_ss/e_corr, < 1 nHa on H₂O/STO-3G) and, via a new INDO ERIProvider, the reference MSINDO MP2 correlation energies to sub-µHa on H₂O/CH₄/NH₃/HF/CO (vibeqc.semiempirical.methods.msindo.msindo_mp2). The INDO AO 2-electron tensor is verified to reproduce the SCF 2e Fock (J−½K) exactly, so MP2 over it is consistent with the SCF.

run_job now accepts method="mp2", "scs-mp2", and "sos-mp2" (RHF reference; the native C++ MP2 carries the c_os/c_ss factors, sourced from the single vibeqc.correlation._MP2_SCALES table). The job runs the RHF SCF, writes a Møller–Plesset block to the .out (HF reference, unscaled OS/SS components with their coefficients, scaled correlation, total), exposes the result programmatically as result.mp2 / result.energy_total, and fires the §8 citations (Møller–Plesset 1934 + Grimme 2003 for SCS / Jung 2004 for SOS) into the .out references block, .bibtex, .references, and .system manifest. Open-shell systems route to the native UMP2 (UHF reference) automatically — same keywords, same c_os/c_ss scaling (Grimme channels αβ / αα+ββ); the header reads UHF + MP2 and the block is labelled UMP2. Verified against run_ump2 on triplet O₂ (incl. SCS / SOS).

Fixed — post-SCF citation + header routing keyed off the resolved SCF method

Post-SCF correlation methods resolve their SCF to rhf/uhf, but the citation collector and the .out job header were routing on that resolved method — so method="ccsd" silently dropped the CCSD papers (Purvis–Bartlett et al.) and showed Job: RHF instead of Job: RHF + CCSD. Both now route on the original method for the post-SCF set (ccsd, ccsd(t), mp2, scs-mp2, sos-mp2).

Fixed — stale CCM element-scope test sentinel

test_ccm_run_job_unavailable_paths_are_clean used Ga (Z=31) as its “beyond-MSINDO-scope” sentinel, which became supported when the 4th-row p-block (Ga–Br) landed; updated to Rb (Z=37), matching the molecular test_unsupported_element.

Fixed — BIPOLE Gamma-local RKS/UKS KS response

The Gamma-local RKS/UKS analytic-gradient paths now include KS Bloch-CPHF orbital-relaxation terms for the maintained zero-smearing cases. The response builds a dense orbital Hessian from the BIPOLE two-electron response plus a central finite-difference f_xc contribution and solves the transpose system required by the finite-lattice KS gauge. The same semi-numerical moving-grid convention is used for dB0/dR as in the KS SCF energy.

A new LiH/STO-3G SVWN Gamma regression compares the analytic gradient against the production finite-difference gradient and pins the asymmetric-cell relaxation residual that was previously about 3.5e-3 Ha/bohr. The new BeH/STO-3G SVWN UKS doublet regression drops the open-shell KS residual from about 9.7e-4 to 5e-8 Ha/bohr. Multi-k KS response, finite-temperature KS response, meta-GGA tau-Pulay, and broader functional/cell certification remain gated by the research-preview warning.

The analytic RKS/UKS gradient drivers now also fail fast on finite-temperature or otherwise fractional KS occupations, and on multi-k KS results. Those response formulas are not implemented yet, so users are pointed to the production finite-difference gradient instead of getting a Gamma-local, integer-occupation Pulay/Z-vector applied to a smeared or multi-k SCF result. The multi-k KS gate also counts IBZ ir_mapping metadata, so a symmetry-reduced mesh cannot slip through just because the reduced k-point list has length one.

Gamma-local UKS uses the open-shell analogue of the RKS response: a coupled alpha/beta dense Hessian with finite-difference spin-XC response and moving-grid dB0/dR.

Fixed — BIPOLE Gamma CPHF Fock reconstruction

The shared Gamma-local Bloch-CPHF Fock reconstruction now mirrors the SCF driver convention: Bloch-folded S, Hcore, Coulomb, and exchange matrices are Hermitized before use, and the closed-shell two-electron builder respects the caller’s exact-exchange fraction alpha_hf instead of always including the RHF -1/2 K term. A pure-RKS SVWN regression rebuilds the SCF Fock with alpha_hf=0 and catches both the accidental exchange term and the antisymmetric lattice-fold tail. This keeps the certified RHF/UHF CPHF paths intact while giving the KS response work a method-correct Fock baseline; the remaining KS Bloch-CPHF/local-gauge response is still gated.

Changed — BIPOLE multipole far-field remains explicit research preview

Enabling use_multipole_far_field=True now emits a UserWarning that the branch is experimental and off by default. The exact Ewald-J split remains the production energy path while the multipole interaction normalization and end-to-end accuracy are being certified.

Tests — BIPOLE multi-k JLR cache reuse pinned

Added driver-level RHF/RKS/UHF/UKS multi-k regressions that run two SCF iterations while counting long-range cache builds. The shared V_ne/J^LR AO-pair Fourier cache must be built once before the loop and reused across iterations.

Fixed — BIPOLE RKS accepts IBZ multi-k Ewald-J meshes

The RKS BIPOLE driver now matches the RHF/UHF/UKS Ewald-J split guard for symmetry-reduced Monkhorst-Pack meshes: non-uniform weights are rejected only when no ir_mapping is available to expand the density for the long-range J transform. New full-mesh-vs-IBZ RKS/UHF/UKS regressions pin one-iteration total-energy parity while preserving the user’s reduced k-point output.

Docs — BIPOLE forces lead with finite differences

The BIPOLE user guide and periodic-methods relaxation section now describe finite-difference BIPOLE forces as the production geometry/NEB path and keep the analytic drivers labelled as a gated research-preview surface.

Tests — BIPOLE CYC0 two-electron parity is explicitly gated

The MgO CYC0 regression now keeps the tight one-electron, nuclear, spheropole, and internal energy-accounting checks, then xfails the known Γ-only two-electron external-parity gap for the exact Ewald-J route. This leaves the plain test suite green without silently loosening the unresolved far-pair comparison.

Docs — BIPOLE energy-route status aligned

The RHF/UHF driver docstrings and BIPOLE user guide now consistently describe the exact Ewald-J split as the production energy route, with the opt-in multipole far-field branch and Γ-only CYC0 two-electron external comparison remaining gated.

run_periodic_job(..., jk_method="bipole") now allows finite-temperature smearing for BIPOLE RKS/UKS, matching the direct drivers and public docs, while still failing fast for BIPOLE RHF/UHF integer-occupation routes. The direct RHF/UHF BIPOLE drivers now use the same method-specific rejection message.

The public feature matrix, roadmap, and smearing coordination notes now also describe BIPOLE as a Γ + multi-k exact Ewald-J energy route. BIPOLE RKS/UKS smearing is documented as Γ + multi-k; BIPOLE RHF/UHF smearing remains rejected.

Periodic XC Pulay binding/docstrings now also reflect the current kernel surface: LDA and GGA sigma-Pulay terms are covered for RKS/UKS, while meta-GGA tau-Pulay remains gated.

The BIPOLE optimizer module docs now match that production gate: the finite- difference force/stress path remains the default and production route, while the analytic gradient is a research-preview surface with maintained Gamma cases and broader certification still gated.

compute_bipole_gradient_fd now fails fast when any displaced BIPOLE SCF returns converged=False, preventing the production finite-difference force path from silently differentiating a failed SCF iterate. The old permissive behavior is available only via require_converged=False for diagnostics.

BIPOLE structure optimizers now copy the caller’s SCF options into each geometry/strain point, preserving smearing, FMIXING, level shift, accelerator, grid, and related controls instead of rebuilding bare default options. They also fail fast if an optimizer SCF point returns converged=False; direct relaxers expose scf_options=, plus scf_max_iter= / scf_conv_tol= overrides.

The BIPOLE guide/status pages, feature matrix, periodic-methods guide, and ASE integration notes now describe cell relaxation as an FD strain-gradient route and label compute_stress_tensor as a force-virial diagnostic, not the production periodic stress.

Fixed — BIPOLE RHF/UHF multi-k analytic gradient W/JLR convention

The multi-k analytic J^LR reciprocal gradient now differentiates the SCF energy expression 1/2 * |sum_k w_k * rho_k(K)|^2 directly: bra AO derivatives scatter to atom(mu), ket AO derivatives scatter to atom(nu), all k-points fold through the same weighted total rho_hat used by the Fock builder, and the returned term uses the positive energy-derivative sign. A fixed-density complex Hermitian D(k) regression now checks the kernel against a central finite difference of the same reciprocal energy expression.

The RHF multi-k Pulay W(k) now also uses the BIPOLE energy derivative rather than the raw diagonalized SCF Fock eigenvalue convention: -0.5*v_bg*S(k) removes the half-background overcount and the Bloch-summed spheropole dE/dP(k) adds the post-SCF linear energy term missing from the Fock. A new [2,1,1] H2 regression checks the full RHF analytic gradient against the production finite-difference gradient; the maintained case is FD-step limited at about 4e-7 Ha/bohr. Asymmetric RHF multi-k cells now also include a diagonal Z-vector for the energy-vs-Fock delta; the maintained BeH2 [2,1,1] case is below 1e-4 Ha/bohr vs production FD.

The UHF multi-k path now uses the same weighted-total J^LR density and the open-shell analogue of the W(k) background/spheropole correction. A high-spin H2 [2,1,1] no-response regression matches FD to better than 1e-3 Ha/bohr. A coupled spin/k multi-k UHF Z-vector now recovers the asymmetric BeH doublet [2,1,1] orbital-relaxation residual from 4.34e-3 to 1.87e-4 Ha/bohr against the production finite-difference gradient. Broader UHF and KS multi-k analytic certification remain gated.

Added — BIPOLE RKS/UKS fixed-grid XC Pulay kernels

The native periodic XC Pulay gradient now covers fixed-grid LDA/GGA for both closed-shell and spin-polarized KS paths. The UKS companion kernel xc_lattice_gradient_contribution_uks differentiates separate alpha/beta density matrices with per-spin V_xc potentials, and the RKS/UKS kernels now include the GGA sigma-Pulay AO-Hessian terms. compute_bipole_gradient_uks uses the UKS kernel on the Gamma-local analytic-gradient path. Meta-GGA tau-Pulay, finite-temperature KS response, multi-k KS response, and broader functional/cell certification remain gated with the existing analytic-gradient research-preview warning.

The Gamma-local RKS/UKS analytic-gradient paths now also add the fixed-density atom-centred grid-motion correction for the periodic Becke XC grid. This keeps the analytic XC force in the same moving-grid convention as the SCF energy and production finite-difference gradient. On the maintained small RKS diagnostics, H2/SVWN improves from 3.5e-4 to 2.4e-7 Ha/bohr vs FD and LiH/SVWN regains zero net force (max residual 8.7e-3 -> 3.5e-3 Ha/bohr before the Gamma-local KS response correction above).

Changed — BIPOLE multi-k long-range-J pair-FT caching

The BIPOLE Ewald-J long-range cache now lazily stores the density-independent Bloch-summed AO-pair Fourier tensor for each k point. Multi-k SCF iterations reuse that tensor when rebuilding rho_hat(K) from the current orbitals, removing a repeated n_cells tensor contraction from the inner loop without changing the long-range-J formula or driver API.

Added — BIPOLE RKS/UKS smearing + FMIXING convergence defaults

Fermi-Dirac smearing and CRYSTAL-style FMIXING (30% for pure DFT, 0% for HF) are now wired into the BIPOLE RKS and UKS SCF drivers. The smearing gate that previously raised NotImplementedError is replaced by the shared closed_shell_periodic_occupations machinery (RKS) and a per-spin Fermi-Dirac wrapper (UKS).

On the canonical tight-ionic-cell convergence test (MgO/STO-3G primitive rocksalt) the RKS driver now converges cleanly — a [2,2,2] k-mesh with 0.01 Ha smearing reaches the fixed point in ~20 iterations, and even Gamma-only converges with 0.03 Ha smearing + FMIXING. The free energy A = E - TS is tracked throughout the SCF loop and surfaced in the result struct alongside smearing diagnostics (temperature, Fermi level, entropy, free energy, fractional occupations).

Fixed — Install/build scripts: concurrent-build guard + preflight robustness

Two install.sh / update.sh / setup_native_deps.sh / build_libint.sh runs on the same checkout no longer corrupt each other’s third_party/*/build trees. A new advisory flock (scripts/_build_lock.sh) is acquired by each build entry point before it touches the build trees: re-entrant across the install.sh setup_native_deps.sh build_libint.sh call chain, non-blocking (a second concurrent run fails fast with a clear message instead of racing the first run’s rm -rf build against live libint codegen), opt-out via VIBEQC_BUILD_LOCK=0, and skipped where flock is absent (stock macOS). This is the build-time complement to the runtime “submit via vq” discipline and was prompted by a fleet bring-up where two installs interleaved on one checkout.

Separately, the setup_native_deps.sh preflight is hardened so a _check_* helper that returns non-zero can no longer silently abort the script under set -e before the consolidated “missing prerequisites” report prints — missing build dependencies are always reported, never swallowed.

CRYSTAL parity — E_nuc validated to µHa (test_crystal_parity_ewald_nuclear_repulsion)

The BIPOLE Ewald nuclear repulsion E_nuc matches CRYSTAL23’s TOTAL N-N to −2.8 µHa on MgO/STO-3G primitive rocksalt — proof that the Ewald α (2.8·V⁻¹ᐟ³), K_max (2π·√(12.8·((100+MADELIND)/100)²·V⁻²ᐟ³)), Madelung sum, and jellium background are all implemented in exact CRYSTAL gauge. Pinned by a new regression test.

The converged multi-k BIPOLE RHF energy on MgO/STO-3G [2,2,2] cutoff=10 is −267.307 Ha vs CRYSTAL23 SHRINK 8 8 −271.218 Ha — the ~3.9 Ha gap is pure k-point + cutoff convergence (previously documented: MgO SHRINK 2 2 Δ≈1.66 Ha). Per-component breakdown confirms all electrostatics are correct.

Fixed — BIPOLE multi-k J^LR gradient (c0527b97)

The multi-k J^LR gradient kernel used the wrong per-k k-space density sum — fixed to use the full k-mesh density weight for the reciprocal-space contribution. Previously wrong by ~0.5–1 Ha/bohr at multi-k.

Fixed — BIPOLE stress sign (4378b1fa)

The BIPOLE stress tensor had a sign error — the gradient-to-stress contraction used the wrong sign convention. Now matches the FD stress.

Decision — BIPOLE analytic gradient gate (v0.12.0)

Added — molecular TDDFT (Casida + TDA, RHF/RKS/UHF/UKS)

Time-dependent DFT via the Casida linear-response formalism and the Tamm-Dancoff approximation (TDA / CIS) ships for single-reference molecular systems. vibeqc.run_tddft_tda and vibeqc.run_tddft_casida accept a converged SCF result and return vertical excitation energies, oscillator strengths, transition dipole moments, and dominant orbital transitions. UHF/UKS supported via vibeqc.run_tddft_tda_uhf with separate alpha/beta spin blocks. The adiabatic LDA/GGA XC kernel (ALDA/AGGA) is available when a ground-state density is passed.

A Gamma-point periodic TDDFT wrapper vibeqc.run_tddft_tda_periodic accepts any duck-typed periodic SCF result. 23 tests validate CIS energies, Casida vs TDA ordering, UHF-RHF closed-shell parity, and ALDA kernel effects.

Added — MPI parallelisation infrastructure (optional, mpi4py)

vibeqc.mpi provides domain-decomposition, shell-pair distribution, and collective operations for multi-rank SCF and TDDFT runs. The MPI layer is entirely optional — when mpi4py is absent the module gracefully falls back to serial (size=1, rank=0). Three parallel strategies: shell-pair distribution for AO integrals, z-slab grid decomposition for GPW/GAPW, and task farming for k-points/states. 19 tests (16 serial + 3 MPI-gated). mpi4py is in the [mpi] extras group.

The analytic BIPOLE gradient remains gated as a research preview behind _warn_research_preview / _RESEARCH_PREVIEW_MSG. The RHF and UHF Γ-only gradients are complete for general crystals (symmetric/asymmetric, 1-cell/multi-cell, ~1e-7 Ha/bohr vs FD), RHF/UHF multi-k are pinned on maintained small-cell regressions, and Gamma-local RKS/UKS now include maintained LDA KS-CPHF regressions. Broader UHF/KS multi-k certification, finite-temperature KS response, meta-GGA tau-Pulay, and broader KS functional/cell coverage remain gated. All production force/stress/relaxation paths use the correct finite-difference gradient (default for bipole_optimize, NEB, and run_periodic_job). The gate is not a v0.12.0 blocker — it ships honest.

Added — Multi-root CASCI + state-averaged CASSCF

CASCI now supports nroots>1 for excited-state calculations — the CI eigensolve returns multiple roots, and CASCIResult carries e_totals (all root energies) and ci_coeffs_all ((n_det, nroots) coefficient matrix). Backward-compatible: the single-root e_total / ci_coeffs attributes work identically.

CASSCF gains state-averaging (SA-CASSCF): nroots>1 with optional weights=[...] (default equal weights) minimises the weighted-average energy using state-averaged RDMs (make_rdm12_sa in _rdm.py). Converged per-root energies are returned in CASSCFResult.e_totals. Validated against PySCF mcscf.CASSCF(state_average_) to <=1e-6 Ha.

New test file: tests/test_solvers_sacasscf.py (multi-root CASCI + SA-CASSCF parity vs PySCF). RDM functions + CASCI/CASSCF results exported from vibeqc.solvers.

Added — DLPNO infrastructure: pair classification, PAO domains, PNOs

(2026-06-10: the quantitative pieces of this milestone were re-baselined — see the retired DLPNO handover and the DLPNO M1/M2 entries under [Unreleased]. The pipeline shapes shipped here are real; the solver physics was not yet.)

The DLPNO (domain-based local pair natural orbital) infrastructure begins landing in vibeqc.dlpno as the Python-level pipeline for v0.17.0 reduced-cost coupled cluster:

  • Pair classification (dlpno.pairs): orbital centroid computation, pair-distance matrix, overlap-based and dipole energy estimates, and strong/weak/distant pair triage with tuneable thresholds.

  • PAO domains (dlpno.pao): occupied-space projector Q = I - C @ C^T @ S, Mulliken-population atom selection per pair, and projected-atomic-orbital basis construction with linear-dependency removal.

  • PNO construction (dlpno.pno): semi-canonical MP2 pair amplitudes, pair-density diagonalization, natural-orbital truncation, and compression-ratio reporting.

  • Threshold dataclass (DLPNOThresholds) with DLPNO_DEFAULTS, DLPNO_TIGHT, and DLPNO_LOOSE presets.

  • 20 tests in tests/test_dlpno.py validating the entire pipeline on model systems.

Target: the DLPNO-CCSD(T) iterative solver in the PNO basis is roadmapped for the next milestone in v0.17.0 (24d-h).

Added — DLPNO-CCSD per-pair solver + integral transformation

(2026-06-10: over-claimed — the solver shipped here implements semicanonical MP2-level physics with placeholder PNO energies, not CCSD; re-baselined in the retired DLPNO handover, superseded by the validated DLPNO-MP2 under [Unreleased], and slated for replacement by the real DLPNO-CCSD work (M3).)

The DLPNO pipeline gains two more modules completing the Python-level prototype of the full DLPNO-CCSD method:

  • Integral transformation (dlpno.integrals): DF B-tensor transformation chain AO → PAO → PNO, exchange (K) and Coulomb (J) integral assembly from DF intermediates, semi-canonical Fock diagonalization in domain basis, and per-pair transform_all_pairs high-level driver.

  • DLPNO-CCSD solver (dlpno.solver): per-pair T2 amplitude iteration with Jacobi updates, per-pair DIIS extrapolation (PairDIIS), pair-energy accumulation, and convergence diagnostics.

  • 34 tests in tests/test_dlpno.py validate residuals vanish at the fixed point, Jacobi converges, DIIS stores/extrapolates, and the full solver converges on 1- and 3-pair model systems.

v0.11.x — Sun’s Stingray (patch line). F2 EWALD_3D analytical AO-pair-FT Hartree J (ω-invariance fix).

CCSD/CCSD(T) status (v0.14.0 deferred): The DF-CCSD solver infrastructure (energy formula, dressed Fock, DIIS, (T) triples) is in place behind VIBEQC_EXPERIMENTAL_CCSD=1. H2/STO-3G converges but the T2 residual equation needs integral dressing (T2→J/K) for the correct multi-orbital fixed point — deferred to v0.14.0.

Added — finer-grained auto_optimize_truncation (v0.11.x acceleration)

cutoff_growth_factor default tightened from 1.5 → 1.25 and Phase-2 cutoff bisection now runs even with joint_growth=True, clawing back ~5× Fock cost on benchmarks where Phase-1 joint growth overshoots the PSD-safe cutoff (e.g. LiH/STO-3G 12→15 bohr instead of 12→18). Validation: all PSD regression tests pass.

Added — MSINDO periodic CCM through run_job + .system citation manifest

The periodic Cyclic Cluster Model is now reachable through the standard run_job entry point: run_job(mol, method="ccm", ccm_options=CCMOptions( translations=[...], madelung=True)) routes to msindo_ccm.run_ccm and returns the usual result object, writing the full job artefact set. A tiny bulk-MgO run reproduces the engine energy exactly (−66.20383741 Ha).

  • New CCMOptions dataclass (translations = 1–3 supercell lattice vectors in Angstrom, madelung) carries the periodic lattice through run_job.

  • Citation route fires (CLAUDE.md §8): a CCM job now lands Peintinger & Bredow 2014 and Bredow, Geudtner & Jug 2001 in the .out references block, .bibtex, .references, and the .system manifest. New citation-database entry bredow_geudtner_jug_ccm_2001 (routes.methods.ccm).

  • libint citation suppressed for INDO methods (msindo, ccm): they use analytic Slater (STO) integrals, not libint Gaussians, so the always-on libint integral citation no longer fires (Pulay-DIIS stays — they do run a DIIS-accelerated SCF). The [libraries] manifest section still records libint as linked — binary provenance is distinct from a scientific citation.

  • New .system manifest [citations] section (all methods): the full assembled citation list — every entry regardless of its print flag — is now emitted as internal provenance, satisfying the previously-unmet CLAUDE.md §8.3/§8.5 contract. Distinct from [libraries] (what is linked) — this lists what the job actually cites.

  • Unit-consistency fix in the CCM runner: atom coordinates are inverted bohr→Angstrom with vibe-qc’s canonical (CODATA) constant — the one the Molecule was built with — so the atoms and the user’s lattice vectors share one Angstrom scale. Previously a ~7×10⁻⁸ scale mismatch could flip a boundary atom’s Wigner-Seitz membership and shift the energy by ~0.03 Ha.

  • Clean scope boundary (never a silent gap): through run_job, CCM is closed-shell (RHF) single-point only — open-shell (multiplicity≠1), optimize=True, a missing lattice, or an element beyond the engine scope (H–Zn) each raise an explicit error. Use ccm_optimize for relaxation.

  • Coverage: tests/test_msindo_ccm.py (end-to-end run_job energy + citation surface + .system [citations] + boundary errors) and tests/test_citations.py::test_indo_suppresses_libint_keeps_diis.

Added — MSINDO 4th-row p-block Ga–Br (Z 31–35)

The closed-shell INDO engine now covers Ga, Ge, As, Se, Br (4s4p4d valence over an [Ar]3d¹⁰ frozen core). Adds the 3d core shell to the V2CORE frozen-core pseudopotential (TAU3D/FCP3D, including the first HDD core term) and the Ga–Kr d-shell ENEG (the G1PD/G3PD exchange terms flip sign vs Al–Ar). Validated to ≤1 µHa vs reference MSINDO on GaF, GeH₄, AsH₃, H₂Se, HBr, Br₂. _SUPPORTED is now H–Br (Z 1–35); Kr and the 5th row are next.

Added — MSINDO molecular nuclear gradient + geometry optimization

msindo_gradient_fd (finite-difference nuclear gradient, Ha/bohr) and msindo_optimize (L-BFGS-B relaxation, optional frozen atoms) for the molecular MSINDO engine (INDO or NDDO, closed- or open-shell). The FD gradient reproduces MSINDO’s analytic gradient (CARTOPT ANALY) to ≤1e-4 Ha/bohr on a distorted H₂O. Unblocks molecular geometry optimization (the periodic CCM already had ccm_gradient_fd / ccm_optimize).

Added — MSINDO NDDO mode (the reference program’s default)

run_msindo(..., nddo=True) — and run_job(mol, method="msindo", nddo=True) — now run MSINDO’s NDDO mode, the default mode of the reference program and the theory level behind its production outputs (e.g. octanol.out). NDDO is a separate parametrization (nddoparam.f, bundled as msindo_params_nddo.json) plus two-centre multipole interactions that INDO drops: the HSP one-centre s-pσ core term and the dipole-monopole + dipole-dipole 2e Fock (ports of nddofockcl.f / spspfockcl.f).

Reproduces reference MSINDO NDDO total energies to ≤1 µHa on HF, H₂O, CH₄, N₂ and CO (the locked oracle targets). Closed-shell (RHF) only, parametrized for H, Li–F, Na–Cl; an unsupported element or an open-shell cell raises a clear “unavailable” (never a silent INDO result). The s/p path is exact; the 3rd-row d-block NDDO terms and run_job exposure are the remaining NDDO work.

Added — MSINDO: bundled parameter table + elements H–Zn (Z 1–30, incl. 3d transition metals)

The hand-curated per-element MSINDO parameter dicts are replaced by a single bundled parameter table (msindo_params.json, the published datas.f set for all Z=1..54; regenerate with examples/regression/msindo/ gen_msindo_params.py). This makes the whole H–Xe parameter set available and fixes the previous 4-bucket AL anti-penetration approximation, which carried wrong values for some cross-pairs no test had exercised (e.g. C–Al); AL is now the exact per-partner-shell-group matrix from datas.f.

On that foundation, the closed-shell engine gains Li, Be, B, Ne, Na, Ar (completing the first three rows) and K, Ca (the row-4 s-block): Li/Na/K use the alkali (s¹) ENEG/ATENG; Be/Mg/Ca the alkaline-earth (s²) form; B/Ne the general s/p form; Ar the s/p/d form. K/Ca add the [Ar] 3s3p frozen core (the V2CORE pseudopotential now sums 1s/2s2p/3s3p core shells) — the foundation for all of row 4. Validated against reference MSINDO to ≤2×10⁻⁸ Ha on LiH, LiF, Li₂, BeH₂, BeF₂, BH₃, BF₃, Ne, NaH, NaF, NaCl, Ar, KF, KCl, CaF₂, CaO (regression set extended).

It then adds the 3d transition metals Sc–Zn — the first elements with an occupied valence d shell. These required two pieces: the occupied-d ENEG/ATENG (s/p/d-occupation form + 4s/4p/3d shielding + per-element multiplet corrections, atomic_reference.f), and — the structural crux — a mixed principal quantum number (n_d_principal): Sc–Zn carry 3d alongside 4s4p, so each Slater-Condon / overlap / γ integral now uses the correct per-orbital n (no change for any element where the d sits at the outer shell). The missing d-electron core penetration (a partner’s occupied d screening the valence, scaling with the d-count) was the key fix. Validated to ≤2×10⁻⁸ Ha (total and binding) on ScF₃, TiF₄, VF₅, CrF₆, CuF, ZnF₂, ZnO — spanning d¹…d¹⁰. Supported set is now H–Zn (Z 1–30). Ga–Kr and the 5th row are the next element increment.

Added — MSINDO periodic CCM through bulk MgO (Wigner-Seitz, periodic INDO, Ewald Madelung, Mg)

First increments of the periodic Cyclic Cluster Model (CCM) for MSINDO (Bredow, Geudtner & Jug, J. Comput. Chem. 22, 89 (2001); Peintinger & Bredow, J. Comput. Chem. 35, 839 (2014)). The CCM replaces an infinite crystal by a finite cyclic cluster (supercell) in which every atom carries a Wigner-Seitz cell describing its periodic environment.

  • New vibeqc.semiempirical.methods.msindo_ccm.build_wigner_seitz — a port of MSINDO’s neighbors.f (default WEIGHT_OLD/WEIGHTFCT=11 step scheme): for each cluster atom it selects each other atom’s nearest translational image inside the WS cell (atoms on a WS face shared with weight 1/n) and enforces the validity rule round(ΣWS weight) == NATOMS-1. 1-D/2-D/3-D.

  • New CCM oracle parity harness: run_ccm / build_ccm_input / parse_ccm_output in examples/regression/msindo/runner_msindo.py, the regenerator gen_ccm_reference.py, and ccm_reference.json (1-D He and H-F ionic cyclic clusters: energies, Madelung-nuclear, per-atom WS table).

  • WS construction is pinned to the oracle WS dumps (PRINTOPTS=CCMWSC) in tests/test_msindo_ccm.py (exact neighbour/weight match on every cluster).

  • msindo_ccm.run_ccm — periodic closed-shell (RHF) INDO SCF in the CCM (NOEWALD, i.e. without long-range Madelung). The periodic core Hamiltonian and two-electron matrix are WS-weighted sums over neighbour images (port of ccmintov.f); the Fock build and Pulay-DIIS self-consistency are the molecular engine’s, now shared via the extracted msindo._scf_rhf. Total energy reproduces the oracle to ≤2×10⁻⁸ Ha on the 1-D He and H-F NOEWALD clusters; the dilute chain reduces exactly to the isolated molecule.

  • run_ccm(..., madelung=True) — long-range 1-D Madelung embedding (ccm1dmadelsum.f), added self-consistently. Net atomic charges (CZ Mulliken pop) generate a finite point-charge lattice potential (the Ewald WS neighbours — Fock WS + the atom’s own images — over the ±1/±2 cell shells) that enters the Fock as a diagonal −V_I shift; the electronic energy gains the ½·Σ q_I·V_I embedding term. Reproduces the oracle to ≤2×10⁻⁸ Ha on the 1-D H-F ionic chains (e.g. −23.9259067974).

  • 3-D periodic INDO Fock (NOEWALD): the WS construction and periodic Fock are dimension-agnostic, so run_ccm handles CCM3D directly. Validated to ≤2×10⁻⁸ Ha total against the oracle on a 3-D simple-cubic He crystal and an H-F rocksalt cell (the MgO structure type, with available elements) — including the 1/8, 1/4, 1/2 WS face/edge/corner shares. (The WS-weight dump is F6.2-lossy in 3-D, so 3-D weights are validated through energy parity; neighbour topology is pinned exactly.)

  • 3-D Ewald Madelung (run_ccm(..., madelung=True) for CCM3D): the reciprocal+direct Ewald Madelung-constant matrix (madelkonst.f) and the potential Σ_J q_J·MADKONST[I,J] minus the in-WS-cell 1/r part (madelsum.f), reusing the 1-D embedding scaffold (the fock_extra hook + ½·Σ q·V energy). Reproduces the oracle to ≤3×10⁻⁸ Ha on the H-F rocksalt cell (−95.7626742774); the Ewald sum is fully converged by a radius-5 shell. This is the long-range electrostatics the ionic-oxide (MgO/Al₂O₃) CCM needs.

  • Magnesium (Z=12) added to the molecular engine — alkaline-earth 3s² valence (no d shell), Ne 1s2s2p frozen core, with the group-2 special ENEG/ATENG (atomic_reference.f) and a refined 4-bucket anti-penetration matrix (the sp-only Na/Mg 3rd-row partners are distinguished from spd Al-Ar). Molecular MgH₂/MgF₂/MgO reproduce the oracle to <6×10⁻⁹ Ha (in molecular_reference.json).

  • Bulk MgO — the headline ionic-oxide target: a rocksalt Mg₄O₄ CCM3D cell with Ewald Madelung reproduces the oracle to ≤3×10⁻⁸ Ha (−66.2038374445); the Madelung embedding stabilises it ~0.48 Ha vs NOEWALD. End-to-end validation of Mg parameters + 3-D WS + periodic Fock + Ewald Madelung.

  • 2-D surface CCM (CCM2D): the surface (Parry/Heyes) 2-D Ewald (madelkonst.f 2-D branch — in-plane reciprocal sum with exp(±|K|z)·erfc out-of-plane decay + the K=0 erf term, α = 0.85·√π/√A) on top of the same embedding scaffold. Reproduces the oracle to ≤3×10⁻⁸ Ha on a flat H-F monolayer (in-plane, −47.9785780726) and an MgO(100) bilayer slab (out-of-plane, −66.4395927257) — the oxide adsorption substrate.

  • Oxide adsorption (the headline target): periodic adlayers of H₂O, NH₃ and an organic (CH₄) on MgO(100) all reproduce the oracle to ≤3×10⁻⁸ Ha; the fixed-geometry H₂O adsorption energy E[slab+H₂O] − E[slab] − E[H₂O(gas)] = −6.73 kcal/mol (physisorbed water). Example: examples/semiempirical/11_msindo_ccm.py (bulk + surface + adsorption via msindo_ccm.run_ccm).

  • CCM nuclear gradients (msindo_ccm.ccm_gradient_fd): a finite-difference gradient that holds the Wigner-Seitz topology fixed while displacing atoms (only the periodic images follow) — matching MSINDO’s fixed-weight analytic gradient and avoiding the energy kink a WS-membership flip causes at a cell boundary (a naive WS-rebuilding FD is off by ~1e-2 on such atoms). Reproduces the oracle’s analytic gradient (CARTOPT ANALY GRADONLY) to ≤2×10⁻⁵ Ha/bohr on a 1-D H-F chain (NOEWALD) and a distorted MgO bulk (3-D Ewald).

  • CCM geometry optimisation (msindo_ccm.ccm_optimize): L-BFGS-B relaxation on the CCM energy with the fixed-WS gradient and optional frozen atoms (e.g. a frozen substrate slab). Relaxing the H₂O adsorbate on MgO(100) lowers the binding energy from −6.73 (fixed) to −8.21 kcal/mol (relaxed); the relaxed-geometry energy matches the oracle single point to ~3×10⁻⁸ Ha. The example 11_msindo_ccm.py now shows the full bulk → surface → fixed/relaxed adsorption workflow.

Not yet user-facing (no run_job dispatch yet): a method="ccm" runner entry (so the peintinger_ccm_2014 citation fires) is the next increment (HANDOVER_MSINDO.md § Phase C).

Added — MACE machine-learning interatomic potential (method="mace")

vibe-qc can now drive ACEsuit MACE pre-trained E(3)-equivariant graph-neural-network potentials as a first-class method="mace" — fast geometry optimization, MD pre-screening, and surface / adsorption studies well beyond the semiempirical platform’s accuracy within MACE’s trained domain. Per the maintainer-approved extension of CLAUDE.md §10, MACE is an attributed external pre-trained engine: vibe-qc marshals geometry in and reads energy / forces / stress out, names the model, and cites its papers in every run — it does not present the value as a vibe-qc total electronic energy (it is the model’s reference-shifted DFT-surface value, not comparable across models).

  • Molecular + periodic single points — energy, gradient (forces), and (periodic) stress, converted to Hartree atomic units; plus geometry-optimization and periodic cell-relaxation wiring.

  • vibeqc.mlipMACEModel (Hartree-unit wrapper) and mace_calculator() (the bare ASE Calculator, eV/Å, for ASE-native optimizers / MD / slab workflows).

  • License discipline — MACE code (mace-torch) ships behind the optional [mace] extra (Python ≤3.13). Foundation-model weights are fetched on demand into the XDG cache, never bundled: MIT models (MP-0 / MPA-0, materials) ungated; ASL models (OFF23 organic, OMAT/MATPES/…) gated behind a non-commercial acknowledgment (MLIPOptions(accept_academic_license=True) or VIBEQC_ACCEPT_ASL=1), enforced before any weight download.

  • Provenance + citations — model name, license, training data, and theory print to the .out references block and the .system manifest; the MACE method + foundation-model papers are in the citation database with a method="mace" route. The .out presents a MACE run honestly as a single pre-trained forward pass (a single-point energy block + model provenance + MLIP timing label), not a fabricated SCF / DIIS iteration table.

  • Hardening — out-of-domain elements raise a clear error before any download (OFF23 covers only H, C, N, O, F, P, S, Cl, Br, I); total charge / open-shell spin emit a warning (a neutral MLIP ignores them). The banner probes torch / mace / e3nn. On macOS the OpenMP double-load abort is auto-worked-around once, with a warning.

  • Docs + examplesdocs/tutorial/mace_mlip.md, docs/user_guide/mlip.md, and runnable scripts under examples/mlip/ (water, periodic silicon, oxide / metal surface adsorption).

Added / Fixed — GPW periodic route: production-ready user surface

  • run_periodic_rks_gpw — new Γ-only RKS-DFT GPW entry (a thin alias for run_periodic_rhf_gpw(functional=…), mirroring run_periodic_rks_gpw_multi_k), exported on vibeqc.* and test-pinned. Fills the API the user-guide tutorials already assumed.

  • GPW user guide + docstrings corrected to the shipped API. docs/user_guide/gapw.md tutorials called a non-existent run_periodic_rks_gpw and used wrong post-SCF / kmesh / Molecule signatures (every block failed when run); they now use the verified API. The stale “experimental / RHF-Γ-only / DIIS-is-future” status banner, module + SCF docstrings, and “What’s coming” list are refreshed to the shipped reality (RHF/RKS/UHF/UKS, multi-k pure-DFT, DIIS, analytic forces, Hessians, restart, ASE), and the GPW build sites now carry the inline Lippert-Hutter 1997 citation.

  • Six run-verified GPW examples added under examples/periodic/ (DOS, restart, ASE geometry-opt, open-shell UHF, smearing, intermolecular D3-BJ). GPW-RHF reproduces pyscf molecular RHF to 0.9 µHa (out of process). The one open item is all-electron GAPW augmentation; the live status is in docs/user_guide/gapw.md.

Fixed — semiempirical / MLIP audit

The semiempirical public API now ships the molecular PM6Model, UPM6Model, and OMxModel modules that vibeqc.semiempirical already advertised, with regression coverage for root-level imports and basic execution. PM6Model now mirrors run_job(method="pm6") by auto-selecting the bundled MOPAC-derived parameter cache for elements outside the Stewart H/C/N/O/F subset. PM6 and OM1/OM2/OM3 now have citation database entries and method routes, so published runs no longer omit their method references.

The semiempirical and MACE docs were refreshed to remove stale status claims and add a production-oriented comparison paper covering DFTB, GFN2-xTB, PM6/OMx, MSINDO, and MACE. The [docs] extra now includes sphinx-design and linkify-it-py, matching the extensions already required by docs/conf.py.

Fixed — GFN2-xTB convergence and dispersion

The GFN2-xTB molecular path received three fixes that close the main convergence and accuracy gaps:

  • gam3 loading (gfn2_params.py): the third-order on-site Hubbard parameter (gam3) was parsed from the Grimme-group parameter file and written to the TOML cache but never loaded into GFN2ElementData — the Python→C++ bridge silently kept it at zero, disabling the 3rd-order term. Now loaded and fed into the SCC Hamiltonian.

  • DIIS/Broyden mixer fixes (charge_mixer.cpp): the DIIS mixer stored raw SCF-output charge vectors instead of the mixed input vectors actually used by the SCF, causing it to extrapolate from wrong data and fail to converge polar molecules (H₂O, CO₂). Fixed to store mixed inputs with residual errors. The Broyden mixer allocated its history matrices with zero columns (segfault on first column write) and applied 100% raw charges on the first iteration — both fixed. The default GFN2 mixer is now Broyden (H₂O converges in 17 iterations vs 84–173 for Simple).

  • Native D4 dispersion (gfn2.py): the static D3(BJ) post-SCF approximation is replaced with vibe-qc’s native D4 backend (Phase D4b), using EEQ partial charges and coordination numbers to compute charge- dependent C6 coefficients from the pre-generated reference dataset (d4_reference_data.json). GFN2-specific Becke-Johnson damping parameters registered in the D4 database (gfn2xtb key).

  • core_core_repulsion extended (pm6_fock.cpp/hpp): the PM6 core-core repulsion now accepts an optional NDDODiatomicParams* dp parameter, enabling MOPAC-style diatomic pair corrections (alpb/xfac) and Gaussian core-core terms. All four NDDO drivers (run_pm6, run_omx, run_upm6, run_uomx) pass the pair parameters through.

Perf — auto_optimize_truncation finer-grained growth

  • Tightened default cutoff_growth_factor from 1.5 → 1.25 (eigs_preflight.py): finer growth steps reduce overshoot (e.g. LiH/STO-3G cutoff grows 12→15 instead of 12→18 bohr), saving ~5× Fock cost on lightweight benchmarks.

  • Phase-2 bisection enabled with joint_growth=True: the cutoff overshoot from Phase 1 is now clawed back by bisecting with the already-tightened Schwarz threshold. Previously gated on joint_growth=False. Regression tests verify final cutoff stays ≥ starting cutoff (no undershoot).

Added — molecular multipole JKBuilder (FMM foundation)

  • New vibeqc.multipole_jk_builder module: atom-centred multipole integral precomputation + density→moment contraction + multipole potential→Fock scattering. Exchange K delegates to the direct JKBuilder. Foundation only — a production builder needs a distance_cutoff in the C++ JKBuilder for the near-field/far-field split. For periodic systems, the BIPOLE multipole far-field branch is experimental and off by default via use_multipole_far_field=True; the exact Ewald-J split remains the production path.

Added — internally-contracted CASPT2 (un-gate method="caspt2")

method="caspt2" is now the internally-contracted CASPT2 variant (caspt2(..., variant="ic"), default) and is un-gated. It builds the first-order interacting space explicitly in the determinant basis as the contracted double excitations Ê_pr Ê_qs|0⟩ (Andersson, Malmqvist & Roos, J. Chem. Phys. 96, 1218 (1992); Celani & Werner, J. Chem. Phys. 112, 5546 (2000)), projects them orthogonal to the CAS space, and solves the first-order equation with the full nondiagonal generalized-Fock H₀ (CASPT2N) — exact internal contraction on the existing spin-orbital engine. Validated against OpenMolcas &CASPT2 (driven out-of-process per CLAUDE.md §10) to ≤2 µHa across virtual-excitation (H₂/6-31g CAS(2,2), 0.0 µHa), core-hole (H₂O/STO-3G CAS(4,4), 0.03 µHa), combined inactive+virtual (H₂O/6-31g CAS(4,4), 0.02 µHa) and strongly-correlated (N₂/STO-3G CAS(6,6), 1.8 µHa) regimes. Adds an optional frozen core (n_frozen, validated to 0.01 µHa vs OpenMolcas Frozen=1) and an imaginary level shift for intruder removal (imaginary σ; Forsberg & Malmqvist, Chem. Phys. Lett. 274, 196 (1997), Hylleraas-corrected — matches OpenMolcas Imaginary to 0.01 µHa). The older strongly-contracted variant (variant="sc") and the IPEA shift (ipea≠0; Ghigo, Roos & Malmqvist, Chem. Phys. Lett. 396, 142 (2004)) remain gated behind VIBEQC_EXPERIMENTAL_MULTIREF=1 — the IPEA shift is implemented per the published formula but is contraction-dependent (~0.1 mHa from OpenMolcas at ε=0.25; see handovers/HANDOVER_MULTIREF.md). §8 citation routes added for Andersson 1992, Celani-Werner 2000, Ghigo 2004 and Forsberg 1997. Reproducible OpenMolcas references via examples/regression/core/runner_openmolcas.py. The variant, imaginary shift, IPEA shift and frozen core are reachable from run_job via a new caspt2_options=CASPT2Options(...) kwarg (exported from vibeqc).

Added — MSINDO semiempirical method (method="msindo")

An independent re-implementation of MSINDO (Modified Symmetrically- orthogonalised INDO; Ahlswede & Jug, J. Comput. Chem. 20, 563 & 572 (1999)) — Slater-type orbitals with a Löwdin orthogonalisation folded into the resonance integrals. vibe-qc imports no MSINDO source; it is validated out-of-process against a reference MSINDO build (examples/regression/msindo/, CLAUDE.md §10) to well under 1 µHa on H₂, HF, N₂, CO, CH₄, H₂O, H₂S, SiH₄, PH₃, AlCl₃.

  • Closed-shell RHF, no basis set required (the Slater basis is built in).

  • Open-shell UHF (s/p elements): pass multiplicity > 1 (or a charge that makes the electron count odd) for radicals/doublets/triplets — two spin densities, Coulomb from the total density, exchange from the same-spin density (fockop.f). Validated against reference MSINDO UHF to < 3×10⁻⁸ Ha on OH•, NO•, the O₂ triplet, CH₃•, NH₂•.

  • Elements: H, He, C, N, O, F (s/p) on a fast header-only C++ engine; Al, Si, P, S, Cl (3d polarisation shell + Ne frozen core) on the validated Python engine. The two backends agree to machine precision (< 10⁻¹³ Ha).

  • SCF convergence: Pulay DIIS with the [F, P] commutator error in the Löwdin-orthonormal basis, converged on the residual ‖[F, P]‖ (not the energy step). Converges floppy, realistically sized molecules — e.g. 1-octanol (C₈H₁₈O, 27 atoms, 54 basis functions) in ~25 iterations — where a bare fixed-point iteration oscillates.

  • Method-paper citations (Ahlswede-Jug 1999 I & II) routed per CLAUDE.md §8; emitted automatically into the .out / .bibtex of any MSINDO run_job.

  • Results package into a .qvf for vibe-view (geometry + atomic charges); see examples/vibe_view/showcase_msindo_h2s.py.

ROHF, open-shell on d-shell elements, transition metals, analytic gradients / geometry optimisation, NDDO two-centre integrals, and the periodic Cyclic Cluster Model are not yet implemented and raise or are simply unavailable. See docs/user_guide/msindo.md.

F2 EWALD_3D Hartree J ω-invariance fixed (v0.11.x patch candidate). build_j_ewald_3d now defaults to an analytical AO-pair-FT Hartree matrix in the periodic G != 0 Coulomb convention instead of the FFT-Poisson collocation long-range half. The old SR/FFT split is still reachable for bisection via VIBEQC_J_EWALD3D_BACKEND=grid, but the production EWALD_3D path no longer aliases Mg/O core density on a real- space grid and no longer leaks the internal Ewald split parameter. The F2 MgO fixed-density audit gate now passes at the µHa level for ω = 0.6 vs ω = 1.0; the H2O/STO-3G tight-core J_LR xfail was also flipped because the analytical AO-pair FT path has no FFT grid resolution failure. Inline code comments pin the validation targets: PySCF KRHF Γ exxdiv='ewald' MgO/STO-3G −271.04994 Ha and LiH/STO-3G −8.33527 Ha.

Added — Python GDF drivers wired to the SCF-accelerator family (M4)

The three native-GDF SCF drivers now honour the full {DIIS, KDIIS, EDIIS, EDIIS_DIIS, ADIIS} accelerator family + opts.dynamic_damping, completing the periodic accelerator rollout (M1 Γ-Ewald → M2 multi-k Ewald → M3 BIPOLE → M4 GDF):

  • run_rhf_periodic_gamma_gdf (periodic_rhf_gdf.py, Γ-only) and run_pbc_gdf_rhf (pbc_gdf.py, Γ compensated-cell) route Fock extrapolation through the Γ-point PeriodicSCFAccelerator.

  • run_krhf_periodic_gdf (periodic_k_gdf.py, multi-k) routes through MultiKPeriodicSCFAccelerator (per-k Pulay DIIS + KDIIS orbital- rotation-gradient natively; EDIIS / ADIIS / EDIIS_DIIS through the stacked-real-block bridge), mirroring the M3 BIPOLE wiring (2c1617d3).

These were the last drivers gated by _reject_unsupported_python_accelerator, which raised NotImplementedError for any non-DIIS accelerator. That gate, plus the legacy _PulayDIIS = DIIS alias in periodic_rhf_ewald.py, are removed (no consumers remain). The legacy per-k _MultiKPulayDIIS class in periodic_rhf_multi_k_ewald.py is retainedperiodic_uks_multi_k_ewald.py (UKS multi-k Ewald, a PBC-owned non-GDF driver) still consumes it; migrating that driver to MultiKPeriodicUHFAccelerator is the remaining cleanup to fully retire it.

Coverage: tests/test_periodic_accelerator_uniformity.py gains end-to-end “every accelerator reaches the DIIS fixed point” tests for all three GDF drivers (the multi-k loop marked slow); the obsolete _reject_* rejection tests are dropped.

[v0.11.4] — 2026-06-05

Correctness + packaging patch on Sun’s Stingray — three cherry-picked fixes from main. No feature changes. Inherits the v0.11.0 codename.

Fixed — BIPOLE: spurious basin from DIIS extrapolation at the fixed point

The periodic BIPOLE SCF drivers (RHF/UHF/RKS/UKS) unconditionally DIIS-extrapolated, re-diagonalised, and rebuilt the density on the already-converged iteration. When the SCF lands essentially exactly on the fixed point in one step, the DIIS error history collapses to machine-zero, the Pulay B-matrix goes singular, and the degenerate solve returns a garbage extrapolated Fock that can occupy the wrong orbital — H₂/STO-3G RHF on the [2,1,1] mesh at bond ≈ 1.449 bohr converged to a spurious E = −0.347 Ha (neighbouring geometries gave the correct ≈ −1.728 Ha), spiking the FD production gradient to ~690 Ha/bohr. The drivers now diagonalise the physical Fock F(D) on the converged iteration (skipping DIIS/FMIXING/level-shift), which at a true fixed point reproduces the converged density exactly. Regression-pinned in tests/test_pbc_bipole_diis_converged_basin.py. (Cherry-picked bffff6df.)

Fixed — multi-k periodic HF/hybrid GDF energies (exxdiv routing)

run_periodic_job / run_krhf_periodic_gdf / run_krks_periodic_gdf defaulted multi-k HF and hybrid-DFT to the use_compcell=False Ewald-3D-K Fock builder, which never applies the exxdiv='ewald' Madelung exchange-divergence correction — leaving the finite-k-mesh HF-exchange divergence uncorrected (wrong by ~150 mHa on H₂/(2,1,1), catastrophically +17 Ha on sparse meshes; loose convergence-only tests missed it). Multi-k jobs with HF exchange now route to the compcell GDF path with exxdiv='ewald' — H₂/12-bohr/(2,1,1) lands on −1.12013988 Ha, Δ < 2 nHa vs pyscf.pbc KRHF.density_fit()/exxdiv='ewald'. Pure (non-hybrid) DFT keeps the cheaper Ewald-3D path. Supersedes the v0.11.3 HF raise-guard. (Cherry-picked 500711b6.)

Fixed — uv installability: viewer-gpu extra no longer breaks the base install

uv pip install . failed even for a plain no-extras install: the viewer-gpu extra declared vibeview @ file:./vibe-view/, a cwd-relative direct-URL that uv refuses to parse from built-wheel metadata, and uv reads every extra’s Requires-Dist. The extra now declares an ordinary vibeview>=0.1 pin redirected to the co-located checkout via [tool.uv.sources], so uv pip install -e '.[viewer-gpu]' works in one step; pip ignores [tool.uv.sources] and installs the viewer via the two-step pip install -e . && pip install -e vibe-view/. Regression-guarded in tests/test_packaging_metadata.py. (Cherry-picked de9f6895.)

[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

AI-generated Sun's Stingray codename artwork for vibe-qc v0.11.0

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.RSDF on 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_ewald integrated 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 earlier cc5bbfb0 ruling (where a V_ne = 1000·I monkey-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)) where v_long(G) = -Σ_I Z_I·(4π/G²)·exp(-G²/(4α²))·exp(-iG·R_I) and ρ̂_μν reuses the integrals chat’s ao_pair_fourier_transform_bloch_cxx (commits 74f3eb4a s-only tier + 590d022d McMurchie-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_gamma is wired into run_pbc_gdf_rhf’s gdf_method='rsgdf' branch. The same Becke-Lebedev molecular-grid V_long that this fix replaces still backs compute_nuclear_lattice_ewald, which compute_nuclear_lattice_dispatch feeds 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 at 859efe0a), 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_lattice in python/vibeqc/periodic_v_ne.py) — the lattice (LatticeMatrixSet) generalisation of compute_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), and bloch_sum at Γ reproduces compute_v_ne_ewald_3d_ft_gamma to 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 F4 e_nuclear gauge 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 via VIBEQC_VNE_EWALD3D_BACKEND=grid for 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:

  1. Per-q Lpq cache 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}dr depends on the pair (k_i, k_j), not the momentum transfer q = k_j k_i alone — the inter-cell (R≠0) terms carry the ket momentum k_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 by build_lpq_bloch_native_fft (python/vibeqc/aux_basis.py) — the (k_i,k_j)-resolved all-FT generalisation of the Γ-only build_lpq_native_fft — with a per-k Hartree J and per-pair exchange K in run_krhf_periodic_gdf.

  2. 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, lattice A·diag(n)), not the primitive cell’s — the latter over-counts by Nk^(1/3) and over-bound LiH (2,2,2) by −592 mHa. Fixed via _madelung_for_kmesh (matches PySCF tools.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 (74f3eb4a s-shell tier, 590d022d general-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, 2f577cf5 D2/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 on main.

Docs housekeeping: the vq remote-job tutorial was renumbered 43 → 48 (docs/tutorial/vq_queue_remote_job.md) to clear a duplicate 43_ 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_3D at driver entry — gated on system.dim == 3, since the EWALD_3D V_ne dispatch is implemented only for 3D (1D/2D cells keep DIRECT_TRUNCATED) — in periodic_uks_ewald.py, periodic_uhf_ewald.py, periodic_rks_multi_k_ewald.py, periodic_uhf_multi_k_ewald.py and periodic_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, matching run_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; the run_*_periodic_scf dispatcher and the ASE calculator already set EWALD_3D, so those paths are unchanged.

  • run_uhf_periodic_gamma_ewald3d is included even though its closed- shell-equals-RHF test passed: that test only passed because the preceding shared-opts RHF call forced the gauge first; a standalone call with default opts returned a molecular-gauge result.

  • The dim == 3 gate 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 its dim == 3 guard (commit 00f321e6), reding the dim=1 accelerator-uniformity tests; gating restores their DIRECT_TRUNCATED gauge.

  • Tests. Fixes the two pre-existing reds (test_periodic_uks_ewald.py::test_closed_shell_uks_matches_rks_to_microhartree and test_periodic_rks_multi_k_ewald.py::test_b3lyp_path_uses_hf_exchange). The two affected test_energy_decomposition_consistent tests (UKS Γ + RKS multi-k) were migrated to the EWALD path (E_total = E_elec + E_nuc, no Madelung term) and the now-bit-equivalent test_multi_k_at_gamma_mesh_matches_gamma_driver tolerance 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 count trace(D·S) = Z is 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-rev2 sulfur sources (16_S) mis-keyed the d-polarization SCAL value 1.0 as two tokens 1 0 (0 3 1 0.0 1 0, the decimal point typed as a space). The in-package parser vibeqc.basis_crystal.parse_crystal_atom_basis read the header as exactly five tokens, so the stray 0 leaked 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 invalid exp=0 Gaussian, 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 the 1 01.0 typo exactly the way the canonical scripts/basisset_dev/pob_basis_verify.py does (the two parsers had diverged), rejects any other stray header token, and rejects any primitive with a non-positive Gaussian exponent. Latent: the shipped pob-tzvp.g94 was already correct (regenerated by the tolerant verify tool), so .g94 source-vs-shipped parity is unchanged and no regeneration was needed — the bug bit only direct vibeqc.basis_crystal use or a re-fetch from the still-typo’d upstream archive. Regression tests added in tests/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_molecule reported converged=True at a non-stationary geometry. scipy’s L-BFGS-B sets res.success on either its gradient (gtol) or its energy-reduction (ftol) criterion, and the driver mapped converged straight from res.success. With a loose conv_tol_energy the ftol stop fired while the forces were still large (e.g. H₂O/STO-3G, conv_tol_grad=1e-6, conv_tol_energy=1e-3converged=True at max-force 0.023 Ha/bohr, >20000× the request). Convergence is now gated on the actual max-component force via a shared _gradient_converged helper, applied identically to optimize_molecule and the BIPOLE relaxers (relax_atoms, relax_cell_gradient).

  • A non-converged NEB image aborted the band with a cryptic C++ error. _evaluate_image ran the per-image SCF then unconditionally built the gradient, which raised RuntimeError: ... not converged from the C++ gradient code. It now raises a clear, image-named vibeqc.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_jk entry with an Hcore fallback, so they needed many more SCF iterations than run_uhf / run_rks (AUTO→SAD) and could hit max_iter where 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 a TypeError. An operator-precedence bug in the SCF dispatch ran opts.functional = None against 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 ViewerState keyed by (isovalue, replication); slider changes that don’t affect the mesh topology hit the cache and skip straight to plotter.add_mesh with 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_mesh calls. 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 matplotlib to 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 ( self-overlap 2, 6). Both now yield unit-normalized AOs; the wavefunction.gto re-evaluation now matches the stored volume.orbital cube. (renderers/wavefunction.py)

  • Vibrations animated at the world origin. The writer hard-coded position: [0,0,0] / atomic_number: 0 for every atom in the vibrations section, 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 the structure section’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:index value 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.0 although eigenvalues are always eV; ECD/VCD spectra dropped every negative Cotton band (intensity > 1e-10abs(...)).

  • 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 Enable switch raised a server error on every toggle (found with a trusted-event Playwright pass): the switch passed its new value to a zero-argument toggle_clip handler, which raised TypeError server-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.gto now 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 optional mo_metadata["k_point"] so the archive is self-describing. Full k-resolved Bloch wavefunctions stay out of scope.

  • CI: a new vibe-view-test job runs the viewer suite (pip-only, under xvfb, allow_failure, scoped to vibe-view/** changes) so the 47 viewer test files are exercised in CI without bloating the native build-test job.

  • 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, and spectra.uvvis was mis-keyed as spectra.uv so 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_idx state key, but Vue 3 refuses to expose _-prefixed identifiers to template expressions — so opening more than one file threw ReferenceError on every render and wiped out the app bar (Files dropdown + screenshot / export / camera / measure buttons). Renamed the key to active_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: true flag 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 bonds section 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 therefore run_periodic_job(optimize=True) default to force_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. Pass force_mode="analytic" only for research on the analytic gradient itself.

  • compute_bipole_gradient_fd generalised 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 UserWarning and 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 fold W(g) = Σ_k w_k Re[e^{-ik·g} W(k)] (the same convention as real_space_density_from_kpoints and the non-BIPOLE multi-k gradient), enabled by passing kmesh=.

  • Tests tightened + expanded (tests/test_bipole_gradient.py): the old <1.0 Ha/bohr analytic-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 an xfail tripwire 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++ in cpp/src/ewald.cpp) differentiates the Ewald E_nn, FD-validated <1e-6 Ha/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_gradient differentiates the Ewald V_ne (erfc V_short via new C++ nuclear_erfc_lattice_gradient_contribution, reciprocal V_long via the linchpin + nuclear structure factor, and background·S), FD-validated to 4e-10 Ha/bohr at fixed density.

  • _j_long_range_ewald_gradient differentiates the long-range Hartree energy ½ Σ_K kernel(K)|ρ̂(K)|² (kernel 4), reusing the FT-gradient linchpin with the electronic structure factor; FD-validated to 3e-13 Ha/bohr.

  • eri_lattice_gradient_contribution gains j_scale (J-suppression → exchange-only) and omega (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 at j_scale=1, ω=0; the screened J_SR branch is FD-validated to 3e-11.

  • The spheropole (EXT EL-SPHEROPOLE) gradient (kernel 5): the C++ compute_ext_el_spheropole_gradient_lattice differentiates the exact emultipole2 bond-symmetrised second moment K_μν = 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 libint emultipole2 deriv_order=1 (requires libint built with LIBINT2_DISABLE_ONEBODY_PROPERTY_DERIVS=OFF) — and _spheropole_ewald_gradient scatters 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 complex-step spheropole kernel, removed with the rest of that spheropole code.) The J^LR electron–electron jellium background derivative was corrected (Coulomb ½ factor; the old V_bg·S block 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 contraction Tr[D(0)·H(0)] (the Γ-only _zero_cross_cell_density projection), so the energy-weighted density must use ∂E/∂P(0) (the home-cell Fock the local energy differentiates), not the mo_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_hat from the Bloch k-density but contracts against the local density, so E_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 (commit fcd16eb5); 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: the KS local-energy gauge/orbital response and multi-k ∂E/∂P(k) assembly are not yet certified. 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-L factor K_L (a {0:1, 1:−½, 2:4⁄3, 3:−4} table). One scalar per L cannot simultaneously normalise the diagonal (∂_xx) and off-diagonal (∂_xz) derivatives, so the recovered derivatives were individually wrong (even ∂_z was 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_L is replaced by the exact closed-form Cartesian derivatives of 1/|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 documented L = l₁+l₂ 3 truncation: 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=False in all four run_pbc_bipole_* drivers), so default-path SCF energies are unaffected; this corrects the experimental far-field J-build (build_j_far_field_multipole) and restores a green main.

  • Cleanup. Removed the dead, stale-convention _wigner_3j_real helper (no longer referenced since the Cartesian-Taylor rework) and its scipy.special.factorial import.

  • 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 a 0 == 0 check 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_components updated to the live Stone (√2) convention; the stale cartesian_component_indices(0) expectation updated (L=0 is 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) spheropole 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 libint emultipole2 via the existing compute_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 × the N_e/6 charge moment), 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_total moves ~+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_qvf now records a human-readable coordinate label + unit (e.g. bond 0–1 / bohr) on the reaction.path section, and vibe-view labels the energy-plot x-axis with them ({label} ({unit})). Additive, optional metadata — no qvf_version bump; v1/v2 readers that don’t know the fields ignore them. Surfaced through write_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 a transition_state waypoint (red TS marker) and suppresses the generic point waypoint 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 every N-th scan frame; vibe-view’s ReactionPathRenderer morphs 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 raises NotImplementedError.

  • QVF schema (v1 + v2) SectionReactionPath gains two optional members — frame_volumes (a new Volume4DBinary, [n_emitted, nx, ny, nz]) + volume_grid — plus volume_frame_index / volume_label / volume_isovalue in 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_mesh into vibeview.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 a ScanResult2D with a [nA, nB] energy grid + per-node geometries. Both molecular and periodic.

  • New scan.surface QVF kind (registered in v1 + v2 schemas + vibe-view). Carries axis_a / axis_b (1D coordinate values), the 2D energies grid, per-axis label + unit, and optional flattened per-node geometries. High-level writer write_scan_surface_qvf(...); ScanResult2D.write_qvf(..., include_geometries=).

  • vibe-view ScanSurfaceRenderer renders a filled-contour energy map (matplotlib) with the minimum starred, a current-node crosshair, and both axes labelled from the coordinate names/units; reader read_scan_surfaceScanSurfaceData; registered in kinds.py, the renderer map, and the app activation + title.

Fixed — vibe-view test coverage gap for basis.ao

  • test_all_supported_kinds_have_renderers lacked a basis.ao fixture after that kind was added to SUPPORTED_KINDS (b9e97161); added one so the dispatch-coverage gate is green again.

Fixed — vibe-view variable-dim reaction-path wrapping

  • ReactionPathRenderer now wraps each frame by its own dimensionality via a new dim_for_frame(index) helper that honours dim_per_frame. Previously get_frame keyed off the scalar dim only, so a variable-dim path (scalar dim absent) silently fell back to dim=3 and 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 into routes.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 / .references sidecars and the .out references block; the .system manifest carries all three. tests/test_citations.py coverage was extended to pin both keys in the assembled citations and the .bibtex body. (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, so auto(30) on Si primitive returned 18×18×18 instead of the intended ~11×11×11. Now consistent per-axis with from_kspacing(2π/length) (VASP’s Auto↔KSPACING equivalence). Pure over-spend of compute — not wrong physics — but auto() is an advertised entry point.

  • madelung_constant_for_cell now 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 the exxdiv='ewald' K-shift. Now raises NotImplementedError, mirroring the existing run_pbc_gdf_rhf and C++ compute_nuclear_lattice_ewald dim guards. Default exxdiv='none' low-dim cells are unaffected.

  • run_krhf_periodic_gdf now 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 with apply_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 (E beyond 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=False bypasses the guard for parity/debug scripts. The working periodic-GDF path remains the Γ-only run_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 left e_j_multipole unbound in the direct (non-Ewald-split) Fock branch, raising UnboundLocalError before the spheropole was even reached (RHF already initialised it). The spheropole call is now dim==3-guarded — the term is identically zero in the direct gauge, so e_ext_el_spheropole is None for 1D/2D — and e_j_multipole is 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=True stays 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_uks omitted e_ext_el_spheropole from its per-iteration PBCBipoleEnergyComponents and hard-coded E_sphero_final = None in its non-converged post-loop branch, so a max_iter-capped 3D UKS run returned result.e_ext_el_spheropole = None even though the term was already folded into E_total (RHF/RKS/UHF all reported it; ~0.003768 Ha on H₂/STO-3G/8-bohr). Both sites now mirror the RKS/UHF siblings; None stays 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) in tests/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 handovers/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.CASCI to ≤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.NEVPT per 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=1 pending 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 handovers/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 arbitrary 0.1 scaling — 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-element REPA(α)/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-fix n0 pop ran the SCC the wrong way.

  • PBE-D4 dispersion bolt-on removed. gfn2.py had added compute_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 H0/basis-reimplementation work, later tracked by the GFN2 H0/angle and AES hardening lanes.

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-3c ran FCI instead of HF. Pure-HF composites (functional is None) routed through the wavefunction auto-ladder (≤ 4 e⁻ → FCI), so a turnkey hf-3c on H₂ silently ran fci(ndet=4). Pure-HF composites now resolve to rhf / 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-3c downgraded RUNNABLEPENDING_ECP. vDZP is a valence-only basis whose small-core ECPs need the libecpint inline-primitive feed (Phase 14g, not on main); it was silently running all-electron (≈ −35.9 Ha on H₂O vs ≈ −76.4). It now raises CompositeUnavailable. hse-3c (all-electron def2-mSVP) is unaffected and stays RUNNABLE. (F1.3)

  • Short-range bond (SRB) correction rewritten to the canonical kind-discriminated HF-3c / B97-3c forms (gcp.f90 basegrad / srb_egrad2) using the bundled DFT-D3 r0ab radii (new vibeqc._d3_r0ab, extracted by scripts/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-3c gCP corrected from +10.18 to +1.12 kcal/mol on H₂O. The def2-mtzvpp n_virt were the generic basis-function counts, not the r²SCAN-3c-specific virtual counts, and the gCP damping term (gcp.f90 damp=.true.) was absent. Added the damped gCP (opt-in damping= on compute_gcp; recipe-side GCPDamping) and corrected n_virt for def2-mTZVPP / def2-mSVP / MINIX. B97-3c’s spurious gCP removed — it is SRB-only (mctc-gcp computes SRB-only for b973c). (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 \uXXXXtomllib 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=True at a non-physical energy (CLAUDE.md § 7). run_rhf_periodic_multi_k_ewald3d hard-codes the Hartree J to the Ewald-3D builder but read V_ne / e_nuc from lat_opts.coulomb_method; a default PeriodicRHFOptions() (DIRECT_TRUNCATED) paired bare-gauge V_ne / e_nuc with 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- picked a261613a.)

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-picked 657a1644.)

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 promotion

      • banner RELEASE_CODENAMES + CITATION.cff + docs/citing.md edits in the release: vX.Y.Z commit.

    • 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-picked 7ad77f10.)

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_field default flipped from None to False so 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_iter raised 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 on tests/test_pbc_gdf_compcell.py).

  • ebe6ca95 fix(bipole): multipole L_max≥2 spherical conversion + RKS XC fixes + multi-iter test (conflicted on python/vibeqc/pbc_bipole_rks.py against 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 refresheddocs/index.md was 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

AI-generated Pisani's Penguin codename artwork for vibe-qc v0.10.0

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 + CoreParameterSet basis 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 misleading fix(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; PySCF atom_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 (dfa34b4a Bloch-summed lattice pair-FT fix in _compcell_aft_correction_3c; 871c5f4a G=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); see docs/roadmap.md for 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"; the dftd4 LGPL 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_vectors semantics (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_hamiltonian now 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_multipole in 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.py to 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.com SSH 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-gpu extra; the two-step install (pip install ./vibe-view then pip 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

AI-generated Knowles's Kingfisher codename artwork for vibe-qc v0.9.0

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 dftd4 dependency. 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 datasetd4_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 to aug-cc-pVDZ for production accuracy is documented.

  • Modules: vibeqc.dispersion_d4_model (14 public functions including compute_c9 and d4_atm_triple_energy), vibeqc.dispersion_d4_parameters (51 functionals), vibeqc.dispersion_d4_reference_data (D4ReferenceDataset), vibeqc.dispersion_d4_reference_systems (21 systems).

  • The dftd4 backend remains the default pending production dataset upgrade; switch via backend="native". See handovers/HANDOVER_D4_NATIVE.md.

Fixed — D4 ATM three-body test API (1655013e)

  • Exported compute_c9 and d4_atm_triple_energy from vibeqc.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. Refactored compute_d4_atm_energy to 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_uks segfaulted the process when an ECP replaced more core electrons than the molecule had: the valence count n_elec = n_electrons total_ncore went negative, producing a negative occupied-orbital count and a cblas_dgemm call with a negative dimension. The drivers now raise a clear std::invalid_argument that names the likely cause (a Molecule charge 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-subtracted Molecule charge against the pre-899ab16a convention. They now pass the physical ionic charge (+2), so Molecule.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.solvers top-level namespace: solve_selected_ci, solve_dmrg, solve_v2rdm, build_transcorrelated_hamiltonian, Hamiltonian, etc.

  • Tutorial: docs/tutorial/non_hf_solvers.md.

Historical Codename Artwork (pre-v0.9.0)

v0.7.0 - Löwdin’s Compass

AI-generated compass jellyfish codename artwork for vibe-qc v0.7.0, Löwdin's Compass

The codename remains Löwdin’s Compass. The original compass-object artwork remains bundled as _static/images-codenames/07-vibe-qc-v0.7.0-lowdins-compass.png for the vibe-qc-and-AI talk; this jellyfish variant keeps the historical changelog artwork aligned with the animal-series motif.