CRYSTAL-style BIPOLE periodic HF and DFT

Feature status

The BIPOLE drivers (run_pbc_bipole_rhf, _uhf, _rks, _uks) form the CRYSTAL-gauge periodic workstream. All four methods support multi-k with optional multipole far-field acceleration. Analytic gradients and structure optimization (atomic + cell relaxation) are available for all methods.

For routine calculations, prefer:

What BIPOLE is

CRYSTAL splits the periodic Fock build into two halves and keeps them in separate gauges:

  • One-electron Coulomb (V_ne) and the nuclear-nuclear energy (E_nn) — 3D Ewald (point-charge ↔ Gaussian-pair doesn’t decay on the nucleus side, so direct truncation diverges on charged-nucleus crystals like MgO). Both share a single Ewald α, mirrored in CRYSTAL by COMMON/VRSMAD/.

  • Two-electron Coulomb + exchange (F^{2e} = J + K) — CRYSTAL’s native code uses direct-space BIPOLE screening and multipole-far-pair replacement. The current vibe-qc parity path implements the same electrostatic gauge as J_SR(ω) + J_LR(ω) + V_bg·S - 1/2 K_full, where ω is the same Ewald α used by V_ne / E_nn and V_bg = N_e/(α²V). This replaces the older broken run_rhf_periodic_multi_k_ewald3d composition, whose long-range J did not share CRYSTAL’s gauge.

What today’s implementation does

vibeqc.run_pbc_bipole_rhf ships:

  • The full real-space one-electron pipeline at Ewald gauge: \(S(g)\), \(T(g)\), \(V_{ne}(g)\) at opts.lattice_opts.cutoff_bohr, Bloch-summed to per-k \(S(k)\) and \(H_{core}(k)\), canonical orthogonalisation \(X(k)\). The singular erfc part of \(V_{ne}\) is analytic via libint, and the smooth reciprocal/background part is analytic by shifted AO-pair Fourier transforms. A tightened unpruned Lebedev grid is still available as an explicit diagnostic fallback via v_ne_grid_options.

  • A CRYSTAL-gauge Ewald-J two-electron build by default for 3D systems (use_ewald_j_split=None, or True explicitly): short-range J from direct erfc-screened ERIs, long-range J from reciprocal AO-pair Fourier transforms, the matching electron background potential, and full direct-space exchange.

  • The standard SCF inner loop: Bloch-sum, CRYSTAL-style real-space energy evaluation (Σ_g D(g)M(g)), optional _MultiKPulayDIIS, optional LEVSHIFT, diagonalisation, optional MOM occupied-subspace reorder, and density rebuild. ODA mixing builds its trial Fock with the same Ewald-J composition; for ODA-mixed densities that no longer have an exact C(k) representation, the long-range-J density transform falls back to the real-space density blocks.

  • The matching nuclear-nuclear Ewald sum so \(E_{nn}\) shares the α used by \(V_{ne}\).

vibeqc.run_pbc_bipole_uhf uses the same one-electron and Ewald-J machinery with unrestricted spin densities:

  • \(D_{tot}(g) = D_\alpha(g) + D_\beta(g)\) drives the Hartree operator.

  • \(F^{2e}_\alpha(g) = J_{tot}(g) - K[D_\alpha](g)\) and \(F^{2e}_\beta(g) = J_{tot}(g) - K[D_\beta](g)\).

  • The UHF energy is evaluated as \(E_{2e} = \frac{1}{2}\mathrm{tr}[D_\alpha F^{2e}_\alpha] + \frac{1}{2}\mathrm{tr}[D_\beta F^{2e}_\beta]\) in the same real-space lattice contraction convention as RHF.

  • Even-electron SAD guesses are spin-averaged for CRYSTAL CYC0 parity: \(D_\alpha = D_\beta = D_{SAD}/2\). The requested multiplicity enters through the alpha/beta occupation counts after diagonalisation.

What it doesn’t ship yet:

  • The Saunders-Dovesi-Roetti 1992 multipole-far-pair branch — at large distances CRYSTAL uses truncated multipole expansions rather than exact direct ERIs. The Ewald-J split captures the same long-range gauge, but it is not yet a line-for-line BIPOLE multipole dispatch.

  • True IBZ orbit expansion for the multi-k long-range-J density transform. Until that lands, symmetry-reduced Monkhorst-Pack inputs are accepted by internally expanding them back to the full mesh for Ewald-J correctness. This is a usability bridge, not a performance speedup.

