Multi-k periodic SCF¶
vibe-qc treats multi-k periodic SCF as a native feature, not as a Python import wrapper around another quantum-chemistry code. PySCF and CRYSTAL are reference programs: run them out-of-process, parse their outputs, and compare energies, occupations, bands, and convergence traces. They are not vibe-qc backends.
Current Status¶
Native, usable today:
run_rhf_periodic_gamma_gdf(...)for Γ-only native Gaussian density fitting RHF, RKS, and hybrids acrossPeriodicSystem.dim = 1, 2, 3.run_krhf_periodic_gdf(..., kmesh, ...)andrun_krks_periodic_gdf(..., kmesh, ...)as the public k-point API for native GDF in 1D, 2D, and 3D. A Γ-only mesh(1, 1, 1)delegates to the Γ-GDF implementation; any other Monkhorst-Pack mesh runs the full native multi-k GDF loop — k-dependent 3c/2c blocks, Bloch-phase assembly, multi-k DIIS. Theuse_compcell=Falsedefault uses the Ewald-gauge Fock path (within 0.000 mHa of the \u0393-point energy in the molecular limit). For tight ionic cells useuse_compcell=True: the multi-k GDFLpqis then built by the symmetry-preserving all-FT path resolved per(k_i, k_j)pair (build_lpq_bloch_native_fft) with a per-k Hartree J, per-pair exchange K, and a k-mesh-aware Madelung exchange shift \u2014 reaching PySCF \u00b5Ha parity (LiH primitive FCC / sto-3g, kmesh (2,2,2): \u22121.0 \u00b5Ha vspyscf.pbc.scf.KRHF.density_fit()at a converged lattice cutoff; +1.6 mHa at the cheaper default). The AFT long-range correction is ON by default since v0.12.0 (\u03b7=1.0, libint convention, pyscf_auto rcut). HF/hybrid functionals are auto-routed to the exxdiv-corrected compcell path. Also reachable throughrun_periodic_job(..., gdf_kmesh=(n, n, n)).compute_2c_eri_lattice_blocks(...)andcompute_3c_eri_lattice_blocks(...)expose translation-resolved periodic DF blocks in 1D, 2D, and 3D. Summing those blocks reproduces the Γ metric/tensor; the native multi-k GDF loop will apply Bloch phases to these blocks instead of rebuilding libint shell pairs.bloch_sum_2c_eri_blocks(...)andbloch_sum_3c_eri_blocks(...)pin that phase convention, andbuild_lpq_bloch_native(...)fits the phase-assembled tensor in the phase-assembled auxiliary metric.run_rhf_periodic_scf(..., kmesh, opts)withopts.lattice_opts.coulomb_method = CoulombMethod.EWALD_3D.run_rks_periodic_scf(..., kmesh, opts)with the same EWALD_3D route.General lattice vectors: the code works with arbitrary 3D Bravais cells and uses the supplied lattice matrix to build reciprocal-space k-points and Bloch sums. Tests include skew/hexagonal cells so the implementation does not silently assume cubic axes.
Closed-shell finite-temperature Fermi-Dirac occupations for periodic multi-k Ewald RHF/RKS via
opts.smearing_temperature(k_B Tin Hartree). The native Γ-only GDF RHF/RKS driver and high-levelrun_periodic_job(smearing_temperature=...)support the same occupation model for Γ-point metal/debug work.
Not native yet:
The non-BIPOLE UKS multi-k Ewald path still raises on positive
smearing_temperatureso metallic spin-polarized inputs do not run with silently integer occupations. Use the BIPOLE UKS route when you need per-spin Fermi-Dirac smearing today.Production-scale all-electron MgO/pob-TZVP at dense k-meshes needs the native FFTDF/GDF path. The legacy Ewald RKS path is useful for debug and correctness work, but its periodic XC memory footprint is intentionally guarded.
Minimal RHF Example (Ewald-3D path)¶
import numpy as np
import vibeqc as vq
a = 10.0
system = vq.PeriodicSystem(
3,
np.eye(3) * a,
[vq.Atom(1, [0.0, 0.0, 0.0]),
vq.Atom(1, [0.0, 0.0, 1.4])],
)
basis = vq.BasisSet(system.unit_cell_molecule(), "sto-3g")
kmesh = vq.monkhorst_pack(system, [2, 2, 2])
opts = vq.PeriodicRHFOptions()
opts.lattice_opts.coulomb_method = vq.CoulombMethod.EWALD_3D
opts.lattice_opts.cutoff_bohr = 12.0
opts.lattice_opts.nuclear_cutoff_bohr = 15.0
opts.damping = 0.3
opts.use_diis = True
result = vq.run_rhf_periodic_scf(
system, basis, kmesh, opts,
omega=0.5,
spacing_bohr=0.3,
)
print(result.energy, result.converged)
Native GDF multi-k example¶
For production accuracy on ionic crystals, use the compcell GDF path:
import numpy as np
import vibeqc as vq
# LiH primitive FCC (Sun-Berkelbach 2017 benchmark)
ANG = 1.0 / 0.529177210903
A_lat = 4.084
lat = np.array([[0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]]) * A_lat * ANG
system = vq.PeriodicSystem(3, lat,
[vq.Atom(3, [0, 0, 0]), vq.Atom(1, [A_lat*ANG/2]*3)])
basis = vq.BasisSet(system.unit_cell_molecule(), "sto-3g")
opts = vq.PeriodicRHFOptions()
opts.use_diis = True
opts.max_iter = 60
opts.conv_tol_energy = 1e-8
# For convergence testing, raise the lattice cutoff:
opts.lattice_opts.cutoff_bohr = 30.0
opts.lattice_opts.nuclear_cutoff_bohr = 30.0
# use_compcell=True enables the AFT-corrected compcell GDF path
# (AFT ON by default since v0.12.0; exxdiv auto-routed for HF/hybrids)
result = vq.run_krhf_periodic_gdf(
system, basis, (2, 2, 2), opts,
aux_basis="def2-svp-jk",
use_compcell=True,
)
print(f"E = {result.energy:.8f} Ha, converged = {result.converged}")
# PySCF GDF reference: -7.92200323 Ha (+1.6 mHa at default cutoff, -1.0 uHa at cutoff 30)
Minimal RKS Example¶
opts = vq.PeriodicKSOptions()
opts.functional = "pbe"
opts.lattice_opts.coulomb_method = vq.CoulombMethod.EWALD_3D
opts.lattice_opts.cutoff_bohr = 12.0
opts.lattice_opts.nuclear_cutoff_bohr = 15.0
opts.damping = 0.3
opts.use_diis = True
result = vq.run_rks_periodic_scf(
system, basis, kmesh, opts,
omega=0.5,
spacing_bohr=0.3,
)
print(result.energy, result.e_xc, result.converged)
For MgO/pob-TZVP-class RKS, prefer this route only for small debug meshes until native FFTDF/GDF lands; the full periodic XC grid can be too memory-heavy for the legacy Ewald driver.
Smearing For Metals¶
Closed-shell periodic RHF/RKS support Fermi-Dirac smearing on the multi-k Ewald path and on the native Γ-only GDF path:
opts.smearing_temperature = vq.resolve_smearing_temperature(
1000.0,
unit="kelvin",
).temperature
The option stores k_B T in Hartree. You can set it directly, convert
from Kelvin/eV/Ry, use a preset, or ask for a conservative automatic
guess:
opts.smearing_temperature = vq.resolve_smearing_temperature("metal").temperature
opts.smearing_temperature = vq.resolve_smearing_temperature("0.1 eV").temperature
opts.smearing_temperature = vq.resolve_smearing_temperature(0.1, unit="eV").temperature
opts.smearing_temperature = vq.resolve_smearing_temperature(
"auto",
metallic=True,
).temperature
Numeric strings may carry their own unit ("1000 K", "0.1 eV",
"0.01 Ha", "0.02 Ry"), which is often clearer in input files than a
bare Hartree number. "auto" is intentionally cautious: if no metallic
hint or prior band gap is supplied, it returns zero smearing rather than
silently changing an insulating calculation. With smearing enabled, the
drivers bisect the chemical potential so that the weighted k-mesh
occupation satisfies the per-cell electron count:
sum_k w_k sum_i n_i(k) = N_electrons
n_i(k) = 2 / (1 + exp((eps_i(k) - mu) / T))
The result object reports:
result.energy: internal total energy.result.free_energy: variational free energyE - T S.result.entropy: dimensionless electronic entropyS/k_B.result.fermi_level: chemical potential in Hartree.result.occupations: per-k occupation arrays in[0, 2].
DIRECT_TRUNCATED currently rejects positive
smearing_temperature; that backend would otherwise ignore the field
and run a hard-Aufbau calculation.
For the high-level native-GDF job runner, pass the same value as a keyword:
result = vq.run_periodic_job(
system,
basis,
method="RKS",
functional="pbe",
smearing_temperature="auto",
smearing_metallic=True,
)
k-point mesh convergence for solids¶
\u0393-only calculations are not appropriate for real solids. A single k-point sees only the \u0393-periodic component of the density; the full Brillouin-zone integral must be sampled with a Monkhorst-Pack mesh.
The required mesh density depends on the crystal system:
Mesh density by crystal type¶
Crystal type |
Typical primitive cell |
Minimum mesh |
Converged mesh |
|---|---|---|---|
Simple cubic (sc) |
1 atom, \u223c3-5 \u00c5 |
4\u00d74\u00d74 |
8\u00d78\u00d78 |
FCC (Cu, Al, Ag) |
1 atom, \u223c3-4 \u00c5 |
6\u00d76\u00d76 |
12\u00d712\u00d712 |
BCC (Fe, W, Na) |
1 atom, \u223c3-4 \u00c5 |
6\u00d76\u00d76 |
12\u00d712\u00d712 |
Rocksalt (MgO, NaCl) |
2 atoms, \u223c4-6 \u00c5 |
4\u00d74\u00d74 |
8\u00d78\u00d78 |
Diamond/Zincblende (Si, GaAs) |
2 atoms, \u223c5-6 \u00c5 |
4\u00d74\u00d74 |
8\u00d78\u00d78 |
Perovskite (BaTiO\u2083) |
5 atoms, \u223c4 \u00c5 |
4\u00d74\u00d74 |
6\u00d76\u00d76 |
Layered / 2D slab |
variable |
6\u00d76\u00d71 |
12\u00d712\u00d71 |
Rule of thumb: the linear k-point density \u00d7 lattice constant should be \u2265 20-30 \u00c5 for insulating systems. For metals, double it.
Symmetry reduction¶
vibe-qc’s monkhorst_pack applies time-reversal symmetry automatically.
Space-group symmetry reduction (IBZ) is available through spglib:
vq.attach_symmetry(system) # populates system.symmetry via spglib
kpts = vq.KPoints.monkhorst_pack(system, [8, 8, 8]) # full mesh
kpts = vq.KPoints.monkhorst_pack(system, [8, 8, 8], symmetry=True) # IBZ
Symmetry reduction can reduce the number of irreducible k-points by factors of 2\u201348 depending on the space group, cutting SCF cost proportionally. Examples on an 8\u00d78\u00d78 mesh:
| System | Space group | Full kpts | IBZ kpts | Factor | |—|—:|—:|—:| | MgO rocksalt | Fm\u20103m (#225) | 512 | 20 | 25.6\u00d7 | | H\u2082 in cubic box | (trivial) | 512 | 40 | 12.8\u00d7 | | Si diamond | Fd\u20103m (#227) | 512 | 29 | 17.7\u00d7 |
The IBZ weights are automatically normalized to sum to 1. The KPoints
object can be passed directly to run_krhf_periodic_gdf \u2014 the driver
handles the normalization internally.
Lattice cutoff convergence¶
The real-space lattice cutoff (lattice_opts.cutoff_bohr, default 15)
controls the number of periodic image cells included in the 2c/3c ERI
lattice sums. For multi-k GDF this affects the Bloch-pair cell list
used in the FT-based cderi construction.
Default cutoff (15 bohr) reaches chemical accuracy (+1.6 mHa vs PySCF)
on LiH FCC at kmesh=(2,2,2). Raising to 30 bohr reaches \u00b5Ha parity
(\u22121.0 \u00b5Ha). The internal FT kinetic-energy cutoff (ke_cutoff,
default 200 Ha) is sufficient for light-atom systems and does not need
tuning for LiH \u2014 the lattice cutoff is the primary accuracy knob.
Convergence order: converge the k-mesh first, THEN the cutoff. A converged k-mesh with an under-converged cutoff can show spurious mesh-convergence plateaus.
Convergence check¶
Always converge the k-mesh:
for n in [2, 4, 6, 8, 10, 12]:
opts = vq.PeriodicRHFOptions()
opts.max_iter = 40
result = vq.run_krhf_periodic_gdf(
system, basis, (n, n, n), opts,
aux_basis="def2-svp-jk", use_compcell=True,
)
print(f"{n}\u00d7{n}\u00d7{n}: {result.energy:.8f} Ha")
A converged mesh shows < 0.1 mHa/atom change between successive densities. For production work, converge the k-mesh before converging any other parameter (cutoff, basis, functional).
CRYSTAL And PySCF Parity¶
CRYSTAL and PySCF remain essential references, but the validation boundary is external:
Write a CRYSTAL or PySCF input next to the vibe-qc input.
Run the external program as a separate executable or script.
Parse its output into a comparison record.
Store method, basis, lattice, k-mesh, energy, occupation, and convergence metadata in the benchmark manifest.
This keeps licensing, dependency, and numerical-responsibility boundaries clean. It also makes it obvious whether a discrepancy is in vibe-qc or in an adapter/parser.
Native FFTDF/GDF Roadmap¶
The production target is a native periodic stack:
FFTDF first for speed and plane-wave-style Coulomb handling.
GDF for all-electron Gaussian density fitting and CRYSTAL/PySCF parity benchmarks.
Range-separated exchange screening for hybrids and HF in large cells.
Disk-backed Cholesky/DF tensors for correlated methods later.
The public run_krhf_periodic_gdf / run_krks_periodic_gdf names are
the stable API. A Γ-only mesh runs through the native Γ-GDF bridge;
any other Monkhorst-Pack mesh runs the native multi-k GDF loop. The
remaining roadmap work is performance and dense-solid scaling
(disk-backed tensors, IBZ-weighted J/K), not correctness of the
multi-k path itself.
See Also¶
periodic_methods.md: comparative tour of vibe-qc’s periodic-SCF kernels (BIPOLE, native GDF, GPW/GAPW), bonding-organised example gallery, and the surface-reactions workflow.k_points.md— Monkhorst-Pack meshes, IBZ reduction, and band paths.ewald.md— Ewald nuclear attraction, Madelung terms, and the memory guard on legacy periodic XC.band_structure.md— band structures, DOS, and Fermi-level reporting.../roadmap.md— FFTDF/GDF and metallic-smearing roadmap items.