Periodic KS-DFT

Switch from HF to DFT by using PeriodicKSOptions instead of PeriodicSCFOptions and calling run_rks_periodic:

import numpy as np
from vibeqc import (
    Atom, BasisSet, PeriodicSystem, PeriodicKSOptions,
    monkhorst_pack, run_rks_periodic,
)

unit_cell = [Atom(1, [0, 0, 0]), Atom(1, [0, 0, 1.4])]
sysp = PeriodicSystem(
    dim=1,
    lattice=np.diag([4.0, 30.0, 30.0]),
    unit_cell=unit_cell,
)
basis = BasisSet(sysp.unit_cell_molecule(), "pob-tzvp")
kmesh = monkhorst_pack(sysp, [6, 1, 1])

opts = PeriodicKSOptions()
opts.functional = "PBE"
opts.lattice_opts.cutoff_bohr = 12.0
opts.lattice_opts.nuclear_cutoff_bohr = 40.0
opts.conv_tol_energy = 1e-10

result = run_rks_periodic(sysp, basis, kmesh, opts)

print(f"E_KS per cell  = {result.energy:.10f}")
print(f"  E_Coulomb    = {result.e_coulomb:.10f}")
print(f"  E_xc         = {result.e_xc:.10f}")
print(f"  E_HF_exch    = {result.e_hf_exchange:.10f}  (0 for pure DFT)")

Functional support

All 500+ libxc functionals work in principle. Today’s validation covers:

  • LDA (Slater + VWN5)

  • PBE — pure GGA

  • BLYP — pure GGA

Hybrid functionals (B3LYP, PBE0, …) work in the code path — the exchange_scale parameter on build_fock_2e_real_space routes the HF-exchange fraction through the lattice sum — but have not yet been validated end-to-end against CRYSTAL reference numbers. That waits for Phase 12e’s Ewald splitting.

Grid quality

Reuse the molecular-DFT grid controls:

opts.grid.n_radial = 99
opts.grid.n_theta  = 29
opts.grid.n_phi    = 58

The grid is built over the unit-cell atoms with the molecular Becke partition scheme. For tight unit cells where image-atom Voronoi cells intrude on the reference cell, expect small (<1e-4 Ha) boundary effects until the periodic Becke partition lands in Phase 12f.