The driver is therefore a parity harness plus an increasingly useful SCF implementation. The first-cycle CRYSTAL comparison is now a high-signal regression test; final-energy sign-off still belongs in the CRYSTAL parity scripts.

Why ship it at all?

Two reasons:

  1. CRYSTAL parity surface. The 15 demo geometries committed alongside the driver (commit c1e00ff, tests/demos/ — LiH / NaCl / MgO / Al2O3 / C-diamond / Si-diamond / ZnO / TiO2 / SiO2 / Ne-fcc) and the matching CRYSTAL14 reference inputs let the periodic-dev chat track multipole-branch progress against ground-truth CRYSTAL energies. Each demo has a parity_<system>.py runner that produces a side-by-side comparison.

  2. Multipole infrastructure. The Phase 1-4b commits (d1cc4027de6431) added the shell-pair Cartesian multipole moments, the multipole-multipole interaction tensor, the IDIPC geometric dispatch per quartet, and the cell-level multipole moments from density. These are the prerequisites for the SDR multipole branch and for the bipole_lattice_self_energy analytic dipole-dipole self-image correction. They are tested in isolation against analytical results; wiring them into the SCF inner loop is the Phase 5 milestone.

What lands in v0.8.x vs v0.9.x

Phase

Status

What

1

landed

Shell-pair Cartesian multipole moments (C++ via libint)

2

landed

Multipole-multipole Coulomb interaction tensor

3

landed

IDIPC geometric dispatch per quartet

4a

landed

Cell-level multipole moments from density

4b

landed

Analytic dipole-dipole self-image energy

4b proper

landed (2026-05-18)

Ewald reciprocal-sum form of EXT EL-POLE, full multipole content via \(\hat{\rho}(K)\)

5 (Γ-only)

landed (2026-05-18)

Γ-only end-to-end SCF with use_ewald_j_split=True

5 + DIIS

landed (2026-05-18 late)

DIIS-compatible via D-consistent energy/error formulation

5 (multi-k)

landed (dense-k sign-off, 2026-05-20)

Per-k F_J^LR(k), MgO/diamond/Si STO-3G within 1 mHa of CRYSTAL14

5K (RKS)

landed (2026-05-20)

BIPOLE RKS driver with libxc V_xc + hybrid support

5U

landed (2026-05-20)

UHF BIPOLE with ODA/MOM/level-shift parity to RHF

5UK

landed (2026-05-20)

BIPOLE UKS driver with spin-polarised V_xc

5b

landed (2026-05-20)

Multipole far-field J builder (bipole_fock_multipole.py); diagnostics in RHF driver

6a

pending

Wire multipole far-pair into Fock build for acceleration

6b

pending

Complete compute_ext_el_spheropole higher-l terms

7

roadmap

CRYSTAL14 numerical-parity sign-off on the 15 demo set

Phase 1-5 give a working CRYSTAL-gauge SCF scaffold. The remaining Phase 6-7 work is what turns it from a parity workbench into the production BIPOLE implementation.

Dense-k STO-3G sign-off results at cutoff 14 bohr:

Demo

vibe-qc E_total (Ha/cell)

Δ vs CRYSTAL14

MgO SHRINK 8

-271.2177748509

+0.369 mHa

diamond SHRINK 8

-74.8771393842

-0.145 mHa

silicon SHRINK 8

-571.3214715798

-0.659 mHa

Quick start: SCF with the Ewald J-split

The Ewald-J split is automatic for 3D parity work. Full multi-k meshes remain fastest to reason about, but IBZ-reduced Monkhorst-Pack inputs are accepted and expanded internally to the full mesh until true IBZ orbit expansion is wired. Pass use_ewald_j_split=False only when intentionally reproducing the legacy direct-only diagnostic branch.

import vibeqc as vq
from vibeqc import InitialGuess, monkhorst_pack
from vibeqc._vibeqc_core import PeriodicRHFOptions
from vibeqc.pbc_bipole import run_pbc_bipole_rhf

system, basis = build_mgo_sto3g()    # 15 demo geometries in crystal_demos/

# Γ-only:
kmesh = monkhorst_pack(system, [1, 1, 1])
# Multi-k:
# kmesh = monkhorst_pack(system, [2, 2, 2], use_symmetry=False)
# kmesh = monkhorst_pack(system, [2, 2, 2], use_symmetry=True)
# The second form is expanded internally to the full mesh for Ewald-J.

opts = PeriodicRHFOptions()
opts.lattice_opts.cutoff_bohr = 14.0
opts.lattice_opts.nuclear_cutoff_bohr = 14.0
opts.initial_guess = InitialGuess.SAD
opts.use_diis = True
opts.diis_start_iter = 2
opts.damping = 0.0
opts.max_iter = 15

result = run_pbc_bipole_rhf(
    system, basis, kmesh, opts,
    # use_ewald_j_split=None defaults to the CRYSTAL-equivalent
    # F^{2e} build for 3D systems.
    ewald_precision=1e-8,
)
print(f"E_total = {result.energy:+.6f} Ha (CRYSTAL: -271.218)")

For open-shell systems, use the UHF scaffold:

from vibeqc.pbc_bipole_uhf import run_pbc_bipole_uhf

result = run_pbc_bipole_uhf(
    system, basis, kmesh, opts,
    ewald_precision=1e-6,
)

Constraints (2026-05-20):

  • True IBZ acceleration is not implemented. The k-space ρ̂(K) formula does not yet expand IBZ symmetry orbits directly. If an IBZ-reduced Monkhorst-Pack mesh carries ir_mapping metadata, the driver expands it to the corresponding full mesh internally. Explicit non-uniform custom k-meshes without that metadata are still rejected on the Ewald-J path.

  • Ewald-J requires dim = 3. With the auto default, dim < 3 runs stay on the legacy direct-only diagnostic branch; passing use_ewald_j_split=True for 2D / 1D raises.

Where to look in the code

python/vibeqc/pbc_bipole.py
    run_pbc_bipole_rhf — the closed-shell RHF driver.

python/vibeqc/pbc_bipole_uhf.py
    run_pbc_bipole_uhf — the spin-unrestricted UHF scaffold.

python/vibeqc/pbc_bipole_rks.py
    run_pbc_bipole_rks — the closed-shell RKS (DFT) driver.

python/vibeqc/pbc_bipole_uks.py
    run_pbc_bipole_uks — the open-shell UKS (spin-DFT) driver.

python/vibeqc/bipole_multipole.py
    Shell-pair multipole moments (Phase 1) + interaction tensor
    (Phase 2).

python/vibeqc/bipole_dispatch.py
    IDIPC geometric dispatch (Phase 3).

python/vibeqc/bipole_cell_moments.py
    Cell-level multipole moments from a density block (Phase 4a).

python/vibeqc/bipole_ext_el_pole.py
    Ewald reciprocal-sum EXT EL-POLE (Phase 4b proper).

python/vibeqc/bipole_fock_multipole.py
    Multipole far-field J builder (Phase 5b) — cell-level
    multipole-multipole interactions for far cell pairs.

python/vibeqc/bipole_fock_ewald.py
    Ewald J-split F²e build — short-range erfc + long-range
    reciprocal + background (used by all four drivers).

python/vibeqc/periodic_jk_method.py
    PeriodicJKMethod.BIPOLE enum — selectable via
    run_periodic_job(..., jk_method="bipole").

examples/periodic/input-bipole-*.py
    Example inputs: MgO RHF, MgO RKS PBE, Li UHF, and
    run_periodic_job with jk_method="bipole".

Analytic gradients

All four BIPOLE methods support analytic atomic gradients. The gradient uses existing C++ functions for the 1-electron and nuclear contributions, and eri_lattice_gradient_contribution for the 2-electron part.

from vibeqc.bipole_gradient import (
    compute_bipole_gradient_rhf,
    compute_bipole_gradient_uhf,
    compute_bipole_gradient_rks,
    compute_bipole_gradient_uks,
    compute_bipole_gradient_fd,   # exact FD fallback
)

# After SCF convergence:
grad = compute_bipole_gradient_rhf(system, basis, result)
print(f"max|grad| = {np.max(np.abs(grad)):.4e} Ha/bohr")

For hybrid DFT, the HF exchange fraction is extracted automatically from the functional. For pure DFT (alpha_hf=0), only the J contribution is included.

The FD fallback compute_bipole_gradient_fd provides exact reference gradients at 6N× SCF cost.

Structure optimization

Atomic positions and lattice parameters can be optimized using :mod:vibeqc.bipole_optimize:

from vibeqc.bipole_optimize import (
    relax_atoms, relax_cell, relax_cell_gradient, relax_full,
)

# Atomic positions only (analytic gradients, L-BFGS-B)
result = relax_atoms(system, "sto-3g", kmesh, method="RHF",
                     max_iter=30, conv_tol_grad=1e-4)

# Lattice only — energy FD (Nelder-Mead, no gradient needed)
result = relax_cell(system, "sto-3g", kmesh, method="RHF")

# Lattice only — gradient-based (force stress, L-BFGS-B, fewer SCF calls)
result = relax_cell_gradient(system, "sto-3g", kmesh, method="RHF")

# Full cell + atom relaxation (alternating)
result = relax_full(system, "sto-3g", kmesh, method="RHF",
                    max_outer=5)

# One-shot via high-level API
from vibeqc.periodic_runner import run_periodic_job
result = run_periodic_job(system, basis, method="RHF", jk_method="bipole",
                          optimize=True, optimize_cell=True)

Atomic relaxation uses fractional coordinates for stability with general lattices. Cell optimization uses the force-based stress tensor σ = -(1/V)·Σ_A R_A F_A with L-BFGS-B for the 6 Voigt strain components. The stress tensor is also available standalone:

from vibeqc.bipole_gradient import compute_stress_tensor
stress = compute_stress_tensor(system, gradient)  # 3×3, Ha/bohr³

Dimensionality support

Dim

Coulomb

Gradient

Optimization

Notes

3D

Ewald J-split (default) or DIRECT_TRUNCATED

Analytic (all 4 methods)

Atoms + cell

Production-ready

2D (surfaces)

DIRECT_TRUNCATED only

Analytic RHF

Atoms only

Requires vacuum padding; Ewald J-split raises

1D (wires)

DIRECT_TRUNCATED only

Analytic RHF

Atoms only

Requires vacuum padding; Ewald J-split raises

For 2D/1D, the BIPOLE driver automatically falls back to DIRECT_TRUNCATED when use_ewald_j_split=None (default). This is adequate for molecular-limit cells with sufficient vacuum padding (~10+ bohr). For tight surfaces/wires, the truncated Coulomb sum may diverge — use GDF for those cases.

References

  • Saunders, Dovesi, Roetti 1992 — V. R. Saunders, R. Dovesi, C. Roetti, M. Causà, N. M. Harrison, R. Orlando, C. M. Zicovich-Wilson, “CRYSTAL92 User’s Manual”, University of Torino (1992). The original BIELET / BIPOLE description of the multipole-far-pair Coulomb construction.

  • CRYSTAL14 — R. Dovesi, V. R. Saunders, C. Roetti, R. Orlando, C. M. Zicovich-Wilson, F. Pascale, B. Civalleri, K. Doll, N. M. Harrison, I. J. Bush, P. D’Arco, M. Llunell, M. Causà, Y. Noël, “CRYSTAL14: A program for the ab initio investigation of crystalline solids,” Int. J. Quantum Chem. 114, 1287 (2014). doi:10.1002/qua.24658. The modern reference for the EXT EL-POLE / EXT EL-SPHEROPOLE conventions vibe-qc is targeting.

  • CRYSTAL23 — A. Erba et al., “CRYSTAL23: a program for computational solid state physics and chemistry,” J. Chem. Theory Comput. 19, 6891 (2023). doi:10.1021/acs.jctc.2c00958.

When the BIPOLE branch reaches production-ready status the citation database (user_guide/citations) will gain a routes.methods["bipole"] entry and Saunders 1992 / Erba 2023 will start firing automatically on every BIPOLE run.

See also

  • Multi-k periodic SCF — the production multi-k RHF/RKS path today.

  • Density fitting — the production Γ-only hybrid-DFT path via native GDF.

  • Ewald summation — the long-range Coulomb machinery used by the legacy run_*_periodic_*_ewald3d drivers.